in sql/sql_table.cc [2282:2795]
int mysql_rm_table_no_locks(THD *thd, TABLE_LIST *tables, bool if_exists,
bool drop_temporary, bool drop_view,
bool dont_log_query)
{
TABLE_LIST *table;
char path[FN_REFLEN + 1], *alias= NULL;
uint path_length= 0;
String wrong_tables;
int error= 0;
int non_temp_tables_count= 0;
bool foreign_key_error=0;
bool non_tmp_error= 0;
bool trans_tmp_table_deleted= 0, non_trans_tmp_table_deleted= 0;
bool non_tmp_table_deleted= 0;
bool have_nonexistent_tmp_table= 0;
bool is_drop_tmp_if_exists_with_no_defaultdb= 0;
String built_query;
String built_trans_tmp_query, built_non_trans_tmp_query;
String nonexistent_tmp_tables;
DBUG_ENTER("mysql_rm_table_no_locks");
/*
Prepares the drop statements that will be written into the binary
log as follows:
1 - If we are not processing a "DROP TEMPORARY" it prepares a
"DROP".
2 - A "DROP" may result in a "DROP TEMPORARY" but the opposite is
not true.
3 - If the current format is row, the IF EXISTS token needs to be
appended because one does not know if CREATE TEMPORARY was previously
written to the binary log.
4 - Add the IF_EXISTS token if necessary, i.e. if_exists is TRUE.
5 - For temporary tables, there is a need to differentiate tables
in transactional and non-transactional storage engines. For that,
reason, two types of drop statements are prepared.
The need to different the type of tables when dropping a temporary
table stems from the fact that such drop does not commit an ongoing
transaction and changes to non-transactional tables must be written
ahead of the transaction in some circumstances.
6 - At the time of writing 'DROP TEMPORARY TABLE IF EXISTS'
statements into the binary log if the default database specified in
thd->db is present then they are binlogged as
'USE `default_db`; DROP TEMPORARY TABLE IF EXISTS `t1`;'
otherwise they will be binlogged with the actual database name to
which the table belongs to.
'DROP TEMPORARY TABLE IF EXISTS `actual_db`.`t1`
*/
if (!dont_log_query)
{
if (!drop_temporary)
{
built_query.set_charset(thd->charset());
if (if_exists)
built_query.append("DROP TABLE IF EXISTS ");
else
built_query.append("DROP TABLE ");
}
if (thd->is_current_stmt_binlog_format_row() || if_exists)
{
/*
If default database doesnot exist in those cases set
'is_drop_tmp_if_exists_with_no_defaultdb flag to 'true' so that the
'DROP TEMPORARY TABLE IF EXISTS' command is logged with a qualified
table name.
*/
if (thd->db != NULL && check_db_dir_existence(thd->db))
is_drop_tmp_if_exists_with_no_defaultdb= true;
built_trans_tmp_query.set_charset(thd->charset());
built_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS ");
built_non_trans_tmp_query.set_charset(thd->charset());
built_non_trans_tmp_query.append("DROP TEMPORARY TABLE IF EXISTS ");
}
else
{
built_trans_tmp_query.set_charset(thd->charset());
built_trans_tmp_query.append("DROP TEMPORARY TABLE ");
built_non_trans_tmp_query.set_charset(thd->charset());
built_non_trans_tmp_query.append("DROP TEMPORARY TABLE ");
}
nonexistent_tmp_tables.set_charset(thd->charset());
}
for (table= tables; table; table= table->next_local)
{
bool is_trans;
char *db=table->db;
int db_len= table->db_length;
handlerton *table_type;
enum legacy_db_type frm_db_type= DB_TYPE_UNKNOWN;
DBUG_PRINT("table", ("table_l: '%s'.'%s' table: 0x%lx s: 0x%lx",
table->db, table->table_name, (long) table->table,
table->table ? (long) table->table->s : (long) -1));
/*
If we are in locked tables mode and are dropping a temporary table,
the ticket should be NULL to ensure that we don't release a lock
on a base table later.
*/
DBUG_ASSERT(!(thd->locked_tables_mode &&
table->open_type != OT_BASE_ONLY &&
find_temporary_table(thd, table) &&
table->mdl_request.ticket != NULL));
thd->add_to_binlog_accessed_dbs(table->db);
/*
drop_temporary_table may return one of the following error codes:
. 0 - a temporary table was successfully dropped.
. 1 - a temporary table was not found.
. -1 - a temporary table is used by an outer statement.
*/
if (table->open_type == OT_BASE_ONLY)
error= 1;
else if ((error= drop_temporary_table(thd, table, &is_trans)) == -1)
{
DBUG_ASSERT(thd->in_sub_stmt);
goto err;
}
if ((drop_temporary && if_exists) || !error)
{
/*
This handles the case of temporary tables. We have the following cases:
. "DROP TEMPORARY" was executed and a temporary table was affected
(i.e. drop_temporary && !error) or the if_exists was specified (i.e.
drop_temporary && if_exists).
. "DROP" was executed but a temporary table was affected (.i.e
!error).
*/
if (!dont_log_query)
{
/*
If there is an error, we don't know the type of the engine
at this point. So, we keep it in the nonexistent_tmp_table list.
*/
if (error == 1)
have_nonexistent_tmp_table= true;
else if (is_trans)
trans_tmp_table_deleted= TRUE;
else
non_trans_tmp_table_deleted= TRUE;
String *built_ptr_query=
(error == 1 ? &nonexistent_tmp_tables :
(is_trans ? &built_trans_tmp_query : &built_non_trans_tmp_query));
/*
Write the database name if it is not the current one or if
thd->db is NULL or 'IF EXISTS' clause is present in 'DROP TEMPORARY'
query.
*/
if (thd->db == NULL || strcmp(db,thd->db) != 0
|| is_drop_tmp_if_exists_with_no_defaultdb )
{
append_identifier(thd, built_ptr_query, db, db_len,
system_charset_info, thd->charset());
built_ptr_query->append(".");
}
append_identifier(thd, built_ptr_query, table->table_name,
strlen(table->table_name), system_charset_info,
thd->charset());
built_ptr_query->append(",");
}
/*
This means that a temporary table was droped and as such there
is no need to proceed with the code that tries to drop a regular
table.
*/
if (!error) continue;
}
else if (!drop_temporary)
{
non_temp_tables_count++;
if (thd->locked_tables_mode)
{
if (wait_while_table_is_used(thd, table->table, HA_EXTRA_FORCE_REOPEN))
{
error= -1;
goto err;
}
close_all_tables_for_name(thd, table->table->s, true, NULL);
table->table= 0;
}
/* Check that we have an exclusive lock on the table to be dropped. */
DBUG_ASSERT(thd->mdl_context.is_lock_owner(MDL_key::TABLE, table->db,
table->table_name,
MDL_EXCLUSIVE));
if (thd->killed)
{
error= -1;
goto err;
}
alias= (lower_case_table_names == 2) ? table->alias : table->table_name;
/* remove .frm file and engine files */
path_length= build_table_filename(path, sizeof(path) - 1, db, alias,
reg_ext,
table->internal_tmp_table ?
FN_IS_TMP : 0);
/*
This handles the case where a "DROP" was executed and a regular
table "may be" dropped as drop_temporary is FALSE and error is
TRUE. If the error was FALSE a temporary table was dropped and
regardless of the status of drop_tempoary a "DROP TEMPORARY"
must be used.
*/
if (!dont_log_query)
{
/*
Note that unless if_exists is TRUE or a temporary table was deleted,
there is no means to know if the statement should be written to the
binary log. See further information on this variable in what follows.
*/
non_tmp_table_deleted= (if_exists ? TRUE : non_tmp_table_deleted);
/*
Don't write the database name if it is the current one (or if
thd->db is NULL).
*/
if (thd->db == NULL || strcmp(db,thd->db) != 0)
{
append_identifier(thd, &built_query, db, db_len,
system_charset_info, thd->charset());
built_query.append(".");
}
append_identifier(thd, &built_query, table->table_name,
strlen(table->table_name), system_charset_info,
thd->charset());
built_query.append(",");
}
}
DEBUG_SYNC(thd, "rm_table_no_locks_before_delete_table");
DBUG_EXECUTE_IF("sleep_before_no_locks_delete_table",
my_sleep(100000););
error= 0;
if (drop_temporary ||
((access(path, F_OK) &&
ha_create_table_from_engine(thd, db, alias)) ||
(!drop_view &&
dd_frm_type(thd, path, &frm_db_type) != FRMTYPE_TABLE)))
{
/*
One of the following cases happened:
. "DROP TEMPORARY" but a temporary table was not found.
. "DROP" but table was not found on disk and table can't be
created from engine.
. ./sql/datadict.cc +32 /Alfranio - TODO: We need to test this.
*/
if (if_exists)
{
String tbl_name;
tbl_name.append(String(db,system_charset_info));
tbl_name.append('.');
tbl_name.append(String(table->table_name,system_charset_info));
push_warning_printf(thd, Sql_condition::WARN_LEVEL_NOTE,
ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR),
tbl_name.c_ptr());
}
else
{
non_tmp_error = (drop_temporary ? non_tmp_error : TRUE);
error= 1;
}
}
else
{
char *end;
if (frm_db_type == DB_TYPE_UNKNOWN)
{
dd_frm_type(thd, path, &frm_db_type);
DBUG_PRINT("info", ("frm_db_type %d from %s", frm_db_type, path));
}
table_type= ha_resolve_by_legacy_type(thd, frm_db_type);
// Remove extension for delete
*(end= path + path_length - reg_ext_length)= '\0';
DBUG_PRINT("info", ("deleting table of type %d",
(table_type ? table_type->db_type : 0)));
error= ha_delete_table(thd, table_type, path, db, table->table_name,
!dont_log_query);
/* No error if non existent table and 'IF EXIST' clause or view */
if ((error == ENOENT || error == HA_ERR_NO_SUCH_TABLE) &&
(if_exists || table_type == NULL))
{
error= 0;
thd->clear_error();
}
if (error == HA_ERR_ROW_IS_REFERENCED)
{
/* the table is referenced by a foreign key constraint */
foreign_key_error= 1;
}
if (!error || error == ENOENT || error == HA_ERR_NO_SUCH_TABLE)
{
int new_error;
/* Delete the table definition file */
strmov(end,reg_ext);
if (!(new_error= mysql_file_delete(key_file_frm, path, MYF(MY_WME))))
{
non_tmp_table_deleted= TRUE;
new_error= Table_triggers_list::drop_all_triggers(thd, db,
table->table_name);
}
error|= new_error;
}
non_tmp_error= error ? TRUE : non_tmp_error;
}
if (error)
{
if (error == HA_ERR_TOO_MANY_CONCURRENT_TRXS)
{
my_error(HA_ERR_TOO_MANY_CONCURRENT_TRXS, MYF(0));
wrong_tables.free();
error= 1;
goto err;
}
if (wrong_tables.length())
wrong_tables.append(',');
wrong_tables.append(String(db,system_charset_info));
wrong_tables.append('.');
wrong_tables.append(String(table->table_name,system_charset_info));
}
DBUG_PRINT("table", ("table: 0x%lx s: 0x%lx", (long) table->table,
table->table ? (long) table->table->s : (long) -1));
DBUG_EXECUTE_IF("bug43138",
my_printf_error(ER_BAD_TABLE_ERROR,
ER(ER_BAD_TABLE_ERROR), MYF(0),
table->table_name););
#ifdef HAVE_PSI_TABLE_INTERFACE
if (drop_temporary && likely(error == 0))
PSI_TABLE_CALL(drop_table_share)
(true, table->db, table->db_length, table->table_name, table->table_name_length);
#endif
}
DEBUG_SYNC(thd, "rm_table_no_locks_before_binlog");
thd->thread_specific_used|= (trans_tmp_table_deleted ||
non_trans_tmp_table_deleted);
error= 0;
err:
if (wrong_tables.length())
{
if (!foreign_key_error)
my_printf_error(ER_BAD_TABLE_ERROR, ER(ER_BAD_TABLE_ERROR), MYF(0),
wrong_tables.c_ptr());
else
my_message(ER_ROW_IS_REFERENCED, ER(ER_ROW_IS_REFERENCED), MYF(0));
error= 1;
}
if (have_nonexistent_tmp_table || non_trans_tmp_table_deleted ||
trans_tmp_table_deleted || non_tmp_table_deleted)
{
query_cache_invalidate3(thd, tables, 0);
if (have_nonexistent_tmp_table || non_trans_tmp_table_deleted ||
trans_tmp_table_deleted)
thd->transaction.stmt.mark_dropped_temp_table();
/*
The statement may contain up to three types of temporary tables:
transactional, non-transactional, and non-existent tables.
The statement is logged using up to two statements: non-transactional
and transactional tables are logged in different statements.
The non-existing tables are logged together with transactional ones, if
any transactional tables exist or if there is only non-existing tables;
otherwise are logged together with non-transactional ones.
This logic ensures that:
- On master, transactional and non-transactional tables are written to
different statements.
- Therefore, slave will never see statements containing both transactional
and non-transactional tables.
- Since non-existing temporary tables are logged together with whatever
type of temporary tables that exist, the slave thus writes any statement
as just one statement. I.e., the slave never splits a statement into two.
This is crucial when GTIDs are enabled, since otherwise the statement,
which already has a GTID, would need two different GTIDs.
*/
if (!dont_log_query && mysql_bin_log.is_open())
{
if (non_trans_tmp_table_deleted)
{
/*
Add the list of nonexistent tmp tables here only if there is no
trans tmp table deleted.
*/
if (!trans_tmp_table_deleted && have_nonexistent_tmp_table)
built_non_trans_tmp_query.append(nonexistent_tmp_tables);
/* Chop of the last comma */
built_non_trans_tmp_query.chop();
built_non_trans_tmp_query.append(" /* generated by server */");
error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
built_non_trans_tmp_query.ptr(),
built_non_trans_tmp_query.length(),
FALSE, FALSE,
is_drop_tmp_if_exists_with_no_defaultdb,
0);
/*
When temporary and regular tables or temporary tables with
different storage engines are dropped on a single
statement, the original statement is split in two or more.
These statements are logged in distinct events to binary
logs, when gtid_mode is ON each DDL event must have its own
GTID. Since drop temporary table does not implicitly
commit, in these cases we must force a commit.
*/
if (gtid_mode > 0 && (trans_tmp_table_deleted || non_tmp_table_deleted))
error |= mysql_bin_log.commit(thd, true, false);
}
if (trans_tmp_table_deleted ||
(have_nonexistent_tmp_table && !non_trans_tmp_table_deleted))
{
if (have_nonexistent_tmp_table)
built_trans_tmp_query.append(nonexistent_tmp_tables);
/* Chop of the last comma */
built_trans_tmp_query.chop();
built_trans_tmp_query.append(" /* generated by server */");
error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
built_trans_tmp_query.ptr(),
built_trans_tmp_query.length(),
TRUE, FALSE,
is_drop_tmp_if_exists_with_no_defaultdb,
0);
/*
When temporary and regular tables are dropped on a single
statement, the original statement is split in two or more.
These statements are logged in distinct events to binary
logs, when gtid_mode is ON each DDL event must have its own
GTID. Since drop temporary table does not implicitly
commit, in these cases we must force a commit.
*/
if (gtid_mode > 0 && non_tmp_table_deleted)
error |= mysql_bin_log.commit(thd, true, false);
}
/*
when the DROP TABLE command is used to DROP a single table and if that
command fails then the query cannot generate 'partial results'. In
that case the query will not be written to the binary log.
*/
if (non_tmp_table_deleted &&
(thd->lex->select_lex.table_list.elements > 1 || !error))
{
/* Chop of the last comma */
built_query.chop();
built_query.append(" /* generated by server */");
int error_code = (non_tmp_error ?
(foreign_key_error ? ER_ROW_IS_REFERENCED : ER_BAD_TABLE_ERROR) : 0);
error |= thd->binlog_query(THD::STMT_QUERY_TYPE,
built_query.ptr(),
built_query.length(),
TRUE, FALSE, FALSE,
error_code);
}
}
}
if (!drop_temporary)
{
/*
Under LOCK TABLES we should release meta-data locks on the tables
which were dropped.
Leave LOCK TABLES mode if we managed to drop all tables which were
locked. Additional check for 'non_temp_tables_count' is to avoid
leaving LOCK TABLES mode if we have dropped only temporary tables.
*/
if (thd->locked_tables_mode)
{
if (thd->lock && thd->lock->table_count == 0 && non_temp_tables_count > 0)
{
thd->locked_tables_list.unlock_locked_tables(thd);
goto end;
}
for (table= tables; table; table= table->next_local)
{
/* Drop locks for all successfully dropped tables. */
if (table->table == NULL && table->mdl_request.ticket)
{
/*
Under LOCK TABLES we may have several instances of table open
and locked and therefore have to remove several metadata lock
requests associated with them.
*/
thd->mdl_context.release_all_locks_for_name(table->mdl_request.ticket);
}
}
}
/*
Rely on the caller to implicitly commit the transaction
and release metadata locks.
*/
}
end:
DBUG_RETURN(error);
}