LCOV - code coverage report
Current view: top level - libgnucash/backend/dbi - gnc-backend-dbi.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 8.0 % 603 48
Test Date: 2026-11-02 18:19:43 Functions: 8.3 % 60 5
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /********************************************************************
       2              :  * gnc-backend-dbi.c: load and save data to SQL via libdbi          *
       3              :  *                                                                  *
       4              :  * This program is free software; you can redistribute it and/or    *
       5              :  * modify it under the terms of the GNU General Public License as   *
       6              :  * published by the Free Software Foundation; either version 2 of   *
       7              :  * the License, or (at your option) any later version.              *
       8              :  *                                                                  *
       9              :  * This program is distributed in the hope that it will be useful,  *
      10              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
      11              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
      12              :  * GNU General Public License for more details.                     *
      13              :  *                                                                  *
      14              :  * You should have received a copy of the GNU General Public License*
      15              :  * along with this program; if not, contact:                        *
      16              :  *                                                                  *
      17              :  * Free Software Foundation           Voice:  +1-617-542-5942       *
      18              :  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
      19              :  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
      20              : \********************************************************************/
      21              : /** @file gnc-backend-dbi.c
      22              :  *  @brief load and save data to SQL
      23              :  *  @author Copyright (c) 2006-2008 Phil Longstaff <plongstaff@rogers.com>
      24              :  *
      25              :  * This file implements the top-level QofBackend API for saving/
      26              :  * restoring data to/from an SQL db using libdbi
      27              :  */
      28              : #include <glib.h>
      29              : #include <glib/gstdio.h>
      30              : 
      31              : #include "config.h"
      32              : 
      33              : #include <platform.h>
      34              : #if PLATFORM(WINDOWS)
      35              : #include <winsock2.h>
      36              : #include <windows.h>
      37              : #endif
      38              : 
      39              : #include <inttypes.h>
      40              : #include <errno.h>
      41              : #include "qof.h"
      42              : #include "qofquery-p.h"
      43              : #include "qofquerycore-p.h"
      44              : #include "Account.h"
      45              : #include "TransLog.h"
      46              : #include "gnc-engine.h"
      47              : #include "SX-book.h"
      48              : #include "Recurrence.h"
      49              : #include <gnc-features.h>
      50              : #include "gnc-uri-utils.h"
      51              : #include "gnc-filepath-utils.h"
      52              : #include <gnc-path.h>
      53              : #include "gnc-locale-utils.h"
      54              : 
      55              : #include "gnc-prefs.h"
      56              : 
      57              : #ifdef S_SPLINT_S
      58              : #include "splint-defs.h"
      59              : #endif
      60              : 
      61              : #include <boost/regex.hpp>
      62              : #include <string>
      63              : #include <iomanip>
      64              : 
      65              : #include <qofsession.hpp>
      66              : #include <gnc-backend-prov.hpp>
      67              : #include "gnc-backend-dbi.h"
      68              : #include "gnc-backend-dbi.hpp"
      69              : 
      70              : #include <gnc-sql-object-backend.hpp>
      71              : #include "gnc-dbisqlresult.hpp"
      72              : #include "gnc-dbisqlconnection.hpp"
      73              : 
      74              : #if LIBDBI_VERSION >= 900
      75              : #define HAVE_LIBDBI_R 1
      76              : static dbi_inst dbi_instance = nullptr;
      77              : #else
      78              : #define HAVE_LIBDBI_R 0
      79              : #define HAVE_LIBDBI_TO_LONGLONG 0
      80              : #endif
      81              : 
      82              : #define TRANSACTION_NAME "trans"
      83              : 
      84              : static QofLogModule log_module = G_LOG_DOMAIN;
      85              : 
      86              : #define FILE_URI_TYPE "file"
      87              : #define FILE_URI_PREFIX (FILE_URI_TYPE "://")
      88              : #define SQLITE3_URI_TYPE "sqlite3"
      89              : #define SQLITE3_URI_PREFIX (SQLITE3_URI_TYPE "://")
      90              : #define PGSQL_DEFAULT_PORT 5432
      91              : 
      92              : static void adjust_sql_options (dbi_conn connection);
      93              : template<DbType Type> bool save_may_clobber_data (dbi_conn conn,
      94              :                                                   const std::string& dbname);
      95              : 
      96              : template <DbType Type>
      97              : class QofDbiBackendProvider : public QofBackendProvider
      98              : {
      99              : public:
     100           20 :     QofDbiBackendProvider (const char* name, const char* type) :
     101           20 :         QofBackendProvider {name, type} {}
     102              :     QofDbiBackendProvider(QofDbiBackendProvider&) = delete;
     103              :     QofDbiBackendProvider operator=(QofDbiBackendProvider&) = delete;
     104              :     QofDbiBackendProvider(QofDbiBackendProvider&&) = delete;
     105              :     QofDbiBackendProvider operator=(QofDbiBackendProvider&&) = delete;
     106            4 :     ~QofDbiBackendProvider () = default;
     107            0 :     QofBackend* create_backend(void)
     108              :     {
     109            0 :         return new GncDbiBackend<Type>(nullptr, nullptr);
     110              :     }
     111            0 :     bool type_check(const char* type) { return true; }
     112              : };
     113              : 
     114              : /* ================================================================= */
     115              : /* ================================================================= */
     116              : struct UriStrings
     117              : {
     118            0 :     UriStrings() = default;
     119              :     UriStrings(const std::string& uri);
     120            0 :     ~UriStrings() = default;
     121              :     std::string basename() const noexcept;
     122              :     const char* dbname() const noexcept;
     123              :     std::string quote_dbname(DbType t) const noexcept;
     124              :     std::string m_protocol;
     125              :     std::string m_host;
     126              :     std::string m_dbname;
     127              :     std::string m_username;
     128              :     std::string m_password;
     129              :     std::string m_basename;
     130              :     int m_portnum;
     131              : };
     132              : 
     133            0 : UriStrings::UriStrings(const std::string& uri)
     134              : {
     135              :     gchar *scheme, *host, *username, *password, *dbname;
     136              :     int portnum;
     137            0 :     gnc_uri_get_components(uri.c_str(), &scheme, &host, &portnum, &username,
     138              :                            &password, &dbname);
     139            0 :     m_protocol = std::string{scheme};
     140            0 :     m_host = std::string{host};
     141            0 :     if (dbname)
     142            0 :         m_dbname = std::string{dbname};
     143            0 :     if (username)
     144            0 :         m_username = std::string{username};
     145            0 :     if (password)
     146            0 :         m_password = std::string{password};
     147            0 :     m_portnum = portnum;
     148            0 :     g_free(scheme);
     149            0 :     g_free(host);
     150            0 :     g_free(username);
     151            0 :     g_free(password);
     152            0 :     g_free(dbname);
     153            0 : }
     154              : 
     155              : std::string
     156            0 : UriStrings::basename() const noexcept
     157              : {
     158            0 :     return m_protocol + "_" + m_host + "_" + m_username + "_" + m_dbname;
     159              : }
     160              : 
     161              : const char*
     162            0 : UriStrings::dbname() const noexcept
     163              : {
     164            0 :     return m_dbname.c_str();
     165              : }
     166              : 
     167              : std::string
     168            0 : UriStrings::quote_dbname(DbType t) const noexcept
     169              : {
     170            0 :     if (m_dbname.empty())
     171            0 :         return "";
     172            0 :     const char quote = (t == DbType::DBI_MYSQL ? '`' : '"');
     173            0 :     std::string retval(1, quote);
     174            0 :     retval += m_dbname + quote;
     175            0 :     return retval;
     176            0 : }
     177              : 
     178              : static void
     179            0 : set_options(dbi_conn conn, const PairVec& options)
     180              : {
     181            0 :     for (const auto& option : options)
     182              :     {
     183            0 :         auto opt = option.first.c_str();
     184            0 :         auto val = option.second.c_str();
     185            0 :         auto result = dbi_conn_set_option(conn, opt, val);
     186            0 :         if (result < 0)
     187              :         {
     188            0 :             const char *msg = nullptr;
     189            0 :             dbi_conn_error(conn, &msg);
     190            0 :             PERR("Error setting %s option to %s: %s", opt, val, msg);
     191            0 :             throw std::runtime_error(msg);
     192              :         }
     193              :     }
     194            0 : }
     195              : 
     196              : /**
     197              :  * Sets standard db options in a dbi_conn.
     198              :  *
     199              :  * @param conn dbi_conn connection
     200              :  * @param uri UriStrings containing the needed parameters.
     201              :  * @return TRUE if successful, FALSE if error
     202              :  */
     203              : template <DbType Type> bool
     204            0 : GncDbiBackend<Type>::set_standard_connection_options (dbi_conn conn,
     205              :                                                 const UriStrings& uri)
     206              : 
     207              : {
     208            0 :     PairVec options;
     209            0 :     options.push_back(std::make_pair("host", uri.m_host));
     210            0 :     options.push_back(std::make_pair("dbname", uri.m_dbname));
     211            0 :     options.push_back(std::make_pair("username", uri.m_username));
     212            0 :     options.push_back(std::make_pair("password", uri.m_password));
     213            0 :     options.push_back(std::make_pair("encoding", "UTF-8"));
     214              :     try
     215              :     {
     216            0 :         set_options(conn, options);
     217            0 :         auto result = dbi_conn_set_option_numeric(conn, "port", uri.m_portnum);
     218            0 :         if (result < 0)
     219              :         {
     220            0 :             const char *msg = nullptr;
     221            0 :             auto err = dbi_conn_error(conn, &msg);
     222            0 :             PERR("Error (%d) setting port option to %d: %s", err, uri.m_portnum, msg);
     223            0 :             throw std::runtime_error(msg);
     224              :         }
     225              :     }
     226            0 :     catch (std::runtime_error& err)
     227              :     {
     228            0 :         set_error (ERR_BACKEND_SERVER_ERR);
     229            0 :         return false;
     230              :     }
     231              : 
     232            0 :     return true;
     233            0 : }
     234              : 
     235              : template <DbType Type> void error_handler(dbi_conn conn, void* data);
     236              : void error_handler(dbi_conn conn, void* data);
     237              : 
     238              : template <DbType Type> dbi_conn
     239            0 : GncDbiBackend<Type>::conn_setup (PairVec& options, UriStrings& uri)
     240              : {
     241            0 :     const char* dbstr = (Type == DbType::DBI_SQLITE ? "sqlite3" :
     242              :                          Type == DbType::DBI_MYSQL ? "mysql" : "pgsql");
     243              : #if HAVE_LIBDBI_R
     244            0 :     dbi_conn conn = nullptr;
     245            0 :     if (dbi_instance)
     246            0 :         conn = dbi_conn_new_r (dbstr, dbi_instance);
     247              :     else
     248            0 :         PERR ("Attempt to connect with an uninitialized dbi_instance");
     249              : #else
     250              :     auto conn = dbi_conn_new (dbstr);
     251              : #endif
     252              : 
     253            0 :     if (conn == nullptr)
     254              :     {
     255            0 :         PERR ("Unable to create %s dbi connection", dbstr);
     256            0 :         set_error (ERR_BACKEND_BAD_URL);
     257            0 :         return nullptr;
     258              :     }
     259              : 
     260            0 :     dbi_conn_error_handler (conn, error_handler<Type>, this);
     261            0 :     if (!uri.m_dbname.empty() &&
     262            0 :         !set_standard_connection_options(conn, uri))
     263              :     {
     264            0 :         dbi_conn_close(conn);
     265            0 :         return nullptr;
     266              :     }
     267            0 :     if(!options.empty())
     268              :     {
     269              :         try {
     270            0 :             set_options(conn, options);
     271              :         }
     272            0 :         catch (std::runtime_error& err)
     273              :         {
     274            0 :             dbi_conn_close(conn);
     275            0 :             set_error (ERR_BACKEND_SERVER_ERR);
     276            0 :             return nullptr;
     277              :         }
     278              :     }
     279              : 
     280            0 :     return conn;
     281              : }
     282              : 
     283              : template <DbType Type>bool
     284            0 : GncDbiBackend<Type>::create_database(dbi_conn conn, const char* db)
     285              : {
     286              :     const char *dbname;
     287              :     const char *dbcreate;
     288              :     if (Type == DbType::DBI_MYSQL)
     289              :     {
     290            0 :         dbname = "mysql";
     291            0 :         dbcreate = "CREATE DATABASE %s CHARACTER SET utf8";
     292              :     }
     293              :     else
     294              :     {
     295            0 :         dbname = "postgres";
     296            0 :         dbcreate = "CREATE DATABASE %s WITH TEMPLATE template0 ENCODING 'UTF8'";
     297              :     }
     298            0 :     PairVec options;
     299            0 :     options.push_back(std::make_pair("dbname", dbname));
     300              :     try
     301              :     {
     302            0 :         set_options(conn, options);
     303              :     }
     304            0 :     catch (std::runtime_error& err)
     305              :     {
     306            0 :         set_error (ERR_BACKEND_SERVER_ERR);
     307            0 :         return false;
     308              :     }
     309              : 
     310            0 :     auto result = dbi_conn_connect (conn);
     311            0 :     if (result < 0)
     312              :     {
     313            0 :         PERR ("Unable to connect to %s database", dbname);
     314            0 :         set_error(ERR_BACKEND_SERVER_ERR);
     315            0 :         return false;
     316              :     }
     317              :     if (Type == DbType::DBI_MYSQL)
     318            0 :         adjust_sql_options(conn);
     319            0 :     auto dresult = dbi_conn_queryf (conn, dbcreate, db);
     320            0 :     if (dresult == nullptr)
     321              :     {
     322            0 :         PERR ("Unable to create database '%s'\n", db);
     323            0 :         set_error (ERR_BACKEND_SERVER_ERR);
     324            0 :         return false;
     325              :     }
     326              :     if (Type == DbType::DBI_PGSQL)
     327              :     {
     328            0 :         const char *alterdb = "ALTER DATABASE %s SET "
     329              :             "standard_conforming_strings TO on";
     330            0 :         dbi_conn_queryf (conn, alterdb, db);
     331              :     }
     332            0 :     dbi_conn_close(conn);
     333            0 :     conn = nullptr;
     334            0 :     return true;
     335            0 : }
     336              : 
     337              : template <> void
     338            0 : error_handler<DbType::DBI_SQLITE> (dbi_conn conn, void* user_data)
     339              : {
     340              :     const char* msg;
     341            0 :     GncDbiBackend<DbType::DBI_SQLITE> *dbi_be =
     342              :         static_cast<decltype(dbi_be)>(user_data);
     343            0 :     int err_num = dbi_conn_error (conn, &msg);
     344              :     /* BADIDX is raised if we attempt to seek outside of a result. We
     345              :      * handle that possibility after checking the return value of the
     346              :      * seek. Having this raise a critical error breaks looping by
     347              :      * testing for the return value of the seek.
     348              :      */
     349            0 :     if (err_num == DBI_ERROR_BADIDX) return;
     350            0 :     PERR ("DBI error: %s\n", msg);
     351            0 :     if (dbi_be->connected())
     352            0 :         dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
     353              : }
     354              : 
     355              : template <> void
     356            0 : GncDbiBackend<DbType::DBI_SQLITE>::session_begin(QofSession* session,
     357              :                                                  const char* new_uri,
     358              :                                                  SessionOpenMode mode)
     359              : {
     360              :     gboolean file_exists;
     361            0 :     PairVec options;
     362              : 
     363            0 :     g_return_if_fail (session != nullptr);
     364            0 :     g_return_if_fail (new_uri != nullptr);
     365              : 
     366            0 :     ENTER (" ");
     367              : 
     368              :     /* Remove uri type if present */
     369            0 :     auto path = gnc_uri_get_path (new_uri);
     370            0 :     std::string filepath{path};
     371            0 :     g_free(path);
     372            0 :     GFileTest ftest = static_cast<decltype (ftest)> (
     373              :         G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS) ;
     374            0 :     file_exists = g_file_test (filepath.c_str(), ftest);
     375            0 :     bool create{mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE};
     376            0 :     if (!create && !file_exists)
     377              :     {
     378            0 :         set_error (ERR_FILEIO_FILE_NOT_FOUND);
     379            0 :         std::string msg{"Sqlite3 file "};
     380            0 :         set_message (msg + filepath + " not found");
     381            0 :         PWARN ("Sqlite3 file %s not found", filepath.c_str());
     382            0 :         LEAVE("Error");
     383            0 :         return;
     384            0 :     }
     385              : 
     386            0 :     if (create && file_exists)
     387              :     {
     388            0 :         if (mode == SESSION_NEW_OVERWRITE)
     389            0 :             g_unlink (filepath.c_str());
     390              :         else
     391              :         {
     392            0 :             set_error (ERR_BACKEND_STORE_EXISTS);
     393            0 :             auto msg = "Might clobber, mode not SESSION_NEW_OVERWRITE";
     394            0 :             PWARN ("%s", msg);
     395            0 :             LEAVE("Error");
     396            0 :             return;
     397              :         }
     398              :     }
     399              : 
     400            0 :     connect(nullptr);
     401              :     /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */
     402            0 :     options.push_back(std::make_pair("host", "localhost"));
     403            0 :     auto dirname = g_path_get_dirname (filepath.c_str());
     404            0 :     auto basename = g_path_get_basename (filepath.c_str());
     405            0 :     options.push_back(std::make_pair("dbname", basename));
     406            0 :     options.push_back(std::make_pair("sqlite3_dbdir", dirname));
     407            0 :     if (basename != nullptr) g_free (basename);
     408            0 :     if (dirname != nullptr) g_free (dirname);
     409            0 :     UriStrings uri;
     410            0 :     auto conn = conn_setup(options, uri);
     411            0 :     if (conn == nullptr)
     412              :     {
     413            0 :         LEAVE("Error");
     414            0 :         return;
     415              :     }
     416              : 
     417            0 :     auto result = dbi_conn_connect (conn);
     418              : 
     419            0 :     if (result < 0)
     420              :     {
     421            0 :         dbi_conn_close(conn);
     422            0 :         PERR ("Unable to connect to %s: %d\n", new_uri, result);
     423            0 :         set_error (ERR_BACKEND_BAD_URL);
     424            0 :         LEAVE("Error");
     425            0 :         return;
     426              :     }
     427              : 
     428            0 :     if (mode != SESSION_READ_ONLY && !conn_test_dbi_library(conn))
     429              :     {
     430            0 :         if (create && !file_exists)
     431              :         {
     432              :          /* File didn't exist before, but it does now, and we don't want to
     433              :           * leave it lying around.
     434              :           */
     435            0 :             dbi_conn_close (conn);
     436            0 :             conn = nullptr;
     437            0 :             g_unlink (filepath.c_str());
     438              :         }
     439            0 :         dbi_conn_close(conn);
     440            0 :         LEAVE("Bad DBI Library");
     441            0 :         return;
     442              :     }
     443              : 
     444              :     try
     445              :     {
     446            0 :         connect(new GncDbiSqlConnection(DbType::DBI_SQLITE,
     447            0 :                                             this, conn, mode));
     448              :     }
     449            0 :     catch (std::runtime_error& err)
     450              :     {
     451            0 :         return;
     452            0 :     }
     453              : 
     454              :     /* We should now have a proper session set up.
     455              :      * Let's start logging */
     456            0 :     xaccLogSetBaseName (filepath.c_str());
     457            0 :     PINFO ("logpath=%s", filepath.c_str() ? filepath.c_str() : "(null)");
     458            0 :     LEAVE ("");
     459            0 : }
     460              : 
     461              : 
     462              : template <> void
     463            0 : error_handler<DbType::DBI_MYSQL> (dbi_conn conn, void* user_data)
     464              : {
     465            0 :     GncDbiBackend<DbType::DBI_MYSQL>* dbi_be =
     466              :         static_cast<decltype(dbi_be)>(user_data);
     467              :     const char* msg;
     468              : 
     469            0 :     auto err_num = dbi_conn_error (conn, &msg);
     470              :     /* BADIDX is raised if we attempt to seek outside of a result. We
     471              :      * handle that possibility after checking the return value of the
     472              :      * seek. Having this raise a critical error breaks looping by
     473              :      * testing for the return value of the seek.
     474              :      */
     475            0 :     if (err_num == DBI_ERROR_BADIDX) return;
     476              : 
     477              :     /* Note: the sql connection may not have been initialized yet
     478              :      *       so let's be careful with using it
     479              :      */
     480              : 
     481              :     /* Database doesn't exist. When this error is triggered the
     482              :      * GncDbiSqlConnection may not exist yet either, so don't use it here
     483              :      */
     484            0 :     if (err_num == 1049)            // Database doesn't exist
     485              :     {
     486            0 :         PINFO ("DBI error: %s\n", msg);
     487            0 :         dbi_be->set_exists(false);
     488            0 :         return;
     489              :     }
     490              : 
     491              :     /* All the other error handling code assumes the GncDbiSqlConnection
     492              :      *  has been initialized. So let's assert it exits here, otherwise
     493              :      * simply return.
     494              :      */
     495            0 :     if (!dbi_be->connected())
     496              :     {
     497            0 :         PINFO ("DBI error: %s\n", msg);
     498            0 :         PINFO ("Note: GncDbiSqlConnection not yet initialized. Skipping further error processing.");
     499            0 :         return;
     500              :     }
     501              : 
     502              :     /* Test for other errors */
     503            0 :     if (err_num == 2006)       // Server has gone away
     504              :     {
     505            0 :         PINFO ("DBI error: %s - Reconnecting...\n", msg);
     506            0 :         dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
     507            0 :         dbi_be->retry_connection(msg);
     508              :     }
     509            0 :     else if (err_num == 2003)       // Unable to connect
     510              :     {
     511            0 :         dbi_be->set_dbi_error (ERR_BACKEND_CANT_CONNECT, 1, true);
     512            0 :         dbi_be->retry_connection (msg);
     513              :     }
     514            0 :     else if (err_num == 1007) //Database exists
     515              :     {
     516            0 :         dbi_be->set_exists(true);
     517            0 :         return;
     518              :     }
     519              : 
     520              :     else                            // Any other error
     521              :     {
     522            0 :         PERR ("DBI error: %s\n", msg);
     523            0 :         dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, FALSE);
     524              :     }
     525              : }
     526              : 
     527              : #define SQL_OPTION_TO_REMOVE "NO_ZERO_DATE"
     528              : 
     529              : /* Given an sql_options string returns a copy of the string adjusted as
     530              :  * necessary.  In particular if string the contains SQL_OPTION_TO_REMOVE it is
     531              :  * removed along with comma separator.
     532              :  */
     533              : std::string
     534            0 : adjust_sql_options_string(const std::string& str)
     535              : {
     536              : /* Regex that finds the SQL_OPTION_TO_REMOVE as the first, last, or middle of a
     537              :  * comma-delimited list.
     538              :  */
     539              :     static const boost::regex reg{"(?:," SQL_OPTION_TO_REMOVE "$|\\b"
     540            0 :             SQL_OPTION_TO_REMOVE "\\b,?)"};
     541            0 :     return regex_replace(str, reg, std::string{""});
     542              : }
     543              : 
     544              : /* checks mysql sql_options and adjusts if necessary */
     545              : static void
     546            0 : adjust_sql_options (dbi_conn connection)
     547              : {
     548            0 :     dbi_result result = dbi_conn_query( connection, "SELECT @@sql_mode");
     549            0 :     if (result == nullptr)
     550              :     {
     551              :         const char* errmsg;
     552            0 :         int err = dbi_conn_error(connection, &errmsg);
     553            0 :         PERR("Unable to read sql_mode %d : %s", err, errmsg);
     554            0 :         return;
     555              :     }
     556            0 :     dbi_result_first_row(result);
     557            0 :     std::string str{dbi_result_get_string_idx(result, 1)};
     558            0 :     dbi_result_free(result);
     559            0 :     if (str.empty())
     560              :     {
     561              :         const char* errmsg;
     562            0 :         int err = dbi_conn_error(connection, &errmsg);
     563            0 :         if (err)
     564            0 :             PERR("Unable to get sql_mode %d : %s", err, errmsg);
     565              :         else
     566            0 :             PINFO("Sql_mode isn't set.");
     567            0 :         return;
     568              :     }
     569            0 :     PINFO("Initial sql_mode: %s", str.c_str());
     570            0 :     if(str.find(SQL_OPTION_TO_REMOVE) != std::string::npos)
     571            0 :         str = adjust_sql_options_string(str);
     572              : 
     573              :     //https://bugs.gnucash.org/show_bug.cgi?id=798112
     574            0 :     const char* backslash_option{"NO_BACKSLASH_ESCAPES"};
     575              : 
     576            0 :     if (str.find(backslash_option) == std::string::npos)
     577              :     {
     578            0 :         if (!str.empty())
     579            0 :             str.append(",");
     580            0 :         str.append(backslash_option);
     581              :     }
     582              : 
     583            0 :     PINFO("Setting sql_mode to %s", str.c_str());
     584            0 :     std::string set_str{"SET sql_mode='" + std::move(str) + "'"};
     585            0 :     dbi_result set_result = dbi_conn_query(connection,
     586              :                                            set_str.c_str());
     587            0 :     if (set_result)
     588              :     {
     589            0 :         dbi_result_free(set_result);
     590              :     }
     591              :     else
     592              :     {
     593              :         const char* errmsg;
     594            0 :         int err = dbi_conn_error(connection, &errmsg);
     595            0 :         PERR("Unable to set sql_mode %d : %s", err, errmsg);
     596              :     }
     597            0 : }
     598              : 
     599              : template <DbType Type> bool
     600            0 : drop_database(dbi_conn conn, const UriStrings& uri)
     601              : {
     602              :     const char *root_db;
     603              :     if (Type == DbType::DBI_PGSQL)
     604              :     {
     605            0 :         root_db = "template1";
     606              :     }
     607              :     else if (Type == DbType::DBI_MYSQL)
     608              :     {
     609            0 :         root_db = "mysql";
     610              :     }
     611              :     else
     612              :     {
     613              :         PERR ("Unknown database type, can't proceed.");
     614              :         LEAVE("Error");
     615              :         return false;
     616              :     }
     617            0 :     if (dbi_conn_select_db (conn, root_db) == -1)
     618              :     {
     619            0 :         PERR ("Failed to switch out of %s, drop will fail.",
     620              :               uri.quote_dbname(Type).c_str());
     621            0 :         LEAVE ("Error");
     622            0 :         return false;
     623              :     }
     624            0 :     if (!dbi_conn_queryf (conn, "DROP DATABASE %s",
     625            0 :                           uri.quote_dbname(Type).c_str()))
     626              :     {
     627            0 :         PERR ("Failed to drop database %s prior to recreating it."
     628              :               "Proceeding would combine old and new data.",
     629              :               uri.quote_dbname(Type).c_str());
     630            0 :         LEAVE ("Error");
     631            0 :         return false;
     632              :     }
     633            0 :     return true;
     634              : }
     635              : 
     636              : template <DbType Type> void
     637            0 : GncDbiBackend<Type>::session_begin (QofSession* session, const char* new_uri,
     638              :                                     SessionOpenMode mode)
     639              : {
     640            0 :     PairVec options;
     641              : 
     642            0 :     g_return_if_fail (session != nullptr);
     643            0 :     g_return_if_fail (new_uri != nullptr);
     644              : 
     645            0 :     ENTER (" ");
     646              : 
     647              :     /* Split the book-id
     648              :      * Format is protocol://username:password@hostname:port/dbname
     649              :      where username, password and port are optional) */
     650            0 :     UriStrings uri(new_uri);
     651              : 
     652              :     if (Type == DbType::DBI_PGSQL)
     653              :     {
     654            0 :         if (uri.m_portnum == 0)
     655            0 :             uri.m_portnum = PGSQL_DEFAULT_PORT;
     656              :         /* Postgres's SQL interface coerces identifiers to lower case, but the
     657              :          * C interface is case-sensitive. This results in a mixed-case dbname
     658              :          * being created (with a lower case name) but then dbi can't connect to
     659              :          * it. To work around this, coerce the name to lowercase first. */
     660            0 :         auto lcname = g_utf8_strdown (uri.dbname(), -1);
     661            0 :         uri.m_dbname = std::string{lcname};
     662            0 :         g_free(lcname);
     663              :     }
     664            0 :     connect(nullptr);
     665              : 
     666            0 :     bool create{mode == SESSION_NEW_STORE || mode == SESSION_NEW_OVERWRITE};
     667            0 :     auto conn = conn_setup(options, uri);
     668            0 :     if (conn == nullptr)
     669              :     {
     670            0 :         LEAVE("Error");
     671            0 :         return;
     672              :     }
     673              : 
     674            0 :     m_exists = true; //May be unset in the error handler.
     675            0 :     auto result = dbi_conn_connect (conn);
     676            0 :     if (result == 0)
     677              :     {
     678              :         if (Type == DbType::DBI_MYSQL)
     679            0 :             adjust_sql_options (conn);
     680            0 :         if (mode != SESSION_READ_ONLY && !conn_test_dbi_library(conn))
     681              :         {
     682            0 :             dbi_conn_close(conn);
     683            0 :             LEAVE("Error");
     684            0 :             return;
     685              :         }
     686            0 :         bool create = (mode == SESSION_NEW_STORE ||
     687              :                        mode == SESSION_NEW_OVERWRITE);
     688            0 :         if (create && save_may_clobber_data<Type>(conn, uri.quote_dbname(Type)))
     689              :         {
     690            0 :             if (mode == SESSION_NEW_OVERWRITE)
     691              :             {
     692            0 :                 if (!drop_database<Type>(conn, uri))
     693            0 :                     return;
     694              :             }
     695              :             else
     696              :             {
     697            0 :                 set_error (ERR_BACKEND_STORE_EXISTS);
     698            0 :                 PWARN ("Database already exists, Might clobber it.");
     699            0 :                 dbi_conn_close(conn);
     700            0 :                 LEAVE("Error");
     701            0 :                 return;
     702              :             }
     703              :             /* Drop successful. */
     704            0 :             m_exists = false;
     705              :         }
     706              : 
     707              :     }
     708            0 :     else if (m_exists)
     709              :     {
     710            0 :         PERR ("Unable to connect to database '%s'\n", uri.dbname());
     711            0 :         set_error (ERR_BACKEND_CANT_CONNECT);
     712            0 :         dbi_conn_close(conn);
     713            0 :         LEAVE("Error");
     714            0 :         return;
     715              :     }
     716            0 :     else if (!create)
     717              :     {
     718            0 :         PERR ("Database '%s' does not exist\n", uri.dbname());
     719            0 :         set_error(ERR_BACKEND_NO_SUCH_DB);
     720            0 :         std::string msg{"Database "};
     721            0 :         set_message(msg + uri.dbname() + " not found");
     722            0 :         LEAVE("Error");
     723            0 :         return;
     724            0 :     }
     725              : 
     726            0 :     if (create)
     727              :     {
     728            0 :         if (!m_exists &&
     729            0 :             !create_database(conn, uri.quote_dbname(Type).c_str()))
     730              :         {
     731            0 :             dbi_conn_close(conn);
     732            0 :             LEAVE("Error");
     733            0 :             return;
     734              :         }
     735            0 :         conn = conn_setup(options, uri);
     736            0 :         result = dbi_conn_connect (conn);
     737            0 :         if (result < 0)
     738              :         {
     739            0 :             PERR ("Unable to create database '%s'\n", uri.dbname());
     740            0 :             set_error (ERR_BACKEND_SERVER_ERR);
     741            0 :             dbi_conn_close(conn);
     742            0 :             LEAVE("Error");
     743            0 :             return;
     744              :         }
     745              :         if (Type == DbType::DBI_MYSQL)
     746            0 :             adjust_sql_options (conn);
     747            0 :         if (!conn_test_dbi_library(conn))
     748              :         {
     749              :             if (Type == DbType::DBI_PGSQL)
     750            0 :                 dbi_conn_select_db (conn, "template1");
     751            0 :             dbi_conn_queryf (conn, "DROP DATABASE %s",
     752            0 :                                 uri.quote_dbname(Type).c_str());
     753            0 :             dbi_conn_close(conn);
     754            0 :             return;
     755              :         }
     756              :     }
     757              : 
     758            0 :     connect(nullptr);
     759              :     try
     760              :     {
     761            0 :         connect(new GncDbiSqlConnection(Type, this, conn, mode));
     762              :     }
     763            0 :     catch (std::runtime_error& err)
     764              :     {
     765            0 :         return;
     766              :     }
     767              :     /* We should now have a proper session set up.
     768              :      * Let's start logging */
     769            0 :     auto translog_path = gnc_build_translog_path (uri.basename().c_str());
     770            0 :     xaccLogSetBaseName (translog_path);
     771            0 :     PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
     772            0 :     g_free (translog_path);
     773              : 
     774            0 :     LEAVE (" ");
     775            0 : }
     776              : 
     777              : template<> void
     778            0 : error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data)
     779              : {
     780            0 :     GncDbiBackend<DbType::DBI_PGSQL>* dbi_be =
     781              :         static_cast<decltype(dbi_be)>(user_data);
     782              :     const char* msg;
     783              : 
     784            0 :     auto err_num = dbi_conn_error (conn, &msg);
     785              :     /* BADIDX is raised if we attempt to seek outside of a result. We
     786              :      * handle that possibility after checking the return value of the
     787              :      * seek. Having this raise a critical error breaks looping by
     788              :      * testing for the return value of the seek.
     789              :      */
     790            0 :     if (err_num == DBI_ERROR_BADIDX) return;
     791            0 :     if (g_str_has_prefix (msg, "FATAL:  database") &&
     792            0 :         g_str_has_suffix (msg, "does not exist\n"))
     793              :     {
     794            0 :         PINFO ("DBI error: %s\n", msg);
     795            0 :         dbi_be->set_exists(false);
     796              :     }
     797            0 :     else if (g_strrstr (msg,
     798              :                         "server closed the connection unexpectedly"))    // Connection lost
     799              :     {
     800            0 :         if (!dbi_be->connected())
     801              :         {
     802            0 :             PWARN ("DBI Error: Connection lost, connection pointer invalid");
     803            0 :             return;
     804              :         }
     805            0 :         PINFO ("DBI error: %s - Reconnecting...\n", msg);
     806            0 :         dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true);
     807            0 :         dbi_be->retry_connection(msg);
     808              :     }
     809            0 :     else if (g_str_has_prefix (msg, "connection pointer is NULL") ||
     810            0 :              g_str_has_prefix (msg, "could not connect to server"))       // No connection
     811              :     {
     812              : 
     813            0 :         if (!dbi_be->connected())
     814            0 :             qof_backend_set_error(reinterpret_cast<QofBackend*>(dbi_be),
     815              :                                   ERR_BACKEND_CANT_CONNECT);
     816              :         else
     817              :         {
     818            0 :             dbi_be->set_dbi_error(ERR_BACKEND_CANT_CONNECT, 1, true);
     819            0 :             dbi_be->retry_connection (msg);
     820              :         }
     821              :     }
     822              :     else
     823              :     {
     824            0 :         PERR ("DBI error: %s\n", msg);
     825            0 :         if (dbi_be->connected())
     826            0 :             dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false);
     827              :     }
     828              : }
     829              : 
     830              : /* ================================================================= */
     831              : 
     832              : template <DbType Type> void
     833            0 : GncDbiBackend<Type>::session_end ()
     834              : {
     835            0 :     ENTER (" ");
     836              : 
     837            0 :     finalize_version_info ();
     838            0 :     connect(nullptr);
     839              : 
     840            0 :     LEAVE (" ");
     841            0 : }
     842              : 
     843              : template <DbType Type>
     844            0 : GncDbiBackend<Type>::~GncDbiBackend()
     845              : {
     846              :     /* Stop transaction logging */
     847            0 :     xaccLogSetBaseName (nullptr);
     848            0 : }
     849              : 
     850              : /* ================================================================= */
     851              : 
     852              : /* GNUCASH_RESAVE_VERSION indicates the earliest database version
     853              :  * compatible with this version of Gnucash; the stored value is the
     854              :  * earliest version of Gnucash conpatible with the database. If the
     855              :  * GNUCASH_RESAVE_VERSION for this Gnucash is newer than the Gnucash
     856              :  * version which created the database, a resave is offered. If the
     857              :  * version of this Gnucash is older than the saved resave version,
     858              :  * then the database will be loaded read-only. A resave will update
     859              :  * both values to match this version of Gnucash.
     860              :  */
     861              : template <DbType Type> void
     862            0 : GncDbiBackend<Type>::load (QofBook* book, QofBackendLoadType loadType)
     863              : {
     864            0 :     g_return_if_fail (book != nullptr);
     865              : 
     866            0 :     ENTER ("dbi_be=%p, book=%p", this, book);
     867              : 
     868            0 :     if (loadType == LOAD_TYPE_INITIAL_LOAD)
     869              :     {
     870              : 
     871              :         // Set up table version information
     872            0 :         init_version_info ();
     873            0 :         assert (m_book == nullptr);
     874            0 :         create_tables();
     875              :     }
     876              : 
     877            0 :     GncSqlBackend::load(book, loadType);
     878              : 
     879              :     if (Type == DbType::DBI_SQLITE)
     880            0 :         gnc_features_set_used(book, GNC_FEATURE_SQLITE3_ISO_DATES);
     881              : 
     882            0 :     if (GNUCASH_RESAVE_VERSION > get_table_version("Gnucash"))
     883              :     {
     884              :         /* The database was loaded with an older database schema or
     885              :          * data semantics. In order to ensure consistency, the whole
     886              :          * thing needs to be saved anew. */
     887            0 :         set_error(ERR_SQL_DB_TOO_OLD);
     888              :     }
     889            0 :     else if (GNUCASH_RESAVE_VERSION < get_table_version("Gnucash-Resave"))
     890              :     {
     891              :         /* Worse, the database was created with a newer version. We
     892              :          * can't safely write to this database, so the user will have
     893              :          * to do a "save as" to make one that we can write to.
     894              :          */
     895            0 :         set_error(ERR_SQL_DB_TOO_NEW);
     896              :     }
     897              : 
     898              : 
     899            0 :     LEAVE ("");
     900              : }
     901              : 
     902              : /* ================================================================= */
     903              : /* This is used too early to call GncDbiProvider::get_table_list(). */
     904              : template <DbType T> bool
     905            0 : save_may_clobber_data (dbi_conn conn, const std::string& dbname)
     906              : {
     907              : 
     908              :     /* Data may be clobbered iff the number of tables != 0 */
     909            0 :     auto result = dbi_conn_get_table_list (conn, dbname.c_str(), nullptr);
     910            0 :     bool retval = false;
     911            0 :     if (result)
     912              :     {
     913            0 :         retval =  dbi_result_get_numrows (result) > 0;
     914            0 :         dbi_result_free (result);
     915              :     }
     916            0 :     return retval;
     917              : }
     918              : 
     919              : template <> bool
     920            0 : save_may_clobber_data <DbType::DBI_PGSQL>(dbi_conn conn,
     921              :                                           const std::string& dbname)
     922              : {
     923              : 
     924              :     /* Data may be clobbered iff the number of tables != 0 */
     925            0 :     const char* query = "SELECT relname FROM pg_class WHERE relname !~ '^(pg|sql)_' AND relkind = 'r' ORDER BY relname";
     926            0 :     auto result = dbi_conn_query (conn, query);
     927            0 :     bool retval = false;
     928            0 :     if (result)
     929              :     {
     930            0 :         retval =  dbi_result_get_numrows (result) > 0;
     931            0 :         dbi_result_free (result);
     932              :     }
     933            0 :     return retval;
     934              : }
     935              : 
     936              : 
     937              : /**
     938              :  * Safely resave a database by renaming all of its tables, recreating
     939              :  * everything, and then dropping the backup tables only if there were
     940              :  * no errors. If there are errors, drop the new tables and restore the
     941              :  * originals.
     942              :  *
     943              :  * @param book: QofBook to be saved in the database.
     944              :  */
     945              : template <DbType Type> void
     946            0 : GncDbiBackend<Type>::safe_sync (QofBook* book)
     947              : {
     948            0 :     auto conn = dynamic_cast<GncDbiSqlConnection*>(m_conn);
     949              : 
     950            0 :     g_return_if_fail (conn != nullptr);
     951            0 :     g_return_if_fail (book != nullptr);
     952              : 
     953            0 :     ENTER ("book=%p, primary=%p", book, m_book);
     954            0 :     if (!conn->begin_transaction())
     955              :     {
     956            0 :         LEAVE("Failed to obtain a transaction.");
     957            0 :         return;
     958              :     }
     959            0 :     if (!conn->table_operation (TableOpType::backup))
     960              :     {
     961            0 :         conn->rollback_transaction();
     962            0 :         LEAVE ("Failed to rename tables");
     963            0 :         return;
     964              :     }
     965            0 :     if (!conn->drop_indexes())
     966              :     {
     967            0 :         conn->rollback_transaction();
     968            0 :         LEAVE ("Failed to drop indexes");
     969            0 :         return;
     970              :     }
     971              : 
     972            0 :     sync(m_book);
     973            0 :     if (check_error())
     974              :     {
     975            0 :         conn->rollback_transaction();
     976            0 :         LEAVE ("Failed to create new database tables");
     977            0 :         return;
     978              :     }
     979            0 :     conn->commit_transaction();
     980            0 :     conn->table_operation (TableOpType::drop_backup);
     981            0 :     LEAVE ("book=%p", m_book);
     982              : }
     983              : /* MySQL commits the transaction and all savepoints after the first CREATE
     984              :  * TABLE, crashing when we try to RELEASE SAVEPOINT because the savepoint
     985              :  * doesn't exist after the commit. We must run without a wrapping transaction in
     986              :  * that case.
     987              :  */
     988              : template <> void
     989            0 : GncDbiBackend<DbType::DBI_MYSQL>::safe_sync (QofBook* book)
     990              : {
     991            0 :     auto conn = dynamic_cast<GncDbiSqlConnection*>(m_conn);
     992              : 
     993            0 :     g_return_if_fail (conn != nullptr);
     994            0 :     g_return_if_fail (book != nullptr);
     995              : 
     996            0 :     ENTER ("book=%p, primary=%p", book, m_book);
     997            0 :     if (!conn->table_operation (TableOpType::backup))
     998              :     {
     999            0 :         set_error(ERR_BACKEND_SERVER_ERR);
    1000            0 :         conn->table_operation (TableOpType::rollback);
    1001            0 :         LEAVE ("Failed to rename tables");
    1002            0 :         return;
    1003              :     }
    1004            0 :     if (!conn->drop_indexes())
    1005              :     {
    1006            0 :         conn->table_operation (TableOpType::rollback);
    1007            0 :         set_error (ERR_BACKEND_SERVER_ERR);
    1008            0 :         set_message("Failed to drop indexes");
    1009            0 :         LEAVE ("Failed to drop indexes");
    1010            0 :         return;
    1011              :     }
    1012              : 
    1013            0 :     sync(m_book);
    1014            0 :     if (check_error())
    1015              :     {
    1016            0 :         conn->table_operation (TableOpType::rollback);
    1017            0 :         LEAVE ("Failed to create new database tables");
    1018            0 :         return;
    1019              :     }
    1020            0 :     conn->table_operation (TableOpType::drop_backup);
    1021            0 :     LEAVE ("book=%p", m_book);
    1022              : }
    1023              : /* ================================================================= */
    1024              : 
    1025              : /*
    1026              :  * Checks to see whether the file is an sqlite file or not
    1027              :  *
    1028              :  */
    1029              : template<> bool
    1030            2 : QofDbiBackendProvider<DbType::DBI_SQLITE>::type_check(const char *uri)
    1031              : {
    1032              :     FILE* f;
    1033            2 :     gchar buf[51]{};
    1034              :     G_GNUC_UNUSED size_t chars_read;
    1035              :     gint status;
    1036              :     gchar* filename;
    1037              : 
    1038              :     // BAD if the path is null
    1039            2 :     g_return_val_if_fail (uri != nullptr, FALSE);
    1040              : 
    1041            2 :     filename = gnc_uri_get_path (uri);
    1042            2 :     f = g_fopen (filename, "r");
    1043            2 :     g_free (filename);
    1044              : 
    1045              :     // OK if the file doesn't exist - new file
    1046            2 :     if (f == nullptr)
    1047              :     {
    1048            0 :         PINFO ("doesn't exist (errno=%d) -> DBI", errno);
    1049            0 :         return TRUE;
    1050              :     }
    1051              : 
    1052              :     // OK if file has the correct header
    1053            2 :     chars_read = fread (buf, sizeof (buf) - 1, 1, f);
    1054            2 :     status = fclose (f);
    1055            2 :     if (status < 0)
    1056              :     {
    1057            0 :         PERR ("Error in fclose(): %d\n", errno);
    1058              :     }
    1059            2 :     if (g_str_has_prefix (buf, "SQLite format 3"))
    1060              :     {
    1061            0 :         PINFO ("has SQLite format string -> DBI");
    1062            0 :         return TRUE;
    1063              :     }
    1064            2 :     PINFO ("exists, does not have SQLite format string -> not DBI");
    1065              : 
    1066              :     // Otherwise, BAD
    1067            2 :     return FALSE;
    1068              : }
    1069              : 
    1070              : void
    1071           10 : gnc_module_init_backend_dbi (void)
    1072              : {
    1073              :     const char* driver_dir;
    1074              :     int num_drivers;
    1075           10 :     gboolean have_sqlite3_driver = FALSE;
    1076           10 :     gboolean have_mysql_driver = FALSE;
    1077           10 :     gboolean have_pgsql_driver = FALSE;
    1078              : 
    1079              :     /* Initialize libdbi and see which drivers are available.  Only register qof backends which
    1080              :        have drivers available. */
    1081           10 :     driver_dir = g_getenv ("GNC_DBD_DIR");
    1082           10 :     if (driver_dir == nullptr)
    1083              :     {
    1084           10 :         PINFO ("GNC_DBD_DIR not set: using libdbi built-in default\n");
    1085              :     }
    1086              : 
    1087              :     /* dbi_initialize returns -1 in case of errors */
    1088              : #if HAVE_LIBDBI_R
    1089           10 :     if (dbi_instance)
    1090            0 :         return;
    1091           10 :     num_drivers = dbi_initialize_r (driver_dir, &dbi_instance);
    1092              : #else
    1093              :     num_drivers = dbi_initialize (driver_dir);
    1094              : #endif
    1095           10 :     if (num_drivers <= 0)
    1096              :     {
    1097              : #if HAVE_LIBDBI_R
    1098            0 :         if (dbi_instance)
    1099              :         {
    1100            0 :             dbi_shutdown_r (dbi_instance);
    1101            0 :             dbi_instance = nullptr;
    1102              :         }
    1103              : #endif
    1104            0 :         gchar *libdir = gnc_path_get_libdir ();
    1105            0 :         gchar *dir = g_build_filename (libdir, "dbd", nullptr);
    1106            0 :         g_free (libdir);
    1107              : #if HAVE_LIBDBI_R
    1108            0 :         num_drivers = dbi_initialize_r (dir, &dbi_instance);
    1109              : #else
    1110              :         num_drivers = dbi_initialize (dir);
    1111              : #endif
    1112            0 :         g_free (dir);
    1113              :     }
    1114           10 :     if (num_drivers <= 0)
    1115              :     {
    1116            0 :         PWARN ("No DBD drivers found\n");
    1117              :     }
    1118              :     else
    1119              :     {
    1120           10 :         dbi_driver driver = nullptr;
    1121           10 :         PINFO ("%d DBD drivers found\n", num_drivers);
    1122              : 
    1123              :         do
    1124              :         {
    1125              : #if HAVE_LIBDBI_R
    1126           20 :             driver = dbi_driver_list_r (driver, dbi_instance);
    1127              : #else
    1128              :             driver = dbi_driver_list (driver);
    1129              : #endif
    1130              : 
    1131           20 :             if (driver != nullptr)
    1132              :             {
    1133           10 :                 const gchar* name = dbi_driver_get_name (driver);
    1134              : 
    1135           10 :                 PINFO ("Driver: %s\n", name);
    1136           10 :                 if (strcmp (name, "sqlite3") == 0)
    1137              :                 {
    1138           10 :                     have_sqlite3_driver = TRUE;
    1139              :                 }
    1140            0 :                 else if (strcmp (name, "mysql") == 0)
    1141              :                 {
    1142            0 :                     have_mysql_driver = TRUE;
    1143              :                 }
    1144            0 :                 else if (strcmp (name, "pgsql") == 0)
    1145              :                 {
    1146            0 :                     have_pgsql_driver = TRUE;
    1147              :                 }
    1148              :             }
    1149              :         }
    1150           20 :         while (driver != nullptr);
    1151              :     }
    1152              : 
    1153           10 :     if (have_sqlite3_driver)
    1154              :     {
    1155           10 :         const char* name = "GnuCash Libdbi (SQLITE3) Backend";
    1156           10 :         auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, FILE_URI_TYPE});
    1157           10 :         qof_backend_register_provider(std::move(prov));
    1158           10 :         prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, SQLITE3_URI_TYPE});
    1159           10 :         qof_backend_register_provider(std::move(prov));
    1160           10 :     }
    1161              : 
    1162           10 :     if (have_mysql_driver)
    1163              :     {
    1164            0 :         const char *name = "GnuCash Libdbi (MYSQL) Backend";
    1165            0 :         auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_MYSQL>{name, "mysql"});
    1166            0 :         qof_backend_register_provider(std::move(prov));
    1167            0 :     }
    1168              : 
    1169           10 :     if (have_pgsql_driver)
    1170              :     {
    1171            0 :         const char* name = "GnuCash Libdbi (POSTGRESQL) Backend";
    1172            0 :         auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_PGSQL>{name, "postgres"});
    1173            0 :         qof_backend_register_provider(std::move(prov));
    1174            0 :     }
    1175              : 
    1176              :     /* If needed, set log level to DEBUG so that SQl statements will be put into
    1177              :        the gnucash.trace file. */
    1178              :     /*    qof_log_set_level( log_module, QOF_LOG_DEBUG ); */
    1179              : }
    1180              : 
    1181              : #ifndef GNC_NO_LOADABLE_MODULES
    1182              : G_MODULE_EXPORT void
    1183           11 : qof_backend_module_init (void)
    1184              : {
    1185           11 :     gnc_module_init_backend_dbi ();
    1186           11 : }
    1187              : 
    1188              : G_MODULE_EXPORT void
    1189            0 : qof_backend_module_finalize (void)
    1190              : {
    1191            0 :     gnc_module_finalize_backend_dbi ();
    1192            0 : }
    1193              : #endif /* GNC_NO_LOADABLE_MODULES */
    1194              : 
    1195              : void
    1196            0 : gnc_module_finalize_backend_dbi (void)
    1197              : {
    1198              : #if HAVE_LIBDBI_R
    1199            0 :     if (dbi_instance)
    1200              :     {
    1201            0 :         dbi_shutdown_r (dbi_instance);
    1202            0 :         dbi_instance = nullptr;
    1203              :     }
    1204              : #else
    1205              :     dbi_shutdown ();
    1206              : #endif
    1207            0 : }
    1208              : 
    1209              : /* --------------------------------------------------------- */
    1210              : 
    1211              : static void
    1212            0 : log_failed_field(dbi_result result, const char* fieldname)
    1213              : {
    1214            0 :     auto idx = dbi_result_get_field_idx(result, fieldname);
    1215            0 :     if (dbi_result_field_is_null_idx(result, idx))
    1216            0 :         PERR("Result field %s is NULL", fieldname);
    1217              :     else
    1218              :     {
    1219            0 :         auto type = dbi_result_get_field_type_idx(result, idx);
    1220            0 :         auto attribs = dbi_result_get_field_attribs_idx(result, idx);
    1221            0 :         PERR("Result field %s has type %d and attribs %d",
    1222              :              fieldname, type, attribs);
    1223              :     }
    1224            0 : }
    1225              : 
    1226              : /** Users discovered a bug in some distributions of libdbi, where if
    1227              :  * it is compiled on certain versions of gcc with the -ffast-math
    1228              :  * compiler option it fails to correctly handle saving of 64-bit
    1229              :  * values. This function tests for the problem.
    1230              :  * @param: conn: The just-opened dbi_conn
    1231              :  * @returns: GNC_DBI_PASS if the dbi library is safe to use,
    1232              :  * GNC_DBI_FAIL_SETUP if the test could not be completed, or
    1233              :  * GNC_DBI_FAIL_TEST if the bug was found.
    1234              :  */
    1235              : static GncDbiTestResult
    1236            0 : dbi_library_test (dbi_conn conn)
    1237              : {
    1238            0 :     int64_t testlonglong = -9223372036854775807LL, resultlonglong = 0;
    1239            0 :     uint64_t testulonglong = 9223372036854775807LLU, resultulonglong = 0;
    1240            0 :     double testdouble = 1.7976921348623157E+307, resultdouble = 0.0;
    1241              :     dbi_result result;
    1242            0 :     GncDbiTestResult retval = GNC_DBI_PASS;
    1243              : 
    1244            0 :     result = dbi_conn_query (conn, "CREATE TEMPORARY TABLE numtest "
    1245              :                              "( test_int BIGINT, test_unsigned BIGINT,"
    1246              :                              " test_double FLOAT8 )");
    1247            0 :     if (result == nullptr)
    1248              :     {
    1249            0 :         PWARN ("Test_DBI_Library: Create table failed");
    1250            0 :         return GNC_DBI_FAIL_SETUP;
    1251              :     }
    1252            0 :     dbi_result_free (result);
    1253            0 :     std::stringstream querystr;
    1254            0 :     querystr << "INSERT INTO numtest VALUES (" << testlonglong <<
    1255            0 :         ", " << testulonglong << ", " << std::setprecision(12) <<
    1256            0 :         testdouble << ")";
    1257            0 :     auto query = querystr.str();
    1258            0 :     result = dbi_conn_query (conn, query.c_str());
    1259            0 :     if (result == nullptr)
    1260              :     {
    1261            0 :         PWARN ("Test_DBI_Library: Failed to insert test row into table");
    1262            0 :         return GNC_DBI_FAIL_SETUP;
    1263              :     }
    1264            0 :     dbi_result_free (result);
    1265            0 :     auto locale = gnc_push_locale (LC_NUMERIC, "C");
    1266            0 :     result = dbi_conn_query (conn, "SELECT * FROM numtest");
    1267            0 :     if (result == nullptr || !dbi_result_get_numrows(result))
    1268              :     {
    1269              :         const char* errmsg;
    1270            0 :         dbi_conn_error (conn, &errmsg);
    1271            0 :         PWARN ("Test_DBI_Library: Failed to retrieve test row into table: %s",
    1272              :                errmsg);
    1273            0 :         dbi_conn_query (conn, "DROP TABLE numtest");
    1274            0 :         gnc_pop_locale (LC_NUMERIC, locale);
    1275            0 :         return GNC_DBI_FAIL_SETUP;
    1276              :     }
    1277            0 :     while (dbi_result_next_row (result))
    1278              :     {
    1279            0 :         resultlonglong = dbi_result_get_longlong (result, "test_int");
    1280            0 :         if (!resultlonglong)
    1281            0 :             log_failed_field(result, "test_int");
    1282            0 :         resultulonglong = dbi_result_get_ulonglong (result, "test_unsigned");
    1283            0 :         if (!resultulonglong)
    1284            0 :             log_failed_field(result, "test_unsigned");
    1285            0 :         resultdouble = dbi_result_get_double (result, "test_double");
    1286            0 :         if (!resultdouble)
    1287            0 :             log_failed_field(result, "test_double");
    1288              :     }
    1289            0 :     dbi_conn_query (conn, "DROP TABLE numtest");
    1290            0 :     gnc_pop_locale (LC_NUMERIC, locale);
    1291            0 :     if (testlonglong != resultlonglong)
    1292              :     {
    1293            0 :         PWARN ("Test_DBI_Library: LongLong Failed %" PRId64 " != % " PRId64,
    1294              :                testlonglong, resultlonglong);
    1295            0 :         retval = GNC_DBI_FAIL_TEST;
    1296              :     }
    1297            0 :     if (testulonglong != resultulonglong)
    1298              :     {
    1299            0 :         PWARN ("Test_DBI_Library: Unsigned longlong Failed %" PRIu64 " != %"
    1300              :                PRIu64, testulonglong, resultulonglong);
    1301            0 :         retval = GNC_DBI_FAIL_TEST;
    1302              :     }
    1303              :     /* A bug in libdbi stores only 7 digits of precision */
    1304            0 :     if (testdouble >= resultdouble + 0.000001e307 ||
    1305            0 :         testdouble <= resultdouble - 0.000001e307)
    1306              :     {
    1307            0 :         PWARN ("Test_DBI_Library: Double Failed %17e != %17e",
    1308              :                testdouble, resultdouble);
    1309            0 :         retval = GNC_DBI_FAIL_TEST;
    1310              :     }
    1311            0 :     return retval;
    1312            0 : }
    1313              : 
    1314              : template <DbType Type> bool
    1315            0 : GncDbiBackend<Type>::conn_test_dbi_library(dbi_conn conn)
    1316              : {
    1317            0 :     auto result = dbi_library_test (conn);
    1318            0 :     switch (result)
    1319              :     {
    1320            0 :         case GNC_DBI_PASS:
    1321            0 :             break;
    1322              : 
    1323            0 :         case GNC_DBI_FAIL_SETUP:
    1324            0 :             set_error(ERR_SQL_DBI_UNTESTABLE);
    1325            0 :             set_message ("DBI library large number test incomplete");
    1326            0 :             break;
    1327              : 
    1328            0 :         case GNC_DBI_FAIL_TEST:
    1329            0 :             set_error (ERR_SQL_BAD_DBI);
    1330            0 :             set_message ("DBI library fails large number test");
    1331            0 :             break;
    1332              :     }
    1333            0 :     return result == GNC_DBI_PASS;
    1334              : }
    1335              : 
    1336              : /* ========================== END OF FILE ===================== */
        

Generated by: LCOV version 2.0-1