summaryrefslogtreecommitdiff
path: root/sql/sql_base.cc
diff options
context:
space:
mode:
authorMichael Widenius <monty@askmonty.org>2014-01-29 15:37:17 +0200
committerMichael Widenius <monty@askmonty.org>2014-01-29 15:37:17 +0200
commit7ffc9da093bf34cf0f524fcf39be9af2a149ce19 (patch)
treec968f45fc849b3586c05c2797c53544a254d3dbe /sql/sql_base.cc
parent659304d410dc56103a9045e1e3476bc530f35398 (diff)
downloadmariadb-git-7ffc9da093bf34cf0f524fcf39be9af2a149ce19.tar.gz
Implementation of MDEV-5491: CREATE OR REPLACE TABLE
Using CREATE OR REPLACE TABLE is be identical to DROP TABLE IF EXISTS table_name; CREATE TABLE ...; Except that: * CREATE OR REPLACE is be atomic (now one can create the same table between drop and create). * Temporary tables will not shadow the table name for the DROP as the CREATE TABLE tells us already if we are using a temporary table or not. * If the table was locked with LOCK TABLES, the new table will be locked with the same lock after it's created. Implementation details: - We don't anymore open the to-be-created table during CREATE TABLE, which the original code did. - There is no need to open a table we are planning to create. It's enough to check if the table exists or not. - Removed some of duplicated code for CREATE IF NOT EXISTS. - Give an error when using CREATE OR REPLACE with IF NOT EXISTS (conflicting options). - As a side effect of the code changes, we don't anymore have to internally re-prepare prepared statements with CREATE TABLE if the table exists. - Made one code path for all testing if log table are in use. - Better error message if one tries to create/drop/alter a log table in use - Added back disabled rpl_row_create_table test as it now seams to work and includes a lot of interesting tests. - Added HA_LEX_CREATE_REPLACE to mark if we are using CREATE OR REPLACE - Aligned CREATE OR REPLACE parsing code in sql_yacc.yy for TABLE and VIEW - Changed interface for drop_temporary_table() to make it more reusable - Changed Locked_tables_list::init_locked_tables() to work on the table object instead of the table list object. Before this it used a mix of both, which was not good. - Locked_tables_list::unlock_locked_tables(THD *thd) now requires a valid thd argument. Old usage of calling this with 0 i changed to instead call Locked_tables_list::reset() - Added functions Locked_tables_list:restore_lock() and Locked_tables_list::add_back_last_deleted_lock() to be able to easily add back a locked table to the lock list. - Added restart_trans_for_tables() to be able to restart a transaction. - DROP_ACL is required if one uses CREATE TABLE OR REPLACE. - Added drop of normal and temporary tables in create_table_imp() if CREATE OR REPLACE was used. - Added reacquiring of table locks in mysql_create_table() and mysql_create_like_table() mysql-test/include/commit.inc: With new code we get fewer status increments mysql-test/r/commit_1innodb.result: With new code we get fewer status increments mysql-test/r/create.result: Added testing of create or replace with timeout mysql-test/r/create_or_replace.result: Basic testing of CREATE OR REPLACE TABLE mysql-test/r/partition_exchange.result: New error message mysql-test/r/ps_ddl.result: Fewer reprepares with new code mysql-test/suite/archive/discover.result: Don't rediscover archive tables if the .frm file exists (Sergei will look at this if there is a better way...) mysql-test/suite/archive/discover.test: Don't rediscover archive tables if the .frm file exists (Sergei will look at this if there is a better way...) mysql-test/suite/funcs_1/r/innodb_views.result: New error message mysql-test/suite/funcs_1/r/memory_views.result: New error message mysql-test/suite/rpl/disabled.def: rpl_row_create_table should now be safe to use mysql-test/suite/rpl/r/rpl_row_create_table.result: Updated results after adding back disabled test mysql-test/suite/rpl/t/rpl_create_if_not_exists.test: Added comment mysql-test/suite/rpl/t/rpl_row_create_table.test: Added CREATE OR REPLACE TABLE test mysql-test/t/create.test: Added CREATE OR REPLACE TABLE test mysql-test/t/create_or_replace-master.opt: Create logs mysql-test/t/create_or_replace.test: Basic testing of CREATE OR REPLACE TABLE mysql-test/t/partition_exchange.test: Error number changed as we are now using same code for all log table change issues mysql-test/t/ps_ddl.test: Fewer reprepares with new code sql/handler.h: Moved things around a bit in a structure to get better alignment. Added HA_LEX_CREATE_REPLACE to mark if we are using CREATE OR REPLACE Added 3 elements to end of HA_CREATE_INFO to be able to store state to add backs locks in case of LOCK TABLES. sql/log.cc: Reimplemented check_if_log_table(): - Simpler and faster usage - Can give error messages This gives us one code path for allmost all error messages if log tables are in use sql/log.h: New interface for check_if_log_table() sql/slave.cc: More logging sql/sql_alter.cc: New interface for check_if_log_table() sql/sql_base.cc: More documentation Changed interface for drop_temporary_table() to make it more reusable Changed Locked_tables_list::init_locked_tables() to work on the table object instead of the table list object. Before this it used a mix of both, which was not good. Locked_tables_list::unlock_locked_tables(THD *thd) now requires a valid thd argument. Old usage of calling this with 0 i changed to instead call Locked_tables_list::reset() Added functions Locked_tables_list:restore_lock() and Locked_tables_list::add_back_last_deleted_lock() to be able to easily add back a locked table to the lock list. Check for command number instead of open_strategy of CREATE TABLE was used. Added restart_trans_for_tables() to be able to restart a transaction. This was needed in "create or replace ... select" between the drop table and the select. sql/sql_base.h: Added and updated function prototypes sql/sql_class.h: Added new prototypes to Locked_tables_list class Added extra argument to select_create to avoid double call to eof() or send_error() - I needed this in some edge case where the table was not created against expections. sql/sql_db.cc: New interface for check_if_log_table() sql/sql_insert.cc: Remember position to lock information so that we can reaquire table lock for LOCK TABLES + CREATE OR REPLACE TABLE SELECT. Later add back the lock by calling restore_lock(). Removed one not needed indentation level in create_table_from_items() Ensure we don't call send_eof() or abort_result_set() twice. sql/sql_lex.h: Removed variable that I temporarly added in an earlier changeset sql/sql_parse.cc: Removed old test code (marked with QQ) Ensure that we have open_strategy set as TABLE_LIST::OPEN_STUB in CREATE TABLE Removed some IF NOT EXISTS code as this is now handled in create_table_table_impl(). Set OPTION_KEEP_LOGS later. This code had to be moved as the test for IF EXISTS has changed place. DROP_ACL is required if one uses CREATE TABLE OR REPLACE. sql/sql_partition_admin.cc: New interface for check_if_log_table() sql/sql_rename.cc: New interface for check_if_log_table() sql/sql_table.cc: New interface for check_if_log_table() Moved some code in mysql_rm_table() under a common test. - Safe as temporary tables doesn't have statistics. - !is_temporary_table(table) test was moved out from drop_temporary_table() and merged with upper level code. - Added drop of normal and temporary tables in create_table_imp() if CREATE OR REPLACE was used. - Added reacquiring of table locks in mysql_create_table() and mysql_create_like_table() - In mysql_create_like_table(), restore table->open_strategy() if it was changed. - Re-test if table was a view after opening it. sql/sql_table.h: New prototype for mysql_create_table_no_lock() sql/sql_yacc.yy: Added syntax for CREATE OR REPLACE TABLE Reuse new code for CREATE OR REPLACE VIEW sql/table.h: Added name for enum type sql/table_cache.cc: More DBUG
Diffstat (limited to 'sql/sql_base.cc')
-rw-r--r--sql/sql_base.cc217
1 files changed, 152 insertions, 65 deletions
diff --git a/sql/sql_base.cc b/sql/sql_base.cc
index e3ce2adf862..0a38cbbeb1a 100644
--- a/sql/sql_base.cc
+++ b/sql/sql_base.cc
@@ -784,12 +784,18 @@ static void close_open_tables(THD *thd)
access the table cache key
@param[in] extra
- HA_EXTRA_PREPRE_FOR_DROP if the table is being dropped
- HA_EXTRA_PREPARE_FOR_REANME if the table is being renamed
- HA_EXTRA_NOT_USED no drop/rename
- In case of drop/reanme the documented behaviour is to
+ HA_EXTRA_PREPARE_FOR_DROP
+ - The table is dropped
+ HA_EXTRA_PREPARE_FOR_RENAME
+ - The table is renamed
+ HA_EXTRA_NOT_USED
+ - The table is marked as closed in the
+ locked_table_list but kept there so one can call
+ locked_table_list->reopen_tables() to put it back.
+
+ In case of drop/rename the documented behavior is to
implicitly remove the table from LOCK TABLES
- list.
+ list.
@pre Must be called with an X MDL lock on the table.
*/
@@ -1588,26 +1594,21 @@ TABLE *find_temporary_table(THD *thd,
thd->temporary_tables list, it's impossible to tell here whether
we're dealing with an internal or a user temporary table.
- If is_trans is not null, we return the type of the table:
- either transactional (e.g. innodb) as TRUE or non-transactional
- (e.g. myisam) as FALSE.
+ @param thd Thread handler
+ @param table Temporary table to be deleted
+ @param is_trans Is set to the type of the table:
+ transactional (e.g. innodb) as TRUE or non-transactional
+ (e.g. myisam) as FALSE.
@retval 0 the table was found and dropped successfully.
- @retval 1 the table was not found in the list of temporary tables
- of this thread
@retval -1 the table is in use by a outer query
*/
-int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans)
+int drop_temporary_table(THD *thd, TABLE *table, bool *is_trans)
{
DBUG_ENTER("drop_temporary_table");
DBUG_PRINT("tmptable", ("closing table: '%s'.'%s'",
- table_list->db, table_list->table_name));
-
- if (!is_temporary_table(table_list))
- DBUG_RETURN(1);
-
- TABLE *table= table_list->table;
+ table->s->db.str, table->s->table_name.str));
/* Table might be in use by some outer statement. */
if (table->query_id && table->query_id != thd->query_id)
@@ -1627,10 +1628,10 @@ int drop_temporary_table(THD *thd, TABLE_LIST *table_list, bool *is_trans)
*/
mysql_lock_remove(thd, thd->lock, table);
close_temporary_table(thd, table, 1, 1);
- table_list->table= NULL;
DBUG_RETURN(0);
}
+
/*
unlink from thd->temporary tables and close temporary table
*/
@@ -2611,9 +2612,9 @@ Locked_tables_list::init_locked_tables(THD *thd)
{
TABLE_LIST *src_table_list= table->pos_in_table_list;
char *db, *table_name, *alias;
- size_t db_len= src_table_list->db_length;
- size_t table_name_len= src_table_list->table_name_length;
- size_t alias_len= strlen(src_table_list->alias);
+ size_t db_len= table->s->db.length;
+ size_t table_name_len= table->s->table_name.length;
+ size_t alias_len= table->alias.length();
TABLE_LIST *dst_table_list;
if (! multi_alloc_root(&m_locked_tables_root,
@@ -2623,23 +2624,15 @@ Locked_tables_list::init_locked_tables(THD *thd)
&alias, alias_len + 1,
NullS))
{
- unlock_locked_tables(0);
+ reset();
return TRUE;
}
- memcpy(db, src_table_list->db, db_len + 1);
- memcpy(table_name, src_table_list->table_name, table_name_len + 1);
- memcpy(alias, src_table_list->alias, alias_len + 1);
- /**
- Sic: remember the *actual* table level lock type taken, to
- acquire the exact same type in reopen_tables().
- E.g. if the table was locked for write, src_table_list->lock_type is
- TL_WRITE_DEFAULT, whereas reginfo.lock_type has been updated from
- thd->update_lock_default.
- */
+ memcpy(db, table->s->db.str, db_len + 1);
+ memcpy(table_name, table->s->table_name.str, table_name_len + 1);
+ strmake(alias, table->alias.ptr(), alias_len);
dst_table_list->init_one_table(db, db_len, table_name, table_name_len,
- alias,
- src_table_list->table->reginfo.lock_type);
+ alias, table->reginfo.lock_type);
dst_table_list->table= table;
dst_table_list->mdl_request.ticket= src_table_list->mdl_request.ticket;
@@ -2660,7 +2653,7 @@ Locked_tables_list::init_locked_tables(THD *thd)
(m_locked_tables_count+1));
if (m_reopen_array == NULL)
{
- unlock_locked_tables(0);
+ reset();
return TRUE;
}
}
@@ -2681,42 +2674,50 @@ Locked_tables_list::init_locked_tables(THD *thd)
void
Locked_tables_list::unlock_locked_tables(THD *thd)
{
- if (thd)
+ DBUG_ASSERT(!thd->in_sub_stmt &&
+ !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
+ /*
+ Sic: we must be careful to not close open tables if
+ we're not in LOCK TABLES mode: unlock_locked_tables() is
+ sometimes called implicitly, expecting no effect on
+ open tables, e.g. from begin_trans().
+ */
+ if (thd->locked_tables_mode != LTM_LOCK_TABLES)
+ return;
+
+ for (TABLE_LIST *table_list= m_locked_tables;
+ table_list; table_list= table_list->next_global)
{
- DBUG_ASSERT(!thd->in_sub_stmt &&
- !(thd->state_flags & Open_tables_state::BACKUPS_AVAIL));
/*
- Sic: we must be careful to not close open tables if
- we're not in LOCK TABLES mode: unlock_locked_tables() is
- sometimes called implicitly, expecting no effect on
- open tables, e.g. from begin_trans().
+ Clear the position in the list, the TABLE object will be
+ returned to the table cache.
*/
- if (thd->locked_tables_mode != LTM_LOCK_TABLES)
- return;
+ if (table_list->table) // If not closed
+ table_list->table->pos_in_locked_tables= NULL;
+ }
+ thd->leave_locked_tables_mode();
- for (TABLE_LIST *table_list= m_locked_tables;
- table_list; table_list= table_list->next_global)
- {
- /*
- Clear the position in the list, the TABLE object will be
- returned to the table cache.
- */
- if (table_list->table) // If not closed
- table_list->table->pos_in_locked_tables= NULL;
- }
- thd->leave_locked_tables_mode();
+ DBUG_ASSERT(thd->transaction.stmt.is_empty());
+ close_thread_tables(thd);
+
+ /*
+ We rely on the caller to implicitly commit the
+ transaction and release transactional locks.
+ */
- DBUG_ASSERT(thd->transaction.stmt.is_empty());
- close_thread_tables(thd);
- /*
- We rely on the caller to implicitly commit the
- transaction and release transactional locks.
- */
- }
/*
After closing tables we can free memory used for storing lock
request for metadata locks and TABLE_LIST elements.
*/
+ reset();
+}
+
+/*
+ Free memory allocated for storing locks
+*/
+
+void Locked_tables_list::reset()
+{
free_root(&m_locked_tables_root, MYF(0));
m_locked_tables= NULL;
m_locked_tables_last= &m_locked_tables;
@@ -2781,6 +2782,7 @@ void Locked_tables_list::unlink_from_list(THD *thd,
m_locked_tables_last= table_list->prev_global;
else
table_list->next_global->prev_global= table_list->prev_global;
+ m_locked_tables_count--;
}
}
@@ -2834,6 +2836,7 @@ unlink_all_closed_tables(THD *thd, MYSQL_LOCK *lock, size_t reopen_count)
m_locked_tables_last= table_list->prev_global;
else
table_list->next_global->prev_global= table_list->prev_global;
+ m_locked_tables_count--;
}
}
}
@@ -2908,6 +2911,57 @@ Locked_tables_list::reopen_tables(THD *thd)
return FALSE;
}
+/**
+ Add back a locked table to the locked list that we just removed from it.
+ This is needed in CREATE OR REPLACE TABLE where we are dropping, creating
+ and re-opening a locked table.
+
+ @return 0 0k
+ @return 1 error
+*/
+
+bool Locked_tables_list::restore_lock(THD *thd, TABLE_LIST *dst_table_list,
+ TABLE *table, MYSQL_LOCK *lock)
+{
+ MYSQL_LOCK *merged_lock;
+ DBUG_ENTER("restore_lock");
+ DBUG_ASSERT(!strcmp(dst_table_list->table_name, table->s->table_name.str));
+
+ /* Ensure we have the memory to add the table back */
+ if (!(merged_lock= mysql_lock_merge(thd->lock, lock)))
+ DBUG_RETURN(1);
+ thd->lock= merged_lock;
+
+ /* Link to the new table */
+ dst_table_list->table= table;
+ /*
+ The lock type may have changed (normally it should not as create
+ table will lock the table in write mode
+ */
+ dst_table_list->lock_type= table->reginfo.lock_type;
+ table->pos_in_locked_tables= dst_table_list;
+
+ add_back_last_deleted_lock(dst_table_list);
+
+ DBUG_RETURN(0);
+}
+
+/*
+ Add back the last deleted lock structure.
+ This should be followed by a call to reopen_tables() to
+ open the table.
+*/
+
+void Locked_tables_list::add_back_last_deleted_lock(TABLE_LIST *dst_table_list)
+{
+ /* Link the lock back in the locked tables list */
+ dst_table_list->prev_global= m_locked_tables_last;
+ *m_locked_tables_last= dst_table_list;
+ m_locked_tables_last= &dst_table_list->next_global;
+ dst_table_list->next_global= 0;
+ m_locked_tables_count++;
+}
+
#ifndef DBUG_OFF
/* Cause a spurious statement reprepare for debug purposes. */
@@ -4045,9 +4099,9 @@ lock_table_names(THD *thd,
if (mdl_requests.is_empty())
DBUG_RETURN(FALSE);
- /* Check if CREATE TABLE was used */
- create_table= (tables_start && tables_start->open_strategy ==
- TABLE_LIST::OPEN_IF_EXISTS);
+ /* Check if CREATE TABLE without REPLACE was used */
+ create_table= (thd->lex->sql_command == SQLCOM_CREATE_TABLE &&
+ !(thd->lex->create_info.options & HA_LEX_CREATE_REPLACE));
if (!(flags & MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK))
{
@@ -5293,6 +5347,39 @@ bool lock_tables(THD *thd, TABLE_LIST *tables, uint count,
}
+/*
+ Restart transaction for tables
+
+ This is used when we had to do an implicit commit after tables are opened
+ and want to restart transactions on tables.
+
+ This is used in case of:
+ LOCK TABLES xx
+ CREATE OR REPLACE TABLE xx;
+*/
+
+bool restart_trans_for_tables(THD *thd, TABLE_LIST *table)
+{
+ DBUG_ENTER("restart_trans_for_tables");
+
+ if (!thd->locked_tables_mode)
+ DBUG_RETURN(FALSE);
+
+ for (; table; table= table->next_global)
+ {
+ if (table->placeholder())
+ continue;
+
+ if (check_lock_and_start_stmt(thd, thd->lex, table))
+ {
+ DBUG_ASSERT(0); // Should never happen
+ DBUG_RETURN(TRUE);
+ }
+ }
+ DBUG_RETURN(FALSE);
+}
+
+
/**
Prepare statement for reopening of tables and recalculation of set of
prelocked tables.