From 6fd13aaa3ccc470f03c51686f781651c9709154e Mon Sep 17 00:00:00 2001
From: unknown <jan@hundin.mysql.fi>
Date: Fri, 22 Jul 2005 14:10:03 +0300
Subject: [PATCH] Implement MySQL framework to support consistent read views in
 cursors for InnoDB. The idea of the patch is that if MySQL requests a
 consistent read view, we open one when open a cursor, set is as the active
 view to a transaction when fetch from the cursor, and close together with
 cursor close. This patch is associated to bugs #11813, #11832, and #11833.
 Contains after review fixes.

---
 innobase/include/read0read.h  |  40 ++++++++++
 innobase/include/read0types.h |   1 +
 innobase/include/trx0trx.h    |  15 +++-
 innobase/read/read0read.c     | 141 +++++++++++++++++++++++++++++++++-
 innobase/row/row0sel.c        |   8 +-
 innobase/trx/trx0trx.c        |  28 +++++--
 sql/ha_innodb.cc              |  45 ++++++++++-
 sql/ha_innodb.h               |  29 +++++++
 8 files changed, 294 insertions(+), 13 deletions(-)

diff --git a/innobase/include/read0read.h b/innobase/include/read0read.h
index db6bf88809..1a7a86470a 100644
--- a/innobase/include/read0read.h
+++ b/innobase/include/read0read.h
@@ -69,6 +69,35 @@ read_view_print(
 /*============*/
 	read_view_t*	view);	/* in: read view */
 
+/*************************************************************************
+Create a consistent cursor view for mysql to be used in cursors. In this 
+consistent read view modifications done by the creating transaction or future
+transactions are not visible. */
+
+cursor_view_t*
+read_cursor_view_create_for_mysql(
+/*==============================*/
+	trx_t*		cr_trx);/* in: trx where cursor view is created */
+
+/*************************************************************************
+Close a given consistent cursor view for and restore global read view
+back to a transaction. */
+
+void
+read_cursor_view_close_for_mysql(
+/*=============================*/
+	trx_t*		trx,		/* in: trx */
+	cursor_view_t*	curview);	/* in: cursor view to be closed */
+/*************************************************************************
+This function sets a given consistent cursor view to a transaction
+read view if given consistent cursor view is not null. Otherwice, function
+restores a global read view to a transaction read view. */
+
+void 
+read_cursor_set_for_mysql(
+/*======================*/
+	trx_t*		trx,	/* in: transaction where cursor is set */
+	cursor_view_t*	curview);/* in: consistent cursor view to be set */
 
 /* Read view lists the trx ids of those transactions for which a consistent
 read should not see the modifications to the database. */
@@ -100,6 +129,17 @@ struct read_view_struct{
 				/* List of read views in trx_sys */
 };
 
+/* Implement InnoDB framework to support consistent read views in
+cursors. This struct holds both heap where consistent read view
+is allocated and pointer to a read view. */
+
+struct cursor_view_struct{
+	mem_heap_t*	heap;
+				/* Memory heap for the cursor view */
+	read_view_t*	read_view;	
+				/* Consistent read view of the cursor*/
+};
+
 #ifndef UNIV_NONINL
 #include "read0read.ic"
 #endif
diff --git a/innobase/include/read0types.h b/innobase/include/read0types.h
index 5eb3e533f8..7d42728523 100644
--- a/innobase/include/read0types.h
+++ b/innobase/include/read0types.h
@@ -10,5 +10,6 @@ Created 2/16/1997 Heikki Tuuri
 #define read0types_h
 
 typedef struct read_view_struct	read_view_t;
+typedef struct cursor_view_struct	cursor_view_t;
 
 #endif
