LCOV - code coverage report
Current view: top level - libgnucash/backend/dbi - gnc-backend-dbi.cpp (source / functions) Hit Total Coverage
Test: gnucash.info Lines: 48 601 8.0 %
Date: 2024-10-31 11:06:40 Functions: 6 66 9.1 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0 0 -

           Branch data     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                 :        114 :     QofDbiBackendProvider (const char* name, const char* type) :
     101                 :        114 :         QofBackendProvider {name, type} {}
     102                 :            :     QofDbiBackendProvider(QofDbiBackendProvider&) = delete;
     103                 :            :     QofDbiBackendProvider operator=(QofDbiBackendProvider&) = delete;
     104                 :            :     QofDbiBackendProvider(QofDbiBackendProvider&&) = delete;
     105                 :            :     QofDbiBackendProvider operator=(QofDbiBackendProvider&&) = delete;
     106                 :        192 :     ~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 (!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                 :            :     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                 :          0 : }
     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                 :            :                           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(!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                 :            :                                 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->table_operation (TableOpType::drop_backup);
     980                 :          0 :     conn->commit_transaction();
     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                 :         57 : gnc_module_init_backend_dbi (void)
    1072                 :            : {
    1073                 :            :     const char* driver_dir;
    1074                 :            :     int num_drivers;
    1075                 :         57 :     gboolean have_sqlite3_driver = FALSE;
    1076                 :         57 :     gboolean have_mysql_driver = FALSE;
    1077                 :         57 :     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                 :         57 :     driver_dir = g_getenv ("GNC_DBD_DIR");
    1082                 :         57 :     if (driver_dir == nullptr)
    1083                 :            :     {
    1084                 :         57 :         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                 :         57 :     if (dbi_instance)
    1090                 :          0 :         return;
    1091                 :         57 :     num_drivers = dbi_initialize_r (driver_dir, &dbi_instance);
    1092                 :            : #else
    1093                 :            :     num_drivers = dbi_initialize (driver_dir);
    1094                 :            : #endif
    1095                 :         57 :     if (num_drivers <= 0)
    1096                 :            :     {
    1097                 :            : #if HAVE_LIBDBI_R
    1098                 :          0 :         if (dbi_instance)
    1099                 :          0 :             return;
    1100                 :            : #endif
    1101                 :          0 :         gchar *libdir = gnc_path_get_libdir ();
    1102                 :          0 :         gchar *dir = g_build_filename (libdir, "dbd", nullptr);
    1103                 :          0 :         g_free (libdir);
    1104                 :            : #if HAVE_LIBDBI_R
    1105                 :          0 :         num_drivers = dbi_initialize_r (dir, &dbi_instance);
    1106                 :            : #else
    1107                 :            :         num_drivers = dbi_initialize (dir);
    1108                 :            : #endif
    1109                 :          0 :         g_free (dir);
    1110                 :            :     }
    1111                 :         57 :     if (num_drivers <= 0)
    1112                 :            :     {
    1113                 :          0 :         PWARN ("No DBD drivers found\n");
    1114                 :            :     }
    1115                 :            :     else
    1116                 :            :     {
    1117                 :         57 :         dbi_driver driver = nullptr;
    1118                 :         57 :         PINFO ("%d DBD drivers found\n", num_drivers);
    1119                 :            : 
    1120                 :            :         do
    1121                 :            :         {
    1122                 :            : #if HAVE_LIBDBI_R
    1123                 :        114 :             driver = dbi_driver_list_r (driver, dbi_instance);
    1124                 :            : #else
    1125                 :            :             driver = dbi_driver_list (driver);
    1126                 :            : #endif
    1127                 :            : 
    1128                 :        114 :             if (driver != nullptr)
    1129                 :            :             {
    1130                 :         57 :                 const gchar* name = dbi_driver_get_name (driver);
    1131                 :            : 
    1132                 :         57 :                 PINFO ("Driver: %s\n", name);
    1133                 :         57 :                 if (strcmp (name, "sqlite3") == 0)
    1134                 :            :                 {
    1135                 :         57 :                     have_sqlite3_driver = TRUE;
    1136                 :            :                 }
    1137                 :          0 :                 else if (strcmp (name, "mysql") == 0)
    1138                 :            :                 {
    1139                 :          0 :                     have_mysql_driver = TRUE;
    1140                 :            :                 }
    1141                 :          0 :                 else if (strcmp (name, "pgsql") == 0)
    1142                 :            :                 {
    1143                 :          0 :                     have_pgsql_driver = TRUE;
    1144                 :            :                 }
    1145                 :            :             }
    1146                 :            :         }
    1147                 :        114 :         while (driver != nullptr);
    1148                 :            :     }
    1149                 :            : 
    1150                 :         57 :     if (have_sqlite3_driver)
    1151                 :            :     {
    1152                 :         57 :         const char* name = "GnuCash Libdbi (SQLITE3) Backend";
    1153                 :         57 :         auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, FILE_URI_TYPE});
    1154                 :         57 :         qof_backend_register_provider(std::move(prov));
    1155                 :         57 :         prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_SQLITE>{name, SQLITE3_URI_TYPE});
    1156                 :         57 :         qof_backend_register_provider(std::move(prov));
    1157                 :         57 :     }
    1158                 :            : 
    1159                 :         57 :     if (have_mysql_driver)
    1160                 :            :     {
    1161                 :          0 :         const char *name = "GnuCash Libdbi (MYSQL) Backend";
    1162                 :          0 :         auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_MYSQL>{name, "mysql"});
    1163                 :          0 :         qof_backend_register_provider(std::move(prov));
    1164                 :          0 :     }
    1165                 :            : 
    1166                 :         57 :     if (have_pgsql_driver)
    1167                 :            :     {
    1168                 :          0 :         const char* name = "GnuCash Libdbi (POSTGRESQL) Backend";
    1169                 :          0 :         auto prov = QofBackendProvider_ptr(new QofDbiBackendProvider<DbType::DBI_PGSQL>{name, "postgres"});
    1170                 :          0 :         qof_backend_register_provider(std::move(prov));
    1171                 :          0 :     }
    1172                 :            : 
    1173                 :            :     /* If needed, set log level to DEBUG so that SQl statements will be put into
    1174                 :            :        the gnucash.trace file. */
    1175                 :            :     /*    qof_log_set_level( log_module, QOF_LOG_DEBUG ); */
    1176                 :            : }
    1177                 :            : 
    1178                 :            : #ifndef GNC_NO_LOADABLE_MODULES
    1179                 :            : G_MODULE_EXPORT void
    1180                 :         58 : qof_backend_module_init (void)
    1181                 :            : {
    1182                 :         58 :     gnc_module_init_backend_dbi ();
    1183                 :         58 : }
    1184                 :            : 
    1185                 :            : G_MODULE_EXPORT void
    1186                 :          0 : qof_backend_module_finalize (void)
    1187                 :            : {
    1188                 :          0 :     gnc_module_finalize_backend_dbi ();
    1189                 :          0 : }
    1190                 :            : #endif /* GNC_NO_LOADABLE_MODULES */
    1191                 :            : 
    1192                 :            : void
    1193                 :          0 : gnc_module_finalize_backend_dbi (void)
    1194                 :            : {
    1195                 :            : #if HAVE_LIBDBI_R
    1196                 :          0 :     if (dbi_instance)
    1197                 :            :     {
    1198                 :          0 :         dbi_shutdown_r (dbi_instance);
    1199                 :          0 :         dbi_instance = nullptr;
    1200                 :            :     }
    1201                 :            : #else
    1202                 :            :     dbi_shutdown ();
    1203                 :            : #endif
    1204                 :          0 : }
    1205                 :            : 
    1206                 :            : /* --------------------------------------------------------- */
    1207                 :            : 
    1208                 :            : static void
    1209                 :          0 : log_failed_field(dbi_result result, const char* fieldname)
    1210                 :            : {
    1211                 :          0 :     auto idx = dbi_result_get_field_idx(result, fieldname);
    1212                 :          0 :     if (dbi_result_field_is_null_idx(result, idx))
    1213                 :          0 :         PERR("Result field %s is NULL", fieldname);
    1214                 :            :     else
    1215                 :            :     {
    1216                 :          0 :         auto type = dbi_result_get_field_type_idx(result, idx);
    1217                 :          0 :         auto attribs = dbi_result_get_field_attribs_idx(result, idx);
    1218                 :          0 :         PERR("Result field %s has type %d and attribs %d",
    1219                 :            :              fieldname, type, attribs);
    1220                 :            :     }
    1221                 :          0 : }
    1222                 :            : 
    1223                 :            : /** Users discovered a bug in some distributions of libdbi, where if
    1224                 :            :  * it is compiled on certain versions of gcc with the -ffast-math
    1225                 :            :  * compiler option it fails to correctly handle saving of 64-bit
    1226                 :            :  * values. This function tests for the problem.
    1227                 :            :  * @param: conn: The just-opened dbi_conn
    1228                 :            :  * @returns: GNC_DBI_PASS if the dbi library is safe to use,
    1229                 :            :  * GNC_DBI_FAIL_SETUP if the test could not be completed, or
    1230                 :            :  * GNC_DBI_FAIL_TEST if the bug was found.
    1231                 :            :  */
    1232                 :            : static GncDbiTestResult
    1233                 :          0 : dbi_library_test (dbi_conn conn)
    1234                 :            : {
    1235                 :          0 :     int64_t testlonglong = -9223372036854775807LL, resultlonglong = 0;
    1236                 :          0 :     uint64_t testulonglong = 9223372036854775807LLU, resultulonglong = 0;
    1237                 :          0 :     double testdouble = 1.7976921348623157E+307, resultdouble = 0.0;
    1238                 :            :     dbi_result result;
    1239                 :          0 :     GncDbiTestResult retval = GNC_DBI_PASS;
    1240                 :            : 
    1241                 :          0 :     result = dbi_conn_query (conn, "CREATE TEMPORARY TABLE numtest "
    1242                 :            :                              "( test_int BIGINT, test_unsigned BIGINT,"
    1243                 :            :                              " test_double FLOAT8 )");
    1244                 :          0 :     if (result == nullptr)
    1245                 :            :     {
    1246                 :          0 :         PWARN ("Test_DBI_Library: Create table failed");
    1247                 :          0 :         return GNC_DBI_FAIL_SETUP;
    1248                 :            :     }
    1249                 :          0 :     dbi_result_free (result);
    1250                 :          0 :     std::stringstream querystr;
    1251                 :          0 :     querystr << "INSERT INTO numtest VALUES (" << testlonglong <<
    1252                 :          0 :         ", " << testulonglong << ", " << std::setprecision(12) <<
    1253                 :          0 :         testdouble << ")";
    1254                 :          0 :     auto query = querystr.str();
    1255                 :          0 :     result = dbi_conn_query (conn, query.c_str());
    1256                 :          0 :     if (result == nullptr)
    1257                 :            :     {
    1258                 :          0 :         PWARN ("Test_DBI_Library: Failed to insert test row into table");
    1259                 :          0 :         return GNC_DBI_FAIL_SETUP;
    1260                 :            :     }
    1261                 :          0 :     dbi_result_free (result);
    1262                 :          0 :     auto locale = gnc_push_locale (LC_NUMERIC, "C");
    1263                 :          0 :     result = dbi_conn_query (conn, "SELECT * FROM numtest");
    1264                 :          0 :     if (result == nullptr || !dbi_result_get_numrows(result))
    1265                 :            :     {
    1266                 :            :         const char* errmsg;
    1267                 :          0 :         dbi_conn_error (conn, &errmsg);
    1268                 :          0 :         PWARN ("Test_DBI_Library: Failed to retrieve test row into table: %s",
    1269                 :            :                errmsg);
    1270                 :          0 :         dbi_conn_query (conn, "DROP TABLE numtest");
    1271                 :          0 :         gnc_pop_locale (LC_NUMERIC, locale);
    1272                 :          0 :         return GNC_DBI_FAIL_SETUP;
    1273                 :            :     }
    1274                 :          0 :     while (dbi_result_next_row (result))
    1275                 :            :     {
    1276                 :          0 :         resultlonglong = dbi_result_get_longlong (result, "test_int");
    1277                 :          0 :         if (!resultlonglong)
    1278                 :          0 :             log_failed_field(result, "test_int");
    1279                 :          0 :         resultulonglong = dbi_result_get_ulonglong (result, "test_unsigned");
    1280                 :          0 :         if (!resultulonglong)
    1281                 :          0 :             log_failed_field(result, "test_unsigned");
    1282                 :          0 :         resultdouble = dbi_result_get_double (result, "test_double");
    1283                 :          0 :         if (!resultdouble)
    1284                 :          0 :             log_failed_field(result, "test_double");
    1285                 :            :     }
    1286                 :          0 :     dbi_conn_query (conn, "DROP TABLE numtest");
    1287                 :          0 :     gnc_pop_locale (LC_NUMERIC, locale);
    1288                 :          0 :     if (testlonglong != resultlonglong)
    1289                 :            :     {
    1290                 :          0 :         PWARN ("Test_DBI_Library: LongLong Failed %" PRId64 " != % " PRId64,
    1291                 :            :                testlonglong, resultlonglong);
    1292                 :          0 :         retval = GNC_DBI_FAIL_TEST;
    1293                 :            :     }
    1294                 :          0 :     if (testulonglong != resultulonglong)
    1295                 :            :     {
    1296                 :          0 :         PWARN ("Test_DBI_Library: Unsigned longlong Failed %" PRIu64 " != %"
    1297                 :            :                PRIu64, testulonglong, resultulonglong);
    1298                 :          0 :         retval = GNC_DBI_FAIL_TEST;
    1299                 :            :     }
    1300                 :            :     /* A bug in libdbi stores only 7 digits of precision */
    1301                 :          0 :     if (testdouble >= resultdouble + 0.000001e307 ||
    1302                 :          0 :         testdouble <= resultdouble - 0.000001e307)
    1303                 :            :     {
    1304                 :          0 :         PWARN ("Test_DBI_Library: Double Failed %17e != %17e",
    1305                 :            :                testdouble, resultdouble);
    1306                 :          0 :         retval = GNC_DBI_FAIL_TEST;
    1307                 :            :     }
    1308                 :          0 :     return retval;
    1309                 :          0 : }
    1310                 :            : 
    1311                 :            : template <DbType Type> bool
    1312                 :          0 : GncDbiBackend<Type>::conn_test_dbi_library(dbi_conn conn)
    1313                 :            : {
    1314                 :          0 :     auto result = dbi_library_test (conn);
    1315                 :          0 :     switch (result)
    1316                 :            :     {
    1317                 :          0 :         case GNC_DBI_PASS:
    1318                 :          0 :             break;
    1319                 :            : 
    1320                 :          0 :         case GNC_DBI_FAIL_SETUP:
    1321                 :          0 :             set_error(ERR_SQL_DBI_UNTESTABLE);
    1322                 :          0 :             set_message ("DBI library large number test incomplete");
    1323                 :          0 :             break;
    1324                 :            : 
    1325                 :          0 :         case GNC_DBI_FAIL_TEST:
    1326                 :          0 :             set_error (ERR_SQL_BAD_DBI);
    1327                 :          0 :             set_message ("DBI library fails large number test");
    1328                 :          0 :             break;
    1329                 :            :     }
    1330                 :          0 :     return result == GNC_DBI_PASS;
    1331                 :            : }
    1332                 :            : 
    1333                 :            : /* ========================== END OF FILE ===================== */

Generated by: LCOV version 1.14