Skip to content
/ server Public

Commit 0397e4e

Browse files
committed
MDEV-27027 Atomic DDL: Assertion failed upon unsuccessful multi-RENAME TABLE
When executing an atomic sequence of RENAME operations, such as: RENAME TABLE t1 TO t2, t3 TO t4, ... any failure in the sequence triggers a rollback of previously completed renames to preserve atomicity. However, when an error occurs, `my_error()` is invoked immediately, which sets the `thd->is_error()` flag. This premature flag setting causes the rollback logic to misinterpret the thread state, leading to incorrect reversion behavior and assertion failures. To address this, the errors are now not emitted immediately but captured and postponed. A new class `Postponed_error_handler` is introduced for this purpose. Only after all operations are completed (including possible DDL reversion), the captured errors are emitted.
1 parent 80e7ce8 commit 0397e4e

File tree

4 files changed

+166
-0
lines changed

4 files changed

+166
-0
lines changed

mysql-test/main/rename.result

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,3 +173,43 @@ drop table t2;
173173
rename table if exists t1 to t2;
174174
alter table if exists t2 rename to t1;
175175
drop table t1;
176+
#
177+
# MDEV-27027 Atomic DDL: Assertion `!param->got_error' failed upon
178+
# unsuccessful multi-RENAME TABLE
179+
#
180+
CREATE TABLE t1 (a INT);
181+
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
182+
INSERT INTO db.t SELECT SLEEP(0.05);
183+
# Missing table in RENAME TABLE (should error)
184+
RENAME TABLE t1 TO t2,
185+
missing1 TO m1;
186+
ERROR 42S02: Table 'test.missing1' doesn't exist
187+
SHOW CREATE TRIGGER tr1;
188+
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation Created
189+
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
190+
INSERT INTO db.t SELECT SLEEP(0.05) latin1 latin1_swedish_ci latin1_swedish_ci #
191+
CREATE TABLE t2 (a INT);
192+
# Attempt to rename to existing table (should error)
193+
RENAME TABLE t2 TO t3,
194+
t1 TO t3;
195+
ERROR 42S01: Table 't3' already exists
196+
SHOW CREATE TRIGGER tr1;
197+
Trigger sql_mode SQL Original Statement character_set_client collation_connection Database Collation Created
198+
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
199+
INSERT INTO db.t SELECT SLEEP(0.05) latin1 latin1_swedish_ci latin1_swedish_ci #
200+
# Test multiple warnings captured during renaming
201+
RENAME TABLE IF EXISTS
202+
missing1 TO m1,
203+
missing2 TO m2,
204+
t2 TO t3, # this rename succeeds
205+
missing3 TO m3;
206+
Warnings:
207+
Note 1146 Table 'test.missing1' doesn't exist
208+
Note 1146 Table 'test.missing2' doesn't exist
209+
Note 1146 Table 'test.missing3' doesn't exist
210+
RENAME TABLE IF EXISTS
211+
missing1 TO m1,
212+
t1 TO t3, # this will error: table exists
213+
missing2 TO m2;
214+
ERROR 42S01: Table 't3' already exists
215+
DROP TABLE t1, t3;

mysql-test/main/rename.test

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,44 @@ drop table t2;
166166
rename table if exists t1 to t2;
167167
alter table if exists t2 rename to t1;
168168
drop table t1;
169+
170+
--echo #
171+
--echo # MDEV-27027 Atomic DDL: Assertion `!param->got_error' failed upon
172+
--echo # unsuccessful multi-RENAME TABLE
173+
--echo #
174+
CREATE TABLE t1 (a INT);
175+
CREATE TRIGGER tr1 AFTER INSERT ON t1 FOR EACH ROW
176+
INSERT INTO db.t SELECT SLEEP(0.05);
177+
178+
--echo # Missing table in RENAME TABLE (should error)
179+
--error ER_NO_SUCH_TABLE
180+
RENAME TABLE t1 TO t2,
181+
missing1 TO m1; # this will error: missing table
182+
183+
--replace_column 7 #
184+
SHOW CREATE TRIGGER tr1;
185+
186+
CREATE TABLE t2 (a INT);
187+
188+
--echo # Attempt to rename to existing table (should error)
189+
--error ER_TABLE_EXISTS_ERROR
190+
RENAME TABLE t2 TO t3,
191+
t1 TO t3; # this will error: table exists
192+
193+
--replace_column 7 #
194+
SHOW CREATE TRIGGER tr1;
195+
196+
--echo # Test multiple warnings captured during renaming
197+
RENAME TABLE IF EXISTS
198+
missing1 TO m1,
199+
missing2 TO m2,
200+
t2 TO t3, # this rename succeeds
201+
missing3 TO m3;
202+
203+
--error ER_TABLE_EXISTS_ERROR
204+
RENAME TABLE IF EXISTS
205+
missing1 TO m1,
206+
t1 TO t3, # this will error: table exists
207+
missing2 TO m2;
208+
209+
DROP TABLE t1, t3;

