1 /* 2 Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 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 #ifndef NDB_DDL_TRANSACTION_CTX_H 26 #define NDB_DDL_TRANSACTION_CTX_H 27 28 #include <string> 29 #include <vector> 30 31 #include "sql/dd/string_type.h" 32 #include "storage/ndb/plugin/ndb_thd.h" 33 34 namespace dd { 35 typedef String_type sdi_t; 36 } 37 38 /* A class representing a DDL statement. */ 39 class Ndb_DDL_stmt { 40 public: 41 enum DDL_type { CREATE_TABLE, RENAME_TABLE, DROP_TABLE }; 42 43 private: 44 const DDL_type m_ddl_type; // DDL type 45 const std::vector<std::string> 46 m_info; // vector to store all the DDL information 47 bool m_stmt_distributed{false}; // Flag that has the distribution status 48 49 public: 50 template <typename... Args> Ndb_DDL_stmt(DDL_type ddl_type,Args...info)51 Ndb_DDL_stmt(DDL_type ddl_type, Args... info) 52 : m_ddl_type(ddl_type), m_info{info...} {} 53 get_info()54 const std::vector<std::string> &get_info() const { return m_info; } get_ddl_type()55 DDL_type get_ddl_type() const { return m_ddl_type; } mark_as_distributed()56 void mark_as_distributed() { m_stmt_distributed = true; } has_been_distributed()57 bool has_been_distributed() const { return m_stmt_distributed; } 58 }; 59 60 /* DDL Transaction context class to log the DDLs being executed. 61 62 A DDL can be executed by making a single request or mutliple requests to 63 the Storage Engine depending on the nature of the DDL. For example, a 64 CREATE TABLE query can be done in a single request to the SE but a ALTER 65 TABLE COPY would require more than a single request. These requests are 66 the statements sent to the SE for execution. Apart from these 67 statements, every DDL would also involve executing statements in InnoDB 68 SE, for updating the entries in DD and executing statements in binlog 69 handlers. A DDL transaction is a collection of all these statements. To 70 make such a transaction Atomic, the SQL Layer uses a 2PC commit protocol 71 derived from the Open/XA distributed transaction specifications. 72 73 In ndbcluster, due to the absence of support for temp tables, 74 maintaining a DDL transaction is not possible and we have to commit the 75 DDL statements then and there. To support Atomic DDLs with such a setup, 76 a logger that logs all the DDL statements executed in ndbcluster is 77 required. And if the SQL Layer asks for a rollback at the end of the 78 transaction, the schema changes can be undone by simply reversing the 79 statements. 80 81 This class implements such a logger to log all the statements that got 82 executed in ndbcluster as the part of a DDL transaction. It also provides 83 various methods that can be used to commit/rollback the changes when 84 requested by the SQL Layer at the end of the DDL transaction. */ 85 class Ndb_DDL_transaction_ctx { 86 private: 87 class THD *const m_thd; 88 89 /* A list to log all the DDL statements executed in ndbcluster. */ 90 std::vector<Ndb_DDL_stmt> m_executed_ddl_stmts; 91 92 /* If a participating engine in the DDL transaction is not atomic, then 93 the SQL Layer requests all the engines involved in the transaction to 94 commit immediately after every statement. Due to this, in an event of 95 failure, it also takes care of rolling back any statements that have been 96 already asked to commit. In such cases, ndbcluster should not rollback the 97 statements that have been asked to commit already by the SQL Layer. 98 An example of such query is running 'ALTER TABLE .. ENGINE MYISAM' on an 99 NDB table. 100 101 This variable tracks the position of the statement in m_executed_ddl_stmts 102 vector until which commit has been requested already by the SQL Layer. */ 103 unsigned int m_latest_committed_stmt{0}; 104 105 /* Original sdi of the table - to be used during rollback of rename */ 106 std::string m_original_sdi_for_rename; 107 108 /* Status of the ongoing DDL */ 109 enum DDL_STATUS { 110 DDL_EMPTY, 111 DDL_IN_PROGRESS, 112 DDL_COMMITED, 113 DDL_ROLLED_BACK 114 } m_ddl_status{DDL_EMPTY}; 115 116 /* @brief Create the Ndb_DDL_stmt objects and append them to the 117 executed_ddl_stmts list */ 118 template <typename... Args> log_ddl_stmt(Ndb_DDL_stmt::DDL_type ddl_op_type,Args...ddl_info)119 void log_ddl_stmt(Ndb_DDL_stmt::DDL_type ddl_op_type, Args... ddl_info) { 120 /* This is a new DDL transaction if there are no ddl stmts yet */ 121 bool first_stmt_in_trx = false; 122 if (m_ddl_status == DDL_EMPTY || m_ddl_status == DDL_COMMITED) { 123 /* If the DDL status is empty, this is the first stmt in the transaction. 124 125 If the DDL is already committed, it implies that the stmts so far were 126 committed and this is a new stmt. This happens when the SQL Layer is 127 calling commit on individual stmts rather than at the end of 128 transaction. We should treat all such stmts as mini transactions but 129 still maintain the log for the overall DDL transaction. 130 131 In both the cases, mark the DDL as in progress and mark this as the 132 first stmt.*/ 133 m_ddl_status = DDL_IN_PROGRESS; 134 first_stmt_in_trx = true; 135 } 136 137 /* Log them only if DDL is in progress */ 138 if (m_ddl_status == DDL_IN_PROGRESS) { 139 m_executed_ddl_stmts.emplace_back(ddl_op_type, ddl_info...); 140 141 /* Register ndbcluster as a part of the stmt. Additionally register 142 it as a part of the transaction if this is the first stmt. */ 143 ndb_thd_register_trans(m_thd, first_stmt_in_trx); 144 } 145 } 146 147 /* Methods to handle rollback of individual DDls */ 148 bool rollback_create_table(const Ndb_DDL_stmt &); 149 bool rollback_rename_table(const Ndb_DDL_stmt &); 150 151 /* Methods to handle updates during post_ddl phase */ 152 /* @brief Update the table object in DD after a rollback of RENAME table. 153 The rollback would have actually changed the version 154 of the NDB table. This method updates that in the DD. */ 155 bool post_ddl_hook_rename_table(const Ndb_DDL_stmt &ddl_stmt); 156 /* @brief Drop the table with the temporary name from NDB after 157 the COPY ALTER has been committed successfully. This drop is 158 delayed until after commit so that if required, ndbcluster can 159 rollback the changes made by the DDL. */ 160 bool post_ddl_hook_drop_temp_table(const Ndb_DDL_stmt &ddl_stmt); 161 162 /* @brief Update the table's id and version in DD. */ 163 bool update_table_id_and_version_in_DD(const char *schema_name, 164 const char *table_name, int object_id, 165 int object_version); 166 167 /* @brief Retrieve the RENAME stmt, which actually was the final phase 168 of the COPY ALTER. This statement would have renamed the new 169 table with the temporary name to a proper name and might 170 have distributed the changes to the other servers. */ 171 const Ndb_DDL_stmt *retrieve_copy_alter_final_rename_stmt(); 172 173 public: Ndb_DDL_transaction_ctx(class THD * thd)174 Ndb_DDL_transaction_ctx(class THD *thd) : m_thd(thd) {} 175 get_original_sdi_for_rename(dd::sdi_t & orig_sdi)176 void get_original_sdi_for_rename(dd::sdi_t &orig_sdi) const { 177 DBUG_ASSERT(!m_original_sdi_for_rename.empty()); 178 orig_sdi.assign(m_original_sdi_for_rename.c_str(), 179 m_original_sdi_for_rename.length()); 180 } 181 182 /* @brief Check if the current DDL execution has made any changes 183 to the Schema that has not been committed yet. 184 185 @return Returns true if ddl_status is in progress 186 false otherwise. */ has_uncommitted_schema_changes()187 bool has_uncommitted_schema_changes() const { 188 return (m_ddl_status == DDL_IN_PROGRESS); 189 } 190 191 /* Helper methods to log the DDL. */ 192 /* @brief Log a create table statement in DDL Context. 193 194 @param path_name Path name of the table. */ 195 void log_create_table(const std::string &path_name); 196 /* @brief Log a rename table statement in DDL Context. 197 198 @param old_db_name Old name of the table's database. 199 @param old_table_name Old name of the table. 200 @param new_db_name New name of the table's database. 201 @param new_table_name New name of the table. 202 @param from Old path name of the table. 203 @param to New path name of the table. 204 @param orig_sdi Original sdi of the table. */ 205 void log_rename_table(const std::string &old_db_name, 206 const std::string &old_table_name, 207 const std::string &new_db_name, 208 const std::string &new_table_name, 209 const std::string &from, const std::string &to, 210 const std::string &orig_sdi); 211 /* @brief Log a drop table(with temp name) statement in DDL Context. 212 213 @param path_name Path name of the table. */ 214 void log_drop_temp_table(const std::string &path_name); 215 216 /* @brief Mark the last DDL stmt as distributed */ mark_last_stmt_as_distributed()217 void mark_last_stmt_as_distributed() { 218 m_executed_ddl_stmts[m_executed_ddl_stmts.size() - 1].mark_as_distributed(); 219 } 220 221 /* @brief Commit the DDL transaction */ 222 void commit(); 223 224 /* @brief Rollback any changes done to the Schema during DDL execution. 225 Iterate the executed_ddl_stmts vector and rollback 226 all the changes in reverse. Also undo any schema change 227 distributed through schema distribution */ 228 bool rollback(); 229 230 /* @brief Check if the DDL is being rollbacked */ rollback_in_progress()231 bool rollback_in_progress() const { 232 return (m_ddl_status == DDL_ROLLED_BACK); 233 } 234 235 /* @brief Updates to be run during the post ddl phase. */ 236 bool run_post_ddl_hooks(); 237 }; 238 239 #endif /* NDB_DDL_TRANSACTION_CTX_H */ 240