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