sql/sql_class.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2047,6 +2047,76 @@ class Counting_error_handler : public Internal_error_handler
20472047
};
20482048

20492049

2050+
extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);
2051+
2052+
/**
2053+
Error handler that captures and postpones errors.
2054+
Warnings and notes are passed through to the next handler.
2055+
Stored errors can be re-emitted later via emit_errors().
2056+
*/
2057+
2058+
class Postponed_error_handler : public Internal_error_handler
2059+
{
2060+
struct Error_entry
2061+
{
2062+
uint sql_errno;
2063+
char message[MYSQL_ERRMSG_SIZE];
2064+
Error_entry *next;
2065+
};
2066+
2067+
Error_entry *m_first;
2068+
Error_entry *m_last;
2069+
MEM_ROOT *m_mem_root;
2070+
2071+
public:
2072+
Postponed_error_handler(MEM_ROOT *mem_root)
2073+
: m_first(nullptr), m_last(nullptr), m_mem_root(mem_root)
2074+
{}
2075+
2076+
bool handle_condition(THD *thd,
2077+
uint sql_errno,
2078+
const char *sqlstate,
2079+
Sql_condition::enum_warning_level *level,
2080+
const char *msg,
2081+
Sql_condition **cond_hdl) override
2082+
{
2083+
/* Only capture errors, let warnings and notes pass through */
2084+
if (*level != Sql_condition::WARN_LEVEL_ERROR)
2085+
return false;
2086+
2087+
Error_entry *entry= (Error_entry*) alloc_root(m_mem_root,
2088+
sizeof(Error_entry));
2089+
if (!entry)
2090+
return false; // Can't store, let error propagate
2091+
2092+
entry->sql_errno= sql_errno;
2093+
strmake(entry->message, msg, sizeof(entry->message) - 1);
2094+
entry->next= nullptr;
2095+
2096+
if (m_last)
2097+
m_last->next= entry;
2098+
else
2099+
m_first= entry;
2100+
m_last= entry;
2101+
2102+
return true;
2103+
}
2104+
2105+
bool has_errors() const { return m_first != nullptr; }
2106+
2107+
void emit_errors()
2108+
{
2109+
for (Error_entry *e= m_first; e; e= e->next)
2110+
my_message_sql(e->sql_errno, e->message, MYF(0));
2111+
}
2112+
2113+
void clear()
2114+
{
2115+
m_first= m_last= nullptr;
2116+
}
2117+
};
2118+
2119+
20502120
/**
20512121
This class is an internal error handler implementation for
20522122
DROP TABLE statements. The thing is that there may be warnings during

sql/sql_rename.cc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121

2222
#include "mariadb.h"
23+
#include "sql_class.h"
2324
#include "sql_priv.h"
2425
#include "unireg.h"
2526
#include "sql_rename.h"
@@ -51,6 +52,7 @@ static bool rename_tables(THD *thd, TABLE_LIST *table_list,
5152
the new name.
5253
*/
5354

55+
5456
bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
5557
bool if_exists)
5658
{
@@ -60,6 +62,7 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
6062
int to_table;
6163
const char *rename_log_table[2]= {NULL, NULL};
6264
DDL_LOG_STATE ddl_log_state;
65+
Postponed_error_handler postponed_handler(thd->mem_root);
6366
DBUG_ENTER("mysql_rename_tables");
6467

6568
/*
@@ -162,6 +165,15 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
162165
An exclusive lock on table names is satisfactory to ensure
163166
no other thread accesses this table.
164167
*/
168+
169+
/*
170+
Do not emit errors which may occur during the sequence of renamings,
171+
because they will raise the 'thd->is_error()' flag, and `ddl_log_revert()`
172+
may fail due to this. Instead, capture and collect errors and warnings
173+
and emit them only after the renaming and possible reversion are complete.
174+
*/
175+
thd->push_internal_handler(&postponed_handler);
176+
165177
error= rename_tables(thd, table_list, &ddl_log_state,
166178
0, if_exists, &force_if_exists);
167179

@@ -192,15 +204,18 @@ bool mysql_rename_tables(THD *thd, TABLE_LIST *table_list, bool silent,
192204
my_ok(thd);
193205
}
194206

207+
thd->pop_internal_handler();
195208
if (likely(!error))
196209
{
197210
query_cache_invalidate3(thd, table_list, 0);
198211
ddl_log_complete(&ddl_log_state);
212+
DBUG_ASSERT(!postponed_handler.has_errors());
199213
}
200214
else
201215
{
202216
/* Revert the renames of normal tables with the help of the ddl log */
203217
ddl_log_revert(thd, &ddl_log_state);
218+
postponed_handler.emit_errors();
204219
}
205220

206221
err:

0 commit comments

Comments
 (0)