mirror of
				https://github.com/MariaDB/server.git
				synced 2025-10-31 02:46:29 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			13723 lines
		
	
	
	
		
			456 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			13723 lines
		
	
	
	
		
			456 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|    Copyright (c) 2000, 2019, Oracle and/or its affiliates.
 | |
|    Copyright (c) 2010, 2022, MariaDB
 | |
| 
 | |
|    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
 | |
| */
 | |
| 
 | |
| /* drop and alter of tables */
 | |
| 
 | |
| #include "mariadb.h"
 | |
| #include "sql_class.h"
 | |
| #include "sql_priv.h"
 | |
| #include "unireg.h"
 | |
| #include "debug_sync.h"
 | |
| #include "sql_table.h"
 | |
| #include "sql_parse.h"                        // test_if_data_home_dir
 | |
| #include "sql_cache.h"                          // query_cache_*
 | |
| #include "sql_base.h"   // lock_table_names
 | |
| #include "lock.h"       // mysql_unlock_tables
 | |
| #include "strfunc.h"    // find_type2, find_set
 | |
| #include "sql_truncate.h"                       // regenerate_locked_table
 | |
| #include "ha_partition.h"                       // PAR_EXT
 | |
|                                                 // mem_alloc_error,
 | |
|                                                 // partition_info
 | |
|                                                 // NOT_A_PARTITION_ID
 | |
| #include "sql_db.h"                             // load_db_opt_by_name
 | |
| #include "records.h"             // init_read_record, end_read_record
 | |
| #include "filesort.h"            // filesort_free_buffers
 | |
| #include "sql_select.h"                // setup_order
 | |
| #include "sql_handler.h"               // mysql_ha_rm_tables
 | |
| #include "discover.h"                  // readfrm
 | |
| #include "my_pthread.h"                // pthread_mutex_t
 | |
| #include "log_event.h"                 // Query_log_event
 | |
| #include "sql_statistics.h"
 | |
| #include <hash.h>
 | |
| #include <myisam.h>
 | |
| #include <my_dir.h>
 | |
| #include "create_options.h"
 | |
| #include "sp_head.h"
 | |
| #include "sp.h"
 | |
| #include "sql_trigger.h"
 | |
| #include "sql_show.h"
 | |
| #include "transaction.h"
 | |
| #include "sql_audit.h"
 | |
| #include "sql_sequence.h"
 | |
| #include "tztime.h"
 | |
| #include "rpl_rli.h"
 | |
| #include "sql_insert.h"                // binlog_drop_table
 | |
| #include "ddl_log.h"
 | |
| #include "debug.h"                     // debug_crash_here()
 | |
| #include <algorithm>
 | |
| #include "rpl_mi.h"
 | |
| #include "rpl_rli.h"
 | |
| #include "log.h"
 | |
| #include "vector_mhnsw.h"
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
| #include "wsrep_mysqld.h"
 | |
| 
 | |
| /** RAII class for temporarily enabling wsrep_ctas in the connection. */
 | |
| class Enable_wsrep_ctas_guard
 | |
| {
 | |
|  public:
 | |
|   /**
 | |
|     @param thd  - pointer to the context of connection in which
 | |
|                   wsrep_ctas mode needs to be enabled.
 | |
|     @param ctas - true if this is CREATE TABLE AS SELECT and
 | |
|                   wsrep_on
 | |
|   */
 | |
|   explicit Enable_wsrep_ctas_guard(THD *thd, const bool ctas)
 | |
|     : m_thd(thd)
 | |
|   {
 | |
|     if (ctas)
 | |
|       thd->wsrep_ctas= true;
 | |
|   }
 | |
| 
 | |
|   ~Enable_wsrep_ctas_guard()
 | |
|   {
 | |
|     m_thd->wsrep_ctas= false;
 | |
|   }
 | |
|  private:
 | |
|   THD* m_thd;
 | |
| };
 | |
| 
 | |
| #endif /* WITH_WSREP */
 | |
| 
 | |
| #include "sql_debug.h"
 | |
| #include "scope.h"
 | |
| 
 | |
| #ifdef _WIN32
 | |
| #include <io.h>
 | |
| #endif
 | |
| 
 | |
| const Lex_ident_column primary_key_name= "PRIMARY"_Lex_ident_column;
 | |
| LEX_CSTRING generated_by_server=
 | |
| { STRING_WITH_LEN(" /* generated by server */") };
 | |
| static const LEX_CSTRING SEQUENCE_clex_str= { STRING_WITH_LEN("SEQUENCE") };
 | |
| static const LEX_CSTRING TABLE_clex_str= { STRING_WITH_LEN("TABLE") };
 | |
| 
 | |
| static int check_if_keyname_exists(const Lex_ident_column &name,
 | |
|                                    const KEY *start, const KEY *end);
 | |
| static Lex_ident_column make_unique_key_name(THD *,
 | |
|                                              const Lex_ident_column &name,
 | |
|                                              const KEY *, const KEY *);
 | |
| static bool make_unique_constraint_name(THD *, LEX_CSTRING *, const char *,
 | |
|                                         List<Virtual_column_info> *, uint *);
 | |
| static Lex_ident_column make_unique_invisible_field_name(THD *,
 | |
|                                              const Lex_ident_column &name,
 | |
|                                              List<Create_field> *);
 | |
| static int copy_data_between_tables(THD *, TABLE *,TABLE *,
 | |
|                                     bool, uint, ORDER *,
 | |
|                                     ha_rows *, ha_rows *,
 | |
|                                     Alter_info *,
 | |
|                                     Alter_table_ctx *, bool, uint64);
 | |
| static int append_system_key_parts(THD *, HA_CREATE_INFO *, Key *);
 | |
| static int mysql_prepare_create_table(THD *, HA_CREATE_INFO *, Alter_info *,
 | |
|                                       uint *, handler *, KEY **, uint *, int);
 | |
| static uint blob_length_by_type(enum_field_types type);
 | |
| static bool fix_constraints_names(THD *, List<Virtual_column_info> *,
 | |
|                                   const HA_CREATE_INFO *);
 | |
| static bool wait_for_master(THD *thd);
 | |
| static int process_master_state(THD *thd, int alter_result,
 | |
|                                 uint64 &start_alter_id, bool if_exists);
 | |
| static bool
 | |
| write_bin_log_start_alter_rollback(THD *thd, uint64 &start_alter_id,
 | |
|                                    bool &partial_alter, bool if_exists);
 | |
| 
 | |
| /**
 | |
|   @brief Helper function for explain_filename
 | |
|   @param thd          Thread handle
 | |
|   @param to_p         Explained name in system_charset_info
 | |
|   @param end_p        End of the to_p buffer
 | |
|   @param name         Name to be converted
 | |
|   @param name_len     Length of the name, in bytes
 | |
| */
 | |
| static char* add_identifier(THD* thd, char *to_p, const char * end_p,
 | |
|                             const char* name, size_t name_len)
 | |
| {
 | |
|   uint res;
 | |
|   uint errors;
 | |
|   const char *conv_name, *conv_name_end;
 | |
|   char tmp_name[FN_REFLEN];
 | |
|   char conv_string[FN_REFLEN];
 | |
|   int quote;
 | |
| 
 | |
|   DBUG_ENTER("add_identifier");
 | |
|   if (!name[name_len])
 | |
|     conv_name= name;
 | |
|   else
 | |
|   {
 | |
|     strnmov(tmp_name, name, name_len);
 | |
|     tmp_name[name_len]= 0;
 | |
|     conv_name= tmp_name;
 | |
|   }
 | |
|   res= strconvert(&my_charset_filename, conv_name, name_len,
 | |
|                   system_charset_info,
 | |
|                   conv_string, FN_REFLEN, &errors);
 | |
|   if (unlikely(!res || errors))
 | |
|   {
 | |
|     DBUG_PRINT("error", ("strconvert of '%s' failed with %u (errors: %u)", conv_name, res, errors));
 | |
|     conv_name= name;
 | |
|     conv_name_end= name + name_len;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     DBUG_PRINT("info", ("conv '%s' -> '%s'", conv_name, conv_string));
 | |
|     conv_name= conv_string;
 | |
|     conv_name_end= conv_string + res;
 | |
|   }
 | |
| 
 | |
|   quote= (likely(thd) ?
 | |
|           get_quote_char_for_identifier(thd, conv_name, res - 1) :
 | |
|           '`');
 | |
| 
 | |
|   if (quote != EOF && (end_p - to_p > 2))
 | |
|   {
 | |
|     *(to_p++)= (char) quote;
 | |
|     while (*conv_name && (end_p - to_p - 1) > 0)
 | |
|     {
 | |
|       int length= system_charset_info->charlen(conv_name, conv_name_end);
 | |
|       if (length <= 0)
 | |
|         length= 1;
 | |
|       if (length == 1 && *conv_name == (char) quote)
 | |
|       { 
 | |
|         if ((end_p - to_p) < 3)
 | |
|           break;
 | |
|         *(to_p++)= (char) quote;
 | |
|         *(to_p++)= *(conv_name++);
 | |
|       }
 | |
|       else if (((long) length) < (end_p - to_p))
 | |
|       {
 | |
|         to_p= strnmov(to_p, conv_name, length);
 | |
|         conv_name+= length;
 | |
|       }
 | |
|       else
 | |
|         break;                               /* string already filled */
 | |
|     }
 | |
|     if (end_p > to_p) {
 | |
|       *(to_p++)= (char) quote;
 | |
|       if (end_p > to_p)
 | |
| 	*to_p= 0; /* terminate by NUL, but do not include it in the count */
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|     to_p= strnmov(to_p, conv_name, end_p - to_p);
 | |
|   DBUG_RETURN(to_p);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Explain a path name by split it to database, table etc.
 | |
|   
 | |
|   @details Break down the path name to its logic parts
 | |
|   (database, table, partition, subpartition).
 | |
|   filename_to_tablename cannot be used on partitions, due to the #P# part.
 | |
|   There can be up to 6 '#', #P# for partition, #SP# for subpartition
 | |
|   and #TMP# or #REN# for temporary or renamed partitions.
 | |
|   This should be used when something should be presented to a user in a
 | |
|   diagnostic, error etc. when it would be useful to know what a particular
 | |
|   file [and directory] means. Such as SHOW ENGINE STATUS, error messages etc.
 | |
| 
 | |
|   Examples:
 | |
| 
 | |
|     t1#P#p1                 table t1 partition p1
 | |
|     t1#P#p1#SP#sp1          table t1 partition p1 subpartition sp1
 | |
|     t1#P#p1#SP#sp1#TMP#     table t1 partition p1 subpartition sp1 temporary
 | |
|     t1#P#p1#SP#sp1#REN#     table t1 partition p1 subpartition sp1 renamed
 | |
| 
 | |
|    @param      thd          Thread handle
 | |
|    @param      from         Path name in my_charset_filename
 | |
|                             Null terminated in my_charset_filename, normalized
 | |
|                             to use '/' as directory separation character.
 | |
|    @param      to           Explained name in system_charset_info
 | |
|    @param      to_length    Size of to buffer
 | |
|    @param      explain_mode Requested output format.
 | |
|                             EXPLAIN_ALL_VERBOSE ->
 | |
|                             [Database `db`, ]Table `tbl`[,[ Temporary| Renamed]
 | |
|                             Partition `p` [, Subpartition `sp`]]
 | |
|                             EXPLAIN_PARTITIONS_VERBOSE -> `db`.`tbl`
 | |
|                             [[ Temporary| Renamed] Partition `p`
 | |
|                             [, Subpartition `sp`]]
 | |
|                             EXPLAIN_PARTITIONS_AS_COMMENT -> `db`.`tbl` |*
 | |
|                             [,[ Temporary| Renamed] Partition `p`
 | |
|                             [, Subpartition `sp`]] *|
 | |
|                             (| is really a /, and it is all in one line)
 | |
| 
 | |
|    @retval     Length of returned string
 | |
| */
 | |
| 
 | |
| uint explain_filename(THD* thd,
 | |
| 		      const char *from,
 | |
|                       char *to,
 | |
|                       uint to_length,
 | |
|                       enum_explain_filename_mode explain_mode)
 | |
| {
 | |
|   char *to_p= to;
 | |
|   char *end_p= to_p + to_length;
 | |
|   const char *db_name= NULL;
 | |
|   size_t  db_name_len= 0;
 | |
|   const char *table_name;
 | |
|   size_t  table_name_len= 0;
 | |
|   const char *part_name= NULL;
 | |
|   size_t  part_name_len= 0;
 | |
|   const char *subpart_name= NULL;
 | |
|   size_t  subpart_name_len= 0;
 | |
|   uint part_type= NORMAL_PART_NAME;
 | |
| 
 | |
|   const char *tmp_p;
 | |
|   DBUG_ENTER("explain_filename");
 | |
|   DBUG_PRINT("enter", ("from '%s'", from));
 | |
|   tmp_p= from;
 | |
|   table_name= from;
 | |
|   /*
 | |
|     If '/' then take last directory part as database.
 | |
|     '/' is the directory separator, not FN_LIB_CHAR
 | |
|   */
 | |
|   while ((tmp_p= strchr(tmp_p, '/')))
 | |
|   {
 | |
|     db_name= table_name;
 | |
|     /* calculate the length */
 | |
|     db_name_len= (int)(tmp_p - db_name);
 | |
|     tmp_p++;
 | |
|     table_name= tmp_p;
 | |
|   }
 | |
|   tmp_p= table_name;
 | |
|   /* Look if there are partition tokens in the table name. */
 | |
|   while ((tmp_p= strchr(tmp_p, '#')))
 | |
|   {
 | |
|     tmp_p++;
 | |
|     switch (tmp_p[0]) {
 | |
|     case 'P':
 | |
|     case 'p':
 | |
|       if (tmp_p[1] == '#')
 | |
|       {
 | |
|         part_name= tmp_p + 2;
 | |
|         tmp_p+= 2;
 | |
|       }
 | |
|       break;
 | |
|     case 'S':
 | |
|     case 's':
 | |
|       if ((tmp_p[1] == 'P' || tmp_p[1] == 'p') && tmp_p[2] == '#')
 | |
|       {
 | |
|         part_name_len= (int)(tmp_p - part_name - 1);
 | |
|         subpart_name= tmp_p + 3;
 | |
| 	tmp_p+= 3;
 | |
|       }
 | |
|       break;
 | |
|     case 'T':
 | |
|     case 't':
 | |
|       if ((tmp_p[1] == 'M' || tmp_p[1] == 'm') &&
 | |
|           (tmp_p[2] == 'P' || tmp_p[2] == 'p') &&
 | |
|           tmp_p[3] == '#' && !tmp_p[4])
 | |
|       {
 | |
|         part_type= TEMP_PART_NAME;
 | |
|         tmp_p+= 4;
 | |
|       }
 | |
|       break;
 | |
|     case 'R':
 | |
|     case 'r':
 | |
|       if ((tmp_p[1] == 'E' || tmp_p[1] == 'e') &&
 | |
|           (tmp_p[2] == 'N' || tmp_p[2] == 'n') &&
 | |
|           tmp_p[3] == '#' && !tmp_p[4])
 | |
|       {
 | |
|         part_type= RENAMED_PART_NAME;
 | |
|         tmp_p+= 4;
 | |
|       }
 | |
|       break;
 | |
|     default:
 | |
|       /* Not partition name part. */
 | |
|       ;
 | |
|     }
 | |
|   }
 | |
|   if (part_name)
 | |
|   {
 | |
|     table_name_len= (int)(part_name - table_name - 3);
 | |
|     if (subpart_name)
 | |
|       subpart_name_len= strlen(subpart_name);
 | |
|     else
 | |
|       part_name_len= strlen(part_name);
 | |
|     if (part_type != NORMAL_PART_NAME)
 | |
|     {
 | |
|       if (subpart_name)
 | |
|         subpart_name_len-= 5;
 | |
|       else
 | |
|         part_name_len-= 5;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|     table_name_len= strlen(table_name);
 | |
|   if (db_name)
 | |
|   {
 | |
|     if (explain_mode == EXPLAIN_ALL_VERBOSE)
 | |
|     {
 | |
|       to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_DATABASE_NAME),
 | |
|                                             end_p - to_p);
 | |
|       *(to_p++)= ' ';
 | |
|       to_p= add_identifier(thd, to_p, end_p, db_name, db_name_len);
 | |
|       to_p= strnmov(to_p, ", ", end_p - to_p);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       to_p= add_identifier(thd, to_p, end_p, db_name, db_name_len);
 | |
|       to_p= strnmov(to_p, ".", end_p - to_p);
 | |
|     }
 | |
|   }
 | |
|   if (explain_mode == EXPLAIN_ALL_VERBOSE)
 | |
|   {
 | |
|     to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_TABLE_NAME), end_p - to_p);
 | |
|     *(to_p++)= ' ';
 | |
|     to_p= add_identifier(thd, to_p, end_p, table_name, table_name_len);
 | |
|   }
 | |
|   else
 | |
|     to_p= add_identifier(thd, to_p, end_p, table_name, table_name_len);
 | |
|   if (part_name)
 | |
|   {
 | |
|     if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT)
 | |
|       to_p= strnmov(to_p, " /* ", end_p - to_p);
 | |
|     else if (explain_mode == EXPLAIN_PARTITIONS_VERBOSE)
 | |
|       to_p= strnmov(to_p, " ", end_p - to_p);
 | |
|     else
 | |
|       to_p= strnmov(to_p, ", ", end_p - to_p);
 | |
|     if (part_type != NORMAL_PART_NAME)
 | |
|     {
 | |
|       if (part_type == TEMP_PART_NAME)
 | |
|         to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_TEMPORARY_NAME),
 | |
|                       end_p - to_p);
 | |
|       else
 | |
|         to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_RENAMED_NAME),
 | |
|                       end_p - to_p);
 | |
|       to_p= strnmov(to_p, " ", end_p - to_p);
 | |
|     }
 | |
|     to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_PARTITION_NAME),
 | |
|                   end_p - to_p);
 | |
|     *(to_p++)= ' ';
 | |
|     to_p= add_identifier(thd, to_p, end_p, part_name, part_name_len);
 | |
|     if (subpart_name)
 | |
|     {
 | |
|       to_p= strnmov(to_p, ", ", end_p - to_p);
 | |
|       to_p= strnmov(to_p, ER_THD_OR_DEFAULT(thd, ER_SUBPARTITION_NAME),
 | |
|                     end_p - to_p);
 | |
|       *(to_p++)= ' ';
 | |
|       to_p= add_identifier(thd, to_p, end_p, subpart_name, subpart_name_len);
 | |
|     }
 | |
|     if (explain_mode == EXPLAIN_PARTITIONS_AS_COMMENT)
 | |
|       to_p= strnmov(to_p, " */", end_p - to_p);
 | |
|   }
 | |
|   DBUG_PRINT("exit", ("to '%s'", to));
 | |
|   DBUG_RETURN((uint)(to_p - to));
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Translate a file name to a table name (WL #1324).
 | |
| 
 | |
|   SYNOPSIS
 | |
|     filename_to_tablename()
 | |
|       from                      The file name in my_charset_filename.
 | |
|       to                OUT     The table name in system_charset_info.
 | |
|       to_length                 The size of the table name buffer.
 | |
| 
 | |
|   RETURN
 | |
|     Table name length.
 | |
| */
 | |
| 
 | |
| uint filename_to_tablename(const char *from, char *to, size_t to_length, 
 | |
|                            bool stay_quiet)
 | |
| {
 | |
|   uint errors;
 | |
|   size_t res;
 | |
|   DBUG_ENTER("filename_to_tablename");
 | |
|   DBUG_PRINT("enter", ("from '%s'", from));
 | |
| 
 | |
|   res= strconvert(&my_charset_filename, from, FN_REFLEN,
 | |
|                   system_charset_info,  to, to_length, &errors);
 | |
|   if (unlikely(errors)) // Old 5.0 name
 | |
|   {
 | |
|     res= strxnmov(to, to_length, MYSQL50_TABLE_NAME_PREFIX, from, NullS) - to;
 | |
|     if (!stay_quiet)
 | |
|       sql_print_error("Invalid (old?) table or database name '%s'", from);
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("exit", ("to '%s'", to));
 | |
|   DBUG_RETURN((uint)res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check if given string begins with "#mysql50#" prefix
 | |
|   
 | |
|   @param   name          string to check cut 
 | |
|   
 | |
|   @retval
 | |
|     FALSE  no prefix found
 | |
|   @retval
 | |
|     TRUE   prefix found
 | |
| */
 | |
| 
 | |
| bool check_mysql50_prefix(const char *name)
 | |
| {
 | |
|   return (name[0] == '#' && 
 | |
|          !strncmp(name, MYSQL50_TABLE_NAME_PREFIX,
 | |
|                   MYSQL50_TABLE_NAME_PREFIX_LENGTH));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check if given string begins with "#mysql50#" prefix, cut it if so.
 | |
|   
 | |
|   @param   from          string to check and cut 
 | |
|   @param   to[out]       buffer for result string
 | |
|   @param   to_length     its size
 | |
|   
 | |
|   @retval
 | |
|     0      no prefix found
 | |
|   @retval
 | |
|     non-0  result string length
 | |
| */
 | |
| 
 | |
| uint check_n_cut_mysql50_prefix(const char *from, char *to, size_t to_length)
 | |
| {
 | |
|   if (check_mysql50_prefix(from))
 | |
|     return (uint) (strmake(to, from + MYSQL50_TABLE_NAME_PREFIX_LENGTH,
 | |
|                            to_length - 1) - to);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool check_if_frm_exists(char *path, const char *db, const char *table)
 | |
| {
 | |
|   fn_format(path, table, db, reg_ext, MYF(0));
 | |
|   return !access(path, F_OK);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Translate a table name to a file name (WL #1324).
 | |
| 
 | |
|   SYNOPSIS
 | |
|     tablename_to_filename()
 | |
|       from                      The table name in system_charset_info.
 | |
|       to                OUT     The file name in my_charset_filename.
 | |
|       to_length                 The size of the file name buffer.
 | |
| 
 | |
|   RETURN
 | |
|     File name length.
 | |
| */
 | |
| 
 | |
| uint tablename_to_filename(const char *from, char *to, size_t to_length)
 | |
| {
 | |
|   uint errors, length;
 | |
|   DBUG_ENTER("tablename_to_filename");
 | |
|   DBUG_PRINT("enter", ("from '%s'", from));
 | |
| 
 | |
|   if ((length= check_n_cut_mysql50_prefix(from, to, to_length)))
 | |
|   {
 | |
|     /*
 | |
|       Check if the name supplied is a valid mysql 5.0 name and 
 | |
|       make the name a zero length string if it's not.
 | |
|       Note that just returning zero length is not enough : 
 | |
|       a lot of places don't check the return value and expect 
 | |
|       a zero terminated string.
 | |
|     */  
 | |
|     if (Lex_ident_table::check_name({to, length}, true))
 | |
|     {
 | |
|       to[0]= 0;
 | |
|       length= 0;
 | |
|     }
 | |
|     DBUG_RETURN(length);
 | |
|   }
 | |
|   length= strconvert(system_charset_info, from, FN_REFLEN,
 | |
|                      &my_charset_filename, to, to_length, &errors);
 | |
|   if (check_if_legal_tablename(to) &&
 | |
|       length + 4 < to_length)
 | |
|   {
 | |
|     memcpy(to + length, "@@@", 4);
 | |
|     length+= 3;
 | |
|   }
 | |
|   DBUG_PRINT("exit", ("to '%s'", to));
 | |
|   DBUG_RETURN(length);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Creates path to a file: mysql_data_dir/db/table.ext
 | |
| 
 | |
|   SYNOPSIS
 | |
|    build_table_filename()
 | |
|      buff                       Where to write result in my_charset_filename.
 | |
|                                 This may be the same as table_name.
 | |
|      bufflen                    buff size
 | |
|      db                         Database name in system_charset_info.
 | |
|      table_name                 Table name in system_charset_info.
 | |
|      ext                        File extension.
 | |
|      flags                      FN_FROM_IS_TMP or FN_TO_IS_TMP or FN_IS_TMP
 | |
|                                 table_name is temporary, do not change.
 | |
| 
 | |
|   NOTES
 | |
| 
 | |
|     Uses database and table name, and extension to create
 | |
|     a file name in mysql_data_dir. Database and table
 | |
|     names are converted from system_charset_info into "fscs".
 | |
|     Unless flags indicate a temporary table name.
 | |
|     'db' is always converted.
 | |
|     'ext' is not converted.
 | |
| 
 | |
|     The conversion suppression is required for ALTER TABLE. This
 | |
|     statement creates intermediate tables. These are regular
 | |
|     (non-temporary) tables with a temporary name. Their path names must
 | |
|     be derivable from the table name. So we cannot use
 | |
|     build_tmptable_filename() for them.
 | |
| 
 | |
|   RETURN
 | |
|     path length
 | |
| */
 | |
| 
 | |
| uint build_table_filename(char *buff, size_t bufflen, const char *db,
 | |
|                           const char *table_name, const char *ext, uint flags)
 | |
| {
 | |
|   char dbbuff[FN_REFLEN];
 | |
|   char tbbuff[FN_REFLEN];
 | |
|   DBUG_ENTER("build_table_filename");
 | |
|   DBUG_PRINT("enter", ("db: '%s'  table_name: '%s'  ext: '%s'  flags: %x",
 | |
|                        db, table_name, ext, flags));
 | |
| 
 | |
|   (void) tablename_to_filename(db, dbbuff, sizeof(dbbuff));
 | |
| 
 | |
|   /*
 | |
|     Check if this is a temporary table name. Allow it if a corresponding .frm
 | |
|     file exists.
 | |
|   */
 | |
|   if (!(flags & FN_IS_TMP) &&
 | |
|       is_prefix(table_name, tmp_file_prefix) &&
 | |
|       strlen(table_name) < NAME_CHAR_LEN &&
 | |
|       check_if_frm_exists(tbbuff, dbbuff, table_name))
 | |
|     flags|= FN_IS_TMP;
 | |
| 
 | |
|   if (flags & FN_IS_TMP) // FN_FROM_IS_TMP | FN_TO_IS_TMP
 | |
|     strmake(tbbuff, table_name, sizeof(tbbuff)-1);
 | |
|   else
 | |
|     (void) tablename_to_filename(table_name, tbbuff, sizeof(tbbuff));
 | |
| 
 | |
|   char *end = buff + bufflen;
 | |
|   char *pos= strnmov(buff, mysql_data_home, bufflen-3);
 | |
|   /*
 | |
|     Add FN_LIBCHAR if mysql_data_home does not include it
 | |
|     In most cases mysql_data_home is just '.'
 | |
|   */
 | |
|   if (pos[-1] != FN_LIBCHAR)
 | |
|     *pos++= FN_LIBCHAR;
 | |
|   pos= strxnmov(pos, end - 2 - pos, dbbuff,NullS);
 | |
|   *pos++= FN_LIBCHAR;
 | |
|   *pos= 0;
 | |
| #ifdef USE_SYMDIR
 | |
|   if (!(flags & SKIP_SYMDIR_ACCESS))
 | |
|   {
 | |
|     unpack_dirname(buff, buff);
 | |
|     pos= strend(buff);
 | |
|   }
 | |
| #endif
 | |
|   pos= strxnmov(pos, end - pos, tbbuff, ext, NullS);
 | |
| 
 | |
|   DBUG_PRINT("exit", ("buff: '%s'", buff));
 | |
|   DBUG_RETURN((uint)(pos - buff));
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Create a temporary table name
 | |
| 
 | |
|   @param to string. Note that to->length should contain buffer length
 | |
| */
 | |
| 
 | |
| void make_tmp_table_name(THD *thd, LEX_STRING *to, const char *prefix)
 | |
| {
 | |
|   to->length= my_snprintf((char*) to->str, to->length, "%s-%s-%lx-%llx-%x",
 | |
|                           tmp_file_prefix, prefix, current_pid,
 | |
|                           thd->thread_id, thd->tmp_table++);
 | |
|   if (lower_case_table_names)
 | |
|   {
 | |
|     // Ok to use latin1 as the file name is in the form '#sql-exchange-abc-def'
 | |
|     my_casedn_str_latin1(to->str);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Create path to a temporary table mysql_tmpdir/#sql-temptable-1234-12-1
 | |
|   (i.e. to its .FRM file but without an extension).
 | |
| 
 | |
|   @param thd      The thread handle.
 | |
|   @param buff     Where to write result in my_charset_filename.
 | |
|   @param bufflen  buff size
 | |
| 
 | |
|   @note
 | |
|     Uses current_pid, thread_id, and tmp_table counter to create
 | |
|     a file name in mysql_tmpdir.
 | |
| 
 | |
|   @return Path length.
 | |
| */
 | |
| 
 | |
| uint build_tmptable_filename(THD* thd, char *buff, size_t bufflen)
 | |
| {
 | |
|   LEX_STRING name;
 | |
|   DBUG_ENTER("build_tmptable_filename");
 | |
| 
 | |
|   char *p= strnmov(buff, mysql_tmpdir, bufflen-2);
 | |
|   *p++= '/';
 | |
| 
 | |
|   name= {p, bufflen - (p - buff) };
 | |
| 
 | |
|   make_tmp_table_name(thd, &name, "temptable");
 | |
| 
 | |
|   size_t length= unpack_filename(buff, buff);
 | |
|   DBUG_PRINT("exit", ("buff: '%s'", buff));
 | |
|   DBUG_RETURN((uint)length);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Create lower case paths for engines that requires them
 | |
| */
 | |
| 
 | |
| void build_lower_case_table_filename(char *buff, size_t bufflen,
 | |
|                                      const LEX_CSTRING *db,
 | |
|                                      const LEX_CSTRING *table,
 | |
|                                      uint flags)
 | |
| {
 | |
|   DBUG_ASSERT(db->length <= SAFE_NAME_LEN && table->length <= SAFE_NAME_LEN);
 | |
|   build_table_filename(buff, bufflen,
 | |
|           IdentBufferCasedn<SAFE_NAME_LEN>(*db).to_lex_cstring().str,
 | |
|           IdentBufferCasedn<SAFE_NAME_LEN>(*table).to_lex_cstring().str, "",
 | |
|           flags & FN_IS_TMP);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    @brief construct a temporary shadow file name.
 | |
| 
 | |
|    @details Make a shadow file name used by ALTER TABLE to construct the
 | |
|    modified table (with keeping the original). The modified table is then
 | |
|    moved back as original table. The name must start with the temp file
 | |
|    prefix so it gets filtered out by table files listing routines. 
 | |
|     
 | |
|    @param[out] buff      buffer to receive the constructed name
 | |
|    @param      bufflen   size of buff
 | |
|    @param      lpt       alter table data structure
 | |
| 
 | |
|    @retval     path length
 | |
| */
 | |
| 
 | |
| uint build_table_shadow_filename(char *buff, size_t bufflen, 
 | |
|                                  ALTER_PARTITION_PARAM_TYPE *lpt,
 | |
|                                  bool backup)
 | |
| {
 | |
|   char tmp_name[FN_REFLEN];
 | |
| 
 | |
|   my_snprintf(tmp_name, sizeof(tmp_name), "%s-%s-%lx-%s", tmp_file_prefix,
 | |
|               backup ? "backup" : "shadow",
 | |
|               (ulong) lpt->thd->thread_id, lpt->alter_info->table_name.str);
 | |
|   return build_table_filename(buff, bufflen, lpt->alter_info->db.str, tmp_name,
 | |
|                               "", FN_IS_TMP);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   SYNOPSIS
 | |
|     mysql_write_frm()
 | |
|     lpt                    Struct carrying many parameters needed for this
 | |
|                            method
 | |
|     flags                  Flags as defined below
 | |
|       WFRM_INITIAL_WRITE        If set we need to prepare table before
 | |
|                                 creating the frm file
 | |
|       WFRM_INSTALL_SHADOW       If set we should install the new frm
 | |
|       WFRM_KEEP_SHARE           If set we know that the share is to be
 | |
|                                 retained and thus we should ensure share
 | |
|                                 object is correct, if not set we don't
 | |
|                                 set the new partition syntax string since
 | |
|                                 we know the share object is destroyed.
 | |
|       WFRM_PACK_FRM             If set we should pack the frm file and delete
 | |
|                                 the frm file
 | |
| 
 | |
|   RETURN VALUES
 | |
|     TRUE                   Error
 | |
|     FALSE                  Success
 | |
| 
 | |
|   DESCRIPTION
 | |
|     A support method that creates a new frm file and in this process it
 | |
|     regenerates the partition data. It works fine also for non-partitioned
 | |
|     tables since it only handles partitioned data if it exists.
 | |
| */
 | |
| 
 | |
| 
 | |
| /*
 | |
|   TODO: Partitioning atomic DDL refactoring: WFRM_WRITE_SHADOW
 | |
|   should be merged with create_table_impl(frm_only == true).
 | |
| */
 | |
| bool mysql_write_frm(ALTER_PARTITION_PARAM_TYPE *lpt, uint flags)
 | |
| {
 | |
|   /*
 | |
|     Prepare table to prepare for writing a new frm file where the
 | |
|     partitions in add/drop state have temporarily changed their state
 | |
|     We set tmp_table to avoid get errors on naming of primary key index.
 | |
|   */
 | |
|   int error= 0;
 | |
|   char path[FN_REFLEN+1];
 | |
|   char shadow_path[FN_REFLEN+1];
 | |
|   char shadow_frm_name[FN_REFLEN+1];
 | |
|   char frm_name[FN_REFLEN+1];
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   char bak_path[FN_REFLEN+1];
 | |
|   char bak_frm_name[FN_REFLEN+1];
 | |
|   char *part_syntax_buf;
 | |
|   uint syntax_len;
 | |
|   partition_info *part_info= lpt->part_info;
 | |
| #endif
 | |
|   DBUG_ENTER("mysql_write_frm");
 | |
| 
 | |
|   /*
 | |
|     Build shadow frm file name
 | |
|   */
 | |
|   build_table_shadow_filename(shadow_path, sizeof(shadow_path) - 1, lpt);
 | |
|   strxmov(shadow_frm_name, shadow_path, reg_ext, NullS);
 | |
|   if (flags & WFRM_WRITE_SHADOW)
 | |
|   {
 | |
|     if (mysql_prepare_create_table(lpt->thd, lpt->create_info, lpt->alter_info,
 | |
|                                    &lpt->db_options, lpt->table->file,
 | |
|                                    &lpt->key_info_buffer, &lpt->key_count,
 | |
|                                    C_ALTER_TABLE))
 | |
|     {
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|     {
 | |
|       partition_info *part_info= lpt->table->part_info;
 | |
|       if (part_info)
 | |
|       {
 | |
|         part_syntax_buf= generate_partition_syntax_for_frm(lpt->thd, part_info,
 | |
|                                &syntax_len, lpt->create_info, lpt->alter_info);
 | |
|         if (!part_syntax_buf)
 | |
|           DBUG_RETURN(TRUE);
 | |
|         part_info->part_info_string= part_syntax_buf;
 | |
|         part_info->part_info_len= syntax_len;
 | |
|       }
 | |
|     }
 | |
| #endif
 | |
|     /* Write shadow frm file */
 | |
|     lpt->create_info->table_options= lpt->db_options;
 | |
|     LEX_CUSTRING frm= build_frm_image(lpt->thd, lpt->alter_info->table_name,
 | |
|                                       lpt->create_info,
 | |
|                                       lpt->alter_info->create_list,
 | |
|                                       lpt->key_count, lpt->key_info_buffer,
 | |
|                                       lpt->table->file);
 | |
|     if (!frm.str)
 | |
|     {
 | |
|       error= 1;
 | |
|       goto end;
 | |
|     }
 | |
| 
 | |
|     int error= writefile(shadow_frm_name, lpt->alter_info->db.str,
 | |
|                          lpt->alter_info->table_name.str,
 | |
|                          lpt->create_info->tmp_table(), frm.str, frm.length);
 | |
|     my_free(const_cast<uchar*>(frm.str));
 | |
| 
 | |
|     if (unlikely(error) ||
 | |
|         unlikely(lpt->table->file->
 | |
|                  ha_create_partitioning_metadata(shadow_path,
 | |
|                                                  NULL, CHF_CREATE_FLAG)))
 | |
|     {
 | |
|       mysql_file_delete(key_file_frm, shadow_frm_name, MYF(0));
 | |
|       error= 1;
 | |
|       goto end;
 | |
|     }
 | |
|   }
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   if (flags & WFRM_WRITE_CONVERTED_TO)
 | |
|   {
 | |
|     THD *thd= lpt->thd;
 | |
|     Alter_table_ctx *alter_ctx= lpt->alter_ctx;
 | |
|     HA_CREATE_INFO *create_info= lpt->create_info;
 | |
| 
 | |
|     LEX_CSTRING new_path= { alter_ctx->get_new_path(), 0 };
 | |
|     partition_info *work_part_info= thd->work_part_info;
 | |
|     handlerton *db_type= create_info->db_type;
 | |
|     DBUG_ASSERT(lpt->table->part_info);
 | |
|     DBUG_ASSERT(lpt->table->part_info == part_info);
 | |
|     handler *file= ((ha_partition *)(lpt->table->file))->get_child_handlers()[0];
 | |
|     DBUG_ASSERT(file);
 | |
|     new_path.length= strlen(new_path.str);
 | |
|     strxnmov(frm_name, sizeof(frm_name) - 1, new_path.str, reg_ext, NullS);
 | |
|     create_info->alias= alter_ctx->table_name;
 | |
|     thd->work_part_info= NULL;
 | |
|     create_info->db_type= work_part_info->default_engine_type;
 | |
|     /* NOTE: partitioned temporary tables are not supported. */
 | |
|     DBUG_ASSERT(!create_info->tmp_table());
 | |
|     if (ddl_log_create_table(part_info, create_info->db_type, &new_path,
 | |
|                              &alter_ctx->new_db, &alter_ctx->new_name, true) ||
 | |
|         ERROR_INJECT("create_before_create_frm"))
 | |
|       DBUG_RETURN(TRUE);
 | |
| 
 | |
|     if (mysql_prepare_create_table(thd, create_info, lpt->alter_info,
 | |
|                                    &lpt->db_options, file,
 | |
|                                    &lpt->key_info_buffer, &lpt->key_count,
 | |
|                                    C_ALTER_TABLE))
 | |
|       DBUG_RETURN(TRUE);
 | |
| 
 | |
|     lpt->create_info->table_options= lpt->db_options;
 | |
|     LEX_CUSTRING frm= build_frm_image(thd, alter_ctx->new_name, create_info,
 | |
|                                       lpt->alter_info->create_list,
 | |
|                                       lpt->key_count, lpt->key_info_buffer,
 | |
|                                       file);
 | |
|     if (unlikely(!frm.str))
 | |
|       DBUG_RETURN(TRUE);
 | |
| 
 | |
|     thd->work_part_info= work_part_info;
 | |
|     create_info->db_type= db_type;
 | |
| 
 | |
|     ERROR_INJECT("alter_partition_after_create_frm");
 | |
| 
 | |
|     error= writefile(frm_name, alter_ctx->new_db.str, alter_ctx->new_name.str,
 | |
|                      create_info->tmp_table(), frm.str, frm.length);
 | |
|     my_free((void *) frm.str);
 | |
|     if (unlikely(error) || ERROR_INJECT("alter_partition_after_write_frm"))
 | |
|     {
 | |
|       mysql_file_delete(key_file_frm, frm_name, MYF(0));
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
| 
 | |
|     DBUG_RETURN(false);
 | |
|   }
 | |
|   if (flags & WFRM_BACKUP_ORIGINAL)
 | |
|   {
 | |
|     build_table_filename(path, sizeof(path) - 1, lpt->alter_info->db.str,
 | |
|                          lpt->alter_info->table_name.str, "", 0);
 | |
|     strxnmov(frm_name, sizeof(frm_name), path, reg_ext, NullS);
 | |
| 
 | |
|     build_table_shadow_filename(bak_path, sizeof(bak_path) - 1, lpt, true);
 | |
|     strxmov(bak_frm_name, bak_path, reg_ext, NullS);
 | |
| 
 | |
|     DDL_LOG_MEMORY_ENTRY *main_entry= part_info->main_entry;
 | |
|     mysql_mutex_lock(&LOCK_gdl);
 | |
|     if (write_log_replace_frm(lpt, part_info->list->entry_pos,
 | |
|                               (const char*) bak_path,
 | |
|                               (const char*) path) ||
 | |
|         ddl_log_write_execute_entry(part_info->list->entry_pos, 0,
 | |
|                                     &part_info->execute_entry))
 | |
|     {
 | |
|       mysql_mutex_unlock(&LOCK_gdl);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     mysql_mutex_unlock(&LOCK_gdl);
 | |
|     part_info->main_entry= main_entry;
 | |
|     if (mysql_file_rename(key_file_frm, frm_name, bak_frm_name, MYF(MY_WME)))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     if (lpt->table->file->ha_create_partitioning_metadata(bak_path, path,
 | |
|                                                           CHF_RENAME_FLAG))
 | |
|       DBUG_RETURN(TRUE);
 | |
|   }
 | |
| #else /* !WITH_PARTITION_STORAGE_ENGINE */
 | |
|   DBUG_ASSERT(!(flags & WFRM_BACKUP_ORIGINAL));
 | |
| #endif /* !WITH_PARTITION_STORAGE_ENGINE */
 | |
|   if (flags & WFRM_INSTALL_SHADOW)
 | |
|   {
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|     partition_info *part_info= lpt->part_info;
 | |
| #endif
 | |
|     /*
 | |
|       Build frm file name
 | |
|     */
 | |
|     build_table_filename(path, sizeof(path) - 1, lpt->alter_info->db.str,
 | |
|                          lpt->alter_info->table_name.str, "", 0);
 | |
|     strxnmov(frm_name, sizeof(frm_name)-1, path, reg_ext, NullS);
 | |
|     /*
 | |
|       When we are changing to use new frm file we need to ensure that we
 | |
|       don't collide with another thread in process to open the frm file.
 | |
|       We start by deleting the .frm file and possible .par file. Then we
 | |
|       write to the DDL log that we have completed the delete phase by
 | |
|       increasing the phase of the log entry. Next step is to rename the
 | |
|       new .frm file and the new .par file to the real name. After
 | |
|       completing this we write a new phase to the log entry that will
 | |
|       deactivate it.
 | |
|     */
 | |
|     if (!(flags & WFRM_BACKUP_ORIGINAL) && (
 | |
|         mysql_file_delete(key_file_frm, frm_name, MYF(MY_WME))
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|         || lpt->table->file->ha_create_partitioning_metadata(path, shadow_path,
 | |
|                                                           CHF_DELETE_FLAG) ||
 | |
|         ddl_log_increment_phase(part_info->main_entry->entry_pos) ||
 | |
|         (ddl_log_sync(), FALSE)
 | |
| #endif
 | |
|       ))
 | |
|     {
 | |
|       error= 1;
 | |
|       goto err;
 | |
|     }
 | |
|     if (mysql_file_rename(key_file_frm, shadow_frm_name, frm_name, MYF(MY_WME))
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|         || lpt->table->file->ha_create_partitioning_metadata(path, shadow_path,
 | |
|                                                           CHF_RENAME_FLAG)
 | |
| #endif
 | |
|       )
 | |
|     {
 | |
|       error= 1;
 | |
|       goto err;
 | |
|     }
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|     if (part_info && (flags & WFRM_KEEP_SHARE))
 | |
|     {
 | |
|       TABLE_SHARE *share= lpt->table->s;
 | |
|       char *tmp_part_syntax_str;
 | |
|       part_syntax_buf= generate_partition_syntax_for_frm(lpt->thd,
 | |
|                    part_info, &syntax_len, lpt->create_info, lpt->alter_info);
 | |
|       if (!part_syntax_buf)
 | |
|       {
 | |
|         error= 1;
 | |
|         goto err;
 | |
|       }
 | |
|       if (share->partition_info_buffer_size < syntax_len + 1)
 | |
|       {
 | |
|         share->partition_info_buffer_size= syntax_len+1;
 | |
|         if (!(tmp_part_syntax_str= (char*) strmake_root(&share->mem_root,
 | |
|                                                         part_syntax_buf,
 | |
|                                                         syntax_len)))
 | |
|         {
 | |
|           error= 1;
 | |
|           goto err;
 | |
|         }
 | |
|         share->partition_info_str= tmp_part_syntax_str;
 | |
|       }
 | |
|       else
 | |
|         memcpy((char*) share->partition_info_str, part_syntax_buf,
 | |
|                syntax_len + 1);
 | |
|       share->partition_info_str_len= part_info->part_info_len= syntax_len;
 | |
|       part_info->part_info_string= part_syntax_buf;
 | |
|     }
 | |
| #endif
 | |
| 
 | |
| err:
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|     ddl_log_increment_phase(part_info->main_entry->entry_pos);
 | |
|     part_info->main_entry= NULL;
 | |
|     (void) ddl_log_sync();
 | |
| #endif
 | |
|     ;
 | |
|   }
 | |
| 
 | |
| end:
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   SYNOPSIS
 | |
|     write_bin_log()
 | |
|     thd                           Thread object
 | |
|     clear_error                   is clear_error to be called
 | |
|     query                         Query to log
 | |
|     query_length                  Length of query
 | |
|     is_trans                      if the event changes either
 | |
|                                   a trans or non-trans engine.
 | |
| 
 | |
|   RETURN VALUES
 | |
|     NONE
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Write the binlog if open, routine used in multiple places in this
 | |
|     file
 | |
| */
 | |
| 
 | |
| int write_bin_log(THD *thd, bool clear_error,
 | |
|                   char const *query, ulong query_length, bool is_trans)
 | |
| {
 | |
|   int error= 0;
 | |
|   if (mysql_bin_log.is_open())
 | |
|   {
 | |
|     int errcode= 0;
 | |
|     thd_proc_info(thd, "Writing to binlog");
 | |
|     if (clear_error)
 | |
|     {
 | |
|       if (global_system_variables.log_warnings > 2)
 | |
|       {
 | |
|         uint err_clear= thd->is_error() ? thd->get_stmt_da()->sql_errno() : 0;
 | |
|         if (err_clear)
 | |
|           sql_print_warning("Error code %d of query '%s' is cleared at its "
 | |
|                             "binary logging.", err_clear, query);
 | |
|       }
 | |
|       thd->clear_error();
 | |
|     }
 | |
|     else
 | |
|       errcode= query_error_code(thd, TRUE);
 | |
|     error= thd->binlog_query(THD::STMT_QUERY_TYPE,
 | |
|                              query, query_length, is_trans, FALSE, FALSE,
 | |
|                              errcode) > 0;
 | |
|     thd_proc_info(thd, 0);
 | |
|   }
 | |
|   return error;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Write to binary log the query event whose text is taken from thd->query().
 | |
| 
 | |
|   @param thd                Thread handle.
 | |
|   @param clear_error        Whether to clear out any error from
 | |
|                             execution context and binlog event.
 | |
|   @param is_trans           Whether the query changed transactional data.
 | |
|   @param add_if_exists      True indicates the binary logging of the query
 | |
|                             should be done with "if exists" option.
 | |
|   @param commit_alter       Whether the query should be binlogged as
 | |
|                             Commit Alter.
 | |
| 
 | |
|   @return false             on Success
 | |
|   @return true              otherwise
 | |
| */
 | |
| 
 | |
| int write_bin_log_with_if_exists(THD *thd, bool clear_error,
 | |
|                                  bool is_trans, bool add_if_exists,
 | |
|                                  bool commit_alter)
 | |
| {
 | |
|   int result;
 | |
|   ulonglong save_option_bits= thd->variables.option_bits;
 | |
|   if (add_if_exists)
 | |
|     thd->variables.option_bits|= OPTION_IF_EXISTS;
 | |
|   if (commit_alter)
 | |
|     thd->set_binlog_flags_for_alter(Gtid_log_event::FL_COMMIT_ALTER_E1);
 | |
| 
 | |
|   result= write_bin_log(thd, clear_error, thd->query(), thd->query_length(),
 | |
|                         is_trans);
 | |
|   if (commit_alter)
 | |
|   {
 | |
|     thd->set_binlog_flags_for_alter(0);
 | |
|     thd->set_binlog_start_alter_seq_no(0);
 | |
|   }
 | |
|   thd->variables.option_bits= save_option_bits;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|  delete (drop) tables.
 | |
| 
 | |
|   SYNOPSIS
 | |
|    mysql_rm_table()
 | |
|    thd			Thread handle
 | |
|    tables		List of tables to delete
 | |
|    if_exists		If 1, don't give error if one table doesn't exists
 | |
|    drop_temporary       1 if DROP TEMPORARY
 | |
|    drop_sequence        1 if DROP SEQUENCE
 | |
|    dont_log_query       1 if no write to binary log and no send of ok
 | |
| 
 | |
|   NOTES
 | |
|     Will delete all tables that can be deleted and give a compact error
 | |
|     messages for tables that could not be deleted.
 | |
|     If a table is in use, we will wait for all users to free the table
 | |
|     before dropping it
 | |
| 
 | |
|     Wait if global_read_lock (FLUSH TABLES WITH READ LOCK) is set, but
 | |
|     not if under LOCK TABLES.
 | |
| 
 | |
|   RETURN
 | |
|     FALSE OK.  In this case ok packet is sent to user
 | |
|     TRUE  Error
 | |
| 
 | |
| */
 | |
| 
 | |
| bool mysql_rm_table(THD *thd,TABLE_LIST *tables, bool if_exists,
 | |
|                     bool drop_temporary, bool drop_sequence,
 | |
|                     bool dont_log_query)
 | |
| {
 | |
|   bool error;
 | |
|   Drop_table_error_handler err_handler;
 | |
|   TABLE_LIST *table;
 | |
|   DBUG_ENTER("mysql_rm_table");
 | |
| 
 | |
|   /* Disable drop of enabled log tables, must be done before name locking */
 | |
|   for (table= tables; table; table= table->next_local)
 | |
|   {
 | |
|     if (check_if_log_table(table, TRUE, "DROP"))
 | |
|       DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   if (!drop_temporary)
 | |
|   {
 | |
|     if (!thd->locked_tables_mode)
 | |
|     {
 | |
|       if (drop_sequence)
 | |
|       {
 | |
|         /* We are trying to drop a sequence.
 | |
|            Change all temporary tables that are not sequences to
 | |
|            normal tables so that we can try to drop them instead.
 | |
|            If we don't do this, we will get an error 'not a sequence'
 | |
|            when trying to drop a sequence that is hidden by a temporary
 | |
|            table.
 | |
|         */
 | |
|         for (table= tables; table; table= table->next_global)
 | |
|         {
 | |
|           if (table->open_type == OT_TEMPORARY_OR_BASE &&
 | |
|             is_temporary_table(table) && !table->table->s->sequence)
 | |
|           {
 | |
|             thd->mark_tmp_table_as_free_for_reuse(table->table);
 | |
|             table->table= NULL;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       if (lock_table_names(thd, tables, NULL,
 | |
|                            thd->variables.lock_wait_timeout, 0))
 | |
|         DBUG_RETURN(true);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       for (table= tables; table; table= table->next_local)
 | |
|       {
 | |
|         if (is_temporary_table(table))
 | |
|         {
 | |
|           /*
 | |
|             A temporary table.
 | |
| 
 | |
|             Don't try to find a corresponding MDL lock or assign it
 | |
|             to table->mdl_request.ticket. There can't be metadata
 | |
|             locks for temporary tables: they are local to the session.
 | |
| 
 | |
|             Later in this function we release the MDL lock only if
 | |
|             table->mdl_request.ticket is not NULL. Thus here we
 | |
|             ensure that we won't release the metadata lock on the base
 | |
|             table locked with LOCK TABLES as a side effect of temporary
 | |
|             table drop.
 | |
|           */
 | |
|           DBUG_ASSERT(table->mdl_request.ticket == NULL);
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           /*
 | |
|             Not a temporary table.
 | |
| 
 | |
|             Since 'tables' list can't contain duplicates (this is ensured
 | |
|             by parser) it is safe to cache pointer to the TABLE instances
 | |
|             in its elements.
 | |
|           */
 | |
|           table->table= find_table_for_mdl_upgrade(thd, table->db.str,
 | |
|                                                    table->table_name.str, NULL);
 | |
|           if (!table->table)
 | |
|             DBUG_RETURN(true);
 | |
|           table->mdl_request.ticket= table->table->mdl_ticket;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     /* We remove statistics for table last, after we have the DDL lock */
 | |
|     for (table= tables; table; table= table->next_local)
 | |
|     {
 | |
|       LEX_CSTRING db_name= table->db;
 | |
|       LEX_CSTRING table_name= table->table_name;
 | |
|       if (!is_temporary_table(table))
 | |
|         (void) delete_statistics_for_table(thd, &db_name, &table_name);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* mark for close and remove all cached entries */
 | |
|   thd->push_internal_handler(&err_handler);
 | |
|   error= mysql_rm_table_no_locks(thd, tables, &thd->db, (DDL_LOG_STATE*) 0,
 | |
|                                  if_exists, drop_temporary,
 | |
|                                  false, drop_sequence, dont_log_query,
 | |
|                                  false);
 | |
|   thd->pop_internal_handler();
 | |
| 
 | |
|   if (unlikely(error))
 | |
|     DBUG_RETURN(TRUE);
 | |
|   if (!dont_log_query)
 | |
|     my_ok(thd);
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Find the comment in the query.
 | |
|   That's auxiliary function to be used handling DROP TABLE [comment].
 | |
| 
 | |
|   @param  thd             Thread handler
 | |
|   @param  comment_pos     How many characters to skip before the comment.
 | |
|                           Can be either 9 for DROP TABLE or
 | |
|                           17 for DROP TABLE IF EXISTS
 | |
|   @param  comment_start   returns the beginning of the comment if found.
 | |
| 
 | |
|   @retval  0  no comment found
 | |
|   @retval  >0 the length of the comment found
 | |
| 
 | |
| */
 | |
| static uint32 get_comment(THD *thd, uint32 comment_pos,
 | |
|                           const char **comment_start)
 | |
| {
 | |
|   /* We use uchar * here to make array indexing portable */
 | |
|   const uchar *query= (uchar*) thd->query();
 | |
|   const uchar *query_end= (uchar*) query + thd->query_length();
 | |
|   const uchar *const state_map= thd->charset()->state_map;
 | |
| 
 | |
|   *comment_start= "";                            // Ensure it points to something
 | |
|   for (; query < query_end; query++)
 | |
|   {
 | |
|     if (state_map[static_cast<uchar>(*query)] == MY_LEX_SKIP)
 | |
|       continue;
 | |
|     if (comment_pos-- == 0)
 | |
|       break;
 | |
|   }
 | |
|   if (query > query_end - 3 /* comment can't be shorter than 4 */ ||
 | |
|       state_map[static_cast<uchar>(*query)] != MY_LEX_LONG_COMMENT || query[1] != '*')
 | |
|     return 0;
 | |
|   
 | |
|   *comment_start= (char*) query;
 | |
|   
 | |
|   for (query+= 3; query < query_end; query++)
 | |
|   {
 | |
|     if (query[-1] == '*' && query[0] == '/')
 | |
|       return (uint32)((char*) query - *comment_start + 1);
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Execute the drop of a sequence, view or table (normal or temporary).
 | |
| 
 | |
|   @param  thd             Thread handler
 | |
|   @param  tables          Tables to drop
 | |
|   @param  current_db      Current database, used for ddl logs
 | |
|   @param  ddl_log_state   DDL log state, for global ddl logging (used by
 | |
|                           DROP DATABASE. If not set, an internal ddl log state
 | |
|                           will be used.  If set then the caller must call
 | |
|                           ddl_log_complete(ddl_log_state);
 | |
|   @param  if_exists       If set, don't give an error if table doesn't exists.
 | |
|                           In this case we give an warning of level 'NOTE'
 | |
|   @param  drop_temporary  Only drop temporary tables
 | |
|   @param  drop_view       Allow to delete VIEW .frm
 | |
|   @param  dont_log_query  Don't write query to log files. This will also not
 | |
|                           generate warnings if the handler files doesn't exists
 | |
|   @param  dont_free_locks Don't do automatic UNLOCK TABLE if no more locked
 | |
|                           tables
 | |
| 
 | |
|   @retval  0  ok
 | |
|   @retval  1  Error
 | |
|   @retval -1  Thread was killed
 | |
| 
 | |
|   @note This function assumes that metadata locks have already been taken.
 | |
|         It is also assumed that the tables have been removed from TDC.
 | |
| 
 | |
|   @note This function assumes that temporary tables to be dropped have
 | |
|         been pre-opened using corresponding table list elements.
 | |
| 
 | |
|   @todo When logging to the binary log, we should log
 | |
|         tmp_tables and transactional tables as separate statements if we
 | |
|         are in a transaction;  This is needed to get these tables into the
 | |
|         cached binary log that is only written on COMMIT.
 | |
|         The current code only writes DROP statements that only uses temporary
 | |
|         tables to the cache binary log.  This should be ok on most cases, but
 | |
|         not all.
 | |
| */
 | |
| 
 | |
| int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables,
 | |
|                             const LEX_CSTRING *current_db,
 | |
|                             DDL_LOG_STATE *ddl_log_state, bool if_exists,
 | |
|                             bool drop_temporary, bool drop_view,
 | |
|                             bool drop_sequence, bool dont_log_query,
 | |
|                             bool dont_free_locks)
 | |
| {
 | |
|   TABLE_LIST *table;
 | |
|   char path[FN_REFLEN + 1];
 | |
|   LEX_CSTRING alias= null_clex_str;
 | |
|   LEX_CUSTRING version= {0, 0};
 | |
|   StringBuffer<160> unknown_tables(system_charset_info);
 | |
|   DDL_LOG_STATE local_ddl_log_state;
 | |
|   const char *comment_start;
 | |
|   uint table_count= 0, non_temp_tables_count= 0;
 | |
|   int error= 0;
 | |
|   size_t comment_len;
 | |
|   bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0;
 | |
|   bool is_drop_tmp_if_exists_added= 0, non_tmp_table_deleted= 0;
 | |
|   bool log_if_exists= if_exists;
 | |
|   const LEX_CSTRING *object_to_drop= drop_sequence ? &SEQUENCE_clex_str
 | |
|                                                    : &TABLE_clex_str;
 | |
|   String normal_tables, built_trans_tmp_query, built_non_trans_tmp_query;
 | |
|   DBUG_ENTER("mysql_rm_table_no_locks");
 | |
| 
 | |
|   if (!ddl_log_state)
 | |
|   {
 | |
|     ddl_log_state= &local_ddl_log_state;
 | |
|     bzero(ddl_log_state, sizeof(*ddl_log_state));
 | |
|   }
 | |
| 
 | |
|   unknown_tables.length(0);
 | |
|   comment_len= get_comment(thd, if_exists ? 17:9, &comment_start);
 | |
| 
 | |
|   /*
 | |
|     Prepares the drop statements that will be written into the binary
 | |
|     log as follows:
 | |
| 
 | |
|     1 - If we are not processing a "DROP TEMPORARY" it prepares a
 | |
|     "DROP".
 | |
| 
 | |
|     2 - A "DROP" may result in a "DROP TEMPORARY" but the opposite is
 | |
|     not true.
 | |
| 
 | |
|     3 - If the current format is row, the IF EXISTS token needs to be
 | |
|     appended because one does not know if CREATE TEMPORARY was previously
 | |
|     written to the binary log.
 | |
| 
 | |
|     4 - Add the IF_EXISTS token if necessary, i.e. if_exists is TRUE.
 | |
| 
 | |
|     5 - For temporary tables, there is a need to differentiate tables
 | |
|     in transactional and non-transactional storage engines. For that,
 | |
|     reason, two types of drop statements are prepared.
 | |
| 
 | |
|     The need to different the type of tables when dropping a temporary
 | |
|     table stems from the fact that such drop does not commit an ongoing
 | |
|     transaction and changes to non-transactional tables must be written
 | |
|     ahead of the transaction in some circumstances.
 | |
| 
 | |
|     6- Slave SQL thread ignores all replicate-* filter rules
 | |
|     for temporary tables with 'IF EXISTS' clause. (See sql/sql_parse.cc:
 | |
|     mysql_execute_command() for details). These commands will be binlogged
 | |
|     as they are, even if the default database (from USE `db`) is not present
 | |
|     on the Slave. This can cause point in time recovery failures later
 | |
|     when user uses the slave's binlog to re-apply. Hence at the time of binary
 | |
|     logging, these commands will be written with fully qualified table names
 | |
|     and use `db` will be suppressed.
 | |
|   */
 | |
| 
 | |
|   normal_tables.set_charset(thd->charset());
 | |
|   if (!dont_log_query)
 | |
|   {
 | |
|     built_trans_tmp_query.set_charset(system_charset_info);
 | |
|     built_trans_tmp_query.append(STRING_WITH_LEN("DROP TEMPORARY "));
 | |
|     built_trans_tmp_query.append(object_to_drop);
 | |
|     built_trans_tmp_query.append(' ');
 | |
|     /*
 | |
|       Better to always use IF EXISTS for temporary tables to handle the case
 | |
|       where the slave died after it created it's temporary tables
 | |
|     */
 | |
|     is_drop_tmp_if_exists_added= true;
 | |
|     built_trans_tmp_query.append(STRING_WITH_LEN("IF EXISTS "));
 | |
|     built_non_trans_tmp_query.set_charset(system_charset_info);
 | |
|     built_non_trans_tmp_query.copy(built_trans_tmp_query);
 | |
|   }
 | |
| 
 | |
|   for (table= tables; table; table= table->next_local)
 | |
|   {
 | |
|     bool is_trans= 0, temporary_table_was_dropped= 0;
 | |
|     bool table_creation_was_logged= 0;
 | |
|     bool wrong_drop_sequence= 0;
 | |
|     bool table_dropped= 0, res;
 | |
|     bool is_temporary= 0;
 | |
|     const LEX_CSTRING db= table->db;
 | |
|     const LEX_CSTRING table_name= table->table_name;
 | |
|     LEX_CSTRING cpath= {0,0};
 | |
|     LEX_CSTRING partition_engine_name= {NULL, 0};
 | |
|     handlerton *hton= 0;
 | |
|     Table_type table_type;
 | |
|     size_t path_length= 0;
 | |
|     char *path_end= 0;
 | |
|     uint drop_index_from= 0, drop_index_to=0;
 | |
| 
 | |
|     error= 0;
 | |
| 
 | |
|     DBUG_PRINT("table", ("table_l: '%s'.'%s'  table: %p  s: %p",
 | |
|                          db.str, table_name.str,  table->table,
 | |
|                          table->table ?  table->table->s : NULL));
 | |
| 
 | |
|     /*
 | |
|       If we are in locked tables mode and are dropping a temporary table,
 | |
|       the ticket should be NULL to ensure that we don't release a lock
 | |
|       on a base table later.
 | |
|     */
 | |
|     DBUG_ASSERT(!(thd->locked_tables_mode &&
 | |
|                   table->open_type != OT_BASE_ONLY &&
 | |
|                   thd->find_temporary_table(table) &&
 | |
|                   table->mdl_request.ticket != NULL));
 | |
| 
 | |
|     if (drop_sequence && table->table &&
 | |
|         table->table->s->table_type != TABLE_TYPE_SEQUENCE)
 | |
|     {
 | |
|       if (if_exists)
 | |
|       {
 | |
|         char buff[FN_REFLEN];
 | |
|         String tbl_name(buff, sizeof(buff), system_charset_info);
 | |
|         tbl_name.length(0);
 | |
|         tbl_name.append(&db);
 | |
|         tbl_name.append('.');
 | |
|         tbl_name.append(&table->table_name);
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_NOT_SEQUENCE2, ER_THD(thd, ER_NOT_SEQUENCE2),
 | |
|                             tbl_name.c_ptr_safe());
 | |
| 
 | |
|         /*
 | |
|           Our job is done here. This statement was added to avoid executing
 | |
|           unnecessary code farther below which in some strange corner cases
 | |
|           caused the server to crash (see MDEV-17896).
 | |
|         */
 | |
|         continue;
 | |
|       }
 | |
|       /* "DROP SEQUENCE" but a sequence table was not found */
 | |
|       unknown_tables.append(&db);
 | |
|       unknown_tables.append('.');
 | |
|       unknown_tables.append(&table_name);
 | |
|       unknown_tables.append(',');
 | |
|       error= ENOENT;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     /* First try to delete temporary tables and temporary sequences */
 | |
|     if (table->open_type != OT_BASE_ONLY && is_temporary_table(table))
 | |
|     {
 | |
|       table_creation_was_logged= table->table->s->table_creation_was_logged;
 | |
|       if (thd->drop_temporary_table(table->table, &is_trans, true))
 | |
|         error= 1;
 | |
|       else
 | |
|       {
 | |
|         table->table= 0;
 | |
|         temporary_table_was_dropped= 1;
 | |
|       }
 | |
|       is_temporary= 1;
 | |
|       thd->reset_sp_cache= true;
 | |
|     }
 | |
| 
 | |
|     if ((drop_temporary && if_exists) || temporary_table_was_dropped)
 | |
|     {
 | |
|       /*
 | |
|         This handles the case of temporary tables. We have the following cases:
 | |
| 
 | |
|           - "DROP TEMPORARY" was executed and table was dropped
 | |
|             temporary_table_was_dropped == 1
 | |
|           - "DROP TEMPORARY IF EXISTS" was specified but no temporary table
 | |
|             existed
 | |
|             temporary_table_was_dropped == 0
 | |
|       */
 | |
|       if (!dont_log_query && table_creation_was_logged)
 | |
|       {
 | |
|         if (is_trans)
 | |
|           trans_tmp_table_deleted= TRUE;
 | |
|         else
 | |
|           non_trans_tmp_table_deleted= TRUE;
 | |
| 
 | |
|         String *built_ptr_query=
 | |
|           (is_trans ? &built_trans_tmp_query : &built_non_trans_tmp_query);
 | |
|         /*
 | |
|           Write the database name if it is not the current one or if
 | |
|           thd->db is NULL or 'IF EXISTS' clause is present in 'DROP TEMPORARY'
 | |
|           query.
 | |
|         */
 | |
|         if (thd->db.str == NULL || cmp(&db, &thd->db) ||
 | |
|             is_drop_tmp_if_exists_added )
 | |
|         {
 | |
|           append_identifier(thd, built_ptr_query, &db);
 | |
|           built_ptr_query->append('.');
 | |
|         }
 | |
|         append_identifier(thd, built_ptr_query, &table_name);
 | |
|         built_ptr_query->append(',');
 | |
|       }
 | |
|       /*
 | |
|         This means that a temporary table was droped and as such there
 | |
|         is no need to proceed with the code that tries to drop a regular
 | |
|         table.
 | |
|       */
 | |
|       if (temporary_table_was_dropped)
 | |
|         continue;
 | |
|     }
 | |
|     else if (!drop_temporary)
 | |
|     {
 | |
|       non_temp_tables_count++;
 | |
| 
 | |
|       DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db.str,
 | |
|                                                  table_name.str, MDL_SHARED));
 | |
| 
 | |
|       alias= (lower_case_table_names == 2) ? table->alias : table_name;
 | |
|       /* remove .frm file and engine files */
 | |
|       path_length= build_table_filename(path, sizeof(path) - 1, db.str,
 | |
|                                         alias.str, reg_ext, 0);
 | |
|       path_end= path + path_length - reg_ext_length;
 | |
|     }
 | |
| 
 | |
|     DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table");
 | |
|     if (drop_temporary)
 | |
|     {
 | |
|       /* "DROP TEMPORARY" but a temporary table was not found */
 | |
|       unknown_tables.append(&db);
 | |
|       unknown_tables.append('.');
 | |
|       unknown_tables.append(&table_name);
 | |
|       unknown_tables.append(',');
 | |
|       error= ENOENT;
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     cpath = { path, (size_t)(path_end - path) };
 | |
| 
 | |
|     Dummy_error_handler err_handler;
 | |
|     thd->push_internal_handler(&err_handler);
 | |
|     if (TABLE_SHARE *share= tdc_acquire_share(thd, table, GTS_TABLE))
 | |
|     {
 | |
|       table_type= share->table_type;
 | |
|       hton= plugin_hton(plugin_lock(thd, share->db_plugin));
 | |
|       version= thd->strmake_lex_custring(share->tabledef_version);
 | |
|       if (plugin_ref pp= IF_PARTITIONING(share->default_part_plugin, NULL))
 | |
|         partition_engine_name= thd->strmake_lex_cstring(*plugin_name(pp));
 | |
|       drop_index_from= share->keys;
 | |
|       drop_index_to= share->total_keys;
 | |
|       tdc_release_share(share);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       switch (err_handler.got_error()) {
 | |
|       case ER_WRONG_OBJECT:
 | |
|         table_type= TABLE_TYPE_VIEW;
 | |
|         break;
 | |
|       case ER_NO_SUCH_TABLE: /* no .frm */
 | |
|         table_type= TABLE_TYPE_UNKNOWN;
 | |
|         break;
 | |
|       case ER_NOT_FORM_FILE:
 | |
|       {
 | |
|         /*
 | |
|           cannot open the frm, let's at least get the engine
 | |
|           see main.partition_not_blackhole and connect.drop-open-error
 | |
|         */
 | |
|         char engine_buf[NAME_CHAR_LEN + 1];
 | |
|         LEX_CSTRING engine= { engine_buf, 0 };
 | |
|         table_type= dd_frm_type(thd, path, &engine, &version);
 | |
|         if (table_type == TABLE_TYPE_NORMAL || table_type == TABLE_TYPE_SEQUENCE)
 | |
|         {
 | |
|           plugin_ref p= plugin_lock_by_name(thd, &engine,
 | |
|                                            MYSQL_STORAGE_ENGINE_PLUGIN);
 | |
|           hton= p ? plugin_hton(p) : NULL;
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|       case ER_FAILED_READ_FROM_PAR_FILE: /* no .par file */
 | |
|         hton= partition_hton;
 | |
|         /* fall through */
 | |
|       default: /* unreadable (corrupted?) .frm, still need to DROP it */
 | |
|         table_type= TABLE_TYPE_NORMAL;
 | |
|       }
 | |
|     }
 | |
|     thd->pop_internal_handler();
 | |
| 
 | |
|     thd->replication_flags= 0;
 | |
|     const bool was_view= table_type == TABLE_TYPE_VIEW;
 | |
| 
 | |
|     if (!table_count++)
 | |
|     {
 | |
|       LEX_CSTRING comment= {comment_start, comment_len};
 | |
|       if (ddl_log_drop_table_init(ddl_log_state, current_db, &comment))
 | |
|       {
 | |
|         error= 1;
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if ((table_type == TABLE_TYPE_UNKNOWN) || (was_view && !drop_view) ||
 | |
|         (table_type != TABLE_TYPE_SEQUENCE && drop_sequence))
 | |
|     {
 | |
|       /*
 | |
|         One of the following cases happened:
 | |
|           . "DROP" but table was not found
 | |
|           . "DROP TABLE" statement, but it's a view.
 | |
|           . "DROP SEQUENCE", but it's not a sequence
 | |
|       */
 | |
|       wrong_drop_sequence= drop_sequence && hton;
 | |
|       error= table_type == TABLE_TYPE_UNKNOWN ? ENOENT : -1;
 | |
|       tdc_remove_table(thd, db.str, table_name.str);
 | |
|       if (wrong_drop_sequence)
 | |
|         goto report_error;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
| #ifdef WITH_WSREP
 | |
|       if (WSREP(thd) && hton)
 | |
|       {
 | |
|         handlerton *ht= hton;
 | |
|         // For partitioned tables resolve underlying handlerton
 | |
|         if (table->table && table->table->file->partition_ht())
 | |
|           ht= table->table->file->partition_ht();
 | |
|         if (!wsrep_should_replicate_ddl(thd, ht))
 | |
|         {
 | |
|           error= 1;
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
| #endif /* WITH_WSREP */
 | |
| 
 | |
|       error= -1;
 | |
|       if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
 | |
|           thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
 | |
|       {
 | |
|         if (wait_while_table_is_used(thd, table->table, HA_EXTRA_NOT_USED))
 | |
|           goto err;
 | |
|         close_all_tables_for_name(thd, table->table->s,
 | |
|                                   HA_EXTRA_PREPARE_FOR_DROP, NULL);
 | |
|         table->table= 0;
 | |
|       }
 | |
|       else
 | |
|         tdc_remove_table(thd, db.str, table_name.str);
 | |
| 
 | |
|       /* Check that we have an exclusive lock on the table to be dropped. */
 | |
|       DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, db.str,
 | |
|                                                 table_name.str, MDL_EXCLUSIVE));
 | |
| 
 | |
|       // Remove extension for delete
 | |
|       *path_end= '\0';
 | |
| 
 | |
|       if (hton && hton->flags & HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE)
 | |
|         log_if_exists= 1;
 | |
| 
 | |
|       bool enoent_warning= !dont_log_query && !(hton && hton->discover_table);
 | |
| 
 | |
|       if (was_view)
 | |
|         res= ddl_log_drop_view(ddl_log_state, &cpath, &db, &table_name);
 | |
|       else
 | |
|         res= ddl_log_drop_table(ddl_log_state, hton, &cpath, &db, &table_name);
 | |
|       if (res)
 | |
|         goto err;
 | |
| 
 | |
|       debug_crash_here("ddl_log_drop_before_delete_hlindex");
 | |
|       if (path + sizeof(path) > path_end + HLINDEX_BUF_LEN)
 | |
|       {
 | |
|         for (uint i= drop_index_from; i < drop_index_to; i++)
 | |
|         {
 | |
|           my_snprintf(path_end, HLINDEX_BUF_LEN, HLINDEX_TEMPLATE, i);
 | |
|           int err= ha_delete_table(thd, hton, path, &db, &table_name, enoent_warning);
 | |
|           set_if_bigger(error, err);
 | |
|         }
 | |
|         *path_end= 0;
 | |
|       }
 | |
| 
 | |
|       debug_crash_here("ddl_log_drop_before_delete_table");
 | |
|       {
 | |
|         int err= ha_delete_table(thd, hton, path, &db, &table_name, enoent_warning);
 | |
|         set_if_bigger(error, err);
 | |
|       }
 | |
|       debug_crash_here("ddl_log_drop_after_delete_table");
 | |
| 
 | |
|       if (!error)
 | |
|         table_dropped= 1;
 | |
|       else if (error < 0)
 | |
|         error= 0;                            // Table didn't exists
 | |
|       else if (error)
 | |
|       {
 | |
|         if (thd->is_killed())
 | |
|         {
 | |
|           error= -1;
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|         Delete the .frm file if we managed to delete the table from the
 | |
|         engine or the table didn't exists in the engine
 | |
|       */
 | |
|       strmov(path_end, reg_ext);
 | |
|       if ((likely(!error) || non_existing_table_error(error)) &&
 | |
|           !access(path, F_OK))
 | |
|       {
 | |
|         int frm_delete_error= 0;
 | |
|         /* Delete the table definition file */
 | |
|         if (hton && (hton->discover_table || error))
 | |
|         {
 | |
|           /*
 | |
|             Table type is using discovery and may not need a .frm file
 | |
|             or the .frm file existed but no table in engine.
 | |
|             Delete it silently if it exists
 | |
|           */
 | |
|           if (mysql_file_delete(key_file_frm, path,
 | |
|                                 MYF(MY_WME | MY_IGNORE_ENOENT)))
 | |
|             frm_delete_error= my_errno;
 | |
|         }
 | |
|         else if (unlikely(mysql_file_delete(key_file_frm, path,
 | |
|                                             !error ? MYF(MY_WME) :
 | |
|                                             MYF(MY_WME | MY_IGNORE_ENOENT))))
 | |
|         {
 | |
|           frm_delete_error= my_errno;
 | |
|           DBUG_ASSERT(frm_delete_error);
 | |
|         }
 | |
| 
 | |
|         if (frm_delete_error)
 | |
|         {
 | |
|           /* Remember unexpected error from dropping the .frm file */
 | |
|           error= frm_delete_error;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           error= 0;                         // We succeeded to delete the frm
 | |
|           table_dropped= 1;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       If there was no .frm file and the table is not temporary,
 | |
|       scan all engines try to drop the table from there.
 | |
|       This is to ensure we don't have any partial table files left.
 | |
|     */
 | |
|     if (non_existing_table_error(error))
 | |
|     {
 | |
|       int ferror= 0;
 | |
|       DBUG_ASSERT(!was_view);
 | |
| 
 | |
|       if (ddl_log_drop_table(ddl_log_state, 0, &cpath, &db, &table_name))
 | |
|       {
 | |
|         error= -1;
 | |
|         goto err;
 | |
|       }
 | |
| 
 | |
|       /* Remove extension for delete */
 | |
|       *path_end= '\0';
 | |
|       ferror= ha_delete_table_force(thd, path, &db, &table_name);
 | |
|       if (!ferror)
 | |
|       {
 | |
|         table_dropped= 1;
 | |
|         error= 0;
 | |
|       }
 | |
|       if (ferror <= 0)
 | |
|       {
 | |
|         ferror= 0;                              // Ignore table not found
 | |
| 
 | |
|         /* Delete the frm file again (just in case it was rediscovered) */
 | |
|         strmov(path_end, reg_ext);
 | |
|         if (mysql_file_delete(key_file_frm, path, MYF(MY_WME|MY_IGNORE_ENOENT)))
 | |
|           ferror= my_errno;
 | |
|       }
 | |
|       if (!error)
 | |
|         error= ferror;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       This may be set
 | |
|        - by the storage engine in handler::delete_table()
 | |
|        - when deleting a table without .frm file: delete_table_force() will
 | |
|          check if the storage engine that had the table had
 | |
|          HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE flag
 | |
|     */
 | |
|     if (thd->replication_flags & OPTION_IF_EXISTS)
 | |
|       log_if_exists= 1;
 | |
| 
 | |
|     if (!was_view)
 | |
|     {
 | |
|       debug_crash_here("ddl_log_drop_before_drop_trigger");
 | |
|       ddl_log_update_phase(ddl_log_state, DDL_DROP_PHASE_TRIGGER);
 | |
|       debug_crash_here("ddl_log_drop_before_drop_trigger2");
 | |
|     }
 | |
| 
 | |
|     if (likely(!error) || non_existing_table_error(error))
 | |
|     {
 | |
|       if (Table_triggers_list::drop_all_triggers(thd, &db, &table_name,
 | |
|                                                MYF(MY_WME | MY_IGNORE_ENOENT)))
 | |
|         error= error ? error : -1;
 | |
|     }
 | |
|     debug_crash_here("ddl_log_drop_after_drop_trigger");
 | |
| 
 | |
| report_error:
 | |
|     if (error)
 | |
|     {
 | |
|       StringBuffer<FN_REFLEN> tbl_name(system_charset_info);
 | |
|       uint is_note= (if_exists && (was_view || wrong_drop_sequence) ?
 | |
|                      ME_NOTE : 0);
 | |
| 
 | |
|       tbl_name.append(&db);
 | |
|       tbl_name.append('.');
 | |
|       tbl_name.append(&table_name);
 | |
| 
 | |
|       if (!non_existing_table_error(error) || is_note)
 | |
|       {
 | |
|         /*
 | |
|           Error from engine already given. Here we only have to take
 | |
|           care about errors for trying to drop view or sequence
 | |
|         */
 | |
|         if (was_view)
 | |
|           my_error(ER_IT_IS_A_VIEW, MYF(is_note), tbl_name.c_ptr_safe());
 | |
|         else if (wrong_drop_sequence)
 | |
|           my_error(ER_NOT_SEQUENCE2, MYF(is_note), tbl_name.c_ptr_safe());
 | |
|         if (is_note)
 | |
|           error= ENOENT;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (unknown_tables.append(tbl_name) || unknown_tables.append(','))
 | |
|         {
 | |
|           error= 1;
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Don't give an error if we are using IF EXISTS for a table that
 | |
|       didn't exists
 | |
|     */
 | |
|     if (if_exists && non_existing_table_error(error))
 | |
|       error= 0;
 | |
| 
 | |
|     if (!error && table_dropped)
 | |
|     {
 | |
|       PSI_CALL_drop_table_share(temporary_table_was_dropped,
 | |
|                                 db.str, (uint)db.length,
 | |
|                                 table_name.str, (uint)table_name.length);
 | |
|       mysql_audit_drop_table(thd, table);
 | |
|       if (!is_temporary)
 | |
|       {
 | |
|         backup_log_info ddl_log;
 | |
|         bzero(&ddl_log, sizeof(ddl_log));
 | |
|         ddl_log.query= { C_STRING_WITH_LEN("DROP") };
 | |
|         if ((ddl_log.org_partitioned= (partition_engine_name.str != 0)))
 | |
|           ddl_log.org_storage_engine_name= partition_engine_name;
 | |
|         else
 | |
|           lex_string_set(&ddl_log.org_storage_engine_name,
 | |
|                          ha_resolve_storage_engine_name(hton));
 | |
|         ddl_log.org_database=     table->db;
 | |
|         ddl_log.org_table=        table->table_name;
 | |
|         ddl_log.org_table_id=     version;
 | |
|         backup_log_ddl(&ddl_log);
 | |
|       }
 | |
|     }
 | |
|     if (!was_view)
 | |
|       ddl_log_update_phase(ddl_log_state, DDL_DROP_PHASE_BINLOG);
 | |
| 
 | |
|     if (!dont_log_query &&
 | |
|         (!error || table_dropped || non_existing_table_error(error)))
 | |
|     {
 | |
|       non_tmp_table_deleted|= (if_exists || table_dropped);
 | |
|       /*
 | |
|          Don't write the database name if it is the current one (or if
 | |
|          thd->db is NULL).
 | |
|        */
 | |
|       if (thd->db.str == NULL || cmp(&db, &thd->db) != 0)
 | |
|       {
 | |
|         append_identifier(thd, &normal_tables, &db);
 | |
|         normal_tables.append('.');
 | |
|       }
 | |
| 
 | |
|       append_identifier(thd, &normal_tables, &table_name);
 | |
|       normal_tables.append(',');
 | |
|     }
 | |
|     DBUG_PRINT("table", ("table: %p  s: %p", table->table,
 | |
|                          table->table ?  table->table->s :  NULL));
 | |
|     if (is_temporary_table(table))
 | |
|       thd->reset_sp_cache= true;
 | |
|   }
 | |
|   DEBUG_SYNC(thd, "rm_table_no_locks_before_binlog");
 | |
| 
 | |
|   thd->used|= THD::THREAD_SPECIFIC_USED;
 | |
|   error= 0;
 | |
| 
 | |
| err:
 | |
|   if (unknown_tables.length() > 1)
 | |
|   {
 | |
|     uint is_note= if_exists ? ME_NOTE : 0;
 | |
|     unknown_tables.chop();
 | |
|     my_error((drop_sequence ? ER_UNKNOWN_SEQUENCES : ER_BAD_TABLE_ERROR),
 | |
|              MYF(is_note), unknown_tables.c_ptr_safe());
 | |
|   }
 | |
|   error= thd->is_error();
 | |
| 
 | |
|   if (non_temp_tables_count)
 | |
|     query_cache_invalidate3(thd, tables, 0);
 | |
| 
 | |
|   if (non_trans_tmp_table_deleted ||
 | |
|       trans_tmp_table_deleted || non_tmp_table_deleted)
 | |
|   {
 | |
|     if (non_trans_tmp_table_deleted || trans_tmp_table_deleted)
 | |
|       thd->transaction->stmt.mark_dropped_temp_table();
 | |
| 
 | |
|     if (!dont_log_query && mysql_bin_log.is_open())
 | |
|     {
 | |
|       debug_crash_here("ddl_log_drop_before_binlog");
 | |
|       if (non_trans_tmp_table_deleted)
 | |
|       {
 | |
|         /* Chop of the last comma */
 | |
|         built_non_trans_tmp_query.chop();
 | |
|         built_non_trans_tmp_query.append(generated_by_server);
 | |
|         error |= (thd->binlog_query(THD::STMT_QUERY_TYPE,
 | |
|                                     built_non_trans_tmp_query.ptr(),
 | |
|                                     built_non_trans_tmp_query.length(), FALSE,
 | |
|                                     FALSE, is_drop_tmp_if_exists_added,
 | |
|                                     0) > 0);
 | |
|       }
 | |
|       if (trans_tmp_table_deleted)
 | |
|       {
 | |
|         /* Chop of the last comma */
 | |
|         built_trans_tmp_query.chop();
 | |
|         built_trans_tmp_query.append(generated_by_server);
 | |
|         error |= (thd->binlog_query(THD::STMT_QUERY_TYPE,
 | |
|                                     built_trans_tmp_query.ptr(),
 | |
|                                     built_trans_tmp_query.length(), TRUE,
 | |
|                                     FALSE, is_drop_tmp_if_exists_added,
 | |
|                                     0) > 0);
 | |
|       }
 | |
|       if (non_tmp_table_deleted)
 | |
|       {
 | |
|         String built_query;
 | |
| 
 | |
|         built_query.set_charset(thd->charset());
 | |
|         built_query.append(STRING_WITH_LEN("DROP "));
 | |
|         built_query.append(object_to_drop);
 | |
|         built_query.append(' ');
 | |
|         if (log_if_exists)
 | |
|           built_query.append(STRING_WITH_LEN("IF EXISTS "));
 | |
| 
 | |
|         /* Preserve comment in original query */
 | |
|         if (comment_len)
 | |
|         {
 | |
|           built_query.append(comment_start, comment_len);
 | |
|           built_query.append(' ');
 | |
|         }
 | |
| 
 | |
|         /* Chop of the last comma */
 | |
|         normal_tables.chop();
 | |
|         built_query.append(normal_tables.ptr(), normal_tables.length());
 | |
|         built_query.append(generated_by_server);
 | |
|         thd->binlog_xid= thd->query_id;
 | |
|         ddl_log_update_xid(ddl_log_state, thd->binlog_xid);
 | |
|         error |= (thd->binlog_query(THD::STMT_QUERY_TYPE,
 | |
|                                     built_query.ptr(), built_query.length(),
 | |
|                                     TRUE, FALSE, FALSE, 0) > 0);
 | |
|         thd->binlog_xid= 0;
 | |
|       }
 | |
|       debug_crash_here("ddl_log_drop_after_binlog");
 | |
|     }
 | |
|   }
 | |
|   if (ddl_log_state == &local_ddl_log_state)
 | |
|     ddl_log_complete(ddl_log_state);
 | |
| 
 | |
|   if (!drop_temporary)
 | |
|   {
 | |
|     /*
 | |
|       Under LOCK TABLES we should release meta-data locks on the tables
 | |
|       which were dropped.
 | |
| 
 | |
|       Leave LOCK TABLES mode if we managed to drop all tables which were
 | |
|       locked. Additional check for 'non_temp_tables_count' is to avoid
 | |
|       leaving LOCK TABLES mode if we have dropped only temporary tables.
 | |
|     */
 | |
|     if (thd->locked_tables_mode)
 | |
|     {
 | |
|       if (thd->lock && thd->lock->table_count == 0 &&
 | |
|           non_temp_tables_count > 0 && !dont_free_locks)
 | |
|       {
 | |
|         if (thd->locked_tables_list.unlock_locked_tables(thd))
 | |
|           error= 1;
 | |
|         goto end;
 | |
|       }
 | |
|       for (table= tables; table; table= table->next_local)
 | |
|       {
 | |
|         /* Drop locks for all successfully dropped tables. */
 | |
|         if (table->table == NULL && table->mdl_request.ticket)
 | |
|         {
 | |
|           /*
 | |
|             Under LOCK TABLES we may have several instances of table open
 | |
|             and locked and therefore have to remove several metadata lock
 | |
|             requests associated with them.
 | |
|           */
 | |
|           thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     /*
 | |
|       Rely on the caller to implicitly commit the transaction
 | |
|       and release metadata locks.
 | |
|     */
 | |
|   }
 | |
| 
 | |
| end:
 | |
|   DBUG_RETURN(error || thd->is_error());
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Log the drop of a table.
 | |
| 
 | |
|   @param thd	           Thread handler
 | |
|   @param db_name           Database name
 | |
|   @param table_name        Table name
 | |
|   @param temporary_table   1 if table was a temporary table
 | |
| 
 | |
|   This code is only used in the case of failed CREATE OR REPLACE TABLE
 | |
|   when the original table was dropped but we could not create the new one.
 | |
| */
 | |
| 
 | |
| bool log_drop_table(THD *thd, const LEX_CSTRING *db_name,
 | |
|                     const LEX_CSTRING *table_name,
 | |
|                     const LEX_CSTRING *handler_name,
 | |
|                     bool partitioned,
 | |
|                     const LEX_CUSTRING *id,
 | |
|                     bool temporary_table)
 | |
| {
 | |
|   char buff[NAME_LEN*2 + 80];
 | |
|   String query(buff, sizeof(buff), system_charset_info);
 | |
|   bool error= 0;
 | |
|   DBUG_ENTER("log_drop_table");
 | |
| 
 | |
|   if (mysql_bin_log.is_open())
 | |
|   {
 | |
|     query.length(0);
 | |
|     query.append(STRING_WITH_LEN("DROP "));
 | |
|     if (temporary_table)
 | |
|       query.append(STRING_WITH_LEN("TEMPORARY "));
 | |
|     query.append(STRING_WITH_LEN("TABLE IF EXISTS "));
 | |
|     append_identifier(thd, &query, db_name);
 | |
|     query.append('.');
 | |
|     append_identifier(thd, &query, table_name);
 | |
|     query.append(STRING_WITH_LEN("/* Generated to handle "
 | |
|                                  "failed CREATE OR REPLACE */"));
 | |
| 
 | |
|     /*
 | |
|       In case of temporary tables we don't have to log the database name
 | |
|       in the binary log. We log this for non temporary tables, as the slave
 | |
|       may use a filter to ignore queries for a specific database.
 | |
|     */
 | |
|     error= thd->binlog_query(THD::STMT_QUERY_TYPE,
 | |
|                              query.ptr(), query.length(),
 | |
|                              FALSE, FALSE, temporary_table, 0) > 0;
 | |
|   }
 | |
|   if (!temporary_table)
 | |
|   {
 | |
|     backup_log_info ddl_log;
 | |
|     bzero(&ddl_log, sizeof(ddl_log));
 | |
|     ddl_log.query= { C_STRING_WITH_LEN("DROP_AFTER_CREATE") };
 | |
|     ddl_log.org_storage_engine_name= *handler_name;
 | |
|     ddl_log.org_partitioned=  partitioned;
 | |
|     ddl_log.org_database=     *db_name;
 | |
|     ddl_log.org_table=        *table_name;
 | |
|     ddl_log.org_table_id=     *id;
 | |
|     backup_log_ddl(&ddl_log);
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| int get_hlindex_keys_by_open(THD *thd, const LEX_CSTRING *db,
 | |
|                              const LEX_CSTRING *table_name,
 | |
|                              const char *path, uint *keys,
 | |
|                              uint *total_keys)
 | |
| {
 | |
|   TABLE_SHARE share;
 | |
|   int error;
 | |
|   DBUG_ENTER("get_hlindex_keys_by_open");
 | |
| 
 | |
|   init_tmp_table_share(thd, &share, db->str, 0, table_name->str, path, 1);
 | |
|   error= open_table_def(thd, &share, GTS_TABLE | GTS_USE_DISCOVERY);
 | |
|   if (!error)
 | |
|   {
 | |
|     *keys= share.keys;
 | |
|     *total_keys= share.total_keys;
 | |
|   }
 | |
|   free_table_share(&share);
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Quickly remove a table, without any logging
 | |
| 
 | |
|   @param thd         Thread context.
 | |
|   @param base        The handlerton handle.
 | |
|   @param db          The database name.
 | |
|   @param table_name  The table name.
 | |
|   @param flags       Flags for build_table_filename() as well as describing
 | |
|                      if handler files / .FRM should be deleted as well.
 | |
| 
 | |
|   @return False in case of success, True otherwise.
 | |
| */
 | |
| 
 | |
| bool quick_rm_table(THD *thd, handlerton *base, const LEX_CSTRING *db,
 | |
|                     const LEX_CSTRING *table_name, uint flags,
 | |
|                     const char *table_path)
 | |
| {
 | |
|   char path[FN_REFLEN + 1];
 | |
|   const size_t pathmax = sizeof(path) - 1 - reg_ext_length;
 | |
|   int error= 0;
 | |
|   DBUG_ENTER("quick_rm_table");
 | |
| 
 | |
|   DBUG_ASSERT(flags & (QRMT_FRM | QRMT_PAR | QRMT_HANDLER));
 | |
|   size_t path_length= table_path ?
 | |
|     (strxnmov(path, pathmax, table_path, NullS) - path) :
 | |
|     build_table_filename(path, pathmax, db->str, table_name->str, "", flags);
 | |
|   if (flags & QRMT_PAR)
 | |
|   {
 | |
|     /*
 | |
|       Normally .par is removed by QRMT_HANDLER. Caller may want to remove it
 | |
|       explicitly along with .FRM in some cases.
 | |
|     */
 | |
|     DBUG_ASSERT(flags & QRMT_FRM);
 | |
|     DBUG_ASSERT(!(flags & QRMT_HANDLER));
 | |
|     handler *file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base);
 | |
|     if (!file)
 | |
|       DBUG_RETURN(true);
 | |
|     (void) file->ha_create_partitioning_metadata(path, NULL, CHF_DELETE_FLAG);
 | |
|     delete file;
 | |
|   }
 | |
|   if (flags & QRMT_HANDLER)
 | |
|   {
 | |
|     uint keys, total_keys;
 | |
|     int hlindex_error= get_hlindex_keys_by_open(thd, db, table_name, path,
 | |
|                                                 &keys, &total_keys);
 | |
|     error|= ha_delete_table(thd, base, path, db, table_name, 0) > 0;
 | |
|     if (!hlindex_error)
 | |
|     {
 | |
|       char idx_path[FN_REFLEN + 1];
 | |
|       char *idx_path_end= strmov(idx_path, path);
 | |
|       for (uint i= keys; i < total_keys; i++)
 | |
|       {
 | |
|         my_snprintf(idx_path_end, HLINDEX_BUF_LEN, HLINDEX_TEMPLATE, i);
 | |
|         if (ha_delete_table(thd, base, idx_path, db, table_name, 0))
 | |
|           error= 1;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|       error= 1;
 | |
|   }
 | |
| 
 | |
|   if (flags & QRMT_FRM)
 | |
|   {
 | |
|     memcpy(path + path_length, reg_ext, reg_ext_length + 1);
 | |
|     if (mysql_file_delete(key_file_frm, path, MYF(0)))
 | |
|       error= 1; /* purecov: inspected */
 | |
|   }
 | |
| 
 | |
|   if (likely(error == 0))
 | |
|   {
 | |
|     PSI_CALL_drop_table_share(flags & FN_IS_TMP, db->str, (uint)db->length,
 | |
|                               table_name->str, (uint)table_name->length);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| #define return_if_nonzero(X) do { if (int r=(X)) return r; } while(0)
 | |
| 
 | |
| /*
 | |
|   Sort keys in the following order:
 | |
|   - PRIMARY KEY
 | |
|   - UNIQUE keys where all column are NOT NULL
 | |
|   - UNIQUE keys that don't contain partial segments
 | |
|   - Other UNIQUE keys
 | |
|   - LONG UNIQUE keys
 | |
|   - Normal keys
 | |
|   - Fulltext keys
 | |
|   - Vector keys
 | |
| 
 | |
|   This will make checking for duplicated keys faster and ensure that
 | |
|   PRIMARY keys are prioritized.
 | |
| */
 | |
| 
 | |
| static int sort_keys(const void *a_, const void *b_)
 | |
| {
 | |
|   const KEY *a= static_cast<const KEY *>(a_);
 | |
|   const KEY *b= static_cast<const KEY *>(b_);
 | |
|   ulong a_flags= a->flags, b_flags= b->flags;
 | |
| 
 | |
|   /*
 | |
|     Do not reorder HA_KEY_ALG_LONG_HASH indexes, because they must match the
 | |
|     order of their LONG_UNIQUE_HASH_FIELD's.
 | |
|   */
 | |
|   if (a->algorithm == HA_KEY_ALG_LONG_HASH &&
 | |
|       b->algorithm == HA_KEY_ALG_LONG_HASH)
 | |
|     return a->usable_key_parts - b->usable_key_parts;
 | |
| 
 | |
|   return_if_nonzero((b_flags & HA_NOSAME) - (a_flags & HA_NOSAME));
 | |
| 
 | |
|   /* historically, rules below apply only to UNIQUE keys */
 | |
|   if (a_flags & HA_NOSAME)
 | |
|   {
 | |
|     /* Long Unique keys should always be last unique key. */
 | |
|     return_if_nonzero((a->algorithm == HA_KEY_ALG_LONG_HASH) -
 | |
|                       (b->algorithm == HA_KEY_ALG_LONG_HASH));
 | |
| 
 | |
|     /* Sort NOT NULL keys before other keys */
 | |
|     return_if_nonzero((a_flags & HA_NULL_PART_KEY) -
 | |
|                       (b_flags & HA_NULL_PART_KEY));
 | |
|     return_if_nonzero((b->name.str == primary_key_name.str) -
 | |
|                       (a->name.str == primary_key_name.str));
 | |
|     /* Sort keys don't containing partial segments before others */
 | |
|     return_if_nonzero((a_flags & HA_KEY_HAS_PART_KEY_SEG) -
 | |
|                       (b_flags & HA_KEY_HAS_PART_KEY_SEG));
 | |
|   }
 | |
| 
 | |
|   /* must be very last */
 | |
|   return_if_nonzero((a->algorithm == HA_KEY_ALG_VECTOR) -
 | |
|                     (b->algorithm == HA_KEY_ALG_VECTOR));
 | |
| 
 | |
|   return_if_nonzero((a->algorithm == HA_KEY_ALG_FULLTEXT) -
 | |
|                     (b->algorithm == HA_KEY_ALG_FULLTEXT));
 | |
| 
 | |
|   /*
 | |
|     Prefer original key order.	usable_key_parts contains here
 | |
|     the original key position.
 | |
|   */
 | |
|   return a->usable_key_parts - b->usable_key_parts;
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Check TYPELIB (set or enum) for duplicates
 | |
| 
 | |
|   SYNOPSIS
 | |
|     check_duplicates_in_interval()
 | |
|     set_or_name   "SET" or "ENUM" string for warning message
 | |
|     name	  name of the checked column
 | |
|     typelib	  list of values for the column
 | |
|     dup_val_count  returns count of duplicate elements
 | |
| 
 | |
|   DESCRIPTION
 | |
|     This function prints an warning for each value in list
 | |
|     which has some duplicates on its right
 | |
| 
 | |
|   RETURN VALUES
 | |
|     0             ok
 | |
|     1             Error
 | |
| */
 | |
| 
 | |
| bool check_duplicates_in_interval(const char *set_or_name,
 | |
|                                   const char *name, const TYPELIB *typelib,
 | |
|                                   CHARSET_INFO *cs, unsigned int *dup_val_count)
 | |
| {
 | |
|   TYPELIB tmp= *typelib;
 | |
|   const char **cur_value= typelib->type_names;
 | |
|   unsigned int *cur_length= typelib->type_lengths;
 | |
|   *dup_val_count= 0;  
 | |
|   
 | |
|   for ( ; tmp.count > 1; cur_value++, cur_length++)
 | |
|   {
 | |
|     tmp.type_names++;
 | |
|     tmp.type_lengths++;
 | |
|     tmp.count--;
 | |
|     if (find_type2(&tmp, (const char*)*cur_value, *cur_length, cs))
 | |
|     {
 | |
|       THD *thd= current_thd;
 | |
|       ErrConvString err(*cur_value, *cur_length, cs);
 | |
|       if (current_thd->is_strict_mode())
 | |
|       {
 | |
|         my_error(ER_DUPLICATED_VALUE_IN_TYPE, MYF(0),
 | |
|                  name, err.ptr(), set_or_name);
 | |
|         return 1;
 | |
|       }
 | |
|       push_warning_printf(thd,Sql_condition::WARN_LEVEL_NOTE,
 | |
|                           ER_DUPLICATED_VALUE_IN_TYPE,
 | |
|                           ER_THD(thd, ER_DUPLICATED_VALUE_IN_TYPE),
 | |
|                           name, err.ptr(), set_or_name);
 | |
|       (*dup_val_count)++;
 | |
|     }
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Resolves the column collation if:
 | |
|   - it was not typed at all, or
 | |
|   - it was contextually typed
 | |
|   according to the table level character set.
 | |
|   Generates an error to the diagnostics area in case of a failure.
 | |
| */
 | |
| bool Column_definition::
 | |
|        prepare_charset_for_string(Sql_used *used,
 | |
|                                   const Charset_collation_map_st &map,
 | |
|                                   const Column_derived_attributes *dattr)
 | |
| {
 | |
|   CHARSET_INFO *tmp= charset_collation_attrs().
 | |
|                        resolved_to_character_set(used, map, dattr->charset());
 | |
|   if (!tmp)
 | |
|     return true;
 | |
|   charset= tmp;
 | |
|   /*
 | |
|     Remove the "is contextually typed collation" indicator on success,
 | |
|     for safety.
 | |
|   */
 | |
|   flags&= ~CONTEXT_COLLATION_FLAG;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage2_blob(handler *file,
 | |
|                                             ulonglong table_flags,
 | |
|                                             uint field_flags)
 | |
| {
 | |
|   if (table_flags & HA_NO_BLOBS)
 | |
|   {
 | |
|     my_error(ER_TABLE_CANT_HANDLE_BLOB, MYF(0), file->table_type());
 | |
|     return true;
 | |
|   }
 | |
|   pack_flag= field_flags |
 | |
|              pack_length_to_packflag(pack_length - portable_sizeof_char_ptr);
 | |
|   if (charset->state & MY_CS_BINSORT)
 | |
|     pack_flag|= FIELDFLAG_BINARY;
 | |
|   length= 8;                        // Unireg field length
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage2_typelib(const char *type_name,
 | |
|                                                uint field_flags,
 | |
|                                                uint *dup_val_count)
 | |
| {
 | |
|   pack_flag= pack_length_to_packflag(pack_length) | field_flags;
 | |
|   if (charset->state & MY_CS_BINSORT)
 | |
|     pack_flag|= FIELDFLAG_BINARY;
 | |
|   return check_duplicates_in_interval(type_name, field_name.str, typelib(),
 | |
|                                       charset, dup_val_count);
 | |
| }
 | |
| 
 | |
| 
 | |
| uint Column_definition::pack_flag_numeric() const
 | |
| {
 | |
|   return (FIELDFLAG_NUMBER |
 | |
|           (flags & UNSIGNED_FLAG ? 0 : FIELDFLAG_DECIMAL)  |
 | |
|           (flags & ZEROFILL_FLAG ? FIELDFLAG_ZEROFILL : 0));
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage2_varchar(ulonglong table_flags)
 | |
| {
 | |
|   pack_flag= (charset->state & MY_CS_BINSORT) ? FIELDFLAG_BINARY : 0;
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Prepare a Column_definition instance for packing
 | |
|   Members such as pack_flag are valid after this call.
 | |
| 
 | |
|   @param IN     handler      - storage engine handler,
 | |
|                                or NULL if preparing for an SP variable
 | |
|   @param IN     table_flags  - table flags
 | |
| 
 | |
|   @retval false  -  ok
 | |
|   @retval true   -  error (not supported type, bad definition, etc)
 | |
| */
 | |
| 
 | |
| bool Column_definition::prepare_stage2(handler *file,
 | |
|                                        ulonglong table_flags)
 | |
| {
 | |
|   DBUG_ENTER("Column_definition::prepare_stage2");
 | |
| 
 | |
|   /*
 | |
|     This code came from mysql_prepare_create_table.
 | |
|     Indent preserved to make patching easier
 | |
|   */
 | |
|   DBUG_ASSERT(charset);
 | |
| 
 | |
|   if (type_handler()->Column_definition_prepare_stage2(this, file, table_flags))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   if (!(flags & NOT_NULL_FLAG) ||
 | |
|       (vcol_info))  /* Make virtual columns allow NULL values */
 | |
|     pack_flag|= FIELDFLAG_MAYBE_NULL;
 | |
|   if (flags & NO_DEFAULT_VALUE_FLAG)
 | |
|     pack_flag|= FIELDFLAG_NO_DEFAULT;
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    Modifies the first column definition whose SQL type is TIMESTAMP
 | |
|    by adding the features DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP.
 | |
| 
 | |
|    If the first TIMESTAMP column appears to be nullable, or to have an
 | |
|    explicit default, or to be a virtual column, or to be part of table period,
 | |
|    then no promotion is done.
 | |
| 
 | |
|    @param column_definitions The list of column definitions, in the physical
 | |
|                              order in which they appear in the table.
 | |
| */
 | |
| 
 | |
| void promote_first_timestamp_column(List<Create_field> *column_definitions)
 | |
| {
 | |
|   bool first= true;
 | |
|   for (Create_field &column_definition : *column_definitions)
 | |
|   {
 | |
|     if (column_definition.is_timestamp_type() ||    // TIMESTAMP
 | |
|         column_definition.unireg_check == Field::TIMESTAMP_OLD_FIELD) // Legacy
 | |
|     {
 | |
|       if (!column_definition.explicitly_nullable)
 | |
|         column_definition.flags|= NOT_NULL_FLAG;
 | |
|       DBUG_PRINT("info", ("field-ptr:%p", column_definition.field));
 | |
|       if (first &&
 | |
|           (column_definition.flags & NOT_NULL_FLAG) != 0 && // NOT NULL,
 | |
|           column_definition.default_value == NULL &&   // no constant default,
 | |
|           column_definition.unireg_check == Field::NONE && // no function default
 | |
|           column_definition.vcol_info == NULL &&
 | |
|           column_definition.period == NULL &&
 | |
|           !(column_definition.flags & VERS_SYSTEM_FIELD)) // column isn't generated
 | |
|       {
 | |
|         DBUG_PRINT("info", ("First TIMESTAMP column '%s' was promoted to "
 | |
|                             "DEFAULT CURRENT_TIMESTAMP ON UPDATE "
 | |
|                             "CURRENT_TIMESTAMP",
 | |
|                             column_definition.field_name.str
 | |
|                             ));
 | |
|         column_definition.unireg_check= Field::TIMESTAMP_DNUN_FIELD;
 | |
|       }
 | |
|       first= false;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| static bool key_cmp(const Key_part_spec &a, const Key_part_spec &b)
 | |
| {
 | |
|   return a.length == b.length && a.asc == b.asc &&
 | |
|          a.field_name.streq(b.field_name);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Check if there is a duplicate key. Report a warning for every duplicate key.
 | |
| 
 | |
|   @param thd              Thread context.
 | |
|   @param key              Key to be checked.
 | |
|   @param key_info         Key meta-data info.
 | |
|   @param key_list         List of existing keys.
 | |
| */
 | |
| static void check_duplicate_key(THD *thd, const Key *key, const KEY *key_info,
 | |
|                                 const List<Key> *key_list)
 | |
| {
 | |
|   /*
 | |
|     We only check for duplicate indexes if it is requested and the
 | |
|     key is not auto-generated.
 | |
| 
 | |
|     Check is requested if the key was explicitly created or altered
 | |
|     by the user (unless it's a foreign key).
 | |
|   */
 | |
|   if (key->old || key->type == Key::FOREIGN_KEY || key->generated)
 | |
|     return;
 | |
| 
 | |
|   for (const Key &k : *key_list)
 | |
|   {
 | |
|     // Looking for a similar key...
 | |
| 
 | |
|     if (&k == key)
 | |
|       break;
 | |
| 
 | |
|     if (k.generated ||
 | |
|         (key->type != k.type) ||
 | |
|         (key->key_create_info.algorithm != k.key_create_info.algorithm) ||
 | |
|         (key->columns.elements != k.columns.elements))
 | |
|     {
 | |
|       // Keys are different.
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (std::equal(key->columns.begin(), key->columns.end(), k.columns.begin(),
 | |
|                    key_cmp))
 | |
|     {
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE, ER_DUP_INDEX,
 | |
|                           ER_THD(thd, ER_DUP_INDEX), key_info->name.str);
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage1_typelib(THD *thd,
 | |
|                                                MEM_ROOT *mem_root,
 | |
|                                                column_definition_type_t deftype)
 | |
| {
 | |
|   /*
 | |
|     Pass the last parameter to prepare_interval_field() as follows:
 | |
|     - If we are preparing for an SP variable, we pass "false",
 | |
|       to force allocation and full copying of TYPELIB values on the given
 | |
|       mem_root, even if no character set conversion is needed. This is needed
 | |
|       because a life cycle of an SP variable is longer than the current query.
 | |
| 
 | |
|     - If we are preparing for a CREATE TABLE, we pass "true".
 | |
|       This will create the typelib in runtime memory - we will free the
 | |
|       occupied memory at the same time when we free this
 | |
|       sql_field -- at the end of execution.
 | |
|       Pass "true" as the last argument to reuse "interval_list"
 | |
|       values in "interval" in cases when no character conversion is needed,
 | |
|       to avoid extra copying.
 | |
|   */
 | |
|   if (prepare_interval_field(mem_root,
 | |
|                              deftype == COLUMN_DEFINITION_TABLE_FIELD))
 | |
|     return true; // E.g. wrong values with commas: SET('a,b')
 | |
|   create_length_to_internal_length_typelib();
 | |
| 
 | |
|   if (default_value && default_value->expr->basic_const_item())
 | |
|   {
 | |
|     if ((charset != default_value->expr->collation.collation &&
 | |
|          prepare_stage1_convert_default(thd, mem_root, charset)) ||
 | |
|          prepare_stage1_check_typelib_default())
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage1_string(THD *thd,
 | |
|                                               MEM_ROOT *mem_root)
 | |
| {
 | |
|   if (real_field_type() == FIELD_TYPE_STRING &&
 | |
|       length*charset->mbmaxlen > 1024)
 | |
|   {
 | |
|     DBUG_ASSERT(charset->mbmaxlen > 4);
 | |
|     /*
 | |
|       Convert long CHAR columns to VARCHAR.
 | |
|       CHAR has an octet length limit of 1024 bytes.
 | |
|       The code in Binlog_type_info_fixed_string::Binlog_type_info_fixed_string
 | |
|       relies on this limit. If octet length of a CHAR column is greater
 | |
|       than 1024, then it cannot write its metadata to binlog properly.
 | |
|       In case of the filename character set with mbmaxlen=5,
 | |
|       the maximum possible character length is 1024/5=204 characters.
 | |
|       Upgrade to VARCHAR if octet length is greater than 1024.
 | |
|     */
 | |
|     char warn_buff[MYSQL_ERRMSG_SIZE];
 | |
|     if (thd->is_strict_mode())
 | |
|     {
 | |
|       my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), field_name.str,
 | |
|                static_cast<ulong>(1024 / charset->mbmaxlen));
 | |
|       return true;
 | |
|     }
 | |
|     set_handler(&type_handler_varchar);
 | |
|     my_snprintf(warn_buff, sizeof(warn_buff), ER_THD(thd, ER_AUTO_CONVERT),
 | |
|                 field_name.str, "CHAR", "VARCHAR");
 | |
|     push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_AUTO_CONVERT,
 | |
|                  warn_buff);
 | |
|   }
 | |
| 
 | |
|   create_length_to_internal_length_string();
 | |
|   if (prepare_blob_field(thd))
 | |
|     return true;
 | |
|   /*
 | |
|     Convert the default value from client character
 | |
|     set into the column character set if necessary.
 | |
|     We can only do this for constants as we have not yet run fix_fields.
 | |
|     But not for blobs, as they will be stored as SQL expressions, not
 | |
|     written down into the record image.
 | |
|   */
 | |
|   if (!(flags & BLOB_FLAG) && default_value &&
 | |
|       default_value->expr->basic_const_item() &&
 | |
|       charset != default_value->expr->collation.collation)
 | |
|   {
 | |
|     if (prepare_stage1_convert_default(thd, mem_root, charset))
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage1_bit(THD *thd,
 | |
|                                            MEM_ROOT *mem_root)
 | |
| {
 | |
|   pack_flag= FIELDFLAG_NUMBER;
 | |
|   create_length_to_internal_length_bit();
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage1(THD *thd,
 | |
|                                        MEM_ROOT *mem_root,
 | |
|                                        column_definition_type_t deftype,
 | |
|                                        const Column_derived_attributes
 | |
|                                              *derived_attr)
 | |
| {
 | |
|   // SP variables have no default_value
 | |
|   DBUG_ASSERT(deftype == COLUMN_DEFINITION_TABLE_FIELD || !default_value);
 | |
| 
 | |
|   return type_handler()->Column_definition_prepare_stage1(thd, mem_root,
 | |
|                                                           this, deftype,
 | |
|                                                           derived_attr);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage1_convert_default(THD *thd,
 | |
|                                                        MEM_ROOT *mem_root,
 | |
|                                                        CHARSET_INFO *cs)
 | |
| {
 | |
|   DBUG_ASSERT(thd->mem_root == mem_root);
 | |
|   Item *item;
 | |
|   if (!(item= default_value->expr->safe_charset_converter(thd, cs)))
 | |
|   {
 | |
|     my_error(ER_INVALID_DEFAULT, MYF(0), field_name.str);
 | |
|     return true; // Could not convert
 | |
|   }
 | |
|   /* Fix for prepare statement */
 | |
|   thd->change_item_tree(&default_value->expr, item);
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Column_definition::prepare_stage1_check_typelib_default()
 | |
| {
 | |
|   StringBuffer<MAX_FIELD_WIDTH> str;
 | |
|   String *def= default_value->expr->val_str(&str);
 | |
|   bool not_found;
 | |
|   if (def == NULL) /* SQL "NULL" maps to NULL */
 | |
|   {
 | |
|     not_found= flags & NOT_NULL_FLAG;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     not_found= false;
 | |
|     if (real_field_type() == MYSQL_TYPE_SET)
 | |
|     {
 | |
|       char *not_used;
 | |
|       uint not_used2;
 | |
|       find_set(typelib(), def->ptr(), def->length(),
 | |
|                charset, ¬_used, ¬_used2, ¬_found);
 | |
|     }
 | |
|     else /* MYSQL_TYPE_ENUM */
 | |
|     {
 | |
|       def->length(charset->lengthsp(def->ptr(), def->length()));
 | |
|       not_found= !find_type2(typelib(), def->ptr(), def->length(), charset);
 | |
|     }
 | |
|   }
 | |
|   if (not_found)
 | |
|   {
 | |
|     my_error(ER_INVALID_DEFAULT, MYF(0), field_name.str);
 | |
|     return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| /*
 | |
|    This function adds a invisible field to field_list
 | |
|    SYNOPSIS
 | |
|     mysql_add_invisible_field()
 | |
|       thd                      Thread Object
 | |
|       field_list               list of all table fields
 | |
|       field_name               name/prefix of invisible field
 | |
|                                ( Prefix in the case when it is
 | |
|                                 *INVISIBLE_FULL*
 | |
|                                and given name is duplicate)
 | |
|       type_handler             field data type
 | |
|       invisible
 | |
|       default value
 | |
|     RETURN VALUE
 | |
|       Create_field pointer
 | |
| */
 | |
| int mysql_add_invisible_field(THD *thd, List<Create_field> * field_list,
 | |
|         const Lex_ident_column &field_name, Type_handler *type_handler,
 | |
|         field_visibility_t invisible, Item* default_value)
 | |
| {
 | |
|   Create_field *fld= new(thd->mem_root)Create_field();
 | |
|   /* Get unique field name if invisible == INVISIBLE_FULL */
 | |
|   if (invisible == INVISIBLE_FULL)
 | |
|   {
 | |
|     Lex_ident_column new_name;
 | |
|     if ((new_name= make_unique_invisible_field_name(thd, field_name,
 | |
|                                                     field_list)).str)
 | |
|       fld->field_name= new_name;
 | |
|     else
 | |
|       return 1;  //Should not happen
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     fld->field_name= thd->lex_ident_copy(field_name);
 | |
|   }
 | |
|   fld->set_handler(type_handler);
 | |
|   fld->invisible= invisible;
 | |
|   if (default_value)
 | |
|   {
 | |
|     Virtual_column_info *v= new (thd->mem_root) Virtual_column_info();
 | |
|     v->expr= default_value;
 | |
|     v->utf8= 0;
 | |
|     fld->default_value= v;
 | |
|   }
 | |
|   field_list->push_front(fld, thd->mem_root);
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| #define INTERNAL_FIELD_NAME_LENGTH  30
 | |
| 
 | |
| static Lex_ident_column make_internal_field_name(THD *thd, const char *prefix,
 | |
|                                           List<Create_field> *create_list)
 | |
| {
 | |
|   char buf[INTERNAL_FIELD_NAME_LENGTH]= {0};
 | |
|   LEX_CSTRING name= { buf, 0 };
 | |
|   bool dup_found= true;
 | |
|   for (uint num= 1; dup_found; num++)
 | |
|   {
 | |
|     name.length= my_snprintf(buf, sizeof(buf), "%s%u", prefix, num);
 | |
|     for (auto &dup_field : *create_list)
 | |
|       if ((dup_found= dup_field.field_name.streq(name)))
 | |
|         break;
 | |
|   }
 | |
|   return Lex_ident_column(thd->strmake_lex_cstring(name));
 | |
| }
 | |
| 
 | |
| static Create_field *add_internal_field(THD *thd, Type_handler *type_handler,
 | |
|                                         uint flags, const char *prefix,
 | |
|                                         List<Create_field> *create_list)
 | |
| {
 | |
|   class Column_derived_attributes dat(&my_charset_bin);
 | |
|   Create_field *cf= new (thd->mem_root) Create_field();
 | |
|   cf->flags|= flags | NOT_NULL_FLAG;
 | |
|   cf->invisible= INVISIBLE_FULL;
 | |
|   cf->field_name= make_internal_field_name(thd, prefix, create_list);
 | |
|   cf->set_handler(type_handler);
 | |
|   cf->prepare_stage1(thd, thd->mem_root, COLUMN_DEFINITION_TABLE_FIELD, &dat);
 | |
|   create_list->push_back(cf,thd->mem_root);
 | |
|   return cf;
 | |
| }
 | |
| 
 | |
| Key *
 | |
| mysql_add_invisible_index(THD *thd, List<Key> *key_list,
 | |
|         LEX_CSTRING* field_name, enum Key::Keytype type)
 | |
| {
 | |
|   Key *key= new (thd->mem_root) Key(type, &null_clex_str, HA_KEY_ALG_UNDEF,
 | |
|                                     false, DDL_options(DDL_options::OPT_NONE));
 | |
|   key->columns.push_back(new(thd->mem_root) Key_part_spec(field_name, 0, true),
 | |
|           thd->mem_root);
 | |
|   key_list->push_back(key, thd->mem_root);
 | |
|   return key;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Type_handler_string::Key_part_spec_init_ft(Key_part_spec *part,
 | |
|                                                 const Column_definition &def)
 | |
|                                                 const
 | |
| {
 | |
|   /*
 | |
|     Set length to 0. It's set to the real column width later for CHAR.
 | |
|     It has to be the correct col width for CHAR, as its data are not
 | |
|     prefixed with length (unlike blobs).
 | |
|   */
 | |
|   part->length= 0;
 | |
|   return !Charset(def.charset).is_good_for_ft();
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Type_handler_varchar::Key_part_spec_init_ft(Key_part_spec *part,
 | |
|                                                  const Column_definition &def)
 | |
|                                                  const
 | |
| {
 | |
|   part->length= 0;
 | |
|   return !Charset(def.charset).is_good_for_ft();
 | |
| }
 | |
| 
 | |
| 
 | |
| bool
 | |
| Type_handler_blob_common::Key_part_spec_init_ft(Key_part_spec *part,
 | |
|                                                 const Column_definition &def)
 | |
|                                                 const
 | |
| {
 | |
|   /*
 | |
|     Set keyseg length to 1 for blobs.
 | |
|     It's ignored in ft code: the data length is taken from the length prefix.
 | |
|   */
 | |
|   part->length= 1;
 | |
|   return !Charset(def.charset).is_good_for_ft();
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool
 | |
| key_add_part_check_null(const handler *file, KEY *key_info,
 | |
|                         const Column_definition *sql_field,
 | |
|                         const Key_part_spec *column)
 | |
| {
 | |
|   if (!(sql_field->flags & NOT_NULL_FLAG))
 | |
|   {
 | |
|     key_info->flags|= HA_NULL_PART_KEY;
 | |
|     if (!(file->ha_table_flags() & HA_NULL_IN_KEY))
 | |
|     {
 | |
|       my_error(ER_NULL_COLUMN_IN_INDEX, MYF(0), column->field_name.str);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| static
 | |
| my_bool key_check_without_overlaps(THD *thd, HA_CREATE_INFO *create_info,
 | |
|                                    Alter_info *alter_info,
 | |
|                                    Key &key)
 | |
| {
 | |
|   DBUG_ENTER("key_check_without_overlaps");
 | |
|   if (!key.without_overlaps)
 | |
|     DBUG_RETURN(FALSE);
 | |
|   // append_system_key_parts is already called, so we should check all the
 | |
|   // columns except the last two.
 | |
|   const auto &period_start= create_info->period_info.period.start;
 | |
|   const auto &period_end= create_info->period_info.period.end;
 | |
|   List_iterator<Key_part_spec> part_it_forwarded(key.columns);
 | |
|   List_iterator<Key_part_spec> part_it(key.columns);
 | |
|   part_it_forwarded++;
 | |
|   part_it_forwarded++;
 | |
|   while (part_it_forwarded++)
 | |
|   {
 | |
|     Key_part_spec *key_part= part_it++;
 | |
| 
 | |
|     if (period_start.streq(key_part->field_name)
 | |
|         || period_end.streq(key_part->field_name))
 | |
|     {
 | |
|       my_error(ER_KEY_CONTAINS_PERIOD_FIELDS, MYF(0), key.name.str,
 | |
|                key_part->field_name.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (key.key_create_info.algorithm == HA_KEY_ALG_HASH ||
 | |
|       key.key_create_info.algorithm == HA_KEY_ALG_LONG_HASH)
 | |
| 
 | |
|   {
 | |
|     my_error(ER_KEY_CANT_HAVE_WITHOUT_OVERLAPS, MYF(0), key.name.str);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   for (Key &key2: alter_info->key_list)
 | |
|   {
 | |
|     if (key2.type != Key::FOREIGN_KEY)
 | |
|       continue;
 | |
|     DBUG_ASSERT(&key != &key2);
 | |
|     const Foreign_key &fk= (Foreign_key&)key2;
 | |
|     if (fk.update_opt != FK_OPTION_CASCADE)
 | |
|       continue;
 | |
|     for (Key_part_spec& kp: key.columns)
 | |
|     {
 | |
|       for (Key_part_spec& kp2: fk.columns)
 | |
|       {
 | |
|         if (kp.field_name.streq(kp2.field_name))
 | |
|         {
 | |
|           my_error(ER_KEY_CANT_HAVE_WITHOUT_OVERLAPS, MYF(0), key.name.str);
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   create_info->period_info.unique_keys++;
 | |
| 
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| static
 | |
| my_bool init_key_part_spec(THD *thd, Alter_info *alter_info,
 | |
|                            const handler *file,
 | |
|                            const Key &key, Key_part_spec &kp,
 | |
|                            uint max_key_length, uint max_key_part_length,
 | |
|                            bool *is_hash_field_needed)
 | |
| {
 | |
|   DBUG_ENTER("init_key_part_spec");
 | |
| 
 | |
|   const Lex_ident_column &field_name= kp.field_name;
 | |
|   Create_field *column= NULL;
 | |
| 
 | |
|   for (Create_field &c: alter_info->create_list)
 | |
|     if (c.field_name.streq(field_name))
 | |
|       column= &c;
 | |
| 
 | |
|   /*
 | |
|     Either field is not present or field visibility is > INVISIBLE_USER
 | |
|   */
 | |
|   if (!column || (column->invisible > INVISIBLE_USER && !kp.generated))
 | |
|   {
 | |
|     my_error(ER_KEY_COLUMN_DOES_NOT_EXIST, MYF(0), field_name.str);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   if (!DBUG_IF("test_invisible_index")
 | |
|       && column->invisible > INVISIBLE_USER
 | |
|       && !(column->flags & VERS_SYSTEM_FIELD) && !key.invisible)
 | |
|   {
 | |
|     my_error(ER_KEY_COLUMN_DOES_NOT_EXIST, MYF(0), column->field_name.str);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   const Type_handler *type_handler= column->type_handler();
 | |
|   switch(key.type)
 | |
|   {
 | |
|   case Key::VECTOR:
 | |
|     if (type_handler->Key_part_spec_init_vector(&kp, *column))
 | |
|     {
 | |
|       my_error(ER_WRONG_ARGUMENTS, MYF(0), "VECTOR INDEX");
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     break;
 | |
|   case Key::FULLTEXT:
 | |
|     if (type_handler->Key_part_spec_init_ft(&kp, *column))
 | |
|     {
 | |
|       my_error(ER_BAD_FT_COLUMN, MYF(0), field_name.str);
 | |
|       DBUG_RETURN(-1);
 | |
|     }
 | |
|     break;
 | |
| 
 | |
|   case Key::SPATIAL:
 | |
|     if (type_handler->Key_part_spec_init_spatial(&kp, *column))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     break;
 | |
| 
 | |
|   case Key::PRIMARY:
 | |
|     if (column->vcol_info)
 | |
|     {
 | |
|       my_error(ER_PRIMARY_KEY_BASED_ON_GENERATED_COLUMN, MYF(0));
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     if (type_handler->Key_part_spec_init_primary(&kp, *column, file))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     break;
 | |
| 
 | |
|   case Key::MULTIPLE:
 | |
|     if (type_handler->Key_part_spec_init_multiple(&kp, *column, file))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     break;
 | |
| 
 | |
|   case Key::FOREIGN_KEY:
 | |
|     if (type_handler->Key_part_spec_init_foreign(&kp, *column, file))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     break;
 | |
| 
 | |
|   case Key::UNIQUE:
 | |
|     if (type_handler->Key_part_spec_init_unique(&kp, *column, file,
 | |
|                                                 is_hash_field_needed))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     break;
 | |
| 
 | |
|   case Key::IGNORE_KEY:
 | |
|     DBUG_ASSERT(0);
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   uint key_part_length= type_handler->calc_key_length(*column);
 | |
| 
 | |
|   if (kp.length)
 | |
|   {
 | |
|     if (f_is_blob(column->pack_flag))
 | |
|     {
 | |
|       key_part_length= MY_MIN(kp.length,
 | |
|                               blob_length_by_type(column->real_field_type())
 | |
|                               * column->charset->mbmaxlen);
 | |
|       if (key_part_length > max_key_length ||
 | |
|           key_part_length > max_key_part_length)
 | |
|       {
 | |
|         if (key.type == Key::MULTIPLE)
 | |
|         {
 | |
|           key_part_length= MY_MIN(max_key_length, max_key_part_length);
 | |
|           /* not a critical problem */
 | |
|           push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                               ER_TOO_LONG_KEY, ER_THD(thd, ER_TOO_LONG_KEY),
 | |
|                               key_part_length);
 | |
|           /* Align key length to multibyte char boundary */
 | |
|           key_part_length-= key_part_length % column->charset->mbmaxlen;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|       // Catch invalid use of partial keys
 | |
|     else if (!f_is_geom(column->pack_flag) &&
 | |
|              // is the key partial?
 | |
|              kp.length != key_part_length &&
 | |
|              // is prefix length bigger than field length?
 | |
|              (kp.length > key_part_length ||
 | |
|               // can the field have a partial key?
 | |
|               !type_handler->type_can_have_key_part() ||
 | |
|               // a packed field can't be used in a partial key
 | |
|               f_is_packed(column->pack_flag) ||
 | |
|               // does the storage engine allow prefixed search?
 | |
|               ((file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS) &&
 | |
|                // and is this a 'unique' key?
 | |
|                (key.type == Key::PRIMARY || key.type == Key::UNIQUE))))
 | |
|     {
 | |
|       my_message(ER_WRONG_SUB_KEY, ER_THD(thd, ER_WRONG_SUB_KEY), MYF(0));
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     else if (!(file->ha_table_flags() & HA_NO_PREFIX_CHAR_KEYS))
 | |
|       key_part_length= kp.length;
 | |
|   }
 | |
|   else if (key_part_length == 0 && (column->flags & NOT_NULL_FLAG) &&
 | |
|            !*is_hash_field_needed)
 | |
|   {
 | |
|     my_error(ER_WRONG_KEY_COLUMN, MYF(0), file->table_type(), field_name.str);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   if (key_part_length > max_key_part_length)
 | |
|   {
 | |
|     if (key.type == Key::MULTIPLE && type_handler->type_can_have_key_part())
 | |
|     {
 | |
|       key_part_length= max_key_part_length;
 | |
|       /* not a critical problem */
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                           ER_TOO_LONG_KEY, ER_THD(thd, ER_TOO_LONG_KEY),
 | |
|                           key_part_length);
 | |
|       /* Align key length to multibyte char boundary */
 | |
|       key_part_length-= key_part_length % column->charset->mbmaxlen;
 | |
|     }
 | |
|     else if (key.type == Key::PRIMARY)
 | |
|     {
 | |
|       key_part_length= MY_MIN(max_key_length, max_key_part_length);
 | |
|       my_error(ER_TOO_LONG_KEY, MYF(0), key_part_length);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (key.type == Key::UNIQUE && key_part_length > MY_MIN(max_key_length,
 | |
|                                                           max_key_part_length))
 | |
|     *is_hash_field_needed= true;
 | |
| 
 | |
|   /* We can not store key_part_length more than 2^16 - 1 in frm. */
 | |
|   if (*is_hash_field_needed && kp.length > UINT_MAX16)
 | |
|   {
 | |
|     my_error(ER_TOO_LONG_KEYPART, MYF(0),  UINT_MAX16);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   kp.length= key_part_length;
 | |
| 
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   @brief Initialize the key length and algorithm (if long hash).
 | |
| 
 | |
|   This function does:
 | |
|   1. Append system key parts (versioning, periods)
 | |
|   2. Call Type_handler key_part initialization function.
 | |
|   3. Determine the length of each key_part.
 | |
|   4. Calculate the total Key length.
 | |
|   5. Determine if the key is long unique based on its length
 | |
|      smd result from type handler. It'll be saved in
 | |
|      key_create_info.algorithm as HA_KEY_ALG_LONG_HASH.
 | |
| 
 | |
|   @return   FALSE   OK
 | |
|   @return   TRUE    error
 | |
|  */
 | |
| static
 | |
| my_bool init_key_info(THD *thd, Alter_info *alter_info,
 | |
|                       HA_CREATE_INFO *create_info,
 | |
|                       const handler *file)
 | |
| {
 | |
|   DBUG_ENTER("init_key_info");
 | |
|   uint max_key_length= file->max_key_length();
 | |
|   uint max_key_part_length= file->max_key_part_length();
 | |
| 
 | |
|   for (Key &key: alter_info->key_list)
 | |
|   {
 | |
|     if (key.type == Key::FOREIGN_KEY)
 | |
|       continue;
 | |
| 
 | |
|     int parts_added= append_system_key_parts(thd, create_info, &key);
 | |
|     if (parts_added < 0)
 | |
|       DBUG_RETURN(true);
 | |
| 
 | |
|     bool is_hash_field_needed= false;
 | |
|     for (Key_part_spec &kp: key.columns)
 | |
|     {
 | |
|       if (init_key_part_spec(thd, alter_info, file, key, kp,
 | |
|                              max_key_length, max_key_part_length,
 | |
|                              &is_hash_field_needed))
 | |
|         DBUG_RETURN(TRUE);
 | |
| 
 | |
|       key.length+= kp.length;
 | |
|       if (key.length > max_key_length)
 | |
|       {
 | |
|         if (key.type == Key::UNIQUE)
 | |
|           is_hash_field_needed= true;  // for case "a BLOB UNIQUE"
 | |
|         else if (key.type <= Key::MULTIPLE)
 | |
|         {
 | |
|           my_error(ER_TOO_LONG_KEY, MYF(0), max_key_length);
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       KEY_CREATE_INFO *key_cinfo= &key.key_create_info;
 | |
| 
 | |
|       if (is_hash_field_needed)
 | |
|       {
 | |
|         if (key_cinfo->algorithm == HA_KEY_ALG_UNDEF)
 | |
|           key_cinfo->algorithm= HA_KEY_ALG_LONG_HASH;
 | |
| 
 | |
|         if (key_cinfo->algorithm != HA_KEY_ALG_HASH &&
 | |
|             key_cinfo->algorithm != HA_KEY_ALG_LONG_HASH)
 | |
|         {
 | |
|           my_error(ER_TOO_LONG_KEY, MYF(0), max_key_length);
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Prepare for a table creation.
 | |
|   Stage 1: prepare the field list.
 | |
| */
 | |
| static bool mysql_prepare_create_table_stage1(THD *thd,
 | |
|                                               HA_CREATE_INFO *create_info,
 | |
|                                               Alter_info *alter_info)
 | |
| {
 | |
|   DBUG_ENTER("mysql_prepare_create_table_stage1");
 | |
|   const Column_derived_attributes dattr(create_info->default_table_charset);
 | |
|   const Column_bulk_alter_attributes
 | |
|     battr(create_info->alter_table_convert_to_charset);
 | |
|   Create_field	*sql_field;
 | |
|   List_iterator_fast<Create_field> it(alter_info->create_list);
 | |
| 
 | |
|   DBUG_EXECUTE_IF("test_pseudo_invisible",{
 | |
|           mysql_add_invisible_field(thd, &alter_info->create_list,
 | |
|                       "invisible"_Lex_ident_column,
 | |
|                       &type_handler_slong, INVISIBLE_SYSTEM,
 | |
|                       new (thd->mem_root)Item_int(thd, 9));
 | |
|           });
 | |
|   DBUG_EXECUTE_IF("test_completely_invisible",{
 | |
|           mysql_add_invisible_field(thd, &alter_info->create_list,
 | |
|                       "invisible"_Lex_ident_column,
 | |
|                       &type_handler_slong, INVISIBLE_FULL,
 | |
|                       new (thd->mem_root)Item_int(thd, 9));
 | |
|           });
 | |
|   DBUG_EXECUTE_IF("test_invisible_index",{
 | |
|           LEX_CSTRING temp= "invisible"_Lex_ident_column;
 | |
|           mysql_add_invisible_index(thd, &alter_info->key_list
 | |
|                   , &temp, Key::MULTIPLE);
 | |
|           });
 | |
| 
 | |
| 
 | |
|   for ( ; (sql_field=it++) ; )
 | |
|   {
 | |
|     /* Virtual fields are always NULL */
 | |
|     if (sql_field->vcol_info)
 | |
|       sql_field->flags&= ~NOT_NULL_FLAG;
 | |
| 
 | |
|     /*
 | |
|       Initialize length from its original value (number of characters),
 | |
|       which was set in the parser. This is necessary if we're
 | |
|       executing a prepared statement for the second time.
 | |
|     */
 | |
|     sql_field->length= sql_field->char_length;
 | |
| 
 | |
|     if (sql_field->bulk_alter(&dattr, &battr))
 | |
|       DBUG_RETURN(true);
 | |
| 
 | |
|     if (sql_field->prepare_stage1(thd, thd->mem_root,
 | |
|                                   COLUMN_DEFINITION_TABLE_FIELD,
 | |
|                                   &dattr))
 | |
|       DBUG_RETURN(true);
 | |
| 
 | |
|     DBUG_ASSERT(sql_field->charset);
 | |
| 
 | |
|     if (check_column_name(sql_field->field_name.str))
 | |
|     {
 | |
|       my_error(ER_WRONG_COLUMN_NAME, MYF(0), sql_field->field_name.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Preparation for table creation, final stage.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_prepare_create_table_finalize()
 | |
|       thd                       Thread object.
 | |
|       create_info               Create information (like MAX_ROWS).
 | |
|       alter_info                List of columns and indexes to create
 | |
|       db_options          INOUT Table options (like HA_OPTION_PACK_RECORD).
 | |
|       file                      The handler for the new table.
 | |
|       key_info_buffer     OUT   An array of KEY structs for the indexes.
 | |
|       key_count           OUT   The number of elements in the array.
 | |
|       create_table_mode         C_ORDINARY_CREATE, C_ALTER_TABLE,
 | |
|                                 C_CREATE_SELECT, C_ASSISTED_DISCOVERY
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Prepares the table and key structures for table creation.
 | |
| 
 | |
|   NOTES
 | |
|     sets create_info->varchar if the table has a varchar
 | |
| 
 | |
|   RETURN VALUES
 | |
|     FALSE    OK
 | |
|     TRUE     error
 | |
| */
 | |
| 
 | |
| static int
 | |
| mysql_prepare_create_table_finalize(THD *thd, HA_CREATE_INFO *create_info,
 | |
|                                     Alter_info *alter_info, uint *db_options,
 | |
|                                     handler *file, KEY **key_info_buffer,
 | |
|                                     uint *key_count, int create_table_mode)
 | |
| {
 | |
|   Lex_ident_column key_name;
 | |
|   Create_field	*sql_field,*dup_field;
 | |
|   uint		field,null_fields;
 | |
|   ulong		record_offset= 0;
 | |
|   KEY_PART_INFO *key_part_info;
 | |
|   int		field_no,dup_no;
 | |
|   int		select_field_pos,auto_increment=0;
 | |
|   List_iterator_fast<Create_field> it(alter_info->create_list);
 | |
|   List_iterator<Create_field> it2(alter_info->create_list);
 | |
|   uint total_uneven_bit_length= 0;
 | |
|   bool tmp_table= create_table_mode == C_ALTER_TABLE;
 | |
|   const bool create_simple= thd->lex->create_simple();
 | |
|   const CHARSET_INFO *scs= system_charset_info;
 | |
|   DBUG_ENTER("mysql_prepare_create_table");
 | |
| 
 | |
|   LEX_CSTRING* connstr = &create_info->connect_string;
 | |
|   if (connstr->length > CONNECT_STRING_MAXLEN &&
 | |
|       scs->charpos(connstr->str, connstr->str + connstr->length,
 | |
|                    CONNECT_STRING_MAXLEN) < connstr->length)
 | |
|   {
 | |
|     my_error(ER_WRONG_STRING_LENGTH, MYF(0), connstr->str, "CONNECTION",
 | |
|              CONNECT_STRING_MAXLEN);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   select_field_pos= get_select_field_pos(alter_info, create_info->versioned());
 | |
|   null_fields= 0;
 | |
|   create_info->varchar= 0;
 | |
| 
 | |
|   /* Handle creation of sequences */
 | |
|   if (create_info->sequence)
 | |
|   {
 | |
|     if (!(file->ha_table_flags() & HA_CAN_TABLES_WITHOUT_ROLLBACK))
 | |
|     {
 | |
|       my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0), file->table_type(),
 | |
|                SEQUENCE_clex_str.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
| 
 | |
|     /* The user specified fields: check that structure is ok */
 | |
|     if (check_sequence_fields(thd->lex, &alter_info->create_list,
 | |
|                               alter_info->db, alter_info->table_name))
 | |
|       DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
| 
 | |
|   for (field_no=0; (sql_field=it++) ; field_no++)
 | |
|   {
 | |
|     if (!(sql_field->flags & NOT_NULL_FLAG))
 | |
|       null_fields++;
 | |
| 
 | |
|     if (sql_field->real_field_type() == MYSQL_TYPE_BIT &&
 | |
|         file->ha_table_flags() & HA_CAN_BIT_FIELD)
 | |
|       total_uneven_bit_length+= sql_field->length & 7;
 | |
| 
 | |
|     /* Check if we have used the same field name before */
 | |
|     for (dup_no=0; (dup_field=it2++) != sql_field; dup_no++)
 | |
|     {
 | |
|       if (sql_field->field_name.streq(dup_field->field_name))
 | |
|       {
 | |
| 	/*
 | |
| 	  If this was a CREATE ... SELECT statement, accept a field
 | |
| 	  redefinition if we are changing a field in the SELECT part
 | |
| 
 | |
|           The cases are:
 | |
| 
 | |
|           field_no < select_field_pos: both field and dup are table fields;
 | |
|           dup_no >= select_field_pos: both field and dup are select fields or
 | |
|             field is implicit system field and dup is select field.
 | |
| 
 | |
|           We are not allowed to put row_start/row_end into SELECT expression.
 | |
| 	*/
 | |
|         DBUG_ASSERT(dup_no < field_no);
 | |
| 	if (field_no < select_field_pos || dup_no >= select_field_pos ||
 | |
|             dup_field->invisible >= INVISIBLE_SYSTEM)
 | |
| 	{
 | |
| 	  my_error(ER_DUP_FIELDNAME, MYF(0), sql_field->field_name.str);
 | |
| 	  DBUG_RETURN(TRUE);
 | |
| 	}
 | |
| 	else
 | |
| 	{
 | |
| 	  /* Field redefined */
 | |
| 
 | |
|           /*
 | |
|             If we are replacing a BIT field, revert the increment
 | |
|             of total_uneven_bit_length that was done above.
 | |
|           */
 | |
|           if (sql_field->real_field_type() == MYSQL_TYPE_BIT &&
 | |
|               file->ha_table_flags() & HA_CAN_BIT_FIELD)
 | |
|             total_uneven_bit_length-= sql_field->length & 7;
 | |
| 
 | |
|           /* 
 | |
|             We're making one field from two, the result field will have
 | |
|             dup_field->flags as flags. If we've incremented null_fields
 | |
|             because of sql_field->flags, decrement it back.
 | |
|           */
 | |
|           if (!(sql_field->flags & NOT_NULL_FLAG))
 | |
|             null_fields--;
 | |
| 
 | |
|           if (sql_field->redefine_stage1(dup_field, file))
 | |
|             DBUG_RETURN(true);
 | |
| 
 | |
| 	  it2.remove();			// Remove first (create) definition
 | |
| 	  select_field_pos--;
 | |
| 	  break;
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|     /* Don't pack rows in old tables if the user has requested this */
 | |
|     if ((sql_field->flags & BLOB_FLAG) ||
 | |
| 	(sql_field->real_field_type() == MYSQL_TYPE_VARCHAR &&
 | |
|          create_info->row_type != ROW_TYPE_FIXED))
 | |
|       (*db_options)|= HA_OPTION_PACK_RECORD;
 | |
|     it2.rewind();
 | |
|   }
 | |
| 
 | |
|   /* record_offset will be increased with 'length-of-null-bits' later */
 | |
|   record_offset= 0;
 | |
|   null_fields+= total_uneven_bit_length;
 | |
| 
 | |
|   it.rewind();
 | |
|   while ((sql_field=it++))
 | |
|   {
 | |
|     DBUG_ASSERT(sql_field->charset != 0);
 | |
|     if (sql_field->prepare_stage2(file, file->ha_table_flags()))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     if (sql_field->real_field_type() == MYSQL_TYPE_VARCHAR)
 | |
|       create_info->varchar= TRUE;
 | |
|     sql_field->offset= record_offset;
 | |
|     if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
 | |
|       auto_increment++;
 | |
|     extend_option_list(thd, create_info->db_type, !sql_field->field,
 | |
|                        &sql_field->option_list,
 | |
|                        create_info->db_type->field_options);
 | |
|     if (parse_option_list(thd, &sql_field->option_struct,
 | |
|                           &sql_field->option_list,
 | |
|                           create_info->db_type->field_options, FALSE,
 | |
|                           thd->mem_root))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     /*
 | |
|       For now skip fields that are not physically stored in the database
 | |
|       (virtual fields) and update their offset later 
 | |
|       (see the next loop).
 | |
|     */
 | |
|     if (sql_field->stored_in_db())
 | |
|       record_offset+= sql_field->pack_length;
 | |
|   }
 | |
|   /* Update virtual fields' offset and give error if
 | |
|      All fields are invisible */
 | |
|   bool is_all_invisible= true;
 | |
|   it.rewind();
 | |
|   while ((sql_field=it++))
 | |
|   {
 | |
|     if (!sql_field->stored_in_db())
 | |
|     {
 | |
|       sql_field->offset= record_offset;
 | |
|       record_offset+= sql_field->pack_length;
 | |
|     }
 | |
|     if (sql_field->invisible == VISIBLE)
 | |
|       is_all_invisible= false;
 | |
|   }
 | |
|   if (is_all_invisible)
 | |
|   {
 | |
|     my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0));
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   if (auto_increment > 1)
 | |
|   {
 | |
|     my_message(ER_WRONG_AUTO_KEY, ER_THD(thd, ER_WRONG_AUTO_KEY), MYF(0));
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   if (auto_increment &&
 | |
|       (file->ha_table_flags() & HA_NO_AUTO_INCREMENT))
 | |
|   {
 | |
|     my_error(ER_TABLE_CANT_HANDLE_AUTO_INCREMENT, MYF(0), file->table_type());
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    CREATE TABLE[with auto_increment column] SELECT is unsafe as the rows
 | |
|    inserted in the created table depends on the order of the rows fetched
 | |
|    from the select tables. This order may differ on master and slave. We
 | |
|    therefore mark it as unsafe.
 | |
|   */
 | |
|   if (alter_info->select_field_count > 0 && auto_increment)
 | |
|     thd->lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_SELECT_AUTOINC);
 | |
| 
 | |
|   /* Create keys */
 | |
| 
 | |
|   List_iterator<Key> key_iterator(alter_info->key_list);
 | |
|   List_iterator<Key> key_iterator2(alter_info->key_list);
 | |
|   uint key_parts=0;
 | |
|   bool primary_key=0,unique_key=0;
 | |
|   Key *key, *key2;
 | |
|   uint tmp, key_number;
 | |
| 
 | |
|   if (init_key_info(thd, alter_info, create_info, file))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   /* Calculate number of key segments */
 | |
|   *key_count= 0;
 | |
| 
 | |
|   while ((key=key_iterator++))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("key name: '%s'  type: %d", key->name.str ? key->name.str :
 | |
|                         "(none)" , key->type));
 | |
|     if (key->type == Key::FOREIGN_KEY)
 | |
|     {
 | |
|       Foreign_key *fk_key= (Foreign_key*) key;
 | |
|       if (fk_key->validate(alter_info->create_list))
 | |
|         DBUG_RETURN(TRUE);
 | |
|       if (fk_key->ref_columns.elements)
 | |
|       {
 | |
|         if (fk_key->ref_columns.elements != fk_key->columns.elements)
 | |
|         {
 | |
|           my_error(ER_WRONG_FK_DEF, MYF(0),
 | |
|                   (fk_key->name.str ? fk_key->name.str :
 | |
|                                       "foreign key without name"),
 | |
|                   ER_THD(thd, ER_KEY_REF_DO_NOT_MATCH_TABLE_REF));
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|         fk_key->ref_columns.append(&fk_key->columns);
 | |
|       continue;
 | |
|     }
 | |
|     (*key_count)++;
 | |
|     tmp=file->max_key_parts();
 | |
|     if (key->columns.elements > tmp)
 | |
|     {
 | |
|       my_error(ER_TOO_MANY_KEY_PARTS,MYF(0),tmp);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     if (check_ident_length(&key->name))
 | |
|       DBUG_RETURN(TRUE);
 | |
|     key_iterator2.rewind ();
 | |
|     if (key->type != Key::FOREIGN_KEY)
 | |
|     {
 | |
|       while ((key2 = key_iterator2++) != key)
 | |
|       {
 | |
| 	/*
 | |
|           is_foreign_key_prefix(key, key2) returns true if key or key2, or
 | |
|           both, is 'generated', and a generated key is a prefix of the other
 | |
|           key. Then we do not need the generated shorter key.
 | |
|         */
 | |
|         if (key2->type != Key::FOREIGN_KEY && key2->type != Key::IGNORE_KEY &&
 | |
|             is_foreign_key_prefix(key, key2))
 | |
|         {
 | |
|           /* mark that the generated key should be ignored */
 | |
|           if (!key2->generated ||
 | |
|               (key->generated && key->columns.elements <
 | |
|                key2->columns.elements))
 | |
|             key->type= Key::IGNORE_KEY;
 | |
|           else
 | |
|           {
 | |
|             key2->type= Key::IGNORE_KEY;
 | |
|             key_parts-= key2->columns.elements;
 | |
|             (*key_count)--;
 | |
|           }
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     if (key->type != Key::IGNORE_KEY)
 | |
|       key_parts+=key->columns.elements;
 | |
|     else
 | |
|       (*key_count)--;
 | |
|     if (key->name.str && !tmp_table && (key->type != Key::PRIMARY) &&
 | |
|         key->name.streq(primary_key_name))
 | |
|     {
 | |
|       my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     if (key->type == Key::PRIMARY && key->name.str &&
 | |
|         !key->name.streq(primary_key_name))
 | |
|     {
 | |
|       bool sav_abort_on_warning= thd->abort_on_warning;
 | |
|       thd->abort_on_warning= FALSE; /* Don't make an error out of this. */
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                           ER_WRONG_NAME_FOR_INDEX,
 | |
|                           "Name '%-.100s' ignored for PRIMARY key.",
 | |
|                           key->name.str);
 | |
|       thd->abort_on_warning= sav_abort_on_warning;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   KEY *key_info= *key_info_buffer= thd->calloc<KEY>(*key_count);
 | |
|   if (!*key_info_buffer)
 | |
|     DBUG_RETURN(true);				// Out of memory
 | |
| 
 | |
|   key_iterator.rewind();
 | |
|   while ((key=key_iterator++))
 | |
|   {
 | |
|     if (key->type == Key::IGNORE_KEY)
 | |
|     {
 | |
|       /* The key was replaced by another key */
 | |
|       if (!create_info->tmp_table() &&
 | |
|           alter_info->add_stat_drop_index(thd, &key->name))
 | |
|         DBUG_RETURN(true);
 | |
|       continue;
 | |
|     }
 | |
|     if (key->type == Key::FOREIGN_KEY)
 | |
|       continue;
 | |
|     /* Create the key name based on the first column (if not given) */
 | |
|     if (key->type == Key::PRIMARY)
 | |
|     {
 | |
|       if (primary_key)
 | |
|       {
 | |
|         my_message(ER_MULTIPLE_PRI_KEY, ER_THD(thd, ER_MULTIPLE_PRI_KEY),
 | |
|                    MYF(0));
 | |
|         DBUG_RETURN(true);
 | |
|       }
 | |
|       key_name= primary_key_name;
 | |
|       primary_key=1;
 | |
|     }
 | |
|     else if (!(key_name= key->name).str)
 | |
|     {
 | |
|       Lex_ident_column field_name(key->columns.elem(0)->field_name);
 | |
|       it.rewind();
 | |
|       while ((sql_field=it++) &&
 | |
|              !field_name.streq(sql_field->field_name))
 | |
|       { }
 | |
|       if (sql_field)
 | |
|         field_name= sql_field->field_name;
 | |
|       key_name= make_unique_key_name(thd, field_name,
 | |
|                                      *key_info_buffer, key_info);
 | |
|     }
 | |
|     if (check_if_keyname_exists(key_name, *key_info_buffer, key_info))
 | |
|     {
 | |
|       my_error(ER_DUP_KEYNAME, MYF(0), key_name.str);
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     key_info->name= key_name;
 | |
|     key->name= key_info->name;
 | |
| 
 | |
|     key_info++;
 | |
|   }
 | |
|   tmp=file->max_keys();
 | |
|   if (*key_count > tmp)
 | |
|   {
 | |
|     my_error(ER_TOO_MANY_KEYS,MYF(0),tmp);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   key_part_info= thd->calloc<KEY_PART_INFO>(key_parts);
 | |
|   if (!key_part_info)
 | |
|     DBUG_RETURN(true);				// Out of memory
 | |
| 
 | |
|   key_info= *key_info_buffer;
 | |
|   key_iterator.rewind();
 | |
|   key_number=0;
 | |
|   for (; (key=key_iterator++) ; key_number++)
 | |
|   {
 | |
|     Create_field *auto_increment_key= 0;
 | |
|     Key_part_spec *column;
 | |
|     st_plugin_int *index_plugin= hton2plugin[create_info->db_type->slot];
 | |
|     ha_create_table_option *index_options= create_info->db_type->index_options;
 | |
| 
 | |
|     bool is_hash_field_needed= key->key_create_info.algorithm
 | |
|                                == HA_KEY_ALG_LONG_HASH;
 | |
|     if (key->type == Key::IGNORE_KEY)
 | |
|     {
 | |
|       /* ignore redundant keys */
 | |
|       do
 | |
| 	key=key_iterator++;
 | |
|       while (key && key->type == Key::IGNORE_KEY);
 | |
|       if (!key)
 | |
| 	break;
 | |
|     }
 | |
| 
 | |
|     if (key_check_without_overlaps(thd, create_info, alter_info, *key))
 | |
|       DBUG_RETURN(true);
 | |
| 
 | |
|     switch (key->type) {
 | |
|     case Key::MULTIPLE:
 | |
|         key_info->flags= 0;
 | |
|         break;
 | |
|     case Key::FULLTEXT:
 | |
|         key_info->flags= HA_FULLTEXT_legacy;
 | |
|         if (key->key_create_info.algorithm == HA_KEY_ALG_UNDEF)
 | |
|           key->key_create_info.algorithm= HA_KEY_ALG_FULLTEXT;
 | |
|         if ((key_info->parser_name= &key->key_create_info.parser_name)->str)
 | |
|           key_info->flags|= HA_USES_PARSER;
 | |
|         else
 | |
|           key_info->parser_name= 0;
 | |
|         break;
 | |
|     case Key::SPATIAL:
 | |
|         key_info->flags= HA_SPATIAL_legacy;
 | |
|         if (key->key_create_info.algorithm == HA_KEY_ALG_UNDEF)
 | |
|           key->key_create_info.algorithm= HA_KEY_ALG_RTREE;
 | |
|         break;
 | |
|     case Key::FOREIGN_KEY:
 | |
|       key_number--;                             // Skip this key
 | |
|       continue;
 | |
|     case Key::VECTOR:
 | |
|         if (IF_PARTITIONING(thd->work_part_info, false))
 | |
|         {
 | |
|           my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0), "VECTOR");
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|         if (key->key_create_info.algorithm == HA_KEY_ALG_UNDEF)
 | |
|           key->key_create_info.algorithm= HA_KEY_ALG_VECTOR;
 | |
|         index_plugin= mhnsw_plugin;
 | |
|         index_options= mhnsw_index_options;
 | |
|         break;
 | |
|     case Key::IGNORE_KEY:
 | |
|       DBUG_ASSERT(0);
 | |
|       break;
 | |
|     case Key::PRIMARY:
 | |
|     case Key::UNIQUE:
 | |
|       key_info->flags = HA_NOSAME;
 | |
|       break;
 | |
|     }
 | |
|     if (key->generated)
 | |
|       key_info->flags|= HA_GENERATED_KEY;
 | |
| 
 | |
|     key_info->key_length= key->length;
 | |
|     key_info->user_defined_key_parts=(uint8) key->columns.elements;
 | |
|     key_info->key_part=key_part_info;
 | |
|     key_info->usable_key_parts= key_number;
 | |
|     key_info->algorithm= key->key_create_info.algorithm;
 | |
|     key_info->without_overlaps= key->without_overlaps;
 | |
|     key_info->option_list= key->option_list;
 | |
|     extend_option_list(thd, index_plugin, !key->old, &key_info->option_list,
 | |
|                        index_options);
 | |
|     if (parse_option_list(thd, &key_info->option_struct, &key_info->option_list,
 | |
|                           index_options, FALSE, thd->mem_root))
 | |
|       DBUG_RETURN(TRUE);
 | |
| 
 | |
|     if (key->type == Key::FULLTEXT)
 | |
|     {
 | |
|       if (!(file->ha_table_flags() & HA_CAN_FULLTEXT))
 | |
|       {
 | |
| 	my_error(ER_TABLE_CANT_HANDLE_FT, MYF(0), file->table_type());
 | |
| 	DBUG_RETURN(TRUE);
 | |
|       }
 | |
|     }
 | |
|     /*
 | |
|        Make SPATIAL to be RTREE by default
 | |
|        SPATIAL only on BLOB or at least BINARY, this
 | |
|        actually should be replaced by special GEOM type
 | |
|        in near future when new frm file is ready
 | |
|        checking for proper key parts number:
 | |
|     */
 | |
| 
 | |
|     /* TODO: Add proper checks if handler supports key_type and algorithm */
 | |
|     if (key_info->algorithm == HA_KEY_ALG_RTREE)
 | |
|     {
 | |
|       if (!(file->ha_table_flags() & HA_CAN_RTREEKEYS))
 | |
|       {
 | |
| 	my_error(ER_TABLE_CANT_HANDLE_SPKEYS, MYF(0), file->table_type());
 | |
|         DBUG_RETURN(TRUE);
 | |
|       }
 | |
|       if (key->type != Key::SPATIAL || key_info->user_defined_key_parts != 1)
 | |
|       {
 | |
| 	my_error(ER_WRONG_ARGUMENTS, MYF(0), "RTREE INDEX");
 | |
| 	DBUG_RETURN(TRUE);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /* Take block size from key part or table part */
 | |
|     /*
 | |
|       TODO: Add warning if block size changes. We can't do it here, as
 | |
|       this may depend on the size of the key
 | |
|     */
 | |
|     key_info->block_size= (key->key_create_info.block_size ?
 | |
|                            key->key_create_info.block_size :
 | |
|                            create_info->key_block_size);
 | |
| 
 | |
|     /*
 | |
|       Remember block_size for the future if the block size was given
 | |
|       either for key or table and it was given for the key during
 | |
|       create/alter table or we have an active key_block_size for the
 | |
|       table.
 | |
|       The idea is that table specific key_block_size > 0 will only affect
 | |
|       new keys and old keys will remember their original value.
 | |
|     */
 | |
|     if (key_info->block_size &&
 | |
|         ((key->key_create_info.flags & HA_USES_BLOCK_SIZE) ||
 | |
|          create_info->key_block_size))
 | |
|       key_info->flags|= HA_USES_BLOCK_SIZE;
 | |
| 
 | |
|     List_iterator<Key_part_spec> cols(key->columns), cols2(key->columns);
 | |
|     CHARSET_INFO *ft_key_charset=0;  // for FULLTEXT
 | |
|     for (uint column_nr=0 ; (column=cols++) ; column_nr++)
 | |
|     {
 | |
|       it.rewind();
 | |
|       field=0;
 | |
|       while ((sql_field=it++) &&
 | |
|              !column->field_name.streq(sql_field->field_name))
 | |
|         field++;
 | |
| 
 | |
|       if (sql_field->compression_method())
 | |
|       {
 | |
|         my_error(ER_COMPRESSED_COLUMN_USED_AS_KEY, MYF(0),
 | |
|                  column->field_name.str);
 | |
|         DBUG_RETURN(TRUE);
 | |
|       }
 | |
| 
 | |
|       cols2.rewind();
 | |
|       switch(key->type)
 | |
|       {
 | |
|       case Key::VECTOR:
 | |
|         if (sql_field->check_vcol_for_key(thd))
 | |
|           DBUG_RETURN(TRUE);
 | |
|         if (!(sql_field->flags & NOT_NULL_FLAG))
 | |
|         {
 | |
|           my_error(ER_INDEX_CANNOT_HAVE_NULL, MYF(0), "VECTOR");
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|         if (create_info->tmp_table())
 | |
|         {
 | |
|           my_error(ER_NO_INDEX_ON_TEMPORARY, MYF(0), "VECTOR",
 | |
|                    file->table_type());
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|         break;
 | |
|       case Key::FULLTEXT:
 | |
|         if (ft_key_charset && sql_field->charset != ft_key_charset)
 | |
|         {
 | |
|           my_error(ER_BAD_FT_COLUMN, MYF(0), column->field_name.str);
 | |
|           DBUG_RETURN(-1);
 | |
|         }
 | |
|         ft_key_charset= sql_field->charset;
 | |
|         break;
 | |
| 
 | |
|       case Key::PRIMARY:
 | |
|         if (!(sql_field->flags & NOT_NULL_FLAG))
 | |
|         {
 | |
|           /* Implicitly set primary key fields to NOT NULL for ISO conformance. */
 | |
|           sql_field->flags|= NOT_NULL_FLAG;
 | |
|           sql_field->pack_flag&= ~FIELDFLAG_MAYBE_NULL;
 | |
|           null_fields--;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case Key::UNIQUE:
 | |
|       case Key::MULTIPLE:
 | |
|       case Key::FOREIGN_KEY:
 | |
|         if (key_add_part_check_null(file, key_info, sql_field, column))
 | |
|           DBUG_RETURN(TRUE);
 | |
|         if (sql_field->check_vcol_for_key(thd))
 | |
|           DBUG_RETURN(TRUE);
 | |
|         break;
 | |
| 
 | |
|       case Key::IGNORE_KEY:
 | |
|         break;
 | |
| 
 | |
|       case Key::SPATIAL:
 | |
|         if (!(sql_field->flags & NOT_NULL_FLAG))
 | |
|         {
 | |
|           my_error(ER_INDEX_CANNOT_HAVE_NULL, MYF(0), "SPATIAL");
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|         if (sql_field->check_vcol_for_key(thd))
 | |
|           DBUG_RETURN(TRUE);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       for (const Key_part_spec &kp2: key->columns)
 | |
|       {
 | |
|         if (column == &kp2)
 | |
|           break;
 | |
|         if (kp2.field_name.streq(column->field_name))
 | |
|         {
 | |
|           my_error(ER_DUP_FIELDNAME, MYF(0), column->field_name.str);
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (MTYP_TYPENR(sql_field->unireg_check) == Field::NEXT_NUMBER)
 | |
|       {
 | |
|         DBUG_ASSERT(key->type != Key::FULLTEXT);
 | |
|         DBUG_ASSERT(key->type != Key::SPATIAL);
 | |
|         if (column_nr == 0 || (file->ha_table_flags() & HA_AUTO_PART_KEY))
 | |
|          auto_increment--;                        // Field is used
 | |
|         auto_increment_key= sql_field;
 | |
|       }
 | |
| 
 | |
|       key_part_info->fieldnr= field;
 | |
|       key_part_info->offset=  (uint16) sql_field->offset;
 | |
|       key_part_info->key_type=sql_field->pack_flag;
 | |
|       key_part_info->key_part_flag= column->asc ? 0 : HA_REVERSE_SORT;
 | |
|       key_part_info->length= column->length;
 | |
|       /* Use packed keys for long strings on the first column */
 | |
|       if (!((*db_options) & HA_OPTION_NO_PACK_KEYS) &&
 | |
|           !((create_info->table_options & HA_OPTION_NO_PACK_KEYS)) &&
 | |
|           (column->length >= KEY_DEFAULT_PACK_LENGTH) &&
 | |
|           !is_hash_field_needed)
 | |
|       {
 | |
|         key_info->flags|= sql_field->type_handler()->KEY_pack_flags(column_nr);
 | |
|       }
 | |
|       /* Check if the key segment is partial, set the key flag accordingly */
 | |
|       if (column->length != sql_field->type_handler()->
 | |
|                                           calc_key_length(*sql_field) &&
 | |
|           column->length != sql_field->type_handler()->max_octet_length())
 | |
|         key_info->flags|= HA_KEY_HAS_PART_KEY_SEG;
 | |
| 
 | |
|       key_part_info++;
 | |
|     }
 | |
|     if (!key_info->name.str || check_column_name(key_info->name.str))
 | |
|     {
 | |
|       my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key_info->name.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     if (key->type == Key::UNIQUE && !(key_info->flags & HA_NULL_PART_KEY))
 | |
|       unique_key=1;
 | |
| 
 | |
|     /* Check long unique keys */
 | |
|     if (is_hash_field_needed)
 | |
|     {
 | |
|       if (auto_increment_key)
 | |
|       {
 | |
|         my_error(ER_NO_AUTOINCREMENT_WITH_UNIQUE, MYF(0),
 | |
|                  sql_field->field_name.str, key_info->name.str);
 | |
|         DBUG_RETURN(TRUE);
 | |
|       }
 | |
|     }
 | |
|     if (is_hash_field_needed ||
 | |
|         (key_info->algorithm == HA_KEY_ALG_HASH &&
 | |
|          key->type != Key::PRIMARY && key_info->flags & HA_NOSAME &&
 | |
|          !(file->ha_table_flags() & HA_CAN_HASH_KEYS ) &&
 | |
|          file->ha_table_flags() & HA_CAN_VIRTUAL_COLUMNS))
 | |
|     {
 | |
|       Create_field *hash_fld=
 | |
|         add_internal_field(thd, &type_handler_ulonglong,
 | |
|                            UNSIGNED_FLAG | LONG_UNIQUE_HASH_FIELD,
 | |
|                            "DB_ROW_HASH_", &alter_info->create_list);
 | |
|       if (!hash_fld)
 | |
|         DBUG_RETURN(TRUE);
 | |
|       key_info->algorithm= HA_KEY_ALG_LONG_HASH;
 | |
|       hash_fld->offset= record_offset;
 | |
|       hash_fld->charset= create_info->default_table_charset;
 | |
|       hash_fld->vcol_info= new (thd->mem_root) Virtual_column_info();
 | |
|       hash_fld->vcol_info->set_vcol_type(VCOL_GENERATED_VIRTUAL);
 | |
|       DBUG_ASSERT(hash_fld->pack_length == HA_HASH_FIELD_LENGTH);
 | |
|       record_offset+= hash_fld->pack_length;
 | |
|       if (key_info->flags & HA_NULL_PART_KEY)
 | |
|       {
 | |
|         null_fields++;
 | |
|         hash_fld->flags&= ~NOT_NULL_FLAG;
 | |
|         hash_fld->pack_flag|= FIELDFLAG_MAYBE_NULL;
 | |
|       }
 | |
|     }
 | |
|     if (validate_comment_length(thd, &key->key_create_info.comment,
 | |
|                                 INDEX_COMMENT_MAXLEN,
 | |
|                                 ER_TOO_LONG_INDEX_COMMENT,
 | |
|                                 key_info->name.str))
 | |
|        DBUG_RETURN(TRUE);
 | |
| 
 | |
|     key_info->comment.length= key->key_create_info.comment.length;
 | |
|     if (key_info->comment.length > 0)
 | |
|     {
 | |
|       key_info->flags|= HA_USES_COMMENT;
 | |
|       key_info->comment.str= key->key_create_info.comment.str;
 | |
|     }
 | |
| 
 | |
|     // Check if a duplicate index is defined.
 | |
|     check_duplicate_key(thd, key, key_info, &alter_info->key_list);
 | |
| 
 | |
|     key_info->is_ignored= key->key_create_info.is_ignored;
 | |
|     key_info++;
 | |
|   }
 | |
| 
 | |
|   if (!unique_key && !primary_key && !create_info->sequence &&
 | |
|       (file->ha_table_flags() & HA_REQUIRE_PRIMARY_KEY))
 | |
|   {
 | |
|     my_message(ER_REQUIRES_PRIMARY_KEY, ER_THD(thd, ER_REQUIRES_PRIMARY_KEY),
 | |
|                MYF(0));
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   if (auto_increment > 0)
 | |
|   {
 | |
|     my_message(ER_WRONG_AUTO_KEY, ER_THD(thd, ER_WRONG_AUTO_KEY), MYF(0));
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
|   /*
 | |
|     We cannot do qsort of key info if MyISAM/Aria does inplace. These engines
 | |
|     do not synchronise key info on inplace alter and that qsort is
 | |
|     indeterministic (MDEV-25803).
 | |
| 
 | |
|     Yet we do not know whether we do inplace or not. That detection is done
 | |
|     after this create_table_impl() and that cannot be changed because of chicken
 | |
|     and egg problem (inplace processing requires key info made by
 | |
|     create_table_impl()).
 | |
| 
 | |
|     MyISAM/Aria cannot add index inplace so we are safe to qsort key info in
 | |
|     that case. And if we don't add index then we do not need qsort at all.
 | |
|   */
 | |
|   if (!(create_info->options & HA_SKIP_KEY_SORT))
 | |
|   {
 | |
|     /*
 | |
|       Sort keys in optimized order.
 | |
| 
 | |
|       Note: PK must be always first key, otherwise init_from_binary_frm_image()
 | |
|       can not understand it.
 | |
|     */
 | |
|     my_qsort((uchar*) *key_info_buffer, *key_count, sizeof(KEY),
 | |
|              (qsort_cmp) sort_keys);
 | |
|   }
 | |
|   create_info->null_bits= null_fields;
 | |
| 
 | |
|   if (*key_count >= 2 &&
 | |
|       (*key_info_buffer)[*key_count-2].algorithm == HA_KEY_ALG_VECTOR)
 | |
|   {
 | |
|     my_error(ER_NOT_SUPPORTED_YET, MYF(0), "multiple VECTOR indexes");
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   /* Check fields. */
 | |
|   it.rewind();
 | |
|   while ((sql_field=it++))
 | |
|   {
 | |
|     Field::utype type= (Field::utype) MTYP_TYPENR(sql_field->unireg_check);
 | |
| 
 | |
|     /*
 | |
|       Set NO_DEFAULT_VALUE_FLAG if this field doesn't have a default value and
 | |
|       it is NOT NULL, not an AUTO_INCREMENT field, not a TIMESTAMP and not
 | |
|       updated trough a NOW() function.
 | |
|     */
 | |
|     if (!sql_field->default_value &&
 | |
|         !sql_field->has_default_function() &&
 | |
|         (sql_field->flags & NOT_NULL_FLAG) &&
 | |
|         (!sql_field->is_timestamp_type() ||
 | |
|          (thd->variables.option_bits & OPTION_EXPLICIT_DEF_TIMESTAMP))&&
 | |
|         !sql_field->vers_sys_field())
 | |
|     {
 | |
|       sql_field->flags|= NO_DEFAULT_VALUE_FLAG;
 | |
|       sql_field->pack_flag|= FIELDFLAG_NO_DEFAULT;
 | |
|     }
 | |
| 
 | |
|     if (thd->variables.sql_mode & MODE_NO_ZERO_DATE &&
 | |
|         !sql_field->default_value && !sql_field->vcol_info &&
 | |
|         !sql_field->vers_sys_field() &&
 | |
|         sql_field->is_timestamp_type() &&
 | |
|         !(thd->variables.option_bits & OPTION_EXPLICIT_DEF_TIMESTAMP) &&
 | |
|         (sql_field->flags & NOT_NULL_FLAG) &&
 | |
|         (type == Field::NONE || type == Field::TIMESTAMP_UN_FIELD))
 | |
|     {
 | |
|       /*
 | |
|         An error should be reported if:
 | |
|           - NO_ZERO_DATE SQL mode is active;
 | |
|           - there is no explicit DEFAULT clause (default column value);
 | |
|           - this is a TIMESTAMP column;
 | |
|           - the column is not NULL;
 | |
|           - this is not the DEFAULT CURRENT_TIMESTAMP column.
 | |
| 
 | |
|         In other words, an error should be reported if
 | |
|           - NO_ZERO_DATE SQL mode is active;
 | |
|           - the column definition is equivalent to
 | |
|             'column_name TIMESTAMP DEFAULT 0'.
 | |
|       */
 | |
| 
 | |
|       my_error(ER_INVALID_DEFAULT, MYF(0), sql_field->field_name.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     if (sql_field->invisible == INVISIBLE_USER &&
 | |
|         sql_field->flags & NOT_NULL_FLAG &&
 | |
|         sql_field->flags & NO_DEFAULT_VALUE_FLAG &&
 | |
|         !sql_field->vers_sys_field())
 | |
|     {
 | |
|       my_error(ER_INVISIBLE_NOT_NULL_WITHOUT_DEFAULT, MYF(0),
 | |
|                           sql_field->field_name.str);
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
| 
 | |
|     if (create_simple)
 | |
|     {
 | |
|       if (sql_field->vcol_info && sql_field->vcol_info->expr &&
 | |
|           check_expression(sql_field->vcol_info, sql_field->field_name,
 | |
|                            sql_field->vcol_info->is_stored()
 | |
|                            ? VCOL_GENERATED_STORED : VCOL_GENERATED_VIRTUAL,
 | |
|                            alter_info))
 | |
|         DBUG_RETURN(TRUE);
 | |
| 
 | |
|       if (sql_field->default_value &&
 | |
|           check_expression(sql_field->default_value, sql_field->field_name,
 | |
|                            VCOL_DEFAULT, alter_info))
 | |
|         DBUG_RETURN(TRUE);
 | |
| 
 | |
|       if (sql_field->check_constraint &&
 | |
|           check_expression(sql_field->check_constraint, sql_field->field_name,
 | |
|                            VCOL_CHECK_FIELD, alter_info))
 | |
|         DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Check table level constraints */
 | |
|   create_info->check_constraint_list= &alter_info->check_constraint_list;
 | |
|   {
 | |
|     List_iterator_fast<Virtual_column_info> c_it(alter_info->check_constraint_list);
 | |
|     while (Virtual_column_info *check= c_it++)
 | |
|     {
 | |
|       if (check->name.length && !check->automatic_name)
 | |
|       {
 | |
|         /* Check that there's no repeating table CHECK constraint names. */
 | |
|         List_iterator_fast<Virtual_column_info>
 | |
|           dup_it(alter_info->check_constraint_list);
 | |
|         const Virtual_column_info *dup_check;
 | |
|         while ((dup_check= dup_it++) && dup_check != check)
 | |
|         {
 | |
|           if (check->name.streq(dup_check->name))
 | |
|           {
 | |
|             my_error(ER_DUP_CONSTRAINT_NAME, MYF(0), "CHECK", check->name.str);
 | |
|             DBUG_RETURN(TRUE);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         /* Check that there's no repeating key constraint names. */
 | |
|         List_iterator_fast<Key> key_it(alter_info->key_list);
 | |
|         while (const Key *key= key_it++)
 | |
|         {
 | |
|           if (key->type != Key::PRIMARY && key->type != Key::UNIQUE &&
 | |
|               key->type != Key::FOREIGN_KEY)
 | |
|             continue;
 | |
| 
 | |
|           if (check->name.streq(key->name))
 | |
|           {
 | |
|             my_error(ER_DUP_CONSTRAINT_NAME, MYF(0), "CHECK", check->name.str);
 | |
|             DBUG_RETURN(TRUE);
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         if (check_string_char_length(&check->name, 0, NAME_CHAR_LEN, scs, 1))
 | |
|         {
 | |
|           my_error(ER_TOO_LONG_IDENT, MYF(0), check->name.str);
 | |
|           DBUG_RETURN(TRUE);
 | |
|         }
 | |
|       }
 | |
|       if (check_expression(check, check->name, VCOL_CHECK_TABLE, alter_info))
 | |
|         DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Give warnings for not supported table options */
 | |
|   if (create_info->used_fields & HA_CREATE_USED_TRANSACTIONAL &&
 | |
|       !file->has_transactional_option() && !thd->rgi_slave)
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_OPTION,
 | |
|                         ER_THD(thd, ER_UNKNOWN_OPTION), "transactional");
 | |
| 
 | |
|   extend_option_list(thd, file->partition_ht(),
 | |
|               !thd->lex->create_like() && create_table_mode > C_ALTER_TABLE,
 | |
|               &create_info->option_list, file->partition_ht()->table_options);
 | |
|   if (parse_option_list(thd, &create_info->option_struct,
 | |
|                         &create_info->option_list,
 | |
|                         file->partition_ht()->table_options, FALSE,
 | |
|                         thd->mem_root))
 | |
|       DBUG_RETURN(TRUE);
 | |
| 
 | |
|   DBUG_EXECUTE_IF("key",
 | |
|     Debug_key::print_keys(thd, "prep_create_table: ",
 | |
|                           *key_info_buffer, *key_count);
 | |
|   );
 | |
| 
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Preparation for table creation
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_prepare_create_table()
 | |
|       thd                       Thread object.
 | |
|       create_info               Create information (like MAX_ROWS).
 | |
|       alter_info                List of columns and indexes to create
 | |
|       db_options          INOUT Table options (like HA_OPTION_PACK_RECORD).
 | |
|       file                      The handler for the new table.
 | |
|       key_info_buffer     OUT   An array of KEY structs for the indexes.
 | |
|       key_count           OUT   The number of elements in the array.
 | |
|       create_table_mode         C_ORDINARY_CREATE, C_ALTER_TABLE,
 | |
|                                 C_CREATE_SELECT, C_ASSISTED_DISCOVERY
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Prepares the table and key structures for table creation.
 | |
| 
 | |
|   NOTES
 | |
|     sets create_info->varchar if the table has a varchar
 | |
| 
 | |
|   RETURN VALUES
 | |
|     FALSE    OK
 | |
|     TRUE     error
 | |
| */
 | |
| 
 | |
| static int
 | |
| mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info,
 | |
|                            Alter_info *alter_info, uint *db_options,
 | |
|                            handler *file, KEY **key_info_buffer,
 | |
|                            uint *key_count, int create_table_mode)
 | |
| {
 | |
|   return mysql_prepare_create_table_stage1(thd, create_info, alter_info) ||
 | |
|          mysql_prepare_create_table_finalize(thd, create_info, alter_info,
 | |
|                                              db_options, file, key_info_buffer,
 | |
|                                              key_count, create_table_mode);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   check comment length of table, column, index and partition
 | |
| 
 | |
|   If comment length is more than the standard length
 | |
|   truncate it and store the comment length upto the standard
 | |
|   comment length size
 | |
| 
 | |
|   @param          thd             Thread handle
 | |
|   @param[in,out]  comment         Comment
 | |
|   @param          max_len         Maximum allowed comment length
 | |
|   @param          err_code        Error message
 | |
|   @param          name            Name of commented object
 | |
| 
 | |
|   @return Operation status
 | |
|     @retval       true            Error found
 | |
|     @retval       false           On Success
 | |
| */
 | |
| bool validate_comment_length(THD *thd, LEX_CSTRING *comment, size_t max_len,
 | |
|                              uint err_code, const char *name)
 | |
| {
 | |
|   DBUG_ENTER("validate_comment_length");
 | |
|   if (comment->length == 0)
 | |
|     DBUG_RETURN(false);
 | |
| 
 | |
|   size_t tmp_len=
 | |
|       Well_formed_prefix(system_charset_info, *comment, max_len).length();
 | |
|   if (tmp_len < comment->length)
 | |
|   {
 | |
|     if (comment->length <= max_len)
 | |
|     {
 | |
|       if (thd->is_strict_mode())
 | |
|       {
 | |
|          my_error(ER_INVALID_CHARACTER_STRING, MYF(0),
 | |
|                   system_charset_info->cs_name.str, comment->str);
 | |
|          DBUG_RETURN(true);
 | |
|       }
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                           ER_INVALID_CHARACTER_STRING,
 | |
|                           ER_THD(thd, ER_INVALID_CHARACTER_STRING),
 | |
|                           system_charset_info->cs_name.str, comment->str);
 | |
|       comment->length= tmp_len;
 | |
|       DBUG_RETURN(false);
 | |
|     }
 | |
|     if (thd->is_strict_mode())
 | |
|     {
 | |
|        my_error(err_code, MYF(0), name, static_cast<ulong>(max_len));
 | |
|        DBUG_RETURN(true);
 | |
|     }
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, err_code,
 | |
|                         ER_THD(thd, err_code), name,
 | |
|                         static_cast<ulong>(max_len));
 | |
|     comment->length= tmp_len;
 | |
|   }
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Extend long VARCHAR fields to blob & prepare field if it's a blob
 | |
| 
 | |
|   SYNOPSIS
 | |
|     prepare_blob_field()
 | |
| 
 | |
|   RETURN
 | |
|     0	ok
 | |
|     1	Error (sql_field can't be converted to blob)
 | |
|         In this case the error is given
 | |
| */
 | |
| 
 | |
| bool Column_definition::prepare_blob_field(THD *thd)
 | |
| {
 | |
|   DBUG_ENTER("Column_definition::prepare_blob_field");
 | |
| 
 | |
|   if (length > MAX_FIELD_VARCHARLENGTH && !(flags & BLOB_FLAG))
 | |
|   {
 | |
|     /* Convert long VARCHAR columns to TEXT or BLOB */
 | |
|     char warn_buff[MYSQL_ERRMSG_SIZE];
 | |
| 
 | |
|     if (thd->is_strict_mode())
 | |
|     {
 | |
|       my_error(ER_TOO_BIG_FIELDLENGTH, MYF(0), field_name.str,
 | |
|                static_cast<ulong>(MAX_FIELD_VARCHARLENGTH / charset->mbmaxlen));
 | |
|       DBUG_RETURN(1);
 | |
|     }
 | |
|     set_handler(&type_handler_blob);
 | |
|     flags|= BLOB_FLAG;
 | |
|     my_snprintf(warn_buff, sizeof(warn_buff), ER_THD(thd, ER_AUTO_CONVERT),
 | |
|                 field_name.str,
 | |
|                 (charset == &my_charset_bin) ? "VARBINARY" : "VARCHAR",
 | |
|                 (charset == &my_charset_bin) ? "BLOB" : "TEXT");
 | |
|     push_warning(thd, Sql_condition::WARN_LEVEL_NOTE, ER_AUTO_CONVERT,
 | |
|                  warn_buff);
 | |
|   }
 | |
| 
 | |
|   if ((flags & BLOB_FLAG) && length)
 | |
|   {
 | |
|     if (real_field_type() == FIELD_TYPE_BLOB ||
 | |
|         real_field_type() == FIELD_TYPE_TINY_BLOB ||
 | |
|         real_field_type() == FIELD_TYPE_MEDIUM_BLOB)
 | |
|     {
 | |
|       /* The user has given a length to the blob column */
 | |
|       set_handler(Type_handler::blob_type_handler((uint) length));
 | |
|       pack_length= type_handler()->calc_pack_length(0);
 | |
|     }
 | |
|     length= 0;
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Preparation of Create_field for SP function return values.
 | |
|   Based on code used in the inner loop of mysql_prepare_create_table()
 | |
|   above.
 | |
| 
 | |
|   SYNOPSIS
 | |
|     sp_prepare_create_field()
 | |
|     thd                 Thread object
 | |
|     mem_root            Memory root to allocate components on (e.g. interval)
 | |
| 
 | |
|   DESCRIPTION
 | |
|     Prepares the field structures for field creation.
 | |
| 
 | |
| */
 | |
| 
 | |
| bool Column_definition::sp_prepare_create_field(THD *thd, MEM_ROOT *mem_root)
 | |
| {
 | |
|   const Column_derived_attributes dattr(thd->variables.collation_database);
 | |
|   return prepare_stage1(thd, mem_root,
 | |
|                         COLUMN_DEFINITION_ROUTINE_LOCAL, &dattr) ||
 | |
|          prepare_stage2(NULL, HA_CAN_GEOMETRY);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Appends key parts generated by mariadb server.
 | |
|   Adds row_end in UNIQUE keys for system versioning,
 | |
|   and period fields for WITHOUT OVERLAPS.
 | |
|   @param thd Thread data
 | |
|   @param create_info Table create info
 | |
|   @param key Parsed key
 | |
|   @return a number of key parts added to key.
 | |
|  */
 | |
| static int append_system_key_parts(THD *thd, HA_CREATE_INFO *create_info,
 | |
|                                     Key *key)
 | |
| {
 | |
|   const Lex_ident_column &row_start_field= create_info->vers_info.as_row.start;
 | |
|   const Lex_ident_column &row_end_field= create_info->vers_info.as_row.end;
 | |
|   DBUG_ASSERT(!create_info->versioned() || (row_start_field && row_end_field));
 | |
| 
 | |
|   int result = 0;
 | |
|   if (create_info->versioned() && (key->type == Key::PRIMARY
 | |
|                                    || key->type == Key::UNIQUE))
 | |
|   {
 | |
|     Key_part_spec *key_part=NULL;
 | |
|     List_iterator<Key_part_spec> part_it(key->columns);
 | |
|     while ((key_part=part_it++))
 | |
|     {
 | |
|       if (row_start_field.streq(key_part->field_name) ||
 | |
|           row_end_field.streq(key_part->field_name))
 | |
|         break;
 | |
|     }
 | |
|     if (!key_part)
 | |
|     {
 | |
|       key->columns.push_back(new (thd->mem_root)
 | |
|                                Key_part_spec(&row_end_field, 0, true));
 | |
|       result++;
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   if (key->without_overlaps)
 | |
|   {
 | |
|     DBUG_ASSERT(key->type == Key::PRIMARY || key->type == Key::UNIQUE);
 | |
|     if (!create_info->period_info.is_set()
 | |
|         || !key->period.streq(create_info->period_info.name))
 | |
|     {
 | |
|       my_error(ER_PERIOD_NOT_FOUND, MYF(0), key->period.str);
 | |
|       return -1;
 | |
|     }
 | |
|     const auto &period= create_info->period_info.period;
 | |
|     key->columns.push_back(new (thd->mem_root)
 | |
|                            Key_part_spec(&period.end, 0, true));
 | |
|     key->columns.push_back(new (thd->mem_root)
 | |
|                            Key_part_spec(&period.start, 0, true));
 | |
|     result += 2;
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| handler *mysql_create_frm_image(THD *thd, HA_CREATE_INFO *create_info,
 | |
|                                 Alter_info *alter_info, int create_table_mode,
 | |
|                                 KEY **key_info, uint *key_count,
 | |
|                                 LEX_CUSTRING *frm)
 | |
| {
 | |
|   uint		db_options;
 | |
|   handler       *file;
 | |
|   DBUG_ENTER("mysql_create_frm_image");
 | |
| 
 | |
|   DBUG_ASSERT(create_info->default_table_charset);
 | |
| 
 | |
|   if (!alter_info->create_list.elements)
 | |
|   {
 | |
|     my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0));
 | |
|     DBUG_RETURN(NULL);
 | |
|   }
 | |
| 
 | |
|   if (mysql_prepare_create_table_stage1(thd, create_info, alter_info))
 | |
|     DBUG_RETURN(NULL);
 | |
| 
 | |
|   db_options= create_info->table_options_with_row_type();
 | |
| 
 | |
|   if (unlikely(!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root,
 | |
|                                        create_info->db_type))))
 | |
|     DBUG_RETURN(NULL);
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   partition_info *part_info= thd->work_part_info;
 | |
| 
 | |
|   if (!part_info && create_info->db_type->partition_flags &&
 | |
|       (create_info->db_type->partition_flags() & HA_USE_AUTO_PARTITION))
 | |
|   {
 | |
|     /*
 | |
|       Table is not defined as a partitioned table but the engine handles
 | |
|       all tables as partitioned. The handler will set up the partition info
 | |
|       object with the default settings.
 | |
|     */
 | |
|     thd->work_part_info= part_info= new partition_info();
 | |
|     if (unlikely(!part_info))
 | |
|       goto err;
 | |
| 
 | |
|     file->set_auto_partitions(part_info);
 | |
|     part_info->default_engine_type= create_info->db_type;
 | |
|     part_info->is_auto_partitioned= TRUE;
 | |
|   }
 | |
|   if (part_info)
 | |
|   {
 | |
|     /*
 | |
|       The table has been specified as a partitioned table.
 | |
|       If this is part of an ALTER TABLE the handler will be the partition
 | |
|       handler but we need to specify the default handler to use for
 | |
|       partitions also in the call to check_partition_info. We transport
 | |
|       this information in the default_db_type variable, it is either
 | |
|       DB_TYPE_DEFAULT or the engine set in the ALTER TABLE command.
 | |
|     */
 | |
|     handlerton *part_engine_type= create_info->db_type;
 | |
|     char *part_syntax_buf;
 | |
|     uint syntax_len;
 | |
|     handlerton *engine_type;
 | |
|     List_iterator<partition_element> part_it(part_info->partitions);
 | |
|     partition_element *part_elem;
 | |
| 
 | |
|     while ((part_elem= part_it++))
 | |
|     {
 | |
|       if (part_elem->part_comment)
 | |
|       {
 | |
|         LEX_CSTRING comment= { part_elem->part_comment,
 | |
|                                strlen(part_elem->part_comment)
 | |
|         };
 | |
|         if (validate_comment_length(thd, &comment,
 | |
|                                      TABLE_PARTITION_COMMENT_MAXLEN,
 | |
|                                      ER_TOO_LONG_TABLE_PARTITION_COMMENT,
 | |
|                                      part_elem->partition_name.str))
 | |
|           DBUG_RETURN(NULL);
 | |
|         /* cut comment length. Safe to do in all cases */
 | |
|         ((char*)part_elem->part_comment)[comment.length]= '\0';
 | |
|       }
 | |
|       if (part_elem->subpartitions.elements)
 | |
|       {
 | |
|         List_iterator<partition_element> sub_it(part_elem->subpartitions);
 | |
|         partition_element *subpart_elem;
 | |
|         while ((subpart_elem= sub_it++))
 | |
|         {
 | |
|           if (subpart_elem->part_comment)
 | |
|           {
 | |
|             LEX_CSTRING comment= {
 | |
|               subpart_elem->part_comment, strlen(subpart_elem->part_comment)
 | |
|             };
 | |
|             if (validate_comment_length(thd, &comment,
 | |
|                                          TABLE_PARTITION_COMMENT_MAXLEN,
 | |
|                                          ER_TOO_LONG_TABLE_PARTITION_COMMENT,
 | |
|                                          subpart_elem->partition_name.str))
 | |
|               DBUG_RETURN(NULL);
 | |
|             /* cut comment length. Safe to do in all cases */
 | |
|             ((char*)subpart_elem->part_comment)[comment.length]= '\0';
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     } 
 | |
| 
 | |
|     if (create_info->tmp_table())
 | |
|     {
 | |
|       my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0), "CREATE TEMPORARY TABLE");
 | |
|       goto err;
 | |
|     }
 | |
|     if ((part_engine_type == partition_hton) &&
 | |
|         part_info->default_engine_type)
 | |
|     {
 | |
|       /*
 | |
|         This only happens at ALTER TABLE.
 | |
|         default_engine_type was assigned from the engine set in the ALTER
 | |
|         TABLE command.
 | |
|       */
 | |
|       ;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (create_info->used_fields & HA_CREATE_USED_ENGINE)
 | |
|       {
 | |
|         part_info->default_engine_type= create_info->db_type;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (part_info->default_engine_type == NULL)
 | |
|         {
 | |
|           part_info->default_engine_type= ha_default_handlerton(thd);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     DBUG_PRINT("info", ("db_type = %s create_info->db_type = %s",
 | |
|              ha_resolve_storage_engine_name(part_info->default_engine_type),
 | |
|              ha_resolve_storage_engine_name(create_info->db_type)));
 | |
|     if (part_info->check_partition_info(thd, &engine_type, file,
 | |
|                                         create_info, FALSE))
 | |
|       goto err;
 | |
|     part_info->default_engine_type= engine_type;
 | |
| 
 | |
|     if (part_info->vers_info && !create_info->versioned())
 | |
|     {
 | |
|       my_error(ER_VERS_NOT_VERSIONED, MYF(0), alter_info->table_name.str);
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       We reverse the partitioning parser and generate a standard format
 | |
|       for syntax stored in frm file.
 | |
|     */
 | |
|     part_syntax_buf= generate_partition_syntax_for_frm(thd, part_info,
 | |
|                                       &syntax_len, create_info, alter_info);
 | |
|     if (!part_syntax_buf)
 | |
|       goto err;
 | |
|     part_info->part_info_string= part_syntax_buf;
 | |
|     part_info->part_info_len= syntax_len;
 | |
|     if ((!(engine_type->partition_flags &&
 | |
|            ((engine_type->partition_flags() & HA_CAN_PARTITION) ||
 | |
|             (part_info->part_type == VERSIONING_PARTITION &&
 | |
|             engine_type->partition_flags() & HA_ONLY_VERS_PARTITION))
 | |
|           )) ||
 | |
|         create_info->db_type == partition_hton)
 | |
|     {
 | |
|       /*
 | |
|         The handler assigned to the table cannot handle partitioning.
 | |
|         Assign the partition handler as the handler of the table.
 | |
|       */
 | |
|       DBUG_PRINT("info", ("db_type: %s",
 | |
|                         ha_resolve_storage_engine_name(create_info->db_type)));
 | |
|       delete file;
 | |
|       create_info->db_type= partition_hton;
 | |
|       if (!(file= get_ha_partition(part_info)))
 | |
|         DBUG_RETURN(NULL);
 | |
| 
 | |
|       /*
 | |
|         If we have default number of partitions or subpartitions we
 | |
|         might require to set-up the part_info object such that it
 | |
|         creates a proper .par file. The current part_info object is
 | |
|         only used to create the frm-file and .par-file.
 | |
|       */
 | |
|       if (part_info->use_default_num_partitions &&
 | |
|           part_info->num_parts &&
 | |
|           (int)part_info->num_parts !=
 | |
|           file->get_default_no_partitions(create_info))
 | |
|       {
 | |
|         uint i;
 | |
|         List_iterator<partition_element> part_it(part_info->partitions);
 | |
|         part_it++;
 | |
|         DBUG_ASSERT(thd->lex->sql_command != SQLCOM_CREATE_TABLE);
 | |
|         for (i= 1; i < part_info->partitions.elements; i++)
 | |
|           (part_it++)->part_state= PART_TO_BE_DROPPED;
 | |
|       }
 | |
|       else if (part_info->is_sub_partitioned() &&
 | |
|                part_info->use_default_num_subpartitions &&
 | |
|                part_info->num_subparts &&
 | |
|                (int)part_info->num_subparts !=
 | |
|                  file->get_default_no_partitions(create_info))
 | |
|       {
 | |
|         DBUG_ASSERT(thd->lex->sql_command != SQLCOM_CREATE_TABLE);
 | |
|         part_info->num_subparts= file->get_default_no_partitions(create_info);
 | |
|       }
 | |
|     }
 | |
|     else if (create_info->db_type != engine_type)
 | |
|     {
 | |
|       /*
 | |
|         We come here when we don't use a partitioned handler.
 | |
|         Since we use a partitioned table it must be "native partitioned".
 | |
|         We have switched engine from defaults, most likely only specified
 | |
|         engines in partition clauses.
 | |
|       */
 | |
|       delete file;
 | |
|       if (unlikely(!(file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root,
 | |
|                                            engine_type))))
 | |
|         DBUG_RETURN(NULL);
 | |
|     }
 | |
|   }
 | |
|   /*
 | |
|     Unless table's storage engine supports partitioning natively
 | |
|     don't allow foreign keys on partitioned tables (they won't
 | |
|     work work even with InnoDB beneath of partitioning engine).
 | |
|     If storage engine handles partitioning natively (like NDB)
 | |
|     foreign keys support is possible, so we let the engine decide.
 | |
|   */
 | |
|   if (create_info->db_type == partition_hton)
 | |
|   {
 | |
|     List_iterator_fast<Key> key_iterator(alter_info->key_list);
 | |
|     Key *key;
 | |
|     while ((key= key_iterator++))
 | |
|     {
 | |
|       if (key->type == Key::FOREIGN_KEY)
 | |
|       {
 | |
|         my_error(ER_FEATURE_NOT_SUPPORTED_WITH_PARTITIONING, MYF(0),
 | |
|                  "FOREIGN KEY");
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (mysql_prepare_create_table_finalize(thd, create_info,
 | |
|                                           alter_info, &db_options,
 | |
|                                           file, key_info, key_count,
 | |
|                                           create_table_mode))
 | |
|     goto err;
 | |
|   create_info->table_options=db_options;
 | |
| 
 | |
|   *frm= build_frm_image(thd, alter_info->table_name, create_info,
 | |
|                         alter_info->create_list, *key_count, *key_info, file);
 | |
| 
 | |
|   if (frm->str)
 | |
|     DBUG_RETURN(file);
 | |
| 
 | |
| err:
 | |
|   delete file;
 | |
|   DBUG_RETURN(NULL);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Create a table
 | |
| 
 | |
|   @param thd                 Thread object
 | |
|   @param orig_db             Database for error messages
 | |
|   @param orig_table_name     Table name for error messages
 | |
|                              (it's different from table_name for ALTER TABLE)
 | |
|   @param db                  Database
 | |
|   @param table_name          Table name
 | |
|   @param path                Path to table (i.e. to its .FRM file without
 | |
|                              the extension).
 | |
|   @param create_info         Create information (like MAX_ROWS)
 | |
|   @param alter_info          Description of fields and keys for new table
 | |
|   @param create_table_mode   C_ORDINARY_CREATE, C_ALTER_TABLE,
 | |
|                              C_ASSISTED_DISCOVERY or C_ALTER_TABLE_FRM_ONLY.
 | |
|                              or any positive number (for C_CREATE_SELECT).
 | |
|                              If set to C_ALTER_TABLE_FRM_ONY then no frm or
 | |
|                              table is created, only the frm image in memory.
 | |
|   @param[out] is_trans       Identifies the type of engine where the table
 | |
|                              was created: either trans or non-trans.
 | |
|   @param[out] key_info       Array of KEY objects describing keys in table
 | |
|                              which was created.
 | |
|   @param[out] key_count      Number of keys in table which was created.
 | |
|   @param[out] frm            The frm image.
 | |
| 
 | |
|   If one creates a temporary table, its is automatically opened and its
 | |
|   TABLE_SHARE is added to THD::all_temp_tables list.
 | |
| 
 | |
|   Note that this function assumes that caller already have taken
 | |
|   exclusive metadata lock on table being created or used some other
 | |
|   way to ensure that concurrent operations won't intervene.
 | |
|   mysql_create_table() is a wrapper that can be used for this.
 | |
| 
 | |
|   @retval 0 OK
 | |
|   @retval 1 error
 | |
|   @retval -1 table existed but IF NOT EXISTS was used
 | |
| */
 | |
| 
 | |
| static
 | |
| int create_table_impl(THD *thd,
 | |
|                       DDL_LOG_STATE *ddl_log_state_create,
 | |
|                       DDL_LOG_STATE *ddl_log_state_rm,
 | |
|                       const Lex_ident_db &orig_db,
 | |
|                       const Lex_ident_table &orig_table_name,
 | |
|                       const LEX_CSTRING &db, const LEX_CSTRING &table_name,
 | |
|                       const LEX_CSTRING &path, const DDL_options_st options,
 | |
|                       HA_CREATE_INFO *create_info, Alter_info *alter_info,
 | |
|                       int create_table_mode, bool *is_trans, KEY **key_info,
 | |
|                       uint *key_count, LEX_CUSTRING *frm)
 | |
| {
 | |
|   LEX_CSTRING	*alias;
 | |
|   handler	*file= 0;
 | |
|   int		error= 1;
 | |
|   bool          frm_only= create_table_mode == C_ALTER_TABLE_FRM_ONLY;
 | |
|   bool          internal_tmp_table= create_table_mode == C_ALTER_TABLE || frm_only;
 | |
|   DBUG_ENTER("create_table_impl");
 | |
|   DBUG_PRINT("enter", ("db: '%s'  table: '%s'  tmp: %d  path: %s",
 | |
|                        db.str, table_name.str, internal_tmp_table, path.str));
 | |
| 
 | |
|   DBUG_ASSERT(create_info->default_table_charset);
 | |
| 
 | |
|   /* Easy check for ddl logging if we are creating a temporary table */
 | |
|   if (create_info->tmp_table())
 | |
|   {
 | |
|     ddl_log_state_create= 0;
 | |
|     ddl_log_state_rm= 0;
 | |
|   }
 | |
| 
 | |
|   if (fix_constraints_names(thd, &alter_info->check_constraint_list,
 | |
|                             create_info))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   if (thd->variables.sql_mode & MODE_NO_DIR_IN_CREATE)
 | |
|   {
 | |
|     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= 0;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (unlikely(error_if_data_home_dir(create_info->data_file_name,
 | |
|                                         "DATA DIRECTORY")) ||
 | |
|         unlikely(error_if_data_home_dir(create_info->index_file_name,
 | |
|                                         "INDEX DIRECTORY")) ||
 | |
|         unlikely(check_partition_dirs(thd->lex->part_info)))
 | |
|       goto err;
 | |
|   }
 | |
| 
 | |
|   alias= const_cast<LEX_CSTRING*>(table_case_name(create_info, &table_name));
 | |
| 
 | |
|   /* Check if table exists */
 | |
|   if (create_info->tmp_table())
 | |
|   {
 | |
|     /*
 | |
|       If a table exists, it must have been pre-opened. Try looking for one
 | |
|       in-use in THD::all_temp_tables list of TABLE_SHAREs.
 | |
|     */
 | |
|     TABLE *tmp_table= internal_tmp_table ? NULL :
 | |
|       thd->find_temporary_table(Lex_ident_db(db), Lex_ident_table(table_name),
 | |
|                                 THD::TMP_TABLE_ANY);
 | |
| 
 | |
|     if (tmp_table)
 | |
|     {
 | |
|       bool table_creation_was_logged= tmp_table->s->table_creation_was_logged;
 | |
|       if (options.or_replace())
 | |
|       {
 | |
|         /*
 | |
|           We are using CREATE OR REPLACE on an existing temporary table
 | |
|           Remove the old table so that we can re-create it.
 | |
|         */
 | |
|         if (thd->drop_temporary_table(tmp_table, NULL, true))
 | |
|           goto err;
 | |
|       }
 | |
|       else if (options.if_not_exists())
 | |
|         goto warn;
 | |
|       else
 | |
|       {
 | |
|         my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alias->str);
 | |
|         goto err;
 | |
|       }
 | |
|       /*
 | |
|         We have to log this query, even if it failed later to ensure the
 | |
|         drop is done.
 | |
|       */
 | |
|       if (table_creation_was_logged)
 | |
|       {
 | |
|         thd->variables.option_bits|= OPTION_BINLOG_THIS;
 | |
|         create_info->table_was_deleted= 1;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (ha_check_if_updates_are_ignored(thd, create_info->db_type, "CREATE"))
 | |
|     {
 | |
|       /*
 | |
|         Don't create table. CREATE will still be logged in binary log
 | |
|         This can happen for shared storage engines that supports
 | |
|         ENGINE= in the create statement (Note that S3 doesn't support this.
 | |
|       */
 | |
|       error= 0;
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     handlerton *db_type;
 | |
|     if (!internal_tmp_table &&
 | |
|         ha_table_exists(thd, &db, &table_name,
 | |
|                         &create_info->org_tabledef_version, &db_type))
 | |
|     {
 | |
|       if (ha_check_if_updates_are_ignored(thd, db_type, "CREATE"))
 | |
|       {
 | |
|         /* Don't create table. CREATE will still be logged in binary log */
 | |
|         error= 0;
 | |
|         goto err;
 | |
|       }
 | |
| 
 | |
|       if (options.or_replace())
 | |
|       {
 | |
|         (void) delete_statistics_for_table(thd, &db, &table_name);
 | |
| 
 | |
|         TABLE_LIST table_list;
 | |
|         table_list.init_one_table(&db, &table_name, 0, TL_WRITE_ALLOW_WRITE);
 | |
|         table_list.table= create_info->table;
 | |
| 
 | |
|         if (check_if_log_table(&table_list, TRUE, "CREATE OR REPLACE"))
 | |
|           goto err;
 | |
|         
 | |
|         /*
 | |
|           Rollback the empty transaction started in mysql_create_table()
 | |
|           call to open_and_lock_tables() when we are using LOCK TABLES.
 | |
|         */
 | |
|         {
 | |
|           uint save_unsafe_rollback_flags=
 | |
|             thd->transaction->stmt.m_unsafe_rollback_flags;
 | |
|           (void) trans_rollback_stmt(thd);
 | |
|           thd->transaction->stmt.m_unsafe_rollback_flags=
 | |
|             save_unsafe_rollback_flags;
 | |
|         }
 | |
|         /* Remove normal table without logging. Keep tables locked */
 | |
|         if (mysql_rm_table_no_locks(thd, &table_list, &thd->db,
 | |
|                                     ddl_log_state_rm,
 | |
|                                     0, 0, 0, 0, 1, 1))
 | |
|           goto err;
 | |
| 
 | |
|         debug_crash_here("ddl_log_create_after_drop");
 | |
| 
 | |
|         /*
 | |
|           We have to log this query, even if it failed later to ensure the
 | |
|           drop is done.
 | |
|         */
 | |
|         thd->variables.option_bits|= OPTION_BINLOG_THIS;
 | |
|         create_info->table_was_deleted= 1;
 | |
|         lex_string_set(&create_info->org_storage_engine_name,
 | |
|                        ha_resolve_storage_engine_name(db_type));
 | |
|         DBUG_EXECUTE_IF("send_kill_after_delete",
 | |
|                         thd->set_killed(KILL_QUERY););
 | |
|         /*
 | |
|           Restart statement transactions for the case of CREATE ... SELECT.
 | |
|         */
 | |
|         if (thd->lex->first_select_lex()->item_list.elements &&
 | |
|             restart_trans_for_tables(thd, thd->lex->query_tables))
 | |
|           goto err;
 | |
|       }
 | |
|       else if (options.if_not_exists())
 | |
|       {
 | |
|         /*
 | |
|           We never come here as part of normal create table as table existence
 | |
|           is  checked in open_and_lock_tables(). We may come here as part of
 | |
|           ALTER TABLE when converting a table for a distributed engine to a
 | |
|           a local one.
 | |
|         */
 | |
| 
 | |
|         /* Log CREATE IF NOT EXISTS on slave for distributed engines */
 | |
|         if (thd->slave_thread && db_type &&
 | |
|             db_type->flags & HTON_IGNORE_UPDATES)
 | |
|           thd->variables.option_bits|= OPTION_BINLOG_THIS;
 | |
|         goto warn;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         my_error(ER_TABLE_EXISTS_ERROR, MYF(0), table_name.str);
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_creating_table);
 | |
| 
 | |
|   if (check_engine(thd, orig_db.str, orig_table_name.str, create_info))
 | |
|     goto err;
 | |
| 
 | |
|   if (create_table_mode == C_ASSISTED_DISCOVERY)
 | |
|   {
 | |
|     /* check that it's used correctly */
 | |
|     DBUG_ASSERT(alter_info->create_list.elements == 0);
 | |
|     DBUG_ASSERT(alter_info->key_list.elements == 0);
 | |
| 
 | |
|     TABLE_SHARE share;
 | |
|     handlerton *hton= create_info->db_type;
 | |
|     int ha_err;
 | |
|     Field *no_fields= 0;
 | |
| 
 | |
|     if (!hton->discover_table_structure)
 | |
|     {
 | |
|       my_error(ER_TABLE_MUST_HAVE_COLUMNS, MYF(0));
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     init_tmp_table_share(thd, &share, db.str, 0, table_name.str, path.str, true);
 | |
| 
 | |
|     /* prepare everything for discovery */
 | |
|     share.field= &no_fields;
 | |
|     share.db_plugin= ha_lock_engine(thd, hton);
 | |
|     share.option_list= create_info->option_list;
 | |
|     share.connect_string= create_info->connect_string;
 | |
| 
 | |
|     if (parse_engine_table_options(thd, hton, &share))
 | |
|       goto err;
 | |
| 
 | |
|     /*
 | |
|       Log that we are going to do discovery. If things fails, any generated
 | |
|       .frm files are deleted
 | |
|     */
 | |
|     if (ddl_log_state_create)
 | |
|       ddl_log_create_table(ddl_log_state_create, (handlerton*) 0, &path,
 | |
|                            &db, &table_name, 1);
 | |
| 
 | |
|     ha_err= hton->discover_table_structure(hton, thd, &share, create_info);
 | |
| 
 | |
|     /*
 | |
|       if discovery failed, the plugin will be auto-unlocked, as it
 | |
|       was locked on the THD, see above.
 | |
|       if discovery succeeded, the plugin was replaced by a globally
 | |
|       locked plugin, that will be unlocked by free_table_share()
 | |
|     */
 | |
|     if (ha_err)
 | |
|       share.db_plugin= 0; // will be auto-freed, locked above on the THD
 | |
| 
 | |
|     free_table_share(&share);
 | |
| 
 | |
|     if (ha_err)
 | |
|     {
 | |
|       my_error(ER_GET_ERRNO, MYF(0), ha_err, hton_name(hton)->str);
 | |
|       goto err;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (ddl_log_state_create)
 | |
|       ddl_log_create_table(ddl_log_state_create, create_info->db_type,
 | |
|                            &path, &db, &table_name, frm_only);
 | |
|     debug_crash_here("ddl_log_create_before_create_frm");
 | |
| 
 | |
|     alter_info->db= orig_db;
 | |
|     alter_info->table_name= orig_table_name;
 | |
|     file= mysql_create_frm_image(thd, create_info, alter_info,
 | |
|                                  create_table_mode, key_info, key_count, frm);
 | |
|     /*
 | |
|       TODO: remove this check of thd->is_error() (now it intercept
 | |
|       errors in some val_*() methods and bring some single place to
 | |
|       such error interception).
 | |
|     */
 | |
|     if (!file || thd->is_error())
 | |
|     {
 | |
|       if (!file)
 | |
|         deletefrm(path.str);
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     if (thd->variables.keep_files_on_create)
 | |
|       create_info->options|= HA_CREATE_KEEP_FILES;
 | |
| 
 | |
|     if (file->ha_create_partitioning_metadata(path.str, NULL, CHF_CREATE_FLAG))
 | |
|       goto err;
 | |
| 
 | |
|     if (!frm_only)
 | |
|     {
 | |
|       debug_crash_here("ddl_log_create_before_create_table");
 | |
|       if (ha_create_table(thd, path.str, db.str, table_name.str, create_info,
 | |
|                           frm, 0))
 | |
|       {
 | |
|         file->ha_create_partitioning_metadata(path.str, NULL, CHF_DELETE_FLAG);
 | |
|         deletefrm(path.str);
 | |
|         goto err;
 | |
|       }
 | |
|       debug_crash_here("ddl_log_create_after_create_table");
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   create_info->table= 0;
 | |
|   if (!frm_only && create_info->tmp_table())
 | |
|   {
 | |
|     TABLE *table= thd->create_and_open_tmp_table(frm, path.str,
 | |
|                                                  Lex_ident_db(db),
 | |
|                                                  Lex_ident_table(table_name),
 | |
|                                                  false);
 | |
| 
 | |
|     if (!table)
 | |
|     {
 | |
|       (void) thd->rm_temporary_table(create_info->db_type, path.str);
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     if (is_trans != NULL)
 | |
|       *is_trans= table->file->has_transactions();
 | |
| 
 | |
|     thd->reset_sp_cache= true;
 | |
|     thd->used|= THD::THREAD_SPECIFIC_USED;
 | |
|     create_info->table= table;                  // Store pointer to table
 | |
|   }
 | |
| 
 | |
|   error= 0;
 | |
| err:
 | |
|   if (unlikely(error) && ddl_log_state_create)
 | |
|   {
 | |
|     /* Table was never created, so we can ignore the ddl log entry */
 | |
|     ddl_log_complete(ddl_log_state_create);
 | |
|   }
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_after_create);
 | |
|   delete file;
 | |
|   DBUG_PRINT("exit", ("return: %d", error));
 | |
|   DBUG_RETURN(error);
 | |
| 
 | |
| warn:
 | |
|   error= -1;
 | |
|   push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                       ER_TABLE_EXISTS_ERROR,
 | |
|                       ER_THD(thd, ER_TABLE_EXISTS_ERROR),
 | |
|                       alias->str);
 | |
|   goto err;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Simple wrapper around create_table_impl() to be used
 | |
|   in various version of CREATE TABLE statement.
 | |
| 
 | |
|   @result
 | |
|     1 unspecified error
 | |
|     2 error; Don't log create statement
 | |
|     0 ok
 | |
|     -1 Table was used with IF NOT EXISTS and table existed (warning, not error)
 | |
| */
 | |
| 
 | |
| int mysql_create_table_no_lock(THD *thd,
 | |
|                                DDL_LOG_STATE *ddl_log_state_create,
 | |
|                                DDL_LOG_STATE *ddl_log_state_rm,
 | |
|                                Table_specification_st *create_info,
 | |
|                                Alter_info *alter_info, bool *is_trans,
 | |
|                                int create_table_mode, TABLE_LIST *table_list)
 | |
| {
 | |
|   KEY *not_used_1;
 | |
|   uint not_used_2;
 | |
|   int res;
 | |
|   uint path_length;
 | |
|   char path[FN_REFLEN + 1];
 | |
|   const Lex_ident_db *db= &table_list->db;
 | |
|   const Lex_ident_table *table_name= &table_list->table_name;
 | |
|   LEX_CUSTRING frm= {0,0};
 | |
| 
 | |
|   DBUG_ASSERT(create_info->default_table_charset);
 | |
| 
 | |
|   if (create_info->tmp_table())
 | |
|     path_length= build_tmptable_filename(thd, path, sizeof(path));
 | |
|   else
 | |
|   {
 | |
|     const LEX_CSTRING *alias= table_case_name(create_info, table_name);
 | |
|     path_length= build_table_filename(path, sizeof(path) - 1, db->str,
 | |
|                                       alias->str,
 | |
|                                  "", 0);
 | |
|     // Check if we hit FN_REFLEN bytes along with file extension.
 | |
|     if (path_length+reg_ext_length > FN_REFLEN)
 | |
|     {
 | |
|       my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), (int) sizeof(path)-1,
 | |
|                path);
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   LEX_CSTRING cpath= { path, path_length };
 | |
|   res= create_table_impl(thd, ddl_log_state_create, ddl_log_state_rm, *db,
 | |
|                          *table_name, *db, *table_name, cpath, *create_info,
 | |
|                          create_info, alter_info, create_table_mode,
 | |
|                          is_trans, ¬_used_1, ¬_used_2, &frm);
 | |
|   my_free(const_cast<uchar*>(frm.str));
 | |
| 
 | |
|   if (!res && create_info->sequence)
 | |
|   {
 | |
|     /* Set create_info.table if temporary table */
 | |
|     if (create_info->tmp_table())
 | |
|       table_list->table= create_info->table;
 | |
|     else
 | |
|       table_list->table= 0;
 | |
|     res= sequence_insert(thd, thd->lex, table_list);
 | |
|     if (res)
 | |
|     {
 | |
|       DBUG_ASSERT(thd->is_error());
 | |
|       /*
 | |
|         Drop the new table, we were not completely done.
 | |
| 
 | |
|         Temporarily modify table_list to avoid dropping source sequence
 | |
|         in CREATE TABLE LIKE <SEQUENCE>.
 | |
|       */
 | |
|       TABLE_LIST *tail= table_list->next_local;
 | |
|       table_list->next_local= NULL;
 | |
|       /* Drop the table as it wasn't completely done */
 | |
|       if (!mysql_rm_table_no_locks(thd, table_list, &thd->db,
 | |
|                                    (DDL_LOG_STATE*) 0,
 | |
|                                    1,
 | |
|                                    create_info->tmp_table(),
 | |
|                                    false, true /* Sequence*/,
 | |
|                                    true /* Don't log_query */,
 | |
|                                    true /* Don't free locks */ ))
 | |
|       {
 | |
|         /*
 | |
|           From the user point of view, the table creation failed
 | |
|           We return 2 to indicate that this statement doesn't have
 | |
|           to be logged.
 | |
|         */
 | |
|         res= 2;
 | |
|       }
 | |
|       table_list->next_local= tail;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
| /** Additional sequence checks for Galera cluster.
 | |
| 
 | |
| @param thd         thread handle
 | |
| @param seq         sequence definition
 | |
| @param used_engine create used ENGINE=
 | |
| @retval false success
 | |
| @retval true  failure
 | |
| */
 | |
| bool wsrep_check_sequence(THD* thd,
 | |
|                           const sequence_definition *seq,
 | |
|                           const bool used_engine)
 | |
| {
 | |
|     enum legacy_db_type db_type;
 | |
| 
 | |
|     DBUG_ASSERT(WSREP(thd));
 | |
| 
 | |
|     if (used_engine)
 | |
|     {
 | |
|       db_type= thd->lex->create_info.db_type->db_type;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       const handlerton *hton= ha_default_handlerton(thd);
 | |
|       db_type= hton->db_type;
 | |
|     }
 | |
| 
 | |
|     // In Galera cluster we support only InnoDB sequences
 | |
|     if (db_type != DB_TYPE_INNODB)
 | |
|     {
 | |
|       // Currently any dynamic storage engine is not possible to identify
 | |
|       // using DB_TYPE_XXXX and ENGINE=SEQUENCE is one of them.
 | |
|       // Therefore, we get storage engine name from lex.
 | |
|       const LEX_CSTRING *tb_name= thd->lex->m_sql_cmd->option_storage_engine_name()->name();
 | |
|       // (1) CREATE TABLE ... ENGINE=SEQUENCE  OR
 | |
|       // (2) ALTER TABLE ... ENGINE=           OR
 | |
|       //     Note in ALTER TABLE table->s->sequence != nullptr
 | |
|       // (3) CREATE SEQUENCE ... ENGINE=
 | |
|       if ((thd->lex->sql_command == SQLCOM_CREATE_TABLE &&
 | |
|            lex_string_eq(tb_name, STRING_WITH_LEN("SEQUENCE"))) ||
 | |
|           (thd->lex->sql_command == SQLCOM_ALTER_TABLE) ||
 | |
|           (thd->lex->sql_command == SQLCOM_CREATE_SEQUENCE))
 | |
|       {
 | |
|         my_error(ER_NOT_SUPPORTED_YET, MYF(0),
 | |
|                  "non-InnoDB sequences in Galera cluster");
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_NOT_SUPPORTED_YET,
 | |
|                             "ENGINE=%s not supported by Galera", tb_name->str);
 | |
| 	return(true);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // In Galera cluster it is best to use INCREMENT BY 0 with CACHE
 | |
|     // or NOCACHE
 | |
|     if (seq &&
 | |
| 	seq->increment &&
 | |
|         seq->cache)
 | |
|     {
 | |
|       my_error(ER_NOT_SUPPORTED_YET, MYF(0),
 | |
|                "CACHE without INCREMENT BY 0 in Galera cluster");
 | |
|       return(true);
 | |
|     }
 | |
| 
 | |
|     return (false);
 | |
| }
 | |
| 
 | |
| /** Additional CREATE TABLE/SEQUENCE checks for Galera cluster.
 | |
| 
 | |
| @param thd         thread handle
 | |
| @param wsrep_ctas  CREATE TABLE AS SELECT ?
 | |
| @param used_engine CREATE TABLE ... ENGINE = ?
 | |
| @param create_info Create information
 | |
| 
 | |
| @retval false      Galera cluster does support used clause
 | |
| @retval true       Galera cluster does not support used clause
 | |
| */
 | |
| static
 | |
| bool wsrep_check_support(THD* thd,
 | |
|                          const bool wsrep_ctas,
 | |
|                          const bool used_engine,
 | |
|                          const HA_CREATE_INFO* create_info)
 | |
| {
 | |
|   /* CREATE TABLE ... AS SELECT */
 | |
|   if (wsrep_ctas &&
 | |
|       thd->variables.wsrep_trx_fragment_size > 0)
 | |
|   {
 | |
|     my_message(ER_NOT_ALLOWED_COMMAND,
 | |
|                "CREATE TABLE AS SELECT is not supported with streaming replication",
 | |
|                MYF(0));
 | |
|     return true;
 | |
|   }
 | |
|   /* CREATE TABLE .. WITH SYSTEM VERSIONING AS SELECT
 | |
|      is not supported in Galera cluster.
 | |
|   */
 | |
|   if (wsrep_ctas &&
 | |
|       create_info->versioned())
 | |
|   {
 | |
|     my_error(ER_NOT_SUPPORTED_YET, MYF(0),
 | |
|              "SYSTEM VERSIONING AS SELECT in Galera cluster");
 | |
|     return true;
 | |
|   }
 | |
|   /*
 | |
|     CREATE TABLE ... ENGINE=SEQUENCE is not supported in
 | |
|     Galera cluster.
 | |
|     CREATE SEQUENCE ... ENGINE=xxx Galera cluster supports
 | |
|     only InnoDB-sequences.
 | |
|   */
 | |
|   if (((used_engine && create_info->db_type &&
 | |
|        (create_info->db_type->db_type == DB_TYPE_SEQUENCE ||
 | |
|         create_info->db_type->db_type >= DB_TYPE_FIRST_DYNAMIC)) ||
 | |
|        thd->lex->sql_command == SQLCOM_CREATE_SEQUENCE) &&
 | |
|       wsrep_check_sequence(thd, create_info->seq_create_info, used_engine))
 | |
|     return true;
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| #endif /* WITH_WSREP */
 | |
| 
 | |
| /**
 | |
|   Implementation of SQLCOM_CREATE_TABLE.
 | |
| 
 | |
|   Take the metadata locks (including a shared lock on the affected
 | |
|   schema) and create the table. Is written to be called from
 | |
|   mysql_execute_command(), to which it delegates the common parts
 | |
|   with other commands (i.e. implicit commit before and after,
 | |
|   close of thread tables.
 | |
| */
 | |
| 
 | |
| 
 | |
| static
 | |
| bool mysql_create_table(THD *thd, TABLE_LIST *create_table,
 | |
|                         Table_specification_st *create_info,
 | |
|                         Alter_info *alter_info)
 | |
| {
 | |
|   TABLE_LIST *pos_in_locked_tables= 0;
 | |
|   MDL_ticket *mdl_ticket= 0;
 | |
|   DDL_LOG_STATE ddl_log_state_create, ddl_log_state_rm;
 | |
|   int create_table_mode;
 | |
|   uint save_thd_create_info_options;
 | |
|   bool is_trans= FALSE;
 | |
|   bool result;
 | |
|   DBUG_ENTER("mysql_create_table");
 | |
| 
 | |
|   DBUG_ASSERT(create_info->default_table_charset);
 | |
| 
 | |
|   DBUG_ASSERT(create_table == thd->lex->query_tables);
 | |
| 
 | |
|   bzero(&ddl_log_state_create, sizeof(ddl_log_state_create));
 | |
|   bzero(&ddl_log_state_rm, sizeof(ddl_log_state_rm));
 | |
| 
 | |
|   /* Copy temporarily the statement flags to thd for lock_table_names() */
 | |
|   save_thd_create_info_options= thd->lex->create_info.options;
 | |
|   thd->lex->create_info.options|= create_info->options;
 | |
| 
 | |
|   /* Open or obtain an exclusive metadata lock on table being created  */
 | |
|   create_table->db_type= 0;
 | |
|   result= open_and_lock_tables(thd, *create_info, create_table, FALSE, 0);
 | |
| 
 | |
|   thd->lex->create_info.options= save_thd_create_info_options;
 | |
| 
 | |
|   if (result)
 | |
|   {
 | |
|     if (thd->slave_thread &&
 | |
|         !thd->is_error() && create_table->db_type &&
 | |
|         (create_table->db_type->flags & HTON_IGNORE_UPDATES))
 | |
|     {
 | |
|       /* Table existed in distributed engine. Log query to binary log */
 | |
|       result= 0;
 | |
|       goto err;
 | |
|     }
 | |
|     /* is_error() may be 0 if table existed and we generated a warning */
 | |
|     DBUG_RETURN(thd->is_error());
 | |
|   }
 | |
|   /* The following is needed only in case of lock tables */
 | |
|   if ((create_info->table= create_table->table))
 | |
|   {
 | |
|     pos_in_locked_tables= create_info->table->pos_in_locked_tables;
 | |
|     mdl_ticket= create_table->table->mdl_ticket;
 | |
|   }
 | |
|   
 | |
|   /* Got lock. */
 | |
|   DEBUG_SYNC(thd, "locked_table_name");
 | |
| 
 | |
|   if (alter_info->create_list.elements || alter_info->key_list.elements)
 | |
|     create_table_mode= C_ORDINARY_CREATE;
 | |
|   else
 | |
|     create_table_mode= C_ASSISTED_DISCOVERY;
 | |
| 
 | |
|   if (!(thd->variables.option_bits & OPTION_EXPLICIT_DEF_TIMESTAMP))
 | |
|     promote_first_timestamp_column(&alter_info->create_list);
 | |
| 
 | |
|   /* We can abort create table for any table type */
 | |
|   thd->abort_on_warning= thd->is_strict_mode();
 | |
| 
 | |
|   if (mysql_create_table_no_lock(thd, &ddl_log_state_create, &ddl_log_state_rm,
 | |
|                                  create_info, alter_info, &is_trans,
 | |
|                                  create_table_mode, create_table) > 0)
 | |
|   {
 | |
|     result= 1;
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Check if we are doing CREATE OR REPLACE TABLE under LOCK TABLES
 | |
|     on a non temporary table
 | |
|   */
 | |
|   if (thd->locked_tables_mode && pos_in_locked_tables &&
 | |
|       create_info->or_replace())
 | |
|   {
 | |
|     DBUG_ASSERT(thd->variables.option_bits & OPTION_TABLE_LOCK);
 | |
|     /*
 | |
|       Add back the deleted table and re-created table as a locked table
 | |
|       This should always work as we have a meta lock on the table.
 | |
|      */
 | |
|     thd->locked_tables_list.add_back_last_deleted_lock(pos_in_locked_tables);
 | |
|     if (thd->locked_tables_list.reopen_tables(thd, false))
 | |
|     {
 | |
|       thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
 | |
|       result= 1;
 | |
|       goto err;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       TABLE *table= pos_in_locked_tables->table;
 | |
|       table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| err:
 | |
|   thd->abort_on_warning= 0;
 | |
| 
 | |
|   /* In RBR or readonly server we don't need to log CREATE TEMPORARY TABLE */
 | |
|   if (!result && create_info->tmp_table() && !thd->binlog_create_tmp_table())
 | |
|   {
 | |
|     /*
 | |
|       Note that mark_created_temp_table was not called and thus
 | |
|       table->s->table_creation_was_logged is not set!
 | |
|     */
 | |
|     DBUG_RETURN(result);
 | |
|   }
 | |
| 
 | |
|   if (create_info->tmp_table())
 | |
|     thd->transaction->stmt.mark_created_temp_table();
 | |
| 
 | |
|   /* Write log if no error or if we already deleted a table */
 | |
|   if (!result || thd->log_current_statement())
 | |
|   {
 | |
|     if (unlikely(result) && create_info->table_was_deleted &&
 | |
|         pos_in_locked_tables)
 | |
|     {
 | |
|       /*
 | |
|         Possible locked table was dropped. We should remove meta data locks
 | |
|         associated with it and do UNLOCK_TABLES if no more locked tables.
 | |
|       */
 | |
|       (void) thd->locked_tables_list.unlock_locked_table(thd, mdl_ticket);
 | |
|     }
 | |
|     else if (likely(!result) && create_info->table)
 | |
|     {
 | |
|       /*
 | |
|         Remember that table creation was logged so that we know if
 | |
|         we should log a delete of it.
 | |
|         If create_info->table was not set, it's a normal table and
 | |
|         table_creation_was_logged will be set when the share is created.
 | |
|       */
 | |
|       create_info->table->s->table_creation_was_logged= 1;
 | |
|     }
 | |
|     thd->binlog_xid= thd->query_id;
 | |
|     ddl_log_update_xid(&ddl_log_state_create, thd->binlog_xid);
 | |
|     if (ddl_log_state_rm.is_active())
 | |
|       ddl_log_update_xid(&ddl_log_state_rm, thd->binlog_xid);
 | |
|     debug_crash_here("ddl_log_create_before_binlog");
 | |
|     if (unlikely(write_bin_log(thd, result ? FALSE : TRUE, thd->query(),
 | |
|                                thd->query_length(), is_trans)))
 | |
|       result= 1;
 | |
|     debug_crash_here("ddl_log_create_after_binlog");
 | |
|     thd->binlog_xid= 0;
 | |
| 
 | |
|     if (!create_info->tmp_table())
 | |
|     {
 | |
|       backup_log_info ddl_log;
 | |
|       bzero(&ddl_log, sizeof(ddl_log));
 | |
|       ddl_log.query= { C_STRING_WITH_LEN("CREATE") };
 | |
|       ddl_log.org_partitioned= (create_info->db_type == partition_hton);
 | |
|       ddl_log.org_storage_engine_name= create_info->new_storage_engine_name;
 | |
|       ddl_log.org_database=     create_table->db;
 | |
|       ddl_log.org_table=        create_table->table_name;
 | |
|       ddl_log.org_table_id=     create_info->tabledef_version;
 | |
|       backup_log_ddl(&ddl_log);
 | |
|     }
 | |
|   }
 | |
|   ddl_log_complete(&ddl_log_state_rm);
 | |
|   ddl_log_complete(&ddl_log_state_create);
 | |
|   DBUG_RETURN(result);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
| ** Give the key name after the first field with an optional '_#' after
 | |
|    @returns
 | |
|     0        if keyname does not exists
 | |
|     [1..)    index + 1 of duplicate key name
 | |
| **/
 | |
| 
 | |
| static int
 | |
| check_if_keyname_exists(const Lex_ident_column &name,
 | |
|                         const KEY *start, const KEY *end)
 | |
| {
 | |
|   uint i= 1;
 | |
|   for (const KEY *key= start; key != end ; key++, i++)
 | |
|   {
 | |
|     if (key->name.streq(name))
 | |
|       return i;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  Returns 1 if field name exists otherwise 0
 | |
| */
 | |
| static bool
 | |
| check_if_field_name_exists(const Lex_ident_column &name,
 | |
|                            List<Create_field> * fields)
 | |
| {
 | |
|   Create_field *fld;
 | |
|   List_iterator<Create_field>it(*fields);
 | |
|   while ((fld = it++))
 | |
|   {
 | |
|     if (fld->field_name.streq(name))
 | |
|       return 1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| static Lex_ident_column
 | |
| make_unique_key_name(THD *thd, const Lex_ident_column &field_name,
 | |
|                      const KEY *start, const KEY *end)
 | |
| {
 | |
|   char buff[MAX_FIELD_NAME],*buff_end;
 | |
| 
 | |
|   if (!check_if_keyname_exists(field_name,start,end) &&
 | |
|       !field_name.streq(primary_key_name))
 | |
|     return field_name;                 // Use fieldname
 | |
|   buff_end= strmake(buff, field_name.str, sizeof(buff)-4);
 | |
| 
 | |
|   /*
 | |
|     Only 3 chars + '\0' left, so need to limit to 2 digit
 | |
|     This is ok as we can't have more than 100 keys anyway
 | |
|   */
 | |
|   for (uint i=2 ; i< 100; i++)
 | |
|   {
 | |
|     *buff_end= '_';
 | |
|     const char *buff_end2= int10_to_str(i, buff_end + 1, 10);
 | |
|     const Lex_ident_column ident(buff, (size_t) (buff_end2 - buff));
 | |
|     if (!check_if_keyname_exists(ident, start, end))
 | |
|       return thd->lex_ident_copy(ident);
 | |
|   }
 | |
|   return "not_specified"_Lex_ident_column;  // Should never happen
 | |
| }
 | |
| 
 | |
| /**
 | |
|    Make an unique name for constraints without a name
 | |
| */
 | |
| 
 | |
| static bool make_unique_constraint_name(THD *thd, LEX_CSTRING *name,
 | |
|                                         const char *own_name_base,
 | |
|                                         List<Virtual_column_info> *vcol,
 | |
|                                         uint *nr)
 | |
| {
 | |
|   char buff[MAX_FIELD_NAME], *end;
 | |
|   List_iterator_fast<Virtual_column_info> it(*vcol);
 | |
|   end=strmov(buff, own_name_base ? own_name_base : "CONSTRAINT_");
 | |
|   for (int round= 0;; round++)
 | |
|   {
 | |
|     Virtual_column_info *check;
 | |
|     char *real_end= end;
 | |
|     if (round == 1 && own_name_base)
 | |
|         *end++= '_';
 | |
|     // if own_base_name provided, try it first
 | |
|     if (round != 0 || !own_name_base)
 | |
|       real_end= int10_to_str((*nr)++, end, 10);
 | |
|     const Lex_ident_column ident(buff, (size_t) (real_end - buff));
 | |
|     it.rewind();
 | |
|     while ((check= it++))
 | |
|     {
 | |
|       if (check->name.str && check->name.streq(ident))
 | |
|         break;
 | |
|     }
 | |
|     if (!check)                                 // Found unique name
 | |
|     {
 | |
|       name->length= ident.length;
 | |
|       name->str= thd->strmake(buff, name->length);
 | |
|       return (name->str == NULL);
 | |
|     }
 | |
|   }
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   INVISIBLE_FULL are internally created. They are completely invisible
 | |
|   to Alter command (Opposite of SYSTEM_INVISIBLE which throws an
 | |
|   error when same name column is added by Alter). So in the case of when
 | |
|   user added a same column name as of INVISIBLE_FULL , we change
 | |
|   INVISIBLE_FULL column name.
 | |
| */
 | |
| static Lex_ident_column
 | |
| make_unique_invisible_field_name(THD *thd,
 | |
|                                  const Lex_ident_column &field_name,
 | |
|                                  List<Create_field> *fields)
 | |
| {
 | |
|   if (!check_if_field_name_exists(field_name, fields))
 | |
|     return field_name;
 | |
|   char buff[MAX_FIELD_NAME], *buff_end;
 | |
|   buff_end= strmake_buf(buff, field_name.str);
 | |
|   if (buff_end - buff < 5)
 | |
|     return Lex_ident_column(); // Should not happen
 | |
| 
 | |
|   for (uint i=1 ; i < 10000; i++)
 | |
|   {
 | |
|     char *real_end= int10_to_str(i, buff_end, 10);
 | |
|     const Lex_ident_column ident(buff, (size_t) (real_end - buff));
 | |
|     if (check_if_field_name_exists(ident, fields))
 | |
|       continue;
 | |
|     return thd->lex_ident_copy(ident);
 | |
|   }
 | |
|   return Lex_ident_column(); // Should not happen
 | |
| }
 | |
| 
 | |
| /****************************************************************************
 | |
| ** Alter a table definition
 | |
| ****************************************************************************/
 | |
| 
 | |
| bool operator!=(const MYSQL_TIME &lhs, const MYSQL_TIME &rhs)
 | |
| {
 | |
|   return lhs.year != rhs.year || lhs.month != rhs.month || lhs.day != rhs.day ||
 | |
|          lhs.hour != rhs.hour || lhs.minute != rhs.minute ||
 | |
|          lhs.second_part != rhs.second_part || lhs.neg != rhs.neg ||
 | |
|          lhs.time_type != rhs.time_type;
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Rename a table.
 | |
| 
 | |
|   @param base      The handlerton handle.
 | |
|   @param old_db    The old database name.
 | |
|   @param old_name  The old table name.
 | |
|   @param new_db    The new database name.
 | |
|   @param new_name  The new table name.
 | |
|   @param id        Table version id (for ddl log)
 | |
|   @param flags     flags
 | |
|                    FN_FROM_IS_TMP old_name is temporary.
 | |
|                    FN_TO_IS_TMP   new_name is temporary.
 | |
|                    NO_FK_CHECKS   Don't check FK constraints during rename.
 | |
|   @return false    OK
 | |
|   @return true     Error
 | |
| */
 | |
| 
 | |
| bool
 | |
| mysql_rename_table(handlerton *base, const LEX_CSTRING *old_db,
 | |
|                    const LEX_CSTRING *old_name, const LEX_CSTRING *new_db,
 | |
|                    const LEX_CSTRING *new_name, const LEX_CUSTRING *id, uint flags)
 | |
| {
 | |
|   THD *thd= current_thd;
 | |
|   char from[FN_REFLEN], to[FN_REFLEN], lc_from[FN_REFLEN], lc_to[FN_REFLEN];
 | |
|   char *from_base= from, *to_base= to;
 | |
|   handler *file;
 | |
|   int error=0;
 | |
|   ulonglong save_bits= thd->variables.option_bits;
 | |
|   int length;
 | |
|   bool log_query= 0;
 | |
|   DBUG_ENTER("mysql_rename_table");
 | |
|   DBUG_ASSERT(base);
 | |
|   DBUG_ASSERT(flags & (QRMT_FRM | QRMT_PAR | QRMT_HANDLER));
 | |
|   DBUG_PRINT("enter", ("old: '%s'.'%s'  new: '%s'.'%s'",
 | |
|                        old_db->str, old_name->str, new_db->str,
 | |
|                        new_name->str));
 | |
| 
 | |
|   // Temporarily disable foreign key checks
 | |
|   if (flags & NO_FK_CHECKS) 
 | |
|     thd->variables.option_bits|= OPTION_NO_FOREIGN_KEY_CHECKS;
 | |
| 
 | |
|   file= get_new_handler((TABLE_SHARE*) 0, thd->mem_root, base);
 | |
| 
 | |
|   build_table_filename(from, sizeof(from) - 1, old_db->str, old_name->str, "",
 | |
|                        flags & FN_FROM_IS_TMP);
 | |
|   length= build_table_filename(to, sizeof(to) - 1, new_db->str,
 | |
|                                new_name->str, "", flags & FN_TO_IS_TMP);
 | |
|   // Check if we hit FN_REFLEN bytes along with file extension.
 | |
|   if (length+reg_ext_length > FN_REFLEN)
 | |
|   {
 | |
|     my_error(ER_IDENT_CAUSES_TOO_LONG_PATH, MYF(0), (int) sizeof(to)-1, to);
 | |
|     DBUG_RETURN(TRUE);
 | |
|   }
 | |
| 
 | |
|   if (file && file->needs_lower_case_filenames())
 | |
|   {
 | |
|     build_lower_case_table_filename(lc_from, sizeof(lc_from) -1,
 | |
|                                     old_db, old_name, flags & FN_FROM_IS_TMP);
 | |
|     build_lower_case_table_filename(lc_to, sizeof(lc_from) -1,
 | |
|                                     new_db, new_name, flags & FN_TO_IS_TMP);
 | |
|     from_base= lc_from;
 | |
|     to_base=   lc_to;
 | |
|   }
 | |
| 
 | |
|   if (!(flags & QRMT_HANDLER))
 | |
|   {
 | |
|     /*
 | |
|       This code expects callers to set QRMT_FRM if QRMT_HANDLER is omitted.
 | |
|       Otherwise this invariant is not strictly required.
 | |
|     */
 | |
|     DBUG_ASSERT(flags & QRMT_FRM);
 | |
|     if (rename_file_ext(from,to,reg_ext))
 | |
|       error= my_errno;
 | |
|     log_query= true;
 | |
|     if (file && (flags & QRMT_PAR))
 | |
|       (void) file->ha_create_partitioning_metadata(to, from, CHF_RENAME_FLAG);
 | |
|   }
 | |
|   else if (!file || likely(!(error=file->ha_rename_table(from_base, to_base))))
 | |
|   {
 | |
|     if ((flags & QRMT_FRM) && unlikely(rename_file_ext(from, to, reg_ext)))
 | |
|     {
 | |
|       error=my_errno;
 | |
|       if (file)
 | |
|       {
 | |
|         if (error == ENOENT)
 | |
|           error= 0; // this is ok if file->ha_rename_table() succeeded
 | |
|         else
 | |
|           file->ha_rename_table(to_base, from_base); // Restore old file name
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|       log_query= true;
 | |
| 
 | |
|     /* Rename high-level indexes */
 | |
|     if (file && !error)
 | |
|     {
 | |
|       char idx_from[FN_REFLEN + 1], idx_to[FN_REFLEN + 1];
 | |
|       char *idx_from_end= strmov(idx_from, from_base);
 | |
|       char *idx_to_end= strmov(idx_to, to_base);
 | |
|       uint keys, total_keys;
 | |
| 
 | |
|       if (!get_hlindex_keys_by_open(thd, new_db, new_name, to, &keys,
 | |
|                                     &total_keys))
 | |
|       {
 | |
|         for (uint i= keys; i < total_keys; i++)
 | |
|         {
 | |
|           my_snprintf(idx_from_end, HLINDEX_BUF_LEN, HLINDEX_TEMPLATE, i);
 | |
|           my_snprintf(idx_to_end, HLINDEX_BUF_LEN, HLINDEX_TEMPLATE, i);
 | |
|           if ((error= file->ha_rename_table(idx_from, idx_to)))
 | |
|           {
 | |
|             for (; i >= keys; i--)
 | |
|             {
 | |
|               my_snprintf(idx_from_end, HLINDEX_BUF_LEN, HLINDEX_TEMPLATE, i);
 | |
|               my_snprintf(idx_to_end, HLINDEX_BUF_LEN, HLINDEX_TEMPLATE, i);
 | |
|               file->ha_rename_table(idx_to, idx_from);
 | |
|             }
 | |
|             file->ha_rename_table(to_base, from_base);
 | |
|             rename_file_ext(to, from, reg_ext);
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         file->ha_rename_table(to_base, from_base);
 | |
|         rename_file_ext(to, from, reg_ext);
 | |
|         error= 1;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (!error && log_query && !(flags & (FN_TO_IS_TMP | FN_FROM_IS_TMP)))
 | |
|   {
 | |
|     backup_log_info ddl_log;
 | |
|     bzero(&ddl_log, sizeof(ddl_log));
 | |
|     ddl_log.query= { C_STRING_WITH_LEN("RENAME") };
 | |
|     ddl_log.org_partitioned=  file->partition_engine();
 | |
|     ddl_log.new_partitioned=  ddl_log.org_partitioned;
 | |
|     lex_string_set(&ddl_log.org_storage_engine_name, file->real_table_type());
 | |
|     ddl_log.org_database=     *old_db;
 | |
|     ddl_log.org_table=        *old_name;
 | |
|     ddl_log.org_table_id=     *id;
 | |
|     ddl_log.new_storage_engine_name= ddl_log.org_storage_engine_name;
 | |
|     ddl_log.new_database=     *new_db;
 | |
|     ddl_log.new_table=        *new_name;
 | |
|     ddl_log.new_table_id=     *id;
 | |
|     backup_log_ddl(&ddl_log);
 | |
|   }
 | |
|   delete file;
 | |
| 
 | |
|   if (error == HA_ERR_WRONG_COMMAND)
 | |
|     my_error(ER_NOT_SUPPORTED_YET, MYF(0), "ALTER TABLE");
 | |
|   else if (error ==  ENOTDIR)
 | |
|     my_error(ER_BAD_DB_ERROR, MYF(0), new_db->str);
 | |
|   else if (error)
 | |
|     my_error(ER_ERROR_ON_RENAME, MYF(0), from, to, error);
 | |
|   else if (!(flags & FN_IS_TMP))
 | |
|     mysql_audit_rename_table(thd, old_db, old_name, new_db, new_name);
 | |
| 
 | |
|   /*
 | |
|     Remove the old table share from the pfs table share array. The new table
 | |
|     share will be created when the renamed table is first accessed.
 | |
|    */
 | |
|   if (likely(error == 0))
 | |
|   {
 | |
|     PSI_CALL_drop_table_share(flags & FN_FROM_IS_TMP,
 | |
|                               old_db->str, (uint)old_db->length,
 | |
|                               old_name->str, (uint)old_name->length);
 | |
|   }
 | |
| 
 | |
|   // Restore options bits to the original value
 | |
|   thd->variables.option_bits= save_bits;
 | |
| 
 | |
|   DBUG_RETURN(error != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Create a table identical to the specified table
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_create_like_table()
 | |
|     thd		Thread object
 | |
|     table       Table list element for target table
 | |
|     src_table   Table list element for source table
 | |
|     create_info Create info
 | |
| 
 | |
|   RETURN VALUES
 | |
|     FALSE OK
 | |
|     TRUE  error
 | |
| */
 | |
| 
 | |
| static
 | |
| bool mysql_create_like_table(THD* thd, TABLE_LIST* table,
 | |
|                              TABLE_LIST* src_table,
 | |
|                              Table_specification_st *create_info)
 | |
| {
 | |
|   Table_specification_st local_create_info;
 | |
|   TABLE_LIST *pos_in_locked_tables= 0;
 | |
|   Alter_info local_alter_info;
 | |
|   Alter_table_ctx local_alter_ctx; // Not used 
 | |
|   DDL_LOG_STATE ddl_log_state_create, ddl_log_state_rm;
 | |
|   int res= 1;
 | |
|   bool is_trans= FALSE;
 | |
|   bool do_logging= FALSE;
 | |
|   bool force_generated_create= false;
 | |
|   bool src_table_exists= FALSE;
 | |
|   uint not_used;
 | |
|   int create_res;
 | |
|   DBUG_ENTER("mysql_create_like_table");
 | |
| 
 | |
|   bzero(&ddl_log_state_create, sizeof(ddl_log_state_create));
 | |
|   bzero(&ddl_log_state_rm, sizeof(ddl_log_state_rm));
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
|   if (WSREP(thd) && !thd->wsrep_applier &&
 | |
|       wsrep_create_like_table(thd, table, src_table, create_info))
 | |
|   {
 | |
|     DBUG_RETURN(res);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   /*
 | |
|     We open source table to get its description in HA_CREATE_INFO
 | |
|     and Alter_info objects. This also acquires a shared metadata lock
 | |
|     on this table which ensures that no concurrent DDL operation will
 | |
|     mess with it.
 | |
|     Also in case when we create non-temporary table open_tables()
 | |
|     call obtains an exclusive metadata lock on target table ensuring
 | |
|     that we can safely perform table creation.
 | |
|     Thus by holding both these locks we ensure that our statement is
 | |
|     properly isolated from all concurrent operations which matter.
 | |
|     If the target table exists, it will also be opened.
 | |
|   */
 | |
| 
 | |
|   res= open_tables(thd, *create_info, &thd->lex->query_tables, ¬_used, 0);
 | |
| 
 | |
|   if (res)
 | |
|   {
 | |
|     /* is_error() may be 0 if table existed and we generated a warning */
 | |
|     res= thd->is_error();
 | |
|     src_table_exists= !res;
 | |
|     goto err;
 | |
|   }
 | |
|   /* Ensure we don't try to create something from which we select from */
 | |
|   if (create_info->or_replace() && !create_info->tmp_table())
 | |
|   {
 | |
|     TABLE_LIST *duplicate;
 | |
|     if ((duplicate= unique_table(thd, table, src_table, 0)))
 | |
|     {
 | |
|       update_non_unique_table_error(src_table, "CREATE", duplicate);
 | |
|       res= 1;
 | |
|       goto err;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   src_table->table->use_all_columns();
 | |
| 
 | |
|   DEBUG_SYNC(thd, "create_table_like_after_open");
 | |
| 
 | |
|   /*
 | |
|     Fill Table_specification_st and Alter_info with the source table description.
 | |
|     Set OR REPLACE and IF NOT EXISTS option as in the CREATE TABLE LIKE
 | |
|     statement.
 | |
|   */
 | |
|   local_create_info.init(create_info->create_like_options());
 | |
|   local_create_info.db_type= src_table->table->s->db_type();
 | |
|   local_create_info.row_type= src_table->table->s->row_type;
 | |
|   local_create_info.alter_info= &local_alter_info;
 | |
|   /*
 | |
|     This statement:
 | |
|       CREATE TABLE t1 LIKE t2
 | |
|     does not support table charset/collation clauses.
 | |
|     No needs to copy. Assert they are empty.
 | |
|   */
 | |
|   DBUG_ASSERT(create_info->default_charset_collation.is_empty());
 | |
|   DBUG_ASSERT(create_info->convert_charset_collation.is_empty());
 | |
|   if (mysql_prepare_alter_table(thd, src_table->table, &local_create_info,
 | |
|                                 &local_alter_info, &local_alter_ctx))
 | |
|     goto err;
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   /* Partition info is not handled by mysql_prepare_alter_table() call. */
 | |
|   if (src_table->table->part_info)
 | |
|   {
 | |
|     /*
 | |
|       The CREATE TABLE LIKE should not inherit the DATA DIRECTORY
 | |
|       and INDEX DIRECTORY from the base table.
 | |
|       So that TRUE argument for the get_clone.
 | |
|     */
 | |
|     thd->work_part_info= src_table->table->part_info->get_clone(thd, TRUE);
 | |
|   }
 | |
| #endif /*WITH_PARTITION_STORAGE_ENGINE*/
 | |
| 
 | |
|   /*
 | |
|     Adjust description of source table before using it for creation of
 | |
|     target table.
 | |
| 
 | |
|     Similarly to SHOW CREATE TABLE we ignore MAX_ROWS attribute of
 | |
|     temporary table which represents I_S table.
 | |
|   */
 | |
|   if (src_table->schema_table)
 | |
|     local_create_info.max_rows= 0;
 | |
|   /* Replace type of source table with one specified in the statement. */
 | |
|   local_create_info.options&= ~HA_LEX_CREATE_TMP_TABLE;
 | |
|   local_create_info.options|= create_info->options;
 | |
|   /* Reset auto-increment counter for the new table. */
 | |
|   local_create_info.auto_increment_value= 0;
 | |
|   /*
 | |
|     Do not inherit values of DATA and INDEX DIRECTORY options from
 | |
|     the original table. This is documented behavior.
 | |
|   */
 | |
|   local_create_info.data_file_name= local_create_info.index_file_name= NULL;
 | |
| 
 | |
|   if (src_table->table->versioned() &&
 | |
|       local_create_info.vers_info.fix_create_like(local_alter_info, local_create_info,
 | |
|                                                   *src_table, *table))
 | |
|   {
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   /* The following is needed only in case of lock tables */
 | |
|   if ((local_create_info.table= thd->lex->query_tables->table))
 | |
|     pos_in_locked_tables= local_create_info.table->pos_in_locked_tables;    
 | |
| 
 | |
|   res= ((create_res=
 | |
|          mysql_create_table_no_lock(thd,
 | |
|                                     &ddl_log_state_create, &ddl_log_state_rm,
 | |
|                                     &local_create_info, &local_alter_info,
 | |
|                                     &is_trans, C_ORDINARY_CREATE,
 | |
|                                     table)) > 0);
 | |
|   /* Remember to log if we deleted something */
 | |
|   do_logging= thd->log_current_statement();
 | |
|   if (res)
 | |
|     goto err;
 | |
| 
 | |
|   /*
 | |
|     Check if we are doing CREATE OR REPLACE TABLE under LOCK TABLES
 | |
|     on a non temporary table
 | |
|   */
 | |
|   if (thd->locked_tables_mode && pos_in_locked_tables &&
 | |
|       create_info->or_replace())
 | |
|   {
 | |
|     /*
 | |
|       Add back the deleted table and re-created table as a locked table
 | |
|       This should always work as we have a meta lock on the table.
 | |
|      */
 | |
|     thd->locked_tables_list.add_back_last_deleted_lock(pos_in_locked_tables);
 | |
|     if (thd->locked_tables_list.reopen_tables(thd, false))
 | |
|     {
 | |
|       thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
 | |
|       res= 1;                                   // We got an error
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         Get pointer to the newly opened table. We need this to ensure we
 | |
|         don't reopen the table when doing statement logging below.
 | |
|       */
 | |
|       table->table= pos_in_locked_tables->table;
 | |
|       table->table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /*
 | |
|       Ensure that we have an exclusive lock on target table if we are creating
 | |
|       non-temporary table. We don't have or need the lock if the create failed
 | |
|       because of existing table when using "if exists".
 | |
|     */
 | |
|     DBUG_ASSERT((create_info->tmp_table()) || create_res < 0 ||
 | |
|                 thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db.str,
 | |
|                                                table->table_name.str,
 | |
|                                                MDL_EXCLUSIVE) ||
 | |
|                 (thd->locked_tables_mode && pos_in_locked_tables &&
 | |
|                  create_info->if_not_exists()));
 | |
|   }
 | |
| 
 | |
|   DEBUG_SYNC(thd, "create_table_like_before_binlog");
 | |
| 
 | |
|   /*
 | |
|     We have to write the query before we unlock the tables.
 | |
|   */
 | |
|   if (thd->is_current_stmt_binlog_disabled() ||
 | |
|       (create_info->tmp_table() && !thd->binlog_create_tmp_table()))
 | |
|     goto err;
 | |
| 
 | |
| #ifdef ENABLE_WHEN_S3_CAN_CREATE_TABLES
 | |
|   /*
 | |
|     If we do a create based on a shared table or a not logged temporary table,
 | |
|     log the full create of the resulting table. This is needed as a shared table
 | |
|     may look different when the slave executes the command or the shared table
 | |
|     was never replicated to the slave.
 | |
|   */
 | |
|   force_generated_create=
 | |
|     ((((src_table->table->file->partition_ht()->flags &
 | |
|         HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE) &&
 | |
|        src_table->table->s->db_type() != local_create_info.db_type)) ||
 | |
|      !src_table->table->s->table_creation_was_logged);
 | |
| #endif
 | |
|   force_generated_create|= !src_table->table->s->table_creation_was_logged;
 | |
| 
 | |
|   if (thd->is_binlog_format_row() || force_generated_create)
 | |
|   {
 | |
|     /*
 | |
|        Since temporary tables are not replicated under row-based
 | |
|        replication, CREATE TABLE ... LIKE ... needs special
 | |
|        treatment.  We have some cases to consider, according to the
 | |
|        following decision table:
 | |
| 
 | |
|            ==== ========= ========= ==============================
 | |
|            Case    Target    Source Write to binary log
 | |
|            ==== ========= ========= ==============================
 | |
|            1       normal    normal Original statement
 | |
|            2       normal temporary Generated statement if the table
 | |
|                                     was created.
 | |
|            3    temporary    normal Nothing
 | |
|            4    temporary temporary Nothing
 | |
|            5       any       shared Generated statement if the table
 | |
|                                     was created if engine changed
 | |
|            ==== ========= ========= ==============================
 | |
|     */
 | |
|     if (!(create_info->tmp_table()) || force_generated_create)
 | |
|     {
 | |
|       // Case 2 & 5
 | |
|       if (src_table->table->s->tmp_table || force_generated_create)
 | |
|       {
 | |
|         char buf[2048];
 | |
|         String query(buf, sizeof(buf), system_charset_info);
 | |
|         query.length(0);  // Have to zero it since constructor doesn't
 | |
|         Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN |
 | |
|                                   MYSQL_OPEN_IGNORE_KILLED);
 | |
|         bool new_table= FALSE; // Whether newly created table is open.
 | |
| 
 | |
|         if (create_res != 0)
 | |
|         {
 | |
|           /*
 | |
|             Table or view with same name already existed and we where using
 | |
|             IF EXISTS. Continue without logging anything.
 | |
|           */
 | |
|           do_logging= 0;
 | |
|           goto err;
 | |
|         }
 | |
|         if (!table->table)
 | |
|         {
 | |
|           TABLE_LIST::enum_open_strategy save_open_strategy;
 | |
|           int open_res;
 | |
|           /* Force the newly created table to be opened */
 | |
|           save_open_strategy= table->open_strategy;
 | |
|           table->open_strategy= TABLE_LIST::OPEN_NORMAL;
 | |
| 
 | |
|           /*
 | |
|             In order for show_create_table() to work we need to open
 | |
|             destination table if it is not already open (i.e. if it
 | |
|             has not existed before). We don't need acquire metadata
 | |
|             lock in order to do this as we already hold exclusive
 | |
|             lock on this table. The table will be closed by
 | |
|             close_thread_table() at the end of this branch.
 | |
|           */
 | |
|           open_res= open_table(thd, table, &ot_ctx);
 | |
|           /* Restore */
 | |
|           table->open_strategy= save_open_strategy;
 | |
|           if (open_res)
 | |
|           {
 | |
|             res= 1;
 | |
|             goto err;
 | |
|           }
 | |
|           new_table= TRUE;
 | |
|         }
 | |
|         /*
 | |
|           We have to re-test if the table was a view as the view may not
 | |
|           have been opened until just above.
 | |
|         */
 | |
|         if (!table->view)
 | |
|         {
 | |
|           /*
 | |
|             After opening a MERGE table add the children to the query list of
 | |
|             tables, so that children tables info can be used on "CREATE TABLE"
 | |
|             statement generation by the binary log.
 | |
|             Note that placeholders don't have the handler open.
 | |
|           */
 | |
|           if (table->table->file->extra(HA_EXTRA_ADD_CHILDREN_LIST))
 | |
|             goto err;
 | |
| 
 | |
|           /*
 | |
|             As the reference table is temporary and may not exist on slave, we
 | |
|             must force the ENGINE to be present into CREATE TABLE.
 | |
|           */
 | |
|           create_info->used_fields|= HA_CREATE_USED_ENGINE;
 | |
| 
 | |
|           int result __attribute__((unused))=
 | |
|             show_create_table(thd, table, &query, create_info, WITH_DB_NAME);
 | |
| 
 | |
|           DBUG_ASSERT(result == 0); // show_create_table() always return 0
 | |
|           do_logging= FALSE;
 | |
|           if (write_bin_log(thd, TRUE, query.ptr(), query.length()))
 | |
|           {
 | |
|             res= 1;
 | |
|             goto err;
 | |
|           }
 | |
| 
 | |
|           if (new_table)
 | |
|           {
 | |
|             DBUG_ASSERT(thd->open_tables == table->table);
 | |
|             /*
 | |
|               When opening the table, we ignored the locked tables
 | |
|               (MYSQL_OPEN_GET_NEW_TABLE). Now we can close the table
 | |
|               without risking to close some locked table.
 | |
|             */
 | |
|             close_thread_table(thd, &thd->open_tables);
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       else                                      // Case 1
 | |
|         do_logging= TRUE;
 | |
|     }
 | |
|     /*
 | |
|       Case 3 and 4 does nothing under RBR
 | |
|     */
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     DBUG_PRINT("info",
 | |
|                ("res: %d  tmp_table: %d  create_info->table: %p",
 | |
|                 res, create_info->tmp_table(), local_create_info.table));
 | |
|     if (create_info->tmp_table())
 | |
|     {
 | |
|       thd->transaction->stmt.mark_created_temp_table();
 | |
|       if (!res && local_create_info.table &&
 | |
|           thd->variables.binlog_format == BINLOG_FORMAT_STMT)
 | |
|       {
 | |
|         /*
 | |
|           Remember that tmp table creation was logged so that we know if
 | |
|           we should log a delete of it.
 | |
|         */
 | |
|         local_create_info.table->s->table_creation_was_logged= 1;
 | |
|         do_logging= TRUE;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|       do_logging= TRUE;
 | |
|   }
 | |
| 
 | |
| err:
 | |
|   if (do_logging)
 | |
|   {
 | |
|     thd->binlog_xid= thd->query_id;
 | |
|     ddl_log_update_xid(&ddl_log_state_create, thd->binlog_xid);
 | |
|     if (ddl_log_state_rm.is_active())
 | |
|       ddl_log_update_xid(&ddl_log_state_rm, thd->binlog_xid);
 | |
|     debug_crash_here("ddl_log_create_before_binlog");      
 | |
|     if (res && create_info->table_was_deleted)
 | |
|     {
 | |
|       /*
 | |
|         Table was not deleted. Original table was deleted.
 | |
|         We have to log it.
 | |
|       */
 | |
|       DBUG_ASSERT(ddl_log_state_rm.is_active());
 | |
|       log_drop_table(thd, &table->db, &table->table_name,
 | |
|                      &create_info->org_storage_engine_name,
 | |
|                      create_info->db_type == partition_hton,
 | |
|                      &create_info->org_tabledef_version,
 | |
|                      create_info->tmp_table());
 | |
|     }
 | |
|     else if (res != 2)                         // Table was not dropped
 | |
|     {
 | |
|       if (write_bin_log(thd, res ? FALSE : TRUE, thd->query(),
 | |
|                         thd->query_length(), is_trans))
 | |
|         res= 1;
 | |
|     }
 | |
|     debug_crash_here("ddl_log_create_after_binlog");
 | |
|     thd->binlog_xid= 0;
 | |
|   }
 | |
| 
 | |
|   if (!res && !src_table_exists && !create_info->tmp_table())
 | |
|   {
 | |
|     backup_log_info ddl_log;
 | |
|     bzero(&ddl_log, sizeof(ddl_log));
 | |
|     ddl_log.query= { C_STRING_WITH_LEN("CREATE") };
 | |
|     ddl_log.org_storage_engine_name= local_create_info.new_storage_engine_name;
 | |
|     ddl_log.org_database=     table->db;
 | |
|     ddl_log.org_table=        table->table_name;
 | |
|     ddl_log.org_table_id=     local_create_info.tabledef_version;
 | |
|     backup_log_ddl(&ddl_log);
 | |
|   }
 | |
| 
 | |
|   ddl_log_complete(&ddl_log_state_rm);
 | |
|   ddl_log_complete(&ddl_log_state_create);
 | |
|   DBUG_RETURN(res != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /* table_list should contain just one table */
 | |
| int mysql_discard_or_import_tablespace(THD *thd,
 | |
|                                        TABLE_LIST *table_list,
 | |
|                                        bool discard)
 | |
| {
 | |
|   Alter_table_prelocking_strategy alter_prelocking_strategy;
 | |
|   int error;
 | |
|   DBUG_ENTER("mysql_discard_or_import_tablespace");
 | |
| 
 | |
|   mysql_audit_alter_table(thd, table_list);
 | |
| 
 | |
|   /*
 | |
|     Note that DISCARD/IMPORT TABLESPACE always is the only operation in an
 | |
|     ALTER TABLE
 | |
|   */
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_discard_or_import_tablespace);
 | |
| 
 | |
|  /*
 | |
|    We set this flag so that ha_innobase::open and ::external_lock() do
 | |
|    not complain when we lock the table
 | |
|  */
 | |
|   thd->tablespace_op= TRUE;
 | |
|   /*
 | |
|     Adjust values of table-level and metadata which was set in parser
 | |
|     for the case general ALTER TABLE.
 | |
|   */
 | |
|   table_list->mdl_request.set_type(MDL_EXCLUSIVE);
 | |
|   table_list->lock_type= TL_WRITE;
 | |
|   /* Do not open views. */
 | |
|   table_list->required_type= TABLE_TYPE_NORMAL;
 | |
| 
 | |
|   if (open_and_lock_tables(thd, table_list, FALSE, 0,
 | |
|                            &alter_prelocking_strategy))
 | |
|   {
 | |
|     thd->tablespace_op=FALSE;
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(table_list->table->s->hlindexes() <= 1);
 | |
|   for (uint i= table_list->table->s->keys; i < table_list->table->s->total_keys; i++)
 | |
|   {
 | |
|     if (table_list->table->hlindex_open(i))
 | |
|     {
 | |
|       thd->tablespace_op= FALSE;
 | |
|       DBUG_RETURN(-1);
 | |
|     }
 | |
|   }
 | |
|   for (uint i= table_list->table->s->keys; i < table_list->table->s->total_keys; i++)
 | |
|   {
 | |
|     error= table_list->table->hlindex->file->
 | |
|       ha_discard_or_import_tablespace(discard);
 | |
|     if (unlikely(error))
 | |
|       goto err;
 | |
|   }
 | |
| 
 | |
|   error= table_list->table->file->ha_discard_or_import_tablespace(discard);
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_end);
 | |
| 
 | |
|   if (unlikely(error))
 | |
|     goto err;
 | |
| 
 | |
|   if (discard)
 | |
|     table_list->table->s->tdc->flush(thd, true);
 | |
| 
 | |
|   /*
 | |
|     The 0 in the call below means 'not in a transaction', which means
 | |
|     immediate invalidation; that is probably what we wish here
 | |
|   */
 | |
|   query_cache_invalidate3(thd, table_list, 0);
 | |
| 
 | |
|   /* The ALTER TABLE is always in its own transaction */
 | |
|   error= trans_commit_stmt(thd);
 | |
|   if (unlikely(trans_commit_implicit(thd)))
 | |
|     error=1;
 | |
|   if (likely(!error))
 | |
|     error= write_bin_log(thd, FALSE, thd->query(), thd->query_length());
 | |
| 
 | |
| err:
 | |
|   thd->tablespace_op=FALSE;
 | |
| 
 | |
|   if (likely(error == 0))
 | |
|   {
 | |
|     my_ok(thd);
 | |
|     DBUG_RETURN(0);
 | |
|   }
 | |
| 
 | |
|   table_list->table->file->print_error(error, MYF(0));
 | |
| 
 | |
|   DBUG_RETURN(-1);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check if key is a candidate key, i.e. a unique index with no index
 | |
|   fields partial or nullable.
 | |
| */
 | |
| 
 | |
| static bool is_candidate_key(KEY *key)
 | |
| {
 | |
|   KEY_PART_INFO *key_part;
 | |
|   KEY_PART_INFO *key_part_end= key->key_part + key->user_defined_key_parts;
 | |
| 
 | |
|   if (!(key->flags & HA_NOSAME) || (key->flags & HA_NULL_PART_KEY) ||
 | |
|       (key->flags & HA_KEY_HAS_PART_KEY_SEG))
 | |
|     return false;
 | |
| 
 | |
|   for (key_part= key->key_part; key_part < key_part_end; key_part++)
 | |
|   {
 | |
|     if (key_part->key_part_flag & HA_PART_KEY_SEG)
 | |
|       return false;
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|    Preparation for table creation
 | |
| 
 | |
|    SYNOPSIS
 | |
|      handle_if_exists_option()
 | |
|        thd                       Thread object.
 | |
|        table                     The altered table.
 | |
|        alter_info                List of columns and indexes to create
 | |
|        period_info               Application-time period info
 | |
| 
 | |
|    DESCRIPTION
 | |
|      Looks for the IF [NOT] EXISTS options, checks the states and remove items
 | |
|      from the list if existing found.
 | |
| 
 | |
|    RETURN VALUES
 | |
|      TRUE error
 | |
|      FALSE OK
 | |
| */
 | |
| 
 | |
| static bool
 | |
| handle_if_exists_options(THD *thd, TABLE *table, Alter_info *alter_info,
 | |
|                          Table_period_info *period_info)
 | |
| {
 | |
|   Field **f_ptr;
 | |
|   DBUG_ENTER("handle_if_exists_option");
 | |
| 
 | |
|   /* Handle ADD COLUMN IF NOT EXISTS. */
 | |
|   {
 | |
|     List_iterator<Create_field> it(alter_info->create_list);
 | |
|     Create_field *sql_field;
 | |
| 
 | |
|     while ((sql_field=it++))
 | |
|     {
 | |
|       if (!sql_field->create_if_not_exists || sql_field->change.str)
 | |
|         continue;
 | |
|       /*
 | |
|          If there is a field with the same name in the table already,
 | |
|          remove the sql_field from the list.
 | |
|       */
 | |
|       for (f_ptr=table->field; *f_ptr; f_ptr++)
 | |
|       {
 | |
|         if (sql_field->field_name.streq((*f_ptr)->field_name))
 | |
|           goto drop_create_field;
 | |
|       }
 | |
|       {
 | |
|         /*
 | |
|           If in the ADD list there is a field with the same name,
 | |
|           remove the sql_field from the list.
 | |
|         */
 | |
|         List_iterator<Create_field> chk_it(alter_info->create_list);
 | |
|         Create_field *chk_field;
 | |
|         while ((chk_field= chk_it++) && chk_field != sql_field)
 | |
|         {
 | |
|           if (sql_field->field_name.streq(chk_field->field_name))
 | |
|             goto drop_create_field;
 | |
|         }
 | |
|       }
 | |
|       continue;
 | |
| drop_create_field:
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                           ER_DUP_FIELDNAME, ER_THD(thd, ER_DUP_FIELDNAME),
 | |
|                           sql_field->field_name.str);
 | |
|       it.remove();
 | |
|       if (alter_info->create_list.is_empty())
 | |
|       {
 | |
|         alter_info->flags&= ~ALTER_PARSER_ADD_COLUMN;
 | |
|         if (alter_info->key_list.is_empty())
 | |
|           alter_info->flags&= ~(ALTER_ADD_INDEX | ALTER_ADD_FOREIGN_KEY);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Handle MODIFY COLUMN IF EXISTS. */
 | |
|   {
 | |
|     List_iterator<Create_field> it(alter_info->create_list);
 | |
|     Create_field *sql_field;
 | |
| 
 | |
|     while ((sql_field=it++))
 | |
|     {
 | |
|       if (!sql_field->create_if_not_exists || !sql_field->change.str)
 | |
|         continue;
 | |
|       /*
 | |
|          If there is NO field with the same name in the table already,
 | |
|          remove the sql_field from the list.
 | |
|       */
 | |
|       for (f_ptr=table->field; *f_ptr; f_ptr++)
 | |
|       {
 | |
|         if (sql_field->change.streq((*f_ptr)->field_name))
 | |
|         {
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if (unlikely(*f_ptr == NULL))
 | |
|       {
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_BAD_FIELD_ERROR,
 | |
|                             ER_THD(thd, ER_BAD_FIELD_ERROR),
 | |
|                             sql_field->change.str, table->s->table_name.str);
 | |
|         it.remove();
 | |
|         if (alter_info->create_list.is_empty())
 | |
|         {
 | |
|           alter_info->flags&= ~(ALTER_PARSER_ADD_COLUMN | ALTER_CHANGE_COLUMN);
 | |
|           if (alter_info->key_list.is_empty())
 | |
|             alter_info->flags&= ~ALTER_ADD_INDEX;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Handle ALTER/RENAME COLUMN IF EXISTS. */
 | |
|   {
 | |
|     List_iterator<Alter_column> it(alter_info->alter_list);
 | |
|     Alter_column *acol;
 | |
| 
 | |
|     while ((acol=it++))
 | |
|     {
 | |
|       if (!acol->alter_if_exists)
 | |
|         continue;
 | |
|       /*
 | |
|          If there is NO field with the same name in the table already,
 | |
|          remove the acol from the list.
 | |
|       */
 | |
|       for (f_ptr=table->field; *f_ptr; f_ptr++)
 | |
|       {
 | |
|         if ((*f_ptr)->field_name.streq(acol->name))
 | |
|           break;
 | |
|       }
 | |
|       if (unlikely(*f_ptr == NULL))
 | |
|       {
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_BAD_FIELD_ERROR,
 | |
|                             ER_THD(thd, ER_BAD_FIELD_ERROR),
 | |
|                             acol->name.str, table->s->table_name.str);
 | |
|         it.remove();
 | |
|         if (alter_info->alter_list.is_empty())
 | |
|         {
 | |
|           alter_info->flags&= ~(ALTER_CHANGE_COLUMN_DEFAULT);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Handle DROP COLUMN/KEY IF EXISTS. */
 | |
|   {
 | |
|     List_iterator<Alter_drop> drop_it(alter_info->drop_list);
 | |
|     Alter_drop *drop;
 | |
|     bool remove_drop;
 | |
|     ulonglong left_flags= 0;
 | |
|     while ((drop= drop_it++))
 | |
|     {
 | |
|       ulonglong cur_flag= 0;
 | |
|       switch (drop->type) {
 | |
|       case Alter_drop::COLUMN:
 | |
|         cur_flag= ALTER_PARSER_DROP_COLUMN;
 | |
|         break;
 | |
|       case Alter_drop::FOREIGN_KEY:
 | |
|         cur_flag= ALTER_DROP_FOREIGN_KEY;
 | |
|         break;
 | |
|       case Alter_drop::KEY:
 | |
|         cur_flag= ALTER_DROP_INDEX;
 | |
|         break;
 | |
|       default:
 | |
|         break;
 | |
|       }
 | |
|       if (!drop->drop_if_exists)
 | |
|       {
 | |
|         left_flags|= cur_flag;
 | |
|         continue;
 | |
|       }
 | |
|       remove_drop= TRUE;
 | |
|       if (drop->type == Alter_drop::COLUMN)
 | |
|       {
 | |
|         /*
 | |
|            If there is NO field with that name in the table,
 | |
|            remove the 'drop' from the list.
 | |
|         */
 | |
|         for (f_ptr=table->field; *f_ptr; f_ptr++)
 | |
|         {
 | |
|           if ((*f_ptr)->field_name.streq(drop->name))
 | |
|           {
 | |
|             remove_drop= FALSE;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       else if (drop->type == Alter_drop::CHECK_CONSTRAINT)
 | |
|       {
 | |
|         for (uint i=table->s->field_check_constraints;
 | |
|              i < table->s->table_check_constraints;
 | |
|              i++)
 | |
|         {
 | |
|           if (table->check_constraints[i]->name.streq(drop->name))
 | |
|           {
 | |
|             remove_drop= FALSE;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       else if (drop->type == Alter_drop::PERIOD)
 | |
|       {
 | |
|         if (table->s->period.name.streq(drop->name))
 | |
|           remove_drop= FALSE;
 | |
|       }
 | |
|       else /* Alter_drop::KEY and Alter_drop::FOREIGN_KEY */
 | |
|       {
 | |
|         uint n_key;
 | |
|         if (drop->type != Alter_drop::FOREIGN_KEY)
 | |
|         {
 | |
|           for (n_key=0; n_key < table->s->total_keys; n_key++)
 | |
|           {
 | |
|             if (table->key_info[n_key].name.streq(drop->name))
 | |
|             {
 | |
|               remove_drop= FALSE;
 | |
|               break;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           List <FOREIGN_KEY_INFO> fk_child_key_list;
 | |
|           FOREIGN_KEY_INFO *f_key;
 | |
|           table->file->get_foreign_key_list(thd, &fk_child_key_list);
 | |
|           List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
 | |
|           while ((f_key= fk_key_it++))
 | |
|           {
 | |
|             if (Lex_ident_column(*f_key->foreign_id).streq(drop->name))
 | |
|             {
 | |
|               remove_drop= FALSE;
 | |
|               break;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (!remove_drop)
 | |
|       {
 | |
|         /*
 | |
|           Check if the name appears twice in the DROP list.
 | |
|         */
 | |
|         List_iterator<Alter_drop> chk_it(alter_info->drop_list);
 | |
|         Alter_drop *chk_drop;
 | |
|         while ((chk_drop= chk_it++) && chk_drop != drop)
 | |
|         {
 | |
|           if (drop->type == chk_drop->type &&
 | |
|               chk_drop->name.streq(drop->name))
 | |
|           {
 | |
|             remove_drop= TRUE;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (remove_drop)
 | |
|       {
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_CANT_DROP_FIELD_OR_KEY,
 | |
|                             ER_THD(thd, ER_CANT_DROP_FIELD_OR_KEY),
 | |
|                             drop->type_name(), drop->name.str);
 | |
|         drop_it.remove();
 | |
|       }
 | |
|       else
 | |
|         left_flags|= cur_flag;
 | |
|     }
 | |
|     /* Reset state to what's left in drop list */
 | |
|     alter_info->flags&= ~(ALTER_PARSER_DROP_COLUMN |
 | |
|                           ALTER_DROP_INDEX |
 | |
|                           ALTER_DROP_FOREIGN_KEY);
 | |
|     alter_info->flags|= left_flags;
 | |
|   }
 | |
| 
 | |
|   /* Handle RENAME KEY IF EXISTS. */
 | |
|   {
 | |
|     List_iterator<Alter_rename_key> rename_key_it(alter_info->alter_rename_key_list);
 | |
|     Alter_rename_key *rename_key;
 | |
|     while ((rename_key= rename_key_it++))
 | |
|     {
 | |
|       if (!rename_key->alter_if_exists)
 | |
|         continue;
 | |
|       bool exists= false;
 | |
|       for (uint n_key= 0; n_key < table->s->total_keys; n_key++)
 | |
|       {
 | |
|         if (table->key_info[n_key].name.streq(rename_key->old_name))
 | |
|         {
 | |
|           exists= true;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if (exists)
 | |
|         continue;
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                           ER_KEY_DOES_NOT_EXISTS,
 | |
|                           ER_THD(thd, ER_KEY_DOES_NOT_EXISTS),
 | |
|                           rename_key->old_name.str, table->s->table_name.str);
 | |
|       rename_key_it.remove();
 | |
|     }
 | |
|   }
 | |
|   /* Handle ALTER KEY IF EXISTS. */
 | |
|   {
 | |
|     List_iterator<Alter_index_ignorability> ignor_it(alter_info->alter_index_ignorability_list);
 | |
|     Alter_index_ignorability *aii;
 | |
|     while ((aii= ignor_it++))
 | |
|     {
 | |
|       if (!aii->if_exists())
 | |
|         continue;
 | |
|       bool exists= false;
 | |
|       for (uint n_key= 0; n_key < table->s->total_keys; n_key++)
 | |
|       {
 | |
|         if (table->key_info[n_key].name.streq(aii->name()))
 | |
|         {
 | |
|           exists= true;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if (exists)
 | |
|         continue;
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                           ER_KEY_DOES_NOT_EXISTS,
 | |
|                           ER_THD(thd, ER_KEY_DOES_NOT_EXISTS),
 | |
|                           aii->name().str, table->s->table_name.str);
 | |
|       ignor_it.remove();
 | |
|     }
 | |
|   }
 | |
|   /* ALTER TABLE ADD KEY IF NOT EXISTS */
 | |
|   /* ALTER TABLE ADD FOREIGN KEY IF NOT EXISTS */
 | |
|   {
 | |
|     Key *key;
 | |
|     List_iterator<Key> key_it(alter_info->key_list);
 | |
|     uint n_key;
 | |
|     Lex_ident_column keyname;
 | |
|     while ((key=key_it++))
 | |
|     {
 | |
|       if (!key->if_not_exists() && !key->or_replace())
 | |
|         continue;
 | |
| 
 | |
|       /* Check if the table already has a PRIMARY KEY */
 | |
|       bool dup_primary_key=
 | |
|             key->type == Key::PRIMARY &&
 | |
|             table->s->primary_key != MAX_KEY &&
 | |
|             (keyname= table->s->key_info[table->s->primary_key].name).str &&
 | |
|             table->s->key_info[table->s->primary_key].name.
 | |
|               streq(primary_key_name);
 | |
|       if (dup_primary_key)
 | |
|         goto remove_key;
 | |
| 
 | |
|       /* If the name of the key is not specified,     */
 | |
|       /* let us check the name of the first key part. */
 | |
|       if ((keyname= key->name).str == NULL)
 | |
|       {
 | |
|         if (key->type == Key::PRIMARY)
 | |
|           keyname= primary_key_name;
 | |
|         else
 | |
|         {
 | |
|           List_iterator<Key_part_spec> part_it(key->columns);
 | |
|           Key_part_spec *kp;
 | |
|           if ((kp= part_it++))
 | |
|             keyname= kp->field_name;
 | |
|           if (keyname.str == NULL)
 | |
|             continue;
 | |
|         }
 | |
|       }
 | |
|       if (key->type != Key::FOREIGN_KEY)
 | |
|       {
 | |
|         for (n_key=0; n_key < table->s->total_keys; n_key++)
 | |
|         {
 | |
|           if (table->key_info[n_key].name.streq(keyname))
 | |
|           {
 | |
|             goto remove_key;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         List <FOREIGN_KEY_INFO> fk_child_key_list;
 | |
|         FOREIGN_KEY_INFO *f_key;
 | |
|         table->file->get_foreign_key_list(thd, &fk_child_key_list);
 | |
|         List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
 | |
|         while ((f_key= fk_key_it++))
 | |
|         {
 | |
|           if (Lex_ident_column(*f_key->foreign_id).streq(keyname))
 | |
|             goto remove_key;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       {
 | |
|         Key *chk_key;
 | |
|         List_iterator<Key> chk_it(alter_info->key_list);
 | |
|         Lex_ident_column chkname;
 | |
|         while ((chk_key=chk_it++) && chk_key != key)
 | |
|         {
 | |
|           if ((chkname= chk_key->name).str == NULL)
 | |
|           {
 | |
|             List_iterator<Key_part_spec> part_it(chk_key->columns);
 | |
|             Key_part_spec *kp;
 | |
|             if ((kp= part_it++))
 | |
|               chkname= kp->field_name;
 | |
|             if (chkname.str == NULL)
 | |
|               continue;
 | |
|           }
 | |
|           if (key->type == chk_key->type &&
 | |
|               chkname.streq(keyname))
 | |
|             goto remove_key;
 | |
|         }
 | |
|       }
 | |
|       continue;
 | |
| 
 | |
| remove_key:
 | |
|       if (key->if_not_exists())
 | |
|       {
 | |
|         push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                             ER_DUP_KEYNAME, ER_THD(thd, dup_primary_key
 | |
|                             ? ER_MULTIPLE_PRI_KEY : ER_DUP_KEYNAME),
 | |
|                             keyname.str);
 | |
|         key_it.remove();
 | |
|         if (key->type == Key::FOREIGN_KEY)
 | |
|         {
 | |
|           /* ADD FOREIGN KEY appends two items. */
 | |
|           key_it.remove();
 | |
|         }
 | |
|         if (alter_info->key_list.is_empty())
 | |
|           alter_info->flags&= ~(ALTER_ADD_INDEX | ALTER_ADD_FOREIGN_KEY);
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         DBUG_ASSERT(key->or_replace());
 | |
|         Alter_drop::drop_type type= (key->type == Key::FOREIGN_KEY) ?
 | |
|           Alter_drop::FOREIGN_KEY : Alter_drop::KEY;
 | |
|         Alter_drop *ad= new (thd->mem_root) Alter_drop(type, key->name, FALSE);
 | |
|         if (ad != NULL)
 | |
|         {
 | |
|           // Adding the index into the drop list for replacing
 | |
|           alter_info->flags |= ALTER_DROP_INDEX;
 | |
|           alter_info->drop_list.push_back(ad, thd->mem_root);
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   partition_info *tab_part_info= table->part_info;
 | |
|   thd->work_part_info= thd->lex->part_info;
 | |
|   if (tab_part_info)
 | |
|   {
 | |
|     /* ALTER TABLE ADD PARTITION IF NOT EXISTS */
 | |
|     if ((alter_info->partition_flags & ALTER_PARTITION_ADD) &&
 | |
|         thd->lex->create_info.if_not_exists())
 | |
|     {
 | |
|       partition_info *alt_part_info= thd->lex->part_info;
 | |
|       if (alt_part_info)
 | |
|       {
 | |
|         List_iterator<partition_element> new_part_it(alt_part_info->partitions);
 | |
|         partition_element *pe;
 | |
|         while ((pe= new_part_it++))
 | |
|         {
 | |
|           if (!tab_part_info->has_unique_name(pe))
 | |
|           {
 | |
|             push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                                 ER_SAME_NAME_PARTITION,
 | |
|                                 ER_THD(thd, ER_SAME_NAME_PARTITION),
 | |
|                                 pe->partition_name.str);
 | |
|             alter_info->partition_flags&= ~ALTER_PARTITION_ADD;
 | |
|             thd->work_part_info= NULL;
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     /* ALTER TABLE DROP PARTITION IF EXISTS */
 | |
|     if ((alter_info->partition_flags & ALTER_PARTITION_DROP) &&
 | |
|         thd->lex->if_exists())
 | |
|     {
 | |
|       List_iterator<const char> names_it(alter_info->partition_names);
 | |
|       const char *name;
 | |
| 
 | |
|       while ((name= names_it++))
 | |
|       {
 | |
|         List_iterator<partition_element> part_it(tab_part_info->partitions);
 | |
|         partition_element *part_elem;
 | |
|         while ((part_elem= part_it++))
 | |
|         {
 | |
|           if (part_elem->partition_name.streq(Lex_cstring_strlen(name)))
 | |
|             break;
 | |
|         }
 | |
|         if (!part_elem)
 | |
|         {
 | |
|           push_warning(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                        ER_PARTITION_DOES_NOT_EXIST,
 | |
|                        ER_THD(thd, ER_PARTITION_DOES_NOT_EXIST));
 | |
|           names_it.remove();
 | |
|         }
 | |
|       }
 | |
|       if (alter_info->partition_names.elements == 0)
 | |
|         alter_info->partition_flags&= ~ALTER_PARTITION_DROP;
 | |
|     }
 | |
|   }
 | |
| #endif /*WITH_PARTITION_STORAGE_ENGINE*/
 | |
| 
 | |
|   /* ADD CONSTRAINT IF NOT EXISTS. */
 | |
|   {
 | |
|     List_iterator<Virtual_column_info> it(alter_info->check_constraint_list);
 | |
|     Virtual_column_info *check;
 | |
|     TABLE_SHARE *share= table->s;
 | |
|     uint c;
 | |
| 
 | |
|     while ((check=it++))
 | |
|     {
 | |
|       if (!check->if_not_exists && check->name.length)
 | |
|         continue;
 | |
|       for (c= share->field_check_constraints;
 | |
|            c < share->table_check_constraints ; c++)
 | |
|       {
 | |
|         Virtual_column_info *dup= table->check_constraints[c];
 | |
|         if (check->name.streq(dup->name))
 | |
|         {
 | |
|           push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|             ER_DUP_CONSTRAINT_NAME, ER_THD(thd, ER_DUP_CONSTRAINT_NAME),
 | |
|             "CHECK", check->name.str);
 | |
|           it.remove();
 | |
|           if (alter_info->check_constraint_list.elements == 0)
 | |
|             alter_info->flags&= ~ALTER_ADD_CHECK_CONSTRAINT;
 | |
| 
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* ADD PERIOD */
 | |
| 
 | |
|   if (period_info->create_if_not_exists && table->s->period.name
 | |
|       && table->s->period.name.streq(period_info->name))
 | |
|   {
 | |
|     DBUG_ASSERT(period_info->is_set());
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                         ER_DUP_FIELDNAME, ER_THD(thd, ER_DUP_FIELDNAME),
 | |
|                         period_info->name.str, table->s->table_name.str);
 | |
| 
 | |
|     List_iterator<Virtual_column_info> vit(alter_info->check_constraint_list);
 | |
|     while (vit++ != period_info->constr)
 | |
|     {
 | |
|       // do nothing
 | |
|     }
 | |
|     vit.remove();
 | |
| 
 | |
|     *period_info= {};
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| static bool fix_constraints_names(THD *thd, List<Virtual_column_info>
 | |
|                                   *check_constraint_list,
 | |
|                                   const HA_CREATE_INFO *create_info)
 | |
| {
 | |
|   List_iterator<Virtual_column_info> it((*check_constraint_list));
 | |
|   Virtual_column_info *check;
 | |
|   uint nr= 1;
 | |
|   DBUG_ENTER("fix_constraints_names");
 | |
|   if (!check_constraint_list)
 | |
|     DBUG_RETURN(FALSE);
 | |
|   // Prevent accessing freed memory during generating unique names
 | |
|   while ((check=it++))
 | |
|   {
 | |
|     if (check->automatic_name)
 | |
|     {
 | |
|       check->name.str= NULL;
 | |
|       check->name.length= 0;
 | |
|     }
 | |
|   }
 | |
|   it.rewind();
 | |
|   // Generate unique names if needed
 | |
|   while ((check=it++))
 | |
|   {
 | |
|     if (!check->name.length)
 | |
|     {
 | |
|       check->automatic_name= TRUE;
 | |
| 
 | |
|       const char *own_name_base= create_info->period_info.constr == check
 | |
|         ? create_info->period_info.name.str : NULL;
 | |
| 
 | |
|       if (make_unique_constraint_name(thd, &check->name,
 | |
|                                       own_name_base,
 | |
|                                       check_constraint_list,
 | |
|                                       &nr))
 | |
|         DBUG_RETURN(TRUE);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(FALSE);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int compare_uint(const void *s_, const void *t_)
 | |
| {
 | |
|   const uint *s= static_cast<const uint *>(s_);
 | |
|   const uint *t= static_cast<const uint *>(t_);
 | |
|   return (*s < *t) ? -1 : ((*s > *t) ? 1 : 0);
 | |
| }
 | |
| 
 | |
| static Compare_keys merge(Compare_keys current, Compare_keys add) {
 | |
|   if (current == Compare_keys::Equal)
 | |
|     return add;
 | |
| 
 | |
|   if (add == Compare_keys::Equal)
 | |
|     return current;
 | |
| 
 | |
|   if (current == add)
 | |
|     return current;
 | |
| 
 | |
|   if (current == Compare_keys::EqualButComment) {
 | |
|     return Compare_keys::NotEqual;
 | |
|   }
 | |
| 
 | |
|   if (current == Compare_keys::EqualButKeyPartLength) {
 | |
|     if (add == Compare_keys::EqualButComment)
 | |
|       return Compare_keys::NotEqual;
 | |
|     DBUG_ASSERT(add == Compare_keys::NotEqual);
 | |
|     return Compare_keys::NotEqual;
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(current == Compare_keys::NotEqual);
 | |
|   return current;
 | |
| }
 | |
| 
 | |
| Compare_keys compare_keys_but_name(const KEY *table_key, const KEY *new_key,
 | |
|                                    Alter_info *alter_info, const TABLE *table,
 | |
|                                    const KEY *const new_pk,
 | |
|                                    const KEY *const old_pk)
 | |
| {
 | |
|   if (table_key->algorithm != new_key->algorithm)
 | |
|     return Compare_keys::NotEqual;
 | |
| 
 | |
|   if ((table_key->flags & HA_KEYFLAG_MASK) !=
 | |
|       (new_key->flags & HA_KEYFLAG_MASK))
 | |
|     return Compare_keys::NotEqual;
 | |
| 
 | |
|   if (table_key->user_defined_key_parts != new_key->user_defined_key_parts)
 | |
|     return Compare_keys::NotEqual;
 | |
| 
 | |
|   if (table_key->block_size != new_key->block_size)
 | |
|     return Compare_keys::NotEqual;
 | |
| 
 | |
|   /*
 | |
|   Rebuild the index if following condition get satisfied:
 | |
| 
 | |
|   (i) Old table doesn't have primary key, new table has it and vice-versa
 | |
|   (ii) Primary key changed to another existing index
 | |
|   */
 | |
|   if ((new_key == new_pk) != (table_key == old_pk))
 | |
|     return Compare_keys::NotEqual;
 | |
| 
 | |
|   if (engine_options_differ(table_key->option_struct, new_key->option_struct,
 | |
|                             table_key->algorithm == HA_KEY_ALG_VECTOR ?
 | |
|                             mhnsw_index_options :
 | |
|                             table->file->ht->index_options))
 | |
|     return Compare_keys::NotEqual;
 | |
| 
 | |
|   Compare_keys result= Compare_keys::Equal;
 | |
| 
 | |
|   for (const KEY_PART_INFO *
 | |
|            key_part= table_key->key_part,
 | |
|           *new_part= new_key->key_part,
 | |
|           *end= table_key->key_part + table_key->user_defined_key_parts;
 | |
|        key_part < end; key_part++, new_part++)
 | |
|   {
 | |
|     /*
 | |
|       For prefix keys KEY_PART_INFO::field points to cloned Field
 | |
|       object with adjusted length. So below we have to check field
 | |
|       indexes instead of simply comparing pointers to Field objects.
 | |
|     */
 | |
|     const Create_field &new_field=
 | |
|         *alter_info->create_list.elem(new_part->fieldnr);
 | |
| 
 | |
|     if (!new_field.field ||
 | |
|         new_field.field->field_index != key_part->fieldnr - 1)
 | |
|     {
 | |
|       return Compare_keys::NotEqual;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Check the descending flag for index field.
 | |
|     */
 | |
|     if ((new_part->key_part_flag ^ key_part->key_part_flag) & HA_REVERSE_SORT)
 | |
|     {
 | |
|       return Compare_keys::NotEqual;
 | |
|     }
 | |
| 
 | |
|     auto compare= table->file->compare_key_parts(
 | |
|         *table->field[key_part->fieldnr - 1], new_field, *key_part, *new_part);
 | |
|     result= merge(result, compare);
 | |
|   }
 | |
| 
 | |
|   /* Check that key comment is not changed. */
 | |
|   if (cmp(table_key->comment, new_key->comment) != 0)
 | |
|     result= merge(result, Compare_keys::EqualButComment);
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Look-up KEY object by index name using case-insensitive comparison.
 | |
| 
 | |
|   @param key_name   Index name.
 | |
|   @param key_start  Start of array of KEYs for table.
 | |
|   @param key_end    End of array of KEYs for table.
 | |
| 
 | |
|   @note Case-insensitive comparison is necessary to correctly
 | |
|         handle renaming of keys.
 | |
| 
 | |
|   @retval non-NULL - pointer to KEY object for index found.
 | |
|   @retval NULL     - no index with such name found (or it is marked
 | |
|                      as renamed).
 | |
| */
 | |
| 
 | |
| static KEY *find_key_ci(const Lex_ident_column &key_name,
 | |
|                         KEY *key_start, KEY *key_end)
 | |
| {
 | |
|   for (KEY *key = key_start; key < key_end; key++)
 | |
|   {
 | |
|     if (key_name.streq(key->name))
 | |
|       return key;
 | |
|   }
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|    Compare original and new versions of a table and fill Alter_inplace_info
 | |
|    describing differences between those versions.
 | |
| 
 | |
|    @param          thd                Thread
 | |
|    @param          table              The original table.
 | |
|    @param[in/out]  ha_alter_info      Data structure which already contains
 | |
|                                       basic information about create options,
 | |
|                                       field and keys for the new version of
 | |
|                                       table and which should be completed with
 | |
|                                       more detailed information needed for
 | |
|                                       in-place ALTER.
 | |
| 
 | |
|    First argument 'table' contains information of the original
 | |
|    table, which includes all corresponding parts that the new
 | |
|    table has in arguments create_list, key_list and create_info.
 | |
| 
 | |
|    Compare the changes between the original and new table definitions.
 | |
|    The result of this comparison is then passed to SE which determines
 | |
|    whether it can carry out these changes in-place.
 | |
| 
 | |
|    Mark any changes detected in the ha_alter_flags.
 | |
|    We generally try to specify handler flags only if there are real
 | |
|    changes. But in cases when it is cumbersome to determine if some
 | |
|    attribute has really changed we might choose to set flag
 | |
|    pessimistically, for example, relying on parser output only.
 | |
| 
 | |
|    If there are no data changes, but index changes, 'index_drop_buffer'
 | |
|    and/or 'index_add_buffer' are populated with offsets into
 | |
|    table->key_info or key_info_buffer respectively for the indexes
 | |
|    that need to be dropped and/or (re-)created.
 | |
| 
 | |
|    Note that this function assumes that it is OK to change Alter_info
 | |
|    and HA_CREATE_INFO which it gets. It is caller who is responsible
 | |
|    for creating copies for this structures if he needs them unchanged.
 | |
| 
 | |
|    @retval true  error
 | |
|    @retval false success
 | |
| */
 | |
| 
 | |
| static bool fill_alter_inplace_info(THD *thd, TABLE *table,
 | |
|                                     Alter_inplace_info *ha_alter_info)
 | |
| {
 | |
|   Field **f_ptr, *field;
 | |
|   List_iterator_fast<Create_field> new_field_it;
 | |
|   Create_field *new_field;
 | |
|   Alter_info *alter_info= ha_alter_info->alter_info;
 | |
|   DBUG_ENTER("fill_alter_inplace_info");
 | |
|   DBUG_PRINT("info", ("alter_info->flags: %llu", alter_info->flags));
 | |
| 
 | |
|   /* Allocate result buffers. */
 | |
|   DBUG_ASSERT(ha_alter_info->rename_keys.mem_root() == thd->mem_root);
 | |
|   if (! (ha_alter_info->index_drop_buffer=
 | |
|            thd->alloc<KEY*>(table->s->total_keys)) ||
 | |
|       ! (ha_alter_info->index_add_buffer=
 | |
|            thd->alloc<uint>(alter_info->key_list.elements)) ||
 | |
|       ha_alter_info->rename_keys.reserve(ha_alter_info->index_add_count) ||
 | |
|       ! (ha_alter_info->index_altered_ignorability_buffer=
 | |
|            thd->alloc<KEY_PAIR>(alter_info->alter_index_ignorability_list.elements)))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     Copy parser flags, but remove some flags that handlers doesn't
 | |
|     need to care about (old engines may not ignore these parser flags).
 | |
|     ALTER_RENAME_COLUMN is replaced by ALTER_COLUMN_NAME.
 | |
|     ALTER_CHANGE_COLUMN_DEFAULT is replaced by ALTER_CHANGE_COLUMN
 | |
|     ALTER_PARSE_ADD_COLUMN, ALTER_PARSE_DROP_COLUMN, ALTER_ADD_INDEX and
 | |
|     ALTER_DROP_INDEX are replaced with versions that have higher granularity.
 | |
|   */
 | |
| 
 | |
|   alter_table_operations flags_to_remove=
 | |
|       ALTER_ADD_INDEX | ALTER_DROP_INDEX | ALTER_PARSER_ADD_COLUMN |
 | |
|       ALTER_PARSER_DROP_COLUMN | ALTER_COLUMN_ORDER | ALTER_RENAME_COLUMN |
 | |
|       ALTER_CHANGE_COLUMN | ALTER_VERS_EXPLICIT;
 | |
| 
 | |
|   if (!table->file->native_versioned())
 | |
|     flags_to_remove|= ALTER_COLUMN_UNVERSIONED;
 | |
| 
 | |
|   ha_alter_info->handler_flags|= (alter_info->flags & ~flags_to_remove);
 | |
|   /*
 | |
|     Comparing new and old default values of column is cumbersome.
 | |
|     So instead of using such a comparison for detecting if default
 | |
|     has really changed we rely on flags set by parser to get an
 | |
|     approximate value for storage engine flag.
 | |
|   */
 | |
|   if (alter_info->flags & ALTER_CHANGE_COLUMN)
 | |
|     ha_alter_info->handler_flags|= ALTER_COLUMN_DEFAULT;
 | |
| 
 | |
|   DBUG_PRINT("info", ("handler_flags: %llu", ha_alter_info->handler_flags));
 | |
| 
 | |
|   /*
 | |
|     Go through fields in old version of table and detect changes to them.
 | |
|     We don't want to rely solely on Alter_info flags for this since:
 | |
|     a) new definition of column can be fully identical to the old one
 | |
|        despite the fact that this column is mentioned in MODIFY clause.
 | |
|     b) even if new column type differs from its old column from metadata
 | |
|        point of view, it might be identical from storage engine point
 | |
|        of view (e.g. when ENUM('a','b') is changed to ENUM('a','b',c')).
 | |
|     c) flags passed to storage engine contain more detailed information
 | |
|        about nature of changes than those provided from parser.
 | |
|   */
 | |
|   bool maybe_alter_vcol= false;
 | |
|   uint field_stored_index= 0;
 | |
|   for (f_ptr= table->field; (field= *f_ptr); f_ptr++,
 | |
|                                field_stored_index+= field->stored_in_db())
 | |
|   {
 | |
|     /* Clear marker for renamed or dropped field
 | |
|     which we are going to set later. */
 | |
|     field->flags&= ~(FIELD_IS_RENAMED | FIELD_IS_DROPPED);
 | |
| 
 | |
|     /* Use transformed info to evaluate flags for storage engine. */
 | |
|     uint new_field_index= 0, new_field_stored_index= 0;
 | |
|     new_field_it.init(alter_info->create_list);
 | |
|     while ((new_field= new_field_it++))
 | |
|     {
 | |
|       if (new_field->field == field)
 | |
|         break;
 | |
|       new_field_index++;
 | |
|       new_field_stored_index+= new_field->stored_in_db();
 | |
|     }
 | |
| 
 | |
|     if (new_field)
 | |
|     {
 | |
|       /* Field is not dropped. Evaluate changes bitmap for it. */
 | |
| 
 | |
|       /*
 | |
|         Check if type of column has changed.
 | |
|       */
 | |
|       bool is_equal= field->is_equal(*new_field);
 | |
| 
 | |
|       if (is_equal)
 | |
|       {
 | |
|         const Type_handler *th= field->type_handler();
 | |
|         if (th != th->type_handler_for_implicit_upgrade())
 | |
|         {
 | |
|           /*
 | |
|             The field data type says it wants upgrade.
 | |
|             This should not be possible:
 | |
|             - if this is a new column definition, e.g. from statements like:
 | |
|                 ALTER TABLE t1 ADD a INT;
 | |
|                 ALTER TABLE t1 MODIFY a INT;
 | |
|               then it's coming from the parser, which returns
 | |
|               only up-to-date data types.
 | |
|             - if this is an old column definition, e.g. from:
 | |
|                 ALTER TABLE t1 COMMENT 'new comment';
 | |
|               it should have ealier called Column_definition_implicit_upgrade(),
 | |
|               which replaces old data types to up-to-date data types.
 | |
|           */
 | |
|           DBUG_ASSERT(0);
 | |
|           is_equal= false;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (!is_equal)
 | |
|       {
 | |
|         if (field->table->file->can_convert_nocopy(*field, *new_field))
 | |
|         {
 | |
|           /*
 | |
|             New column type differs from the old one, but storage engine can
 | |
|             change it by itself.
 | |
|             (for example, VARCHAR(300) is changed to VARCHAR(400)).
 | |
|           */
 | |
|           ha_alter_info->handler_flags|= ALTER_COLUMN_TYPE_CHANGE_BY_ENGINE;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           /* New column type is incompatible with old one. */
 | |
|           ha_alter_info->handler_flags|= field->stored_in_db()
 | |
|                                              ? ALTER_STORED_COLUMN_TYPE
 | |
|                                              : ALTER_VIRTUAL_COLUMN_TYPE;
 | |
| 
 | |
|           if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|           {
 | |
|             if (alter_info->drop_stat_fields.push_back(field, thd->mem_root))
 | |
|               DBUG_RETURN(true);
 | |
| 
 | |
|             KEY *key_info= table->key_info;
 | |
|             for (uint i= 0; i < table->s->total_keys; i++, key_info++)
 | |
|             {
 | |
|               if (!field->part_of_key.is_set(i))
 | |
|                 continue;
 | |
| 
 | |
|               uint key_parts= table->actual_n_key_parts(key_info);
 | |
|               for (uint j= 0; j < key_parts; j++)
 | |
|               {
 | |
|                 if (key_info->key_part[j].fieldnr - 1 == field->field_index)
 | |
|                 {
 | |
|                   if (alter_info->add_stat_drop_index(key_info,
 | |
|                                                       j >= key_info->user_defined_key_parts,
 | |
|                                                       thd->mem_root))
 | |
|                     DBUG_RETURN(true);
 | |
|                   break;
 | |
|                 }
 | |
|               }
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (field->vcol_info || new_field->vcol_info)
 | |
|       {
 | |
|         /* base <-> virtual or stored <-> virtual */
 | |
|         if (field->stored_in_db() != new_field->stored_in_db())
 | |
|           ha_alter_info->handler_flags|= ( ALTER_STORED_COLUMN_TYPE |
 | |
|                                            ALTER_VIRTUAL_COLUMN_TYPE);
 | |
|         if (field->vcol_info && new_field->vcol_info)
 | |
|         {
 | |
|           bool value_changes= !is_equal;
 | |
|           alter_table_operations alter_expr;
 | |
|           if (field->stored_in_db())
 | |
|             alter_expr= ALTER_STORED_GCOL_EXPR;
 | |
|           else
 | |
|             alter_expr= ALTER_VIRTUAL_GCOL_EXPR;
 | |
|           if (!field->vcol_info->is_equal(new_field->vcol_info))
 | |
|           {
 | |
|             ha_alter_info->handler_flags|= alter_expr;
 | |
|             value_changes= true;
 | |
|           }
 | |
| 
 | |
|           if ((ha_alter_info->handler_flags & ALTER_COLUMN_DEFAULT)
 | |
|               && !(ha_alter_info->handler_flags & alter_expr))
 | |
|           { /*
 | |
|               a DEFAULT value of a some column was changed.  see if this vcol
 | |
|               uses DEFAULT() function. The check is kind of expensive, so don't
 | |
|               do it if ALTER_COLUMN_VCOL is already set.
 | |
|             */
 | |
|             if (field->vcol_info->expr->walk(
 | |
|                                  &Item::check_func_default_processor, 0, 0))
 | |
|             {
 | |
|               ha_alter_info->handler_flags|= alter_expr;
 | |
|               value_changes= true;
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           if (field->vcol_info->is_in_partitioning_expr() ||
 | |
|               field->flags & PART_KEY_FLAG || field->stored_in_db())
 | |
|           {
 | |
|             if (value_changes)
 | |
|               ha_alter_info->handler_flags|= ALTER_COLUMN_VCOL;
 | |
|             else
 | |
|               maybe_alter_vcol= true;
 | |
|           }
 | |
|         }
 | |
|         else /* base <-> stored */
 | |
|           ha_alter_info->handler_flags|= ALTER_STORED_COLUMN_TYPE;
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|         Check if field was renamed (case-sensitive for detecting case change)
 | |
|       */
 | |
|       if (cmp(&field->field_name, &new_field->field_name))
 | |
|       {
 | |
|         field->flags|= FIELD_IS_RENAMED;
 | |
|         ha_alter_info->handler_flags|= ALTER_COLUMN_NAME;
 | |
|         if (alter_info->add_stat_rename_field(field,
 | |
|                                               &new_field->field_name,
 | |
|                                               thd->mem_root))
 | |
|           DBUG_RETURN(true);
 | |
|       }
 | |
| 
 | |
|       /* Check that NULL behavior is same for old and new fields */
 | |
|       if ((new_field->flags & NOT_NULL_FLAG) !=
 | |
|           (uint) (field->flags & NOT_NULL_FLAG))
 | |
|       {
 | |
|         if (new_field->flags & NOT_NULL_FLAG)
 | |
|           ha_alter_info->handler_flags|= ALTER_COLUMN_NOT_NULLABLE;
 | |
|         else
 | |
|           ha_alter_info->handler_flags|= ALTER_COLUMN_NULLABLE;
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|         We do not detect changes to default values in this loop.
 | |
|         See comment above for more details.
 | |
|       */
 | |
| 
 | |
|       /*
 | |
|         Detect changes in column order.
 | |
|       */
 | |
|       if (field->stored_in_db())
 | |
|       {
 | |
|         if (field_stored_index != new_field_stored_index)
 | |
|           ha_alter_info->handler_flags|= ALTER_STORED_COLUMN_ORDER;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         if (field->field_index != new_field_index)
 | |
|           ha_alter_info->handler_flags|= ALTER_VIRTUAL_COLUMN_ORDER;
 | |
|       }
 | |
| 
 | |
|       /* Detect changes in storage type of column */
 | |
|       if (new_field->field_storage_type() != field->field_storage_type())
 | |
|         ha_alter_info->handler_flags|= ALTER_COLUMN_STORAGE_TYPE;
 | |
| 
 | |
|       /* Detect changes in column format of column */
 | |
|       if (new_field->column_format() != field->column_format())
 | |
|         ha_alter_info->handler_flags|= ALTER_COLUMN_COLUMN_FORMAT;
 | |
| 
 | |
|       if (engine_options_differ(field->option_struct, new_field->option_struct,
 | |
|                                 table->file->ht->field_options))
 | |
|       {
 | |
|         ha_alter_info->handler_flags|= ALTER_COLUMN_OPTION;
 | |
|         ha_alter_info->create_info->fields_option_struct[f_ptr - table->field]=
 | |
|           new_field->option_struct;
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       // Field is not present in new version of table and therefore was dropped.
 | |
|       field->flags|= FIELD_IS_DROPPED;
 | |
|       if (field->stored_in_db())
 | |
|         ha_alter_info->handler_flags|= ALTER_DROP_STORED_COLUMN;
 | |
|       else
 | |
|         ha_alter_info->handler_flags|= ALTER_DROP_VIRTUAL_COLUMN;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (maybe_alter_vcol)
 | |
|   {
 | |
|     /*
 | |
|       What if one of the normal columns was altered and it was part of the some
 | |
|       virtual column expression?  Currently we don't detect this correctly
 | |
|       (FIXME), so let's just say that a vcol *might* be affected if any other
 | |
|       column was altered.
 | |
|     */
 | |
|     if (ha_alter_info->handler_flags & (ALTER_STORED_COLUMN_TYPE |
 | |
|                                         ALTER_VIRTUAL_COLUMN_TYPE |
 | |
|                                         ALTER_COLUMN_NOT_NULLABLE |
 | |
|                                         ALTER_COLUMN_OPTION))
 | |
|       ha_alter_info->handler_flags|= ALTER_COLUMN_VCOL;
 | |
|   }
 | |
| 
 | |
|   new_field_it.init(alter_info->create_list);
 | |
|   while ((new_field= new_field_it++))
 | |
|   {
 | |
|     if (! new_field->field)
 | |
|     {
 | |
|       // Field is not present in old version of table and therefore was added.
 | |
|       if (new_field->vcol_info)
 | |
|       {
 | |
|         if (new_field->stored_in_db())
 | |
|           ha_alter_info->handler_flags|= ALTER_ADD_STORED_GENERATED_COLUMN;
 | |
|         else
 | |
|           ha_alter_info->handler_flags|= ALTER_ADD_VIRTUAL_COLUMN;
 | |
|       }
 | |
|       else
 | |
|         ha_alter_info->handler_flags|= ALTER_ADD_STORED_BASE_COLUMN;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Go through keys and check if the original ones are compatible
 | |
|     with new table.
 | |
|   */
 | |
|   KEY *table_key;
 | |
|   KEY *table_key_end= table->key_info + table->s->total_keys;
 | |
|   KEY *new_key;
 | |
|   KEY *new_key_end=
 | |
|     ha_alter_info->key_info_buffer + ha_alter_info->key_count;
 | |
|   /*
 | |
|     Primary key index for the new table
 | |
|   */
 | |
|   const KEY* const new_pk= (ha_alter_info->key_count > 0 &&
 | |
|                             (ha_alter_info->key_info_buffer->name.
 | |
|                                streq(primary_key_name) ||
 | |
|                             is_candidate_key(ha_alter_info->key_info_buffer))) ?
 | |
|                            ha_alter_info->key_info_buffer : NULL;
 | |
|   const KEY *const old_pk= table->s->primary_key == MAX_KEY ? NULL :
 | |
|                            table->key_info + table->s->primary_key;
 | |
| 
 | |
|   DBUG_PRINT("info", ("index count old: %d  total: %d  new: %d",
 | |
|                       table->s->keys, table->s->total_keys,
 | |
|                       ha_alter_info->key_count));
 | |
| 
 | |
|   /*
 | |
|     Step through all keys of the old table and search matching new keys.
 | |
|   */
 | |
|   ha_alter_info->index_drop_count= 0;
 | |
|   ha_alter_info->index_add_count= 0;
 | |
|   for (table_key= table->key_info; table_key < table_key_end; table_key++)
 | |
|   {
 | |
|     /* Search a new key with the same name. */
 | |
|     for (new_key= ha_alter_info->key_info_buffer;
 | |
|          new_key < new_key_end;
 | |
|          new_key++)
 | |
|     {
 | |
|       if (table_key->name.streq(new_key->name))
 | |
|         break;
 | |
|     }
 | |
|     if (new_key >= new_key_end)
 | |
|     {
 | |
|       /* Key not found. Add the key to the drop buffer. */
 | |
|       ha_alter_info->index_drop_buffer
 | |
|         [ha_alter_info->index_drop_count++]=
 | |
|         table_key;
 | |
|       DBUG_PRINT("info", ("index dropped: '%s'", table_key->name.str));
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     switch (compare_keys_but_name(table_key, new_key, alter_info, table, new_pk,
 | |
|                                   old_pk))
 | |
|     {
 | |
|     case Compare_keys::Equal:
 | |
|       continue;
 | |
|     case Compare_keys::EqualButKeyPartLength:
 | |
|       ha_alter_info->handler_flags|= ALTER_COLUMN_INDEX_LENGTH;
 | |
|       continue;
 | |
|     case Compare_keys::EqualButComment:
 | |
|       ha_alter_info->handler_flags|= ALTER_CHANGE_INDEX_COMMENT;
 | |
|       continue;
 | |
|     case Compare_keys::NotEqual:
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     /* Key modified. Add the key / key offset to both buffers. */
 | |
|     ha_alter_info->index_drop_buffer
 | |
|       [ha_alter_info->index_drop_count++]=
 | |
|       table_key;
 | |
|     ha_alter_info->index_add_buffer
 | |
|       [ha_alter_info->index_add_count++]=
 | |
|       (uint)(new_key - ha_alter_info->key_info_buffer);
 | |
|     /* Mark all old fields which are used in newly created index. */
 | |
|     DBUG_PRINT("info", ("index changed: '%s'", table_key->name.str));
 | |
|   }
 | |
|   /*end of for (; table_key < table_key_end;) */
 | |
| 
 | |
|   /*
 | |
|     Step through all keys of the new table and find matching old keys.
 | |
|   */
 | |
|   for (new_key= ha_alter_info->key_info_buffer;
 | |
|        new_key < new_key_end;
 | |
|        new_key++)
 | |
|   {
 | |
|     /* Search an old key with the same name. */
 | |
|     for (table_key= table->key_info; table_key < table_key_end; table_key++)
 | |
|     {
 | |
|       if (table_key->name.streq(new_key->name))
 | |
|         break;
 | |
|     }
 | |
|     if (table_key >= table_key_end)
 | |
|     {
 | |
|       /* Key not found. Add the offset of the key to the add buffer. */
 | |
|       ha_alter_info->index_add_buffer
 | |
|         [ha_alter_info->index_add_count++]=
 | |
|         (uint)(new_key - ha_alter_info->key_info_buffer);
 | |
|       DBUG_PRINT("info", ("index added: '%s'", new_key->name.str));
 | |
|     }
 | |
|     else
 | |
|       ha_alter_info->create_info->indexes_option_struct[table_key - table->key_info]=
 | |
|         new_key->option_struct;
 | |
|   }
 | |
| 
 | |
|   for (uint i= 0; i < ha_alter_info->index_add_count; i++)
 | |
|   {
 | |
|     uint *add_buffer= ha_alter_info->index_add_buffer;
 | |
|     const KEY *new_key= ha_alter_info->key_info_buffer + add_buffer[i];
 | |
| 
 | |
|     for (uint j= 0; j < ha_alter_info->index_drop_count; j++)
 | |
|     {
 | |
|       KEY **drop_buffer= ha_alter_info->index_drop_buffer;
 | |
|       const KEY *old_key= drop_buffer[j];
 | |
| 
 | |
|       if (compare_keys_but_name(old_key, new_key, alter_info, table, new_pk,
 | |
|                                 old_pk) != Compare_keys::Equal)
 | |
|       {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       DBUG_ASSERT(!old_key->name.streq(new_key->name));
 | |
| 
 | |
|       ha_alter_info->handler_flags|= ALTER_RENAME_INDEX;
 | |
|       ha_alter_info->rename_keys.push_back(
 | |
|           Alter_inplace_info::Rename_key_pair(old_key, new_key));
 | |
|       /* Renaming high-level index is algorithm=copy operation. */
 | |
|       if (old_key->algorithm == HA_KEY_ALG_VECTOR)
 | |
|         ha_alter_info->inplace_supported= HA_ALTER_INPLACE_NOT_SUPPORTED;
 | |
| 
 | |
|       --ha_alter_info->index_add_count;
 | |
|       --ha_alter_info->index_drop_count;
 | |
|       memmove(add_buffer + i, add_buffer + i + 1,
 | |
|               sizeof(add_buffer[0]) * (ha_alter_info->index_add_count - i));
 | |
|       memmove(drop_buffer + j, drop_buffer + j + 1,
 | |
|               sizeof(drop_buffer[0]) * (ha_alter_info->index_drop_count - j));
 | |
|       --i; // this index once again
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   List_iterator<Alter_index_ignorability>
 | |
|        ignorability_index_it(alter_info->alter_index_ignorability_list);
 | |
|   Alter_index_ignorability *alter_index_ignorability;
 | |
|   while((alter_index_ignorability= ignorability_index_it++))
 | |
|   {
 | |
|     const Lex_ident_column &name= alter_index_ignorability->name();
 | |
| 
 | |
|     KEY *old_key, *new_key;
 | |
|     old_key= find_key_ci(name, table->key_info, table_key_end);
 | |
|     new_key= find_key_ci(name, ha_alter_info->key_info_buffer, new_key_end);
 | |
| 
 | |
|     DBUG_ASSERT(old_key != NULL);
 | |
| 
 | |
|     if (new_key == NULL)
 | |
|     {
 | |
|       my_error(ER_KEY_DOES_NOT_EXISTS, MYF(0),
 | |
|                name.str, table->s->table_name.str);
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|     new_key->is_ignored= alter_index_ignorability->is_ignored();
 | |
|     ha_alter_info->handler_flags|= ALTER_RENAME_INDEX;
 | |
|     ha_alter_info->add_altered_index_ignorability(old_key, new_key);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Sort index_add_buffer according to how key_info_buffer is sorted.
 | |
|     I.e. with primary keys first - see sort_keys().
 | |
|   */
 | |
|   my_qsort(ha_alter_info->index_add_buffer,
 | |
|            ha_alter_info->index_add_count,
 | |
|            sizeof(uint), (qsort_cmp) compare_uint);
 | |
| 
 | |
|   /* Now let us calculate flags for storage engine API. */
 | |
| 
 | |
|   /* Figure out what kind of indexes we are dropping. */
 | |
|   KEY **dropped_key;
 | |
|   KEY **dropped_key_end= ha_alter_info->index_drop_buffer +
 | |
|                          ha_alter_info->index_drop_count;
 | |
| 
 | |
|   for (dropped_key= ha_alter_info->index_drop_buffer;
 | |
|        dropped_key < dropped_key_end; dropped_key++)
 | |
|   {
 | |
|     table_key= *dropped_key;
 | |
| 
 | |
|     if (table_key->flags & HA_NOSAME)
 | |
|     {
 | |
|       if (table_key == old_pk)
 | |
|         ha_alter_info->handler_flags|= ALTER_DROP_PK_INDEX;
 | |
|       else
 | |
|         ha_alter_info->handler_flags|= ALTER_DROP_UNIQUE_INDEX;
 | |
|     }
 | |
|     else
 | |
|       ha_alter_info->handler_flags|= ALTER_DROP_NON_UNIQUE_NON_PRIM_INDEX;
 | |
|   }
 | |
| 
 | |
|   /* Now figure out what kind of indexes we are adding. */
 | |
|   for (uint add_key_idx= 0; add_key_idx < ha_alter_info->index_add_count; add_key_idx++)
 | |
|   {
 | |
|     new_key= ha_alter_info->key_info_buffer + ha_alter_info->index_add_buffer[add_key_idx];
 | |
| 
 | |
|     if (new_key->flags & HA_NOSAME)
 | |
|     {
 | |
|       if (new_key == new_pk)
 | |
|         ha_alter_info->handler_flags|= ALTER_ADD_PK_INDEX;
 | |
|       else
 | |
|         ha_alter_info->handler_flags|= ALTER_ADD_UNIQUE_INDEX;
 | |
|     }
 | |
|     else
 | |
|       ha_alter_info->handler_flags|= ALTER_ADD_NON_UNIQUE_NON_PRIM_INDEX;
 | |
|     /* Adding high-level index is algorithm=copy operation. */
 | |
|     if (new_key->algorithm == HA_KEY_ALG_VECTOR)
 | |
|       ha_alter_info->inplace_supported= HA_ALTER_INPLACE_NOT_SUPPORTED;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Adding/dropping any indexes in a table that already has high-level indexes
 | |
|     may shift high-level indexes numbers. And thus require high-level indexes
 | |
|     rename, which algorithm=inplace (storage engines) shouldn't do.
 | |
| 
 | |
|     If we aren't adding/dropping indexes, ha_alter_info->key_count is
 | |
|     table->s->total_keys, but must be table->s->keys to not confuse the engine.
 | |
|   */
 | |
|   if (table->s->hlindexes())
 | |
|   {
 | |
|     if (ha_alter_info->index_drop_count || ha_alter_info->index_add_count)
 | |
|       ha_alter_info->inplace_supported= HA_ALTER_INPLACE_NOT_SUPPORTED;
 | |
|     else
 | |
|       ha_alter_info->key_count= table->s->keys;
 | |
|   }
 | |
| 
 | |
|   DBUG_PRINT("exit", ("handler_flags: %llu", ha_alter_info->handler_flags));
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Mark fields participating in newly added indexes in TABLE object which
 | |
|   corresponds to new version of altered table.
 | |
| 
 | |
|   @param ha_alter_info  Alter_inplace_info describing in-place ALTER.
 | |
|   @param altered_table  TABLE object for new version of TABLE in which
 | |
|                         fields should be marked.
 | |
| */
 | |
| 
 | |
| static void update_altered_table(const Alter_inplace_info &ha_alter_info,
 | |
|                                  TABLE *altered_table)
 | |
| {
 | |
|   uint field_idx, add_key_idx;
 | |
|   KEY *key;
 | |
|   KEY_PART_INFO *end, *key_part;
 | |
| 
 | |
|   /*
 | |
|     Clear marker for all fields, as we are going to set it only
 | |
|     for fields which participate in new indexes.
 | |
|   */
 | |
|   for (field_idx= 0; field_idx < altered_table->s->fields; ++field_idx)
 | |
|     altered_table->field[field_idx]->flags&= ~FIELD_IN_ADD_INDEX;
 | |
| 
 | |
|   /*
 | |
|     Go through array of newly added indexes and mark fields
 | |
|     participating in them.
 | |
|   */
 | |
|   for (add_key_idx= 0; add_key_idx < ha_alter_info.index_add_count;
 | |
|        add_key_idx++)
 | |
|   {
 | |
|     key= ha_alter_info.key_info_buffer +
 | |
|          ha_alter_info.index_add_buffer[add_key_idx];
 | |
| 
 | |
|     end= key->key_part + key->user_defined_key_parts;
 | |
|     for (key_part= key->key_part; key_part < end; key_part++)
 | |
|       altered_table->field[key_part->fieldnr]->flags|= FIELD_IN_ADD_INDEX;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Compare two tables to see if their metadata are compatible.
 | |
|   One table specified by a TABLE instance, the other using Alter_info
 | |
|   and HA_CREATE_INFO.
 | |
| 
 | |
|   @param[in]  table          The first table.
 | |
|   @param[in]  alter_info     Alter options, fields and keys for the
 | |
|                              second table.
 | |
|   @param[in]  create_info    Create options for the second table.
 | |
|   @param[out] metadata_equal Result of comparison.
 | |
| 
 | |
|   @retval true   error
 | |
|   @retval false  success
 | |
| */
 | |
| 
 | |
| bool mysql_compare_tables(TABLE *table, Alter_info *alter_info,
 | |
|                           HA_CREATE_INFO *create_info, bool *metadata_equal)
 | |
| {
 | |
|   DBUG_ENTER("mysql_compare_tables");
 | |
| 
 | |
|   uint changes= IS_EQUAL_NO;
 | |
|   uint key_count;
 | |
|   List_iterator_fast<Create_field> tmp_new_field_it;
 | |
|   THD *thd= table->in_use;
 | |
|   *metadata_equal= false;
 | |
| 
 | |
|   /*
 | |
|     Create a copy of alter_info.
 | |
|     To compare definitions, we need to "prepare" the definition - transform it
 | |
|     from parser output to a format that describes the table layout (all column
 | |
|     defaults are initialized, duplicate columns are removed). This is done by
 | |
|     mysql_prepare_create_table.  Unfortunately, mysql_prepare_create_table
 | |
|     performs its transformations "in-place", that is, modifies the argument.
 | |
|     Since we would like to keep mysql_compare_tables() idempotent (not altering
 | |
|     any of the arguments) we create a copy of alter_info here and pass it to
 | |
|     mysql_prepare_create_table, then use the result to compare the tables, and
 | |
|     then destroy the copy.
 | |
|   */
 | |
|   Alter_info tmp_alter_info(*alter_info, thd->mem_root);
 | |
|   uint db_options= 0; /* not used */
 | |
|   KEY *key_info_buffer= NULL;
 | |
| 
 | |
|   /* Create the prepared information. */
 | |
|   int create_table_mode= table->s->tmp_table == NO_TMP_TABLE ?
 | |
|                            C_ORDINARY_CREATE : C_ALTER_TABLE;
 | |
|   if (mysql_prepare_create_table(thd, create_info, &tmp_alter_info,
 | |
|                                  &db_options, table->file, &key_info_buffer,
 | |
|                                  &key_count, create_table_mode))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   /* Some very basic checks. */
 | |
|   uint fields= table->s->fields;
 | |
| 
 | |
|   /* There is no field count on fully-invisible fields, count them. */
 | |
|   for (Field **f_ptr= table->field; *f_ptr; f_ptr++)
 | |
|   {
 | |
|     if ((*f_ptr)->invisible >= INVISIBLE_FULL)
 | |
|       fields--;
 | |
|   }
 | |
| 
 | |
|   if (fields != alter_info->create_list.elements ||
 | |
|       table->s->db_type() != create_info->db_type ||
 | |
|       table->s->tmp_table ||
 | |
|       (table->s->row_type != create_info->row_type))
 | |
|     DBUG_RETURN(false);
 | |
| 
 | |
|   /* Go through fields and check if they are compatible. */
 | |
|   tmp_new_field_it.init(tmp_alter_info.create_list);
 | |
|   for (Field **f_ptr= table->field; *f_ptr; f_ptr++)
 | |
|   {
 | |
|     Field *field= *f_ptr;
 | |
|     /* Skip hidden generated field like long hash index. */
 | |
|     if (field->invisible >= INVISIBLE_SYSTEM)
 | |
|       continue;
 | |
|     Create_field *tmp_new_field= tmp_new_field_it++;
 | |
| 
 | |
|     /* Check that NULL behavior is the same. */
 | |
|     if ((tmp_new_field->flags & NOT_NULL_FLAG) !=
 | |
| 	(uint) (field->flags & NOT_NULL_FLAG))
 | |
|       DBUG_RETURN(false);
 | |
| 
 | |
|     if (field->vcol_info)
 | |
|     {
 | |
|       if (!tmp_new_field->field->vcol_info)
 | |
|         DBUG_RETURN(false);
 | |
|       bool err;
 | |
|       if (!field->vcol_info->is_equivalent(thd, table->s, create_info->table->s,
 | |
|                                            tmp_new_field->field->vcol_info, err))
 | |
|         DBUG_RETURN(false);
 | |
|       if (err)
 | |
|         DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       mysql_prepare_alter_table() clears HA_OPTION_PACK_RECORD bit when
 | |
|       preparing description of existing table. In ALTER TABLE it is later
 | |
|       updated to correct value by create_table_impl() call.
 | |
|       So to get correct value of this bit in this function we have to
 | |
|       mimic behavior of create_table_impl().
 | |
|     */
 | |
|     if (create_info->row_type == ROW_TYPE_DYNAMIC ||
 | |
|         create_info->row_type == ROW_TYPE_PAGE ||
 | |
| 	(tmp_new_field->flags & BLOB_FLAG) ||
 | |
| 	(tmp_new_field->real_field_type() == MYSQL_TYPE_VARCHAR &&
 | |
| 	create_info->row_type != ROW_TYPE_FIXED))
 | |
|       create_info->table_options|= HA_OPTION_PACK_RECORD;
 | |
| 
 | |
|     /* Check if field was renamed */
 | |
|     if (!field->field_name.streq(tmp_new_field->field_name))
 | |
|       DBUG_RETURN(false);
 | |
| 
 | |
|     /* Evaluate changes bitmap and send to check_if_incompatible_data() */
 | |
|     uint field_changes= field->is_equal(*tmp_new_field);
 | |
|     if (field_changes != IS_EQUAL_YES)
 | |
|       DBUG_RETURN(false);
 | |
| 
 | |
|     changes|= field_changes;
 | |
|   }
 | |
| 
 | |
|   /* Check if changes are compatible with current handler. */
 | |
|   if (table->file->check_if_incompatible_data(create_info, changes))
 | |
|     DBUG_RETURN(false);
 | |
| 
 | |
|   /* Go through keys and check if they are compatible. */
 | |
|   KEY *table_key= table->s->key_info;
 | |
|   KEY *table_key_end= table_key + table->s->keys;
 | |
|   KEY *new_key;
 | |
|   KEY *new_key_end= key_info_buffer + key_count;
 | |
| 
 | |
|   /* Step through all keys of the first table and search matching keys. */
 | |
|   for (; table_key < table_key_end; table_key++)
 | |
|   {
 | |
|     /* Search a key with the same name. */
 | |
|     for (new_key= key_info_buffer; new_key < new_key_end; new_key++)
 | |
|     {
 | |
|       if (table_key->name.streq(new_key->name))
 | |
|         break;
 | |
|     }
 | |
|     if (new_key >= new_key_end)
 | |
|       DBUG_RETURN(false);
 | |
| 
 | |
|     /* Check that the key types are compatible. */
 | |
|     if ((table_key->algorithm != new_key->algorithm) ||
 | |
| 	((table_key->flags & HA_KEYFLAG_MASK) !=
 | |
|          (new_key->flags & HA_KEYFLAG_MASK)) ||
 | |
|         (table_key->user_defined_key_parts !=
 | |
|          new_key->user_defined_key_parts))
 | |
|       DBUG_RETURN(false);
 | |
| 
 | |
|     /* Check that the key parts remain compatible. */
 | |
|     KEY_PART_INFO *table_part;
 | |
|     KEY_PART_INFO *table_part_end= table_key->key_part + table_key->user_defined_key_parts;
 | |
|     KEY_PART_INFO *new_part;
 | |
|     for (table_part= table_key->key_part, new_part= new_key->key_part;
 | |
|          table_part < table_part_end;
 | |
|          table_part++, new_part++)
 | |
|     {
 | |
|       /*
 | |
| 	Key definition is different if we are using a different field or
 | |
| 	if the used key part length is different. We know that the fields
 | |
|         are equal. Comparing field numbers is sufficient.
 | |
|       */
 | |
|       if ((table_part->length != new_part->length) ||
 | |
|           (table_part->fieldnr - 1 != new_part->fieldnr) ||
 | |
|           ((table_part->key_part_flag ^ new_part->key_part_flag) & HA_REVERSE_SORT))
 | |
|         DBUG_RETURN(false);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Step through all keys of the second table and find matching keys. */
 | |
|   for (new_key= key_info_buffer; new_key < new_key_end; new_key++)
 | |
|   {
 | |
|     /* Search a key with the same name. */
 | |
|     for (table_key= table->s->key_info; table_key < table_key_end; table_key++)
 | |
|     {
 | |
|       if (table_key->name.streq(new_key->name))
 | |
|         break;
 | |
|     }
 | |
|     if (table_key >= table_key_end)
 | |
|       DBUG_RETURN(false);
 | |
|   }
 | |
| 
 | |
|   *metadata_equal= true; // Tables are compatible
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Manages enabling/disabling of indexes for ALTER TABLE
 | |
| 
 | |
|   SYNOPSIS
 | |
|     alter_table_manage_keys()
 | |
|       table                  Target table
 | |
|       indexes_were_disabled  Whether the indexes of the from table
 | |
|                              were disabled
 | |
|       keys_onoff             ENABLE | DISABLE | LEAVE_AS_IS
 | |
| 
 | |
|   RETURN VALUES
 | |
|     FALSE  OK
 | |
|     TRUE   Error
 | |
| */
 | |
| 
 | |
| static
 | |
| bool alter_table_manage_keys(TABLE *table, int indexes_were_disabled,
 | |
|                              Alter_info::enum_enable_or_disable keys_onoff)
 | |
| {
 | |
|   int error= 0;
 | |
|   DBUG_ENTER("alter_table_manage_keys");
 | |
|   DBUG_PRINT("enter", ("table=%p were_disabled=%d on_off=%d",
 | |
|              table, indexes_were_disabled, keys_onoff));
 | |
| 
 | |
|   switch (keys_onoff) {
 | |
|   case Alter_info::ENABLE:
 | |
|     DEBUG_SYNC(table->in_use, "alter_table_enable_indexes");
 | |
|     error= table->file->ha_enable_indexes(key_map(table->s->keys), true);
 | |
|     break;
 | |
|   case Alter_info::LEAVE_AS_IS:
 | |
|     if (!indexes_were_disabled)
 | |
|       break;
 | |
|     /* fall through */
 | |
|   case Alter_info::DISABLE:
 | |
|   {
 | |
|     key_map map= table->s->keys_in_use;
 | |
|     bool do_clear= false;
 | |
|     for (uint i=0; i < table->s->keys; i++)
 | |
|     {
 | |
|       if (!(table->s->key_info[i].flags & HA_NOSAME) &&
 | |
|           i != table->s->next_number_index)
 | |
|       {
 | |
|         map.clear_bit(i);
 | |
|         do_clear= true;
 | |
|       }
 | |
|     }
 | |
|     if (do_clear)
 | |
|       error= table->file->ha_disable_indexes(map, true);
 | |
|   }
 | |
|   }
 | |
| 
 | |
|   if (unlikely(error))
 | |
|   {
 | |
|     if (error == HA_ERR_WRONG_COMMAND)
 | |
|     {
 | |
|       THD *thd= table->in_use;
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                           ER_ILLEGAL_HA, ER_THD(thd, ER_ILLEGAL_HA),
 | |
|                           table->file->table_type(),
 | |
|                           table->s->db.str, table->s->table_name.str);
 | |
|       error= 0;
 | |
|     }
 | |
|     else
 | |
|       table->file->print_error(error, MYF(0));
 | |
|   }
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check if the pending ALTER TABLE operations support the in-place
 | |
|   algorithm based on restrictions in the SQL layer or given the
 | |
|   nature of the operations themselves. If in-place isn't supported,
 | |
|   it won't be necessary to check with the storage engine.
 | |
| 
 | |
|   @param table        The original TABLE.
 | |
|   @param create_info  Information from the parsing phase about new
 | |
|                       table properties.
 | |
|   @param alter_info   Data related to detected changes.
 | |
| 
 | |
|   @return false       In-place is possible, check with storage engine.
 | |
|   @return true        Incompatible operations, must use table copy.
 | |
| */
 | |
| 
 | |
| static bool is_inplace_alter_impossible(TABLE *table,
 | |
|                                         HA_CREATE_INFO *create_info,
 | |
|                                         const Alter_info *alter_info)
 | |
| {
 | |
|   DBUG_ENTER("is_inplace_alter_impossible");
 | |
| 
 | |
|   /* At the moment we can't handle altering temporary tables without a copy. */
 | |
|   if (table->s->tmp_table)
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     For the ALTER TABLE tbl_name ORDER BY ... we always use copy
 | |
|     algorithm. In theory, this operation can be done in-place by some
 | |
|     engine, but since a) no current engine does this and b) our current
 | |
|     API lacks infrastructure for passing information about table ordering
 | |
|     to storage engine we simply always do copy now.
 | |
| 
 | |
|     ENABLE/DISABLE KEYS is a MyISAM/Heap specific operation that is
 | |
|     not supported for in-place in combination with other operations.
 | |
|     Alone, it will be done by simple_rename_or_index_change().
 | |
|   */
 | |
|   if (alter_info->flags & (ALTER_ORDER | ALTER_KEYS_ONOFF))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     If the table engine is changed explicitly (using ENGINE clause)
 | |
|     or implicitly (e.g. when non-partitioned table becomes
 | |
|     partitioned) a regular alter table (copy) needs to be
 | |
|     performed.
 | |
|   */
 | |
|   if (create_info->db_type != table->s->db_type())
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     There was a bug prior to mysql-4.0.25. Number of null fields was
 | |
|     calculated incorrectly. As a result frm and data files gets out of
 | |
|     sync after fast alter table. There is no way to determine by which
 | |
|     mysql version (in 4.0 and 4.1 branches) table was created, thus we
 | |
|     disable fast alter table for all tables created by mysql versions
 | |
|     prior to 5.0 branch.
 | |
|     See BUG#6236.
 | |
|   */
 | |
|   if (!table->s->mysql_version)
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     If we are using a MySQL 5.7 table with virtual fields, ALTER TABLE must
 | |
|     recreate the table as we need to rewrite generated fields
 | |
|   */
 | |
|   if (table->s->mysql_version > 50700 && table->s->mysql_version < 100000 &&
 | |
|       table->s->virtual_fields)
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Notify engine that table definition has changed as part of inplace alter
 | |
|   table
 | |
| */
 | |
| 
 | |
| static bool notify_tabledef_changed(TABLE_LIST *table_list)
 | |
| {
 | |
|   TABLE *table= table_list->table;
 | |
|   DBUG_ENTER("notify_tabledef_changed");
 | |
| 
 | |
|   if (table->file->partition_ht()->notify_tabledef_changed)
 | |
|   {
 | |
|     char db_buff[FN_REFLEN], table_buff[FN_REFLEN];
 | |
|     handlerton *hton= table->file->ht;
 | |
|     LEX_CSTRING tmp_db, tmp_table;
 | |
| 
 | |
|     tmp_db.str=       db_buff;
 | |
|     tmp_table.str=    table_buff;
 | |
|     tmp_db.length=    tablename_to_filename(table_list->db.str,
 | |
|                                             db_buff, sizeof(db_buff));
 | |
|     tmp_table.length= tablename_to_filename(table_list->table_name.str,
 | |
|                                             table_buff, sizeof(table_buff));
 | |
|     if ((hton->notify_tabledef_changed)(hton, &tmp_db, &tmp_table,
 | |
|                                         table->s->frm_image,
 | |
|                                         &table->s->tabledef_version,
 | |
|                                         table->file))
 | |
|     {
 | |
|       my_error(HA_ERR_INCOMPATIBLE_DEFINITION, MYF(0));
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|   }
 | |
|   DBUG_RETURN(0);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   The function is invoked in error branches of ALTER processing.
 | |
|   Write Rollback alter in case of partial_alter is true else
 | |
|   call process_master_state.
 | |
| 
 | |
|   @param thd                Thread handle.
 | |
|   @param[in/out]
 | |
|          start_alter_id     Start Alter identifier or zero,
 | |
|                             it is reset to zero.
 | |
|   @param[in/out]
 | |
|          partial_alter      When is true at the function enter
 | |
|                             that indicates Start Alter phase completed;
 | |
|                             it then is reset to false.
 | |
|   @param if_exists          True indicates the binary logging of the query
 | |
|                             should be done with "if exists" option.
 | |
| 
 | |
|   @return false             on Success
 | |
|   @return true              otherwise
 | |
| */
 | |
| static bool
 | |
| write_bin_log_start_alter_rollback(THD *thd, uint64 &start_alter_id,
 | |
|                                    bool &partial_alter, bool if_exists)
 | |
| {
 | |
| #if defined(HAVE_REPLICATION)
 | |
|   if (start_alter_id)
 | |
|   {
 | |
|     start_alter_info *info= thd->rgi_slave->sa_info;
 | |
|     Master_info *mi= thd->rgi_slave->rli->mi;
 | |
| 
 | |
|     if (info->sa_seq_no == 0)
 | |
|     {
 | |
|       /*
 | |
|          Error occurred before SA got to processing incl its binlogging.
 | |
|          So it's a failure to apply and thus no need to wait for master's
 | |
|          completion result.
 | |
|       */
 | |
|       return true;
 | |
|     }
 | |
|     mysql_mutex_lock(&mi->start_alter_lock);
 | |
|     if (info->direct_commit_alter)
 | |
|     {
 | |
|       DBUG_ASSERT(info->state == start_alter_state::ROLLBACK_ALTER);
 | |
| 
 | |
|       /*
 | |
|         SA may end up in the rollback state through FTWRL that breaks
 | |
|         SA's waiting for a master decision.
 | |
|         Then it completes "officially", and `direct_commit_alter` true status
 | |
|         will affect the future of CA to re-execute the whole query.
 | |
|       */
 | |
|       info->state= start_alter_state::COMPLETED;
 | |
|       if (info->direct_commit_alter)
 | |
|         mysql_cond_broadcast(&info->start_alter_cond);
 | |
|       mysql_mutex_unlock(&mi->start_alter_lock);
 | |
| 
 | |
|       return true; // not really an error to be handled by caller specifically
 | |
|     }
 | |
|     mysql_mutex_unlock(&mi->start_alter_lock);
 | |
|     /*
 | |
|       We have to call wait for master here because in main calculation
 | |
|       we can error out before calling wait for master
 | |
|       (for example if copy_data_between_tables fails)
 | |
|     */
 | |
|     if (info->state == start_alter_state::REGISTERED)
 | |
|       wait_for_master(thd);
 | |
|     if(process_master_state(thd, 1, start_alter_id, if_exists))
 | |
|       return true;
 | |
|   }
 | |
|   else
 | |
| #endif
 | |
|   if (partial_alter) // Write only if SA written
 | |
|   {
 | |
|     // Send the rollback message
 | |
|     Write_log_with_flags wlwf(thd, Gtid_log_event::FL_ROLLBACK_ALTER_E1);
 | |
|     if(write_bin_log_with_if_exists(thd, false, false, if_exists, false))
 | |
|       return true;
 | |
|     partial_alter= false;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Perform in-place alter table.
 | |
| 
 | |
|   @param thd                Thread handle.
 | |
|   @param table_list         TABLE_LIST for the table to change.
 | |
|   @param table              The original TABLE.
 | |
|   @param altered_table      TABLE object for new version of the table.
 | |
|   @param ha_alter_info      Structure describing ALTER TABLE to be carried
 | |
|                             out and serving as a storage place for data
 | |
|                             used during different phases.
 | |
|   @param target_mdl_request Metadata request/lock on the target table name.
 | |
|   @param alter_ctx          ALTER TABLE runtime context.
 | |
|   @param partial_alter      Is set to true to return the fact of the first
 | |
|                             "START ALTER" binlogging phase is done.
 | |
|   @param[in/out]
 | |
|          start_alter_id     Gtid seq_no of START ALTER or zero otherwise;
 | |
|                             it may get changed to return to the caller.
 | |
|   @param if_exists          True indicates the binary logging of the query
 | |
|                             should be done with "if exists" option.
 | |
| 
 | |
|   @retval   >=1               Error{ 1= ROLLBACK recieved from master , 2= error
 | |
|                                     in alter so no ROLLBACK in binlog }
 | |
|   @retval   0                 Success
 | |
| 
 | |
|   @note
 | |
|     If mysql_alter_table does not need to copy the table, it is
 | |
|     either an alter table where the storage engine does not
 | |
|     need to know about the change, only the frm will change,
 | |
|     or the storage engine supports performing the alter table
 | |
|     operation directly, in-place without mysql having to copy
 | |
|     the table.
 | |
| 
 | |
|   @note This function frees the TABLE object associated with the new version of
 | |
|         the table and removes the .FRM file for it in case of both success and
 | |
|         failure.
 | |
| */
 | |
| 
 | |
| static bool mysql_inplace_alter_table(THD *thd,
 | |
|                                       TABLE_LIST *table_list,
 | |
|                                       TABLE *table,
 | |
|                                       TABLE *altered_table,
 | |
|                                       Alter_inplace_info *ha_alter_info,
 | |
|                                       MDL_request *target_mdl_request,
 | |
|                                       DDL_LOG_STATE *ddl_log_state,
 | |
|                                       TRIGGER_RENAME_PARAM *trigger_param,
 | |
|                                       Alter_table_ctx *alter_ctx,
 | |
|                                       bool &partial_alter,
 | |
|                                       uint64 &start_alter_id, bool if_exists)
 | |
| {
 | |
|   Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN | MYSQL_OPEN_IGNORE_KILLED);
 | |
|   handlerton *db_type= table->s->db_type();
 | |
|   Alter_info *alter_info= ha_alter_info->alter_info;
 | |
|   bool reopen_tables= false;
 | |
|   bool res, commit_succeded_with_error= 0;
 | |
| 
 | |
|   const enum_alter_inplace_result inplace_supported=
 | |
|     ha_alter_info->inplace_supported;
 | |
|   DBUG_ENTER("mysql_inplace_alter_table");
 | |
| 
 | |
|   /* Downgrade DDL lock while we are waiting for exclusive lock below */
 | |
|   backup_set_alter_copy_lock(thd, table);
 | |
| 
 | |
|   /*
 | |
|     Upgrade to EXCLUSIVE lock if:
 | |
|     - This is requested by the storage engine
 | |
|     - Or the storage engine needs exclusive lock for just the prepare
 | |
|       phase
 | |
|     - Or requested by the user
 | |
| 
 | |
|     Note that we handle situation when storage engine needs exclusive
 | |
|     lock for prepare phase under LOCK TABLES in the same way as when
 | |
|     exclusive lock is required for duration of the whole statement.
 | |
|   */
 | |
|   if (inplace_supported == HA_ALTER_INPLACE_EXCLUSIVE_LOCK ||
 | |
|       ((inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK ||
 | |
|         inplace_supported == HA_ALTER_INPLACE_COPY_LOCK ||
 | |
|         inplace_supported == HA_ALTER_INPLACE_NOCOPY_NO_LOCK ||
 | |
|         inplace_supported == HA_ALTER_INPLACE_NOCOPY_LOCK ||
 | |
|         inplace_supported == HA_ALTER_INPLACE_INSTANT) &&
 | |
|        (thd->locked_tables_mode == LTM_LOCK_TABLES ||
 | |
|         thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)) ||
 | |
|       alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)
 | |
|   {
 | |
|     if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
 | |
|       goto cleanup;
 | |
|     /*
 | |
|       Get rid of all TABLE instances belonging to this thread
 | |
|       except one to be used for in-place ALTER TABLE.
 | |
| 
 | |
|       This is mostly needed to satisfy InnoDB assumptions/asserts.
 | |
|     */
 | |
|     close_all_tables_for_name(thd, table->s,
 | |
|                               alter_ctx->is_table_renamed() ?
 | |
|                               HA_EXTRA_PREPARE_FOR_RENAME :
 | |
| 			      HA_EXTRA_NOT_USED,
 | |
|                               table);
 | |
|     /*
 | |
|       If we are under LOCK TABLES we will need to reopen tables which we
 | |
|       just have closed in case of error.
 | |
|     */
 | |
|     reopen_tables= true;
 | |
|   }
 | |
|   else if (inplace_supported == HA_ALTER_INPLACE_COPY_LOCK ||
 | |
|            inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK ||
 | |
|            inplace_supported == HA_ALTER_INPLACE_NOCOPY_LOCK ||
 | |
|            inplace_supported == HA_ALTER_INPLACE_NOCOPY_NO_LOCK ||
 | |
|            inplace_supported == HA_ALTER_INPLACE_INSTANT)
 | |
|   {
 | |
|     /*
 | |
|       Storage engine has requested exclusive lock only for prepare phase
 | |
|       and we are not under LOCK TABLES.
 | |
|       Don't mark TABLE_SHARE as old in this case, as this won't allow opening
 | |
|       of table by other threads during main phase of in-place ALTER TABLE.
 | |
|     */
 | |
|     if (thd->mdl_context.upgrade_shared_lock(table->mdl_ticket, MDL_EXCLUSIVE,
 | |
|                                              thd->variables.lock_wait_timeout))
 | |
|       goto cleanup;
 | |
| 
 | |
|     table->s->tdc->flush(thd, false);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Upgrade to SHARED_NO_WRITE lock if:
 | |
|     - The storage engine needs writes blocked for the whole duration
 | |
|     - Or this is requested by the user
 | |
|     Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE.
 | |
|   */
 | |
|   if ((inplace_supported == HA_ALTER_INPLACE_SHARED_LOCK ||
 | |
|        alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED) &&
 | |
|       thd->mdl_context.upgrade_shared_lock(table->mdl_ticket,
 | |
|                                            MDL_SHARED_NO_WRITE,
 | |
|                                            thd->variables.lock_wait_timeout))
 | |
|     goto cleanup;
 | |
| 
 | |
|   DBUG_ASSERT(table->s->tmp_table == NO_TMP_TABLE ||  start_alter_id == 0);
 | |
| 
 | |
|   if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|   {
 | |
|     if (write_bin_log_start_alter(thd, partial_alter, start_alter_id,
 | |
|                                   if_exists))
 | |
|       goto cleanup;
 | |
|   }
 | |
|   else if (start_alter_id)
 | |
|   {
 | |
|     DBUG_ASSERT(thd->rgi_slave);
 | |
| 
 | |
|     my_error(ER_INCONSISTENT_SLAVE_TEMP_TABLE, MYF(0), thd->query(),
 | |
|              table_list->db.str, table_list->table_name.str);
 | |
|     goto cleanup;
 | |
|   }
 | |
| 
 | |
|   DBUG_EXECUTE_IF("start_alter_kill_after_binlog", {
 | |
|       DBUG_SUICIDE();
 | |
|       });
 | |
| 
 | |
| 
 | |
|   // It's now safe to take the table level lock.
 | |
|   if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0))
 | |
|     goto cleanup;
 | |
|   DEBUG_SYNC(thd, "alter_table_inplace_after_lock_upgrade");
 | |
|   THD_STAGE_INFO(thd, stage_alter_inplace_prepare);
 | |
| 
 | |
|   switch (inplace_supported) {
 | |
|   case HA_ALTER_ERROR:
 | |
|   case HA_ALTER_INPLACE_NOT_SUPPORTED:
 | |
|     DBUG_ASSERT(0);
 | |
|     // fall through
 | |
|   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:
 | |
|     switch (alter_info->requested_lock) {
 | |
|     case Alter_info::ALTER_TABLE_LOCK_DEFAULT:
 | |
|     case Alter_info::ALTER_TABLE_LOCK_NONE:
 | |
|       ha_alter_info->online= true;
 | |
|       break;
 | |
|     case Alter_info::ALTER_TABLE_LOCK_SHARED:
 | |
|     case Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE:
 | |
|       break;
 | |
|     }
 | |
|     break;
 | |
|   case HA_ALTER_INPLACE_EXCLUSIVE_LOCK:
 | |
|   case HA_ALTER_INPLACE_SHARED_LOCK:
 | |
|   case HA_ALTER_INPLACE_COPY_LOCK:
 | |
|   case HA_ALTER_INPLACE_NOCOPY_LOCK:
 | |
|     break;
 | |
|   }
 | |
| 
 | |
|   ddl_log_update_phase(ddl_log_state, DDL_ALTER_TABLE_PHASE_PREPARE_INPLACE);
 | |
| 
 | |
|   if (table->file->ha_prepare_inplace_alter_table(altered_table,
 | |
|                                                   ha_alter_info))
 | |
|     goto rollback;
 | |
| 
 | |
|   debug_crash_here("ddl_log_alter_after_prepare_inplace");
 | |
| 
 | |
|   /*
 | |
|     Store the new table_version() as it may have not been available before
 | |
|     in some engines, like InnoDB.
 | |
|   */
 | |
|   ddl_log_update_unique_id(ddl_log_state,
 | |
|                            table->file->table_version());
 | |
|   /*
 | |
|     Mark that we have started inplace alter table. DDL recover will
 | |
|     have to decide if it should use the old or new version of the table, based
 | |
|     on if the new version did commit or not.
 | |
|   */
 | |
|   ddl_log_update_phase(ddl_log_state, DDL_ALTER_TABLE_PHASE_INPLACE);
 | |
| 
 | |
|   /*
 | |
|     Downgrade the lock if storage engine has told us that exclusive lock was
 | |
|     necessary only for prepare phase (unless we are not under LOCK TABLES) and
 | |
|     user has not explicitly requested exclusive lock.
 | |
|   */
 | |
|   if (!ha_alter_info->mdl_exclusive_after_prepare &&
 | |
|       (inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK ||
 | |
|        inplace_supported == HA_ALTER_INPLACE_COPY_LOCK ||
 | |
|        inplace_supported == HA_ALTER_INPLACE_NOCOPY_LOCK ||
 | |
|        inplace_supported == HA_ALTER_INPLACE_NOCOPY_NO_LOCK) &&
 | |
|       !(thd->locked_tables_mode == LTM_LOCK_TABLES ||
 | |
|         thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
 | |
|       (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE))
 | |
|   {
 | |
|     /* If storage engine or user requested shared lock downgrade to SNW. */
 | |
|     if (inplace_supported == HA_ALTER_INPLACE_COPY_LOCK ||
 | |
|         inplace_supported == HA_ALTER_INPLACE_NOCOPY_LOCK ||
 | |
|         alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED)
 | |
|       table->mdl_ticket->downgrade_lock(MDL_SHARED_NO_WRITE);
 | |
|     else
 | |
|     {
 | |
|       DBUG_ASSERT(inplace_supported == HA_ALTER_INPLACE_COPY_NO_LOCK ||
 | |
|                   inplace_supported == HA_ALTER_INPLACE_NOCOPY_NO_LOCK);
 | |
|       table->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_inplace_after_lock_downgrade");
 | |
|   THD_STAGE_INFO(thd, stage_alter_inplace);
 | |
|   DBUG_EXECUTE_IF("start_alter_delay_master", {
 | |
|     debug_sync_set_action(thd,
 | |
|                           STRING_WITH_LEN("now wait_for alter_cont NO_CLEAR_EVENT"));
 | |
|       });
 | |
| 
 | |
|   /* We can abort alter table for any table type */
 | |
|   thd->abort_on_warning= !ha_alter_info->ignore && thd->is_strict_mode();
 | |
|   res= table->file->ha_inplace_alter_table(altered_table, ha_alter_info);
 | |
|   thd->abort_on_warning= false;
 | |
| 
 | |
|   if (start_alter_id && wait_for_master(thd))
 | |
|     goto rollback;
 | |
| 
 | |
|   if (res)
 | |
|     goto rollback;
 | |
| 
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_inplace_before_lock_upgrade");
 | |
|   // Upgrade to EXCLUSIVE before commit.
 | |
|   if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
 | |
|     goto rollback;
 | |
| 
 | |
|   /* Set MDL_BACKUP_DDL */
 | |
|   if (backup_reset_alter_copy_lock(thd))
 | |
|     goto rollback;
 | |
| 
 | |
|   /* Crashing here should cause the original table to be used */
 | |
|   debug_crash_here("ddl_log_alter_after_copy");
 | |
|   /*
 | |
|     If we are killed after this point, we should ignore and continue.
 | |
|     We have mostly completed the operation at this point, there should
 | |
|     be no long waits left.
 | |
|   */
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_inplace_before_commit");
 | |
|   THD_STAGE_INFO(thd, stage_alter_inplace_commit);
 | |
| 
 | |
|   DBUG_EXECUTE_IF("alter_table_rollback_new_index", {
 | |
|       table->file->ha_commit_inplace_alter_table(altered_table,
 | |
|                                                  ha_alter_info,
 | |
|                                                  false);
 | |
|       my_error(ER_UNKNOWN_ERROR, MYF(0));
 | |
|       goto cleanup;
 | |
|     });
 | |
| 
 | |
|   /*
 | |
|     Notify the engine that the table definition has changed so that it can
 | |
|     store the new ID as part of the commit
 | |
|   */
 | |
| 
 | |
|   if (!(table->file->partition_ht()->flags &
 | |
|         HTON_REQUIRES_NOTIFY_TABLEDEF_CHANGED_AFTER_COMMIT) &&
 | |
|       notify_tabledef_changed(table_list))
 | |
|     goto rollback;
 | |
| 
 | |
|   {
 | |
|     TR_table trt(thd, true);
 | |
|     if (trt != *table_list && table->file->ht->prepare_commit_versioned)
 | |
|     {
 | |
|       ulonglong trx_start_id= 0;
 | |
|       ulonglong trx_end_id= table->file->ht->prepare_commit_versioned(thd, &trx_start_id);
 | |
|       if (trx_end_id)
 | |
|       {
 | |
|         if (!TR_table::use_transaction_registry)
 | |
|         {
 | |
|           my_error(ER_VERS_TRT_IS_DISABLED, MYF(0));
 | |
|           goto rollback;
 | |
|         }
 | |
|         if (trt.update(trx_start_id, trx_end_id))
 | |
|           goto rollback;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (table->file->ha_commit_inplace_alter_table(altered_table,
 | |
|                                                   ha_alter_info,
 | |
|                                                   true))
 | |
|       goto rollback;
 | |
|     DEBUG_SYNC(thd, "alter_table_inplace_after_commit");
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     We are new ready to use the new table. Update the state in the
 | |
|     ddl log so that we recovery know that the new table is ready and
 | |
|     in case of crash it should use the new one and log the query
 | |
|     to the binary log.
 | |
|   */
 | |
|   ha_alter_info->alter_info->apply_statistics_deletes_renames(thd, table);
 | |
| 
 | |
|   ddl_log_update_phase(ddl_log_state, DDL_ALTER_TABLE_PHASE_INPLACE_COPIED);
 | |
|   debug_crash_here("ddl_log_alter_after_log");
 | |
| 
 | |
|   if ((table->file->partition_ht()->flags &
 | |
|        HTON_REQUIRES_NOTIFY_TABLEDEF_CHANGED_AFTER_COMMIT) &&
 | |
|       notify_tabledef_changed(table_list))
 | |
|   {
 | |
|     /*
 | |
|       The above should never fail. If it failed, the new structure is
 | |
|       commited and we have no way to roll back.
 | |
|       The best we can do is to continue, but send an error to the
 | |
|       user that something when wrong
 | |
|     */
 | |
|     commit_succeded_with_error= 1;
 | |
|   }
 | |
| 
 | |
|   close_all_tables_for_name(thd, table->s,
 | |
|                             alter_ctx->is_table_renamed() ?
 | |
|                             HA_EXTRA_PREPARE_FOR_RENAME :
 | |
|                             HA_EXTRA_NOT_USED,
 | |
|                             NULL);
 | |
|   table_list->table= table= NULL;
 | |
| 
 | |
|   /*
 | |
|     Replace the old .FRM with the new .FRM, but keep the old name for now.
 | |
|     Rename to the new name (if needed) will be handled separately below.
 | |
|   */
 | |
|   /*
 | |
|     TODO: remove this check of thd->is_error() (now it intercept
 | |
|     errors in some val_*() methods and bring some single place to
 | |
|     such error interception).
 | |
|   */
 | |
|   if (mysql_rename_table(db_type, &alter_ctx->new_db, &alter_ctx->tmp_name,
 | |
|                          &alter_ctx->db, &alter_ctx->alias,
 | |
|                          &alter_ctx->tmp_id,
 | |
|                          QRMT_FRM | QRMT_PAR | FN_FROM_IS_TMP) ||
 | |
|                          thd->is_error())
 | |
|   {
 | |
|     // Since changes were done in-place, we can't revert them.
 | |
|     goto err;
 | |
|   }
 | |
|   debug_crash_here("ddl_log_alter_after_rename_frm");
 | |
| 
 | |
|   // Rename altered table in case of ALTER TABLE ... RENAME
 | |
|   if (alter_ctx->is_table_renamed())
 | |
|   {
 | |
|     DBUG_ASSERT(!tdc_share_is_cached(thd, alter_ctx->db.str,
 | |
|                                      alter_ctx->table_name.str));
 | |
|     if (mysql_rename_table(db_type, &alter_ctx->db, &alter_ctx->table_name,
 | |
|                            &alter_ctx->new_db, &alter_ctx->new_alias,
 | |
|                            &alter_ctx->tmp_id, QRMT_DEFAULT))
 | |
|     {
 | |
|       /*
 | |
|         If the rename fails we will still have a working table
 | |
|         with the old name, but with other changes applied.
 | |
|       */
 | |
|       goto err;
 | |
|     }
 | |
|     debug_crash_here("ddl_log_alter_before_rename_triggers");
 | |
|     if (Table_triggers_list::change_table_name(thd, trigger_param,
 | |
|                                                &alter_ctx->db,
 | |
|                                                &alter_ctx->alias,
 | |
|                                                &alter_ctx->table_name,
 | |
|                                                &alter_ctx->new_db,
 | |
|                                                &alter_ctx->new_alias))
 | |
|     {
 | |
|       /*
 | |
|         If the rename of trigger files fails, try to rename the table
 | |
|         back so we at least have matching table and trigger files.
 | |
|       */
 | |
|       (void) mysql_rename_table(db_type,
 | |
|                                 &alter_ctx->new_db, &alter_ctx->new_alias,
 | |
|                                 &alter_ctx->db, &alter_ctx->alias,
 | |
|                                 &alter_ctx->id,
 | |
|                                 QRMT_DEFAULT | NO_FK_CHECKS);
 | |
|       ddl_log_disable_entry(ddl_log_state);
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|     rename_table_in_stat_tables(thd, &alter_ctx->db, &alter_ctx->alias,
 | |
|                                 &alter_ctx->new_db, &alter_ctx->new_alias);
 | |
|     debug_crash_here("ddl_log_alter_after_rename_triggers");
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(commit_succeded_with_error);
 | |
| 
 | |
|  rollback:
 | |
|   table->file->ha_commit_inplace_alter_table(altered_table,
 | |
|                                              ha_alter_info,
 | |
|                                              false);
 | |
|  cleanup:
 | |
|   if (reopen_tables)
 | |
|   {
 | |
|     /* Close the only table instance which is still around. */
 | |
|     close_all_tables_for_name(thd, table->s,
 | |
|                               alter_ctx->is_table_renamed() ?
 | |
|                               HA_EXTRA_PREPARE_FOR_RENAME :
 | |
|                               HA_EXTRA_NOT_USED,
 | |
|                               NULL);
 | |
|     if (thd->locked_tables_list.reopen_tables(thd, false))
 | |
|       thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
 | |
|   }
 | |
| 
 | |
| err:
 | |
|   DBUG_RETURN(true);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   maximum possible length for certain blob types.
 | |
| 
 | |
|   @param[in]      type        Blob type (e.g. MYSQL_TYPE_TINY_BLOB)
 | |
| 
 | |
|   @return
 | |
|     length
 | |
| */
 | |
| 
 | |
| static uint
 | |
| blob_length_by_type(enum_field_types type)
 | |
| {
 | |
|   switch (type)
 | |
|   {
 | |
|   case MYSQL_TYPE_TINY_BLOB:
 | |
|     return 255;
 | |
|   case MYSQL_TYPE_BLOB:
 | |
|     return 65535;
 | |
|   case MYSQL_TYPE_MEDIUM_BLOB:
 | |
|     return 16777215;
 | |
|   case MYSQL_TYPE_LONG_BLOB:
 | |
|     return (uint) UINT_MAX32;
 | |
|   default:
 | |
|     DBUG_ASSERT(0); // we should never go here
 | |
|     return 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline
 | |
| void append_drop_column(THD *thd, String *str, Field *field)
 | |
| {
 | |
|   if (str->length())
 | |
|     str->append(STRING_WITH_LEN(", "));
 | |
|   str->append(STRING_WITH_LEN("DROP COLUMN "));
 | |
|   append_identifier(thd, str, &field->field_name);
 | |
| }
 | |
| 
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
| static inline
 | |
| void rename_field_in_list(Create_field *field, List<const char> *field_list)
 | |
| {
 | |
|   DBUG_ASSERT(field->change.str);
 | |
|   List_iterator<const char> it(*field_list);
 | |
|   while (const char *name= it++)
 | |
|   {
 | |
|     if (!field->change.streq(Lex_cstring_strlen(name)))
 | |
|       continue;
 | |
|     it.replace(field->field_name.str);
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Prepare column and key definitions for CREATE TABLE in ALTER TABLE.
 | |
| 
 | |
|   This function transforms parse output of ALTER TABLE - lists of
 | |
|   columns and keys to add, drop or modify into, essentially,
 | |
|   CREATE TABLE definition - a list of columns and keys of the new
 | |
|   table. While doing so, it also performs some (bug not all)
 | |
|   semantic checks.
 | |
| 
 | |
|   This function is invoked when we know that we're going to
 | |
|   perform ALTER TABLE via a temporary table -- i.e. in-place ALTER TABLE
 | |
|   is not possible, perhaps because the ALTER statement contains
 | |
|   instructions that require change in table data, not only in
 | |
|   table definition or indexes.
 | |
| 
 | |
|   @param[in,out]  thd         thread handle. Used as a memory pool
 | |
|                               and source of environment information.
 | |
|   @param[in]      table       the source table, open and locked
 | |
|                               Used as an interface to the storage engine
 | |
|                               to acquire additional information about
 | |
|                               the original table.
 | |
|   @param[in,out]  create_info A blob with CREATE/ALTER TABLE
 | |
|                               parameters
 | |
|   @param[in,out]  alter_info  Another blob with ALTER/CREATE parameters.
 | |
|                               Originally create_info was used only in
 | |
|                               CREATE TABLE and alter_info only in ALTER TABLE.
 | |
|                               But since ALTER might end-up doing CREATE,
 | |
|                               this distinction is gone and we just carry
 | |
|                               around two structures.
 | |
|   @param[in,out]  alter_ctx   Runtime context for ALTER TABLE.
 | |
| 
 | |
|   @return
 | |
|     Fills various create_info members based on information retrieved
 | |
|     from the storage engine.
 | |
|     Sets create_info->varchar if the table has a VARCHAR column.
 | |
|     Prepares alter_info->create_list and alter_info->key_list with
 | |
|     columns and keys of the new table.
 | |
| 
 | |
|   @retval TRUE   error, out of memory or a semantical error in ALTER
 | |
|                  TABLE instructions
 | |
|   @retval FALSE  success
 | |
| */
 | |
| 
 | |
| bool
 | |
| mysql_prepare_alter_table(THD *thd, TABLE *table,
 | |
|                           Table_specification_st *create_info,
 | |
|                           Alter_info *alter_info,
 | |
|                           Alter_table_ctx *alter_ctx)
 | |
| {
 | |
|   /* New column definitions are added here */
 | |
|   List<Create_field> new_create_list;
 | |
|   /* System-invisible fields must be added last */
 | |
|   List<Create_field> new_create_tail;
 | |
|   /* New key definitions are added here */
 | |
|   List<Key> new_key_list;
 | |
|   List<FOREIGN_KEY_INFO> fk_list;
 | |
|   List<Alter_rename_key> rename_key_list(alter_info->alter_rename_key_list);
 | |
|   MEM_ROOT *root= thd->mem_root;
 | |
| 
 | |
|   /*
 | |
|     Create a deep copy of the list of visibility for indexes, as it will be
 | |
|     altered here.
 | |
|   */
 | |
|   List<Alter_index_ignorability>
 | |
|          alter_index_ignorability_list(alter_info->alter_index_ignorability_list,
 | |
|                                        root);
 | |
| 
 | |
|   list_copy_and_replace_each_value(alter_index_ignorability_list, root);
 | |
| 
 | |
|   List_iterator<Alter_drop> drop_it(alter_info->drop_list);
 | |
|   List_iterator<Create_field> def_it(alter_info->create_list);
 | |
|   List_iterator<Alter_column> alter_it(alter_info->alter_list);
 | |
|   List_iterator<Key> key_it(alter_info->key_list);
 | |
|   List_iterator<Create_field> find_it(new_create_list);
 | |
|   List_iterator<Create_field> field_it(new_create_list);
 | |
|   List<Key_part_spec> key_parts;
 | |
|   List<Virtual_column_info> new_constraint_list;
 | |
|   uint db_create_options= (table->s->db_create_options
 | |
|                            & ~(HA_OPTION_PACK_RECORD));
 | |
|   Item::func_processor_rename column_rename_param;
 | |
|   uint used_fields, dropped_sys_vers_fields= 0;
 | |
|   KEY *key_info=table->key_info;
 | |
|   bool rc= TRUE;
 | |
|   bool vers_system_invisible= false;
 | |
|   Create_field *def;
 | |
|   Field **f_ptr,*field;
 | |
|   MY_BITMAP *dropped_fields= NULL; // if it's NULL - no dropped fields
 | |
|   bool drop_period= false;
 | |
|   Lex_ident_column period_start_name;
 | |
|   Lex_ident_column period_end_name;
 | |
|   DBUG_ENTER("mysql_prepare_alter_table");
 | |
| 
 | |
|   if (table->s->period.name)
 | |
|   {
 | |
|     period_start_name= table->s->period_start_field()->field_name;
 | |
|     period_end_name= table->s->period_end_field()->field_name;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Merge incompatible changes flag in case of upgrade of a table from an
 | |
|     old MariaDB or MySQL version.  This ensures that we don't try to do an
 | |
|     online alter table if field packing or character set changes are required.
 | |
|   */
 | |
|   create_info->used_fields|= table->s->incompatible_version;
 | |
|   used_fields= create_info->used_fields;
 | |
| 
 | |
|   create_info->varchar= FALSE;
 | |
|   /* Let new create options override the old ones */
 | |
|   if (!(used_fields & HA_CREATE_USED_MIN_ROWS))
 | |
|     create_info->min_rows= table->s->min_rows;
 | |
|   if (!(used_fields & HA_CREATE_USED_MAX_ROWS))
 | |
|     create_info->max_rows= table->s->max_rows;
 | |
|   if (!(used_fields & HA_CREATE_USED_AVG_ROW_LENGTH))
 | |
|     create_info->avg_row_length= table->s->avg_row_length;
 | |
| 
 | |
|   if (create_info->resolve_to_charset_collation_context(thd,
 | |
|         thd->charset_collation_context_alter_table(table->s)))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   if (!(used_fields & HA_CREATE_USED_AUTO) && table->found_next_number_field)
 | |
|   {
 | |
|     /* Table has an autoincrement, copy value to new table */
 | |
|     table->file->info(HA_STATUS_AUTO);
 | |
|     create_info->auto_increment_value= table->file->stats.auto_increment_value;
 | |
|   }
 | |
| 
 | |
|   if (!(used_fields & HA_CREATE_USED_KEY_BLOCK_SIZE))
 | |
|     create_info->key_block_size= table->s->key_block_size;
 | |
| 
 | |
|   if (!(used_fields & HA_CREATE_USED_STATS_SAMPLE_PAGES))
 | |
|     create_info->stats_sample_pages= table->s->stats_sample_pages;
 | |
| 
 | |
|   if (!(used_fields & HA_CREATE_USED_STATS_AUTO_RECALC))
 | |
|     create_info->stats_auto_recalc= table->s->stats_auto_recalc;
 | |
| 
 | |
|   if (!(used_fields & HA_CREATE_USED_TRANSACTIONAL))
 | |
|     create_info->transactional= table->s->transactional;
 | |
| 
 | |
|   if (!(used_fields & HA_CREATE_USED_CONNECTION))
 | |
|     create_info->connect_string= table->s->connect_string;
 | |
| 
 | |
|   if (!(used_fields & HA_CREATE_USED_SEQUENCE))
 | |
|     create_info->sequence= table->s->table_type == TABLE_TYPE_SEQUENCE;
 | |
| 
 | |
|   column_rename_param.db_name=       table->s->db;
 | |
|   column_rename_param.table_name=    table->s->table_name;
 | |
|   if (column_rename_param.fields.copy(&alter_info->create_list, root))
 | |
|     DBUG_RETURN(1);                             // OOM
 | |
| 
 | |
|   restore_record(table, s->default_values);     // Empty record for DEFAULT
 | |
| 
 | |
|   if ((create_info->fields_option_struct= 
 | |
|          thd->calloc<ha_field_option_struct*>(table->s->fields)) == NULL ||
 | |
|       (create_info->indexes_option_struct= 
 | |
|          thd->calloc<ha_index_option_struct*>(table->s->total_keys)) == NULL)
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   if (merge_engine_options(table->s->option_list, create_info->option_list,
 | |
|                            &create_info->option_list, root))
 | |
|     DBUG_RETURN(1);
 | |
| 
 | |
|   table->file->get_foreign_key_list(thd, &fk_list);
 | |
| 
 | |
|   /*
 | |
|     First collect all fields from table which isn't in drop_list
 | |
|   */
 | |
|   bitmap_clear_all(&table->tmp_set);
 | |
|   for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
 | |
|   {
 | |
|     if (field->invisible == INVISIBLE_FULL)
 | |
|         continue;
 | |
|     Alter_drop *drop;
 | |
|     if (field->type() == MYSQL_TYPE_VARCHAR)
 | |
|       create_info->varchar= TRUE;
 | |
|     /* Check if field should be dropped */
 | |
|     drop_it.rewind();
 | |
|     while ((drop=drop_it++))
 | |
|     {
 | |
|       if (drop->type == Alter_drop::COLUMN &&
 | |
|           field->field_name.streq(drop->name))
 | |
|         break;
 | |
|     }
 | |
|     /*
 | |
|       DROP COLUMN xxx
 | |
|       1. it does not see INVISIBLE_SYSTEM columns
 | |
|       2. otherwise, normally a column is dropped
 | |
|       3. unless it's a system versioning column (but see below).
 | |
|     */
 | |
|     if (drop && field->invisible < INVISIBLE_SYSTEM &&
 | |
|         !(field->flags & VERS_SYSTEM_FIELD &&
 | |
|           !(alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING)))
 | |
|     {
 | |
|       /* Reset auto_increment value if it was dropped */
 | |
|       if (MTYP_TYPENR(field->unireg_check) == Field::NEXT_NUMBER &&
 | |
|           !(used_fields & HA_CREATE_USED_AUTO))
 | |
|       {
 | |
|         create_info->auto_increment_value=0;
 | |
|         create_info->used_fields|=HA_CREATE_USED_AUTO;
 | |
|       }
 | |
|       if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|       {
 | |
|         if (alter_info->drop_stat_fields.push_back(field, root))
 | |
|           DBUG_RETURN(true);
 | |
|       }
 | |
|       dropped_sys_vers_fields|= field->flags;
 | |
|       drop_it.remove();
 | |
|       dropped_fields= &table->tmp_set;
 | |
|       bitmap_set_bit(dropped_fields, field->field_index);
 | |
|       continue;
 | |
|     }
 | |
|     if (field->invisible == INVISIBLE_SYSTEM &&
 | |
|         field->flags & VERS_SYSTEM_FIELD)
 | |
|     {
 | |
|       vers_system_invisible= true;
 | |
|     }
 | |
|     /*
 | |
|       invisible versioning column is dropped automatically on
 | |
|       DROP SYSTEM VERSIONING
 | |
|     */
 | |
|     if (!drop && field->invisible >= INVISIBLE_SYSTEM &&
 | |
|         field->flags & VERS_SYSTEM_FIELD &&
 | |
|         alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING)
 | |
|     {
 | |
|       if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|       {
 | |
|         if (alter_info->drop_stat_fields.push_back(field, root))
 | |
|           DBUG_RETURN(true);
 | |
|       }
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     /* Check if field is changed */
 | |
|     def_it.rewind();
 | |
|     while ((def=def_it++))
 | |
|     {
 | |
|       if (def->change.str && field->field_name.streq(def->change))
 | |
| 	break;
 | |
|     }
 | |
|     if (def && field->invisible < INVISIBLE_SYSTEM)
 | |
|     {						// Field is changed
 | |
|       def->field=field;
 | |
|       /*
 | |
|         Add column being updated to the list of new columns.
 | |
|         Note that columns with AFTER clauses are added to the end
 | |
|         of the list for now. Their positions will be corrected later.
 | |
|       */
 | |
|       new_create_list.push_back(def, root);
 | |
|       if (field->stored_in_db() != def->stored_in_db())
 | |
|       {
 | |
|         my_error(ER_UNSUPPORTED_ACTION_ON_GENERATED_COLUMN, MYF(0));
 | |
|         goto err;
 | |
|       }
 | |
|       if (!def->after.str)
 | |
|       {
 | |
|         /*
 | |
|           If this ALTER TABLE doesn't have an AFTER clause for the modified
 | |
|           column then remove this column from the list of columns to be
 | |
|           processed. So later we can iterate over the columns remaining
 | |
|           in this list and process modified columns with AFTER clause or
 | |
|           add new columns.
 | |
|         */
 | |
| 	def_it.remove();
 | |
|       }
 | |
|     }
 | |
|     else if (alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING &&
 | |
|              field->flags & VERS_SYSTEM_FIELD &&
 | |
|              field->invisible < INVISIBLE_SYSTEM)
 | |
|     {
 | |
|       StringBuffer<NAME_LEN*3> tmp;
 | |
|       append_drop_column(thd, &tmp, field);
 | |
|       my_error(ER_MISSING, MYF(0), table->s->table_name.str, tmp.c_ptr());
 | |
|       goto err;
 | |
|     }
 | |
|     else if (drop && field->invisible < INVISIBLE_SYSTEM &&
 | |
|              field->flags & VERS_SYSTEM_FIELD &&
 | |
|              !(alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING))
 | |
|     {
 | |
|       /* "dropping" a versioning field only hides it from the user */
 | |
|       def= new (root) Create_field(thd, field, field);
 | |
|       def->invisible= INVISIBLE_SYSTEM;
 | |
|       alter_info->flags|= ALTER_CHANGE_COLUMN;
 | |
|       if (field->flags & VERS_ROW_START)
 | |
|         create_info->vers_info.period.start=
 | |
|           create_info->vers_info.as_row.start=
 | |
|           def->field_name= Vers_parse_info::default_start;
 | |
| 
 | |
|       else
 | |
|         create_info->vers_info.period.end=
 | |
|           create_info->vers_info.as_row.end=
 | |
|           def->field_name= Vers_parse_info::default_end;
 | |
|       new_create_list.push_back(def, root);
 | |
|       dropped_sys_vers_fields|= field->flags;
 | |
|       drop_it.remove();
 | |
|     }
 | |
|     else if (field->invisible < INVISIBLE_SYSTEM ||
 | |
|              (alter_info->flags & ALTER_VERS_EXPLICIT))
 | |
|     {
 | |
|       /*
 | |
|         This field was not dropped and not changed, add it to the list
 | |
|         for the new table.
 | |
|       */
 | |
|       def= new (root) Create_field(thd, field, field);
 | |
|       new_create_list.push_back(def, root);
 | |
|       alter_it.rewind();			// Change default if ALTER
 | |
|       Alter_column *alter;
 | |
|       while ((alter=alter_it++))
 | |
|       {
 | |
|         if (field->field_name.streq(alter->name))
 | |
| 	  break;
 | |
|       }
 | |
|       if (alter)
 | |
|       {
 | |
|         if (alter->is_rename())
 | |
|         {
 | |
|           def->change= Lex_ident_column(alter->name);
 | |
|           def->field_name= Lex_ident_column(alter->new_name);
 | |
|           if (vers_system_invisible)
 | |
|             def->invisible= VISIBLE;
 | |
|           column_rename_param.fields.push_back(def);
 | |
|           if (field->flags & VERS_ROW_START)
 | |
|           {
 | |
|             create_info->vers_info.as_row.start= Lex_ident_column(alter->new_name);
 | |
|             create_info->vers_info.period.start= Lex_ident_column(alter->new_name);
 | |
|           }
 | |
|           else if (field->flags & VERS_ROW_END)
 | |
|           {
 | |
|             create_info->vers_info.as_row.end= Lex_ident_column(alter->new_name);
 | |
|             create_info->vers_info.period.end= Lex_ident_column(alter->new_name);
 | |
|           }
 | |
|           if (table->s->period.name)
 | |
|           {
 | |
|             if (field == table->period_start_field())
 | |
|               period_start_name= Lex_ident_column(alter->new_name);
 | |
|             else if (field == table->period_end_field())
 | |
|               period_end_name= Lex_ident_column(alter->new_name);
 | |
|           }
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           if ((def->default_value= alter->default_value))
 | |
|             def->flags&= ~NO_DEFAULT_VALUE_FLAG;
 | |
|           else
 | |
|             def->flags|= NO_DEFAULT_VALUE_FLAG;
 | |
|         }
 | |
| 	alter_it.remove();
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       DBUG_ASSERT(field->invisible == INVISIBLE_SYSTEM);
 | |
|       def= new (root) Create_field(thd, field, field);
 | |
|       new_create_tail.push_back(def, root);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     If we are doing a rename of a column, update all references in virtual
 | |
|     column expressions, constraints and defaults to use the new column name
 | |
|   */
 | |
|   if (alter_info->flags & ALTER_RENAME_COLUMN)
 | |
|   {
 | |
|     alter_it.rewind();
 | |
|     Alter_column *alter;
 | |
|     while ((alter=alter_it++))
 | |
|     {
 | |
|       if (alter->is_rename())
 | |
|       {
 | |
|         my_error(ER_BAD_FIELD_ERROR, MYF(0), alter->name.str,
 | |
|                  table->s->table_name.str);
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|     for (f_ptr=table->field ; (field= *f_ptr) ; f_ptr++)
 | |
|     {
 | |
|       if (field->vcol_info)
 | |
|         field->vcol_info->expr->walk(&Item::rename_fields_processor, 1,
 | |
|                                     &column_rename_param);
 | |
|       if (field->check_constraint)
 | |
|         field->check_constraint->expr->walk(&Item::rename_fields_processor, 1,
 | |
|                                             &column_rename_param);
 | |
|       if (field->default_value)
 | |
|         field->default_value->expr->walk(&Item::rename_fields_processor, 1,
 | |
|                                         &column_rename_param);
 | |
|     }
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|     if (thd->work_part_info)
 | |
|     {
 | |
|       partition_info *part_info= thd->work_part_info;
 | |
|       List_iterator<Create_field> def_it(column_rename_param.fields);
 | |
|       const bool part_field_list= !part_info->part_field_list.is_empty();
 | |
|       const bool subpart_field_list= !part_info->subpart_field_list.is_empty();
 | |
|       if (part_info->part_expr)
 | |
|         part_info->part_expr->walk(&Item::rename_fields_processor, 1,
 | |
|                                   &column_rename_param);
 | |
|       if (part_info->subpart_expr)
 | |
|         part_info->subpart_expr->walk(&Item::rename_fields_processor, 1,
 | |
|                                       &column_rename_param);
 | |
|       if (part_field_list || subpart_field_list)
 | |
|       {
 | |
|         while (Create_field *def= def_it++)
 | |
|         {
 | |
|           if (def->change.str)
 | |
|           {
 | |
|             if (part_field_list)
 | |
|               rename_field_in_list(def, &part_info->part_field_list);
 | |
|             if (subpart_field_list)
 | |
|               rename_field_in_list(def, &part_info->subpart_field_list);
 | |
|           } /* if (def->change.str) */
 | |
|         } /* while (def) */
 | |
|       } /* if (part_field_list || subpart_field_list) */
 | |
|       // Force reopen because new column name is on root
 | |
|       table->mark_table_for_reopen();
 | |
|     } /* if (part_info) */
 | |
| #endif
 | |
|     // Force reopen because new column name is on root
 | |
|     table->mark_table_for_reopen();
 | |
|   }
 | |
| 
 | |
|   dropped_sys_vers_fields &= VERS_SYSTEM_FIELD;
 | |
|   if ((dropped_sys_vers_fields ||
 | |
|        alter_info->flags & ALTER_DROP_PERIOD) &&
 | |
|       dropped_sys_vers_fields != VERS_SYSTEM_FIELD &&
 | |
|       !vers_system_invisible)
 | |
|   {
 | |
|     StringBuffer<NAME_LEN*3> tmp;
 | |
|     if (!(dropped_sys_vers_fields & VERS_ROW_START))
 | |
|       append_drop_column(thd, &tmp, table->vers_start_field());
 | |
|     if (!(dropped_sys_vers_fields & VERS_ROW_END))
 | |
|       append_drop_column(thd, &tmp, table->vers_end_field());
 | |
|     my_error(ER_MISSING, MYF(0), table->s->table_name.str, tmp.c_ptr());
 | |
|     goto err;
 | |
|   }
 | |
|   else if (alter_info->flags & ALTER_DROP_PERIOD && vers_system_invisible)
 | |
|   {
 | |
|     my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), "PERIOD FOR SYSTEM_TIME on", table->s->table_name.str);
 | |
|     goto err;
 | |
|   }
 | |
|   alter_info->flags &= ~(ALTER_DROP_PERIOD | ALTER_ADD_PERIOD);
 | |
|   def_it.rewind();
 | |
|   while ((def=def_it++))			// Add new columns
 | |
|   {
 | |
|     Create_field *find;
 | |
|     if (def->change.str && ! def->field)
 | |
|     {
 | |
|       /*
 | |
|         Check if there is modify for newly added field.
 | |
|       */
 | |
|       find_it.rewind();
 | |
|       while((find=find_it++))
 | |
|       {
 | |
|         if (find->field_name.streq(def->field_name))
 | |
|           break;
 | |
|       }
 | |
| 
 | |
|       if (likely(find && !find->field))
 | |
| 	find_it.remove();
 | |
|       else
 | |
|       {
 | |
|         my_error(ER_BAD_FIELD_ERROR, MYF(0), def->change.str,
 | |
|                  table->s->table_name.str);
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|     /*
 | |
|       Check that the DATE/DATETIME not null field we are going to add is
 | |
|       either has a default value or the '0000-00-00' is allowed by the
 | |
|       set sql mode.
 | |
|       If the '0000-00-00' value isn't allowed then raise the error_if_not_empty
 | |
|       flag to allow ALTER TABLE only if the table to be altered is empty.
 | |
|     */
 | |
|     if (!alter_ctx->implicit_default_value_error_field && !def->field &&
 | |
|         !(~def->flags & (NO_DEFAULT_VALUE_FLAG | NOT_NULL_FLAG)) &&
 | |
|         def->type_handler()->validate_implicit_default_value(thd, *def))
 | |
|     {
 | |
|         alter_ctx->implicit_default_value_error_field= def;
 | |
|         alter_ctx->error_if_not_empty= TRUE;
 | |
|     }
 | |
|     if (!def->after.str)
 | |
|       new_create_list.push_back(def, root);
 | |
|     else
 | |
|     {
 | |
|       if (def->change.str)
 | |
|       {
 | |
|         find_it.rewind();
 | |
|         /*
 | |
|           For columns being modified with AFTER clause we should first remove
 | |
|           these columns from the list and then add them back at their correct
 | |
|           positions.
 | |
|         */
 | |
|         while ((find=find_it++))
 | |
|         {
 | |
|           /*
 | |
|             Create_fields representing changed columns are added directly
 | |
|             from Alter_info::create_list to new_create_list. We can therefore
 | |
|             safely use pointer equality rather than name matching here.
 | |
|             This prevents removing the wrong column in case of column rename.
 | |
|           */
 | |
|           if (find == def)
 | |
|           {
 | |
|             find_it.remove();
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       if (def->after.str == first_keyword)
 | |
|         new_create_list.push_front(def, root);
 | |
|       else
 | |
|       {
 | |
|         find_it.rewind();
 | |
|         while ((find=find_it++))
 | |
|         {
 | |
|           if (def->after.streq(find->field_name))
 | |
|             break;
 | |
|         }
 | |
|         if (unlikely(!find))
 | |
|         {
 | |
|           my_error(ER_BAD_FIELD_ERROR, MYF(0), def->after.str,
 | |
|                    table->s->table_name.str);
 | |
|           goto err;
 | |
|         }
 | |
|         find_it.after(def);			// Put column after this
 | |
|       }
 | |
|     }
 | |
|     /*
 | |
|       Check if there is alter for newly added field.
 | |
|     */
 | |
|     alter_it.rewind();
 | |
|     Alter_column *alter;
 | |
|     while ((alter=alter_it++))
 | |
|     {
 | |
|       if (def->field_name.streq(alter->name))
 | |
|         break;
 | |
|     }
 | |
|     if (alter)
 | |
|     {
 | |
|       if ((def->default_value= alter->default_value)) // Use new default
 | |
|         def->flags&= ~NO_DEFAULT_VALUE_FLAG;
 | |
|       else
 | |
|         def->flags|= NO_DEFAULT_VALUE_FLAG;
 | |
|       alter_it.remove();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   new_create_list.append(&new_create_tail);
 | |
| 
 | |
|   if (unlikely(alter_info->alter_list.elements))
 | |
|   {
 | |
|     my_error(ER_BAD_FIELD_ERROR, MYF(0),
 | |
|              alter_info->alter_list.head()->name.str, table->s->table_name.str);
 | |
|     goto err;
 | |
|   }
 | |
|   if (unlikely(!new_create_list.elements))
 | |
|   {
 | |
|     my_message(ER_CANT_REMOVE_ALL_FIELDS,
 | |
|                ER_THD(thd, ER_CANT_REMOVE_ALL_FIELDS),
 | |
|                MYF(0));
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Collect all keys which isn't in drop list. Add only those
 | |
|     for which some fields exists.
 | |
|   */
 | |
|   for (uint i= 0; i < table->s->total_keys; i++, key_info++)
 | |
|   {
 | |
|     bool long_hash_key= false;
 | |
|     if (key_info->flags & HA_INVISIBLE_KEY)
 | |
|       continue;
 | |
|     Lex_ident_column key_name(key_info->name);
 | |
|     const bool primary_key= table->s->primary_key == i;
 | |
|     const bool explicit_pk= primary_key &&
 | |
|                             key_name.streq(primary_key_name);
 | |
|     const bool implicit_pk= primary_key && !explicit_pk;
 | |
| 
 | |
|     Alter_drop *drop;
 | |
|     drop_it.rewind();
 | |
|     while ((drop=drop_it++))
 | |
|     {
 | |
|       if (drop->type == Alter_drop::KEY &&
 | |
|           key_name.streq(drop->name))
 | |
| 	break;
 | |
|     }
 | |
|     if (drop)
 | |
|     {
 | |
|       if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|       {
 | |
|         if (alter_info->add_stat_drop_index(key_info, FALSE, root))
 | |
|           DBUG_RETURN(true);
 | |
|         if (primary_key)
 | |
| 	{
 | |
|           KEY *tab_key_info= table->key_info;
 | |
| 	  for (uint j=0; j < table->s->keys; j++, tab_key_info++)
 | |
| 	  {
 | |
|             if (tab_key_info != key_info &&
 | |
|                 tab_key_info->user_defined_key_parts !=
 | |
|                 tab_key_info->ext_key_parts)
 | |
|             {
 | |
|               if (alter_info->add_stat_drop_index(tab_key_info, TRUE, root))
 | |
|                 DBUG_RETURN(true);
 | |
|             }
 | |
|           }
 | |
| 	}
 | |
|       }
 | |
|       drop_it.remove();
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     List_iterator<Alter_index_ignorability>
 | |
|        ignorability_index_it(alter_index_ignorability_list);
 | |
| 
 | |
|     Alter_index_ignorability *index_ignorability;
 | |
|     while((index_ignorability= ignorability_index_it++))
 | |
|     {
 | |
|       if (key_name.streq(index_ignorability->name()))
 | |
|         ignorability_index_it.remove();
 | |
|     }
 | |
| 
 | |
| 
 | |
|     /* If this index is to stay in the table check if it has to be renamed. */
 | |
|     List_iterator<Alter_rename_key> rename_key_it(rename_key_list);
 | |
|     Alter_rename_key *rename_key;
 | |
| 
 | |
|     while ((rename_key= rename_key_it++))
 | |
|     {
 | |
|       if (key_name.streq(rename_key->old_name))
 | |
|       {
 | |
|         if (key_name.streq(primary_key_name))
 | |
|         {
 | |
|           my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), rename_key->old_name.str);
 | |
|           goto err;
 | |
|         }
 | |
|         else if (rename_key->new_name.streq(primary_key_name))
 | |
|         {
 | |
|           my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), rename_key->new_name.str);
 | |
|           goto err;
 | |
|         }
 | |
| 
 | |
|         key_name= rename_key->new_name;     // New name of current key_info
 | |
|         if (cmp(&rename_key->old_name, &rename_key->new_name))
 | |
|         {
 | |
|           /* Key was renamed */
 | |
|           alter_info->add_stat_rename_index(key_info, &rename_key->new_name, root);
 | |
|         }
 | |
|         rename_key_it.remove();
 | |
| 
 | |
|         /*
 | |
|           If the user has explicitly renamed the key, we should no longer
 | |
|           treat it as generated. Otherwise this key might be automatically
 | |
|           dropped by mysql_prepare_create_table() and this will confuse
 | |
|           code in fill_alter_inplace_info().
 | |
|         */
 | |
|         key_info->flags&= ~HA_GENERATED_KEY;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (key_info->algorithm == HA_KEY_ALG_LONG_HASH)
 | |
|     {
 | |
|       setup_keyinfo_hash(key_info);
 | |
|       long_hash_key= true;
 | |
|     }
 | |
|     const char *dropped_key_part= NULL;
 | |
|     bool user_keyparts= false; // some user-defined keyparts left
 | |
|     KEY_PART_INFO *key_part= key_info->key_part;
 | |
|     key_parts.empty();
 | |
|     uint key_parts_nr= key_info->user_defined_key_parts;
 | |
|     if (key_info->without_overlaps)
 | |
|       key_parts_nr-= 2;
 | |
| 
 | |
|     bool delete_index_stat= FALSE;
 | |
|     for (uint j=0 ; j < key_parts_nr ; j++,key_part++)
 | |
|     {
 | |
|       Field *kfield= key_part->field;
 | |
|       if (!kfield)
 | |
| 	continue;				// Wrong field (from UNIREG)
 | |
|       const Lex_ident_column key_part_name(kfield->field_name);
 | |
|       Create_field *cfield;
 | |
|       uint key_part_length;
 | |
| 
 | |
|       field_it.rewind();
 | |
|       while ((cfield=field_it++))
 | |
|       {
 | |
| 	if (cfield->change.str)
 | |
| 	{
 | |
| 	  if (cfield->change.streq(key_part_name))
 | |
| 	    break;
 | |
| 	}
 | |
| 	else if (cfield->field_name.streq(key_part_name))
 | |
| 	  break;
 | |
|       }
 | |
|       if (!cfield)
 | |
|       {
 | |
|         if (primary_key)
 | |
|           alter_ctx->modified_primary_key= true;
 | |
|         delete_index_stat= TRUE;
 | |
|         if (!(kfield->flags & VERS_SYSTEM_FIELD))
 | |
|           dropped_key_part= key_part_name.str;
 | |
| 	continue;				// Field is removed
 | |
|       }
 | |
| 
 | |
|       DBUG_ASSERT(!primary_key || kfield->flags & NOT_NULL_FLAG);
 | |
|       if (implicit_pk && !alter_ctx->modified_primary_key &&
 | |
|           !(cfield->flags & NOT_NULL_FLAG))
 | |
|         alter_ctx->modified_primary_key= true;
 | |
| 
 | |
|       key_part_length= key_part->length;
 | |
|       if (cfield->field)			// Not new field
 | |
|       {
 | |
|         /*
 | |
|           If the field can't have only a part used in a key according to its
 | |
|           new type, or should not be used partially according to its
 | |
|           previous type, or the field length is less than the key part
 | |
|           length, unset the key part length.
 | |
| 
 | |
|           We also unset the key part length if it is the same as the
 | |
|           old field's length, so the whole new field will be used.
 | |
| 
 | |
|           BLOBs may have cfield->length == 0, which is why we test it before
 | |
|           checking whether cfield->length < key_part_length (in chars).
 | |
|           
 | |
|           In case of TEXTs we check the data type maximum length *in bytes*
 | |
|           to key part length measured *in characters* (i.e. key_part_length
 | |
|           devided to mbmaxlen). This is because it's OK to have:
 | |
|           CREATE TABLE t1 (a tinytext, key(a(254)) character set utf8);
 | |
|           In case of this example:
 | |
|           - data type maximum length is 255.
 | |
|           - key_part_length is 1016 (=254*4, where 4 is mbmaxlen)
 | |
|          */
 | |
|         if (!cfield->field->type_handler()->type_can_have_key_part() ||
 | |
|             !cfield->type_handler()->type_can_have_key_part() ||
 | |
|             /* spatial keys can't have sub-key length */
 | |
|             key_info->algorithm == HA_KEY_ALG_RTREE ||
 | |
|             (cfield->field->field_length == key_part_length &&
 | |
|              !f_is_blob(key_part->key_type)) ||
 | |
|             (cfield->length &&
 | |
|              (((cfield->real_field_type() >= MYSQL_TYPE_TINY_BLOB &&
 | |
|                 cfield->real_field_type() <= MYSQL_TYPE_BLOB) ?
 | |
|                 blob_length_by_type(cfield->real_field_type()) :
 | |
|                 cfield->length) <
 | |
| 	     key_part_length / kfield->charset()->mbmaxlen)))
 | |
| 	  key_part_length= 0;			// Use whole field
 | |
|       }
 | |
|       key_part_length /= kfield->charset()->mbmaxlen;
 | |
|       Key_part_spec *kps= new (root) Key_part_spec(&cfield->field_name,
 | |
|                                                             key_part_length, true);
 | |
|       kps->asc= !(key_part->key_part_flag & HA_REVERSE_SORT);
 | |
|       key_parts.push_back(kps, root);
 | |
|       if (!(cfield->invisible == INVISIBLE_SYSTEM && cfield->vers_sys_field()))
 | |
|         user_keyparts= true;
 | |
|     }
 | |
|     if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|     {
 | |
|       if (delete_index_stat) 
 | |
|       {
 | |
|         if (alter_info->add_stat_drop_index(key_info, FALSE, root))
 | |
|           DBUG_RETURN(true);
 | |
|       }
 | |
|       else if (alter_ctx->modified_primary_key &&
 | |
|                key_info->user_defined_key_parts != key_info->ext_key_parts)
 | |
|       {
 | |
|         if (alter_info->add_stat_drop_index(key_info, FALSE, root))
 | |
|           DBUG_RETURN(true);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (!user_keyparts && key_parts.elements)
 | |
|     {
 | |
|       /*
 | |
|         If we dropped all user key-parts we also drop implicit system fields.
 | |
|       */
 | |
|       key_parts.empty();
 | |
|     }
 | |
| 
 | |
|     if (key_parts.elements)
 | |
|     {
 | |
|       KEY_CREATE_INFO key_create_info;
 | |
|       Key *key;
 | |
|       enum Key::Keytype key_type;
 | |
|       LEX_CSTRING tmp_name;
 | |
|       engine_option_value *option_list;
 | |
| 
 | |
|       bzero((char*) &key_create_info, sizeof(key_create_info));
 | |
|       if (key_info->algorithm == HA_KEY_ALG_LONG_HASH)
 | |
|         key_info->algorithm= alter_ctx->fast_alter_partition ?
 | |
|           HA_KEY_ALG_HASH : HA_KEY_ALG_UNDEF;
 | |
|       /*
 | |
|         For fast alter partition we set HA_KEY_ALG_HASH above to make sure it
 | |
|         doesn't lose the hash property.
 | |
|         Otherwise we let mysql_prepare_create_table() decide if the hash field
 | |
|         is needed depending on the (possibly changed) data types.
 | |
|       */
 | |
|       key_create_info.algorithm= key_info->algorithm;
 | |
|       /*
 | |
|         We copy block size directly as some engines, like Area, sets this
 | |
|         automatically
 | |
|       */
 | |
|       key_create_info.block_size= key_info->block_size;
 | |
|       key_create_info.flags=      key_info->flags;  // HA_USE_BLOCK_SIZE
 | |
|       if (key_info->flags & HA_USES_PARSER)
 | |
|         key_create_info.parser_name= *plugin_name(key_info->parser);
 | |
|       if (key_info->flags & HA_USES_COMMENT)
 | |
|         key_create_info.comment= key_info->comment;
 | |
|       key_create_info.is_ignored= key_info->is_ignored;
 | |
| 
 | |
|       if (key_info->algorithm == HA_KEY_ALG_RTREE)
 | |
|         key_type= Key::SPATIAL;
 | |
|       else if (key_info->flags & HA_NOSAME)
 | |
|       {
 | |
|         if (explicit_pk)
 | |
|           key_type= Key::PRIMARY;
 | |
|         else
 | |
|           key_type= Key::UNIQUE;
 | |
|         if (dropped_key_part)
 | |
|         {
 | |
|           my_error(ER_KEY_COLUMN_DOES_NOT_EXIST, MYF(0), dropped_key_part);
 | |
|           if (long_hash_key)
 | |
|           {
 | |
|             key_info->algorithm= HA_KEY_ALG_LONG_HASH;
 | |
|             re_setup_keyinfo_hash(key_info);
 | |
|           }
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
|       else if (key_info->algorithm == HA_KEY_ALG_FULLTEXT)
 | |
|         key_type= Key::FULLTEXT;
 | |
|       else if (key_info->algorithm == HA_KEY_ALG_VECTOR)
 | |
|         key_type= Key::VECTOR;
 | |
|       else
 | |
|         key_type= Key::MULTIPLE;
 | |
| 
 | |
|       List_iterator<Alter_index_ignorability>
 | |
|            ignorability_index_it(alter_info->alter_index_ignorability_list);
 | |
|       Alter_index_ignorability *index_ignorability;
 | |
|       while((index_ignorability= ignorability_index_it++))
 | |
|       {
 | |
|         if (key_name.streq(index_ignorability->name()))
 | |
|         {
 | |
|           if (table->s->primary_key <= MAX_KEY &&
 | |
|               table->key_info + table->s->primary_key == key_info)
 | |
|           {
 | |
|             my_error(ER_PK_INDEX_CANT_BE_IGNORED, MYF(0));
 | |
|             goto err;
 | |
|           }
 | |
|           key_create_info.is_ignored= index_ignorability->is_ignored();
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (merge_engine_options(key_info->option_list, 0, &option_list, root))
 | |
|         goto err;
 | |
| 
 | |
|       tmp_name= key_name;
 | |
|       /* We dont need LONG_UNIQUE_HASH_FIELD flag because it will be autogenerated */
 | |
|       key= new (root) Key(key_type, &tmp_name, &key_create_info,
 | |
|                           key_info->flags & HA_GENERATED_KEY, &key_parts,
 | |
|                           option_list, DDL_options());
 | |
|       key->without_overlaps= key_info->without_overlaps;
 | |
|       key->period= table->s->period.name;
 | |
|       key->old= true;
 | |
|       new_key_list.push_back(key, root);
 | |
|     }
 | |
|     if (long_hash_key)
 | |
|     {
 | |
|       key_info->algorithm= HA_KEY_ALG_LONG_HASH;
 | |
|       re_setup_keyinfo_hash(key_info);
 | |
|     }
 | |
|   }
 | |
|   {
 | |
|     // add existing foreign keys
 | |
|     for (auto &fk : fk_list)
 | |
|     {
 | |
|       Alter_drop *drop;
 | |
|       for(drop_it.rewind(); (drop=drop_it++); )
 | |
|         if (drop->type == Alter_drop::FOREIGN_KEY &&
 | |
|             drop->name.streq(*fk.foreign_id))
 | |
|           break;
 | |
|       if (drop)
 | |
|         continue;
 | |
|       List<Key_part_spec> cols, ref_cols;
 | |
|       for (LEX_CSTRING &c : fk.foreign_fields)
 | |
|         cols.push_back(new (root) Key_part_spec(&c, 0));
 | |
|       for (LEX_CSTRING &c : fk.referenced_fields)
 | |
|         ref_cols.push_back(new (root) Key_part_spec(&c, 0));
 | |
|       auto key= new (root)
 | |
|         Foreign_key(fk.foreign_id, &cols, fk.foreign_id, fk.referenced_db,
 | |
|           fk.referenced_table, &ref_cols, fk.delete_method, fk.update_method,
 | |
|           Foreign_key::FK_MATCH_UNDEF, DDL_options());
 | |
|       key->old= true;
 | |
|       new_key_list.push_back(key, root);
 | |
|     }
 | |
|   }
 | |
|   {
 | |
|     Key *key;
 | |
|     while ((key=key_it++))			// Add new keys
 | |
|     {
 | |
|       if (key->type == Key::FOREIGN_KEY &&
 | |
|           ((Foreign_key *)key)->validate(new_create_list))
 | |
|         goto err;
 | |
|       new_key_list.push_back(key, root);
 | |
|       if (key->name.str &&
 | |
|           key->name.streq(primary_key_name))
 | |
|       {
 | |
| 	my_error(ER_WRONG_NAME_FOR_INDEX, MYF(0), key->name.str);
 | |
|         goto err;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (table->s->period.name)
 | |
|   {
 | |
|     drop_it.rewind();
 | |
|     Alter_drop *drop;
 | |
|     for (bool found= false; !found && (drop= drop_it++); )
 | |
|     {
 | |
|       found= drop->type == Alter_drop::PERIOD &&
 | |
|              table->s->period.name.streq(drop->name);
 | |
|     }
 | |
| 
 | |
|     if (drop)
 | |
|     {
 | |
|       drop_period= true;
 | |
|       drop_it.remove();
 | |
|     }
 | |
|     else if (create_info->period_info.is_set() && table->s->period.name)
 | |
|     {
 | |
|       my_error(ER_MORE_THAN_ONE_PERIOD, MYF(0));
 | |
|       goto err;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       create_info->period_info.set_period(period_start_name, period_end_name);
 | |
|       create_info->period_info.name= table->s->period.name;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Add all table level constraints which are not in the drop list */
 | |
|   if (table->s->table_check_constraints)
 | |
|   {
 | |
|     TABLE_SHARE *share= table->s;
 | |
| 
 | |
|     for (uint i= share->field_check_constraints;
 | |
|          i < share->table_check_constraints ; i++)
 | |
|     {
 | |
|       Virtual_column_info *check= table->check_constraints[i];
 | |
|       Alter_drop *drop;
 | |
|       bool keep= true;
 | |
|       drop_it.rewind();
 | |
|       while ((drop=drop_it++))
 | |
|       {
 | |
|         if (drop->type == Alter_drop::CHECK_CONSTRAINT &&
 | |
|             check->name.streq(drop->name))
 | |
|         {
 | |
|           drop_it.remove();
 | |
|           keep= false;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (share->period.constr_name.streq(check->name))
 | |
|       {
 | |
|         if (!drop_period && !keep)
 | |
|         {
 | |
|           my_error(ER_PERIOD_CONSTRAINT_DROP, MYF(0), check->name.str,
 | |
|                    share->period.name.str);
 | |
|           goto err;
 | |
|         }
 | |
|         keep= keep && !drop_period;
 | |
| 
 | |
|         DBUG_ASSERT(create_info->period_info.constr == NULL || drop_period);
 | |
| 
 | |
|         if (keep)
 | |
|         {
 | |
|           Item *expr_copy= check->expr->get_copy(thd);
 | |
|           check= new Virtual_column_info();
 | |
|           check->name= share->period.constr_name;
 | |
|           check->automatic_name= true;
 | |
|           check->expr= expr_copy;
 | |
|           create_info->period_info.constr= check;
 | |
|         }
 | |
|       }
 | |
|       /* see if the constraint depends on *only* on dropped fields */
 | |
|       if (keep && dropped_fields)
 | |
|       {
 | |
|         table->default_column_bitmaps();
 | |
|         bitmap_clear_all(table->read_set);
 | |
|         check->expr->walk(&Item::register_field_in_read_map, 1, 0);
 | |
|         if (bitmap_is_subset(table->read_set, dropped_fields))
 | |
|           keep= false;
 | |
|         else if (bitmap_is_overlapping(dropped_fields, table->read_set))
 | |
|         {
 | |
|           bitmap_intersect(table->read_set, dropped_fields);
 | |
|           uint field_nr= bitmap_get_first_set(table->read_set);
 | |
|           my_error(ER_BAD_FIELD_ERROR, MYF(0),
 | |
|                    table->field[field_nr]->field_name.str, "CHECK");
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
|       if (keep)
 | |
|       {
 | |
|         if (alter_info->flags & ALTER_RENAME_COLUMN)
 | |
|         {
 | |
|           check->expr->walk(&Item::rename_fields_processor, 1,
 | |
|                             &column_rename_param);
 | |
|           // Force reopen because new column name is on root
 | |
|           table->mark_table_for_reopen();
 | |
|         }
 | |
|         new_constraint_list.push_back(check, root);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!alter_info->check_constraint_list.is_empty())
 | |
|   {
 | |
|     /* Check the table FOREIGN KEYs for name duplications. */
 | |
|     FOREIGN_KEY_INFO *f_key;
 | |
|     List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_list);
 | |
|     while ((f_key= fk_key_it++))
 | |
|     {
 | |
|       List_iterator_fast<Virtual_column_info>
 | |
|         c_it(alter_info->check_constraint_list);
 | |
|       Virtual_column_info *check;
 | |
|       while ((check= c_it++))
 | |
|       {
 | |
|         if (!check->name.length || check->automatic_name)
 | |
|           continue;
 | |
| 
 | |
|         if (check->name.streq(*f_key->foreign_id))
 | |
|         {
 | |
|           my_error(ER_DUP_CONSTRAINT_NAME, MYF(0), "CHECK", check->name.str);
 | |
|           goto err;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /* Add new constraints */
 | |
|   new_constraint_list.append(&alter_info->check_constraint_list);
 | |
| 
 | |
|   if (alter_info->drop_list.elements)
 | |
|   {
 | |
|     Alter_drop *drop;
 | |
|     drop_it.rewind();
 | |
|     while ((drop=drop_it++)) {
 | |
|       switch (drop->type) {
 | |
|       case Alter_drop::KEY:
 | |
|       case Alter_drop::COLUMN:
 | |
|       case Alter_drop::CHECK_CONSTRAINT:
 | |
|       case Alter_drop::PERIOD:
 | |
|         my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop->type_name(),
 | |
|                  drop->name.str);
 | |
|         goto err;
 | |
|       case Alter_drop::FOREIGN_KEY:
 | |
|         // Leave the DROP FOREIGN KEY names in the alter_info->drop_list.
 | |
|         /* If this is DROP FOREIGN KEY without IF EXIST,
 | |
|         we can now check does it exists and if not report a error. */
 | |
|         if (!drop->drop_if_exists)
 | |
|         {
 | |
|           List <FOREIGN_KEY_INFO> fk_child_key_list;
 | |
|           table->file->get_foreign_key_list(thd, &fk_child_key_list);
 | |
|           if (fk_child_key_list.is_empty())
 | |
|           {
 | |
| 	fk_not_found:
 | |
|             my_error(ER_CANT_DROP_FIELD_OR_KEY, MYF(0), drop->type_name(),
 | |
|                      drop->name.str);
 | |
|             goto err;
 | |
|           }
 | |
|           List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
 | |
|           while (FOREIGN_KEY_INFO *f_key= fk_key_it++)
 | |
|           {
 | |
|             if (Lex_ident_column(*f_key->foreign_id).streq(drop->name))
 | |
|               goto fk_found;
 | |
|           }
 | |
|           goto fk_not_found;
 | |
|         fk_found:
 | |
|           break;
 | |
|         }
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (rename_key_list.elements)
 | |
|   {
 | |
|     my_error(ER_KEY_DOES_NOT_EXISTS, MYF(0), rename_key_list.head()->old_name.str,
 | |
|              table->s->table_name.str);
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   if (alter_index_ignorability_list.elements)
 | |
|   {
 | |
|     my_error(ER_KEY_DOES_NOT_EXISTS, MYF(0),
 | |
|              alter_index_ignorability_list.head()->name().str,
 | |
|              table->s->table_name.str);
 | |
|     goto err;
 | |
|   }
 | |
| 
 | |
|   if (!create_info->comment.str)
 | |
|   {
 | |
|     create_info->comment.str= table->s->comment.str;
 | |
|     create_info->comment.length= table->s->comment.length;
 | |
|   }
 | |
| 
 | |
|   table->file->update_create_info(create_info);
 | |
|   if ((create_info->table_options &
 | |
|        (HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS)) ||
 | |
|       (used_fields & HA_CREATE_USED_PACK_KEYS))
 | |
|     db_create_options&= ~(HA_OPTION_PACK_KEYS | HA_OPTION_NO_PACK_KEYS);
 | |
|   if ((create_info->table_options &
 | |
|        (HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT)) ||
 | |
|       (used_fields & HA_CREATE_USED_STATS_PERSISTENT))
 | |
|     db_create_options&= ~(HA_OPTION_STATS_PERSISTENT | HA_OPTION_NO_STATS_PERSISTENT);
 | |
| 
 | |
|   if (create_info->table_options &
 | |
|       (HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM))
 | |
|     db_create_options&= ~(HA_OPTION_CHECKSUM | HA_OPTION_NO_CHECKSUM);
 | |
|   if (create_info->table_options &
 | |
|       (HA_OPTION_DELAY_KEY_WRITE | HA_OPTION_NO_DELAY_KEY_WRITE))
 | |
|     db_create_options&= ~(HA_OPTION_DELAY_KEY_WRITE |
 | |
| 			  HA_OPTION_NO_DELAY_KEY_WRITE);
 | |
|   create_info->table_options|= db_create_options;
 | |
| 
 | |
|   if (table->s->tmp_table)
 | |
|     create_info->options|=HA_LEX_CREATE_TMP_TABLE;
 | |
| 
 | |
|   rc= FALSE;
 | |
|   alter_info->create_list.swap(new_create_list);
 | |
|   alter_info->key_list.swap(new_key_list);
 | |
|   alter_info->check_constraint_list.swap(new_constraint_list);
 | |
| err:
 | |
|   DBUG_RETURN(rc);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Get Create_field object for newly created table by its name
 | |
|   in the old version of table.
 | |
| 
 | |
|   @param alter_info  Alter_info describing newly created table.
 | |
|   @param old_name    Name of field in old table.
 | |
| 
 | |
|   @returns Pointer to Create_field object, NULL - if field is
 | |
|            not present in new version of table.
 | |
| */
 | |
| 
 | |
| static Create_field *get_field_by_old_name(Alter_info *alter_info,
 | |
|                                            const LEX_CSTRING &old_name)
 | |
| {
 | |
|   List_iterator_fast<Create_field> new_field_it(alter_info->create_list);
 | |
|   Create_field *new_field;
 | |
| 
 | |
|   while ((new_field= new_field_it++))
 | |
|   {
 | |
|     if (new_field->field &&
 | |
|         new_field->field->field_name.streq(old_name))
 | |
|       break;
 | |
|   }
 | |
|   return new_field;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** Type of change to foreign key column, */
 | |
| 
 | |
| enum fk_column_change_type
 | |
| {
 | |
|   FK_COLUMN_NO_CHANGE, FK_COLUMN_DATA_CHANGE,
 | |
|   FK_COLUMN_RENAMED, FK_COLUMN_DROPPED, FK_COLUMN_NOT_NULL
 | |
| };
 | |
| 
 | |
| /**
 | |
|   Check that ALTER TABLE's changes on columns of a foreign key are allowed.
 | |
| 
 | |
|   @param[in]   thd              Thread context.
 | |
|   @param[in]   table            table to be altered
 | |
|   @param[in]   alter_info       Alter_info describing changes to be done
 | |
|                                 by ALTER TABLE.
 | |
|   @param[in]   fk               Foreign key information.
 | |
|   @param[out]  bad_column_name  Name of field on which ALTER TABLE tries to
 | |
|                                 do prohibited operation.
 | |
|   @param[in]   referenced       Check the referenced fields
 | |
| 
 | |
|   @note This function takes into account value of @@foreign_key_checks
 | |
|         setting.
 | |
| 
 | |
|   @retval FK_COLUMN_NO_CHANGE    No significant changes are to be done on
 | |
|                                  foreign key columns.
 | |
|   @retval FK_COLUMN_DATA_CHANGE  ALTER TABLE might result in value
 | |
|                                  change in foreign key column.
 | |
|   @retval FK_COLUMN_RENAMED      Foreign key column is renamed.
 | |
|   @retval FK_COLUMN_DROPPED      Foreign key column is dropped.
 | |
|   @retval FK_COLUMN_NOT_NULL     Foreign key column cannot be null
 | |
|                                  if ON...SET NULL or ON UPDATE
 | |
|                                  CASCADE conflicts with NOT NULL
 | |
| */
 | |
| 
 | |
| static enum fk_column_change_type
 | |
| fk_check_column_changes(THD *thd, const TABLE *table,
 | |
|                         Alter_info *alter_info,
 | |
|                         FOREIGN_KEY_INFO *fk,
 | |
|                         const char **bad_column_name,
 | |
|                         bool referenced=false)
 | |
| {
 | |
|   List<LEX_CSTRING> &fk_columns= referenced
 | |
|     ? fk->referenced_fields
 | |
|     : fk->foreign_fields;
 | |
|   List_iterator_fast<LEX_CSTRING> column_it(fk_columns);
 | |
|   LEX_CSTRING *column;
 | |
|   int n_col= 0;
 | |
| 
 | |
|   *bad_column_name= NULL;
 | |
|   enum fk_column_change_type result= FK_COLUMN_NO_CHANGE;
 | |
|   bool strict_mode= thd->is_strict_mode();
 | |
| 
 | |
|   while ((column= column_it++))
 | |
|   {
 | |
|     Create_field *new_field= get_field_by_old_name(alter_info, *column);
 | |
| 
 | |
|     if (new_field)
 | |
|     {
 | |
|       Field *old_field= new_field->field;
 | |
| 
 | |
|       if (!old_field->field_name.streq(new_field->field_name))
 | |
|       {
 | |
|         /*
 | |
|           Copy algorithm doesn't support proper renaming of columns in
 | |
|           the foreign key yet. At the moment we lack API which will tell
 | |
|           SE that foreign keys should be updated to use new name of column
 | |
|           like it happens in case of in-place algorithm.
 | |
|         */
 | |
|         result= FK_COLUMN_RENAMED;
 | |
|         goto func_exit;
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|         Field_{num|decimal}::is_equal evaluates to IS_EQUAL_NO where
 | |
|         the new_field adds an AUTO_INCREMENT flag on a column due to a
 | |
| 	limitation in MyISAM/ARIA. For the purposes of FK determination
 | |
|         it doesn't matter if AUTO_INCREMENT is there or not.
 | |
|       */
 | |
|       const uint flags= new_field->flags;
 | |
|       new_field->flags&= ~AUTO_INCREMENT_FLAG;
 | |
|       const bool equal_result= old_field->is_equal(*new_field);
 | |
|       new_field->flags= flags;
 | |
|       const bool old_field_not_null= old_field->flags & NOT_NULL_FLAG;
 | |
|       const bool new_field_not_null= new_field->flags & NOT_NULL_FLAG;
 | |
| 
 | |
|       if ((equal_result == IS_EQUAL_NO))
 | |
|       {
 | |
|         /*
 | |
|           Column in a FK has changed significantly and it
 | |
|           may break referential integrity.
 | |
|         */
 | |
|         result= FK_COLUMN_DATA_CHANGE;
 | |
|         goto func_exit;
 | |
|       }
 | |
| 
 | |
|       if (strict_mode && old_field_not_null != new_field_not_null)
 | |
|       {
 | |
|         if (referenced && !new_field_not_null)
 | |
|         {
 | |
|           /*
 | |
|             Don't allow referenced column to change from
 | |
|             NOT NULL to NULL when foreign key relation is
 | |
|             ON UPDATE CASCADE and the referencing column
 | |
|             is declared as NOT NULL
 | |
|           */
 | |
|           if (fk->update_method == FK_OPTION_CASCADE &&
 | |
|               !fk->is_nullable(false, n_col))
 | |
|           {
 | |
|             result= FK_COLUMN_DATA_CHANGE;
 | |
|             goto func_exit;
 | |
|           }
 | |
|         }
 | |
|         else if (!referenced && new_field_not_null)
 | |
|         {
 | |
|           /*
 | |
|             Don't allow the foreign column to change
 | |
|             from NULL to NOT NULL when foreign key type is
 | |
|             1) UPDATE SET NULL
 | |
|             2) DELETE SET NULL
 | |
|             3) UPDATE CASCADE and referenced column is declared as NULL
 | |
| 	  */
 | |
|           if (fk->update_method == FK_OPTION_SET_NULL ||
 | |
|               fk->delete_method == FK_OPTION_SET_NULL ||
 | |
|               (fk->update_method == FK_OPTION_CASCADE &&
 | |
|                fk->referenced_key_name &&
 | |
|                fk->is_nullable(true, n_col)))
 | |
|           {
 | |
|             result= FK_COLUMN_NOT_NULL;
 | |
|             goto func_exit;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         Column in FK was dropped. Most likely this will break
 | |
|         integrity constraints of InnoDB data-dictionary (and thus
 | |
|         InnoDB will emit an error), so we prohibit this right away
 | |
|         even if foreign_key_checks are off.
 | |
|         This also includes a rare case when another field replaces
 | |
|         field being dropped since it is easy to break referential
 | |
|         integrity in this case.
 | |
|       */
 | |
|       result= FK_COLUMN_DROPPED;
 | |
|       goto func_exit;
 | |
|     }
 | |
|     n_col++;
 | |
|   }
 | |
|   return FK_COLUMN_NO_CHANGE;
 | |
| func_exit:
 | |
|   *bad_column_name= column->str;
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Check if ALTER TABLE we are about to execute using COPY algorithm
 | |
|   is not supported as it might break referential integrity.
 | |
| 
 | |
|   @note If foreign_key_checks is disabled (=0), we allow to break
 | |
|         referential integrity. But we still disallow some operations
 | |
|         like dropping or renaming columns in foreign key since they
 | |
|         are likely to break consistency of InnoDB data-dictionary
 | |
|         and thus will end-up in error anyway.
 | |
| 
 | |
|   @param[in]  thd          Thread context.
 | |
|   @param[in]  table        Table to be altered.
 | |
|   @param[in]  alter_info   Lists of fields, keys to be changed, added
 | |
|                            or dropped.
 | |
|   @param[out] alter_ctx    ALTER TABLE runtime context.
 | |
|                            Alter_table_ctx::fk_error_if_delete flag
 | |
|                            is set if deletion during alter can break
 | |
|                            foreign key integrity.
 | |
| 
 | |
|   @retval false  Success.
 | |
|   @retval true   Error, ALTER - tries to do change which is not compatible
 | |
|                  with foreign key definitions on the table.
 | |
| */
 | |
| 
 | |
| static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table,
 | |
|                                         Alter_info *alter_info,
 | |
|                                         Alter_table_ctx *alter_ctx)
 | |
| {
 | |
|   List <FOREIGN_KEY_INFO> fk_parent_key_list;
 | |
|   List <FOREIGN_KEY_INFO> fk_child_key_list;
 | |
|   FOREIGN_KEY_INFO *f_key;
 | |
| 
 | |
|   DBUG_ENTER("fk_prepare_copy_alter_table");
 | |
| 
 | |
|   table->file->get_parent_foreign_key_list(thd, &fk_parent_key_list);
 | |
| 
 | |
|   /* OOM when building list. */
 | |
|   if (unlikely(thd->is_error()))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     Remove from the list all foreign keys in which table participates as
 | |
|     parent which are to be dropped by this ALTER TABLE. This is possible
 | |
|     when a foreign key has the same table as child and parent.
 | |
|   */
 | |
|   List_iterator<FOREIGN_KEY_INFO> fk_parent_key_it(fk_parent_key_list);
 | |
| 
 | |
|   while ((f_key= fk_parent_key_it++))
 | |
|   {
 | |
|     Alter_drop *drop;
 | |
|     List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list);
 | |
| 
 | |
|     while ((drop= drop_it++))
 | |
|     {
 | |
|       /*
 | |
|         InnoDB treats foreign key names in case-insensitive fashion.
 | |
|         So we do it here too. For database and table name type of
 | |
|         comparison used depends on lower-case-table-names setting.
 | |
|         For l_c_t_n = 0 we use case-sensitive comparison, for
 | |
|         l_c_t_n > 0 modes case-insensitive comparison is used.
 | |
|       */
 | |
|       if ((drop->type == Alter_drop::FOREIGN_KEY) &&
 | |
|           drop->name.streq(*f_key->foreign_id) &&
 | |
|           table->s->db.streq(*f_key->foreign_db) &&
 | |
|           table->s->table_name.streq(*f_key->foreign_table))
 | |
|         fk_parent_key_it.remove();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     If there are FKs in which this table is parent which were not
 | |
|     dropped we need to prevent ALTER deleting rows from the table,
 | |
|     as it might break referential integrity. OTOH it is OK to do
 | |
|     so if foreign_key_checks are disabled.
 | |
|   */
 | |
|   if (!fk_parent_key_list.is_empty() &&
 | |
|       !(thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
 | |
|     alter_ctx->set_fk_error_if_delete_row(fk_parent_key_list.head());
 | |
| 
 | |
|   fk_parent_key_it.rewind();
 | |
|   while ((f_key= fk_parent_key_it++))
 | |
|   {
 | |
|     enum fk_column_change_type changes;
 | |
|     const char *bad_column_name;
 | |
| 
 | |
|     changes= fk_check_column_changes(thd, table, alter_info, f_key,
 | |
|                                      &bad_column_name, true);
 | |
| 
 | |
|     switch(changes)
 | |
|     {
 | |
|     case FK_COLUMN_NO_CHANGE:
 | |
|       /* No significant changes. We can proceed with ALTER! */
 | |
|       break;
 | |
|     case FK_COLUMN_DATA_CHANGE:
 | |
|     {
 | |
|       char buff[NAME_LEN*2+2];
 | |
|       strxnmov(buff, sizeof(buff)-1, f_key->foreign_db->str, ".",
 | |
|                f_key->foreign_table->str, NullS);
 | |
|       my_error(ER_FK_COLUMN_CANNOT_CHANGE_CHILD, MYF(0), bad_column_name,
 | |
|                f_key->foreign_id->str, buff);
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|     case FK_COLUMN_RENAMED:
 | |
|       my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
 | |
|                "ALGORITHM=COPY",
 | |
|                ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME),
 | |
|                "ALGORITHM=INPLACE");
 | |
|       DBUG_RETURN(true);
 | |
|     case FK_COLUMN_DROPPED:
 | |
|     {
 | |
|       StringBuffer<NAME_LEN*2+2> buff(system_charset_info);
 | |
|       LEX_CSTRING *db= f_key->foreign_db, *tbl= f_key->foreign_table;
 | |
| 
 | |
|       append_identifier(thd, &buff, db);
 | |
|       buff.append('.');
 | |
|       append_identifier(thd, &buff, tbl);
 | |
|       my_error(ER_FK_COLUMN_CANNOT_DROP_CHILD, MYF(0), bad_column_name,
 | |
|                f_key->foreign_id->str, buff.c_ptr());
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|     /* FK_COLUMN_NOT_NULL error happens only when changing
 | |
|     the foreign key column from NULL to NOT NULL */
 | |
|     case FK_COLUMN_NOT_NULL:
 | |
|     default:
 | |
|       DBUG_ASSERT(0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   table->file->get_foreign_key_list(thd, &fk_child_key_list);
 | |
| 
 | |
|   /* OOM when building list. */
 | |
|   if (unlikely(thd->is_error()))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     Remove from the list all foreign keys which are to be dropped
 | |
|     by this ALTER TABLE.
 | |
|   */
 | |
|   List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
 | |
| 
 | |
|   while ((f_key= fk_key_it++))
 | |
|   {
 | |
|     Alter_drop *drop;
 | |
|     List_iterator_fast<Alter_drop> drop_it(alter_info->drop_list);
 | |
| 
 | |
|     while ((drop= drop_it++))
 | |
|     {
 | |
|       /* Names of foreign keys in InnoDB are case-insensitive. */
 | |
|       if ((drop->type == Alter_drop::FOREIGN_KEY) &&
 | |
|           (Lex_ident_column(*f_key->foreign_id).streq(drop->name)))
 | |
|         fk_key_it.remove();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   fk_key_it.rewind();
 | |
|   while ((f_key= fk_key_it++))
 | |
|   {
 | |
|     enum fk_column_change_type changes;
 | |
|     const char *bad_column_name;
 | |
| 
 | |
|     changes= fk_check_column_changes(thd, table, alter_info, f_key,
 | |
|                                      &bad_column_name);
 | |
| 
 | |
|     switch(changes)
 | |
|     {
 | |
|     case FK_COLUMN_NO_CHANGE:
 | |
|       /* No significant changes. We can proceed with ALTER! */
 | |
|       break;
 | |
|     case FK_COLUMN_DATA_CHANGE:
 | |
|       my_error(ER_FK_COLUMN_CANNOT_CHANGE, MYF(0), bad_column_name,
 | |
|                f_key->foreign_id->str);
 | |
|       DBUG_RETURN(true);
 | |
|     case FK_COLUMN_RENAMED:
 | |
|       my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
 | |
|                "ALGORITHM=COPY",
 | |
|                ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_FK_RENAME),
 | |
|                "ALGORITHM=INPLACE");
 | |
|       DBUG_RETURN(true);
 | |
|     case FK_COLUMN_DROPPED:
 | |
|       my_error(ER_FK_COLUMN_CANNOT_DROP, MYF(0), bad_column_name,
 | |
|                f_key->foreign_id->str);
 | |
|       DBUG_RETURN(true);
 | |
|     case FK_COLUMN_NOT_NULL:
 | |
|       my_error(ER_FK_COLUMN_NOT_NULL, MYF(0), bad_column_name,
 | |
|                f_key->foreign_id->str);
 | |
|       DBUG_RETURN(true);
 | |
|     default:
 | |
|       DBUG_ASSERT(0);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Normally, an attempt to modify an FK parent table will cause
 | |
|     FK children to be prelocked, so the table-being-altered cannot
 | |
|     be modified by a cascade FK action, because ALTER holds a lock
 | |
|     and prelocking will wait.
 | |
| 
 | |
|     But if a new FK is being added by this very ALTER, then the target
 | |
|     table is not locked yet (it's a temporary table). So, we have to
 | |
|     lock FK parents explicitly.
 | |
|   */
 | |
|   if (alter_info->flags & ALTER_ADD_FOREIGN_KEY)
 | |
|   {
 | |
|     List_iterator<Key> fk_list_it(alter_info->key_list);
 | |
| 
 | |
|     while (Key *key= fk_list_it++)
 | |
|     {
 | |
|       if (key->type != Key::FOREIGN_KEY || key->old)
 | |
|         continue;
 | |
| 
 | |
|       Foreign_key *fk= static_cast<Foreign_key*>(key);
 | |
|       IdentBuffer<NAME_LEN> dbuf, tbuf;
 | |
|       LEX_CSTRING ref_db= fk->ref_db.str ? fk->ref_db : alter_ctx->new_db;
 | |
|       LEX_CSTRING ref_table= fk->ref_table;
 | |
|       MDL_request mdl_request;
 | |
| 
 | |
|       if (lower_case_table_names)
 | |
|       {
 | |
|         ref_db= dbuf.copy_casedn(ref_db).to_lex_cstring();
 | |
|         ref_table= tbuf.copy_casedn(ref_table).to_lex_cstring();
 | |
|       }
 | |
| 
 | |
|       MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE,
 | |
|                        ref_db.str, ref_table.str,
 | |
|                        MDL_SHARED_NO_WRITE, MDL_TRANSACTION);
 | |
|       if (thd->mdl_context.acquire_lock(&mdl_request,
 | |
|                                         thd->variables.lock_wait_timeout))
 | |
|         DBUG_RETURN(true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   Rename temporary table and/or turn indexes on/off without touching .FRM.
 | |
|   Its a variant of simple_rename_or_index_change() to be used exclusively
 | |
|   for temporary tables.
 | |
| 
 | |
|   @param thd            Thread handler
 | |
|   @param table_list     TABLE_LIST for the table to change
 | |
|   @param keys_onoff     ENABLE or DISABLE KEYS?
 | |
|   @param alter_ctx      ALTER TABLE runtime context.
 | |
| 
 | |
|   @return Operation status
 | |
|     @retval false           Success
 | |
|     @retval true            Failure
 | |
| */
 | |
| static bool
 | |
| simple_tmp_rename_or_index_change(THD *thd, TABLE_LIST *table_list,
 | |
|                                   Alter_info::enum_enable_or_disable keys_onoff,
 | |
|                                   Alter_table_ctx *alter_ctx)
 | |
| {
 | |
|   DBUG_ENTER("simple_tmp_rename_or_index_change");
 | |
| 
 | |
|   TABLE *table= table_list->table;
 | |
|   bool error= false;
 | |
| 
 | |
|   DBUG_ASSERT(table->s->tmp_table);
 | |
| 
 | |
|   if (keys_onoff != Alter_info::LEAVE_AS_IS)
 | |
|   {
 | |
|     THD_STAGE_INFO(thd, stage_manage_keys);
 | |
|     error= alter_table_manage_keys(table, table->file->indexes_are_disabled(),
 | |
|                                    keys_onoff);
 | |
|   }
 | |
| 
 | |
|   if (likely(!error) && alter_ctx->is_table_renamed())
 | |
|   {
 | |
|     THD_STAGE_INFO(thd, stage_rename);
 | |
| 
 | |
|     /*
 | |
|       If THD::rename_temporary_table() fails, there is no need to rename it
 | |
|       back to the original name (unlike the case for non-temporary tables),
 | |
|       as it was an allocation error and the table was not renamed.
 | |
|     */
 | |
|     error= thd->rename_temporary_table(table, &alter_ctx->new_db,
 | |
|                                        &alter_ctx->new_alias);
 | |
|   }
 | |
| 
 | |
|   if (likely(!error))
 | |
|   {
 | |
|     /*
 | |
|       Only replicate temporary tables that has already been logged to the
 | |
|       binary log.
 | |
|     */
 | |
|     if (table->s->table_creation_was_logged)
 | |
|       error= write_bin_log(thd, true, thd->query(), thd->query_length()) != 0;
 | |
|     if (likely(!error))
 | |
|       my_ok(thd);
 | |
|   }
 | |
| 
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Rename table and/or turn indexes on/off without touching .FRM
 | |
| 
 | |
|   @param thd            Thread handler
 | |
|   @param table_list     TABLE_LIST for the table to change
 | |
|   @param keys_onoff     ENABLE or DISABLE KEYS?
 | |
|   @param alter_ctx      ALTER TABLE runtime context.
 | |
| 
 | |
|   @return Operation status
 | |
|     @retval false           Success
 | |
|     @retval true            Failure
 | |
| 
 | |
|   @notes
 | |
|     Normally with ALTER TABLE we roll forward as soon as data is copied
 | |
|     or new table is committed.  For an ALTER TABLE that only does a RENAME,
 | |
|     we will roll back unless the RENAME fully completes.
 | |
|     If we crash while using enable/disable keys, this may have completed
 | |
|     and will not be rolled back.
 | |
| */
 | |
| 
 | |
| static bool
 | |
| simple_rename_or_index_change(THD *thd, TABLE_LIST *table_list,
 | |
|                               Alter_info::enum_enable_or_disable keys_onoff,
 | |
|                               TRIGGER_RENAME_PARAM *trigger_param,
 | |
|                               Alter_table_ctx *alter_ctx)
 | |
| {
 | |
|   TABLE *table= table_list->table;
 | |
|   MDL_ticket *mdl_ticket= table->mdl_ticket;
 | |
|   DDL_LOG_STATE ddl_log_state;
 | |
|   LEX_CSTRING storage_engine;
 | |
|   LEX_CUSTRING table_version;
 | |
|   uchar table_version_buff[MY_UUID_SIZE];
 | |
|   char storage_engine_buff[NAME_LEN];
 | |
|   int error= 0;
 | |
|   bool partitioned;
 | |
|   enum ha_extra_function extra_func= thd->locked_tables_mode
 | |
|                                        ? HA_EXTRA_NOT_USED
 | |
|                                        : HA_EXTRA_FORCE_REOPEN;
 | |
|   DBUG_ENTER("simple_rename_or_index_change");
 | |
|   bzero(&ddl_log_state, sizeof(ddl_log_state));
 | |
| 
 | |
|   table_version.str= table_version_buff;
 | |
|   storage_engine.str= storage_engine_buff;
 | |
|   if ((table_version.length= table->s->tabledef_version.length))
 | |
|     memcpy((char*) table_version.str, table->s->tabledef_version.str,
 | |
|            table_version.length);
 | |
|   partitioned= table->file->partition_engine();
 | |
|   storage_engine.length= (strmake((char*) storage_engine.str,
 | |
|                                   table->file->real_table_type(),
 | |
|                                   sizeof(storage_engine_buff)-1) -
 | |
|                           storage_engine.str);
 | |
| 
 | |
| 
 | |
|   if (keys_onoff != Alter_info::LEAVE_AS_IS)
 | |
|   {
 | |
|     if (wait_while_table_is_used(thd, table, extra_func))
 | |
|       DBUG_RETURN(true);
 | |
| 
 | |
|     // It's now safe to take the table level lock.
 | |
|     if (lock_tables(thd, table_list, alter_ctx->tables_opened, 0))
 | |
|       DBUG_RETURN(true);
 | |
| 
 | |
|     THD_STAGE_INFO(thd, stage_manage_keys);
 | |
|     error= alter_table_manage_keys(table,
 | |
|                                    table->file->indexes_are_disabled(),
 | |
|                                    keys_onoff);
 | |
|     if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|     {
 | |
|       backup_log_info ddl_log;
 | |
|       bzero(&ddl_log, sizeof(ddl_log));
 | |
|       ddl_log.query= { C_STRING_WITH_LEN("CHANGE_INDEX") };
 | |
|       ddl_log.org_storage_engine_name= storage_engine;
 | |
|       ddl_log.org_partitioned=  partitioned;
 | |
|       ddl_log.org_database=     table_list->table->s->db;
 | |
|       ddl_log.org_table=        table_list->table->s->table_name;
 | |
|       ddl_log.org_table_id=     table_version;
 | |
|       backup_log_ddl(&ddl_log);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (likely(!error) && alter_ctx->is_table_renamed())
 | |
|   {
 | |
|     THD_STAGE_INFO(thd, stage_rename);
 | |
|     handlerton *old_db_type= table->s->db_type();
 | |
| 
 | |
|     /*
 | |
|       Then do a 'simple' rename of the table. First we need to close all
 | |
|       instances of 'source' table.
 | |
|       Note that if wait_while_table_is_used() returns error here (i.e. if
 | |
|       this thread was killed) then it must be that previous step of
 | |
|       simple rename did nothing and therefore we can safely return
 | |
|       without additional clean-up.
 | |
|     */
 | |
|     if (wait_while_table_is_used(thd, table, extra_func))
 | |
|       DBUG_RETURN(true);
 | |
|     close_all_tables_for_name(thd, table->s, HA_EXTRA_PREPARE_FOR_RENAME,
 | |
|                               NULL);
 | |
| 
 | |
|     (void) ddl_log_rename_table(&ddl_log_state, old_db_type,
 | |
|                                 &alter_ctx->db, &alter_ctx->table_name,
 | |
|                                 &alter_ctx->new_db, &alter_ctx->new_alias);
 | |
|     if (mysql_rename_table(old_db_type, &alter_ctx->db, &alter_ctx->table_name,
 | |
|                            &alter_ctx->new_db, &alter_ctx->new_alias,
 | |
|                            &table_version, QRMT_DEFAULT))
 | |
|       error= -1;
 | |
|     if (!error)
 | |
|       ddl_log_update_phase(&ddl_log_state, DDL_RENAME_PHASE_TRIGGER);
 | |
|     debug_crash_here("ddl_log_alter_before_rename_triggers");
 | |
|     if (!error &&
 | |
|         Table_triggers_list::change_table_name(thd, trigger_param,
 | |
|                                                &alter_ctx->db,
 | |
|                                                &alter_ctx->alias,
 | |
|                                                &alter_ctx->table_name,
 | |
|                                                &alter_ctx->new_db,
 | |
|                                                &alter_ctx->new_alias))
 | |
|     {
 | |
|       (void) mysql_rename_table(old_db_type,
 | |
|                                 &alter_ctx->new_db, &alter_ctx->new_alias,
 | |
|                                 &alter_ctx->db, &alter_ctx->table_name,
 | |
|                                 &table_version,
 | |
|                                 QRMT_DEFAULT | NO_FK_CHECKS);
 | |
|       ddl_log_disable_entry(&ddl_log_state);
 | |
|       error= -1;
 | |
|     }
 | |
|     /*
 | |
|       Update stat tables last. This is to be able to handle rename of
 | |
|       a stat table.
 | |
|     */
 | |
|     if (error == 0)
 | |
|       (void) rename_table_in_stat_tables(thd, &alter_ctx->db,
 | |
|                                          &alter_ctx->table_name,
 | |
|                                          &alter_ctx->new_db,
 | |
|                                          &alter_ctx->new_alias);
 | |
|     debug_crash_here("ddl_log_alter_after_rename_triggers");
 | |
|   }
 | |
| 
 | |
|   if (likely(!error))
 | |
|   {
 | |
|     thd->binlog_xid= thd->query_id;
 | |
|     ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
 | |
|     error= write_bin_log(thd, TRUE, thd->query(), thd->query_length());
 | |
|     thd->binlog_xid= 0;
 | |
|     if (likely(!error))
 | |
|       my_ok(thd);
 | |
|   }
 | |
|   ddl_log_complete(&ddl_log_state);
 | |
|   table_list->table= NULL;                    // For query cache
 | |
|   query_cache_invalidate3(thd, table_list, 0);
 | |
| 
 | |
|   if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
 | |
|        thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES))
 | |
|   {
 | |
|     /*
 | |
|       Under LOCK TABLES we should adjust meta-data locks before finishing
 | |
|       statement. Otherwise we can rely on them being released
 | |
|       along with the implicit commit.
 | |
|     */
 | |
|     if (alter_ctx->is_table_renamed())
 | |
|       thd->mdl_context.release_all_locks_for_name(mdl_ticket);
 | |
|     else
 | |
|       mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
 | |
|   }
 | |
|   DBUG_RETURN(error != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void cleanup_table_after_inplace_alter_keep_files(TABLE *table)
 | |
| {
 | |
|   TABLE_SHARE *share= table->s;
 | |
|   closefrm(table);
 | |
|   free_table_share(share);
 | |
| }
 | |
| 
 | |
| 
 | |
| static void cleanup_table_after_inplace_alter(TABLE *table)
 | |
| {
 | |
|   table->file->ha_create_partitioning_metadata(table->s->normalized_path.str, 0,
 | |
|                                                CHF_DELETE_FLAG);
 | |
|   deletefrm(table->s->normalized_path.str);
 | |
|   cleanup_table_after_inplace_alter_keep_files(table);
 | |
| }
 | |
| 
 | |
| 
 | |
| static int create_table_for_inplace_alter(THD *thd,
 | |
|                                           const Alter_table_ctx &alter_ctx,
 | |
|                                           LEX_CUSTRING *frm,
 | |
|                                           TABLE_SHARE *share,
 | |
|                                           TABLE *table)
 | |
| {
 | |
|   init_tmp_table_share(thd, share, alter_ctx.new_db.str, 0,
 | |
|                        alter_ctx.new_name.str, alter_ctx.get_tmp_path(), true);
 | |
|   if (share->init_from_binary_frm_image(thd, true, frm->str, frm->length) ||
 | |
|       open_table_from_share(thd, share, &alter_ctx.new_name, 0,
 | |
|                             EXTRA_RECORD, thd->open_options,
 | |
|                             table, false))
 | |
|   {
 | |
|     free_table_share(share);
 | |
|     deletefrm(alter_ctx.get_tmp_path());
 | |
|     return 1;
 | |
|   }
 | |
|   if (table->internal_tables && open_and_lock_internal_tables(table, false))
 | |
|   {
 | |
|     cleanup_table_after_inplace_alter(table);
 | |
|     return 1;
 | |
|   }
 | |
|   return 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   log query if slave thread and send my_ok()
 | |
| 
 | |
|   Help function for mysql_alter_table()
 | |
| */
 | |
| 
 | |
| static bool log_and_ok(THD *thd)
 | |
| {
 | |
|   if (thd->slave_thread &&
 | |
|       write_bin_log(thd, true, thd->query(), thd->query_length()))
 | |
|     return(true);
 | |
|   my_ok(thd);
 | |
|   return(0);
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Wait for master to send result of Alter table.
 | |
|   Returns
 | |
|      true    when Rollback is decided
 | |
|      false   otherwise
 | |
| */
 | |
| static bool wait_for_master(THD *thd)
 | |
| {
 | |
| #ifdef HAVE_REPLICATION
 | |
|   start_alter_info* info= thd->rgi_slave->sa_info;
 | |
|   Master_info *mi= thd->rgi_slave->rli->mi;
 | |
| 
 | |
|   DBUG_ASSERT(info);
 | |
|   DBUG_ASSERT(info->state != start_alter_state::INVALID);
 | |
|   DBUG_ASSERT(mi);
 | |
| 
 | |
|   mysql_mutex_lock(&mi->start_alter_lock);
 | |
| 
 | |
|   DBUG_ASSERT(!info->direct_commit_alter ||
 | |
|               info->state == start_alter_state::ROLLBACK_ALTER);
 | |
| 
 | |
|   while (info->state == start_alter_state::REGISTERED)
 | |
|   {
 | |
|     mysql_cond_wait(&info->start_alter_cond, &mi->start_alter_lock);
 | |
|   }
 | |
|   if (info->state == start_alter_state::ROLLBACK_ALTER)
 | |
|   {
 | |
|     /*
 | |
|       SA thread will not give error , We will set the error in info->error
 | |
|       and then RA worker will output the error
 | |
|       We can modify the info->error without taking mutex, because CA worker
 | |
|       will be waiting on ::COMPLETED wait condition
 | |
|     */
 | |
|     if(thd->is_error())
 | |
|     {
 | |
|       info->error= thd->get_stmt_da()->sql_errno();
 | |
|       thd->clear_error();
 | |
|       thd->reset_killed();
 | |
|     }
 | |
|   }
 | |
|   mysql_mutex_unlock(&mi->start_alter_lock);
 | |
| 
 | |
|   return info->state == start_alter_state::ROLLBACK_ALTER;
 | |
| #else
 | |
|   return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| #ifdef HAVE_REPLICATION
 | |
| /**
 | |
|   In this function, we are going to change info->state to ::COMPLETED.
 | |
|   This means we are messaging CA/RA worker that we have binlogged, so our
 | |
|   here is finished.
 | |
| 
 | |
|   @param thd                Thread handle
 | |
|   @param start_alter_state  ALTER replication execution context
 | |
|   @param mi                 Master_info of the replication source
 | |
| */
 | |
| static void alter_committed(THD *thd, start_alter_info* info, Master_info *mi)
 | |
| {
 | |
|   start_alter_state tmp= info->state;
 | |
|   mysql_mutex_lock(&mi->start_alter_lock);
 | |
|   info->state= start_alter_state::COMPLETED;
 | |
|   mysql_cond_broadcast(&info->start_alter_cond);
 | |
|   mysql_mutex_unlock(&mi->start_alter_lock);
 | |
|   if (tmp == start_alter_state::ROLLBACK_ALTER)
 | |
|   {
 | |
|     thd->clear_error();
 | |
|     thd->reset_killed();
 | |
|   }
 | |
| }
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|   process_master_state:- process the info->state recieved from master
 | |
|   We will comapre master state with  alter_result
 | |
|     In the case of ROLLBACK_ALTER alter_result > 0
 | |
|     In the case of COMMIT_ALTER alter_result == 0
 | |
|   if the condition is not satisfied we will report error and
 | |
|   Return 1. Make sure wait_for_master is called before calling this function
 | |
|   This function should be called only at the write binlog time of commit/
 | |
|   rollback alter. We will alter_committed if everything is fine.
 | |
| 
 | |
|   @param thd                Thread handle.
 | |
|   @param alter_result       Result of execution.
 | |
|   @param[in/out]
 | |
|          start_alter_id     Start Alter identifier or zero,
 | |
|                             it is reset to zero.
 | |
|   @param if_exists          True indicates the binary logging of the query
 | |
|                             should be done with "if exists" option.
 | |
|   @retval 1 error
 | |
|   @retval 0 Ok
 | |
| */
 | |
| static int process_master_state(THD *thd, int alter_result,
 | |
|                                 uint64 &start_alter_id, bool if_exists)
 | |
| {
 | |
| #ifdef HAVE_REPLICATION
 | |
|   start_alter_info *info= thd->rgi_slave->sa_info;
 | |
|   bool partial_alter= false;
 | |
| 
 | |
|   if (info->state == start_alter_state::INVALID)
 | |
|   {
 | |
|     /* the caller has not yet called SA logging nor wait for master decision */
 | |
|     if (!write_bin_log_start_alter(thd, partial_alter, start_alter_id,
 | |
|                                    if_exists))
 | |
|       wait_for_master(thd);
 | |
| 
 | |
|     DBUG_ASSERT(!partial_alter);
 | |
|   }
 | |
| 
 | |
|   /* this function shouldn't be called twice */
 | |
|   DBUG_ASSERT(start_alter_id);
 | |
| 
 | |
|   start_alter_id= 0;
 | |
|   if ((info->state == start_alter_state::ROLLBACK_ALTER && alter_result >= 0)
 | |
|       || (info->state == start_alter_state::COMMIT_ALTER && !alter_result))
 | |
|   {
 | |
|     alter_committed(thd, info, thd->rgi_slave->rli->mi);
 | |
|     return 0;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     thd->is_slave_error= 1;
 | |
|     return 1;
 | |
|   }
 | |
| #else
 | |
|   return 0;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| /*
 | |
|   Returns a (global unique) identifier of START Alter when slave applier
 | |
|   executes mysql_alter_table().
 | |
|   In non-slave context it is zero.
 | |
| */
 | |
| static uint64 get_start_alter_id(THD *thd)
 | |
| {
 | |
|   DBUG_ASSERT(!(thd->rgi_slave &&
 | |
|                 (thd->rgi_slave->gtid_ev_flags_extra &
 | |
|                  Gtid_log_event::FL_START_ALTER_E1)) ||
 | |
|               !thd->rgi_slave->direct_commit_alter);
 | |
|   return
 | |
|     thd->rgi_slave &&
 | |
|     (thd->rgi_slave->gtid_ev_flags_extra & Gtid_log_event::FL_START_ALTER_E1) ?
 | |
|     thd->variables.gtid_seq_no : 0;
 | |
| }
 | |
| 
 | |
| 
 | |
| static
 | |
| bool online_alter_check_autoinc(const THD *thd, const Alter_info *alter_info,
 | |
|                                 const TABLE *table)
 | |
| {
 | |
|   /*
 | |
|     We can't go online, if all of the following is presented:
 | |
|     * Autoinc is added to existing field
 | |
|     * Disabled NO_AUTO_VALUE_ON_ZERO
 | |
|     * No non-nullable unique key in the old table, that has all the key parts
 | |
|       remaining unchanged.
 | |
|   */
 | |
| 
 | |
|   // Exit earlier when possible
 | |
|   if (thd->variables.sql_mode & MODE_NO_AUTO_VALUE_ON_ZERO)
 | |
|     return true;
 | |
|   if ((alter_info->flags | ALTER_CHANGE_COLUMN) != alter_info->flags)
 | |
|     return true;
 | |
| 
 | |
|   /*
 | |
|     Find at least one unique index (without NULLs), all columns of which
 | |
|     remain in the table unchanged to presume it's a safe ALTER TABLE.
 | |
|   */
 | |
|   for (uint k= 0; k < table->s->keys; k++)
 | |
|   {
 | |
|     const KEY &key= table->key_info[k];
 | |
|     if ((key.flags & HA_NOSAME) == 0 || key.flags & HA_NULL_PART_KEY)
 | |
|       continue;
 | |
|     bool key_parts_good= true;
 | |
|     for (uint kp= 0; kp < key.user_defined_key_parts && key_parts_good; kp++)
 | |
|     {
 | |
|       const Field *f= key.key_part[kp].field;
 | |
|       // tmp_set contains dropped fields after mysql_prepare_alter_table
 | |
|       key_parts_good= !bitmap_is_set(&table->tmp_set, f->field_index);
 | |
| 
 | |
|       if (key_parts_good)
 | |
|         for (const auto &c: alter_info->create_list)
 | |
|           if (c.field == f)
 | |
|           {
 | |
|             key_parts_good= f->is_equal(c);
 | |
|             break;
 | |
|           }
 | |
|     }
 | |
|     if (key_parts_good)
 | |
|       return true;
 | |
|   }
 | |
| 
 | |
|   for (const auto &c: alter_info->create_list)
 | |
|   {
 | |
|     if (c.flags & AUTO_INCREMENT_FLAG)
 | |
|     {
 | |
|       if (c.field && !(c.field->flags & AUTO_INCREMENT_FLAG))
 | |
|         return false;
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static
 | |
| const char *online_alter_check_supported(THD *thd,
 | |
|                                          const Alter_info *alter_info,
 | |
|                                          const TABLE *table,
 | |
|                                          const TABLE *new_table, bool *online)
 | |
| {
 | |
|   DBUG_ASSERT(*online);
 | |
| 
 | |
|   *online= thd->locked_tables_mode != LTM_LOCK_TABLES && !table->s->tmp_table;
 | |
|   if (!*online)
 | |
|     return NULL;
 | |
| 
 | |
|   *online= (new_table->file->ha_table_flags() & HA_NO_ONLINE_ALTER) == 0;
 | |
|   if (!*online)
 | |
|     return new_table->file->engine_name()->str;
 | |
| 
 | |
|   *online= table->s->sequence == NULL;
 | |
|   if (!*online)
 | |
|     return "SEQUENCE";
 | |
| 
 | |
|   *online= (alter_info->flags & ALTER_DROP_SYSTEM_VERSIONING) == 0;
 | |
|   if (!*online)
 | |
|     return "DROP SYSTEM VERSIONING";
 | |
| 
 | |
|   *online= !thd->lex->ignore;
 | |
|   if (!*online)
 | |
|     return "ALTER IGNORE TABLE";
 | |
| 
 | |
|   *online= !table->versioned(VERS_TRX_ID);
 | |
|   if (!*online)
 | |
|     return "BIGINT GENERATED ALWAYS AS ROW_START";
 | |
| 
 | |
|   List<FOREIGN_KEY_INFO> fk_list;
 | |
|   table->file->get_foreign_key_list(thd, &fk_list);
 | |
|   for (auto &fk: fk_list)
 | |
|   {
 | |
|     if (fk_modifies_child(fk.delete_method) ||
 | |
|         fk_modifies_child(fk.update_method))
 | |
|     {
 | |
|       *online= false;
 | |
|       // Don't fall to a common unsupported case to avoid heavy string ops.
 | |
|       if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
 | |
|       {
 | |
|         return fk_modifies_child(fk.delete_method)
 | |
|                ? thd->strcat({STRING_WITH_LEN("ON DELETE ")},
 | |
|                              *fk_option_name(fk.delete_method)).str
 | |
|                : thd->strcat({STRING_WITH_LEN("ON UPDATE ")},
 | |
|                              *fk_option_name(fk.update_method)).str;
 | |
|       }
 | |
|       return NULL;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (auto &c: alter_info->create_list)
 | |
|   {
 | |
|     *online= c.field || !(c.flags & AUTO_INCREMENT_FLAG);
 | |
|     if (!*online)
 | |
|       return "ADD COLUMN ... AUTO_INCREMENT";
 | |
| 
 | |
|     auto *def= c.default_value;
 | |
|     *online= !(def && def->flags & VCOL_NEXTVAL
 | |
|             // either it's a new field, or a NULL -> NOT NULL change
 | |
|              && (!c.field || (!(c.field->flags & NOT_NULL_FLAG)
 | |
|                               && (c.flags & NOT_NULL_FLAG))));
 | |
|     if (!*online)
 | |
|     {
 | |
|       if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_NONE)
 | |
|         return NULL; // Avoid heavy string op
 | |
|       const char *fmt= ER_THD(thd, ER_GENERATED_COLUMN_FUNCTION_IS_NOT_ALLOWED);
 | |
| 
 | |
|       LEX_CSTRING dflt{STRING_WITH_LEN("DEFAULT")};
 | |
|       LEX_CSTRING nxvl{STRING_WITH_LEN("NEXTVAL()")};
 | |
|       size_t len= strlen(fmt) + nxvl.length + c.field_name.length + dflt.length;
 | |
|       char *resp= thd->alloc(len);
 | |
|       // expression %s cannot be used in the %s clause of %sQ
 | |
|       my_snprintf(resp, len, fmt, nxvl.str, dflt.str, c.field_name.str);
 | |
|       return resp;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   *online= online_alter_check_autoinc(thd, alter_info, table);
 | |
|   if (!*online)
 | |
|     return "CHANGE COLUMN ... AUTO_INCREMENT";
 | |
| 
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Alter table
 | |
| 
 | |
|   @param thd              Thread handle
 | |
|   @param new_db           If there is a RENAME clause
 | |
|   @param new_name         If there is a RENAME clause
 | |
|   @param create_info      Information from the parsing phase about new
 | |
|                           table properties.
 | |
|   @param table_list       The table to change.
 | |
|   @param alter_info       Lists of fields, keys to be changed, added
 | |
|                           or dropped.
 | |
|   @param order_num        How many ORDER BY fields has been specified.
 | |
|   @param order            List of fields to ORDER BY.
 | |
|   @param ignore           Whether we have ALTER IGNORE TABLE
 | |
| 
 | |
|   @retval   true          Error
 | |
|   @retval   false         Success
 | |
| 
 | |
|   This is a veery long function and is everything but the kitchen sink :)
 | |
|   It is used to alter a table and not only by ALTER TABLE but also
 | |
|   CREATE|DROP INDEX are mapped on this function.
 | |
| 
 | |
|   When the ALTER TABLE statement just does a RENAME or ENABLE|DISABLE KEYS,
 | |
|   or both, then this function short cuts its operation by renaming
 | |
|   the table and/or enabling/disabling the keys. In this case, the FRM is
 | |
|   not changed, directly by mysql_alter_table. However, if there is a
 | |
|   RENAME + change of a field, or an index, the short cut is not used.
 | |
|   See how `create_list` is used to generate the new FRM regarding the
 | |
|   structure of the fields. The same is done for the indices of the table.
 | |
| 
 | |
|   Altering a table can be done in two ways. The table can be modified
 | |
|   directly using an in-place algorithm, or the changes can be done using
 | |
|   an intermediate temporary table (copy). In-place is the preferred
 | |
|   algorithm as it avoids copying table data. The storage engine
 | |
|   selects which algorithm to use in check_if_supported_inplace_alter()
 | |
|   based on information about the table changes from fill_alter_inplace_info().
 | |
| */
 | |
| 
 | |
| bool mysql_alter_table(THD *thd, const LEX_CSTRING *new_db,
 | |
|                        const LEX_CSTRING *new_name,
 | |
|                        Table_specification_st *create_info,
 | |
|                        TABLE_LIST *table_list,
 | |
|                        Recreate_info *recreate_info,
 | |
|                        Alter_info *alter_info,
 | |
|                        uint order_num, ORDER *order, bool ignore,
 | |
|                        bool if_exists)
 | |
| {
 | |
|   bool engine_changed, error, frm_is_created= false, error_handler_pushed= false;
 | |
|   bool no_ha_table= true;  /* We have not created table in storage engine yet */
 | |
|   TABLE *table, *new_table= nullptr;
 | |
|   DDL_LOG_STATE ddl_log_state;
 | |
|   Turn_errors_to_warnings_handler errors_to_warnings;
 | |
|   HA_CHECK_OPT check_opt;
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   bool partition_changed= false;
 | |
| #endif
 | |
|   bool require_copy_algorithm;
 | |
|   bool partial_alter= false;
 | |
|   /*
 | |
|     start_alter_id is the gtid seq no of the START Alter - the 1st part
 | |
|     of two-phase loggable ALTER. The variable is meaningful only
 | |
|     on slave execution.
 | |
|   */
 | |
|   uint64 start_alter_id= get_start_alter_id(thd);
 | |
| 
 | |
|   /*
 | |
|     Create .FRM for new version of table with a temporary name.
 | |
|     We don't log the statement, it will be logged later.
 | |
| 
 | |
|     Keep information about keys in newly created table as it
 | |
|     will be used later to construct Alter_inplace_info object
 | |
|     and by fill_alter_inplace_info() call.
 | |
|   */
 | |
|   KEY *key_info;
 | |
|   uint key_count;
 | |
|   bool table_creation_was_logged= 0;
 | |
|   bool binlog_as_create_select= 0, log_if_exists= 0;
 | |
|   uint tables_opened;
 | |
|   handlerton *new_db_type= create_info->db_type, *old_db_type;
 | |
|   ha_rows copied=0, deleted=0;
 | |
|   LEX_CUSTRING frm= {0,0};
 | |
|   LEX_CSTRING backup_name;
 | |
|   char index_file[FN_REFLEN], data_file[FN_REFLEN], backup_name_buff[60];
 | |
|   uchar uuid_buffer[MY_UUID_SIZE];
 | |
|   MDL_request target_mdl_request;
 | |
|   MDL_ticket *mdl_ticket= 0;
 | |
|   Alter_table_prelocking_strategy alter_prelocking_strategy;
 | |
| #ifdef HAVE_REPLICATION
 | |
|   bool online= order == NULL && !opt_bootstrap;
 | |
| #else
 | |
|   bool online= false;
 | |
| #endif
 | |
|   TRIGGER_RENAME_PARAM trigger_param;
 | |
| 
 | |
|   /*
 | |
|     Callback function that an engine can request to be called after executing
 | |
|     inplace alter table.
 | |
|   */
 | |
|   Alter_inplace_info::inplace_alter_table_commit_callback
 | |
|     *inplace_alter_table_committed= 0;
 | |
|   void *inplace_alter_table_committed_argument= 0;
 | |
|   DBUG_ENTER("mysql_alter_table");
 | |
| 
 | |
|   /*
 | |
|     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.
 | |
|   */
 | |
|   int table_kind= check_if_log_table(table_list, FALSE, NullS);
 | |
|   const bool used_engine= create_info->used_fields & HA_CREATE_USED_ENGINE;
 | |
| 
 | |
|   if (table_kind)
 | |
|   {
 | |
|     /* Disable alter of enabled log tables */
 | |
|     if (logger.is_log_table_enabled(table_kind))
 | |
|     {
 | |
|       my_error(ER_BAD_LOG_STATEMENT, MYF(0), "ALTER");
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     /* Disable alter of log tables to unsupported engine */
 | |
|     if ((used_engine) &&
 | |
|         (!create_info->db_type || /* unknown engine */
 | |
|          !(create_info->db_type->flags & HTON_SUPPORT_LOG_TABLES)))
 | |
|     {
 | |
|     unsupported:
 | |
|       my_error(ER_UNSUPORTED_LOG_ENGINE, MYF(0),
 | |
|                hton_name(create_info->db_type)->str);
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     if (create_info->db_type == maria_hton &&
 | |
|         create_info->transactional != HA_CHOICE_NO)
 | |
|       goto unsupported;
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|     if (alter_info->partition_flags & ALTER_PARTITION_INFO)
 | |
|     {
 | |
|       my_error(ER_WRONG_USAGE, MYF(0), "PARTITION", "log table");
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| #endif
 | |
|   }
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_init_update);
 | |
|   bzero(&ddl_log_state, sizeof(ddl_log_state));
 | |
| 
 | |
|   /* Temporary name for backup of original table */
 | |
|   backup_name.str= backup_name_buff;
 | |
|   backup_name.length= my_snprintf(backup_name_buff, sizeof(backup_name_buff)-1,
 | |
|                                   "%s-backup-%lx-%llx", tmp_file_prefix,
 | |
|                                   current_pid, thd->thread_id);
 | |
| 
 | |
|   /* Check if the new table type is a shared table */
 | |
|   if (ha_check_if_updates_are_ignored(thd, create_info->db_type, "ALTER"))
 | |
|   {
 | |
|     /*
 | |
|       Remove old local .frm file if it exists. We should use the new
 | |
|       shared one in the future. The drop is not logged, the ALTER table is
 | |
|       logged.
 | |
|     */
 | |
|     table_list->mdl_request.type= MDL_EXCLUSIVE;
 | |
|     /* This will only drop the .frm file and local tables, not shared ones */
 | |
|     error= mysql_rm_table(thd, table_list, 1, 0, 0, 1);
 | |
|     DBUG_RETURN(log_and_ok(thd));
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Code below can handle only base tables so ensure that we won't open a view.
 | |
|     Note that RENAME TABLE the only ALTER clause which is supported for views
 | |
|     has been already processed.
 | |
|   */
 | |
|   table_list->required_type= TABLE_TYPE_NORMAL;
 | |
| 
 | |
|   if ((alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_DEFAULT
 | |
|        && (thd->variables.old_behavior & OLD_MODE_LOCK_ALTER_TABLE_COPY))
 | |
|       || alter_info->requested_lock > Alter_info::ALTER_TABLE_LOCK_NONE
 | |
|       || thd->lex->sql_command == SQLCOM_OPTIMIZE
 | |
|       || alter_info->algorithm(thd) > Alter_info::ALTER_TABLE_ALGORITHM_COPY)
 | |
|     online= false;
 | |
| 
 | |
|   if (online)
 | |
|   {
 | |
|     table_list->lock_type= TL_READ;
 | |
|   }
 | |
| 
 | |
|   enum_tx_isolation iso_level_initial= thd->tx_isolation;
 | |
|   SCOPE_EXIT([thd, iso_level_initial](){
 | |
|     thd->tx_isolation= iso_level_initial;
 | |
|   });
 | |
|   thd->tx_isolation= ISO_REPEATABLE_READ;
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_before_open_tables");
 | |
| 
 | |
|   thd->open_options|= HA_OPEN_FOR_ALTER;
 | |
|   thd->mdl_backup_ticket= 0;
 | |
|   error= open_tables(thd, &table_list, &tables_opened, 0,
 | |
|                      &alter_prelocking_strategy);
 | |
|   thd->open_options&= ~HA_OPEN_FOR_ALTER;
 | |
| 
 | |
|   if (unlikely(error))
 | |
|   {
 | |
|     if (if_exists)
 | |
|     {
 | |
|       int tmp_errno= thd->get_stmt_da()->sql_errno();
 | |
|       if (tmp_errno == ER_NO_SUCH_TABLE)
 | |
|       {
 | |
|         /*
 | |
|           ALTER TABLE IF EXISTS was used on not existing table
 | |
|           We have to log the query on a slave as the table may be a shared one
 | |
|           from the master and we need to ensure that the next slave can see
 | |
|           the statement as this slave may not have the table shared
 | |
|         */
 | |
|         thd->clear_error();
 | |
|         DBUG_RETURN(log_and_ok(thd));
 | |
|       }
 | |
|     }
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   table= table_list->table;
 | |
|   bool is_reg_table= table->s->tmp_table == NO_TMP_TABLE;
 | |
|   
 | |
| #ifdef WITH_WSREP
 | |
|   /*
 | |
|     If this ALTER TABLE is actually SEQUENCE we need to check
 | |
|     if we can support implementing storage engine.
 | |
|   */
 | |
|   if (WSREP(thd) && table && table->s->sequence &&
 | |
|       wsrep_check_sequence(thd, create_info->seq_create_info, used_engine))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   if (WSREP(thd) && table &&
 | |
|       (thd->lex->sql_command == SQLCOM_ALTER_TABLE ||
 | |
|        thd->lex->sql_command == SQLCOM_CREATE_INDEX ||
 | |
|        thd->lex->sql_command == SQLCOM_DROP_INDEX))
 | |
|   {
 | |
|     handlerton *ht= table->s->db_type();
 | |
| 
 | |
|     // If alter used ENGINE= we use that
 | |
|     if (create_info->used_fields & HA_CREATE_USED_ENGINE)
 | |
|       ht= create_info->db_type;
 | |
|     // For partitioned tables resolve underlying handlerton
 | |
|     else if (table->file->partition_ht())
 | |
|       ht= table->file->partition_ht();
 | |
| 
 | |
|     if (!wsrep_should_replicate_ddl(thd, ht))
 | |
|       DBUG_RETURN(true);
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_after_open_tables");
 | |
| 
 | |
|   if (table->versioned())
 | |
|   {
 | |
|     if (handlerton *hton1= create_info->db_type)
 | |
|     {
 | |
|       handlerton *hton2= table->file->partition_ht();
 | |
|       if (hton1 != hton2 &&
 | |
|           (ha_check_storage_engine_flag(hton1, HTON_NATIVE_SYS_VERSIONING) ||
 | |
|            ha_check_storage_engine_flag(hton2, HTON_NATIVE_SYS_VERSIONING)))
 | |
|       {
 | |
|         my_error(ER_VERS_ALTER_ENGINE_PROHIBITED, MYF(0), table_list->db.str,
 | |
|                  table_list->table_name.str);
 | |
|         DBUG_RETURN(true);
 | |
|       }
 | |
|     }
 | |
|     if (alter_info->vers_prohibited(thd))
 | |
|     {
 | |
|       my_error(ER_VERS_ALTER_NOT_ALLOWED, MYF(0),
 | |
|                table_list->db.str, table_list->table_name.str);
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_opened_table");
 | |
| 
 | |
| #if defined WITH_WSREP && defined ENABLED_DEBUG_SYNC
 | |
|   DBUG_EXECUTE_IF("sync.alter_opened_table",
 | |
|                   {
 | |
|                     const char act[]=
 | |
|                       "now "
 | |
|                       "wait_for signal.alter_opened_table";
 | |
|                     DBUG_ASSERT(!debug_sync_set_action(thd,
 | |
|                                                        STRING_WITH_LEN(act)));
 | |
|                   };);
 | |
| #endif // WITH_WSREP
 | |
| 
 | |
|   Alter_table_ctx alter_ctx(thd, table_list, tables_opened, new_db, new_name);
 | |
|   mdl_ticket= table->mdl_ticket;
 | |
| 
 | |
|   /*
 | |
|     We have to do a check also after table is opened as there could be no
 | |
|     ENGINE= on the command line or the table could a partitioned S3 table.
 | |
|   */
 | |
|   if (table->file->check_if_updates_are_ignored("ALTER"))
 | |
|   {
 | |
|     /*
 | |
|       Table is a shared table. Remove the .frm file. Discovery will create
 | |
|       a new one if needed.
 | |
|     */
 | |
|     table->s->tdc->flushed= 1;         // Force close of all instances
 | |
|     if (thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_EXCLUSIVE,
 | |
|                                              thd->variables.lock_wait_timeout))
 | |
|       DBUG_RETURN(1);
 | |
|     quick_rm_table(thd, table->file->ht, &table_list->db,
 | |
|                    &table_list->table_name, QRMT_FRM | QRMT_PAR);
 | |
|     goto end_inplace;
 | |
|   }
 | |
|   if (!if_exists &&
 | |
|       (table->file->partition_ht()->flags & HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE))
 | |
|   {
 | |
|     /*
 | |
|       Table is a shared table that may not exist on the slave.
 | |
|       We add 'if_exists' to the query if it was not used
 | |
|     */
 | |
|     log_if_exists= 1;
 | |
|   }
 | |
|   table_creation_was_logged= table->s->table_creation_was_logged;
 | |
| 
 | |
|   table->use_all_columns();
 | |
| 
 | |
|   /*
 | |
|     Prohibit changing of the UNION list of a non-temporary MERGE table
 | |
|     under LOCK tables. It would be quite difficult to reuse a shrinked
 | |
|     set of tables from the old table or to open a new TABLE object for
 | |
|     an extended list and verify that they belong to locked tables.
 | |
|   */
 | |
|   if ((thd->locked_tables_mode == LTM_LOCK_TABLES ||
 | |
|        thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES) &&
 | |
|       (create_info->used_fields & HA_CREATE_USED_UNION) &&
 | |
|       (table->s->tmp_table == NO_TMP_TABLE))
 | |
|   {
 | |
|     my_error(ER_LOCK_OR_ACTIVE_TRANSACTION, MYF(0));
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   /* Check that we are not trying to rename to an existing table */
 | |
|   if (alter_ctx.is_table_renamed())
 | |
|   {
 | |
|     if (table->s->tmp_table != NO_TMP_TABLE)
 | |
|     {
 | |
|       /*
 | |
|         Check whether a temporary table exists with same requested new name.
 | |
|         If such table exists, there must be a corresponding TABLE_SHARE in
 | |
|         THD::all_temp_tables list.
 | |
|       */
 | |
|       if (thd->find_tmp_table_share(alter_ctx.new_db, alter_ctx.new_name))
 | |
|       {
 | |
|         my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias.str);
 | |
|         DBUG_RETURN(true);
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       MDL_request_list mdl_requests;
 | |
|       MDL_request target_db_mdl_request;
 | |
| 
 | |
|       MDL_REQUEST_INIT(&target_mdl_request, MDL_key::TABLE,
 | |
|                        alter_ctx.new_db.str, alter_ctx.new_name.str,
 | |
|                        MDL_EXCLUSIVE, MDL_TRANSACTION);
 | |
|       mdl_requests.push_front(&target_mdl_request);
 | |
| 
 | |
|       /*
 | |
|         If we are moving the table to a different database, we also
 | |
|         need IX lock on the database name so that the target database
 | |
|         is protected by MDL while the table is moved.
 | |
|       */
 | |
|       if (alter_ctx.is_database_changed())
 | |
|       {
 | |
|         MDL_REQUEST_INIT(&target_db_mdl_request, MDL_key::SCHEMA,
 | |
|                          alter_ctx.new_db.str, "", MDL_INTENTION_EXCLUSIVE,
 | |
|                          MDL_TRANSACTION);
 | |
|         mdl_requests.push_front(&target_db_mdl_request);
 | |
|       }
 | |
| 
 | |
|       /*
 | |
|         Protection against global read lock must have been acquired when table
 | |
|         to be altered was being opened.
 | |
|       */
 | |
|       DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::BACKUP,
 | |
|                                                  "", "",
 | |
|                                                  MDL_BACKUP_DDL));
 | |
| 
 | |
|       if (thd->mdl_context.acquire_locks(&mdl_requests,
 | |
|                                          thd->variables.lock_wait_timeout))
 | |
|         DBUG_RETURN(true);
 | |
| 
 | |
|       DEBUG_SYNC(thd, "locked_table_name");
 | |
|       /*
 | |
|         Table maybe does not exist, but we got an exclusive lock
 | |
|         on the name, now we can safely try to find out for sure.
 | |
|       */
 | |
|       if (!(alter_info->partition_flags & ALTER_PARTITION_CONVERT_IN) &&
 | |
|           ha_table_exists(thd, &alter_ctx.new_db, &alter_ctx.new_name))
 | |
|       {
 | |
|         /* Table will be closed in do_command() */
 | |
|         my_error(ER_TABLE_EXISTS_ERROR, MYF(0), alter_ctx.new_alias.str);
 | |
|         DBUG_RETURN(true);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!create_info->db_type)
 | |
|   {
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|     if (table->part_info &&
 | |
|         create_info->used_fields & HA_CREATE_USED_ENGINE)
 | |
|     {
 | |
|       /*
 | |
|         This case happens when the user specified
 | |
|         ENGINE = x where x is a non-existing storage engine
 | |
|         We set create_info->db_type to default_engine_type
 | |
|         to ensure we don't change underlying engine type
 | |
|         due to a erroneously given engine name.
 | |
|       */
 | |
|       create_info->db_type= table->part_info->default_engine_type;
 | |
|     }
 | |
|     else
 | |
| #endif
 | |
|       create_info->db_type= table->s->db_type();
 | |
|   }
 | |
| 
 | |
|   if (check_engine(thd, alter_ctx.new_db.str, alter_ctx.new_name.str, create_info))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   create_info->vers_check_native();
 | |
|   if (create_info->vers_info.fix_alter_info(thd, alter_info, create_info, table))
 | |
|   {
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   if ((create_info->db_type != table->s->db_type() ||
 | |
|        (alter_info->partition_flags & ALTER_PARTITION_INFO)) &&
 | |
|       !table->file->can_switch_engines())
 | |
|   {
 | |
|     my_error(ER_ROW_IS_REFERENCED, MYF(0));
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    If foreign key is added then check permission to access parent table.
 | |
| 
 | |
|    In function "check_fk_parent_table_access", create_info->db_type is used
 | |
|    to identify whether engine supports FK constraint or not. Since
 | |
|    create_info->db_type is set here, check to parent table access is delayed
 | |
|    till this point for the alter operation.
 | |
|   */
 | |
|   if ((alter_info->flags & ALTER_ADD_FOREIGN_KEY) &&
 | |
|       check_fk_parent_table_access(thd, create_info, alter_info, *new_db))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     If this is an ALTER TABLE and no explicit row type specified reuse
 | |
|     the table's row type.
 | |
|     Note: this is the same as if the row type was specified explicitly.
 | |
|   */
 | |
|   if (create_info->row_type == ROW_TYPE_NOT_USED)
 | |
|   {
 | |
|     /* ALTER TABLE without explicit row type */
 | |
|     create_info->row_type= table->s->row_type;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* ALTER TABLE with specific row type */
 | |
|     create_info->used_fields |= HA_CREATE_USED_ROW_FORMAT;
 | |
|   }
 | |
| 
 | |
|   old_db_type= table->s->db_type();
 | |
|   new_db_type= create_info->db_type;
 | |
| 
 | |
|   DBUG_PRINT("info", ("old type: %s  new type: %s",
 | |
|              ha_resolve_storage_engine_name(old_db_type),
 | |
|              ha_resolve_storage_engine_name(new_db_type)));
 | |
|   if (ha_check_storage_engine_flag(old_db_type, HTON_ALTER_NOT_SUPPORTED))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("doesn't support alter"));
 | |
|     my_error(ER_ILLEGAL_HA, MYF(0), hton_name(old_db_type)->str,
 | |
|              alter_ctx.db.str, alter_ctx.table_name.str);
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   if (ha_check_storage_engine_flag(new_db_type, HTON_ALTER_NOT_SUPPORTED))
 | |
|   {
 | |
|     DBUG_PRINT("info", ("doesn't support alter"));
 | |
|     my_error(ER_ILLEGAL_HA, MYF(0), hton_name(new_db_type)->str,
 | |
|              alter_ctx.new_db.str, alter_ctx.new_name.str);
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|     mysql_audit_alter_table(thd, table_list);
 | |
|   else if (table_creation_was_logged && mysql_bin_log.is_open())
 | |
|   {
 | |
|     /* Protect against MDL error in binary logging */
 | |
|     MDL_request mdl_request;
 | |
|     DBUG_ASSERT(!mdl_ticket);
 | |
|     MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_COMMIT,
 | |
|                      MDL_TRANSACTION);
 | |
|     if (thd->mdl_context.acquire_lock(&mdl_request,
 | |
|                                       thd->variables.lock_wait_timeout))
 | |
|       DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_setup);
 | |
| 
 | |
|   if (alter_info->flags & ALTER_DROP_CHECK_CONSTRAINT)
 | |
|   {
 | |
|     /*
 | |
|       ALTER TABLE DROP CONSTRAINT
 | |
|       should be replaced with ... DROP [FOREIGN] KEY
 | |
|       if the constraint is the FOREIGN KEY or UNIQUE one.
 | |
|     */
 | |
| 
 | |
|     List_iterator<Alter_drop> drop_it(alter_info->drop_list);
 | |
|     Alter_drop *drop;
 | |
|     List <FOREIGN_KEY_INFO> fk_child_key_list;
 | |
|     table->file->get_foreign_key_list(thd, &fk_child_key_list);
 | |
| 
 | |
|     alter_info->flags&= ~ALTER_DROP_CHECK_CONSTRAINT;
 | |
| 
 | |
|     while ((drop= drop_it++))
 | |
|     {
 | |
|       if (drop->type == Alter_drop::CHECK_CONSTRAINT)
 | |
|       {
 | |
|         {
 | |
|           /* Test if there is a FOREIGN KEY with this name. */
 | |
|           FOREIGN_KEY_INFO *f_key;
 | |
|           List_iterator<FOREIGN_KEY_INFO> fk_key_it(fk_child_key_list);
 | |
| 
 | |
|           while ((f_key= fk_key_it++))
 | |
|           {
 | |
|             if (Lex_ident_column(*f_key->foreign_id).streq(drop->name))
 | |
|             {
 | |
|               drop->type= Alter_drop::FOREIGN_KEY;
 | |
|               alter_info->flags|= ALTER_DROP_FOREIGN_KEY;
 | |
|               goto do_continue;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
| 
 | |
|         {
 | |
|           /* Test if there is an UNIQUE with this name. */
 | |
|           uint n_key;
 | |
| 
 | |
|           for (n_key=0; n_key < table->s->keys; n_key++)
 | |
|           {
 | |
|             if ((table->key_info[n_key].flags & HA_NOSAME) &&
 | |
|                 table->key_info[n_key].name.streq(drop->name))
 | |
|             {
 | |
|               drop->type= Alter_drop::KEY;
 | |
|               alter_info->flags|= ALTER_DROP_INDEX;
 | |
|               goto do_continue;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       alter_info->flags|= ALTER_DROP_CHECK_CONSTRAINT;
 | |
| do_continue:;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (handle_if_exists_options(thd, table, alter_info,
 | |
|                                &create_info->period_info) ||
 | |
|       fix_constraints_names(thd, &alter_info->check_constraint_list,
 | |
|                             create_info))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /* Check if rename of triggers are supported */
 | |
|   if (alter_ctx.is_table_renamed() &&
 | |
|       Table_triggers_list::prepare_for_rename(thd, &trigger_param,
 | |
|                                               alter_ctx.db,
 | |
|                                               Lex_ident_table(alter_ctx.alias),
 | |
|                                               alter_ctx.table_name,
 | |
|                                               alter_ctx.new_db,
 | |
|                                               Lex_ident_table(alter_ctx.new_alias)))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     Look if we have to do anything at all.
 | |
|     ALTER can become NOOP after handling
 | |
|     the IF (NOT) EXISTS options.
 | |
|   */
 | |
|   if (alter_info->flags == 0 && alter_info->partition_flags == 0)
 | |
|   {
 | |
|     my_snprintf(alter_ctx.tmp_buff, sizeof(alter_ctx.tmp_buff),
 | |
|                 ER_THD(thd, ER_INSERT_INFO), 0L, 0L,
 | |
|                 thd->get_stmt_da()->current_statement_warn_count());
 | |
|     my_ok(thd, 0L, 0L, alter_ctx.tmp_buff);
 | |
| 
 | |
|     /* We don't replicate alter table statement on temporary tables */
 | |
|     if (table_creation_was_logged)
 | |
|     {
 | |
|       if (write_bin_log_with_if_exists(thd, true, false, log_if_exists))
 | |
|         DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     DBUG_RETURN(false);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|      Test if we are only doing RENAME or KEYS ON/OFF. This works
 | |
|      as we are testing if flags == 0 above.
 | |
|   */
 | |
|   if (!(alter_info->flags & ~(ALTER_RENAME | ALTER_KEYS_ONOFF)) &&
 | |
|       alter_info->partition_flags == 0 &&
 | |
|       alter_info->algorithm(thd) !=
 | |
|       Alter_info::ALTER_TABLE_ALGORITHM_COPY)   // No need to touch frm.
 | |
|   {
 | |
|     bool res;
 | |
| 
 | |
|     if (!table->s->tmp_table)
 | |
|     {
 | |
|       // This requires X-lock, no other lock levels supported.
 | |
|       if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_DEFAULT &&
 | |
|           alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE)
 | |
|       {
 | |
|         my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
 | |
|                  "LOCK=NONE/SHARED", "LOCK=EXCLUSIVE");
 | |
|         DBUG_RETURN(true);
 | |
|       }
 | |
|       res= simple_rename_or_index_change(thd, table_list,
 | |
|                                          alter_info->keys_onoff,
 | |
|                                          &trigger_param,
 | |
|                                          &alter_ctx);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       res= simple_tmp_rename_or_index_change(thd, table_list,
 | |
|                                              alter_info->keys_onoff,
 | |
|                                              &alter_ctx);
 | |
|     }
 | |
|     DBUG_RETURN(res);
 | |
|   }
 | |
| 
 | |
|   /* We have to do full alter table. */
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   {
 | |
|     /*
 | |
|       Partitioning: part_info is prepared and returned via thd->work_part_info
 | |
|     */
 | |
|     if (prep_alter_part_table(thd, table, alter_info, create_info,
 | |
|                         &partition_changed, &alter_ctx.fast_alter_partition))
 | |
|     {
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|     if (parse_engine_part_options(thd, table))
 | |
|       DBUG_RETURN(true);
 | |
|   }
 | |
|   /*
 | |
|     If the old table had partitions and we are doing ALTER TABLE ...
 | |
|     engine= <new_engine>, the new table must preserve the original
 | |
|     partitioning. This means that the new engine is still the
 | |
|     partitioning engine, not the engine specified in the parser.
 | |
|     This is discovered in prep_alter_part_table, which in such case
 | |
|     updates create_info->db_type.
 | |
|     It's therefore important that the assignment below is done
 | |
|     after prep_alter_part_table.
 | |
|   */
 | |
|   new_db_type= create_info->db_type;
 | |
| #endif
 | |
| 
 | |
|   if (mysql_prepare_alter_table(thd, table, create_info, alter_info,
 | |
|                                 &alter_ctx))
 | |
|   {
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
| 
 | |
|   DBUG_ASSERT(create_info->default_table_charset);
 | |
| 
 | |
|   /*
 | |
|     The ALTER related code cannot alter partitions and change column data types
 | |
|     at the same time. So in case of partition change statements like:
 | |
|       ALTER TABLE t1 DROP PARTITION p1;
 | |
|     we skip implicit data type upgrade (such as "MariaDB 5.3 TIME" to
 | |
|     "MySQL 5.6 TIME" or vice versa according to mysql56_temporal_format).
 | |
|     Note, one can run a separate "ALTER TABLE t1 FORCE;" statement
 | |
|     before or after the partition change ALTER statement to upgrade data types.
 | |
|   */
 | |
|   if (!alter_ctx.fast_alter_partition)
 | |
|     Create_field::upgrade_data_types(alter_info->create_list);
 | |
| 
 | |
|   if (create_info->check_fields(thd, alter_info,
 | |
|                                 table_list->table_name, table_list->db) ||
 | |
|       create_info->fix_period_fields(thd, alter_info))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   if (!(thd->variables.option_bits & OPTION_EXPLICIT_DEF_TIMESTAMP))
 | |
|     promote_first_timestamp_column(&alter_info->create_list);
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   if (alter_ctx.fast_alter_partition)
 | |
|   {
 | |
|     /*
 | |
|       ALGORITHM and LOCK clauses are generally not allowed by the
 | |
|       parser for operations related to partitioning.
 | |
|       The exceptions are ALTER_PARTITION_INFO and ALTER_PARTITION_REMOVE.
 | |
|       For consistency, we report ER_ALTER_OPERATION_NOT_SUPPORTED here.
 | |
|     */
 | |
|     if (alter_info->requested_lock !=
 | |
|         Alter_info::ALTER_TABLE_LOCK_DEFAULT)
 | |
|     {
 | |
|       my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
 | |
|                "LOCK=NONE/SHARED/EXCLUSIVE",
 | |
|                ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION),
 | |
|                "LOCK=DEFAULT");
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
|     else if (alter_info->algorithm(thd) !=
 | |
|              Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT)
 | |
|     {
 | |
|       my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
 | |
|                "ALGORITHM=COPY/INPLACE",
 | |
|                ER_THD(thd, ER_ALTER_OPERATION_NOT_SUPPORTED_REASON_PARTITION),
 | |
|                "ALGORITHM=DEFAULT");
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       Upgrade from MDL_SHARED_UPGRADABLE to MDL_SHARED_NO_WRITE.
 | |
|       Afterwards it's safe to take the table level lock.
 | |
|     */
 | |
|     if ((thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
 | |
|              thd->variables.lock_wait_timeout)) ||
 | |
|         lock_tables(thd, table_list, alter_ctx.tables_opened, 0))
 | |
|     {
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     // In-place execution of ALTER TABLE for partitioning.
 | |
|     alter_info->db= alter_ctx.db;
 | |
|     alter_info->table_name= alter_ctx.table_name;
 | |
|     DBUG_RETURN(fast_alter_partition_table(thd, table, alter_info, &alter_ctx,
 | |
|                                            create_info, table_list));
 | |
|   }
 | |
| #endif
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
|   // ALTER TABLE for sequence object, check can we support it
 | |
|   if (table->s->sequence && WSREP(thd) &&
 | |
|       wsrep_check_sequence(thd, create_info->seq_create_info, used_engine))
 | |
|       DBUG_RETURN(TRUE);
 | |
| #endif /* WITH_WSREP */
 | |
| 
 | |
|   /*
 | |
|     ALTER TABLE ... ENGINE to the same engine is a common way to
 | |
|     request table rebuild. Set ALTER_RECREATE flag to force table
 | |
|     rebuild.
 | |
|   */
 | |
|   if (new_db_type == old_db_type)
 | |
|   {
 | |
|     if (create_info->used_fields & HA_CREATE_USED_ENGINE)
 | |
|       alter_info->flags|= ALTER_RECREATE;
 | |
| 
 | |
|     /*
 | |
|       Check if we are using ALTER TABLE FORCE without any other options
 | |
|       (except ENGINE == current_engine).
 | |
|       In this case we will try to recreate an identical to the original.
 | |
|       This code is also used with REPAIR and OPTIMIZE.
 | |
|     */
 | |
|     if (alter_info->flags == ALTER_RECREATE &&
 | |
|         ((create_info->used_fields & ~HA_CREATE_USED_ENGINE) == 0))
 | |
|       create_info->recreate_identical_table= 1;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     We can use only copy algorithm if one of the following is true:
 | |
|     - If the table is from an old MariaDB version and requires data
 | |
|       modifications. In this case we ignore --alter-algorithm as
 | |
|       as we cannot use any other algorithm than COPY (using other
 | |
|       algorithms could open up the problem that the table .frm version
 | |
|       is updated without data transformations and the table would be
 | |
|       corrupted without any way for MariaDB to notice this during
 | |
|       check/upgrade).
 | |
|       This logic ensures that ALTER TABLE ... FORCE (no other
 | |
|       options) will always be be able to repair a table structure and
 | |
|       convert data from any old format.
 | |
|     - In-place is impossible for given operation.
 | |
|     - Changes to partitioning which were not handled by fast_alter_part_table()
 | |
|       needs to be handled using table copying algorithm unless the engine
 | |
|       supports auto-partitioning as such engines can do some changes
 | |
|       using in-place API.
 | |
|   */
 | |
|   check_opt.init();
 | |
| 
 | |
|   require_copy_algorithm= (table->file->ha_check_for_upgrade(&check_opt) ==
 | |
|                            HA_ADMIN_NEEDS_DATA_CONVERSION);
 | |
|   if (require_copy_algorithm ||
 | |
|       is_inplace_alter_impossible(table, create_info, alter_info) ||
 | |
|       IF_PARTITIONING((partition_changed &&
 | |
|                        !(old_db_type->partition_flags() & HA_USE_AUTO_PARTITION)), 0))
 | |
|   {
 | |
|     if (alter_info->algorithm_is_nocopy(thd) &&
 | |
|         !(require_copy_algorithm && alter_info->algorithm_not_specified()))
 | |
|     {
 | |
|       my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
 | |
|                alter_info->algorithm_clause(thd), "ALGORITHM=COPY");
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     alter_info->set_requested_algorithm(Alter_info::ALTER_TABLE_ALGORITHM_COPY);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Handling of symlinked tables:
 | |
|     If no rename:
 | |
|       Create new data file and index file on the same disk as the
 | |
|       old data and index files.
 | |
|       Copy data.
 | |
|       Rename new data file over old data file and new index file over
 | |
|       old index file.
 | |
|       Symlinks are not changed.
 | |
| 
 | |
|    If rename:
 | |
|       Create new data file and index file on the same disk as the
 | |
|       old data and index files.  Create also symlinks to point at
 | |
|       the new tables.
 | |
|       Copy data.
 | |
|       At end, rename intermediate tables, and symlinks to intermediate
 | |
|       table, to final table name.
 | |
|       Remove old table and old symlinks
 | |
| 
 | |
|     If rename is made to another database:
 | |
|       Create new tables in new database.
 | |
|       Copy data.
 | |
|       Remove old table and symlinks.
 | |
|   */
 | |
|   if (!alter_ctx.is_database_changed())
 | |
|   {
 | |
|     if (create_info->index_file_name)
 | |
|     {
 | |
|       /* Fix index_file_name to have 'tmp_name' as basename */
 | |
|       strmov(index_file, alter_ctx.tmp_name.str);
 | |
|       create_info->index_file_name=fn_same(index_file,
 | |
|                                            create_info->index_file_name,
 | |
|                                            1);
 | |
|     }
 | |
|     if (create_info->data_file_name)
 | |
|     {
 | |
|       /* Fix data_file_name to have 'tmp_name' as basename */
 | |
|       strmov(data_file, alter_ctx.tmp_name.str);
 | |
|       create_info->data_file_name=fn_same(data_file,
 | |
|                                           create_info->data_file_name,
 | |
|                                           1);
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* Ignore symlink if db is changed. */
 | |
|     create_info->data_file_name=create_info->index_file_name=0;
 | |
|   }
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_before_create_table_no_lock");
 | |
| 
 | |
|   /* Create a new table version id for the new table */
 | |
|   my_uuid(uuid_buffer);
 | |
|   create_info->tabledef_version.str= uuid_buffer;
 | |
|   create_info->tabledef_version.length= MY_UUID_SIZE;
 | |
| 
 | |
|   if (!table->s->tmp_table)
 | |
|   {
 | |
|     LEX_CSTRING path_to_frm= alter_ctx.get_tmp_cstring_path();
 | |
|     LEX_CSTRING tmp_table= backup_name;
 | |
|     if (alter_ctx.is_table_renamed())
 | |
|       tmp_table= alter_ctx.new_alias;
 | |
| 
 | |
|     if (ddl_log_alter_table(&ddl_log_state,
 | |
|                             old_db_type,
 | |
|                             &alter_ctx.db, &alter_ctx.table_name,
 | |
|                             new_db_type,
 | |
|                             table->file->partition_ht(),
 | |
|                             &alter_ctx.new_db, &alter_ctx.tmp_name,
 | |
|                             &path_to_frm,
 | |
|                             &tmp_table,
 | |
|                             &create_info->tabledef_version,
 | |
|                             table->file->table_version(),
 | |
|                             alter_ctx.is_table_renamed()) ||
 | |
|         ddl_log_store_query(thd, &ddl_log_state,
 | |
|                             thd->query(), thd->query_length()))
 | |
|     {
 | |
|       error= 1;
 | |
|       goto err_cleanup;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   tmp_disable_binlog(thd);
 | |
|   create_info->options|=HA_CREATE_TMP_ALTER;
 | |
|   if (!(alter_info->flags & ALTER_ADD_INDEX) && !alter_ctx.modified_primary_key)
 | |
|     create_info->options|= HA_SKIP_KEY_SORT;
 | |
|   else
 | |
|     alter_info->flags|= ALTER_INDEX_ORDER;
 | |
|   create_info->alias= alter_ctx.table_name;
 | |
|   thd->abort_on_warning= !ignore && thd->is_strict_mode();
 | |
| 
 | |
|   /*
 | |
|     This is to be able to call Alter_info::add_stat_drop_index(thd, key_name)
 | |
|     from mysql_prepare_create_table()
 | |
|   */
 | |
|   alter_info->original_table= table;
 | |
| 
 | |
|   /*
 | |
|     Create the .frm file for the new table. Storage engine table will not be
 | |
|     created at this stage.
 | |
| 
 | |
|     No ddl logging needed as ddl_log_alter_query will take care of failed
 | |
|     table creations.
 | |
| 
 | |
|     Partitioning: part_info is passed via thd->work_part_info
 | |
|   */
 | |
|   error= create_table_impl(thd, (DDL_LOG_STATE*) 0, (DDL_LOG_STATE*) 0,
 | |
|                            alter_ctx.db, alter_ctx.table_name,
 | |
|                            alter_ctx.new_db, alter_ctx.tmp_name,
 | |
|                            alter_ctx.get_tmp_cstring_path(),
 | |
|                            thd->lex->create_info,
 | |
|                            create_info, alter_info, C_ALTER_TABLE_FRM_ONLY,
 | |
|                            NULL, &key_info, &key_count, &frm);
 | |
|   thd->abort_on_warning= false;
 | |
|   reenable_binlog(thd);
 | |
| 
 | |
|   debug_crash_here("ddl_log_alter_after_create_frm");
 | |
| 
 | |
|   if (unlikely(error))
 | |
|     goto err_cleanup;
 | |
| 
 | |
|   /* Remember version id for temporary table */
 | |
|   alter_ctx.tmp_id= create_info->tabledef_version;
 | |
| 
 | |
|   /* Remember that we have not created table in storage engine yet. */
 | |
|   no_ha_table= true;
 | |
| 
 | |
|   if (alter_info->algorithm(thd) != Alter_info::ALTER_TABLE_ALGORITHM_COPY)
 | |
|   {
 | |
|     Alter_inplace_info ha_alter_info(create_info, alter_info,
 | |
|                                      key_info, key_count,
 | |
|                                      IF_PARTITIONING(thd->work_part_info, NULL),
 | |
|                                      ignore, alter_ctx.error_if_not_empty);
 | |
|     TABLE_SHARE altered_share;
 | |
|     TABLE altered_table;
 | |
|     bool use_inplace= true;
 | |
| 
 | |
|     /* Fill the Alter_inplace_info structure. */
 | |
|     if (fill_alter_inplace_info(thd, table, &ha_alter_info))
 | |
|       goto err_new_table_cleanup;
 | |
| 
 | |
|     if (ha_alter_info.inplace_supported == HA_ALTER_INPLACE_NOT_SUPPORTED)
 | |
|     {
 | |
|       if (alter_info->algorithm_is_nocopy(thd))
 | |
|       {
 | |
|         my_error(ER_ALTER_OPERATION_NOT_SUPPORTED, MYF(0),
 | |
|                  alter_info->algorithm_clause(thd), "ALGORITHM=COPY");
 | |
|         goto err_new_table_cleanup;
 | |
|       }
 | |
|       goto alter_copy;
 | |
|     }
 | |
| 
 | |
|     alter_ctx.tmp_storage_engine_name_partitioned=
 | |
|       table->file->partition_engine();
 | |
|     alter_ctx.tmp_storage_engine_name.length=
 | |
|       (strmake((char*) alter_ctx.tmp_storage_engine_name.str,
 | |
|                table->file->real_table_type(),
 | |
|                sizeof(alter_ctx.tmp_storage_engine_buff)-1) -
 | |
|        alter_ctx.tmp_storage_engine_name.str);
 | |
| 
 | |
|     /*
 | |
|       We can ignore ALTER_COLUMN_ORDER and instead check
 | |
|       ALTER_STORED_COLUMN_ORDER & ALTER_VIRTUAL_COLUMN_ORDER. This
 | |
|       is ok as ALTER_COLUMN_ORDER may be wrong if we use AFTER last_field
 | |
|       ALTER_COLUMN_NAME is set if field really was renamed.
 | |
|     */
 | |
| 
 | |
|     if (!(ha_alter_info.handler_flags &
 | |
|           ~(ALTER_COLUMN_ORDER | ALTER_RENAME_COLUMN | ALTER_INDEX_ORDER)))
 | |
|     {
 | |
|       /*
 | |
|         No-op ALTER, no need to call handler API functions.
 | |
| 
 | |
|         If this code path is entered for an ALTER statement that
 | |
|         should not be a real no-op, new handler flags should be added
 | |
|         and fill_alter_inplace_info() adjusted.
 | |
| 
 | |
|         Note that we can end up here if an ALTER statement has clauses
 | |
|         that cancel each other out (e.g. ADD/DROP identically index).
 | |
| 
 | |
|         Also note that we ignore the LOCK clause here.
 | |
| 
 | |
|         TODO don't create partitioning metadata in the first place
 | |
| 
 | |
|         TODO: Now case-change index name is treated as noop which is not quite
 | |
|               correct.
 | |
|       */
 | |
|       table->file->ha_create_partitioning_metadata(alter_ctx.get_tmp_path(),
 | |
|                                                    NULL, CHF_DELETE_FLAG);
 | |
|       goto end_inplace;
 | |
|     }
 | |
| 
 | |
|     // We assume that the table is non-temporary.
 | |
|     DBUG_ASSERT(!table->s->tmp_table);
 | |
| 
 | |
|     if (create_table_for_inplace_alter(thd, alter_ctx, &frm, &altered_share,
 | |
|                                        &altered_table))
 | |
|       goto err_new_table_cleanup;
 | |
|     /*
 | |
|       Avoid creating frm again in ha_create_table() if inplace alter will not
 | |
|       be used.
 | |
|     */
 | |
|     frm_is_created= 1;
 | |
| 
 | |
|     /* Set markers for fields in TABLE object for altered table. */
 | |
|     update_altered_table(ha_alter_info, &altered_table);
 | |
| 
 | |
|     /*
 | |
|       Mark all columns in 'altered_table' as used to allow usage
 | |
|       of its record[0] buffer and Field objects during in-place
 | |
|       ALTER TABLE.
 | |
|     */
 | |
|     altered_table.column_bitmaps_set_no_signal(&altered_table.s->all_set,
 | |
|                                                &altered_table.s->all_set);
 | |
|     restore_record(&altered_table, s->default_values); // Create empty record
 | |
|     /* Check that we can call default functions with default field values */
 | |
|     thd->count_cuted_fields= CHECK_FIELD_EXPRESSION;
 | |
|     altered_table.reset_default_fields();
 | |
|     if (altered_table.default_field &&
 | |
|         altered_table.update_default_fields(true))
 | |
|     {
 | |
|       cleanup_table_after_inplace_alter(&altered_table);
 | |
|       goto err_new_table_cleanup;
 | |
|     }
 | |
|     thd->count_cuted_fields= CHECK_FIELD_IGNORE;
 | |
| 
 | |
|     if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
 | |
|       ha_alter_info.online= true;
 | |
|     // Ask storage engine whether to use copy or in-place
 | |
|     {
 | |
|       Check_level_instant_set check_level_save(thd, CHECK_FIELD_WARN);
 | |
|       ha_alter_info.inplace_supported=
 | |
|         table->file->check_if_supported_inplace_alter(&altered_table,
 | |
|                                                       &ha_alter_info);
 | |
|     }
 | |
| 
 | |
|     if (ha_alter_info.inplace_supported != HA_ALTER_INPLACE_NOT_SUPPORTED)
 | |
|     {
 | |
|       List_iterator<Key> it(alter_info->key_list);
 | |
|       while (Key *k= it++)
 | |
|       {
 | |
|         if (k->without_overlaps)
 | |
|         {
 | |
|           ha_alter_info.inplace_supported= HA_ALTER_INPLACE_NOT_SUPPORTED;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     if (alter_info->supports_algorithm(thd, &ha_alter_info) ||
 | |
|         alter_info->supports_lock(thd, online, &ha_alter_info))
 | |
|     {
 | |
|       cleanup_table_after_inplace_alter(&altered_table);
 | |
|       goto err_new_table_cleanup;
 | |
|     }
 | |
| 
 | |
|     // If SHARED lock and no particular algorithm was requested, use COPY.
 | |
|     if (ha_alter_info.inplace_supported == HA_ALTER_INPLACE_EXCLUSIVE_LOCK &&
 | |
|         alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_SHARED &&
 | |
|         alter_info->algorithm(thd) == Alter_info::ALTER_TABLE_ALGORITHM_DEFAULT)
 | |
|       use_inplace= false;
 | |
| 
 | |
|     if (ha_alter_info.inplace_supported == HA_ALTER_INPLACE_NOT_SUPPORTED)
 | |
|       use_inplace= false;
 | |
| 
 | |
|     if (use_inplace)
 | |
|     {
 | |
|       table->s->frm_image= &frm;
 | |
|       /*
 | |
|         Set the truncated column values of thd as warning
 | |
|         for alter table.
 | |
|       */
 | |
|       enum_check_fields org_count_cuted_fields= thd->count_cuted_fields;
 | |
|       thd->count_cuted_fields= CHECK_FIELD_WARN;
 | |
|       int res= mysql_inplace_alter_table(thd, table_list, table, &altered_table,
 | |
|                                          &ha_alter_info,
 | |
|                                          &target_mdl_request, &ddl_log_state,
 | |
|                                          &trigger_param,
 | |
|                                          &alter_ctx, partial_alter,
 | |
|                                          start_alter_id, if_exists);
 | |
|       thd->count_cuted_fields= org_count_cuted_fields;
 | |
|       inplace_alter_table_committed= ha_alter_info.inplace_alter_table_committed;
 | |
|       inplace_alter_table_committed_argument=
 | |
|         ha_alter_info.inplace_alter_table_committed_argument;
 | |
|       if (res)
 | |
|       {
 | |
|         cleanup_table_after_inplace_alter(&altered_table);
 | |
|         goto err_cleanup;
 | |
|       }
 | |
|       cleanup_table_after_inplace_alter_keep_files(&altered_table);
 | |
| 
 | |
|       goto end_inplace;
 | |
|     }
 | |
|     else
 | |
|       cleanup_table_after_inplace_alter_keep_files(&altered_table);
 | |
|   }
 | |
| 
 | |
| alter_copy:
 | |
|   /* ALTER TABLE using copy algorithm. */
 | |
| 
 | |
|   /* Check if ALTER TABLE is compatible with foreign key definitions. */
 | |
|   if (fk_prepare_copy_alter_table(thd, table, alter_info, &alter_ctx))
 | |
|     goto err_new_table_cleanup;
 | |
| 
 | |
|   if (!table->s->tmp_table)
 | |
|   {
 | |
|     // If EXCLUSIVE lock is requested, upgrade already.
 | |
|     if (alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE &&
 | |
|         wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
 | |
|       goto err_new_table_cleanup;
 | |
| 
 | |
|     /*
 | |
|       Otherwise upgrade to SHARED_NO_WRITE.
 | |
|       Note that under LOCK TABLES, we will already have SHARED_NO_READ_WRITE.
 | |
|     */
 | |
|     if (alter_info->requested_lock != Alter_info::ALTER_TABLE_LOCK_EXCLUSIVE &&
 | |
|         thd->mdl_context.upgrade_shared_lock(mdl_ticket, MDL_SHARED_NO_WRITE,
 | |
|                                              thd->variables.lock_wait_timeout))
 | |
|       goto err_new_table_cleanup;
 | |
| 
 | |
|     DEBUG_SYNC(thd, "alter_table_copy_after_lock_upgrade");
 | |
|   }
 | |
|   else
 | |
|     thd->close_unused_temporary_table_instances(table_list);
 | |
| 
 | |
|   if (table->s->tmp_table == NO_TMP_TABLE)
 | |
|   {
 | |
|     if (write_bin_log_start_alter(thd, partial_alter, start_alter_id,
 | |
|                                   if_exists))
 | |
|       goto err_new_table_cleanup;
 | |
|   }
 | |
|   else if (start_alter_id)
 | |
|   {
 | |
|     DBUG_ASSERT(thd->rgi_slave);
 | |
| 
 | |
|     my_error(ER_INCONSISTENT_SLAVE_TEMP_TABLE, MYF(0), thd->query(),
 | |
|              table_list->db.str, table_list->table_name.str);
 | |
|     goto err_new_table_cleanup;
 | |
|   }
 | |
| 
 | |
|   DBUG_EXECUTE_IF("start_alter_delay_master", {
 | |
|     debug_sync_set_action(thd,
 | |
|                           STRING_WITH_LEN("now wait_for alter_cont NO_CLEAR_EVENT"));
 | |
|       });
 | |
|   // It's now safe to take the table level lock.
 | |
|   if (lock_tables(thd, table_list, alter_ctx.tables_opened,
 | |
|                   MYSQL_LOCK_USE_MALLOC))
 | |
|     goto err_new_table_cleanup;
 | |
| 
 | |
|   ddl_log_update_phase(&ddl_log_state, DDL_ALTER_TABLE_PHASE_CREATED);
 | |
| 
 | |
|   if (ha_create_table(thd, alter_ctx.get_tmp_path(),
 | |
|                       alter_ctx.new_db.str, alter_ctx.new_name.str,
 | |
|                       create_info, &frm, frm_is_created))
 | |
|     goto err_new_table_cleanup;
 | |
| 
 | |
|   debug_crash_here("ddl_log_alter_after_create_table");
 | |
| 
 | |
|   /* Mark that we have created table in storage engine. */
 | |
|   no_ha_table= false;
 | |
|   DEBUG_SYNC(thd, "alter_table_intermediate_table_created");
 | |
| 
 | |
|   /* Open the table since we need to copy the data. */
 | |
|   new_table= thd->create_and_open_tmp_table(&frm, alter_ctx.get_tmp_path(),
 | |
|                                             alter_ctx.new_db,
 | |
|                                             alter_ctx.new_name, true);
 | |
|   if (!new_table)
 | |
|     goto err_new_table_cleanup;
 | |
| 
 | |
|   DBUG_ASSERT(new_table->s->hlindexes() <= 1);
 | |
|   for (uint i= new_table->s->keys; i < new_table->s->total_keys; i++)
 | |
|     if (new_table->hlindex_open(i))
 | |
|       goto err_new_table_cleanup;
 | |
| 
 | |
|   if (table->s->tmp_table != NO_TMP_TABLE)
 | |
|   {
 | |
|     /* in case of alter temp table send the tracker in OK packet */
 | |
|     thd->session_tracker.state_change.mark_as_changed(thd);
 | |
|   }
 | |
| 
 | |
|   if (online)
 | |
|   {
 | |
|     const char *reason= online_alter_check_supported(thd, alter_info, table,
 | |
|                                                      new_table,
 | |
|                                                      &online);
 | |
|     if (reason &&
 | |
|         alter_info->requested_lock == Alter_info::ALTER_TABLE_LOCK_NONE)
 | |
|     {
 | |
|       DBUG_ASSERT(!online);
 | |
|       my_error(ER_ALTER_OPERATION_NOT_SUPPORTED_REASON, MYF(0),
 | |
|                "LOCK=NONE", reason, "LOCK=SHARED");
 | |
|       goto err_new_table_cleanup;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Note: In case of MERGE table, we do not attach children. We do not
 | |
|     copy data for MERGE tables. Only the children have data.
 | |
|   */
 | |
| 
 | |
|   /* Copy the data if necessary. */
 | |
|   thd->count_cuted_fields= CHECK_FIELD_WARN;	// calc cuted fields
 | |
|   thd->cuted_fields=0L;
 | |
| 
 | |
|   /*
 | |
|     Collect fields that was renamed.
 | |
|     We do not do that if fill_alter_inplace_info() has
 | |
|     already collected renamed fields.
 | |
|   */
 | |
|   if (alter_info->flags & (ALTER_CHANGE_COLUMN | ALTER_RENAME_COLUMN) &&
 | |
|       alter_info->rename_stat_fields.is_empty())
 | |
|     if (alter_info->collect_renamed_fields(thd))
 | |
|       goto err_new_table_cleanup;
 | |
| 
 | |
|   /*
 | |
|     We do not copy data for MERGE tables. Only the children have data.
 | |
|     MERGE tables have HA_NO_COPY_ON_ALTER set.
 | |
|   */
 | |
|   if (!(new_table->file->ha_table_flags() & HA_NO_COPY_ON_ALTER))
 | |
|   {
 | |
|     new_table->next_number_field=new_table->found_next_number_field;
 | |
|     THD_STAGE_INFO(thd, stage_copy_to_tmp_table);
 | |
|     DBUG_EXECUTE_IF("abort_copy_table", {
 | |
|         my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
 | |
|         goto err_new_table_cleanup;
 | |
|       });
 | |
| 
 | |
|     /*
 | |
|       If old table was a shared table and new table is not same type,
 | |
|       the slaves will not be able to recreate the data. In this case we
 | |
|       write the CREATE TABLE statement for the new table to the log and
 | |
|       log all inserted rows to the table.
 | |
|     */
 | |
|     if ((table->file->partition_ht()->flags &
 | |
|          HTON_TABLE_MAY_NOT_EXIST_ON_SLAVE) &&
 | |
|         (table->file->partition_ht() != new_table->file->partition_ht()) &&
 | |
|         thd->binlog_table_should_be_logged(&new_table->s->db))
 | |
|     {
 | |
|       /*
 | |
|         'new_table' is marked as internal temp table, but we want to have
 | |
|         the logging based on the original table type
 | |
|       */
 | |
|       bool res;
 | |
|       tmp_table_type org_tmp_table= new_table->s->tmp_table;
 | |
|       new_table->s->tmp_table= table->s->tmp_table;
 | |
| 
 | |
|       /* Force row logging, even if the table was created as 'temporary' */
 | |
|       new_table->s->can_do_row_logging= 1;
 | |
|       thd->binlog_start_trans_and_stmt();
 | |
|       thd->variables.option_bits|= OPTION_BIN_COMMIT_OFF;
 | |
|       res= (binlog_drop_table(thd, table) ||
 | |
|             binlog_create_table(thd, new_table, 1));
 | |
|       new_table->s->tmp_table= org_tmp_table;
 | |
|       if (res)
 | |
|         goto err_new_table_cleanup;
 | |
|       /*
 | |
|         ha_write_row() will log inserted rows in copy_data_between_tables().
 | |
|         No additional logging of query is needed
 | |
|       */
 | |
|       binlog_as_create_select= 1;
 | |
|       DBUG_ASSERT(new_table->file->row_logging);
 | |
|       new_table->mark_columns_needed_for_insert();
 | |
|       mysql_bin_log.write_table_map(thd, new_table, 1);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       if ORDER BY: sorting
 | |
|       always: copying, building indexes.
 | |
|       if online: reading up the binlog (second binlog is being written)
 | |
|                  reading up the second binlog under exclusive lock
 | |
|     */
 | |
|     thd_progress_init(thd, MY_TEST(order) + 2 + 2 * MY_TEST(online));
 | |
|     
 | |
|     if (copy_data_between_tables(thd, table, new_table,
 | |
|                                  ignore,
 | |
|                                  order_num, order, &copied, &deleted,
 | |
|                                  alter_info,
 | |
|                                  &alter_ctx, online, start_alter_id))
 | |
|       goto err_new_table_cleanup;
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     if (!table->s->tmp_table &&
 | |
|         wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
 | |
|       goto err_new_table_cleanup;
 | |
|     THD_STAGE_INFO(thd, stage_manage_keys);
 | |
|     alter_table_manage_keys(table, table->file->indexes_are_disabled(),
 | |
|                             alter_info->keys_onoff);
 | |
|     if (trans_commit_stmt(thd) || trans_commit_implicit(thd))
 | |
|       goto err_new_table_cleanup;
 | |
|   }
 | |
|   thd->count_cuted_fields= CHECK_FIELD_IGNORE;
 | |
| 
 | |
|   if (start_alter_id)
 | |
|   {
 | |
|     DBUG_ASSERT(thd->slave_thread);
 | |
| 
 | |
|     if (wait_for_master(thd))
 | |
|       goto err_new_table_cleanup;
 | |
|   }
 | |
|   if (table->s->tmp_table != NO_TMP_TABLE)
 | |
|   {
 | |
|     /* Release lock if this is a transactional temporary table */
 | |
|     if (thd->lock)
 | |
|     {
 | |
|       if (thd->locked_tables_mode != LTM_LOCK_TABLES &&
 | |
|           thd->locked_tables_mode != LTM_PRELOCKED_UNDER_LOCK_TABLES)
 | |
|       {
 | |
|         int tmp_error= mysql_unlock_tables(thd, thd->lock);
 | |
|         thd->lock= NULL;
 | |
|         if (tmp_error)
 | |
|           goto err_new_table_cleanup;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         /*
 | |
|           If LOCK TABLES list is not empty and contains this table,
 | |
|           unlock the table and remove the table from this list.
 | |
|         */
 | |
|         if (mysql_lock_remove(thd, thd->lock, table))
 | |
|           goto err_new_table_cleanup;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /* Remove link to old table and rename the new one */
 | |
|     thd->drop_temporary_table(table, NULL, true);
 | |
|     /* Should pass the 'new_name' as we store table name in the cache */
 | |
|     if (thd->rename_temporary_table(new_table, &alter_ctx.new_db,
 | |
|                                     &alter_ctx.new_name))
 | |
|       goto err_new_table_cleanup;
 | |
| 
 | |
|     if (binlog_as_create_select)
 | |
|     {
 | |
|       /*
 | |
|         The original table is now deleted. Copy the
 | |
|         DROP + CREATE + data statement to the binary log
 | |
|       */
 | |
|       thd->variables.option_bits&= ~OPTION_BIN_COMMIT_OFF;
 | |
|       binlog_commit(thd, true);
 | |
|     }
 | |
| 
 | |
|     DBUG_ASSERT(!start_alter_id);  // no 2 phase logging for
 | |
|     DBUG_ASSERT(!partial_alter);   // temporary table alter
 | |
| 
 | |
|     /* We don't replicate alter table statement on temporary tables */
 | |
|     if (!thd->is_binlog_format_row() &&
 | |
|         table_creation_was_logged &&
 | |
|         !binlog_as_create_select)
 | |
|     {
 | |
|       int tmp_error;
 | |
|       thd->binlog_xid= thd->query_id;
 | |
|       ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
 | |
|       tmp_error= write_bin_log_with_if_exists(thd, true, false, log_if_exists);
 | |
|       thd->binlog_xid= 0;
 | |
|       if (tmp_error)
 | |
|         goto err_cleanup;
 | |
|       new_table->s->table_creation_was_logged= 1;
 | |
|     }
 | |
|     goto end_temporary;
 | |
|   }
 | |
| 
 | |
|   /* Remember storage engine name for the new table */
 | |
|   alter_ctx.tmp_storage_engine_name_partitioned=
 | |
|     new_table->file->partition_engine();
 | |
|   alter_ctx.tmp_storage_engine_name.length=
 | |
|     (strmake((char*) alter_ctx.tmp_storage_engine_name.str,
 | |
|              new_table->file->real_table_type(),
 | |
|              sizeof(alter_ctx.tmp_storage_engine_buff)-1) -
 | |
|      alter_ctx.tmp_storage_engine_name.str);
 | |
| 
 | |
|   /*
 | |
|     Check if file names for the engine are unique.  If we change engine
 | |
|     and file names are unique then we don't need to rename the original
 | |
|     table to a temporary name during the rename phase
 | |
| 
 | |
|     File names are unique if engine changed and
 | |
|     - Either new or old engine does not store the table in files
 | |
|     - Neither old or new engine uses files from another engine
 | |
|       The above is mainly true for the sequence and the partition engine.
 | |
|   */
 | |
|   engine_changed= ((new_table->file->storage_ht() !=
 | |
|                     table->file->storage_ht()) &&
 | |
|                    ((!(new_table->file->ha_table_flags() & HA_FILE_BASED) ||
 | |
|                      !(table->file->ha_table_flags() & HA_FILE_BASED))) &&
 | |
|                    !(table->file->ha_table_flags() & HA_REUSES_FILE_NAMES) &&
 | |
|                    !(new_table->file->ha_table_flags() &
 | |
|                      HA_REUSES_FILE_NAMES));
 | |
|   /*
 | |
|     Close the intermediate table that will be the new table, but do
 | |
|     not delete it! Even though MERGE tables do not have their children
 | |
|     attached here it is safe to call THD::drop_temporary_table().
 | |
|   */
 | |
|   thd->drop_temporary_table(new_table, NULL, false);
 | |
|   new_table= NULL;
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_before_rename_result_table");
 | |
| 
 | |
|   /*
 | |
|     Data is copied. Now we:
 | |
|     1) Wait until all other threads will stop using old version of table
 | |
|        by upgrading shared metadata lock to exclusive one.
 | |
|     2) Close instances of table open by this thread and replace them
 | |
|        with placeholders to simplify reopen process.
 | |
|     3) Rename the old table to a temp name, rename the new one to the
 | |
|        old name.
 | |
|     4) If we are under LOCK TABLES and don't do ALTER TABLE ... RENAME
 | |
|        we reopen new version of table.
 | |
|     5) Write statement to the binary log.
 | |
|     6) If we are under LOCK TABLES and do ALTER TABLE ... RENAME we
 | |
|        remove placeholders and release metadata locks.
 | |
|     7) If we are not not under LOCK TABLES we rely on the caller
 | |
|       (mysql_execute_command()) to release metadata locks.
 | |
|   */
 | |
| 
 | |
|   debug_crash_here("ddl_log_alter_after_copy");      // Use old table
 | |
|   /*
 | |
|     We are now ready to use the new table. Update the state in the
 | |
|     ddl log so that we recovery know that the new table is ready and
 | |
|     in case of crash it should use the new one and log the query
 | |
|     to the binary log.
 | |
|   */
 | |
|   if (engine_changed)
 | |
|     ddl_log_add_flag(&ddl_log_state, DDL_LOG_FLAG_ALTER_ENGINE_CHANGED);
 | |
|   ddl_log_update_phase(&ddl_log_state, DDL_ALTER_TABLE_PHASE_COPIED);
 | |
|   debug_crash_here("ddl_log_alter_after_log");       // Use new table
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_rename_result_table);
 | |
| 
 | |
|   if (wait_while_table_is_used(thd, table, HA_EXTRA_PREPARE_FOR_RENAME))
 | |
|     goto err_new_table_cleanup;
 | |
| 
 | |
|   /* Now we are the only user. Update the data in EITS tables */
 | |
|   alter_info->apply_statistics_deletes_renames(thd, table);
 | |
| 
 | |
|   close_all_tables_for_name(thd, table->s,
 | |
|                             alter_ctx.is_table_renamed() ?
 | |
|                             HA_EXTRA_PREPARE_FOR_RENAME:
 | |
|                             HA_EXTRA_NOT_USED,
 | |
|                             NULL);
 | |
|   table_list->table= table= NULL;                  /* Safety */
 | |
| 
 | |
|   thd_progress_end(thd);
 | |
| 
 | |
|   DBUG_PRINT("info", ("is_table_renamed: %d  engine_changed: %d",
 | |
|                       alter_ctx.is_table_renamed(), engine_changed));
 | |
| 
 | |
|   /*
 | |
|     InnoDB cannot use the rename optimization when foreign key
 | |
|     constraint is involved because InnoDB fails to drop the
 | |
|     parent table due to foreign key constraint
 | |
|   */
 | |
|   if (!alter_ctx.is_table_renamed() || alter_ctx.fk_error_if_delete_row)
 | |
|   {
 | |
|     /*
 | |
|       Rename the old table to temporary name to have a backup in case
 | |
|       anything goes wrong while renaming the new table.
 | |
| 
 | |
|       We only have to do this if name of the table is not changed.
 | |
|       If we are changing to use another table handler, we don't
 | |
|       have to do the rename as the table names will not interfer.
 | |
|     */
 | |
|     if (mysql_rename_table(old_db_type, &alter_ctx.db, &alter_ctx.table_name,
 | |
|                            &alter_ctx.db, &backup_name, &alter_ctx.id,
 | |
|                            FN_TO_IS_TMP |
 | |
|                            (engine_changed ? QRMT_FRM : QRMT_DEFAULT)))
 | |
|     {
 | |
|       // Rename to temporary name failed, delete the new table, abort ALTER.
 | |
|       (void) quick_rm_table(thd, new_db_type, &alter_ctx.new_db,
 | |
|                             &alter_ctx.tmp_name, QRMT_DEFAULT | FN_IS_TMP);
 | |
|       goto err_with_mdl;
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* The original table is the backup */
 | |
|     backup_name= alter_ctx.table_name;
 | |
|     PSI_CALL_drop_table_share(0, alter_ctx.db.str, (int) alter_ctx.db.length,
 | |
|                               alter_ctx.table_name.str, (int) alter_ctx.table_name.length);
 | |
|   }
 | |
|   debug_crash_here("ddl_log_alter_after_rename_to_backup");
 | |
| 
 | |
|   if (!alter_ctx.is_table_renamed())
 | |
|   {
 | |
|     /*
 | |
|       We should not set this stage in case of rename as we in this case
 | |
|       must execute DDL_ALTER_TABLE_PHASE_COPIED to remove the orignal table
 | |
|     */
 | |
|     ddl_log_update_phase(&ddl_log_state, DDL_ALTER_TABLE_PHASE_OLD_RENAMED);
 | |
|   }
 | |
| 
 | |
|   debug_crash_here("ddl_log_alter_after_rename_to_backup_log");
 | |
| 
 | |
|   // Rename the new table to the correct name.
 | |
|   if (mysql_rename_table(new_db_type, &alter_ctx.new_db, &alter_ctx.tmp_name,
 | |
|                          &alter_ctx.new_db, &alter_ctx.new_alias,
 | |
|                          &alter_ctx.tmp_id,
 | |
|                          QRMT_DEFAULT | FN_FROM_IS_TMP))
 | |
|   {
 | |
|     // Rename failed, delete the temporary table.
 | |
|     ddl_log_update_phase(&ddl_log_state, DDL_ALTER_TABLE_PHASE_RENAME_FAILED);
 | |
|     (void) quick_rm_table(thd, new_db_type, &alter_ctx.new_db,
 | |
|                           &alter_ctx.tmp_name, QRMT_DEFAULT | FN_IS_TMP);
 | |
| 
 | |
|     if (!alter_ctx.is_table_renamed() || alter_ctx.fk_error_if_delete_row)
 | |
|     {
 | |
|       // Restore the backup of the original table to the old name.
 | |
|       (void) mysql_rename_table(old_db_type, &alter_ctx.db, &backup_name,
 | |
|                                 &alter_ctx.db, &alter_ctx.alias, &alter_ctx.id,
 | |
|                                 FN_FROM_IS_TMP | NO_FK_CHECKS |
 | |
|                                 (engine_changed ? QRMT_FRM : QRMT_DEFAULT));
 | |
|     }
 | |
|     goto err_with_mdl;
 | |
|   }
 | |
|   debug_crash_here("ddl_log_alter_after_rename_to_original");
 | |
| 
 | |
|   // Check if we renamed the table and if so update trigger files.
 | |
|   if (alter_ctx.is_table_renamed())
 | |
|   {
 | |
|     debug_crash_here("ddl_log_alter_before_rename_triggers");
 | |
|     if (Table_triggers_list::change_table_name(thd, &trigger_param,
 | |
|                                                &alter_ctx.db,
 | |
|                                                &alter_ctx.alias,
 | |
|                                                &alter_ctx.table_name,
 | |
|                                                &alter_ctx.new_db,
 | |
|                                                &alter_ctx.new_alias))
 | |
|     {
 | |
|       // Rename succeeded, delete the new table.
 | |
|       (void) quick_rm_table(thd, new_db_type, &alter_ctx.new_db,
 | |
|                             &alter_ctx.new_alias, QRMT_DEFAULT);
 | |
|       // Restore the backup of the original table to the old name.
 | |
|       (void) mysql_rename_table(old_db_type, &alter_ctx.db, &backup_name,
 | |
|                                 &alter_ctx.db, &alter_ctx.alias, &alter_ctx.id,
 | |
|                                 FN_FROM_IS_TMP | NO_FK_CHECKS |
 | |
|                                 (engine_changed ? QRMT_FRM : QRMT_DEFAULT));
 | |
|       goto err_with_mdl;
 | |
|     }
 | |
|     rename_table_in_stat_tables(thd, &alter_ctx.db, &alter_ctx.alias,
 | |
|                                 &alter_ctx.new_db, &alter_ctx.new_alias);
 | |
|     debug_crash_here("ddl_log_alter_after_rename_triggers");
 | |
|   }
 | |
| 
 | |
|   // ALTER TABLE succeeded, delete the backup of the old table.
 | |
|   // a failure to delete isn't an error, as we cannot rollback ALTER anymore
 | |
|   thd->push_internal_handler(&errors_to_warnings);
 | |
|   error_handler_pushed=1;
 | |
| 
 | |
|   if (engine_changed)
 | |
|   {
 | |
|     /* the .frm file was removed but not the original table */
 | |
|     quick_rm_table(thd, old_db_type, &alter_ctx.db, &alter_ctx.table_name,
 | |
|                    QRMT_HANDLER | (engine_changed ? 0 : FN_IS_TMP));
 | |
|   }
 | |
| 
 | |
|   debug_crash_here("ddl_log_alter_after_delete_backup");
 | |
| 
 | |
|   quick_rm_table(thd, old_db_type, &alter_ctx.db, &backup_name,
 | |
|                  FN_IS_TMP | (engine_changed ? QRMT_FRM : QRMT_DEFAULT));
 | |
| 
 | |
|   debug_crash_here("ddl_log_alter_after_drop_original_table");
 | |
|   if (binlog_as_create_select)
 | |
|   {
 | |
|     /*
 | |
|       The original table is now deleted. Copy the
 | |
|       DROP + CREATE + data statement to the binary log
 | |
|     */
 | |
|     thd->variables.option_bits&= ~OPTION_BIN_COMMIT_OFF;
 | |
|     thd->binlog_xid= thd->query_id;
 | |
|     ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
 | |
|     binlog_commit(thd, true);
 | |
|     thd->binlog_xid= 0;
 | |
|   }
 | |
| 
 | |
| end_inplace:
 | |
|   thd->variables.option_bits&= ~OPTION_BIN_COMMIT_OFF;
 | |
| 
 | |
|   if (!error_handler_pushed)
 | |
|     thd->push_internal_handler(&errors_to_warnings);
 | |
| 
 | |
|   thd->locked_tables_list.reopen_tables(thd, false);
 | |
| 
 | |
|   thd->pop_internal_handler();
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_end);
 | |
|   DEBUG_SYNC(thd, "alter_table_before_main_binlog");
 | |
| 
 | |
|   DBUG_ASSERT(!(mysql_bin_log.is_open() &&
 | |
|                 thd->is_binlog_format_row() &&
 | |
|                 (create_info->tmp_table())));
 | |
| 
 | |
|   if (start_alter_id)
 | |
|   {
 | |
|     if (!is_reg_table)
 | |
|     {
 | |
|       my_error(ER_INCONSISTENT_SLAVE_TEMP_TABLE, MYF(0), thd->query(),
 | |
|                table_list->db.str, table_list->table_name.str);
 | |
|       DBUG_RETURN(true);
 | |
|     }
 | |
| 
 | |
|     if (process_master_state(thd, 0, start_alter_id, if_exists))
 | |
|       DBUG_RETURN(true);
 | |
|   }
 | |
|   else if (!binlog_as_create_select)
 | |
|   {
 | |
|     int tmp_error;
 | |
|     thd->binlog_xid= thd->query_id;
 | |
|     ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
 | |
|     tmp_error= write_bin_log_with_if_exists(thd, true, false, log_if_exists,
 | |
|                                             partial_alter);
 | |
|     thd->binlog_xid= 0;
 | |
|     if (tmp_error)
 | |
|       goto err_cleanup;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     We have to close the ddl log as soon as possible, after binlogging the
 | |
|     query, for inplace alter table.
 | |
|   */
 | |
|   ddl_log_complete(&ddl_log_state);
 | |
|   if (inplace_alter_table_committed)
 | |
|   {
 | |
|     /* Signal to storage engine that ddl log is committed */
 | |
|     (*inplace_alter_table_committed)(inplace_alter_table_committed_argument);
 | |
|     inplace_alter_table_committed= 0;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     We do not log ALTER ENTRY, when used with REPAIR, to the ddl.log as the
 | |
|     logging will done in mysql_admin_table()
 | |
|   */
 | |
|   if (!alter_ctx.tmp_table && !create_info->repair)
 | |
|   {
 | |
|     backup_log_info ddl_log;
 | |
|     bzero(&ddl_log, sizeof(ddl_log));
 | |
|     ddl_log.query= { C_STRING_WITH_LEN("ALTER") };
 | |
|     ddl_log.org_storage_engine_name= alter_ctx.storage_engine_name;
 | |
|     ddl_log.org_partitioned=         alter_ctx.storage_engine_partitioned;
 | |
|     ddl_log.org_database=            alter_ctx.db;
 | |
|     ddl_log.org_table=               alter_ctx.table_name;
 | |
|     ddl_log.org_table_id=            alter_ctx.id;
 | |
|     ddl_log.new_storage_engine_name= alter_ctx.tmp_storage_engine_name;
 | |
|     ddl_log.new_partitioned=    alter_ctx.tmp_storage_engine_name_partitioned;
 | |
|     ddl_log.new_database=            alter_ctx.new_db;
 | |
|     ddl_log.new_table=               alter_ctx.new_alias;
 | |
|     ddl_log.new_table_id=            alter_ctx.tmp_id;
 | |
|     backup_log_ddl(&ddl_log);
 | |
|   }
 | |
|   if (create_info->repair)
 | |
|     memcpy(recreate_info->tabledef_version, alter_ctx.tmp_id.str,
 | |
|            sizeof(recreate_info->tabledef_version));
 | |
| 
 | |
|   table_list->table= NULL;			// For query cache
 | |
|   query_cache_invalidate3(thd, table_list, false);
 | |
| 
 | |
|   if (thd->locked_tables_mode == LTM_LOCK_TABLES ||
 | |
|       thd->locked_tables_mode == LTM_PRELOCKED_UNDER_LOCK_TABLES)
 | |
|   {
 | |
|     if (alter_ctx.is_table_renamed())
 | |
|       thd->mdl_context.release_all_locks_for_name(mdl_ticket);
 | |
|     else
 | |
|       mdl_ticket->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
 | |
|   }
 | |
| 
 | |
| end_temporary:
 | |
|   my_free(const_cast<uchar*>(frm.str));
 | |
| 
 | |
|   thd->variables.option_bits&= ~OPTION_BIN_COMMIT_OFF;
 | |
| 
 | |
|   thd_progress_end(thd);
 | |
|   recreate_info->copied= copied;
 | |
|   recreate_info->duplicate= deleted;
 | |
|   thd->my_ok_with_recreate_info(*recreate_info,
 | |
|                                 (ulong) thd->get_stmt_da()->
 | |
|                                           current_statement_warn_count());
 | |
|   DEBUG_SYNC(thd, "alter_table_inplace_trans_commit");
 | |
|   DBUG_RETURN(false);
 | |
| 
 | |
| err_new_table_cleanup:
 | |
|   DBUG_PRINT("error", ("err_new_table_cleanup"));
 | |
|   thd->variables.option_bits&= ~OPTION_BIN_COMMIT_OFF;
 | |
| 
 | |
|   thd_progress_end(thd);
 | |
| 
 | |
|   /*
 | |
|     No default value was provided for a DATE/DATETIME field, the
 | |
|     current sql_mode doesn't allow the '0000-00-00' value and
 | |
|     the table to be altered isn't empty.
 | |
|     Report error here.
 | |
|   */
 | |
|   if (unlikely(alter_ctx.error_if_not_empty &&
 | |
|                thd->get_stmt_da()->current_row_for_warning()))
 | |
|   {
 | |
|     Abort_on_warning_instant_set aws(thd, true);
 | |
|     alter_ctx.report_implicit_default_value_error(thd, new_table
 | |
|                                                   ? new_table->s : table->s);
 | |
|   }
 | |
| 
 | |
|   if (new_table)
 | |
|   {
 | |
|     thd->drop_temporary_table(new_table, NULL, true);
 | |
|   }
 | |
|   else
 | |
|     (void) quick_rm_table(thd, new_db_type,
 | |
|                           &alter_ctx.new_db, &alter_ctx.tmp_name,
 | |
|                           FN_IS_TMP | (no_ha_table ?
 | |
|                                        QRMT_FRM | QRMT_PAR : QRMT_DEFAULT),
 | |
|                           alter_ctx.get_tmp_path());
 | |
|   DEBUG_SYNC(thd, "alter_table_after_temp_table_drop");
 | |
| err_cleanup:
 | |
|   my_free(const_cast<uchar*>(frm.str));
 | |
|   ddl_log_complete(&ddl_log_state);
 | |
|   if (inplace_alter_table_committed)
 | |
|   {
 | |
|     /* Signal to storage engine that ddl log is committed */
 | |
|     (*inplace_alter_table_committed)(inplace_alter_table_committed_argument);
 | |
|   }
 | |
|   DEBUG_SYNC(thd, "alter_table_after_temp_table_drop");
 | |
|   if (partial_alter || start_alter_id)
 | |
|     write_bin_log_start_alter_rollback(thd, start_alter_id, partial_alter,
 | |
|                                        if_exists);
 | |
|   DBUG_RETURN(true);
 | |
| 
 | |
| err_with_mdl:
 | |
|   ddl_log_complete(&ddl_log_state);
 | |
|   /*
 | |
|     An error happened while we were holding exclusive name metadata lock
 | |
|     on table being altered. To be safe under LOCK TABLES we should
 | |
|     remove all references to the altered table from the list of locked
 | |
|     tables and release the exclusive metadata lock.
 | |
|   */
 | |
|   thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
 | |
|   if (!table_list->table)
 | |
|     thd->mdl_context.release_all_locks_for_name(mdl_ticket);
 | |
|   goto err_cleanup;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Prepare the transaction for the alter table's copy phase.
 | |
| */
 | |
| 
 | |
| bool mysql_trans_prepare_alter_copy_data(THD *thd)
 | |
| {
 | |
|   DBUG_ENTER("mysql_trans_prepare_alter_copy_data");
 | |
|   /*
 | |
|     Turn off recovery logging since rollback of an alter table is to
 | |
|     delete the new table so there is no need to log the changes to it.
 | |
| 
 | |
|     This needs to be done before external_lock.
 | |
|   */
 | |
|   DBUG_RETURN(ha_enable_transaction(thd, FALSE) != 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Commit the copy phase of the alter table.
 | |
| */
 | |
| 
 | |
| bool mysql_trans_commit_alter_copy_data(THD *thd)
 | |
| {
 | |
|   bool error= FALSE;
 | |
|   uint save_unsafe_rollback_flags;
 | |
|   DBUG_ENTER("mysql_trans_commit_alter_copy_data");
 | |
| 
 | |
|   /* Save flags as trans_commit_implicit are deleting them */
 | |
|   save_unsafe_rollback_flags= thd->transaction->stmt.m_unsafe_rollback_flags;
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_copy_trans_commit");
 | |
| 
 | |
|   if (ha_enable_transaction(thd, TRUE))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   /*
 | |
|     Ensure that the new table is saved properly to disk before installing
 | |
|     the new .frm.
 | |
|     And that InnoDB's internal latches are released, to avoid deadlock
 | |
|     when waiting on other instances of the table before rename (Bug#54747).
 | |
|   */
 | |
|   if (trans_commit_stmt(thd))
 | |
|     error= TRUE;
 | |
|   if (trans_commit_implicit(thd))
 | |
|     error= TRUE;
 | |
| 
 | |
|   thd->transaction->stmt.m_unsafe_rollback_flags= save_unsafe_rollback_flags;
 | |
|   DBUG_RETURN(error);
 | |
| }
 | |
| 
 | |
| #ifdef HAVE_REPLICATION
 | |
| /*
 | |
|   locking ALTER TABLE doesn't issue ER_NO_DEFAULT_FOR_FIELD, so online
 | |
|   ALTER shouldn't either
 | |
| */
 | |
| class Has_default_error_handler : public Internal_error_handler
 | |
| {
 | |
| public:
 | |
|   bool handle_condition(THD *, uint sql_errno, const char *,
 | |
|                         Sql_condition::enum_warning_level *,
 | |
|                         const char *, Sql_condition **) override
 | |
|   {
 | |
|     return sql_errno == ER_NO_DEFAULT_FOR_FIELD;
 | |
|   }
 | |
| };
 | |
| 
 | |
| 
 | |
| static int online_alter_read_from_binlog(THD *thd, rpl_group_info *rgi,
 | |
|                                          Cache_flip_event_log *log,
 | |
|                                          ha_rows *found_rows)
 | |
| {
 | |
|   int error= 0;
 | |
| 
 | |
|   IO_CACHE *log_file= log->flip();
 | |
| 
 | |
|   thd_progress_report(thd, 1, MY_MAX(1, my_b_write_tell(log_file)));
 | |
| 
 | |
|   Has_default_error_handler hdeh;
 | |
|   thd->push_internal_handler(&hdeh);
 | |
|   do
 | |
|   {
 | |
|     const auto *descr_event= rgi->rli->relay_log.description_event_for_exec;
 | |
|     auto *ev= Log_event::read_log_event(log_file, &error, descr_event, 0, 1, ~0UL);
 | |
|     error= log_file->error;
 | |
|     if (unlikely(!ev))
 | |
|     {
 | |
|       if (error)
 | |
|         my_error(ER_IO_READ_ERROR,MYF(0), (ulong)EIO, strerror(EIO), "");
 | |
|       break;
 | |
|     }
 | |
|     DBUG_ASSERT(!error);
 | |
| 
 | |
|     ev->thd= thd;
 | |
|     error= ev->apply_event(rgi);
 | |
| 
 | |
|     error= error || thd->is_error();
 | |
|     if(likely(!error))
 | |
|       ev->online_alter_update_row_count(found_rows);
 | |
| 
 | |
|     if (ev != rgi->rli->relay_log.description_event_for_exec)
 | |
|       delete ev;
 | |
|     thd_progress_report(thd, my_b_tell(log_file), thd->progress.max_counter);
 | |
|     DEBUG_SYNC(thd, "alter_table_online_progress");
 | |
|   } while(!error);
 | |
|   thd->pop_internal_handler();
 | |
| 
 | |
|   return MY_TEST(error);
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /** Handle the error when copying data from source to target table.
 | |
| @param error          error code
 | |
| @param ignore         alter ignore statement
 | |
| @param to             target table handler
 | |
| @param thd            Mysql Thread
 | |
| @param alter_ctx      Runtime context for alter statement
 | |
| @retval false in case of error
 | |
| @retval true in case of skipping the row and continue alter operation */
 | |
| static bool
 | |
| copy_data_error_ignore(int &error, bool ignore, TABLE *to,
 | |
|                        THD *thd, Alter_table_ctx *alter_ctx)
 | |
| {
 | |
|   if (to->file->is_fatal_error(error, HA_CHECK_DUP))
 | |
|   {
 | |
|     /* Not a duplicate key error. */
 | |
|     to->file->print_error(error, MYF(0));
 | |
|     error= 1;
 | |
|     return false;
 | |
|   }
 | |
|   /* Duplicate key error. */
 | |
|   if (unlikely(alter_ctx->fk_error_if_delete_row))
 | |
|   {
 | |
|     /* We are trying to omit a row from the table which serves
 | |
|     as parent in a foreign key. This might have broken
 | |
|     referential integrity so emit an error. Note that we
 | |
|     can't ignore this error even if we are
 | |
|     executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but
 | |
|     doesn't allow to break unique or foreign key constraints, */
 | |
|     my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0),
 | |
|              alter_ctx->fk_error_id,
 | |
|              alter_ctx->fk_error_table);
 | |
|     return false;
 | |
|   }
 | |
|   if (ignore)
 | |
|     return true;
 | |
|   /* Ordinary ALTER TABLE. Report duplicate key error. */
 | |
|   uint key_nr= to->file->get_dup_key(error);
 | |
|   if (key_nr <= MAX_KEY)
 | |
|   {
 | |
|     const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME);
 | |
|     if (key_nr == 0 && to->s->keys > 0 &&
 | |
|         (to->key_info[0].key_part[0].field->flags &
 | |
|             AUTO_INCREMENT_FLAG))
 | |
|       err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE);
 | |
|     print_keydup_error(to,
 | |
|                        key_nr >= to->s->keys ? NULL :
 | |
|                        &to->key_info[key_nr],
 | |
|                        err_msg, MYF(0));
 | |
|   }
 | |
|   else
 | |
|     to->file->print_error(error, MYF(0));
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| static int
 | |
| copy_data_between_tables(THD *thd, TABLE *from, TABLE *to,
 | |
|                          bool ignore,
 | |
|                          uint order_num, ORDER *order,
 | |
|                          ha_rows *copied, ha_rows *deleted,
 | |
|                          Alter_info *alter_info,
 | |
|                          Alter_table_ctx *alter_ctx, bool online,
 | |
|                          uint64 start_alter_id)
 | |
| {
 | |
|   int error= 1;
 | |
|   Copy_field *copy= NULL, *copy_end;
 | |
|   ha_rows found_count= 0, delete_count= 0;
 | |
|   SORT_INFO  *file_sort= 0;
 | |
|   READ_RECORD info;
 | |
|   TABLE_LIST   tables;
 | |
|   List<Item>   fields;
 | |
|   List<Item>   all_fields;
 | |
|   bool auto_increment_field_copied= 0;
 | |
|   bool cleanup_done= 0;
 | |
|   bool init_read_record_done= 0;
 | |
|   sql_mode_t save_sql_mode= thd->variables.sql_mode;
 | |
|   ulonglong prev_insert_id, time_to_report_progress;
 | |
|   Field **dfield_ptr= to->default_field;
 | |
|   uint save_to_s_default_fields= to->s->default_fields;
 | |
|   bool make_versioned= !from->versioned() && to->versioned();
 | |
|   bool make_unversioned= from->versioned() && !to->versioned();
 | |
|   bool keep_versioned= from->versioned() && to->versioned();
 | |
|   bool bulk_insert_started= 0;
 | |
|   Field *to_row_start= NULL, *to_row_end= NULL, *from_row_end= NULL;
 | |
|   MYSQL_TIME query_start;
 | |
|   DBUG_ENTER("copy_data_between_tables");
 | |
| 
 | |
|   if (!(copy= new (thd->mem_root) Copy_field[to->s->fields]))
 | |
|     DBUG_RETURN(-1);
 | |
| 
 | |
|   if (mysql_trans_prepare_alter_copy_data(thd))
 | |
|   {
 | |
|     delete [] copy;
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
| 
 | |
|   /* We need external lock before we can disable/enable keys */
 | |
|   if (to->file->ha_external_lock(thd, F_WRLCK))
 | |
|   {
 | |
|     /* Undo call to mysql_trans_prepare_alter_copy_data() */
 | |
|     ha_enable_transaction(thd, TRUE);
 | |
|     delete [] copy;
 | |
|     DBUG_RETURN(-1);
 | |
|   }
 | |
| 
 | |
|   backup_set_alter_copy_lock(thd, from);
 | |
|   DEBUG_SYNC(thd, "copy_data_between_tables_after_set_backup_lock");
 | |
| 
 | |
|   alter_table_manage_keys(to, from->file->indexes_are_disabled(),
 | |
|                           alter_info->keys_onoff);
 | |
| 
 | |
|   from->default_column_bitmaps();
 | |
| 
 | |
|   /* We can abort alter table for any table type */
 | |
|   thd->abort_on_warning= !ignore && thd->is_strict_mode();
 | |
| 
 | |
|   from->file->info(HA_STATUS_VARIABLE);
 | |
|   to->file->extra(HA_EXTRA_PREPARE_FOR_ALTER_TABLE);
 | |
|   if (!to->s->long_unique_table && !to->s->hlindexes())
 | |
|   {
 | |
|     to->file->ha_start_bulk_insert(from->file->stats.records,
 | |
|                                    ignore ? 0 : HA_CREATE_UNIQUE_INDEX_BY_SORT);
 | |
|     bulk_insert_started= 1;
 | |
|   }
 | |
|   mysql_stage_set_work_estimated(thd->m_stage_progress_psi, from->file->stats.records);
 | |
|   List_iterator<Create_field> it(alter_info->create_list);
 | |
|   Create_field *def;
 | |
|   copy_end=copy;
 | |
|   to->s->default_fields= 0;
 | |
|   error= 1;
 | |
|   for (Field **ptr=to->field ; *ptr ; ptr++)
 | |
|   {
 | |
|     def=it++;
 | |
|     if (def->field)
 | |
|     {
 | |
|       if (*ptr == to->next_number_field)
 | |
|       {
 | |
|         auto_increment_field_copied= TRUE;
 | |
|         /*
 | |
|           If we are going to copy contents of one auto_increment column to
 | |
|           another auto_increment column it is sensible to preserve zeroes.
 | |
|           This condition also covers case when we are don't actually alter
 | |
|           auto_increment column.
 | |
|         */
 | |
|         if (def->field == from->found_next_number_field)
 | |
|           thd->variables.sql_mode|= MODE_NO_AUTO_VALUE_ON_ZERO;
 | |
|       }
 | |
|       if (!(*ptr)->vcol_info)
 | |
|       {
 | |
|         bitmap_set_bit(from->read_set, def->field->field_index);
 | |
|         if ((*ptr)->check_assignability_from(def->field, ignore))
 | |
|           goto err;
 | |
|         (copy_end++)->set(*ptr,def->field,0);
 | |
|       }
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /*
 | |
|         Update the set of auto-update fields to contain only the new fields
 | |
|         added to the table. Only these fields should be updated automatically.
 | |
|         Old fields keep their current values, and therefore should not be
 | |
|         present in the set of autoupdate fields.
 | |
|       */
 | |
|       if ((*ptr)->default_value)
 | |
|       {
 | |
|         *(dfield_ptr++)= *ptr;
 | |
|         ++to->s->default_fields;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if (dfield_ptr)
 | |
|     *dfield_ptr= NULL;
 | |
| 
 | |
|   if (order)
 | |
|   {
 | |
|     if (to->s->primary_key != MAX_KEY &&
 | |
|         to->file->ha_table_flags() & HA_TABLE_SCAN_ON_INDEX)
 | |
|     {
 | |
|       char warn_buff[MYSQL_ERRMSG_SIZE];
 | |
|       Abort_on_warning_instant_set aws(thd, false);
 | |
|       my_snprintf(warn_buff, sizeof(warn_buff),
 | |
|                   "ORDER BY ignored as there is a user-defined clustered index"
 | |
|                   " in the table '%-.192s'", from->s->table_name.str);
 | |
|       push_warning(thd, Sql_condition::WARN_LEVEL_WARN, ER_UNKNOWN_ERROR,
 | |
|                    warn_buff);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       bzero((char *) &tables, sizeof(tables));
 | |
|       tables.table= from;
 | |
|       tables.alias= tables.table_name= from->s->table_name;
 | |
|       tables.db= from->s->db;
 | |
| 
 | |
|       THD_STAGE_INFO(thd, stage_sorting);
 | |
|       Filesort_tracker dummy_tracker(false);
 | |
|       Filesort fsort(order, HA_POS_ERROR, true, NULL);
 | |
| 
 | |
|       if (thd->lex->first_select_lex()->setup_ref_array(thd, order_num) ||
 | |
|           setup_order(thd, thd->lex->first_select_lex()->ref_pointer_array,
 | |
|                       &tables, fields, all_fields, order))
 | |
|         goto err;
 | |
| 
 | |
|       if (!(file_sort= filesort(thd, from, &fsort, &dummy_tracker)))
 | |
|         goto err;
 | |
|     }
 | |
|     thd_progress_next_stage(thd);
 | |
|   }
 | |
| 
 | |
|   if (make_versioned)
 | |
|   {
 | |
|     query_start= thd->query_start_TIME();
 | |
|     to_row_start= to->vers_start_field();
 | |
|     to_row_end= to->vers_end_field();
 | |
|   }
 | |
|   else if (make_unversioned)
 | |
|   {
 | |
|     from_row_end= from->vers_end_field();
 | |
|   }
 | |
| 
 | |
|   if (from_row_end)
 | |
|     bitmap_set_bit(from->read_set, from_row_end->field_index);
 | |
| 
 | |
|   from->file->column_bitmaps_signal();
 | |
| 
 | |
|   to->file->prepare_for_modify(true, false);
 | |
|   DBUG_ASSERT(to->file->inited == handler::NONE);
 | |
| 
 | |
|   /* Tell handler that we have values for all columns in the to table */
 | |
|   to->use_all_columns();
 | |
|   /* Add virtual columns to vcol_set to ensure they are updated */
 | |
|   if (to->vfield)
 | |
|     to->mark_virtual_columns_for_write(TRUE);
 | |
|   if (init_read_record(&info, thd, from, (SQL_SELECT *) 0, file_sort, 1, 1,
 | |
|                        FALSE))
 | |
|     goto err;
 | |
|   init_read_record_done= 1;
 | |
| 
 | |
|   if (ignore && !alter_ctx->fk_error_if_delete_row)
 | |
|     to->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
 | |
|   thd->get_stmt_da()->reset_current_row_for_warning(1);
 | |
|   restore_record(to, s->default_values);        // Create empty record
 | |
|   to->reset_default_fields();
 | |
| 
 | |
|   thd->progress.max_counter= from->file->records();
 | |
|   time_to_report_progress= MY_HOW_OFTEN_TO_WRITE/10;
 | |
|   /* for now, InnoDB needs the undo log for ALTER IGNORE */
 | |
|   if (!ignore && !to->s->hlindexes())
 | |
|     to->file->extra(HA_EXTRA_BEGIN_ALTER_COPY);
 | |
| 
 | |
|   if (!(error= info.read_record()))
 | |
|   {
 | |
| #ifdef HAVE_REPLICATION
 | |
|     if (online)
 | |
|     {
 | |
|       DBUG_ASSERT(from->s->online_alter_binlog == NULL);
 | |
|       from->s->online_alter_binlog= new Cache_flip_event_log();
 | |
|       if (!from->s->online_alter_binlog)
 | |
|         goto err;
 | |
|       from->s->online_alter_binlog->init_pthread_objects();
 | |
|       error= from->s->online_alter_binlog->open(WRITE_CACHE);
 | |
| 
 | |
|       if (error)
 | |
|       {
 | |
|         from->s->online_alter_binlog->release();
 | |
|         from->s->online_alter_binlog= NULL;
 | |
|         goto err;
 | |
|       }
 | |
| 
 | |
|       from->mdl_ticket->downgrade_lock(MDL_SHARED_UPGRADABLE);
 | |
|       DEBUG_SYNC(thd, "alter_table_online_downgraded");
 | |
|     }
 | |
| #else
 | |
|     DBUG_ASSERT(!online);
 | |
| #endif // HAVE_REPLICATION
 | |
| 
 | |
|     do
 | |
|     {
 | |
|       if (unlikely(thd->killed))
 | |
|       {
 | |
|         thd->send_kill_message();
 | |
|         error= 1;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       if (make_unversioned)
 | |
|       {
 | |
|         if (!from_row_end->is_max())
 | |
|           continue; // Drop history rows.
 | |
|       }
 | |
| 
 | |
|       if (unlikely(++thd->progress.counter >= time_to_report_progress))
 | |
|       {
 | |
|         time_to_report_progress+= MY_HOW_OFTEN_TO_WRITE/10;
 | |
|         thd_progress_report(thd, thd->progress.counter,
 | |
|                             thd->progress.max_counter);
 | |
|       }
 | |
| 
 | |
|       /* Return error if source table isn't empty. */
 | |
|       if (unlikely(alter_ctx->error_if_not_empty))
 | |
|       {
 | |
|         error= 1;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       for (Copy_field *copy_ptr=copy ; copy_ptr != copy_end ; copy_ptr++)
 | |
|       {
 | |
|         copy_ptr->do_copy(copy_ptr);
 | |
|       }
 | |
| 
 | |
|       if (make_versioned)
 | |
|       {
 | |
|         to_row_start->set_notnull();
 | |
|         to_row_start->store_time(&query_start);
 | |
|         to_row_end->set_max();
 | |
|       }
 | |
| 
 | |
|       prev_insert_id= to->file->next_insert_id;
 | |
|       if (to->default_field)
 | |
|         to->update_default_fields(ignore);
 | |
|       if (to->vfield)
 | |
|         to->update_virtual_fields(to->file, VCOL_UPDATE_FOR_WRITE);
 | |
| 
 | |
|       /* This will set thd->is_error() if fatal failure */
 | |
|       if (to->verify_constraints(ignore) == VIEW_CHECK_SKIP)
 | |
|         continue;
 | |
|       if (unlikely(thd->is_error()))
 | |
|       {
 | |
|         error= 1;
 | |
|         break;
 | |
|       }
 | |
|       if (keep_versioned && to->versioned(VERS_TRX_ID))
 | |
|         to->vers_write= false;
 | |
| 
 | |
|       if (to->next_number_field)
 | |
|       {
 | |
|         if (auto_increment_field_copied)
 | |
|           to->auto_increment_field_not_null= TRUE;
 | |
|         else
 | |
|           to->next_number_field->reset();
 | |
|       }
 | |
|       error= to->file->ha_write_row(to->record[0]);
 | |
|       to->auto_increment_field_not_null= FALSE;
 | |
|       if (unlikely(error))
 | |
|       {
 | |
|         if (to->file->is_fatal_error(error, HA_CHECK_DUP))
 | |
|         {
 | |
|           /* Not a duplicate key error. */
 | |
|           to->file->print_error(error, MYF(0));
 | |
|           error= 1;
 | |
|           break;
 | |
|         }
 | |
|         else
 | |
|         {
 | |
|           /* Duplicate key error. */
 | |
|           if (unlikely(alter_ctx->fk_error_if_delete_row))
 | |
|           {
 | |
|             /*
 | |
|               We are trying to omit a row from the table which serves as parent
 | |
|               in a foreign key. This might have broken referential integrity so
 | |
|               emit an error. Note that we can't ignore this error even if we are
 | |
|               executing ALTER IGNORE TABLE. IGNORE allows to skip rows, but
 | |
|               doesn't allow to break unique or foreign key constraints,
 | |
|             */
 | |
|             my_error(ER_FK_CANNOT_DELETE_PARENT, MYF(0),
 | |
|                      alter_ctx->fk_error_id,
 | |
|                      alter_ctx->fk_error_table);
 | |
|             break;
 | |
|           }
 | |
| 
 | |
|           if (ignore)
 | |
|           {
 | |
|             /* This ALTER IGNORE TABLE. Simply skip row and continue. */
 | |
|             to->file->restore_auto_increment(prev_insert_id);
 | |
|             delete_count++;
 | |
|           }
 | |
|           else
 | |
|           {
 | |
|             /* Ordinary ALTER TABLE. Report duplicate key error. */
 | |
|             uint key_nr= to->file->get_dup_key(error);
 | |
|             if ((int) key_nr >= 0)
 | |
|             {
 | |
|               const char *err_msg= ER_THD(thd, ER_DUP_ENTRY_WITH_KEY_NAME);
 | |
|               if (key_nr == 0 && to->s->keys > 0 &&
 | |
|                   (to->key_info[0].key_part[0].field->flags &
 | |
|                    AUTO_INCREMENT_FLAG))
 | |
|                 err_msg= ER_THD(thd, ER_DUP_ENTRY_AUTOINCREMENT_CASE);
 | |
|               print_keydup_error(to,
 | |
|                                  key_nr >= to->s->keys ? NULL :
 | |
|                                      &to->key_info[key_nr],
 | |
|                                  err_msg, MYF(0));
 | |
|             }
 | |
|             else
 | |
|               to->file->print_error(error, MYF(0));
 | |
|             break;
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|         DEBUG_SYNC(thd, "copy_data_between_tables_before");
 | |
|         found_count++;
 | |
|         mysql_stage_set_work_completed(thd->m_stage_progress_psi, found_count);
 | |
|       }
 | |
|       thd->get_stmt_da()->inc_current_row_for_warning();
 | |
|     } while (!(error= info.read_record()));
 | |
|   }
 | |
|   else
 | |
|     online= false;
 | |
| 
 | |
|   DEBUG_SYNC(thd, "alter_table_copy_end");
 | |
| 
 | |
|   THD_STAGE_INFO(thd, stage_enabling_keys);
 | |
|   thd_progress_next_stage(thd);
 | |
| 
 | |
|   if (bulk_insert_started && to->file->ha_end_bulk_insert() && error <= 0)
 | |
|   {
 | |
|     /* Give error, if not already given */
 | |
|     if (!thd->is_error())
 | |
|       to->file->print_error(my_errno,MYF(0));
 | |
|     error= 1;
 | |
|   }
 | |
| 
 | |
|   bulk_insert_started= 0;
 | |
|   if (!ignore && !to->s->hlindexes() && error <= 0)
 | |
|   {
 | |
|     int alt_error= to->file->extra(HA_EXTRA_END_ALTER_COPY);
 | |
|     if (alt_error > 0)
 | |
|     {
 | |
|       error= alt_error;
 | |
|       copy_data_error_ignore(error, false, to, thd, alter_ctx);
 | |
|     }
 | |
|   }
 | |
|   cleanup_done= 1;
 | |
|   to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
 | |
| 
 | |
| #ifdef HAVE_REPLICATION
 | |
|   if (online && error < 0)
 | |
|   {
 | |
|     MEM_UNDEFINED(from->record[0], from->s->rec_buff_length * 2);
 | |
|     MEM_UNDEFINED(to->record[0], to->s->rec_buff_length * 2);
 | |
|     thd_progress_next_stage(thd);
 | |
|     enum_sql_command saved_sql_command= thd->lex->sql_command;
 | |
|     Table_map_log_event table_event(thd, from, from->s->table_map_id,
 | |
|                                     from->file->has_transactions());
 | |
|     Relay_log_info rli(false);
 | |
|     rpl_group_info rgi(&rli);
 | |
|     RPL_TABLE_LIST rpl_table(to, TL_WRITE, from, table_event.get_table_def(),
 | |
|                              copy, copy_end);
 | |
|     DBUG_ASSERT(to->pos_in_table_list == NULL);
 | |
|     to->pos_in_table_list= &rpl_table;
 | |
|     rgi.thd= thd;
 | |
|     rgi.tables_to_lock= &rpl_table;
 | |
| 
 | |
|     rgi.m_table_map.set_table(from->s->table_map_id, to);
 | |
| 
 | |
|     Cache_flip_event_log *binlog= from->s->online_alter_binlog;
 | |
|     DBUG_ASSERT(binlog->is_open());
 | |
| 
 | |
|     rli.relay_log.description_event_for_exec=
 | |
|                                             new Format_description_log_event(4);
 | |
| 
 | |
|     // We'll be filling from->record[0] from row events
 | |
|     bitmap_set_all(from->write_set);
 | |
|     // We restore bitmaps, because update event is going to mess up with them.
 | |
|     to->default_column_bitmaps();
 | |
| 
 | |
|     end_read_record(&info);
 | |
|     init_read_record_done= false;
 | |
|     mysql_unlock_tables(thd, thd->lock);
 | |
|     thd->lock= NULL;
 | |
| 
 | |
|     error= online_alter_read_from_binlog(thd, &rgi, binlog, &found_count);
 | |
|     if (start_alter_id)
 | |
|     {
 | |
|       DBUG_ASSERT(thd->slave_thread);
 | |
| 
 | |
|       int rpl_error= wait_for_master(thd);
 | |
|       if (rpl_error)
 | |
|         error= 1;
 | |
|     }
 | |
| 
 | |
|     DEBUG_SYNC(thd, "alter_table_online_before_lock");
 | |
| 
 | |
|     int lock_error=
 | |
|         thd->mdl_context.upgrade_shared_lock(from->mdl_ticket, MDL_SHARED_NO_WRITE,
 | |
|                                      (double)thd->variables.lock_wait_timeout);
 | |
|     if (!error)
 | |
|       error= lock_error;
 | |
| 
 | |
|     if (!error)
 | |
|     {
 | |
|       thd_progress_next_stage(thd);
 | |
|       error= online_alter_read_from_binlog(thd, &rgi, binlog, &found_count);
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|       We'll do it earlier than usually in mysql_alter_table to handle the
 | |
|       online-related errors more comfortably.
 | |
|     */
 | |
|     lock_error= thd->mdl_context.upgrade_shared_lock(from->mdl_ticket,
 | |
|                                                      MDL_EXCLUSIVE,
 | |
|                                       (double)thd->variables.lock_wait_timeout);
 | |
|     if (!error)
 | |
|       error= lock_error;
 | |
| 
 | |
|     to->pos_in_table_list= NULL; // Safety
 | |
|     DBUG_ASSERT(thd->lex->sql_command == saved_sql_command);
 | |
|     thd->lex->sql_command= saved_sql_command; // Just in case
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   if (error > 0 && !from->s->tmp_table)
 | |
|   {
 | |
|     /* We are going to drop the temporary table */
 | |
|     to->file->extra(HA_EXTRA_PREPARE_FOR_DROP);
 | |
|   }
 | |
| 
 | |
|   DEBUG_SYNC(thd, "copy_data_between_tables_before_reset_backup_lock");
 | |
|   if (backup_reset_alter_copy_lock(thd))
 | |
|     error= 1;
 | |
| 
 | |
|   if (unlikely(mysql_trans_commit_alter_copy_data(thd)))
 | |
|     error= 1;
 | |
|   
 | |
|   if (unlikely(error) && online)
 | |
|   {
 | |
|     /*
 | |
|        We can't free the resources properly now, as we can still be in
 | |
|        non-exclusive state. So this s->online_alter_binlog will be used
 | |
|        until all transactions will release it.
 | |
|        Once the transaction commits, it can release online_alter_binlog
 | |
|        by decreasing ref_count.
 | |
| 
 | |
|        online_alter_binlog->ref_count can be reached 0 only once.
 | |
|        Proof:
 | |
|        If share exists, we'll always have ref_count >= 1.
 | |
|        Once it reaches destroy(), nobody can acquire it again,
 | |
|        therefore, only release() is possible at this moment.
 | |
|        
 | |
|        Also, this will release the binlog.
 | |
|     */
 | |
|     from->s->tdc->flush_unused(1);
 | |
|   }
 | |
| 
 | |
|  err:
 | |
|   if (bulk_insert_started)
 | |
|     (void) to->file->ha_end_bulk_insert();
 | |
| 
 | |
|   if (init_read_record_done)
 | |
|     end_read_record(&info);
 | |
|   delete [] copy;
 | |
|   delete file_sort;
 | |
| 
 | |
|   thd->variables.sql_mode= save_sql_mode;
 | |
|   thd->abort_on_warning= 0;
 | |
|   *copied= found_count;
 | |
|   *deleted=delete_count;
 | |
|   to->file->ha_release_auto_increment();
 | |
|   to->s->default_fields= save_to_s_default_fields;
 | |
| 
 | |
|   if (!cleanup_done)
 | |
|   {
 | |
|     /* This happens if we get an error during initialization of data */
 | |
|     DBUG_ASSERT(error);
 | |
|     ha_enable_transaction(thd, TRUE);
 | |
|   }
 | |
| 
 | |
|   if (to->file->ha_external_unlock(thd))
 | |
|     error=1;
 | |
|   if (error < 0 && !from->s->tmp_table &&
 | |
|       to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME))
 | |
|     error= 1;
 | |
|   DBUG_RETURN(error > 0 ? -1 : 0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*
 | |
|   Recreates one table by calling mysql_alter_table().
 | |
| 
 | |
|   SYNOPSIS
 | |
|     mysql_recreate_table()
 | |
|     thd			Thread handler
 | |
|     table_list          Table to recreate
 | |
|     partition_admin     Optimizing partitions
 | |
|     table_copy          Recreate the table by using
 | |
|                         ALTER TABLE COPY algorithm
 | |
| 
 | |
|  RETURN
 | |
|     Like mysql_alter_table().
 | |
| */
 | |
| 
 | |
| bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list,
 | |
|                           Recreate_info *recreate_info,
 | |
|                           bool table_copy)
 | |
| {
 | |
|   Table_specification_st create_info;
 | |
|   Alter_info alter_info;
 | |
|   TABLE_LIST *next_table= table_list->next_global;
 | |
|   DBUG_ENTER("mysql_recreate_table");
 | |
| 
 | |
|   /* Set lock type which is appropriate for ALTER TABLE. */
 | |
|   table_list->lock_type= TL_READ_NO_INSERT;
 | |
|   /* Same applies to MDL request. */
 | |
|   table_list->mdl_request.set_type(MDL_SHARED_NO_WRITE);
 | |
|   /* hide following tables from open_tables() */
 | |
|   table_list->next_global= NULL;
 | |
| 
 | |
|   create_info.init();
 | |
|   create_info.row_type=ROW_TYPE_NOT_USED;
 | |
|   create_info.alter_info= &alter_info;
 | |
|   create_info.recreate_identical_table= 1;
 | |
|   create_info.repair= 1;     // Don't write ALTER TABLE to ddl log
 | |
|   /* Force alter table to recreate table */
 | |
|   alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE);
 | |
|   alter_info.partition_flags= thd->lex->alter_info.partition_flags;
 | |
| 
 | |
|   if (table_copy && !(alter_info.partition_flags & ALTER_PARTITION_ADMIN))
 | |
|     alter_info.set_requested_algorithm(Alter_info::ALTER_TABLE_ALGORITHM_COPY);
 | |
| 
 | |
|   bool res= mysql_alter_table(thd, &null_clex_str, &null_clex_str, &create_info,
 | |
|                               table_list, recreate_info, &alter_info, 0,
 | |
|                               (ORDER *) 0,
 | |
|                               // Ignore duplicate records on REPAIR
 | |
|                               thd->lex->sql_command == SQLCOM_REPAIR,
 | |
|                               0);
 | |
|   table_list->next_global= next_table;
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| /**
 | |
|   Collect field names of result set that will be sent to a client in result of
 | |
|   handling the CHECKSUM TABLE statement.
 | |
| 
 | |
|   @param      thd     Thread data object
 | |
|   @param[out] fields  List of fields whose metadata should be collected for
 | |
|                       sending to client
 | |
|  */
 | |
| 
 | |
| void fill_checksum_table_metadata_fields(THD *thd, List<Item> *fields)
 | |
| {
 | |
|   Item *item;
 | |
| 
 | |
|   item= new (thd->mem_root) Item_empty_string(thd, "Table", NAME_LEN*2);
 | |
|   item->set_maybe_null();
 | |
|   fields->push_back(item, thd->mem_root);
 | |
| 
 | |
|   item= new (thd->mem_root) Item_int(thd, "Checksum", (longlong) 1,
 | |
|                                      MY_INT64_NUM_DECIMAL_DIGITS);
 | |
|   item->set_maybe_null();
 | |
|   fields->push_back(item, thd->mem_root);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool mysql_checksum_table(THD *thd, TABLE_LIST *tables,
 | |
|                           HA_CHECK_OPT *check_opt)
 | |
| {
 | |
|   TABLE_LIST *table;
 | |
|   List<Item> field_list;
 | |
|   Protocol *protocol= thd->protocol;
 | |
|   DBUG_ENTER("mysql_checksum_table");
 | |
| 
 | |
|   /*
 | |
|     CHECKSUM TABLE returns results and rollbacks statement transaction,
 | |
|     so it should not be used in stored function or trigger.
 | |
|   */
 | |
|   DBUG_ASSERT(! thd->in_sub_stmt);
 | |
| 
 | |
|   fill_checksum_table_metadata_fields(thd, &field_list);
 | |
| 
 | |
|   if (protocol->send_result_set_metadata(&field_list,
 | |
|                             Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF))
 | |
|     DBUG_RETURN(TRUE);
 | |
| 
 | |
|   /*
 | |
|     Close all temporary tables which were pre-open to simplify
 | |
|     privilege checking. Clear all references to closed tables.
 | |
|   */
 | |
|   close_thread_tables(thd);
 | |
|   for (table= tables; table; table= table->next_local)
 | |
|     table->table= NULL;
 | |
| 
 | |
|   /* Open one table after the other to keep lock time as short as possible. */
 | |
|   for (table= tables; table; table= table->next_local)
 | |
|   {
 | |
|     char table_name_buff[SAFE_NAME_LEN*2+2];
 | |
|     LEX_CSTRING table_name= { table_name_buff, 0};
 | |
|     TABLE *t;
 | |
|     TABLE_LIST *save_next_global;
 | |
| 
 | |
|     table_name.length= strxmov(table_name_buff, table->db.str ,".",
 | |
|                                table->table_name.str, NullS) - table_name_buff;
 | |
| 
 | |
|     /* Remember old 'next' pointer and break the list.  */
 | |
|     save_next_global= table->next_global;
 | |
|     table->next_global= NULL;
 | |
|     table->lock_type= TL_READ;
 | |
|     /* Allow to open real tables only. */
 | |
|     table->required_type= TABLE_TYPE_NORMAL;
 | |
| 
 | |
|     if (thd->open_temporary_tables(table) ||
 | |
|         open_and_lock_tables(thd, table, FALSE, 0))
 | |
|     {
 | |
|       t= NULL;
 | |
|     }
 | |
|     else
 | |
|       t= table->table;
 | |
| 
 | |
|     table->next_global= save_next_global;
 | |
| 
 | |
|     protocol->prepare_for_resend();
 | |
|     protocol->store(&table_name, system_charset_info);
 | |
| 
 | |
|     if (!t)
 | |
|     {
 | |
|       /* Table didn't exist */
 | |
|       protocol->store_null();
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       /* Call ->checksum() if the table checksum matches 'old_mode' settings */
 | |
|       if (!(check_opt->flags & T_EXTEND) &&
 | |
|           (((t->file->ha_table_flags() & HA_HAS_OLD_CHECKSUM) &&
 | |
|            (thd->variables.old_behavior & OLD_MODE_COMPAT_5_1_CHECKSUM)) ||
 | |
|            ((t->file->ha_table_flags() & HA_HAS_NEW_CHECKSUM) &&
 | |
|            !(thd->variables.old_behavior & OLD_MODE_COMPAT_5_1_CHECKSUM))))
 | |
|       {
 | |
|         if (t->file->info(HA_STATUS_VARIABLE) || t->file->stats.checksum_null)
 | |
|           protocol->store_null();
 | |
|         else
 | |
|           protocol->store((longlong)t->file->stats.checksum);
 | |
|       }
 | |
|       else if (check_opt->flags & T_QUICK)
 | |
|         protocol->store_null();
 | |
|       else
 | |
|       {
 | |
|         DEBUG_SYNC(thd, "mysql_checksum_table_before_calculate_checksum");
 | |
|         int error= t->file->calculate_checksum();
 | |
|         DEBUG_SYNC(thd, "mysql_checksum_table_after_calculate_checksum");
 | |
|         if (thd->killed)
 | |
|         {
 | |
|           /*
 | |
|              we've been killed; let handler clean up, and remove the
 | |
|              partial current row from the recordset (embedded lib)
 | |
|           */
 | |
|           thd->protocol->remove_last_row();
 | |
|           goto err;
 | |
|         }
 | |
|         if (error || t->file->stats.checksum_null)
 | |
|           protocol->store_null();
 | |
|         else
 | |
|           protocol->store((longlong)t->file->stats.checksum);
 | |
|       }
 | |
|       trans_rollback_stmt(thd);
 | |
|       close_thread_tables(thd);
 | |
|     }
 | |
| 
 | |
|     if (thd->transaction_rollback_request)
 | |
|     {
 | |
|       /*
 | |
|         If transaction rollback was requested we honor it. To do this we
 | |
|         abort statement and return error as not only CHECKSUM TABLE is
 | |
|         rolled back but the whole transaction in which it was used.
 | |
|       */
 | |
|       thd->protocol->remove_last_row();
 | |
|       goto err;
 | |
|     }
 | |
| 
 | |
|     /* Hide errors from client. Return NULL for problematic tables instead. */
 | |
|     thd->clear_error();
 | |
| 
 | |
|     if (protocol->write())
 | |
|       goto err;
 | |
|   }
 | |
| 
 | |
|   my_eof(thd);
 | |
|   DBUG_RETURN(FALSE);
 | |
| 
 | |
| err:
 | |
|   DBUG_RETURN(TRUE);
 | |
| }
 | |
| 
 | |
| /**
 | |
|   @brief Check if the table can be created in the specified storage engine.
 | |
| 
 | |
|   Checks if the storage engine is enabled and supports the given table
 | |
|   type (e.g. normal, temporary, system). May do engine substitution
 | |
|   if the requested engine is disabled.
 | |
| 
 | |
|   @param thd          Thread descriptor.
 | |
|   @param db_name      Database name.
 | |
|   @param table_name   Name of table to be created.
 | |
|   @param create_info  Create info from parser, including engine.
 | |
| 
 | |
|   @retval true  Engine not available/supported, error has been reported.
 | |
|   @retval false Engine available/supported.
 | |
|                 create_info->db_type & create_info->new_storage_engine_name
 | |
|                 are updated.
 | |
| */
 | |
| 
 | |
| bool check_engine(THD *thd, const char *db_name,
 | |
|                   const char *table_name, HA_CREATE_INFO *create_info)
 | |
| {
 | |
|   DBUG_ENTER("check_engine");
 | |
|   handlerton **new_engine= &create_info->db_type;
 | |
|   handlerton *req_engine= *new_engine;
 | |
|   handlerton *enf_engine= NULL;
 | |
|   bool no_substitution= thd->variables.sql_mode & MODE_NO_ENGINE_SUBSTITUTION;
 | |
|   *new_engine= ha_checktype(thd, req_engine, no_substitution);
 | |
|   DBUG_ASSERT(*new_engine);
 | |
|   if (!*new_engine)
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   /*
 | |
|     Enforced storage engine should not be used in ALTER TABLE that does not
 | |
|     use explicit ENGINE = x to avoid unwanted unrelated changes. It should not
 | |
|     be used in CREATE INDEX too.
 | |
|   */
 | |
|   if (!((thd->lex->sql_command == SQLCOM_ALTER_TABLE &&
 | |
|              !(create_info->used_fields & HA_CREATE_USED_ENGINE)) ||
 | |
|          thd->lex->sql_command == SQLCOM_CREATE_INDEX))
 | |
|   {
 | |
|     plugin_ref enf_plugin= thd->variables.enforced_table_plugin;
 | |
|     enf_engine= enf_plugin ? plugin_hton(enf_plugin) : NULL;
 | |
|   }
 | |
| 
 | |
|   if (enf_engine && enf_engine != *new_engine)
 | |
|   {
 | |
|     if (no_substitution)
 | |
|     {
 | |
|       my_error(ER_OPTION_PREVENTS_STATEMENT, MYF(0), "NO_ENGINE_SUBSTITUTION");
 | |
|       DBUG_RETURN(TRUE);
 | |
|     }
 | |
|     *new_engine= enf_engine;
 | |
|   }
 | |
| 
 | |
|   if (req_engine && req_engine != *new_engine)
 | |
|   {
 | |
|     push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
 | |
|                         ER_WARN_USING_OTHER_HANDLER,
 | |
|                         ER_THD(thd, ER_WARN_USING_OTHER_HANDLER),
 | |
|                         ha_resolve_storage_engine_name(*new_engine),
 | |
|                         table_name);
 | |
|   }
 | |
|   if (create_info->tmp_table() &&
 | |
|       ha_check_storage_engine_flag(*new_engine, HTON_TEMPORARY_NOT_SUPPORTED))
 | |
|   {
 | |
|     my_error(ER_ILLEGAL_HA_CREATE_OPTION, MYF(0),
 | |
|              hton_name(*new_engine)->str, "TEMPORARY");
 | |
|     *new_engine= 0;
 | |
|     DBUG_RETURN(true);
 | |
|   }
 | |
|   lex_string_set(&create_info->new_storage_engine_name,
 | |
|                  ha_resolve_storage_engine_name(*new_engine));
 | |
|   DBUG_RETURN(false);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool Sql_cmd_create_table_like::execute(THD *thd)
 | |
| {
 | |
|   DBUG_ENTER("Sql_cmd_create_table::execute");
 | |
|   LEX *lex= thd->lex;
 | |
|   SELECT_LEX *select_lex= lex->first_select_lex();
 | |
|   TABLE_LIST *first_table= select_lex->table_list.first;
 | |
|   DBUG_ASSERT(first_table == lex->query_tables);
 | |
|   DBUG_ASSERT(first_table != 0);
 | |
|   bool link_to_local;
 | |
|   TABLE_LIST *create_table= first_table;
 | |
|   TABLE_LIST *select_tables= lex->create_last_non_select_table->next_global;
 | |
|   /* most outer SELECT_LEX_UNIT of query */
 | |
|   SELECT_LEX_UNIT *unit= &lex->unit;
 | |
|   int res= 0;
 | |
| 
 | |
|   const bool used_engine= lex->create_info.used_fields & HA_CREATE_USED_ENGINE;
 | |
|   ulong binlog_format= thd->wsrep_binlog_format(thd->variables.binlog_format);
 | |
|   DBUG_ASSERT((m_storage_engine_name.str != NULL) == used_engine);
 | |
| 
 | |
|   if (lex->create_info.resolve_to_charset_collation_context(thd,
 | |
|         thd->charset_collation_context_create_table_in_db(first_table->db.str)))
 | |
|     DBUG_RETURN(true);
 | |
| 
 | |
|   if (used_engine)
 | |
|   {
 | |
|     if (resolve_storage_engine_with_error(thd, &lex->create_info.db_type,
 | |
|                                           lex->create_info.tmp_table()))
 | |
|       DBUG_RETURN(true); // Engine not found, substitution is not allowed
 | |
| 
 | |
|     if (!lex->create_info.db_type) // Not found, but substitution is allowed
 | |
|     {
 | |
|       lex->create_info.use_default_db_type(thd);
 | |
|       push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN,
 | |
|                           ER_WARN_USING_OTHER_HANDLER,
 | |
|                           ER_THD(thd, ER_WARN_USING_OTHER_HANDLER),
 | |
|                           hton_name(lex->create_info.db_type)->str,
 | |
|                           create_table->table_name.str);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (lex->tmp_table())
 | |
|   {
 | |
|     status_var_decrement(thd->status_var.com_stat[SQLCOM_CREATE_TABLE]);
 | |
|     status_var_increment(thd->status_var.com_create_tmp_table);
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|     Code below (especially in mysql_create_table() and select_create
 | |
|     methods) may modify HA_CREATE_INFO structure in LEX, so we have to
 | |
|     use a copy of this structure to make execution prepared statement-
 | |
|     safe. A shallow copy is enough as this code won't modify any memory
 | |
|     referenced from this structure.
 | |
|   */
 | |
|   Table_specification_st create_info(lex->create_info);
 | |
|   /*
 | |
|     We need to copy alter_info for the same reasons of re-execution
 | |
|     safety, only in case of Alter_info we have to do (almost) a deep
 | |
|     copy.
 | |
|   */
 | |
|   Alter_info alter_info(lex->alter_info, thd->mem_root);
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
|   bool wsrep_ctas= false;
 | |
|   // If CREATE TABLE AS SELECT and wsrep_on
 | |
|   if (WSREP(thd) && (select_lex->item_list.elements ||
 | |
|      // Only CTAS may be applied not using TOI.
 | |
|      (wsrep_thd_is_applying(thd) && !wsrep_thd_is_toi(thd))))
 | |
|   {
 | |
|     wsrep_ctas= true;
 | |
| 
 | |
|     // MDEV-22232: Disable CTAS retry by setting the retry counter to the
 | |
|     // threshold value.
 | |
|     thd->wsrep_retry_counter= thd->variables.wsrep_retry_autocommit;
 | |
|   }
 | |
| 
 | |
|   // This will be used in THD::decide_logging_format if CTAS
 | |
|   Enable_wsrep_ctas_guard wsrep_ctas_guard(thd, wsrep_ctas);
 | |
| #endif
 | |
| 
 | |
|   if (unlikely(thd->is_fatal_error))
 | |
|   {
 | |
|     /* If out of memory when creating a copy of alter_info. */
 | |
|     res= 1;
 | |
|     goto end_with_restore_list;
 | |
|   }
 | |
| 
 | |
|   /*
 | |
|    Since CREATE_INFO is not full without Alter_info, it is better to pass them
 | |
|    as a single parameter. TODO: remove alter_info argument where create_info is
 | |
|    passed.
 | |
|   */
 | |
|   create_info.alter_info= &alter_info;
 | |
| 
 | |
|   /* Check privileges */
 | |
|   if ((res= create_table_precheck(thd, select_tables, create_table)))
 | |
|     goto end_with_restore_list;
 | |
| 
 | |
|   /* Might have been updated in create_table_precheck */
 | |
|   create_info.alias= create_table->alias;
 | |
| 
 | |
|   /* Fix names if symlinked or relocated tables */
 | |
|   if (append_file_to_dir(thd, &create_info.data_file_name,
 | |
|                          &create_table->table_name) ||
 | |
|       append_file_to_dir(thd, &create_info.index_file_name,
 | |
|                          &create_table->table_name))
 | |
|     goto end_with_restore_list;
 | |
| 
 | |
|   /*
 | |
|     If no engine type was given, work out the default now
 | |
|     rather than at parse-time.
 | |
|   */
 | |
|   if (!(create_info.used_fields & HA_CREATE_USED_ENGINE))
 | |
|     create_info.use_default_db_type(thd);
 | |
| 
 | |
|   DBUG_ASSERT(!(create_info.used_fields & HA_CREATE_USED_CHARSET));
 | |
|   DBUG_ASSERT(create_info.convert_charset_collation.is_empty());
 | |
| 
 | |
|   /*
 | |
|     If we are a slave, we should add OR REPLACE if we don't have
 | |
|     IF EXISTS. This will help a slave to recover from
 | |
|     CREATE TABLE OR EXISTS failures by dropping the table and
 | |
|     retrying the create.
 | |
|   */
 | |
|   if (thd->slave_thread &&
 | |
|       slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT &&
 | |
|       !lex->create_info.if_not_exists())
 | |
|   {
 | |
|     create_info.add(DDL_options_st::OPT_OR_REPLACE);
 | |
|     create_info.add(DDL_options_st::OPT_OR_REPLACE_SLAVE_GENERATED);
 | |
|   }
 | |
| 
 | |
| #ifdef WITH_PARTITION_STORAGE_ENGINE
 | |
|   thd->work_part_info= 0;
 | |
|   {
 | |
|     partition_info *part_info= thd->lex->part_info;
 | |
|     if (part_info && !(part_info= part_info->get_clone(thd)))
 | |
|     {
 | |
|       res= -1;
 | |
|       goto end_with_restore_list;
 | |
|     }
 | |
|     thd->work_part_info= part_info;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
|   if (WSREP(thd) &&
 | |
|       wsrep_check_support(thd, wsrep_ctas, used_engine, &create_info))
 | |
|   {
 | |
|     res= 1;
 | |
|     goto end_with_restore_list;
 | |
|   }
 | |
| #endif /* WITH_WSREP */
 | |
| 
 | |
|   if (select_lex->item_list.elements || select_lex->tvc) // With select or TVC
 | |
|   {
 | |
|     select_result *result;
 | |
| 
 | |
|     /*
 | |
|       CREATE TABLE...IGNORE/REPLACE SELECT... can be unsafe, unless
 | |
|       ORDER BY PRIMARY KEY clause is used in SELECT statement. We therefore
 | |
|       use row based logging if mixed or row based logging is available.
 | |
|       TODO: Check if the order of the output of the select statement is
 | |
|       deterministic. Waiting for BUG#42415
 | |
|     */
 | |
|     if(lex->ignore)
 | |
|       lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_IGNORE_SELECT);
 | |
| 
 | |
|     if(lex->duplicates == DUP_REPLACE)
 | |
|       lex->set_stmt_unsafe(LEX::BINLOG_STMT_UNSAFE_CREATE_REPLACE_SELECT);
 | |
| 
 | |
|     /*
 | |
|       If:
 | |
|       a) we inside an SP and there was NAME_CONST substitution,
 | |
|       b) binlogging is on (STMT mode),
 | |
|       c) we log the SP as separate statements
 | |
|       raise a warning, as it may cause problems
 | |
|       (see 'NAME_CONST issues' in 'Binary Logging of Stored Programs')
 | |
|      */
 | |
|     if (thd->query_name_consts && mysql_bin_log.is_open() &&
 | |
|         binlog_format == BINLOG_FORMAT_STMT &&
 | |
|         !mysql_bin_log.is_query_in_union(thd, thd->query_id))
 | |
|     {
 | |
|       List_iterator_fast<Item> it(select_lex->item_list);
 | |
|       Item *item;
 | |
|       uint splocal_refs= 0;
 | |
|       /* Count SP local vars in the top-level SELECT list */
 | |
|       while ((item= it++))
 | |
|       {
 | |
|         if (item->get_item_splocal())
 | |
|           splocal_refs++;
 | |
|       }
 | |
|       /*
 | |
|         If it differs from number of NAME_CONST substitution applied,
 | |
|         we may have a SOME_FUNC(NAME_CONST()) in the SELECT list,
 | |
|         that may cause a problem with binary log (see BUG#35383),
 | |
|         raise a warning.
 | |
|       */
 | |
|       if (splocal_refs != thd->query_name_consts)
 | |
|         push_warning(thd,
 | |
|                      Sql_condition::WARN_LEVEL_WARN,
 | |
|                      ER_UNKNOWN_ERROR,
 | |
| "Invoked routine ran a statement that may cause problems with "
 | |
| "binary log, see 'NAME_CONST issues' in 'Binary Logging of Stored Programs' "
 | |
| "section of the manual.");
 | |
|     }
 | |
| 
 | |
|     select_lex->options|= SELECT_NO_UNLOCK;
 | |
|     unit->set_limit(select_lex);
 | |
| 
 | |
|     /*
 | |
|       Disable non-empty MERGE tables with CREATE...SELECT. Too
 | |
|       complicated. See Bug #26379. Empty MERGE tables are read-only
 | |
|       and don't allow CREATE...SELECT anyway.
 | |
|     */
 | |
|     if (create_info.used_fields & HA_CREATE_USED_UNION)
 | |
|     {
 | |
|       my_error(ER_WRONG_OBJECT, MYF(0), create_table->db.str,
 | |
|                create_table->table_name.str, "BASE TABLE");
 | |
|       res= 1;
 | |
|       goto end_with_restore_list;
 | |
|     }
 | |
| 
 | |
|     res= open_and_lock_tables(thd, create_info, lex->query_tables, TRUE, 0);
 | |
|     if (unlikely(res))
 | |
|     {
 | |
|       /* Got error or warning. Set res to 1 if error */
 | |
|       if (!(res= thd->is_error()))
 | |
|         my_ok(thd);                           // CREATE ... IF NOT EXISTS
 | |
|       goto end_with_restore_list;
 | |
|     }
 | |
| 
 | |
|     /* Ensure we don't try to create something from which we select from */
 | |
|     if (create_info.or_replace() && !create_info.tmp_table())
 | |
|     {
 | |
|       if (TABLE_LIST *duplicate= unique_table(thd, lex->query_tables,
 | |
|                                               lex->query_tables->next_global,
 | |
|                                               CHECK_DUP_FOR_CREATE |
 | |
|                                               CHECK_DUP_SKIP_TEMP_TABLE))
 | |
|       {
 | |
|         update_non_unique_table_error(lex->query_tables, "CREATE",
 | |
|                                       duplicate);
 | |
|         res= TRUE;
 | |
|         goto end_with_restore_list;
 | |
|       }
 | |
|     }
 | |
|     {
 | |
|       /*
 | |
|         Remove target table from main select and name resolution
 | |
|         context. This can't be done earlier as it will break view merging in
 | |
|         statements like "CREATE TABLE IF NOT EXISTS existing_view SELECT".
 | |
|       */
 | |
|       lex->unlink_first_table(&link_to_local);
 | |
| 
 | |
|       /* Store reference to table in case of LOCK TABLES */
 | |
|       create_info.table= create_table->table;
 | |
| 
 | |
|       DEBUG_SYNC(thd, "wsrep_create_table_as_select");
 | |
| 
 | |
|       /*
 | |
|         select_create is currently not re-execution friendly and
 | |
|         needs to be created for every execution of a PS/SP.
 | |
|         Note: In wsrep-patch, CTAS is handled like a regular transaction.
 | |
|       */
 | |
|       if ((result= new (thd->mem_root) select_create(thd, create_table,
 | |
|                                                      &create_info,
 | |
|                                                      &alter_info,
 | |
|                                                      select_lex->item_list,
 | |
|                                                      lex->duplicates,
 | |
|                                                      lex->ignore,
 | |
|                                                      select_tables)))
 | |
|       {
 | |
|         /*
 | |
|           CREATE from SELECT give its SELECT_LEX for SELECT,
 | |
|           and item_list belong to SELECT
 | |
|         */
 | |
|         if (!(res= handle_select(thd, lex, result, 0)))
 | |
|         {
 | |
|           if (create_info.tmp_table())
 | |
|             thd->variables.option_bits|= OPTION_BINLOG_THIS_TRX;
 | |
|         }
 | |
|         delete result;
 | |
|       }
 | |
|       lex->link_first_table_back(create_table, link_to_local);
 | |
|     }
 | |
|   }
 | |
|   else
 | |
|   {
 | |
|     /* regular create */
 | |
|     if (create_info.like())
 | |
|     {
 | |
|       /* CREATE TABLE ... LIKE ... */
 | |
|       res= mysql_create_like_table(thd, create_table, select_tables,
 | |
|                                    &create_info);
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|       if (create_info.fix_create_fields(thd, &alter_info, *create_table) ||
 | |
|           create_info.check_fields(thd, &alter_info,
 | |
|                                    create_table->table_name, create_table->db))
 | |
| 	goto end_with_restore_list;
 | |
| 
 | |
| #ifdef WITH_WSREP
 | |
|       /*
 | |
|         In STATEMENT format, we probably have to replicate also temporary
 | |
|         tables, like mysql replication does. Also check if the requested
 | |
|         engine is allowed/supported.
 | |
|       */
 | |
|       if (WSREP(thd))
 | |
|       {
 | |
|         handlerton *orig_ht= create_info.db_type;
 | |
| 
 | |
|         if (!check_engine(thd, create_table->db.str,
 | |
|                           create_table->table_name.str,
 | |
|                           &create_info) &&
 | |
|             (!thd->is_binlog_format_row() ||
 | |
|              !create_info.tmp_table()))
 | |
|         {
 | |
|           if (thd->lex->sql_command == SQLCOM_CREATE_SEQUENCE &&
 | |
|               wsrep_check_sequence(thd, lex->create_info.seq_create_info, used_engine))
 | |
|             DBUG_RETURN(true);
 | |
| 
 | |
|           WSREP_TO_ISOLATION_BEGIN_ALTER(create_table->db.str, create_table->table_name.str,
 | |
|                                          first_table, &alter_info, NULL, &create_info)
 | |
|           {
 | |
|             WSREP_WARN("CREATE TABLE isolation failure");
 | |
|             res= true;
 | |
|             goto end_with_restore_list;
 | |
|           }
 | |
|         }
 | |
|         // check_engine will set db_type to  NULL if e.g. TEMPORARY is
 | |
|         // not supported by the storage engine, this case is checked
 | |
|         // again in mysql_create_table
 | |
|         create_info.db_type= orig_ht;
 | |
|       }
 | |
| #endif /* WITH_WSREP */
 | |
| 
 | |
|       /* Regular CREATE TABLE */
 | |
|       res= mysql_create_table(thd, create_table, &create_info, &alter_info);
 | |
|     }
 | |
|     if (!res)
 | |
|     {
 | |
|       /* So that CREATE TEMPORARY TABLE gets to binlog at commit/rollback */
 | |
|       if (create_info.tmp_table())
 | |
|         thd->variables.option_bits|= OPTION_BINLOG_THIS_TRX;
 | |
|       /* in case of create temp tables if @@session_track_state_change is
 | |
|          ON then send session state notification in OK packet */
 | |
|       if (create_info.options & HA_LEX_CREATE_TMP_TABLE)
 | |
|       {
 | |
|         thd->session_tracker.state_change.mark_as_changed(thd);
 | |
|       }
 | |
|       my_ok(thd);
 | |
|     }
 | |
|   }
 | |
| 
 | |
| end_with_restore_list:
 | |
|   DBUG_RETURN(res);
 | |
| }
 | |
| 
 | |
| 
 | |
| bool HA_CREATE_INFO::
 | |
|        resolve_to_charset_collation_context(THD *thd,
 | |
|              const Lex_table_charset_collation_attrs_st &default_cscl_arg,
 | |
|              const Lex_table_charset_collation_attrs_st &convert_cscl,
 | |
|              const Charset_collation_context &ctx)
 | |
| {
 | |
|   /*
 | |
|     If CONVERT TO clauses are specified only (without table default clauses),
 | |
|     then we copy CONVERT TO clauses to default clauses, so e.g:
 | |
|       CONVERT TO CHARACTER SET utf8mb4
 | |
|     means
 | |
|       CONVERT TO CHARACTER SET utf8mb4, DEFAULT CHARACTER SET utf8mb4
 | |
|   */
 | |
|   Lex_table_charset_collation_attrs_st default_cscl=
 | |
|     !convert_cscl.is_empty() && default_cscl_arg.is_empty() ?
 | |
|     convert_cscl : default_cscl_arg;
 | |
| 
 | |
|   if (default_cscl.is_empty())
 | |
|     default_table_charset= ctx.collate_default().charset_info();
 | |
|   else
 | |
|   {
 | |
|     // Make sure we don't do double resolution in direct SQL execution
 | |
|     DBUG_ASSERT(!default_table_charset ||
 | |
|                 thd->stmt_arena->is_stmt_execute() ||
 | |
|                 thd->stmt_arena->state == Query_arena::STMT_INITIALIZED_FOR_SP);
 | |
|     if (!(default_table_charset=
 | |
|             default_cscl.resolved_to_context(thd,
 | |
|                            thd->variables.character_set_collations, ctx)))
 | |
|       return true;
 | |
|   }
 | |
| 
 | |
|   if (convert_cscl.is_empty())
 | |
|     alter_table_convert_to_charset= NULL;
 | |
|   else
 | |
|   {
 | |
|     // Make sure we don't do double resolution in direct SQL execution
 | |
|     DBUG_ASSERT(!alter_table_convert_to_charset ||
 | |
|                 thd->stmt_arena->is_stmt_execute() ||
 | |
|                 thd->stmt_arena->state == Query_arena::STMT_INITIALIZED_FOR_SP);
 | |
|     if (!(alter_table_convert_to_charset=
 | |
|             convert_cscl.resolved_to_context(thd,
 | |
|                            thd->variables.character_set_collations, ctx)))
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | 
