From 09f6306ac31854dab52dcf8b5dc043acf82c96e6 Mon Sep 17 00:00:00 2001
From: "gshchepa/uchum@gleb.loc" <>
Date: Tue, 9 Oct 2007 01:07:15 +0500
Subject: [PATCH] Fixed bug #31310. Locked rows of the InnoDB storage was
 silently skipped in the read-committed isolation level.

QUICK_RANGE_SELECT for unique ranges lacks second (blocking) read
of the record that was read semi-consistently and just skip it.

The handler::read_multi_range_next method has been modified
to retry previous unique range if the previous read was
semi-consistent.
---
 mysql-test/include/mix1.inc      | 125 +++++++++++++++++++++++++++++++
 mysql-test/r/innodb_mysql.result |  80 ++++++++++++++++++++
 sql/handler.cc                   |   8 +-
 3 files changed, 210 insertions(+), 3 deletions(-)

diff --git a/mysql-test/include/mix1.inc b/mysql-test/include/mix1.inc
index e2a22303d9d..b88e3a3cde5 100644
--- a/mysql-test/include/mix1.inc
+++ b/mysql-test/include/mix1.inc
@@ -29,6 +29,7 @@ eval SET SESSION STORAGE_ENGINE = $engine_type;
 
 --disable_warnings
 drop table if exists t1,t2,t3,t1m,t1i,t2m,t2i,t4;
+drop procedure if exists p1;
 --enable_warnings
 
 
@@ -1146,4 +1147,128 @@ select @b:=f2 from t1;
 select if(@a=@b,"ok","wrong");
 drop table t1;
 
+#
+# Bug #31310: Locked rows silently skipped in read-committed isolation level.
+#
+
+connect (con1,localhost,root,,);
+connect (con2,localhost,root,,);
+SET SESSION AUTOCOMMIT = 0;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+--echo # Switch to connection con1
+connection con1;
+
+eval
+CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(256))
+ENGINE = $engine_type;
+INSERT INTO t1 VALUES (1,2);
+
+--#echo 1. test for locking:
+
+BEGIN;
+--enable_info
+UPDATE t1 SET b = 12 WHERE a = 1;
+--disable_info
+SELECT * FROM t1;
+
+--echo # Switch to connection con2
+connection con2;
+
+--enable_info
+--disable_abort_on_error
+--error ER_LOCK_WAIT_TIMEOUT
+UPDATE t1 SET b = 21 WHERE a = 1;
+--disable_info
+
+--echo # Switch to connection con1
+connection con1;
+SELECT * FROM t1;
+ROLLBACK;
+
+--echo # 2. test for serialized update:
+
+CREATE TABLE t2 (a INT);
+
+TRUNCATE t1;
+INSERT INTO t1 VALUES (1,'init');
+
+DELIMITER |;
+CREATE PROCEDURE p1()
+BEGIN
+  UPDATE t1 SET b = CONCAT(b, '+con2')  WHERE a = 1;
+  INSERT INTO t2 VALUES ();
+END|
+DELIMITER ;|
+
+BEGIN;
+--enable_info
+UPDATE t1 SET b = CONCAT(b, '+con1') WHERE a = 1;
+--disable_info
+SELECT * FROM t1;
+
+--echo # Switch to connection con2
+connection con2;
+
+--send CALL p1;
+
+--echo # Switch to connection con1
+connection con1;
+SELECT * FROM t1;
+COMMIT;
+
+let $bug31310 = 1;
+while ($bug31310)
+{
+  let $bug31310= `SELECT 1 - COUNT(*) FROM t2`;
+}
+
+SELECT * FROM t1;
+
+--echo # Switch to connection con2
+connection con2;
+SELECT * FROM t1;
+
+--echo # Switch to connection con1
+connection con1;
+
+--echo # 3. test for updated key column:
+
+TRUNCATE t1;
+TRUNCATE t2;
+
+INSERT INTO t1 VALUES (1,'init');
+
+BEGIN;
+--enable_info
+UPDATE t1 SET a = 2, b = CONCAT(b, '+con1') WHERE a = 1;
+--disable_info
+SELECT * FROM t1;
+
+--echo # Switch to connection con2
+connection con2;
+
+--send CALL p1;
+
+--echo # Switch to connection con1
+connection con1;
+SELECT * FROM t1;
+COMMIT;
+
+let $bug31310 = 1;
+while ($bug31310)
+{
+  let $bug31310= `SELECT 1 - COUNT(*) FROM t2`;
+}
+
+SELECT * FROM t1;
+
+--echo # Switch to connection con2
+connection con2;
+SELECT * FROM t1;
+
+connection default;
+disconnect con1;
+disconnect con2;
+DROP PROCEDURE p1;
+DROP TABLE t1, t2;
 --echo End of 5.1 tests
