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