diff --git a/src/backend/dbi/gnc-backend-dbi.cpp b/src/backend/dbi/gnc-backend-dbi.cpp index 3a4a01b00e..f832a11f53 100644 --- a/src/backend/dbi/gnc-backend-dbi.cpp +++ b/src/backend/dbi/gnc-backend-dbi.cpp @@ -79,6 +79,8 @@ extern "C" #include "gnc-backend-dbi.h" #include "gnc-backend-dbi.hpp" +#include +#include #include "gnc-dbisqlresult.hpp" #include "gnc-dbisqlconnection.hpp" diff --git a/src/backend/dbi/gnc-backend-dbi.hpp b/src/backend/dbi/gnc-backend-dbi.hpp index 66c87320aa..07ab675626 100644 --- a/src/backend/dbi/gnc-backend-dbi.hpp +++ b/src/backend/dbi/gnc-backend-dbi.hpp @@ -34,7 +34,10 @@ extern "C" #define GETPID() getpid() #endif } -#include "gnc-backend-sql.h" +#include +#include + +class GncSqlRow; #define GNC_HOST_NAME_MAX 255 diff --git a/src/backend/dbi/gnc-dbiprovider.hpp b/src/backend/dbi/gnc-dbiprovider.hpp index 2f168d8702..7f416a24ea 100644 --- a/src/backend/dbi/gnc-dbiprovider.hpp +++ b/src/backend/dbi/gnc-dbiprovider.hpp @@ -36,8 +36,8 @@ extern "C" */ class GncSqlConnection; struct GncSqlColumnInfo; -using ColVec=std::vector; - +using ColVec = std::vector; +using StrVec = std::vector; class GncDbiProvider { public: diff --git a/src/backend/dbi/gnc-dbiproviderimpl.hpp b/src/backend/dbi/gnc-dbiproviderimpl.hpp index 4c3f449f7f..10fdec1050 100644 --- a/src/backend/dbi/gnc-dbiproviderimpl.hpp +++ b/src/backend/dbi/gnc-dbiproviderimpl.hpp @@ -27,8 +27,15 @@ extern "C" { #include } +#include +#include + #include "gnc-backend-dbi.hpp" #include "gnc-dbiprovider.hpp" +#include "gnc-backend-dbi.h" +#include + +using StrVec = std::vector; template class GncDbiProviderImpl : public GncDbiProvider diff --git a/src/backend/dbi/gnc-dbisqlconnection.hpp b/src/backend/dbi/gnc-dbisqlconnection.hpp index fd332948b2..466e5d97ae 100644 --- a/src/backend/dbi/gnc-dbisqlconnection.hpp +++ b/src/backend/dbi/gnc-dbisqlconnection.hpp @@ -23,10 +23,16 @@ #ifndef _GNC_DBISQLCONNECTION_HPP_ #define _GNC_DBISQLCONNECTION_HPP_ +#include +#include + +#include #include "gnc-backend-dbi.hpp" #include "gnc-dbisqlresult.hpp" #include "gnc-dbiprovider.hpp" +#include "gnc-backend-dbi.h" +using StrVec = std::vector; class GncDbiProvider; /** diff --git a/src/backend/dbi/gnc-dbisqlresult.hpp b/src/backend/dbi/gnc-dbisqlresult.hpp index acb658d9e0..faf51435bb 100644 --- a/src/backend/dbi/gnc-dbisqlresult.hpp +++ b/src/backend/dbi/gnc-dbisqlresult.hpp @@ -26,6 +26,7 @@ #define __GNC_DBISQLBACKEND_HPP__ #include "gnc-backend-dbi.h" +#include class GncDbiSqlConnection; diff --git a/src/backend/dbi/test/test-backend-dbi-basic.cpp b/src/backend/dbi/test/test-backend-dbi-basic.cpp index 593d7df7bd..a7b90b2152 100644 --- a/src/backend/dbi/test/test-backend-dbi-basic.cpp +++ b/src/backend/dbi/test/test-backend-dbi-basic.cpp @@ -57,6 +57,10 @@ extern "C" #include #include } + +#include +#include + #include "test-dbi-stuff.h" #include "test-dbi-business-stuff.h" @@ -70,6 +74,8 @@ static dbi_inst dbi_instance = NULL; static const gchar* suitename = "/backend/dbi"; void test_suite_gnc_backend_dbi (void); +using StrVec = std::vector; + typedef struct { QofSession* session; diff --git a/src/backend/sql/CMakeLists.txt b/src/backend/sql/CMakeLists.txt index 6c4ece55ea..925befefe9 100644 --- a/src/backend/sql/CMakeLists.txt +++ b/src/backend/sql/CMakeLists.txt @@ -25,6 +25,10 @@ SET (backend_sql_SOURCES gnc-tax-table-sql.cpp gnc-transaction-sql.cpp gnc-vendor-sql.cpp + gnc-sql-backend.cpp + gnc-sql-result.cpp + gnc-sql-column-table-entry.cpp + gnc-sql-object-backend.cpp escape.cpp ) SET (backend_sql_noinst_HEADERS @@ -48,6 +52,11 @@ SET (backend_sql_noinst_HEADERS gnc-tax-table-sql.h gnc-transaction-sql.h gnc-vendor-sql.h + gnc-sql-backend.hpp + gnc-sql-connection.hpp + gnc-sql-result.hpp + gnc-sql-column-table-entry.hpp + gnc-sql-object-backend.hpp escape.h ) diff --git a/src/backend/sql/Makefile.am b/src/backend/sql/Makefile.am index 6f0dabee14..7e8d845b60 100644 --- a/src/backend/sql/Makefile.am +++ b/src/backend/sql/Makefile.am @@ -43,6 +43,10 @@ libgnc_backend_sql_la_SOURCES = \ gnc-tax-table-sql.cpp \ gnc-transaction-sql.cpp \ gnc-vendor-sql.cpp \ + gnc-sql-backend.cpp \ + gnc-sql-result.cpp \ + gnc-sql-column-table-entry.cpp \ + gnc-sql-object-backend.cpp \ escape.cpp noinst_HEADERS = \ @@ -66,6 +70,11 @@ noinst_HEADERS = \ gnc-tax-table-sql.h \ gnc-transaction-sql.h \ gnc-vendor-sql.h \ + gnc-sql-backend.hpp \ + gnc-sql-connection.hpp \ + gnc-sql-result.hpp \ + gnc-sql-column-table-entry.hpp \ + gnc-sql-object-backend.hpp \ escape.h libgnc_backend_sql_la_LIBADD = \ diff --git a/src/backend/sql/gnc-account-sql.cpp b/src/backend/sql/gnc-account-sql.cpp index 8c58f075f1..7908a451ce 100644 --- a/src/backend/sql/gnc-account-sql.cpp +++ b/src/backend/sql/gnc-account-sql.cpp @@ -41,8 +41,11 @@ extern "C" #include "splint-defs.h" #endif } -#include "gnc-backend-sql.h" +#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-account-sql.h" #include "gnc-commodity-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-address-sql.cpp b/src/backend/sql/gnc-address-sql.cpp index 63bf9cebcc..5effac316a 100644 --- a/src/backend/sql/gnc-address-sql.cpp +++ b/src/backend/sql/gnc-address-sql.cpp @@ -32,14 +32,16 @@ extern "C" #include "config.h" #include -#include -#include #include "gnc-engine.h" #include "gncAddress.h" } -#include "gnc-backend-sql.h" +#include +#include +#include +#include "gnc-sql-backend.hpp" +#include "gnc-sql-column-table-entry.hpp" G_GNUC_UNUSED static QofLogModule log_module = G_LOG_DOMAIN; diff --git a/src/backend/sql/gnc-backend-sql.cpp b/src/backend/sql/gnc-backend-sql.cpp index 892dc0f170..c426015417 100644 --- a/src/backend/sql/gnc-backend-sql.cpp +++ b/src/backend/sql/gnc-backend-sql.cpp @@ -37,6 +37,7 @@ extern "C" #include #include +#include #include #include #include @@ -47,7 +48,6 @@ extern "C" #include #include #include -#include "gnc-prefs.h" #include "gnc-pricedb.h" @@ -59,6 +59,10 @@ extern "C" #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-backend-sql.h" #include "gnc-account-sql.h" @@ -82,11 +86,6 @@ extern "C" #include "gnc-tax-table-sql.h" #include "gnc-vendor-sql.h" -#define VERSION_TABLE_NAME "versions" -#define MAX_TABLE_NAME_LEN 50 -#define TABLE_COL_NAME "table_name" -#define VERSION_COL_NAME "table_version" - static void gnc_sql_init_object_handlers (void); static GncSqlStatementPtr build_insert_statement (GncSqlBackend* sql_be, const gchar* table_name, @@ -419,303 +418,6 @@ write_schedXactions (GncSqlBackend* sql_be) return is_ok; } -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, - const char* format) : - qof_be {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, ERR_BACKEND_NO_ERR, nullptr, 0, - nullptr}, m_conn{conn}, m_book{book}, m_loading{false}, - m_in_query{false}, m_is_pristine_db{false}, m_timespec_format{format} -{ - if (conn != nullptr) - connect (conn); -} - -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->create_statement_from_sql(str); - 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->execute_select_statement(stmt); - 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 -{ - auto result = m_conn->execute_nonselect_statement(stmt); - 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 -{ - return m_conn->quote_string(str); -} - -bool -GncSqlBackend::create_table(const std::string& table_name, - const EntryVec& col_table) const noexcept -{ - ColVec info_vec; - gboolean ok = FALSE; - - for (auto const& table_row : col_table) - { - table_row->add_to_table (this, 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 -{ - 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 -{ - ColVec info_vec; - - for (auto const& table_row : col_table) - { - table_row->add_to_table (this, info_vec); - } - return m_conn->add_columns_to_table(table_name, info_vec); -} - -void -GncSqlBackend::update_progress() const noexcept -{ - if (qof_be.percentage != nullptr) - (qof_be.percentage) (nullptr, 101.0); -} - -void -GncSqlBackend::finish_progress() const noexcept -{ - if (qof_be.percentage != nullptr) - (qof_be.percentage) (nullptr, -1.0); -} - -/** - * 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 -{ - - 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 = true; - if (!m_conn->does_table_exist (VERSION_TABLE_NAME)) - 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); -} - -/* This is required because we're passing be->timespace_format to - * g_strdup_printf. - */ -#pragma GCC diagnostic ignored "-Wformat-nonliteral" -std::string -GncSqlBackend::time64_to_string (time64 t) const noexcept -{ - auto tm = gnc_gmtime (&t); - - auto year = tm->tm_year + 1900; - - auto datebuf = g_strdup_printf (m_timespec_format, - year, tm->tm_mon + 1, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec); - gnc_tm_free (tm); - std::string date{datebuf}; - g_free(datebuf); - return date; -} #pragma GCC diagnostic warning "-Wformat-nonliteral" void @@ -1305,669 +1007,6 @@ gnc_sql_run_query (QofBackend* qof_be, gpointer pQuery) LEAVE (""); } #endif //if 0: query creation isn't used yet, code never tested. -/* ================================================================= */ - -static void -business_core_sql_init (void) -{ - /* Initialize our pointers into the backend subsystem */ - gnc_billterm_sql_initialize (); - gnc_customer_sql_initialize (); - gnc_employee_sql_initialize (); - gnc_entry_sql_initialize (); - gnc_invoice_sql_initialize (); - gnc_job_sql_initialize (); - gnc_order_sql_initialize (); - gnc_taxtable_sql_initialize (); - gnc_vendor_sql_initialize (); -} - -static void -gnc_sql_init_object_handlers (void) -{ - gnc_sql_init_book_handler (); - gnc_sql_init_commodity_handler (); - gnc_sql_init_account_handler (); - gnc_sql_init_budget_handler (); - gnc_sql_init_price_handler (); - gnc_sql_init_transaction_handler (); - gnc_sql_init_slots_handler (); - gnc_sql_init_recurrence_handler (); - gnc_sql_init_schedxaction_handler (); - gnc_sql_init_lot_handler (); - - /* And the business objects */ - business_core_sql_init (); -} - -/* ================================================================= */ -static gpointer -get_autoinc_id (void* object, const QofParam* param) -{ - // Just need a 0 to force a new autoinc value - return (gpointer)0; -} - -static void -set_autoinc_id (void* object, void* item) -{ - // Nowhere to put the ID -} - -QofAccessFunc -GncSqlColumnTableEntry::get_getter (QofIdTypeConst obj_name) const noexcept -{ - QofAccessFunc getter; - - g_return_val_if_fail (obj_name != NULL, NULL); - - if (m_flags & COL_AUTOINC) - { - getter = get_autoinc_id; - } - else if (m_qof_param_name != NULL) - { - getter = qof_class_get_parameter_getter (obj_name, m_qof_param_name); - } - else - { - getter = m_getter; - } - - return getter; -} - -QofSetterFunc -GncSqlColumnTableEntry::get_setter(QofIdTypeConst obj_name) const noexcept -{ - QofSetterFunc setter = nullptr; - if (m_flags & COL_AUTOINC) - { - setter = set_autoinc_id; - } - else if (m_qof_param_name != nullptr) - { - g_assert (obj_name != NULL); - setter = qof_class_get_parameter_setter (obj_name, m_qof_param_name); - } - else - { - setter = m_setter; - } - return setter; -} - -void -GncSqlColumnTableEntry::add_objectref_guid_to_query (const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - auto inst = get_row_value_from_object(obj_name, pObject); - if (inst == nullptr) return; - auto guid = qof_instance_get_guid (inst); - if (guid != nullptr) - vec.emplace_back (std::make_pair (std::string{m_col_name}, - std::string{guid_to_string(guid)})); -} - -void -GncSqlColumnTableEntry::add_objectref_guid_to_table (const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_STRING, GUID_ENCODING_LENGTH, FALSE}; - vec.emplace_back(std::move(info)); -} - - -/* ----------------------------------------------------------------- */ -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) const noexcept -{ - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != NULL || get_setter(obj_name) != NULL); - - try - { - auto s = row.get_string_at_col (m_col_name); - set_parameter(pObject, s.c_str(), get_setter(obj_name), m_gobj_param_name); - } - catch (std::invalid_argument) {} -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_STRING, m_size, TRUE}; - vec.emplace_back(std::move(info)); -} - -/* char is unusual in that we get a pointer but don't deref it to pass - * it to operator<<(). - */ -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - auto s = get_row_value_from_object(obj_name, pObject); - - if (s != nullptr) - { - std::ostringstream stream; - stream << s; - vec.emplace_back (std::make_pair (std::string{m_col_name}, stream.str())); - return; - } -} - -/* ----------------------------------------------------------------- */ -typedef gint (*IntAccessFunc) (const gpointer); -typedef void (*IntSetterFunc) (const gpointer, gint); - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) const noexcept -{ - - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != NULL || get_setter(obj_name) != NULL); - - auto val = row.get_int_at_col(m_col_name); - set_parameter(pObject, val, - reinterpret_cast(get_setter(obj_name)), m_gobj_param_name); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_INT, 0, FALSE}; - vec.emplace_back(std::move(info)); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - add_value_to_vec(sql_be, obj_name, pObject, vec); -} - -/* ----------------------------------------------------------------- */ -typedef gboolean (*BooleanAccessFunc) (const gpointer); -typedef void (*BooleanSetterFunc) (const gpointer, gboolean); - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) - const noexcept -{ - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != NULL || get_setter(obj_name) != NULL); - - auto val = row.get_int_at_col (m_col_name); - set_parameter(pObject, val, - reinterpret_cast(get_setter(obj_name)), - m_gobj_param_name); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_INT, 0, FALSE}; - vec.emplace_back(std::move(info)); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - add_value_to_vec(sql_be, obj_name, pObject, vec); -} - -/* ----------------------------------------------------------------- */ -typedef gint64 (*Int64AccessFunc) (const gpointer); -typedef void (*Int64SetterFunc) (const gpointer, gint64); - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) - const noexcept -{ - g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); - - auto val = row.get_int_at_col (m_col_name); - set_parameter(pObject, val, - reinterpret_cast(get_setter(obj_name)), - m_gobj_param_name); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_INT64, 0, FALSE}; - vec.emplace_back(std::move(info)); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - add_value_to_vec(sql_be, obj_name, pObject, vec); -} -/* ----------------------------------------------------------------- */ - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) - const noexcept -{ - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); - double val; - try - { - val = static_cast(row.get_int_at_col(m_col_name)); - } - catch (std::invalid_argument) - { - try - { - val = static_cast(row.get_float_at_col(m_col_name)); - } - catch (std::invalid_argument) - { - try - { - val = row.get_double_at_col(m_col_name); - } - catch (std::invalid_argument) - { - val = 0.0; - } - } - } - set_parameter(pObject, val, get_setter(obj_name), m_gobj_param_name); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_DOUBLE, 0, FALSE}; - vec.emplace_back(std::move(info)); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - add_value_to_vec(sql_be, obj_name, pObject, vec); -} - -/* ----------------------------------------------------------------- */ - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) - const noexcept -{ - - GncGUID guid; - const GncGUID* pGuid; - - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); - - std::string str; - try - { - str = row.get_string_at_col(m_col_name); - } - catch (std::invalid_argument) - { - return; - } - (void)string_to_guid (str.c_str(), &guid); - set_parameter(pObject, &guid, get_setter(obj_name), m_gobj_param_name); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_STRING, GUID_ENCODING_LENGTH, FALSE}; - vec.emplace_back(std::move(info)); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - auto s = get_row_value_from_object(obj_name, pObject); - - if (s != nullptr) - { - - vec.emplace_back (std::make_pair (std::string{m_col_name}, - std::string{guid_to_string(s)})); - return; - } -} -/* ----------------------------------------------------------------- */ -typedef Timespec (*TimespecAccessFunc) (const gpointer); -typedef void (*TimespecSetterFunc) (const gpointer, Timespec*); - -#define TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d" -#define TIMESPEC_COL_SIZE (4+2+2+2+2+2) - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) const noexcept -{ - - Timespec ts = {0, 0}; - gboolean isOK = FALSE; - - - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); - - try - { - auto val = row.get_time64_at_col(m_col_name); - timespecFromTime64 (&ts, val); - } - catch (std::invalid_argument) - { - try - { - auto val = row.get_string_at_col(m_col_name); - auto s = val.c_str(); - auto buf = g_strdup_printf ("%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c", - s[0], s[1], s[2], s[3], s[4], s[5], - s[6], s[7], s[8], s[9], s[10], s[11], - s[12], s[13]); - ts = gnc_iso8601_to_timespec_gmt (buf); - g_free (buf); - } - catch (std::invalid_argument) - { - return; - } - } - set_parameter(pObject, &ts, - reinterpret_cast(get_setter(obj_name)), - m_gobj_param_name); - } - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != nullptr); - - GncSqlColumnInfo info{*this, BCT_DATETIME, TIMESPEC_COL_SIZE, FALSE}; - vec.emplace_back(std::move(info)); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - TimespecAccessFunc ts_getter; - Timespec ts; -/* Can't use get_row_value_from_object because g_object_get returns a - * Timespec* and the getter returns a Timespec. Will be fixed by the - * replacement of timespecs with time64s. - */ - g_return_if_fail (sql_be != NULL); - g_return_if_fail (obj_name != NULL); - g_return_if_fail (pObject != NULL); - - if (m_gobj_param_name != NULL) - { - Timespec* pts; - g_object_get (pObject, m_gobj_param_name, &pts, NULL); - ts = *pts; - } - else - { - ts_getter = (TimespecAccessFunc)get_getter (obj_name); - g_return_if_fail (ts_getter != NULL); - ts = (*ts_getter) (pObject); - } - - if (ts.tv_sec != 0 || ts.tv_nsec != 0) - { - auto datebuf = sql_be->time64_to_string (ts.tv_sec); - vec.emplace_back (std::make_pair (std::string{m_col_name}, datebuf)); - return; - } -} - -/* ----------------------------------------------------------------- */ -#define DATE_COL_SIZE 8 - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) const noexcept -{ - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); - if (row.is_col_null(m_col_name)) - return; - GDate date; - g_date_clear (&date, 1); - try - { - /* timespec_to_gdate applies the tz, and gdates are saved - * as ymd, so we don't want that. - */ - auto time = row.get_time64_at_col(m_col_name); - auto tm = gnc_gmtime(&time); - g_date_set_dmy(&date, tm->tm_mday, - static_cast(tm->tm_mon + 1), - tm->tm_year + 1900); - free(tm); - } - catch (std::invalid_argument) - { - try - { - std::string str = row.get_string_at_col(m_col_name); - if (str.empty()) return; - auto year = static_cast(stoi (str.substr (0,4))); - auto month = static_cast(stoi (str.substr (4,2))); - auto day = static_cast(stoi (str.substr (6,2))); - - if (year != 0 || month != 0 || day != (GDateDay)0) - g_date_set_dmy(&date, day, month, year); - - } - catch (std::invalid_argument) - { - return; - } - } - set_parameter(pObject, &date, get_setter(obj_name), m_gobj_param_name); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - GncSqlColumnInfo info{*this, BCT_DATE, DATE_COL_SIZE, FALSE}; - vec.emplace_back(std::move(info)); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ - GDate *date = get_row_value_from_object(obj_name, pObject); - - if (date && g_date_valid (date)) - { - std::ostringstream buf; - buf << std::setfill ('0') << std::setw (4) << g_date_get_year (date) << - std::setw (2) << g_date_get_month (date) << - std::setw (2) << static_cast(g_date_get_day (date)); - vec.emplace_back (std::make_pair (std::string{m_col_name}, buf.str())); - return; - } -} - -/* ----------------------------------------------------------------- */ -typedef gnc_numeric (*NumericGetterFunc) (const gpointer); -typedef void (*NumericSetterFunc) (gpointer, gnc_numeric*); - -static const EntryVec numeric_col_table = -{ - gnc_sql_make_table_entry("num", 0, COL_NNUL, "guid"), - gnc_sql_make_table_entry("denom", 0, COL_NNUL, "guid") -}; - -template<> void -GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, - GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject) const noexcept -{ - - - g_return_if_fail (pObject != NULL); - g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); - gnc_numeric n; - try - { - auto buf = g_strdup_printf ("%s_num", m_col_name); - auto num = row.get_int_at_col (buf); - g_free (buf); - buf = g_strdup_printf ("%s_denom", m_col_name); - auto denom = row.get_int_at_col (buf); - n = gnc_numeric_create (num, denom); - } - catch (std::invalid_argument) - { - return; - } - set_parameter(pObject, &n, - reinterpret_cast(get_setter(obj_name)), - m_gobj_param_name); -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, - ColVec& vec) const noexcept -{ - g_return_if_fail (sql_be != NULL); - - for (auto const& subtable_row : numeric_col_table) - { - gchar* buf = g_strdup_printf("%s_%s", m_col_name, - subtable_row->m_col_name); - GncSqlColumnInfo info(buf, BCT_INT64, 0, false, false, - m_flags & COL_PKEY, m_flags & COL_NNUL); - vec.emplace_back(std::move(info)); - } -} - -template<> void -GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept -{ -/* We can't use get_row_value_from_object for the same reason as Timespec. */ - NumericGetterFunc getter; - gnc_numeric n; - - g_return_if_fail (sql_be != NULL); - g_return_if_fail (obj_name != NULL); - g_return_if_fail (pObject != NULL); - - if (m_gobj_param_name != nullptr) - { - gnc_numeric* s; - g_object_get (pObject, m_gobj_param_name, &s, NULL); - n = *s; - } - else - { - getter = reinterpret_cast(get_getter (obj_name)); - if (getter != NULL) - { - n = (*getter) (pObject); - } - else - { - n = gnc_numeric_zero (); - } - } - - std::ostringstream buf; - std::string num_col{m_col_name}; - std::string denom_col{m_col_name}; - num_col += "_num"; - denom_col += "_denom"; - buf << gnc_numeric_num (n); - vec.emplace_back (std::make_pair (num_col, buf.str ())); - buf.str (""); - buf << gnc_numeric_denom (n); - vec.emplace_back (denom_col, buf.str ()); -} /* ================================================================= */ @@ -2238,109 +1277,42 @@ build_delete_statement (GncSqlBackend* sql_be, } /* ================================================================= */ -bool -GncSqlObjectBackend::commit (GncSqlBackend* sql_be, QofInstance* inst) -{ - const GncGUID* guid; - gboolean is_infant; - E_DB_OPERATION op; - gboolean is_ok; - - is_infant = qof_instance_get_infant (inst); - if (qof_instance_get_destroying (inst)) - { - op = OP_DB_DELETE; - } - else if (sql_be->pristine() || is_infant) - { - op = OP_DB_INSERT; - } - else - { - op = OP_DB_UPDATE; - } - is_ok = gnc_sql_do_db_operation (sql_be, op, m_table_name.c_str(), - m_type_name.c_str(), inst, m_col_table); - - if (is_ok) - { - // Now, commit any slots - guid = qof_instance_get_guid (inst); - if (!qof_instance_get_destroying (inst)) - { - is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst); - } - else - { - is_ok = gnc_sql_slots_delete (sql_be, guid); - } - } - - return is_ok; -} - -/* ================================================================= */ -void -GncSqlObjectBackend::create_tables (GncSqlBackend* sql_be) -{ - g_return_if_fail (sql_be != nullptr); - int version = sql_be->get_table_version (m_table_name); - if (version == 0) //No tables, otherwise version will sql_be >= 1. - { - sql_be->create_table(m_table_name, m_col_table); - sql_be->set_table_version(m_table_name, m_version); - } - else if (version != m_version) - PERR("Version mismatch in table %s, expecting %d but backend is %d." - "Table creation aborted.", m_table_name.c_str(), m_version, version); -} /* ================================================================= */ +/* ================================================================= */ -/* This is necessary for 64-bit builds because g++ complains - * that reinterpret_casting a void* (64 bits) to an int (32 bits) - * loses precision, so we have to explicitly dispose of the precision. - * FIXME: We shouldn't be storing ints in ptrs in the first place. - */ -#ifdef __LP64__ -template <> int -GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, - const gpointer pObject, - std::false_type) const +static void +business_core_sql_init (void) { - g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, 0); - int result = 0; - if (m_gobj_param_name != nullptr) - g_object_get(pObject, m_gobj_param_name, &result, NULL ); - else - { - QofAccessFunc getter = get_getter(obj_name); - if (getter != nullptr) - { - auto value = ((getter)(pObject, nullptr)); - result = reinterpret_cast(value) & - UINT64_C(0x00000000FFFFFFFF); - } - } - return result; -} -#endif - -GncSqlRow& -GncSqlRow::operator++() -{ - auto& new_row = m_iter->operator++(); - if (new_row != *this) - m_iter = nullptr; - return new_row; + /* Initialize our pointers into the backend subsystem */ + gnc_billterm_sql_initialize (); + gnc_customer_sql_initialize (); + gnc_employee_sql_initialize (); + gnc_entry_sql_initialize (); + gnc_invoice_sql_initialize (); + gnc_job_sql_initialize (); + gnc_order_sql_initialize (); + gnc_taxtable_sql_initialize (); + gnc_vendor_sql_initialize (); +} + +static void +gnc_sql_init_object_handlers (void) +{ + gnc_sql_init_book_handler (); + gnc_sql_init_commodity_handler (); + gnc_sql_init_account_handler (); + gnc_sql_init_budget_handler (); + gnc_sql_init_price_handler (); + gnc_sql_init_transaction_handler (); + gnc_sql_init_slots_handler (); + gnc_sql_init_recurrence_handler (); + gnc_sql_init_schedxaction_handler (); + gnc_sql_init_lot_handler (); + + /* And the business objects */ + business_core_sql_init (); } -/* - GncSqlResult* - GncSqlRow::operator*() - { - return m_iter->operator*(); - } -*/ /* ========================== END OF FILE ===================== */ diff --git a/src/backend/sql/gnc-backend-sql.h b/src/backend/sql/gnc-backend-sql.h index 9721e68ade..7d472331da 100644 --- a/src/backend/sql/gnc-backend-sql.h +++ b/src/backend/sql/gnc-backend-sql.h @@ -40,164 +40,21 @@ #define GNC_BACKEND_SQL_H extern "C" { -#include "qof.h" -#include "qofbackend-p.h" +#include +#include #include } -#include -#include #include #include -#include -#include +#include "gnc-sql-object-backend.hpp" -using uint_t = unsigned int; -struct GncSqlColumnInfo; -class GncSqlColumnTableEntry; -using GncSqlColumnTableEntryPtr = std::shared_ptr; -using EntryVec = std::vector; -using ColVec = std::vector; using StrVec = std::vector; using InstanceVec = std::vector; using PairVec = std::vector>; -using VersionPair = std::pair; -using VersionVec = std::vector; -class GncSqlConnection; -class GncSqlStatement; -using GncSqlStatementPtr = std::unique_ptr; -class GncSqlResult; -//using GncSqlResultPtr = std::unique_ptr; -using GncSqlResultPtr = GncSqlResult*; - -/** - * @struct GncSqlBackend - * - * Main SQL backend structure. - */ -class GncSqlBackend -{ -public: - GncSqlBackend(GncSqlConnection *conn, QofBook* book, - const char* format = nullptr); - virtual ~GncSqlBackend() = default; - /** Connect the backend to a GncSqlConnection. - * Sets up version info. Calling with nullptr clears the connection and - * destroys the version info. - */ - void connect(GncSqlConnection *conn) noexcept; - /** - * Initializes DB table version information. - */ - void init_version_info() noexcept; - bool reset_version_info() noexcept; - /** - * Finalizes DB table version information. - */ - void finalize_version_info() noexcept; - /* FIXME: These are just pass-throughs of m_conn functions. */ - GncSqlStatementPtr create_statement_from_sql(const std::string& str) const noexcept; - /** Executes an SQL SELECT statement and returns the result rows. If an - * error occurs, an entry is added to the log, an error status is returned - * to qof and nullptr is returned. - * - * @param statement Statement - * @return Results, or nullptr if an error has occured - */ - GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr& stmt) const noexcept; - int execute_nonselect_statement(const GncSqlStatementPtr& stmt) const noexcept; - std::string quote_string(const std::string&) const noexcept; - /** - * Creates a table in the database - * - * @param table_name Table name - * @param col_table DB table description - * @return TRUE if successful, FALSE if unsuccessful - */ - bool create_table(const std::string& table_name, const EntryVec& col_table) const noexcept; - /** - * Creates a table in the database and sets its version - * - * @param table_name Table name - * @param table_version Table version - * @param col_table DB table description - * @return TRUE if successful, FALSE if unsuccessful - */ - bool create_table(const std::string& table_name, int table_version, - const EntryVec& col_table) noexcept; - /** - * Creates an index in the database - * - * @param index_name Index name - * @param table_name Table name - * @param col_table Columns that the index should index - * @return TRUE if successful, FALSE if unsuccessful - */ - bool create_index(const std::string& index_name, - const std::string& table_name, - const EntryVec& col_table) const noexcept; - /** - * Adds one or more columns to an existing table. - * - * @param table_name SQL table name - * @param new_col_table Column table for new columns - * @return TRUE if successful, FALSE if unsuccessful - */ - bool add_columns_to_table(const std::string& table_name, - const EntryVec& col_table) const noexcept; - /** - * Upgrades a table to a new structure. - * - * The upgrade is done by creating a new table with the new structure, - * SELECTing the old data into the new table, deleting the old table, then - * renaming the new table. Therefore, this will only work if the new table - * structure is similar enough to the old table that the SELECT will work. - * - * @param table_name SQL table name - * @param col_table Column table - */ - void upgrade_table (const std::string& table_name, - const EntryVec& col_table) noexcept; - /** - * Returns the version number for a DB table. - * - * @param table_name Table name - * @return Version number, or 0 if the table does not exist - */ - uint_t get_table_version(const std::string& table_name) const noexcept; - bool set_table_version (const std::string& table_name, uint_t version) noexcept; - /** - * Converts a time64 value to a string value for the database. - * - * @param t time64 to be converted. - * @return String representation of the Timespec - */ - std::string time64_to_string (time64 t) const noexcept; - - QofBook* book() const noexcept { return m_book; } - - bool pristine() const noexcept { return m_is_pristine_db; } - void update_progress() const noexcept; - void finish_progress() const noexcept; - void set_loading(bool val) noexcept { m_loading = val; } - const char* timespec_format() const noexcept { return m_timespec_format; } - - friend void gnc_sql_load(GncSqlBackend*, QofBook*, QofBackendLoadType); - friend void gnc_sql_sync_all(GncSqlBackend*, QofBook*); - friend void gnc_sql_commit_edit(GncSqlBackend*, QofInstance*); - -protected: - QofBackend qof_be; /**< QOF backend. Not a pointer, nor really a member */ - GncSqlConnection* m_conn; /**< SQL connection */ - QofBook* m_book; /**< The primary, main open book */ - bool m_loading; /**< We are performing an initial load */ - bool m_in_query; /**< We are processing a query */ - bool m_is_pristine_db; /**< Are we saving to a new pristine db? */ - VersionVec m_versions; /**< Version number for each table */ - const char* m_timespec_format; /**< Format string for SQL for timespec values */ -private: -}; +using uint_t = unsigned int; +class GncSqlRow; /** * Initialize the SQL backend. @@ -260,214 +117,9 @@ void gnc_sql_commit_edit (GncSqlBackend* qsql_be, QofInstance* inst); /** */ -/** - * SQL statement provider. - */ -class GncSqlStatement -{ -public: - virtual ~GncSqlStatement() {} - virtual const char* to_sql() const = 0; - virtual void add_where_cond (QofIdTypeConst, const PairVec&) = 0; -}; - -/** - * Encapsulate the connection to the database. - */ -class GncSqlConnection -{ -public: - /** Returns NULL if error */ - virtual ~GncSqlConnection() = default; - virtual GncSqlResultPtr execute_select_statement (const GncSqlStatementPtr&) - noexcept = 0; - /** Returns false if error */ - virtual int execute_nonselect_statement (const GncSqlStatementPtr&) - noexcept = 0; - virtual GncSqlStatementPtr create_statement_from_sql (const std::string&) - const noexcept = 0; - /** Returns true if successful */ - virtual bool does_table_exist (const std::string&) const noexcept = 0; - /** Returns TRUE if successful, false if error */ - virtual bool begin_transaction () noexcept = 0; - /** Returns TRUE if successful, FALSE if error */ - virtual bool rollback_transaction () const noexcept = 0; - /** Returns TRUE if successful, FALSE if error */ - virtual bool commit_transaction () const noexcept = 0; - /** Returns TRUE if successful, FALSE if error */ - virtual bool create_table (const std::string&, const ColVec&) - const noexcept = 0; - /** Returns TRUE if successful, FALSE if error */ - virtual bool create_index (const std::string&, const std::string&, - const EntryVec&) const noexcept = 0; - /** Returns TRUE if successful, FALSE if error */ - virtual bool add_columns_to_table (const std::string&, const ColVec&) - const noexcept = 0; - virtual std::string quote_string (const std::string&) - const noexcept = 0; - /** Get the connection error value. - * If not 0 will normally be meaningless outside of implementation code. - */ - virtual int dberror() const noexcept = 0; - virtual void set_error(int error, unsigned int repeat, bool retry) noexcept = 0; - virtual bool verify() noexcept = 0; - virtual bool retry_connection(const char* msg) noexcept = 0; - -}; - -/** - * Struct used to represent a row in the result of an SQL SELECT statement. - * SQL backends must provide a structure which implements all of the functions. - */ - -class GncSqlRow; -/** - * Pure virtual class to iterate over a query result set. - */ -class GncSqlResult -{ -public: - virtual ~GncSqlResult() = default; - virtual uint64_t size() const noexcept = 0; - virtual GncSqlRow& begin() = 0; - virtual GncSqlRow& end() = 0; - friend GncSqlRow; -protected: - class IteratorImpl { - public: - virtual ~IteratorImpl() = default; - virtual GncSqlRow& operator++() = 0; - virtual GncSqlResult* operator*() = 0; - virtual int64_t get_int_at_col (const char* col) const = 0; - virtual float get_float_at_col (const char* col) const = 0; - virtual double get_double_at_col (const char* col) const = 0; - virtual std::string get_string_at_col (const char* col) const = 0; - virtual time64 get_time64_at_col (const char* col) const = 0; - virtual bool is_col_null (const char* col) const noexcept = 0; - }; -}; - -/** - * Row of SQL Query results. - * - * This is a "pointer" class of a pimpl pattern, the implementation being - * GncSqlResult::IteratorImpl. It's designed to present a std::forward_iterator - * like interface for use with range-for while allowing for wrapping a C API. - * - * Important Implementation Note: Operator++() as written requires that the - * sentinel GncSqlRow returned by GncSqlResult::end() has m_impl = nullptr in - * order to terminate the loop condition. This is a bit of a hack and might be a - * problem with a different SQL interface library from libdbi. - * - * Note that GncSqlResult::begin and GncSqlRow::operator++() return - * GncSqlRow&. This is necessary for operator++() to be called: Using operator - * ++() on a pointer performs pointer arithmetic rather than calling the - * pointed-to-class's operator++() and C++'s range-for uses operator++() - * directly on whatever begin() gives it. - */ -class GncSqlRow -{ -public: - GncSqlRow (GncSqlResult::IteratorImpl* iter) : m_iter{iter} {} - ~GncSqlRow() { } - GncSqlRow& operator++(); - GncSqlRow& operator*() { return *this; } - friend bool operator!=(const GncSqlRow&, const GncSqlRow&); - int64_t get_int_at_col (const char* col) const { - return m_iter->get_int_at_col (col); } - float get_float_at_col (const char* col) const { - return m_iter->get_float_at_col (col); } - double get_double_at_col (const char* col) const { - return m_iter->get_double_at_col (col); } - std::string get_string_at_col (const char* col) const { - return m_iter->get_string_at_col (col); } - time64 get_time64_at_col (const char* col) const { - return m_iter->get_time64_at_col (col); } - bool is_col_null (const char* col) const noexcept { - return m_iter->is_col_null (col); } -private: - GncSqlResult::IteratorImpl* m_iter; -}; - -inline bool operator!=(const GncSqlRow& lr, const GncSqlRow& rr) { - return lr.m_iter != rr.m_iter; -} - -inline bool operator==(const GncSqlRow& lr, const GncSqlRow& rr) { - return !(lr != rr); -} - #define GNC_SQL_BACKEND "gnc:sql:1" #define GNC_SQL_BACKEND_VERSION 1 -/** - * Encapsulates per-class table schema with functions to load, create a table, - * commit a changed front-end object (note that database transaction semantics - * are not yet implemented; edit/commit applies to the front-end object!) and - * write all front-end objects of the type to the database. Additional functions - * for creating and runing queries existed but were unused and untested. They've - * been temporarily removed until the front end is ready to use them. - */ -class GncSqlObjectBackend -{ -public: - GncSqlObjectBackend (int version, const std::string& type, - const std::string& table, const EntryVec& vec) : - m_table_name{table}, m_version{version}, m_type_name{type}, - m_col_table(vec) {} - /** - * Load all objects of m_type in the database into memory. - * @param sql_be The GncSqlBackend containing the database connection. - */ - virtual void load_all (GncSqlBackend* sql_be) = 0; - /** - * Conditionally create or update a database table from m_col_table. The - * condition is the version returned by querying the database's version - * table: If it's 0 then the table wasn't found and will be created; All - * tables areat least version 1. If the database's version is less than the - * compiled version then the table schema is upgraded but the data isn't, - * that's the engine's responsibility when the object is loaded. If the - * version is greater than the compiled version then nothing is touched. - * @param sql_be The GncSqlBackend containing the database connection. - */ - virtual void create_tables (GncSqlBackend* sql_be); - /** - * UPDATE/INSERT a single instance of m_type_name into the database. - * @param sql_be The GncSqlBackend containing the database. - * @param inst The QofInstance to be written out. - */ - virtual bool commit (GncSqlBackend* sql_be, QofInstance* inst); - /** - * Write all objects of m_type_name to the database. - * @param sql_be The GncSqlBackend containing the database. - * @return true if the objects were successfully written, false otherwise. - */ - virtual bool write (GncSqlBackend* sql_be) { return true; } - /** - * Return the m_type_name for the class. This value is created at - * compilation time and is called QofIdType or QofIdTypeConst in other parts - * of GnuCash. Most values are defined in src/engine/gnc-engine.h. - * @return m_type_name. - */ - const char* type () const noexcept { return m_type_name.c_str(); } - /** - * Compare a version with the compiled version (m_version). - * @return true if they match. - */ - const bool is_version (int version) const noexcept { - return version == m_version; - } -protected: - const std::string m_table_name; - const int m_version; - const std::string m_type_name; /// The front-end QofIdType - const EntryVec& m_col_table; /// The ORM table definition. -}; - -using GncSqlObjectBackendPtr = GncSqlObjectBackend*; - -using OBEEntry = std::tuple; -using OBEVec = std::vector; void gnc_sql_register_backend(OBEEntry&&); void gnc_sql_register_backend(GncSqlObjectBackendPtr); const OBEVec& gnc_sql_get_backend_registry(); @@ -492,364 +144,8 @@ struct write_objects_t GncSqlObjectBackendPtr obe; }; -/** - * Basic column type - */ -typedef enum -{ - BCT_STRING, - BCT_INT, - BCT_INT64, - BCT_DATE, - BCT_DOUBLE, - BCT_DATETIME -} GncSqlBasicColumnType; -// Type for conversion of db row to object. -enum GncSqlObjectType -{ - CT_STRING, - CT_GUID, - CT_INT, - CT_INT64, - CT_TIMESPEC, - CT_GDATE, - CT_NUMERIC, - CT_DOUBLE, - CT_BOOLEAN, - CT_ACCOUNTREF, - CT_BUDGETREF, - CT_COMMODITYREF, - CT_LOTREF, - CT_TXREF, - CT_ADDRESS, - CT_BILLTERMREF, - CT_INVOICEREF, - CT_ORDERREF, - CT_OWNERREF, - CT_TAXTABLEREF -}; - -enum ColumnFlags : int -{ - COL_NO_FLAG = 0, - COL_PKEY = 0x01, /**< The column is a primary key */ - COL_NNUL = 0x02, /**< The column may not contain a NULL value */ - COL_UNIQUE = 0x04, /**< The column must contain unique values */ - COL_AUTOINC = 0x08 /**< The column is an auto-incrementing int */ - }; - -/** - * Contains all of the information required to copy information between an - * object and the database for a specific object property. - * - * If an entry contains a gobj_param_name value, this string is used as the - * property name for a call to g_object_get() or g_object_set(). If the - * gobj_param_name value is NULL but qof_param_name is not NULL, this value - * is used as the parameter name for a call to - * qof_class_get_parameter_getter(). If both of these values are NULL, getter - * and setter are the addresses of routines to return or set the parameter - * value, respectively. - * - * The database description for an object consists of an array of - * GncSqlColumnTableEntry objects, with a final member having col_name == NULL. - */ - -class GncSqlColumnTableEntry -{ -public: - GncSqlColumnTableEntry (const char* name, const GncSqlObjectType type, - unsigned int s, - int f, const char* gobj_name = nullptr, - const char* qof_name = nullptr, - QofAccessFunc get = nullptr, - QofSetterFunc set = nullptr) : - m_col_name{name}, m_col_type{type}, m_size{s}, - m_flags{static_cast(f)}, - m_gobj_param_name{gobj_name}, m_qof_param_name{qof_name}, m_getter{get}, - m_setter{set} {} - - /** - * Load a value into an object from the database row. - */ - virtual void load(const GncSqlBackend* sql_be, GncSqlRow& row, - QofIdTypeConst obj_name, gpointer pObject) const noexcept = 0; - /** - * Add a GncSqlColumnInfo structure for the column type to a - * ColVec. - */ - virtual void add_to_table(const GncSqlBackend* sql_be, ColVec& vec) const noexcept = 0; - /** - * Add a pair of the table column heading and object's value's string - * representation to a PairVec; used for constructing WHERE clauses and - * UPDATE statements. - */ - virtual void add_to_query(const GncSqlBackend* sql_be, QofIdTypeConst obj_name, - gpointer pObject, PairVec& vec) const noexcept = 0; - /** - * Retrieve the getter function depending on whether it's an auto-increment - * field, a QofClass getter, or a function passed to the constructor. - */ - QofAccessFunc get_getter(QofIdTypeConst obj_name) const noexcept; - /** - * Retrieve the setter function depending on whether it's an auto-increment - * field, a QofClass getter, or a function passed to the constructor. - */ - QofSetterFunc get_setter(QofIdTypeConst obj_name) const noexcept; - /** - * Retrieve the field name so that we don't need to make - * create_single_col_select_statement and friend. - */ - const char* name() const noexcept { return m_col_name; } - /** - * Report if the entry is an auto-increment field. - */ - bool is_autoincr() const noexcept { return m_flags & COL_AUTOINC; } - /* On the other hand, our implementation class and GncSqlColumnInfo need to - * be able to read our member variables. - */ - template friend class GncSqlColumnTableEntryImpl; - friend struct GncSqlColumnInfo; - template void load_from_guid_ref(GncSqlRow& row, - QofIdTypeConst obj_name, - gpointer pObject, T get_ref) - const noexcept - { - g_return_if_fail (pObject != NULL); - - try - { - GncGUID guid; - auto val = row.get_string_at_col (m_col_name); - (void)string_to_guid (val.c_str(), &guid); - auto target = get_ref(&guid); - if (target != nullptr) - set_parameter (pObject, target, get_setter(obj_name), - m_gobj_param_name); - } - catch (std::invalid_argument) {} - } - -protected: - template T - get_row_value_from_object(QofIdTypeConst obj_name, const gpointer pObject) const; - template void - add_value_to_vec(const GncSqlBackend* sql_be, QofIdTypeConst obj_name, - const gpointer pObject, PairVec& vec) const; -/** - * Adds a name/guid std::pair to a PairVec for creating a query. - * - * @param sql_be SQL backend struct - * @param obj_name QOF object type name - * @param pObject Object - * @param pList List - */ - void add_objectref_guid_to_query (const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const noexcept; -/** - * Adds a column info structure for an object reference GncGUID to a ColVec. - * - * @param sql_be SQL backend struct - * @param pList List - */ - void add_objectref_guid_to_table (const GncSqlBackend* sql_be, - ColVec& vec) const noexcept; -private: - const char* m_col_name; /**< Column name */ - const GncSqlObjectType m_col_type; /**< Column type */ - unsigned int m_size; /**< Column size in bytes, for string columns */ - ColumnFlags m_flags; /**< Column flags */ - const char* m_gobj_param_name; /**< If non-null, g_object param name */ - const char* m_qof_param_name; /**< If non-null, qof parameter name */ - QofAccessFunc m_getter; /**< General access function */ - QofSetterFunc m_setter; /**< General setter function */ - template T get_row_value_from_object(QofIdTypeConst obj_name, - const gpointer pObject, - std::true_type) const; - template T get_row_value_from_object(QofIdTypeConst obj_name, - const gpointer pObject, - std::false_type) const; - template void add_value_to_vec(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec, std::true_type) const; - template void add_value_to_vec(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec, std::false_type) const; - -}; - -template -class GncSqlColumnTableEntryImpl : public GncSqlColumnTableEntry -{ -public: - GncSqlColumnTableEntryImpl (const char* name, const GncSqlObjectType type, - unsigned int s, - int f, const char* gobj_name = nullptr, - const char* qof_name = nullptr, - QofAccessFunc get = nullptr, - QofSetterFunc set = nullptr) : - GncSqlColumnTableEntry (name, type, s, f, gobj_name,qof_name, get, set) - {} - void load(const GncSqlBackend* sql_be, GncSqlRow& row, QofIdTypeConst obj_name, - gpointer pObject) const noexcept override; - void add_to_table(const GncSqlBackend* sql_be, ColVec& vec) const noexcept override; - void add_to_query(const GncSqlBackend* sql_be, QofIdTypeConst obj_name, - gpointer pObject, PairVec& vec) const noexcept override; -}; - -template -std::shared_ptr> -gnc_sql_make_table_entry(const char* name, unsigned int s, int f) -{ - return std::make_shared>(name, Type, s, f); -} - -template -std::shared_ptr> -gnc_sql_make_table_entry(const char* name, unsigned int s, int f, - const char* param) -{ - return std::make_shared>(name, Type, s, - f, param); -} - -class is_qof : public std::true_type {}; - -template -std::shared_ptr> -gnc_sql_make_table_entry(const char* name, unsigned int s, int f, - const char* param, bool qofp) -{ - return std::make_shared>(name, Type, s, - f, nullptr, - param); -} - -template -std::shared_ptr> -gnc_sql_make_table_entry(const char* name, unsigned int s, int f, - QofAccessFunc get, QofSetterFunc set) -{ - return std::make_shared>( - name, Type, s, f, nullptr, nullptr, get, set); -} - -/** - * information required to create a column in a table. - */ -struct GncSqlColumnInfo -{ - GncSqlColumnInfo (std::string&& name, GncSqlBasicColumnType type, - unsigned int size = 0, bool unicode = false, - bool autoinc = false, bool primary = false, - bool not_null = false) : - m_name{name}, m_type{type}, m_size{size}, m_unicode{unicode}, - m_autoinc{autoinc}, m_primary_key{primary}, m_not_null{not_null} - {} - GncSqlColumnInfo(const GncSqlColumnTableEntry& e, GncSqlBasicColumnType t, - unsigned int size = 0, bool unicode = true) : - m_name{e.m_col_name}, m_type{t}, m_size{size}, m_unicode{unicode}, - m_autoinc(e.m_flags & COL_AUTOINC), - m_primary_key(e.m_flags & COL_PKEY), - m_not_null(e.m_flags & COL_NNUL) {} - std::string m_name; /**< Column name */ - GncSqlBasicColumnType m_type; /**< Column basic type */ - unsigned int m_size; /**< Column size (string types) */ - bool m_unicode; /**< Column is unicode (string types) */ - bool m_autoinc; /**< Column is autoinc (int type) */ - bool m_primary_key; /**< Column is the primary key */ - bool m_not_null; /**< Column forbids NULL values */ -}; - -inline bool operator==(const GncSqlColumnInfo& l, - const GncSqlColumnInfo& r) -{ - return l.m_name == r.m_name && l.m_type == r.m_type; -} - -inline bool operator!=(const GncSqlColumnInfo& l, - const GncSqlColumnInfo& r) -{ - return !(l == r); -} - -typedef enum -{ - OP_DB_INSERT, - OP_DB_UPDATE, - OP_DB_DELETE -} E_DB_OPERATION; - - -/** - * Set an object property with a setter function. - * @param pObject void* to the object being set. - * @param item the value to be set in the property. - * @param setter The function to set the property. - * The void* is an obvious wart occasioned by the fact that we're using GLists - * to hold objects. As the rewrite progresses we'll replace that with another - * template paramter. - */ -template -void set_parameter(T object, P item, F& setter) -{ - (*setter)(object, item); -} - -template -void set_parameter(T object, P item, QofSetterFunc setter, std::true_type) -{ - (*setter)(object, (void*)item); -} - -template -void set_parameter(T object, P item, QofSetterFunc setter, std::false_type) -{ - (*setter)(object, (void*)(&item)); -} - -template -void set_parameter(T object, P item, QofSetterFunc setter) -{ - set_parameter(object, item, setter, std::is_pointer

