diff options
author | Michael Widenius <monty@askmonty.org> | 2014-01-29 15:37:17 +0200 |
---|---|---|
committer | Michael Widenius <monty@askmonty.org> | 2014-01-29 15:37:17 +0200 |
commit | 7ffc9da093bf34cf0f524fcf39be9af2a149ce19 (patch) | |
tree | c968f45fc849b3586c05c2797c53544a254d3dbe /sql/sql_base.cc | |
parent | 659304d410dc56103a9045e1e3476bc530f35398 (diff) | |
download | mariadb-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.cc | 217 |
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. |