From 2e0687e12d117e4388b46dc13c411362c179c27e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= <marko.makela@mariadb.com>
Date: Fri, 15 Dec 2017 19:10:36 +0200
Subject: [PATCH 6/6] WIP (dead end) defer DROP TABLE to COMMIT

This cannot work. Instead, we must stop using multiple transactions
per DDL operation, and we must split the dict_operation_lock into
MDL, so that multiple DDL operations may concurrently update the
InnoDB data dictionary tables.
---
 storage/innobase/fil/fil0fil.cc       |  2 +-
 storage/innobase/handler/ha_innodb.cc | 65 ++++++++++++++++++-----------------
 storage/innobase/include/fil0fil.h    |  2 +-
 storage/innobase/include/trx0trx.h    |  9 ++++-
 storage/innobase/trx/trx0trx.cc       | 19 ++++++++++
 5 files changed, 62 insertions(+), 35 deletions(-)

diff --git a/storage/innobase/fil/fil0fil.cc b/storage/innobase/fil/fil0fil.cc
index 8c86e272853..1bbf97571c4 100644
--- a/storage/innobase/fil/fil0fil.cc
+++ b/storage/innobase/fil/fil0fil.cc
@@ -6152,7 +6152,7 @@ fil_mtr_rename_log(
 		!is_system_tablespace(old_table->space);
 
 	bool	new_is_file_per_table =
-		!is_system_tablespace(new_table->space);
+		new_table && !is_system_tablespace(new_table->space);
 
 	/* If neither table is file-per-table,
 	there will be no renaming of files. */
diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc
index 3f08f84a4cd..f277e97f54b 100644
--- a/storage/innobase/handler/ha_innodb.cc
+++ b/storage/innobase/handler/ha_innodb.cc
@@ -13339,15 +13339,6 @@ ha_innobase::delete_table(
 	dberr_t	err;
 	THD*	thd = ha_thd();
 	int	sql_command = thd_sql_command(thd);
-
-	if (sql_command == SQLCOM_CREATE_TABLE) {
-		/* On an error, CREATE TABLE...SELECT may invoke
-		the DROP TABLE functionality. Let us ignore this,
-		and instead let the transaction rollback
-		drop the table. */
-		return 0;
-	}
-
 	char	norm_name[FN_REFLEN];
 
 	DBUG_ENTER("ha_innobase::delete_table");
@@ -13374,26 +13365,6 @@ ha_innobase::delete_table(
 
 	TrxInInnoDB	trx_in_innodb(parent_trx);
 
-	/* Remove the to-be-dropped table from the list of modified tables
-	by parent_trx. Otherwise we may end up with an orphaned pointer to
-	the table object from parent_trx::mod_tables. This could happen in:
-	SET AUTOCOMMIT=0;
-	CREATE TABLE t (PRIMARY KEY (a)) ENGINE=INNODB SELECT 1 AS a UNION
-	ALL SELECT 1 AS a; */
-	trx_mod_tables_t::const_iterator	iter;
-
-	for (iter = parent_trx->mod_tables.begin();
-	     iter != parent_trx->mod_tables.end();
-	     ++iter) {
-
-		dict_table_t*	table_to_drop = iter->first;
-
-		if (strcmp(norm_name, table_to_drop->name.m_name) == 0) {
-			parent_trx->mod_tables.erase(table_to_drop);
-			break;
-		}
-	}
-
 	trx_t*	trx = innobase_trx_allocate(thd);
 
 	ulint	name_len = strlen(name);
@@ -13408,6 +13379,32 @@ ha_innobase::delete_table(
 	/* We are doing a DDL operation. */
 	++trx->will_lock;
 
+	/* Defer the dropping of the table to parent_trx if it is
+	locked there, and only rename the table to a temporary name
+	here. This is needed in the following:
+	CREATE TABLE t (PRIMARY KEY(a)) ENGINE=INNODB
+	SELECT 1 a UNION SELECT 1 a; */
+
+	for (trx_mod_tables_t::iterator i = parent_trx->mod_tables.begin();
+	     i != parent_trx->mod_tables.end(); ++i) {
+		const char* name = i->first->name.m_name;
+		if (!strcmp(norm_name, name)) {
+			ut_ad(sql_command == SQLCOM_CREATE_TABLE);
+			i->second.drop();
+			mem_heap_t* heap = mem_heap_create(FN_REFLEN);
+			trx_set_dict_operation(trx, TRX_DICT_OP_INDEX);
+			trx_start_if_not_started(trx, true);
+			row_mysql_lock_data_dictionary(trx);
+			err = row_rename_table_for_mysql(
+				name,
+				dict_mem_create_temporary_tablename(
+					heap, name, i->first->id),
+				trx, false);
+			mem_heap_free(heap);
+			goto func_exit;
+		}
+	}
+
 	/* Drop the table in InnoDB */
 
 	err = row_drop_table_for_mysql(
@@ -13507,15 +13504,19 @@ ha_innobase::delete_table(
 		}
 	}
 
-	ut_ad(!srv_read_only_mode);
+func_exit:
+	innobase_commit_low(trx);
+
+	if (trx->dict_operation_lock_mode) {
+		row_mysql_unlock_data_dictionary(trx);
+	}
+	
 	/* Flush the log to reduce probability that the .frm files and
 	the InnoDB data dictionary get out-of-sync if the user runs
 	with innodb_flush_log_at_trx_commit = 0 */
 
 	log_buffer_flush_to_disk();
 
-	innobase_commit_low(trx);
-
 	trx_free_for_mysql(trx);
 
 	DBUG_RETURN(convert_error_code_to_mysql(err, 0, NULL));
diff --git a/storage/innobase/include/fil0fil.h b/storage/innobase/include/fil0fil.h
index febf47c1d08..c6e1c76904e 100644
--- a/storage/innobase/include/fil0fil.h
+++ b/storage/innobase/include/fil0fil.h
@@ -1516,7 +1516,7 @@ fil_mtr_rename_log(
 	const dict_table_t*	new_table,
 	const char*		tmp_name,
 	mtr_t*			mtr)
-	MY_ATTRIBUTE((nonnull, warn_unused_result));
+	MY_ATTRIBUTE((nonnull(1,3,4), warn_unused_result));
 
 /** Acquire the fil_system mutex. */
 #define fil_system_enter()	mutex_enter(&fil_system->mutex)
diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h
index 6e6ad11c265..3fdff93a1bd 100644
--- a/storage/innobase/include/trx0trx.h
+++ b/storage/innobase/include/trx0trx.h
@@ -782,6 +782,8 @@ struct trx_lock_t {
 /** Logical first modification time of a table in a transaction */
 class trx_mod_table_time_t
 {
+	/** Whether the table is to be dropped at transaction commit */
+	bool		drop_table;
 	/** First modification of the table */
 	undo_no_t	first;
 
@@ -789,7 +791,12 @@ class trx_mod_table_time_t
 	/** Constructor
 	@param[in]	rows	number of modified rows so far */
 	trx_mod_table_time_t(undo_no_t rows)
-		: first(rows) {}
+		: drop_table(false), first(rows) {}
+
+	/** Note that the table will be dropped on transaction commit */
+	void drop() { drop_table= true; }
+	/** @return whether the table shall be dropped on commit */
+	bool must_drop() const { return drop_table; }
 
 	/** Invoked after partial rollback
 	@param[in]	limit	number of surviving modified rows
diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc
index 986eb760cef..1a6d40e884b 100644
--- a/storage/innobase/trx/trx0trx.cc
+++ b/storage/innobase/trx/trx0trx.cc
@@ -1628,6 +1628,23 @@ trx_update_mod_tables_timestamp(
 	trx->mod_tables.clear();
 }
 
+/* If there was any deferred DROP TABLE operation, modify the InnoDB
+data dictionary as the last step before commit or XA PREPARE. */
+static
+void
+trx_drop_tables_at_commit(trx_t* trx)
+{
+	const bool dict_locked = !trx->dict_operation_lock_mode;
+	if (dict_locked) {
+		row_mysql_lock_data_dictionary(trx);
+	}
+
+	if (dict_locked) {
+		row_mysql_unlock_data_dictionary(trx);
+	}
+}
+
+
 /**
 Erase the transaction from running transaction lists and serialization
 list. Active RW transaction list of a MVCC snapshot(ReadView::prepare)
@@ -1921,6 +1938,7 @@ trx_commit_low(
 	bool	serialised;
 
 	if (mtr != NULL) {
+		trx_drop_tables_at_commit(trx);
 
 		mtr->set_sync();
 
@@ -2700,6 +2718,7 @@ trx_prepare(
 	/* Only fresh user transactions can be prepared.
 	Recovered transactions cannot. */
 	ut_a(!trx->is_recovered);
+	trx_drop_tables_at_commit(trx);
 
 	lsn_t	lsn = trx_prepare_low(trx);
 
-- 
2.15.1

