From 8b1609a63717227b8931bb3a7fdaf45a43b831c7 Mon Sep 17 00:00:00 2001
From: "guilhem@gbichot3.local" <>
Date: Thu, 15 Feb 2007 15:39:03 +0100
Subject: [PATCH] Fix for BUG#25507 "multi-row insert delayed + auto increment
 causes duplicate key entries on slave" (two concurrrent connections doing
 multi-row INSERT DELAYED to insert into an auto_increment column, caused
 replication slave to stop with "duplicate key error" (and binlog was wrong)),
 and BUG#26116 "If multi-row INSERT DELAYED has errors, statement-based
 binlogging breaks" (the binlog was not accounting for all rows inserted, or
 slave could stop). The fix is that: if (statement-based) binlogging is on, a
 multi-row INSERT DELAYED is silently converted to a non-delayed INSERT. Note:
 it is not possible to test BUG#25507 in 5.0 (requires mysqlslap), so it is
 tested only in the changeset for 5.1. However, BUG#26116 is tested here, and
 the fix for BUG#25507 is the same code change.

---
 mysql-test/r/innodb-replace.result     |  4 +-
 mysql-test/r/rpl_insert_delayed.result | 31 ++++++++++++
 mysql-test/t/innodb-replace.test       |  4 +-
 mysql-test/t/rpl_insert_delayed.test   | 67 ++++++++++++++++++++++++++
 sql/sql_insert.cc                      | 21 ++++++++
 5 files changed, 123 insertions(+), 4 deletions(-)
 create mode 100644 mysql-test/r/rpl_insert_delayed.result
 create mode 100644 mysql-test/t/rpl_insert_delayed.test

diff --git a/mysql-test/r/innodb-replace.result b/mysql-test/r/innodb-replace.result
index b7edcc49e5..77e0aeb38f 100644
--- a/mysql-test/r/innodb-replace.result
+++ b/mysql-test/r/innodb-replace.result
@@ -2,11 +2,11 @@ drop table if exists t1;
 create table t1 (c1 char(5) unique not null, c2 int, stamp timestamp) engine=innodb;
 select * from t1;
 c1	c2	stamp
-replace delayed into t1 (c1, c2)  values ( "text1","11"),( "text2","12");
+replace delayed into t1 (c1, c2)  values ( "text1","11");
 ERROR HY000: Table storage engine for 't1' doesn't have this option
 select * from t1;
 c1	c2	stamp
-replace delayed into t1 (c1, c2)  values ( "text1","12"),( "text2","13"),( "text3","14", "a" ),( "text4","15", "b" );
+replace delayed into t1 (c1, c2)  values ( "text1","12");
 ERROR HY000: Table storage engine for 't1' doesn't have this option
 select * from t1;
 c1	c2	stamp
diff --git a/mysql-test/r/rpl_insert_delayed.result b/mysql-test/r/rpl_insert_delayed.result
new file mode 100644
index 0000000000..38e2cddd65
--- /dev/null
+++ b/mysql-test/r/rpl_insert_delayed.result
@@ -0,0 +1,31 @@
+stop slave;
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
+reset master;
+reset slave;
+drop table if exists t1,t2,t3,t4,t5,t6,t7,t8,t9;
+start slave;
+CREATE TABLE t1 (id INT primary key auto_increment, name VARCHAR(64));
+truncate table t1;
+insert delayed into t1 values(10, "my name");
+insert delayed into t1 values(10, "is Bond"), (20, "James Bond");
+ERROR 23000: Duplicate entry '10' for key 1
+flush table t1;
+select * from t1;
+id	name
+10	my name
+select * from t1;
+id	name
+10	my name
+delete from t1 where id!=10;
+insert delayed into t1 values(20, "is Bond"), (10, "James Bond");
+ERROR 23000: Duplicate entry '10' for key 1
+flush table t1;
+select * from t1;
+id	name
+10	my name
+20	is Bond
+select * from t1;
+id	name
+10	my name
+20	is Bond
+drop table t1;
diff --git a/mysql-test/t/innodb-replace.test b/mysql-test/t/innodb-replace.test
index 51b70f34b6..d44ede65ce 100644
--- a/mysql-test/t/innodb-replace.test
+++ b/mysql-test/t/innodb-replace.test
@@ -12,10 +12,10 @@ drop table if exists t1;
 create table t1 (c1 char(5) unique not null, c2 int, stamp timestamp) engine=innodb;
 select * from t1;
 --error 1031
