diff --git a/mysql-test/r/ctype_create.result b/mysql-test/r/ctype_create.result index e2dc8c1be66..0da76c556e2 100644 --- a/mysql-test/r/ctype_create.result +++ b/mysql-test/r/ctype_create.result @@ -1,15 +1,37 @@ SET @@character_set_server=latin5; -CREATE DATABASE db1 DEFAULT CHARACTER SET cp1251; -USE db1; -CREATE DATABASE db2; -SHOW CREATE DATABASE db1; +CREATE DATABASE mysqltest1 DEFAULT CHARACTER SET cp1251; +USE mysqltest1; +CREATE DATABASE mysqltest2; +SHOW CREATE DATABASE mysqltest1; Database Create Database -db1 CREATE DATABASE `db1` /*!40100 DEFAULT CHARACTER SET cp1251 */ -SHOW CREATE DATABASE db2; +mysqltest1 CREATE DATABASE `mysqltest1` /*!40100 DEFAULT CHARACTER SET cp1251 */ +SHOW CREATE DATABASE mysqltest2; Database Create Database -db2 CREATE DATABASE `db2` /*!40100 DEFAULT CHARACTER SET latin5 */ -DROP DATABASE db2; -USE db1; +mysqltest2 CREATE DATABASE `mysqltest2` /*!40100 DEFAULT CHARACTER SET latin5 */ +CREATE TABLE mysqltest2.t1 (a char(10)); +SHOW CREATE TABLE mysqltest2.t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` char(10) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin5 +DROP TABLE mysqltest2.t1; +ALTER DATABASE mysqltest2 DEFAULT CHARACTER SET latin7; +CREATE TABLE mysqltest2.t1 (a char(10)); +SHOW CREATE TABLE mysqltest2.t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` char(10) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin7 +DROP DATABASE mysqltest2; +CREATE DATABASE mysqltest2 CHARACTER SET latin2; +CREATE TABLE mysqltest2.t1 (a char(10)); +SHOW CREATE TABLE mysqltest2.t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `a` char(10) default NULL +) ENGINE=MyISAM DEFAULT CHARSET=latin2 +DROP DATABASE mysqltest2; +USE mysqltest1; CREATE TABLE t1 (a char(10)); SHOW CREATE TABLE t1; Table Create Table @@ -32,4 +54,4 @@ t1 CREATE TABLE `t1` ( `a` char(10) collate latin1_german1_ci default NULL ) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_german1_ci DROP TABLE t1; -DROP DATABASE db1; +DROP DATABASE mysqltest1; diff --git a/mysql-test/t/ctype_create.test b/mysql-test/t/ctype_create.test index bd8c22bb4f6..6d7ed6fc205 100644 --- a/mysql-test/t/ctype_create.test +++ b/mysql-test/t/ctype_create.test @@ -13,25 +13,48 @@ SET @@character_set_server=latin5; -CREATE DATABASE db1 DEFAULT CHARACTER SET cp1251; -USE db1; -CREATE DATABASE db2; +CREATE DATABASE mysqltest1 DEFAULT CHARACTER SET cp1251; +USE mysqltest1; +CREATE DATABASE mysqltest2; # # This should be cp1251 # -SHOW CREATE DATABASE db1; +SHOW CREATE DATABASE mysqltest1; # -# This should take the default latin5 value from server level. +# Database "mysqltest2" should take the default latin5 value from +# the server level. +# Afterwards, table "d2.t1" should inherit the default latin5 value from +# the database "mysqltest2", using database option hash. # -SHOW CREATE DATABASE db2; -DROP DATABASE db2; +SHOW CREATE DATABASE mysqltest2; +CREATE TABLE mysqltest2.t1 (a char(10)); +SHOW CREATE TABLE mysqltest2.t1; +DROP TABLE mysqltest2.t1; + +# +# Now we check if the database charset is updated in +# the database options hash when we ALTER DATABASE. +# +ALTER DATABASE mysqltest2 DEFAULT CHARACTER SET latin7; +CREATE TABLE mysqltest2.t1 (a char(10)); +SHOW CREATE TABLE mysqltest2.t1; +DROP DATABASE mysqltest2; + +# +# Now we check if the database charset is removed from +# the database option hash when we DROP DATABASE. +# +CREATE DATABASE mysqltest2 CHARACTER SET latin2; +CREATE TABLE mysqltest2.t1 (a char(10)); +SHOW CREATE TABLE mysqltest2.t1; +DROP DATABASE mysqltest2; # # Check that table value uses database level by default # -USE db1; +USE mysqltest1; CREATE TABLE t1 (a char(10)); SHOW CREATE TABLE t1; DROP TABLE t1; @@ -50,4 +73,4 @@ DROP TABLE t1; # # # -DROP DATABASE db1; +DROP DATABASE mysqltest1; diff --git a/sql/mysql_priv.h b/sql/mysql_priv.h index 72ac3af70ff..9553af13431 100644 --- a/sql/mysql_priv.h +++ b/sql/mysql_priv.h @@ -801,6 +801,7 @@ bool is_keyword(const char *name, uint len); #define MY_DB_OPT_FILE "db.opt" bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create); +void my_dbopt_free(void); /* External variables @@ -894,7 +895,8 @@ extern pthread_mutex_t LOCK_mysql_create_db,LOCK_Acl,LOCK_open, LOCK_delayed_status, LOCK_delayed_create, LOCK_crypt, LOCK_timezone, LOCK_slave_list, LOCK_active_mi, LOCK_manager, LOCK_global_system_variables, LOCK_user_conn; -extern rw_lock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave; +extern rw_lock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave, + LOCK_dboptions; extern pthread_cond_t COND_refresh, COND_thread_count, COND_manager; extern pthread_attr_t connection_attrib; extern I_List threads; diff --git a/sql/mysqld.cc b/sql/mysqld.cc index 436f693d734..75f1aaeb775 100644 --- a/sql/mysqld.cc +++ b/sql/mysqld.cc @@ -382,7 +382,8 @@ pthread_mutex_t LOCK_mysql_create_db, LOCK_Acl, LOCK_open, LOCK_thread_count, LOCK_crypt, LOCK_bytes_sent, LOCK_bytes_received, LOCK_global_system_variables, LOCK_user_conn, LOCK_slave_list, LOCK_active_mi; -rw_lock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave; +rw_lock_t LOCK_grant, LOCK_sys_init_connect, LOCK_sys_init_slave, + LOCK_dboptions; pthread_cond_t COND_refresh,COND_thread_count, COND_slave_stopped, COND_slave_start; pthread_cond_t COND_thread_cache,COND_flush_thread_cache; @@ -901,6 +902,7 @@ void clean_up(bool print_message) bitmap_free(&slave_error_mask); #endif my_tz_free(); + my_dbopt_free(); #ifndef NO_EMBEDDED_ACCESS_CHECKS acl_free(1); grant_free(); @@ -986,6 +988,7 @@ static void clean_up_mutexes() (void) pthread_mutex_destroy(&LOCK_mysql_create_db); (void) pthread_mutex_destroy(&LOCK_Acl); (void) rwlock_destroy(&LOCK_grant); + (void) rwlock_destroy(&LOCK_dboptions); (void) pthread_mutex_destroy(&LOCK_open); (void) pthread_mutex_destroy(&LOCK_thread_count); (void) pthread_mutex_destroy(&LOCK_mapped_file); @@ -2408,6 +2411,7 @@ static int init_thread_environment() (void) my_rwlock_init(&LOCK_sys_init_connect, NULL); (void) my_rwlock_init(&LOCK_sys_init_slave, NULL); (void) my_rwlock_init(&LOCK_grant, NULL); + (void) my_rwlock_init(&LOCK_dboptions, NULL); (void) pthread_cond_init(&COND_thread_count,NULL); (void) pthread_cond_init(&COND_refresh,NULL); (void) pthread_cond_init(&COND_thread_cache,NULL); diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 9db2198268a..47c50be33fa 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -39,6 +39,184 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, const char *path, uint level); +/* Database options hash */ +static HASH dboptions; +static my_bool dboptions_init= 0; + +/* Structure for database options */ +typedef struct my_dbopt_st +{ + char *name; /* Database name */ + uint name_length; /* Database length name */ + CHARSET_INFO *charset; /* Database default character set */ +} my_dbopt_t; + +/* + Function we use in the creation of our hash to get key. +*/ +static byte* dboptions_get_key(my_dbopt_t *opt, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length= opt->name_length; + return (byte*) opt->name; +} + + +/* + Function to free dboptions hash element +*/ + +static void free_dbopt(void *dbopt) +{ + my_free((gptr) dbopt, MYF(0)); +} + + +/* + Initialize database option hash. +*/ + +static my_bool my_dbopt_init(void) +{ + my_bool rc; + rw_wrlock(&LOCK_dboptions); + if (!dboptions_init) + { + dboptions_init= 1; + rc= hash_init(&dboptions, lower_case_table_names ? + &my_charset_bin : system_charset_info, + 32, 0, 0, (hash_get_key) dboptions_get_key, + free_dbopt,0); + } + else + rc= 0; + rw_unlock(&LOCK_dboptions); + return rc; +} + + +/* + Free database option hash. +*/ +void my_dbopt_free(void) +{ + rw_wrlock(&LOCK_dboptions); + if (dboptions_init) + { + hash_free(&dboptions); + dboptions_init= 0; + } + rw_unlock(&LOCK_dboptions); +} + + +/* + Find database options in the hash. + + DESCRIPTION + Search a database options in the hash, usings its path. + Fills "create" on success. + + RETURN VALUES + 0 on success. + 1 on error. +*/ + +static my_bool get_dbopt(const char *dbname, HA_CREATE_INFO *create) +{ + my_dbopt_t *opt; + uint length; + my_bool rc; + + if (my_dbopt_init()) + return 1; + + length= (uint) strlen(dbname); + + rw_rdlock(&LOCK_dboptions); + if ((opt= (my_dbopt_t*) hash_search(&dboptions, (byte*) dbname, length))) + { + create->default_table_charset= opt->charset; + rc= 0; + } + else + rc= 1; + rw_unlock(&LOCK_dboptions); + + return rc; +} + + +/* + Writes database options into the hash. + + DESCRIPTION + Inserts database options into the hash, or updates + options if they are already in the hash. + + RETURN VALUES + 0 on success. + 1 on error. +*/ + +static my_bool put_dbopt(const char *dbname, HA_CREATE_INFO *create) +{ + my_dbopt_t *opt; + uint length; + my_bool rc; + + if (my_dbopt_init()) + return 1; + + length= (uint) strlen(dbname); + + rw_wrlock(&LOCK_dboptions); + if ((opt= (my_dbopt_t*) hash_search(&dboptions, (byte*) dbname, length))) + { + /* Options are already in hash, update them */ + opt->charset= create->default_table_charset; + rc= 0; + } + else + { + /* Options are not in the hash, insert them */ + char *tmp_name; + if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), + &opt, sizeof(*opt), &tmp_name, length+1, NullS)) + { + rc= 1; + goto ret; + } + + opt->name= tmp_name; + opt->name_length= length; + opt->charset= create->default_table_charset; + strmov(opt->name, dbname); + + if ((rc= my_hash_insert(&dboptions, (byte*) opt))) + my_free((gptr) opt, MYF(0)); + } + +ret: + rw_unlock(&LOCK_dboptions); + return rc; +} + + +/* + Deletes database options from the hash. +*/ + +void del_dbopt(const char *path) +{ + my_dbopt_t *opt; + rw_wrlock(&LOCK_dboptions); + if ((opt= (my_dbopt_t *)hash_search(&dboptions, path, strlen(path)))) + hash_delete(&dboptions, (byte*) opt); + rw_unlock(&LOCK_dboptions); +} + + /* Create database options file: @@ -56,15 +234,19 @@ static bool write_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) char buf[256]; // Should be enough for one option bool error=1; + if (!create->default_table_charset) + create->default_table_charset= thd->variables.collation_server; + + if (put_dbopt(path, create)) + return 1; + if ((file=my_create(path, CREATE_MODE,O_RDWR | O_TRUNC,MYF(MY_WME))) >= 0) { ulong length; - CHARSET_INFO *cs= ((create && create->default_table_charset) ? - create->default_table_charset : - thd->variables.collation_server); length= my_sprintf(buf,(buf, "default-character-set=%s\ndefault-collation=%s\n", - cs->csname,cs->name)); + create->default_table_charset->csname, + create->default_table_charset->name)); /* Error is written by my_write */ if (!my_write(file,(byte*) buf, length, MYF(MY_NABP+MY_WME))) @@ -101,6 +283,12 @@ bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) bzero((char*) create,sizeof(*create)); create->default_table_charset= thd->variables.collation_server; + + /* Check if options for this database are already in the hash */ + if (!get_dbopt(path, create)) + DBUG_RETURN(0); + + /* Otherwise, load options from the .opt file */ if ((file=my_open(path, O_RDONLY | O_SHARE, MYF(0))) >= 0) { IO_CACHE cache; @@ -137,9 +325,16 @@ bool load_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) } } } - error=0; end_io_cache(&cache); my_close(file,MYF(0)); + /* + Put the loaded value into the hash. + Note that another thread could've added the same + entry to the hash after we called get_dbopt(), + but it's not an error, as put_dbopt() takes this + possibility into account. + */ + error= put_dbopt(path, create); } DBUG_RETURN(error); } @@ -338,6 +533,8 @@ int mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) int error = 0; char path[FN_REFLEN+16], tmp_db[NAME_LEN+1]; MY_DIR *dirp; + uint length; + my_dbopt_t *dbopt; DBUG_ENTER("mysql_rm_db"); VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); @@ -350,7 +547,11 @@ int mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) } (void) sprintf(path,"%s/%s",mysql_data_home,db); - unpack_dirname(path,path); // Convert if not unix + length= unpack_dirname(path,path); // Convert if not unix + strmov(path+length, MY_DB_OPT_FILE); // Append db option file name + del_dbopt(path); // Remove dboption hash entry + path[length]= '\0'; // Remove file name + /* See if the directory exists */ if (!(dirp = my_dir(path,MYF(MY_DONT_SORT)))) { diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index 0cc25c4fe6e..3ce1c334f96 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -4805,6 +4805,7 @@ bool reload_acl_and_cache(THD *thd, ulong options, TABLE_LIST *tables, if (lock_global_read_lock(thd)) return 1; } + my_dbopt_free(); result=close_cached_tables(thd,(options & REFRESH_FAST) ? 0 : 1, tables); } if (options & REFRESH_HOSTS) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index a560bd40028..c0be95c0453 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -1158,6 +1158,23 @@ int 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; + uint length; + char path[FN_REFLEN]; + (void) sprintf(path,"%s/%s", mysql_data_home, db); + length= unpack_dirname(path,path); // Convert if not unix + strmov(path+length, MY_DB_OPT_FILE); + load_db_opt(thd, path, &db_info); + create_info->default_table_charset= db_info.default_table_charset; + } + if (mysql_prepare_table(thd, create_info, fields, keys, tmp_table, db_options, file, key_info_buffer, &key_count, diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 2860077b6a0..ccbaf7c0112 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -1023,7 +1023,7 @@ create: bzero((char*) &lex->create_info,sizeof(lex->create_info)); lex->create_info.options=$2 | $4; lex->create_info.db_type= (enum db_type) lex->thd->variables.table_type; - lex->create_info.default_table_charset= thd->variables.collation_database; + lex->create_info.default_table_charset= NULL; lex->name=0; } create2 @@ -1815,7 +1815,7 @@ alter: lex->select_lex.db=lex->name=0; bzero((char*) &lex->create_info,sizeof(lex->create_info)); lex->create_info.db_type= DB_TYPE_DEFAULT; - lex->create_info.default_table_charset= thd->variables.collation_database; + lex->create_info.default_table_charset= NULL; lex->create_info.row_type= ROW_TYPE_NOT_USED; lex->alter_info.reset(); lex->alter_info.is_simple= 1;