diff options
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 560 |
1 files changed, 472 insertions, 88 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 44df4bba767..e694c52e272 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1,6 +1,6 @@ /* - Copyright (c) 2000, 2016, Oracle and/or its affiliates. - Copyright (c) 2010, 2016, MariaDB + Copyright (c) 2000, 2019, Oracle and/or its affiliates. + Copyright (c) 2010, 2019, 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 @@ -13,7 +13,7 @@ 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-1301 USA + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */ /* drop and alter of tables */ @@ -2070,7 +2070,7 @@ bool mysql_rm_table(THD *thd,TABLE_LIST *tables, my_bool if_exists, in its elements. */ table->table= find_table_for_mdl_upgrade(thd, table->db, - table->table_name, false); + table->table_name, NULL); if (!table->table) DBUG_RETURN(true); table->mdl_request.ticket= table->table->mdl_ticket; @@ -2265,7 +2265,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, for (table= tables; table; table= table->next_local) { bool is_trans= 0; - bool table_creation_was_logged= 1; + bool table_creation_was_logged= 0; char *db=table->db; size_t db_length= table->db_length; handlerton *table_type= 0; @@ -2418,6 +2418,13 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, ER_BAD_TABLE_ERROR, ER_THD(thd, ER_BAD_TABLE_ERROR), 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; } else { @@ -2529,8 +2536,7 @@ int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists, table->table_name);); } DEBUG_SYNC(thd, "rm_table_no_locks_before_binlog"); - thd->thread_specific_used|= (trans_tmp_table_deleted || - non_trans_tmp_table_deleted); + thd->thread_specific_used= TRUE; error= 0; err: if (wrong_tables.length()) @@ -3316,6 +3322,10 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, } } + /* Virtual fields are always NULL */ + if (sql_field->vcol_info) + sql_field->flags&= ~NOT_NULL_FLAG; + if (sql_field->sql_type == MYSQL_TYPE_SET || sql_field->sql_type == MYSQL_TYPE_ENUM) { @@ -4056,16 +4066,16 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, /* 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)) && - (key_part_length >= KEY_DEFAULT_PACK_LENGTH && - (sql_field->sql_type == MYSQL_TYPE_STRING || - sql_field->sql_type == MYSQL_TYPE_VARCHAR || - sql_field->pack_flag & FIELDFLAG_BLOB))) + (key_part_length >= KEY_DEFAULT_PACK_LENGTH && + (sql_field->sql_type == MYSQL_TYPE_STRING || + sql_field->sql_type == MYSQL_TYPE_VARCHAR || + f_is_blob(sql_field->pack_flag)))) { - if ((column_nr == 0 && (sql_field->pack_flag & FIELDFLAG_BLOB)) || + if ((column_nr == 0 && f_is_blob(sql_field->pack_flag)) || sql_field->sql_type == MYSQL_TYPE_VARCHAR) - key_info->flags|= HA_BINARY_PACK_KEY | HA_VAR_LENGTH_KEY; - else - key_info->flags|= HA_PACK_KEY; + key_info->flags|= HA_BINARY_PACK_KEY | HA_VAR_LENGTH_KEY; + else + key_info->flags|= HA_PACK_KEY; } /* Check if the key segment is partial, set the key flag accordingly */ if (key_part_length != sql_field->key_length) @@ -4198,7 +4208,7 @@ mysql_prepare_create_table(THD *thd, HA_CREATE_INFO *create_info, /* Give warnings for not supported table options */ #if defined(WITH_ARIA_STORAGE_ENGINE) extern handlerton *maria_hton; - if (file->ht != maria_hton) + if (file->partition_ht() != maria_hton) #endif if (create_info->transactional) push_warning_printf(thd, Sql_condition::WARN_LEVEL_WARN, @@ -4770,6 +4780,10 @@ int create_table_impl(THD *thd, { if (options.or_replace()) { + LEX_STRING db_name= {(char *) db, strlen(db)}; + LEX_STRING tab_name= {(char *) table_name, strlen(table_name)}; + (void) delete_statistics_for_table(thd, &db_name, &tab_name); + TABLE_LIST table_list; table_list.init_one_table(db, strlen(db), table_name, strlen(table_name), table_name, @@ -5071,7 +5085,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_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)) + if (thd->locked_tables_list.reopen_tables(thd, false)) { thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); result= 1; @@ -5085,7 +5099,7 @@ bool mysql_create_table(THD *thd, TABLE_LIST *create_table, err: /* In RBR we don't need to log CREATE TEMPORARY TABLE */ - if (thd->is_current_stmt_binlog_format_row() && create_info->tmp_table()) + if (!result && thd->is_current_stmt_binlog_format_row() && create_info->tmp_table()) DBUG_RETURN(result); if (create_info->tmp_table()) @@ -5094,7 +5108,7 @@ err: /* Write log if no error or if we already deleted a table */ if (!result || thd->log_current_statement) { - if (result && create_info->table_was_deleted) + if (result && create_info->table_was_deleted && pos_in_locked_tables) { /* Possible locked table was dropped. We should remove meta data locks @@ -5268,6 +5282,8 @@ mysql_rename_table(handlerton *base, const char *old_db, 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); else if (error) my_error(ER_ERROR_ON_RENAME, MYF(0), from, to, error); else if (!(flags & FN_IS_TMP)) @@ -5429,7 +5445,7 @@ bool mysql_create_like_table(THD* thd, TABLE_LIST* 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)) + 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 @@ -5707,7 +5723,8 @@ 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)) + 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++) @@ -5835,10 +5852,28 @@ drop_create_field: 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_info::ALTER_DROP_COLUMN; + break; + case Alter_drop::FOREIGN_KEY: + cur_flag= Alter_info::DROP_FOREIGN_KEY; + break; + case Alter_drop::KEY: + cur_flag= Alter_info::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) { @@ -5915,12 +5950,15 @@ drop_create_field: ER_THD(thd, ER_CANT_DROP_FIELD_OR_KEY), drop->name); drop_it.remove(); - if (alter_info->drop_list.is_empty()) - alter_info->flags&= ~(Alter_info::ALTER_DROP_COLUMN | - Alter_info::ALTER_DROP_INDEX | - Alter_info::DROP_FOREIGN_KEY); } + else + left_flags|= cur_flag; } + /* Reset state to what's left in drop list */ + alter_info->flags&= ~(Alter_info::ALTER_DROP_COLUMN | + Alter_info::ALTER_DROP_INDEX | + Alter_info::DROP_FOREIGN_KEY); + alter_info->flags|= left_flags; } /* ALTER TABLE ADD KEY IF NOT EXISTS */ @@ -5936,8 +5974,11 @@ drop_create_field: continue; /* Check if the table already has a PRIMARY KEY */ - bool dup_primary_key= key->type == Key::PRIMARY && - table->s->primary_key != MAX_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) && + my_strcasecmp(system_charset_info, keyname, primary_key_name) == 0; if (dup_primary_key) goto remove_key; @@ -6169,9 +6210,7 @@ static int compare_uint(const uint *s, const uint *t) @retval false success */ -static bool fill_alter_inplace_info(THD *thd, - TABLE *table, - bool varchar, +static bool fill_alter_inplace_info(THD *thd, TABLE *table, bool varchar, Alter_inplace_info *ha_alter_info) { Field **f_ptr, *field; @@ -6179,7 +6218,6 @@ static bool fill_alter_inplace_info(THD *thd, Create_field *new_field; KEY_PART_INFO *key_part, *new_part; KEY_PART_INFO *end; - uint candidate_key_count= 0; Alter_info *alter_info= ha_alter_info->alter_info; DBUG_ENTER("fill_alter_inplace_info"); @@ -6451,6 +6489,17 @@ static bool fill_alter_inplace_info(THD *thd, 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 && + (!my_strcasecmp(system_charset_info, + ha_alter_info->key_info_buffer->name, + 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 new: %d", table->s->keys, ha_alter_info->key_count)); @@ -6526,6 +6575,15 @@ static bool fill_alter_inplace_info(THD *thd, goto index_changed; } + /* + 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)) + goto index_changed; + /* Check that key comment is not changed. */ if (table_key->comment.length != new_key->comment.length || (table_key->comment.length && @@ -6584,22 +6642,6 @@ static bool fill_alter_inplace_info(THD *thd, /* Now let us calculate flags for storage engine API. */ - /* Count all existing candidate keys. */ - for (table_key= table->key_info; table_key < table_key_end; table_key++) - { - /* - Check if key is a candidate key, This key is either already primary key - or could be promoted to primary key if the original primary key is - dropped. - In MySQL one is allowed to create primary key with partial fields (i.e. - primary key which is not considered candidate). For simplicity we count - such key as a candidate key here. - */ - if (((uint) (table_key - table->key_info) == table->s->primary_key) || - is_candidate_key(table_key)) - candidate_key_count++; - } - /* Figure out what kind of indexes we are dropping. */ KEY **dropped_key; KEY **dropped_key_end= ha_alter_info->index_drop_buffer + @@ -6612,21 +6654,10 @@ static bool fill_alter_inplace_info(THD *thd, if (table_key->flags & HA_NOSAME) { - /* - Unique key. Check for PRIMARY KEY. Also see comment about primary - and candidate keys above. - */ - if ((uint) (table_key - table->key_info) == table->s->primary_key) - { + if (table_key == old_pk) ha_alter_info->handler_flags|= Alter_inplace_info::DROP_PK_INDEX; - candidate_key_count--; - } else - { ha_alter_info->handler_flags|= Alter_inplace_info::DROP_UNIQUE_INDEX; - if (is_candidate_key(table_key)) - candidate_key_count--; - } } else ha_alter_info->handler_flags|= Alter_inplace_info::DROP_INDEX; @@ -6639,23 +6670,10 @@ static bool fill_alter_inplace_info(THD *thd, if (new_key->flags & HA_NOSAME) { - bool is_pk= !my_strcasecmp(system_charset_info, new_key->name, primary_key_name); - - if ((!(new_key->flags & HA_KEY_HAS_PART_KEY_SEG) && - !(new_key->flags & HA_NULL_PART_KEY)) || - is_pk) - { - /* Candidate key or primary key! */ - if (candidate_key_count == 0 || is_pk) - ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PK_INDEX; - else - ha_alter_info->handler_flags|= Alter_inplace_info::ADD_UNIQUE_INDEX; - candidate_key_count++; - } + if (new_key == new_pk) + ha_alter_info->handler_flags|= Alter_inplace_info::ADD_PK_INDEX; else - { ha_alter_info->handler_flags|= Alter_inplace_info::ADD_UNIQUE_INDEX; - } } else ha_alter_info->handler_flags|= Alter_inplace_info::ADD_INDEX; @@ -7294,7 +7312,7 @@ static bool mysql_inplace_alter_table(THD *thd, HA_EXTRA_PREPARE_FOR_RENAME : HA_EXTRA_NOT_USED, NULL); - if (thd->locked_tables_list.reopen_tables(thd)) + if (thd->locked_tables_list.reopen_tables(thd, false)) thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0); /* QQ; do something about metadata locks ? */ } @@ -8230,6 +8248,50 @@ static bool fk_prepare_copy_alter_table(THD *thd, TABLE *table, } } + /* + 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_info::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) + continue; + + Foreign_key *fk= static_cast<Foreign_key*>(key); + char dbuf[NAME_LEN]; + char tbuf[NAME_LEN]; + const char *ref_db= fk->ref_db.str ? fk->ref_db.str : alter_ctx->new_db; + const char *ref_table= fk->ref_table.str; + MDL_request mdl_request; + + if (lower_case_table_names) + { + strmake_buf(dbuf, ref_db); + my_casedn_str(system_charset_info, dbuf); + strmake_buf(tbuf, ref_table); + my_casedn_str(system_charset_info, tbuf); + ref_db= dbuf; + ref_table= tbuf; + } + + mdl_request.init(MDL_key::TABLE, ref_db, ref_table, 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); } @@ -9081,6 +9143,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, /* Mark that we have created table in storage engine. */ no_ha_table= false; + DEBUG_SYNC(thd, "alter_table_intermediate_table_created"); if (create_info->tmp_table()) { @@ -9099,8 +9162,10 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, alter_ctx.tmp_name, strlen(alter_ctx.tmp_name), alter_ctx.tmp_name, TL_READ_NO_INSERT); /* Table is in thd->temporary_tables */ - (void) open_temporary_table(thd, &tbl); + if (open_temporary_table(thd, &tbl)) + goto err_new_table_cleanup; new_table= tbl.table; + DBUG_ASSERT(new_table); } else { @@ -9110,9 +9175,9 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, alter_ctx.get_tmp_path(), alter_ctx.new_db, alter_ctx.tmp_name, true, true); + if (!new_table) + goto err_new_table_cleanup; } - if (!new_table) - 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. @@ -9300,7 +9365,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, end_inplace: - if (thd->locked_tables_list.reopen_tables(thd)) + if (thd->locked_tables_list.reopen_tables(thd, false)) goto err_with_mdl_after_alter; THD_STAGE_INFO(thd, stage_end); @@ -9414,9 +9479,7 @@ bool mysql_trans_prepare_alter_copy_data(THD *thd) This needs to be done before external_lock. */ - if (ha_enable_transaction(thd, FALSE)) - DBUG_RETURN(TRUE); - DBUG_RETURN(FALSE); + DBUG_RETURN(ha_enable_transaction(thd, FALSE) != 0); } @@ -9472,6 +9535,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, ha_rows examined_rows; ha_rows found_rows; bool auto_increment_field_copied= 0; + bool cleanup_done= 0; ulonglong save_sql_mode= thd->variables.sql_mode; ulonglong prev_insert_id, time_to_report_progress; Field **dfield_ptr= to->default_field; @@ -9480,15 +9544,23 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, /* Two or 3 stages; Sorting, copying data and update indexes */ thd_progress_init(thd, 2 + MY_TEST(order)); - if (mysql_trans_prepare_alter_copy_data(thd)) - DBUG_RETURN(-1); - if (!(copy= new Copy_field[to->s->fields])) DBUG_RETURN(-1); /* purecov: inspected */ + 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); + } alter_table_manage_keys(to, from->file->indexes_are_disabled(), keys_onoff); @@ -9498,7 +9570,6 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, from->file->info(HA_STATUS_VARIABLE); to->file->ha_start_bulk_insert(from->file->stats.records, ignore ? 0 : HA_CREATE_UNIQUE_INDEX_BY_SORT); - List_iterator<Create_field> it(create); Create_field *def; copy_end=copy; @@ -9702,7 +9773,6 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, } end_read_record(&info); free_io_cache(from); - delete [] copy; THD_STAGE_INFO(thd, stage_enabling_keys); thd_progress_next_stage(thd); @@ -9717,6 +9787,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, to->file->print_error(my_errno,MYF(0)); error= 1; } + cleanup_done= 1; to->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY); if (mysql_trans_commit_alter_copy_data(thd)) @@ -9728,6 +9799,16 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, *copied= found_count; *deleted=delete_count; to->file->ha_release_auto_increment(); + delete [] copy; + + if (!cleanup_done) + { + /* This happens if we get an error during initialzation of data */ + DBUG_ASSERT(error); + to->file->ha_end_bulk_insert(); + ha_enable_transaction(thd, TRUE); + } + if (to->file->ha_external_lock(thd,F_UNLCK)) error=1; if (error < 0 && to->file->extra(HA_EXTRA_PREPARE_FOR_RENAME)) @@ -9866,7 +9947,10 @@ bool mysql_checksum_table(THD *thd, TABLE_LIST *tables, { /* calculating table's checksum */ ha_checksum crc= 0; - uchar null_mask=256 - (1 << t->s->last_null_bit_pos); + DBUG_ASSERT(t->s->last_null_bit_pos < 8); + uchar null_mask= (t->s->last_null_bit_pos ? + (256 - (1 << t->s->last_null_bit_pos)): + 0); t->use_all_columns(); @@ -10038,3 +10122,303 @@ bool check_engine(THD *thd, const char *db_name, DBUG_RETURN(false); } + + +bool Sql_cmd_create_table::execute(THD *thd) +{ + DBUG_ENTER("Sql_cmd_create_table::execute"); + LEX *lex= thd->lex; + TABLE_LIST *all_tables= lex->query_tables; + SELECT_LEX *select_lex= &lex->select_lex; + TABLE_LIST *first_table= select_lex->table_list.first; + DBUG_ASSERT(first_table == all_tables && 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; + DBUG_ASSERT((m_storage_engine_name.str != NULL) == used_engine); + if (used_engine) + { + if (resolve_storage_engine_with_error(thd, &lex->create_info.db_type, + lex->create_info.tmp_table())) + 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); + } + } + + 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); + + if (thd->is_fatal_error) + { + /* If out of memory when creating a copy of alter_info. */ + res= 1; + goto end_with_restore_list; + } + + /* 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); + /* + If we are using SET CHARSET without DEFAULT, add an implicit + DEFAULT to not confuse old users. (This may change). + */ + if ((create_info.used_fields & + (HA_CREATE_USED_DEFAULT_CHARSET | HA_CREATE_USED_CHARSET)) == + HA_CREATE_USED_CHARSET) + { + create_info.used_fields&= ~HA_CREATE_USED_CHARSET; + create_info.used_fields|= HA_CREATE_USED_DEFAULT_CHARSET; + create_info.default_table_charset= create_info.table_charset; + create_info.table_charset= 0; + } + + /* + 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 + { + 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 + + if (select_lex->item_list.elements) // With select + { + 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() && + thd->wsrep_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, + create_table->table_name, "BASE TABLE"); + res= 1; + goto end_with_restore_list; + } + + /* Copy temporarily the statement flags to thd for lock_table_names() */ + uint save_thd_create_info_options= thd->lex->create_info.options; + thd->lex->create_info.options|= create_info.options; + res= open_and_lock_tables(thd, create_info, lex->query_tables, TRUE, 0); + thd->lex->create_info.options= save_thd_create_info_options; + if (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()) + { + TABLE_LIST *duplicate; + if ((duplicate= unique_table(thd, lex->query_tables, + lex->query_tables->next_global, + CHECK_DUP_FOR_CREATE))) + { + 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; + + /* + 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_KEEP_LOG; + } + 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 + { + /* + 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) && + !check_engine(thd, create_table->db, create_table->table_name, + &create_info) && + (!thd->is_current_stmt_binlog_format_row() || + !create_info.tmp_table())) + { + WSREP_TO_ISOLATION_BEGIN(create_table->db, create_table->table_name, NULL) + } + /* 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_KEEP_LOG; + my_ok(thd); + } + } + +end_with_restore_list: + DBUG_RETURN(res); + +WSREP_ERROR_LABEL: + DBUG_RETURN(true); +} |