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 auto dbi_error{dbi_conn_error (m_conn,
nullptr)};
177 g_return_if_fail (dbi_error == DBI_ERROR_NONE || dbi_error == DBI_ERROR_BADIDX);
179 auto tables = m_provider->get_table_list (m_conn, lock_table);
182 PWARN (
"No lock table in database, so not unlocking it.");
188 char hostname[ GNC_HOST_NAME_MAX + 1 ];
190 memset (hostname, 0,
sizeof (hostname));
191 gethostname (hostname, GNC_HOST_NAME_MAX);
192 auto result = dbi_conn_queryf (m_conn,
193 "SELECT * FROM %s WHERE Hostname = '%s' " 194 "AND PID = '%d'", lock_table.c_str(),
197 if (result && dbi_result_get_numrows (result))
201 dbi_result_free (result);
204 result = dbi_conn_queryf (m_conn,
"DELETE FROM %s",
208 PERR (
"Failed to delete the lock entry");
215 dbi_result_free (result);
222 PWARN (
"There was no lock entry in the Lock table");
225 PWARN (
"Unable to get a lock on LOCK, so failed to clear the lock entry.");
230 GncDbiSqlConnection::check_and_rollback_failed_save()
232 auto backup_tables = m_provider->get_table_list(m_conn,
"%back");
233 if (backup_tables.empty())
235 auto merge_tables = m_provider->get_table_list(m_conn,
"%_merge");
236 if (!merge_tables.empty())
238 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.");
247 GncDbiSqlConnection::~GncDbiSqlConnection()
252 dbi_conn_close(m_conn);
258 GncDbiSqlConnection::execute_select_statement (
const GncSqlStatementPtr& stmt)
263 DEBUG (
"SQL: %s\n", stmt->to_sql());
264 auto locale = gnc_push_locale (LC_NUMERIC,
"C");
268 result = dbi_conn_query (m_conn, stmt->to_sql());
271 if (result ==
nullptr)
273 PERR (
"Error executing SQL %s\n", stmt->to_sql());
275 m_qbe->set_error(m_last_error);
279 gnc_pop_locale (LC_NUMERIC, locale);
289 DEBUG (
"SQL: %s\n", stmt->to_sql());
293 result = dbi_conn_query (m_conn, stmt->to_sql());
296 if (result ==
nullptr && m_last_error)
298 PERR (
"Error executing SQL %s\n", stmt->to_sql());
300 m_qbe->set_error(m_last_error);
307 auto num_rows = (gint)dbi_result_get_numrows_affected (result);
308 auto status = dbi_result_free (result);
311 PERR (
"Error in dbi_result_free() result\n");
313 m_qbe->set_error(m_last_error);
321 GncDbiSqlConnection::create_statement_from_sql (
const std::string& sql)
331 return ! m_provider->get_table_list(m_conn, table_name).empty();
343 PERR (
"gnc_dbi_verify_conn() failed\n");
351 if (m_sql_savepoint == 0)
352 result = dbi_conn_queryf (m_conn,
"BEGIN");
355 std::ostringstream savepoint;
356 savepoint <<
"savepoint_" << m_sql_savepoint;
357 result = dbi_conn_queryf(m_conn,
"SAVEPOINT %s",
358 savepoint.str().c_str());
365 PERR (
"BEGIN transaction failed()\n");
369 if (dbi_result_free (result) < 0)
371 PERR (
"Error in dbi_result_free() result\n");
382 DEBUG (
"ROLLBACK\n");
383 if (m_sql_savepoint == 0)
return false;
385 if (m_sql_savepoint == 1)
386 result = dbi_conn_query (m_conn,
"ROLLBACK");
389 std::ostringstream savepoint;
390 savepoint <<
"savepoint_" << m_sql_savepoint - 1;
391 result = dbi_conn_queryf(m_conn,
"ROLLBACK TO SAVEPOINT %s",
392 savepoint.str().c_str());
396 PERR (
"Error in conn_rollback_transaction()\n");
401 if (dbi_result_free (result) < 0)
403 PERR (
"Error in dbi_result_free() result\n");
416 if (m_sql_savepoint == 0)
return false;
418 if (m_sql_savepoint == 1)
419 result = dbi_conn_queryf (m_conn,
"COMMIT");
422 std::ostringstream savepoint;
423 savepoint <<
"savepoint_" << m_sql_savepoint - 1;
424 result = dbi_conn_queryf(m_conn,
"RELEASE SAVEPOINT %s",
425 savepoint.str().c_str());
430 PERR (
"Error in conn_commit_transaction()\n");
435 if (dbi_result_free (result) < 0)
437 PERR (
"Error in dbi_result_free() result\n");
448 const ColVec& info_vec)
const noexcept
451 unsigned int col_num = 0;
453 ddl +=
"CREATE TABLE " + table_name +
"(";
454 for (
auto const& info : info_vec)
460 m_provider->append_col_def (ddl, info);
467 DEBUG (
"SQL: %s\n", ddl.c_str());
468 auto result = dbi_conn_query (m_conn, ddl.c_str());
469 auto status = dbi_result_free (result);
472 PERR (
"Error in dbi_result_free() result\n");
480 create_index_ddl (
const GncSqlConnection* conn,
const std::string& index_name,
481 const std::string& table_name,
const EntryVec& col_table)
484 ddl +=
"CREATE INDEX " + index_name +
" ON " + table_name +
"(";
485 for (
const auto& table_row : col_table)
487 if (table_row != *col_table.begin())
491 ddl += table_row->name();
499 const std::string& table_name,
500 const EntryVec& col_table)
const noexcept
502 auto ddl = create_index_ddl (
this, index_name, table_name, col_table);
505 DEBUG (
"SQL: %s\n", ddl.c_str());
506 auto result = dbi_conn_query (m_conn, ddl.c_str());
507 auto status = dbi_result_free (result);
510 PERR (
"Error in dbi_result_free() result\n");
519 const ColVec& info_vec)
522 auto ddl = add_columns_ddl(table_name, info_vec);
526 DEBUG (
"SQL: %s\n", ddl.c_str());
527 auto result = dbi_conn_query (m_conn, ddl.c_str());
528 auto status = dbi_result_free (result);
531 PERR(
"Error in dbi_result_free() result\n" );
539 GncDbiSqlConnection::quote_string (
const std::string& unquoted_str)
544 dbi_conn_quote_string_copy (m_conn, unquoted_str.c_str(),
546 if (quoted_str ==
nullptr)
547 return std::string{
""};
548 std::string retval{quoted_str};
570 (void)dbi_conn_connect (m_conn);
576 GncDbiSqlConnection::retry_connection(
const char* msg)
579 while (m_retry && m_error_repeat <= DBI_MAX_CONN_ATTEMPTS)
582 if (dbi_conn_connect(m_conn) == 0)
589 const guint backoff_msecs = 1;
590 Sleep (backoff_msecs * 2 << ++m_error_repeat);
592 const guint backoff_usecs = 1000;
593 usleep (backoff_usecs * 2 << ++m_error_repeat);
595 PINFO (
"DBI error: %s - Reconnecting...\n", msg);
598 PERR (
"DBI error: %s - Giving up after %d consecutive attempts.\n", msg,
599 DBI_MAX_CONN_ATTEMPTS);
605 GncDbiSqlConnection::rename_table(
const std::string& old_name,
606 const std::string& new_name)
608 std::string sql =
"ALTER TABLE " + old_name +
" RENAME TO " + new_name;
609 auto stmt = create_statement_from_sql(sql);
614 GncDbiSqlConnection::drop_table(
const std::string&
table)
616 std::string sql =
"DROP TABLE " +
table;
617 auto stmt = create_statement_from_sql(sql);
622 GncDbiSqlConnection::merge_tables(
const std::string&
table,
623 const std::string& other)
625 auto merge_table =
table +
"_merge";
626 std::string sql =
"CREATE TABLE " + merge_table +
" AS SELECT * FROM " +
627 table +
" UNION SELECT * FROM " + other;
628 auto stmt = create_statement_from_sql(sql);
631 if (!drop_table(
table))
633 if (!rename_table(merge_table,
table))
635 return drop_table(other);
666 auto backup_tables = m_provider->get_table_list(m_conn,
"%_back");
667 auto all_tables = m_provider->get_table_list(m_conn,
"");
669 auto new_end = std::remove(all_tables.begin(), all_tables.end(), lock_table);
670 all_tables.erase(new_end, all_tables.end());
672 data_tables.reserve(all_tables.size() - backup_tables.size());
673 std::set_difference(all_tables.begin(), all_tables.end(),
674 backup_tables.begin(), backup_tables.end(),
675 std::back_inserter(data_tables));
679 if (!backup_tables.empty())
681 PERR(
"Unable to backup database, an existing backup is present.");
685 for (
auto table : data_tables)
690 for (
auto table : backup_tables)
692 auto data_table =
table.substr(0,
table.find(
"_back"));
693 if (std::find(data_tables.begin(), data_tables.end(),
694 data_table) != data_tables.end())
697 rename_table(
table, data_table);
701 for (
auto table : backup_tables)
703 auto data_table =
table.substr(0,
table.find(
"_back"));
704 if (std::find(data_tables.begin(), data_tables.end(),
705 data_table) != data_tables.end())
706 drop_table(data_table);
707 rename_table(
table, data_table);
711 for (
auto table : backup_tables)
713 auto data_table =
table.substr(0,
table.find(
"_back"));
714 if (std::find(data_tables.begin(), data_tables.end(),
715 data_table) != data_tables.end())
717 if (!merge_tables(data_table,
table))
722 if (!rename_table(
table, data_table))
732 GncDbiSqlConnection::drop_indexes() noexcept
734 auto index_list = m_provider->get_index_list (m_conn);
735 for (
auto index : index_list)
738 m_provider->drop_index (m_conn, index);
739 if (DBI_ERROR_NONE != dbi_conn_error (m_conn, &errmsg))
741 PERR(
"Failed to drop indexes %s", errmsg);
749 GncDbiSqlConnection::add_columns_ddl(
const std::string& table_name,
750 const ColVec& info_vec)
const noexcept
754 ddl +=
"ALTER TABLE " + table_name;
755 for (
auto const& info : info_vec)
757 if (info != *info_vec.begin())
761 ddl +=
"ADD COLUMN ";
762 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.