diff --git a/innobase/include/trx0trx.h b/innobase/include/trx0trx.h
index 146730d46f..2fc4d5a289 100644
--- a/innobase/include/trx0trx.h
+++ b/innobase/include/trx0trx.h
@@ -602,8 +602,19 @@ struct trx_struct{
 	UT_LIST_BASE_NODE_T(lock_t) 
 			trx_locks;	/* locks reserved by the transaction */
 	/*------------------------------*/
-	mem_heap_t*	read_view_heap;	/* memory heap for the read view */
-	read_view_t*	read_view;	/* consistent read view or NULL */
+	mem_heap_t*	global_read_view_heap;	
+					/* memory heap for the global read 
+					view */
+	read_view_t*	global_read_view;
+					/* consistent read view used in the
+					transaction is stored here if
+					transaction is using a consistent
+					read view associated to a cursor */
+	read_view_t*	read_view;	/* consistent read view used in the
+					transaction or NULL, this read view
+					can be normal read view associated
+					to a transaction or read view
+					associated to a cursor */
 	/*------------------------------*/
 	UT_LIST_BASE_NODE_T(trx_named_savept_t) 
 			trx_savepoints;	/* savepoints set with SAVEPOINT ...,
diff --git a/innobase/read/read0read.c b/innobase/read/read0read.c
index 0c4a037508..76df7cdbee 100644
--- a/innobase/read/read0read.c
+++ b/innobase/read/read0read.c
@@ -212,15 +212,16 @@ read_view_close_for_mysql(
 /*======================*/
 	trx_t*	trx)	/* in: trx which has a read view */
 {
-	ut_a(trx->read_view);
+	ut_a(trx->global_read_view);
 
 	mutex_enter(&kernel_mutex);
 
-	read_view_close(trx->read_view);
+	read_view_close(trx->global_read_view);
 
-	mem_heap_empty(trx->read_view_heap);
+	mem_heap_empty(trx->global_read_view_heap);
 
 	trx->read_view = NULL;
+	trx->global_read_view = NULL;
 
 	mutex_exit(&kernel_mutex);
 }
@@ -258,3 +259,137 @@ read_view_print(
 			(ulong) ut_dulint_get_low(read_view_get_nth_trx_id(view, i)));
 	}
 }
+
+/*************************************************************************
+Create a consistent cursor view for mysql to be used in cursors. In this 
+consistent read view modifications done by the creating transaction or future
+transactions are not visible. */
+
+cursor_view_t*
+read_cursor_view_create_for_mysql(
+/*==============================*/
+	trx_t*	cr_trx)	/* in: trx where cursor view is created */
+{
+	cursor_view_t*	curview;
+	read_view_t*	view;
+	mem_heap_t*	heap;
+	trx_t*		trx;
+	ulint		n;
+
+	ut_a(cr_trx);
+
+	/* Use larger heap than in trx_create when creating a read_view 
+	because cursors are quite long. */
+
+	heap = mem_heap_create(512);
+
+	curview = (cursor_view_t*) mem_heap_alloc(heap, sizeof(cursor_view_t));
+	curview->heap = heap;
+
+	mutex_enter(&kernel_mutex);
+
+	curview->read_view = read_view_create_low(
+				UT_LIST_GET_LEN(trx_sys->trx_list), 
+					curview->heap);
+
+	view = curview->read_view;
+	view->creator = cr_trx;
+
+	/* No future transactions should be visible in the view */
+
+  	view->low_limit_no = trx_sys->max_trx_id;
+	view->low_limit_id = view->low_limit_no;
+
+	view->can_be_too_old = FALSE;
+
+	n = 0;
+	trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
+
+	/* No active transaction should be visible, not even cr_trx !*/
+
+	while (trx) {
+                if (trx->conc_state == TRX_ACTIVE ||
+			trx->conc_state == TRX_PREPARED) {
+
+			read_view_set_nth_trx_id(view, n, trx->id);
+
+			n++;
+
+			/* NOTE that a transaction whose trx number is <
+			trx_sys->max_trx_id can still be active, if it is
+			in the middle of its commit! Note that when a
+			transaction starts, we initialize trx->no to
+			ut_dulint_max. */
+
+			if (ut_dulint_cmp(view->low_limit_no, trx->no) > 0) {
+
+				view->low_limit_no = trx->no;
+			}	
+		}
+
+		trx = UT_LIST_GET_NEXT(trx_list, trx);
+	}
+
+	view->n_trx_ids = n;		
+
+	if (n > 0) {
+		/* The last active transaction has the smallest id: */
+		view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
+	} else {
+		view->up_limit_id = view->low_limit_id;
+	}
+
+	UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);
+	
+	mutex_exit(&kernel_mutex);
+
+	return(curview);
+}
+
+/*************************************************************************
+Close a given consistent cursor view for and restore global read view
+back to a transaction. */
+
+void
+read_cursor_view_close_for_mysql(
+/*=============================*/
+	trx_t*		trx,	/* in: trx */
+	cursor_view_t*	curview)/* in: cursor view to be closed */
+{
+	ut_a(curview);
+	ut_a(curview->read_view); 
+	ut_a(curview->heap);
+
+	mutex_enter(&kernel_mutex);
+
+	read_view_close(curview->read_view);
+	trx->read_view = trx->global_read_view;
+
+	mutex_exit(&kernel_mutex);
+
+	mem_heap_free(curview->heap);
+}
+	
+/*************************************************************************
+This function sets a given consistent cursor view to a transaction
+read view if given consistent cursor view is not null. Otherwice, function
+restores a global read view to a transaction read view. */
+
+void 
+read_cursor_set_for_mysql(
+/*======================*/
+	trx_t*		trx,	/* in: transaction where cursor is set */
+	cursor_view_t*	curview)/* in: consistent cursor view to be set */
+{
+	ut_a(trx);
+
+	mutex_enter(&kernel_mutex);
+
+	if (UNIV_LIKELY(curview != NULL)) {
+		trx->read_view = curview->read_view;
+	} else {
+		trx->read_view = trx->global_read_view;
+	}
+
+	mutex_exit(&kernel_mutex);
+}
diff --git a/innobase/row/row0sel.c b/innobase/row/row0sel.c
index 15439bed7e..602f585517 100644
--- a/innobase/row/row0sel.c
+++ b/innobase/row/row0sel.c
@@ -4083,6 +4083,11 @@ normal_return:
 	}
 
 func_exit:
