415 lines
14 KiB
C++
415 lines
14 KiB
C++
/* Copyright (c) 2018, 2021, Alibaba and/or its affiliates. All rights reserved.
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License, version 2.0,
|
|
as published by the Free Software Foundation.
|
|
This program is also distributed with certain software (including
|
|
but not limited to OpenSSL) that is licensed under separate terms,
|
|
as designated in a particular file or component or in included license
|
|
documentation. The authors of MySQL/PolarDB-X Engine hereby grant you an
|
|
additional permission to link the program and your derivative works with the
|
|
separately licensed software that they have included with
|
|
MySQL/PolarDB-X Engine.
|
|
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, version 2.0, 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-1301 USA */
|
|
|
|
|
|
#include "sql/recycle_bin/recycle_proc.h"
|
|
#include "sql/auth/auth_acls.h"
|
|
#include "sql/recycle_bin/recycle.h"
|
|
#include "sql/recycle_bin/recycle_table.h"
|
|
#include "sql/sql_time.h"
|
|
#include "sql/tztime.h"
|
|
|
|
#include "sql/auth/auth_common.h"
|
|
#include "sql/dd/cache/dictionary_client.h"
|
|
#include "sql/dd/dd_schema.h"
|
|
#include "sql/dd/properties.h"
|
|
#include "sql/sql_rename.h"
|
|
#include "sql/thd_raii.h"
|
|
|
|
#include "mysql/components/services/log_builtins.h"
|
|
|
|
namespace im {
|
|
|
|
namespace recycle_bin {
|
|
|
|
/* Uniform schema name for recycle bin */
|
|
LEX_CSTRING RECYCLE_BIN_PROC_SCHEMA = {STRING_WITH_LEN("dbms_recycle")};
|
|
|
|
|
|
/* Singleton instance for show_tables */
|
|
Proc *Recycle_proc_show::instance() {
|
|
static Proc *proc = new Recycle_proc_show(key_memory_recycle);
|
|
|
|
return proc;
|
|
}
|
|
|
|
/* Singleton instance for purge_table */
|
|
Proc *Recycle_proc_purge::instance() {
|
|
static Proc *proc = new Recycle_proc_purge(key_memory_recycle);
|
|
|
|
return proc;
|
|
}
|
|
|
|
/**
|
|
Evoke the sql_cmd object for show_tables() proc.
|
|
*/
|
|
Sql_cmd *Recycle_proc_show::evoke_cmd(THD *thd, List<Item> *list) const {
|
|
return new (thd->mem_root) Sql_cmd_type(thd, list, this);
|
|
}
|
|
|
|
/**
|
|
Evoke the sql_cmd object for purge_table() proc.
|
|
*/
|
|
Sql_cmd *Recycle_proc_purge::evoke_cmd(THD *thd, List<Item> *list) const {
|
|
return new (thd->mem_root) Sql_cmd_type(thd, list, this);
|
|
}
|
|
/**
|
|
Show the tables in recycle_bin
|
|
|
|
@param[in] THD Thread context
|
|
|
|
@retval true Failure
|
|
@retval false Success
|
|
*/
|
|
bool Sql_cmd_recycle_proc_show::pc_execute(THD *) {
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_show::pc_execute");
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
/**
|
|
Convert the YYMMDDHHMMSS number to MYSQL_TIME.
|
|
*/
|
|
static void get_local_time(THD *thd, ulonglong nr, MYSQL_TIME *timestamp) {
|
|
MYSQL_TIME t_mysql_time;
|
|
bool not_used;
|
|
Time_zone *tz = thd->variables.time_zone;
|
|
|
|
/* Convert longlong to MYSQL_TIME (UTC) */
|
|
my_longlong_to_datetime_with_warn(nr, &t_mysql_time, MYF(0));
|
|
|
|
my_time_t t_time;
|
|
/* Convert MYSQL_TIME to epoch time */
|
|
t_time = my_tz_OFFSET0->TIME_to_gmt_sec(&t_mysql_time, ¬_used);
|
|
|
|
/* Convert epoch time to local time */
|
|
tz->gmt_sec_to_TIME(timestamp, t_time);
|
|
}
|
|
|
|
/**
|
|
Calculate future time by adding xxx seconds.
|
|
*/
|
|
static bool get_future_time(THD *thd, MYSQL_TIME *timestamp,
|
|
ulonglong seconds) {
|
|
Interval interval;
|
|
memset(&interval, 0, sizeof(Interval));
|
|
interval.second = seconds;
|
|
return date_add_interval_with_warn(thd, timestamp, INTERVAL_SECOND, interval);
|
|
}
|
|
|
|
/**
|
|
Query all recycle tables.
|
|
*/
|
|
void Sql_cmd_recycle_proc_show::send_result(THD *thd, bool error) {
|
|
Protocol *protocol = thd->get_protocol();
|
|
std::vector<Recycle_show_result *> container;
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_show::send_result");
|
|
if (error) {
|
|
DBUG_ASSERT(thd->is_error());
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
if (get_recycle_tables(thd, thd->mem_root, &container)) DBUG_VOID_RETURN;
|
|
|
|
if (m_proc->send_result_metadata(thd)) DBUG_VOID_RETURN;
|
|
|
|
for (auto it = container.cbegin(); it != container.cend(); it++) {
|
|
MYSQL_TIME timestamp;
|
|
|
|
protocol->start_row();
|
|
protocol->store_string((*it)->schema.str, (*it)->schema.length,
|
|
system_charset_info);
|
|
protocol->store_string((*it)->table.str, (*it)->table.length,
|
|
system_charset_info);
|
|
protocol->store_string((*it)->origin_schema.str,
|
|
(*it)->origin_schema.length,
|
|
system_charset_info);
|
|
protocol->store_string((*it)->origin_table.str, (*it)->origin_table.length,
|
|
system_charset_info);
|
|
|
|
get_local_time(thd, (*it)->recycled_time, ×tamp);
|
|
protocol->store_datetime(timestamp, 0);
|
|
|
|
if (get_future_time(thd, ×tamp, recycle_bin_retention))
|
|
protocol->store_null();
|
|
else
|
|
protocol->store_datetime(timestamp, 0);
|
|
|
|
if (protocol->end_row()) DBUG_VOID_RETURN;
|
|
}
|
|
my_eof(thd);
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/* Override the default send result. */
|
|
void Sql_cmd_recycle_proc_purge::send_result(THD *thd MY_ATTRIBUTE((unused)),
|
|
bool error) {
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_purge::send_result");
|
|
if (error) {
|
|
DBUG_ASSERT(thd->is_error());
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
/** Ok protocal package has been set within mysql_rm_table(); */
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/* Override the default check_access. */
|
|
bool Sql_cmd_recycle_proc_purge::check_access(THD *thd) {
|
|
char buff[128];
|
|
String str(buff, sizeof(buff), system_charset_info);
|
|
String *res;
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_purge::check_access");
|
|
Recycle_process_context recycle_context(thd);
|
|
/* Special privilege flag for ddl on recycle schema */
|
|
thd->recycle_state->set_priv_relax();
|
|
|
|
res = (*m_list)[0]->val_str(&str);
|
|
const char *table = strmake_root(thd->mem_root, res->ptr(), res->length());
|
|
if (table == NULL || strlen(table) == 0) {
|
|
my_error(ER_NATIVE_PROC_PARAMETER_MISMATCH, MYF(0), 1,
|
|
m_proc->qname().c_str());
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
TABLE_LIST *table_list =
|
|
build_table_list(thd, RECYCLE_BIN_SCHEMA.str, RECYCLE_BIN_SCHEMA.length,
|
|
table, strlen(table));
|
|
if (check_table_access(thd, DROP_ACL, table_list, false, 1, false))
|
|
DBUG_RETURN(true);
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
/**
|
|
Purge table in recycle_bin
|
|
|
|
@param[in] THD Thread context
|
|
|
|
@retval true Failure
|
|
@retval false Success
|
|
*/
|
|
bool Sql_cmd_recycle_proc_purge::pc_execute(THD *thd) {
|
|
char buff[128];
|
|
String str(buff, sizeof(buff), system_charset_info);
|
|
String *res;
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_purge::pc_execute");
|
|
res = (*m_list)[0]->val_str(&str);
|
|
const char *table = strmake_root(thd->mem_root, res->ptr(), res->length());
|
|
|
|
DBUG_RETURN(recycle_purge_table(thd, table));
|
|
}
|
|
|
|
/* Singleton instance for restore_table */
|
|
Proc *Recycle_proc_restore::instance() {
|
|
static Proc *proc = new Recycle_proc_restore(key_memory_recycle);
|
|
|
|
return proc;
|
|
}
|
|
|
|
/**
|
|
Evoke the sql_cmd object for restore_table() proc.
|
|
*/
|
|
Sql_cmd *Recycle_proc_restore::evoke_cmd(THD *thd, List<Item> *list) const {
|
|
return new (thd->mem_root) Sql_cmd_type(thd, list, this);
|
|
}
|
|
|
|
/* Override the default send result. */
|
|
void Sql_cmd_recycle_proc_restore::send_result(THD *thd, bool error) {
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_restore::restore_result");
|
|
if (error) {
|
|
(void)thd;
|
|
DBUG_ASSERT(thd->is_error());
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
/** Ok protocal package has been set within mysql_rename_tables(); */
|
|
DBUG_VOID_RETURN;
|
|
}
|
|
|
|
/* Override the default check_access. */
|
|
bool Sql_cmd_recycle_proc_restore::check_access(THD *thd) {
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_restore::check_access");
|
|
if (Sql_cmd_proc::check_access(thd)) DBUG_RETURN(true);
|
|
|
|
char buff[128];
|
|
String str(buff, sizeof(buff), system_charset_info);
|
|
String *res;
|
|
Recycle_process_context recycle_context(thd);
|
|
/* Special privilege flag for ddl on recycle schema */
|
|
thd->recycle_state->set_priv_relax();
|
|
|
|
/* 1. Parse the input parameters */
|
|
const char *old_table = nullptr;
|
|
const char *new_db = nullptr;
|
|
const char *new_table = nullptr;
|
|
res = (*m_list)[0]->val_str(&str);
|
|
old_table = strmake_root(thd->mem_root, res->ptr(), res->length());
|
|
if (old_table == NULL || strlen(old_table) == 0) {
|
|
my_error(ER_NATIVE_PROC_PARAMETER_MISMATCH, MYF(0), 1,
|
|
m_proc->qname().c_str());
|
|
DBUG_RETURN(true);
|
|
} else if (m_list->size() == 3) {
|
|
res = (*m_list)[1]->val_str(&str);
|
|
new_db = strmake_root(thd->mem_root, res->ptr(), res->length());
|
|
res = (*m_list)[2]->val_str(&str);
|
|
new_table = strmake_root(thd->mem_root, res->ptr(), res->length());
|
|
}
|
|
|
|
/* 2. The old table must exist in recycle_bin */
|
|
{
|
|
if (!thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::SCHEMA, RECYCLE_BIN_SCHEMA.str, "",
|
|
MDL_INTENTION_EXCLUSIVE)) {
|
|
MDL_request schema_mdl_request;
|
|
MDL_REQUEST_INIT(&schema_mdl_request, MDL_key::SCHEMA,
|
|
RECYCLE_BIN_SCHEMA.str, "", MDL_INTENTION_EXCLUSIVE,
|
|
MDL_TRANSACTION);
|
|
if (thd->mdl_context.acquire_lock(&schema_mdl_request,
|
|
thd->variables.lock_wait_timeout))
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
if (!thd->mdl_context.owns_equal_or_stronger_lock(
|
|
MDL_key::TABLE, RECYCLE_BIN_SCHEMA.str, old_table, MDL_EXCLUSIVE)) {
|
|
MDL_request table_mdl_request;
|
|
MDL_REQUEST_INIT(&table_mdl_request, MDL_key::TABLE,
|
|
RECYCLE_BIN_SCHEMA.str, old_table, MDL_EXCLUSIVE,
|
|
MDL_TRANSACTION);
|
|
if (thd->mdl_context.acquire_lock(&table_mdl_request,
|
|
thd->variables.lock_wait_timeout))
|
|
DBUG_RETURN(true);
|
|
}
|
|
}
|
|
|
|
const dd::Schema *recycle_schema = nullptr;
|
|
const dd::Table *old_table_def = nullptr;
|
|
dd::cache::Dictionary_client::Auto_releaser releaser(thd->dd_client());
|
|
if (thd->dd_client()->acquire(RECYCLE_BIN_SCHEMA.str, &recycle_schema)) {
|
|
LogErr(WARNING_LEVEL, ER_RECYCLE_BIN, "acquire recycle schema");
|
|
DBUG_RETURN(true);
|
|
}
|
|
if (recycle_schema == nullptr) {
|
|
my_error(ER_PREPARE_RECYCLE_TABLE_ERROR, MYF(0),
|
|
"recycle schema didn't exist.");
|
|
DBUG_RETURN(true);
|
|
}
|
|
if (thd->dd_client()->acquire(RECYCLE_BIN_SCHEMA.str, old_table,
|
|
&old_table_def)) {
|
|
DBUG_RETURN(true);
|
|
}
|
|
if (old_table_def == nullptr) {
|
|
std::stringstream ss;
|
|
ss << old_table << " didn't exist";
|
|
my_error(ER_PREPARE_RECYCLE_TABLE_ERROR, MYF(0), ss.str().c_str());
|
|
DBUG_RETURN(true);
|
|
}
|
|
if (!old_table_def->options().exists(ORIGIN_SCHEMA.str) ||
|
|
!old_table_def->options().exists(ORIGIN_TABLE.str)) {
|
|
std::stringstream ss;
|
|
ss << old_table << " isn't recycled table";
|
|
my_error(ER_PREPARE_RECYCLE_TABLE_ERROR, MYF(0), ss.str().c_str());
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
/* 3. If the new_db and new_table is unvalid, just restore to the original
|
|
name */
|
|
Table_ident *old_table_ident = nullptr;
|
|
Table_ident *new_table_ident = nullptr;
|
|
old_table_ident = new (thd->mem_root)
|
|
Table_ident(to_lex_cstring(thd->mem_strdup(RECYCLE_BIN_SCHEMA.str)),
|
|
to_lex_cstring(old_table));
|
|
if (new_db && new_table && strlen(new_db) && strlen(new_table)) {
|
|
new_table_ident = new (thd->mem_root)
|
|
Table_ident(to_lex_cstring(new_db), to_lex_cstring(new_table));
|
|
} else {
|
|
LEX_STRING origin_db;
|
|
LEX_STRING origin_table;
|
|
old_table_def->options().get(ORIGIN_SCHEMA.str, &origin_db, thd->mem_root);
|
|
old_table_def->options().get(ORIGIN_TABLE.str, &origin_table,
|
|
thd->mem_root);
|
|
new_table_ident = new (thd->mem_root)
|
|
Table_ident(to_lex_cstring(origin_db), to_lex_cstring(origin_table));
|
|
}
|
|
|
|
/* 4. Add table to table_list array */
|
|
LEX *const lex = thd->lex;
|
|
SELECT_LEX *const select_lex = lex->current_select();
|
|
if (select_lex->add_table_to_list(thd, old_table_ident, NULL,
|
|
TL_OPTION_UPDATING, TL_IGNORE,
|
|
MDL_EXCLUSIVE) == NULL ||
|
|
select_lex->add_table_to_list(thd, new_table_ident, NULL,
|
|
TL_OPTION_UPDATING, TL_IGNORE,
|
|
MDL_EXCLUSIVE) == NULL) {
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
/* 5. Check access, old table in recycle_bin need ALTER_ACL and DROP_ACL,
|
|
and new table need CREATE_ACL and INSERT_ACL */
|
|
TABLE_LIST *const first_table = select_lex->get_table_list();
|
|
TABLE_LIST *table;
|
|
for (table = first_table; table; table = table->next_local->next_local) {
|
|
if (check_table_access(thd, ALTER_ACL | DROP_ACL, table, false, 1, false) ||
|
|
check_table_access(thd, INSERT_ACL | CREATE_ACL, table->next_local,
|
|
false, 1, false)) {
|
|
DBUG_RETURN(true);
|
|
}
|
|
}
|
|
|
|
DBUG_RETURN(false);
|
|
}
|
|
|
|
/**
|
|
Restore table in recycle_bin
|
|
|
|
@param[in] THD Thread context
|
|
|
|
@retval true Failure
|
|
@retval false Success
|
|
*/
|
|
bool Sql_cmd_recycle_proc_restore::pc_execute(THD *thd) {
|
|
DBUG_ENTER("Sql_cmd_recycle_proc_restore::pc_execute");
|
|
|
|
Disable_binlog_guard binlog_guard(thd);
|
|
/*
|
|
For statements which need this, prevent InnoDB from automatically
|
|
committing InnoDB transaction each time data-dictionary tables are
|
|
closed after being updated.
|
|
*/
|
|
Disable_autocommit_guard autocommit_guard(thd);
|
|
|
|
LEX *const lex = thd->lex;
|
|
SELECT_LEX *const select_lex = lex->current_select();
|
|
TABLE_LIST *const first_table = select_lex->get_table_list();
|
|
|
|
/* Reuse the rename process */
|
|
bool ret = mysql_rename_tables(thd, first_table, true);
|
|
if (!ret) {
|
|
std::stringstream ss;
|
|
ss << " only restore table " << first_table->table_name
|
|
<< " on current node, other replicas should be handled separately.";
|
|
my_error(ER_PREPARE_RECYCLE_TABLE_ERROR, MYF(0), ss.str().c_str());
|
|
}
|
|
|
|
DBUG_RETURN(true);
|
|
}
|
|
|
|
} /* namespace recycle_bin */
|
|
|
|
} /* namespace im */
|
|
|
|
|