summaryrefslogtreecommitdiff
path: root/sql/sql_db.cc
diff options
context:
space:
mode:
Diffstat (limited to 'sql/sql_db.cc')
-rw-r--r--sql/sql_db.cc484
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);
+}