mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Separate DBI classes into their own files.
This commit is contained in:
parent
c2082bea99
commit
ccc1cc49ab
@ -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
|
||||
|
@ -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 = \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
55
src/backend/dbi/gnc-dbiprovider.hpp
Normal file
55
src/backend/dbi/gnc-dbiprovider.hpp
Normal 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__
|
293
src/backend/dbi/gnc-dbiproviderimpl.hpp
Normal file
293
src/backend/dbi/gnc-dbiproviderimpl.hpp
Normal 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__
|
@ -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
|
||||
{
|
||||
|
@ -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_
|
||||
|
187
src/backend/dbi/gnc-dbisqlresult.cpp
Normal file
187
src/backend/dbi/gnc-dbisqlresult.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------- */
|
||||
|
77
src/backend/dbi/gnc-dbisqlresult.hpp
Normal file
77
src/backend/dbi/gnc-dbisqlresult.hpp
Normal 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__
|
@ -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
|
||||
|
@ -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\" \
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user