Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions mysql-test/main/rename.result
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,43 @@ drop table t2;
rename table if exists t1 to t2;
alter table if exists t2 rename to t1;
drop table t1;
#
# MDEV-27027 Atomic DDL: Assertion `!param->got_error' failed upon
# unsuccessful multi-RENAME TABLE
#
CREATE TABLE t1 (a INT);
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
INSERT INTO db.t SELECT SLEEP(0.05);
# Missing table in RENAME TABLE (should error)
RENAME TABLE t1 TO t2,
missing1 TO m1;
ERROR 42S02: Table 'test.missing1' doesn't exist
SHOW CREATE TRIGGER tr1;
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation Created
tr1 STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`root`@`localhost` TRIGGER tr1 AFTER INSERT ON `t1` FOR EACH ROW
INSERT INTO db.t SELECT SLEEP(0.05) latin1 latin1_swedish_ci latin1_swedish_ci #
CREATE TABLE t2 (a INT);
# Attempt to rename to existing table (should error)
RENAME TABLE t2 TO t3,
t1 TO t3;
ERROR 42S01: Table 't3' already exists
SHOW CREATE TRIGGER tr1;
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation Created
tr1 STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION CREATE DEFINER=`root`@`localhost` TRIGGER tr1 AFTER INSERT ON `t1` FOR EACH ROW
INSERT INTO db.t SELECT SLEEP(0.05) latin1 latin1_swedish_ci latin1_swedish_ci #
# Test multiple warnings captured during renaming
RENAME TABLE IF EXISTS
missing1 TO m1,
missing2 TO m2,
t2 TO t3, # this rename succeeds
missing3 TO m3;
Warnings:
Note 1146 Table 'test.missing1' doesn't exist
Note 1146 Table 'test.missing2' doesn't exist
Note 1146 Table 'test.missing3' doesn't exist
RENAME TABLE IF EXISTS
missing1 TO m1,
t1 TO t3, # this will error: table exists
missing2 TO m2;
ERROR 42S01: Table 't3' already exists
DROP TABLE t1, t3;
41 changes: 41 additions & 0 deletions mysql-test/main/rename.test
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,44 @@ drop table t2;
rename table if exists t1 to t2;
alter table if exists t2 rename to t1;
drop table t1;

--echo #
--echo # MDEV-27027 Atomic DDL: Assertion `!param->got_error' failed upon
--echo # unsuccessful multi-RENAME TABLE
--echo #
CREATE TABLE t1 (a INT);
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
INSERT INTO db.t SELECT SLEEP(0.05);

--echo # Missing table in RENAME TABLE (should error)
--error ER_NO_SUCH_TABLE
RENAME TABLE t1 TO t2,
missing1 TO m1; # this will error: missing table

--replace_column 7 #
SHOW CREATE TRIGGER tr1;

CREATE TABLE t2 (a INT);

--echo # Attempt to rename to existing table (should error)
--error ER_TABLE_EXISTS_ERROR
RENAME TABLE t2 TO t3,
t1 TO t3; # this will error: table exists

--replace_column 7 #
SHOW CREATE TRIGGER tr1;

--echo # Test multiple warnings captured during renaming
RENAME TABLE IF EXISTS
missing1 TO m1,
missing2 TO m2,
t2 TO t3, # this rename succeeds
missing3 TO m3;

--error ER_TABLE_EXISTS_ERROR
RENAME TABLE IF EXISTS
missing1 TO m1,
t1 TO t3, # this will error: table exists
missing2 TO m2;

DROP TABLE t1, t3;
70 changes: 70 additions & 0 deletions sql/sql_class.h
Original file line number Diff line number Diff line change
Expand Up @@ -2047,6 +2047,76 @@ class Counting_error_handler : public Internal_error_handler
};


extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);

/**
Error handler that captures and postpones errors.
Warnings and notes are passed through to the next handler.
Stored errors can be re-emitted later via emit_errors().
*/

class Postponed_error_handler : public Internal_error_handler
{
struct Error_entry
{
uint sql_errno;
char message[MYSQL_ERRMSG_SIZE];
Error_entry *next;
};

Error_entry *m_first;
Error_entry *m_last;
MEM_ROOT *m_mem_root;

public:
Postponed_error_handler(MEM_ROOT *mem_root)
: m_first(nullptr), m_last(nullptr), m_mem_root(mem_root)
{}

bool handle_condition(THD *thd,
uint sql_errno,
const char *sqlstate,
Sql_condition::enum_warning_level *level,
const char *msg,
Sql_condition **cond_hdl) override
{
/* Only capture errors, let warnings and notes pass through */
if (*level != Sql_condition::WARN_LEVEL_ERROR)
return false;

Error_entry *entry= (Error_entry*) alloc_root(m_mem_root,
sizeof(Error_entry));
if (!entry)
return false; // Can't store, let error propagate

entry->sql_errno= sql_errno;
strmake(entry->message, msg, sizeof(entry->message) - 1);
entry->next= nullptr;

if (m_last)
m_last->next= entry;
else
m_first= entry;
m_last= entry;

return true;
}

bool has_errors() const { return m_first != nullptr; }

void emit_errors()
{
for (Error_entry *e= m_first; e; e= e->next)
my_message_sql(e->sql_errno, e->message, MYF(0));
}

void clear()
{
m_first= m_last= nullptr;
}
};


/**
This class is an internal error handler implementation for
DROP TABLE statements. The thing is that there may be warnings during
Expand Down
14 changes: 14 additions & 0 deletions sql/sql_rename.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/

#include "mariadb.h"
#include "sql_class.h"
#include "sql_priv.h"
#include "unireg.h"
#include "sql_rename.h"
Expand Down Expand Up @@ -51,6 +52,7 @@ static bool rename_tables(THD *thd, TABLE_LIST *table_list,
the new name.
*/


bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
bool if_exists)
{
Expand All @@ -60,6 +62,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
int to_table;
const char *rename_log_table[2]= {NULL, NULL};
DDL_LOG_STATE ddl_log_state;
Postponed_error_handler postponed_handler(thd->mem_root);
DBUG_ENTER("mysql_rename_tables");

/*
Expand Down Expand Up @@ -162,6 +165,15 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
An exclusive lock on table names is satisfactory to ensure
no other thread accesses this table.
*/

/*
Do not emit errors which may occur during the sequence of renamings,
because they will raise the 'thd->is_error()' flag, and `ddl_log_revert()`
may fail due to this. Instead, capture and collect errors and warnings
and emit them only after the renaming and possible reversion are complete.
*/
thd->push_internal_handler(&postponed_handler);

error= rename_tables(thd, table_list, &ddl_log_state,
0, if_exists, &force_if_exists);

Expand Down Expand Up @@ -192,6 +204,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
my_ok(thd);
}

thd->pop_internal_handler();
if (likely(!error))
{
query_cache_invalidate3(thd, table_list, 0);
Expand All @@ -201,6 +214,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
{
/* Revert the renames of normal tables with the help of the ddl log */
ddl_log_revert(thd, &ddl_log_state);
postponed_handler.emit_errors();
}

err:
Expand Down