1 /*
2 Copyright (c) 2006, 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 <string>
27 #include <sstream>
28 #include <vector>
29 #include <algorithm>
30 #include <memory>
31 #include <iostream>
32 #include "sql_string.h"
33 #include "mysqld_error.h"
34 #include "my_default.h"
35 #include "check/mysqlcheck.h"
36 #include "../scripts/mysql_fix_privilege_tables_sql.c"
37 #include "../scripts/sql_commands_sys_schema.h"
38
39 #include "base/abstract_connection_program.h"
40 #include "base/abstract_options_provider.h"
41 #include "base/show_variable_query_extractor.h"
42 #include "base/mysql_query_runner.h"
43
44 using std::string;
45 using std::vector;
46 using namespace Mysql::Tools::Base;
47 using std::stringstream;
48
49 int mysql_check_errors;
50
51 const int SYS_TABLE_COUNT = 1;
52 const int SYS_VIEW_COUNT = 100;
53 const int SYS_TRIGGER_COUNT = 2;
54 const int SYS_FUNCTION_COUNT = 22;
55 const int SYS_PROCEDURE_COUNT = 26;
56
57 /**
58 Error callback to be called from mysql_check functionality.
59 */
mysql_check_error_callback(MYSQL * mysql,string when)60 static void mysql_check_error_callback(MYSQL *mysql, string when)
61 {
62 mysql_check_errors= 1;
63 }
64
65 const char *load_default_groups[]=
66 {
67 "client", /* Read settings how to connect to server */
68 "mysql_upgrade", /* Read special settings for mysql_upgrade*/
69 0
70 };
71
72 namespace Mysql{
73 namespace Tools{
74 namespace Upgrade{
75
76 using std::vector;
77 using std::string;
78 using std::stringstream;
79
80 enum exit_codes
81 {
82 EXIT_INIT_ERROR = 1,
83 EXIT_ALREADY_UPGRADED = 2,
84 EXIT_BAD_VERSION = 3,
85 EXIT_MYSQL_CHECK_ERROR = 4,
86 EXIT_UPGRADING_QUERIES_ERROR = 5,
87 };
88
89
90 class Mysql_connection_holder
91 {
92 MYSQL* m_mysql_connection;
93
94 public:
Mysql_connection_holder(MYSQL * mysql_connection)95 explicit Mysql_connection_holder(MYSQL *mysql_connection)
96 : m_mysql_connection(mysql_connection)
97 { }
98
~Mysql_connection_holder()99 ~Mysql_connection_holder()
100 {
101 mysql_close(this->m_mysql_connection);
102 }
103 };
104
105
106 class Program : public Base::Abstract_connection_program
107 {
108 public:
Program()109 Program()
110 : Abstract_connection_program(),
111 m_mysql_connection(NULL),
112 m_temporary_verbose(false)
113 {}
114
get_version()115 string get_version()
116 {
117 return "2.0";
118 }
119
get_first_release_year()120 int get_first_release_year()
121 {
122 return 2000;
123 }
124
get_description()125 string get_description()
126 {
127 return "MySQL utility for upgrading databases to new MySQL versions.";
128 }
129
short_usage()130 void short_usage()
131 {
132 std::cout << "Usage: " << get_name() <<" [OPTIONS]" << std::endl;
133 }
134
get_error_code()135 int get_error_code()
136 {
137 return 0;
138 }
139
140 /**
141 @param query Query to execute
142 @return -1 if error, 1 if equal to value, 0 if different from value
143 */
execute_conditional_query(const char * query,const char * value_to_compare)144 int execute_conditional_query(const char* query, const char* value_to_compare)
145 {
146 int condition_result= -1;
147 if (!mysql_query(this->m_mysql_connection, query))
148 {
149 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
150 if (result)
151 {
152 MYSQL_ROW row;
153 if ((row = mysql_fetch_row(result)))
154 {
155 condition_result= (strcmp(row[0], value_to_compare) == 0);
156 }
157 mysql_free_result(result);
158 }
159 }
160 return condition_result;
161 }
162
163 /**
164 @return -1 if error, 1 if user is not there, 0 if it is
165 */
check_session_user_absence()166 int check_session_user_absence()
167 {
168 int no_session_user =
169 execute_conditional_query(
170 "SELECT COUNT(*) FROM mysql.user WHERE user = 'mysql.session'",
171 "0");
172 return no_session_user;
173 }
174
175 /**
176 @return -1 if error, 1 if the user is correctly configured, 0 if not
177 */
check_session_user_configuration()178 int check_session_user_configuration()
179 {
180 int is_user_configured = 0;
181 is_user_configured =
182 execute_conditional_query(
183 "SELECT SUM(count)=3 FROM ( "
184 "SELECT COUNT(*) as count FROM mysql.tables_priv WHERE "
185 "Table_priv='Select' and User='mysql.session' and Db='mysql' and Table_name='user' "
186 "UNION ALL "
187 "SELECT COUNT(*) as count FROM mysql.db WHERE "
188 "Select_priv='Y' and User='mysql.session' and Db='performance_schema' "
189 "UNION ALL "
190 "SELECT COUNT(*) as count FROM mysql.user WHERE "
191 "Super_priv='Y' and User='mysql.session') as user_priv;",
192 "1");
193 return is_user_configured;
194 }
195 /**
196 Error codes:
197 EXIT_INIT_ERROR - Initialization error.
198 EXIT_ALREADY_UPGRADED - Server already upgraded.
199 EXIT_BAD_VERSION - Bad server version.
200 EXIT_MYSQL_CHECK_ERROR - Error during calling mysql_check functionality.
201 EXIT_UPGRADING_QUERIES_ERROR - Error during execution of upgrading queries.
202 */
execute(vector<string> positional_options)203 int execute(vector<string> positional_options)
204 {
205 /*
206 Disables output buffering to make printing to stdout and stderr order
207 deterministic.
208 */
209 setbuf(stdout, NULL);
210
211 this->m_mysql_connection= this->create_connection();
212 // Remember to call mysql_close()
213 Mysql_connection_holder connection_holder(m_mysql_connection);
214 this->m_query_runner= new Mysql_query_runner(this->m_mysql_connection);
215 this->m_query_runner->add_message_callback(new Instance_callback
216 <int64, const Message_data&, Program>(this, &Program::process_error));
217
218 /*
219 Master and slave should be upgraded separately. All statements executed
220 by mysql_upgrade will not be binlogged.
221 'SET SQL_LOG_BIN=0' is executed before any other statements.
222 */
223 if (this->m_upgrade_systables_only)
224 {
225 printf("The --upgrade-system-tables option was used, databases won't be "
226 "touched.\n");
227 }
228 if (!this->m_write_binlog)
229 {
230 if (mysql_query(this->m_mysql_connection, "SET SQL_LOG_BIN=0") != 0)
231 {
232 return this->print_error(1, "Cannot setup server variables.");
233 }
234 }
235
236 #ifdef WITH_WSREP
237 if (mysql_query(this->m_mysql_connection, "SET wsrep_on=0") != 0)
238 {
239 return this->print_error(1, "Cannot setup server wsrep variables.");
240 }
241 #endif /* WITH_WSREP */
242 if (mysql_query(this->m_mysql_connection, "USE mysql") != 0)
243 {
244 return this->print_error(1, "Cannot select database.");
245 }
246
247 /*
248 Read the mysql_upgrade_info file to check if mysql_upgrade
249 already has been run for this installation of MySQL
250 */
251 if (this->m_ignore_errors == false && this->is_upgrade_already_done())
252 {
253 printf("This installation of MySQL is already upgraded to %s, "
254 "use --force if you still need to run mysql_upgrade\n",
255 MYSQL_SERVER_VERSION);
256 return EXIT_ALREADY_UPGRADED;
257 }
258
259 if (this->m_check_version && this->is_version_matching() == false)
260 return EXIT_BAD_VERSION;
261
262 /*
263 Check and see if the Server Session Service default user exists
264 */
265
266 int user_is_not_there= check_session_user_absence();
267 int is_user_correctly_configured= 1;
268
269 if(!user_is_not_there)
270 {
271 is_user_correctly_configured= check_session_user_configuration();
272 }
273
274 if(!is_user_correctly_configured)
275 {
276 return this->print_error(
277 EXIT_UPGRADING_QUERIES_ERROR,
278 "The mysql.session exists but is not correctly configured."
279 " The mysql.session needs SELECT privileges in the"
280 " performance_schema database and the mysql.db table and also"
281 " SUPER privileges.");
282 }
283
284 if (user_is_not_there < 0 || is_user_correctly_configured < 0)
285 {
286 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
287 "Query against mysql.user table failed "
288 "when checking the mysql.session.");
289 }
290
291 /*
292 Run "mysql_fix_privilege_tables.sql" and "mysqlcheck".
293
294 First, upgrade all tables in the system database and then check
295 them.
296
297 The order is important here because we might encounter really old
298 log tables in CSV engine which have NULLable columns and old TIMESTAMPs.
299 Trying to do REPAIR TABLE on such table prior to upgrading it will fail,
300 because REPAIR will detect old TIMESTAMPs and try to upgrade them to
301 the new ones. In the process it will attempt to create a table with
302 NULLable columns which is not supported by CSV engine nowadays.
303
304 After that, run mysqlcheck on all tables.
305 */
306 if (this->run_sql_fix_privilege_tables() != 0)
307 {
308 return EXIT_UPGRADING_QUERIES_ERROR;
309 }
310
311 if (this->m_upgrade_systables_only == false)
312 {
313 this->print_verbose_message("Checking system database.");
314
315 if (this->run_mysqlcheck_mysql_db_fixnames() != 0)
316 {
317 return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
318 }
319 if (this->run_mysqlcheck_mysql_db_upgrade() != 0)
320 {
321 return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
322 }
323 }
324
325 if (this->m_skip_sys_schema == false)
326 {
327 /*
328 If the sys schema does not exist, then create it
329 Otherwise, try to select from sys.version, if this does not
330 exist but the schema does, then raise an error rather than
331 overwriting/adding to the existing schema
332 */
333 if (mysql_query(this->m_mysql_connection, "USE sys") != 0)
334 {
335 if (this->run_sys_schema_upgrade() != 0)
336 {
337 return EXIT_UPGRADING_QUERIES_ERROR;
338 }
339 } else {
340 /* If the database is empty, upgrade */
341 if (!mysql_query(this->m_mysql_connection,
342 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sys'"))
343 {
344 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
345 if (result)
346 {
347 MYSQL_ROW row;
348 if ((row = mysql_fetch_row(result)))
349 {
350 if (strcmp(row[0], "0") == 0)
351 {
352 // The sys database contained nothing
353 stringstream ss;
354 ss << "Found empty sys database. Installing the sys schema.";
355 this->print_verbose_message(ss.str());
356 if (this->run_sys_schema_upgrade() != 0)
357 {
358 return EXIT_UPGRADING_QUERIES_ERROR;
359 }
360 }
361 }
362 mysql_free_result(result);
363 }
364 }
365
366 /* If the version is smaller, upgrade */
367 if (mysql_query(this->m_mysql_connection, "SELECT * FROM sys.version") != 0)
368 {
369 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
370 "A sys schema exists with no sys.version view. "
371 "If you have a user created sys schema, this must be "
372 "renamed for the upgrade to succeed.");
373 } else {
374 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
375 if (result)
376 {
377 MYSQL_ROW row;
378
379 while ((row = mysql_fetch_row(result)))
380 {
381 stringstream ss;
382 ulong installed_sys_version = calc_server_version(row[0]);
383 ulong expected_sys_version = calc_server_version(SYS_SCHEMA_VERSION);
384 if (installed_sys_version >= expected_sys_version)
385 {
386 ss << "The sys schema is already up to date (version " << row[0] << ").";
387 this->print_verbose_message(ss.str());
388 } else {
389 ss << "Found outdated sys schema version " << row[0] << ".";
390 this->print_verbose_message(ss.str());
391 if (this->run_sys_schema_upgrade() != 0)
392 {
393 return EXIT_UPGRADING_QUERIES_ERROR;
394 }
395 }
396 }
397 mysql_free_result(result);
398 } else {
399 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
400 "A sys schema exists with a sys.version view, but it returns no results.");
401 }
402 }
403 /*
404 The version may be the same, but in some upgrade scenarios
405 such as importing a 5.6 dump in to a fresh 5.7 install that
406 includes the mysql schema, and then running mysql_upgrade,
407 the functions/procedures will be removed.
408
409 In this case, we check for the expected counts of objects,
410 and if those do not match, we just re-install the schema.
411 */
412 if (mysql_query(this->m_mysql_connection,
413 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sys' AND TABLE_TYPE = 'BASE TABLE'") != 0)
414 {
415 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
416 "Query against INFORMATION_SCHEMA.TABLES failed when checking the sys schema.");
417 } else {
418 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
419 if (result)
420 {
421 MYSQL_ROW row;
422
423 while ((row = mysql_fetch_row(result)))
424 {
425 if (SYS_TABLE_COUNT > atoi(row[0]))
426 {
427 stringstream ss;
428 ss << "Found " << row[0] << " sys tables, but expected " << SYS_TABLE_COUNT << "."
429 " Re-installing the sys schema.";
430 this->print_verbose_message(ss.str());
431 if (this->run_sys_schema_upgrade() != 0)
432 {
433 return EXIT_UPGRADING_QUERIES_ERROR;
434 }
435 }
436 }
437
438 mysql_free_result(result);
439 }
440 }
441
442 if (mysql_query(this->m_mysql_connection,
443 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'sys' AND TABLE_TYPE = 'VIEW'") != 0)
444 {
445 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
446 "Query against INFORMATION_SCHEMA.TABLES failed when checking the sys schema.");
447 } else {
448 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
449 if (result)
450 {
451 MYSQL_ROW row;
452
453 while ((row = mysql_fetch_row(result)))
454 {
455 if (SYS_VIEW_COUNT > atoi(row[0]))
456 {
457 stringstream ss;
458 ss << "Found " << row[0] << " sys views, but expected " << SYS_VIEW_COUNT << "."
459 " Re-installing the sys schema.";
460 this->print_verbose_message(ss.str());
461 if (this->run_sys_schema_upgrade() != 0)
462 {
463 return EXIT_UPGRADING_QUERIES_ERROR;
464 }
465 }
466 }
467
468 mysql_free_result(result);
469 }
470 }
471
472 if (mysql_query(this->m_mysql_connection,
473 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_SCHEMA = 'sys'") != 0)
474 {
475 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
476 "Query against INFORMATION_SCHEMA.TRIGGERS failed when checking the sys schema.");
477 } else {
478 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
479 if (result)
480 {
481 MYSQL_ROW row;
482
483 while ((row = mysql_fetch_row(result)))
484 {
485 if (SYS_TRIGGER_COUNT > atoi(row[0]))
486 {
487 stringstream ss;
488 ss << "Found " << row[0] << " sys triggers, but expected " << SYS_TRIGGER_COUNT << "."
489 " Re-installing the sys schema.";
490 this->print_verbose_message(ss.str());
491 if (this->run_sys_schema_upgrade() != 0)
492 {
493 return EXIT_UPGRADING_QUERIES_ERROR;
494 }
495 }
496 }
497
498 mysql_free_result(result);
499 }
500 }
501
502 if (mysql_query(this->m_mysql_connection,
503 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'sys' AND ROUTINE_TYPE = 'FUNCTION'") != 0)
504 {
505 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
506 "Query against INFORMATION_SCHEMA.ROUTINES failed when checking the sys schema.");
507 } else {
508 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
509 if (result)
510 {
511 MYSQL_ROW row;
512
513 while ((row = mysql_fetch_row(result)))
514 {
515 if (SYS_FUNCTION_COUNT > atoi(row[0]))
516 {
517 stringstream ss;
518 ss << "Found " << row[0] << " sys functions, but expected " << SYS_FUNCTION_COUNT << "."
519 " Re-installing the sys schema.";
520 this->print_verbose_message(ss.str());
521 if (this->run_sys_schema_upgrade() != 0)
522 {
523 return EXIT_UPGRADING_QUERIES_ERROR;
524 }
525 }
526 }
527
528 mysql_free_result(result);
529 }
530 }
531
532 if (mysql_query(this->m_mysql_connection,
533 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_SCHEMA = 'sys' AND ROUTINE_TYPE = 'PROCEDURE'") != 0)
534 {
535 return this->print_error(EXIT_UPGRADING_QUERIES_ERROR,
536 "Query against INFORMATION_SCHEMA.ROUTINES failed when checking the sys schema.");
537 } else {
538 MYSQL_RES *result = mysql_store_result(this->m_mysql_connection);
539 if (result)
540 {
541 MYSQL_ROW row;
542
543 while ((row = mysql_fetch_row(result)))
544 {
545 if (SYS_PROCEDURE_COUNT > atoi(row[0]))
546 {
547 stringstream ss;
548 ss << "Found " << row[0] << " sys procedures, but expected " << SYS_PROCEDURE_COUNT << "."
549 " Re-installing the sys schema.";
550 this->print_verbose_message(ss.str());
551 if (this->run_sys_schema_upgrade() != 0)
552 {
553 return EXIT_UPGRADING_QUERIES_ERROR;
554 }
555 }
556 }
557
558 mysql_free_result(result);
559 }
560 }
561
562 }
563 if (mysql_query(this->m_mysql_connection, "USE mysql") != 0)
564 {
565 return this->print_error(1, "Cannot select mysql database.");
566 }
567 } else {
568 this->print_verbose_message("Skipping installation/upgrade of the sys schema.");
569 }
570
571 if (!this->m_upgrade_systables_only)
572 {
573 this->print_verbose_message("Checking databases.");
574
575 if (this->run_mysqlcheck_fixnames() != 0)
576 {
577 return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
578 }
579 if (this->run_mysqlcheck_upgrade() != 0)
580 {
581 return this->print_error(EXIT_MYSQL_CHECK_ERROR, "Error during call to mysql_check.");
582 }
583 }
584
585 this->print_verbose_message("Upgrade process completed successfully.");
586
587 /* Create a file indicating upgrade has been performed */
588 this->create_mysql_upgrade_info_file();
589
590 return 0;
591 }
592
create_options()593 void create_options()
594 {
595 this->create_new_option(&this->m_check_version, "version-check",
596 "Run this program only if its \'server version\' "
597 "matches the version of the server to which it's connecting, "
598 "(enabled by default); use --skip-version-check to avoid this check. "
599 "Note: the \'server version\' of the program is the version of the "
600 "MySQL server with which it was built/distributed.")
601 ->set_short_character('k')
602 ->set_value(true);
603
604 this->create_new_option(&this->m_upgrade_systables_only, "upgrade-system-tables",
605 "Only upgrade the system tables, do not try to upgrade the data.")
606 ->set_short_character('s');
607
608 this->create_new_option(&this->m_verbose, "verbose",
609 "Display more output about the process.")
610 ->set_short_character('v')
611 ->set_value(true);
612
613 this->create_new_option(&this->m_write_binlog, "write-binlog",
614 "Write all executed SQL statements to binary log. Disabled by default; "
615 "use when statements should be sent to replication slaves.");
616
617 this->create_new_option(&this->m_ignore_errors, "force",
618 "Force execution of SQL statements even if mysql_upgrade has already "
619 "been executed for the current version of MySQL.")
620 ->set_short_character('f');
621
622 this->create_new_option(&this->m_skip_sys_schema, "skip-sys-schema",
623 "Do not upgrade/install the sys schema.")
624 ->set_value(false);
625 }
626
error(const Message_data & message)627 void error(const Message_data& message)
628 {
629 std::cerr << "Upgrade process encountered error and will not continue."
630 << std::endl;
631
632 exit(message.get_code());
633 }
634
635 private:
636 /**
637 Process messages and decides if to prints them.
638 */
process_error(const Message_data & message)639 int64 process_error(const Message_data& message)
640 {
641 if (this->m_temporary_verbose
642 || message.get_message_type() == Message_type_error)
643 {
644 message.print_error(this->get_name());
645 }
646 if (this->m_ignore_errors == false
647 && message.get_message_type() == Message_type_error)
648 {
649 return message.get_code();
650 }
651 return 0;
652 }
653
654 /**
655 Process warning messages during upgrades.
656 */
process_warning(const Message_data & message)657 void process_warning(const Message_data& message)
658 {
659 if (this->m_temporary_verbose &&
660 message.get_message_type() == Message_type_warning)
661 {
662 message.print_error(this->get_name());
663 }
664 }
665 /**
666 Prints error occurred in main routine.
667 */
print_error(int exit_code,string message)668 int print_error(int exit_code, string message)
669 {
670 std::cout << "Error occurred: " << message << std::endl;
671 return exit_code;
672 }
673
674 /**
675 Update all system tables in MySQL Server to current
676 version executing all the SQL commands
677 compiled into the mysql_fix_privilege_tables array
678 */
run_sql_fix_privilege_tables()679 int64 run_sql_fix_privilege_tables()
680 {
681 const char **query_ptr;
682 int64 result;
683
684 Mysql_query_runner runner(*this->m_query_runner);
685 Instance_callback<int64, const Mysql_query_runner::Row&, Program>
686 result_cb(this, &Program::result_callback);
687 Instance_callback<int64, const Message_data&, Program>
688 message_cb(this, &Program::fix_privilage_tables_error);
689
690 runner.add_result_callback(&result_cb);
691 runner.add_message_callback(&message_cb);
692
693 this->print_verbose_message("Running queries to upgrade MySQL server.");
694
695 for ( query_ptr= &mysql_fix_privilege_tables[0];
696 *query_ptr != NULL;
697 query_ptr++
698 )
699 {
700 /*
701 Check if next query is SHOW WARNINGS, if so enable temporarily
702 verbosity of server messages.
703 */
704 this->m_temporary_verbose= (*(query_ptr+1) != NULL
705 && strcmp(*(query_ptr+1), "SHOW WARNINGS;\n") == 0);
706
707 result= runner.run_query(*query_ptr);
708 if (!this->m_ignore_errors && result != 0)
709 {
710 return result;
711 }
712 }
713
714 return 0;
715 }
716
717 /**
718 Update the sys schema
719 */
run_sys_schema_upgrade()720 int run_sys_schema_upgrade()
721 {
722 const char **query_ptr;
723 int result;
724
725 Mysql_query_runner runner(*this->m_query_runner);
726 Instance_callback<int64, const Mysql_query_runner::Row&, Program>
727 result_cb(this, &Program::result_callback);
728 Instance_callback<int64, const Message_data&, Program>
729 message_cb(this, &Program::fix_privilage_tables_error);
730
731 runner.add_result_callback(&result_cb);
732 runner.add_message_callback(&message_cb);
733
734 this->print_verbose_message("Upgrading the sys schema.");
735
736 for ( query_ptr= &mysql_sys_schema[0];
737 *query_ptr != NULL;
738 query_ptr++
739 )
740 {
741 result= runner.run_query(*query_ptr);
742 if (!this->m_ignore_errors && result != 0)
743 {
744 return result;
745 }
746 }
747
748 return 0;
749 }
750
751 /**
752 Gets path to file to write upgrade info into. Path is based on datadir of
753 server.
754 */
get_upgrade_info_file_name(char * name)755 int64 get_upgrade_info_file_name(char* name)
756 {
757 bool exists;
758
759 if (m_datadir.empty())
760 {
761 int64 res= Show_variable_query_extractor::get_variable_value(
762 this->m_query_runner, "datadir", m_datadir, exists);
763
764 res |= !exists;
765 if (res != 0)
766 {
767 return res;
768 }
769 }
770
771 fn_format(name, "mysql_upgrade_info", m_datadir.c_str(), "", MYF(0));
772
773 return 0;
774 }
775 /**
776 Read the content of mysql_upgrade_info file and
777 compare the version number form file against
778 version number which mysql_upgrade was compiled for.
779
780 NOTE
781 This is an optimization to avoid running mysql_upgrade
782 when it's already been performed for the particular
783 version of MySQL.
784
785 In case the MySQL server can't return the upgrade info
786 file it's always better to report that the upgrade hasn't
787 been performed.
788 */
is_upgrade_already_done()789 bool is_upgrade_already_done()
790 {
791 FILE *in;
792 char upgrade_info_file[FN_REFLEN]= {0};
793 char buf[sizeof(MYSQL_SERVER_VERSION)+1];
794 char *res;
795
796 this->print_verbose_message("Checking if update is needed.");
797
798 if (this->get_upgrade_info_file_name(upgrade_info_file) != 0)
799 return false; /* Could not get filename => not sure */
800
801 if (!(in= my_fopen(upgrade_info_file, O_RDONLY, MYF(0))))
802 return false; /* Could not open file => not sure */
803
804 /*
805 Read from file, don't care if it fails since it
806 will be detected by the strncmp
807 */
808 memset(buf, 0, sizeof(buf));
809 res= fgets(buf, sizeof(buf), in);
810
811 my_fclose(in, MYF(0));
812
813 if (!res)
814 return false; /* Could not read from file => not sure */
815
816 return (strncmp(res, MYSQL_SERVER_VERSION,
817 sizeof(MYSQL_SERVER_VERSION)-1)==0);
818 }
819
820 /**
821 Write mysql_upgrade_info file in servers data dir indicating that
822 upgrade has been done for this version
823
824 NOTE
825 This might very well fail but since it's just an optimization
826 to run mysql_upgrade only when necessary the error can be
827 ignored.
828 */
create_mysql_upgrade_info_file()829 void create_mysql_upgrade_info_file()
830 {
831 FILE *out;
832 char upgrade_info_file[FN_REFLEN]= {0};
833
834 if (this->get_upgrade_info_file_name(upgrade_info_file) != 0)
835 return; /* Could not get filename => skip */
836
837 if (!(out= my_fopen(upgrade_info_file, O_TRUNC | O_WRONLY, MYF(0))))
838 {
839 fprintf(stderr,
840 "Could not create the upgrade info file '%s' in "
841 "the MySQL Servers datadir, errno: %d\n",
842 upgrade_info_file, errno);
843 return;
844 }
845
846 /* Write new version to file */
847 fputs(MYSQL_SERVER_VERSION, out);
848 my_fclose(out, MYF(0));
849
850 /*
851 Check if the upgrade_info_file was properly created/updated
852 It's not a fatal error -> just print a message if it fails.
853 */
854 if (!this->is_upgrade_already_done())
855 fprintf(stderr,
856 "Could not write to the upgrade info file '%s' in "
857 "the MySQL Servers datadir, errno: %d\n",
858 upgrade_info_file, errno);
859 return;
860 }
861
862 /**
863 Check if the server version matches with the server version mysql_upgrade
864 was compiled with.
865
866 @return true match successful
867 false failed
868 */
is_version_matching()869 bool is_version_matching()
870 {
871 string version;
872 bool exists;
873
874 this->print_verbose_message("Checking server version.");
875
876 if (Show_variable_query_extractor::get_variable_value(
877 this->m_query_runner, "version", version, exists) != 0)
878 {
879 return false;
880 }
881
882 if (this->calc_server_version(version.c_str()) != MYSQL_VERSION_ID)
883 {
884 fprintf(stderr, "Error: Server version (%s) does not match with the "
885 "version of\nthe server (%s) with which this program was built/"
886 "distributed. You can\nuse --skip-version-check to skip this "
887 "check.\n", version.c_str(), MYSQL_SERVER_VERSION);
888 return false;
889 }
890 else
891 return true;
892 }
893
894 /**
895 Convert the specified version string into the numeric format.
896 */
calc_server_version(const char * some_version)897 ulong calc_server_version(const char *some_version)
898 {
899 uint major, minor, version;
900 const char *point= some_version, *end_point;
901 major= (uint) strtoul(point, (char**)&end_point, 10); point=end_point+1;
902 minor= (uint) strtoul(point, (char**)&end_point, 10); point=end_point+1;
903 version= (uint) strtoul(point, (char**)&end_point, 10);
904 return (ulong) major * 10000L + (ulong)(minor * 100 + version);
905 }
906
907 /**
908 Server message callback to be called during execution of upgrade queries.
909 */
fix_privilage_tables_error(const Message_data & message)910 int64 fix_privilage_tables_error(const Message_data& message)
911 {
912 // This if it is error message and if it is not expected one.
913 if (message.get_message_type() == Message_type_error
914 && is_expected_error(message.get_code()) == false)
915 {
916 // Pass this message to other callbacks, i.e. print_error to be printed out.
917 return 0;
918 }
919 process_warning(message);
920 // Do not pass filtered out messages to other callbacks, i.e. print_error.
921 return -1;
922 }
923
result_callback(const Mysql_query_runner::Row & result_row)924 int64 result_callback(const Mysql_query_runner::Row& result_row)
925 {
926 /*
927 This is an old hacky way used in upgrade queries to show warnings from
928 executed queries in fix_privilege_tables. It is not result from
929 "SHOW WARNINGS" query.
930 */
931 if (result_row.size() == 1 && !(result_row.is_value_null(0)))
932 {
933 String error;
934 uint dummy_errors;
935 error.copy("warning:", 8, &my_charset_latin1,
936 this->m_mysql_connection->charset, &dummy_errors);
937 std::string result= result_row[0];
938 result= result.substr(0, 8);
939
940 if (my_strcasecmp(this->m_mysql_connection->charset,
941 result.c_str(), error.c_ptr()) == 0)
942 {
943 std::cerr << result_row[0] << std::endl;
944 }
945 }
946 Mysql_query_runner::cleanup_result(result_row);
947 return 0;
948 }
949
950 /**
951 Checks if given error code is expected during upgrade queries execution.
952 */
is_expected_error(int64 error_no)953 bool is_expected_error(int64 error_no)
954 {
955 static const int64 expected_errors[]=
956 {
957 ER_DUP_FIELDNAME, /* Duplicate column name */
958 ER_DUP_KEYNAME, /* Duplicate key name */
959 ER_BAD_FIELD_ERROR, /* Unknown column */
960 0
961 };
962
963 const int64* expected_error= expected_errors;
964 while (*expected_error)
965 {
966 if (*expected_error == error_no)
967 {
968 return true; /* Found expected error */
969 }
970 expected_error++;
971 }
972 return false;
973 }
974
975 /**
976 Prepares mysqlcheck program instance to be used by mysql_upgrade.
977 */
prepare_mysqlcheck(Mysql::Tools::Check::Program & mysql_check)978 Mysql::Tools::Check::Program* prepare_mysqlcheck(
979 Mysql::Tools::Check::Program& mysql_check)
980 {
981 mysql_check_errors= 0;
982
983 return (&mysql_check)
984 ->set_ignore_errors(this->m_ignore_errors)
985 ->enable_writing_binlog(this->m_write_binlog)
986 ->enable_verbosity(this->m_verbose)
987 ->set_error_callback(::mysql_check_error_callback);
988 }
989
990 /**
991 Check and upgrade(if necessary) all tables in the server using mysqlcheck.
992 */
run_mysqlcheck_upgrade()993 int run_mysqlcheck_upgrade()
994 {
995 Mysql::Tools::Check::Program mysql_check;
996 this->prepare_mysqlcheck(mysql_check)
997 ->enable_auto_repair(true)
998 ->enable_upgrade(true)
999 ->set_skip_database("mysql")
1000 ->check_all_databases(this->m_mysql_connection);
1001 return mysql_check_errors;
1002 }
1003
1004 /**
1005 Upgrade all tables and DBs names in the server using mysqlcheck.
1006 */
run_mysqlcheck_fixnames()1007 int run_mysqlcheck_fixnames()
1008 {
1009 Mysql::Tools::Check::Program mysql_check;
1010 this->prepare_mysqlcheck(mysql_check)
1011 ->enable_fixing_db_names(true)
1012 ->enable_fixing_table_names(true)
1013 ->set_skip_database("mysql")
1014 ->upgrade_all_databases(this->m_mysql_connection);
1015 return mysql_check_errors;
1016 }
1017
1018 /**
1019 Check and upgrade(if necessary) all system tables in the server using
1020 mysqlcheck.
1021 */
run_mysqlcheck_mysql_db_upgrade()1022 int run_mysqlcheck_mysql_db_upgrade()
1023 {
1024 vector<string> databases;
1025 Mysql::Tools::Check::Program mysql_check;
1026
1027 databases.push_back("mysql");
1028 this->prepare_mysqlcheck(mysql_check)
1029 ->enable_auto_repair(true)
1030 ->enable_upgrade(true)
1031 ->check_databases(this->m_mysql_connection, databases);
1032 return mysql_check_errors;
1033 }
1034
1035 /**
1036 Upgrade all system tables and system DB names in the server using
1037 mysqlcheck.
1038 */
run_mysqlcheck_mysql_db_fixnames()1039 int run_mysqlcheck_mysql_db_fixnames()
1040 {
1041 vector<string> databases;
1042 Mysql::Tools::Check::Program mysql_check;
1043
1044 databases.push_back("mysql");
1045 this->prepare_mysqlcheck(mysql_check)
1046 ->enable_fixing_db_names(true)
1047 ->enable_fixing_table_names(true)
1048 ->upgrade_databases(this->m_mysql_connection, databases);
1049 return mysql_check_errors;
1050 }
1051
print_verbose_message(string message)1052 void print_verbose_message(string message)
1053 {
1054 if (!this->m_verbose)
1055 return;
1056
1057 std::cout << message << std::endl;
1058 }
1059
1060 MYSQL* m_mysql_connection;
1061 Mysql_query_runner* m_query_runner;
1062 string m_datadir;
1063 bool m_write_binlog;
1064 bool m_upgrade_systables_only;
1065 bool m_skip_sys_schema;
1066 bool m_check_version;
1067 bool m_ignore_errors;
1068 bool m_verbose;
1069 /**
1070 Enabled during some queries execution to force printing all notes and
1071 warnings regardless "verbose" option.
1072 */
1073 bool m_temporary_verbose;
1074 };
1075
1076 }
1077 }
1078 }
1079
1080 static ::Mysql::Tools::Upgrade::Program program;
1081
main(int argc,char ** argv)1082 int main(int argc, char **argv)
1083 {
1084 program.run(argc, argv);
1085 return 0;
1086 }
1087