mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 19:06:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			728 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			728 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
 | |
|    Copyright (c) 2016, 2020, MariaDB Corporation
 | |
| 
 | |
|    This program is free software; you can redistribute it and/or modify
 | |
|    it under the terms of the GNU General Public License as published by
 | |
|    the Free Software Foundation; version 2 of the License.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU General Public License
 | |
|    along with this program; if not, write to the Free Software
 | |
|    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335  USA */
 | |
| 
 | |
| #include "mariadb.h"
 | |
| #include "sql_parse.h"                       // check_access
 | |
| #include "sql_table.h"                       // mysql_alter_table,
 | |
|                                              // mysql_exchange_partition
 | |
| #include "sql_statistics.h"                  // delete_statistics_for_column
 | |
| #include "sql_alter.h"
 | |
| #include "rpl_mi.h"
 | |
| #include "slave.h"
 | |
| #include "debug_sync.h"
 | |
| #include "wsrep_mysqld.h"
 | |
| 
 | |
| Alter_info::Alter_info(const Alter_info &rhs, MEM_ROOT *mem_root)
 | |
|   :drop_list(rhs.drop_list, mem_root),
 | |
|   alter_list(rhs.alter_list, mem_root),
 | |
|   key_list(rhs.key_list, mem_root),
 | |
|   alter_rename_key_list(rhs.alter_rename_key_list, mem_root),
 | |
|   create_list(rhs.create_list, mem_root),
 | |
|   alter_index_ignorability_list(rhs.alter_index_ignorability_list, mem_root),
 | |
|   check_constraint_list(rhs.check_constraint_list, mem_root),
 | |
|   flags(rhs.flags), partition_flags(rhs.partition_flags),
 | |
|   keys_onoff(rhs.keys_onoff),
 | |
|   original_table(0),
 | |
|   partition_names(rhs.partition_names, mem_root),
 | |
|   num_parts(rhs.num_parts),
 | |
|   requested_algorithm(rhs.requested_algorithm),
 | |
|   requested_lock(rhs.requested_lock)
 | |
