1 /* Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved.
2 
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License, version 2.0,
5    as published by the Free Software Foundation.
6 
7    This program is also distributed with certain software (including
8    but not limited to OpenSSL) that is licensed under separate terms,
9    as designated in a particular file or component or in included license
10    documentation.  The authors of MySQL hereby grant you an additional
11    permission to link the program and your derivative works with the
12    separately licensed software that they have included with MySQL.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License, version 2.0, for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */
22 
23 #include "sql/dd/impl/upgrade/dd.h"
24 #include "sql/dd/impl/bootstrap/bootstrapper.h"
25 #include "sql/dd/impl/utils.h"
26 
27 #include <set>
28 
29 #include "my_dbug.h"
30 #include "mysql/components/services/log_builtins.h"
31 #include "mysql_version.h"  // MYSQL_VERSION_ID
32 #include "mysqld_error.h"
33 #include "sql/dd/cache/dictionary_client.h"  // dd::cache::Dictionary_client
34 #include "sql/dd/impl/bootstrap/bootstrap_ctx.h"        // DD_bootstrap_ctx
35 #include "sql/dd/impl/cache/shared_dictionary_cache.h"  // Shared_dictionary_cache
36 #include "sql/dd/impl/system_registry.h"                // dd::System_tables
37 #include "sql/dd/impl/tables/columns.h"                 // dd::tables::Columns
38 #include "sql/dd/impl/tables/dd_properties.h"  // dd::tables::DD_properties
39 #include "sql/dd/impl/tables/events.h"         // dd::tables::Events
40 #include "sql/dd/impl/tables/foreign_key_column_usage.h"  // dd::tables::Fore...
41 #include "sql/dd/impl/tables/foreign_keys.h"      // dd::tables::Foreign_keys
42 #include "sql/dd/impl/tables/index_partitions.h"  // dd::tables::Index_partitions
43 #include "sql/dd/impl/tables/indexes.h"           // dd::tables::Indexes
44 #include "sql/dd/impl/tables/routines.h"          // dd::tables::Routines
45 #include "sql/dd/impl/tables/schemata.h"          // dd::tables::Schemata
46 #include "sql/dd/impl/tables/table_partitions.h"  // dd::tables::Table_partitions
47 #include "sql/dd/impl/tables/tables.h"            // dd::tables::Tables
48 #include "sql/dd/impl/tables/tablespaces.h"       // dd::tables::Tablespaces
49 #include "sql/dd/impl/tables/triggers.h"          // dd::tables::Triggers
50 #include "sql/dd/object_id.h"
51 #include "sql/dd/types/object_table.h"             // dd::Object_table
52 #include "sql/dd/types/object_table_definition.h"  // dd::Object_table_definition
53 #include "sql/dd/types/schema.h"
54 #include "sql/sd_notify.h"  // sysd::notify
55 #include "sql/sql_class.h"  // THD
56 #include "sql/table.h"      // MYSQL_SCHEMA_NAME
57 
58 namespace dd {
59 
60 namespace {
61 /*
62   Create the temporary schemas needed during upgrade, and fetch their ids.
63 */
64 /* purecov: begin inspected */
create_temporary_schemas(THD * thd,Object_id * mysql_schema_id,Object_id * target_table_schema_id,String_type * target_table_schema_name,Object_id * actual_table_schema_id)65 bool create_temporary_schemas(THD *thd, Object_id *mysql_schema_id,
66                               Object_id *target_table_schema_id,
67                               String_type *target_table_schema_name,
68                               Object_id *actual_table_schema_id) {
69   /*
70     Find an unused target schema name. Prepare a base name, and append
71     a counter, increment until a non-existing name is found
72   */
73   dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
74   const dd::Schema *schema = nullptr;
75   std::stringstream ss;
76 
77   DBUG_ASSERT(target_table_schema_name != nullptr);
78   *target_table_schema_name = String_type("");
79   ss << "dd_upgrade_targets_" << MYSQL_VERSION_ID;
80   String_type tmp_schema_name_base{ss.str().c_str()};
81   int count = 0;
82   do {
83     if (thd->dd_client()->acquire(ss.str().c_str(), &schema)) return true;
84     if (schema == nullptr) {
85       *target_table_schema_name = ss.str().c_str();
86       break;
87     }
88     ss.str("");
89     ss.clear();
90     ss << tmp_schema_name_base << "_" << count++;
91   } while (count < 1000);
92 
93   if (target_table_schema_name->empty()) {
94     LogErr(ERROR_LEVEL, ER_DD_UPGRADE_SCHEMA_UNAVAILABLE, ss.str().c_str());
95     return true;
96   }
97 
98   /*
99     Find an unused schema name where we can temporarily move the actual
100     tables to be removed or modified.
101   */
102   String_type actual_table_schema_name{""};
103   ss.str("");
104   ss.clear();
105   ss << "dd_upgrade_garbage_" << MYSQL_VERSION_ID;
106   tmp_schema_name_base = String_type(ss.str().c_str());
107   count = 0;
108   do {
109     if (thd->dd_client()->acquire(ss.str().c_str(), &schema)) return true;
110     if (schema == nullptr) {
111       actual_table_schema_name = ss.str().c_str();
112       break;
113     }
114     ss.str("");
115     ss.clear();
116     ss << tmp_schema_name_base << "_" << count++;
117   } while (count < 1000);
118 
119   if (actual_table_schema_name.empty()) {
120     LogErr(ERROR_LEVEL, ER_DD_UPGRADE_SCHEMA_UNAVAILABLE, ss.str().c_str());
121     return true;
122   }
123 
124   /*
125     Store the schema names in DD_properties and commit. The schemas will
126     now be removed on next restart.
127   */
128   if (dd::tables::DD_properties::instance().set(thd, "UPGRADE_TARGET_SCHEMA",
129                                                 *target_table_schema_name) ||
130       dd::tables::DD_properties::instance().set(thd, "UPGRADE_ACTUAL_SCHEMA",
131                                                 actual_table_schema_name)) {
132     return dd::end_transaction(thd, true);
133   }
134 
135   if (dd::end_transaction(thd, false)) return true;
136 
137   if (dd::execute_query(
138           thd, dd::String_type("CREATE SCHEMA ") + actual_table_schema_name +
139                    dd::String_type(" DEFAULT COLLATE '") +
140                    dd::String_type(default_charset_info->name) + "'") ||
141       dd::execute_query(
142           thd, dd::String_type("CREATE SCHEMA ") + *target_table_schema_name +
143                    dd::String_type(" DEFAULT COLLATE '") +
144                    dd::String_type(default_charset_info->name) + "'") ||
145       dd::execute_query(thd,
146                         dd::String_type("USE ") + *target_table_schema_name)) {
147     return true;
148   }
149 
150   /*
151     Get hold of the schema ids of the temporary target schema, the
152     temporary actual schema, and the mysql schema. These are needed
153     later in various situations in the upgrade execution.
154   */
155   if (thd->dd_client()->acquire(MYSQL_SCHEMA_NAME.str, &schema) ||
156       schema == nullptr)
157     return true;
158 
159   DBUG_ASSERT(mysql_schema_id != nullptr);
160   *mysql_schema_id = schema->id();
161   DBUG_ASSERT(*mysql_schema_id == 1);
162 
163   if (thd->dd_client()->acquire(*target_table_schema_name, &schema) ||
164       schema == nullptr)
165     return true;
166 
167   DBUG_ASSERT(target_table_schema_id != nullptr);
168   *target_table_schema_id = schema->id();
169 
170   if (thd->dd_client()->acquire(actual_table_schema_name, &schema) ||
171       schema == nullptr)
172     return true;
173 
174   DBUG_ASSERT(actual_table_schema_id != nullptr);
175   *actual_table_schema_id = schema->id();
176 
177   return false;
178 }
179 /* purecov: end */
180 
181 /*
182   Establish the sets of names of tables to be created and/or removed.
183 */
184 /* purecov: begin inspected */
establish_table_name_sets(std::set<String_type> * create_set,std::set<String_type> * remove_set)185 void establish_table_name_sets(std::set<String_type> *create_set,
186                                std::set<String_type> *remove_set) {
187   /*
188     Establish the table change sets:
189     - The 'remove' set contains the tables that will eventually be removed,
190       i.e., they are present in the actual version, and either abandoned
191       or replaced by another table definition in the target version.
192     - The 'create' set contains the tables that must be created, i.e., they
193       are either new tables in the target version, or they replace an
194       existing table in the actual version.
195   */
196   DBUG_ASSERT(create_set != nullptr && create_set->empty());
197   DBUG_ASSERT(remove_set != nullptr && remove_set->empty());
198   for (System_tables::Const_iterator it = System_tables::instance()->begin();
199        it != System_tables::instance()->end(); ++it) {
200     if (is_non_inert_dd_or_ddse_table((*it)->property())) {
201       /*
202         In this context, all tables should have an Object_table. Minor
203         downgrade is the only situation where an Object_table may not exist,
204         but minor upgrade will never enter this code path.
205       */
206       DBUG_ASSERT((*it)->entity() != nullptr);
207 
208       String_type target_ddl_statement("");
209       const Object_table_definition *target_table_def =
210           (*it)->entity()->target_table_definition();
211       /*
212          The target table definition may not be present if the table
213          is abandoned.
214       */
215       if (target_table_def) {
216         target_ddl_statement = target_table_def->get_ddl();
217       }
218 
219       String_type actual_ddl_statement("");
220       const Object_table_definition *actual_table_def =
221           (*it)->entity()->actual_table_definition();
222       /*
223         The actual definition may not be present if this is a new table
224         which has been added.
225       */
226       if (actual_table_def) {
227         actual_ddl_statement = actual_table_def->get_ddl();
228       }
229 
230       /*
231         Remove and/or create as needed. If the definitions are non-null
232         and equal, no change has been done, and hence upgrade of the table
233         is irrelevant.
234       */
235       if (target_table_def == nullptr && actual_table_def != nullptr)
236         remove_set->insert((*it)->entity()->name());
237       else if (target_table_def != nullptr && actual_table_def == nullptr)
238         create_set->insert((*it)->entity()->name());
239       else if (target_ddl_statement != actual_ddl_statement) {
240         /*
241           Abandoned tables that are not present will have target and actual
242           statements == "", and will therefore not be added to the create
243           nor remove set.
244         */
245         remove_set->insert((*it)->entity()->name());
246         create_set->insert((*it)->entity()->name());
247       }
248     }
249   }
250 }
251 /* purecov: end */
252 
253 /**
254   Adjust metadata in source DD tables in mysql schema. This is done by
255   mostly executing UPDATE queries on them, but we do not migrate data to
256   destination DD tables.
257 
258   @param   thd         Thread context.
259 
260   @returns false if success. otherwise true.
261 */
262 /* purecov: begin inspected */
update_meta_data(THD * thd)263 bool update_meta_data(THD *thd) {
264   /*
265     Turn off foreign key checks while migrating the meta data.
266   */
267   if (dd::execute_query(thd, "SET FOREIGN_KEY_CHECKS= 0"))
268     return dd::end_transaction(thd, true);
269 
270   /* Version dependent migration of meta data can be added here. */
271 
272   /*
273     8.0.11 allowed entries with 0 timestamps to be created. These must
274     be updated, otherwise, upgrade will fail since 0 timestamps are not
275     allowed with the default SQL mode.
276   */
277   if (bootstrap::DD_bootstrap_ctx::instance().is_dd_upgrade_from_before(
278           bootstrap::DD_VERSION_80012)) {
279     if (dd::execute_query(thd,
280                           "UPDATE mysql.tables SET last_altered = "
281                           "CURRENT_TIMESTAMP WHERE last_altered = 0"))
282       return dd::end_transaction(thd, true);
283     if (dd::execute_query(thd,
284                           "UPDATE mysql.tables SET created = CURRENT_TIMESTAMP "
285                           "WHERE created = 0"))
286       return dd::end_transaction(thd, true);
287   }
288 
289   /* Upgrade from 80015. */
290   if (bootstrap::DD_bootstrap_ctx::instance().is_dd_upgrade_from_before(
291           bootstrap::DD_VERSION_80016)) {
292     // A) REMOVE 'ENCRYPTION' KEY FROM MYSQL.TABLESPACES.OPTIONS FOR
293     //    NON-INNODB TABLESPACES/TABLES
294 
295     /*
296       Remove ENCRYPTION clause for unencrypted non-InnoDB tablespaces.
297       Because its only InnoDB that support encryption in 8.0.16.
298     */
299     static_assert(dd::tables::Tablespaces::NUMBER_OF_FIELDS == 7,
300                   "SQL statements rely on a specific table definition");
301     if (dd::execute_query(
302             thd,
303             "UPDATE mysql.tablespaces ts "
304             "SET ts.options=REMOVE_DD_PROPERTY_KEY(ts.options, 'encryption') "
305             "WHERE ts.engine!='InnoDB' AND "
306             "GET_DD_PROPERTY_KEY_VALUE(ts.options,'encryption') IS NOT "
307             "NULL")) {
308       return dd::end_transaction(thd, true);
309     }
310 
311     // Remove ENCRYPTION clause for non-InnoDB tables.
312     static_assert(dd::tables::Tables::NUMBER_OF_FIELDS == 37,
313                   "SQL statements rely on a specific table definition");
314     if (dd::execute_query(
315             thd,
316             "UPDATE mysql.tables tbl "
317             "SET tbl.options=REMOVE_DD_PROPERTY_KEY(tbl.options, "
318             "'encrypt_type') "
319             "WHERE tbl.tablespace_id IS NULL AND tbl.engine!='InnoDB' AND "
320             "GET_DD_PROPERTY_KEY_VALUE(tbl.options,'encrypt_type') IS NOT "
321             "NULL")) {
322       return dd::end_transaction(thd, true);
323     }
324 
325     // B) UPDATE MYSQL.TABLESPACES.OPTIONS 'ENCRYPTION' KEY FOR INNODB SE.
326 
327     /*
328       Add ENCRYPTION clause for InnoDB file-per-table tablespaces used by
329       partitioned table.
330 
331       For a partitioned table using file-per-table tablespace, the
332       tablespace_id is stored in tables corresponding
333       mysql.index_partitions.tablespace_id. The following query finds the
334       partitioned tablespace by joining several DD tables and updates
335       the 'encryption' key same as that of tables encryption type.
336 
337       This is done as we expect all innodb tablespaces to have proper
338       'encryption' flag set.
339     */
340     static_assert(dd::tables::Index_partitions::NUMBER_OF_FIELDS == 5,
341                   "SQL statements rely on a specific table definition");
342     static_assert(dd::tables::Table_partitions::NUMBER_OF_FIELDS == 12,
343                   "SQL statements rely on a specific table definition");
344     static_assert(dd::tables::Tables::NUMBER_OF_FIELDS == 37,
345                   "SQL statements rely on a specific table definition");
346     static_assert(dd::tables::Indexes::NUMBER_OF_FIELDS == 17,
347                   "SQL statements rely on a specific table definition");
348     if (dd::execute_query(
349             thd,
350             "UPDATE mysql.index_partitions ip "
351             "JOIN mysql.tablespaces ts ON ts.id = ip.tablespace_id "
352             "JOIN mysql.table_partitions p ON p.id = ip.partition_id "
353             "JOIN mysql.tables t ON t.id = p.table_id "
354             "JOIN mysql.indexes i ON i.table_id = t.id "
355             "SET ts.options=CONCAT(IFNULL(ts.options,''), "
356             "IF(LOWER(GET_DD_PROPERTY_KEY_VALUE(t.options,'encrypt_type'))='y' "
357             ", 'encryption=Y;','encryption=N;')) "
358             "WHERE t.tablespace_id IS NULL AND i.tablespace_id IS NULL AND "
359             "p.tablespace_id IS NULL AND ts.engine='InnoDB' AND "
360             "GET_DD_PROPERTY_KEY_VALUE(t.options,'encrypt_type') IS NOT NULL "
361             "AND "
362             "GET_DD_PROPERTY_KEY_VALUE(ts.options,'encryption') IS NULL ")) {
363       return dd::end_transaction(thd, true);
364     }
365 
366     /*
367       Add ENCRYPTION clause for InnoDB file-per-table tablespaces same as
368       encryption type of the table.
369 
370       For a tables using file-per-table tablespace, the tablespace_id is
371       stored in tables corresponding mysql.indexes.tablespace_id.
372       The following query finds the tablespace by joining
373       several DD tables and updates the 'encryption' key same as that of
374       tables encryption type.
375 
376       This is done as we expect all innodb tablespaces to have proper
377       'encryption' flag set.
378     */
379     static_assert(dd::tables::Indexes::NUMBER_OF_FIELDS == 17,
380                   "SQL statements rely on a specific table definition");
381     static_assert(dd::tables::Tablespaces::NUMBER_OF_FIELDS == 7,
382                   "SQL statements rely on a specific table definition");
383     static_assert(dd::tables::Tables::NUMBER_OF_FIELDS == 37,
384                   "SQL statements rely on a specific table definition");
385     if (dd::execute_query(
386             thd,
387             "UPDATE mysql.indexes i "
388             "JOIN mysql.tablespaces ts ON ts.id = i.tablespace_id "
389             "JOIN mysql.tables t ON t.id = i.table_id "
390             "SET ts.options=CONCAT(IFNULL(ts.options,''), "
391             "IF(LOWER(GET_DD_PROPERTY_KEY_VALUE(t.options,'encrypt_type'))='y'"
392             ", 'encryption=Y;','encryption=N;')) "
393             "WHERE ts.engine='InnoDB' AND t.tablespace_id IS NULL "
394             "AND GET_DD_PROPERTY_KEY_VALUE(ts.options,'encryption') IS NULL")) {
395       return dd::end_transaction(thd, true);
396     }
397 
398     /*
399       Update ENCRYPTION clause for unencrypted InnoDB tablespaces.
400       Where the 'encryption' key value is empty string ''.
401     */
402     static_assert(dd::tables::Tablespaces::NUMBER_OF_FIELDS == 7,
403                   "SQL statements rely on a specific table definition");
404     if (dd::execute_query(
405             thd,
406             "UPDATE mysql.tablespaces ts "
407             "SET ts.options=CONCAT(IFNULL(REMOVE_DD_PROPERTY_KEY(ts.options, "
408             "'encryption'),''), 'encryption=N;') "
409             "WHERE ts.engine='InnoDB' AND "
410             "GET_DD_PROPERTY_KEY_VALUE(ts.options,'encryption') = ''")) {
411       return dd::end_transaction(thd, true);
412     }
413 
414     /*
415       Store ENCRYPTION clause for unencrypted InnoDB general tablespaces,
416       when the 'encryption' key is not yet present.
417     */
418     static_assert(dd::tables::Tablespaces::NUMBER_OF_FIELDS == 7,
419                   "SQL statements rely on a specific table definition");
420     if (dd::execute_query(
421             thd,
422             "UPDATE mysql.tablespaces ts "
423             "SET ts.options=CONCAT(IFNULL(ts.options,''), 'encryption=N;') "
424             "WHERE ts.engine='InnoDB' AND "
425             "GET_DD_PROPERTY_KEY_VALUE(ts.options,'encryption') IS NULL ")) {
426       return dd::end_transaction(thd, true);
427     }
428 
429     // C) UPDATE MYSQL.TABLES.OPTIONS 'ENCRYPT_TYPE' KEY FOR INNODB TABLES.
430 
431     /*
432       Update 'encrypt_type' flag for innodb tables using general tablespace.
433       It is not possible to have general tablespaces used in partitioned
434       table as of 8.0.15, so we ignore to check for partitioned tables
435       using general tablespace.
436     */
437     static_assert(dd::tables::Tables::NUMBER_OF_FIELDS == 37,
438                   "SQL statements rely on a specific table definition");
439     static_assert(dd::tables::Tablespaces::NUMBER_OF_FIELDS == 7,
440                   "SQL statements rely on a specific table definition");
441     if (dd::execute_query(
442             thd,
443             "UPDATE mysql.tables t "
444             "JOIN mysql.tablespaces ts ON ts.id = t.tablespace_id "
445             "SET t.options=CONCAT(IFNULL(t.options,''), "
446             "IF(LOWER(GET_DD_PROPERTY_KEY_VALUE(ts.options,'encryption'))='y'"
447             ", 'encrypt_type=Y;','encrypt_type=N;')) "
448             "WHERE t.engine='InnoDB' AND t.tablespace_id IS NOT NULL AND "
449             "GET_DD_PROPERTY_KEY_VALUE(ts.options,'encryption') IS NOT NULL "
450             "AND GET_DD_PROPERTY_KEY_VALUE(t.options,'encrypt_type') IS "
451             "NULL")) {
452       return dd::end_transaction(thd, true);
453     }
454 
455     /*
456       Store 'encrypt_type=N' for unencrypted InnoDB file-per-table tables,
457       for tables which does not have a 'encrypt_type' key stored already.
458     */
459     static_assert(dd::tables::Tables::NUMBER_OF_FIELDS == 37,
460                   "SQL statements rely on a specific table definition");
461     if (dd::execute_query(
462             thd,
463             "UPDATE mysql.tables t "
464             "SET t.options=CONCAT(IFNULL(t.options,''), 'encrypt_type=N;') "
465             "WHERE t.tablespace_id IS NULL AND t.engine='InnoDB' AND "
466             "GET_DD_PROPERTY_KEY_VALUE(t.options,'encrypt_type') IS NULL")) {
467       return dd::end_transaction(thd, true);
468     }
469   }
470   /* Upgrade from 8.0.20 or previous. */
471   if (bootstrap::DD_bootstrap_ctx::instance().is_dd_upgrade_from_before(
472           bootstrap::DD_VERSION_80021)) {
473     /*
474       Fix discard attribute for partitioned Tables.
475 
476       Due to bug, prior to 8.0.21, discard attribute for partitioned tables
477       doesn't reflect reality - that is, it can be set to true even if no
478       partition is discarded or it can be set to false, even if some
479       partitions are discarded.
480 
481       Moreover, due to the bug, the attribute is only set for a whole Table,
482       whereas only some partitions of the table may be discarded.
483 
484       However, Tablespace state==discarded reflect reality properly.
485 
486       Since 8.0.21, discard attribute for partitioned tables follow the rules:
487       a) dd::Table for partitioned Table cannot have discard attribute
488       b) only leaf dd::Partitions can have discard attribute
489 
490       We can recreate such state looking at Tablespace state in three steps:
491       a) Remove discard attribute from dd::Tables which have any partitions
492       b) Remove discard attribute from all dd::Partitions
493       c) For each leaf_partition (taken from index_partitions) set discard
494       attribute depending on whether its Tablespace state is discarded or not
495     */
496     static_assert(dd::tables::Index_partitions::NUMBER_OF_FIELDS == 5,
497                   "SQL statements rely on a specific table definition");
498     static_assert(dd::tables::Table_partitions::NUMBER_OF_FIELDS == 12,
499                   "SQL statements rely on a specific table definition");
500     static_assert(dd::tables::Tables::NUMBER_OF_FIELDS == 37,
501                   "SQL statements rely on a specific table definition");
502     static_assert(dd::tables::Tablespaces::NUMBER_OF_FIELDS == 7,
503                   "SQL statements rely on a specific table definition");
504 
505     /*
506       Remove discard attribute from all Tables
507       Use mysql.index_partitions to find all partitions
508     */
509     if (dd::execute_query(
510             thd,
511             "UPDATE mysql.tables tbl "
512             "JOIN mysql.table_partitions tp ON tp.table_id = tbl.id "
513             "JOIN mysql.index_partitions ip ON ip.partition_id = tp.id "
514             "SET "
515             "tbl.se_private_data=NULLIF(REMOVE_DD_PROPERTY_KEY(tbl.se_private_"
516             "data, 'discard'), '') "
517             "WHERE tbl.engine='InnoDB' AND "
518             "GET_DD_PROPERTY_KEY_VALUE(tbl.se_private_data, 'discard') IS NOT "
519             "NULL ")) {
520       return dd::end_transaction(thd, true);
521     }
522 
523     /*
524       Remove discard attribute from all table_partitions.
525 
526       Even though we didn't store discard attribute in mysql.table_partitions,
527       it's still possible there is such attribute there: in versions 8.0.0 to
528       8.0.14 it was possible to upgrade from 5.7, when MySQL had discarded
529       partitions. Such upgrade would add discard attribute to
530       mysql.table_partitions
531     */
532     if (dd::execute_query(thd,
533                           "UPDATE mysql.table_partitions tp "
534                           "SET "
535                           "tp.se_private_data=NULLIF(REMOVE_DD_PROPERTY_KEY(tp."
536                           "se_private_data, 'discard'), '') "
537                           "WHERE tp.engine='InnoDB' AND "
538                           "GET_DD_PROPERTY_KEY_VALUE(tp.se_private_data, "
539                           "'discard') IS NOT NULL ")) {
540       return dd::end_transaction(thd, true);
541     }
542 
543     /* Set discard attribute for all leaf_partitions by looking at its
544      * Tablespace state. mysql.index_partitions is used to access Tablespaces */
545     if (dd::execute_query(
546             thd,
547             "UPDATE mysql.table_partitions tp "
548             "JOIN mysql.index_partitions ip ON ip.partition_id = tp.id "
549             "JOIN mysql.tablespaces ts ON ip.tablespace_id = ts.id "
550             "SET tp.se_private_data = "
551             "CONCAT('discard=1;',IFNULL(tp.se_private_data,'')) "
552             "WHERE tp.engine='InnoDB' AND "
553             "GET_DD_PROPERTY_KEY_VALUE(ts.se_private_data, "
554             "'state')='discarded' ")) {
555       return dd::end_transaction(thd, true);
556     }
557   }
558 
559   /*
560     Turn foreign key checks back on and commit explicitly.
561   */
562   if (dd::execute_query(thd, "SET FOREIGN_KEY_CHECKS= 1"))
563     return dd::end_transaction(thd, true);
564 
565   return false;
566 }
567 /* purecov: end */
568 
569 /**
570   Copy meta data from the actual tables to the target tables.
571 
572   The default is to copy all data. This is sufficient if we e.g. add a
573   new index in the new DD version. If there are changes to the table
574   columns, e.g. if we add or remove a column, we must add code to handle
575   each case specifically. Suppose e.g. we add a new column to allow defining
576   a default tablespace for each schema, and store the tablespace id
577   in that column. Then, we could migrate the meta data for 'schemata' and
578   set a default value for all existing schemas:
579 
580   ...
581   migrated_set.insert("schemata");
582   if (dd::execute_query(thd, "INSERT INTO schemata "
583          "SELECT id, catalog_id, name, default_collation_id, 1, "
584          "       created, last_altered, options FROM mysql.schemata"))
585   ...
586 
587   The code block above would go into the 'Version dependent migration'
588   part of the function below.
589 
590   @param   thd         Thread context.
591   @param   create_set  Set of new or modified tables to be created.
592   @param   remove_set  Set of abandoned or modified tables to be removed.
593 
594   @returns false if success. otherwise true.
595 */
596 /* purecov: begin inspected */
migrate_meta_data(THD * thd,const std::set<String_type> & create_set,const std::set<String_type> & remove_set)597 bool migrate_meta_data(THD *thd, const std::set<String_type> &create_set,
598                        const std::set<String_type> &remove_set) {
599   /*
600     Turn off foreign key checks while migrating the meta data.
601   */
602   if (dd::execute_query(thd, "SET FOREIGN_KEY_CHECKS= 0"))
603     return dd::end_transaction(thd, true);
604 
605   /*
606     Explicitly migrate meta data for each table which has been modified.
607     Register the table name in the migrated_set to skip it in the default
608     handling below.
609   */
610   std::set<String_type> migrated_set{};
611 
612   /*
613     Version dependent migration of meta data can be added here. The migration
614     should be grouped by table with a conditional expression for each table,
615     branching out to do one single migration step for each table. Note that
616     if more than one migration step (i.e., an INSERT into the target table)
617     being executed for a table, then each step will overwrite the result of
618     the previous one.
619   */
620   auto migrate_table = [&](const String_type &name, const String_type &stmt) {
621     DBUG_ASSERT(create_set.find(name) != create_set.end());
622     /* A table must be migrated only once. */
623     DBUG_ASSERT(migrated_set.find(name) == migrated_set.end());
624     migrated_set.insert(name);
625     if (dd::execute_query(thd, stmt)) {
626       return dd::end_transaction(thd, true);
627     }
628     return false;
629   };
630 
631   auto is_dd_upgrade_from_before = [](uint dd_version) {
632     return bootstrap::DD_bootstrap_ctx::instance().is_dd_upgrade_from_before(
633         dd_version);
634   };
635 
636   /********************* Migration of mysql.tables *********************/
637   /* Upgrade from 80012 or earlier. */
638   static_assert(dd::tables::Tables::NUMBER_OF_FIELDS == 37,
639                 "SQL statements rely on a specific table definition");
640   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80013)) {
641     /* Column 'last_checked_for_upgrade' was added. */
642     if (migrate_table(
643             "tables",
644             "INSERT INTO tables SELECT *, 0, NULL, NULL FROM mysql.tables")) {
645       return true;
646     }
647   } else if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80021)) {
648     /*
649       Upgrade from 80020 and before.
650       Store NULL for new columns mysql.tables.engine_attribute and
651       mysql.tables.secondary_engine_attribute
652     */
653 
654     if (migrate_table(
655             "tables",
656             "INSERT INTO tables SELECT *, NULL, NULL FROM mysql.tables")) {
657       return true;
658     }
659   }
660   /********************* Migration of mysql.columns *********************/
661   /* Upgrade from 80020 or earlier. */
662   static_assert(dd::tables::Columns::NUMBER_OF_FIELDS == 32,
663                 "SQL statements rely on a specific table definition");
664   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80021)) {
665     /*
666       Upgrade from 80020 and before.
667       Store NULL for new columns mysql.columns.engine_attribute and
668       mysql.columns.secondary_engine_attribute
669     */
670     if (migrate_table(
671             "columns",
672             "INSERT INTO columns SELECT *, NULL, NULL FROM mysql.columns")) {
673       return true;
674     }
675   }
676 
677   /********************* Migration of mysql.events *********************/
678   /* Upgrade from 80013 or earlier. */
679   static_assert(dd::tables::Events::NUMBER_OF_FIELDS == 24,
680                 "SQL statements rely on a specific table definition");
681   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80014)) {
682     /*
683       SQL mode 'INVALID_DATES' was renamed to 'ALLOW_INVALID_DATES'.
684       Migrate the SQL mode as an integer.
685     */
686     if (migrate_table(
687             "events",
688             "INSERT INTO events SELECT  id, schema_id, name, definer, "
689             "  time_zone, definition, definition_utf8, execute_at, "
690             "  interval_value, interval_field, sql_mode+0, starts, ends, "
691             "  status, on_completion, created, last_altered, "
692             "  last_executed, comment, originator, client_collation_id, "
693             "  connection_collation_id, schema_collation_id, options "
694             "  FROM mysql.events")) {
695       return true;
696     }
697   }
698 
699   /********************* Migration of mysql.routines *********************/
700   /* Upgrade from 80013 or earlier. */
701   static_assert(dd::tables::Routines::NUMBER_OF_FIELDS == 29,
702                 "SQL statements rely on a specific table definition");
703   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80014)) {
704     /*
705       SQL mode 'INVALID_DATES' was renamed to 'ALLOW_INVALID_DATES'.
706       Migrate the SQL mode as an integer.
707     */
708     if (migrate_table(
709             "routines",
710             "INSERT INTO routines SELECT id, schema_id, name, type, "
711             "  result_data_type, result_data_type_utf8, result_is_zerofill, "
712             "  result_is_unsigned, result_char_length, "
713             "  result_numeric_precision, result_numeric_scale, "
714             "  result_datetime_precision, result_collation_id, "
715             "  definition, definition_utf8, parameter_str, is_deterministic, "
716             "  sql_data_access, security_type, definer, sql_mode+0, "
717             "  client_collation_id, connection_collation_id, "
718             "  schema_collation_id, created, last_altered, comment, options, "
719             "  external_language FROM mysql.routines")) {
720       return true;
721     }
722   }
723 
724   /********************* Migration of mysql.triggers *********************/
725   /* Upgrade from 80013 or earlier. */
726   static_assert(dd::tables::Triggers::NUMBER_OF_FIELDS == 17,
727                 "SQL statements rely on a specific table definition");
728   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80014)) {
729     /*
730       SQL mode 'INVALID_DATES' was renamed to 'ALLOW_INVALID_DATES'.
731       Migrate the SQL mode as an integer.
732     */
733     if (migrate_table(
734             "triggers",
735             "INSERT INTO triggers SELECT id, schema_id, name, event_type, "
736             "  table_id, action_timing, action_order, action_statement, "
737             "  action_statement_utf8, created, last_altered, sql_mode+0, "
738             "  definer, client_collation_id, connection_collation_id, "
739             "  schema_collation_id, options FROM mysql.triggers")) {
740       return true;
741     }
742   }
743 
744   /********************* Migration of mysql.schemata *********************/
745   /*
746     DD version 80016 adds a new column 'default_encryption' and
747     DD version 80017 adds a new column 'se_private_data' to the schemata table.
748     Handle them both during upgrade.
749   */
750   static_assert(dd::tables::Schemata::NUMBER_OF_FIELDS == 9,
751                 "SQL statements rely on a specific table definition");
752   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80016)) {
753     /*
754       Upgrade from 80014 and before.
755       Store 'NO' for new mysql.schemata.default_encryption column and
756       store NULL for new mysql.schemata.se_private_data column
757     */
758     if (migrate_table(
759             "schemata",
760             "INSERT INTO schemata SELECT *, 'NO', NULL FROM mysql.schemata")) {
761       return true;
762     }
763   } else if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80017)) {
764     /*
765       Upgrade from 80016.
766       Store NULL for new mysql.schemata.se_private_data column
767     */
768     if (migrate_table(
769             "schemata",
770             "INSERT INTO schemata SELECT *, NULL FROM mysql.schemata")) {
771       return true;
772     }
773   }
774 
775   /********************* Migration of mysql.indexes *********************/
776   /*
777     DD version 80020 adds new columns 'engine_attribute' and
778     'secondary_engine_atribute'
779   */
780   static_assert(dd::tables::Indexes::NUMBER_OF_FIELDS == 17,
781                 "SQL statements rely on a specific table definition");
782   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80021)) {
783     /*
784       Upgrade from 80020 and before.
785       Store NULL for new columns mysql.indexes.engine_attribute and
786       mysql.indexes.secondary_engine_attribute
787     */
788     if (migrate_table(
789             "indexes",
790             "INSERT INTO indexes SELECT *, NULL, NULL FROM mysql.indexes")) {
791       return true;
792     }
793   }
794 
795   /********************* Migration of mysql.tablespaces *********************/
796   /*
797     DD version 80020 adds new columns 'engine_attribute' and
798     'secondary_engine_atribute'
799   */
800   static_assert(dd::tables::Tablespaces::NUMBER_OF_FIELDS == 7,
801                 "SQL statements rely on a specific table definition");
802   if (is_dd_upgrade_from_before(bootstrap::DD_VERSION_80021)) {
803     /*
804       Upgrade from 80021 and before.
805       Store NULL for new columns mysql.tablespaces.engine_attribute and
806       mysql.tablespaces.secondary_engine_attribute
807     */
808     if (migrate_table("tablespaces",
809                       "INSERT INTO tablespaces SELECT *, NULL FROM "
810                       "mysql.tablespaces")) {
811       return true;
812     }
813   }
814 
815   /*
816     Default handling: Copy all meta data for the tables that have been
817     modified (i.e., all tables which are both in the remove- and create set),
818     unless they were migrated explicitly above.
819   */
820   for (std::set<String_type>::const_iterator it = create_set.begin();
821        it != create_set.end(); ++it) {
822     if (migrated_set.find(*it) == migrated_set.end() &&
823         remove_set.find(*it) != remove_set.end()) {
824       std::stringstream ss;
825       ss << "INSERT INTO " << (*it) << " SELECT * FROM "
826          << MYSQL_SCHEMA_NAME.str << "." << (*it);
827       if (dd::execute_query(thd, ss.str().c_str()))
828         return dd::end_transaction(thd, true);
829     }
830   }
831 
832   /*
833     Turn foreign key checks back on and commit explicitly.
834   */
835   if (dd::execute_query(thd, "SET FOREIGN_KEY_CHECKS= 1"))
836     return dd::end_transaction(thd, true);
837 
838   return false;
839 }
840 /* purecov: end */
841 
842 /*
843   Adjust the object ids to "move" tables between schemas by using DML.
844 
845   At this point, we have a set of old DD tables, in the 'remove_set', that
846   should be removed. These are a subset of the actual DD tables. And we
847   have a set of new DD tables, in the 'create_set', that should replace the
848   old ones. The tables in the 'create_set' are a subset of the target DD
849   tables.
850 
851   What we want to do is to move the tables in the 'remove_set' out of the
852   'mysql' schema and into a different schema with id 'actual_table_schema_id',
853   and then move the tables in the 'create_set' (which are in a schema with
854   id 'target_table_schema_id' and name 'target_table_schema_name') out of
855   that schema and into the 'mysql' schema.
856 
857   We could do this by 'RENAME TABLE' statements, but that would not be atomic
858   since the statements will be auto committing. So instead, we manipulate the
859   DD tables directly, and update the schema ids related to the relevant tables.
860   This is possible since the tables are stored in a general tablespace, and
861   moving them to a different schema will not affect the DDSE.
862 
863   The updates we need to do on the DD tables are the following:
864 
865   - For the tables in the 'remove_set' and the 'create_set', we must change
866     the schema id of the entry in the 'tables' table according to where we
867     want to move the tables.
868   - For the tables in the 'remove_set', we delete all foreign keys where the
869     table to be removed is a child.
870   - For the tables in the 'create_set', we change the schema id and name of
871     all foreign keys, where the table to be created is a child, from the
872     'target_table_schema_name' to that of the 'mysql' schema.
873 
874   See also additional comments in the code below.
875 
876   @param  create_set               Set of tables to be created.
877   @param  remove_set               Set of tables to be removed.
878   @param  mysql_schema_id          Id of the 'mysql' schema.
879   @param  target_table_schema_id   Id of the schema where the tables in the
880                                    create_set are located.
881   @param  target_table_schema_name Name of the schema where the tables in the
882                                    create_set are located.
883   @param  actual_table_schema_id   Id of the schema where the tables in the
884                                    remove_set will be moved.
885 
886   @returns false if success, true otherwise.
887 */
888 /* purecov: begin inspected */
update_object_ids(THD * thd,const std::set<String_type> & create_set,const std::set<String_type> & remove_set,Object_id mysql_schema_id,Object_id target_table_schema_id,const String_type & target_table_schema_name,Object_id actual_table_schema_id)889 bool update_object_ids(THD *thd, const std::set<String_type> &create_set,
890                        const std::set<String_type> &remove_set,
891                        Object_id mysql_schema_id,
892                        Object_id target_table_schema_id,
893                        const String_type &target_table_schema_name,
894                        Object_id actual_table_schema_id) {
895   if (dd::execute_query(thd, "SET FOREIGN_KEY_CHECKS= 0"))
896     return dd::end_transaction(thd, true);
897 
898   /*
899     If mysql.tables has been modified, do the change on the copy, otherwise
900     do the change on mysql.tables
901   */
902   String_type tables_table = tables::Tables::instance().name();
903   if (create_set.find(tables_table) != create_set.end()) {
904     tables_table = target_table_schema_name + String_type(".") + tables_table;
905   } else {
906     tables_table =
907         String_type(MYSQL_SCHEMA_NAME.str) + String_type(".") + tables_table;
908   }
909 
910   /*
911     For each actual table to be removed (i.e., modified or abandoned),
912     change tables.schema_id to the actual table schema id.
913   */
914   for (std::set<String_type>::const_iterator it = remove_set.begin();
915        it != remove_set.end(); ++it) {
916     std::stringstream ss;
917     ss << "UPDATE " << tables_table
918        << " SET schema_id= " << actual_table_schema_id
919        << " WHERE schema_id= " << mysql_schema_id << " AND name LIKE '" << (*it)
920        << "'";
921 
922     if (dd::execute_query(thd, ss.str().c_str()))
923       return dd::end_transaction(thd, true);
924   }
925 
926   /*
927     For each target table to be created (i.e., modified or added),
928     change tables.schema_id to the mysql schema id, and set the hidden
929     property according to the corresponding Object_table.
930   */
931   for (std::set<String_type>::const_iterator it = create_set.begin();
932        it != create_set.end(); ++it) {
933     // Get the corresponding Object_table instance.
934     String_type hidden{""};
935     if (System_tables::instance()
936             ->find_table(MYSQL_SCHEMA_NAME.str, (*it))
937             ->is_hidden())
938       hidden = String_type(", hidden= 'System'");
939 
940     std::stringstream ss;
941     ss << "UPDATE " << tables_table << " SET schema_id= " << mysql_schema_id
942        << hidden << " WHERE schema_id= " << target_table_schema_id
943        << " AND name LIKE '" << (*it) << "'";
944 
945     if (dd::execute_query(thd, ss.str().c_str()))
946       return dd::end_transaction(thd, true);
947   }
948 
949   /*
950     If mysql.foreign_keys has been modified, do the change on the copy,
951     otherwise do the change on mysql.foreign_keys. And likewise, if
952     mysql.foreign_key_column_usage has been modified, do the change on
953     the copy, otherwise do the change on mysql.foreign_key_column_usage.
954   */
955   String_type foreign_keys_table = tables::Foreign_keys::instance().name();
956   ;
957   if (create_set.find(foreign_keys_table) != create_set.end()) {
958     foreign_keys_table =
959         target_table_schema_name + String_type(".") + foreign_keys_table;
960   } else {
961     foreign_keys_table = String_type(MYSQL_SCHEMA_NAME.str) + String_type(".") +
962                          foreign_keys_table;
963   }
964 
965   String_type foreign_key_column_usage_table =
966       tables::Foreign_key_column_usage::instance().name();
967   ;
968   if (create_set.find(foreign_key_column_usage_table) != create_set.end()) {
969     foreign_key_column_usage_table = target_table_schema_name +
970                                      String_type(".") +
971                                      foreign_key_column_usage_table;
972   } else {
973     foreign_key_column_usage_table = String_type(MYSQL_SCHEMA_NAME.str) +
974                                      String_type(".") +
975                                      foreign_key_column_usage_table;
976   }
977 
978   /*
979     For each actual (i.e., modified or abandoned) table to be removed,
980     remove the entries from the foreign_keys and foreign_key_column_usage
981     table. There is no point in trying to maintain the foreign keys since
982     the tables will be removed eventually anyway.
983   */
984   for (std::set<String_type>::const_iterator it = remove_set.begin();
985        it != remove_set.end(); ++it) {
986     std::stringstream ss;
987     ss << "DELETE FROM " << foreign_key_column_usage_table
988        << " WHERE foreign_key_id IN ("
989        << "  SELECT id FROM " << foreign_keys_table
990        << "   WHERE table_id= (SELECT id FROM " << tables_table
991        << "     WHERE name LIKE '" << (*it) << "' AND "
992        << "     schema_id= " << actual_table_schema_id << "))";
993     if (dd::execute_query(thd, ss.str().c_str()))
994 
995       return dd::end_transaction(thd, true);
996 
997     ss.str("");
998     ss.clear();
999     ss << "DELETE FROM " << foreign_keys_table
1000        << "   WHERE table_id= (SELECT id FROM " << tables_table
1001        << "     WHERE name LIKE '" << (*it) << "' AND "
1002        << "     schema_id= " << actual_table_schema_id << ")";
1003     if (dd::execute_query(thd, ss.str().c_str()))
1004       return dd::end_transaction(thd, true);
1005   }
1006 
1007   /*
1008     For each target (i.e., modified or added)  table to be moved, change
1009     foreign_keys.schema_id and foreign_keys.referenced_schema_name to the
1010     mysql schema id and name. For the created tables, the target schema id
1011     and name are reflected in the foreign_keys tables, so we don't need a
1012     subquery based on table names.
1013   */
1014   std::stringstream ss;
1015   ss << "UPDATE " << foreign_keys_table << " SET schema_id= " << mysql_schema_id
1016      << ", "
1017      << "     referenced_table_schema= '" << MYSQL_SCHEMA_NAME.str << "'"
1018      << " WHERE schema_id= " << target_table_schema_id
1019      << "       AND referenced_table_schema= '" << target_table_schema_name
1020      << "'";
1021 
1022   if (dd::execute_query(thd, ss.str().c_str()))
1023     return dd::end_transaction(thd, true);
1024 
1025   if (dd::execute_query(thd, "SET FOREIGN_KEY_CHECKS= 1"))
1026     return dd::end_transaction(thd, true);
1027 
1028   // Delay commit in the case of success, since we need to do this atomically.
1029   return false;
1030 }
1031 /* purecov: end */
1032 
1033 }  // namespace
1034 
1035 namespace upgrade {
1036 // Create the target tables for upgrade and migrate the meta data.
1037 /* purecov: begin inspected */
upgrade_tables(THD * thd)1038 bool upgrade_tables(THD *thd) {
1039   if (!bootstrap::DD_bootstrap_ctx::instance().is_dd_upgrade()) return false;
1040 
1041   /*
1042     Create the temporary schemas used for target and actual tables,
1043     and get hold of their ids.
1044   */
1045   Object_id mysql_schema_id = INVALID_OBJECT_ID;
1046   Object_id target_table_schema_id = INVALID_OBJECT_ID;
1047   Object_id actual_table_schema_id = INVALID_OBJECT_ID;
1048   String_type target_table_schema_name;
1049   if (create_temporary_schemas(thd, &mysql_schema_id, &target_table_schema_id,
1050                                &target_table_schema_name,
1051                                &actual_table_schema_id))
1052     return true;
1053 
1054   /*
1055     Establish the sets of table names to be removed and/or created.
1056   */
1057   std::set<String_type> remove_set = {};
1058   std::set<String_type> create_set = {};
1059   establish_table_name_sets(&create_set, &remove_set);
1060 
1061   /*
1062     Loop over all DD tables, and create the target tables. We may do version
1063     specific handling, but the default is to create the target table if it is
1064     different from the actual table (or if there is no corresponding actual
1065     table). The table creation is done by executing DDL statements that are
1066     auto committed.
1067   */
1068   if (create_tables(thd, &create_set)) return true;
1069 
1070   /*
1071     Loop over all DD tables and migrate the meta data. We may do version
1072     specific handling, but the default is to just copy all meta data from
1073     the actual to the target table, assuming the number and type of columns
1074     are the same (e.g. if an index is added). The data migration is committed.
1075 
1076     We achieve data migration in two steps:
1077 
1078     1) update_meta_data() is used to adjust metadata in source DD tables in
1079     mysql schema. This is done by mostly executing UPDATE queries on
1080     them, but we do not migrate data to destination DD tables.
1081 
1082     2) migrate_meta_data() is used to adjust metadata in destination DD
1083     tables using UPDATE command and also migrate data to destination DD
1084     tables using INSERT command.
1085 
1086     Note that the changes done during migration of meta data are committed
1087     in next step at the end of 'atomic switch' described below.
1088   */
1089   if (update_meta_data(thd) || migrate_meta_data(thd, create_set, remove_set))
1090     return true;
1091 
1092   /*
1093     We are now ready to do the atomic switch of the actual and target DD
1094     tables. Thus, the next three steps must be done without intermediate
1095     commits. Note that in case of failure, rollback is done immediately.
1096     In case of success, no commit is done until at the very end of
1097     update_versions(). The switch is done as follows:
1098 
1099     - First, update the DD properties. Note that we must acquire the
1100       modified DD tables from the temporary target schema. This is done
1101       before the object ids are modified, because that step also may mess
1102       up object acquisition (if we change the schema id of a newly created
1103       table to that of the 'mysql' schema, and then try acquire(), we will
1104       get the table from the core registry in the storage adapter, and that
1105       is not what we want).
1106 
1107     - Then, update the object ids and schema names to simulate altering the
1108       schema of the modified tables. The changes are done on the 'tables',
1109       'foreign_keys' and 'foreign_key_column_usage' tables. If these tables
1110       are modified, the changes must be done on the corresponding new table
1111       in the target schema. If not, the change must be done on the actual
1112       table in the 'mysql' schema.
1113 
1114     - Finally, update the version numbers and commit. In update_versions(),
1115       the atomic switch will either be committed.
1116   */
1117   if (update_properties(thd, &create_set, &remove_set,
1118                         target_table_schema_name) ||
1119       update_object_ids(thd, create_set, remove_set, mysql_schema_id,
1120                         target_table_schema_id, target_table_schema_name,
1121                         actual_table_schema_id) ||
1122       update_versions(thd, false))
1123     return true;
1124 
1125   LogErr(SYSTEM_LEVEL, ER_DD_UPGRADE_COMPLETED,
1126          bootstrap::DD_bootstrap_ctx::instance().get_actual_dd_version(),
1127          dd::DD_VERSION);
1128   log_sink_buffer_check_timeout();
1129   sysd::notify("STATUS=Data Dictionary upgrade complete\n");
1130 
1131   /*
1132     At this point, the DD upgrade is committed. Below, we will reset the
1133     DD cache and re-initialize based on 'mysql.dd_properties', hence,
1134     we will lose track of the fact that we have done a DD upgrade as part
1135     of this restart. Thus, we record this fact in the bootstrap context
1136     so we can check it e.g. when initializeing the information schema,
1137     where we need to regenerate the meta data if the underlying tables
1138     have changed.
1139   */
1140   bootstrap::DD_bootstrap_ctx::instance().set_dd_upgrade_done();
1141 
1142   /*
1143     Flush tables, reset the shared dictionary cache and the storage adapter.
1144     Start over DD bootstrap from the beginning.
1145   */
1146   if (dd::execute_query(thd, "FLUSH TABLES")) return true;
1147 
1148   dd::cache::Shared_dictionary_cache::instance()->reset(false);
1149 
1150   /*
1151     Reset the encryption attribute in object table def since we will now
1152     start over by creating the scaffolding, which expectes an unencrypted
1153     DD tablespace.
1154   */
1155   Object_table_definition_impl::set_dd_tablespace_encrypted(false);
1156 
1157   // Reset the DDSE local dictionary cache.
1158   handlerton *ddse = ha_resolve_by_legacy_type(thd, DB_TYPE_INNODB);
1159   if (ddse->dict_cache_reset == nullptr) return true;
1160 
1161   for (System_tables::Const_iterator it =
1162            System_tables::instance()->begin(System_tables::Types::CORE);
1163        it != System_tables::instance()->end();
1164        it = System_tables::instance()->next(it, System_tables::Types::CORE)) {
1165     ddse->dict_cache_reset(MYSQL_SCHEMA_NAME.str,
1166                            (*it)->entity()->name().c_str());
1167     ddse->dict_cache_reset(target_table_schema_name.c_str(),
1168                            (*it)->entity()->name().c_str());
1169   }
1170 
1171   /*
1172     We need to start over DD initialization. This is done by executing the
1173     first stages of the procedure followed at restart. Note that this
1174     will see and use the newly upgraded DD that was created above. Cleanup
1175     of the temporary schemas is done at the end of 'sync_meta_data()'.
1176   */
1177   bootstrap::DD_bootstrap_ctx::instance().set_stage(bootstrap::Stage::STARTED);
1178 
1179   store_predefined_tablespace_metadata(thd);
1180   if (create_dd_schema(thd) || initialize_dd_properties(thd) ||
1181       create_tables(thd, nullptr) || sync_meta_data(thd)) {
1182     return true;
1183   }
1184 
1185   bootstrap::DD_bootstrap_ctx::instance().set_stage(
1186       bootstrap::Stage::UPGRADED_TABLES);
1187 
1188   return false;
1189 }
1190 
1191 }  // namespace upgrade
1192 /* purecov: end */
1193 }  // namespace dd
1194