+	/* Restore a global read view back to transaction. This forces
+	MySQL always to set cursor view before fetch if it is used. */
+
+	trx->read_view = trx->global_read_view;
+
 	trx->op_info = "";
 	if (UNIV_LIKELY_NULL(heap)) {
 		mem_heap_free(heap);
@@ -4136,7 +4141,8 @@ row_search_check_if_query_cache_permitted(
 		    && !trx->read_view) {
 
 			trx->read_view = read_view_open_now(trx,
-						trx->read_view_heap);
+						trx->global_read_view_heap);
+			trx->global_read_view = trx->read_view;
 		}
 	}
 	
diff --git a/innobase/trx/trx0trx.c b/innobase/trx/trx0trx.c
index 10fbf3468c..f95491443e 100644
--- a/innobase/trx/trx0trx.c
+++ b/innobase/trx/trx0trx.c
@@ -159,7 +159,8 @@ trx_create(
 
 	trx->auto_inc_lock = NULL;
 	
-	trx->read_view_heap = mem_heap_create(256);
+	trx->global_read_view_heap = mem_heap_create(256);
+	trx->global_read_view = NULL;
 	trx->read_view = NULL;
 
 	/* Set X/Open XA transaction identification to NULL */
@@ -318,10 +319,12 @@ trx_free(
 
 	ut_a(UT_LIST_GET_LEN(trx->trx_locks) == 0);
 
-	if (trx->read_view_heap) {
-		mem_heap_free(trx->read_view_heap);
+	if (trx->global_read_view_heap) {
+		mem_heap_free(trx->global_read_view_heap);
 	}
 
+	trx->global_read_view = NULL;
+
 	ut_a(trx->read_view == NULL);
 	
 	mem_free(trx);
@@ -831,10 +834,23 @@ trx_commit_off_kernel(
 	lock_release_off_kernel(trx);
 
 	if (trx->read_view) {
+		/* If transaction has a global read view this case
+		means that transaction has been using a consistent
+		read view associated to a cursor. Only the global
+		read view associated to a transaction is closed
+		and read view is then removed from the transaction.
+		If read view associated to a cursor is still used
+		it must be re-registered to another transaction. */
+
+		if (UNIV_LIKELY_NULL(trx->global_read_view)) {
+			trx->read_view = trx->global_read_view;
+		}
+
 		read_view_close(trx->read_view);
 
-		mem_heap_empty(trx->read_view_heap);
+		mem_heap_empty(trx->global_read_view_heap);
 		trx->read_view = NULL;
+		trx->global_read_view = NULL;
 	}
 
 	if (must_flush_log) {
@@ -964,7 +980,9 @@ trx_assign_read_view(
 	mutex_enter(&kernel_mutex);
 
 	if (!trx->read_view) {
-		trx->read_view = read_view_open_now(trx, trx->read_view_heap);
+		trx->read_view = read_view_open_now(trx,
+					trx->global_read_view_heap);
+		trx->global_read_view = trx->read_view;
 	}
 
 	mutex_exit(&kernel_mutex);
diff --git a/sql/ha_innodb.cc b/sql/ha_innodb.cc
index 690f1354d2..1c3bca0803 100644
--- a/sql/ha_innodb.cc
+++ b/sql/ha_innodb.cc
@@ -5879,7 +5879,7 @@ ha_innobase::start_stmt(
 	innobase_release_stat_resources(trx);
 
 	if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
-	    						&& trx->read_view) {
+	    					&& trx->global_read_view) {
 	    	/* At low transaction isolation levels we let
 		each consistent read set its own snapshot */
 
@@ -6100,7 +6100,7 @@ ha_innobase::external_lock(
 			}
 		} else {
 			if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
-	    						&& trx->read_view) {
+	    					&& trx->global_read_view) {
 
 				/* At low transaction isolation levels we let
 				each consistent read set its own snapshot */
@@ -7130,4 +7130,45 @@ innobase_rollback_by_xid(
 	}
 }
 
+/***********************************************************************
+This function creates a consistent view for a cursor and start a transaction
+if it has not been started. This consistent view is then used inside of MySQL 
+when accesing records using a cursor. */
+
+void*
+innobase_create_cursor_view(void)
+/*=============================*/
+			/* out: Pointer to cursor view or NULL */
+{
+	return(read_cursor_view_create_for_mysql(
+					check_trx_exists(current_thd)));
+}
+
+/***********************************************************************
+This function closes the given consistent cursor view. Note that
+global read view is restored to a transaction and a transaction is
+started if it has not been started. */
+
+void
+innobase_close_cursor_view(
+/*=======================*/
+	void*	curview)/* in: Consistent read view to be closed */
+{
+	read_cursor_view_close_for_mysql(check_trx_exists(current_thd),
+						(cursor_view_t*) curview);
+}
+
+/***********************************************************************
+This function sets the given consistent cursor view to a transaction.
+If a transaction does not exist, transaction is started. */
+
+void
+innobase_set_cursor_view(
+/*=====================*/
+	void*	curview)/* in: Consistent cursor view to be closed */
+{
+	read_cursor_set_for_mysql(check_trx_exists(current_thd), 
+						(cursor_view_t*) curview);
+}
+
 #endif /* HAVE_INNOBASE_DB */
diff --git a/sql/ha_innodb.h b/sql/ha_innodb.h
index 1584a2182c..2cbc1c8d91 100644
--- a/sql/ha_innodb.h
+++ b/sql/ha_innodb.h
@@ -310,3 +310,32 @@ int innobase_xa_end(THD *thd);
 
 int innobase_repl_report_sent_binlog(THD *thd, char *log_file_name,
                                my_off_t end_offset);
+
+/***********************************************************************
+This function creates a consistent view for a cursor and start a transaction
+if it has not been started. This consistent view is then used inside of MySQL 
+when accesing records using a cursor. */
+
+void*
+innobase_create_cursor_view(void);
+/*=============================*/
+				/* out: Pointer to cursor view or NULL */
+
+/***********************************************************************
+This function closes the given consistent cursor view. Note that
+global read view is restored to a transaction and a transaction is
+started if it has not been started. */
+
+void
+innobase_close_cursor_view(
+/*=======================*/
+	void*	curview);	/* in: Consistent read view to be closed */
+
+/***********************************************************************
+This function sets the given consistent cursor view to a transaction.
+If a transaction does not exist, transaction is started. */
+
+void
+innobase_set_cursor_view(
+/*=====================*/
+	void*	curview);	/* in: Consistent read view to be closed */
-- 
2.30.9