/******************************************************************** * gnc-sql-backend.cpp: Implementation of GncSqlBackend * * * * Copyright 2016 John Ralls * * * * 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; either version 2 of * * the License, or (at your option) any later version. * * * * 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, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * * Boston, MA 02110-1301, USA gnu@gnu.org * \********************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include "gnc-sql-connection.hpp" #include "gnc-sql-backend.hpp" #include "gnc-sql-object-backend.hpp" #include "gnc-sql-column-table-entry.hpp" #include "gnc-sql-result.hpp" #include "gnc-account-sql.h" #include "gnc-book-sql.h" #include "gnc-budget-sql.h" #include "gnc-commodity-sql.h" #include "gnc-lots-sql.h" #include "gnc-price-sql.h" #include "gnc-recurrence-sql.h" #include "gnc-schedxaction-sql.h" #include "gnc-slots-sql.h" #include "gnc-transaction-sql.h" #include "gnc-bill-term-sql.h" #include "gnc-customer-sql.h" #include "gnc-employee-sql.h" #include "gnc-entry-sql.h" #include "gnc-invoice-sql.h" #include "gnc-job-sql.h" #include "gnc-order-sql.h" #include "gnc-tax-table-sql.h" #include "gnc-vendor-sql.h" static QofLogModule log_module = G_LOG_DOMAIN; #define VERSION_TABLE_NAME "versions" #define MAX_TABLE_NAME_LEN 50 #define TABLE_COL_NAME "table_name" #define VERSION_COL_NAME "table_version" using StrVec = std::vector; static std::string empty_string{}; static EntryVec version_table { gnc_sql_make_table_entry( TABLE_COL_NAME, MAX_TABLE_NAME_LEN, COL_PKEY | COL_NNUL), gnc_sql_make_table_entry(VERSION_COL_NAME, 0, COL_NNUL) }; GncSqlBackend::GncSqlBackend(GncSqlConnection *conn, QofBook* book) : QofBackend {}, m_conn{conn}, m_book{book}, m_loading{false}, m_in_query{false}, m_is_pristine_db{false} { if (conn != nullptr) connect (conn); } GncSqlBackend::~GncSqlBackend() { connect(nullptr); } void GncSqlBackend::connect(GncSqlConnection *conn) noexcept { if (m_conn != nullptr && m_conn != conn) delete m_conn; finalize_version_info(); m_conn = conn; } GncSqlStatementPtr GncSqlBackend::create_statement_from_sql(const std::string& str) const noexcept { auto stmt = m_conn ? m_conn->create_statement_from_sql(str) : nullptr; if (stmt == nullptr) { PERR ("SQL error: %s\n", str.c_str()); qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR); } return stmt; } GncSqlResultPtr GncSqlBackend::execute_select_statement(const GncSqlStatementPtr& stmt) const noexcept { auto result = m_conn ? m_conn->execute_select_statement(stmt) : nullptr; if (result == nullptr) { PERR ("SQL error: %s\n", stmt->to_sql()); qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR); } return result; } int GncSqlBackend::execute_nonselect_statement(const GncSqlStatementPtr& stmt) const noexcept { int result = m_conn ? m_conn->execute_nonselect_statement(stmt) : -1; if (result == -1) { PERR ("SQL error: %s\n", stmt->to_sql()); qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR); } return result; } std::string GncSqlBackend::quote_string(const std::string& str) const noexcept { g_return_val_if_fail (m_conn != nullptr, empty_string); if (!m_conn) return empty_string; return m_conn->quote_string(str); } bool GncSqlBackend::create_table(const std::string& table_name, const EntryVec& col_table) const noexcept { g_return_val_if_fail (m_conn != nullptr, false); ColVec info_vec; for (auto const& table_row : col_table) { table_row->add_to_table (info_vec); } return m_conn->create_table (table_name, info_vec); } bool GncSqlBackend::create_table(const std::string& table_name, int table_version, const EntryVec& col_table) noexcept { if (create_table (table_name, col_table)) return set_table_version (table_name, table_version); return false; } bool GncSqlBackend::create_index(const std::string& index_name, const std::string& table_name, const EntryVec& col_table) const noexcept { g_return_val_if_fail (m_conn != nullptr, false); return m_conn->create_index(index_name, table_name, col_table); } bool GncSqlBackend::add_columns_to_table(const std::string& table_name, const EntryVec& col_table) const noexcept { g_return_val_if_fail (m_conn != nullptr, false); ColVec info_vec; for (auto const& table_row : col_table) { table_row->add_to_table (info_vec); } return m_conn->add_columns_to_table(table_name, info_vec); } void GncSqlBackend::update_progress(double pct) const noexcept { if (m_percentage != nullptr) (m_percentage) (nullptr, pct); } void GncSqlBackend::finish_progress() const noexcept { if (m_percentage != nullptr) (m_percentage) (nullptr, -1.0); } void GncSqlBackend::create_tables() noexcept { for(auto entry : m_backend_registry) { update_progress(101.0); std::get<1>(entry)->create_tables(this); } } /* Main object load order */ static const StrVec fixed_load_order { GNC_ID_BOOK, GNC_ID_COMMODITY, GNC_ID_ACCOUNT, GNC_ID_LOT, GNC_ID_TRANS }; /* Order in which business objects need to be loaded */ static const StrVec business_fixed_load_order = { GNC_ID_BILLTERM, GNC_ID_TAXTABLE, GNC_ID_INVOICE }; void GncSqlBackend::ObjectBackendRegistry::load_remaining(GncSqlBackend* sql_be) { auto num_types = m_registry.size(); auto num_done = fixed_load_order.size() + business_fixed_load_order.size(); for (const auto& entry : m_registry) { std::string type; GncSqlObjectBackendPtr obe = nullptr; std::tie(type, obe) = entry; /* Don't need to load anything if it has already been loaded with * the fixed order. */ if (std::find(fixed_load_order.begin(), fixed_load_order.end(), type) != fixed_load_order.end()) continue; if (std::find(business_fixed_load_order.begin(), business_fixed_load_order.end(), type) != business_fixed_load_order.end()) continue; num_done++; sql_be->update_progress(num_done * 100 / num_types); obe->load_all (sql_be); } } typedef struct { QofIdType searchObj; gpointer pCompiledQuery; } gnc_sql_query_info; /* callback structure */ typedef struct { gboolean is_known; gboolean is_ok; GncSqlBackend* sql_be; QofInstance* inst; QofQuery* pQuery; gpointer pCompiledQuery; gnc_sql_query_info* pQueryInfo; } sql_backend; void GncSqlBackend::load (QofBook* book, QofBackendLoadType loadType) { Account* root; g_return_if_fail (book != NULL); ENTER ("sql_be=%p, book=%p", this, book); m_loading = TRUE; if (loadType == LOAD_TYPE_INITIAL_LOAD) { assert (m_book == nullptr); m_book = book; auto num_types = m_backend_registry.size(); auto num_done = 0; /* Load any initial stuff. Some of this needs to happen in a certain order */ for (const auto& type : fixed_load_order) { num_done++; auto obe = m_backend_registry.get_object_backend(type); if (obe) { update_progress(num_done * 100 / num_types); obe->load_all(this); } } for (const auto& type : business_fixed_load_order) { num_done++; auto obe = m_backend_registry.get_object_backend(type); if (obe) { update_progress(num_done * 100 / num_types); obe->load_all(this); } } root = gnc_book_get_root_account( book ); gnc_account_foreach_descendant(root, (AccountCb)xaccAccountBeginEdit, nullptr); m_backend_registry.load_remaining(this); gnc_account_foreach_descendant(root, (AccountCb)xaccAccountCommitEdit, nullptr); } else if (loadType == LOAD_TYPE_LOAD_ALL) { // Load all transactions auto obe = m_backend_registry.get_object_backend (GNC_ID_TRANS); obe->load_all (this); } m_loading = FALSE; std::for_each(m_postload_commodities.begin(), m_postload_commodities.end(), [](gnc_commodity* comm) { gnc_commodity_begin_edit(comm); gnc_commodity_commit_edit(comm); }); m_postload_commodities.clear(); /* Mark the session as clean -- though it should never be marked * dirty with this backend */ qof_book_mark_session_saved (book); finish_progress(); LEAVE (""); } /* ================================================================= */ bool GncSqlBackend::write_account_tree(Account* root) { GList* descendants; GList* node; bool is_ok = true; g_return_val_if_fail (root != nullptr, false); auto obe = m_backend_registry.get_object_backend(GNC_ID_ACCOUNT); is_ok = obe->commit (this, QOF_INSTANCE (root)); if (is_ok) { descendants = gnc_account_get_descendants (root); for (node = descendants; node != NULL && is_ok; node = g_list_next (node)) { is_ok = obe->commit(this, QOF_INSTANCE (GNC_ACCOUNT (node->data))); if (!is_ok) break; } g_list_free (descendants); } update_progress(101.0); return is_ok; } bool GncSqlBackend::write_accounts() { update_progress(101.0); auto is_ok = write_account_tree (gnc_book_get_root_account (m_book)); if (is_ok) { update_progress(101.0); is_ok = write_account_tree (gnc_book_get_template_root(m_book)); } return is_ok; } static gboolean // Can't be bool because of signature for xaccAccountTreeForEach write_tx (Transaction* tx, gpointer data) { auto s = static_cast(data); g_return_val_if_fail (tx != NULL, 0); g_return_val_if_fail (data != NULL, 0); s->commit (QOF_INSTANCE (tx)); auto splitbe = s->be->get_object_backend(GNC_ID_SPLIT); for (auto split_node = xaccTransGetSplitList (tx); split_node != nullptr && s->is_ok; split_node = g_list_next (split_node)) { s->is_ok = splitbe->commit(s->be, QOF_INSTANCE(split_node->data)); } s->be->update_progress (101.0); return (s->is_ok ? 0 : 1); } bool GncSqlBackend::write_transactions() { auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS); write_objects_t data{this, TRUE, obe.get()}; (void)xaccAccountTreeForEachTransaction ( gnc_book_get_root_account (m_book), write_tx, &data); update_progress(101.0); return data.is_ok; } bool GncSqlBackend::write_template_transactions() { auto obe = m_backend_registry.get_object_backend(GNC_ID_TRANS); write_objects_t data{this, true, obe.get()}; auto ra = gnc_book_get_template_root (m_book); if (gnc_account_n_descendants (ra) > 0) { (void)xaccAccountTreeForEachTransaction (ra, write_tx, &data); update_progress(101.0); } return data.is_ok; } bool GncSqlBackend::write_schedXactions() { GList* schedXactions; SchedXaction* tmpSX; bool is_ok = true; schedXactions = gnc_book_get_schedxactions (m_book)->sx_list; auto obe = m_backend_registry.get_object_backend(GNC_ID_SCHEDXACTION); for (; schedXactions != NULL && is_ok; schedXactions = schedXactions->next) { tmpSX = static_cast (schedXactions->data); is_ok = obe->commit (this, QOF_INSTANCE (tmpSX)); } update_progress(101.0); return is_ok; } #pragma GCC diagnostic warning "-Wformat-nonliteral" void GncSqlBackend::sync(QofBook* book) { g_return_if_fail (book != NULL); g_return_if_fail (m_conn != nullptr); reset_version_info(); ENTER ("book=%p, sql_be->book=%p", book, m_book); update_progress(101.0); /* Create new tables */ m_is_pristine_db = true; create_tables(); /* Save all contents */ m_book = book; auto is_ok = m_conn->begin_transaction(); // FIXME: should write the set of commodities that are used // write_commodities(sql_be, book); if (is_ok) { auto obe = m_backend_registry.get_object_backend(GNC_ID_BOOK); is_ok = obe->commit (this, QOF_INSTANCE (book)); } if (is_ok) { is_ok = write_accounts(); } if (is_ok) { is_ok = write_transactions(); } if (is_ok) { is_ok = write_template_transactions(); } if (is_ok) { is_ok = write_schedXactions(); } if (is_ok) { for (auto entry : m_backend_registry) std::get<1>(entry)->write (this); } if (is_ok) { is_ok = m_conn->commit_transaction(); } if (is_ok) { m_is_pristine_db = false; /* Mark the session as clean -- though it shouldn't ever get * marked dirty with this backend */ qof_book_mark_session_saved(book); } else { set_error (ERR_BACKEND_SERVER_ERR); m_conn->rollback_transaction (); } finish_progress(); LEAVE ("book=%p", book); } /* ================================================================= */ /* Routines to deal with the creation of multiple books. */ void GncSqlBackend::begin(QofInstance* inst) { //g_return_if_fail (inst != NULL); //ENTER (" "); //LEAVE (""); } void GncSqlBackend::rollback(QofInstance* inst) { //g_return_if_fail (inst != NULL); //ENTER (" "); //LEAVE (""); } void GncSqlBackend::commodity_for_postload_processing(gnc_commodity* commodity) { m_postload_commodities.push_back(commodity); } GncSqlObjectBackendPtr GncSqlBackend::get_object_backend(const std::string& type) const noexcept { return m_backend_registry.get_object_backend(type); } /* Commit_edit handler - find the correct backend handler for this object * type and call its commit handler */ void GncSqlBackend::commit (QofInstance* inst) { gboolean is_dirty; gboolean is_destroying; gboolean is_infant; g_return_if_fail (inst != NULL); g_return_if_fail (m_conn != nullptr); if (qof_book_is_readonly(m_book)) { set_error (ERR_BACKEND_READONLY); (void)m_conn->rollback_transaction (); return; } /* During initial load where objects are being created, don't commit anything, but do mark the object as clean. */ if (m_loading) { qof_instance_mark_clean (inst); return; } // The engine has a PriceDB object but it isn't in the database if (strcmp (inst->e_type, "PriceDB") == 0) { qof_instance_mark_clean (inst); qof_book_mark_session_saved (m_book); return; } ENTER (" "); is_dirty = qof_instance_get_dirty_flag (inst); is_destroying = qof_instance_get_destroying (inst); is_infant = qof_instance_get_infant (inst); DEBUG ("%s dirty = %d, do_free = %d, infant = %d\n", (inst->e_type ? inst->e_type : "(null)"), is_dirty, is_destroying, is_infant); if (!is_dirty && !is_destroying) { LEAVE ("!dirty OR !destroying"); return; } if (!m_conn->begin_transaction ()) { PERR ("begin_transaction failed\n"); LEAVE ("Rolled back - database transaction begin error"); return; } bool is_ok = true; auto obe = m_backend_registry.get_object_backend(std::string{inst->e_type}); if (obe != nullptr) is_ok = obe->commit(this, inst); else { PERR ("Unknown object type '%s'\n", inst->e_type); (void)m_conn->rollback_transaction (); // Don't let unknown items still mark the book as being dirty qof_book_mark_session_saved(m_book); qof_instance_mark_clean (inst); LEAVE ("Rolled back - unknown object type"); return; } if (!is_ok) { // Error - roll it back (void)m_conn->rollback_transaction(); // This *should* leave things marked dirty LEAVE ("Rolled back - database error"); return; } (void)m_conn->commit_transaction (); qof_book_mark_session_saved(m_book); qof_instance_mark_clean (inst); LEAVE (""); } /** * Sees if the version table exists, and if it does, loads the info into * the version hash table. Otherwise, it creates an empty version table. * * @param be Backend struct */ void GncSqlBackend::init_version_info() noexcept { g_return_if_fail (m_conn != nullptr); if (m_conn->does_table_exist (VERSION_TABLE_NAME)) { std::string sql {"SELECT * FROM "}; sql += VERSION_TABLE_NAME; auto stmt = m_conn->create_statement_from_sql(sql); auto result = m_conn->execute_select_statement (stmt); for (const auto& row : *result) { auto name = row.get_string_at_col (TABLE_COL_NAME); unsigned int version = row.get_int_at_col (VERSION_COL_NAME); m_versions.push_back(std::make_pair(name, version)); } } else { create_table (VERSION_TABLE_NAME, version_table); set_table_version("Gnucash", gnc_prefs_get_long_version ()); set_table_version("Gnucash-Resave", GNUCASH_RESAVE_VERSION); } } /** * Resets the version table information by removing all version table info. * It also recreates the version table in the db. * * @param be Backend struct * @return TRUE if successful, FALSE if error */ bool GncSqlBackend::reset_version_info() noexcept { bool ok = create_table (VERSION_TABLE_NAME, version_table); m_versions.clear(); set_table_version ("Gnucash", gnc_prefs_get_long_version ()); set_table_version ("Gnucash-Resave", GNUCASH_RESAVE_VERSION); return ok; } /** * Finalizes the version table info by destroying the hash table. * * @param be Backend struct */ void GncSqlBackend::finalize_version_info() noexcept { m_versions.clear(); } unsigned int GncSqlBackend::get_table_version(const std::string& table_name) const noexcept { /* If the db is pristine because it's being saved, the table does not exist. */ if (m_is_pristine_db) return 0; auto version = std::find_if(m_versions.begin(), m_versions.end(), [table_name](const VersionPair& version) { return version.first == table_name; }); if (version != m_versions.end()) return version->second; return 0; } /** * Registers the version for a table. Registering involves updating the * db version table and also the hash table. * * @param be Backend struct * @param table_name Table name * @param version Version number * @return TRUE if successful, FALSE if unsuccessful */ bool GncSqlBackend::set_table_version (const std::string& table_name, uint_t version) noexcept { g_return_val_if_fail (version > 0, false); unsigned int cur_version{0}; std::stringstream sql; auto ver_entry = std::find_if(m_versions.begin(), m_versions.end(), [table_name](const VersionPair& ver) { return ver.first == table_name; }); if (ver_entry != m_versions.end()) cur_version = ver_entry->second; if (cur_version != version) { if (cur_version == 0) { sql << "INSERT INTO " << VERSION_TABLE_NAME << " VALUES('" << table_name << "'," << version <<")"; m_versions.push_back(std::make_pair(table_name, version)); } else { sql << "UPDATE " << VERSION_TABLE_NAME << " SET " << VERSION_COL_NAME << "=" << version << " WHERE " << TABLE_COL_NAME << "='" << table_name << "'"; ver_entry->second = version; } auto stmt = create_statement_from_sql(sql.str()); auto status = execute_nonselect_statement (stmt); if (status == -1) { PERR ("SQL error: %s\n", sql.str().c_str()); qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR); return false; } } return true; } void GncSqlBackend::upgrade_table (const std::string& table_name, const EntryVec& col_table) noexcept { DEBUG ("Upgrading %s table\n", table_name.c_str()); auto temp_table_name = table_name + "_new"; create_table (temp_table_name, col_table); std::stringstream sql; sql << "INSERT INTO " << temp_table_name << " SELECT * FROM " << table_name; auto stmt = create_statement_from_sql(sql.str()); execute_nonselect_statement(stmt); sql.str(""); sql << "DROP TABLE " << table_name; stmt = create_statement_from_sql(sql.str()); execute_nonselect_statement(stmt); sql.str(""); sql << "ALTER TABLE " << temp_table_name << " RENAME TO " << table_name; stmt = create_statement_from_sql(sql.str()); execute_nonselect_statement(stmt); } static inline PairVec get_object_values (QofIdTypeConst obj_name, gpointer pObject, const EntryVec& table) { PairVec vec; for (auto const& table_row : table) { if (!(table_row->is_autoincr())) { table_row->add_to_query (obj_name, pObject, vec); } } return vec; } bool GncSqlBackend::object_in_db (const char* table_name, QofIdTypeConst obj_name, const gpointer pObject, const EntryVec& table) const noexcept { g_return_val_if_fail (table_name != nullptr, false); g_return_val_if_fail (obj_name != nullptr, false); g_return_val_if_fail (pObject != nullptr, false); /* SELECT * FROM */ auto sql = std::string{"SELECT "} + table[0]->name() + " FROM " + table_name; auto stmt = create_statement_from_sql(sql.c_str()); assert (stmt != nullptr); /* WHERE */ PairVec values{get_object_values(obj_name, pObject, table)}; /* We want only the first item in the table, which should be the PK. */ values.resize(1); stmt->add_where_cond(obj_name, values); auto result = execute_select_statement (stmt); return (result != nullptr && result->size() > 0); } bool GncSqlBackend::do_db_operation (E_DB_OPERATION op, const char* table_name, QofIdTypeConst obj_name, gpointer pObject, const EntryVec& table) const noexcept { GncSqlStatementPtr stmt; g_return_val_if_fail (table_name != nullptr, false); g_return_val_if_fail (obj_name != nullptr, false); g_return_val_if_fail (pObject != nullptr, false); switch(op) { case OP_DB_INSERT: stmt = build_insert_statement (table_name, obj_name, pObject, table); break; case OP_DB_UPDATE: stmt = build_update_statement (table_name, obj_name, pObject, table); break; case OP_DB_DELETE: stmt = build_delete_statement (table_name, obj_name, pObject, table); break; } if (stmt == nullptr) return false; return (execute_nonselect_statement(stmt) != -1); } bool GncSqlBackend::save_commodity(gnc_commodity* comm) noexcept { if (comm == nullptr) return false; QofInstance* inst = QOF_INSTANCE(comm); auto obe = m_backend_registry.get_object_backend(std::string(inst->e_type)); if (obe && !obe->instance_in_db(this, inst)) return obe->commit(this, inst); return true; } GncSqlStatementPtr GncSqlBackend::build_insert_statement (const char* table_name, QofIdTypeConst obj_name, gpointer pObject, const EntryVec& table) const noexcept { GncSqlStatementPtr stmt; PairVec col_values; std::ostringstream sql; g_return_val_if_fail (table_name != nullptr, nullptr); g_return_val_if_fail (obj_name != nullptr, nullptr); g_return_val_if_fail (pObject != nullptr, nullptr); PairVec values{get_object_values(obj_name, pObject, table)}; sql << "INSERT INTO " << table_name <<"("; for (auto const& col_value : values) { if (col_value != *values.begin()) sql << ","; sql << col_value.first; } sql << ") VALUES("; for (const auto& col_value : values) { if (col_value != *values.begin()) sql << ","; sql << col_value.second; } sql << ")"; stmt = create_statement_from_sql(sql.str()); return stmt; } GncSqlStatementPtr GncSqlBackend::build_update_statement(const gchar* table_name, QofIdTypeConst obj_name, gpointer pObject, const EntryVec& table) const noexcept { GncSqlStatementPtr stmt; std::ostringstream sql; g_return_val_if_fail (table_name != nullptr, nullptr); g_return_val_if_fail (obj_name != nullptr, nullptr); g_return_val_if_fail (pObject != nullptr, nullptr); PairVec values{get_object_values (obj_name, pObject, table)}; // Create the SQL statement sql << "UPDATE " << table_name << " SET "; for (auto const& col_value : values) { if (col_value != *values.begin()) sql << ","; sql << col_value.first << "=" << col_value.second; } stmt = create_statement_from_sql(sql.str()); /* We want our where condition to be just the first column and * value, i.e. the guid of the object. */ values.erase(values.begin() + 1, values.end()); stmt->add_where_cond(obj_name, values); return stmt; } GncSqlStatementPtr GncSqlBackend::build_delete_statement(const gchar* table_name, QofIdTypeConst obj_name, gpointer pObject, const EntryVec& table) const noexcept { std::ostringstream sql; g_return_val_if_fail (table_name != nullptr, nullptr); g_return_val_if_fail (obj_name != nullptr, nullptr); g_return_val_if_fail (pObject != nullptr, nullptr); sql << "DELETE FROM " << table_name; auto stmt = create_statement_from_sql (sql.str()); /* WHERE */ PairVec values; table[0]->add_to_query (obj_name, pObject, values); PairVec col_values{values[0]}; stmt->add_where_cond (obj_name, col_values); return stmt; } GncSqlBackend::ObjectBackendRegistry::ObjectBackendRegistry() { register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); register_backend(std::make_shared()); } void GncSqlBackend::ObjectBackendRegistry::register_backend(OBEEntry&& entry) noexcept { m_registry.emplace_back(entry); } void GncSqlBackend::ObjectBackendRegistry::register_backend(GncSqlObjectBackendPtr obe) noexcept { m_registry.emplace_back(make_tuple(std::string{obe->type()}, obe)); } GncSqlObjectBackendPtr GncSqlBackend::ObjectBackendRegistry::get_object_backend(const std::string& type) const { auto entry = std::find_if(m_registry.begin(), m_registry.end(), [type](const OBEEntry& entry){ return type == std::get<0>(entry); }); if (entry == m_registry.end()) return nullptr; return std::get<1>(*entry); }