From ccc1cc49ab542fb96660d81af354e96c4a20c1e1 Mon Sep 17 00:00:00 2001 From: John Ralls Date: Mon, 25 Jul 2016 11:29:30 -0700 Subject: [PATCH] Separate DBI classes into their own files. --- src/backend/dbi/CMakeLists.txt | 10 +- src/backend/dbi/Makefile.am | 9 +- src/backend/dbi/gnc-backend-dbi.cpp | 431 +---------------------- src/backend/dbi/gnc-backend-dbi.hpp | 140 +------- src/backend/dbi/gnc-dbiprovider.hpp | 55 +++ src/backend/dbi/gnc-dbiproviderimpl.hpp | 293 +++++++++++++++ src/backend/dbi/gnc-dbisqlconnection.cpp | 3 +- src/backend/dbi/gnc-dbisqlconnection.hpp | 87 +++++ src/backend/dbi/gnc-dbisqlresult.cpp | 187 ++++++++++ src/backend/dbi/gnc-dbisqlresult.hpp | 77 ++++ src/backend/dbi/test/CMakeLists.txt | 1 + src/backend/dbi/test/Makefile.am | 3 +- src/backend/sql/gnc-address-sql.cpp | 2 +- src/backend/sql/gnc-transaction-sql.cpp | 2 +- 14 files changed, 739 insertions(+), 561 deletions(-) create mode 100644 src/backend/dbi/gnc-dbiprovider.hpp create mode 100644 src/backend/dbi/gnc-dbiproviderimpl.hpp create mode 100644 src/backend/dbi/gnc-dbisqlresult.cpp create mode 100644 src/backend/dbi/gnc-dbisqlresult.hpp diff --git a/src/backend/dbi/CMakeLists.txt b/src/backend/dbi/CMakeLists.txt index be5fc155d5..af6147434f 100644 --- a/src/backend/dbi/CMakeLists.txt +++ b/src/backend/dbi/CMakeLists.txt @@ -5,10 +5,16 @@ ADD_SUBDIRECTORY(test) # Source file gncmod-backend-dbi.c does not appear to be use in Makefile.in, so not included here. SET (backend_dbi_SOURCES - gnc-backend-dbi.cpp gnc-dbisqlconnection.cpp + gnc-backend-dbi.cpp + gnc-dbisqlresult.cpp + gnc-dbisqlconnection.cpp ) SET (backend_dbi_noinst_HEADERS - gnc-backend-dbi.h gnc-backend-dbi.hpp + gnc-backend-dbi.h + gnc-backend-dbi.hpp + gnc-dbisqlresult.hpp + gnc-dbisqlconnection.hpp + gnc-dbiprovider.hpp ) # Add dependency on config.h diff --git a/src/backend/dbi/Makefile.am b/src/backend/dbi/Makefile.am index a2a2baaed2..800f5eec36 100644 --- a/src/backend/dbi/Makefile.am +++ b/src/backend/dbi/Makefile.am @@ -22,11 +22,16 @@ AM_CPPFLAGS = \ libgncmod_backend_dbi_la_SOURCES = \ gnc-backend-dbi.cpp \ - gnc-dbisqlconnection.cpp + gnc-dbisqlconnection.cpp \ + gnc-dbisqlresult.cpp noinst_HEADERS = \ gnc-backend-dbi.h \ - gnc-backend-dbi.hpp + gnc-backend-dbi.hpp \ + gnc-dbisqlconnection.hpp \ + gnc-dbisqlresult.hpp \ + gnc-dbiprovider.hpp \ + gnc-dbiproviderimpl.hpp libgncmod_backend_dbi_la_LDFLAGS = -shared -avoid-version libgncmod_backend_dbi_la_LIBADD = \ diff --git a/src/backend/dbi/gnc-backend-dbi.cpp b/src/backend/dbi/gnc-backend-dbi.cpp index b194b635bf..d23593239e 100644 --- a/src/backend/dbi/gnc-backend-dbi.cpp +++ b/src/backend/dbi/gnc-backend-dbi.cpp @@ -68,18 +68,17 @@ extern "C" #include "splint-defs.h" #endif - - /* For direct access to dbi data structs, sadly needed for datetime */ -#include } #include #include -#include #include #include "gnc-backend-dbi.h" #include "gnc-backend-dbi.hpp" +#include "gnc-dbisqlresult.hpp" +#include "gnc-dbisqlconnection.hpp" + #if PLATFORM(WINDOWS) #ifdef __STRICT_ANSI_UNSET__ #undef __STRICT_ANSI_UNSET__ @@ -99,6 +98,8 @@ static dbi_inst dbi_instance = nullptr; #define TRANSACTION_NAME "trans" static QofLogModule log_module = G_LOG_DOMAIN; +// gnc-dbiproviderimpl.hpp has templates that need log_module defined. +#include "gnc-dbiproviderimpl.hpp" static gchar lock_table[] = "gnclock"; @@ -117,28 +118,6 @@ static gboolean save_may_clobber_data (QofBackend* qbe); static GncDbiTestResult conn_test_dbi_library (dbi_conn conn); -enum class DbType -{ - DBI_SQLITE, - DBI_MYSQL, - DBI_PGSQL -}; - - -template -class GncDbiProviderImpl : public GncDbiProvider -{ -public: - std::string create_table_ddl(const GncSqlConnection* conn, - const std::string& table_name, - const ColVec& info_vec); - StrVec get_table_list(dbi_conn conn, - const std::string& dbname); - void append_col_def(std::string& ddl, const GncSqlColumnInfo& info); - StrVec get_index_list (dbi_conn conn); - void drop_index(dbi_conn conn, const std::string& index); -}; - template class QofDbiBackendProvider : public QofBackendProvider { @@ -957,7 +936,6 @@ pgsql_error_fn (dbi_conn conn, void* user_data) { PINFO ("DBI error: %s\n", msg); be->set_exists(false); - be->set_error (ERR_BACKEND_NO_SUCH_DB, 0, FALSE); } else if (g_strrstr (msg, "server closed the connection unexpectedly")) // Connection lost @@ -971,17 +949,23 @@ pgsql_error_fn (dbi_conn conn, void* user_data) be->set_error (ERR_BACKEND_CONN_LOST, 1, true); be->retry_connection(msg); } - else if (be->connected() && - (g_str_has_prefix (msg, "connection pointer is NULL") || - g_str_has_prefix (msg, "could not connect to server"))) // No connection + else if (g_str_has_prefix (msg, "connection pointer is NULL") || + g_str_has_prefix (msg, "could not connect to server")) // No connection { - be->set_error(ERR_BACKEND_CANT_CONNECT, 1, true); - be->retry_connection (msg); + + if (!be->connected()) + qof_backend_set_error((QofBackend*)be, ERR_BACKEND_CANT_CONNECT); + else + { + be->set_error(ERR_BACKEND_CANT_CONNECT, 1, true); + be->retry_connection (msg); + } } else { PERR ("DBI error: %s\n", msg); - be->set_error (ERR_BACKEND_MISC, 0, false); + if (be->connected()) + be->set_error (ERR_BACKEND_MISC, 0, false); } } @@ -1678,387 +1662,6 @@ gnc_module_finalize_backend_dbi (void) } /* --------------------------------------------------------- */ -GncSqlRow& -GncDbiSqlResult::IteratorImpl::operator++() -{ - int status = dbi_result_next_row (m_inst->m_dbi_result); - if (status) - return m_inst->m_row; - int error = m_inst->dberror(); - if (error == DBI_ERROR_BADIDX || error == 0) //ran off the end of the results - return m_inst->m_sentinel; - PERR("Error %d incrementing results iterator.", error); - qof_backend_set_error (m_inst->m_conn->qbe(), ERR_BACKEND_SERVER_ERR); - return m_inst->m_sentinel; -} - -int64_t -GncDbiSqlResult::IteratorImpl::get_int_at_col(const char* col) const -{ - auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); - if(type != DBI_TYPE_INTEGER) - throw (std::invalid_argument{"Requested integer from non-integer column."}); - return dbi_result_get_longlong (m_inst->m_dbi_result, col); -} - -float -GncDbiSqlResult::IteratorImpl::get_float_at_col(const char* col) const -{ - auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); - auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); - if(type != DBI_TYPE_DECIMAL || - (attrs & DBI_DECIMAL_SIZEMASK) != DBI_DECIMAL_SIZE4) - throw (std::invalid_argument{"Requested float from non-float column."}); - gnc_push_locale (LC_NUMERIC, "C"); - auto retval = dbi_result_get_float(m_inst->m_dbi_result, col); - gnc_pop_locale (LC_NUMERIC); - return retval; -} - -double -GncDbiSqlResult::IteratorImpl::get_double_at_col(const char* col) const -{ - auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); - auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); - if(type != DBI_TYPE_DECIMAL || - (attrs & DBI_DECIMAL_SIZEMASK) != DBI_DECIMAL_SIZE8) - throw (std::invalid_argument{"Requested double from non-double column."}); - gnc_push_locale (LC_NUMERIC, "C"); - auto retval = dbi_result_get_double(m_inst->m_dbi_result, col); - gnc_pop_locale (LC_NUMERIC); - return retval; -} - -std::string -GncDbiSqlResult::IteratorImpl::get_string_at_col(const char* col) const -{ - auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); - auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); - if(type != DBI_TYPE_STRING) - throw (std::invalid_argument{"Requested string from non-string column."}); - gnc_push_locale (LC_NUMERIC, "C"); - auto strval = dbi_result_get_string(m_inst->m_dbi_result, col); - if (strval == nullptr) - { - gnc_pop_locale (LC_NUMERIC); - throw (std::invalid_argument{"Column empty."}); - } - auto retval = std::string{strval}; - gnc_pop_locale (LC_NUMERIC); - return retval; -} -time64 -GncDbiSqlResult::IteratorImpl::get_time64_at_col (const char* col) const -{ - auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); - auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); - if (type != DBI_TYPE_DATETIME) - throw (std::invalid_argument{"Requested double from non-double column."}); - gnc_push_locale (LC_NUMERIC, "C"); -#if HAVE_LIBDBI_TO_LONGLONG - /* A less evil hack than the one equrie by libdbi-0.8, but - * still necessary to work around the same bug. - */ - auto retval = dbi_result_get_as_longlong(dbi_row->result, - col_name); -#else - /* A seriously evil hack to work around libdbi bug #15 - * https://sourceforge.net/p/libdbi/bugs/15/. When libdbi - * v0.9 is widely available this can be replaced with - * dbi_result_get_as_longlong. - * Note: 0.9 is available in Debian Jessie and Fedora 21. - */ - auto result = (dbi_result_t*) (m_inst->m_dbi_result); - auto row = dbi_result_get_currow (result); - auto idx = dbi_result_get_field_idx (result, col) - 1; - time64 retval = result->rows[row]->field_values[idx].d_datetime; - if (retval < MINTIME || retval > MAXTIME) - retval = 0; -#endif //HAVE_LIBDBI_TO_LONGLONG - gnc_pop_locale (LC_NUMERIC); - return retval; -} - - -/* --------------------------------------------------------- */ - -GncDbiSqlResult::~GncDbiSqlResult() -{ - int status = dbi_result_free (m_dbi_result); - - if (status == 0) - return; - - PERR ("Error %d in dbi_result_free() result.", dberror() ); - qof_backend_set_error (m_conn->qbe(), ERR_BACKEND_SERVER_ERR); -} - -GncSqlRow& -GncDbiSqlResult::begin() -{ - - if (m_dbi_result == nullptr || - dbi_result_get_numrows(m_dbi_result) == 0) - return m_sentinel; - int status = dbi_result_first_row(m_dbi_result); - if (status) - return m_row; - int error = dberror(); // - - if (error != DBI_ERROR_BADIDX) //otherwise just an empty result set - { - PERR ("Error %d in dbi_result_first_row()", dberror()); - qof_backend_set_error (m_conn->qbe(), ERR_BACKEND_SERVER_ERR); - } - return m_sentinel; -} - -uint64_t -GncDbiSqlResult::size() const noexcept -{ - return dbi_result_get_numrows(m_dbi_result); -} - -/* --------------------------------------------------------- */ - -template<> void -GncDbiProviderImpl::append_col_def(std::string& ddl, - const GncSqlColumnInfo& info) -{ - const char* type_name = nullptr; - - if (info.m_type == BCT_INT) - { - type_name = "integer"; - } - else if (info.m_type == BCT_INT64) - { - type_name = "bigint"; - } - else if (info.m_type == BCT_DOUBLE) - { - type_name = "float8"; - } - else if (info.m_type == BCT_STRING || info.m_type == BCT_DATE - || info.m_type == BCT_DATETIME) - { - type_name = "text"; - } - else - { - PERR ("Unknown column type: %d\n", info.m_type); - type_name = ""; - } - ddl += (info.m_name + " " + type_name); - if (info.m_size != 0) - { - ddl += "(" + std::to_string(info.m_size) + ")"; - } - if (info.m_primary_key) - { - ddl += " PRIMARY KEY"; - } - if (info.m_autoinc) - { - ddl += " AUTOINCREMENT"; - } - if (info.m_not_null) - { - ddl += " NOT NULL"; - } -} - -template std::string -GncDbiProviderImpl

