diff --git a/src/backend/dbi/gnc-backend-dbi.cpp b/src/backend/dbi/gnc-backend-dbi.cpp index 1d1ee0487d5..1e2ad4d2395 100644 --- a/src/backend/dbi/gnc-backend-dbi.cpp +++ b/src/backend/dbi/gnc-backend-dbi.cpp @@ -101,8 +101,6 @@ 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"; - #define FILE_URI_TYPE "file" #define FILE_URI_PREFIX (FILE_URI_TYPE "://") #define SQLITE3_URI_TYPE "sqlite3" @@ -114,7 +112,6 @@ constexpr const char* MYSQL_TIMESPEC_STR_FORMAT = "%04d%02d%02d%02d%02d%02d"; constexpr const char* PGSQL_TIMESPEC_STR_FORMAT = "%04d%02d%02d %02d%02d%02d"; static void adjust_sql_options (dbi_conn connection); -static gboolean gnc_dbi_lock_database (QofBackend*, dbi_conn, gboolean); static bool save_may_clobber_data (dbi_conn conn); static void init_sql_backend (GncDbiBackend* dbi_be); @@ -480,19 +477,18 @@ gnc_dbi_session_begin(QofBackend* qbe, QofSession* session, g_unlink (filepath.c_str()); } LEAVE("Bad DBI Library"); - return; - } - if (!gnc_dbi_lock_database (qbe, conn, ignore_lock)) - { - qof_backend_set_error (qbe, ERR_BACKEND_LOCKED); - LEAVE("Locked"); - return; } be->connect(nullptr); - be->connect( - new GncDbiSqlConnection (new GncDbiProviderImpl, - qbe, conn, lock_table)); + try + { + be->connect(new GncDbiSqlConnection(make_dbi_provider(), + qbe, conn, ignore_lock)); + } + catch (std::runtime_error& err) + { + return; + } /* We should now have a proper session set up. * Let's start logging */ @@ -555,136 +551,6 @@ mysql_error_fn (dbi_conn conn, void* user_data) } } - -/* FIXME: Move to GncDbiSqlConnection. */ -static gboolean -gnc_dbi_lock_database (QofBackend* qbe, dbi_conn conn, gboolean ignore_lock) -{ - - GncDbiBackend* qe = (GncDbiBackend*)qbe; - - dbi_result result; - const gchar* dbname = dbi_conn_get_option (conn, "dbname"); - /* Create the table if it doesn't exist */ - result = dbi_conn_get_table_list (conn, dbname, lock_table); - if (! (result && dbi_result_get_numrows (result))) - { - if (result) - { - dbi_result_free (result); - result = nullptr; - } - result = dbi_conn_queryf (conn, - "CREATE TABLE %s ( Hostname varchar(%d), PID int )", lock_table, - GNC_HOST_NAME_MAX); - if (dbi_conn_error (conn, nullptr)) - { - const gchar* errstr; - dbi_conn_error (conn, &errstr); - PERR ("Error %s creating lock table", errstr); - qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); - if (result) - { - dbi_result_free (result); - result = nullptr; - } - return FALSE; - } - if (result) - { - dbi_result_free (result); - result = nullptr; - } - } - if (result) - { - dbi_result_free (result); - result = nullptr; - } - - /* Protect everything with a single transaction to prevent races */ - if ((result = dbi_conn_query (conn, "BEGIN"))) - { - /* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */ - gchar hostname[ GNC_HOST_NAME_MAX + 1 ]; - if (result) - { - dbi_result_free (result); - result = nullptr; - } - result = dbi_conn_queryf (conn, "SELECT * FROM %s", lock_table); - if (result && dbi_result_get_numrows (result)) - { - dbi_result_free (result); - result = nullptr; - if (!ignore_lock) - { - qof_backend_set_error (qbe, ERR_BACKEND_LOCKED); - /* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */ - dbi_conn_query (conn, "ROLLBACK"); - return FALSE; - } - result = dbi_conn_queryf (conn, "DELETE FROM %s", lock_table); - if (!result) - { - qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); - qof_backend_set_message (qbe, "Failed to delete lock record"); - result = dbi_conn_query (conn, "ROLLBACK"); - if (result) - { - dbi_result_free (result); - result = nullptr; - } - return FALSE; - } - if (result) - { - dbi_result_free (result); - result = nullptr; - } - } - /* Add an entry and commit the transaction */ - memset (hostname, 0, sizeof (hostname)); - gethostname (hostname, GNC_HOST_NAME_MAX); - result = dbi_conn_queryf (conn, - "INSERT INTO %s VALUES ('%s', '%d')", - lock_table, hostname, (int)GETPID ()); - if (!result) - { - qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); - qof_backend_set_message (qbe, "Failed to create lock record"); - result = dbi_conn_query (conn, "ROLLBACK"); - if (result) - { - dbi_result_free (result); - result = nullptr; - } - return FALSE; - } - if (result) - { - dbi_result_free (result); - result = nullptr; - } - result = dbi_conn_query (conn, "COMMIT"); - if (result) - { - dbi_result_free (result); - result = nullptr; - } - return TRUE; - } - /* Couldn't get a transaction (probably couldn't get a lock), so fail */ - qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); - qof_backend_set_message (qbe, "SQL Backend failed to obtain a transaction"); - if (result) - { - dbi_result_free (result); - result = nullptr; - } - return FALSE; -} - #define SQL_OPTION_TO_REMOVE "NO_ZERO_DATE" /* Given an sql_options string returns a copy of the string adjusted as @@ -796,8 +662,6 @@ gnc_dbi_session_begin (QofBackend* qbe, QofSession* session, return; } - if (!gnc_dbi_lock_database (qbe, conn, ignore_lock)) - return; } else { @@ -832,8 +696,6 @@ gnc_dbi_session_begin (QofBackend* qbe, QofSession* session, dbi_conn_queryf (conn, "DROP DATABASE %s", uri.dbname()); return; } - if (!gnc_dbi_lock_database (qbe, conn, ignore_lock)) - return; } else { @@ -843,10 +705,15 @@ gnc_dbi_session_begin (QofBackend* qbe, QofSession* session, } be->connect(nullptr); - be->connect( - new GncDbiSqlConnection (new GncDbiProviderImpl, - qbe, conn, lock_table)); - + try + { + be->connect(new GncDbiSqlConnection(make_dbi_provider(), + qbe, conn, ignore_lock)); + } + catch (std::runtime_error& err) + { + return; + } /* We should now have a proper session set up. * Let's start logging */ auto translog_path = gnc_build_translog_path (uri.basename().c_str()); @@ -956,8 +823,6 @@ gnc_dbi_session_begin (QofBackend* qbe, QofSession* session, return; } - if (!gnc_dbi_lock_database (qbe, conn, ignore_lock)) - return; } else { @@ -993,8 +858,6 @@ gnc_dbi_session_begin (QofBackend* qbe, QofSession* session, LEAVE("Error"); return; } - if (!gnc_dbi_lock_database (qbe, conn, ignore_lock)) - return; } else { @@ -1003,9 +866,15 @@ gnc_dbi_session_begin (QofBackend* qbe, QofSession* session, } } be->connect(nullptr); - be->connect( - new GncDbiSqlConnection (new GncDbiProviderImpl, - qbe, conn, lock_table)); + try + { + be->connect(new GncDbiSqlConnection(make_dbi_provider(), + qbe, conn, ignore_lock)); + } + catch (std::runtime_error& err) + { + return; + } /* We should now have a proper session set up. * Let's start logging */ diff --git a/src/backend/dbi/gnc-dbiprovider.hpp b/src/backend/dbi/gnc-dbiprovider.hpp index 453b911a67d..e0a25616bb5 100644 --- a/src/backend/dbi/gnc-dbiprovider.hpp +++ b/src/backend/dbi/gnc-dbiprovider.hpp @@ -49,4 +49,6 @@ class GncDbiProvider virtual void drop_index(dbi_conn conn, const std::string& index) = 0; }; +using GncDbiProviderPtr = std::unique_ptr; + #endif //__GNC_DBIPROVIDER_HPP__ diff --git a/src/backend/dbi/gnc-dbiproviderimpl.hpp b/src/backend/dbi/gnc-dbiproviderimpl.hpp index f991be649b9..a96461b91bb 100644 --- a/src/backend/dbi/gnc-dbiproviderimpl.hpp +++ b/src/backend/dbi/gnc-dbiproviderimpl.hpp @@ -30,7 +30,6 @@ extern "C" #include "gnc-backend-dbi.hpp" #include "gnc-dbiprovider.hpp" - template class GncDbiProviderImpl : public GncDbiProvider { @@ -41,6 +40,12 @@ class GncDbiProviderImpl : public GncDbiProvider void drop_index(dbi_conn conn, const std::string& index); }; +template GncDbiProviderPtr +make_dbi_provider() +{ + return GncDbiProviderPtr(new GncDbiProviderImpl); +} + template<> void GncDbiProviderImpl::append_col_def(std::string& ddl, const GncSqlColumnInfo& info) diff --git a/src/backend/dbi/gnc-dbisqlconnection.cpp b/src/backend/dbi/gnc-dbisqlconnection.cpp index 454e72ceb04..98b9617fda4 100644 --- a/src/backend/dbi/gnc-dbisqlconnection.cpp +++ b/src/backend/dbi/gnc-dbisqlconnection.cpp @@ -33,6 +33,7 @@ extern "C" static QofLogModule log_module = G_LOG_DOMAIN; static const unsigned int DBI_MAX_CONN_ATTEMPTS = 5; +constexpr const char *lock_table_name = "gnclock"; /* --------------------------------------------------------- */ class GncDbiSqlStatement : public GncSqlStatement @@ -70,6 +71,108 @@ GncDbiSqlStatement::add_where_cond(QofIdTypeConst type_name, } } +bool +GncDbiSqlConnection::lock_database (bool ignore_lock) +{ + + auto dbname = dbi_conn_get_option (m_conn, "dbname"); + /* Create the table if it doesn't exist */ + auto result = dbi_conn_get_table_list (m_conn, dbname, lock_table_name); + int numrows = 0; + const char* errstr; + if (result) { + numrows = dbi_result_get_numrows (result); + dbi_result_free (result); + result = nullptr; + } + if (numrows == 0) + { + result = dbi_conn_queryf (m_conn, + "CREATE TABLE %s ( Hostname varchar(%d), PID int )", lock_table_name, + GNC_HOST_NAME_MAX); + if (result) + { + dbi_result_free (result); + result = nullptr; + } + if (dbi_conn_error (m_conn, &errstr)) + { + PERR ("Error %s creating lock table", errstr); + qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); + return false; + } + } + + /* Protect everything with a single transaction to prevent races */ + result = dbi_conn_query (m_conn, "BEGIN"); + if (dbi_conn_error (m_conn, &errstr)) + { + /* Couldn't get a transaction (probably couldn't get a lock), so fail */ + std::string err{"SQL Backend failed to obtain a transaction: "}; + err += errstr; + qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); + qof_backend_set_message (m_qbe, err.c_str()); + return false; + } + dbi_result_free(result); + result = nullptr; + /* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */ + char hostname[ GNC_HOST_NAME_MAX + 1 ]; + result = dbi_conn_queryf (m_conn, "SELECT * FROM %s", lock_table_name); + if (result && dbi_result_get_numrows (result)) + { + dbi_result_free (result); + result = nullptr; + if (!ignore_lock) + { + qof_backend_set_error (m_qbe, ERR_BACKEND_LOCKED); + /* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */ + dbi_conn_query (m_conn, "ROLLBACK"); + return false; + } + result = dbi_conn_queryf (m_conn, "DELETE FROM %s", lock_table_name); + if (!result) + { + qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); + qof_backend_set_message (m_qbe, "Failed to delete lock record"); + result = dbi_conn_query (m_conn, "ROLLBACK"); + if (result) + dbi_result_free (result); + return false; + } + dbi_result_free (result); + result = nullptr; + } + /* Add an entry and commit the transaction */ + memset (hostname, 0, sizeof (hostname)); + gethostname (hostname, GNC_HOST_NAME_MAX); + result = dbi_conn_queryf (m_conn, + "INSERT INTO %s VALUES ('%s', '%d')", + lock_table_name, hostname, (int)GETPID ()); + if (!result) + { + qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR); + qof_backend_set_message (m_qbe, "Failed to create lock record"); + result = dbi_conn_query (m_conn, "ROLLBACK"); + if (result) + dbi_result_free (result); + return false; + } + dbi_result_free (result); + result = dbi_conn_query (m_conn, "COMMIT"); + if (!result) + { + auto errnum = dbi_conn_error(m_conn, &errstr); + qof_backend_set_error(m_qbe, ERR_BACKEND_SERVER_ERR); + std::string err{"Failed to commit transaction: "}; + err += errstr; + qof_backend_set_message(m_qbe, err.c_str()); + return false; + } + dbi_result_free (result); + return true; +} + void GncDbiSqlConnection::unlock_database () { @@ -81,7 +184,7 @@ GncDbiSqlConnection::unlock_database () auto dbname = dbi_conn_get_option (m_conn, "dbname"); /* Check if the lock table exists */ g_return_if_fail (dbname != nullptr); - auto result = dbi_conn_get_table_list (m_conn, dbname, m_lock_table); + auto result = dbi_conn_get_table_list (m_conn, dbname, lock_table_name); if (! (result && dbi_result_get_numrows (result))) { if (result) @@ -105,7 +208,7 @@ GncDbiSqlConnection::unlock_database () memset (hostname, 0, sizeof (hostname)); gethostname (hostname, GNC_HOST_NAME_MAX); result = dbi_conn_queryf (m_conn, - "SELECT * FROM %s WHERE Hostname = '%s' AND PID = '%d'", m_lock_table, hostname, + "SELECT * FROM %s WHERE Hostname = '%s' AND PID = '%d'", lock_table_name, hostname, (int)GETPID ()); if (result && dbi_result_get_numrows (result)) { @@ -114,7 +217,7 @@ GncDbiSqlConnection::unlock_database () dbi_result_free (result); result = nullptr; } - result = dbi_conn_queryf (m_conn, "DELETE FROM %s", m_lock_table); + result = dbi_conn_queryf (m_conn, "DELETE FROM %s", lock_table_name); if (!result) { PERR ("Failed to delete the lock entry"); @@ -166,7 +269,6 @@ GncDbiSqlConnection::~GncDbiSqlConnection() dbi_conn_close(m_conn); m_conn = nullptr; } - delete m_provider; } GncSqlResultPtr @@ -542,7 +644,7 @@ GncDbiSqlConnection::table_operation(const StrVec& table_names, TableOpType op) noexcept { const char* dbname = dbi_conn_get_option (m_conn, "dbname"); - std::string lock_table{m_lock_table}; + std::string lock_table{lock_table_name}; g_return_val_if_fail (!table_names.empty(), FALSE); bool retval{true}; for (auto table : table_names) diff --git a/src/backend/dbi/gnc-dbisqlconnection.hpp b/src/backend/dbi/gnc-dbisqlconnection.hpp index 87e9b34caba..2118fe2fca9 100644 --- a/src/backend/dbi/gnc-dbisqlconnection.hpp +++ b/src/backend/dbi/gnc-dbisqlconnection.hpp @@ -35,11 +35,14 @@ class GncDbiProvider; 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 (std::unique_ptr provider, + QofBackend* qbe, dbi_conn conn, bool ignore_lock) : + m_qbe{qbe}, m_conn{conn}, m_provider{std::move(provider)}, + m_conn_ok{true}, m_last_error{ERR_BACKEND_NO_ERR}, m_error_repeat{0}, + m_retry{false} { + if (!lock_database(ignore_lock)) + throw std::runtime_error("Failed to lock database!"); + } ~GncDbiSqlConnection() override; GncSqlResultPtr execute_select_statement (const GncSqlStatementPtr&) noexcept override; @@ -61,7 +64,6 @@ class GncDbiSqlConnection : public GncSqlConnection 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; @@ -87,7 +89,7 @@ class GncDbiSqlConnection : public GncSqlConnection private: QofBackend* m_qbe; dbi_conn m_conn; - GncDbiProvider* m_provider; + std::unique_ptr m_provider; /** Used by the error handler routines to flag if the connection is ok to * use */ @@ -106,7 +108,7 @@ class GncDbiSqlConnection : public GncSqlConnection * original query) */ gboolean m_retry; - const char* m_lock_table; + bool lock_database(bool ignore_lock); void unlock_database(); };