MDEV-38796 Refactor: clean up THD::decide_logging_format() checks#5136
MDEV-38796 Refactor: clean up THD::decide_logging_format() checks#5136bnestere wants to merge 5 commits into
Conversation
Test binlog_spurious_ddl_errors is updated here because now decide_logging_format doesn't error out when it can't log in statement format, but just switches to row (aa4b22c). @@ -7259,7 +7262,15 @@ int THD::decide_logging_format(TABLE_LIST *tables) wsrep_cs().mode() == wsrep::client_state::m_local),1)) { - my_error((error= ER_BINLOG_STMT_MODE_AND_ROW_ENGINE), MYF(0), ""); + if (is_current_stmt_binlog_format_stmt()) + { + my_printf_error(ER_BINLOG_STMT_MODE_AND_ROW_ENGINE, + "Statement cannot be logged as STATEMENT as at " + "least one table is limited to row-based logging." + " Switching temporarly to row format", + MYF(ME_NOTE)); + set_current_stmt_binlog_format_row(); + } }
There was a problem hiding this comment.
Code Review
This pull request introduces Global Temporary Tables (GTT) support, implementing the syntax, lifecycle, transaction integration, and replication/binlogging adjustments. Review feedback highlights several critical issues: a debug fprintf statement was left active in thd_binlog_filter_ok(); the in-use check in drop_tmp_table_share() is bypassed when table is NULL; committed in All_tmp_tables_list and binlog_state in THD are uninitialized; thd->query() can return NULL in log_write_error(), potentially causing a crash; and table_ref->table should be passed to drop_tmp_table_share() during truncate table operations instead of NULL.
| bool new_value= !(thd->binlog_state & BINLOG_STATE_FILTER); | ||
| bool old_value= binlog_filter->db_ok(thd->db.str); | ||
| //if (filter_on != old_value_on) | ||
| fprintf(stderr, | ||
| "\n\tDB %s thd_binlog_filter return\n\t\tbinlog_state: %d " | ||
| "\n\t\tlegacy: %d\n", | ||
| thd->db.str, new_value, old_value); | ||
| return new_value; |
| if (table) | ||
| { | ||
| if (tab != table && tab->query_id != 0) | ||
| /* Table might be in use by some outer statement. */ | ||
| All_share_tables_list::Iterator it(share->all_tmp_tables); | ||
| while ((tab= it++)) | ||
| { | ||
| /* Found a table instance in use. This table cannot be be dropped. */ | ||
| my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); | ||
| result= true; | ||
| goto end; | ||
| if (tab != table && tab->query_id != 0) | ||
| { | ||
| /* Found a table instance in use. This table cannot be dropped. */ | ||
| my_error(ER_CANT_REOPEN_TABLE, MYF(0), table->alias.c_ptr()); | ||
| result= true; | ||
| goto end; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
If table is NULL (which happens when called from global_tmp_drop_database or truncate_table), the check for active table instances in use is completely bypassed because of the if (table) guard. This can lead to use-after-free or crashes if any of the global temporary table instances are currently in use by an outer query. The check should be performed regardless of whether table is NULL or not, and if table is NULL, we should check all instances.
/* Table might be in use by some outer statement. */
All_share_tables_list::Iterator it(share->all_tmp_tables);
while ((tab= it++))
{
if (tab != table && tab->query_id != 0)
{
/* Found a table instance in use. This table cannot be dropped. */
my_error(ER_CANT_REOPEN_TABLE, MYF(0),
table ? table->alias.c_ptr() : share->table_name.str);
result= true;
goto end;
}
}| I_P_List_null_counter, | ||
| I_P_List_fast_push_back<TMP_TABLE_SHARE>> | ||
| { | ||
| bool committed; |
|
|
||
| void log_write_error(THD *thd, const char *msg) | ||
| { | ||
| my_error(ER_BINLOG_LOGGING_IMPOSSIBLE, MYF(ME_ERROR_LOG), msg, thd->query()); |
There was a problem hiding this comment.
thd->query() can return NULL if there is no active query (e.g., in background or replication threads). Passing NULL to %s in my_error can lead to undefined behavior or crashes on some platforms. It should be guarded with a null check.
my_error(ER_BINLOG_LOGGING_IMPOSSIBLE, MYF(ME_ERROR_LOG), msg, thd->query() ? thd->query() : "");| { | ||
| TABLE_SHARE *s= table_ref->table->s; | ||
| DBUG_ASSERT(s->tmp_table); | ||
| error= thd->drop_tmp_table_share(NULL, (TMP_TABLE_SHARE *)s, true); |
There was a problem hiding this comment.
| Describe the current state of binary logging for the current statement. | ||
| Includes state of binary logging and also what disables it | ||
| */ | ||
| enum_binlog_state binlog_state; |
Addresses TODO
- Update binlog_state with BINLOG_STATE_FILTER only when database or filter
is changed. This means we do not have to call binlog_filter->db_ok() for
every statement in decide_binlog_format().
TODO Addressed:
- Remove clear/reset xxx_binlog_local_stmt_filter() as this is now handled
by binlog_state.
TODO addressed
- Remove 'silent' option from mysql_create_db_internal and instead use
tmp_disable_binlog() / reenable_binlog()
Addresses TODO
- Remove all testing of mysql_bin_log.is_open(). Instead test for
binlog_ready() in main code and add testing of is_open() when
trying to commit the binary log. This is needed as currently
mysql_bin_log.is_open() is tested without a mutex which makes
it unreliable.
And also adds tests for the binlog closing for various types of
statements in-between a thd->binlog_ready() check and the commit-time
acquiring of mysql_bin_log.LOCK_log.
Still TODO from the above request:
* WSREP related mysql_bin_log.is_open() changes as a part of MDEV-38865
PR is ongoing & draft for now, making this a PR so I can get intermittent BB results.