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.cc777
1 files changed, 691 insertions, 86 deletions
diff --git a/sql/sql_db.cc b/sql/sql_db.cc
index 91b0c02d23b..cd7ad048802 100644
--- a/sql/sql_db.cc
+++ b/sql/sql_db.cc
@@ -19,8 +19,10 @@
#include "mysql_priv.h"
#include <mysys_err.h>
#include "sp.h"
+#include "events.h"
#include <my_dir.h>
#include <m_ctype.h>
+#include "log.h"
#ifdef __WIN__
#include <direct.h>
#endif
@@ -37,6 +39,112 @@ 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);
+static void mysql_change_db_impl(THD *thd,
+ LEX_STRING *new_db_name,
+ ulong new_db_access,
+ CHARSET_INFO *new_db_charset);
+
+
+/* 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.
+*/
+
+extern "C" uchar* lock_db_get_key(my_dblock_t *, size_t *, my_bool not_used);
+
+uchar* lock_db_get_key(my_dblock_t *ptr, size_t *length,
+ my_bool not_used __attribute__((unused)))
+{
+ *length= ptr->name_length;
+ return (uchar*) ptr->name;
+}
+
+
+/*
+ Free lock_db hash element.
+*/
+
+extern "C" void lock_db_free_element(void *ptr);
+
+void lock_db_free_element(void *ptr)
+{
+ my_free(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,
+ (uchar*) 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, (uint) 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, (uchar*) opt)))
+ my_free(opt, MYF(0));
+ }
+
+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 uchar*) name, length)))
+ hash_delete(&lock_db_cache, (uchar*) opt);
+}
+
+
/* Database options hash */
static HASH dboptions;
static my_bool dboptions_init= 0;
@@ -55,11 +163,14 @@ typedef struct my_dbopt_st
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)))
+extern "C" uchar* dboptions_get_key(my_dbopt_t *opt, size_t *length,
+ my_bool not_used);
+
+uchar* dboptions_get_key(my_dbopt_t *opt, size_t *length,
+ my_bool not_used __attribute__((unused)))
{
*length= opt->name_length;
- return (byte*) opt->name;
+ return (uchar*) opt->name;
}
@@ -82,17 +193,19 @@ static inline void write_to_binlog(THD *thd, char *query, uint q_len,
Function to free dboptions hash element
*/
-static void free_dbopt(void *dbopt)
+extern "C" void free_dbopt(void *dbopt);
+
+void free_dbopt(void *dbopt)
{
- my_free((gptr) dbopt, MYF(0));
+ my_free((uchar*) dbopt, MYF(0));
}
/*
- 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.
@@ -102,7 +215,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);
@@ -112,27 +225,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);
@@ -166,7 +290,7 @@ static my_bool get_dbopt(const char *dbname, HA_CREATE_INFO *create)
length= (uint) strlen(dbname);
rw_rdlock(&LOCK_dboptions);
- if ((opt= (my_dbopt_t*) hash_search(&dboptions, (byte*) dbname, length)))
+ if ((opt= (my_dbopt_t*) hash_search(&dboptions, (uchar*) dbname, length)))
{
create->default_table_charset= opt->charset;
error= 0;
@@ -198,12 +322,12 @@ static my_bool put_dbopt(const char *dbname, HA_CREATE_INFO *create)
length= (uint) strlen(dbname);
rw_wrlock(&LOCK_dboptions);
- if (!(opt= (my_dbopt_t*) hash_search(&dboptions, (byte*) dbname, length)))
+ if (!(opt= (my_dbopt_t*) hash_search(&dboptions, (uchar*) dbname, length)))
{
/* Options are not in the hash, insert them */
char *tmp_name;
if (!my_multi_malloc(MYF(MY_WME | MY_ZEROFILL),
- &opt, (uint) sizeof(*opt), &tmp_name, length+1,
+ &opt, (uint) sizeof(*opt), &tmp_name, (uint) length+1,
NullS))
{
error= 1;
@@ -214,9 +338,9 @@ static my_bool put_dbopt(const char *dbname, HA_CREATE_INFO *create)
strmov(opt->name, dbname);
opt->name_length= length;
- if ((error= my_hash_insert(&dboptions, (byte*) opt)))
+ if ((error= my_hash_insert(&dboptions, (uchar*) opt)))
{
- my_free((gptr) opt, MYF(0));
+ my_free(opt, MYF(0));
goto end;
}
}
@@ -238,9 +362,9 @@ void del_dbopt(const char *path)
{
my_dbopt_t *opt;
rw_wrlock(&LOCK_dboptions);
- if ((opt= (my_dbopt_t *)hash_search(&dboptions, (const byte*) path,
+ if ((opt= (my_dbopt_t *)hash_search(&dboptions, (const uchar*) path,
strlen(path))))
- hash_delete(&dboptions, (byte*) opt);
+ hash_delete(&dboptions, (uchar*) opt);
rw_unlock(&LOCK_dboptions);
}
@@ -271,14 +395,14 @@ 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,
"\n", NullS) - buf);
/* Error is written by my_write */
- if (!my_write(file,(byte*) buf, length, MYF(MY_NABP+MY_WME)))
+ if (!my_write(file,(uchar*) buf, length, MYF(MY_NABP+MY_WME)))
error=0;
my_close(file,MYF(0));
}
@@ -416,15 +540,48 @@ bool load_db_opt_by_name(THD *thd, const char *db_name,
{
char db_opt_path[FN_REFLEN];
- strxnmov(db_opt_path, sizeof (db_opt_path) - 1, mysql_data_home, "/",
- db_name, "/", MY_DB_OPT_FILE, NullS);
-
- unpack_filename(db_opt_path, db_opt_path);
+ /*
+ Pass an empty file name, and the database options file name as extension
+ to avoid table name to file name encoding.
+ */
+ (void) build_table_filename(db_opt_path, sizeof(db_opt_path),
+ db_name, "", MY_DB_OPT_FILE, 0);
return load_db_opt(thd, db_opt_path, db_create_info);
}
+/**
+ Return default database collation.
+
+ @param thd Thread context.
+ @param db_name Database name.
+
+ @return CHARSET_INFO object. The operation always return valid character
+ set, even if the database does not exist.
+*/
+
+CHARSET_INFO *get_default_db_collation(THD *thd, const char *db_name)
+{
+ HA_CREATE_INFO db_info;
+
+ if (thd->db != NULL && strcmp(db_name, thd->db) == 0)
+ return thd->db_charset;
+
+ load_db_opt_by_name(thd, db_name, &db_info);
+
+ /*
+ NOTE: even if load_db_opt_by_name() fails,
+ db_info.default_table_charset contains valid character set
+ (collation_server). We should not fail if load_db_opt_by_name() fails,
+ because it is valid case. If a database has been created just by
+ "mkdir", it does not contain db.opt file, but it is valid database.
+ */
+
+ return db_info.default_table_charset;
+}
+
+
/*
Create a database
@@ -453,6 +610,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;
@@ -488,8 +646,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, "", "", 0);
path[path_len-1]= 0; // Remove last '/' from path
if (my_stat(path,&stat_info,MYF(0)))
@@ -550,15 +707,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,
@@ -624,16 +786,17 @@ 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, 0);
if ((error=write_db_opt(thd, path, create_info)))
goto exit;
- /*
- Change options if current database is being altered
- TODO: Delete this code
- */
+ /* Change options if current database is being altered. */
+
if (thd->db && !strcmp(thd->db,db))
{
thd->db_charset= create_info->default_table_charset ?
@@ -642,6 +805,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,
@@ -716,8 +883,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, "", "", 0);
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
@@ -793,7 +959,7 @@ bool mysql_rm_db(THD *thd,char *db,bool if_exists, bool silent)
TABLE_LIST *tbl;
uint db_len;
- if (!(query= thd->alloc(MAX_DROP_TABLE_Q_LEN)))
+ if (!(query= (char*) thd->alloc(MAX_DROP_TABLE_Q_LEN)))
goto exit; /* not much else we can do */
query_pos= query_data_start= strmov(query,"drop table ");
query_end= query + MAX_DROP_TABLE_Q_LEN;
@@ -827,6 +993,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 */
+ 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
@@ -834,7 +1001,7 @@ exit:
it to 0.
*/
if (thd->db && !strcmp(thd->db, db))
- thd->set_db(NULL, 0);
+ mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
VOID(pthread_mutex_unlock(&LOCK_mysql_create_db));
start_waiting_global_read_lock(thd);
exit2:
@@ -893,7 +1060,7 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db,
DBUG_PRINT("my",("New subdir found: %s", newpath));
if ((mysql_rm_known_files(thd, new_dirp, NullS, newpath,1,0)) < 0)
goto err;
- if (!(copy_of_path= thd->memdup(newpath, length+1)) ||
+ if (!(copy_of_path= (char*) thd->memdup(newpath, length+1)) ||
!(dir= new (thd->mem_root) String(copy_of_path, length,
&my_charset_bin)) ||
raid_dirs.push_back(dir))
@@ -921,7 +1088,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)
@@ -939,7 +1107,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;
@@ -956,7 +1126,7 @@ static long mysql_rm_known_files(THD *thd, MY_DIR *dirp, const char *db,
}
}
if (thd->killed ||
- (tot_list && mysql_rm_table_part2_with_lock(thd, tot_list, 1, 0, 1)))
+ (tot_list && mysql_rm_table_part2(thd, tot_list, 1, 0, 1, 1)))
goto err;
/* Remove RAID directories */
@@ -1189,22 +1359,108 @@ static void mysql_change_db_impl(THD *thd,
}
+
+/**
+ Backup the current database name before switch.
+
+ @param[in] thd thread handle
+ @param[in, out] saved_db_name IN: "str" points to a buffer where to store
+ the old database name, "length" contains the
+ buffer size
+ OUT: if the current (default) database is
+ not NULL, its name is copied to the
+ buffer pointed at by "str"
+ and "length" is updated accordingly.
+ Otherwise "str" is set to NULL and
+ "length" is set to 0.
+*/
+
+static void backup_current_db_name(THD *thd,
+ LEX_STRING *saved_db_name)
+{
+ if (!thd->db)
+ {
+ /* No current (default) database selected. */
+
+ saved_db_name->str= NULL;
+ saved_db_name->length= 0;
+ }
+ else
+ {
+ strmake(saved_db_name->str, thd->db, saved_db_name->length);
+ saved_db_name->length= thd->db_length;
+ }
+}
+
+
+/**
+ Return TRUE if db1_name is equal to db2_name, FALSE otherwise.
+
+ The function allows to compare database names according to the MySQL
+ rules. The database names db1 and db2 are equal if:
+ - db1 is NULL and db2 is NULL;
+ or
+ - db1 is not-NULL, db2 is not-NULL, db1 is equal (ignoring case) to
+ db2 in system character set (UTF8).
+*/
+
+static inline bool
+cmp_db_names(const char *db1_name,
+ const char *db2_name)
+{
+ return
+ /* db1 is NULL and db2 is NULL */
+ !db1_name && !db2_name ||
+
+ /* db1 is not-NULL, db2 is not-NULL, db1 == db2. */
+ db1_name && db2_name &&
+ my_strcasecmp(system_charset_info, db1_name, db2_name) == 0;
+}
+
+
/**
- @brief Change the current database.
+ @brief Change the current database and its attributes unconditionally.
@param thd thread handle
- @param name database name
- @param force_switch if this flag is set (TRUE), mysql_change_db() will
- switch to NULL db if the specified database is not
- available anymore. Corresponding warning will be
- thrown in this case. This flag is used to change
- database in stored-routine-execution code.
-
- @details Check that the database name corresponds to a valid and existent
- database, check access rights (unless called with no_access_check), and
- set the current database. This function is called to change the current
- database upon user request (COM_CHANGE_DB command) or temporarily, to
- execute a stored routine.
+ @param new_db_name database name
+ @param force_switch if force_switch is FALSE, then the operation will fail if
+
+ - new_db_name is NULL or empty;
+
+ - OR new database name is invalid
+ (check_db_name() failed);
+
+ - OR user has no privilege on the new database;
+
+ - OR new database does not exist;
+
+ if force_switch is TRUE, then
+
+ - if new_db_name is NULL or empty, the current
+ database will be NULL, @@collation_database will
+ be set to @@collation_server, the operation will
+ succeed.
+
+ - if new database name is invalid
+ (check_db_name() failed), the current database
+ will be NULL, @@collation_database will be set to
+ @@collation_server, but the operation will fail;
+
+ - user privileges will not be checked
+ (THD::db_access however is updated);
+
+ TODO: is this really the intention?
+ (see sp-security.test).
+
+ - if new database does not exist,the current database
+ will be NULL, @@collation_database will be set to
+ @@collation_server, a warning will be thrown, the
+ operation will succeed.
+
+ @details The function checks that the database name corresponds to a
+ valid and existent database, checks access rights and changes the current
+ database with database attributes (@@collation_database session variable,
+ THD::db_access).
This function is not the only way to switch the database that is
currently employed. When the replication slave thread switches the
@@ -1230,19 +1486,24 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
Security_context *sctx= thd->security_ctx;
ulong db_access= sctx->db_access;
+ CHARSET_INFO *db_default_cl;
DBUG_ENTER("mysql_change_db");
DBUG_PRINT("enter",("name: '%s'", new_db_name->str));
if (new_db_name == NULL ||
- new_db_name->length == 0 ||
- new_db_name->str == NULL)
+ new_db_name->length == 0)
{
if (force_switch)
{
/*
- This can only happen when we restore the old db in THD after
- execution of a routine is complete. Change db to NULL.
+ This can happen only if we're switching the current database back
+ after loading stored program. The thing is that loading of stored
+ program can happen when there is no current database.
+
+ TODO: actually, new_db_name and new_db_name->str seem to be always
+ non-NULL. In case of stored program, new_db_name->str == "" and
+ new_db_name->length == 0.
*/
mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
@@ -1260,7 +1521,7 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
if (my_strcasecmp(system_charset_info, new_db_name->str,
INFORMATION_SCHEMA_NAME.str) == 0)
{
- /* Switch database to INFORMATION_SCHEMA. */
+ /* Switch the current database to INFORMATION_SCHEMA. */
mysql_change_db_impl(thd, &INFORMATION_SCHEMA_NAME, SELECT_ACL,
system_charset_info);
@@ -1275,7 +1536,8 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
TODO: fix check_db_name().
*/
- new_db_file_name.str= my_strdup(new_db_name->str, MYF(MY_WME));
+ new_db_file_name.str= my_strndup(new_db_name->str, new_db_name->length,
+ MYF(MY_WME));
new_db_file_name.length= new_db_name->length;
if (new_db_file_name.str == NULL)
@@ -1286,21 +1548,17 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
even if we are called from sp_head::execute().
It's next to impossible however to get this error when we are called
- from sp_head::execute(). But let's switch database to NULL in this case
- to be sure.
+ from sp_head::execute(). But let's switch the current database to NULL
+ in this case to be sure.
*/
- if (check_db_name(new_db_file_name.str))
+ if (check_db_name(&new_db_file_name))
{
my_error(ER_WRONG_DB_NAME, MYF(0), new_db_file_name.str);
my_free(new_db_file_name.str, MYF(0));
if (force_switch)
- {
- /* Change db to NULL. */
-
mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
- }
DBUG_RETURN(TRUE);
}
@@ -1319,14 +1577,14 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
if (!force_switch &&
!(db_access & DB_ACLS) &&
- (!grant_option || check_grant_db(thd, new_db_file_name.str)))
+ check_grant_db(thd, new_db_file_name.str))
{
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0),
sctx->priv_user,
sctx->priv_host,
new_db_file_name.str);
- mysql_log.write(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR),
- sctx->priv_user, sctx->priv_host, new_db_file_name.str);
+ general_log_print(thd, COM_INIT_DB, ER(ER_DBACCESS_DENIED_ERROR),
+ sctx->priv_user, sctx->priv_host, new_db_file_name.str);
my_free(new_db_file_name.str, MYF(0));
DBUG_RETURN(TRUE);
}
@@ -1336,6 +1594,8 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
{
if (force_switch)
{
+ /* Throw a warning and free new_db_file_name. */
+
push_warning_printf(thd, MYSQL_ERROR::WARN_LEVEL_NOTE,
ER_BAD_DB_ERROR, ER(ER_BAD_DB_ERROR),
new_db_file_name.str);
@@ -1346,12 +1606,19 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
mysql_change_db_impl(thd, NULL, 0, thd->variables.collation_server);
+ /* The operation succeed. */
+
DBUG_RETURN(FALSE);
}
else
{
+ /* Report an error and free new_db_file_name. */
+
my_error(ER_BAD_DB_ERROR, MYF(0), new_db_file_name.str);
my_free(new_db_file_name.str, MYF(0));
+
+ /* The operation failed. */
+
DBUG_RETURN(TRUE);
}
}
@@ -1361,21 +1628,363 @@ bool mysql_change_db(THD *thd, const LEX_STRING *new_db_name, bool force_switch)
attributes and will be freed in THD::~THD().
*/
+ db_default_cl= get_default_db_collation(thd, new_db_file_name.str);
+
+ mysql_change_db_impl(thd, &new_db_file_name, db_access, db_default_cl);
+
+ DBUG_RETURN(FALSE);
+}
+
+
+/**
+ Change the current database and its attributes if needed.
+
+ @param thd thread handle
+ @param new_db_name database name
+ @param[in, out] saved_db_name IN: "str" points to a buffer where to store
+ the old database name, "length" contains the
+ buffer size
+ OUT: if the current (default) database is
+ not NULL, its name is copied to the
+ buffer pointed at by "str"
+ and "length" is updated accordingly.
+ Otherwise "str" is set to NULL and
+ "length" is set to 0.
+ @param force_switch @see mysql_change_db()
+ @param[out] cur_db_changed out-flag to indicate whether the current
+ database has been changed (valid only if
+ the function suceeded)
+*/
+
+bool mysql_opt_change_db(THD *thd,
+ const LEX_STRING *new_db_name,
+ LEX_STRING *saved_db_name,
+ bool force_switch,
+ bool *cur_db_changed)
+{
+ *cur_db_changed= !cmp_db_names(thd->db, new_db_name->str);
+
+ if (!*cur_db_changed)
+ return FALSE;
+
+ backup_current_db_name(thd, saved_db_name);
+
+ return mysql_change_db(thd, new_db_name, force_switch);
+}
+
+
+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,(uchar*) db1, length1) ||
+ hash_search(&lock_db_cache,(uchar*) db2, length2)))
{
- HA_CREATE_INFO db_options;
+ wait_for_condition(thd, &LOCK_lock_db, &COND_refresh);
+ pthread_mutex_lock(&LOCK_lock_db);
+ }
- load_db_opt_by_name(thd, new_db_name->str, &db_options);
+ 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.
+ */
- mysql_change_db_impl(thd, &new_db_file_name, db_access,
- db_options.default_table_charset ?
- db_options.default_table_charset :
- thd->variables.collation_server);
+ while (!thd->killed && creating_table)
+ {
+ wait_for_condition(thd, &LOCK_lock_db, &COND_refresh);
+ pthread_mutex_lock(&LOCK_lock_db);
}
- DBUG_RETURN(FALSE);
+ 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, 0);
+ 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, "", "", 0);
+ 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= (char*) 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, 0);
+ my_delete(path, MYF(MY_WME));
+ length= build_table_filename(path, sizeof(path)-1, new_db->str, "", "", 0);
+ 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, 0);
+ build_table_filename(newname, sizeof(newname)-1,
+ new_db->str, "", file->name, 0);
+ 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, FALSE);
+
+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);
}
+
/*
Check if there is directory for the database name.
@@ -1393,12 +2002,8 @@ bool check_db_dir_existence(const char *db_name)
char db_dir_path[FN_REFLEN];
uint db_dir_path_len;
- strxnmov(db_dir_path, sizeof (db_dir_path) - 1, mysql_data_home, "/",
- db_name, NullS);
-
- db_dir_path_len= unpack_dirname(db_dir_path, db_dir_path);
-
- /* Remove trailing '/' or '\' if exists. */
+ db_dir_path_len= build_table_filename(db_dir_path, sizeof(db_dir_path),
+ db_name, "", "", 0);
if (db_dir_path_len && db_dir_path[db_dir_path_len - 1] == FN_LIBCHAR)
db_dir_path[db_dir_path_len - 1]= 0;