Separate DBI classes into their own files.

This commit is contained in:
John Ralls 2016-07-25 11:29:30 -07:00
parent c2082bea99
commit ccc1cc49ab
14 changed files with 739 additions and 561 deletions

View File

@ -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

View File

@ -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 = \

View File

@ -68,18 +68,17 @@ extern "C"
#include "splint-defs.h"
#endif
/* For direct access to dbi data structs, sadly needed for datetime */
#include <dbi/dbi-dev.h>
}
#include <boost/regex.hpp>
#include <string>
#include <gnc-datetime.hpp>
#include <gnc-backend-prov.hpp>
#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 <DbType T>
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 <DbType T>
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<DbType::DBI_SQLITE>::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 <DbType P> std::string
GncDbiProviderImpl<P>::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<DbType::DBI_MYSQL>::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<DbType::DBI_PGSQL>::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<DbType::DBI_SQLITE>::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<DbType::DBI_MYSQL>::get_table_list (dbi_conn conn,
const std::string& dbname)
{
return conn_get_table_list (conn, dbname);
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_PGSQL>::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

View File

@ -34,7 +34,8 @@ extern "C"
#define GETPID() getpid()
#endif
}
#include <gnc-backend-sql.h>
#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<std::string> 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<std::string> 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

View File

@ -0,0 +1,55 @@
/********************************************************************
* gnc-dbiprovider.cpp: Encapsulate differences among Dbi backends. *
* *
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
* *
* 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 <dbi/dbi.h>
}
#include <string>
#include <vector>
/**
* Provides the primary abstraction for different DBI backends.
*/
class GncSqlConnection;
struct GncSqlColumnInfo;
using ColVec=std::vector<GncSqlColumnInfo>;
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__

View File

@ -0,0 +1,293 @@
/************************************************************************
* gnc-dbiproviderimpl.hpp: Encapsulate differences among Dbi backends. *
* *
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
* *
* 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 <guid.hpp>
extern "C"
{
#include <config.h>
}
#include "gnc-backend-dbi.hpp"
#include "gnc-dbiprovider.hpp"
enum class DbType
{
DBI_SQLITE,
DBI_MYSQL,
DBI_PGSQL
};
template <DbType T>
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<DbType::DBI_SQLITE>::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 <DbType P> std::string
GncDbiProviderImpl<P>::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<DbType::DBI_MYSQL>::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<DbType::DBI_PGSQL>::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<DbType::DBI_SQLITE>::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<DbType::DBI_MYSQL>::get_table_list (dbi_conn conn,
const std::string& dbname)
{
return conn_get_table_list (conn, dbname);
}
template<> StrVec
GncDbiProviderImpl<DbType::DBI_PGSQL>::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__

View File

@ -28,13 +28,12 @@ extern "C"
#include <platform.h>
#include <gnc-locale-utils.h>
}
#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
{

View File

@ -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_

View File

@ -0,0 +1,187 @@
/********************************************************************
* gnc-dbisqlresult.cpp: Encapsulate libdbi dbi_result *
* *
* Copyright 2016 John Ralls <jralls@ceridwen.us> *
* *
* 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 <guid.hpp>
extern "C"
{
#include <config.h>
#include <gnc-locale-utils.h>
#include <dbi/dbi.h>
/* For direct access to dbi data structs, sadly needed for datetime */
#include <dbi/dbi-dev.h>
}
#include <gnc-datetime.hpp>
#include <gnc-backend-sql.h>
#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;
}
/* --------------------------------------------------------- */

View File

@ -0,0 +1,77 @@
/********************************************************************
* gnc-dbisqlresult.hpp: Iterable wrapper for dbi_result. *
* *
* Copyright 2016 John Ralls <jralls@ceridwen.us *
* *
* 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 *
\********************************************************************/
/* Private structures and variables for gnc-backend-dbi.c and its unit tests */
#ifndef __GNC_DBISQLBACKEND_HPP__
#define __GNC_DBISQLBACKEND_HPP__
#include "gnc-backend-dbi.h"
class GncDbiSqlConnection;
/**
* 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() const noexcept;
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_DBISQLRESULT_HPP__

View File

@ -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

View File

@ -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\" \

View File

@ -84,7 +84,7 @@ GncSqlColumnTableEntryImpl<CT_ADDRESS>::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)
{

View File

@ -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)