-replace delayed into t1 (c1, c2)  values ( "text1","11"),( "text2","12");
+replace delayed into t1 (c1, c2)  values ( "text1","11");
 select * from t1;
 --error 1031
-replace delayed into t1 (c1, c2)  values ( "text1","12"),( "text2","13"),( "text3","14", "a" ),( "text4","15", "b" );
+replace delayed into t1 (c1, c2)  values ( "text1","12");
 select * from t1;
 drop table t1;
 
diff --git a/mysql-test/t/rpl_insert_delayed.test b/mysql-test/t/rpl_insert_delayed.test
new file mode 100644
index 0000000000..3f72f3a362
--- /dev/null
+++ b/mysql-test/t/rpl_insert_delayed.test
@@ -0,0 +1,67 @@
+--source include/master-slave.inc
+--source include/not_embedded.inc
+--source include/not_windows.inc
+
+connection master;
+
+let $binlog_format_statement=1;
+
+CREATE TABLE t1 (id INT primary key auto_increment, name VARCHAR(64));
+
+sync_slave_with_master;
+
+#
+# BUG#26116 "If multi-row INSERT DELAYED has errors,
+# statement-based binlogging breaks";
+# happened only in statement-based binlogging.
+#
+
+connection master;
+truncate table t1;
+# first scenario: duplicate on first row
+insert delayed into t1 values(10, "my name");
+if ($binlog_format_statement)
+{
+  # statement below will be converted to non-delayed INSERT and so
+  # will stop at first error, guaranteeing replication.
+  --error ER_DUP_ENTRY
+  insert delayed into t1 values(10, "is Bond"), (20, "James Bond");
+}
+if (!$binlog_format_statement)
+{
+  insert delayed into t1 values(10, "is Bond"), (20, "James Bond");
+}
+flush table t1; # to wait for INSERT DELAYED to be done
+select * from t1;
+sync_slave_with_master;
+# when bug existed in statement-based binlogging, t1 on slave had
+# different content from on master
+select * from t1;
+
+# second scenario: duplicate on second row
+connection master;
+delete from t1 where id!=10;
+if ($binlog_format_statement)
+{
+  # statement below will be converted to non-delayed INSERT and so
+  # will be binlogged with its ER_DUP_ENTRY error code, guaranteeing
+  # replication (slave will hit the same error code and so be fine).
+  --error ER_DUP_ENTRY
+  insert delayed into t1 values(20, "is Bond"), (10, "James Bond");
+}
+if (!$binlog_format_statement)
+{
+  insert delayed into t1 values(20, "is Bond"), (10, "James Bond");
+}
+flush table t1; # to wait for INSERT DELAYED to be done
+select * from t1;
+sync_slave_with_master;
+# when bug existed in statement-based binlogging, query was binlogged
+# with error_code=0 so slave stopped
+select * from t1;
+
+# clean up
+connection master;
+drop table t1;
+sync_slave_with_master;
+connection master;
diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc
index c5f1524c55..f0708ee548 100644
--- a/sql/sql_insert.cc
+++ b/sql/sql_insert.cc
@@ -356,6 +356,27 @@ bool mysql_insert(THD *thd,TABLE_LIST *table_list,
       (duplic == DUP_UPDATE))
     lock_type=TL_WRITE;
 #endif
+  if ((lock_type == TL_WRITE_DELAYED) &&
+      log_on && mysql_bin_log.is_open() &&
+      (values_list.elements > 1))
+  {
+    /*
+      Statement-based binary logging does not work in this case, because:
+      a) two concurrent statements may have their rows intermixed in the
+      queue, leading to autoincrement replication problems on slave (because
+      the values generated used for one statement don't depend only on the
+      value generated for the first row of this statement, so are not
+      replicable)
+      b) if first row of the statement has an error the full statement is
+      not binlogged, while next rows of the statement may be inserted.
+      c) if first row succeeds, statement is binlogged immediately with a
+      zero error code (i.e. "no error"), if then second row fails, query
+      will fail on slave too and slave will stop (wrongly believing that the
+      master got no error).
+      So we fallback to non-delayed INSERT.
+    */
+    lock_type= TL_WRITE;
+  }
   table_list->lock_type= lock_type;
 
 #ifndef EMBEDDED_LIBRARY
-- 
2.30.9