()); -} - -/** - * Set an object property with g_object_set. - * @param pObject void* to the object being set. - * @param item the value to set in the property. - * @param property the property name. - * The void* is an obvious wart. So is g_object_set, partly because it's GObject - * but mostly because it works off of string comparisons. - */ -template -void set_parameter(T object, P item, const char* property) -{ - qof_instance_increase_editlevel(object); - g_object_set(object, property, item, nullptr); - qof_instance_decrease_editlevel(object); -}; - -/** - * Set an object property with either a g_object_set or a setter. - * - * See previous templates for the parameter meanings. This is clunky but fits in - * the current architecture for refactoring. - */ -template -void set_parameter(T object, P item, F setter, const char* property) -{ - if (property) - set_parameter(object, item, property); - else - set_parameter(object, item, setter); -} - /** * Performs an operation on the database. * @@ -926,91 +222,6 @@ gpointer gnc_sql_compile_query (QofBackend* qof_be, QofQuery* pQuery); void gnc_sql_free_query (QofBackend* qof_be, gpointer pQuery); void gnc_sql_run_query (QofBackend* qof_be, gpointer pQuery); -template T -GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, - const gpointer pObject) const -{ - return get_row_value_from_object(obj_name, pObject, - std::is_pointer()); -} - -template T -GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, - const gpointer pObject, - std::true_type) const -{ - g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, nullptr); - T result = nullptr; - if (m_gobj_param_name != nullptr) - g_object_get(pObject, m_gobj_param_name, &result, NULL ); - else - { - QofAccessFunc getter = get_getter(obj_name); - if (getter != nullptr) - result = reinterpret_cast((getter)(pObject, nullptr)); - } - return result; -} - -template T -GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, - const gpointer pObject, - std::false_type) const -{ - g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, - static_cast(0)); - T result = static_cast(0); - if (m_gobj_param_name != nullptr) - g_object_get(pObject, m_gobj_param_name, &result, NULL ); - else - { - QofAccessFunc getter = get_getter(obj_name); - if (getter != nullptr) - result = reinterpret_cast((getter)(pObject, nullptr)); - } - return result; -} - -template void -GncSqlColumnTableEntry::add_value_to_vec(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec) const -{ - add_value_to_vec(sql_be, obj_name, pObject, vec, std::is_pointer()); -} - -template void -GncSqlColumnTableEntry::add_value_to_vec(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec, std::true_type) const -{ - T s = get_row_value_from_object(obj_name, pObject); - - if (s != nullptr) - { - std::ostringstream stream; - stream << *s; - vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str())); - return; - } -} - -template void -GncSqlColumnTableEntry::add_value_to_vec(const GncSqlBackend* sql_be, - QofIdTypeConst obj_name, - const gpointer pObject, - PairVec& vec, std::false_type) const -{ - T s = get_row_value_from_object(obj_name, pObject); - - std::ostringstream stream; - stream << s; - vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str())); - return; -} - #endif /* GNC_BACKEND_SQL_H */ /** diff --git a/src/backend/sql/gnc-bill-term-sql.cpp b/src/backend/sql/gnc-bill-term-sql.cpp index d81ad514ce..70073f2a4a 100644 --- a/src/backend/sql/gnc-bill-term-sql.cpp +++ b/src/backend/sql/gnc-bill-term-sql.cpp @@ -41,6 +41,10 @@ extern "C" #include "qof.h" } +#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-backend-sql.h" #include "gnc-slots-sql.h" #include "gnc-bill-term-sql.h" diff --git a/src/backend/sql/gnc-book-sql.cpp b/src/backend/sql/gnc-book-sql.cpp index fc5bd96aa8..36fd79bba5 100644 --- a/src/backend/sql/gnc-book-sql.cpp +++ b/src/backend/sql/gnc-book-sql.cpp @@ -41,6 +41,11 @@ extern "C" #include "splint-defs.h" #endif } + +#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-backend-sql.h" #include "gnc-book-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-budget-sql.cpp b/src/backend/sql/gnc-budget-sql.cpp index f25aadaa26..c3aff03bf7 100644 --- a/src/backend/sql/gnc-budget-sql.cpp +++ b/src/backend/sql/gnc-budget-sql.cpp @@ -40,6 +40,11 @@ extern "C" #include "splint-defs.h" #endif } + +#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-backend-sql.h" #include "gnc-budget-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-commodity-sql.cpp b/src/backend/sql/gnc-commodity-sql.cpp index 68d9ce5790..b1e47fc054 100644 --- a/src/backend/sql/gnc-commodity-sql.cpp +++ b/src/backend/sql/gnc-commodity-sql.cpp @@ -36,6 +36,10 @@ extern "C" #include "gnc-commodity.h" } +#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-backend-sql.h" #include "gnc-commodity-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-customer-sql.cpp b/src/backend/sql/gnc-customer-sql.cpp index 4061311b95..76bc7d4ced 100644 --- a/src/backend/sql/gnc-customer-sql.cpp +++ b/src/backend/sql/gnc-customer-sql.cpp @@ -40,6 +40,10 @@ extern "C" #include "gncTaxTableP.h" } +#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-backend-sql.h" #include "gnc-slots-sql.h" #include "gnc-customer-sql.h" diff --git a/src/backend/sql/gnc-employee-sql.cpp b/src/backend/sql/gnc-employee-sql.cpp index 25b406a5bd..07da99f0d2 100644 --- a/src/backend/sql/gnc-employee-sql.cpp +++ b/src/backend/sql/gnc-employee-sql.cpp @@ -39,6 +39,10 @@ extern "C" #include "gncEmployeeP.h" } +#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-backend-sql.h" #include "gnc-slots-sql.h" #include "gnc-commodity-sql.h" diff --git a/src/backend/sql/gnc-entry-sql.cpp b/src/backend/sql/gnc-entry-sql.cpp index d20152bffc..4f1d4679b6 100644 --- a/src/backend/sql/gnc-entry-sql.cpp +++ b/src/backend/sql/gnc-entry-sql.cpp @@ -40,6 +40,11 @@ extern "C" #include "gncInvoiceP.h" #include "gncTaxTableP.h" } + +#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-backend-sql.h" #include "gnc-slots-sql.h" #include "gnc-bill-term-sql.h" diff --git a/src/backend/sql/gnc-invoice-sql.cpp b/src/backend/sql/gnc-invoice-sql.cpp index 41e601ac59..3c69f4cdd7 100644 --- a/src/backend/sql/gnc-invoice-sql.cpp +++ b/src/backend/sql/gnc-invoice-sql.cpp @@ -41,6 +41,11 @@ extern "C" #include "gncBillTermP.h" #include "gncInvoiceP.h" } + +#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-backend-sql.h" #include "gnc-commodity-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-job-sql.cpp b/src/backend/sql/gnc-job-sql.cpp index 3c793696cf..b01f02de1b 100644 --- a/src/backend/sql/gnc-job-sql.cpp +++ b/src/backend/sql/gnc-job-sql.cpp @@ -38,6 +38,10 @@ extern "C" #include "gncJobP.h" } +#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-backend-sql.h" #include "gnc-slots-sql.h" #include "gnc-job-sql.h" diff --git a/src/backend/sql/gnc-lots-sql.cpp b/src/backend/sql/gnc-lots-sql.cpp index a5806a623b..4b12f5ec05 100644 --- a/src/backend/sql/gnc-lots-sql.cpp +++ b/src/backend/sql/gnc-lots-sql.cpp @@ -41,6 +41,11 @@ extern "C" #include "splint-defs.h" #endif } + +#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-backend-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-object-backend.cpp b/src/backend/sql/gnc-object-backend.cpp new file mode 100644 index 0000000000..a27283c4e2 --- /dev/null +++ b/src/backend/sql/gnc-object-backend.cpp @@ -0,0 +1,86 @@ +/***********************************************************************\ + * gnc-sql-object-backend.hpp: Encapsulate per-class table schema. * + * * + * 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 * +\***********************************************************************/ + +extern "C" +{ +#include +} +#include "gnc-sql-object-backend.hpp" +#include "gnc-sql-backend.hpp" +#include "gnc-sql-column-table-entry.hpp" + +bool +GncSqlObjectBackend::commit (GncSqlBackend* sql_be, QofInstance* inst) +{ + const GncGUID* guid; + gboolean is_infant; + E_DB_OPERATION op; + gboolean is_ok; + + is_infant = qof_instance_get_infant (inst); + if (qof_instance_get_destroying (inst)) + { + op = OP_DB_DELETE; + } + else if (sql_be->pristine() || is_infant) + { + op = OP_DB_INSERT; + } + else + { + op = OP_DB_UPDATE; + } + is_ok = gnc_sql_do_db_operation (sql_be, op, m_table_name.c_str(), + m_type_name.c_str(), inst, m_col_table); + + if (is_ok) + { + // Now, commit any slots + guid = qof_instance_get_guid (inst); + if (!qof_instance_get_destroying (inst)) + { + is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst); + } + else + { + is_ok = gnc_sql_slots_delete (sql_be, guid); + } + } + + return is_ok; +} + +void +GncSqlObjectBackend::create_tables (GncSqlBackend* sql_be) +{ + g_return_if_fail (sql_be != nullptr); + int version = sql_be->get_table_version (m_table_name); + if (version == 0) //No tables, otherwise version will sql_be >= 1. + { + sql_be->create_table(m_table_name, m_col_table); + sql_be->set_table_version(m_table_name, m_version); + } + else if (version != m_version) + PERR("Version mismatch in table %s, expecting %d but backend is %d." + "Table creation aborted.", m_table_name.c_str(), m_version, version); +} diff --git a/src/backend/sql/gnc-order-sql.cpp b/src/backend/sql/gnc-order-sql.cpp index 8ea397075e..6edc38709f 100644 --- a/src/backend/sql/gnc-order-sql.cpp +++ b/src/backend/sql/gnc-order-sql.cpp @@ -38,6 +38,11 @@ extern "C" #include #include "gncOrderP.h" } + +#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-backend-sql.h" #include "gnc-slots-sql.h" #include "gnc-order-sql.h" diff --git a/src/backend/sql/gnc-owner-sql.cpp b/src/backend/sql/gnc-owner-sql.cpp index 6c0d1784c3..69f32fa797 100644 --- a/src/backend/sql/gnc-owner-sql.cpp +++ b/src/backend/sql/gnc-owner-sql.cpp @@ -30,17 +30,19 @@ #include extern "C" { -#include "config.h" - +#include +#include #include -#include -#include #include "gncCustomerP.h" #include "gncJobP.h" #include "gncEmployeeP.h" #include "gncVendorP.h" } -#include "gnc-backend-sql.h" +#include +#include +#include +#include "gnc-sql-backend.hpp" +#include "gnc-sql-column-table-entry.hpp" static QofLogModule log_module = G_LOG_DOMAIN; diff --git a/src/backend/sql/gnc-price-sql.cpp b/src/backend/sql/gnc-price-sql.cpp index 386dec4e48..11c72b3ffc 100644 --- a/src/backend/sql/gnc-price-sql.cpp +++ b/src/backend/sql/gnc-price-sql.cpp @@ -38,8 +38,12 @@ extern "C" #include "splint-defs.h" #endif } -#include "gnc-backend-sql.h" +#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-backend-sql.h" #include "gnc-commodity-sql.h" #include "gnc-price-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-recurrence-sql.cpp b/src/backend/sql/gnc-recurrence-sql.cpp index 0fa83c7361..05de77960c 100644 --- a/src/backend/sql/gnc-recurrence-sql.cpp +++ b/src/backend/sql/gnc-recurrence-sql.cpp @@ -40,6 +40,10 @@ extern "C" #endif } +#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-backend-sql.h" #include "gnc-recurrence-sql.h" diff --git a/src/backend/sql/gnc-schedxaction-sql.cpp b/src/backend/sql/gnc-schedxaction-sql.cpp index 326cf8365b..563f9b30f3 100644 --- a/src/backend/sql/gnc-schedxaction-sql.cpp +++ b/src/backend/sql/gnc-schedxaction-sql.cpp @@ -41,6 +41,10 @@ extern "C" #endif } +#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-backend-sql.h" #include "gnc-schedxaction-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-slots-sql.cpp b/src/backend/sql/gnc-slots-sql.cpp index b87c641490..d9912eedbc 100644 --- a/src/backend/sql/gnc-slots-sql.cpp +++ b/src/backend/sql/gnc-slots-sql.cpp @@ -39,6 +39,11 @@ extern "C" #include "splint-defs.h" #endif } + +#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-backend-sql.h" #include "gnc-slots-sql.h" diff --git a/src/backend/sql/gnc-sql-backend.cpp b/src/backend/sql/gnc-sql-backend.cpp new file mode 100644 index 0000000000..8d169b3bec --- /dev/null +++ b/src/backend/sql/gnc-sql-backend.cpp @@ -0,0 +1,340 @@ +/******************************************************************** + * 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 * +\********************************************************************/ +extern "C" +{ +#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-backend-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" + +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, + const char* format) : + qof_be {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, ERR_BACKEND_NO_ERR, nullptr, 0, + nullptr}, m_conn{conn}, m_book{book}, m_loading{false}, + m_in_query{false}, m_is_pristine_db{false}, m_timespec_format{format} +{ + if (conn != nullptr) + connect (conn); +} + +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->create_statement_from_sql(str); + 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->execute_select_statement(stmt); + 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 +{ + auto result = m_conn->execute_nonselect_statement(stmt); + 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 +{ + return m_conn->quote_string(str); +} + +bool +GncSqlBackend::create_table(const std::string& table_name, + const EntryVec& col_table) const noexcept +{ + ColVec info_vec; + gboolean ok = FALSE; + + for (auto const& table_row : col_table) + { + table_row->add_to_table (this, 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 +{ + 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 +{ + ColVec info_vec; + + for (auto const& table_row : col_table) + { + table_row->add_to_table (this, info_vec); + } + return m_conn->add_columns_to_table(table_name, info_vec); +} + +void +GncSqlBackend::update_progress() const noexcept +{ + if (qof_be.percentage != nullptr) + (qof_be.percentage) (nullptr, 101.0); +} + +void +GncSqlBackend::finish_progress() const noexcept +{ + if (qof_be.percentage != nullptr) + (qof_be.percentage) (nullptr, -1.0); +} + +/** + * 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 +{ + + 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 = true; + if (!m_conn->does_table_exist (VERSION_TABLE_NAME)) + 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); +} + +/* This is required because we're passing be->timespace_format to + * g_strdup_printf. + */ +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +std::string +GncSqlBackend::time64_to_string (time64 t) const noexcept +{ + auto tm = gnc_gmtime (&t); + + auto year = tm->tm_year + 1900; + + auto datebuf = g_strdup_printf (m_timespec_format, + year, tm->tm_mon + 1, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec); + gnc_tm_free (tm); + std::string date{datebuf}; + g_free(datebuf); + return date; +} diff --git a/src/backend/sql/gnc-sql-backend.hpp b/src/backend/sql/gnc-sql-backend.hpp new file mode 100644 index 0000000000..522eac1d07 --- /dev/null +++ b/src/backend/sql/gnc-sql-backend.hpp @@ -0,0 +1,181 @@ +/***********************************************************************\ + * gnc-sql-backend.hpp: Qof Backend for SQL Databases * + * * + * 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 * +\***********************************************************************/ + +#ifndef __GNC_SQL_BACKEND_HPP__ +#define __GNC_SQL_BACKEND_HPP__ + +extern "C" +{ +#include +#include +#include +} +#include +#include +#include +#include +class GncSqlColumnTableEntry; +using GncSqlColumnTableEntryPtr = std::shared_ptr; +using EntryVec = std::vector; +class GncSqlObjectBackend; +using GncSqlObjectBackendPtr = GncSqlObjectBackend*; +using OBEEntry = std::tuple; +using OBEVec = std::vector; +class GncSqlConnection; +class GncSqlStatement; +using GncSqlStatementPtr = std::unique_ptr; +class GncSqlResult; +using GncSqlResultPtr = GncSqlResult*; +using VersionPair = std::pair; +using VersionVec = std::vector; +using uint_t = unsigned int; + +/** + * + * Main SQL backend structure. + */ +class GncSqlBackend +{ +public: + GncSqlBackend(GncSqlConnection *conn, QofBook* book, + const char* format = nullptr); + virtual ~GncSqlBackend() = default; + /** Connect the backend to a GncSqlConnection. + * Sets up version info. Calling with nullptr clears the connection and + * destroys the version info. + */ + void connect(GncSqlConnection *conn) noexcept; + /** + * Initializes DB table version information. + */ + void init_version_info() noexcept; + bool reset_version_info() noexcept; + /** + * Finalizes DB table version information. + */ + void finalize_version_info() noexcept; + /* FIXME: These are just pass-throughs of m_conn functions. */ + GncSqlStatementPtr create_statement_from_sql(const std::string& str) const noexcept; + /** Executes an SQL SELECT statement and returns the result rows. If an + * error occurs, an entry is added to the log, an error status is returned + * to qof and nullptr is returned. + * + * @param statement Statement + * @return Results, or nullptr if an error has occured + */ + GncSqlResultPtr execute_select_statement(const GncSqlStatementPtr& stmt) const noexcept; + int execute_nonselect_statement(const GncSqlStatementPtr& stmt) const noexcept; + std::string quote_string(const std::string&) const noexcept; + /** + * Creates a table in the database + * + * @param table_name Table name + * @param col_table DB table description + * @return TRUE if successful, FALSE if unsuccessful + */ + bool create_table(const std::string& table_name, const EntryVec& col_table) const noexcept; + /** + * Creates a table in the database and sets its version + * + * @param table_name Table name + * @param table_version Table version + * @param col_table DB table description + * @return TRUE if successful, FALSE if unsuccessful + */ + bool create_table(const std::string& table_name, int table_version, + const EntryVec& col_table) noexcept; + /** + * Create/update all tables in the database + */ + void create_tables() noexcept; + + /** + * Creates an index in the database + * + * @param index_name Index name + * @param table_name Table name + * @param col_table Columns that the index should index + * @return TRUE if successful, FALSE if unsuccessful + */ + bool create_index(const std::string& index_name, + const std::string& table_name, + const EntryVec& col_table) const noexcept; + /** + * Adds one or more columns to an existing table. + * + * @param table_name SQL table name + * @param new_col_table Column table for new columns + * @return TRUE if successful, FALSE if unsuccessful + */ + bool add_columns_to_table(const std::string& table_name, + const EntryVec& col_table) const noexcept; + /** + * Upgrades a table to a new structure. + * + * The upgrade is done by creating a new table with the new structure, + * SELECTing the old data into the new table, deleting the old table, then + * renaming the new table. Therefore, this will only work if the new table + * structure is similar enough to the old table that the SELECT will work. + * + * @param table_name SQL table name + * @param col_table Column table + */ + void upgrade_table (const std::string& table_name, + const EntryVec& col_table) noexcept; + /** + * Returns the version number for a DB table. + * + * @param table_name Table name + * @return Version number, or 0 if the table does not exist + */ + uint_t get_table_version(const std::string& table_name) const noexcept; + bool set_table_version (const std::string& table_name, uint_t version) noexcept; + /** + * Converts a time64 value to a string value for the database. + * + * @param t time64 to be converted. + * @return String representation of the Timespec + */ + std::string time64_to_string (time64 t) const noexcept; + QofBook* book() const noexcept { return m_book; } + void set_loading(bool loading) noexcept { m_loading = loading; } + bool pristine() const noexcept { return m_is_pristine_db; } + void update_progress() const noexcept; + void finish_progress() const noexcept; + + friend void gnc_sql_load (GncSqlBackend* sql_be, QofBook* book, QofBackendLoadType loadType); + friend void gnc_sql_sync_all (GncSqlBackend* sql_be, QofBook* book); + friend void gnc_sql_commit_edit (GncSqlBackend* sql_be, QofInstance* inst); + + protected: + QofBackend qof_be; /**< QOF backend. Not a pointer, nor really a member */ + GncSqlConnection* m_conn; /**< SQL connection */ + QofBook* m_book; /**< The primary, main open book */ + bool m_loading; /**< We are performing an initial load */ + bool m_in_query; /**< We are processing a query */ + bool m_is_pristine_db; /**< Are we saving to a new pristine db? */ + const char* m_timespec_format; /**< Server-specific date-time string format */ + VersionVec m_versions; /**< Version number for each table */ +}; + +#endif //__GNC_SQL_BACKEND_HPP__ diff --git a/src/backend/sql/gnc-sql-column-table-entry.cpp b/src/backend/sql/gnc-sql-column-table-entry.cpp new file mode 100644 index 0000000000..ea536a2ddd --- /dev/null +++ b/src/backend/sql/gnc-sql-column-table-entry.cpp @@ -0,0 +1,698 @@ +/******************************************************************** + * gnc-sql-column-table-entry.cpp: Implement GncSqlColumnTableEntry * + * * + * 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 * +\********************************************************************/ + +extern "C" +{ +#include +#include +} +#include +#include + +#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-backend-sql.h" + +static QofLogModule log_module = G_LOG_DOMAIN; + +/* ================================================================= */ +static gpointer +get_autoinc_id (void* object, const QofParam* param) +{ + // Just need a 0 to force a new autoinc value + return (gpointer)0; +} + +static void +set_autoinc_id (void* object, void* item) +{ + // Nowhere to put the ID +} + +QofAccessFunc +GncSqlColumnTableEntry::get_getter (QofIdTypeConst obj_name) const noexcept +{ + QofAccessFunc getter; + + g_return_val_if_fail (obj_name != NULL, NULL); + + if (m_flags & COL_AUTOINC) + { + getter = get_autoinc_id; + } + else if (m_qof_param_name != NULL) + { + getter = qof_class_get_parameter_getter (obj_name, m_qof_param_name); + } + else + { + getter = m_getter; + } + + return getter; +} + +QofSetterFunc +GncSqlColumnTableEntry::get_setter(QofIdTypeConst obj_name) const noexcept +{ + QofSetterFunc setter = nullptr; + if (m_flags & COL_AUTOINC) + { + setter = set_autoinc_id; + } + else if (m_qof_param_name != nullptr) + { + g_assert (obj_name != NULL); + setter = qof_class_get_parameter_setter (obj_name, m_qof_param_name); + } + else + { + setter = m_setter; + } + return setter; +} + +void +GncSqlColumnTableEntry::add_objectref_guid_to_query (const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const void* pObject, + PairVec& vec) const noexcept +{ + auto inst = get_row_value_from_object(obj_name, pObject); + if (inst == nullptr) return; + auto guid = qof_instance_get_guid (inst); + if (guid != nullptr) + vec.emplace_back (std::make_pair (std::string{m_col_name}, + std::string{guid_to_string(guid)})); +} + +void +GncSqlColumnTableEntry::add_objectref_guid_to_table (const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_STRING, GUID_ENCODING_LENGTH, FALSE}; + vec.emplace_back(std::move(info)); +} + + +/* ----------------------------------------------------------------- */ +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) const noexcept +{ + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != NULL || get_setter(obj_name) != NULL); + + try + { + auto s = row.get_string_at_col (m_col_name); + set_parameter(pObject, s.c_str(), get_setter(obj_name), m_gobj_param_name); + } + catch (std::invalid_argument) {} +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_STRING, m_size, TRUE}; + vec.emplace_back(std::move(info)); +} + +/* char is unusual in that we get a pointer but don't deref it to pass + * it to operator<<(). + */ +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + auto s = get_row_value_from_object(obj_name, pObject); + + if (s != nullptr) + { + std::ostringstream stream; + stream << s; + vec.emplace_back (std::make_pair (std::string{m_col_name}, stream.str())); + return; + } +} + +/* ----------------------------------------------------------------- */ +typedef gint (*IntAccessFunc) (const gpointer); +typedef void (*IntSetterFunc) (const gpointer, gint); + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) const noexcept +{ + + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != NULL || get_setter(obj_name) != NULL); + + auto val = row.get_int_at_col(m_col_name); + set_parameter(pObject, val, + reinterpret_cast(get_setter(obj_name)), m_gobj_param_name); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_INT, 0, FALSE}; + vec.emplace_back(std::move(info)); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + add_value_to_vec(sql_be, obj_name, pObject, vec); +} + +/* ----------------------------------------------------------------- */ +typedef gboolean (*BooleanAccessFunc) (const gpointer); +typedef void (*BooleanSetterFunc) (const gpointer, gboolean); + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) + const noexcept +{ + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != NULL || get_setter(obj_name) != NULL); + + auto val = row.get_int_at_col (m_col_name); + set_parameter(pObject, val, + reinterpret_cast(get_setter(obj_name)), + m_gobj_param_name); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_INT, 0, FALSE}; + vec.emplace_back(std::move(info)); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + add_value_to_vec(sql_be, obj_name, pObject, vec); +} + +/* ----------------------------------------------------------------- */ +typedef gint64 (*Int64AccessFunc) (const gpointer); +typedef void (*Int64SetterFunc) (const gpointer, gint64); + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) + const noexcept +{ + g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); + + auto val = row.get_int_at_col (m_col_name); + set_parameter(pObject, val, + reinterpret_cast(get_setter(obj_name)), + m_gobj_param_name); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_INT64, 0, FALSE}; + vec.emplace_back(std::move(info)); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + add_value_to_vec(sql_be, obj_name, pObject, vec); +} +/* ----------------------------------------------------------------- */ + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) + const noexcept +{ + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); + double val; + try + { + val = static_cast(row.get_int_at_col(m_col_name)); + } + catch (std::invalid_argument) + { + try + { + val = static_cast(row.get_float_at_col(m_col_name)); + } + catch (std::invalid_argument) + { + try + { + val = row.get_double_at_col(m_col_name); + } + catch (std::invalid_argument) + { + val = 0.0; + } + } + } + set_parameter(pObject, val, get_setter(obj_name), m_gobj_param_name); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_DOUBLE, 0, FALSE}; + vec.emplace_back(std::move(info)); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + add_value_to_vec(sql_be, obj_name, pObject, vec); +} + +/* ----------------------------------------------------------------- */ + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) + const noexcept +{ + + GncGUID guid; + const GncGUID* pGuid; + + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); + + std::string str; + try + { + str = row.get_string_at_col(m_col_name); + } + catch (std::invalid_argument) + { + return; + } + (void)string_to_guid (str.c_str(), &guid); + set_parameter(pObject, &guid, get_setter(obj_name), m_gobj_param_name); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_STRING, GUID_ENCODING_LENGTH, FALSE}; + vec.emplace_back(std::move(info)); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + auto s = get_row_value_from_object(obj_name, pObject); + + if (s != nullptr) + { + + vec.emplace_back (std::make_pair (std::string{m_col_name}, + std::string{guid_to_string(s)})); + return; + } +} +/* ----------------------------------------------------------------- */ +typedef Timespec (*TimespecAccessFunc) (const gpointer); +typedef void (*TimespecSetterFunc) (const gpointer, Timespec*); + +#define TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d" +#define TIMESPEC_COL_SIZE (4+2+2+2+2+2) + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) const noexcept +{ + + Timespec ts = {0, 0}; + gboolean isOK = FALSE; + + + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); + + try + { + auto val = row.get_time64_at_col(m_col_name); + timespecFromTime64 (&ts, val); + } + catch (std::invalid_argument) + { + try + { + auto val = row.get_string_at_col(m_col_name); + auto s = val.c_str(); + auto buf = g_strdup_printf ("%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c", + s[0], s[1], s[2], s[3], s[4], s[5], + s[6], s[7], s[8], s[9], s[10], s[11], + s[12], s[13]); + ts = gnc_iso8601_to_timespec_gmt (buf); + g_free (buf); + } + catch (std::invalid_argument) + { + return; + } + } + set_parameter(pObject, &ts, + reinterpret_cast(get_setter(obj_name)), + m_gobj_param_name); + } + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != nullptr); + + GncSqlColumnInfo info{*this, BCT_DATETIME, TIMESPEC_COL_SIZE, FALSE}; + vec.emplace_back(std::move(info)); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + TimespecAccessFunc ts_getter; + Timespec ts; +/* Can't use get_row_value_from_object because g_object_get returns a + * Timespec* and the getter returns a Timespec. Will be fixed by the + * replacement of timespecs with time64s. + */ + g_return_if_fail (sql_be != NULL); + g_return_if_fail (obj_name != NULL); + g_return_if_fail (pObject != NULL); + + if (m_gobj_param_name != NULL) + { + Timespec* pts; + g_object_get (pObject, m_gobj_param_name, &pts, NULL); + ts = *pts; + } + else + { + ts_getter = (TimespecAccessFunc)get_getter (obj_name); + g_return_if_fail (ts_getter != NULL); + ts = (*ts_getter) (pObject); + } + + if (ts.tv_sec != 0 || ts.tv_nsec != 0) + { + auto datebuf = sql_be->time64_to_string (ts.tv_sec); + vec.emplace_back (std::make_pair (std::string{m_col_name}, datebuf)); + return; + } +} + +/* ----------------------------------------------------------------- */ +#define DATE_COL_SIZE 8 + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) const noexcept +{ + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); + if (row.is_col_null(m_col_name)) + return; + GDate date; + g_date_clear (&date, 1); + try + { + /* timespec_to_gdate applies the tz, and gdates are saved + * as ymd, so we don't want that. + */ + auto time = row.get_time64_at_col(m_col_name); + auto tm = gnc_gmtime(&time); + g_date_set_dmy(&date, tm->tm_mday, + static_cast(tm->tm_mon + 1), + tm->tm_year + 1900); + free(tm); + } + catch (std::invalid_argument) + { + try + { + std::string str = row.get_string_at_col(m_col_name); + if (str.empty()) return; + auto year = static_cast(stoi (str.substr (0,4))); + auto month = static_cast(stoi (str.substr (4,2))); + auto day = static_cast(stoi (str.substr (6,2))); + + if (year != 0 || month != 0 || day != (GDateDay)0) + g_date_set_dmy(&date, day, month, year); + + } + catch (std::invalid_argument) + { + return; + } + } + set_parameter(pObject, &date, get_setter(obj_name), m_gobj_param_name); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + GncSqlColumnInfo info{*this, BCT_DATE, DATE_COL_SIZE, FALSE}; + vec.emplace_back(std::move(info)); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ + GDate *date = get_row_value_from_object(obj_name, pObject); + + if (date && g_date_valid (date)) + { + std::ostringstream buf; + buf << std::setfill ('0') << std::setw (4) << g_date_get_year (date) << + std::setw (2) << g_date_get_month (date) << + std::setw (2) << static_cast(g_date_get_day (date)); + vec.emplace_back (std::make_pair (std::string{m_col_name}, buf.str())); + return; + } +} + +/* ----------------------------------------------------------------- */ +typedef gnc_numeric (*NumericGetterFunc) (const gpointer); +typedef void (*NumericSetterFunc) (gpointer, gnc_numeric*); + +static const EntryVec numeric_col_table = +{ + gnc_sql_make_table_entry("num", 0, COL_NNUL, "guid"), + gnc_sql_make_table_entry("denom", 0, COL_NNUL, "guid") +}; + +template<> void +GncSqlColumnTableEntryImpl::load (const GncSqlBackend* sql_be, + GncSqlRow& row, + QofIdTypeConst obj_name, + gpointer pObject) const noexcept +{ + + + g_return_if_fail (pObject != NULL); + g_return_if_fail (m_gobj_param_name != nullptr || get_setter(obj_name) != nullptr); + gnc_numeric n; + try + { + auto buf = g_strdup_printf ("%s_num", m_col_name); + auto num = row.get_int_at_col (buf); + g_free (buf); + buf = g_strdup_printf ("%s_denom", m_col_name); + auto denom = row.get_int_at_col (buf); + n = gnc_numeric_create (num, denom); + } + catch (std::invalid_argument) + { + return; + } + set_parameter(pObject, &n, + reinterpret_cast(get_setter(obj_name)), + m_gobj_param_name); +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_table(const GncSqlBackend* sql_be, + ColVec& vec) const noexcept +{ + g_return_if_fail (sql_be != NULL); + + for (auto const& subtable_row : numeric_col_table) + { + gchar* buf = g_strdup_printf("%s_%s", m_col_name, + subtable_row->m_col_name); + GncSqlColumnInfo info(buf, BCT_INT64, 0, false, false, + m_flags & COL_PKEY, m_flags & COL_NNUL); + vec.emplace_back(std::move(info)); + } +} + +template<> void +GncSqlColumnTableEntryImpl::add_to_query(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const gpointer pObject, + PairVec& vec) const noexcept +{ +/* We can't use get_row_value_from_object for the same reason as Timespec. */ + NumericGetterFunc getter; + gnc_numeric n; + + g_return_if_fail (sql_be != NULL); + g_return_if_fail (obj_name != NULL); + g_return_if_fail (pObject != NULL); + + if (m_gobj_param_name != nullptr) + { + gnc_numeric* s; + g_object_get (pObject, m_gobj_param_name, &s, NULL); + n = *s; + } + else + { + getter = reinterpret_cast(get_getter (obj_name)); + if (getter != NULL) + { + n = (*getter) (pObject); + } + else + { + n = gnc_numeric_zero (); + } + } + + std::ostringstream buf; + std::string num_col{m_col_name}; + std::string denom_col{m_col_name}; + num_col += "_num"; + denom_col += "_denom"; + buf << gnc_numeric_num (n); + vec.emplace_back (std::make_pair (num_col, buf.str ())); + buf.str (""); + buf << gnc_numeric_denom (n); + vec.emplace_back (denom_col, buf.str ()); +} + + +/* This is necessary for 64-bit builds because g++ complains + * that reinterpret_casting a void* (64 bits) to an int (32 bits) + * loses precision, so we have to explicitly dispose of the precision. + * FIXME: We shouldn't be storing ints in ptrs in the first place. + */ +#ifdef __LP64__ +template <> int +GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, + const void* pObject, + std::false_type) const +{ + g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, 0); + int result = 0; + if (m_gobj_param_name != nullptr) + g_object_get(const_cast(pObject), m_gobj_param_name, &result, + nullptr); + else + { + QofAccessFunc getter = get_getter(obj_name); + if (getter != nullptr) + { + auto value = ((getter)(const_cast(pObject), nullptr)); + result = reinterpret_cast(value) & + UINT64_C(0x00000000FFFFFFFF); + } + } + return result; +} +#endif diff --git a/src/backend/sql/gnc-sql-column-table-entry.hpp b/src/backend/sql/gnc-sql-column-table-entry.hpp new file mode 100644 index 0000000000..69710fdd29 --- /dev/null +++ b/src/backend/sql/gnc-sql-column-table-entry.hpp @@ -0,0 +1,483 @@ +/***********************************************************************\ + * gnc-sql-column-table-entry.hpp: Column Specification for SQL Table. * + * * + * 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 * +\***********************************************************************/ + +#ifndef __GNC_SQL_COLUMN_TABLE_ENTRY_HPP__ +#define __GNC_SQL_COLUMN_TABLE_ENTRY_HPP__ + +extern "C" +{ +#include +#include "qofbackend-p.h" +} +#include +#include +#include "gnc-sql-result.hpp" + +struct GncSqlColumnInfo; +using ColVec = std::vector; +using PairVec = std::vector>; +class GncSqlBackend; + +/** + * Basic column type + */ +typedef enum +{ + BCT_STRING, + BCT_INT, + BCT_INT64, + BCT_DATE, + BCT_DOUBLE, + BCT_DATETIME +} GncSqlBasicColumnType; + +enum ColumnFlags : int +{ + COL_NO_FLAG = 0, + COL_PKEY = 0x01, /**< The column is a primary key */ + COL_NNUL = 0x02, /**< The column may not contain a NULL value */ + COL_UNIQUE = 0x04, /**< The column must contain unique values */ + COL_AUTOINC = 0x08 /**< The column is an auto-incrementing int */ +}; + +// Type for conversion of db row to object. +enum GncSqlObjectType +{ + CT_STRING, + CT_GUID, + CT_INT, + CT_INT64, + CT_TIMESPEC, + CT_GDATE, + CT_NUMERIC, + CT_DOUBLE, + CT_BOOLEAN, + CT_ACCOUNTREF, + CT_BUDGETREF, + CT_COMMODITYREF, + CT_LOTREF, + CT_TXREF, + CT_ADDRESS, + CT_BILLTERMREF, + CT_INVOICEREF, + CT_ORDERREF, + CT_OWNERREF, + CT_TAXTABLEREF +}; + +/** + * Contains all of the information required to copy information between an + * object and the database for a specific object property. + * + * If an entry contains a gobj_param_name value, this string is used as the + * property name for a call to g_object_get() or g_object_set(). If the + * gobj_param_name value is NULL but qof_param_name is not NULL, this value + * is used as the parameter name for a call to + * qof_class_get_parameter_getter(). If both of these values are NULL, getter + * and setter are the addresses of routines to return or set the parameter + * value, respectively. + * + * The database description for an object consists of an array of + * GncSqlColumnTableEntry objects, with a final member having col_name == NULL. + */ + +class GncSqlColumnTableEntry +{ +public: + GncSqlColumnTableEntry (const char* name, const GncSqlObjectType type, + unsigned int s, + int f, const char* gobj_name = nullptr, + const char* qof_name = nullptr, + QofAccessFunc get = nullptr, + QofSetterFunc set = nullptr) : + m_col_name{name}, m_col_type{type}, m_size{s}, + m_flags{static_cast(f)}, + m_gobj_param_name{gobj_name}, m_qof_param_name{qof_name}, m_getter{get}, + m_setter{set} {} + + /** + * Load a value into an object from the database row. + */ + virtual void load(const GncSqlBackend* sql_be, GncSqlRow& row, + QofIdTypeConst obj_name, void* pObject) const noexcept = 0; + /** + * Add a GncSqlColumnInfo structure for the column type to a + * ColVec. + */ + virtual void add_to_table(const GncSqlBackend* sql_be, ColVec& vec) const noexcept = 0; + /** + * Add a pair of the table column heading and object's value's string + * representation to a PairVec; used for constructing WHERE clauses and + * UPDATE statements. + */ + virtual void add_to_query(const GncSqlBackend* sql_be, QofIdTypeConst obj_name, + void* pObject, PairVec& vec) const noexcept = 0; + /** + * Retrieve the getter function depending on whether it's an auto-increment + * field, a QofClass getter, or a function passed to the constructor. + */ + QofAccessFunc get_getter(QofIdTypeConst obj_name) const noexcept; + /** + * Retrieve the setter function depending on whether it's an auto-increment + * field, a QofClass getter, or a function passed to the constructor. + */ + QofSetterFunc get_setter(QofIdTypeConst obj_name) const noexcept; + /** + * Retrieve the field name so that we don't need to make + * create_single_col_select_statement and friend. + */ + const char* name() const noexcept { return m_col_name; } + /** + * Report if the entry is an auto-increment field. + */ + bool is_autoincr() const noexcept { return m_flags & COL_AUTOINC; } + /* On the other hand, our implementation class and GncSqlColumnInfo need to + * be able to read our member variables. + */ + template friend class GncSqlColumnTableEntryImpl; + friend struct GncSqlColumnInfo; + template void load_from_guid_ref(GncSqlRow& row, + QofIdTypeConst obj_name, + void* pObject, T get_ref) + const noexcept + { + g_return_if_fail (pObject != NULL); + + try + { + GncGUID guid; + auto val = row.get_string_at_col (m_col_name); + (void)string_to_guid (val.c_str(), &guid); + auto target = get_ref(&guid); + if (target != nullptr) + set_parameter (pObject, target, get_setter(obj_name), + m_gobj_param_name); + } + catch (std::invalid_argument) {} + } + +protected: + template T + get_row_value_from_object(QofIdTypeConst obj_name, const void* pObject) const; + template void + add_value_to_vec(const GncSqlBackend* sql_be, QofIdTypeConst obj_name, + const void* pObject, PairVec& vec) const; +/** + * Adds a name/guid std::pair to a PairVec for creating a query. + * + * @param sql_be SQL backend struct + * @param obj_name QOF object type name + * @param pObject Object + * @param pList List + */ + void add_objectref_guid_to_query (const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const void* pObject, + PairVec& vec) const noexcept; +/** + * Adds a column info structure for an object reference GncGUID to a ColVec. + * + * @param sql_be SQL backend struct + * @param pList List + */ + void add_objectref_guid_to_table (const GncSqlBackend* sql_be, + ColVec& vec) const noexcept; +private: + const char* m_col_name; /**< Column name */ + const GncSqlObjectType m_col_type; /**< Column type */ + unsigned int m_size; /**< Column size in bytes, for string columns */ + ColumnFlags m_flags; /**< Column flags */ + const char* m_gobj_param_name; /**< If non-null, g_object param name */ + const char* m_qof_param_name; /**< If non-null, qof parameter name */ + QofAccessFunc m_getter; /**< General access function */ + QofSetterFunc m_setter; /**< General setter function */ + template T get_row_value_from_object(QofIdTypeConst obj_name, + const void* pObject, + std::true_type) const; + template T get_row_value_from_object(QofIdTypeConst obj_name, + const void* pObject, + std::false_type) const; + template void add_value_to_vec(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const void* pObject, + PairVec& vec, std::true_type) const; + template void add_value_to_vec(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const void* pObject, + PairVec& vec, std::false_type) const; + +}; + +template +class GncSqlColumnTableEntryImpl : public GncSqlColumnTableEntry +{ +public: + GncSqlColumnTableEntryImpl (const char* name, const GncSqlObjectType type, + unsigned int s, + int f, const char* gobj_name = nullptr, + const char* qof_name = nullptr, + QofAccessFunc get = nullptr, + QofSetterFunc set = nullptr) : + GncSqlColumnTableEntry (name, type, s, f, gobj_name,qof_name, get, set) + {} + void load(const GncSqlBackend* sql_be, GncSqlRow& row, QofIdTypeConst obj_name, + void* pObject) const noexcept override; + void add_to_table(const GncSqlBackend* sql_be, ColVec& vec) const noexcept override; + void add_to_query(const GncSqlBackend* sql_be, QofIdTypeConst obj_name, + void* pObject, PairVec& vec) const noexcept override; +}; + +using GncSqlColumnTableEntryPtr = std::shared_ptr; +using EntryVec = std::vector; + +template +std::shared_ptr> +gnc_sql_make_table_entry(const char* name, unsigned int s, int f) +{ + return std::make_shared>(name, Type, s, f); +} + +template +std::shared_ptr> +gnc_sql_make_table_entry(const char* name, unsigned int s, int f, + const char* param) +{ + return std::make_shared>(name, Type, s, + f, param); +} + +class is_qof : public std::true_type {}; + +template +std::shared_ptr> +gnc_sql_make_table_entry(const char* name, unsigned int s, int f, + const char* param, bool qofp) +{ + return std::make_shared>(name, Type, s, + f, nullptr, + param); +} + +template +std::shared_ptr> +gnc_sql_make_table_entry(const char* name, unsigned int s, int f, + QofAccessFunc get, QofSetterFunc set) +{ + return std::make_shared>( + name, Type, s, f, nullptr, nullptr, get, set); +} + + +template T +GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, + const void* pObject) const +{ + return get_row_value_from_object(obj_name, pObject, + std::is_pointer()); +} + +template T +GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, + const void* pObject, + std::true_type) const +{ + g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, nullptr); + T result = nullptr; + if (m_gobj_param_name != nullptr) + g_object_get(const_cast(pObject), m_gobj_param_name, + &result, nullptr); + else + { + QofAccessFunc getter = get_getter(obj_name); + if (getter != nullptr) + result = reinterpret_cast((getter)(const_cast(pObject), + nullptr)); + } + return result; +} + +template T +GncSqlColumnTableEntry::get_row_value_from_object(QofIdTypeConst obj_name, + const void* pObject, + std::false_type) const +{ + g_return_val_if_fail(obj_name != nullptr && pObject != nullptr, + static_cast(0)); + T result = static_cast(0); + if (m_gobj_param_name != nullptr) + g_object_get(const_cast(pObject), m_gobj_param_name, + &result, nullptr); + else + { + QofAccessFunc getter = get_getter(obj_name); + if (getter != nullptr) + result = reinterpret_cast((getter)(const_cast(pObject), + nullptr)); + } + return result; +} + +template void +GncSqlColumnTableEntry::add_value_to_vec(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const void* pObject, + PairVec& vec) const +{ + add_value_to_vec(sql_be, obj_name, pObject, vec, std::is_pointer()); +} + +template void +GncSqlColumnTableEntry::add_value_to_vec(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const void* pObject, + PairVec& vec, std::true_type) const +{ + T s = get_row_value_from_object(obj_name, pObject); + + if (s != nullptr) + { + std::ostringstream stream; + stream << *s; + vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str())); + return; + } +} + +template void +GncSqlColumnTableEntry::add_value_to_vec(const GncSqlBackend* sql_be, + QofIdTypeConst obj_name, + const void* pObject, + PairVec& vec, std::false_type) const +{ + T s = get_row_value_from_object(obj_name, pObject); + + std::ostringstream stream; + stream << s; + vec.emplace_back(std::make_pair(std::string{m_col_name}, stream.str())); + return; +} + +/** + * information required to create a column in a table. + */ +struct GncSqlColumnInfo +{ + GncSqlColumnInfo (std::string&& name, GncSqlBasicColumnType type, + unsigned int size = 0, bool unicode = false, + bool autoinc = false, bool primary = false, + bool not_null = false) : + m_name{name}, m_type{type}, m_size{size}, m_unicode{unicode}, + m_autoinc{autoinc}, m_primary_key{primary}, m_not_null{not_null} + {} + GncSqlColumnInfo(const GncSqlColumnTableEntry& e, GncSqlBasicColumnType t, + unsigned int size = 0, bool unicode = true) : + m_name{e.m_col_name}, m_type{t}, m_size{size}, m_unicode{unicode}, + m_autoinc(e.m_flags & COL_AUTOINC), + m_primary_key(e.m_flags & COL_PKEY), + m_not_null(e.m_flags & COL_NNUL) {} + std::string m_name; /**< Column name */ + GncSqlBasicColumnType m_type; /**< Column basic type */ + unsigned int m_size; /**< Column size (string types) */ + bool m_unicode; /**< Column is unicode (string types) */ + bool m_autoinc; /**< Column is autoinc (int type) */ + bool m_primary_key; /**< Column is the primary key */ + bool m_not_null; /**< Column forbids NULL values */ +}; + +inline bool operator==(const GncSqlColumnInfo& l, + const GncSqlColumnInfo& r) +{ + return l.m_name == r.m_name && l.m_type == r.m_type; +} + +inline bool operator!=(const GncSqlColumnInfo& l, + const GncSqlColumnInfo& r) +{ + return !(l == r); +} + +/** + * Set an object property with a setter function. + * @param pObject void* to the object being set. + * @param item the value to be set in the property. + * @param setter The function to set the property. + * The void* is an obvious wart occasioned by the fact that we're using GLists + * to hold objects. As the rewrite progresses we'll replace that with another + * template paramter. + */ +template +void set_parameter(T object, P item, F& setter) +{ + (*setter)(object, item); +} + +template +void set_parameter(T object, P item, QofSetterFunc setter, std::true_type) +{ + (*setter)(object, (void*)item); +} + +template +void set_parameter(T object, P item, QofSetterFunc setter, std::false_type) +{ + (*setter)(object, (void*)(&item)); +} + +template +void set_parameter(T object, P item, QofSetterFunc setter) +{ + set_parameter(object, item, setter, std::is_pointer

()); +} + +/** + * Set an object property with g_object_set. + * @param pObject void* to the object being set. + * @param item the value to set in the property. + * @param property the property name. + * The void* is an obvious wart. So is g_object_set, partly because it's GObject + * but mostly because it works off of string comparisons. + */ +template +void set_parameter(T object, P item, const char* property) +{ + qof_instance_increase_editlevel(object); + g_object_set(object, property, item, nullptr); + qof_instance_decrease_editlevel(object); +}; + +/** + * Set an object property with either a g_object_set or a setter. + * + * See previous templates for the parameter meanings. This is clunky but fits in + * the current architecture for refactoring. + */ +template +void set_parameter(T object, P item, F setter, const char* property) +{ + if (property) + set_parameter(object, item, property); + else + set_parameter(object, item, setter); +} + +#endif //__GNC_SQL_COLUMN_TABLE_ENTRY_HPP__ diff --git a/src/backend/sql/gnc-sql-connection.hpp b/src/backend/sql/gnc-sql-connection.hpp new file mode 100644 index 0000000000..1522f47504 --- /dev/null +++ b/src/backend/sql/gnc-sql-connection.hpp @@ -0,0 +1,103 @@ +/***********************************************************************\ + * gnc-sql-connection.hpp: Encapsulate a SQL database connection. * + * * + * 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 * +\***********************************************************************/ + +#ifndef __GNC_SQL_CONNECTION_HPP__ +#define __GNC_SQL_CONNECTION_HPP__ + +extern "C" +{ +#include +} +#include +#include +#include + +class GncSqlResult; +using GncSqlResultPtr = GncSqlResult*; +class GncSqlColumnTableEntry; +using GncSqlColumnTableEntryPtr = std::shared_ptr; +using EntryVec = std::vector; +using PairVec = std::vector>; +struct GncSqlColumnInfo; +using ColVec = std::vector; + +/** + * SQL statement provider. + */ +class GncSqlStatement +{ +public: + virtual ~GncSqlStatement() {} + virtual const char* to_sql() const = 0; + virtual void add_where_cond (QofIdTypeConst, const PairVec&) = 0; +}; + +using GncSqlStatementPtr = std::unique_ptr; + +/** + * Encapsulate the connection to the database. This is an abstract class; the + * implmentation is database-specific. + */ +class GncSqlConnection +{ +public: + /** Returns NULL if error */ + virtual ~GncSqlConnection() = default; + virtual GncSqlResultPtr execute_select_statement (const GncSqlStatementPtr&) + noexcept = 0; + /** Returns false if error */ + virtual int execute_nonselect_statement (const GncSqlStatementPtr&) + noexcept = 0; + virtual GncSqlStatementPtr create_statement_from_sql (const std::string&) + const noexcept = 0; + /** Returns true if successful */ + virtual bool does_table_exist (const std::string&) const noexcept = 0; + /** Returns TRUE if successful, false if error */ + virtual bool begin_transaction () noexcept = 0; + /** Returns TRUE if successful, FALSE if error */ + virtual bool rollback_transaction () const noexcept = 0; + /** Returns TRUE if successful, FALSE if error */ + virtual bool commit_transaction () const noexcept = 0; + /** Returns TRUE if successful, FALSE if error */ + virtual bool create_table (const std::string&, const ColVec&) + const noexcept = 0; + /** Returns TRUE if successful, FALSE if error */ + virtual bool create_index (const std::string&, const std::string&, + const EntryVec&) const noexcept = 0; + /** Returns TRUE if successful, FALSE if error */ + virtual bool add_columns_to_table (const std::string&, const ColVec&) + const noexcept = 0; + virtual std::string quote_string (const std::string&) + const noexcept = 0; + /** Get the connection error value. + * If not 0 will normally be meaningless outside of implementation code. + */ + virtual int dberror() const noexcept = 0; + virtual void set_error(int error, unsigned int repeat, bool retry) noexcept = 0; + virtual bool verify() noexcept = 0; + virtual bool retry_connection(const char* msg) noexcept = 0; + +}; + + +#endif //__GNC_SQL_CONNECTION_HPP__ diff --git a/src/backend/sql/gnc-sql-object-backend.cpp b/src/backend/sql/gnc-sql-object-backend.cpp new file mode 100644 index 0000000000..a945e2575d --- /dev/null +++ b/src/backend/sql/gnc-sql-object-backend.cpp @@ -0,0 +1,89 @@ +/***********************************************************************\ + * gnc-sql-object-backend.hpp: Encapsulate per-class table schema. * + * * + * 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 * +\***********************************************************************/ + +extern "C" +{ +#include +} +#include "gnc-sql-object-backend.hpp" +#include "gnc-sql-backend.hpp" +#include "gnc-sql-column-table-entry.hpp" +#include "gnc-slots-sql.h" + +static QofLogModule log_module = G_LOG_DOMAIN; + +bool +GncSqlObjectBackend::commit (GncSqlBackend* sql_be, QofInstance* inst) +{ + const GncGUID* guid; + gboolean is_infant; + E_DB_OPERATION op; + gboolean is_ok; + + is_infant = qof_instance_get_infant (inst); + if (qof_instance_get_destroying (inst)) + { + op = OP_DB_DELETE; + } + else if (sql_be->pristine() || is_infant) + { + op = OP_DB_INSERT; + } + else + { + op = OP_DB_UPDATE; + } + is_ok = gnc_sql_do_db_operation (sql_be, op, m_table_name.c_str(), + m_type_name.c_str(), inst, m_col_table); + + if (is_ok) + { + // Now, commit any slots + guid = qof_instance_get_guid (inst); + if (!qof_instance_get_destroying (inst)) + { + is_ok = gnc_sql_slots_save (sql_be, guid, is_infant, inst); + } + else + { + is_ok = gnc_sql_slots_delete (sql_be, guid); + } + } + + return is_ok; +} + +void +GncSqlObjectBackend::create_tables (GncSqlBackend* sql_be) +{ + g_return_if_fail (sql_be != nullptr); + int version = sql_be->get_table_version (m_table_name); + if (version == 0) //No tables, otherwise version will sql_be >= 1. + { + sql_be->create_table(m_table_name, m_col_table); + sql_be->set_table_version(m_table_name, m_version); + } + else if (version != m_version) + PERR("Version mismatch in table %s, expecting %d but backend is %d." + "Table creation aborted.", m_table_name.c_str(), m_version, version); +} diff --git a/src/backend/sql/gnc-sql-object-backend.hpp b/src/backend/sql/gnc-sql-object-backend.hpp new file mode 100644 index 0000000000..7c5604f726 --- /dev/null +++ b/src/backend/sql/gnc-sql-object-backend.hpp @@ -0,0 +1,116 @@ +/***********************************************************************\ + * gnc-sql-object-backend.hpp: Encapsulate per-class table schema. * + * * + * 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 * +\***********************************************************************/ + +#ifndef __GNC_SQL_OBJECT_BACKEND_HPP__ +#define __GNC_SQL_OBJECT_BACKEND_HPP__ + +extern "C" +{ +#include +} +#include +#include +#include + +class GncSqlBackend; +class GncSqlColumnTableEntry; +using GncSqlColumnTableEntryPtr = std::shared_ptr; +using EntryVec = std::vector; + +typedef enum +{ + OP_DB_INSERT, + OP_DB_UPDATE, + OP_DB_DELETE +} E_DB_OPERATION; + +/** + * Encapsulates per-class table schema with functions to load, create a table, + * commit a changed front-end object (note that database transaction semantics + * are not yet implemented; edit/commit applies to the front-end object!) and + * write all front-end objects of the type to the database. Additional functions + * for creating and runing queries existed but were unused and untested. They've + * been temporarily removed until the front end is ready to use them. + */ +class GncSqlObjectBackend +{ +public: + GncSqlObjectBackend (int version, const std::string& type, + const std::string& table, const EntryVec& vec) : + m_table_name{table}, m_version{version}, m_type_name{type}, + m_col_table(vec) {} + /** + * Load all objects of m_type in the database into memory. + * @param sql_be The GncSqlBackend containing the database connection. + */ + virtual void load_all (GncSqlBackend* sql_be) = 0; + /** + * Conditionally create or update a database table from m_col_table. The + * condition is the version returned by querying the database's version + * table: If it's 0 then the table wasn't found and will be created; All + * tables areat least version 1. If the database's version is less than the + * compiled version then the table schema is upgraded but the data isn't, + * that's the engine's responsibility when the object is loaded. If the + * version is greater than the compiled version then nothing is touched. + * @param sql_be The GncSqlBackend containing the database connection. + */ + virtual void create_tables (GncSqlBackend* sql_be); + /** + * UPDATE/INSERT a single instance of m_type_name into the database. + * @param sql_be The GncSqlBackend containing the database. + * @param inst The QofInstance to be written out. + */ + virtual bool commit (GncSqlBackend* sql_be, QofInstance* inst); + /** + * Write all objects of m_type_name to the database. + * @param sql_be The GncSqlBackend containing the database. + * @return true if the objects were successfully written, false otherwise. + */ + virtual bool write (GncSqlBackend* sql_be) { return true; } + /** + * Return the m_type_name for the class. This value is created at + * compilation time and is called QofIdType or QofIdTypeConst in other parts + * of GnuCash. Most values are defined in src/engine/gnc-engine.h. + * @return m_type_name. + */ + const char* type () const noexcept { return m_type_name.c_str(); } + /** + * Compare a version with the compiled version (m_version). + * @return true if they match. + */ + const bool is_version (int version) const noexcept { + return version == m_version; + } +protected: + const std::string m_table_name; + const int m_version; + const std::string m_type_name; /// The front-end QofIdType + const EntryVec& m_col_table; /// The ORM table definition. +}; + +using GncSqlObjectBackendPtr = GncSqlObjectBackend*; + +using OBEEntry = std::tuple; +using OBEVec = std::vector; + +#endif //__GNC_SQL_OBJECT_BACKEND_HPP__ diff --git a/src/backend/sql/gnc-sql-result.cpp b/src/backend/sql/gnc-sql-result.cpp new file mode 100644 index 0000000000..20f774b68c --- /dev/null +++ b/src/backend/sql/gnc-sql-result.cpp @@ -0,0 +1,48 @@ +/***********************************************************************\ + * gnc-sql-result.cpp: Encapsulate the results of a SQL query. * + * * + * 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 * +\***********************************************************************/ + +extern "C" +{ +#include +} +#include +#include "gnc-sql-column-table-entry.hpp" + +#include "gnc-sql-result.hpp" + +GncSqlRow& +GncSqlRow::operator++() +{ + auto& new_row = m_iter->operator++(); + if (new_row != *this) + m_iter = nullptr; + return new_row; +} + +/* + GncSqlResult* + GncSqlRow::operator*() + { + return m_iter->operator*(); + } +*/ diff --git a/src/backend/sql/gnc-sql-result.hpp b/src/backend/sql/gnc-sql-result.hpp new file mode 100644 index 0000000000..818df82c25 --- /dev/null +++ b/src/backend/sql/gnc-sql-result.hpp @@ -0,0 +1,114 @@ +/***********************************************************************\ + * gnc-sql-result.hpp: Encapsulate the results of a SQL query. * + * * + * 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 * +\***********************************************************************/ + +#ifndef __GNC_SQL_RESULT_HPP__ +#define __GNC_SQL_RESULT_HPP__ + +extern "C" +{ +#include +} +#include +#include +#include + +class GncSqlRow; +/** + * Pure virtual class to iterate over a query result set. + */ + +class GncSqlResult +{ +public: + virtual ~GncSqlResult() = default; + virtual uint64_t size() const noexcept = 0; + virtual GncSqlRow& begin() = 0; + virtual GncSqlRow& end() = 0; + friend GncSqlRow; +protected: + class IteratorImpl { + public: + virtual ~IteratorImpl() = default; + virtual GncSqlRow& operator++() = 0; + virtual GncSqlResult* operator*() = 0; + virtual int64_t get_int_at_col (const char* col) const = 0; + virtual float get_float_at_col (const char* col) const = 0; + virtual double get_double_at_col (const char* col) const = 0; + virtual std::string get_string_at_col (const char* col) const = 0; + virtual time64 get_time64_at_col (const char* col) const = 0; + virtual bool is_col_null (const char* col) const noexcept = 0; + }; +}; + +/** + * Row of SQL Query results. + * + * This is a "pointer" class of a pimpl pattern, the implementation being + * GncSqlResult::IteratorImpl. It's designed to present a std::forward_iterator + * like interface for use with range-for while allowing for wrapping a C API. + * + * Important Implementation Note: Operator++() as written requires that the + * sentinel GncSqlRow returned by GncSqlResult::end() has m_impl = nullptr in + * order to terminate the loop condition. This is a bit of a hack and might be a + * problem with a different SQL interface library from libdbi. + * + * Note that GncSqlResult::begin and GncSqlRow::operator++() return + * GncSqlRow&. This is necessary for operator++() to be called: Using operator + * ++() on a pointer performs pointer arithmetic rather than calling the + * pointed-to-class's operator++() and C++'s range-for uses operator++() + * directly on whatever begin() gives it. + */ +class GncSqlRow +{ +public: + GncSqlRow (GncSqlResult::IteratorImpl* iter) : m_iter{iter} {} + ~GncSqlRow() { } + GncSqlRow& operator++(); + GncSqlRow& operator*() { return *this; } + friend bool operator!=(const GncSqlRow&, const GncSqlRow&); + int64_t get_int_at_col (const char* col) const { + return m_iter->get_int_at_col (col); } + float get_float_at_col (const char* col) const { + return m_iter->get_float_at_col (col); } + double get_double_at_col (const char* col) const { + return m_iter->get_double_at_col (col); } + std::string get_string_at_col (const char* col) const { + return m_iter->get_string_at_col (col); } + time64 get_time64_at_col (const char* col) const { + return m_iter->get_time64_at_col (col); } + bool is_col_null (const char* col) const noexcept { + return m_iter->is_col_null (col); } +private: + GncSqlResult::IteratorImpl* m_iter; +}; + +inline bool operator!=(const GncSqlRow& lr, const GncSqlRow& rr) { + return lr.m_iter != rr.m_iter; +} + +inline bool operator==(const GncSqlRow& lr, const GncSqlRow& rr) { + return !(lr != rr); +} + + +#endif //__GNC_SQL_RESULT_HPP__ diff --git a/src/backend/sql/gnc-tax-table-sql.cpp b/src/backend/sql/gnc-tax-table-sql.cpp index 407200dfb6..645037a170 100644 --- a/src/backend/sql/gnc-tax-table-sql.cpp +++ b/src/backend/sql/gnc-tax-table-sql.cpp @@ -40,6 +40,10 @@ extern "C" #include "gncTaxTableP.h" } +#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-backend-sql.h" #include "gnc-slots-sql.h" #include "gnc-tax-table-sql.h" diff --git a/src/backend/sql/gnc-transaction-sql.cpp b/src/backend/sql/gnc-transaction-sql.cpp index bbca8a483e..4fdc540b2b 100644 --- a/src/backend/sql/gnc-transaction-sql.cpp +++ b/src/backend/sql/gnc-transaction-sql.cpp @@ -51,6 +51,10 @@ extern "C" #include "escape.h" +#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-backend-sql.h" #include "gnc-transaction-sql.h" #include "gnc-commodity-sql.h" diff --git a/src/backend/sql/gnc-vendor-sql.cpp b/src/backend/sql/gnc-vendor-sql.cpp index 777456de04..3660b3ce35 100644 --- a/src/backend/sql/gnc-vendor-sql.cpp +++ b/src/backend/sql/gnc-vendor-sql.cpp @@ -41,6 +41,10 @@ extern "C" #include "gncTaxTableP.h" } +#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-vendor-sql.h" #include "gnc-bill-term-sql.h" #include "gnc-tax-table-sql.h" diff --git a/src/backend/sql/test/utest-gnc-backend-sql.cpp b/src/backend/sql/test/utest-gnc-backend-sql.cpp index 2a05d19229..ce3558c466 100644 --- a/src/backend/sql/test/utest-gnc-backend-sql.cpp +++ b/src/backend/sql/test/utest-gnc-backend-sql.cpp @@ -28,6 +28,9 @@ extern "C" #include } /* Add specific headers for this class */ +#include "../gnc-sql-connection.hpp" +#include "../gnc-sql-backend.hpp" +#include "../gnc-sql-result.hpp" #include "../gnc-backend-sql.h" static const gchar* suitename = "/backend/sql/gnc-backend-sql";