27 #include <gnc-locale-utils.h> 33 #include "gnc-dbisqlconnection.hpp" 37 #include "gnc-dbiproviderimpl.hpp" 39 static const unsigned int DBI_MAX_CONN_ATTEMPTS = 5;
40 const std::string lock_table =
"gnclock";
49 const char* to_sql()
const override;
58 GncDbiSqlStatement::to_sql()
const 65 const PairVec& col_values)
68 for (
auto colpair : col_values)
70 if (colpair != *col_values.begin())
72 if (colpair.second ==
"NULL")
73 m_sql += colpair.first +
" IS " + colpair.second;
75 m_sql += colpair.first +
" = " + colpair.second;
79 GncDbiSqlConnection::GncDbiSqlConnection (DbType type,
QofBackend* qbe,
81 m_qbe{qbe}, m_conn{conn},
82 m_provider{type == DbType::DBI_SQLITE ?
83 make_dbi_provider<DbType::DBI_SQLITE>() :
84 type == DbType::DBI_MYSQL ?
85 make_dbi_provider<DbType::DBI_MYSQL>() :
86 make_dbi_provider<DbType::DBI_PGSQL>()},
87 m_conn_ok{
true}, m_last_error{ERR_BACKEND_NO_ERR}, m_error_repeat{0},
88 m_retry{
false}, m_sql_savepoint{0}, m_readonly{
false}
93 throw std::runtime_error(
"Failed to lock database!");
94 if (!check_and_rollback_failed_save())
97 throw std::runtime_error(
"A failed safe-save was detected and rolling it back failed.");
102 GncDbiSqlConnection::lock_database (
bool break_lock)
108 auto tables = m_provider->get_table_list(m_conn, lock_table);
111 auto result = dbi_conn_queryf (m_conn,
112 "CREATE TABLE %s ( Hostname varchar(%d), PID int )",
117 dbi_result_free (result);
120 if (dbi_conn_error (m_conn, &errstr))
122 PERR (
"Error %s creating lock table", errstr);
129 char hostname[ GNC_HOST_NAME_MAX + 1 ];
130 auto result = dbi_conn_queryf (m_conn,
"SELECT * FROM %s",
132 if (result && dbi_result_get_numrows (result))
134 dbi_result_free (result);
143 result = dbi_conn_queryf (m_conn,
"DELETE FROM %s", lock_table.c_str());
147 m_qbe->
set_message(
"Failed to delete lock record");
151 dbi_result_free (result);
155 memset (hostname, 0,
sizeof (hostname));
156 gethostname (hostname, GNC_HOST_NAME_MAX);
157 result = dbi_conn_queryf (m_conn,
158 "INSERT INTO %s VALUES ('%s', '%d')",
159 lock_table.c_str(), hostname, (int)GETPID ());
163 m_qbe->
set_message(
"Failed to create lock record");
167 dbi_result_free (result);
172 GncDbiSqlConnection::unlock_database ()
174 if (m_conn ==
nullptr)
return;
175 if (m_readonly)
return;
176 g_return_if_fail (dbi_conn_error (m_conn,
nullptr) == 0);
178 auto tables = m_provider->get_table_list (m_conn, lock_table);
181 PWARN (
"No lock table in database, so not unlocking it.");
187 char hostname[ GNC_HOST_NAME_MAX + 1 ];
189 memset (hostname, 0,
sizeof (hostname));
190 gethostname (hostname, GNC_HOST_NAME_MAX);
191 auto result = dbi_conn_queryf (m_conn,
192 "SELECT * FROM %s WHERE Hostname = '%s' " 193 "AND PID = '%d'", lock_table.c_str(),
196 if (result && dbi_result_get_numrows (result))
200 dbi_result_free (result);
203 result = dbi_conn_queryf (m_conn,
"DELETE FROM %s",
207 PERR (
"Failed to delete the lock entry");
214 dbi_result_free (result);
221 PWARN (
"There was no lock entry in the Lock table");
224 PWARN (
"Unable to get a lock on LOCK, so failed to clear the lock entry.");
229 GncDbiSqlConnection::check_and_rollback_failed_save()
231 auto backup_tables = m_provider->get_table_list(m_conn,
"%back");
232 if (backup_tables.empty())
234 auto merge_tables = m_provider->get_table_list(m_conn,
"%_merge");
235 if (!merge_tables.empty())
237 PERR(
"Merge tables exist in the database indicating a previous" 238 "attempt to recover from a failed safe-save. Automatic" 239 "recovery is beyond GnuCash's ability, you must recover" 240 "by hand or restore from a good backup.");
246 GncDbiSqlConnection::~GncDbiSqlConnection()
251 dbi_conn_close(m_conn);
257 GncDbiSqlConnection::execute_select_statement (
const GncSqlStatementPtr& stmt)
262 DEBUG (
"SQL: %s\n", stmt->to_sql());
263 auto locale = gnc_push_locale (LC_NUMERIC,
"C");
267 result = dbi_conn_query (m_conn, stmt->to_sql());
270 if (result ==
nullptr)
272 PERR (
"Error executing SQL %s\n", stmt->to_sql());
274 m_qbe->set_error(m_last_error);
278 gnc_pop_locale (LC_NUMERIC, locale);
288 DEBUG (
"SQL: %s\n", stmt->to_sql());
292 result = dbi_conn_query (m_conn, stmt->to_sql());
295 if (result ==
nullptr && m_last_error)
297 PERR (
"Error executing SQL %s\n", stmt->to_sql());
299 m_qbe->set_error(m_last_error);
306 auto num_rows = (gint)dbi_result_get_numrows_affected (result);
307 auto status = dbi_result_free (result);
310 PERR (
"Error in dbi_result_free() result\n");
312 m_qbe->set_error(m_last_error);
320 GncDbiSqlConnection::create_statement_from_sql (
const std::string& sql)
330 return ! m_provider->get_table_list(m_conn, table_name).empty();
342 PERR (
"gnc_dbi_verify_conn() failed\n");
350 if (m_sql_savepoint == 0)
351 result = dbi_conn_queryf (m_conn,
"BEGIN");
354 std::ostringstream savepoint;
355 savepoint <<
"savepoint_" << m_sql_savepoint;
356 result = dbi_conn_queryf(m_conn,
"SAVEPOINT %s",
357 savepoint.str().c_str());
364 PERR (
"BEGIN transaction failed()\n");
368 if (dbi_result_free (result) < 0)
370 PERR (
"Error in dbi_result_free() result\n");
381 DEBUG (
"ROLLBACK\n");
382 if (m_sql_savepoint == 0)
return false;
384 if (m_sql_savepoint == 1)
385 result = dbi_conn_query (m_conn,
"ROLLBACK");
388 std::ostringstream savepoint;
389 savepoint <<
"savepoint_" << m_sql_savepoint - 1;
390 result = dbi_conn_queryf(m_conn,
"ROLLBACK TO SAVEPOINT %s",
391 savepoint.str().c_str());
395 PERR (
"Error in conn_rollback_transaction()\n");
400 if (dbi_result_free (result) < 0)
402 PERR (
"Error in dbi_result_free() result\n");
415 if (m_sql_savepoint == 0)
return false;
417 if (m_sql_savepoint == 1)
418 result = dbi_conn_queryf (m_conn,
"COMMIT");
421 std::ostringstream savepoint;
422 savepoint <<
"savepoint_" << m_sql_savepoint - 1;
423 result = dbi_conn_queryf(m_conn,
"RELEASE SAVEPOINT %s",
424 savepoint.str().c_str());
429 PERR (
"Error in conn_commit_transaction()\n");
434 if (dbi_result_free (result) < 0)
436 PERR (
"Error in dbi_result_free() result\n");
447 const ColVec& info_vec)
const noexcept
450 unsigned int col_num = 0;
452 ddl +=
"CREATE TABLE " + table_name +
"(";
453 for (
auto const& info : info_vec)
459 m_provider->append_col_def (ddl, info);
466 DEBUG (
"SQL: %s\n", ddl.c_str());
467 auto result = dbi_conn_query (m_conn, ddl.c_str());
468 auto status = dbi_result_free (result);
471 PERR (
"Error in dbi_result_free() result\n");
479 create_index_ddl (
const GncSqlConnection* conn,
const std::string& index_name,
480 const std::string& table_name,
const EntryVec& col_table)
483 ddl +=
"CREATE INDEX " + index_name +
" ON " + table_name +
"(";
484 for (
const auto& table_row : col_table)
486 if (table_row != *col_table.begin())
490 ddl += table_row->name();
498 const std::string& table_name,
499 const EntryVec& col_table)
const noexcept
501 auto ddl = create_index_ddl (
this, index_name, table_name, col_table);
504 DEBUG (
"SQL: %s\n", ddl.c_str());
505 auto result = dbi_conn_query (m_conn, ddl.c_str());
506 auto status = dbi_result_free (result);
509 PERR (
"Error in dbi_result_free() result\n");
518 const ColVec& info_vec)
521 auto ddl = add_columns_ddl(table_name, info_vec);
525 DEBUG (
"SQL: %s\n", ddl.c_str());
526 auto result = dbi_conn_query (m_conn, ddl.c_str());
527 auto status = dbi_result_free (result);
530 PERR(
"Error in dbi_result_free() result\n" );
538 GncDbiSqlConnection::quote_string (
const std::string& unquoted_str)
543 dbi_conn_quote_string_copy (m_conn, unquoted_str.c_str(),
545 if (quoted_str ==
nullptr)
546 return std::string{
""};
547 std::string retval{quoted_str};
569 (void)dbi_conn_connect (m_conn);
575 GncDbiSqlConnection::retry_connection(
const char* msg)
578 while (m_retry && m_error_repeat <= DBI_MAX_CONN_ATTEMPTS)
581 if (dbi_conn_connect(m_conn) == 0)
588 const guint backoff_msecs = 1;
589 Sleep (backoff_msecs * 2 << ++m_error_repeat);
591 const guint backoff_usecs = 1000;
592 usleep (backoff_usecs * 2 << ++m_error_repeat);
594 PINFO (
"DBI error: %s - Reconnecting...\n", msg);
597 PERR (
"DBI error: %s - Giving up after %d consecutive attempts.\n", msg,
598 DBI_MAX_CONN_ATTEMPTS);
604 GncDbiSqlConnection::rename_table(
const std::string& old_name,
605 const std::string& new_name)
607 std::string sql =
"ALTER TABLE " + old_name +
" RENAME TO " + new_name;
608 auto stmt = create_statement_from_sql(sql);
613 GncDbiSqlConnection::drop_table(
const std::string&
table)
615 std::string sql =
"DROP TABLE " +
table;
616 auto stmt = create_statement_from_sql(sql);
621 GncDbiSqlConnection::merge_tables(
const std::string&
table,
622 const std::string& other)
624 auto merge_table =
table +
"_merge";
625 std::string sql =
"CREATE TABLE " + merge_table +
" AS SELECT * FROM " +
626 table +
" UNION SELECT * FROM " + other;
627 auto stmt = create_statement_from_sql(sql);
630 if (!drop_table(
table))
632 if (!rename_table(merge_table,
table))
634 return drop_table(other);
665 auto backup_tables = m_provider->get_table_list(m_conn,
"%_back");
666 auto all_tables = m_provider->get_table_list(m_conn,
"");
668 auto new_end = std::remove(all_tables.begin(), all_tables.end(), lock_table);
669 all_tables.erase(new_end, all_tables.end());
671 data_tables.reserve(all_tables.size() - backup_tables.size());
672 std::set_difference(all_tables.begin(), all_tables.end(),
673 backup_tables.begin(), backup_tables.end(),
674 std::back_inserter(data_tables));
678 if (!backup_tables.empty())
680 PERR(
"Unable to backup database, an existing backup is present.");
684 for (
auto table : data_tables)
689 for (
auto table : backup_tables)
691 auto data_table =
table.substr(0,
table.find(
"_back"));
692 if (std::find(data_tables.begin(), data_tables.end(),
693 data_table) != data_tables.end())
696 rename_table(
table, data_table);
700 for (
auto table : backup_tables)
702 auto data_table =
table.substr(0,
table.find(
"_back"));
703 if (std::find(data_tables.begin(), data_tables.end(),
704 data_table) != data_tables.end())
705 drop_table(data_table);
706 rename_table(
table, data_table);
710 for (
auto table : backup_tables)
712 auto data_table =
table.substr(0,
table.find(
"_back"));
713 if (std::find(data_tables.begin(), data_tables.end(),
714 data_table) != data_tables.end())
716 if (!merge_tables(data_table,
table))
721 if (!rename_table(
table, data_table))
731 GncDbiSqlConnection::drop_indexes() noexcept
733 auto index_list = m_provider->get_index_list (m_conn);
734 for (
auto index : index_list)
737 m_provider->drop_index (m_conn, index);
738 if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg))
740 PERR(
"Failed to drop indexes %s", errmsg);
748 GncDbiSqlConnection::add_columns_ddl(
const std::string& table_name,
749 const ColVec& info_vec)
const noexcept
753 ddl +=
"ALTER TABLE " + table_name;
754 for (
auto const& info : info_vec)
756 if (info != *info_vec.begin())
760 ddl +=
"ADD COLUMN ";
761 m_provider->append_col_def (ddl, info);
bool verify() noexcept override
Check if the dbi connection is valid.
void set_message(std::string &&)
Set a descriptive message that can be displayed to the user when there's an error.
#define G_LOG_DOMAIN
Functions providing the SX List as a plugin page.
#define PINFO(format, args...)
Print an informational note.
bool does_table_exist(const std::string &) const noexcept override
Returns true if successful.
const gchar * QofIdTypeConst
QofIdTypeConst declaration.
void qof_backend_set_error(QofBackend *qof_be, QofBackendError err)
Set the error on the specified QofBackend.
#define DEBUG(format, args...)
Print a debugging message.
in use by another user (ETXTBSY)
Open the session read-only, ignoring any existing lock and not creating one if the URI isn't locked...
#define PERR(format, args...)
Log a serious error.
Create a new store at the URI even if a store already exists there.
#define PWARN(format, args...)
Log a warning.
error in response from server
bool table_operation(TableOpType op) noexcept
Perform a specified SQL operation on every table in a database.
bool commit_transaction() noexcept override
Returns TRUE if successful, FALSE if error.
bool create_index(const std::string &, const std::string &, const EntryVec &) const noexcept override
Returns TRUE if successful, FALSE if error.
bool begin_transaction() noexcept override
Returns TRUE if successful, false if error.
SessionOpenMode
Mode for opening sessions.
bool add_columns_to_table(const std::string &, const ColVec &) const noexcept override
Returns TRUE if successful, FALSE if error.
Encapsulate the connection to the database.
An iterable wrapper for dbi_result; allows using C++11 range for.
Pure virtual class to iterate over a query result set.
bool create_table(const std::string &, const ColVec &) const noexcept override
Returns TRUE if successful, FALSE if error.
bool rollback_transaction() noexcept override
Returns TRUE if successful, FALSE if error.
int execute_nonselect_statement(const GncSqlStatementPtr &) noexcept override
Returns false if error.
void set_error(QofBackendError err)
Set the error value only if there isn't already an error already.