diff --git a/mysql-test/suite/backup/backup_aria_concurrent.result b/mysql-test/suite/backup/backup_aria_concurrent.result new file mode 100644 index 0000000000000..07b2341253c2c --- /dev/null +++ b/mysql-test/suite/backup/backup_aria_concurrent.result @@ -0,0 +1,20 @@ +15 30 7500 +Back up the database +BACKUP SERVER TO '$target_directory' 4 CONCURRENT; +Restore the database +# restart: --datadir=MYSQLTEST_VARDIR/some_directory +Check contents after restore +SELECT COUNT(*) FROM table_checks; +COUNT(*) +400 +SELECT * FROM table_checks WHERE sum_id <> 15; +tbl_name sum_id str_len blob_len num_rows +SELECT * FROM table_checks WHERE str_len <> 30; +tbl_name sum_id str_len blob_len num_rows +SELECT * FROM table_checks WHERE blob_len <> 7500; +tbl_name sum_id str_len blob_len num_rows +SELECT * FROM table_checks WHERE num_rows <> 5; +tbl_name sum_id str_len blob_len num_rows +Restart database in original data directory +# restart +Clean up diff --git a/mysql-test/suite/backup/backup_aria_concurrent.test b/mysql-test/suite/backup/backup_aria_concurrent.test new file mode 100644 index 0000000000000..fd77d05a31a80 --- /dev/null +++ b/mysql-test/suite/backup/backup_aria_concurrent.test @@ -0,0 +1,145 @@ + +--source include/have_aria.inc + +--disable_query_log + + +DELIMITER //; +CREATE PROCEDURE populate_data(IN t_name VARCHAR(64), IN num_rows INT) +BEGIN + DECLARE i INT DEFAULT 1; + SET @query = CONCAT('INSERT INTO ', t_name, ' (id, str_val, blob_val) VALUES (?, ?, ?)'); + PREPARE stmt FROM @query; + + WHILE i <= num_rows DO + SET @str = CONCAT('_row_', i); + # Generate a predictable but repeating blob based on the row index + SET @blb = REPEAT(CHAR(97 + (i % 26)), 1500); + EXECUTE stmt USING i, @str, @blb; + SET i = i + 1; + END WHILE; + + DEALLOCATE PREPARE stmt; +END// +DELIMITER ;// + +# Create this many tables transactional and non-transactional each +let $tab_num= 200; +let $num_rows= 5; + +let $i = 1; +while ($i <= $tab_num) { + + # Create Transactional Table + + let $tr=0; + while ($tr <= 1) { + + let $suff= _$i; + let $table_name= ta_tr$tr$suff; + + eval CREATE TABLE $table_name ( + id INT PRIMARY KEY, + str_val VARCHAR(255), + blob_val BLOB, + INDEX idx_str (str_val) + ) ENGINE=Aria TRANSACTIONAL=$tr; + + eval CALL populate_data('$table_name', $num_rows); + + inc $tr; + } + + inc $i; +} + +--enable_query_log + +# All tables have the same data, so we query only one for reference + +let $sum_id= `SELECT SUM(id) FROM ta_tr0_1`; +let $str_len= `SELECT SUM(LENGTH(str_val)) FROM ta_tr0_1`; +let $blob_len= `SELECT SUM(LENGTH(blob_val)) FROM ta_tr0_1`; + +echo $sum_id $str_len $blob_len; + +--let $target_directory=$MYSQLTEST_VARDIR/some_directory + +# Clean up after a previous failed test, in case we are retrying. +--error 0,1 +--rmdir $target_directory + +--echo Back up the database +evalp BACKUP SERVER TO '$target_directory' 4 CONCURRENT; + +--echo Restore the database +--let $restart_parameters=--datadir=$target_directory +--source include/restart_mysqld.inc + +--echo Check contents after restore + +--disable_query_log +CREATE TEMPORARY TABLE table_checks ( + tbl_name VARCHAR(64), + sum_id INT, + str_len INT, + blob_len INT, + num_rows INT +) ENGINE=MEMORY; + +let $i = 1; +while ($i <= $tab_num) { + let $tr=0; + while ($tr <= 1) { + + let $suff= _$i; + let $table_name= ta_tr$tr$suff; + + let $r_sum_id= `SELECT SUM(id) FROM $table_name`; + let $r_str_len= `SELECT SUM(LENGTH(str_val)) FROM $table_name`; + let $r_blob_len= `SELECT SUM(LENGTH(blob_val)) FROM $table_name`; + let $r_num_rows= `SELECT COUNT(*) FROM $table_name`; + + eval INSERT INTO table_checks VALUES ('$table_name', $r_sum_id, $r_str_len, $r_blob_len, $r_num_rows); + + inc $tr; + } + inc $i; +} + +--enable_query_log + +SELECT COUNT(*) FROM table_checks; + +# We expect results in the table to always match the results captured before the BACKUP +# Returned rowsets should be empty +eval SELECT * FROM table_checks WHERE sum_id <> $sum_id; +eval SELECT * FROM table_checks WHERE str_len <> $str_len; +eval SELECT * FROM table_checks WHERE blob_len <> $blob_len; +eval SELECT * FROM table_checks WHERE num_rows <> $num_rows; + +--echo Restart database in original data directory +--let $restart_parameters= +--source include/restart_mysqld.inc + +--echo Clean up + +--disable_query_log + +let $i = 1; +while ($i <= $tab_num) { + let $tr=0; + while ($tr <= 1) { + let $suff= _$i; + let $table_name= ta_tr$tr$suff; + eval DROP TABLE $table_name; + inc $tr; + } + inc $i; +} + +DROP PROCEDURE populate_data; + +--enable_query_log + +--rmdir $MYSQLTEST_VARDIR/some_directory diff --git a/sql/sql_backup.cc b/sql/sql_backup.cc index e1e7f55f951f3..95b762aa37f2d 100644 --- a/sql/sql_backup.cc +++ b/sql/sql_backup.cc @@ -280,8 +280,8 @@ static my_bool backup_start(THD *thd, plugin_ref plugin, void *arg) noexcept static my_bool backup_end(THD *thd, plugin_ref plugin, void *arg) noexcept { - handlerton *hton= plugin_hton(plugin); const backup_target_phase &t{*static_cast(arg)}; + handlerton *hton= plugin_hton(plugin); if (hton->backup_end) return hton->backup_end(thd, t.target, t.phase); return false; diff --git a/storage/innobase/handler/backup_innodb.cc b/storage/innobase/handler/backup_innodb.cc index ea99a255061f7..3dee26debece6 100644 --- a/storage/innobase/handler/backup_innodb.cc +++ b/storage/innobase/handler/backup_innodb.cc @@ -51,6 +51,8 @@ class InnoDB_backup backup_context &context() const noexcept { ut_ad(log_sys.latch_have_any()); ut_ad(trx); return trx->lock.backup; } + /** Calls to step() should be ignored */ + bool ignore_step=false; public: /** Start of BACKUP SERVER: collect all files to be backed up @@ -135,6 +137,13 @@ class InnoDB_backup return fail; } + void stop_processing() noexcept + { + log_sys.latch.wr_lock(); + ignore_step= true; + log_sys.latch.wr_unlock(); + } + /** Process a file that was collected at init(). This may be invoked from multiple concurrent threads. @@ -150,6 +159,11 @@ class InnoDB_backup log_sys.latch.wr_lock(); backup_context &ctx{context()}; ut_ad(ctx.max_first_lsn); + if (ignore_step) + { + log_sys.latch.wr_unlock(); + return 0; + } size_t size{queue.size()}; if (!logs.empty()) { diff --git a/storage/maria/ma_backup.cc b/storage/maria/ma_backup.cc index 9c65b8f6a09e4..9773796c2ee67 100644 --- a/storage/maria/ma_backup.cc +++ b/storage/maria/ma_backup.cc @@ -15,22 +15,25 @@ #include "maria_def.h" #include "ma_backup.h" -#include "mysqld_error.h" -#if 1 // tc_purge(), tdc_purge() -# include "sql_class.h" -# include "table_cache.h" -#endif +#include +#include +#include +#include +#include #include #include #include #include +#include +#include +#include +#include /* Implementation of functions declatred in ma_backup.h: BACKUP SERVER support for Aria engine */ -using namespace std::string_literals; namespace { class Source_dir @@ -67,8 +70,58 @@ namespace MY_DIR *dir_info {nullptr}; }; + /* Utility class to implement the "backup step" interface when + processing several lists. It implements the logic where an item + is processed (copied) from the first list which has available + items, and a "remaining" counter accumulates the number of + items remaining to be processed on all lists, regardless of + whether an item from that list was processed or not. */ + class Copy_from_list + { + int m_remaining {0}; + bool m_copy_done; + public: + Copy_from_list(bool copy_done= false) noexcept + : m_copy_done(copy_done) + { + } + + bool copy_done() const noexcept + { + return m_copy_done; + } + + int remaining() const noexcept + { + return m_remaining; + } + + template + bool operator()(const T &list, std::atomic &copied, + Fn copy_action) noexcept + { + if(!m_copy_done) + { + size_t idx= copied.fetch_add(1, std::memory_order_relaxed); + if (idx < list.size()) + { + if (copy_action(list[idx]) != 0) + return true; + m_copy_done= true; + m_remaining+= static_cast(list.size() - idx - 1U); + } + } + else + { + size_t current_copied= copied.load(std::memory_order_relaxed); + if (current_copied < list.size()) + m_remaining+= static_cast(list.size() - current_copied); + } + return false; + } + }; + - /** Backup state; protected by log_sys.latch */ class Aria_backup { public: @@ -105,95 +158,265 @@ namespace #endif // _WIN32 } - int end(THD *thd) noexcept + bool start_copy_dml_safe() noexcept + { + if (scan_dbdirs()) + return true; + flatten_table_lists(); + return ensure_target_dirs(); + } + + bool start_copy_unsafe() noexcept + { + if (scan_logs()) + return true; + return false; + } + + /* Copy an Aria table that is safe to be copied while concurrent DML + is in progress. */ + int dml_safe_copy_step() noexcept + { + Copy_from_list copy_from_list; + auto copy_table_action= [this](const table_ref &table) noexcept + { + return copy_table(table); + }; + if (copy_from_list(dml_safe_table_list, dml_safe_tables_copied, + copy_table_action) != 0) + return -1; + if (copy_from_list(unsafe_tables_list, unsafe_tables_copied, + copy_table_action) != 0) + return -1; + if (copy_from_list(misc_files, misc_files_copied, + [this](const std::string &path) noexcept + { + return copy_file(path); + }) != 0) + return -1; + return copy_from_list.remaining(); + } + + /* Copy an entity that is not safe to copy if there are concurrent + writes to it. One entity is copied, of the first category that has + any remaning entities to be copied. Returns the total number of + entities to be copied in all categories. Categories in order: + - log control file + - log files + - Aria tables + - other ("miscellaneous") files + */ + int unsafe_copy_step() noexcept + { + bool copy_done= false; + + /* If control file is always the first file copied and there is only + one, it is never included in the "steps remaining" calculation. + Should the order be changed, the calculation needs to be updated for + the control file as well. */ + if (have_control_file) + { + bool already_copied= control_file_copied.exchange(true); + if (!already_copied) + { + if (copy_control_file() != 0) + return -1; + copy_done= true; + } + } + + Copy_from_list copy_from_list(copy_done); + if (copy_from_list(log_files, log_files_copied, + [this](const std::string &path) noexcept + { + return copy_file(path); + }) != 0) + return -1; + + return copy_from_list.remaining(); + } + + int end(bool /*abort*/) noexcept { - int ret_val= perform_backup(); translog_enable_purge(); - return ret_val; + return 0; } private: backup_target target; #ifndef _WIN32 const int datadir_fd; #endif - static const std::vector data_exts; - static const std::string log_file_prefix; + /* All file suffixes are 4 characters long (dot and 3 letter extension) */ + static constexpr size_t suffix_len= 4; + static constexpr const char* data_ext {MARIA_NAME_DEXT}; + static constexpr const char* index_ext {MARIA_NAME_IEXT}; + static constexpr LEX_CSTRING log_file_prefix {C_STRING_WITH_LEN("aria_log.")}; + static constexpr LEX_CSTRING tmp_prefix {C_STRING_WITH_LEN(tmp_file_prefix)}; + /* TODO: .frm failes are not Aria-specific, .MYD and .MYI are MyISAM files; + they are copied here as a stop-gap */ + static constexpr const char* misc_exts[] {".MYD", ".MYI", ".frm"}; + static constexpr const char* control_file_name {"aria_log_control"}; using dir_name = std::string; using dir_contents = std::vector; using database_dir = std::pair; - std::vector database_dirs; + using database_dirs = std::vector; + /* Transactional tables with checksum */ + database_dirs dml_safe_tables; + /* All other Aria tables */ + database_dirs unsafe_tables; + /* Aria log files */ std::vector log_files; + std::vector misc_files; + /* directories in which misc files are */ + std::vector misc_dirs; + bool have_control_file = false; + bool safe_files_copied = false; - int perform_backup() noexcept - { - if (scan_datadir()) - return 1; - if (copy_databases()) - return 1; - if (copy_control_file()) - return 1; - if(translog_flush(translog_get_horizon())) - return 1; - if (copy_logs()) - return 1; - return 0; - } + /* Refer to a string stored elsewhere */ + using dir_ref= std::string_view; + using tablename_ref= std::string_view; + using table_ref= std::pair; + using table_list= std::vector; + + /* Flattened versions of dml_safe_tables and unsafe_tables. */ + table_list dml_safe_table_list; + table_list unsafe_tables_list; + std::atomic dml_safe_tables_copied {0}; + std::atomic unsafe_tables_copied {0}; + std::atomic log_files_copied {0}; + std::atomic misc_files_copied {0}; + std::atomic control_file_copied {false}; - int scan_datadir() noexcept + int scan_dbdirs() noexcept { const char* base_dir = maria_data_root; Source_dir datadir(base_dir, MYF(MY_WANT_STAT)); if (datadir.is_error()) return 1; - datadir.for_each([this](const fileinfo &fi) + int error = datadir.for_each([this](const fileinfo &fi) { if (fi.mystat->st_mode & S_IFDIR) { - if (scan_database_dir(fi.name) != 0) - return 1; - } else if (begins_with(fi.name, log_file_prefix)) - log_files.emplace_back(fi.name); - else if (strcmp(fi.name, "aria_log_control") == 0) - have_control_file = true; + return scan_database_dir(fi.name); + } return 0; }); - return 0; + return error; } int scan_database_dir(const char* dir_name) noexcept { const char* base_dir = maria_data_root; - std::string dir_path = std::string(base_dir) + "/" + dir_name; + const std::string dir_path= build_path(base_dir, dir_name); Source_dir db_dir(dir_path.c_str(), MYF(0)); if (db_dir.is_error()) return 1; - std::vector files_to_backup; - db_dir.for_each([&files_to_backup](const fileinfo &fi) + dir_contents safe; + dir_contents unsafe; + int error= db_dir.for_each([this, &safe, &unsafe, dir_name] + (const fileinfo &fi) { - if (is_db_file(fi.name)) - files_to_backup.emplace_back(fi.name); + const char* filename= fi.name; + size_t filename_len = strlen(filename); + if (filename_len >= suffix_len) + { + const char* suffix = filename + filename_len - suffix_len; + if(match_suffix(suffix, index_ext)) + { + if (!is_tmp_table(filename)) + { + auto is_safe = is_safe_table(dir_name, filename); + if (std::holds_alternative(is_safe)) + { + std::string table_name(filename, filename_len - suffix_len); + if (std::get(is_safe)) + safe.push_back(std::move(table_name)); + else + unsafe.push_back(std::move(table_name)); + } + else + { + return std::get(is_safe); + } + } + } + else if (match_misc_ext(suffix) || !strcmp(filename, "db.opt")) + { + if(misc_dirs.empty() || misc_dirs.back() != dir_name) + misc_dirs.emplace_back(dir_name); + misc_files.push_back(build_path(dir_name, filename)); + } + } return 0; }); - if (!files_to_backup.empty()) - database_dirs.emplace_back(dir_name, std::move(files_to_backup)); - return 0; + if(!error) + { + if (!safe.empty()) + dml_safe_tables.emplace_back(dir_name, std::move(safe)); + if (!unsafe.empty()) + unsafe_tables.emplace_back(dir_name, std::move(unsafe)); + } + return error; } - int copy_databases() noexcept + static bool is_tmp_table(const char* filename) noexcept { - for (const database_dir& dir : database_dirs) + return begins_with(filename, tmp_prefix); + } + + void flatten_table_lists() noexcept + { + flatten_table_list(dml_safe_tables, dml_safe_table_list); + flatten_table_list(unsafe_tables, unsafe_tables_list); + } + + static void flatten_table_list(const database_dirs& dirs, table_list& list) noexcept + { + for (const database_dir& dir : dirs) { - const char* dir_name = dir.first.c_str(); - if (ensure_target_subdir(dir_name) != 0) - { - my_error(ER_CANT_CREATE_FILE, MYF(0), dir_name, errno); - return 1; - } - if (copy_database(dir) != 0) - return 1; + for (const std::string& table : dir.second) + list.emplace_back(dir.first, table); } - return 0; + } + + int scan_logs() noexcept + { + const char* base_dir = maria_data_root; + Source_dir datadir(base_dir, MYF(0)); + if (datadir.is_error()) + return 1; + int error = datadir.for_each([this](const fileinfo &fi) + { + if (begins_with(fi.name, log_file_prefix)) + log_files.emplace_back(fi.name); + else if (strcmp(fi.name, "aria_log_control") == 0) + have_control_file = true; + return 0; + }); + return error; + } + + bool ensure_target_dirs() noexcept + { + using string = std::string; + std::vector dirs; + for (const database_dir &dir : dml_safe_tables) + dirs.push_back(&dir.first); + for (const database_dir &dir : unsafe_tables) + dirs.push_back(&dir.first); + for (const string &dir: misc_dirs) + dirs.push_back(&dir); + std::sort(dirs.begin(), dirs.end(), + [](const string *a, const string *b) { return *a < *b; }); + auto dirs_end = std::unique(dirs.begin(), dirs.end(), + [](const string *a, const string *b) { return *a == *b; }); + for (auto it = dirs.begin(); it != dirs_end; ++it) + { + if (ensure_target_subdir((*it)->c_str())) + return true; + } + return false; } /* @@ -203,7 +426,7 @@ namespace int ensure_target_subdir(const char* name) noexcept { #ifdef _WIN32 - std::string dir_path= targetPath() + "/" + name; + const std::string dir_path= build_path(targetPath(), name); if (!CreateDirectory(dir_path.c_str(), nullptr)) { DWORD err = GetLastError(); @@ -220,55 +443,98 @@ namespace return 0; } - int copy_database(const database_dir& dir) noexcept + /* Returns result or error code. */ + std::variant is_safe_table(const char* dir_name, const char* myi_file_name) { - for (const std::string& file : dir.second) + ARIA_TABLE_CAPABILITIES cap; +#ifndef _WIN32 + std::string path= std::string(dir_name) + "/" + myi_file_name; + File fd= openat(datadir_fd, path.c_str(), O_RDONLY); + if (fd < 0) { - std::string file_path= dir.first + "/" + file; - if (copy_file(file_path) != 0) - return 1; + my_errno= errno; + my_error(ER_CANT_OPEN_FILE, MYF(0), path.c_str(), errno); } - return 0; +#else + std::string path= std::string(maria_data_root) + "/" + + dir_name + "/" + myi_file_name; + File fd= my_open(path.c_str(), O_RDONLY, MYF(MY_WME)); +#endif + if (fd < 0) + { + return my_errno; + } + std::variant result; + mysql_mutex_lock(&THR_LOCK_maria); + int fail = aria_get_capabilities(fd, myi_file_name, &cap); + if (fail) + { + my_error(ER_FILE_CORRUPT, MYF(0), path.c_str()); + result= fail; + goto end; + } + result = cap.transactional && cap.checksum; + aria_free_capabilities(&cap); +end: + mysql_mutex_unlock(&THR_LOCK_maria); +#ifndef _WIN32 + close(fd); +#else + my_close(fd, MYF(0)); +#endif + return result; + } + + int copy_table(const table_ref& table) noexcept + { + dir_ref dir_name = table.first; + tablename_ref table_name = table.second; + std::string index_path; + index_path.reserve(dir_name.size() + table_name.size() + 5); + index_path= dir_name; + index_path += '/'; + index_path.append(table_name.begin(), table_name.end()); + std::string data_path; + data_path.reserve(dir_name.size() + table_name.size() + 5); + data_path= index_path; + index_path+= index_ext; + data_path+= data_ext; + return copy_file(index_path) || copy_file(data_path); } int copy_control_file() noexcept { if (!have_control_file) return 0; - return copy_file("aria_log_control"); + return copy_file(control_file_name); } - int copy_logs() noexcept + int copy_file(const std::string &path) const noexcept { - for (const std::string& file : log_files) - { - if (copy_file(file) != 0) - return 1; - } - return 0; + return copy_file(path.c_str()); } - int copy_file(const std::string &path) const noexcept + int copy_file(const char *path) const noexcept { #ifndef _WIN32 int ret_val = 0; - int src_fd = openat(datadir_fd, path.c_str(), O_RDONLY); + int src_fd = openat(datadir_fd, path, O_RDONLY); if (src_fd < 0) { - my_error(ER_CANT_OPEN_FILE, MYF(0), path.c_str(), errno); + my_error(ER_CANT_OPEN_FILE, MYF(0), path, errno); return 1; } - int tgt_fd = openat(target.fd, path.c_str(), + int tgt_fd = openat(target.fd, path, O_CREAT | O_EXCL | O_WRONLY, 0777); if (tgt_fd < 0) { - my_error(ER_CANT_CREATE_FILE, MYF(0), path.c_str(), errno); + my_error(ER_CANT_CREATE_FILE, MYF(0), path, errno); ret_val = 1; goto finish; } if (copy_entire_file(src_fd, tgt_fd) != 0) { - my_error(ER_ERROR_ON_WRITE, MYF(0), path.c_str(), errno); + my_error(ER_ERROR_ON_WRITE, MYF(0), path, errno); ret_val = 1; } close(tgt_fd); @@ -276,9 +542,9 @@ namespace close(src_fd); return ret_val; #else - std::string src_path= std::string(maria_data_root) + "/" + path; - std::string dest_path= targetPath() + "/" + path; - if(!CopyFileExA(src_path.c_str(), dest_path.c_str(), nullptr, nullptr, nullptr, + const std::string src_path= build_path(maria_data_root, path); + const std::string dest_path= build_path(targetPath(), path); + if (!CopyFileExA(src_path.c_str(), dest_path.c_str(), nullptr, nullptr, nullptr, COPY_FILE_NO_BUFFERING)) { my_osmaperr(GetLastError()); @@ -289,76 +555,126 @@ namespace #endif } - - static bool is_db_file(const char* file_name) noexcept + /* Match if suffix is one of the "other" extensions we need to copy */ + static bool match_misc_ext(const char* suffix) noexcept { - for (const std::string& ext : data_exts) - { - if (ends_with(file_name, ext)) - return true; - } - /* As a stop-gap db/opt files are also copied here, this should be done in SQL layer. */ - return !strcmp(file_name, "db.opt"); + return std::find_if(std::begin(misc_exts), std::end(misc_exts), + [suffix](const char* ext) { + return match_suffix(suffix, ext); + }) != std::end(misc_exts); } - static bool ends_with(const char* str, const std::string& suffix) noexcept + static bool match_suffix(const char* suffix1, const char* suffix2) noexcept { - size_t str_len = strlen(str); - size_t suffix_len = suffix.size(); - if (str_len < suffix_len) - return false; - return memcmp(str + str_len - suffix_len, - suffix.data(), + return memcmp(suffix1, + suffix2, suffix_len) == 0; } - static bool begins_with(const char* str, const std::string& prefix) noexcept + static bool begins_with(const char* str, const LEX_CSTRING &prefix) noexcept + { + return strncmp(str, prefix.str, prefix.length) == 0; + } + + static std::string build_path(const char *base_path, const char *filename) noexcept { - return strncmp(str, prefix.data(), prefix.size()) == 0; + std::string path; + const size_t base_len= strlen(base_path); + const size_t filename_len= strlen(filename); + path.reserve(base_len + filename_len + 1); + path.append(base_path, base_len); + path+= '/'; + path.append(filename, filename_len); + return path; } #ifdef _WIN32 /** @return the target directory path */ - std::string targetPath() const + const char* targetPath() const { - return std::string(target.path); + return target.path; } #endif }; - /* TODO: .frm failes are not Aria-specific, .MYD and .MYI are MyISAM files; - they are copied here as a stop-gap */ - const std::vector - Aria_backup::data_exts {".MAD"s, ".MAI"s, "MYD"s, "MYI"s, "frm"s}; - const std::string Aria_backup::log_file_prefix {"aria_log."}; - - std::unique_ptr aria_backup; + std::optional aria_backup; } +/** + Start of a BACKUP SERVER phase, + when no aria_backup_step() or aria_backup_end() is pending. + @param thd current session + @param target backup target + @param phase BACKUP_PHASE_START, ... + @return error code + @retval 0 on success +*/ int aria_backup_start(THD *thd, const backup_target &target, backup_phase phase) noexcept { - if (phase != BACKUP_PHASE_NO_COMMIT) + switch (phase) + { + case BACKUP_PHASE_START: + assert(!aria_backup); + aria_backup.emplace(thd, target); + return !aria_backup->is_initialized(); + case BACKUP_PHASE_NO_DML_NON_TRANS: + /* FIXME: Would be better to selectively purge only the tables we need. */ + tc_purge(); + tdc_purge(true); + return 0; + case BACKUP_PHASE_NO_DDL: + return aria_backup->start_copy_dml_safe(); + case BACKUP_PHASE_NO_COMMIT: + return aria_backup->start_copy_unsafe(); + default: return 0; - aria_backup= std::make_unique(thd, target); - return !aria_backup->is_initialized(); + } } -int aria_backup_step(THD *, const backup_target &, backup_phase) noexcept +/** + Process a file that was collected in aria_backup_start(). + @param thd current session + @param target backup target + @param phase last phase on which backup_start() was successfully invoked + @retval 0 on completion +*/ +int aria_backup_step(THD *thd, const backup_target &target, + backup_phase phase) noexcept { - return 0; + switch (phase) + { + case BACKUP_PHASE_NO_DDL: + return aria_backup->dml_safe_copy_step(); + case BACKUP_PHASE_NO_COMMIT: + return aria_backup->unsafe_copy_step(); + default: + return 0; + } } -int aria_backup_end(THD *thd, const backup_target &, +/** + Finish a phase, once all calls for the current phase are completed. + @param thd current session + @param target backup target + @param phase current backup phase, or + one of the special values BACKUP_PHASE_ABORT or BACKUP_PHASE_FINISH + @return error code + @retval 0 on success +*/ +int aria_backup_end(THD *thd, const backup_target &target, backup_phase phase) noexcept { - if (phase != BACKUP_PHASE_NO_COMMIT) - return 0; -#if 1 // FIXME: invoke these only for Aria, MyISAM, CSV but not InnoDB, RocksDB - tc_purge(); - tdc_purge(true); -#endif - int ret_val= aria_backup->end(thd); - aria_backup.reset(); - return ret_val; + /* Note - aria_backup is currently a singleton object, and it is + reset before BACKUP_PHASE_FINISH to allow for a new backup to run + concurrently. With this design BACKUP_PHASE_FINISH must be a no-op. + If BACKUP_PHASE_FINISH is implemented, the code should be adjusted + to allow for multiple backups to be run in parallel from different + THD, given that only one backup may run in a phase other than + BACKUP_PHASE_FINISH, but any number may run in BACKUP_PHASE_FINISH. + A possible solution would be to have s eparate Ariab_backup object + per THD. */ + if (phase == BACKUP_PHASE_NO_COMMIT || phase == BACKUP_PHASE_ABORT) + aria_backup.reset(); + return 0; }