::create_table_ddl (const GncSqlConnection* conn, - const std::string& table_name, - const ColVec& info_vec) -{ - std::string ddl; - unsigned int col_num = 0; - - g_return_val_if_fail (conn != nullptr, ddl); - ddl += "CREATE TABLE " + table_name + "("; - for (auto const& info : info_vec) - { - if (col_num++ != 0) - { - ddl += ", "; - } - append_col_def (ddl, info); - } - ddl += ")"; - - return ddl; -} - -template<> void -GncDbiProviderImpl::append_col_def (std::string& ddl, - const GncSqlColumnInfo& info) -{ - const char* type_name = nullptr; - - if (info.m_type == BCT_INT) - { - type_name = "integer"; - } - else if (info.m_type == BCT_INT64) - { - type_name = "bigint"; - } - else if (info.m_type == BCT_DOUBLE) - { - type_name = "double"; - } - else if (info.m_type == BCT_STRING) - { - type_name = "varchar"; - } - else if (info.m_type == BCT_DATE) - { - type_name = "date"; - } - else if (info.m_type == BCT_DATETIME) - { - type_name = "TIMESTAMP NULL DEFAULT 0"; - } - else - { - PERR ("Unknown column type: %d\n", info.m_type); - type_name = ""; - } - ddl += info.m_name + " " + type_name; - if (info.m_size != 0 && info.m_type == BCT_STRING) - { - ddl += "(" + std::to_string(info.m_size) + ")"; - } - if (info.m_unicode) - { - ddl += " CHARACTER SET utf8"; - } - if (info.m_primary_key) - { - ddl += " PRIMARY KEY"; - } - if (info.m_autoinc) - { - ddl += " AUTO_INCREMENT"; - } - if (info.m_not_null) - { - ddl += " NOT NULL"; - } -} - - -template<> void -GncDbiProviderImpl::append_col_def (std::string& ddl, - const GncSqlColumnInfo& info) -{ - const char* type_name = nullptr; - - if (info.m_type == BCT_INT) - { - if (info.m_autoinc) - { - type_name = "serial"; - } - else - { - type_name = "integer"; - } - } - else if (info.m_type == BCT_INT64) - { - type_name = "int8"; - } - else if (info.m_type == BCT_DOUBLE) - - { - type_name = "double precision"; - } - else if (info.m_type == BCT_STRING) - { - type_name = "varchar"; - } - else if (info.m_type == BCT_DATE) - { - type_name = "date"; - } - else if (info.m_type == BCT_DATETIME) - { - type_name = "timestamp without time zone"; - } - else - { - PERR ("Unknown column type: %d\n", info.m_type); - type_name = ""; - } - ddl += info.m_name + " " + type_name; - if (info.m_size != 0 && info.m_type == BCT_STRING) - { - ddl += "(" + std::to_string(info.m_size) + ")"; - } - if (info.m_primary_key) - { - ddl += " PRIMARY KEY"; - } - if (info.m_not_null) - { - ddl += " NOT NULL"; - } -} - -static StrVec -conn_get_table_list (dbi_conn conn, const std::string& dbname) -{ - StrVec retval; - auto tables = dbi_conn_get_table_list (conn, dbname.c_str(), nullptr); - while (dbi_result_next_row (tables) != 0) - { - std::string table_name {dbi_result_get_string_idx (tables, 1)}; - retval.push_back(table_name); - } - dbi_result_free (tables); - return retval; -} - -template<> StrVec -GncDbiProviderImpl::get_table_list (dbi_conn conn, - const std::string& dbname) -{ - /* Return the list, but remove the tables that sqlite3 adds for - * its own use. */ - auto list = conn_get_table_list (conn, dbname); - auto end = std::remove(list.begin(), list.end(), "sqlite_sequence"); - list.erase(end, list.end()); - return list; -} - -template<> StrVec -GncDbiProviderImpl::get_table_list (dbi_conn conn, - const std::string& dbname) -{ - return conn_get_table_list (conn, dbname); -} - -template<> StrVec -GncDbiProviderImpl::get_table_list (dbi_conn conn, - const std::string& dbname) -{ - auto list = conn_get_table_list (conn, dbname); - auto end = std::remove_if (list.begin(), list.end(), - [](std::string& table_name){ - return table_name == "sql_features" || - table_name == "sql_implementation_info" || - table_name == "sql_languages" || - table_name == "sql_packages" || - table_name == "sql_parts" || - table_name == "sql_sizing" || - table_name == "sql_sizing_profiles"; - }); - list.erase(end, list.end()); - return list; -} /** Users discovered a bug in some distributions of libdbi, where if * it is compiled on certain versions of gcc with the -ffast-math diff --git a/src/backend/dbi/gnc-backend-dbi.hpp b/src/backend/dbi/gnc-backend-dbi.hpp index 75e4c5e876..b959f51f15 100644 --- a/src/backend/dbi/gnc-backend-dbi.hpp +++ b/src/backend/dbi/gnc-backend-dbi.hpp @@ -34,7 +34,8 @@ extern "C" #define GETPID() getpid() #endif } -#include +#include "gnc-backend-sql.h" + #define GNC_HOST_NAME_MAX 255 /** @@ -67,20 +68,6 @@ typedef enum GNC_DBI_FAIL_TEST } GncDbiTestResult; -class GncDbiProvider -{ -public: - virtual ~GncDbiProvider() = default; - virtual std::string create_table_ddl(const GncSqlConnection* conn, - const std::string& table_name, - const ColVec& info_vec) = 0; - virtual std::vector get_table_list(dbi_conn conn, - const std::string& dbname) = 0; - virtual void append_col_def(std::string& ddl, - const GncSqlColumnInfo& info) = 0; - virtual std::vector get_index_list (dbi_conn conn) = 0; - virtual void drop_index(dbi_conn conn, const std::string& index) = 0; -}; /** * Implementations of GncSqlBackend. @@ -112,135 +99,12 @@ private: bool m_exists; // Does the database exist? }; -class GncDbiSqlConnection : public GncSqlConnection -{ -public: - GncDbiSqlConnection (GncDbiProvider* provider, QofBackend* qbe, - dbi_conn conn, const char* lock_table) : - m_qbe{qbe}, m_conn{conn}, m_provider{provider}, m_conn_ok{true}, - m_last_error{ERR_BACKEND_NO_ERR}, m_error_repeat{0}, m_retry{false}, - m_lock_table{lock_table} {} - ~GncDbiSqlConnection() override; - GncSqlResultPtr execute_select_statement (const GncSqlStatementPtr&) - noexcept override; - int execute_nonselect_statement (const GncSqlStatementPtr&) - noexcept override; - GncSqlStatementPtr create_statement_from_sql (const std::string&) - const noexcept override; - bool does_table_exist (const std::string&) const noexcept override; - bool begin_transaction () noexcept override; - bool rollback_transaction () const noexcept override; - bool commit_transaction () const noexcept override; - bool create_table (const std::string&, const ColVec&) const noexcept override; - bool create_index (const std::string&, const std::string&, const EntryVec&) - const noexcept override; - bool add_columns_to_table (const std::string&, const ColVec&) - const noexcept override; - std::string quote_string (const std::string&) const noexcept override; - int dberror() const noexcept override { - return dbi_conn_error(m_conn, nullptr); } - QofBackend* qbe () const noexcept { return m_qbe; } - dbi_conn conn() const noexcept { return m_conn; } - GncDbiProvider* provider() { return m_provider; } - inline void set_error(int error, int repeat, bool retry) noexcept override - { - m_last_error = error; - m_error_repeat = repeat; - m_retry = retry; - } - inline void init_error() noexcept - { - set_error(ERR_BACKEND_NO_ERR, 0, false); - } - /** Check if the dbi connection is valid. If not attempt to re-establish it - * Returns TRUE is there is a valid connection in the end or FALSE otherwise - */ - bool verify() noexcept override; - bool retry_connection(const char* msg) noexcept override; - dbi_result table_manage_backup(const std::string& table_name, TableOpType op); - /* FIXME: These three friend functions should really be members, but doing - * that is too invasive just yet. */ - bool table_operation (const StrVec& table_name_list, - TableOpType op) noexcept; - std::string add_columns_ddl(const std::string& table_name, - const ColVec& info_vec) const noexcept; - friend void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book); - -private: - QofBackend* m_qbe; - dbi_conn m_conn; - GncDbiProvider* m_provider; - /** Used by the error handler routines to flag if the connection is ok to - * use - */ - bool m_conn_ok; - /** Code of the last error that occurred. This is set in the error callback - * function. - */ - int m_last_error; - /** Used in case of transient errors. After such error, another attempt at - * the original call is allowed. error_repeat tracks the number of attempts - * and can be used to prevent infinite loops. - */ - int m_error_repeat; - /** Signals the calling function that it should retry (the error handler - * detected transient error and managed to resolve it, but it can't run the - * original query) - */ - gboolean m_retry; - const char* m_lock_table; - void unlock_database(); - -}; void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book); /* external access required for tests */ std::string adjust_sql_options_string(const std::string&); -/** - * An iterable wrapper for dbi_result; allows using C++11 range for. - */ -class GncDbiSqlResult : public GncSqlResult -{ -public: - GncDbiSqlResult(const GncDbiSqlConnection* conn, dbi_result result) : - m_conn{conn}, m_dbi_result{result}, m_iter{this}, m_row{&m_iter}, - m_sentinel{nullptr} {} - ~GncDbiSqlResult(); - uint64_t size() const noexcept; - int dberror() { return m_conn->dberror(); } - GncSqlRow& begin(); - GncSqlRow& end() { return m_sentinel; } -protected: - class IteratorImpl : public GncSqlResult::IteratorImpl - { - public: - ~IteratorImpl() = default; - IteratorImpl(GncDbiSqlResult* inst) : m_inst{inst} {} - virtual GncSqlRow& operator++(); - virtual GncSqlRow& operator++(int) { return ++(*this); }; - virtual GncSqlResult* operator*() { return m_inst; } - virtual int64_t get_int_at_col (const char* col) const; - virtual float get_float_at_col (const char* col) const; - virtual double get_double_at_col (const char* col) const; - virtual std::string get_string_at_col (const char* col)const; - virtual time64 get_time64_at_col (const char* col) const; - virtual bool is_col_null(const char* col) const noexcept - { - return dbi_result_field_is_null(m_inst->m_dbi_result, col); - } - private: - GncDbiSqlResult* m_inst; - }; -private: - const GncDbiSqlConnection* m_conn; - dbi_result m_dbi_result; - IteratorImpl m_iter; - GncSqlRow m_row; - GncSqlRow m_sentinel; - -}; #endif //GNC_BACKEND_DBI_HPP diff --git a/src/backend/dbi/gnc-dbiprovider.hpp b/src/backend/dbi/gnc-dbiprovider.hpp new file mode 100644 index 0000000000..21d749e713 --- /dev/null +++ b/src/backend/dbi/gnc-dbiprovider.hpp @@ -0,0 +1,55 @@ +/******************************************************************** + * gnc-dbiprovider.cpp: Encapsulate differences among Dbi backends. * + * * + * 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_DBIPROVIDER_HPP__ +#define __GNC_DBIPROVIDER_HPP__ + +extern "C" +{ +#include +} +#include +#include + +/** + * Provides the primary abstraction for different DBI backends. + */ +class GncSqlConnection; +struct GncSqlColumnInfo; +using ColVec=std::vector; + +class GncDbiProvider +{ +public: + virtual ~GncDbiProvider() = default; + virtual std::string create_table_ddl(const GncSqlConnection* conn, + const std::string& table_name, + const ColVec& info_vec) = 0; + virtual StrVec get_table_list(dbi_conn conn, const std::string& dbname) = 0; + virtual void append_col_def(std::string& ddl, + const GncSqlColumnInfo& info) = 0; + virtual StrVec get_index_list (dbi_conn conn) = 0; + virtual void drop_index(dbi_conn conn, const std::string& index) = 0; +}; + +#endif //__GNC_DBIPROVIDER_HPP__ diff --git a/src/backend/dbi/gnc-dbiproviderimpl.hpp b/src/backend/dbi/gnc-dbiproviderimpl.hpp new file mode 100644 index 0000000000..2b9103a381 --- /dev/null +++ b/src/backend/dbi/gnc-dbiproviderimpl.hpp @@ -0,0 +1,293 @@ +/************************************************************************ + * gnc-dbiproviderimpl.hpp: Encapsulate differences among Dbi backends. * + * * + * 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_DBISQLPROVIDERIMPL_HPP__ +#define __GNC_DBISQLPROVIDERIMPL_HPP__ +#include +extern "C" +{ +#include +} +#include "gnc-backend-dbi.hpp" +#include "gnc-dbiprovider.hpp" + +enum class DbType +{ + DBI_SQLITE, + DBI_MYSQL, + DBI_PGSQL +}; + +template +class GncDbiProviderImpl : public GncDbiProvider +{ +public: + std::string create_table_ddl(const GncSqlConnection* conn, + const std::string& table_name, + const ColVec& info_vec); + StrVec get_table_list(dbi_conn conn, + const std::string& dbname); + void append_col_def(std::string& ddl, const GncSqlColumnInfo& info); + StrVec get_index_list (dbi_conn conn); + void drop_index(dbi_conn conn, const std::string& index); +}; + +template<> void +GncDbiProviderImpl::append_col_def(std::string& ddl, + const GncSqlColumnInfo& info) +{ + const char* type_name = nullptr; + + if (info.m_type == BCT_INT) + { + type_name = "integer"; + } + else if (info.m_type == BCT_INT64) + { + type_name = "bigint"; + } + else if (info.m_type == BCT_DOUBLE) + { + type_name = "float8"; + } + else if (info.m_type == BCT_STRING || info.m_type == BCT_DATE + || info.m_type == BCT_DATETIME) + { + type_name = "text"; + } + else + { + PERR ("Unknown column type: %d\n", info.m_type); + type_name = ""; + } + ddl += (info.m_name + " " + type_name); + if (info.m_size != 0) + { + ddl += "(" + std::to_string(info.m_size) + ")"; + } + if (info.m_primary_key) + { + ddl += " PRIMARY KEY"; + } + if (info.m_autoinc) + { + ddl += " AUTOINCREMENT"; + } + if (info.m_not_null) + { + ddl += " NOT NULL"; + } +} + +template std::string +GncDbiProviderImpl

