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 // Implements the interface defined in
26 #include "storage/ndb/plugin/ndb_ddl_transaction_ctx.h"
27 
28 #include "sql/handler.h"
29 #include "sql/sql_class.h"
30 #include "sql/sql_lex.h"
31 #include "storage/ndb/plugin/ndb_dd.h"
32 #include "storage/ndb/plugin/ndb_dd_client.h"
33 #include "storage/ndb/plugin/ndb_ddl_definitions.h"
34 #include "storage/ndb/plugin/ndb_name_util.h"
35 #include "storage/ndb/plugin/ndb_schema_dist.h"
36 #include "storage/ndb/plugin/ndb_table_guard.h"
37 #include "storage/ndb/plugin/ndb_thd_ndb.h"
38 
log_create_table(const std::string & path_name)39 void Ndb_DDL_transaction_ctx::log_create_table(const std::string &path_name) {
40   log_ddl_stmt(Ndb_DDL_stmt::CREATE_TABLE, path_name);
41 }
42 
rollback_create_table(const Ndb_DDL_stmt & ddl_stmt)43 bool Ndb_DDL_transaction_ctx::rollback_create_table(
44     const Ndb_DDL_stmt &ddl_stmt) {
45   DBUG_TRACE;
46 
47   /* extract info from ddl_info */
48   const std::vector<std::string> &ddl_info = ddl_stmt.get_info();
49   DBUG_ASSERT(ddl_info.size() == 1);
50   const char *path_name = ddl_info[0].c_str();
51   char db_name[FN_HEADLEN];
52   char table_name[FN_HEADLEN];
53   ndb_set_dbname(path_name, db_name);
54   ndb_set_tabname(path_name, table_name);
55 
56   /* Prepare schema client for rollback if required */
57   Thd_ndb *thd_ndb = get_thd_ndb(m_thd);
58   Ndb_schema_dist_client schema_dist_client(m_thd);
59   bool schema_dist_prepared = false;
60   if (ddl_stmt.has_been_distributed()) {
61     /* The stmt was distributed.
62        So rollback should be distributed too.
63        Prepare the schema client */
64     schema_dist_prepared = schema_dist_client.prepare(db_name, table_name);
65     if (!schema_dist_prepared) {
66       /* Report the error and just drop it locally */
67       thd_ndb->push_warning(
68           "Failed to distribute rollback to connected servers.");
69     }
70   }
71 
72   DBUG_PRINT("info",
73              ("Rollback : Dropping table '%s.%s'", db_name, table_name));
74 
75   /* Drop the table created during this DDL execution */
76   Ndb *ndb = thd_ndb->ndb;
77   if (drop_table_impl(m_thd, ndb,
78                       schema_dist_prepared ? &schema_dist_client : nullptr,
79                       path_name, db_name, table_name)) {
80     thd_ndb->push_warning("Failed to rollback after CREATE TABLE failure.");
81     return false;
82   }
83 
84   return true;
85 }
86 
log_rename_table(const std::string & old_db_name,const std::string & old_table_name,const std::string & new_db_name,const std::string & new_table_name,const std::string & from,const std::string & to,const std::string & orig_sdi)87 void Ndb_DDL_transaction_ctx::log_rename_table(
88     const std::string &old_db_name, const std::string &old_table_name,
89     const std::string &new_db_name, const std::string &new_table_name,
90     const std::string &from, const std::string &to,
91     const std::string &orig_sdi) {
92   log_ddl_stmt(Ndb_DDL_stmt::RENAME_TABLE, old_db_name, old_table_name,
93                new_db_name, new_table_name, from, to, orig_sdi);
94 }
95 
rollback_rename_table(const Ndb_DDL_stmt & ddl_stmt)96 bool Ndb_DDL_transaction_ctx::rollback_rename_table(
97     const Ndb_DDL_stmt &ddl_stmt) {
98   DBUG_TRACE;
99 
100   /* extract info from ddl_info */
101   const std::vector<std::string> &ddl_info = ddl_stmt.get_info();
102   DBUG_ASSERT(ddl_info.size() == 7);
103   const char *old_db_name = ddl_info[0].c_str();
104   const char *old_table_name = ddl_info[1].c_str();
105   const char *new_db_name = ddl_info[2].c_str();
106   const char *new_table_name = ddl_info[3].c_str();
107   const char *from = ddl_info[4].c_str();
108   const char *to = ddl_info[5].c_str();
109   m_original_sdi_for_rename = ddl_info[6];
110 
111   DBUG_PRINT("info",
112              ("Rollback : Renaming table '%s.%s' to '%s.%s'", new_db_name,
113               new_table_name, old_db_name, old_table_name));
114 
115   /* Load the table from NDB */
116   Thd_ndb *thd_ndb = get_thd_ndb(m_thd);
117   Ndb *ndb = thd_ndb->ndb;
118   ndb->setDatabaseName(new_db_name);
119   Ndb_table_guard ndbtab_g(ndb->getDictionary(), new_table_name);
120   const NdbDictionary::Table *renamed_table;
121   if (!(renamed_table = ndbtab_g.get_table())) {
122     const NdbError err = ndb->getDictionary()->getNdbError();
123     thd_ndb->push_ndb_error_warning(err);
124     thd_ndb->push_warning("Failed to rename table during rollback.");
125     return false;
126   }
127 
128   /* Various parameters to send to rename_table_impl.
129      Deduct all these from the available information */
130   bool real_rename = false;
131   std::string real_rename_db_buff, real_rename_table_buff;
132   bool distribute_table_changes = false;
133   bool new_table_name_is_temp = ndb_name_is_temp(new_table_name);
134   bool old_table_name_is_temp = ndb_name_is_temp(old_table_name);
135 
136   /* Decide whether the events have to be dropped and/or created. The new_name
137      is the source and the old_name is the target. So, if the new_name is not
138      temp, we would have to drop the events and if the old_name is not temp,
139      we would have to create the events. */
140   const bool drop_events = !new_table_name_is_temp;
141   const bool create_events = !old_table_name_is_temp;
142 
143   /* Deduce the real rename parameter values. They are set only when a real
144      rename, during the actual DDL transaction, got distributed to the
145      participants. When these are set during rollback, they distribute the
146      rollback of the table rename to the participants. */
147   if (ddl_stmt.has_been_distributed() && !old_table_name_is_temp &&
148       !new_table_name_is_temp) {
149     /* This stmt was a simple RENAME and was distributed successfully. */
150     real_rename = true;
151     real_rename_db_buff = new_db_name;
152     real_rename_table_buff = new_table_name;
153     distribute_table_changes = true;
154   } else if (!old_table_name_is_temp && new_table_name_is_temp) {
155     /* This is the first rename of a COPY ALTER. It renamed the old table from
156        the original name to a temp name. We need to retrieve the last RENAME
157        of the ALTER to check if the ALTER involved renaming the table. */
158     const Ndb_DDL_stmt *ndb_final_rename_stmt =
159         retrieve_copy_alter_final_rename_stmt();
160     if (ndb_final_rename_stmt != nullptr) {
161       /* Found the final RENAME of the ALTER */
162       const std::vector<std::string> &final_rename_ddl_info =
163           ndb_final_rename_stmt->get_info();
164 
165       /* Extract info and use them to set the rename_table_impl parameters */
166       DBUG_ASSERT(final_rename_ddl_info.size() == 7);
167       std::string final_db_name = final_rename_ddl_info[2];
168       std::string final_table_name = final_rename_ddl_info[3];
169       if ((final_db_name.compare(old_db_name) != 0) ||
170           (final_table_name.compare(old_table_name) != 0)) {
171         /* The actual ALTER renamed the table. */
172         real_rename = true;
173         real_rename_db_buff = final_db_name;
174         real_rename_table_buff = final_table_name;
175       }
176     }
177     /* Always distribute this phase of ALTER during rollback - this is to
178        make sure that all the participant's DD gets updated with latest table
179        version after rollback. */
180     distribute_table_changes = true;
181   }
182 
183   const char *real_rename_db =
184       real_rename_db_buff.empty() ? nullptr : real_rename_db_buff.c_str();
185   const char *real_rename_table =
186       real_rename_table_buff.empty() ? nullptr : real_rename_table_buff.c_str();
187 
188   /* Prepare the schema client if required */
189   bool schema_dist_prepared = false;
190   Ndb_schema_dist_client schema_dist_client(m_thd);
191   if (distribute_table_changes) {
192     if (real_rename) {
193       /* This is also a rename. Prepare the schema client */
194       schema_dist_prepared = schema_dist_client.prepare_rename(
195           real_rename_db, real_rename_table, old_db_name, old_table_name);
196     } else {
197       /* Prepare the schema client for an ALTER */
198       schema_dist_prepared =
199           schema_dist_client.prepare(old_db_name, old_table_name);
200     }
201     if (!schema_dist_prepared) {
202       /* Report the error and carry on */
203       thd_ndb->push_warning(
204           "Failed to distribute rollback to connected servers.");
205     }
206   }
207 
208   /* Rename back the table.
209      The rename is done from new_name to old_name as this is a rollback. */
210   if (rename_table_impl(
211           m_thd, ndb, schema_dist_prepared ? &schema_dist_client : nullptr,
212           renamed_table,
213           nullptr,  // table_def
214           to, from, new_db_name, new_table_name, old_db_name, old_table_name,
215           real_rename, real_rename_db, real_rename_table, drop_events,
216           create_events, distribute_table_changes)) {
217     thd_ndb->push_warning("Failed to rollback rename table.");
218     return false;
219   }
220 
221   return true;
222 }
223 
update_table_id_and_version_in_DD(const char * schema_name,const char * table_name,int object_id,int object_version)224 bool Ndb_DDL_transaction_ctx::update_table_id_and_version_in_DD(
225     const char *schema_name, const char *table_name, int object_id,
226     int object_version) {
227   DBUG_TRACE;
228   Ndb_dd_client dd_client(m_thd);
229   Thd_ndb *thd_ndb = get_thd_ndb(m_thd);
230 
231   /* Lock the table exclusively */
232   if (!dd_client.mdl_locks_acquire_exclusive(schema_name, table_name)) {
233     thd_ndb->push_warning(
234         "Failed to acquire exclusive lock on table : '%s.%s' during rollback",
235         schema_name, table_name);
236     return false;
237   }
238 
239   /* Update the table with new object id and version */
240   if (!dd_client.set_object_id_and_version_in_table(
241           schema_name, table_name, object_id, object_version)) {
242     thd_ndb->push_warning(
243         "Failed to update id and version of table : '%s.%s' during rollback",
244         schema_name, table_name);
245     return false;
246   }
247 
248   /* commit the changes */
249   dd_client.commit();
250 
251   return true;
252 }
253 
post_ddl_hook_rename_table(const Ndb_DDL_stmt & ddl_stmt)254 bool Ndb_DDL_transaction_ctx::post_ddl_hook_rename_table(
255     const Ndb_DDL_stmt &ddl_stmt) {
256   DBUG_TRACE;
257   DBUG_ASSERT(m_ddl_status != DDL_IN_PROGRESS);
258 
259   if (m_ddl_status == DDL_COMMITED) {
260     /* DDL committed. Nothing to do */
261     return true;
262   }
263 
264   Thd_ndb *thd_ndb = get_thd_ndb(m_thd);
265   Ndb *ndb = thd_ndb->ndb;
266 
267   /* extract info from ddl_info */
268   const std::vector<std::string> &ddl_info = ddl_stmt.get_info();
269   const char *db_name = ddl_info[0].c_str();
270   const char *table_name = ddl_info[1].c_str();
271 
272   if (ndb_name_is_temp(table_name)) {
273     /* The target table was a temp table. No need to update id and version */
274     return true;
275   }
276 
277   ndb->setDatabaseName(db_name);
278 
279   /* Load the table from NDB */
280   Ndb_table_guard ndbtab_g(ndb->getDictionary(), table_name);
281   const NdbDictionary::Table *ndb_table;
282   if (!(ndb_table = ndbtab_g.get_table())) {
283     const NdbError err = ndb->getDictionary()->getNdbError();
284     thd_ndb->push_ndb_error_warning(err);
285     thd_ndb->push_warning("Unable to load table during rollback");
286     return false;
287   }
288 
289   /* Update table id and version */
290   if (!update_table_id_and_version_in_DD(db_name, table_name,
291                                          ndb_table->getObjectId(),
292                                          ndb_table->getObjectVersion())) {
293     return false;
294   }
295 
296   return true;
297 }
298 
log_drop_temp_table(const std::string & path_name)299 void Ndb_DDL_transaction_ctx::log_drop_temp_table(
300     const std::string &path_name) {
301   log_ddl_stmt(Ndb_DDL_stmt::DROP_TABLE, path_name);
302 }
303 
post_ddl_hook_drop_temp_table(const Ndb_DDL_stmt & ddl_stmt)304 bool Ndb_DDL_transaction_ctx::post_ddl_hook_drop_temp_table(
305     const Ndb_DDL_stmt &ddl_stmt) {
306   DBUG_TRACE;
307   DBUG_ASSERT(m_ddl_status != DDL_IN_PROGRESS);
308 
309   if (m_ddl_status == DDL_ROLLED_BACK) {
310     /* DDL was rollbacked. Nothing to do */
311     return true;
312   }
313 
314   Thd_ndb *thd_ndb = get_thd_ndb(m_thd);
315   Ndb *ndb = thd_ndb->ndb;
316 
317   /* extract info from ddl_info */
318   const std::vector<std::string> &ddl_info = ddl_stmt.get_info();
319   DBUG_ASSERT(ddl_info.size() == 1);
320   const char *path_name = ddl_info[0].c_str();
321   char db_name[FN_HEADLEN];
322   char table_name[FN_HEADLEN];
323   ndb_set_dbname(path_name, db_name);
324   ndb_set_tabname(path_name, table_name);
325 
326   /* Verify that the table is a temp table. */
327   if (!ndb_name_is_temp(table_name)) {
328     DBUG_ASSERT(false);
329     return false;
330   }
331 
332   DBUG_PRINT("info", ("Dropping table '%s.%s'", db_name, table_name));
333 
334   /* Finally drop the temp table as the DDL has been committed  */
335   if (drop_table_impl(m_thd, ndb, nullptr, path_name, db_name, table_name) !=
336       0) {
337     thd_ndb->push_warning("Failed to drop a temp table.");
338     return false;
339   }
340 
341   /* The table has been dropped successfully. Only thing remaining is handling
342      the special case where `ALTER TABLE .. ENGINE` is requested. So exit and
343      return if this DDL is not a ALTER query. */
344   if (thd_sql_command(m_thd) != SQLCOM_ALTER_TABLE) {
345     return true;
346   }
347 
348   /* Detect the special case which occurs when a table is altered to another
349      engine. In such case the altered table has been renamed to a temporary
350      name in the same engine before copying the data to the new table in the
351      other engine. When copying is successful, the original table
352      (which now has a temporary name) is asked to be dropped. Since this table
353      has a temporary name, the actual drop was done only after a successful
354      commit as a part of this function. Now that the drop is done, inform the
355      participants that the original table is no longer in NDB. Unfortunately
356      the original table name is not available in this function, but it's
357      possible to look that up via THD. */
358   const HA_CREATE_INFO *create_info = m_thd->lex->create_info;
359   if ((create_info->used_fields & HA_CREATE_USED_ENGINE) &&
360       create_info->db_type != ndbcluster_hton) {
361     DBUG_PRINT("info", ("ALTER to different engine = '%s' detected",
362                         ha_resolve_storage_engine_name(create_info->db_type)));
363 
364     const char *orig_db_name = m_thd->lex->select_lex->table_list.first->db;
365     const char *orig_table_name =
366         m_thd->lex->select_lex->table_list.first->table_name;
367     DBUG_PRINT("info",
368                ("original table name: '%s.%s'", orig_db_name, orig_table_name));
369 
370     Ndb_schema_dist_client schema_dist_client(m_thd);
371 
372     /* Prepare the schema client */
373     if (!schema_dist_client.prepare(orig_db_name, orig_table_name)) {
374       thd_ndb->push_warning("Failed to distribute 'DROP TABLE '%s.%s''",
375                             orig_db_name, orig_table_name);
376       return false;
377     }
378 
379     /* Do a drop in all connected servers */
380     if (!schema_dist_client.drop_table(orig_db_name, orig_table_name, 0, 0)) {
381       thd_ndb->push_warning("Failed to distribute 'DROP TABLE '%s.%s''",
382                             orig_db_name, orig_table_name);
383       return false;
384     }
385   }
386 
387   return true;
388 }
389 
390 const Ndb_DDL_stmt *
retrieve_copy_alter_final_rename_stmt()391 Ndb_DDL_transaction_ctx::retrieve_copy_alter_final_rename_stmt() {
392   DBUG_TRACE;
393   /* Loop all the logged stmts and find the copy alter info */
394   for (auto it = m_executed_ddl_stmts.rbegin();
395        it != m_executed_ddl_stmts.rend(); ++it) {
396     Ndb_DDL_stmt &ddl_stmt = *it;
397     switch (ddl_stmt.get_ddl_type()) {
398       case Ndb_DDL_stmt::RENAME_TABLE: {
399         const std::vector<std::string> &ddl_info = ddl_stmt.get_info();
400         const char *old_table_name = ddl_info[1].c_str();
401         const char *new_table_name = ddl_info[3].c_str();
402         if (ndb_name_is_temp(old_table_name) &&
403             !ndb_name_is_temp(new_table_name)) {
404           /* This was a rename from #sql -> proper_name.
405              This was the final rename of a COPY ALTER. */
406           return &ddl_stmt;
407         }
408       } break;
409       default:
410         break;
411     }
412   }
413   return nullptr;
414 }
415 
commit()416 void Ndb_DDL_transaction_ctx::commit() {
417   DBUG_TRACE;
418   DBUG_ASSERT(m_ddl_status == DDL_IN_PROGRESS);
419   /* The schema changes would have been already committed internally to the NDB
420      by the respective handler functions that made the change. So just update
421      the status of the DDL and make note of the latest stmt on which the
422      Server has requested a commit. */
423   m_ddl_status = DDL_COMMITED;
424   m_latest_committed_stmt = m_executed_ddl_stmts.size();
425 }
426 
rollback()427 bool Ndb_DDL_transaction_ctx::rollback() {
428   DBUG_TRACE;
429   DBUG_ASSERT(m_ddl_status == DDL_IN_PROGRESS);
430 
431   bool result = true;
432   m_ddl_status = DDL_ROLLED_BACK;
433   /* Rollback all the uncommitted DDL statements in reverse order */
434   for (auto it = m_executed_ddl_stmts.rbegin();
435        it != (m_executed_ddl_stmts.rend() - m_latest_committed_stmt); ++it) {
436     const Ndb_DDL_stmt &ddl_stmt = *it;
437     switch (ddl_stmt.get_ddl_type()) {
438       case Ndb_DDL_stmt::CREATE_TABLE:
439         result &= rollback_create_table(ddl_stmt);
440         break;
441       case Ndb_DDL_stmt::RENAME_TABLE:
442         result &= rollback_rename_table(ddl_stmt);
443         break;
444       case Ndb_DDL_stmt::DROP_TABLE:
445         /* Nothing to do as the table has not been dropped yet */
446         break;
447       default:
448         result = false;
449         DBUG_ASSERT(false);
450         break;
451     }
452   }
453   return result;
454 }
455 
run_post_ddl_hooks()456 bool Ndb_DDL_transaction_ctx::run_post_ddl_hooks() {
457   DBUG_TRACE;
458   if (m_ddl_status == DDL_EMPTY) {
459     /* Nothing to run */
460     return true;
461   }
462   DBUG_ASSERT(m_ddl_status == DDL_COMMITED || m_ddl_status == DDL_ROLLED_BACK);
463   bool result = true;
464   for (auto it = m_executed_ddl_stmts.begin(); it != m_executed_ddl_stmts.end();
465        ++it) {
466     const Ndb_DDL_stmt &ddl_stmt = *it;
467     switch (ddl_stmt.get_ddl_type()) {
468       case Ndb_DDL_stmt::RENAME_TABLE:
469         result &= post_ddl_hook_rename_table(ddl_stmt);
470         break;
471       case Ndb_DDL_stmt::DROP_TABLE:
472         result &= post_ddl_hook_drop_temp_table(ddl_stmt);
473         break;
474       default:
475         break;
476     }
477   }
478   return result;
479 }
480