The Write_rows_log_event originally allocated the m_rows_buf up-front, and
thus is_valid() checks that the buffer is allocated correctly. But at some
point this was changed to allocate the buffer lazily on demand. This means
that a a valid event can now have m_rows_buf==NULL. The is_valid() code was
not changed, and thus is_valid() could return false on a valid event.
This caused a bug for REPLACE INTO t() VALUES(), () which generates a
write_rows event with no after image; then the m_rows_buf was never
allocated and is_valid() incorrectly returned false, causing an error in
some other parts of the code.
Also fix a couple of missing special cases in the code for mysqlbinlog to
correctly decode (in comments) row events with missing after image.
Signed-off-by: Kristian Nielsen <knielsen(a)knielsen-hq.org>
---
mysql-test/main/mysqlbinlog.result | 18 ++++++++++++
mysql-test/main/mysqlbinlog.test | 45 ++++++++++++++++++++++++++++++
sql/log_event.h | 2 +-
sql/log_event_client.cc | 21 ++++++++++++--
4 files changed, 82 insertions(+), 4 deletions(-)
diff --git a/mysql-test/main/mysqlbinlog.result b/mysql-test/main/mysqlbinlog.result
index 4e0f570c899..5bde2b05a98 100644
--- a/mysql-test/main/mysqlbinlog.result
+++ b/mysql-test/main/mysqlbinlog.result
@@ -1314,3 +1314,21 @@ a b
2 2023-07-22 00:36:20.567890
DROP TABLE t;
SET time_zone= default;
+#
+# MDEV-24959: ER_BINLOG_ROW_LOGGING_FAILED (1534: Writing one row to the row-based binary log failed)
+#
+SET SESSION binlog_format= ROW;
+SET SESSION binlog_row_image= MINIMAL;
+RESET MASTER;
+CREATE TABLE t1 (a INT NOT NULL DEFAULT 0 PRIMARY KEY);
+REPLACE INTO t1 () VALUES (),();
+DROP TABLE t1;
+FLUSH BINARY LOGS;
+SET SESSION binlog_format= STATEMENT;
+SET SESSION binlog_row_image= default;
+FOUND 1 /Number of rows: 2/ in mdev24959_1.txt
+FOUND 1 /DROP TABLE/ in mdev24959_1.txt
+FOUND 1 /Number of rows: 2/ in mdev24959_2.txt
+FOUND 1 /DROP TABLE/ in mdev24959_2.txt
+FOUND 1 /INSERT INTO .* VALUES/ in mdev24959_2.txt
+FOUND 1 /SET /[*] no columns [*]// in mdev24959_2.txt
diff --git a/mysql-test/main/mysqlbinlog.test b/mysql-test/main/mysqlbinlog.test
index 0e5fd9efb70..ceb6ff2eee6 100644
--- a/mysql-test/main/mysqlbinlog.test
+++ b/mysql-test/main/mysqlbinlog.test
@@ -671,3 +671,48 @@ SELECT * FROM t;
SELECT * FROM t;
DROP TABLE t;
SET time_zone= default;
+
+
+--echo #
+--echo # MDEV-24959: ER_BINLOG_ROW_LOGGING_FAILED (1534: Writing one row to the row-based binary log failed)
+--echo #
+
+SET SESSION binlog_format= ROW;
+SET SESSION binlog_row_image= MINIMAL;
+
+RESET MASTER;
+CREATE TABLE t1 (a INT NOT NULL DEFAULT 0 PRIMARY KEY);
+REPLACE INTO t1 () VALUES (),();
+DROP TABLE t1;
+FLUSH BINARY LOGS;
+SET SESSION binlog_format= STATEMENT;
+SET SESSION binlog_row_image= default;
+
+--exec $MYSQL_BINLOG --base64-output=decode-rows $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mdev24959_1.txt
+--exec $MYSQL_BINLOG --base64-output=decode-rows --verbose $MYSQLD_DATADIR/master-bin.000001 > $MYSQLTEST_VARDIR/tmp/mdev24959_2.txt
+
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mdev24959_1.txt
+--let SEARCH_ABORT= NOT FOUND
+--let SEARCH_PATTERN= Number of rows: 2
+--source include/search_pattern_in_file.inc
+
+# There was a bug that mysqlbinlog would get an error while decoding the
+# update rows event with no after image and abort the dump; test that now
+# the dump is complete and includes the final DROP TABLE.
+--let SEARCH_PATTERN= DROP TABLE
+--source include/search_pattern_in_file.inc
+
+--let SEARCH_FILE= $MYSQLTEST_VARDIR/tmp/mdev24959_2.txt
+--let SEARCH_PATTERN= Number of rows: 2
+--source include/search_pattern_in_file.inc
+
+--let SEARCH_PATTERN= DROP TABLE
+--source include/search_pattern_in_file.inc
+
+--let SEARCH_PATTERN= INSERT INTO .* VALUES
+--source include/search_pattern_in_file.inc
+--let SEARCH_PATTERN= SET /[*] no columns [*]/
+--source include/search_pattern_in_file.inc
+
+--remove_file $MYSQLTEST_VARDIR/tmp/mdev24959_1.txt
+--remove_file $MYSQLTEST_VARDIR/tmp/mdev24959_2.txt
diff --git a/sql/log_event.h b/sql/log_event.h
index a869c6e04f1..0a1d6502932 100644
--- a/sql/log_event.h
+++ b/sql/log_event.h
@@ -5086,7 +5086,7 @@ class Rows_log_event : public Log_event
*/
bool is_valid() const override
{
- return m_rows_buf && m_cols.bitmap;
+ return m_cols.bitmap;
}
uint m_row_count; /* The number of rows added to the event */
diff --git a/sql/log_event_client.cc b/sql/log_event_client.cc
index 720cc5ab611..a04776e6404 100644
--- a/sql/log_event_client.cc
+++ b/sql/log_event_client.cc
@@ -1384,6 +1384,13 @@ void Rows_log_event::count_row_events(PRINT_EVENT_INFO *print_event_info)
switch (general_type_code) {
case WRITE_ROWS_EVENT:
+ /*
+ A write rows event containing no after image (can happen for REPLACE
+ INTO t() VALUES ()), count this correctly as 1 row and no 0.
+ */
+ if (unlikely(m_rows_buf == m_rows_end))
+ print_event_info->row_events++;
+ /* Fall through. */
case DELETE_ROWS_EVENT:
row_events= 1;
break;
@@ -1509,6 +1516,7 @@ bool Rows_log_event::print_verbose(IO_CACHE *file,
/* If the write rows event contained no values for the AI */
if (((general_type_code == WRITE_ROWS_EVENT) && (m_rows_buf==m_rows_end)))
{
+ print_event_info->row_events++;
if (my_b_printf(file, "### INSERT INTO %`s.%`s VALUES ()\n",
map->get_db_name(), map->get_table_name()))
goto err;
@@ -1542,9 +1550,16 @@ bool Rows_log_event::print_verbose(IO_CACHE *file,
/* Print the second image (for UPDATE only) */
if (sql_clause2)
{
- if (!(length= print_verbose_one_row(file, td, print_event_info,
- &m_cols_ai, value,
- (const uchar*) sql_clause2)))
+ /* If the update rows event contained no values for the AI */
+ if (unlikely(bitmap_is_clear_all(&m_cols_ai)))
+ {
+ length= (bitmap_bits_set(&m_cols_ai) + 7) / 8;
+ if (my_b_printf(file, "### SET /* no columns */\n"))
+ goto err;
+ }
+ else if (!(length= print_verbose_one_row(file, td, print_event_info,
+ &m_cols_ai, value,
+ (const uchar*) sql_clause2)))
goto err;
value+= length;
}
--
2.39.5