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 % 397 0
Test Date: 2025-02-07 16:25:45 Functions: 0.0 % 29 0
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     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 :     if (!drop_table(table))
     632                 :           0 :         return false;
     633                 :           0 :     if (!rename_table(merge_table, table))
     634                 :           0 :         return false;
     635                 :           0 :     return drop_table(other);
     636                 :           0 : }
     637                 :             : 
     638                 :             : /**
     639                 :             :  * Perform a specified SQL operation on every table in a
     640                 :             :  * database. Possible operations are:
     641                 :             :  * * drop: to DROP all tables from the database
     642                 :             :  * * empty: to DELETE all records from each table in the database.
     643                 :             :  * * backup: Rename every table from "name" to "name_back"
     644                 :             :  * * drop_backup: DROP the backup tables.
     645                 :             :  * * rollback: DROP the new table "name" and rename "name_back" to
     646                 :             :  *   "name", restoring the database to its previous state.
     647                 :             :  *
     648                 :             :  * The intent of the last two is to be able to move an existing table
     649                 :             :  * aside, query its contents with a transformation (in 2.4.x this is
     650                 :             :  * already done as the contents are loaded completely when a Qof
     651                 :             :  * session is started), save them to a new table according to a new
     652                 :             :  * database format, and finally drop the backup table; if there's an
     653                 :             :  * error during the process, rollback allows returning the table to
     654                 :             :  * its original state.
     655                 :             :  *
     656                 :             :  * @param sql_conn: The sql connection (via dbi) to which the
     657                 :             :  * transactions will be sent
     658                 :             :  * @param table_namess: StrVec of tables to operate on.
     659                 :             :  * @param op: The operation to perform.
     660                 :             :  * @return Success (TRUE) or failure.
     661                 :             :  */
     662                 :             : 
     663                 :             : bool
     664                 :           0 : GncDbiSqlConnection::table_operation(TableOpType op) noexcept
     665                 :             : {
     666                 :           0 :     auto backup_tables = m_provider->get_table_list(m_conn, "%_back");
     667                 :           0 :     auto all_tables = m_provider->get_table_list(m_conn, "");
     668                 :             :     /* No operations on the lock table */
     669                 :           0 :     auto new_end = std::remove(all_tables.begin(), all_tables.end(), lock_table);
     670                 :           0 :     all_tables.erase(new_end, all_tables.end());
     671                 :           0 :     StrVec data_tables;
     672                 :           0 :     data_tables.reserve(all_tables.size() - backup_tables.size());
     673                 :           0 :     std::set_difference(all_tables.begin(), all_tables.end(),
     674                 :             :                         backup_tables.begin(), backup_tables.end(),
     675                 :             :                         std::back_inserter(data_tables));
     676                 :           0 :     switch(op)
     677                 :             :     {
     678                 :           0 :     case backup:
     679                 :           0 :         if (!backup_tables.empty())
     680                 :             :         {
     681                 :           0 :             PERR("Unable to backup database, an existing backup is present.");
     682                 :           0 :             qof_backend_set_error(m_qbe, ERR_BACKEND_DATA_CORRUPT);
     683                 :           0 :             return false;
     684                 :             :         }
     685                 :           0 :         for (auto table : data_tables)
     686                 :           0 :             if (!rename_table(table, table +"_back"))
     687                 :           0 :                 return false; /* Error, trigger rollback. */
     688                 :           0 :         break;
     689                 :           0 :     case drop_backup:
     690                 :           0 :         for (auto table : backup_tables)
     691                 :             :         {
     692                 :           0 :             auto data_table = table.substr(0, table.find("_back"));
     693                 :           0 :             if (std::find(data_tables.begin(), data_tables.end(),
     694                 :           0 :                           data_table) != data_tables.end())
     695                 :           0 :                 drop_table(table); /* Other table exists, OK. */
     696                 :             :             else /* No data table, restore the backup */
     697                 :           0 :                 rename_table(table, data_table);
     698                 :           0 :         }
     699                 :           0 :         break;
     700                 :           0 :     case rollback:
     701                 :           0 :         for (auto table : backup_tables)
     702                 :             :         {
     703                 :           0 :             auto data_table = table.substr(0, table.find("_back"));
     704                 :           0 :             if (std::find(data_tables.begin(), data_tables.end(),
     705                 :           0 :                           data_table) != data_tables.end())
     706                 :           0 :                 drop_table(data_table); /* Other table exists, OK. */
     707                 :           0 :             rename_table(table, data_table);
     708                 :           0 :         }
     709                 :           0 :         break;
     710                 :           0 :     case recover:
     711                 :           0 :         for (auto table : backup_tables)
     712                 :             :         {
     713                 :           0 :             auto data_table = table.substr(0, table.find("_back"));
     714                 :           0 :             if (std::find(data_tables.begin(), data_tables.end(),
     715                 :           0 :                           data_table) != data_tables.end())
     716                 :             :             {
     717                 :           0 :                 if (!merge_tables(data_table, table))
     718                 :           0 :                     return false;
     719                 :             :             }
     720                 :             :             else
     721                 :             :             {
     722                 :           0 :                 if (!rename_table(table, data_table))
     723                 :           0 :                     return false;
     724                 :             :             }
     725                 :           0 :         }
     726                 :           0 :         break;
     727                 :             :     }
     728                 :           0 :    return true;
     729                 :           0 : }
     730                 :             : 
     731                 :             : bool
     732                 :           0 : GncDbiSqlConnection::drop_indexes() noexcept
     733                 :             : {
     734                 :           0 :     auto index_list = m_provider->get_index_list (m_conn);
     735                 :           0 :     for (auto index : index_list)
     736                 :             :     {
     737                 :             :         const char* errmsg;
     738                 :           0 :         m_provider->drop_index (m_conn, index);
     739                 :           0 :         if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg))
     740                 :             :         {
     741                 :           0 :             PERR("Failed to drop indexes %s", errmsg);
     742                 :           0 :             return false;
     743                 :             :         }
     744                 :           0 :     }
     745                 :           0 :     return true;
     746                 :           0 : }
     747                 :             : 
     748                 :             : std::string
     749                 :           0 : GncDbiSqlConnection::add_columns_ddl(const std::string& table_name,
     750                 :             :                                      const ColVec& info_vec) const noexcept
     751                 :             : {
     752                 :           0 :     std::string ddl;
     753                 :             : 
     754                 :           0 :     ddl += "ALTER TABLE " + table_name;
     755                 :           0 :     for (auto const& info : info_vec)
     756                 :             :     {
     757                 :           0 :         if (info != *info_vec.begin())
     758                 :             :         {
     759                 :           0 :             ddl += ", ";
     760                 :             :         }
     761                 :           0 :         ddl += "ADD COLUMN ";
     762                 :           0 :         m_provider->append_col_def (ddl, info);
     763                 :             :     }
     764                 :           0 :     return ddl;
     765                 :             : }
        

Generated by: LCOV version 2.0-1