static bool create_database(DbType type, QofBackend *qbe, dbi_conn conn, const char* db) { const char *dbname; const char *dbcreate; if (type == DbType::DBI_MYSQL) { dbname = "mysql"; dbcreate = "CREATE DATABASE %s CHARACTER SET utf8"; } else { dbname = "postgres"; dbcreate = "CREATE DATABASE %s WITH TEMPLATE template0 ENCODING 'UTF8'"; } PairVec options; options.push_back(std::make_pair("dbname", dbname)); try { set_options(conn, options); } catch (std::runtime_error& err) { qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); return false; } auto result = dbi_conn_connect (conn); if (result < 0) { PERR ("Unable to connect to %s database", dbname); qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); return false; } if (type == DbType::DBI_MYSQL) adjust_sql_options(conn); auto dresult = dbi_conn_queryf (conn, dbcreate, db); if (dresult == nullptr) { PERR ("Unable to create database '%s'\n", db); qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); return false; } if (type == DbType::DBI_PGSQL) { const char *alterdb = "ALTER DATABASE %s SET " "standard_conforming_strings TO on"; dbi_conn_queryf (conn, alterdb, db); } dbi_conn_close(conn); conn = nullptr; return true; }
gboolean qof_commit_edit_part2(QofInstance *inst, void (*on_error)(QofInstance *, QofBackendError), void (*on_done)(QofInstance *), void (*on_free)(QofInstance *)) { QofInstancePrivate *priv; QofBackend * be; gboolean dirty; priv = GET_PRIVATE(inst); dirty = priv->dirty; /* See if there's a backend. If there is, invoke it. */ be = qof_book_get_backend(priv->book); if (be && qof_backend_commit_exists(be)) { QofBackendError errcode; /* clear errors */ do { errcode = qof_backend_get_error(be); } while (ERR_BACKEND_NO_ERR != errcode); qof_backend_run_commit(be, inst); errcode = qof_backend_get_error(be); if (ERR_BACKEND_NO_ERR != errcode) { /* XXX Should perform a rollback here */ priv->do_free = FALSE; /* Push error back onto the stack */ qof_backend_set_error (be, errcode); if (on_error) on_error(inst, errcode); return FALSE; } /* XXX the backend commit code should clear dirty!! */ priv->dirty = FALSE; } // if (dirty && qof_get_alt_dirty_mode() && // !(priv->infant && priv->do_free)) { // qof_collection_mark_dirty(priv->collection); // qof_book_mark_dirty(priv->book); // } priv->infant = FALSE; if (priv->do_free) { if (on_free) on_free(inst); return TRUE; } if (on_done) on_done(inst); return TRUE; }
static /*@ null @*/ Transaction* load_single_tx( GncSqlBackend* be, GncSqlRow* row ) { const GncGUID* guid; GncGUID tx_guid; Transaction* pTx; g_return_val_if_fail( be != NULL, NULL ); g_return_val_if_fail( row != NULL, NULL ); guid = gnc_sql_load_guid( be, row ); if ( guid == NULL ) return NULL; tx_guid = *guid; // Don't overwrite the transaction if it's already been loaded (and possibly modified). pTx = xaccTransLookup( &tx_guid, be->book ); if ( pTx != NULL ) { return NULL; } pTx = xaccMallocTransaction( be->book ); xaccTransBeginEdit( pTx ); gnc_sql_load_object( be, row, GNC_ID_TRANS, pTx, tx_col_table ); if (pTx != xaccTransLookup( &tx_guid, be->book )) { PERR("A malformed transaction with id %s was found in the dataset.", guid_to_string(qof_instance_get_guid(pTx))); qof_backend_set_error( &be->be, ERR_BACKEND_DATA_CORRUPT); pTx = NULL; } return pTx; }
/** * Sets standard db options in a dbi_conn. * * @param qbe QOF backend * @param conn dbi_conn connection * @param uri UriStrings containing the needed paramters. * @return TRUE if successful, FALSE if error */ static bool set_standard_connection_options (QofBackend* qbe, dbi_conn conn, const UriStrings& uri) { gint result; PairVec options; options.push_back(std::make_pair("host", uri.m_host)); options.push_back(std::make_pair("dbname", uri.m_dbname)); options.push_back(std::make_pair("username", uri.m_username)); options.push_back(std::make_pair("password", uri.m_password)); options.push_back(std::make_pair("encoding", "UTF-8")); try { set_options(conn, options); auto result = dbi_conn_set_option_numeric(conn, "port", uri.m_portnum); if (result < 0) { const char *msg = nullptr; auto err = dbi_conn_error(conn, &msg); PERR("Error setting port option to %d: %s", uri.m_portnum, msg); throw std::runtime_error(msg); } } catch (std::runtime_error& err) { qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); return false; } return true; }
template <DbType Type> dbi_conn conn_setup (QofBackend* qbe, PairVec& options, UriStrings& uri) { const char* dbstr = (Type == DbType::DBI_SQLITE ? "sqlite3" : Type == DbType::DBI_MYSQL ? "mysql" : "pgsql"); #if HAVE_LIBDBI_R dbi_conn conn = nullptr; if (dbi_instance) conn = dbi_conn_new_r (dbstr, dbi_instance); else PERR ("Attempt to connect with an uninitialized dbi_instance"); #else auto conn = dbi_conn_new (dbstr); #endif if (conn == nullptr) { PERR ("Unable to create %s dbi connection", dbstr); qof_backend_set_error (qbe, ERR_BACKEND_BAD_URL); return nullptr; } dbi_conn_error_handler (conn, error_handler<Type>, qbe); if (!uri.m_dbname.empty() && !set_standard_connection_options(qbe, conn, uri)) { dbi_conn_close(conn); return nullptr; } if(!options.empty()) { try { set_options(conn, options); } catch (std::runtime_error& err) { dbi_conn_close(conn); qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); return nullptr; } } return conn; }
/* GNUCASH_RESAVE_VERSION indicates the earliest database version * compatible with this version of Gnucash; the stored value is the * earliest version of Gnucash conpatible with the database. If the * GNUCASH_RESAVE_VERSION for this Gnucash is newer than the Gnucash * version which created the database, a resave is offered. If the * version of this Gnucash is older than the saved resave version, * then the database will be loaded read-only. A resave will update * both values to match this version of Gnucash. */ void gnc_dbi_load (QofBackend* qbe, QofBook* book, QofBackendLoadType loadType) { GncDbiBackend* be = (GncDbiBackend*)qbe; g_return_if_fail (qbe != nullptr); g_return_if_fail (book != nullptr); ENTER ("be=%p, book=%p", be, book); if (loadType == LOAD_TYPE_INITIAL_LOAD) { // Set up table version information be->init_version_info (); assert (be->m_book == nullptr); // Call all object backends to create any required tables auto registry = gnc_sql_get_backend_registry(); for (auto entry : registry) create_tables(entry, be); } gnc_sql_load (be, book, loadType); if (GNUCASH_RESAVE_VERSION > be->get_table_version("Gnucash")) { /* The database was loaded with an older database schema or * data semantics. In order to ensure consistency, the whole * thing needs to be saved anew. */ qof_backend_set_error (qbe, ERR_SQL_DB_TOO_OLD); } else if (GNUCASH_RESAVE_VERSION < be->get_table_version("Gnucash-Resave")) { /* Worse, the database was created with a newer version. We * can't safely write to this database, so the user will have * to do a "save as" to make one that we can write to. */ qof_backend_set_error (qbe, ERR_SQL_DB_TOO_NEW); } LEAVE (""); }
/** * Safely resave a database by renaming all of its tables, recreating * everything, and then dropping the backup tables only if there were * no errors. If there are errors, drop the new tables and restore the * originals. * * @param qbe: QofBackend for the session. * @param book: QofBook to be saved in the database. */ void gnc_dbi_safe_sync_all (QofBackend* qbe, QofBook* book) { GncDbiBackend* be = (GncDbiBackend*)qbe; auto conn = dynamic_cast<GncDbiSqlConnection*>(be->m_conn); g_return_if_fail (conn != nullptr); g_return_if_fail (be != nullptr); g_return_if_fail (book != nullptr); ENTER ("book=%p, primary=%p", book, be->m_book); auto table_list = conn->m_provider->get_table_list (conn->conn(), ""); if (!conn->table_operation (table_list, backup)) { qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); conn->table_operation (table_list, rollback); LEAVE ("Failed to rename tables"); return; } auto index_list = conn->m_provider->get_index_list (conn->m_conn); for (auto index : index_list) { const char* errmsg; conn->m_provider->drop_index (conn->m_conn, index); if (DBI_ERROR_NONE != dbi_conn_error (conn->m_conn, &errmsg)) { qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); conn->table_operation (table_list, rollback); LEAVE ("Failed to drop indexes %s", errmsg); return; } } gnc_sql_sync_all (be, book); if (qof_backend_check_error (qbe)) { conn->table_operation (table_list, rollback); LEAVE ("Failed to create new database tables"); return; } conn->table_operation (table_list, drop_backup); LEAVE ("book=%p", book); }
int GncSqlBackend::execute_nonselect_statement(const GncSqlStatementPtr& stmt) const noexcept { auto result = m_conn->execute_nonselect_statement(stmt); if (result == -1) { PERR ("SQL error: %s\n", stmt->to_sql()); qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR); } return result; }
GncSqlStatementPtr GncSqlBackend::create_statement_from_sql(const std::string& str) const noexcept { auto stmt = m_conn->create_statement_from_sql(str); if (stmt == nullptr) { PERR ("SQL error: %s\n", str.c_str()); qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR); } return stmt; }
/* GNUCASH_RESAVE_VERSION indicates the earliest database version * compatible with this version of Gnucash; the stored value is the * earliest version of Gnucash conpatible with the database. If the * GNUCASH_RESAVE_VERSION for this Gnucash is newer than the Gnucash * version which created the database, a resave is offered. If the * version of this Gnucash is older than the saved resave version, * then the database will be loaded read-only. A resave will update * both values to match this version of Gnucash. */ void gnc_dbi_load (QofBackend* qof_be, QofBook* book, QofBackendLoadType loadType) { GncDbiBackend* dbi_be = reinterpret_cast<decltype(dbi_be)>(qof_be); g_return_if_fail (qof_be != nullptr); g_return_if_fail (book != nullptr); ENTER ("dbi_be=%p, book=%p", dbi_be, book); if (loadType == LOAD_TYPE_INITIAL_LOAD) { // Set up table version information dbi_be->init_version_info (); assert (dbi_be->m_book == nullptr); dbi_be->create_tables(); } dbi_be->load(book, loadType); if (GNUCASH_RESAVE_VERSION > dbi_be->get_table_version("Gnucash")) { /* The database was loaded with an older database schema or * data semantics. In order to ensure consistency, the whole * thing needs to be saved anew. */ qof_backend_set_error (qof_be, ERR_SQL_DB_TOO_OLD); } else if (GNUCASH_RESAVE_VERSION < dbi_be->get_table_version("Gnucash-Resave")) { /* Worse, the database was created with a newer version. We * can't safely write to this database, so the user will have * to do a "save as" to make one that we can write to. */ qof_backend_set_error (qof_be, ERR_SQL_DB_TOO_NEW); } LEAVE (""); }
static bool conn_test_dbi_library(dbi_conn conn, QofBackend* qbe) { auto result = dbi_library_test (conn); switch (result) { case GNC_DBI_PASS: break; case GNC_DBI_FAIL_SETUP: qof_backend_set_error (qbe, ERR_SQL_DBI_UNTESTABLE); qof_backend_set_message (qbe, "DBI library large number test incomplete"); break; case GNC_DBI_FAIL_TEST: qof_backend_set_error (qbe, ERR_SQL_BAD_DBI); qof_backend_set_message (qbe, "DBI library fails large number test"); break; } return result == GNC_DBI_PASS; }
static /*@ null @*/ Split* load_single_split( GncSqlBackend* be, GncSqlRow* row ) { const GncGUID* guid; GncGUID split_guid; Split* pSplit = NULL; gboolean bad_guid = FALSE; g_return_val_if_fail( be != NULL, NULL ); g_return_val_if_fail( row != NULL, NULL ); guid = gnc_sql_load_guid( be, row ); if ( guid == NULL ) return NULL; if (guid_equal(guid, guid_null())) { PWARN("Bad GUID, creating new"); bad_guid = TRUE; split_guid = guid_new_return(); } else { split_guid = *guid; pSplit = xaccSplitLookup( &split_guid, be->book ); } if ( pSplit == NULL ) { pSplit = xaccMallocSplit( be->book ); } /* If the split is dirty, don't overwrite it */ if ( !qof_instance_is_dirty( QOF_INSTANCE(pSplit) ) ) { gnc_sql_load_object( be, row, GNC_ID_SPLIT, pSplit, split_col_table ); } /*# -ifempty */ if (pSplit != xaccSplitLookup( &split_guid, be->book )) { gchar guidstr[GUID_ENCODING_LENGTH+1]; guid_to_string_buff(qof_instance_get_guid(pSplit), guidstr); PERR("A malformed split with id %s was found in the dataset.", guidstr); qof_backend_set_error( &be->be, ERR_BACKEND_DATA_CORRUPT); pSplit = NULL; } return pSplit; }
template<> void error_handler<DbType::DBI_PGSQL> (dbi_conn conn, void* user_data) { GncDbiBackend<DbType::DBI_PGSQL>* dbi_be = static_cast<decltype(dbi_be)>(user_data); const char* msg; (void)dbi_conn_error (conn, &msg); if (g_str_has_prefix (msg, "FATAL: database") && g_str_has_suffix (msg, "does not exist\n")) { PINFO ("DBI error: %s\n", msg); dbi_be->set_exists(false); } else if (g_strrstr (msg, "server closed the connection unexpectedly")) // Connection lost { if (!dbi_be->connected()) { PWARN ("DBI Error: Connection lost, connection pointer invalid"); return; } PINFO ("DBI error: %s - Reconnecting...\n", msg); dbi_be->set_dbi_error (ERR_BACKEND_CONN_LOST, 1, true); dbi_be->retry_connection(msg); } else if (g_str_has_prefix (msg, "connection pointer is NULL") || g_str_has_prefix (msg, "could not connect to server")) // No connection { if (!dbi_be->connected()) qof_backend_set_error(reinterpret_cast<QofBackend*>(dbi_be), ERR_BACKEND_CANT_CONNECT); else { dbi_be->set_dbi_error(ERR_BACKEND_CANT_CONNECT, 1, true); dbi_be->retry_connection (msg); } } else { PERR ("DBI error: %s\n", msg); if (dbi_be->connected()) dbi_be->set_dbi_error (ERR_BACKEND_MISC, 0, false); } }
/** * Registers the version for a table. Registering involves updating the * db version table and also the hash table. * * @param be Backend struct * @param table_name Table name * @param version Version number * @return TRUE if successful, FALSE if unsuccessful */ bool GncSqlBackend::set_table_version (const std::string& table_name, uint_t version) noexcept { g_return_val_if_fail (version > 0, false); unsigned int cur_version{0}; std::stringstream sql; auto ver_entry = std::find_if(m_versions.begin(), m_versions.end(), [table_name](const VersionPair& ver) { return ver.first == table_name; }); if (ver_entry != m_versions.end()) cur_version = ver_entry->second; if (cur_version != version) { if (cur_version == 0) { sql << "INSERT INTO " << VERSION_TABLE_NAME << " VALUES('" << table_name << "'," << version <<")"; m_versions.push_back(std::make_pair(table_name, version)); } else { sql << "UPDATE " << VERSION_TABLE_NAME << " SET " << VERSION_COL_NAME << "=" << version << " WHERE " << TABLE_COL_NAME << "='" << table_name << "'"; ver_entry->second = version; } auto stmt = create_statement_from_sql(sql.str()); auto status = execute_nonselect_statement (stmt); if (status == -1) { PERR ("SQL error: %s\n", sql.str().c_str()); qof_backend_set_error ((QofBackend*)this, ERR_BACKEND_SERVER_ERR); return false; } } return true; }
static gboolean save_transaction( GncSqlBackend* be, Transaction* pTx, gboolean do_save_splits ) { const GncGUID* guid; gint op; gboolean is_infant; QofInstance* inst; gboolean is_ok = TRUE; gchar* err = NULL; g_return_val_if_fail( be != NULL, FALSE ); g_return_val_if_fail( pTx != NULL, FALSE ); inst = QOF_INSTANCE(pTx); is_infant = qof_instance_get_infant( inst ); if ( qof_instance_get_destroying( inst ) ) { op = OP_DB_DELETE; } else if ( be->is_pristine_db || is_infant ) { op = OP_DB_INSERT; } else { op = OP_DB_UPDATE; } if ( op != OP_DB_DELETE ) { gnc_commodity *commodity = xaccTransGetCurrency( pTx ); // Ensure the commodity is in the db is_ok = gnc_sql_save_commodity( be, commodity ); if ( ! is_ok ) { err = "Commodity save failed: Probably an invalid or missing currency"; qof_backend_set_error( &be->be, ERR_BACKEND_DATA_CORRUPT); } } if ( is_ok ) { is_ok = gnc_sql_do_db_operation( be, op, TRANSACTION_TABLE, GNC_ID_TRANS, pTx, tx_col_table ); if ( ! is_ok ) { err = "Transaction header save failed. Check trace log for SQL errors"; } } if ( is_ok ) { // Commit slots and splits guid = qof_instance_get_guid( inst ); if ( !qof_instance_get_destroying(inst) ) { is_ok = gnc_sql_slots_save( be, guid, is_infant, qof_instance_get_slots( inst ) ); if ( ! is_ok ) { err = "Slots save failed. Check trace log for SQL errors"; } if ( is_ok && do_save_splits ) { is_ok = save_splits( be, guid, xaccTransGetSplitList( pTx ) ); if ( ! is_ok ) { err = "Split save failed. Check trace log for SQL errors"; } } } else { is_ok = gnc_sql_slots_delete( be, guid ); if ( ! is_ok ) { err = "Slots delete failed. Check trace log for SQL errors"; } if ( is_ok ) { is_ok = delete_splits( be, pTx ); if ( ! is_ok ) { err = "Split delete failed. Check trace log for SQL errors"; } } } } if (! is_ok ) { G_GNUC_UNUSED gchar *message1 = "Transaction %s dated %s in account %s not saved due to %s.%s"; G_GNUC_UNUSED gchar *message2 = "\nDatabase may be corrupted, check your data carefully."; Split* split = xaccTransGetSplit( pTx, 0); Account *acc = xaccSplitGetAccount( split ); /* FIXME: This needs to be implemented qof_error_format_secondary_text( GTK_MESSAGE_DIALOG( msg ), message1, xaccTransGetDescription( pTx ), qof_print_date( xaccTransGetDate( pTx ) ), xaccAccountGetName( acc ), err, message2 ); */ PERR( "Transaction %s dated %s in account %s not saved due to %s.\n", xaccTransGetDescription( pTx ), qof_print_date( xaccTransGetDate( pTx ) ), xaccAccountGetName( acc ), err ); } return is_ok; }
template <> void gnc_dbi_session_begin<DbType::DBI_SQLITE>(QofBackend* qbe, QofSession* session, const char* book_id, gboolean ignore_lock, gboolean create, gboolean force) { GncDbiBackend* be = (GncDbiBackend*)qbe; const char* msg = nullptr; gboolean file_exists; PairVec options; g_return_if_fail (qbe != nullptr); g_return_if_fail (session != nullptr); g_return_if_fail (book_id != nullptr); ENTER (" "); /* Remove uri type if present */ auto path = gnc_uri_get_path (book_id); std::string filepath{path}; g_free(path); GFileTest ftest = static_cast<decltype (ftest)> ( G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS) ; file_exists = g_file_test (filepath.c_str(), ftest); if (!create && !file_exists) { qof_backend_set_error (qbe, ERR_FILEIO_FILE_NOT_FOUND); qof_backend_set_message (qbe, "Sqlite3 file %s not found", filepath.c_str()); PWARN ("Sqlite3 file %s not found", filepath.c_str()); LEAVE("Error"); return; } if (create && !force && file_exists) { qof_backend_set_error (qbe, ERR_BACKEND_STORE_EXISTS); msg = "Might clobber, no force"; PWARN ("%s", msg); LEAVE("Error"); return; } be->connect(nullptr); /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */ options.push_back(std::make_pair("host", "localhost")); auto dirname = g_path_get_dirname (filepath.c_str()); auto basename = g_path_get_basename (filepath.c_str()); options.push_back(std::make_pair("dbname", basename)); options.push_back(std::make_pair("sqlite3_dbdir", dirname)); if (basename != nullptr) g_free (basename); if (dirname != nullptr) g_free (dirname); UriStrings uri; auto conn = conn_setup<DbType::DBI_SQLITE>(qbe, options, uri); if (conn == nullptr) { LEAVE("Error"); return; } auto result = dbi_conn_connect (conn); if (result < 0) { dbi_conn_close(conn); PERR ("Unable to connect to %s: %d\n", book_id, result); qof_backend_set_error (qbe, ERR_BACKEND_BAD_URL); LEAVE("Error"); return; } if (!conn_test_dbi_library(conn, qbe)) { if (create && !file_exists) { /* File didn't exist before, but it does now, and we don't want to * leave it lying around. */ dbi_conn_close (conn); conn = nullptr; g_unlink (filepath.c_str()); } dbi_conn_close(conn); LEAVE("Bad DBI Library"); return; } try { be->connect(new GncDbiSqlConnection(DbType::DBI_SQLITE, qbe, conn, ignore_lock)); } catch (std::runtime_error& err) { return; } /* We should now have a proper session set up. * Let's start logging */ xaccLogSetBaseName (filepath.c_str()); PINFO ("logpath=%s", filepath.c_str() ? filepath.c_str() : "(null)"); LEAVE (""); }
/* Commit_edit handler - find the correct backend handler for this object * type and call its commit handler */ void GncSqlBackend::commit_edit (QofInstance* inst) { sql_backend be_data; gboolean is_dirty; gboolean is_destroying; gboolean is_infant; g_return_if_fail (inst != NULL); if (qof_book_is_readonly(m_book)) { qof_backend_set_error (&qof_be, ERR_BACKEND_READONLY); (void)m_conn->rollback_transaction (); return; } /* During initial load where objects are being created, don't commit anything, but do mark the object as clean. */ if (m_loading) { qof_instance_mark_clean (inst); return; } // The engine has a PriceDB object but it isn't in the database if (strcmp (inst->e_type, "PriceDB") == 0) { qof_instance_mark_clean (inst); qof_book_mark_session_saved (m_book); return; } ENTER (" "); is_dirty = qof_instance_get_dirty_flag (inst); is_destroying = qof_instance_get_destroying (inst); is_infant = qof_instance_get_infant (inst); DEBUG ("%s dirty = %d, do_free = %d, infant = %d\n", (inst->e_type ? inst->e_type : "(null)"), is_dirty, is_destroying, is_infant); if (!is_dirty && !is_destroying) { LEAVE ("!dirty OR !destroying"); return; } if (!m_conn->begin_transaction ()) { PERR ("begin_transaction failed\n"); LEAVE ("Rolled back - database transaction begin error"); return; } bool is_ok = true; auto obe = m_backend_registry.get_object_backend(std::string{inst->e_type}); if (obe != nullptr) is_ok = obe->commit(this, inst); else { PERR ("Unknown object type '%s'\n", inst->e_type); (void)m_conn->rollback_transaction (); // Don't let unknown items still mark the book as being dirty qof_book_mark_session_saved(m_book); qof_instance_mark_clean (inst); LEAVE ("Rolled back - unknown object type"); return; } if (!is_ok) { // Error - roll it back (void)m_conn->rollback_transaction(); // This *should* leave things marked dirty LEAVE ("Rolled back - database error"); return; } (void)m_conn->commit_transaction (); qof_book_mark_session_saved(m_book); qof_instance_mark_clean (inst); LEAVE (""); }
void GncSqlBackend::sync_all(QofBook* book) { g_return_if_fail (book != NULL); reset_version_info(); ENTER ("book=%p, sql_be->book=%p", book, m_book); update_progress(); /* Create new tables */ m_is_pristine_db = true; create_tables(); /* Save all contents */ m_book = book; auto is_ok = m_conn->begin_transaction(); // FIXME: should write the set of commodities that are used // write_commodities(sql_be, book); if (is_ok) { auto obe = m_backend_registry.get_object_backend(GNC_ID_BOOK); is_ok = obe->commit (this, QOF_INSTANCE (book)); } if (is_ok) { is_ok = write_accounts(); } if (is_ok) { is_ok = write_transactions(); } if (is_ok) { is_ok = write_template_transactions(); } if (is_ok) { is_ok = write_schedXactions(); } if (is_ok) { for (auto entry : m_backend_registry) std::get<1>(entry)->write (this); } if (is_ok) { is_ok = m_conn->commit_transaction(); } if (is_ok) { m_is_pristine_db = false; /* Mark the session as clean -- though it shouldn't ever get * marked dirty with this backend */ qof_book_mark_session_saved(book); } else { if (!qof_backend_check_error (&qof_be)) qof_backend_set_error (&qof_be, ERR_BACKEND_SERVER_ERR); is_ok = m_conn->rollback_transaction (); } finish_progress(); LEAVE ("book=%p", book); }
template <DbType T> void gnc_dbi_session_begin (QofBackend* qbe, QofSession* session, const char* book_id, gboolean ignore_lock, gboolean create, gboolean force) { GncDbiBackend* be = (GncDbiBackend*)qbe; GncDbiTestResult dbi_test_result = GNC_DBI_PASS; PairVec options; g_return_if_fail (qbe != nullptr); g_return_if_fail (session != nullptr); g_return_if_fail (book_id != nullptr); ENTER (" "); /* Split the book-id * Format is protocol://username:password@hostname:port/dbname where username, password and port are optional) */ UriStrings uri(book_id); if (T == DbType::DBI_PGSQL) { if (uri.m_portnum == 0) uri.m_portnum = PGSQL_DEFAULT_PORT; /* Postgres's SQL interface coerces identifiers to lower case, but the * C interface is case-sensitive. This results in a mixed-case dbname * being created (with a lower case name) but then dbi can't conect to * it. To work around this, coerce the name to lowercase first. */ auto lcname = g_utf8_strdown (uri.dbname(), -1); uri.m_dbname = std::string{lcname}; g_free(lcname); } be->connect(nullptr); auto conn = conn_setup<T>(qbe, options, uri); if (conn == nullptr) { LEAVE("Error"); return; } be->set_exists(true); //May be unset in the error handler. auto result = dbi_conn_connect (conn); if (result == 0) { if (T == DbType::DBI_MYSQL) adjust_sql_options (conn); if(!conn_test_dbi_library(conn, qbe)) { dbi_conn_close(conn); LEAVE("Error"); return; } if (create && !force && save_may_clobber_data (conn, uri.quote_dbname(T))) { qof_backend_set_error (qbe, ERR_BACKEND_STORE_EXISTS); PWARN ("Databse already exists, Might clobber it."); dbi_conn_close(conn); LEAVE("Error"); return; } } else { if (be->exists()) { PERR ("Unable to connect to database '%s'\n", uri.dbname()); qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); dbi_conn_close(conn); LEAVE("Error"); return; } if (create) { if (!create_database(T, qbe, conn, uri.quote_dbname(T).c_str())) { dbi_conn_close(conn); LEAVE("Error"); return; } conn = conn_setup<T>(qbe, options, uri); result = dbi_conn_connect (conn); if (result < 0) { PERR ("Unable to create database '%s'\n", uri.dbname()); qof_backend_set_error (qbe, ERR_BACKEND_SERVER_ERR); dbi_conn_close(conn); LEAVE("Error"); return; } if (T == DbType::DBI_MYSQL) adjust_sql_options (conn); if (!conn_test_dbi_library(conn, qbe)) { if (T == DbType::DBI_PGSQL) dbi_conn_select_db (conn, "template1"); dbi_conn_queryf (conn, "DROP DATABASE %s", uri.quote_dbname(T).c_str()); dbi_conn_close(conn); return; } } else { qof_backend_set_error (qbe, ERR_BACKEND_NO_SUCH_DB); qof_backend_set_message (qbe, "Database %s not found", uri.dbname()); } } be->connect(nullptr); try { be->connect(new GncDbiSqlConnection(T, qbe, conn, ignore_lock)); } catch (std::runtime_error& err) { return; } /* We should now have a proper session set up. * Let's start logging */ auto translog_path = gnc_build_translog_path (uri.basename().c_str()); xaccLogSetBaseName (translog_path); PINFO ("logpath=%s", translog_path ? translog_path : "(null)"); g_free (translog_path); LEAVE (" "); }