diff options
Diffstat (limited to 'sql/sql_db.cc')
-rw-r--r-- | sql/sql_db.cc | 484 |
1 files changed, 460 insertions, 24 deletions
diff --git a/sql/sql_db.cc b/sql/sql_db.cc index 902539dfdec..77d99862bf0 100644 --- a/sql/sql_db.cc +++ b/sql/sql_db.cc @@ -20,6 +20,7 @@ #include "mysql_priv.h" #include <mysys_err.h> #include "sp.h" +#include "events.h" #include <my_dir.h> #include <m_ctype.h> #ifdef __WIN__ @@ -38,6 +39,107 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, static long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path); static my_bool rm_dir_w_symlink(const char *org_path, my_bool send_error); + + +/* Database lock hash */ +HASH lock_db_cache; +pthread_mutex_t LOCK_lock_db; +int creating_database= 0; // how many database locks are made + + +/* Structure for database lock */ +typedef struct my_dblock_st +{ + char *name; /* Database name */ + uint name_length; /* Database length name */ +} my_dblock_t; + + +/* + lock_db key. +*/ + +static byte* lock_db_get_key(my_dblock_t *ptr, uint *length, + my_bool not_used __attribute__((unused))) +{ + *length= ptr->name_length; + return (byte*) ptr->name; +} + + +/* + Free lock_db hash element. +*/ + +static void lock_db_free_element(void *ptr) +{ + my_free((gptr) ptr, MYF(0)); +} + + +/* + Put a database lock entry into the hash. + + DESCRIPTION + Insert a database lock entry into hash. + LOCK_db_lock must be previously locked. + + RETURN VALUES + 0 on success. + 1 on error. +*/ + +static my_bool lock_db_insert(const char *dbname, uint length) +{ + my_dblock_t *opt; + my_bool error= 0; + DBUG_ENTER("lock_db_insert"); + + safe_mutex_assert_owner(&LOCK_lock_db); + + if (!(opt= (my_dblock_t*) hash_search(&lock_db_cache, + (byte*) dbname, length))) + { + /* Db is not in the hash, insert it */ + char *tmp_name; + if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL), + &opt, (uint) sizeof(*opt), &tmp_name, length+1, + NullS)) + { + error= 1; + goto end; + } + + opt->name= tmp_name; + strmov(opt->name, dbname); + opt->name_length= length; + + if ((error= my_hash_insert(&lock_db_cache, (byte*) opt))) + { + my_free((gptr) opt, MYF(0)); + goto end; + } + } + +end: + DBUG_RETURN(error); +} + + +/* + Delete a database lock entry from hash. +*/ + +void lock_db_delete(const char *name, uint length) +{ + my_dblock_t *opt; + safe_mutex_assert_owner(&LOCK_lock_db); + if ((opt= (my_dblock_t *)hash_search(&lock_db_cache, + (const byte*) name, length))) + hash_delete(&lock_db_cache, (byte*) opt); +} + + /* Database options hash */ static HASH dboptions; static my_bool dboptions_init= 0; @@ -90,10 +192,10 @@ static void free_dbopt(void *dbopt) /* - Initialize database option hash + Initialize database option hash and locked database hash. SYNOPSIS - my_dbopt_init() + my_database_names() NOTES Must be called before any other database function is called. @@ -103,7 +205,7 @@ static void free_dbopt(void *dbopt) 1 Fatal error */ -bool my_dbopt_init(void) +bool my_database_names_init(void) { bool error= 0; (void) my_rwlock_init(&LOCK_dboptions, NULL); @@ -113,27 +215,38 @@ bool my_dbopt_init(void) error= 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); + free_dbopt,0) || + hash_init(&lock_db_cache, lower_case_table_names ? + &my_charset_bin : system_charset_info, + 32, 0, 0, (hash_get_key) lock_db_get_key, + lock_db_free_element,0); + } return error; } + /* - Free database option hash. + Free database option hash and locked databases hash. */ -void my_dbopt_free(void) +void my_database_names_free(void) { if (dboptions_init) { dboptions_init= 0; hash_free(&dboptions); (void) rwlock_destroy(&LOCK_dboptions); + hash_free(&lock_db_cache); } } +/* + Cleanup cached options +*/ + void my_dbopt_cleanup(void) { rw_wrlock(&LOCK_dboptions); @@ -272,7 +385,7 @@ static bool write_db_opt(THD *thd, const char *path, HA_CREATE_INFO *create) if ((file=my_create(path, CREATE_MODE,O_RDWR | O_TRUNC,MYF(MY_WME))) >= 0) { ulong length; - length= (ulong) (strxnmov(buf, sizeof(buf), "default-character-set=", + length= (ulong) (strxnmov(buf, sizeof(buf)-1, "default-character-set=", create->default_table_charset->csname, "\ndefault-collation=", create->default_table_charset->name, @@ -411,6 +524,7 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, bool silent) { char path[FN_REFLEN+16]; + char tmp_query[FN_REFLEN+16]; long result= 1; int error= 0; MY_STAT stat_info; @@ -446,8 +560,7 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); /* Check directory */ - strxmov(path, mysql_data_home, "/", db, NullS); - path_len= unpack_dirname(path,path); // Convert if not unix + path_len= build_table_filename(path, sizeof(path), db, "", ""); path[path_len-1]= 0; // Remove last '/' from path if (my_stat(path,&stat_info,MYF(0))) @@ -508,15 +621,20 @@ bool mysql_create_db(THD *thd, char *db, HA_CREATE_INFO *create_info, if (!thd->query) // Only in replication { - query= path; - query_length= (uint) (strxmov(path,"create database `", db, "`", NullS) - - path); + query= tmp_query; + query_length= (uint) (strxmov(tmp_query,"create database `", + db, "`", NullS) - tmp_query); } else { query= thd->query; query_length= thd->query_length; } + + ha_binlog_log_query(thd, 0, LOGCOM_CREATE_DB, + query, query_length, + db, ""); + if (mysql_bin_log.is_open()) { Query_log_event qinfo(thd, query, query_length, 0, @@ -581,9 +699,12 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); - /* Check directory */ - strxmov(path, mysql_data_home, "/", db, "/", MY_DB_OPT_FILE, NullS); - fn_format(path, path, "", "", MYF(MY_UNPACK_FILENAME)); + /* + Recreate db options file: /dbpath/.db.opt + We pass MY_DB_OPT_FILE as "extension" to avoid + "table name to file name" encoding. + */ + build_table_filename(path, sizeof(path), db, "", MY_DB_OPT_FILE); if ((error=write_db_opt(thd, path, create_info))) goto exit; @@ -599,6 +720,10 @@ bool mysql_alter_db(THD *thd, const char *db, HA_CREATE_INFO *create_info) thd->variables.collation_database= thd->db_charset; } + ha_binlog_log_query(thd, 0, LOGCOM_ALTER_DB, + thd->query, thd->query_length, + db, ""); + if (mysql_bin_log.is_open()) { Query_log_event qinfo(thd, thd->query, thd->query_length, 0, @@ -672,8 +797,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) VOID(pthread_mutex_lock(&LOCK_mysql_create_db)); - (void) sprintf(path,"%s/%s",mysql_data_home,db); - length= unpack_dirname(path,path); // Convert if not unix + length= build_table_filename(path, sizeof(path), db, "", ""); 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 @@ -780,6 +904,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent) exit: (void)sp_drop_db_routines(thd, db); /* QQ Ignore errors for now */ + error= Events::drop_schema_events(thd, db); /* If this database was the client's selected database, we silently change the client's selected database to nothing (to have an empty @@ -874,7 +999,8 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, found_other_files++; continue; } - extension= fn_ext(file->name); + if (!(extension= strrchr(file->name, '.'))) + extension= strend(file->name); if (find_type(extension, &deletable_extentions,1+2) <= 0) { if (find_type(extension, ha_known_exts(),1+2) <= 0) @@ -892,7 +1018,9 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db, if (!table_list) goto err; table_list->db= (char*) (table_list+1); - strmov(table_list->table_name= strmov(table_list->db,db)+1, file->name); + table_list->table_name= strmov(table_list->db, db) + 1; + VOID(filename_to_tablename(file->name, table_list->table_name, + strlen(file->name) + 1)); table_list->alias= table_list->table_name; // If lower_case_table_names=2 /* Link into list */ (*tot_list_next)= table_list; @@ -1189,15 +1317,14 @@ bool mysql_change_db(THD *thd, const char *name, bool no_access_check) sctx->priv_user, sctx->priv_host, db_name); - mysql_log.write(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR), - sctx->priv_user, sctx->priv_host, db_name); - my_free(db_name, MYF(0)); + general_log_print(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR), + sctx->priv_user, sctx->priv_host, db_name); + my_free(db_name,MYF(0)); DBUG_RETURN(1); } } #endif - (void) sprintf(path,"%s/%s", mysql_data_home, db_name); - path_length= unpack_dirname(path, path); // Convert if not UNIX + path_length= build_table_filename(path, sizeof(path), db_name, "", ""); if (path_length && path[path_length-1] == FN_LIBCHAR) path[path_length-1]= '\0'; // remove ending '\' if (my_access(path,F_OK)) @@ -1230,3 +1357,312 @@ end: } DBUG_RETURN(0); } + + +static int +lock_databases(THD *thd, const char *db1, uint length1, + const char *db2, uint length2) +{ + pthread_mutex_lock(&LOCK_lock_db); + while (!thd->killed && + (hash_search(&lock_db_cache,(byte*) db1, length1) || + hash_search(&lock_db_cache,(byte*) db2, length2))) + { + wait_for_condition(thd, &LOCK_lock_db, &COND_refresh); + pthread_mutex_lock(&LOCK_lock_db); + } + + if (thd->killed) + { + pthread_mutex_unlock(&LOCK_lock_db); + return 1; + } + + lock_db_insert(db1, length1); + lock_db_insert(db2, length2); + creating_database++; + + /* + Wait if a concurent thread is creating a table at the same time. + The assumption here is that it will not take too long until + there is a point in time when a table is not created. + */ + + while (!thd->killed && creating_table) + { + wait_for_condition(thd, &LOCK_lock_db, &COND_refresh); + pthread_mutex_lock(&LOCK_lock_db); + } + + if (thd->killed) + { + lock_db_delete(db1, length1); + lock_db_delete(db2, length2); + creating_database--; + pthread_mutex_unlock(&LOCK_lock_db); + pthread_cond_signal(&COND_refresh); + return(1); + } + + /* + We can unlock now as the hash will protect against anyone creating a table + in the databases we are using + */ + pthread_mutex_unlock(&LOCK_lock_db); + return 0; +} + + +/* + Rename database. + + SYNOPSIS + mysql_rename_db() + thd Thread handler + olddb Old database name + newdb New database name + + DESCRIPTION + This function is invoked whenever a RENAME DATABASE query is executed: + + RENAME DATABASE 'olddb' TO 'newdb'. + + NOTES + + If we have managed to rename (move) tables to the new database + but something failed on a later step, then we store the + RENAME DATABASE event in the log. mysql_rename_db() is atomic in + the sense that it will rename all or none of the tables. + + TODO: + - Better trigger, stored procedure, event, grant handling, + see the comments below. + NOTE: It's probably a good idea to call wait_if_global_read_lock() + once in mysql_rename_db(), instead of locking inside all + the required functions for renaming triggerts, SP, events, grants, etc. + + RETURN VALUES + 0 ok + 1 error +*/ + + +bool mysql_rename_db(THD *thd, LEX_STRING *old_db, LEX_STRING *new_db) +{ + int error= 0, change_to_newdb= 0; + char path[FN_REFLEN+16]; + uint length; + HA_CREATE_INFO create_info; + MY_DIR *dirp; + TABLE_LIST *table_list; + SELECT_LEX *sl= thd->lex->current_select; + DBUG_ENTER("mysql_rename_db"); + + if (lock_databases(thd, old_db->str, old_db->length, + new_db->str, new_db->length)) + return 1; + + /* + Let's remember if we should do "USE newdb" afterwards. + thd->db will be cleared in mysql_rename_db() + */ + if (thd->db && !strcmp(thd->db, old_db->str)) + change_to_newdb= 1; + + build_table_filename(path, sizeof(path)-1, old_db->str, "", MY_DB_OPT_FILE); + if ((load_db_opt(thd, path, &create_info))) + create_info.default_table_charset= thd->variables.collation_server; + + length= build_table_filename(path, sizeof(path)-1, old_db->str, "", ""); + if (length && path[length-1] == FN_LIBCHAR) + path[length-1]=0; // remove ending '\' + if ((error= my_access(path,F_OK))) + { + my_error(ER_BAD_DB_ERROR, MYF(0), old_db->str); + goto exit; + } + + /* Step1: Create the new database */ + if ((error= mysql_create_db(thd, new_db->str, &create_info, 1))) + goto exit; + + /* Step2: Move tables to the new database */ + if ((dirp = my_dir(path,MYF(MY_DONT_SORT)))) + { + uint nfiles= (uint) dirp->number_off_files; + for (uint idx=0 ; idx < nfiles && !thd->killed ; idx++) + { + FILEINFO *file= dirp->dir_entry + idx; + char *extension, tname[FN_REFLEN]; + LEX_STRING table_str; + DBUG_PRINT("info",("Examining: %s", file->name)); + + /* skiping non-FRM files */ + if (my_strcasecmp(files_charset_info, + (extension= fn_rext(file->name)), reg_ext)) + continue; + + /* A frm file found, add the table info rename list */ + *extension= '\0'; + + table_str.length= filename_to_tablename(file->name, + tname, sizeof(tname)-1); + table_str.str= sql_memdup(tname, table_str.length + 1); + Table_ident *old_ident= new Table_ident(thd, *old_db, table_str, 0); + Table_ident *new_ident= new Table_ident(thd, *new_db, table_str, 0); + if (!old_ident || !new_ident || + !sl->add_table_to_list(thd, old_ident, NULL, + TL_OPTION_UPDATING, TL_IGNORE) || + !sl->add_table_to_list(thd, new_ident, NULL, + TL_OPTION_UPDATING, TL_IGNORE)) + { + error= 1; + my_dirend(dirp); + goto exit; + } + } + my_dirend(dirp); + } + + if ((table_list= thd->lex->query_tables) && + (error= mysql_rename_tables(thd, table_list, 1))) + { + /* + Failed to move all tables from the old database to the new one. + In the best case mysql_rename_tables() moved all tables back to the old + database. In the worst case mysql_rename_tables() moved some tables + to the new database, then failed, then started to move the tables back, and + then failed again. In this situation we have some tables in the + old database and some tables in the new database. + Let's delete the option file, and then the new database directory. + If some tables were left in the new directory, rmdir() will fail. + It garantees we never loose any tables. + */ + build_table_filename(path, sizeof(path)-1, new_db->str,"",MY_DB_OPT_FILE); + my_delete(path, MYF(MY_WME)); + length= build_table_filename(path, sizeof(path)-1, new_db->str, "", ""); + if (length && path[length-1] == FN_LIBCHAR) + path[length-1]=0; // remove ending '\' + rmdir(path); + goto exit; + } + + + /* + Step3: move all remaining files to the new db's directory. + Skip db opt file: it's been created by mysql_create_db() in + the new directory, and will be dropped by mysql_rm_db() in the old one. + Trigger TRN and TRG files are be moved as regular files at the moment, + without any special treatment. + + Triggers without explicit database qualifiers in table names work fine: + use d1; + create trigger trg1 before insert on t2 for each row set @a:=1 + rename database d1 to d2; + + TODO: Triggers, having the renamed database explicitely written + in the table qualifiers. + 1. when the same database is renamed: + create trigger d1.trg1 before insert on d1.t1 for each row set @a:=1; + rename database d1 to d2; + Problem: After database renaming, the trigger's body + still points to the old database d1. + 2. when another database is renamed: + create trigger d3.trg1 before insert on d3.t1 for each row + insert into d1.t1 values (...); + rename database d1 to d2; + Problem: After renaming d1 to d2, the trigger's body + in the database d3 still points to database d1. + */ + + if ((dirp = my_dir(path,MYF(MY_DONT_SORT)))) + { + uint nfiles= (uint) dirp->number_off_files; + for (uint idx=0 ; idx < nfiles ; idx++) + { + FILEINFO *file= dirp->dir_entry + idx; + char oldname[FN_REFLEN], newname[FN_REFLEN]; + DBUG_PRINT("info",("Examining: %s", file->name)); + + /* skiping . and .. and MY_DB_OPT_FILE */ + if ((file->name[0] == '.' && + (!file->name[1] || (file->name[1] == '.' && !file->name[2]))) || + !my_strcasecmp(files_charset_info, file->name, MY_DB_OPT_FILE)) + continue; + + /* pass empty file name, and file->name as extension to avoid encoding */ + build_table_filename(oldname, sizeof(oldname)-1, + old_db->str, "", file->name); + build_table_filename(newname, sizeof(newname)-1, + new_db->str, "", file->name); + my_rename(oldname, newname, MYF(MY_WME)); + } + my_dirend(dirp); + } + + /* + Step4: TODO: moving stored procedures in the 'proc' system table + We need a new function: sp_move_db_routines(thd, olddb, newdb) + Which will basically have the same effect with: + UPDATE proc SET db='newdb' WHERE db='olddb' + Note, for 5.0 to 5.1 upgrade purposes we don't really need it. + + The biggest problem here is that we can't have a lock on LOCK_open() while + calling open_table() for 'proc'. + + Two solutions: + - Start by opening the 'event' and 'proc' (and other) tables for write + even before creating the 'to' database. (This will have the nice + effect of blocking another 'rename database' while the lock is active). + - Use the solution "Disable create of new tables during lock table" + + For an example of how to read through all rows, see: + sql_help.cc::search_topics() + */ + + /* + Step5: TODO: moving events in the 'event' system table + We need a new function evex_move_db_events(thd, olddb, newdb) + Which will have the same effect with: + UPDATE event SET db='newdb' WHERE db='olddb' + Note, for 5.0 to 5.1 upgrade purposes we don't really need it. + */ + + /* + Step6: TODO: moving grants in the 'db', 'tables_priv', 'columns_priv'. + Update each grant table, doing the same with: + UPDATE system_table SET db='newdb' WHERE db='olddb' + */ + + /* + Step7: drop the old database. + remove_db_from_cache(olddb) and query_cache_invalidate(olddb) + are done inside mysql_rm_db(), no needs to execute them again. + mysql_rm_db() also "unuses" if we drop the current database. + */ + error= mysql_rm_db(thd, old_db->str, 0, 1); + + /* Step8: logging */ + if (mysql_bin_log.is_open()) + { + Query_log_event qinfo(thd, thd->query, thd->query_length, 0, TRUE); + thd->clear_error(); + mysql_bin_log.write(&qinfo); + } + + /* Step9: Let's do "use newdb" if we renamed the current database */ + if (change_to_newdb) + error|= mysql_change_db(thd, new_db->str, 0); + +exit: + pthread_mutex_lock(&LOCK_lock_db); + /* Remove the databases from db lock cache */ + lock_db_delete(old_db->str, old_db->length); + lock_db_delete(new_db->str, new_db->length); + creating_database--; + /* Signal waiting CREATE TABLE's to continue */ + pthread_cond_signal(&COND_refresh); + pthread_mutex_unlock(&LOCK_lock_db); + + DBUG_RETURN(error); +} |