mirror of
https://github.com/MariaDB/server.git
synced 2025-08-08 11:22:35 +03:00
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
This commit is contained in:
@@ -3916,6 +3916,16 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
|
||||
|
||||
DEBUG_SYNC(thd,"create_table_select_before_create");
|
||||
|
||||
/* Check if LOCK TABLES + CREATE OR REPLACE of existing normal table*/
|
||||
if (thd->locked_tables_mode && create_table->table &&
|
||||
!create_info->tmp_table())
|
||||
{
|
||||
/* Remember information about the locked table */
|
||||
create_info->pos_in_locked_tables=
|
||||
create_table->table->pos_in_locked_tables;
|
||||
create_info->mdl_ticket= create_table->table->mdl_ticket;
|
||||
}
|
||||
|
||||
/*
|
||||
Create and lock table.
|
||||
|
||||
@@ -3932,51 +3942,62 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
|
||||
TABLE, which is a wrong order. So we keep binary logging disabled when we
|
||||
open_table().
|
||||
*/
|
||||
{
|
||||
if (!mysql_create_table_no_lock(thd, create_table->db,
|
||||
create_table->table_name,
|
||||
create_info, alter_info, NULL,
|
||||
select_field_count))
|
||||
{
|
||||
DEBUG_SYNC(thd,"create_table_select_before_open");
|
||||
|
||||
if (!create_info->tmp_table())
|
||||
{
|
||||
Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
|
||||
/*
|
||||
Here we open the destination table, on which we already have
|
||||
an exclusive metadata lock.
|
||||
*/
|
||||
if (open_table(thd, create_table, thd->mem_root, &ot_ctx))
|
||||
{
|
||||
quick_rm_table(thd, create_info->db_type, create_table->db,
|
||||
table_case_name(create_info, create_table->table_name),
|
||||
0);
|
||||
}
|
||||
else
|
||||
table= create_table->table;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (open_temporary_table(thd, create_table))
|
||||
{
|
||||
/*
|
||||
This shouldn't happen as creation of temporary table should make
|
||||
it preparable for open. Anyway we can't drop temporary table if
|
||||
we are unable to find it.
|
||||
*/
|
||||
DBUG_ASSERT(0);
|
||||
}
|
||||
else
|
||||
table= create_table->table;
|
||||
}
|
||||
}
|
||||
if (!table) // open failed
|
||||
if (!mysql_create_table_no_lock(thd, create_table->db,
|
||||
create_table->table_name,
|
||||
create_info, alter_info, NULL,
|
||||
select_field_count))
|
||||
{
|
||||
DEBUG_SYNC(thd,"create_table_select_before_open");
|
||||
|
||||
/*
|
||||
If we had a temporary table or a table used with LOCK TABLES,
|
||||
it was closed by mysql_create()
|
||||
*/
|
||||
create_table->table= 0;
|
||||
|
||||
if (!create_info->tmp_table())
|
||||
{
|
||||
if (!thd->is_error()) // CREATE ... IF NOT EXISTS
|
||||
my_ok(thd); // succeed, but did nothing
|
||||
DBUG_RETURN(0);
|
||||
Open_table_context ot_ctx(thd, MYSQL_OPEN_REOPEN);
|
||||
TABLE_LIST::enum_open_strategy save_open_strategy;
|
||||
|
||||
/* Force the newly created table to be opened */
|
||||
save_open_strategy= create_table->open_strategy;
|
||||
create_table->open_strategy= TABLE_LIST::OPEN_NORMAL;
|
||||
/*
|
||||
Here we open the destination table, on which we already have
|
||||
an exclusive metadata lock.
|
||||
*/
|
||||
if (open_table(thd, create_table, thd->mem_root, &ot_ctx))
|
||||
{
|
||||
quick_rm_table(thd, create_info->db_type, create_table->db,
|
||||
table_case_name(create_info, create_table->table_name),
|
||||
0);
|
||||
}
|
||||
/* Restore */
|
||||
create_table->open_strategy= save_open_strategy;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (open_temporary_table(thd, create_table))
|
||||
{
|
||||
/*
|
||||
This shouldn't happen as creation of temporary table should make
|
||||
it preparable for open. Anyway we can't drop temporary table if
|
||||
we are unable to find it.
|
||||
*/
|
||||
DBUG_ASSERT(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
create_table->table= 0; // Create failed
|
||||
|
||||
if (!(table= create_table->table))
|
||||
{
|
||||
if (!thd->is_error()) // CREATE ... IF NOT EXISTS
|
||||
my_ok(thd); // succeed, but did nothing
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
DEBUG_SYNC(thd,"create_table_select_before_lock");
|
||||
@@ -3994,7 +4015,7 @@ static TABLE *create_table_from_items(THD *thd, HA_CREATE_INFO *create_info,
|
||||
/* purecov: begin tested */
|
||||
/*
|
||||
This can happen in innodb when you get a deadlock when using same table
|
||||
in insert and select
|
||||
in insert and select or when you run out of memory.
|
||||
*/
|
||||
my_error(ER_CANT_LOCK, MYF(0), my_errno);
|
||||
if (*lock)
|
||||
@@ -4092,8 +4113,6 @@ select_create::prepare(List<Item> &values, SELECT_LEX_UNIT *u)
|
||||
thd->binlog_start_trans_and_stmt();
|
||||
}
|
||||
|
||||
DBUG_ASSERT(create_table->table == NULL);
|
||||
|
||||
DEBUG_SYNC(thd,"create_table_select_before_check_if_exists");
|
||||
|
||||
if (!(table= create_table_from_items(thd, create_info, create_table,
|
||||
@@ -4236,32 +4255,53 @@ void select_create::send_error(uint errcode,const char *err)
|
||||
|
||||
bool select_create::send_eof()
|
||||
{
|
||||
bool tmp=select_insert::send_eof();
|
||||
if (tmp)
|
||||
abort_result_set();
|
||||
else
|
||||
if (select_insert::send_eof())
|
||||
{
|
||||
/*
|
||||
Do an implicit commit at end of statement for non-temporary
|
||||
tables. This can fail, but we should unlock the table
|
||||
nevertheless.
|
||||
*/
|
||||
if (!table->s->tmp_table)
|
||||
{
|
||||
trans_commit_stmt(thd);
|
||||
trans_commit_implicit(thd);
|
||||
}
|
||||
|
||||
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
|
||||
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
|
||||
if (m_plock)
|
||||
{
|
||||
mysql_unlock_tables(thd, *m_plock);
|
||||
*m_plock= NULL;
|
||||
m_plock= NULL;
|
||||
}
|
||||
abort_result_set();
|
||||
return 1;
|
||||
}
|
||||
return tmp;
|
||||
|
||||
exit_done= 1; // Avoid double calls
|
||||
/*
|
||||
Do an implicit commit at end of statement for non-temporary
|
||||
tables. This can fail, but we should unlock the table
|
||||
nevertheless.
|
||||
*/
|
||||
if (!table->s->tmp_table)
|
||||
{
|
||||
trans_commit_stmt(thd);
|
||||
trans_commit_implicit(thd);
|
||||
}
|
||||
|
||||
table->file->extra(HA_EXTRA_NO_IGNORE_DUP_KEY);
|
||||
table->file->extra(HA_EXTRA_WRITE_CANNOT_REPLACE);
|
||||
|
||||
if (m_plock)
|
||||
{
|
||||
MYSQL_LOCK *lock= *m_plock;
|
||||
*m_plock= NULL;
|
||||
m_plock= NULL;
|
||||
|
||||
if (create_info->pos_in_locked_tables)
|
||||
{
|
||||
/*
|
||||
If we are under lock tables, we have created a table that was
|
||||
originally locked. We should add back the lock to ensure that
|
||||
all tables in the thd->open_list are locked!
|
||||
*/
|
||||
table->mdl_ticket= create_info->mdl_ticket;
|
||||
|
||||
/* The following should never fail, except if out of memory */
|
||||
if (!thd->locked_tables_list.restore_lock(thd,
|
||||
create_info->
|
||||
pos_in_locked_tables,
|
||||
table, lock))
|
||||
return 0; // ok
|
||||
/* Fail. Continue without locking the table */
|
||||
}
|
||||
mysql_unlock_tables(thd, lock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4269,6 +4309,11 @@ void select_create::abort_result_set()
|
||||
{
|
||||
DBUG_ENTER("select_create::abort_result_set");
|
||||
|
||||
/* Avoid double calls, could happen in case of out of memory on cleanup */
|
||||
if (exit_done)
|
||||
DBUG_VOID_RETURN;
|
||||
exit_done= 1;
|
||||
|
||||
/*
|
||||
In select_insert::abort_result_set() we roll back the statement, including
|
||||
truncating the transaction cache of the binary log. To do this, we
|
||||
|
Reference in New Issue
Block a user