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