::create_table_ddl (const GncSqlConnection* conn, + const std::string& table_name, + const ColVec& info_vec) +{ + std::string ddl; + unsigned int col_num = 0; + + g_return_val_if_fail (conn != nullptr, ddl); + ddl += "CREATE TABLE " + table_name + "("; + for (auto const& info : info_vec) + { + if (col_num++ != 0) + { + ddl += ", "; + } + append_col_def (ddl, info); + } + ddl += ")"; + + return ddl; +} + +template<> void +GncDbiProviderImpl::append_col_def (std::string& ddl, + const GncSqlColumnInfo& info) +{ + const char* type_name = nullptr; + + if (info.m_type == BCT_INT) + { + type_name = "integer"; + } + else if (info.m_type == BCT_INT64) + { + type_name = "bigint"; + } + else if (info.m_type == BCT_DOUBLE) + { + type_name = "double"; + } + else if (info.m_type == BCT_STRING) + { + type_name = "varchar"; + } + else if (info.m_type == BCT_DATE) + { + type_name = "date"; + } + else if (info.m_type == BCT_DATETIME) + { + type_name = "TIMESTAMP NULL DEFAULT 0"; + } + else + { + PERR ("Unknown column type: %d\n", info.m_type); + type_name = ""; + } + ddl += info.m_name + " " + type_name; + if (info.m_size != 0 && info.m_type == BCT_STRING) + { + ddl += "(" + std::to_string(info.m_size) + ")"; + } + if (info.m_unicode) + { + ddl += " CHARACTER SET utf8"; + } + if (info.m_primary_key) + { + ddl += " PRIMARY KEY"; + } + if (info.m_autoinc) + { + ddl += " AUTO_INCREMENT"; + } + if (info.m_not_null) + { + ddl += " NOT NULL"; + } +} + + +template<> void +GncDbiProviderImpl::append_col_def (std::string& ddl, + const GncSqlColumnInfo& info) +{ + const char* type_name = nullptr; + + if (info.m_type == BCT_INT) + { + if (info.m_autoinc) + { + type_name = "serial"; + } + else + { + type_name = "integer"; + } + } + else if (info.m_type == BCT_INT64) + { + type_name = "int8"; + } + else if (info.m_type == BCT_DOUBLE) + + { + type_name = "double precision"; + } + else if (info.m_type == BCT_STRING) + { + type_name = "varchar"; + } + else if (info.m_type == BCT_DATE) + { + type_name = "date"; + } + else if (info.m_type == BCT_DATETIME) + { + type_name = "timestamp without time zone"; + } + else + { + PERR ("Unknown column type: %d\n", info.m_type); + type_name = ""; + } + ddl += info.m_name + " " + type_name; + if (info.m_size != 0 && info.m_type == BCT_STRING) + { + ddl += "(" + std::to_string(info.m_size) + ")"; + } + if (info.m_primary_key) + { + ddl += " PRIMARY KEY"; + } + if (info.m_not_null) + { + ddl += " NOT NULL"; + } +} + +static StrVec +conn_get_table_list (dbi_conn conn, const std::string& dbname) +{ + StrVec retval; + auto tables = dbi_conn_get_table_list (conn, dbname.c_str(), nullptr); + while (dbi_result_next_row (tables) != 0) + { + std::string table_name {dbi_result_get_string_idx (tables, 1)}; + retval.push_back(table_name); + } + dbi_result_free (tables); + return retval; +} + +template<> StrVec +GncDbiProviderImpl::get_table_list (dbi_conn conn, + const std::string& dbname) +{ + /* Return the list, but remove the tables that sqlite3 adds for + * its own use. */ + auto list = conn_get_table_list (conn, dbname); + auto end = std::remove(list.begin(), list.end(), "sqlite_sequence"); + list.erase(end, list.end()); + return list; +} + +template<> StrVec +GncDbiProviderImpl::get_table_list (dbi_conn conn, + const std::string& dbname) +{ + return conn_get_table_list (conn, dbname); +} + +template<> StrVec +GncDbiProviderImpl::get_table_list (dbi_conn conn, + const std::string& dbname) +{ + auto list = conn_get_table_list (conn, dbname); + auto end = std::remove_if (list.begin(), list.end(), + [](std::string& table_name){ + return table_name == "sql_features" || + table_name == "sql_implementation_info" || + table_name == "sql_languages" || + table_name == "sql_packages" || + table_name == "sql_parts" || + table_name == "sql_sizing" || + table_name == "sql_sizing_profiles"; + }); + list.erase(end, list.end()); + return list; +} + +#endif //__GNC_DBISQLPROVIDERIMPL_HPP__ diff --git a/src/backend/dbi/gnc-dbisqlconnection.cpp b/src/backend/dbi/gnc-dbisqlconnection.cpp index fabc26fbfe..aa53d6a349 100644 --- a/src/backend/dbi/gnc-dbisqlconnection.cpp +++ b/src/backend/dbi/gnc-dbisqlconnection.cpp @@ -28,13 +28,12 @@ extern "C" #include #include } -#include "gnc-backend-dbi.hpp" +#include "gnc-dbisqlconnection.hpp" static QofLogModule log_module = G_LOG_DOMAIN; static const unsigned int DBI_MAX_CONN_ATTEMPTS = 5; - /* --------------------------------------------------------- */ class GncDbiSqlStatement : public GncSqlStatement { diff --git a/src/backend/dbi/gnc-dbisqlconnection.hpp b/src/backend/dbi/gnc-dbisqlconnection.hpp index 64aedb0d60..547ae2b295 100644 --- a/src/backend/dbi/gnc-dbisqlconnection.hpp +++ b/src/backend/dbi/gnc-dbisqlconnection.hpp @@ -22,8 +22,95 @@ \********************************************************************/ #ifndef _GNC_DBISQLCONNECTION_HPP_ #define _GNC_DBISQLCONNECTION_HPP_ + +#include "gnc-backend-dbi.hpp" +#include "gnc-dbisqlresult.hpp" +#include "gnc-dbiprovider.hpp" + +class GncDbiProvider; + /** * Encapsulate a libdbi dbi_conn connection. */ +class GncDbiSqlConnection : public GncSqlConnection +{ +public: + GncDbiSqlConnection (GncDbiProvider* provider, QofBackend* qbe, + dbi_conn conn, const char* lock_table) : + m_qbe{qbe}, m_conn{conn}, m_provider{provider}, m_conn_ok{true}, + m_last_error{ERR_BACKEND_NO_ERR}, m_error_repeat{0}, m_retry{false}, + m_lock_table{lock_table} {} + ~GncDbiSqlConnection() override; + GncSqlResultPtr execute_select_statement (const GncSqlStatementPtr&) + noexcept override; + int execute_nonselect_statement (const GncSqlStatementPtr&) + noexcept override; + GncSqlStatementPtr create_statement_from_sql (const std::string&) + const noexcept override; + bool does_table_exist (const std::string&) const noexcept override; + bool begin_transaction () noexcept override; + bool rollback_transaction () const noexcept override; + bool commit_transaction () const noexcept override; + bool create_table (const std::string&, const ColVec&) const noexcept override; + bool create_index (const std::string&, const std::string&, const EntryVec&) + const noexcept override; + bool add_columns_to_table (const std::string&, const ColVec&) + const noexcept override; + std::string quote_string (const std::string&) const noexcept override; + int dberror() const noexcept override { + return dbi_conn_error(m_conn, nullptr); } + QofBackend* qbe () const noexcept { return m_qbe; } + dbi_conn conn() const noexcept { return m_conn; } + GncDbiProvider* provider() { return m_provider; } + inline void set_error(int error, int repeat, bool retry) noexcept override + { + m_last_error = error; + m_error_repeat = repeat; + m_retry = retry; + } + inline void init_error() noexcept + { + set_error(ERR_BACKEND_NO_ERR, 0, false); + } + /** Check if the dbi connection is valid. If not attempt to re-establish it + * Returns TRUE is there is a valid connection in the end or FALSE otherwise + */ + bool verify() noexcept override; + bool retry_connection(const char* msg) noexcept override; + dbi_result table_manage_backup(const std::string& table_name, TableOpType op); + /* FIXME: These three friend functions should really be members, but doing + * that is too invasive just yet. */ + bool table_operation (const StrVec& table_name_list, + TableOpType op) noexcept; + std::string add_columns_ddl(const std::string& table_name, + const ColVec& info_vec) const noexcept; + friend void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book); + +private: + QofBackend* m_qbe; + dbi_conn m_conn; + GncDbiProvider* m_provider; + /** Used by the error handler routines to flag if the connection is ok to + * use + */ + bool m_conn_ok; + /** Code of the last error that occurred. This is set in the error callback + * function. + */ + int m_last_error; + /** Used in case of transient errors. After such error, another attempt at + * the original call is allowed. error_repeat tracks the number of attempts + * and can be used to prevent infinite loops. + */ + int m_error_repeat; + /** Signals the calling function that it should retry (the error handler + * detected transient error and managed to resolve it, but it can't run the + * original query) + */ + gboolean m_retry; + const char* m_lock_table; + void unlock_database(); + +}; #endif //_GNC_DBISQLCONNECTION_HPP_ diff --git a/src/backend/dbi/gnc-dbisqlresult.cpp b/src/backend/dbi/gnc-dbisqlresult.cpp new file mode 100644 index 0000000000..07e7fa3535 --- /dev/null +++ b/src/backend/dbi/gnc-dbisqlresult.cpp @@ -0,0 +1,187 @@ +/******************************************************************** + * gnc-dbisqlresult.cpp: Encapsulate libdbi dbi_result * + * * + * Copyright 2016 John Ralls * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * + * Boston, MA 02110-1301, USA gnu@gnu.org * +\********************************************************************/ + +#include +extern "C" +{ +#include +#include +#include +/* For direct access to dbi data structs, sadly needed for datetime */ +#include +} +#include +#include +#include "gnc-dbisqlresult.hpp" +#include "gnc-dbisqlconnection.hpp" + +static QofLogModule log_module = G_LOG_DOMAIN; + +GncDbiSqlResult::~GncDbiSqlResult() +{ + int status = dbi_result_free (m_dbi_result); + + if (status == 0) + return; + + PERR ("Error %d in dbi_result_free() result.", m_conn->dberror() ); + qof_backend_set_error (m_conn->qbe(), ERR_BACKEND_SERVER_ERR); +} + +int +GncDbiSqlResult::dberror() const noexcept +{ + return m_conn->dberror(); +} + +GncSqlRow& +GncDbiSqlResult::begin() +{ + + if (m_dbi_result == nullptr || + dbi_result_get_numrows(m_dbi_result) == 0) + return m_sentinel; + int status = dbi_result_first_row(m_dbi_result); + if (status) + return m_row; + int error = dberror(); // + + if (error != DBI_ERROR_BADIDX) //otherwise just an empty result set + { + PERR ("Error %d in dbi_result_first_row()", dberror()); + qof_backend_set_error (m_conn->qbe(), ERR_BACKEND_SERVER_ERR); + } + return m_sentinel; +} + +uint64_t +GncDbiSqlResult::size() const noexcept +{ + return dbi_result_get_numrows(m_dbi_result); +} +/* --------------------------------------------------------- */ + +GncSqlRow& +GncDbiSqlResult::IteratorImpl::operator++() +{ + int status = dbi_result_next_row (m_inst->m_dbi_result); + if (status) + return m_inst->m_row; + int error = m_inst->dberror(); + if (error == DBI_ERROR_BADIDX || error == 0) //ran off the end of the results + return m_inst->m_sentinel; + PERR("Error %d incrementing results iterator.", error); + qof_backend_set_error (m_inst->m_conn->qbe(), ERR_BACKEND_SERVER_ERR); + return m_inst->m_sentinel; +} + +int64_t +GncDbiSqlResult::IteratorImpl::get_int_at_col(const char* col) const +{ + auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); + if(type != DBI_TYPE_INTEGER) + throw (std::invalid_argument{"Requested integer from non-integer column."}); + return dbi_result_get_longlong (m_inst->m_dbi_result, col); +} + +float +GncDbiSqlResult::IteratorImpl::get_float_at_col(const char* col) const +{ + auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); + auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); + if(type != DBI_TYPE_DECIMAL || + (attrs & DBI_DECIMAL_SIZEMASK) != DBI_DECIMAL_SIZE4) + throw (std::invalid_argument{"Requested float from non-float column."}); + gnc_push_locale (LC_NUMERIC, "C"); + auto retval = dbi_result_get_float(m_inst->m_dbi_result, col); + gnc_pop_locale (LC_NUMERIC); + return retval; +} + +double +GncDbiSqlResult::IteratorImpl::get_double_at_col(const char* col) const +{ + auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); + auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); + if(type != DBI_TYPE_DECIMAL || + (attrs & DBI_DECIMAL_SIZEMASK) != DBI_DECIMAL_SIZE8) + throw (std::invalid_argument{"Requested double from non-double column."}); + gnc_push_locale (LC_NUMERIC, "C"); + auto retval = dbi_result_get_double(m_inst->m_dbi_result, col); + gnc_pop_locale (LC_NUMERIC); + return retval; +} + +std::string +GncDbiSqlResult::IteratorImpl::get_string_at_col(const char* col) const +{ + auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); + auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); + if(type != DBI_TYPE_STRING) + throw (std::invalid_argument{"Requested string from non-string column."}); + gnc_push_locale (LC_NUMERIC, "C"); + auto strval = dbi_result_get_string(m_inst->m_dbi_result, col); + if (strval == nullptr) + { + gnc_pop_locale (LC_NUMERIC); + throw (std::invalid_argument{"Column empty."}); + } + auto retval = std::string{strval}; + gnc_pop_locale (LC_NUMERIC); + return retval; +} +time64 +GncDbiSqlResult::IteratorImpl::get_time64_at_col (const char* col) const +{ + auto type = dbi_result_get_field_type (m_inst->m_dbi_result, col); + auto attrs = dbi_result_get_field_attribs (m_inst->m_dbi_result, col); + if (type != DBI_TYPE_DATETIME) + throw (std::invalid_argument{"Requested double from non-double column."}); + gnc_push_locale (LC_NUMERIC, "C"); +#if HAVE_LIBDBI_TO_LONGLONG + /* A less evil hack than the one equrie by libdbi-0.8, but + * still necessary to work around the same bug. + */ + auto retval = dbi_result_get_as_longlong(dbi_row->result, + col_name); +#else + /* A seriously evil hack to work around libdbi bug #15 + * https://sourceforge.net/p/libdbi/bugs/15/. When libdbi + * v0.9 is widely available this can be replaced with + * dbi_result_get_as_longlong. + * Note: 0.9 is available in Debian Jessie and Fedora 21. + */ + auto result = (dbi_result_t*) (m_inst->m_dbi_result); + auto row = dbi_result_get_currow (result); + auto idx = dbi_result_get_field_idx (result, col) - 1; + time64 retval = result->rows[row]->field_values[idx].d_datetime; + if (retval < MINTIME || retval > MAXTIME) + retval = 0; +#endif //HAVE_LIBDBI_TO_LONGLONG + gnc_pop_locale (LC_NUMERIC); + return retval; +} + + +/* --------------------------------------------------------- */ + diff --git a/src/backend/dbi/gnc-dbisqlresult.hpp b/src/backend/dbi/gnc-dbisqlresult.hpp new file mode 100644 index 0000000000..acb658d9e0 --- /dev/null +++ b/src/backend/dbi/gnc-dbisqlresult.hpp @@ -0,0 +1,77 @@ +/******************************************************************** + * gnc-dbisqlresult.hpp: Iterable wrapper for dbi_result. * + * * + * Copyright 2016 John Ralls m_dbi_result, col); + } + private: + GncDbiSqlResult* m_inst; + }; + +private: + const GncDbiSqlConnection* m_conn; + dbi_result m_dbi_result; + IteratorImpl m_iter; + GncSqlRow m_row; + GncSqlRow m_sentinel; + +}; + +#endif //__GNC_DBISQLRESULT_HPP__ diff --git a/src/backend/dbi/test/CMakeLists.txt b/src/backend/dbi/test/CMakeLists.txt index cc09d9dfe0..68c859cc84 100644 --- a/src/backend/dbi/test/CMakeLists.txt +++ b/src/backend/dbi/test/CMakeLists.txt @@ -20,6 +20,7 @@ SET(test_dbi_backend_SOURCES test-dbi-stuff.cpp ../gnc-backend-dbi.cpp ../gnc-dbisqlconnection.cpp + ../gnc-dbisqlresult.cpp ) # This test does not work on Win32 diff --git a/src/backend/dbi/test/Makefile.am b/src/backend/dbi/test/Makefile.am index cd6a5f44a4..50c5fb5113 100644 --- a/src/backend/dbi/test/Makefile.am +++ b/src/backend/dbi/test/Makefile.am @@ -62,7 +62,8 @@ test_backend_dbi_SOURCES = \ test-dbi-stuff.cpp \ test-dbi-business-stuff.cpp \ ../gnc-dbisqlconnection.cpp \ - ../gnc-backend-dbi.cpp + ../gnc-backend-dbi.cpp \ + ../gnc-dbisqlresult.cpp test_backend_dbi_CPPFLAGS = \ -DDBI_TEST_XML_FILENAME=\"${srcdir}/test-dbi.xml\" \ diff --git a/src/backend/sql/gnc-address-sql.cpp b/src/backend/sql/gnc-address-sql.cpp index c60ff7223d..cd6dd70220 100644 --- a/src/backend/sql/gnc-address-sql.cpp +++ b/src/backend/sql/gnc-address-sql.cpp @@ -84,7 +84,7 @@ GncSqlColumnTableEntryImpl::load (const GncSqlBackend* be, g_return_if_fail (be != NULL); g_return_if_fail (pObject != NULL); - auto addr = gncAddressCreate (be->book, QOF_INSTANCE(pObject)); + auto addr = gncAddressCreate (be->book(), QOF_INSTANCE(pObject)); for (auto const& subtable_row : col_table) { diff --git a/src/backend/sql/gnc-transaction-sql.cpp b/src/backend/sql/gnc-transaction-sql.cpp index 250c94daf0..1ac88aec0b 100644 --- a/src/backend/sql/gnc-transaction-sql.cpp +++ b/src/backend/sql/gnc-transaction-sql.cpp @@ -971,7 +971,7 @@ convert_query_term_to_sql (const GncSqlBackend* be, const gchar* fieldName, query_date_t date_data = (query_date_t)pPredData; auto datebuf = be->time64_to_string (date_data->date.tv_sec); - g_string_append_printf (sql, "'%s'", datebuf); + g_string_append_printf (sql, "'%s'", datebuf.c_str()); } else if (strcmp (pPredData->type_name, QOF_TYPE_INT32) == 0)