MDEV-35207 ignored error at binlogging by CREATE-TABLE-SELECT leads to assert

MDEV-35499 Errored-out CREATE-or-REPLACE-SELECT does not log DROP table into binlog
MDEV-35502 Failed at ROW-format binlogging CREATE-TABLE-SELECT should
           not generate Incident event

When a CREATE TABLE .. SELECT errors while inserting data, a user
would expect that all changes are rolled back
and the table would not exist after executing the query.

However CREATE-TABLE-SELECT can face an error near the end of its execution
select_create::send_eof() so that the error was never checked which
led to various assert inside binlogging path that should not be
attended at all.
Specifically when binlog_commit() of ha_commit_one_phase() that
CREATE-TABLE-SELECT employs errored out because of a limited cache size
(binlog_commit may try writing to a transactional cache) the cache
was not flushed to binlog. The missed error check allowed further
execution down to trans_commit_implicit() in whose stack

  DBUG_ASSERT(!(entry->using_trx_cache && !mngr->trx_cache.empty() &&
                mngr->get_binlog_cache_log(TRUE)->error));

fired. In a non-debug build that table remains created/populated
inconsistently with binlog.

The fixes need and install the error checking in select_create::send_eof().
That prevents from any further execution when ha_commit_one_phase() fails
for any reason (typically due to binlog_commit()).

This commit also covers CREATE-or-REPLACE-SELECT that additionally had
a specific issue in that DROP TABLE was not logged the binary log, MDEV-35499.
See changes select_create::abort_result_set().

The current commit also corrects an unnecessary Incident event
logging when CREATE-TABLE-SELECT encounters a binloging issue, MDEV-35502.
The Incident was actually only harmful in this case as the table was
never going to be created, therefore replicated, in such a case.
In "normal" cases when the SELECT phase errors due to binlogging, an
internal incident flag gets reset inside select_create::abort_result_set().

A hunk in select_insert::prepare_eof() addresses a specific kind of
this issue that deals with incorrect computation of the binlog cache type.
Because of that in the OLD version execution was allowed to proceed along
ha_commit_trans()..binlog_commit() while a Pending event was not
flushed to the transactional cache. That might lead to the unnecessary
binlogged Incident despite the select_create::abort_result_set()
measures. However now with the corrected cache type any binlogging error
to flush the Pending event is covered according to the normal case.
non-transaction table, updates to the non-transactional table

NOTE the commit contains few tests overlapping with unfixed yet MDEV-36027.

Thanks to Brandon Nesterenko and Kristian Nielsen for thorough review,
and Kristian additionally for ideas to simplify the patch and some
code contribution.
This commit is contained in:
Andrei Elkin 2024-11-25 19:48:23 +02:00
commit 1b4efbeb8c
5 changed files with 374 additions and 7 deletions

View file

@ -4349,7 +4349,11 @@ bool select_insert::store_values(List<Item> &values)
bool select_insert::prepare_eof()
{
int error;
bool const trans_table= table->file->has_transactions_and_rollback();
// make sure any ROW format pending event is logged in the same binlog cache
bool const trans_table= (thd->is_current_stmt_binlog_format_row() &&
table->file->row_logging) ?
table->file->row_logging_has_trans :
table->file->has_transactions_and_rollback();
bool changed;
bool binary_logged= 0;
killed_state killed_status= thd->killed;
@ -4574,7 +4578,8 @@ void select_insert::abort_result_set()
query_cache_invalidate3(thd, table, 1);
}
DBUG_ASSERT(transactional_table || !changed ||
thd->transaction->stmt.modified_non_trans_table);
(thd->transaction->stmt.modified_non_trans_table ||
thd->transaction->all.modified_non_trans_table));
table->s->table_creation_was_logged|= binary_logged;
table->file->ha_release_auto_increment();
@ -5267,9 +5272,14 @@ bool select_create::send_eof()
/* Remember xid's for the case of row based logging */
ddl_log_update_xid(&ddl_log_state_create, thd->binlog_xid);
ddl_log_update_xid(&ddl_log_state_rm, thd->binlog_xid);
trans_commit_stmt(thd);
if (!(thd->variables.option_bits & OPTION_GTID_BEGIN))
trans_commit_implicit(thd);
if (trans_commit_stmt(thd) ||
(!(thd->variables.option_bits & OPTION_GTID_BEGIN) &&
trans_commit_implicit(thd)))
{
abort_result_set();
DBUG_RETURN(true);
}
thd->binlog_xid= 0;
#ifdef WITH_WSREP
@ -5389,7 +5399,13 @@ void select_create::abort_result_set()
/* possible error of writing binary log is ignored deliberately */
(void) thd->binlog_flush_pending_rows_event(TRUE, TRUE);
/*
In the error case, we remove any partially created table. So clear any
incident event generates due to cache error, as it no longer relevant.
*/
binlog_clear_incident(thd);
bool drop_table_was_logged= false;
if (table)
{
bool tmp_table= table->s->tmp_table;
@ -5436,6 +5452,7 @@ void select_create::abort_result_set()
create_info->db_type == partition_hton,
&create_info->tabledef_version,
tmp_table);
drop_table_was_logged= true;
debug_crash_here("ddl_log_create_after_binlog");
thd->binlog_xid= 0;
}
@ -5460,8 +5477,21 @@ void select_create::abort_result_set()
if (create_info->table_was_deleted)
{
/* Unlock locked table that was dropped by CREATE. */
(void) trans_rollback_stmt(thd);
if (drop_table_was_logged)
{
/* for DROP binlogging the error status has to be canceled first */
Diagnostics_area new_stmt_da(thd->query_id, false, true);
Diagnostics_area *old_stmt_da= thd->get_stmt_da();
thd->set_stmt_da(&new_stmt_da);
(void) trans_rollback_stmt(thd);
thd->set_stmt_da(old_stmt_da);
}
else
{
/* Unlock locked table that was dropped by CREATE. */
(void) trans_rollback_stmt(thd);
}
thd->locked_tables_list.unlock_locked_table(thd, create_info->mdl_ticket);
}