| {
 | |
|   /*
 | |
|     Make deep copies of used objects.
 | |
|     This is not a fully deep copy - clone() implementations
 | |
|     of Alter_drop, Alter_column, Key, foreign_key, Key_part_spec
 | |
|     do not copy string constants. At the same length the only
 | |
|     reason we make a copy currently is that ALTER/CREATE TABLE
 | |
|     code changes input Alter_info definitions, but string
 | |
|     constants never change.
 | |
|   */
 | |
|   list_copy_and_replace_each_value(drop_list, mem_root);
 | |
|   list_copy_and_replace_each_value(alter_list, mem_root);
 | |
|   list_copy_and_replace_each_value(key_list, mem_root);
 | |
|   list_copy_and_replace_each_value(alter_rename_key_list, mem_root);
 | |
|   list_copy_and_replace_each_value(create_list, mem_root);
 | |
|   /* partition_names are not deeply copied currently */
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Alter_info::set_requested_algorithm(const LEX_CSTRING *str)
 | |
| {
 | |
|   // To avoid adding new keywords to the grammar, we match strings here.
 | |
|   if (lex_string_eq(str, STRING_WITH_LEN("INPLACE")))
 | |
|     requested_algorithm= ALTER_TABLE_ALGORITHM_INPLACE;
 | |
|   else if (lex_string_eq(str, STRING_WITH_LEN("COPY")))
 | |
|     requested_algorithm= ALTER_TABLE_ALGORITHM_COPY;
 | |
|   else if (lex_string_eq(str, STRING_WITH_LEN("DEFAULT")))
 | |
|     requested_algorithm= ALTER_TABLE_ALGORITHM_DEFAULT;
 | |
|   else if (lex_string_eq(str, STRING_WITH_LEN("NOCOPY")))
 | |
|     requested_algorithm= ALTER_TABLE_ALGORITHM_NOCOPY;
 | |
|   else if (lex_string_eq(str, STRING_WITH_LEN("INSTANT")))
 | |
|     requested_algorithm= ALTER_TABLE_ALGORITHM_INSTANT;
 | |
|   else
 | |
|     return true;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void Alter_info::set_requested_algorithm(enum_alter_table_algorithm algo_val)
 | |
| {
 | |
|   requested_algorithm= algo_val;
 | |
| }
 | |
| 
 | |
| bool Alter_info::set_requested_lock(const LEX_CSTRING *str)
 | |
| {
 | |
|   // To avoid adding new keywords to the grammar, we match strings here.
 | |
|   if (lex_string_eq(str, STRING_WITH_LEN("NONE")))
 | |
|     requested_lock= ALTER_TABLE_LOCK_NONE;
 | |
|   else if (lex_string_eq(str, STRING_WITH_LEN("SHARED")))
 | |
|     requested_lock= ALTER_TABLE_LOCK_SHARED;
 | |
|   else if (lex_string_eq(str, STRING_WITH_LEN("EXCLUSIVE")))
 | |
|     requested_lock= ALTER_TABLE_LOCK_EXCLUSIVE;
 | |
|   else if (lex_string_eq(str, STRING_WITH_LEN("DEFAULT")))
 | |
|     requested_lock= ALTER_TABLE_LOCK_DEFAULT;
 | |
|   else
 | |
|     return true;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| const char* Alter_info::algorithm_clause(THD *thd) const
 | |
| {
 | |
|   switch (algorithm(thd)) {
 | |
|   case ALTER_TABLE_ALGORITHM_INPLACE:
 | |
|     return "ALGORITHM=INPLACE";
 | |
|   case ALTER_TABLE_ALGORITHM_COPY:
 | |
|     return "ALGORITHM=COPY";
 | |
|   case ALTER_TABLE_ALGORITHM_NONE:
 | |
|     DBUG_ASSERT(0);
 | |
|     /* Fall through */
 | |
|   case ALTER_TABLE_ALGORITHM_DEFAULT:
 | |
|     return "ALGORITHM=DEFAULT";
 | |
|   case ALTER_TABLE_ALGORITHM_NOCOPY:
 | |
|     return "ALGORITHM=NOCOPY";
 | |
|   case ALTER_TABLE_ALGORITHM_INSTANT:
 | |
|     return "ALGORITHM=INSTANT";
 | |
|   }
 | |
| 
 | |
|   return NULL; /* purecov: begin deadcode */
 | |
| }
 | |
| 
 | |
| const char* Alter_info::lock() const
 | |
| {
 | |
|   switch (requested_lock) {
 | |
|   case ALTER_TABLE_LOCK_SHARED:
 | |
|     return "LOCK=SHARED";
 | |
|   case ALTER_TABLE_LOCK_NONE:
 | |
|     return "LOCK=NONE";
 | |
|   case ALTER_TABLE_LOCK_DEFAULT:
 | |
|     return "LOCK=DEFAULT";
 | |
|   case ALTER_TABLE_LOCK_EXCLUSIVE:
 | |
|     return "LOCK=EXCLUSIVE";
 | |
|   }
 | |
|   return NULL; /* purecov: begin deadcode */
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Alter_info::supports_algorithm(THD *thd,
 | |
|                                     const Alter_inplace_info *ha_alter_info)
 | |
| {
 | |
|   switch (ha_alter_info->inplace_supported) {
 | |
|   case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
 | |
|   case HA_ALTER_INPLACE_SHARED_LOCK:
 | |
|   case HA_ALTER_INPLACE_NO_LOCK:
 | |
|   case HA_ALTER_INPLACE_INSTANT:
 | |
|      return false;
 | |
|   case HA_ALTER_INPLACE_COPY_NO_LOCK:
 | |
|   case HA_ALTER_INPLACE_COPY_LOCK:
 | |
|     if (algorithm(thd) >= Alter_info::ALTER_TABLE_ALGORITHM_NOCOPY)
 | |
|     {
 | |
|       ha_alter_info->report_unsupported_error(algorithm_clause(thd),
 | |
|                                               "ALGORITHM=INPLACE");
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   case HA_ALTER_INPLACE_NOCOPY_NO_LOCK:
 | |
|   case HA_ALTER_INPLACE_NOCOPY_LOCK:
 | |
|     if (algorithm(thd) == Alter_info::ALTER_TABLE_ALGORITHM_INSTANT)
 | |
|     {
 | |
|       ha_alter_info->report_unsupported_error("ALGORITHM=INSTANT",
 | |
|                                               "ALGORITHM=NOCOPY");
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   case HA_ALTER_INPLACE_NOT_SUPPORTED:
 | |
|     if (algorithm(thd) >= Alter_info::ALTER_TABLE_ALGORITHM_INPLACE)
 | |
|     {
 | |
|       ha_alter_info->report_unsupported_error(algorithm_clause(thd),
 | |
| 					      "ALGORITHM=COPY");
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   case HA_ALTER_ERROR:
 | |
|     return true;
 | |
|   }
 | |
|   /* purecov: begin deadcode */
 | |
|   DBUG_ASSERT(0);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Alter_info::supports_lock(THD *thd,
 | |
|                                const Alter_inplace_info *ha_alter_info)
 | |
| {
 | |
|   switch (ha_alter_info->inplace_supported) {
 | |
|   case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
 | |
|     // If SHARED lock and no particular algorithm was requested, use COPY.
 | |
|     if (requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED &&
 | |
|         algorithm(thd) == Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT &&
 | |
|         thd->variables.alter_algorithm ==
 | |
|                 Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT)
 | |
|          return false;
 | |
| 
 | |
|     if (requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED ||
 | |
|         requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
 | |
|     {
 | |
|       ha_alter_info->report_unsupported_error(lock(), "LOCK=EXCLUSIVE");
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   case HA_ALTER_INPLACE_NO_LOCK:
 | |
|   case HA_ALTER_INPLACE_INSTANT:
 | |
|   case HA_ALTER_INPLACE_COPY_NO_LOCK:
 | |
|   case HA_ALTER_INPLACE_NOCOPY_NO_LOCK:
 | |
|     return false;
 | |
|   case HA_ALTER_INPLACE_COPY_LOCK:
 | |
|   case HA_ALTER_INPLACE_NOCOPY_LOCK:
 | |
|   case HA_ALTER_INPLACE_NOT_SUPPORTED:
 | |
|   case HA_ALTER_INPLACE_SHARED_LOCK:
 | |
|     if (requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
 | |
|     {
 | |
|       ha_alter_info->report_unsupported_error("LOCK=NONE", "LOCK=SHARED");
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   case HA_ALTER_ERROR:
 | |
|     return true;
 | |
|   }
 | |
|   /* purecov: begin deadcode */
 | |
|   DBUG_ASSERT(0);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool Alter_info::vers_prohibited(THD *thd) const
 | |
| {
 | |
|   if (thd->slave_thread ||
 | |
|       thd->variables.vers_alter_history != VERS_ALTER_HISTORY_ERROR)
 | |
|   {
 | |
|     return false;
 | |
|   }
 | |
|   if (flags & (
 | |
|     ALTER_PARSER_ADD_COLUMN |
 | |
|     ALTER_PARSER_DROP_COLUMN |
 | |
|     ALTER_CHANGE_COLUMN |
 | |
|     ALTER_COLUMN_ORDER))
 | |
|   {
 | |
|     return true;
 | |
|   }
 | |
|   if (flags & ALTER_ADD_INDEX)
 | |
|   {
 | |
|     List_iterator_fast<Key> key_it(const_cast<List<Key> &>(key_list));
 | |
|     Key *key;
 | |
|     while ((key= key_it++))
 | |
|     {
 | |
|       if (key->type == Key::PRIMARY || key->type == Key::UNIQUE)
 | |
|         return true;
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| Alter_info::enum_alter_table_algorithm
 | |
| Alter_info::algorithm(const THD *thd) const
 | |
| {
 | |
|   if (requested_algorithm == ALTER_TABLE_ALGORITHM_NONE)
 | |
|    return (Alter_info::enum_alter_table_algorithm) thd->variables.alter_algorithm;
 | |
|   return requested_algorithm;
 | |
| }
 | |
| 
 | |
| 
 | |
| uint Alter_info::check_vcol_field(Item_field *item) const
 | |
| {
 | |
|   /*
 | |
|     vcol->flags are modified in-place, so we'll need to reset them
 | |
|     if ALTER fails for any reason
 | |
|   */
 | |
|   if (item->field && !item->field->table->needs_reopen())
 | |
|     item->field->table->mark_table_for_reopen();
 | |
| 
 | |
|   if (!item->field &&
 | |
|       ((item->db_name.length && !db.streq(item->db_name)) ||
 | |
|        (item->table_name.length && !table_name.streq(item->table_name))))
 | |
|   {
 | |
|     char *ptr= (char*)current_thd->alloc(item->db_name.length +
 | |
|                                          item->table_name.length +
 | |
|                                          item->field_name.length + 3);
 | |
|     strxmov(ptr, safe_str(item->db_name.str), item->db_name.length ? "." : "",
 | |
|             item->table_name.str, ".", item->field_name.str, NullS);
 | |
|     item->field_name.str= ptr;
 | |
|     return VCOL_IMPOSSIBLE;
 | |
|   }
 | |
|   for (Key &k: key_list)
 | |
|   {
 | |
|     if (k.type != Key::FOREIGN_KEY)
 | |
|       continue;
 | |
|     Foreign_key *fk= (Foreign_key*) &k;
 | |
|     if (fk->update_opt < FK_OPTION_CASCADE &&
 | |
|         fk->delete_opt < FK_OPTION_SET_NULL)
 | |
|       continue;
 | |
|     for (Key_part_spec& kp: fk->columns)
 | |
|     {
 | |
|       if (item->field_name.streq(kp.field_name))
 | |
|         return VCOL_NON_DETERMINISTIC;
 | |
|     }
 | |
|   }
 | |
|   for (Create_field &cf: create_list)
 | |
|   {
 | |
|     if (item->field_name.streq(cf.field_name))
 | |
|       return cf.vcol_info ? cf.vcol_info->flags : 0;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Alter_info::collect_renamed_fields(THD *thd)
 | |
| {
 | |
|   List_iterator_fast<Create_field> new_field_it;
 | |
|   Create_field *new_field;
 | |
|   DBUG_ENTER("Alter_info::collect_renamed_fields");
 | |
| 
 | |
|   new_field_it.init(create_list);
 | |
|   while ((new_field= new_field_it++))
 | |
|   {
 | |
|     Field *field= new_field->field;
 | |
| 
 | |
|     if (new_field->field &&
 | |
|         cmp(&field->field_name, &new_field->field_name))
 | |
|     {
 | |
|       field->flags|= FIELD_IS_RENAMED;
 | |
|       if (add_stat_rename_field(field,
 | |
|                                 &new_field->field_name,
 | |
|                                 thd->mem_root))
 | |
|         DBUG_RETURN(true);
 | |
| 
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Delete duplicate index found during mysql_prepare_create_table()
 | |
| 
 | |
|   Notes:
 | |
|     - In case of temporary generated foreign keys, the key_name may not
 | |
|       be set!  These keys are ignored.
 | |
| */
 | |
| 
 | |
| bool Alter_info::add_stat_drop_index(THD *thd, const LEX_CSTRING *key_name)
 | |
| {
 | |
|   if (original_table && key_name->length)       // If from alter table
 | |
|   {
 | |
|     KEY *key_info= original_table->key_info;
 | |
|     for (uint i= 0; i < original_table->s->keys; i++, key_info++)
 | |
|     {
 | |
|       if (key_info->name.length &&
 | |
|           !lex_string_cmp(system_charset_info, &key_info->name,
 | |
|                           key_name))
 | |
|         return add_stat_drop_index(key_info, false, thd->mem_root);
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Alter_info::apply_statistics_deletes_renames(THD *thd, TABLE *table)
 | |
| {
 | |
|   List_iterator<Field>                     it_drop_field(drop_stat_fields);
 | |
|   List_iterator<RENAME_COLUMN_STAT_PARAMS> it_rename_field(rename_stat_fields);
 | |
|   List_iterator<DROP_INDEX_STAT_PARAMS>    it_drop_index(drop_stat_indexes);
 | |
|   List_iterator<RENAME_INDEX_STAT_PARAMS>  it_rename_index(rename_stat_indexes);
 | |
| 
 | |
|   while (Field *field= it_drop_field++)
 | |
|     delete_statistics_for_column(thd, table, field);
 | |
| 
 | |
|   if (!rename_stat_fields.is_empty())
 | |
|     (void) rename_columns_in_stat_table(thd, table, &rename_stat_fields);
 | |
| 
 | |
|   while (DROP_INDEX_STAT_PARAMS *key= it_drop_index++)
 | |
|     (void) delete_statistics_for_index(thd, table, key->key,
 | |
|                                        key->ext_prefixes_only);
 | |
| 
 | |
|   if (!rename_stat_indexes.is_empty())
 | |
|     (void) rename_indexes_in_stat_table(thd, table, &rename_stat_indexes);
 | |
| }
 | |
| 
 | |
| 
 | |
| Alter_table_ctx::Alter_table_ctx()
 | |
|   : db(null_clex_str), table_name(null_clex_str), alias(null_clex_str),
 | |
|     new_db(null_clex_str), new_name(null_clex_str), new_alias(null_clex_str)
 | |
| {
 | |
| }
 | |
| 
 | |
| /*
 | |
|   TODO: new_name_arg changes if lower case table names.
 | |
|   Should be copied or converted before call
 | |
| */
 | |
| 
 | |
| Alter_table_ctx::Alter_table_ctx(THD *thd, TABLE_LIST *table_list,
 | |
|                                  uint tables_opened_arg,
 | |
|                                  const LEX_CSTRING *new_db_arg,
 | |
|                                  const LEX_CSTRING *new_name_arg)
 | |
|   : tables_opened(tables_opened_arg),
 | |
|     new_db(*new_db_arg), new_name(*new_name_arg)
 | |
| {
 | |
|   /*
 | |
|     Assign members db, table_name, new_db and new_name
 | |
|     to simplify further comparisions: we want to see if it's a RENAME
 | |
|     later just by comparing the pointers, avoiding the need for strcmp.
 | |
|   */
 | |
|   db= table_list->db;
 | |
|   table_name= table_list->table_name;
 | |
|   alias= (lower_case_table_names == 2) ? table_list->alias : table_name;
 | |
| 
 | |
|   if (!new_db.str || !my_strcasecmp(table_alias_charset, new_db.str, db.str))
 | |
|     new_db= db;
 | |
| 
 | |
|   if (new_name.str)
 | |
|   {
 | |
|     DBUG_PRINT("info", ("new_db.new_name: '%s'.'%s'", new_db.str, new_name.str));
 | |
| 
 | |
|     if (lower_case_table_names == 1) // Convert new_name/new_alias to lower
 | |
|     {
 | |
|       new_name.length= my_casedn_str(files_charset_info, (char*) new_name.str);
 | |
|       new_alias= new_name;
 | |
|     }
 | |
|     else if (lower_case_table_names == 2) // Convert new_name to lower case
 | |
|     {
 | |
|       new_alias.str=    new_alias_buff;
 | |
|       new_alias.length= new_name.length;
 | |
|       strmov(new_alias_buff, new_name.str);
 | |
|       new_name.length= my_casedn_str(files_charset_info, (char*) new_name.str);
 | |
| 
 | |
|     }
 | |
|     else
 | |
|       new_alias= new_name; // LCTN=0 => case sensitive + case preserving
 | |
| 
 | |
|     if (!is_database_changed() &&
 | |
|         !my_strcasecmp(table_alias_charset, new_name.str, table_name.str))
 | |
|     {
 | |
|       /*
 | |
|         Source and destination table names are equal:
 | |
|         make is_table_renamed() more efficient.
 | |
|       */
 | |
|       new_alias= table_name;
 | |
|       new_name= table_name;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     new_alias= alias;
 | |
|     new_name= table_name;
 | |
|   }
 | |
| 
 | |
|   tmp_name.str= tmp_name_buff;
 | |
|   tmp_name.length= my_snprintf(tmp_name_buff, sizeof(tmp_name_buff),
 | |
|                                "%s-alter-%lx-%llx",
 | |
|                                tmp_file_prefix, current_pid, thd->thread_id);
 | |
|   /* Safety fix for InnoDB */
 | |
|   if (lower_case_table_names)
 | |
|     tmp_name.length= my_casedn_str(files_charset_info, tmp_name_buff);
 | |
| 
 | |
|   if (table_list->table->s->tmp_table == NO_TMP_TABLE)
 | |
|   {
 | |
|     build_table_filename(path, sizeof(path) - 1, db.str, table_name.str, "", 0);
 | |
| 
 | |
|     build_table_filename(new_path, sizeof(new_path) - 1, new_db.str, new_name.str, "", 0);
 | |
| 
 | |
|     build_table_filename(new_filename, sizeof(new_filename) - 1,
 | |
|                          new_db.str, new_name.str, reg_ext, 0);
 | |
| 
 | |
|     build_table_filename(tmp_path, sizeof(tmp_path) - 1, new_db.str, tmp_name.str, "",
 | |
|                          FN_IS_TMP);
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       We are not filling path, new_path and new_filename members if
 | |
|       we are altering temporary table as these members are not used in
 | |
|       this case. This fact is enforced with assert.
 | |
|     */
 | |
|     build_tmptable_filename(thd, tmp_path, sizeof(tmp_path));
 | |
|     tmp_table= true;
 | |
|   }
 | |
|   if ((id.length= table_list->table->s->tabledef_version.length))
 | |
|     memcpy(id_buff, table_list->table->s->tabledef_version.str, MY_UUID_SIZE);
 | |
|   id.str= id_buff;
 | |
|   storage_engine_partitioned= table_list->table->file->partition_engine();
 | |
|   storage_engine_name.str= storage_engine_buff;
 | |
|   storage_engine_name.length= ((strmake(storage_engine_buff,
 | |
|                                         table_list->table->file->
 | |
|                                         real_table_type(),
 | |
|                                         sizeof(storage_engine_buff)-1)) -
 | |
|                                storage_engine_buff);
 | |
|   tmp_storage_engine_name.str= tmp_storage_engine_buff;
 | |
|   tmp_storage_engine_name.length= 0;
 | |
|   tmp_id.str= 0;
 | |
|   tmp_id.length= 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| void Alter_table_ctx::report_implicit_default_value_error(THD *thd,
 | |
|                                                           const TABLE_SHARE *s)
 | |
|                                                           const
 | |
| {
 | |
|   Create_field *error_field= implicit_default_value_error_field;
 | |
|   const Type_handler *h= error_field->type_handler();
 | |
|   thd->push_warning_truncated_value_for_field(Sql_condition::WARN_LEVEL_WARN,
 | |
|                                               h->name().ptr(),
 | |
|                                               h->default_value().ptr(),
 | |
|                                               s ? s->db.str : nullptr,
 | |
|                                               s ? s->table_name.str : nullptr,
 | |
|                                               error_field->field_name.str);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_alter_table::execute(THD *thd)
 | |
| {
 | |
|   LEX *lex= thd->lex;
 | |
|   /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
 | |
|   SELECT_LEX *select_lex= lex->first_select_lex();
 | |
|   /* first table of first SELECT_LEX */
 | |
|   TABLE_LIST *first_table= (TABLE_LIST*) select_lex->table_list.first;
 | |
| 
 | |
|   const bool used_engine= lex->create_info.used_fields & HA_CREATE_USED_ENGINE;
 | |
|   DBUG_ASSERT((m_storage_engine_name.str != NULL) == used_engine);
 | |
|   if (used_engine)
 | |
|   {
 | |
|     if (resolve_storage_engine_with_error(thd, &lex->create_info.db_type,
 | |
|                                           lex->create_info.tmp_table()))
 | |
|       return true; // Engine not found, substitution is not allowed
 | |
|     if (!lex->create_info.db_type) // Not found, but substitution is allowed
 | |
|       lex->create_info.used_fields&= ~HA_CREATE_USED_ENGINE;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Code in mysql_alter_table() may modify its HA_CREATE_INFO argument,
 | |
|     so we have to use a copy of this structure to make execution
 | |
|     prepared statement- safe. A shallow copy is enough as no memory
 | |
|     referenced from this structure will be modified.
 | |
|     @todo move these into constructor...
 | |
|   */
 | |
|   Table_specification_st create_info(lex->create_info);
 | |
|   Alter_info alter_info(lex->alter_info, thd->mem_root);
 | |
|   create_info.alter_info= &alter_info;
 | |
|   privilege_t priv(NO_ACL);
 | |
|   privilege_t priv_needed(ALTER_ACL);
 | |
|   bool result;
 | |
| 
 | |
|   DBUG_ENTER("Sql_cmd_alter_table::execute");
 | |
| 
 | |
|   if (unlikely(thd->is_fatal_error))
 | |
|   {
 | |
|     /* out of memory creating a copy of alter_info */
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   /*
 | |
|     We also require DROP priv for ALTER TABLE ... DROP PARTITION, as well
 | |
|     as for RENAME TO, as being done by SQLCOM_RENAME_TABLE
 | |
|   */
 | |
|   if ((alter_info.partition_flags & ALTER_PARTITION_DROP) ||
 | |
|       (alter_info.partition_flags & ALTER_PARTITION_CONVERT_IN) ||
 | |
|       (alter_info.partition_flags & ALTER_PARTITION_CONVERT_OUT) ||
 | |
|       (alter_info.flags & ALTER_RENAME))
 | |
|     priv_needed|= DROP_ACL;
 | |
| 
 | |
|   /* Must be set in the parser */
 | |
|   DBUG_ASSERT(select_lex->db.str);
 | |
|   DBUG_ASSERT(!(alter_info.partition_flags & ALTER_PARTITION_EXCHANGE));
 | |
|   DBUG_ASSERT(!(alter_info.partition_flags & ALTER_PARTITION_ADMIN));
 | |
|   if (check_access(thd, priv_needed, first_table->db.str,
 | |
|                    &first_table->grant.privilege,
 | |
|                    &first_table->grant.m_internal,
 | |
|                    0, 0) ||
 | |
|       check_access(thd, INSERT_ACL | CREATE_ACL, select_lex->db.str,
 | |
|                    &priv,
 | |
|                    NULL, /* Don't use first_tab->grant with sel_lex->db */
 | |
|                    0, 0))
 | |
|     DBUG_RETURN(TRUE);                  /* purecov: inspected */
 | |
| 
 | |
|   if ((alter_info.partition_flags & ALTER_PARTITION_CONVERT_IN))
 | |
|   {
 | |
|     TABLE_LIST *tl= first_table->next_local;
 | |
|     tl->grant.privilege= first_table->grant.privilege;
 | |
|     tl->grant.m_internal= first_table->grant.m_internal;
 | |
|   }
 | |
| 
 | |
| 
 | |
|   /* If it is a merge table, check privileges for merge children. */
 | |
|   if (create_info.merge_list)
 | |
|   {
 | |
|     /*
 | |
|       The user must have (SELECT_ACL | UPDATE_ACL | DELETE_ACL) on the
 | |
|       underlying base tables, even if there are temporary tables with the same
 | |
|       names.
 | |
| 
 | |
|       From user's point of view, it might look as if the user must have these
 | |
|       privileges on temporary tables to create a merge table over them. This is
 | |
|       one of two cases when a set of privileges is required for operations on
 | |
|       temporary tables (see also CREATE TABLE).
 | |
| 
 | |
|       The reason for this behavior stems from the following facts:
 | |
| 
 | |
|         - For merge tables, the underlying table privileges are checked only
 | |
|           at CREATE TABLE / ALTER TABLE time.
 | |
| 
 | |
|           In other words, once a merge table is created, the privileges of
 | |
|           the underlying tables can be revoked, but the user will still have
 | |
|           access to the merge table (provided that the user has privileges on
 | |
|           the merge table itself). 
 | |
| 
 | |
|         - Temporary tables shadow base tables.
 | |
| 
 | |
|           I.e. there might be temporary and base tables with the same name, and
 | |
|           the temporary table takes the precedence in all operations.
 | |
| 
 | |
|         - For temporary MERGE tables we do not track if their child tables are
 | |
|           base or temporary. As result we can't guarantee that privilege check
 | |
|           which was done in presence of temporary child will stay relevant
 | |
|           later as this temporary table might be removed.
 | |
| 
 | |
|       If SELECT_ACL | UPDATE_ACL | DELETE_ACL privileges were not checked for
 | |
|       the underlying *base* tables, it would create a security breach as in
 | |
|       Bug#12771903.
 | |
|     */
 | |
| 
 | |
|     if (check_table_access(thd, SELECT_ACL | UPDATE_ACL | DELETE_ACL,
 | |
|                            create_info.merge_list, FALSE, UINT_MAX, FALSE))
 | |
|       DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   if (check_grant(thd, priv_needed, first_table, FALSE, UINT_MAX, FALSE))
 | |
|     DBUG_RETURN(TRUE);                  /* purecov: inspected */
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
|   if (WSREP(thd) && wsrep_thd_is_local(thd) &&
 | |
|       (!thd->is_current_stmt_binlog_format_row() ||
 | |
|        !thd->find_temporary_table(first_table)))
 | |
|   {
 | |
|     /*
 | |
|       It makes sense to set auto_increment_* to defaults in TOI operations.
 | |
|       Must be done before wsrep_TOI_begin() since Query_log_event encapsulating
 | |
|       TOI statement and auto inc variables for wsrep replication is constructed
 | |
|       there. Variables are reset back in THD::reset_for_next_command() before
 | |
|       processing of next command.
 | |
|     */
 | |
|     if (wsrep_auto_increment_control)
 | |
|     {
 | |
|       thd->variables.auto_increment_offset = 1;
 | |
|       thd->variables.auto_increment_increment = 1;
 | |
|     }
 | |
| 
 | |
|     wsrep::key_array keys;
 | |
|     if (!wsrep_append_fk_parent_table(thd, first_table, &keys))
 | |
|     {
 | |
|       WSREP_TO_ISOLATION_BEGIN_ALTER(lex->name.str ? select_lex->db.str
 | |
|                                      : first_table->db.str,
 | |
|                                      lex->name.str ? lex->name.str
 | |
|                                      : first_table->table_name.str,
 | |
|                                      first_table, &alter_info, &keys,
 | |
|                                      used_engine ? &create_info : nullptr)
 | |
|       {
 | |
|         WSREP_WARN("ALTER TABLE isolation failure");
 | |
|         DBUG_RETURN(TRUE);
 | |
|       }
 | |
|     }
 | |
|     DEBUG_SYNC(thd, "wsrep_alter_table_after_toi");
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (lex->name.str && !test_all_bits(priv, INSERT_ACL | CREATE_ACL))
 | |
|   {
 | |
|     // Rename of table
 | |
|     TABLE_LIST tmp_table;
 | |
|     tmp_table.init_one_table(&select_lex->db, &lex->name, 0, TL_IGNORE);
 | |
|     tmp_table.grant.privilege= priv;
 | |
|     if (check_grant(thd, INSERT_ACL | CREATE_ACL, &tmp_table, FALSE,
 | |
|                     UINT_MAX, FALSE))
 | |
|       DBUG_RETURN(TRUE);                  /* purecov: inspected */
 | |
|   }
 | |
| 
 | |
|   /* Don't yet allow changing of symlinks with ALTER TABLE */
 | |
|   if (create_info.data_file_name)
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                         WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED),
 | |
|                         "DATA DIRECTORY");
 | |
|   if (create_info.index_file_name)
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                         WARN_OPTION_IGNORED, ER_THD(thd, WARN_OPTION_IGNORED),
 | |
|                         "INDEX DIRECTORY");
 | |
|   create_info.data_file_name= create_info.index_file_name= NULL;
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   thd->work_part_info= 0;
 | |
| #endif
 | |
| 
 | |
|   Recreate_info recreate_info;
 | |
|   result= mysql_alter_table(thd, &select_lex->db, &lex->name,
 | |
|                             &create_info,
 | |
|                             first_table,
 | |
|                             &recreate_info,
 | |
|                             &alter_info,
 | |
|                             select_lex->order_list.elements,
 | |
|                             select_lex->order_list.first,
 | |
|                             lex->ignore, lex->if_exists());
 | |
| 
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| bool Sql_cmd_discard_import_tablespace::execute(THD *thd)
 | |
| {
 | |
|   /* first SELECT_LEX (have special meaning for many of non-SELECTcommands) */
 | |
|   SELECT_LEX *select_lex= thd->lex->first_select_lex();
 | |
|   /* first table of first SELECT_LEX */
 | |
|   TABLE_LIST *table_list= (TABLE_LIST*) select_lex->table_list.first;
 | |
| 
 | |
|   if (check_access(thd, ALTER_ACL, table_list->db.str,
 | |
|                    &table_list->grant.privilege,
 | |
|                    &table_list->grant.m_internal,
 | |
|                    0, 0))
 | |
|     return true;
 | |
| 
 | |
|   if (check_grant(thd, ALTER_ACL, table_list, false, UINT_MAX, false))
 | |
|     return true;
 | |
| 
 | |
|   /*
 | |
|     Check if we attempt to alter mysql.slow_log or
 | |
|     mysql.general_log table and return an error if
 | |
|     it is the case.
 | |
|     TODO: this design is obsolete and will be removed.
 | |
|   */
 | |
|   if (check_if_log_table(table_list, TRUE, "ALTER"))
 | |
|     return true;
 | |
| 
 | |
|   return
 | |
|     mysql_discard_or_import_tablespace(thd, table_list,
 | |
|                                        m_tablespace_op == DISCARD_TABLESPACE);
 | |
| }
 | 
