/*
Copyright ( c ) 2000 , 2014 , Oracle and / or its affiliates .
Copyright ( c ) 2009 , 2016 , MariaDB Corporation
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
/* create and drop of databases */
#include "mariadb.h" /* NO_EMBEDDED_ACCESS_CHECKS */
#include "sql_priv.h"
#include "unireg.h"
#include "sql_db.h"
#include "sql_cache.h" // query_cache_*
#include "lock.h" // lock_schema_name
#include "sql_table.h" // build_table_filename,
// filename_to_tablename
// validate_comment_length
#include "sql_rename.h" // mysql_rename_tables
#include "sql_acl.h" // SELECT_ACL, DB_ACLS,
// acl_get, check_grant_db
#include "log_event.h" // Query_log_event
#include "sql_base.h" // lock_table_names
#include "sql_handler.h" // mysql_ha_rm_tables
#include "sql_class.h"
#include <mysys_err.h>
#include "sp_head.h"
#include "sp.h"
#include "events.h"
#include "sql_handler.h"
#include "sql_statistics.h"
#include "ddl_log.h" // ddl_log functions
#include <my_dir.h>
#include <m_ctype.h>
#include "log.h"
#ifdef _WIN32
#include <direct.h>
#endif
#include "debug.h" // debug_crash_here
#define MAX_DROP_TABLE_Q_LEN 1024
const char *del_exts[]= {".BAK" , ".opt" , NullS};
static TYPELIB deletable_extensions= CREATE_TYPELIB_FOR(del_exts);
static bool find_db_tables_and_rm_known_files(THD *, MY_DIR *,
const Lex_ident_db_normalized &db,
const char *, TABLE_LIST **);
long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path);
my_bool rm_dir_w_symlink(const char *org_path, my_bool send_error);
static void mysql_change_db_impl(THD *, LEX_CSTRING *, const access_t&,
CHARSET_INFO *);
static bool mysql_rm_db_internal(THD *thd, const Lex_ident_db &db,
bool if_exists, bool silent);
/* Database options hash */
static HASH dboptions;
static my_bool dboptions_init= 0 ;
static mysql_rwlock_t LOCK_dboptions;
/* 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 */
LEX_STRING comment; /* Database comment */
} my_dbopt_t;
/**
Return TRUE if db1_name is equal to db2_name , FALSE otherwise .
The function allows to compare database names according to the MariaDB
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 to db2 in
table_alias_charset
This is the same rules as we use for filenames .
*/
static inline bool
cmp_db_names(const Lex_ident_db &db1_name, const Lex_ident_db &db2_name)
{
return (db1_name.length == 0 && db2_name.length == 0 ) ||
db1_name.streq_safe(db2_name);
}
#ifdef HAVE_PSI_INTERFACE
static PSI_rwlock_key key_rwlock_LOCK_dboptions;
static PSI_rwlock_key key_rwlock_LOCK_dbnames;
static PSI_rwlock_key key_rwlock_LOCK_rmdir;
static PSI_rwlock_info all_database_names_rwlocks[]= {
{&key_rwlock_LOCK_dboptions, "LOCK_dboptions" , PSI_FLAG_GLOBAL},
{&key_rwlock_LOCK_dbnames, "LOCK_dbnames" , PSI_FLAG_GLOBAL},
{&key_rwlock_LOCK_rmdir, "LOCK_rmdir" ,PSI_FLAG_GLOBAL},
};
static void init_database_names_psi_keys(void )
{
const char *category= "sql" ;
int count;
if (PSI_server == NULL)
return ;
count= array_elements(all_database_names_rwlocks);
PSI_server->register_rwlock(category, all_database_names_rwlocks, count);
}
#endif
static mysql_rwlock_t rmdir_lock;
/*
Cache of C strings for existing database names .
The only use of it is to avoid repeated expensive
my_access ( ) calls .
Provided operations are lookup , insert ( after successfull my_access ( ) )
and clear ( this is called whenever rmdir is called ) .
*/
struct dbname_cache_t
{
private :
Hash_set<LEX_STRING> m_set;
mysql_rwlock_t m_lock;
static const uchar *get_key(const void *ls_, size_t *sz, my_bool)
{
const LEX_STRING *ls= static_cast <const LEX_STRING*>(ls_);
*sz= ls->length;
return reinterpret_cast <const uchar*>(ls->str);
}
public :
dbname_cache_t()
: m_set(key_memory_dbnames_cache, table_alias_charset, 10 , 0 ,
sizeof (char *), get_key, my_free, 0 )
{
mysql_rwlock_init(key_rwlock_LOCK_dbnames, &m_lock);
}
bool contains(const char *s)
{
auto sz= strlen(s);
mysql_rwlock_rdlock(&m_lock);
bool ret= m_set.find(s, sz) != 0 ;
mysql_rwlock_unlock(&m_lock);
return ret;
}
void insert(const char *s)
{
auto len= strlen(s);
auto ls= (LEX_STRING *) my_malloc(key_memory_dbnames_cache,
sizeof (LEX_STRING) + strlen(s) + 1 , 0 );
if (!ls)
return ;
ls->length= len;
ls->str= (char *) (ls + 1 );
memcpy(ls->str, s, len + 1 );
mysql_rwlock_wrlock(&m_lock);
bool found= m_set.find(s, len) != 0 ;
if (!found)
m_set.insert(ls);
mysql_rwlock_unlock(&m_lock);
if (found)
my_free(ls);
}
void clear()
{
mysql_rwlock_wrlock(&m_lock);
m_set.clear();
mysql_rwlock_unlock(&m_lock);
}
~dbname_cache_t()
{
mysql_rwlock_destroy(&m_lock);
}
};
static dbname_cache_t* dbname_cache;
static void dbname_cache_init()
{
static MY_ALIGNED(16 ) char buf[sizeof (dbname_cache_t)];
DBUG_ASSERT(!dbname_cache);
dbname_cache= new (buf) dbname_cache_t;
mysql_rwlock_init(key_rwlock_LOCK_rmdir, &rmdir_lock);
}
static void dbname_cache_destroy()
{
if (!dbname_cache)
return ;
dbname_cache->~dbname_cache_t();
dbname_cache= 0 ;
mysql_rwlock_destroy(&rmdir_lock);
}
static int my_rmdir(const char *dir)
{
auto ret= rmdir(dir);
if (ret)
return ret;
mysql_rwlock_wrlock(&rmdir_lock);
dbname_cache->clear();
mysql_rwlock_unlock(&rmdir_lock);
return 0 ;
}
/*
Function we use in the creation of our hash to get key .
*/
extern "C" const uchar *dboptions_get_key(const void *opt, size_t *length,
my_bool);
const uchar *dboptions_get_key(const void *opt_, size_t *length, my_bool)
{
auto opt= static_cast <const my_dbopt_t *>(opt_);
*length= opt->name_length;
return reinterpret_cast <const uchar *>(opt->name);
}
/*
Helper function to write a query to binlog used by mysql_rm_db ( )
*/
static inline int write_to_binlog(THD *thd, const char *query, size_t q_len,
const char *db, size_t db_len)
{
Query_log_event qinfo(thd, query, q_len, FALSE , TRUE , FALSE , 0 );
qinfo.db= db;
qinfo.db_len= (uint32)db_len;
return mysql_bin_log.write(&qinfo);
}
/*
Function to free dboptions hash element
*/
extern "C" void free_dbopt(void *dbopt);
void free_dbopt(void *dbopt)
{
my_free(dbopt);
}
/**
Initialize database option cache .
@ note Must be called before any other database function is called .
@ retval 0 ok
@ retval 1 Fatal error
*/
bool my_dboptions_cache_init(void )
{
#ifdef HAVE_PSI_INTERFACE
init_database_names_psi_keys();
#endif
bool error= 0 ;
mysql_rwlock_init(key_rwlock_LOCK_dboptions, &LOCK_dboptions);
if (!dboptions_init)
{
dboptions_init= 1 ;
error= my_hash_init(key_memory_dboptions_hash, &dboptions,
table_alias_charset, 32 , 0 , 0 , dboptions_get_key,
free_dbopt, 0 );
}
dbname_cache_init();
return error;
}
/**
Free database option hash and locked databases hash .
*/
void my_dboptions_cache_free(void )
{
if (dboptions_init)
{
dboptions_init= 0 ;
my_hash_free(&dboptions);
dbname_cache_destroy();
mysql_rwlock_destroy(&LOCK_dboptions);
}
}
/**
Cleanup cached options .
*/
void my_dbopt_cleanup(void )
{
mysql_rwlock_wrlock(&LOCK_dboptions);
my_hash_free(&dboptions);
my_hash_init(key_memory_dboptions_hash, &dboptions, table_alias_charset, 32 ,
0 , 0 , dboptions_get_key, free_dbopt, 0 );
mysql_rwlock_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(THD *thd, const char *dbname,
Schema_specification_st *create)
{
my_dbopt_t *opt;
uint length;
my_bool error= 1 ;
length= (uint) strlen(dbname);
mysql_rwlock_rdlock(&LOCK_dboptions);
if ((opt= (my_dbopt_t*) my_hash_search(&dboptions, (uchar*) dbname, length)))
{
create->default_table_charset= opt->charset;
if (opt->comment.length)
{
create->schema_comment= thd->make_clex_string(opt->comment.str,
opt->comment.length);
}
error= 0 ;
}
mysql_rwlock_unlock(&LOCK_dboptions);
return error;
}
/*
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, Schema_specification_st *create)
{
my_dbopt_t *opt;
uint length;
my_bool error= 0 ;
DBUG_ENTER("put_dbopt" );
length= (uint) strlen(dbname);
mysql_rwlock_wrlock(&LOCK_dboptions);
if (!(opt= (my_dbopt_t*) my_hash_search(&dboptions, (uchar*) dbname,
length)))
{
/* Options are not in the hash, insert them */
char *tmp_name;
char *tmp_comment= NULL;
if (!my_multi_malloc(key_memory_dboptions_hash, MYF(MY_WME | MY_ZEROFILL),
&opt, (uint) sizeof (*opt), &tmp_name, (uint) length+1 ,
&tmp_comment, (uint) DATABASE_COMMENT_MAXLEN+1 ,
NullS))
{
error= 1 ;
goto end;
}
opt->name= tmp_name;
strmov(opt->name, dbname);
opt->name_length= length;
opt->comment.str= tmp_comment;
if (unlikely((error= my_hash_insert(&dboptions, (uchar*) opt))))
{
my_free(opt);
goto end;
}
}
/* Update / write options in hash */
opt->charset= create->default_table_charset;
if (create->schema_comment)
{
strmov(opt->comment.str, create->schema_comment->str);
opt->comment.length= create->schema_comment->length;
}
end:
mysql_rwlock_unlock(&LOCK_dboptions);
DBUG_RETURN(error);
}
/*
Deletes database options from the hash .
*/
static void del_dbopt(const char *path)
{
my_dbopt_t *opt;
mysql_rwlock_wrlock(&LOCK_dboptions);
if ((opt= (my_dbopt_t *)my_hash_search(&dboptions, (const uchar*) path,
strlen(path))))
my_hash_delete(&dboptions, (uchar*) opt);
mysql_rwlock_unlock(&LOCK_dboptions);
}
/*
Create database options file :
DESCRIPTION
Currently database default charset , default collation
and comment are stored there .
RETURN VALUES
0 ok
1 Could not create file or write to it . Error sent through my_error ( )
*/
static bool write_db_opt(THD *thd, const char *path,
Schema_specification_st *create)
{
File file;
char buf[256 +DATABASE_COMMENT_MAXLEN];
bool error=1 ;
if (create->schema_comment)
{
if (validate_comment_length(thd, create->schema_comment,
DATABASE_COMMENT_MAXLEN,
ER_TOO_LONG_DATABASE_COMMENT,
thd->lex->name.str))
return error;
}
if (thd->lex->sql_command == SQLCOM_ALTER_DB &&
(!create->schema_comment || !create->default_table_charset))
{
/* Use existing values of schema_comment and charset for
ALTER DATABASE queries */
Schema_specification_st tmp;
tmp.init();
load_db_opt(thd, path, &tmp);
if (!create->schema_comment)
create->schema_comment= tmp.schema_comment;
if (!create->default_table_charset)
create->default_table_charset= tmp.default_table_charset;
}
if (!create->default_table_charset)
create->default_table_charset= thd->variables.collation_server;
if (put_dbopt(path, create))
return 1 ;
if ((file= mysql_file_create(key_file_dbopt, path, CREATE_MODE,
O_RDWR | O_TRUNC, MYF(MY_WME))) >= 0 )
{
ulong length;
length= (ulong) (strxnmov(buf, sizeof (buf)-1 , "default-character-set=" ,
create->default_table_charset->cs_name.str,
"\ndefault-collation=" ,
create->default_table_charset->coll_name.str,
"\n" , NullS) - buf);
if (create->schema_comment)
length= (ulong) (strxnmov(buf+length, sizeof (buf)-1 -length,
"comment=" , create->schema_comment->str,
"\n" , NullS) - buf);
/* Error is written by mysql_file_write */
if (!mysql_file_write(file, (uchar*) buf, length, MYF(MY_NABP+MY_WME)))
error=0 ;
mysql_file_close(file, MYF(0 ));
}
return error;
}
/*
Load database options file
load_db_opt ( )
path Path for option file
create Where to store the read options
DESCRIPTION
create - > default_table_charset is guaranteed to be alway set
Required by some callers
RETURN VALUES
0 File found
- 1 No database file ( file was not found or ' empty ' file was cached )
1 Could not open it
*/
int load_db_opt(THD *thd, const char *path, Schema_specification_st *create)
{
File file;
char buf[256 +DATABASE_COMMENT_MAXLEN];
DBUG_ENTER("load_db_opt" );
int error= 0 ;
size_t nbytes;
myf utf8_flag= thd->get_utf8_flag();
create->init();
/* Check if options for this database are already in the hash */
if (!*path || !get_dbopt(thd, path, create))
{
if (!create->default_table_charset)
error= -1 ; // db.opt did not exists
goto err1;
}
/* Otherwise, load options from the .opt file */
if ((file= mysql_file_open(key_file_dbopt,
path, O_RDONLY | O_SHARE, MYF(0 ))) < 0 )
{
/*
Create an empty entry , to avoid doing an extra file open for every create
table .
*/
put_dbopt(path, create);
error= -1 ;
goto err1;
}
IO_CACHE cache;
if (init_io_cache(&cache, file, IO_SIZE, READ_CACHE, 0 , 0 , MYF(0 )))
{
error= 1 ;
goto err2; // Not cached
}
while ((int ) (nbytes= my_b_gets(&cache, (char *) buf, sizeof (buf))) > 0 )
{
char *pos= buf+nbytes-1 ;
/* Remove end space and control characters */
while (pos > buf && !my_isgraph(&my_charset_latin1, pos[-1 ]))
pos--;
*pos=0 ;
if ((pos= strchr(buf, '=' )))
{
if (!strncmp(buf,"default-character-set" , (pos-buf)))
{
/*
Try character set name , and if it fails
try collation name , probably it ' s an old
4 . 1 . 0 db . opt file , which didn ' t have
separate default - character - set and
default - collation commands .
*/
if (!(create->default_table_charset=
get_charset_by_csname(pos+1 , MY_CS_PRIMARY, MYF(utf8_flag))) &&
!(create->default_table_charset=
get_charset_by_name(pos+1 , MYF(utf8_flag))))
{
sql_print_error("Error while loading database options: '%s':" ,path);
sql_print_error(ER_DEFAULT(ER_UNKNOWN_CHARACTER_SET),pos+1 );
create->default_table_charset= default_charset_info;
}
}
else if (!strncmp(buf,"default-collation" , (pos-buf)))
{
if (!(create->default_table_charset= get_charset_by_name(pos+1 , MYF(utf8_flag))))
{
sql_print_error("Error while loading database options: '%s':" ,path);
sql_print_error(ER_DEFAULT(ER_UNKNOWN_COLLATION),pos+1 );
create->default_table_charset= default_charset_info;
}
}
else if (!strncmp(buf, "comment" , (pos-buf)))
create->schema_comment= thd->make_clex_string(pos+1 , strlen(pos+1 ));
}
}
/*
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);
end_io_cache(&cache);
err2:
mysql_file_close(file, MYF(0 ));
err1:
if (!create->default_table_charset) // In case of error
create->default_table_charset= thd->variables.collation_server;
DBUG_RETURN(error);
}
/*
Retrieve database options by name . Load database options file or fetch from
cache .
SYNOPSIS
load_db_opt_by_name ( )
db_name Database name
db_create_info Where to store the database options
DESCRIPTION
load_db_opt_by_name ( ) is a shortcut for load_db_opt ( ) .
NOTE
Although load_db_opt_by_name ( ) ( and load_db_opt ( ) ) returns status of
the operation , it is useless usually and should be ignored . The problem
is that there are 1 ) system databases ( " mysql " ) and 2 ) virtual
databases ( " information_schema " ) , which do not contain options file .
So , load_db_opt [ _ by_name ] ( ) returns FALSE for these databases , but this
is not an error .
load_db_opt [ _ by_name ] ( ) clears db_create_info structure in any case , so
even on failure it contains valid data . So , common use case is just
call load_db_opt [ _ by_name ] ( ) without checking return value and use
db_create_info right after that .
RETURN VALUES ( read NOTE ! )
0 File found
- 1 No database file ( file was not found or ' empty ' file was cached )
1 Could not open it
*/
int load_db_opt_by_name(THD *thd, const char *db_name,
Schema_specification_st *db_create_info)
{
char db_opt_path[FN_REFLEN + 1 ];
/*
Pass an empty file name , and the database options file name as extension
to avoid table name to file name encoding .
*/
if (*db_name)
build_table_filename(db_opt_path, sizeof (db_opt_path) - 1 ,
db_name, "" , MY_DB_OPT_FILE, 0 );
else
db_opt_path[0 ]= 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)
{
Schema_specification_st db_info;
if (thd->db.str != NULL && strcmp(db_name, thd->db.str) == 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
SYNOPSIS
mysql_create_db_internal ( )
thd Thread handler
db Name of database to create
Function assumes that this is already validated .
options DDL options , e . g . IF NOT EXISTS
create_info Database create options ( like character set )
silent Used by replication when internally creating a database .
In this case the entry should not be logged .
SIDE - EFFECTS
1 . Report back to client that command succeeded ( my_ok )
2 . Report errors to client
3 . Log event to binary log
( The ' silent ' flags turns off 1 and 3 . )
RETURN VALUES
FALSE ok
TRUE Error
*/
static int
mysql_create_db_internal(THD *thd, const Lex_ident_db &db,
const DDL_options_st &options,
Schema_specification_st *create_info,
bool silent)
{
char path[FN_REFLEN+16 ];
MY_STAT stat_info;
uint path_len;
DBUG_ENTER("mysql_create_db" );
/* do not create 'information_schema' db */
if (is_infoschema_db(&db))
{
my_error(ER_DB_CREATE_EXISTS, MYF(0 ), db.str);
DBUG_RETURN(-1 );
}
if (error_if_mysql50_prefix(db.str, ER_WRONG_DB_NAME))
DBUG_RETURN(-1 );
const DBNameBuffer dbnorm_buffer(db, lower_case_table_names);
Lex_ident_db_normalized dbnorm(dbnorm_buffer.to_lex_cstring());
if (lock_schema_name(thd, dbnorm))
DBUG_RETURN(-1 );
/* Check directory */
path_len= build_table_filename(path, sizeof (path) - 1 , db.str, "" , "" , 0 );
path[path_len-1 ]= 0 ; // Remove last '/' from path
long affected_rows= 1 ;
if (!mysql_file_stat(key_file_misc, path, &stat_info, MYF(0 )))
{
// The database directory does not exist, or my_file_stat() failed
if (my_errno != ENOENT)
{
my_error(EE_STAT, MYF(0 ), path, my_errno);
DBUG_RETURN(1 );
}
}
else if (options.or_replace())
{
if (mysql_rm_db_internal(thd, db, 0 , true )) // Removing the old database
DBUG_RETURN(1 );
/*
Reset the diagnostics m_status .
It might be set ot DA_OK in mysql_rm_db .
*/
thd->get_stmt_da()->reset_diagnostics_area();
affected_rows= 2 ;
}
else if (options.if_not_exists())
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_DB_CREATE_EXISTS, ER_THD(thd, ER_DB_CREATE_EXISTS),
db.str);
affected_rows= 0 ;
goto not_silent;
}
else
{
my_error(ER_DB_CREATE_EXISTS, MYF(0 ), db.str);
DBUG_RETURN(-1 );
}
if (my_mkdir(path, 0777 , MYF(0 )) < 0 )
{
my_error(ER_CANT_CREATE_DB, MYF(0 ), db.str, my_errno);
DBUG_RETURN(-1 );
}
path[path_len-1 ]= FN_LIBCHAR;
strmake(path+path_len, MY_DB_OPT_FILE, sizeof (path)-path_len-1 );
if (write_db_opt(thd, path, create_info))
{
/*
Could not create options file .
Restore things to beginning .
*/
path[path_len]= 0 ;
if (my_rmdir(path) >= 0 )
DBUG_RETURN(-1 );
/*
We come here when we managed to create the database , but not the option
file . In this case it ' s best to just continue as if nothing has
happened . ( This is a very unlikely scenario )
*/
thd->clear_error();
}
/* Log command to ddl log */
backup_log_info ddl_log;
bzero(&ddl_log, sizeof (ddl_log));
ddl_log.query= { C_STRING_WITH_LEN("CREATE" ) };
ddl_log.org_storage_engine_name= { C_STRING_WITH_LEN("DATABASE" ) };
ddl_log.org_database= db;
backup_log_ddl(&ddl_log);
not_silent:
if (!silent)
{
char *query;
uint query_length;
query= thd->query();
query_length= thd->query_length();
DBUG_ASSERT(query);
if (mysql_bin_log.is_open())
{
int errcode= query_error_code(thd, TRUE );
Query_log_event qinfo(thd, query, query_length, FALSE , TRUE ,
/* suppress_use */ TRUE, errcode);
/*
Write should use the database being created as the " current
database " and not the threads current database , which is the
default . If we do not change the " current database " to the
database being created , the CREATE statement will not be
replicated when using - - binlog - do - db to select databases to be
replicated .
An example ( - - binlog - do - db = sisyfos ) :
CREATE DATABASE bob ; # Not replicated
USE bob ; # ' bob ' is the current database
CREATE DATABASE sisyfos ; # Not replicated since ' bob ' is
# current database .
USE sisyfos ; # Will give error on slave since
# database does not exist .
*/
qinfo.db = db.str;
qinfo.db_len = (uint32)db.length;
/*
These DDL methods and logging are protected with the exclusive
metadata lock on the schema
*/
if (mysql_bin_log.write(&qinfo))
DBUG_RETURN(-1 );
}
my_ok(thd, affected_rows);
}
DBUG_RETURN(0 );
}
/* db-name is already validated when we come here */
static bool
mysql_alter_db_internal(THD *thd, const Lex_ident_db &db,
Schema_specification_st *create_info)
{
char path[FN_REFLEN+16 ];
long result=1 ;
int error= 0 ;
DBUG_ENTER("mysql_alter_db" );
const DBNameBuffer dbnorm_buffer(db, lower_case_table_names);
Lex_ident_db_normalized dbnorm(dbnorm_buffer.to_lex_cstring());
if (lock_schema_name(thd, dbnorm))
DBUG_RETURN(TRUE );
/*
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) - 1 , db.str, "" , MY_DB_OPT_FILE, 0 );
if (unlikely((error=write_db_opt(thd, path, create_info))))
goto exit ;
/* Change options if current database is being altered. */
if (thd->db.str && !cmp(&thd->db, &db))
{
thd->db_charset= create_info->default_table_charset ?
create_info->default_table_charset :
thd->variables.collation_server;
thd->variables.collation_database= thd->db_charset;
}
/* Log command to ddl log */
backup_log_info ddl_log;
bzero(&ddl_log, sizeof (ddl_log));
ddl_log.query= { C_STRING_WITH_LEN("ALTER" ) };
ddl_log.org_storage_engine_name= { C_STRING_WITH_LEN("DATABASE" ) };
ddl_log.org_database= db;
backup_log_ddl(&ddl_log);
if (mysql_bin_log.is_open())
{
int errcode= query_error_code(thd, TRUE );
Query_log_event qinfo(thd, thd->query(), thd->query_length(), FALSE , TRUE ,
/* suppress_use */ TRUE, errcode);
/*
Write should use the database being created as the " current
database " and not the threads current database , which is the
default .
*/
qinfo.db= db.str;
qinfo.db_len= (uint) db.length;
/*
These DDL methods and logging are protected with the exclusive
metadata lock on the schema .
*/
if (unlikely((error= mysql_bin_log.write(&qinfo))))
goto exit ;
}
my_ok(thd, result);
exit :
DBUG_RETURN(error);
}
int mysql_create_db(THD *thd, const Lex_ident_db &db, DDL_options_st options,
const Schema_specification_st *create_info)
{
DBUG_ASSERT(create_info->default_table_charset);
/*
As mysql_create_db_internal ( ) may modify Db_create_info structure passed
to it , we need to use a copy to make execution prepared statement - safe .
*/
Schema_specification_st tmp(*create_info);
if (thd->slave_thread &&
slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT)
options.add(DDL_options::OPT_IF_NOT_EXISTS);
return mysql_create_db_internal(thd, db, options, &tmp, false );
}
bool mysql_alter_db(THD *thd, const Lex_ident_db &db,
const Schema_specification_st *create_info)
{
DBUG_ASSERT(create_info->default_table_charset);
/*
As mysql_alter_db_internal ( ) may modify Db_create_info structure passed
to it , we need to use a copy to make execution prepared statement - safe .
*/
Schema_specification_st tmp(*create_info);
return mysql_alter_db_internal(thd, db, &tmp);
}
/**
Drop database objects
@ param thd THD object
@ param path Path to database ( for ha_drop_database )
@ param db Normalized database name
@ param rm_mysql_schema If the schema is ' mysql ' , in which case we don ' t
log the query to binary log or delete related
routines or events .
*/
void drop_database_objects(THD *thd, const LEX_CSTRING *path,
const LEX_CSTRING *db,
bool rm_mysql_schema)
{
debug_crash_here("ddl_log_drop_before_ha_drop_database" );
ha_drop_database(path->str);
/*
We temporarily disable the binary log while dropping the objects
in the database . Since the DROP DATABASE statement is always
replicated as a statement , execution of it will drop all objects
in the database on the slave as well , so there is no need to
replicate the removal of the individual objects in the database
as well .
This is more of a safety precaution , since normally no objects
should be dropped while the database is being cleaned , but in
the event that a change in the code to remove other objects is
made , these drops should still not be logged .
*/
debug_crash_here("ddl_log_drop_before_drop_db_routines" );
query_cache_invalidate1(thd, *db);
if (!rm_mysql_schema)
{
tmp_disable_binlog(thd);
(void ) sp_drop_db_routines(thd, *db); /* @todo Do not ignore errors */
#ifdef HAVE_EVENT_SCHEDULER
Events::drop_schema_events(thd, *db);
#endif
reenable_binlog(thd);
}
debug_crash_here("ddl_log_drop_after_drop_db_routines" );
}
/**
Drop all tables , routines and events in a database and the database itself .
@ param thd Thread handle
@ param db Database name in the case given by user
It ' s already validated and set to lower case
( if needed ) when we come here
@ param if_exists Don ' t give error if database doesn ' t exists
@ param silent Don ' t write the statement to the binary log and don ' t
send ok packet to the client
@ retval false OK ( Database dropped )
@ retval true Error
*/
static bool
mysql_rm_db_internal(THD *thd, const Lex_ident_db &db, bool if_exists,
bool silent)
{
ulong deleted_tables= 0 ;
bool error= true , rm_mysql_schema;
char path[FN_REFLEN + 16 ];
MY_DIR *dirp;
uint path_length;
TABLE_LIST *tables= NULL;
TABLE_LIST *table;
DDL_LOG_STATE ddl_log_state;
Drop_table_error_handler err_handler;
DBUG_ENTER("mysql_rm_db" );
const DBNameBuffer dbnorm_buffer(db, lower_case_table_names);
Lex_ident_db_normalized dbnorm(dbnorm_buffer.to_lex_cstring());
bzero(&ddl_log_state, sizeof (ddl_log_state));
if (lock_schema_name(thd, dbnorm))
DBUG_RETURN(true );
path_length= build_table_filename(path, sizeof (path) - 1 , db.str, "" , "" , 0 );
/* See if the directory exists */
if (!(dirp= my_dir(path,MYF(MY_DONT_SORT))))
{
if (!if_exists)
{
my_error(ER_DB_DROP_EXISTS, MYF(0 ), db.str);
DBUG_RETURN(true );
}
else
{
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_DB_DROP_EXISTS, ER_THD(thd, ER_DB_DROP_EXISTS),
db.str);
error= false ;
goto update_binlog;
}
}
if (find_db_tables_and_rm_known_files(thd, dirp, dbnorm, path, &tables))
goto exit ;
/*
Disable drop of enabled log tables , must be done before name locking .
This check is only needed if we are dropping the " mysql " database .
*/
if ((rm_mysql_schema= MYSQL_SCHEMA_NAME.streq(db)))
{
for (table= tables; table; table= table->next_local)
if (check_if_log_table(table, TRUE , "DROP" ))
goto exit ;
}
/* Lock all tables and stored routines about to be dropped. */
if (lock_table_names(thd, tables, NULL, thd->variables.lock_wait_timeout,
0 ) ||
lock_db_routines(thd, dbnorm))
goto exit ;
if (!rm_mysql_schema)
{
for (table= tables; table; table= table->next_local)
{
if (table->open_type == OT_BASE_ONLY ||
!thd->find_temporary_table(table))
(void ) delete_statistics_for_table(thd, &table->db, &table->table_name);
}
}
/*
Close active HANDLER ' s for tables in the database .
Note that mysql_ha_rm_tables ( ) requires a non - null TABLE_LIST .
*/
if (tables)
mysql_ha_rm_tables(thd, tables);
for (table= tables; table; table= table->next_local)
deleted_tables++;
thd->push_internal_handler(&err_handler);
if (!thd->killed &&
!(tables &&
mysql_rm_table_no_locks(thd, tables, &dbnorm, &ddl_log_state, true , false ,
true , false , true , false )))
{
debug_crash_here("ddl_log_drop_after_drop_tables" );
LEX_CSTRING cpath{ path, path_length};
ddl_log_drop_db(&ddl_log_state, &dbnorm, &cpath);
drop_database_objects(thd, &cpath, &dbnorm, rm_mysql_schema);
/*
Now remove the db . opt file .
The ' find_db_tables_and_rm_known_files ' doesn ' t remove this file
if there exists a table with the name ' db ' , so let ' s just do it
separately . We know this file exists and needs to be deleted anyway .
*/
debug_crash_here("ddl_log_drop_before_drop_option_file" );
strmov(path+path_length, MY_DB_OPT_FILE); // Append db option file name
if (mysql_file_delete_with_symlink(key_file_misc, path, "" , MYF(0 )) &&
my_errno != ENOENT)
{
thd->pop_internal_handler();
my_error(EE_DELETE, MYF(0 ), path, my_errno);
error= true ;
ddl_log_complete(&ddl_log_state);
goto end;
}
del_dbopt(path); // Remove dboption hash entry
path[path_length]= '\0' ; // Remove file name
/*
If the directory is a symbolic link , remove the link first , then
remove the directory the symbolic link pointed at
*/
debug_crash_here("ddl_log_drop_before_drop_dir" );
error= rm_dir_w_symlink(path, true );
debug_crash_here("ddl_log_drop_after_drop_dir" );
}
thd->pop_internal_handler();
update_binlog:
if (likely(!error))
{
/* Log command to ddl log */
backup_log_info ddl_log;
bzero(&ddl_log, sizeof (ddl_log));
ddl_log.query= { C_STRING_WITH_LEN("DROP" ) };
ddl_log.org_storage_engine_name= { C_STRING_WITH_LEN("DATABASE" ) };
ddl_log.org_database= db;
backup_log_ddl(&ddl_log);
}
if (!silent && likely(!error))
{
const char *query;
ulong query_length;
query= thd->query();
query_length= thd->query_length();
DBUG_ASSERT(query);
if (mysql_bin_log.is_open())
{
int errcode= query_error_code(thd, TRUE );
int res;
Query_log_event qinfo(thd, query, query_length, FALSE , TRUE ,
/* suppress_use */ TRUE, errcode);
/*
Write should use the database being created as the " current
database " and not the threads current database , which is the
default .
*/
qinfo.db = db.str;
qinfo.db_len = (uint32) db.length;
/*
These DDL methods and logging are protected with the exclusive
metadata lock on the schema .
*/
debug_crash_here("ddl_log_drop_before_binlog" );
thd->binlog_xid= thd->query_id;
ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
res= mysql_bin_log.write(&qinfo);
thd->binlog_xid= 0 ;
debug_crash_here("ddl_log_drop_after_binlog" );
if (res)
{
error= true ;
goto exit ;
}
}
thd->clear_error();
thd->server_status|= SERVER_STATUS_DB_DROPPED;
my_ok(thd, deleted_tables);
}
else if (mysql_bin_log.is_open() && !silent)
{
char *query, *query_pos, *query_end, *query_data_start;
TABLE_LIST *tbl;
if (!(query= thd->alloc(MAX_DROP_TABLE_Q_LEN)))
goto exit ; /* not much else we can do */
query_pos= query_data_start= strmov(query,"DROP TABLE IF EXISTS " );
query_end= query + MAX_DROP_TABLE_Q_LEN;
for (tbl= tables; tbl; tbl= tbl->next_local)
{
size_t tbl_name_len;
char quoted_name[FN_REFLEN+3 ];
// Only write drop table to the binlog for tables that no longer exist.
if (ha_table_exists(thd, &tbl->db, &tbl->table_name))
continue ;
tbl_name_len= my_snprintf(quoted_name, sizeof (quoted_name), "%sQ" ,
tbl->table_name.str);
tbl_name_len++; /* +1 for the comma */
if (query_pos + tbl_name_len + 1 >= query_end)
{
/*
These DDL methods and logging are protected with the exclusive
metadata lock on the schema .
*/
if (write_to_binlog(thd, query, (uint)(query_pos -1 - query), db.str, db.length))
{
error= true ;
goto exit ;
}
query_pos= query_data_start;
}
query_pos= strmov(query_pos, quoted_name);
*query_pos++ = ',' ;
}
if (query_pos != query_data_start) // If database was not empty
{
int res;
/*
These DDL methods and logging are protected with the exclusive
metadata lock on the schema .
*/
debug_crash_here("ddl_log_drop_before_binlog" );
thd->binlog_xid= thd->query_id;
ddl_log_update_xid(&ddl_log_state, thd->binlog_xid);
res= write_to_binlog(thd, query, (uint)(query_pos -1 - query), db.str,
db.length);
thd->binlog_xid= 0 ;
debug_crash_here("ddl_log_drop_after_binlog" );
if (res)
{
error= true ;
goto exit ;
}
}
}
exit :
ddl_log_complete(&ddl_log_state);
/*
If this database was the client ' s selected database , we silently
change the client ' s selected database to nothing ( to have an empty
SELECT DATABASE ( ) in the future ) . For this we free ( ) thd - > db and set
it to 0 .
*/
if (unlikely(thd->db.str &&
cmp_db_names(Lex_ident_db(thd->db), db) && !error))
{
mysql_change_db_impl(thd, NULL, access_t(NO_ACL), thd->variables.collation_server);
thd->session_tracker.current_schema.mark_as_changed(thd);
}
end:
my_dirend(dirp);
DBUG_RETURN(error);
}
bool mysql_rm_db(THD *thd, const Lex_ident_db &db, bool if_exists)
{
if (thd->slave_thread &&
slave_ddl_exec_mode_options == SLAVE_EXEC_MODE_IDEMPOTENT)
if_exists= true ;
return mysql_rm_db_internal(thd, db, if_exists, false );
}
static bool find_db_tables_and_rm_known_files(THD *thd, MY_DIR *dirp,
const Lex_ident_db_normalized &db,
const char *path,
TABLE_LIST **tables)
{
char filePath[FN_REFLEN];
TABLE_LIST *tot_list=0 , **tot_list_next_local, **tot_list_next_global;
DBUG_ENTER("find_db_tables_and_rm_known_files" );
DBUG_PRINT("enter" ,("path: %s" , path));
/* first, get the list of tables */
Dynamic_array<LEX_CSTRING*> files(PSI_INSTRUMENT_MEM, dirp->number_of_files);
Discovered_table_list tl(thd, &files);
if (ha_discover_table_names(thd, &db, dirp, &tl, true ))
DBUG_RETURN(1 );
/* Now put the tables in the list */
tot_list_next_local= tot_list_next_global= &tot_list;
for (size_t idx=0 ; idx < files.elements(); idx++)
{
const LEX_CSTRING *table= files.at(idx);
/* Drop the table nicely */
TABLE_LIST *table_list= thd->calloc<TABLE_LIST>(1 );
if (!table_list)
DBUG_RETURN(true );
table_list->db= db;
/*
On the case - insensitive file systems table is opened
with the lowercased file name . So we should lowercase
as well to look up the cache properly .
*/
table_list->table_name= lower_case_file_system ?
Lex_ident_table(thd->make_ident_casedn(*table)) :
Lex_ident_table(*table);
table_list->open_type= OT_BASE_ONLY;
table_list->alias= table_list->table_name; // If lower_case_table_names=2
MDL_REQUEST_INIT(&table_list->mdl_request, MDL_key::TABLE,
table_list->db.str, table_list->table_name.str,
MDL_EXCLUSIVE, MDL_TRANSACTION);
/* Link into list */
(*tot_list_next_local)= table_list;
(*tot_list_next_global)= table_list;
tot_list_next_local= &table_list->next_local;
tot_list_next_global= &table_list->next_global;
}
*tables= tot_list;
/* and at last delete all non-table files */
for (size_t idx=0 ; idx < dirp->number_of_files && !thd->killed; idx++)
{
FILEINFO *file=dirp->dir_entry+idx;
char *extension;
DBUG_PRINT("info" ,("Examining: %s" , file->name));
if (file->name[0 ] == 'a' && file->name[1 ] == 'r' &&
file->name[2 ] == 'c' && file->name[3 ] == '\0' )
{
/* .frm archive:
Those archives are obsolete , but following code should
exist to remove existent " arc " directories .
*/
char newpath[FN_REFLEN];
MY_DIR *new_dirp;
strxmov(newpath, path, "/" , "arc" , NullS);
(void ) unpack_filename(newpath, newpath);
if ((new_dirp = my_dir(newpath, MYF(MY_DONT_SORT))))
{
DBUG_PRINT("my" ,("Archive subdir found: %s" , newpath));
if ((mysql_rm_arc_files(thd, new_dirp, newpath)) < 0 )
DBUG_RETURN(true );
}
continue ;
}
if (!(extension= strrchr(file->name, '.' )))
extension= strend(file->name);
if (find_type(extension, &deletable_extensions, FIND_TYPE_NO_PREFIX) > 0 )
{
strxmov(filePath, path, "/" , file->name, NullS);
/*
We ignore ENOENT error in order to skip files that was deleted
by concurrently running statement like REPAIR TABLE . . .
*/
if (mysql_file_delete_with_symlink(key_file_misc, filePath, "" , MYF(0 )) &&
my_errno != ENOENT)
{
my_error(EE_DELETE, MYF(0 ), filePath, my_errno);
DBUG_RETURN(true );
}
}
}
DBUG_RETURN(false );
}
/*
Remove directory with symlink
SYNOPSIS
rm_dir_w_symlink ( )
org_path path of directory
send_error send errors
RETURN
0 OK
1 ERROR
*/
my_bool rm_dir_w_symlink(const char *org_path, my_bool send_error)
{
char tmp_path[FN_REFLEN], *pos;
char *path= tmp_path;
DBUG_ENTER("rm_dir_w_symlink" );
unpack_filename(tmp_path, org_path);
/* Remove end FN_LIBCHAR as this causes problem on Linux and OS/2 */
pos= strend(path);
if (pos > path && pos[-1 ] == FN_LIBCHAR)
*--pos=0 ;
#ifdef HAVE_READLINK
int error;
char tmp2_path[FN_REFLEN];
if (unlikely((error= my_readlink(tmp2_path, path,
MYF(send_error ? MY_WME : 0 ))) < 0 ))
DBUG_RETURN(1 );
if (likely(!error))
{
if (mysql_file_delete(key_file_misc, path, MYF(send_error ? MY_WME : 0 )))
{
DBUG_RETURN(send_error);
}
/* Delete directory symbolic link pointed at */
path= tmp2_path;
}
#endif
if (unlikely(my_rmdir(path) < 0 && send_error))
{
my_error(ER_DB_DROP_RMDIR, MYF(0 ), path, errno);
DBUG_RETURN(1 );
}
DBUG_RETURN(0 );
}
/*
Remove . frm archives from directory
SYNOPSIS
thd thread handler
dirp list of files in archive directory
db data base name
org_path path of archive directory
RETURN
> 0 number of removed files
- 1 error
NOTE
A support of " arc " directories is obsolete , however this
function should exist to remove existent " arc " directories .
*/
long mysql_rm_arc_files(THD *thd, MY_DIR *dirp, const char *org_path)
{
long deleted= 0 ;
ulong found_other_files= 0 ;
char filePath[FN_REFLEN];
DBUG_ENTER("mysql_rm_arc_files" );
DBUG_PRINT("enter" , ("path: %s" , org_path));
for (size_t idx=0 ; idx < dirp->number_of_files && !thd->killed; idx++)
{
FILEINFO *file=dirp->dir_entry+idx;
char *extension, *revision;
DBUG_PRINT("info" ,("Examining: %s" , file->name));
extension= fn_ext(file->name);
if (extension[0 ] != '.' ||
extension[1 ] != 'f' || extension[2 ] != 'r' ||
extension[3 ] != 'm' || extension[4 ] != '-' )
{
found_other_files++;
continue ;
}
revision= extension+5 ;
while (*revision && my_isdigit(system_charset_info, *revision))
revision++;
if (*revision)
{
found_other_files++;
continue ;
}
strxmov(filePath, org_path, "/" , file->name, NullS);
if (mysql_file_delete_with_symlink(key_file_misc, filePath, "" , MYF(MY_WME)))
{
goto err;
}
deleted++;
}
if (thd->killed)
goto err;
my_dirend(dirp);
/*
If the directory is a symbolic link , remove the link first , then
remove the directory the symbolic link pointed at
*/
if (!found_other_files &&
rm_dir_w_symlink(org_path, 0 ))
DBUG_RETURN(-1 );
DBUG_RETURN(deleted);
err:
my_dirend(dirp);
DBUG_RETURN(-1 );
}
/**
@ brief Internal implementation : switch current database to a valid one .
@ param thd Thread context .
@ param new_db_name Name of the database to switch to . The function will
take ownership of the name ( the caller must not free
the allocated memory ) . If the name is NULL , we ' re
going to switch to NULL db .
@ param new_db_access Privileges of the new database .
@ param new_db_charset Character set of the new database .
*/
static void mysql_change_db_impl(THD *thd,
LEX_CSTRING *new_db_name,
const access_t& new_db_access,
CHARSET_INFO *new_db_charset)
{
/* 1. Change current database in THD. */
if (new_db_name == NULL)
{
/*
THD : : set_db ( ) does all the job - - it frees previous database name and
sets the new one .
*/
thd->set_db(&null_clex_str);
}
else if (new_db_name->str == INFORMATION_SCHEMA_NAME.str)
{
/*
Here we must use THD : : set_db ( ) , because we want to copy
INFORMATION_SCHEMA_NAME constant .
*/
thd->set_db(&INFORMATION_SCHEMA_NAME);
}
else
{
/*
Here we already have a copy of database name to be used in THD . So ,
we just call THD : : reset_db ( ) . Since THD : : reset_db ( ) does not releases
the previous database name , we should do it explicitly .
*/
thd->set_db(&null_clex_str);
thd->reset_db(new_db_name);
}
/* 2. Update security context. */
#ifndef NO_EMBEDDED_ACCESS_CHECKS
thd->security_ctx->db_access= new_db_access;
#endif
/* 3. Update db-charset environment variables. */
thd->db_charset= new_db_charset;
thd->variables.collation_database= new_db_charset;
}
/**
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)
{
DBUG_ASSERT(saved_db_name->length >= SAFE_NAME_LEN +1 );
if (!thd->db.str)
{
/* No current (default) database selected. */
saved_db_name->str= 0 ;
saved_db_name->length= 0 ;
}
else
{
memcpy(saved_db_name->str, thd->db.str, thd->db.length + 1 );
saved_db_name->length= thd->db.length;
}
}
/**
@ brief Change the current database and its attributes unconditionally .
@ param thd thread handle
@ param new_db_name database name
@ param force if force is FALSE , then the operation will fail if
- new_db_name is NULL or empty ;
- OR new database name is invalid
( Lex_ident_db : : check_name ( ) failed ) ;
- OR user has no privilege on the new database ;
- OR new database does not exist ;
if force 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
( Lex_ident_db : : check_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
database before executing a query , it calls thd - > set_db directly .
However , if the query , in turn , uses a stored routine , the stored routine
will use this function , even if it ' s run on the slave .
This function allocates the name of the database on the system heap : this
is necessary to be able to uniformly change the database from any module
of the server . Up to 5 . 0 different modules were using different memory to
store the name of the database , and this led to memory corruption :
a stack pointer set by Stored Procedures was used by replication after
the stack address was long gone .
@ return error code ( ER_XXX )
@ retval 0 Success
@ retval > 0 Error
*/
uint mysql_change_db(THD *thd, const LEX_CSTRING &new_db_name, bool force)
{
DBNameBuffer new_db_buff;
LEX_CSTRING new_db_file_name;
Security_context *sctx= thd->security_ctx;
access_t db_access(sctx->db_access);
CHARSET_INFO *db_default_cl;
DBUG_ENTER("mysql_change_db" );
if (new_db_name.length == 0 )
{
if (force)
{
/*
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 .
In case of stored program , new_db_name - > str = = " " and
new_db_name - > length = = 0 .
*/
mysql_change_db_impl(thd, NULL, access_t(NO_ACL), thd->variables.collation_server);
goto done;
}
else
{
my_message(ER_NO_DB_ERROR, ER_THD(thd, ER_NO_DB_ERROR), MYF(0 ));
DBUG_RETURN(ER_NO_DB_ERROR);
}
}
DBUG_PRINT("enter" ,("name: '%s'" , new_db_name.str));
if (is_infoschema_db(&new_db_name))
{
/* Switch the current database to INFORMATION_SCHEMA. */
mysql_change_db_impl(thd, &INFORMATION_SCHEMA_NAME, access_t(SELECT_ACL),
system_charset_info);
goto done;
}
new_db_file_name= lower_case_table_names == 1 ?
new_db_buff.copy_casedn(&my_charset_utf8mb3_general_ci,
new_db_name).to_lex_cstring() :
new_db_name;
/*
NOTE : if Lex_ident_db : : check_name ( ) fails ,
we should throw an error in any case ,
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 the current database to NULL
in this case to be sure .
The cast below ok here as new_db_file_name was just allocated
*/
if (Lex_ident_db::check_name_with_error(new_db_file_name))
{
if (force)
mysql_change_db_impl(thd, NULL, access_t(NO_ACL), thd->variables.collation_server);
DBUG_RETURN(ER_WRONG_DB_NAME);
}
DBUG_PRINT("info" ,("Use database: %s" , new_db_file_name.str));
#ifndef NO_EMBEDDED_ACCESS_CHECKS
if (test_all_bits(sctx->master_access, DB_ACLS))
{
db_access= access_t(DB_ACLS);
}
else
{
db_access= acl_get_all3(sctx, new_db_file_name.str, FALSE );
db_access.merge_with_parent(sctx->master_access);
}
if (!force&& !(db_access & DB_ACLS) &&
check_grant_db(thd, db_access, new_db_file_name.str))
{
my_error(ER_DBACCESS_DENIED_ERROR, MYF(0 ),
sctx->priv_user,
sctx->priv_host,
new_db_file_name.str);
general_log_print(thd, COM_INIT_DB, ER_THD(thd, ER_DBACCESS_DENIED_ERROR),
sctx->priv_user, sctx->priv_host, new_db_file_name.str);
DBUG_RETURN(ER_DBACCESS_DENIED_ERROR);
}
#endif
DEBUG_SYNC(thd, "before_db_dir_check" );
if (check_db_dir_existence(new_db_file_name.str))
{
if (force)
{
/* Throw a warning and free new_db_file_name. */
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_BAD_DB_ERROR, ER_THD(thd, ER_BAD_DB_ERROR),
new_db_file_name.str);
/* Change db to NULL. */
mysql_change_db_impl(thd, NULL, access_t(NO_ACL), thd->variables.collation_server);
/* The operation succeed. */
goto done;
}
else
{
/* Report an error and free new_db_file_name. */
my_error(ER_BAD_DB_ERROR, MYF(0 ), new_db_file_name.str);
/* The operation failed. */
DBUG_RETURN(ER_BAD_DB_ERROR);
}
}
db_default_cl= get_default_db_collation(thd, new_db_file_name.str);
/*
new_db_file_name allocated memory on the stack .
mysql_change_db_impl ( ) expects a my_alloc - ed memory .
NOTE : in mysql_change_db_impl ( ) new_db_file_name is assigned to THD
attributes and will be freed in THD : : ~ THD ( ) .
*/
if (const char *tmp= my_strndup(key_memory_THD_db, new_db_file_name.str,
new_db_file_name.length, MYF(MY_WME)))
{
LEX_CSTRING new_db_malloced({tmp, new_db_file_name.length});
mysql_change_db_impl(thd, &new_db_malloced, db_access, db_default_cl);
}
else
DBUG_RETURN(ER_OUT_OF_RESOURCES); /* the error is set */
done:
thd->session_tracker.current_schema.mark_as_changed(thd);
thd->session_tracker.state_change.mark_as_changed(thd);
DBUG_RETURN(0 );
}
/**
Change the current database and its attributes if needed .
@ param thd thread handle
@ param new_db database name
@ param [ in , out ] saved_db 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_CSTRING &new_db,
LEX_STRING *saved_db, bool force_switch,
bool *cur_db_changed)
{
*cur_db_changed= !cmp_db_names(Lex_ident_db(thd->db), Lex_ident_db(new_db));
if (!*cur_db_changed)
return FALSE ;
backup_current_db_name(thd, saved_db);
return mysql_change_db(thd, new_db, force_switch);
}
/**
Upgrade a 5 . 0 database .
This function is invoked whenever an ALTER DATABASE UPGRADE query is executed :
ALTER DATABASE ' olddb ' UPGRADE DATA DIRECTORY NAME .
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 .
@ param thd Current thread
@ param old_db 5 . 0 database name , in # mysql50 # name format
@ return 0 on success , 1 on error
*/
bool mysql_upgrade_db(THD *thd, const Lex_ident_db &old_db)
{
bool error= 0 , change_to_newdb= 0 ;
char path[FN_REFLEN+16 ];
uint length;
Schema_specification_st create_info;
MY_DIR *dirp;
TABLE_LIST *table_list;
SELECT_LEX *sl= thd->lex->current_select;
Lex_ident_db new_db;
DBUG_ENTER("mysql_upgrade_db" );
if ((old_db.length <= MYSQL50_TABLE_NAME_PREFIX_LENGTH) ||
(strncmp(old_db.str,
MYSQL50_TABLE_NAME_PREFIX,
MYSQL50_TABLE_NAME_PREFIX_LENGTH) != 0 ))
{
my_error(ER_WRONG_USAGE, MYF(0 ),
"ALTER DATABASE UPGRADE DATA DIRECTORY NAME" ,
"name" );
DBUG_RETURN(1 );
}
/* `#mysql50#<name>` converted to encoded `<name>` */
new_db= Lex_ident_db(old_db.str + MYSQL50_TABLE_NAME_PREFIX_LENGTH,
old_db.length - MYSQL50_TABLE_NAME_PREFIX_LENGTH);
const DBNameBuffer dbnorm_buffer_old(old_db, lower_case_table_names);
Lex_ident_db_normalized old_dbnorm(dbnorm_buffer_old.to_lex_cstring());
/* Lock the old name, the new name will be locked by mysql_create_db().*/
if (lock_schema_name(thd, old_dbnorm))
DBUG_RETURN(1 );
/*
Let ' s remember if we should do " USE newdb " afterwards .
thd - > db will be cleared in mysql_rename_db ( )
*/
if (thd->db.str && !cmp(&thd->db, &old_db))
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 (unlikely((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 (unlikely((error= mysql_create_db_internal(thd, new_db, DDL_options(),
&create_info, 1 ))))
goto exit ;
/* Step2: Move tables to the new database */
if ((dirp = my_dir(path,MYF(MY_DONT_SORT))))
{
size_t nfiles= dirp->number_of_files;
for (size_t idx=0 ; idx < nfiles && !thd->killed ; idx++)
{
FILEINFO *file= dirp->dir_entry + idx;
char *extension, tname[FN_REFLEN + 1 ];
LEX_CSTRING table_str;
DBUG_PRINT("info" ,("Examining: %s" , file->name));
/* skipping non-FRM files */
if (!(extension= (char *) fn_frm_ext(file->name)))
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 *) thd->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,
MDL_EXCLUSIVE) ||
!sl->add_table_to_list(thd, new_ident, NULL,
TL_OPTION_UPDATING, TL_IGNORE,
MDL_EXCLUSIVE))
{
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 , 0 )))
{
/*
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 guarantees we never lose any tables .
*/
build_table_filename(path, sizeof (path)-1 , new_db.str,"" ,MY_DB_OPT_FILE, 0 );
mysql_file_delete(key_file_dbopt, 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 '\'
my_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 explicitly 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))))
{
size_t nfiles= dirp->number_of_files;
for (size_t idx=0 ; idx < nfiles ; idx++)
{
FILEINFO *file= dirp->dir_entry + idx;
char oldname[FN_REFLEN + 1 ], newname[FN_REFLEN + 1 ];
DBUG_PRINT("info" ,("Examining: %s" , file->name));
/* skipping MY_DB_OPT_FILE */
if (!files_charset_info->strnncoll(Lex_cstring_strlen(file->name),
Lex_cstring_strlen(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 );
mysql_file_rename(key_file_misc, oldname, newname, MYF(MY_WME));
}
my_dirend(dirp);
}
/*
Step7 : drop the old database .
query_cache_invalidate ( olddb ) is done inside mysql_rm_db ( ) , no need
to execute them again .
mysql_rm_db ( ) also " unuses " if we drop the current database .
*/
error= mysql_rm_db_internal(thd, old_db, 0 , true );
/* Step8: logging */
if (mysql_bin_log.is_open())
{
int errcode= query_error_code(thd, TRUE );
Query_log_event qinfo(thd, thd->query(), thd->query_length(),
FALSE , TRUE , TRUE , errcode);
thd->clear_error();
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 ) != 0 ;
exit :
DBUG_RETURN(error);
}
/*
Check if there is directory for the database name .
SYNOPSIS
check_db_dir_existence ( )
db_name database name
RETURN VALUES
FALSE There is directory for the specified database name .
TRUE The directory does not exist .
*/
bool check_db_dir_existence(const char *db_name)
{
char db_dir_path[FN_REFLEN + 1 ];
uint db_dir_path_len;
if (dbname_cache->contains(db_name))
return 0 ;
db_dir_path_len= build_table_filename(db_dir_path, sizeof (db_dir_path) - 1 ,
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 ;
/*
Check access .
The locking is to prevent creating permanent stale
entries for deleted databases , in case of
race condition with my_rmdir .
*/
mysql_rwlock_rdlock(&rmdir_lock);
int ret= my_access(db_dir_path, F_OK);
if (!ret)
dbname_cache->insert(db_name);
mysql_rwlock_unlock(&rmdir_lock);
return ret;
}
Messung V0.5 in Prozent C=94 H=87 G=90
¤ Dauer der Verarbeitung: 0.66 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland