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