diff --git a/mysql-test/r/innodb_mysql.result b/mysql-test/r/innodb_mysql.result
index 0757936a8f7..0b3c1d33536 100644
--- a/mysql-test/r/innodb_mysql.result
+++ b/mysql-test/r/innodb_mysql.result
@@ -1,5 +1,6 @@
 SET SESSION STORAGE_ENGINE = InnoDB;
 drop table if exists t1,t2,t3,t1m,t1i,t2m,t2i,t4;
+drop procedure if exists p1;
 create table t1 (
 c_id int(11) not null default '0',
 org_id int(11) default null,
@@ -1419,4 +1420,83 @@ select if(@a=@b,"ok","wrong");
 if(@a=@b,"ok","wrong")
 ok
 drop table t1;
+SET SESSION AUTOCOMMIT = 0;
+SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
+# Switch to connection con1
+CREATE TABLE t1 (a INT PRIMARY KEY, b VARCHAR(256))
+ENGINE = InnoDB;
+INSERT INTO t1 VALUES (1,2);
+BEGIN;
+UPDATE t1 SET b = 12 WHERE a = 1;
+affected rows: 1
+info: Rows matched: 1  Changed: 1  Warnings: 0
+SELECT * FROM t1;
+a	b
+1	12
+# Switch to connection con2
+UPDATE t1 SET b = 21 WHERE a = 1;
+ERROR HY000: Lock wait timeout exceeded; try restarting transaction
+# Switch to connection con1
+SELECT * FROM t1;
+a	b
+1	12
+ROLLBACK;
+# 2. test for serialized update:
+CREATE TABLE t2 (a INT);
+TRUNCATE t1;
+INSERT INTO t1 VALUES (1,'init');
+CREATE PROCEDURE p1()
+BEGIN
+UPDATE t1 SET b = CONCAT(b, '+con2')  WHERE a = 1;
+INSERT INTO t2 VALUES ();
+END|
+BEGIN;
+UPDATE t1 SET b = CONCAT(b, '+con1') WHERE a = 1;
+affected rows: 1
+info: Rows matched: 1  Changed: 1  Warnings: 0
+SELECT * FROM t1;
+a	b
+1	init+con1
+# Switch to connection con2
+CALL p1;;
+# Switch to connection con1
+SELECT * FROM t1;
+a	b
+1	init+con1
+COMMIT;
+SELECT * FROM t1;
+a	b
+1	init+con1
+# Switch to connection con2
+SELECT * FROM t1;
+a	b
+1	init+con1+con2
+# Switch to connection con1
+# 3. test for updated key column:
+TRUNCATE t1;
+TRUNCATE t2;
+INSERT INTO t1 VALUES (1,'init');
+BEGIN;
+UPDATE t1 SET a = 2, b = CONCAT(b, '+con1') WHERE a = 1;
+affected rows: 1
+info: Rows matched: 1  Changed: 1  Warnings: 0
+SELECT * FROM t1;
+a	b
+2	init+con1
+# Switch to connection con2
+CALL p1;;
+# Switch to connection con1
+SELECT * FROM t1;
+a	b
+2	init+con1
+COMMIT;
+SELECT * FROM t1;
+a	b
+2	init+con1
+# Switch to connection con2
+SELECT * FROM t1;
+a	b
+2	init+con1
+DROP PROCEDURE p1;
+DROP TABLE t1, t2;
 End of 5.1 tests
diff --git a/sql/handler.cc b/sql/handler.cc
index 75c3a64bc27..3939873ec7d 100644
--- a/sql/handler.cc
+++ b/sql/handler.cc
@@ -3145,6 +3145,8 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p)
     }
     else
     {
+      if (was_semi_consistent_read())
+        goto scan_it_again;
       /*
         We need to set this for the last range only, but checking this
         condition is more expensive than just setting the result code.
@@ -3152,10 +3154,10 @@ int handler::read_multi_range_next(KEY_MULTI_RANGE **found_range_p)
       result= HA_ERR_END_OF_FILE;
     }
 
+    multi_range_curr++;
+scan_it_again:
     /* Try the next range(s) until one matches a record. */
-    for (multi_range_curr++;
-         multi_range_curr < multi_range_end;
-         multi_range_curr++)
+    for (; multi_range_curr < multi_range_end; multi_range_curr++)
     {
       result= read_range_first(multi_range_curr->start_key.keypart_map ?
                                &multi_range_curr->start_key : 0,
-- 
2.30.9