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