mirror of
https://github.com/MariaDB/server.git
synced 2026-10-23 18:49:29 +03:00
Atomic CREATE OR REPLACE allows to keep an old table intact if the
command fails or during the crash. That is done by renaming the
original table to temporary name, as a backup and restoring it if the
CREATE fails. When the command is complete and logged the backup
table is deleted.
Atomic replace algorithm
Two DDL chains are used for CREATE OR REPLACE:
ddl_log_state_create (C) and ddl_log_state_rm (D).
1. (C) Log rename of ORIG to TMP table (Rename TMP to original).
2. Rename orignal to TMP.
3. (C) Log CREATE_TABLE_ACTION of ORIG (drops ORIG);
4. Do everything with ORIG (like insert data)
5. (D) Log drop of TMP
6. Write query to binlog (this marks (C) to be closed in
case of failure)
7. Execute drop of TMP through (D)
8. Close (C) and (D)
If there is a failure before 6) we revert the changes in (C)
Chain (D) is only executed if 6) succeded (C is closed on
crash recovery).
Foreign key errors will be found at the 1) stage.
Additional notes
- CREATE TABLE without REPLACE and temporary tables is not affected
by this commit.
set @@drop_before_create_or_replace=1 can be used to
get old behaviour where existing tables are dropped
in CREATE OR REPLACE.
- CREATE TABLE is reverted if binlogging the query fails.
- Engines having HTON_EXPENSIVE_RENAME flag set are not affected by
this commit. Conflicting tables marked with this flag will be
deleted with CREATE OR REPLACE.
- Replication execution is not affected by this commit.
- Replication will first drop the conflicting table and then
creating the new one.
- CREATE TABLE .. SELECT XID usage is fixed and now there is no need
to log DROP TABLE via DDL_CREATE_TABLE_PHASE_LOG (see comments in
do_postlock()). XID is now correctly updated so it disables
DDL_LOG_DROP_TABLE_ACTION. Note that binary log is flushed at the
final stage when the table is ready. So if we have XID in the
binary log we don't need to drop the table.
- Three variations of CREATE OR REPLACE handled:
1. CREATE OR REPLACE TABLE t1 (..);
2. CREATE OR REPLACE TABLE t1 LIKE t2;
3. CREATE OR REPLACE TABLE t1 SELECT ..;
- Test case uses 6 combinations for engines (aria, aria_notrans,
myisam, ib, lock_tables, expensive_rename) and 2 combinations for
binlog types (row, stmt). Combinations help to check differences
between the results. Error failures are tested for the above three
variations.
- expensive_rename tests CREATE OR REPLACE without atomic
replace. The effect should be the same as with the old behaviour
before this commit.
- Triggers mechanism is unaffected by this change. This is tested in
create_replace.test.
- LOCK TABLES is affected. Lock restoration must be done after new
table is created or TMP is renamed back to ORIG
- Moved ddl_log_complete() from send_eof() to finalize_ddl(). This
checkpoint was not executed before for normal CREATE TABLE but is
executed now.
- CREATE TABLE will now rollback also if writing to the binary
logging failed. See rpl_gtid_strict.test
backup ddl log changes
- In case of a successfull CREATE OR REPLACE we only log
the CREATE event, not the DROP TABLE event of the old table.
ddl_log.cc changes
ddl_log_execute_action() now properly return error conditions.
ddl_log_disable_entry() added to allow one to disable one entry.
The entry on disk is still reserved until ddl_log_complete() is
executed.
On XID usage
Like with all other atomic DDL operations XID is used to avoid
inconsistency between master and slave in the case of a crash after
binary log is written and before ddl_log_state_create is closed. On
recovery XIDs are taken from binary log and corresponding DDL log
events get disabled. That is done by
ddl_log_close_binlogged_events().
On linking two chains together
Chains are executed in the ascending order of entry_pos of execute
entries. But entry_pos assignment order is undefined: it may assign
bigger number for the first chain and then smaller number for the
second chain. So the execution order in that case will be reverse:
second chain will be executed first.
To avoid that we link one chain to another. While the base chain
(ddl_log_state_create) is active the secondary chain
(ddl_log_state_rm) is not executed. That is: only one chain can be
executed in two linked chains.
The interface ddl_log_link_chains() was defined in "MDEV-22166
ddl_log_write_execute_entry() extension".
Atomic info parameters in HA_CREATE_INFO
Many functions in CREATE TABLE pass the same parameters. These
parameters are part of table creation info and should be in
HA_CREATE_INFO (or whatever). Passing parameters via single
structure is much easier for adding new data and
refactoring.
Aria changes:
- Fixed issue in Aria engine with CREATE + locked tables
that data was not properly commited in some cases in
case of crashes.
InnoDB related changes (by Marko Mäkelä):
- table_name_t::is_create_or_replace(): A new predicate to check for
CREATE OR REPLACE TABLE will rename an old table to
and eventually drop after creating the replacement.
- dict_table_t::parse_name(): Do acquire MDL on #sql-create- names
for partitioned tables.
- dict_table_rename_in_cache(): On CREATE OR REPLACE TABLE ... SELECT,
forget the original dict_table_t::mdl_name so that purge will
acquire MDL on the #sql-create- name instead. In this way, the
MDL_EXCLUSIVE that the CREATE OR REPLACE TABLE holds on the
user-visible name will not unnecessarily block any purge of old history
until the very end when the #sql-create- table will be dropped.
- ha_innobase::delete_table(): Do not check FOREIGN KEY consistency
when dropping an #sql-create- table.
- row_rename_table_for_mysql(): Update SYS_FOREIGN.ID also
when renaming to #sql-create- in order to avoid any
duplicate key error when CREATE OR REPLACE TABLE is
creating some FOREIGN KEY constraints by names
that existed in the old table.
Other changes:
- Removed some auto variables in log.cc for better code readability.
- Fixed old bug that CREATE ... SELECT would not be able to auto repair
a table that is part of the SELECT.
- Marked MyISAM that it does not support ROLLBACK (not required but
done for better consistency with other engines).
- maria_create_trn_for_mysql() does not register a new transaction
handler for commits. This was needed to ensure create or replace
will not end with an active transaction.
- We do not get anymore warnings about "Engine not supporting atomic
create" when doing a legal CREATE OR REPLACE on a table with
foreign key constraints.
- Updated VIDEX engine flags to disable CREATE SEQUENCE.
- Removed mysql_mutex_unlock(&LOCK_gdl) / mysql_mutex_lock(&LOCK_gdl)
around calls to binlog as these are unsafe. The binlog code uses
global variables that needs protection from other caller.
- EITS data is preserved if create or replace fails if
drop_before_create_or_replace=OFF. If ON, then create or replace
will drop EITS before the drop of the original table (as before).
- Using CREATE OR REPLACE on a encrypted table that the user cannot
decrypt will fail instead of replacing the encrypted table.
The encrypted table will unchanged.
Known issues:
- One cannot use create or replace on an InnoDB tables that has foreign
key references point to it
- CREATE OR REPLACE TEMPORARY table is not full atomic. Any conflicting
table will always be dropped before creating a new one. (Old behaviour).
Bug fixes related to this MDEV:
MDEV-36435 Assertion failure in finalize_locked_tables()
MDEV-36439 Assertion `thd_arg->lex->sql_command != SQLCOM_CREATE_SEQUENCE...
MDEV-36498 Failed CoR in non-atomic mode no longer generates DROP in RBR...
MDEV-36508 Temporary files #sql-create-....frm occasionally stay after
crash recovery
MDEV-38479 Crash in CREATE OR REPLACE SEQUENCE when new sequence cannot
be created
MDEV-36497 Assertion failure after atomic CoR with Aria under lock in
transactional context
MDEV-36501 EITS data is lost after failed attempt to CREATE OR REPLACE
table
MDEV-36493 Atomic CREATE OR REPLACE ... SELECT blocks InnoDB purge
MDEV-39367 MSAN/valgrind errors in temp_file_size_cb_func,
main.tmp_space_usage fails
MDEV-39446 Atomic CREATE OR REPLACE fails if a table cannot be decrypted
MDEV-40776 Atomic CREATE OR REPLACE silently breaks the foreign key
MDEV-40765 Assertion `"unexpected references" == 0' failed upon failing
CREATE OR REPLACE
MDEV-40994 Atomic CoR: Transaction not registered for MariaDB 2PC, but
transaction is active
Reverted commits:
- MDEV-36685 "CREATE-SELECT may lose in binlog side-effects of
stored-routine" as it did not take into account that it safe to
clear binlogs if the created table is non transactional and there
are no other non transactional tables used.
This was done because it caused extra logging when it is not
needed (not using any non transactional tables) and it also did
not solve side effects when using statement based loggging.
609 lines
19 KiB
C++
609 lines
19 KiB
C++
/* Copyright (c) 2010, 2015, Oracle and/or its affiliates.
|
|
Copyright (c) 2012, 2018, MariaDB
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; version 2 of the License.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1335 USA */
|
|
|
|
#include "mariadb.h"
|
|
#include "debug_sync.h" // DEBUG_SYNC
|
|
#include "table.h" // TABLE, FOREIGN_KEY_INFO
|
|
#include "sql_class.h" // THD
|
|
#include "sql_base.h" // open_and_lock_tables
|
|
#include "sql_table.h" // write_bin_log
|
|
#include "datadict.h" // dd_recreate_table()
|
|
#include "lock.h" // MYSQL_OPEN_* flags
|
|
#include "sql_acl.h" // DROP_ACL
|
|
#include "sql_parse.h" // check_one_table_access()
|
|
#include "sql_truncate.h"
|
|
#include "wsrep_mysqld.h"
|
|
#include "sql_show.h" //append_identifier()
|
|
#include "sql_select.h"
|
|
#include "sql_delete.h"
|
|
|
|
/**
|
|
Append a list of field names to a string.
|
|
|
|
@param str The string.
|
|
@param fields The list of field names.
|
|
|
|
@return TRUE on failure, FALSE otherwise.
|
|
*/
|
|
|
|
static bool fk_info_append_fields(THD *thd, String *str,
|
|
List<LEX_CSTRING> *fields)
|
|
{
|
|
bool res= FALSE;
|
|
LEX_CSTRING *field;
|
|
List_iterator_fast<LEX_CSTRING> it(*fields);
|
|
|
|
while ((field= it++))
|
|
{
|
|
res|= append_identifier(thd, str, field);
|
|
res|= str->append(STRING_WITH_LEN(", "));
|
|
}
|
|
|
|
str->chop();
|
|
str->chop();
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/**
|
|
Generate a foreign key description suitable for a error message.
|
|
|
|
@param thd Thread context.
|
|
@param fk_info The foreign key information.
|
|
|
|
@return A human-readable string describing the foreign key.
|
|
*/
|
|
|
|
static const char *fk_info_str(THD *thd, FOREIGN_KEY_INFO *fk_info)
|
|
{
|
|
bool res= FALSE;
|
|
char buffer[STRING_BUFFER_USUAL_SIZE*2];
|
|
String str(buffer, sizeof(buffer), system_charset_info);
|
|
|
|
str.length(0);
|
|
|
|
/*
|
|
`db`.`tbl`, CONSTRAINT `id` FOREIGN KEY (`fk`) REFERENCES `db`.`tbl` (`fk`)
|
|
*/
|
|
|
|
res|= append_identifier(thd, &str, fk_info->foreign_db);
|
|
res|= str.append('.');
|
|
res|= append_identifier(thd, &str, fk_info->foreign_table);
|
|
res|= str.append(STRING_WITH_LEN(", CONSTRAINT "));
|
|
res|= append_identifier(thd, &str, fk_info->foreign_id);
|
|
res|= str.append(STRING_WITH_LEN(" FOREIGN KEY ("));
|
|
res|= fk_info_append_fields(thd, &str, &fk_info->foreign_fields);
|
|
res|= str.append(STRING_WITH_LEN(") REFERENCES "));
|
|
res|= append_identifier(thd, &str, fk_info->referenced_db);
|
|
res|= str.append('.');
|
|
res|= append_identifier(thd, &str, fk_info->referenced_table);
|
|
res|= str.append(STRING_WITH_LEN(" ("));
|
|
res|= fk_info_append_fields(thd, &str, &fk_info->referenced_fields);
|
|
res|= str.append(')');
|
|
|
|
return res ? NULL : thd->strmake(str.ptr(), str.length());
|
|
}
|
|
|
|
|
|
/**
|
|
Check and emit a fatal error if the table which is going to be
|
|
affected by TRUNCATE TABLE is a parent table in some non-self-
|
|
referencing foreign key.
|
|
|
|
@remark The intention is to allow truncate only for tables that
|
|
are not dependent on other tables.
|
|
|
|
@param thd Thread context.
|
|
@param table Table handle.
|
|
@param ddl DDL operation (CREATE OR REPLACE or TRUNCATE)
|
|
|
|
@retval FALSE This table is not parent in a non-self-referencing foreign
|
|
key. Statement can proceed.
|
|
@retval TRUE This table is parent in a non-self-referencing foreign key,
|
|
error was emitted.
|
|
*/
|
|
|
|
bool fk_truncate_illegal_if_parent(THD *thd, TABLE *table,
|
|
const char *ddl)
|
|
{
|
|
FOREIGN_KEY_INFO *fk_info;
|
|
List<FOREIGN_KEY_INFO> fk_list;
|
|
List_iterator_fast<FOREIGN_KEY_INFO> it;
|
|
|
|
/*
|
|
Bail out early if the table is not referenced by a foreign key.
|
|
In this case, the table could only be, if at all, a child table.
|
|
*/
|
|
if (! table->file->referenced_by_foreign_key())
|
|
return FALSE;
|
|
|
|
/*
|
|
This table _is_ referenced by a foreign key. At this point, only
|
|
self-referencing keys are acceptable. For this reason, get the list
|
|
of foreign keys referencing this table in order to check the name
|
|
of the child (dependent) tables.
|
|
*/
|
|
table->file->get_parent_foreign_key_list(thd, &fk_list);
|
|
|
|
/* Out of memory when building list. */
|
|
if (unlikely(thd->is_error()))
|
|
return TRUE;
|
|
|
|
it.init(fk_list);
|
|
|
|
/* Loop over the set of foreign keys for which this table is a parent. */
|
|
while ((fk_info= it++))
|
|
{
|
|
if (!table->s->db.streq(*fk_info->referenced_db) ||
|
|
!table->s->table_name.streq(*fk_info->referenced_table) ||
|
|
!table->s->db.streq(*fk_info->foreign_db) ||
|
|
!table->s->table_name.streq(*fk_info->foreign_table))
|
|
break;
|
|
}
|
|
|
|
/* Table is parent in a non-self-referencing foreign key. */
|
|
if (fk_info)
|
|
{
|
|
my_error(ER_DDL_ILLEGAL_FK, MYF(0), ddl, fk_info_str(thd, fk_info));
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
Open and truncate a locked table.
|
|
|
|
@param thd Thread context.
|
|
@param table_ref Table list element for the table to be truncated.
|
|
@param is_tmp_table True if element refers to a temp table.
|
|
|
|
@retval TRUNCATE_OK Truncate was successful and statement can be safely
|
|
binlogged.
|
|
@retval TRUNCATE_FAILED_BUT_BINLOG Truncate failed but still go ahead with
|
|
binlogging as in case of non transactional tables
|
|
partial truncation is possible.
|
|
|
|
@retval TRUNCATE_FAILED_SKIP_BINLOG Truncate was not successful hence donot
|
|
binlong the statement.
|
|
*/
|
|
|
|
enum Sql_cmd_truncate_table::truncate_result
|
|
Sql_cmd_truncate_table::handler_truncate(THD *thd, TABLE_LIST *table_ref,
|
|
bool is_tmp_table)
|
|
{
|
|
int error= 0;
|
|
uint flags= 0;
|
|
TABLE *table;
|
|
DBUG_ENTER("Sql_cmd_truncate_table::handler_truncate");
|
|
|
|
/*
|
|
Can't recreate, the engine must mechanically delete all rows
|
|
in the table. Use open_and_lock_tables() to open a write cursor.
|
|
*/
|
|
|
|
/* If it is a temporary table, no need to take locks. */
|
|
if (!is_tmp_table)
|
|
{
|
|
/* We don't need to load triggers. */
|
|
DBUG_ASSERT(table_ref->trg_event_map == 0);
|
|
/*
|
|
Our metadata lock guarantees that no transaction is reading
|
|
or writing into the table. Yet, to open a write cursor we need
|
|
a thr_lock lock. Allow to open base tables only.
|
|
*/
|
|
table_ref->required_type= TABLE_TYPE_NORMAL;
|
|
/*
|
|
Ignore pending FLUSH TABLES since we don't want to release
|
|
the MDL lock taken above and otherwise there is no way to
|
|
wait for FLUSH TABLES in deadlock-free fashion.
|
|
*/
|
|
flags= MYSQL_OPEN_IGNORE_FLUSH;
|
|
/*
|
|
Even though we have an MDL lock on the table here, we don't
|
|
pass MYSQL_OPEN_HAS_MDL_LOCK to open_and_lock_tables
|
|
since to truncate a MERGE table, we must open and lock
|
|
merge children, and on those we don't have an MDL lock.
|
|
Thus clear the ticket to satisfy MDL asserts.
|
|
*/
|
|
table_ref->mdl_request.ticket= NULL;
|
|
}
|
|
|
|
/* Open the table as it will handle some required preparations. */
|
|
if (open_and_lock_tables(thd, table_ref, FALSE, flags))
|
|
DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
|
|
|
|
/* Whether to truncate regardless of foreign keys. */
|
|
if (! (thd->variables.option_bits & OPTION_NO_FOREIGN_KEY_CHECKS))
|
|
if (fk_truncate_illegal_if_parent(thd, table_ref->table, "TRUNCATE"))
|
|
DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
|
|
|
|
table= table_ref->table;
|
|
|
|
if ((table->file->ht->flags & HTON_TRUNCATE_REQUIRES_EXCLUSIVE_USE) &&
|
|
!is_tmp_table)
|
|
{
|
|
if (wait_while_table_is_used(thd, table, HA_EXTRA_FORCE_REOPEN))
|
|
DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
|
|
/*
|
|
Get rid of all TABLE instances belonging to this thread
|
|
except one to be used for TRUNCATE
|
|
*/
|
|
close_all_tables_for_name(thd, table->s,
|
|
HA_EXTRA_NOT_USED,
|
|
table);
|
|
}
|
|
|
|
error= table->file->ha_truncate();
|
|
|
|
if (!is_tmp_table && !error)
|
|
{
|
|
backup_log_info ddl_log;
|
|
bzero(&ddl_log, sizeof(ddl_log));
|
|
ddl_log.query= { C_STRING_WITH_LEN("TRUNCATE") };
|
|
ddl_log.org_partitioned= table->file->partition_engine();
|
|
lex_string_set(&ddl_log.org_storage_engine_name,
|
|
table->file->real_table_type());
|
|
ddl_log.org_database= table->s->db;
|
|
ddl_log.org_table= table->s->table_name;
|
|
ddl_log.org_table_id= table->s->tabledef_version;
|
|
backup_log_ddl(&ddl_log);
|
|
}
|
|
|
|
if (unlikely(error))
|
|
{
|
|
table->file->print_error(error, MYF(0));
|
|
/*
|
|
If truncate method is not implemented then we don't binlog the
|
|
statement. If truncation has failed in a transactional engine then also
|
|
we don't binlog the statement. Only in non transactional engine we binlog
|
|
inspite of errors.
|
|
*/
|
|
if (error == HA_ERR_WRONG_COMMAND ||
|
|
table->file->has_transactions_and_rollback())
|
|
DBUG_RETURN(TRUNCATE_FAILED_SKIP_BINLOG);
|
|
else
|
|
DBUG_RETURN(TRUNCATE_FAILED_BUT_BINLOG);
|
|
}
|
|
DBUG_RETURN(TRUNCATE_OK);
|
|
}
|
|
|
|
|
|
/*
|
|
Handle locking a base table for truncate.
|
|
|
|
@param[in] thd Thread context.
|
|
@param[in] table_ref Table list element for the table to
|
|
be truncated.
|
|
@param[out] hton_can_recreate Set to TRUE if table can be dropped
|
|
and recreated.
|
|
|
|
@retval FALSE Success.
|
|
@retval TRUE Error.
|
|
*/
|
|
|
|
bool Sql_cmd_truncate_table::lock_table(THD *thd, TABLE_LIST *table_ref,
|
|
bool *hton_can_recreate)
|
|
{
|
|
const handlerton *hton;
|
|
bool versioned;
|
|
bool sequence= false;
|
|
TABLE *table= NULL;
|
|
DBUG_ENTER("Sql_cmd_truncate_table::lock_table");
|
|
|
|
/* Lock types are set in the parser. */
|
|
DBUG_ASSERT(table_ref->lock_type == TL_WRITE);
|
|
/* The handler truncate protocol dictates a exclusive lock. */
|
|
DBUG_ASSERT(table_ref->mdl_request.type == MDL_EXCLUSIVE);
|
|
|
|
/*
|
|
Before doing anything else, acquire a metadata lock on the table,
|
|
or ensure we have one. We don't use open_and_lock_tables()
|
|
right away because we want to be able to truncate (and recreate)
|
|
corrupted tables, those that we can't fully open.
|
|
|
|
MySQL manual documents that TRUNCATE can be used to repair a
|
|
damaged table, i.e. a table that can not be fully "opened".
|
|
In particular MySQL manual says: As long as the table format
|
|
file tbl_name.frm is valid, the table can be re-created as
|
|
an empty table with TRUNCATE TABLE, even if the data or index
|
|
files have become corrupted.
|
|
*/
|
|
if (thd->locked_tables_mode)
|
|
{
|
|
if (!(table= find_table_for_mdl_upgrade(thd, table_ref->db.str,
|
|
table_ref->table_name.str, NULL)))
|
|
DBUG_RETURN(TRUE);
|
|
|
|
versioned= table->versioned();
|
|
hton= table->file->ht;
|
|
#ifdef WITH_WSREP
|
|
/* Resolve should we replicate truncate. It should
|
|
be replicated if storage engine(s) associated
|
|
are replicated by Galera. If this is partitioned
|
|
table we need to find out default partition
|
|
handlerton.
|
|
*/
|
|
if (WSREP(thd) &&
|
|
!wsrep_should_replicate_ddl(thd, table->file->partition_ht() ?
|
|
table->file->partition_ht() : hton))
|
|
DBUG_RETURN(TRUE);
|
|
#endif
|
|
|
|
table_ref->mdl_request.ticket= table->mdl_ticket;
|
|
}
|
|
else
|
|
{
|
|
DBUG_ASSERT(table_ref->next_global == NULL);
|
|
if (lock_table_names(thd, table_ref, NULL,
|
|
thd->variables.lock_wait_timeout, 0))
|
|
DBUG_RETURN(TRUE);
|
|
|
|
TABLE_SHARE *share= tdc_acquire_share(thd, table_ref, GTS_TABLE | GTS_VIEW);
|
|
if (share == NULL)
|
|
DBUG_RETURN(TRUE);
|
|
DBUG_ASSERT(share != UNUSABLE_TABLE_SHARE);
|
|
|
|
versioned= share->versioned;
|
|
sequence= share->table_type == TABLE_TYPE_SEQUENCE;
|
|
hton= share->db_type();
|
|
#ifdef WITH_WSREP
|
|
if (WSREP(thd) && hton != view_pseudo_hton)
|
|
{
|
|
/* Resolve should we replicate truncate. It should
|
|
be replicated if storage engine(s) associated
|
|
are replicated by Galera. If this is partitioned
|
|
table we need to find out default partition
|
|
handlerton.
|
|
*/
|
|
const handlerton* const ht=
|
|
#ifdef WITH_PARTITION_STORAGE_ENGINE
|
|
share->default_part_plugin ?
|
|
plugin_hton(share->default_part_plugin) :
|
|
#endif
|
|
hton;
|
|
|
|
if (ht && !wsrep_should_replicate_ddl(thd, ht))
|
|
{
|
|
tdc_release_share(share);
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!versioned)
|
|
tdc_remove_referenced_share(thd, share);
|
|
else
|
|
tdc_release_share(share);
|
|
|
|
if (hton == view_pseudo_hton)
|
|
{
|
|
my_error(ER_NO_SUCH_TABLE, MYF(0), table_ref->db.str,
|
|
table_ref->table_name.str);
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
}
|
|
|
|
*hton_can_recreate= (!sequence &&
|
|
ha_check_storage_engine_flag(hton, HTON_CAN_RECREATE));
|
|
|
|
if (versioned)
|
|
{
|
|
my_error(ER_VERS_NOT_SUPPORTED, MYF(0), "TRUNCATE TABLE");
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
|
|
/*
|
|
A storage engine can recreate or truncate the table only if there
|
|
are no references to it from anywhere, i.e. no cached TABLE in the
|
|
table cache.
|
|
*/
|
|
if (thd->locked_tables_mode)
|
|
{
|
|
DEBUG_SYNC(thd, "upgrade_lock_for_truncate");
|
|
/* To remove the table from the cache we need an exclusive lock. */
|
|
if (wait_while_table_is_used(thd, table,
|
|
*hton_can_recreate ? HA_EXTRA_PREPARE_FOR_DROP : HA_EXTRA_NOT_USED))
|
|
DBUG_RETURN(TRUE);
|
|
m_ticket_downgrade= table->mdl_ticket;
|
|
/* Close if table is going to be recreated. */
|
|
if (*hton_can_recreate)
|
|
close_all_tables_for_name(thd, table->s, HA_EXTRA_NOT_USED, NULL);
|
|
}
|
|
DBUG_RETURN(FALSE);
|
|
}
|
|
|
|
|
|
/*
|
|
Optimized delete of all rows by doing a full generate of the table.
|
|
|
|
@remark Will work even if the .MYI and .MYD files are destroyed.
|
|
In other words, it works as long as the .FRM is intact and
|
|
the engine supports re-create.
|
|
|
|
@param thd Thread context.
|
|
@param table_ref Table list element for the table to be truncated.
|
|
|
|
@retval FALSE Success.
|
|
@retval TRUE Error.
|
|
*/
|
|
|
|
bool Sql_cmd_truncate_table::truncate_table(THD *thd, TABLE_LIST *table_ref)
|
|
{
|
|
int error;
|
|
bool binlog_stmt;
|
|
DBUG_ENTER("Sql_cmd_truncate_table::truncate_table");
|
|
|
|
DBUG_ASSERT((!table_ref->table) ||
|
|
(table_ref->table && table_ref->table->s));
|
|
|
|
/* Initialize, or reinitialize in case of reexecution (SP). */
|
|
m_ticket_downgrade= NULL;
|
|
|
|
/* If it is a temporary table, no need to take locks. */
|
|
if (is_temporary_table(table_ref))
|
|
{
|
|
/*
|
|
In RBR, the statement is not binlogged if the table is temporary or
|
|
table is not up to date in binlog.
|
|
*/
|
|
binlog_stmt= (!thd->is_binlog_format_row() &&
|
|
table_ref->table->s->using_binlog());
|
|
|
|
thd->close_unused_temporary_table_instances(table_ref);
|
|
|
|
error= handler_truncate(thd, table_ref, TRUE);
|
|
|
|
/*
|
|
No need to invalidate the query cache, queries with temporary
|
|
tables are not in the cache. No need to write to the binary
|
|
log a failed row-by-row delete even if under RBR as the table
|
|
might not exist on the slave.
|
|
*/
|
|
}
|
|
else /* It's not a temporary table. */
|
|
{
|
|
bool hton_can_recreate;
|
|
|
|
#ifdef WITH_WSREP
|
|
if (WSREP(thd) && wsrep_thd_is_local(thd))
|
|
{
|
|
wsrep::key_array keys;
|
|
/* Do not start TOI if table is not found */
|
|
if (!wsrep_append_fk_parent_table(thd, table_ref, &keys))
|
|
{
|
|
if (keys.empty())
|
|
{
|
|
if (wsrep_to_isolation_begin(thd, table_ref->db.str, table_ref->table_name.str, NULL))
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
else
|
|
{
|
|
if (wsrep_to_isolation_begin(thd, NULL, NULL, table_ref, NULL, &keys))
|
|
DBUG_RETURN(TRUE);
|
|
}
|
|
}
|
|
}
|
|
#endif /* WITH_WSREP */
|
|
|
|
if (lock_table(thd, table_ref, &hton_can_recreate))
|
|
DBUG_RETURN(TRUE);
|
|
|
|
/*
|
|
This is mainly here for truncate_notembedded.test, but it is still
|
|
useful to check killed after we got the lock
|
|
*/
|
|
|
|
if (thd->killed)
|
|
DBUG_RETURN(TRUE);
|
|
|
|
if (hton_can_recreate)
|
|
{
|
|
/*
|
|
The storage engine can truncate the table by creating an
|
|
empty table with the same structure.
|
|
*/
|
|
error= dd_recreate_table(thd, table_ref->db.str, table_ref->table_name.str);
|
|
|
|
if (thd->locked_tables_mode && thd->locked_tables_list.reopen_tables(thd, false))
|
|
{
|
|
thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
|
|
error= 1;
|
|
}
|
|
/* No need to binlog a failed truncate-by-recreate. */
|
|
binlog_stmt= !error;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
The engine does not support truncate-by-recreate.
|
|
Attempt to use the handler truncate method.
|
|
*/
|
|
error= handler_truncate(thd, table_ref, FALSE);
|
|
|
|
if (error == TRUNCATE_OK && thd->locked_tables_mode &&
|
|
(table_ref->table->file->ht->flags &
|
|
(HTON_REQUIRES_CLOSE_AFTER_TRUNCATE |
|
|
HTON_TRUNCATE_REQUIRES_EXCLUSIVE_USE)))
|
|
{
|
|
thd->locked_tables_list.mark_table_for_reopen(table_ref->table);
|
|
if (unlikely(thd->locked_tables_list.reopen_tables(thd, false)))
|
|
thd->locked_tables_list.unlink_all_closed_tables(thd, NULL, 0);
|
|
}
|
|
|
|
/*
|
|
All effects of a TRUNCATE TABLE operation are committed even if
|
|
truncation fails in the case of non transactional tables. Thus, the
|
|
query must be written to the binary log. The only exception is a
|
|
unimplemented truncate method.
|
|
*/
|
|
if (unlikely(error == TRUNCATE_OK || error == TRUNCATE_FAILED_BUT_BINLOG))
|
|
binlog_stmt= true;
|
|
else
|
|
binlog_stmt= false;
|
|
}
|
|
|
|
/*
|
|
If we tried to open a MERGE table and failed due to problems with the
|
|
children tables, the table will have been closed and table_ref->table
|
|
will be invalid. Reset the pointer here in any case as
|
|
query_cache_invalidate does not need a valid TABLE object.
|
|
*/
|
|
table_ref->table= NULL;
|
|
query_cache_invalidate3(thd, table_ref, FALSE);
|
|
}
|
|
|
|
/* DDL is logged in statement format, regardless of binlog format. */
|
|
if (binlog_stmt)
|
|
error|= MY_TEST(write_bin_log(thd, !error, thd->query(),
|
|
thd->query_length()));
|
|
|
|
/*
|
|
A locked table ticket was upgraded to a exclusive lock. After the
|
|
the query has been written to the binary log, downgrade the lock
|
|
to a shared one.
|
|
*/
|
|
if (m_ticket_downgrade)
|
|
m_ticket_downgrade->downgrade_lock(MDL_SHARED_NO_READ_WRITE);
|
|
|
|
DBUG_RETURN(error);
|
|
}
|
|
|
|
/**
|
|
Execute a TRUNCATE statement at runtime.
|
|
|
|
@param thd The current thread.
|
|
|
|
@return FALSE on success.
|
|
*/
|
|
|
|
bool Sql_cmd_truncate_table::execute(THD *thd)
|
|
{
|
|
bool res= TRUE;
|
|
TABLE_LIST *table= thd->lex->first_select_lex()->table_list.first;
|
|
DBUG_ENTER("Sql_cmd_truncate_table::execute");
|
|
|
|
if (check_one_table_access(thd, DROP_ACL, table))
|
|
DBUG_RETURN(res);
|
|
|
|
if (! (res= truncate_table(thd, table)))
|
|
my_ok(thd);
|
|
|
|
DBUG_RETURN(res);
|
|
}
|