diff options
Diffstat (limited to 'sql/sql_table.cc')
-rw-r--r-- | sql/sql_table.cc | 230 |
1 files changed, 205 insertions, 25 deletions
diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 99f3466084c..a0e31b34a9e 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1348,6 +1348,34 @@ static int mysql_prepare_table(THD *thd, HA_CREATE_INFO *create_info, /* + Set table default charset, if not set + + SYNOPSIS + set_table_default_charset() + create_info Table create information + + DESCRIPTION + If the table character set was not given explicitely, + let's fetch the database default character set and + apply it to the table. +*/ + +static void set_table_default_charset(THD *thd, + HA_CREATE_INFO *create_info, char *db) +{ + if (!create_info->default_table_charset) + { + HA_CREATE_INFO db_info; + char path[FN_REFLEN]; + /* Abuse build_table_path() to build the path to the db.opt file */ + build_table_path(path, sizeof(path), db, MY_DB_OPT_FILE, ""); + load_db_opt(thd, path, &db_info); + create_info->default_table_charset= db_info.default_table_charset; + } +} + + +/* Extend long VARCHAR fields to blob & prepare field if it's a blob SYNOPSIS @@ -1581,20 +1609,7 @@ bool mysql_create_table(THD *thd,const char *db, const char *table_name, } #endif - /* - If the table character set was not given explicitely, - let's fetch the database default character set and - apply it to the table. - */ - if (!create_info->default_table_charset) - { - HA_CREATE_INFO db_info; - char path[FN_REFLEN]; - /* Abuse build_table_path() to build the path to the db.opt file */ - build_table_path(path, sizeof(path), db, MY_DB_OPT_FILE, ""); - load_db_opt(thd, path, &db_info); - create_info->default_table_charset= db_info.default_table_charset; - } + set_table_default_charset(thd, create_info, (char*) db); if (mysql_prepare_table(thd, create_info, &fields, &keys, internal_tmp_table, &db_options, file, @@ -3090,6 +3105,166 @@ int mysql_drop_indexes(THD *thd, TABLE_LIST *table_list, #endif /* NOT_USED */ + +#define ALTER_TABLE_DATA_CHANGED 1 +#define ALTER_TABLE_INDEX_CHANGED 2 + +/* + SYNOPSIS + compare tables() + table original table + create_list fields in new table + key_list keys in new table + create_info create options in new table + + DESCRIPTION + 'table' (first argument) 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. + + By comparing the changes between the original and new table + we can determine how much it has changed after ALTER TABLE + and whether we need to make a copy of the table, or just change + the .frm file. + + RETURN VALUES + 0 No copy needed + 1 Data changes, copy needed + 2 Index changes, copy needed +*/ + +uint compare_tables(TABLE *table, List<create_field> *create_list, + List<Key> *key_list, HA_CREATE_INFO *create_info, + ALTER_INFO *alter_info, uint order_num) +{ + Field **f_ptr, *field; + uint changes= 0, tmp; + List_iterator_fast<create_field> new_field_it(*create_list); + create_field *new_field; + + /* + Some very basic checks. If number of fields changes, or the + handler, we need to run full ALTER TABLE. In the future + new fields can be added and old dropped without copy, but + not yet. + + Test also that engine was not given during ALTER TABLE, or + we are force to run regular alter table (copy). + E.g. ALTER TABLE tbl_name ENGINE=MyISAM. + + For the following ones we also want to run regular alter table: + ALTER TABLE tbl_name ORDER BY .. + ALTER TABLE tbl_name CONVERT TO CHARACTER SET .. + + At the moment we can't handle altering temporary tables without a copy. + We also test if OPTIMIZE TABLE was given and was mapped to alter table. + In that case we always do full copy. + */ + if (table->s->fields != create_list->elements || + table->s->db_type != create_info->db_type || + table->s->tmp_table || + create_info->used_fields & HA_CREATE_USED_ENGINE || + create_info->used_fields & HA_CREATE_USED_CHARSET || + create_info->used_fields & HA_CREATE_USED_DEFAULT_CHARSET || + (alter_info->flags & ALTER_RECREATE) || + order_num) + return ALTER_TABLE_DATA_CHANGED; + + /* + Go through fields and check if the original ones are compatible + with new table. + */ + for (f_ptr= table->field, new_field= new_field_it++; + (field= *f_ptr); f_ptr++, new_field= new_field_it++) + { + /* Make sure we have at least the default charset in use. */ + if (!new_field->charset) + new_field->charset= create_info->default_table_charset; + + /* Check that NULL behavior is same for old and new fields */ + if ((new_field->flags & NOT_NULL_FLAG) != + (uint) (field->flags & NOT_NULL_FLAG)) + return ALTER_TABLE_DATA_CHANGED; + + /* Don't pack rows in old tables if the user has requested this. */ + if (create_info->row_type == ROW_TYPE_DYNAMIC || + (new_field->flags & BLOB_FLAG) || + new_field->sql_type == MYSQL_TYPE_VARCHAR && + create_info->row_type != ROW_TYPE_FIXED) + create_info->table_options|= HA_OPTION_PACK_RECORD; + + /* Evaluate changes bitmap and send to check_if_incompatible_data() */ + if (!(tmp= field->is_equal(new_field))) + return ALTER_TABLE_DATA_CHANGED; + + changes|= tmp; + } + /* Check if changes are compatible with current handler without a copy */ + if (table->file->check_if_incompatible_data(create_info, changes)) + return ALTER_TABLE_DATA_CHANGED; + + /* + Go through keys and check if the original ones are compatible + with new table. + */ + KEY *table_key_info= table->key_info; + List_iterator_fast<Key> key_it(*key_list); + Key *key= key_it++; + + /* Check if the number of key elements has changed */ + if (table->s->keys != key_list->elements) + return ALTER_TABLE_INDEX_CHANGED; + + for (uint i= 0; i < table->s->keys; i++, table_key_info++, key= key_it++) + { + /* + Check that the key types are compatible between old and new tables. + */ + if (table_key_info->algorithm != key->algorithm || + ((key->type == Key::PRIMARY || key->type == Key::UNIQUE) && + !(table_key_info->flags & HA_NOSAME)) || + (!(key->type == Key::PRIMARY || key->type == Key::UNIQUE) && + (table_key_info->flags & HA_NOSAME)) || + ((key->type == Key::SPATIAL) && + !(table_key_info->flags & HA_SPATIAL)) || + (!(key->type == Key::SPATIAL) && + (table_key_info->flags & HA_SPATIAL)) || + ((key->type == Key::FULLTEXT) && + !(table_key_info->flags & HA_FULLTEXT)) || + (!(key->type == Key::FULLTEXT) && + (table_key_info->flags & HA_FULLTEXT))) + return ALTER_TABLE_INDEX_CHANGED; + + if (table_key_info->key_parts != key->columns.elements) + return ALTER_TABLE_INDEX_CHANGED; + + /* + Check that the key parts remain compatible between the old and + new tables. + */ + KEY_PART_INFO *table_key_part= table_key_info->key_part; + List_iterator_fast<key_part_spec> key_part_it(key->columns); + key_part_spec *key_part= key_part_it++; + for (uint j= 0; j < table_key_info->key_parts; j++, + table_key_part++, key_part= key_part_it++) + { + /* + Key definition has changed if we are using a different field or + if the used key length is different + (If key_part->length == 0 it means we are using the whole field) + */ + if (strcmp(key_part->field_name, table_key_part->field->field_name) || + (key_part->length && key_part->length != table_key_part->length) || + (key_part->length == 0 && table_key_part->length != + table_key_part->field->pack_length())) + return ALTER_TABLE_INDEX_CHANGED; + } + } + + return 0; // Tables are compatible +} + + /* Alter table */ @@ -3111,7 +3286,7 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, ulonglong next_insert_id; uint db_create_options, used_fields; enum db_type old_db_type,new_db_type; - bool need_copy_table; + uint need_copy_table= 0; DBUG_ENTER("mysql_alter_table"); thd->proc_info="init"; @@ -3389,8 +3564,8 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, def_it.remove(); } } - else - { // Use old field value + else // This field was not dropped and not changed, add it to the list + { // for the new table. create_list.push_back(def=new create_field(field,field)); alter_it.rewind(); // Change default if ALTER Alter_column *alter; @@ -3603,17 +3778,22 @@ bool mysql_alter_table(THD *thd,char *new_db, char *new_name, if (table->s->tmp_table) create_info->options|=HA_LEX_CREATE_TMP_TABLE; + set_table_default_charset(thd, create_info, db); + + if (thd->variables.old_alter_table) + need_copy_table= 1; + else + need_copy_table= compare_tables(table, &create_list, &key_list, + create_info, alter_info, order_num); + /* better have a negative test here, instead of positive, like alter_info->flags & ALTER_ADD_COLUMN|ALTER_ADD_INDEX|... so that ALTER TABLE won't break when somebody will add new flag */ - need_copy_table= (alter_info->flags & - ~(ALTER_CHANGE_COLUMN_DEFAULT|ALTER_OPTIONS) || - (create_info->used_fields & - ~(HA_CREATE_USED_COMMENT|HA_CREATE_USED_PASSWORD)) || - table->s->tmp_table); - create_info->frm_only= !need_copy_table; + + if (!need_copy_table) + create_info->frm_only= 1; /* Handling of symlinked tables: @@ -3920,7 +4100,7 @@ end_temporary: err: DBUG_RETURN(TRUE); } - +/* mysql_alter_table */ static int copy_data_between_tables(TABLE *from,TABLE *to, @@ -4132,7 +4312,7 @@ bool mysql_recreate_table(THD *thd, TABLE_LIST *table_list, create_info.row_type=ROW_TYPE_NOT_USED; create_info.default_table_charset=default_charset_info; /* Force alter table to recreate table */ - lex->alter_info.flags= ALTER_CHANGE_COLUMN; + lex->alter_info.flags= (ALTER_CHANGE_COLUMN | ALTER_RECREATE); DBUG_RETURN(mysql_alter_table(thd, NullS, NullS, &create_info, table_list, lex->create_list, lex->key_list, 0, (ORDER *) 0, |