LCOV - code coverage report
Current view: top level - libgnucash/backend/dbi - gnc-dbisqlconnection.cpp (source / functions) Coverage Total Hit
Test: gnucash.info Lines: 0.0 % 404 0
Test Date: 2026-11-02 18:19:43 Functions: 0.0 % 29 0
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /********************************************************************
       2              :  * gnc-dbisqlconnection.cpp: Encapsulate libdbi dbi_conn            *
       3              :  *                                                                  *
       4              :  * Copyright 2016 John Ralls <jralls@ceridwen.us>                   *
       5              :  *                                                                  *
       6              :  * This program is free software; you can redistribute it and/or    *
       7              :  * modify it under the terms of the GNU General Public License as   *
       8              :  * published by the Free Software Foundation; either version 2 of   *
       9              :  * the License, or (at your option) any later version.              *
      10              :  *                                                                  *
      11              :  * This program is distributed in the hope that it will be useful,  *
      12              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
      13              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
      14              :  * GNU General Public License for more details.                     *
      15              :  *                                                                  *
      16              :  * You should have received a copy of the GNU General Public License*
      17              :  * along with this program; if not, contact:                        *
      18              :  *                                                                  *
      19              :  * Free Software Foundation           Voice:  +1-617-542-5942       *
      20              :  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
      21              :  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
      22              : \********************************************************************/
      23              : 
      24              : #include <guid.hpp>
      25              : #include <config.h>
      26              : #include <platform.h>
      27              : #include <gnc-locale-utils.h>
      28              : 
      29              : #include <string>
      30              : #include <regex>
      31              : #include <sstream>
      32              : 
      33              : #include "gnc-dbisqlconnection.hpp"
      34              : 
      35              : static QofLogModule log_module = G_LOG_DOMAIN;
      36              : // gnc-dbiproviderimpl.hpp has templates that need log_module defined.
      37              : #include "gnc-dbiproviderimpl.hpp"
      38              : 
      39              : static const unsigned int DBI_MAX_CONN_ATTEMPTS = 5;
      40              : const std::string lock_table = "gnclock";
      41              : 
      42              : /* --------------------------------------------------------- */
      43              : class GncDbiSqlStatement : public GncSqlStatement
      44              : {
      45              : public:
      46            0 :     GncDbiSqlStatement(const std::string& sql) :
      47            0 :         m_sql {sql} {}
      48            0 :     ~GncDbiSqlStatement() {}
      49              :     const char* to_sql() const override;
      50              :     void add_where_cond(QofIdTypeConst, const PairVec&) override;
      51              : 
      52              : private:
      53              :     std::string m_sql;
      54              : };
      55              : 
      56              : 
      57              : const char*
      58            0 : GncDbiSqlStatement::to_sql() const
      59              : {
      60            0 :     return m_sql.c_str();
      61              : }
      62              : 
      63              : void
      64            0 : GncDbiSqlStatement::add_where_cond(QofIdTypeConst type_name,
      65              :                                    const PairVec& col_values)
      66              : {
      67            0 :     m_sql += " WHERE ";
      68            0 :     for (auto colpair : col_values)
      69              :     {
      70            0 :         if (colpair != *col_values.begin())
      71            0 :             m_sql += " AND ";
      72            0 :         if (colpair.second == "NULL")
      73            0 :             m_sql += colpair.first + " IS " + colpair.second;
      74              :         else
      75            0 :             m_sql += colpair.first + " = " + colpair.second;
      76            0 :     }
      77            0 : }
      78              : 
      79            0 : GncDbiSqlConnection::GncDbiSqlConnection (DbType type, QofBackend* qbe,
      80            0 :                                           dbi_conn conn, SessionOpenMode mode) :
      81            0 :     m_qbe{qbe}, m_conn{conn},
      82            0 :     m_provider{type == DbType::DBI_SQLITE ?
      83              :             make_dbi_provider<DbType::DBI_SQLITE>() :
      84            0 :             type == DbType::DBI_MYSQL ?
      85              :             make_dbi_provider<DbType::DBI_MYSQL>() :
      86              :             make_dbi_provider<DbType::DBI_PGSQL>()},
      87            0 :     m_conn_ok{true}, m_last_error{ERR_BACKEND_NO_ERR}, m_error_repeat{0},
      88            0 :     m_retry{false}, m_sql_savepoint{0}, m_readonly{false}
      89              : {
      90            0 :     if (mode == SESSION_READ_ONLY)
      91            0 :         m_readonly = true;
      92            0 :     else if (!lock_database(mode == SESSION_BREAK_LOCK))
      93            0 :         throw std::runtime_error("Failed to lock database!");
      94            0 :     if (!check_and_rollback_failed_save())
      95              :     {
      96            0 :         unlock_database();
      97            0 :         throw std::runtime_error("A failed safe-save was detected and rolling it back failed.");
      98              :     }
      99            0 : }
     100              : 
     101              : bool
     102            0 : GncDbiSqlConnection::lock_database (bool break_lock)
     103              : {
     104              :     const char *errstr;
     105              :     /* Protect everything with a single transaction to prevent races */
     106            0 :     if (!begin_transaction())
     107            0 :         return false;
     108            0 :     auto tables = m_provider->get_table_list(m_conn, lock_table);
     109            0 :     if (tables.empty())
     110              :     {
     111            0 :         auto result = dbi_conn_queryf (m_conn,
     112              :                                        "CREATE TABLE %s ( Hostname varchar(%d), PID int )",
     113              :                                        lock_table.c_str(),
     114              :                                        GNC_HOST_NAME_MAX);
     115            0 :         if (result)
     116              :         {
     117            0 :             dbi_result_free (result);
     118            0 :             result = nullptr;
     119              :         }
     120            0 :         if (dbi_conn_error (m_conn, &errstr))
     121              :         {
     122            0 :             PERR ("Error %s creating lock table", errstr);
     123            0 :             qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     124            0 :             return false;
     125              :         }
     126              :     }
     127              : 
     128              :     /* Check for an existing entry; delete it if break_lock is true, otherwise fail */
     129              :     char hostname[ GNC_HOST_NAME_MAX + 1 ];
     130            0 :     auto result = dbi_conn_queryf (m_conn, "SELECT * FROM %s",
     131              :                                    lock_table.c_str());
     132            0 :     if (result && dbi_result_get_numrows (result))
     133              :     {
     134            0 :         dbi_result_free (result);
     135            0 :         result = nullptr;
     136            0 :         if (!break_lock)
     137              :         {
     138            0 :             qof_backend_set_error (m_qbe, ERR_BACKEND_LOCKED);
     139              :             /* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */
     140            0 :             rollback_transaction();
     141            0 :             return false;
     142              :         }
     143            0 :         result = dbi_conn_queryf (m_conn, "DELETE FROM %s", lock_table.c_str());
     144            0 :         if (!result)
     145              :         {
     146            0 :             qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     147            0 :             m_qbe->set_message("Failed to delete lock record");
     148            0 :             rollback_transaction();
     149            0 :             return false;
     150              :         }
     151            0 :         dbi_result_free (result);
     152            0 :         result = nullptr;
     153              :     }
     154              :     /* Add an entry and commit the transaction */
     155            0 :     memset (hostname, 0, sizeof (hostname));
     156            0 :     gethostname (hostname, GNC_HOST_NAME_MAX);
     157            0 :     result = dbi_conn_queryf (m_conn,
     158              :                               "INSERT INTO %s VALUES ('%s', '%d')",
     159            0 :                               lock_table.c_str(), hostname, (int)GETPID ());
     160            0 :     if (!result)
     161              :     {
     162            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     163            0 :         m_qbe->set_message("Failed to create lock record");
     164            0 :         rollback_transaction();
     165            0 :         return false;
     166              :     }
     167            0 :     dbi_result_free (result);
     168            0 :     return commit_transaction();
     169            0 : }
     170              : 
     171              : void
     172            0 : GncDbiSqlConnection::unlock_database ()
     173              : {
     174            0 :     if (m_conn == nullptr) return;
     175            0 :     if (m_readonly) return;
     176            0 :     auto dbi_error{dbi_conn_error (m_conn, nullptr)};
     177            0 :     g_return_if_fail (dbi_error == DBI_ERROR_NONE || dbi_error == DBI_ERROR_BADIDX);
     178              : 
     179            0 :     auto tables = m_provider->get_table_list (m_conn, lock_table);
     180            0 :     if (tables.empty())
     181              :     {
     182            0 :         PWARN ("No lock table in database, so not unlocking it.");
     183            0 :         return;
     184              :     }
     185            0 :     if (begin_transaction())
     186              :     {
     187              :         /* Delete the entry if it's our hostname and PID */
     188              :         char hostname[ GNC_HOST_NAME_MAX + 1 ];
     189              : 
     190            0 :         memset (hostname, 0, sizeof (hostname));
     191            0 :         gethostname (hostname, GNC_HOST_NAME_MAX);
     192            0 :         auto result = dbi_conn_queryf (m_conn,
     193              :                                        "SELECT * FROM %s WHERE Hostname = '%s' "
     194              :                                        "AND PID = '%d'", lock_table.c_str(),
     195              :                                        hostname,
     196            0 :                                        (int)GETPID ());
     197            0 :         if (result && dbi_result_get_numrows (result))
     198              :         {
     199            0 :             if (result)
     200              :             {
     201            0 :                 dbi_result_free (result);
     202            0 :                 result = nullptr;
     203              :             }
     204            0 :             result = dbi_conn_queryf (m_conn, "DELETE FROM %s",
     205              :                                       lock_table.c_str());
     206            0 :             if (!result)
     207              :             {
     208            0 :                 PERR ("Failed to delete the lock entry");
     209            0 :                 m_qbe->set_error (ERR_BACKEND_SERVER_ERR);
     210            0 :                 rollback_transaction();
     211            0 :                 return;
     212              :             }
     213              :             else
     214              :             {
     215            0 :                 dbi_result_free (result);
     216            0 :                 result = nullptr;
     217              :             }
     218            0 :             commit_transaction();
     219            0 :             return;
     220              :         }
     221            0 :         rollback_transaction();
     222            0 :         PWARN ("There was no lock entry in the Lock table");
     223            0 :         return;
     224              :     }
     225            0 :     PWARN ("Unable to get a lock on LOCK, so failed to clear the lock entry.");
     226            0 :     m_qbe->set_error (ERR_BACKEND_SERVER_ERR);
     227            0 : }
     228              : 
     229              : bool
     230            0 : GncDbiSqlConnection::check_and_rollback_failed_save()
     231              : {
     232            0 :     auto backup_tables = m_provider->get_table_list(m_conn, "%back");
     233            0 :     if (backup_tables.empty())
     234            0 :         return true;
     235            0 :     auto merge_tables = m_provider->get_table_list(m_conn, "%_merge");
     236            0 :     if (!merge_tables.empty())
     237              :     {
     238            0 :         PERR("Merge tables exist in the database indicating a previous"
     239              :              "attempt to recover from a failed safe-save. Automatic"
     240              :              "recovery is beyond GnuCash's ability, you must recover"
     241              :              "by hand or restore from a good backup.");
     242            0 :         return false;
     243              :     }
     244            0 :     return table_operation(recover);
     245            0 : }
     246              : 
     247            0 : GncDbiSqlConnection::~GncDbiSqlConnection()
     248              : {
     249            0 :     if (m_conn)
     250              :     {
     251            0 :         unlock_database();
     252            0 :         dbi_conn_close(m_conn);
     253            0 :         m_conn = nullptr;
     254              :     }
     255            0 : }
     256              : 
     257              : GncSqlResultPtr
     258            0 : GncDbiSqlConnection::execute_select_statement (const GncSqlStatementPtr& stmt)
     259              :     noexcept
     260              : {
     261              :     dbi_result result;
     262              : 
     263            0 :     DEBUG ("SQL: %s\n", stmt->to_sql());
     264            0 :     auto locale = gnc_push_locale (LC_NUMERIC, "C");
     265              :     do
     266              :     {
     267            0 :         init_error ();
     268            0 :         result = dbi_conn_query (m_conn, stmt->to_sql());
     269              :     }
     270            0 :     while (m_retry);
     271            0 :     if (result == nullptr)
     272              :     {
     273            0 :         PERR ("Error executing SQL %s\n", stmt->to_sql());
     274            0 :         if(m_last_error)
     275            0 :             m_qbe->set_error(m_last_error);
     276              :         else
     277            0 :             m_qbe->set_error(ERR_BACKEND_SERVER_ERR);
     278              :     }
     279            0 :     gnc_pop_locale (LC_NUMERIC, locale);
     280            0 :     return GncSqlResultPtr(new GncDbiSqlResult (this, result));
     281            0 : }
     282              : 
     283              : int
     284            0 : GncDbiSqlConnection::execute_nonselect_statement (const GncSqlStatementPtr& stmt)
     285              :     noexcept
     286              : {
     287              :     dbi_result result;
     288              : 
     289            0 :     DEBUG ("SQL: %s\n", stmt->to_sql());
     290              :     do
     291              :     {
     292            0 :         init_error ();
     293            0 :         result = dbi_conn_query (m_conn, stmt->to_sql());
     294              :     }
     295            0 :     while (m_retry);
     296            0 :     if (result == nullptr && m_last_error)
     297              :     {
     298            0 :         PERR ("Error executing SQL %s\n", stmt->to_sql());
     299            0 :         if(m_last_error)
     300            0 :             m_qbe->set_error(m_last_error);
     301              :         else
     302            0 :             m_qbe->set_error(ERR_BACKEND_SERVER_ERR);
     303            0 :         return -1;
     304              :     }
     305            0 :     if (!result)
     306            0 :         return 0;
     307            0 :     auto num_rows = (gint)dbi_result_get_numrows_affected (result);
     308            0 :     auto status = dbi_result_free (result);
     309            0 :     if (status < 0)
     310              :     {
     311            0 :         PERR ("Error in dbi_result_free() result\n");
     312            0 :         if(m_last_error)
     313            0 :             m_qbe->set_error(m_last_error);
     314              :         else
     315            0 :             m_qbe->set_error(ERR_BACKEND_SERVER_ERR);
     316              :     }
     317            0 :     return num_rows;
     318              : }
     319              : 
     320              : GncSqlStatementPtr
     321            0 : GncDbiSqlConnection::create_statement_from_sql (const std::string& sql)
     322              :     const noexcept
     323              : {
     324            0 :     return std::unique_ptr<GncSqlStatement>{new GncDbiSqlStatement (sql)};
     325              : }
     326              : 
     327              : bool
     328            0 : GncDbiSqlConnection::does_table_exist (const std::string& table_name)
     329              :     const noexcept
     330              : {
     331            0 :     return ! m_provider->get_table_list(m_conn, table_name).empty();
     332              : }
     333              : 
     334              : bool
     335            0 : GncDbiSqlConnection::begin_transaction () noexcept
     336              : {
     337              :     dbi_result result;
     338              : 
     339            0 :     DEBUG ("BEGIN\n");
     340              : 
     341            0 :     if (!verify ())
     342              :     {
     343            0 :         PERR ("gnc_dbi_verify_conn() failed\n");
     344            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     345            0 :         return false;
     346              :     }
     347              : 
     348              :     do
     349              :     {
     350            0 :         init_error ();
     351            0 :         if (m_sql_savepoint == 0)
     352            0 :             result = dbi_conn_queryf (m_conn, "BEGIN");
     353              :         else
     354              :         {
     355            0 :             std::ostringstream savepoint;
     356            0 :             savepoint << "savepoint_" << m_sql_savepoint;
     357            0 :             result = dbi_conn_queryf(m_conn, "SAVEPOINT %s",
     358            0 :                                      savepoint.str().c_str());
     359            0 :         }
     360              :     }
     361            0 :     while (m_retry);
     362              : 
     363            0 :     if (!result)
     364              :     {
     365            0 :         PERR ("BEGIN transaction failed()\n");
     366            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     367            0 :         return false;
     368              :     }
     369            0 :     if (dbi_result_free (result) < 0)
     370              :     {
     371            0 :         PERR ("Error in dbi_result_free() result\n");
     372            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     373            0 :         return false;
     374              :     }
     375            0 :     ++m_sql_savepoint;
     376            0 :     return true;
     377              : }
     378              : 
     379              : bool
     380            0 : GncDbiSqlConnection::rollback_transaction () noexcept
     381              : {
     382            0 :     DEBUG ("ROLLBACK\n");
     383            0 :     if (m_sql_savepoint == 0) return false;
     384              :     dbi_result result;
     385            0 :     if (m_sql_savepoint == 1)
     386            0 :         result = dbi_conn_query (m_conn, "ROLLBACK");
     387              :     else
     388              :     {
     389            0 :         std::ostringstream savepoint;
     390            0 :         savepoint << "savepoint_" << m_sql_savepoint - 1;
     391            0 :         result = dbi_conn_queryf(m_conn, "ROLLBACK TO SAVEPOINT %s",
     392            0 :                                  savepoint.str().c_str());
     393            0 :     }
     394            0 :     if (!result)
     395              :     {
     396            0 :         PERR ("Error in conn_rollback_transaction()\n");
     397            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     398            0 :         return false;
     399              :     }
     400              : 
     401            0 :     if (dbi_result_free (result) < 0)
     402              :     {
     403            0 :         PERR ("Error in dbi_result_free() result\n");
     404            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     405            0 :         return false;
     406              :     }
     407              : 
     408            0 :     --m_sql_savepoint;
     409            0 :     return true;
     410              : }
     411              : 
     412              : bool
     413            0 : GncDbiSqlConnection::commit_transaction () noexcept
     414              : {
     415            0 :     DEBUG ("COMMIT\n");
     416            0 :     if (m_sql_savepoint == 0) return false;
     417              :     dbi_result result;
     418            0 :     if (m_sql_savepoint == 1)
     419            0 :         result = dbi_conn_queryf (m_conn, "COMMIT");
     420              :     else
     421              :     {
     422            0 :         std::ostringstream savepoint;
     423            0 :         savepoint << "savepoint_" << m_sql_savepoint - 1;
     424            0 :         result = dbi_conn_queryf(m_conn, "RELEASE SAVEPOINT %s",
     425            0 :                                  savepoint.str().c_str());
     426            0 :     }
     427              : 
     428            0 :     if (!result)
     429              :     {
     430            0 :         PERR ("Error in conn_commit_transaction()\n");
     431            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     432            0 :         return false;
     433              :     }
     434              : 
     435            0 :     if (dbi_result_free (result) < 0)
     436              :     {
     437            0 :         PERR ("Error in dbi_result_free() result\n");
     438            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     439            0 :         return false;
     440              :     }
     441            0 :     --m_sql_savepoint;
     442            0 :     return true;
     443              : }
     444              : 
     445              : 
     446              : bool
     447            0 : GncDbiSqlConnection::create_table (const std::string& table_name,
     448              :                                    const ColVec& info_vec) const noexcept
     449              : {
     450            0 :     std::string ddl;
     451            0 :     unsigned int col_num = 0;
     452              : 
     453            0 :     ddl += "CREATE TABLE " + table_name + "(";
     454            0 :     for (auto const& info : info_vec)
     455              :     {
     456            0 :         if (col_num++ != 0)
     457              :         {
     458            0 :             ddl += ", ";
     459              :         }
     460            0 :         m_provider->append_col_def (ddl, info);
     461              :     }
     462            0 :     ddl += ")";
     463              : 
     464            0 :     if (ddl.empty())
     465            0 :         return false;
     466              : 
     467            0 :     DEBUG ("SQL: %s\n", ddl.c_str());
     468            0 :     auto result = dbi_conn_query (m_conn, ddl.c_str());
     469            0 :     auto status = dbi_result_free (result);
     470            0 :     if (status < 0)
     471              :     {
     472            0 :         PERR ("Error in dbi_result_free() result\n");
     473            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     474              :     }
     475              : 
     476            0 :     return true;
     477            0 : }
     478              : 
     479              : static std::string
     480            0 : create_index_ddl (const GncSqlConnection* conn, const std::string& index_name,
     481              :                   const std::string& table_name, const EntryVec& col_table)
     482              : {
     483            0 :     std::string ddl;
     484            0 :     ddl += "CREATE INDEX " + index_name + " ON " + table_name + "(";
     485            0 :     for (const auto& table_row : col_table)
     486              :     {
     487            0 :         if (table_row != *col_table.begin())
     488              :         {
     489            0 :             ddl =+ ", ";
     490              :         }
     491            0 :         ddl += table_row->name();
     492              :     }
     493            0 :     ddl += ")";
     494            0 :     return ddl;
     495            0 : }
     496              : 
     497              : bool
     498            0 : GncDbiSqlConnection::create_index(const std::string& index_name,
     499              :                                   const std::string& table_name,
     500              :                                   const EntryVec& col_table) const noexcept
     501              : {
     502            0 :     auto ddl = create_index_ddl (this, index_name, table_name, col_table);
     503            0 :     if (ddl.empty())
     504            0 :         return false;
     505            0 :     DEBUG ("SQL: %s\n", ddl.c_str());
     506            0 :     auto result = dbi_conn_query (m_conn, ddl.c_str());
     507            0 :     auto status = dbi_result_free (result);
     508            0 :     if (status < 0)
     509              :     {
     510            0 :         PERR ("Error in dbi_result_free() result\n");
     511            0 :         qof_backend_set_error (m_qbe, ERR_BACKEND_SERVER_ERR);
     512              :     }
     513              : 
     514            0 :     return true;
     515            0 : }
     516              : 
     517              : bool
     518            0 : GncDbiSqlConnection::add_columns_to_table(const std::string& table_name,
     519              :                                           const ColVec& info_vec)
     520              :     const noexcept
     521              : {
     522            0 :     auto ddl = add_columns_ddl(table_name, info_vec);
     523            0 :     if (ddl.empty())
     524            0 :         return false;
     525              : 
     526            0 :     DEBUG ("SQL: %s\n", ddl.c_str());
     527            0 :     auto result = dbi_conn_query (m_conn, ddl.c_str());
     528            0 :     auto status = dbi_result_free (result);
     529            0 :     if (status < 0)
     530              :     {
     531            0 :         PERR( "Error in dbi_result_free() result\n" );
     532            0 :         qof_backend_set_error(m_qbe, ERR_BACKEND_SERVER_ERR );
     533              :     }
     534              : 
     535            0 :     return true;
     536            0 : }
     537              : 
     538              : std::string
     539            0 : GncDbiSqlConnection::quote_string (const std::string& unquoted_str)
     540              :     const noexcept
     541              : {
     542              :     char* quoted_str;
     543              : 
     544            0 :     dbi_conn_quote_string_copy (m_conn, unquoted_str.c_str(),
     545              :                                 &quoted_str);
     546            0 :     if (quoted_str == nullptr)
     547            0 :         return std::string{""};
     548            0 :     std::string retval{quoted_str};
     549            0 :     free(quoted_str);
     550            0 :     return retval;
     551            0 : }
     552              : 
     553              : 
     554              : /** Check if the dbi connection is valid. If not attempt to re-establish it
     555              :  * Returns TRUE if there is a valid connection in the end or FALSE otherwise
     556              :  */
     557              : bool
     558            0 : GncDbiSqlConnection::verify () noexcept
     559              : {
     560            0 :     if (m_conn_ok)
     561            0 :         return true;
     562              : 
     563              :     /* We attempt to connect only once here. The error function will
     564              :      * automatically re-attempt up until DBI_MAX_CONN_ATTEMPTS time to connect
     565              :      * if this call fails.  After all these attempts, conn_ok will indicate if
     566              :      * there is a valid connection or not.
     567              :      */
     568            0 :     init_error ();
     569            0 :     m_conn_ok = true;
     570            0 :     (void)dbi_conn_connect (m_conn);
     571              : 
     572            0 :     return m_conn_ok;
     573              : }
     574              : 
     575              : bool
     576            0 : GncDbiSqlConnection::retry_connection(const char* msg)
     577              :     noexcept
     578              : {
     579            0 :     while (m_retry && m_error_repeat <= DBI_MAX_CONN_ATTEMPTS)
     580              :     {
     581            0 :         m_conn_ok = false;
     582            0 :         if (dbi_conn_connect(m_conn) == 0)
     583              :         {
     584            0 :             init_error();
     585            0 :             m_conn_ok = true;
     586            0 :             return true;
     587              :         }
     588              : #ifdef G_OS_WIN32
     589              :         const guint backoff_msecs = 1;
     590              :         Sleep (backoff_msecs * 2 << ++m_error_repeat);
     591              : #else
     592            0 :         const guint backoff_usecs = 1000;
     593            0 :         usleep (backoff_usecs * 2 << ++m_error_repeat);
     594              : #endif
     595            0 :         PINFO ("DBI error: %s - Reconnecting...\n", msg);
     596              : 
     597              :     }
     598            0 :     PERR ("DBI error: %s - Giving up after %d consecutive attempts.\n", msg,
     599              :           DBI_MAX_CONN_ATTEMPTS);
     600            0 :     m_conn_ok = false;
     601            0 :     return false;
     602              : }
     603              : 
     604              : bool
     605            0 : GncDbiSqlConnection::rename_table(const std::string& old_name,
     606              :                                   const std::string& new_name)
     607              : {
     608            0 :     std::string sql = "ALTER TABLE " + old_name + " RENAME TO " + new_name;
     609            0 :     auto stmt = create_statement_from_sql(sql);
     610            0 :     return execute_nonselect_statement(stmt) >= 0;
     611            0 : }
     612              : 
     613              : bool
     614            0 : GncDbiSqlConnection::drop_table(const std::string& table)
     615              : {
     616            0 :     std::string sql = "DROP TABLE " + table;
     617            0 :     auto stmt = create_statement_from_sql(sql);
     618            0 :     return execute_nonselect_statement(stmt) >= 0;
     619            0 : }
     620              : 
     621              : bool
     622            0 : GncDbiSqlConnection::merge_tables(const std::string& table,
     623              :                                   const std::string& other)
     624              : {
     625            0 :     auto merge_table = table + "_merge";
     626            0 :     std::string sql = "CREATE TABLE " + merge_table + " AS SELECT * FROM " +
     627            0 :         table + " UNION SELECT * FROM " + other;
     628            0 :     auto stmt = create_statement_from_sql(sql);
     629            0 :     if (execute_nonselect_statement(stmt) < 0)
     630            0 :         return false;
     631            0 :     sql = std::string("DELETE FROM ") + table;
     632            0 :     stmt = create_statement_from_sql(sql);
     633            0 :     if (execute_nonselect_statement(stmt) < 0)
     634            0 :         return false;
     635            0 :     sql = std::string("INSERT INTO ") + table +
     636            0 :         " SELECT * FROM " + merge_table;
     637            0 :     stmt = create_statement_from_sql(sql);
     638            0 :     if (execute_nonselect_statement(stmt) < 0)
     639            0 :         return false;
     640            0 :     if (!drop_table(merge_table))
     641            0 :         return false;
     642            0 :     return drop_table(other);
     643            0 : }
     644              : 
     645              : /**
     646              :  * Perform a specified SQL operation on every table in a
     647              :  * database. Possible operations are:
     648              :  * * drop: to DROP all tables from the database
     649              :  * * empty: to DELETE all records from each table in the database.
     650              :  * * backup: Rename every table from "name" to "name_back"
     651              :  * * drop_backup: DROP the backup tables.
     652              :  * * rollback: DROP the new table "name" and rename "name_back" to
     653              :  *   "name", restoring the database to its previous state.
     654              :  *
     655              :  * The intent of the last two is to be able to move an existing table
     656              :  * aside, query its contents with a transformation (in 2.4.x this is
     657              :  * already done as the contents are loaded completely when a Qof
     658              :  * session is started), save them to a new table according to a new
     659              :  * database format, and finally drop the backup table; if there's an
     660              :  * error during the process, rollback allows returning the table to
     661              :  * its original state.
     662              :  *
     663              :  * @param sql_conn: The sql connection (via dbi) to which the
     664              :  * transactions will be sent
     665              :  * @param table_namess: StrVec of tables to operate on.
     666              :  * @param op: The operation to perform.
     667              :  * @return Success (TRUE) or failure.
     668              :  */
     669              : 
     670              : bool
     671            0 : GncDbiSqlConnection::table_operation(TableOpType op) noexcept
     672              : {
     673            0 :     auto backup_tables = m_provider->get_table_list(m_conn, "%_back");
     674            0 :     auto all_tables = m_provider->get_table_list(m_conn, "");
     675              :     /* No operations on the lock table */
     676            0 :     auto new_end = std::remove(all_tables.begin(), all_tables.end(), lock_table);
     677            0 :     all_tables.erase(new_end, all_tables.end());
     678            0 :     StrVec data_tables;
     679            0 :     data_tables.reserve(all_tables.size() - backup_tables.size());
     680            0 :     std::set_difference(all_tables.begin(), all_tables.end(),
     681              :                         backup_tables.begin(), backup_tables.end(),
     682              :                         std::back_inserter(data_tables));
     683            0 :     switch(op)
     684              :     {
     685            0 :     case backup:
     686            0 :         if (!backup_tables.empty())
     687              :         {
     688            0 :             PERR("Unable to backup database, an existing backup is present.");
     689            0 :             qof_backend_set_error(m_qbe, ERR_BACKEND_DATA_CORRUPT);
     690            0 :             return false;
     691              :         }
     692            0 :         for (auto table : data_tables)
     693            0 :             if (!rename_table(table, table +"_back"))
     694            0 :                 return false; /* Error, trigger rollback. */
     695            0 :         break;
     696            0 :     case drop_backup:
     697            0 :         for (auto table : backup_tables)
     698              :         {
     699            0 :             auto data_table = table.substr(0, table.find("_back"));
     700            0 :             if (std::find(data_tables.begin(), data_tables.end(),
     701            0 :                           data_table) != data_tables.end())
     702            0 :                 drop_table(table); /* Other table exists, OK. */
     703              :             else /* No data table, restore the backup */
     704            0 :                 rename_table(table, data_table);
     705            0 :         }
     706            0 :         break;
     707            0 :     case rollback:
     708            0 :         for (auto table : backup_tables)
     709              :         {
     710            0 :             auto data_table = table.substr(0, table.find("_back"));
     711            0 :             if (std::find(data_tables.begin(), data_tables.end(),
     712            0 :                           data_table) != data_tables.end())
     713            0 :                 drop_table(data_table); /* Other table exists, OK. */
     714            0 :             rename_table(table, data_table);
     715            0 :         }
     716            0 :         break;
     717            0 :     case recover:
     718            0 :         for (auto table : backup_tables)
     719              :         {
     720            0 :             auto data_table = table.substr(0, table.find("_back"));
     721            0 :             if (std::find(data_tables.begin(), data_tables.end(),
     722            0 :                           data_table) != data_tables.end())
     723              :             {
     724            0 :                 if (!merge_tables(data_table, table))
     725            0 :                     return false;
     726              :             }
     727              :             else
     728              :             {
     729            0 :                 if (!rename_table(table, data_table))
     730            0 :                     return false;
     731              :             }
     732            0 :         }
     733            0 :         break;
     734              :     }
     735            0 :    return true;
     736            0 : }
     737              : 
     738              : bool
     739            0 : GncDbiSqlConnection::drop_indexes() noexcept
     740              : {
     741            0 :     auto index_list = m_provider->get_index_list (m_conn);
     742            0 :     for (auto index : index_list)
     743              :     {
     744              :         const char* errmsg;
     745            0 :         m_provider->drop_index (m_conn, index);
     746            0 :         if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg))
     747              :         {
     748            0 :             PERR("Failed to drop indexes %s", errmsg);
     749            0 :             return false;
     750              :         }
     751            0 :     }
     752            0 :     return true;
     753            0 : }
     754              : 
     755              : std::string
     756            0 : GncDbiSqlConnection::add_columns_ddl(const std::string& table_name,
     757              :                                      const ColVec& info_vec) const noexcept
     758              : {
     759            0 :     std::string ddl;
     760              : 
     761            0 :     ddl += "ALTER TABLE " + table_name;
     762            0 :     for (auto const& info : info_vec)
     763              :     {
     764            0 :         if (info != *info_vec.begin())
     765              :         {
     766            0 :             ddl += ", ";
     767              :         }
     768            0 :         ddl += "ADD COLUMN ";
     769            0 :         m_provider->append_col_def (ddl, info);
     770              :     }
     771            0 :     return ddl;
     772              : }
        

Generated by: LCOV version 2.0-1