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