1 /* Copyright (c) 2010, 2013, 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_parse.h" // check_access,
24 // check_merge_table_access
25 // check_one_table_access
26 #include "sql_table.h" // mysql_alter_table, etc.
27 #include "sql_cmd.h" // Sql_cmd
28 #include "sql_alter.h" // Sql_cmd_alter_table
29 #include "sql_partition.h" // struct partition_info, etc.
30 #include "sql_base.h" // open_and_lock_tables, etc
31 #include "debug_sync.h" // DEBUG_SYNC
32 #include "sql_truncate.h" // mysql_truncate_table,
33 // Sql_cmd_truncate_table
34 #include "sql_admin.h" // Sql_cmd_Analyze/Check/.._table
35 #include "sql_partition_admin.h" // Alter_table_*_partition
36 #ifdef WITH_PARTITION_STORAGE_ENGINE
37 #include "ha_partition.h" // ha_partition
38 #endif
39 #include "sql_base.h" // open_and_lock_tables
40
41 #ifndef WITH_PARTITION_STORAGE_ENGINE
42
execute(THD *)43 bool Sql_cmd_partition_unsupported::execute(THD *)
44 {
45 DBUG_ENTER("Sql_cmd_partition_unsupported::execute");
46 /* error, partitioning support not compiled in... */
47 my_error(ER_FEATURE_DISABLED, MYF(0), "partitioning",
48 "--with-plugin-partition");
49 DBUG_RETURN(TRUE);
50 }
51
52 #else
53
execute(THD * thd)54 bool Sql_cmd_alter_table_exchange_partition::execute(THD *thd)
55 {
56 /* Moved from mysql_execute_command */
57 LEX *lex= thd->lex;
58 /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
59 SELECT_LEX *select_lex= &lex->select_lex;
60 /* first table of first SELECT_LEX */
61 TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first;
62 /*
63 Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
64 so we have to use a copy of this structure to make execution
65 prepared statement- safe. A shallow copy is enough as no memory
66 referenced from this structure will be modified.
67 @todo move these into constructor...
68 */
69 HA_CREATE_INFO create_info(lex->create_info);
70 Alter_info alter_info(lex->alter_info, thd->mem_root);
71 ulong priv_needed= ALTER_ACL | DROP_ACL | INSERT_ACL | CREATE_ACL;
72
73 DBUG_ENTER("Sql_cmd_alter_table_exchange_partition::execute");
74
75 if (thd->is_fatal_error) /* out of memory creating a copy of alter_info */
76 DBUG_RETURN(TRUE);
77
78 /* Must be set in the parser */
79 DBUG_ASSERT(select_lex->db);
80 /* also check the table to be exchanged with the partition */
81 DBUG_ASSERT(alter_info.flags & Alter_info::ALTER_EXCHANGE_PARTITION);
82
83 if (check_access(thd, priv_needed, first_table->db,
84 &first_table->grant.privilege,
85 &first_table->grant.m_internal,
86 0, 0) ||
87 check_access(thd, priv_needed, first_table->next_local->db,
88 &first_table->next_local->grant.privilege,
89 &first_table->next_local->grant.m_internal,
90 0, 0))
91 DBUG_RETURN(TRUE);
92
93 if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
94 DBUG_RETURN(TRUE);
95
96 /* Not allowed with EXCHANGE PARTITION */
97 DBUG_ASSERT(!create_info.data_file_name && !create_info.index_file_name);
98 WSREP_TO_ISOLATION_BEGIN_WRTCHK(NULL, NULL, first_table);
99
100 thd->enable_slow_log= opt_log_slow_admin_statements;
101 DBUG_RETURN(exchange_partition(thd, first_table, &alter_info));
102 #ifdef WITH_WSREP
103 error:
104 /* handle errors in TO_ISOLATION here */
105 DBUG_RETURN(true);
106 #endif /* WITH_WSREP */
107 }
108
109
110 /**
111 @brief Checks that the tables will be able to be used for EXCHANGE PARTITION.
112 @param table Non partitioned table.
113 @param part_table Partitioned table.
114
115 @retval FALSE if OK, otherwise error is reported and TRUE is returned.
116 */
check_exchange_partition(TABLE * table,TABLE * part_table)117 static bool check_exchange_partition(TABLE *table, TABLE *part_table)
118 {
119 DBUG_ENTER("check_exchange_partition");
120
121 /* Both tables must exist */
122 if (!part_table || !table)
123 {
124 my_error(ER_CHECK_NO_SUCH_TABLE, MYF(0));
125 DBUG_RETURN(TRUE);
126 }
127
128 /* The first table must be partitioned, and the second must not */
129 if (!part_table->part_info)
130 {
131 my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
132 DBUG_RETURN(TRUE);
133 }
134 if (table->part_info)
135 {
136 my_error(ER_PARTITION_EXCHANGE_PART_TABLE, MYF(0),
137 table->s->table_name.str);
138 DBUG_RETURN(TRUE);
139 }
140
141 if (part_table->file->ht != partition_hton)
142 {
143 /*
144 Only allowed on partitioned tables throught the generic ha_partition
145 handler, i.e not yet for native partitioning (NDB).
146 */
147 my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
148 DBUG_RETURN(TRUE);
149 }
150
151 if (table->file->ht != part_table->part_info->default_engine_type)
152 {
153 my_error(ER_MIX_HANDLER_ERROR, MYF(0));
154 DBUG_RETURN(TRUE);
155 }
156
157 /* Verify that table is not tmp table, partitioned tables cannot be tmp. */
158 if (table->s->tmp_table != NO_TMP_TABLE)
159 {
160 my_error(ER_PARTITION_EXCHANGE_TEMP_TABLE, MYF(0),
161 table->s->table_name.str);
162 DBUG_RETURN(TRUE);
163 }
164
165 /* The table cannot have foreign keys constraints or be referenced */
166 if(!table->file->can_switch_engines())
167 {
168 my_error(ER_PARTITION_EXCHANGE_FOREIGN_KEY, MYF(0),
169 table->s->table_name.str);
170 DBUG_RETURN(TRUE);
171 }
172 DBUG_RETURN(FALSE);
173 }
174
175
176 /**
177 @brief Compare table structure/options between a non partitioned table
178 and a specific partition of a partitioned table.
179
180 @param thd Thread object.
181 @param table Non partitioned table.
182 @param part_table Partitioned table.
183 @param part_elem Partition element to use for partition specific compare.
184 */
compare_table_with_partition(THD * thd,TABLE * table,TABLE * part_table,partition_element * part_elem)185 static bool compare_table_with_partition(THD *thd, TABLE *table,
186 TABLE *part_table,
187 partition_element *part_elem)
188 {
189 HA_CREATE_INFO table_create_info, part_create_info;
190 Alter_info part_alter_info;
191 Alter_table_ctx part_alter_ctx; // Not used
192 DBUG_ENTER("compare_table_with_partition");
193
194 bool metadata_equal= false;
195 memset(&part_create_info, 0, sizeof(HA_CREATE_INFO));
196 memset(&table_create_info, 0, sizeof(HA_CREATE_INFO));
197
198 update_create_info_from_table(&table_create_info, table);
199 /* get the current auto_increment value */
200 table->file->update_create_info(&table_create_info);
201 /* mark all columns used, since they are used when preparing the new table */
202 part_table->use_all_columns();
203 table->use_all_columns();
204 if (mysql_prepare_alter_table(thd, part_table, &part_create_info,
205 &part_alter_info, &part_alter_ctx))
206 {
207 my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0));
208 DBUG_RETURN(TRUE);
209 }
210 /* db_type is not set in prepare_alter_table */
211 part_create_info.db_type= part_table->part_info->default_engine_type;
212 /*
213 Since we exchange the partition with the table, allow exchanging
214 auto_increment value as well.
215 */
216 part_create_info.auto_increment_value=
217 table_create_info.auto_increment_value;
218
219 /* Check compatible row_types and set create_info accordingly. */
220 {
221 enum row_type part_row_type= part_table->file->get_row_type();
222 enum row_type table_row_type= table->file->get_row_type();
223 if (part_row_type != table_row_type)
224 {
225 my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
226 "ROW_FORMAT");
227 DBUG_RETURN(true);
228 }
229 part_create_info.row_type= table->s->row_type;
230 }
231
232 /*
233 NOTE: ha_blackhole does not support check_if_compatible_data,
234 so this always fail for blackhole tables.
235 ha_myisam compares pointers to verify that DATA/INDEX DIRECTORY is
236 the same, so any table using data/index_file_name will fail.
237 */
238 if (mysql_compare_tables(table, &part_alter_info, &part_create_info,
239 &metadata_equal))
240 {
241 my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0));
242 DBUG_RETURN(TRUE);
243 }
244
245 DEBUG_SYNC(thd, "swap_partition_after_compare_tables");
246 if (!metadata_equal)
247 {
248 my_error(ER_TABLES_DIFFERENT_METADATA, MYF(0));
249 DBUG_RETURN(TRUE);
250 }
251 DBUG_ASSERT(table->s->db_create_options ==
252 part_table->s->db_create_options);
253 DBUG_ASSERT(table->s->db_options_in_use ==
254 part_table->s->db_options_in_use);
255
256 if (table_create_info.avg_row_length != part_create_info.avg_row_length)
257 {
258 my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
259 "AVG_ROW_LENGTH");
260 DBUG_RETURN(TRUE);
261 }
262
263 if (table_create_info.table_options != part_create_info.table_options)
264 {
265 my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
266 "TABLE OPTION");
267 DBUG_RETURN(TRUE);
268 }
269
270 if (table->s->table_charset != part_table->s->table_charset)
271 {
272 my_error(ER_PARTITION_EXCHANGE_DIFFERENT_OPTION, MYF(0),
273 "CHARACTER SET");
274 DBUG_RETURN(TRUE);
275 }
276
277 /*
278 NOTE: We do not support update of frm-file, i.e. change
279 max/min_rows, data/index_file_name etc.
280 The workaround is to use REORGANIZE PARTITION to rewrite
281 the frm file and then use EXCHANGE PARTITION when they are the same.
282 */
283 if (compare_partition_options(&table_create_info, part_elem))
284 DBUG_RETURN(TRUE);
285
286 DBUG_RETURN(FALSE);
287 }
288
289
290 /**
291 @brief Exchange partition/table with ddl log.
292
293 @details How to handle a crash in the middle of the rename (break on error):
294 1) register in ddl_log that we are going to exchange swap_table with part.
295 2) do the first rename (swap_table -> tmp-name) and sync the ddl_log.
296 3) do the second rename (part -> swap_table) and sync the ddl_log.
297 4) do the last rename (tmp-name -> part).
298 5) mark the entry done.
299
300 Recover by:
301 5) is done, All completed. Nothing to recover.
302 4) is done see 3). (No mark or sync in the ddl_log...)
303 3) is done -> try rename part -> tmp-name (ignore failure) goto 2).
304 2) is done -> try rename swap_table -> part (ignore failure) goto 1).
305 1) is done -> try rename tmp-name -> swap_table (ignore failure).
306 before 1) Nothing to recover...
307
308 @param thd Thread handle
309 @param name name of table/partition 1 (to be exchanged with 2)
310 @param from_name name of table/partition 2 (to be exchanged with 1)
311 @param tmp_name temporary name to use while exchaning
312 @param ht handlerton of the table/partitions
313
314 @return Operation status
315 @retval TRUE Error
316 @retval FALSE Success
317
318 @note ha_heap always succeeds in rename (since it is created upon usage).
319 This is OK when to recover from a crash since all heap are empty and the
320 recover is done early in the startup of the server (right before
321 read_init_file which can populate the tables).
322
323 And if no crash we can trust the syncs in the ddl_log.
324
325 What about if the rename is put into a background thread? That will cause
326 corruption and is avoided by the exlusive metadata lock.
327 */
exchange_name_with_ddl_log(THD * thd,const char * name,const char * from_name,const char * tmp_name,handlerton * ht)328 static bool exchange_name_with_ddl_log(THD *thd,
329 const char *name,
330 const char *from_name,
331 const char *tmp_name,
332 handlerton *ht)
333 {
334 DDL_LOG_ENTRY exchange_entry;
335 DDL_LOG_MEMORY_ENTRY *log_entry= NULL;
336 DDL_LOG_MEMORY_ENTRY *exec_log_entry= NULL;
337 bool error= TRUE;
338 bool error_set= FALSE;
339 handler *file= NULL;
340 DBUG_ENTER("exchange_name_with_ddl_log");
341
342 if (!(file= get_new_handler(NULL, thd->mem_root, ht)))
343 {
344 mem_alloc_error(sizeof(handler));
345 DBUG_RETURN(TRUE);
346 }
347
348 /* prepare the action entry */
349 exchange_entry.entry_type= DDL_LOG_ENTRY_CODE;
350 exchange_entry.action_type= DDL_LOG_EXCHANGE_ACTION;
351 exchange_entry.next_entry= 0;
352 exchange_entry.name= name;
353 exchange_entry.from_name= from_name;
354 exchange_entry.tmp_name= tmp_name;
355 exchange_entry.handler_name= ha_resolve_storage_engine_name(ht);
356 exchange_entry.phase= EXCH_PHASE_NAME_TO_TEMP;
357
358 mysql_mutex_lock(&LOCK_gdl);
359 /*
360 write to the ddl log what to do by:
361 1) write the action entry (i.e. which names to be exchanged)
362 2) write the execution entry with a link to the action entry
363 */
364 DBUG_EXECUTE_IF("exchange_partition_fail_1", goto err_no_action_written;);
365 DBUG_EXECUTE_IF("exchange_partition_abort_1", DBUG_SUICIDE(););
366 if (write_ddl_log_entry(&exchange_entry, &log_entry))
367 goto err_no_action_written;
368
369 DBUG_EXECUTE_IF("exchange_partition_fail_2", goto err_no_execute_written;);
370 DBUG_EXECUTE_IF("exchange_partition_abort_2", DBUG_SUICIDE(););
371 if (write_execute_ddl_log_entry(log_entry->entry_pos, FALSE, &exec_log_entry))
372 goto err_no_execute_written;
373 /* ddl_log is written and synced */
374
375 mysql_mutex_unlock(&LOCK_gdl);
376 /*
377 Execute the name exchange.
378 Do one rename, increase the phase, update the action entry and sync.
379 In case of errors in the ddl_log we must fail and let the ddl_log try
380 to revert the changes, since otherwise it could revert the command after
381 we sent OK to the client.
382 */
383 /* call rename table from table to tmp-name */
384 DBUG_EXECUTE_IF("exchange_partition_fail_3",
385 my_error(ER_ERROR_ON_RENAME, MYF(0),
386 name, tmp_name, 0, "n/a");
387 error_set= TRUE;
388 goto err_rename;);
389 DBUG_EXECUTE_IF("exchange_partition_abort_3", DBUG_SUICIDE(););
390 if (file->ha_rename_table(name, tmp_name))
391 {
392 char errbuf[MYSYS_STRERROR_SIZE];
393 my_error(ER_ERROR_ON_RENAME, MYF(0), name, tmp_name,
394 my_errno, my_strerror(errbuf, sizeof(errbuf), my_errno));
395 error_set= TRUE;
396 goto err_rename;
397 }
398 DBUG_EXECUTE_IF("exchange_partition_fail_4", goto err_rename;);
399 DBUG_EXECUTE_IF("exchange_partition_abort_4", DBUG_SUICIDE(););
400 if (deactivate_ddl_log_entry(log_entry->entry_pos))
401 goto err_rename;
402
403 /* call rename table from partition to table */
404 DBUG_EXECUTE_IF("exchange_partition_fail_5",
405 my_error(ER_ERROR_ON_RENAME, MYF(0),
406 from_name, name, 0, "n/a");
407 error_set= TRUE;
408 goto err_rename;);
409 DBUG_EXECUTE_IF("exchange_partition_abort_5", DBUG_SUICIDE(););
410 if (file->ha_rename_table(from_name, name))
411 {
412 char errbuf[MYSYS_STRERROR_SIZE];
413 my_error(ER_ERROR_ON_RENAME, MYF(0), from_name, name,
414 my_errno, my_strerror(errbuf, sizeof(errbuf), my_errno));
415 error_set= TRUE;
416 goto err_rename;
417 }
418 DBUG_EXECUTE_IF("exchange_partition_fail_6", goto err_rename;);
419 DBUG_EXECUTE_IF("exchange_partition_abort_6", DBUG_SUICIDE(););
420 if (deactivate_ddl_log_entry(log_entry->entry_pos))
421 goto err_rename;
422
423 /* call rename table from tmp-nam to partition */
424 DBUG_EXECUTE_IF("exchange_partition_fail_7",
425 my_error(ER_ERROR_ON_RENAME, MYF(0),
426 tmp_name, from_name, 0, "n/a");
427 error_set= TRUE;
428 goto err_rename;);
429 DBUG_EXECUTE_IF("exchange_partition_abort_7", DBUG_SUICIDE(););
430 if (file->ha_rename_table(tmp_name, from_name))
431 {
432 char errbuf[MYSYS_STRERROR_SIZE];
433 my_error(ER_ERROR_ON_RENAME, MYF(0), tmp_name, from_name,
434 my_errno, my_strerror(errbuf, sizeof(errbuf), my_errno));
435 error_set= TRUE;
436 goto err_rename;
437 }
438 DBUG_EXECUTE_IF("exchange_partition_fail_8", goto err_rename;);
439 DBUG_EXECUTE_IF("exchange_partition_abort_8", DBUG_SUICIDE(););
440 if (deactivate_ddl_log_entry(log_entry->entry_pos))
441 goto err_rename;
442
443 /* The exchange is complete and ddl_log is deactivated */
444 DBUG_EXECUTE_IF("exchange_partition_fail_9", goto err_rename;);
445 DBUG_EXECUTE_IF("exchange_partition_abort_9", DBUG_SUICIDE(););
446 /* all OK */
447 error= FALSE;
448 delete file;
449 DBUG_RETURN(error);
450 err_rename:
451 /*
452 Nothing to do if any of these commands fails :( the commands itselfs
453 will log to the error log about the failures...
454 */
455 /* execute the ddl log entry to revert the renames */
456 (void) execute_ddl_log_entry(current_thd, log_entry->entry_pos);
457 mysql_mutex_lock(&LOCK_gdl);
458 /* mark the execute log entry done */
459 (void) write_execute_ddl_log_entry(0, TRUE, &exec_log_entry);
460 /* release the execute log entry */
461 (void) release_ddl_log_memory_entry(exec_log_entry);
462 err_no_execute_written:
463 /* release the action log entry */
464 (void) release_ddl_log_memory_entry(log_entry);
465 err_no_action_written:
466 mysql_mutex_unlock(&LOCK_gdl);
467 delete file;
468 if (!error_set)
469 my_error(ER_DDL_LOG_ERROR, MYF(0));
470 DBUG_RETURN(error);
471 }
472
473
474 /**
475 @brief Swap places between a partition and a table.
476
477 @details Verify that the tables are compatible (same engine, definition etc),
478 verify that all rows in the table will fit in the partition,
479 if all OK, rename table to tmp name, rename partition to table
480 and finally rename tmp name to partition.
481
482 1) Take upgradable mdl, open tables and then lock them (inited in parse)
483 2) Verify that metadata matches
484 3) verify data
485 4) Upgrade to exclusive mdl for both tables
486 5) Rename table <-> partition
487 6) Rely on close_thread_tables to release mdl and table locks
488
489 @param thd Thread handle
490 @param table_list Table where the partition exists as first table,
491 Table to swap with the partition as second table
492 @param alter_info Contains partition name to swap
493
494 @note This is a DDL operation so triggers will not be used.
495 */
496 bool Sql_cmd_alter_table_exchange_partition::
exchange_partition(THD * thd,TABLE_LIST * table_list,Alter_info * alter_info)497 exchange_partition(THD *thd, TABLE_LIST *table_list, Alter_info *alter_info)
498 {
499 TABLE *part_table, *swap_table;
500 TABLE_LIST *swap_table_list;
501 handlerton *table_hton;
502 partition_element *part_elem;
503 char *partition_name;
504 char temp_name[FN_REFLEN+1];
505 char part_file_name[FN_REFLEN+1];
506 char swap_file_name[FN_REFLEN+1];
507 char temp_file_name[FN_REFLEN+1];
508 uint swap_part_id;
509 uint part_file_name_len;
510 Alter_table_prelocking_strategy alter_prelocking_strategy;
511 MDL_ticket *swap_table_mdl_ticket= NULL;
512 MDL_ticket *part_table_mdl_ticket= NULL;
513 uint table_counter;
514 bool error= TRUE;
515 DBUG_ENTER("mysql_exchange_partition");
516 DBUG_ASSERT(alter_info->flags & Alter_info::ALTER_EXCHANGE_PARTITION);
517
518 /* Don't allow to exchange with log table */
519 swap_table_list= table_list->next_local;
520 if (check_if_log_table(swap_table_list->db_length, swap_table_list->db,
521 swap_table_list->table_name_length,
522 swap_table_list->table_name, 0))
523 {
524 my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table");
525 DBUG_RETURN(TRUE);
526 }
527
528 /*
529 Currently no MDL lock that allows both read and write and is upgradeable
530 to exclusive, so leave the lock type to TL_WRITE_ALLOW_READ also on the
531 partitioned table.
532
533 TODO: add MDL lock that allows both read and write and is upgradable to
534 exclusive lock. This would allow to continue using the partitioned table
535 also with update/insert/delete while the verification of the swap table
536 is running.
537 */
538
539 /*
540 NOTE: It is not possible to exchange a crashed partition/table since
541 we need some info from the engine, which we can only access after open,
542 to be able to verify the structure/metadata.
543 */
544 table_list->mdl_request.set_type(MDL_SHARED_NO_WRITE);
545 if (open_tables(thd, &table_list, &table_counter, 0,
546 &alter_prelocking_strategy))
547 DBUG_RETURN(true);
548
549 part_table= table_list->table;
550 swap_table= swap_table_list->table;
551
552 if (check_exchange_partition(swap_table, part_table))
553 DBUG_RETURN(TRUE);
554
555 /* set lock pruning on first table */
556 partition_name= alter_info->partition_names.head();
557 if (table_list->table->part_info->
558 set_named_partition_bitmap(partition_name, strlen(partition_name)))
559 DBUG_RETURN(true);
560
561 if (lock_tables(thd, table_list, table_counter, 0))
562 DBUG_RETURN(true);
563
564
565 table_hton= swap_table->file->ht;
566
567 THD_STAGE_INFO(thd, stage_verifying_table);
568
569 /* Will append the partition name later in part_info->get_part_elem() */
570 part_file_name_len= build_table_filename(part_file_name,
571 sizeof(part_file_name),
572 table_list->db,
573 table_list->table_name,
574 "", 0);
575 build_table_filename(swap_file_name,
576 sizeof(swap_file_name),
577 swap_table_list->db,
578 swap_table_list->table_name,
579 "", 0);
580 /* create a unique temp name #sqlx-nnnn_nnnn, x for eXchange */
581 my_snprintf(temp_name, sizeof(temp_name), "%sx-%lx_%lx",
582 tmp_file_prefix, current_pid, thd->thread_id);
583 if (lower_case_table_names)
584 my_casedn_str(files_charset_info, temp_name);
585 build_table_filename(temp_file_name, sizeof(temp_file_name),
586 table_list->next_local->db,
587 temp_name, "", FN_IS_TMP);
588
589 if (!(part_elem= part_table->part_info->get_part_elem(partition_name,
590 part_file_name +
591 part_file_name_len,
592 &swap_part_id)))
593 {
594 my_error(ER_UNKNOWN_PARTITION, MYF(0), partition_name,
595 part_table->alias);
596 DBUG_RETURN(TRUE);
597 }
598
599 if (swap_part_id == NOT_A_PARTITION_ID)
600 {
601 DBUG_ASSERT(part_table->part_info->is_sub_partitioned());
602 my_error(ER_PARTITION_INSTEAD_OF_SUBPARTITION, MYF(0));
603 DBUG_RETURN(TRUE);
604 }
605
606 if (compare_table_with_partition(thd, swap_table, part_table, part_elem))
607 DBUG_RETURN(TRUE);
608
609 /* Table and partition has same structure/options, OK to exchange */
610
611 thd_proc_info(thd, "verifying data with partition");
612
613 if (verify_data_with_partition(swap_table, part_table, swap_part_id))
614 DBUG_RETURN(TRUE);
615
616 /*
617 Get exclusive mdl lock on both tables, alway the non partitioned table
618 first. Remember the tickets for downgrading locks later.
619 */
620 swap_table_mdl_ticket= swap_table->mdl_ticket;
621 part_table_mdl_ticket= part_table->mdl_ticket;
622
623 /*
624 No need to set used_partitions to only propagate
625 HA_EXTRA_PREPARE_FOR_RENAME to one part since no built in engine uses
626 that flag. And the action would probably be to force close all other
627 instances which is what we are doing any way.
628 */
629 if (wait_while_table_is_used(thd, swap_table, HA_EXTRA_PREPARE_FOR_RENAME) ||
630 wait_while_table_is_used(thd, part_table, HA_EXTRA_PREPARE_FOR_RENAME))
631 goto err;
632
633 DEBUG_SYNC(thd, "swap_partition_after_wait");
634
635 close_all_tables_for_name(thd, swap_table->s, false, NULL);
636 close_all_tables_for_name(thd, part_table->s, false, NULL);
637
638 DEBUG_SYNC(thd, "swap_partition_before_rename");
639
640 if (exchange_name_with_ddl_log(thd, swap_file_name, part_file_name,
641 temp_file_name, table_hton))
642 goto err;
643
644 /*
645 Reopen tables under LOCK TABLES. Ignore the return value for now. It's
646 better to keep master/slave in consistent state. Alternative would be to
647 try to revert the exchange operation and issue error.
648 */
649 (void) thd->locked_tables_list.reopen_tables(thd);
650
651 if ((error= write_bin_log(thd, TRUE, thd->query(), thd->query_length())))
652 {
653 /*
654 The error is reported in write_bin_log().
655 We try to revert to make it easier to keep the master/slave in sync.
656 */
657 (void) exchange_name_with_ddl_log(thd, part_file_name, swap_file_name,
658 temp_file_name, table_hton);
659 }
660
661 err:
662 if (thd->locked_tables_mode)
663 {
664 if (swap_table_mdl_ticket)
665 swap_table_mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
666 if (part_table_mdl_ticket)
667 part_table_mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
668 }
669
670 if (!error)
671 my_ok(thd);
672
673 // For query cache
674 table_list->table= NULL;
675 table_list->next_local->table= NULL;
676 query_cache_invalidate3(thd, table_list, FALSE);
677
678 DBUG_RETURN(error);
679 }
680
681
execute(THD * thd)682 bool Sql_cmd_alter_table_analyze_partition::execute(THD *thd)
683 {
684 bool res;
685 DBUG_ENTER("Sql_cmd_alter_table_analyze_partition::execute");
686
687 /*
688 Flag that it is an ALTER command which administrates partitions, used
689 by ha_partition
690 */
691 thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
692
693 res= Sql_cmd_analyze_table::execute(thd);
694
695 DBUG_RETURN(res);
696 }
697
698
execute(THD * thd)699 bool Sql_cmd_alter_table_check_partition::execute(THD *thd)
700 {
701 bool res;
702 DBUG_ENTER("Sql_cmd_alter_table_check_partition::execute");
703
704 /*
705 Flag that it is an ALTER command which administrates partitions, used
706 by ha_partition
707 */
708 thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
709
710 res= Sql_cmd_check_table::execute(thd);
711
712 DBUG_RETURN(res);
713 }
714
715
execute(THD * thd)716 bool Sql_cmd_alter_table_optimize_partition::execute(THD *thd)
717 {
718 bool res;
719 DBUG_ENTER("Alter_table_optimize_partition_statement::execute");
720
721 /*
722 Flag that it is an ALTER command which administrates partitions, used
723 by ha_partition
724 */
725 thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
726
727 res= Sql_cmd_optimize_table::execute(thd);
728
729 DBUG_RETURN(res);
730 }
731
732
execute(THD * thd)733 bool Sql_cmd_alter_table_repair_partition::execute(THD *thd)
734 {
735 bool res;
736 DBUG_ENTER("Sql_cmd_alter_table_repair_partition::execute");
737
738 /*
739 Flag that it is an ALTER command which administrates partitions, used
740 by ha_partition
741 */
742 thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION;
743
744 res= Sql_cmd_repair_table::execute(thd);
745
746 DBUG_RETURN(res);
747 }
748
749
execute(THD * thd)750 bool Sql_cmd_alter_table_truncate_partition::execute(THD *thd)
751 {
752 int error;
753 ha_partition *partition;
754 ulong timeout= thd->variables.lock_wait_timeout;
755 TABLE_LIST *first_table= thd->lex->select_lex.table_list.first;
756 Alter_info *alter_info= &thd->lex->alter_info;
757 uint table_counter, i;
758 List<String> partition_names_list;
759 bool binlog_stmt;
760 DBUG_ENTER("Sql_cmd_alter_table_truncate_partition::execute");
761
762 /*
763 Flag that it is an ALTER command which administrates partitions, used
764 by ha_partition.
765 */
766 thd->lex->alter_info.flags|= Alter_info::ALTER_ADMIN_PARTITION |
767 Alter_info::ALTER_TRUNCATE_PARTITION;
768
769 /* Fix the lock types (not the same as ordinary ALTER TABLE). */
770 first_table->lock_type= TL_WRITE;
771 first_table->mdl_request.set_type(MDL_EXCLUSIVE);
772
773 /*
774 Check table permissions and open it with a exclusive lock.
775 Ensure it is a partitioned table and finally, upcast the
776 handler and invoke the partition truncate method. Lastly,
777 write the statement to the binary log if necessary.
778 */
779
780 if (check_one_table_access(thd, DROP_ACL, first_table))
781 DBUG_RETURN(TRUE);
782
783 #ifdef WITH_WSREP
784 TABLE *find_temporary_table(THD *thd, const TABLE_LIST *tl);
785
786 if (WSREP(thd) && (!thd->is_current_stmt_binlog_format_row() ||
787 !find_temporary_table(thd, first_table)) &&
788 wsrep_to_isolation_begin(
789 thd, first_table->db, first_table->table_name, NULL)
790 )
791 {
792 WSREP_WARN("ALTER TABLE isolation failure");
793 DBUG_RETURN(TRUE);
794 }
795 #endif /* WITH_WSREP */
796 if (open_tables(thd, &first_table, &table_counter, 0))
797 DBUG_RETURN(true);
798
799 /*
800 TODO: Add support for TRUNCATE PARTITION for NDB and other
801 engines supporting native partitioning.
802 */
803
804 if (!first_table->table || first_table->view ||
805 first_table->table->s->db_type() != partition_hton)
806 {
807 my_error(ER_PARTITION_MGMT_ON_NONPARTITIONED, MYF(0));
808 DBUG_RETURN(TRUE);
809 }
810
811
812 /*
813 Prune all, but named partitions,
814 to avoid excessive calls to external_lock().
815 */
816 List_iterator<char> partition_names_it(alter_info->partition_names);
817 uint num_names= alter_info->partition_names.elements;
818 for (i= 0; i < num_names; i++)
819 {
820 char *partition_name= partition_names_it++;
821 String *str_partition_name= new (thd->mem_root)
822 String(partition_name, system_charset_info);
823 if (!str_partition_name)
824 DBUG_RETURN(true);
825 partition_names_list.push_back(str_partition_name);
826 }
827 first_table->partition_names= &partition_names_list;
828 if (first_table->table->part_info->set_partition_bitmaps(first_table))
829 DBUG_RETURN(true);
830
831 if (lock_tables(thd, first_table, table_counter, 0))
832 DBUG_RETURN(true);
833
834 /*
835 Under locked table modes this might still not be an exclusive
836 lock. Hence, upgrade the lock since the handler truncate method
837 mandates an exclusive metadata lock.
838 */
839 MDL_ticket *ticket= first_table->table->mdl_ticket;
840 if (thd->mdl_context.upgrade_shared_lock(ticket, MDL_EXCLUSIVE, timeout))
841 DBUG_RETURN(TRUE);
842
843 tdc_remove_table(thd, TDC_RT_REMOVE_NOT_OWN, first_table->db,
844 first_table->table_name, FALSE);
845
846 partition= (ha_partition*) first_table->table->file;
847 /* Invoke the handler method responsible for truncating the partition. */
848 if ((error= partition->truncate_partition(alter_info, &binlog_stmt)))
849 partition->print_error(error, MYF(0));
850
851 /*
852 All effects of a truncate operation are committed even if the
853 operation fails. Thus, the query must be written to the binary
854 log. The exception is a unimplemented truncate method or failure
855 before any call to handler::truncate() is done.
856 Also, it is logged in statement format, regardless of the binlog format.
857 */
858 if (error != HA_ERR_WRONG_COMMAND && binlog_stmt)
859 error|= write_bin_log(thd, !error, thd->query(), thd->query_length());
860
861 /*
862 A locked table ticket was upgraded to a exclusive lock. After the
863 the query has been written to the binary log, downgrade the lock
864 to a shared one.
865 */
866 if (thd->locked_tables_mode)
867 ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
868
869 if (! error)
870 my_ok(thd);
871
872 // Invalidate query cache
873 DBUG_ASSERT(!first_table->next_local);
874 query_cache_invalidate3(thd, first_table, FALSE);
875
876 DBUG_RETURN(error);
877 }
878
879 #endif /* WITH_PARTITION_STORAGE_ENGINE */
880