polardbxengine/storage/ndb/plugin/ndb_ddl_transaction_ctx.h

240 lines
10 KiB
C++

/*
Copyright (c) 2019, Oracle 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 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.
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
*/
#ifndef NDB_DDL_TRANSACTION_CTX_H
#define NDB_DDL_TRANSACTION_CTX_H
#include <string>
#include <vector>
#include "sql/dd/string_type.h"
#include "storage/ndb/plugin/ndb_thd.h"
namespace dd {
typedef String_type sdi_t;
}
/* A class representing a DDL statement. */
class Ndb_DDL_stmt {
public:
enum DDL_type { CREATE_TABLE, RENAME_TABLE, DROP_TABLE };
private:
const DDL_type m_ddl_type; // DDL type
const std::vector<std::string>
m_info; // vector to store all the DDL information
bool m_stmt_distributed{false}; // Flag that has the distribution status
public:
template <typename... Args>
Ndb_DDL_stmt(DDL_type ddl_type, Args... info)
: m_ddl_type(ddl_type), m_info{info...} {}
const std::vector<std::string> &get_info() const { return m_info; }
DDL_type get_ddl_type() const { return m_ddl_type; }
void mark_as_distributed() { m_stmt_distributed = true; }
bool has_been_distributed() const { return m_stmt_distributed; }
};
/* DDL Transaction context class to log the DDLs being executed.
A DDL can be executed by making a single request or mutliple requests to
the Storage Engine depending on the nature of the DDL. For example, a
CREATE TABLE query can be done in a single request to the SE but a ALTER
TABLE COPY would require more than a single request. These requests are
the statements sent to the SE for execution. Apart from these
statements, every DDL would also involve executing statements in InnoDB
SE, for updating the entries in DD and executing statements in binlog
handlers. A DDL transaction is a collection of all these statements. To
make such a transaction Atomic, the SQL Layer uses a 2PC commit protocol
derived from the Open/XA distributed transaction specifications.
In ndbcluster, due to the absence of support for temp tables,
maintaining a DDL transaction is not possible and we have to commit the
DDL statements then and there. To support Atomic DDLs with such a setup,
a logger that logs all the DDL statements executed in ndbcluster is
required. And if the SQL Layer asks for a rollback at the end of the
transaction, the schema changes can be undone by simply reversing the
statements.
This class implements such a logger to log all the statements that got
executed in ndbcluster as the part of a DDL transaction. It also provides
various methods that can be used to commit/rollback the changes when
requested by the SQL Layer at the end of the DDL transaction. */
class Ndb_DDL_transaction_ctx {
private:
class THD *const m_thd;
/* A list to log all the DDL statements executed in ndbcluster. */
std::vector<Ndb_DDL_stmt> m_executed_ddl_stmts;
/* If a participating engine in the DDL transaction is not atomic, then
the SQL Layer requests all the engines involved in the transaction to
commit immediately after every statement. Due to this, in an event of
failure, it also takes care of rolling back any statements that have been
already asked to commit. In such cases, ndbcluster should not rollback the
statements that have been asked to commit already by the SQL Layer.
An example of such query is running 'ALTER TABLE .. ENGINE MYISAM' on an
NDB table.
This variable tracks the position of the statement in m_executed_ddl_stmts
vector until which commit has been requested already by the SQL Layer. */
unsigned int m_latest_committed_stmt{0};
/* Original sdi of the table - to be used during rollback of rename */
std::string m_original_sdi_for_rename;
/* Status of the ongoing DDL */
enum DDL_STATUS {
DDL_EMPTY,
DDL_IN_PROGRESS,
DDL_COMMITED,
DDL_ROLLED_BACK
} m_ddl_status{DDL_EMPTY};
/* @brief Create the Ndb_DDL_stmt objects and append them to the
executed_ddl_stmts list */
template <typename... Args>
void log_ddl_stmt(Ndb_DDL_stmt::DDL_type ddl_op_type, Args... ddl_info) {
/* This is a new DDL transaction if there are no ddl stmts yet */
bool first_stmt_in_trx = false;
if (m_ddl_status == DDL_EMPTY || m_ddl_status == DDL_COMMITED) {
/* If the DDL status is empty, this is the first stmt in the transaction.
If the DDL is already committed, it implies that the stmts so far were
committed and this is a new stmt. This happens when the SQL Layer is
calling commit on individual stmts rather than at the end of
transaction. We should treat all such stmts as mini transactions but
still maintain the log for the overall DDL transaction.
In both the cases, mark the DDL as in progress and mark this as the
first stmt.*/
m_ddl_status = DDL_IN_PROGRESS;
first_stmt_in_trx = true;
}
/* Log them only if DDL is in progress */
if (m_ddl_status == DDL_IN_PROGRESS) {
m_executed_ddl_stmts.emplace_back(ddl_op_type, ddl_info...);
/* Register ndbcluster as a part of the stmt. Additionally register
it as a part of the transaction if this is the first stmt. */
ndb_thd_register_trans(m_thd, first_stmt_in_trx);
}
}
/* Methods to handle rollback of individual DDls */
bool rollback_create_table(const Ndb_DDL_stmt &);
bool rollback_rename_table(const Ndb_DDL_stmt &);
/* Methods to handle updates during post_ddl phase */
/* @brief Update the table object in DD after a rollback of RENAME table.
The rollback would have actually changed the version
of the NDB table. This method updates that in the DD. */
bool post_ddl_hook_rename_table(const Ndb_DDL_stmt &ddl_stmt);
/* @brief Drop the table with the temporary name from NDB after
the COPY ALTER has been committed successfully. This drop is
delayed until after commit so that if required, ndbcluster can
rollback the changes made by the DDL. */
bool post_ddl_hook_drop_temp_table(const Ndb_DDL_stmt &ddl_stmt);
/* @brief Update the table's id and version in DD. */
bool update_table_id_and_version_in_DD(const char *schema_name,
const char *table_name, int object_id,
int object_version);
/* @brief Retrieve the RENAME stmt, which actually was the final phase
of the COPY ALTER. This statement would have renamed the new
table with the temporary name to a proper name and might
have distributed the changes to the other servers. */
const Ndb_DDL_stmt *retrieve_copy_alter_final_rename_stmt();
public:
Ndb_DDL_transaction_ctx(class THD *thd) : m_thd(thd) {}
void get_original_sdi_for_rename(dd::sdi_t &orig_sdi) const {
DBUG_ASSERT(!m_original_sdi_for_rename.empty());
orig_sdi.assign(m_original_sdi_for_rename.c_str(),
m_original_sdi_for_rename.length());
}
/* @brief Check if the current DDL execution has made any changes
to the Schema that has not been committed yet.
@return Returns true if ddl_status is in progress
false otherwise. */
bool has_uncommitted_schema_changes() const {
return (m_ddl_status == DDL_IN_PROGRESS);
}
/* Helper methods to log the DDL. */
/* @brief Log a create table statement in DDL Context.
@param path_name Path name of the table. */
void log_create_table(const std::string &path_name);
/* @brief Log a rename table statement in DDL Context.
@param old_db_name Old name of the table's database.
@param old_table_name Old name of the table.
@param new_db_name New name of the table's database.
@param new_table_name New name of the table.
@param from Old path name of the table.
@param to New path name of the table.
@param orig_sdi Original sdi of the table. */
void log_rename_table(const std::string &old_db_name,
const std::string &old_table_name,
const std::string &new_db_name,
const std::string &new_table_name,
const std::string &from, const std::string &to,
const std::string &orig_sdi);
/* @brief Log a drop table(with temp name) statement in DDL Context.
@param path_name Path name of the table. */
void log_drop_temp_table(const std::string &path_name);
/* @brief Mark the last DDL stmt as distributed */
void mark_last_stmt_as_distributed() {
m_executed_ddl_stmts[m_executed_ddl_stmts.size() - 1].mark_as_distributed();
}
/* @brief Commit the DDL transaction */
void commit();
/* @brief Rollback any changes done to the Schema during DDL execution.
Iterate the executed_ddl_stmts vector and rollback
all the changes in reverse. Also undo any schema change
distributed through schema distribution */
bool rollback();
/* @brief Check if the DDL is being rollbacked */
bool rollback_in_progress() const {
return (m_ddl_status == DDL_ROLLED_BACK);
}
/* @brief Updates to be run during the post ddl phase. */
bool run_post_ddl_hooks();
};
#endif /* NDB_DDL_TRANSACTION_CTX_H */