gnucash maint: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Sat Feb 4 20:37:11 EST 2017
Updated via https://github.com/Gnucash/gnucash/commit/93301cd2 (commit)
via https://github.com/Gnucash/gnucash/commit/a70637f3 (commit)
via https://github.com/Gnucash/gnucash/commit/979e6397 (commit)
via https://github.com/Gnucash/gnucash/commit/db73c39b (commit)
from https://github.com/Gnucash/gnucash/commit/b682fe6b (commit)
commit 93301cd285a88eb5b58b2902d23118bfa9edbc44
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Feb 4 17:36:09 2017 -0800
Make gnc_dbi_safe_sync_all safer.
Creates a new safe_sync function in struct provider and a new
gnc_db_do_safe_sync_all function with the guts of gnc_dbi_do_safe_sync_all.
The last calls the providerâs safe_sync function, which for SQLite3 and
PGSql wraps the call to gnc_dbi_do_safe_sync_all in a SQL transaction.
Unfortunately MySQL commits the transaction on the first schema-altering
query (CREATE_TABLE in this case) without decrementing sql_savepoint, so
raising an error when we try to release the (non-existent) save point at
the end of writing the tables.
Add a gnc_dbi_check_and_rollback_failed_save() to restore the database
after a failed safe_save; this is performed at the next connection.
diff --git a/src/backend/dbi/gnc-backend-dbi-priv.h b/src/backend/dbi/gnc-backend-dbi-priv.h
index 6e816e8..dab3331 100644
--- a/src/backend/dbi/gnc-backend-dbi-priv.h
+++ b/src/backend/dbi/gnc-backend-dbi-priv.h
@@ -63,6 +63,7 @@ typedef GSList* (*GET_TABLE_LIST_FN) ( dbi_conn conn, const gchar* dbname );
typedef void (*APPEND_COLUMN_DEF_FN) ( GString* ddl, GncSqlColumnInfo* info );
typedef GSList* (*GET_INDEX_LIST_FN) ( dbi_conn conn );
typedef void (*DROP_INDEX_FN) ( dbi_conn conn, const gchar* index );
+typedef void (*SAFE_SYNC) ( QofBackend *qbe, QofBook * book );
typedef struct
{
CREATE_TABLE_DDL_FN create_table_ddl;
@@ -70,6 +71,7 @@ typedef struct
APPEND_COLUMN_DEF_FN append_col_def;
GET_INDEX_LIST_FN get_index_list;
DROP_INDEX_FN drop_index;
+ SAFE_SYNC safe_sync;
} provider_functions_t;
diff --git a/src/backend/dbi/gnc-backend-dbi.c b/src/backend/dbi/gnc-backend-dbi.c
index 215c658..912a9fa 100644
--- a/src/backend/dbi/gnc-backend-dbi.c
+++ b/src/backend/dbi/gnc-backend-dbi.c
@@ -101,13 +101,16 @@ static GSList* conn_get_table_list_sqlite3( dbi_conn conn, const gchar* dbname )
static void append_sqlite3_col_def( GString* ddl, GncSqlColumnInfo* info );
static GSList *conn_get_index_list_sqlite3( dbi_conn conn );
static void conn_drop_index_sqlite3 (dbi_conn conn, const gchar *index );
+static void conn_safe_sync_sqlite3 (QofBackend* qbe, QofBook *book);
+
static provider_functions_t provider_sqlite3 =
{
conn_create_table_ddl_sqlite3,
conn_get_table_list_sqlite3,
append_sqlite3_col_def,
conn_get_index_list_sqlite3,
- conn_drop_index_sqlite3
+ conn_drop_index_sqlite3,
+ conn_safe_sync_sqlite3
};
#define SQLITE3_TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d"
@@ -117,13 +120,16 @@ static /*@ null @*/ gchar* conn_create_table_ddl_mysql( GncSqlConnection* conn,
static void append_mysql_col_def( GString* ddl, GncSqlColumnInfo* info );
static GSList *conn_get_index_list_mysql( dbi_conn conn );
static void conn_drop_index_mysql (dbi_conn conn, const gchar *index );
+static void conn_safe_sync_mysql (QofBackend* qbe, QofBook *book);
+
static provider_functions_t provider_mysql =
{
conn_create_table_ddl_mysql,
conn_get_table_list,
append_mysql_col_def,
conn_get_index_list_mysql,
- conn_drop_index_mysql
+ conn_drop_index_mysql,
+ conn_safe_sync_mysql
};
#define MYSQL_TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d"
@@ -134,6 +140,7 @@ static GSList* conn_get_table_list_pgsql( dbi_conn conn, const gchar* dbname );
static void append_pgsql_col_def( GString* ddl, GncSqlColumnInfo* info );
static GSList *conn_get_index_list_pgsql( dbi_conn conn );
static void conn_drop_index_pgsql (dbi_conn conn, const gchar *index );
+static void conn_safe_sync_pgsql (QofBackend* qbe, QofBook *book);
static provider_functions_t provider_pgsql =
{
@@ -141,14 +148,18 @@ static provider_functions_t provider_pgsql =
conn_get_table_list_pgsql,
append_pgsql_col_def,
conn_get_index_list_pgsql,
- conn_drop_index_pgsql
+ conn_drop_index_pgsql,
+ conn_safe_sync_pgsql
};
#define PGSQL_TIMESPEC_STR_FORMAT "%04d%02d%02d %02d%02d%02d"
static gboolean gnc_dbi_lock_database( QofBackend *qbe, gboolean ignore_lock );
static void gnc_dbi_unlock( QofBackend *qbe );
+static gboolean gnc_dbi_check_and_rollback_failed_save(QofBackend *qbe);
static gboolean save_may_clobber_data( QofBackend* qbe );
-
+static gboolean gnc_dbi_do_safe_sync_all(QofBackend* qbe, QofBook *book);
+static gboolean conn_table_operation(GncSqlConnection *sql_conn,
+ GSList *table_name_list, TableOpType op);
static /*@ null @*/ gchar* create_index_ddl( GncSqlConnection* conn,
const gchar* index_name,
const gchar* table_name,
@@ -227,7 +238,7 @@ gnc_dbi_transaction_begin(QofBackend* qbe, dbi_conn conn)
{
dbi_result result;
if (sql_savepoint == 0)
- result = dbi_conn_queryf(conn, "BEGIN");
+ result = dbi_conn_queryf(conn, "BEGIN");
else
{
char *savepoint = g_strdup_printf("savepoint_%d", sql_savepoint);
@@ -255,7 +266,7 @@ gnc_dbi_transaction_commit(QofBackend *qbe, dbi_conn conn)
dbi_result result;
g_return_val_if_fail(sql_savepoint > 0, FALSE);
if (sql_savepoint == 1)
- result = dbi_conn_queryf(conn, "COMMIT");
+ result = dbi_conn_queryf(conn, "COMMIT");
else
{
char *savepoint = g_strdup_printf("savepoint_%d", sql_savepoint - 1);
@@ -285,7 +296,7 @@ gnc_dbi_transaction_rollback(QofBackend *qbe, dbi_conn conn)
DEBUG( "ROLLBACK\n" );
g_return_val_if_fail(sql_savepoint > 0, FALSE);
if (sql_savepoint == 1)
- result = dbi_conn_queryf(conn, "ROLLBACK");
+ result = dbi_conn_queryf(conn, "ROLLBACK");
else
{
char *savepoint = g_strdup_printf("savepoint_%d", sql_savepoint - 1);
@@ -308,6 +319,60 @@ gnc_dbi_transaction_rollback(QofBackend *qbe, dbi_conn conn)
return FALSE;
}
+static gboolean
+gnc_dbi_do_safe_sync_all(QofBackend* qbe, QofBook* book)
+{
+ GncDbiBackend *be = (GncDbiBackend*)qbe;
+ GncDbiSqlConnection *conn = (GncDbiSqlConnection*)(((GncSqlBackend*)be)->conn);
+ GSList *table_list, *index_list, *iter;
+ const gchar* dbname = NULL;
+ dbname = dbi_conn_get_option( be->conn, "dbname" );
+ table_list = conn->provider->get_table_list( conn->conn, dbname );
+ if ( !conn_table_operation( (GncSqlConnection*)conn, table_list,
+ backup ) )
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ conn_table_operation( (GncSqlConnection*)conn, table_list,
+ rollback );
+ LEAVE( "Failed to rename tables" );
+ gnc_table_slist_free( table_list );
+ return FALSE;
+ }
+ index_list = conn->provider->get_index_list( conn->conn );
+ for ( iter = index_list; iter != NULL; iter = g_slist_next( iter) )
+ {
+ const char *errmsg;
+ conn->provider->drop_index (conn->conn, iter->data);
+ if ( DBI_ERROR_NONE != dbi_conn_error( conn->conn, &errmsg ) )
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ gnc_table_slist_free( index_list );
+ conn_table_operation( (GncSqlConnection*)conn, table_list,
+ rollback );
+ gnc_table_slist_free( table_list );
+ LEAVE( "Failed to drop indexes %s", errmsg );
+ return FALSE;
+ }
+ }
+ gnc_table_slist_free( index_list );
+
+ be->is_pristine_db = TRUE;
+ be->primary_book = book;
+
+ gnc_sql_sync_all( &be->sql_be, book );
+ if (qof_backend_check_error (qbe))
+ {
+ conn_table_operation( (GncSqlConnection*)conn, table_list,
+ rollback );
+ gnc_dbi_transaction_rollback(qbe, be->conn);
+ LEAVE( "Failed to create new database tables" );
+ return FALSE;
+ }
+ conn_table_operation( (GncSqlConnection*)conn, table_list,
+ drop_backup );
+ gnc_table_slist_free( table_list );
+ return TRUE;
+}
/* ================================================================= */
static void
@@ -469,14 +534,17 @@ gnc_dbi_sqlite3_session_begin( QofBackend *qbe, QofSession *session,
msg = "Locked";
goto exit;
}
-
if ( be->sql_be.conn != NULL )
{
gnc_sql_connection_dispose( be->sql_be.conn );
}
be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_SQLITE, qbe, be->conn );
be->sql_be.timespec_format = SQLITE3_TIMESPEC_STR_FORMAT;
-
+ if (! gnc_dbi_check_and_rollback_failed_save(qbe))
+ {
+ gnc_sql_connection_dispose(be->sql_be.conn);
+ goto exit;
+ }
/* We should now have a proper session set up.
* Let's start logging */
xaccLogSetBaseName (filepath);
@@ -520,6 +588,24 @@ conn_drop_index_sqlite3 (dbi_conn conn, const gchar *index )
}
static void
+conn_safe_sync_sqlite3 (QofBackend* qbe, QofBook *book)
+{
+ GncDbiBackend *be = (GncDbiBackend*)qbe;
+ if (!gnc_dbi_transaction_begin(qbe, be->conn))
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ qof_backend_set_message(qbe, "Backend save failed, couldn't obtain the lock.");
+ }
+ if (!gnc_dbi_do_safe_sync_all(qbe, book))
+ gnc_dbi_transaction_rollback(qbe, be->conn);
+ else if (!gnc_dbi_transaction_commit(qbe, be->conn))
+ {
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
+ qof_backend_set_message(qbe, "Save failed, unable to commit the SQL transaction.");
+ }
+}
+
+static void
mysql_error_fn( dbi_conn conn, void* user_data )
{
GncDbiBackend *be = (GncDbiBackend*)user_data;
@@ -716,43 +802,24 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
result = NULL;
}
- /* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */
- result = dbi_conn_queryf( dcon, "SELECT * FROM %s", lock_table );
- if ( result && dbi_result_get_numrows( result ) )
+ /* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */
+ result = dbi_conn_queryf( dcon, "SELECT * FROM %s", lock_table );
+ if ( result && dbi_result_get_numrows( result ) )
+ {
+ dbi_result_free( result );
+ result = NULL;
+ if ( !ignore_lock )
{
- dbi_result_free( result );
- result = NULL;
- if ( !ignore_lock )
- {
- qof_backend_set_error( qbe, ERR_BACKEND_LOCKED );
- /* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */
- gnc_dbi_transaction_rollback(qbe, dcon);
- return FALSE;
- }
- result = dbi_conn_queryf( dcon, "DELETE FROM %s", lock_table );
- if ( !result)
- {
- qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
- qof_backend_set_message( qbe, "Failed to delete lock record" );
- gnc_dbi_transaction_rollback(qbe, dcon);
- return FALSE;
- }
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
+ qof_backend_set_error( qbe, ERR_BACKEND_LOCKED );
+ /* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */
+ gnc_dbi_transaction_rollback(qbe, dcon);
+ return FALSE;
}
- /* Add an entry and commit the transaction */
- memset( hostname, 0, sizeof(hostname) );
- gethostname( hostname, GNC_HOST_NAME_MAX );
- result = dbi_conn_queryf( dcon,
- "INSERT INTO %s VALUES ('%s', '%d')",
- lock_table, hostname, (int)GETPID() );
+ result = dbi_conn_queryf( dcon, "DELETE FROM %s", lock_table );
if ( !result)
{
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
- qof_backend_set_message( qbe, "Failed to create lock record" );
+ qof_backend_set_message( qbe, "Failed to delete lock record" );
gnc_dbi_transaction_rollback(qbe, dcon);
return FALSE;
}
@@ -761,10 +828,29 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
dbi_result_free( result );
result = NULL;
}
- if (gnc_dbi_transaction_commit(qbe, dcon))
- return TRUE;
- /* Uh-oh, abort! */
+ }
+ /* Add an entry and commit the transaction */
+ memset( hostname, 0, sizeof(hostname) );
+ gethostname( hostname, GNC_HOST_NAME_MAX );
+ result = dbi_conn_queryf( dcon,
+ "INSERT INTO %s VALUES ('%s', '%d')",
+ lock_table, hostname, (int)GETPID() );
+ if ( !result)
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ qof_backend_set_message( qbe, "Failed to create lock record" );
gnc_dbi_transaction_rollback(qbe, dcon);
+ return FALSE;
+ }
+ if (result)
+ {
+ dbi_result_free( result );
+ result = NULL;
+ }
+ if (gnc_dbi_transaction_commit(qbe, dcon))
+ return TRUE;
+ /* Uh-oh, abort! */
+ gnc_dbi_transaction_rollback(qbe, dcon);
/* Couldn't commit the transaction, database isn't locked! */
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
qof_backend_set_message( qbe, "SQL Backend failed to lock the database, it was unable to commit the SQL transaction." );
@@ -1134,14 +1220,18 @@ gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session,
gnc_sql_connection_dispose( be->sql_be.conn );
}
be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_MYSQL, qbe, be->conn );
- be->sql_be.timespec_format = MYSQL_TIMESPEC_STR_FORMAT;
-
- /* We should now have a proper session set up.
- * Let's start logging */
- basename = g_strjoin("_", protocol, host, username, dbname, NULL);
- translog_path = gnc_build_translog_path (basename);
- xaccLogSetBaseName (translog_path);
- PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
+ be->sql_be.timespec_format = MYSQL_TIMESPEC_STR_FORMAT;
+ if (! gnc_dbi_check_and_rollback_failed_save(qbe))
+ {
+ gnc_sql_connection_dispose(be->sql_be.conn);
+ goto exit;
+ }
+ /* We should now have a proper session set up.
+ * Let's start logging */
+ basename = g_strjoin("_", protocol, host, username, dbname, NULL);
+ translog_path = gnc_build_translog_path (basename);
+ xaccLogSetBaseName (translog_path);
+ PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
}
exit:
g_free( protocol );
@@ -1218,6 +1308,18 @@ conn_drop_index_mysql (dbi_conn conn, const gchar *index )
g_strfreev (index_table_split);
}
+/* MySQL automatically commits after any operation that changes a
+ * database's structure, like CREATE_TABLE or DROP_TABLE. Even if one
+ * disables that the changes can't be rolled back, so there's
+ * no way to make the save atomic. If the save doesn't complete we
+ * must rely on gnc_dbi_check_and_rollback_failed_save.
+ */
+static void
+conn_safe_sync_mysql (QofBackend* qbe, QofBook *book)
+{
+ gnc_dbi_do_safe_sync_all(qbe, book);
+}
+
static void
pgsql_error_fn( dbi_conn conn, void* user_data )
{
@@ -1480,19 +1582,23 @@ gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session,
}
if ( success )
{
- if ( be->sql_be.conn != NULL )
- {
- gnc_sql_connection_dispose( be->sql_be.conn );
- }
- be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_PGSQL, qbe, be->conn );
- be->sql_be.timespec_format = PGSQL_TIMESPEC_STR_FORMAT;
-
- /* We should now have a proper session set up.
- * Let's start logging */
- basename = g_strjoin("_", protocol, host, username, dbname, NULL);
- translog_path = gnc_build_translog_path (basename);
- xaccLogSetBaseName (translog_path);
- PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
+ if ( be->sql_be.conn != NULL )
+ {
+ gnc_sql_connection_dispose( be->sql_be.conn );
+ }
+ be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_PGSQL, qbe, be->conn );
+ be->sql_be.timespec_format = PGSQL_TIMESPEC_STR_FORMAT;
+ if (! gnc_dbi_check_and_rollback_failed_save(qbe))
+ {
+ gnc_sql_connection_dispose(be->sql_be.conn);
+ goto exit;
+ }
+ /* We should now have a proper session set up.
+ * Let's start logging */
+ basename = g_strjoin("_", protocol, host, username, dbname, NULL);
+ translog_path = gnc_build_translog_path (basename);
+ xaccLogSetBaseName (translog_path);
+ PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
}
exit:
g_free( protocol );
@@ -1539,6 +1645,23 @@ conn_drop_index_pgsql (dbi_conn conn, const gchar *index )
dbi_result_free( result );
}
+static void
+conn_safe_sync_pgsql (QofBackend* qbe, QofBook *book)
+{
+ GncDbiBackend *be = (GncDbiBackend*)qbe;
+ if (!gnc_dbi_transaction_begin(qbe, be->conn))
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ qof_backend_set_message(qbe, "Backend save failed, couldn't obtain the lock.");
+ }
+ if (!gnc_dbi_do_safe_sync_all(qbe, book))
+ gnc_dbi_transaction_rollback(qbe, be->conn);
+ else if (!gnc_dbi_transaction_commit(qbe, be->conn))
+ {
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
+ qof_backend_set_message(qbe, "Save failed, unable to commit the SQL transaction.");
+ }
+}
/* ================================================================= */
@@ -1791,59 +1914,63 @@ gnc_dbi_safe_sync_all( QofBackend *qbe, QofBook *book )
{
GncDbiBackend *be = (GncDbiBackend*)qbe;
GncDbiSqlConnection *conn = (GncDbiSqlConnection*)(((GncSqlBackend*)be)->conn);
- GSList *table_list, *index_list, *iter;
- const gchar* dbname = NULL;
-
g_return_if_fail( be != NULL );
g_return_if_fail( book != NULL );
-
+ conn->provider->safe_sync(qbe, book);
ENTER( "book=%p, primary=%p", book, be->primary_book );
- dbname = dbi_conn_get_option( be->conn, "dbname" );
- table_list = conn->provider->get_table_list( conn->conn, dbname );
- if ( !conn_table_operation( (GncSqlConnection*)conn, table_list,
- backup ) )
+ LEAVE("book=%p", book);
+}
+
+static int
+find_backup_cb(gconstpointer data, gconstpointer user)
+{
+ char* table_name = (char*)data;
+ char* suffix = (char*)user;
+ if (g_str_has_suffix(table_name, suffix))
+ return 0;
+ return 1;
+}
+
+static gboolean
+gnc_dbi_check_and_rollback_failed_save(QofBackend *qbe)
+{
+ GncDbiBackend *be = (GncDbiBackend*)qbe;
+ GncSqlConnection *conn = (GncSqlConnection*)(((GncSqlBackend*)be)->conn);
+ dbi_result result;
+ GSList *table_list = NULL;
+ const gchar* dbname = NULL;
+ static const char* suffix = "%back";
+ g_return_val_if_fail(be != NULL, FALSE);
+ g_return_val_if_fail(conn != NULL, FALSE);
+
+ dbname = dbi_conn_get_option(be->conn, "dbname");
+ result = dbi_conn_get_table_list(be->conn, dbname, suffix);
+ while (dbi_result_next_row(result) != 0)
{
- qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
- conn_table_operation( (GncSqlConnection*)conn, table_list,
- rollback );
- LEAVE( "Failed to rename tables" );
- gnc_table_slist_free( table_list );
- return;
+ const char *table_name = dbi_result_get_string_idx(result, 1);
+ table_list = g_slist_prepend(table_list, g_strdup(table_name));
}
- index_list = conn->provider->get_index_list( conn->conn );
- for ( iter = index_list; iter != NULL; iter = g_slist_next( iter) )
+ if (result)
+ dbi_result_free(result);
+ if (table_list == NULL)
+ return TRUE;
+ if (!gnc_dbi_transaction_begin(qbe, be->conn))
{
- const char *errmsg;
- conn->provider->drop_index (conn->conn, iter->data);
- if ( DBI_ERROR_NONE != dbi_conn_error( conn->conn, &errmsg ) )
- {
- qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
- gnc_table_slist_free( index_list );
- conn_table_operation( (GncSqlConnection*)conn, table_list,
- rollback );
- gnc_table_slist_free( table_list );
- LEAVE( "Failed to drop indexes %s", errmsg );
- return;
- }
+ qof_backend_set_message(qbe, "Backup tables found from a failed safe sync, unable to lock the database to restore them.");
+ g_slist_free_full(table_list, g_free);
+ return FALSE;
}
- gnc_table_slist_free( index_list );
-
- be->is_pristine_db = TRUE;
- be->primary_book = book;
-
- gnc_sql_sync_all( &be->sql_be, book );
- if (qof_backend_check_error (qbe))
+ conn_table_operation((GncSqlConnection*)conn, table_list, rollback);
+ g_slist_free_full(table_list, g_free);
+ if (!gnc_dbi_transaction_commit(qbe, be->conn))
{
- conn_table_operation( (GncSqlConnection*)conn, table_list,
- rollback );
- LEAVE( "Failed to create new database tables" );
- return;
+ qof_backend_set_message(qbe, "Backup tables found from a failed safe sync, unable to commit the restoration transaction.");
+ gnc_dbi_transaction_rollback(qbe, be->conn);
+ return FALSE;
}
- conn_table_operation( (GncSqlConnection*)conn, table_list,
- drop_backup );
- gnc_table_slist_free( table_list );
- LEAVE("book=%p", book);
+ return TRUE;
}
+
/* ================================================================= */
static void
gnc_dbi_begin_edit( QofBackend *qbe, QofInstance *inst )
commit a70637f34d48461cb8551d205935d8b3663869af
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Feb 4 09:58:27 2017 -0800
Move the transaction-lock on obtaining the database lock earlier.
So that opening/creating the table is included. Also modify the condition
to reduce code nesting.
diff --git a/src/backend/dbi/gnc-backend-dbi.c b/src/backend/dbi/gnc-backend-dbi.c
index 4ef104d..215c658 100644
--- a/src/backend/dbi/gnc-backend-dbi.c
+++ b/src/backend/dbi/gnc-backend-dbi.c
@@ -673,6 +673,14 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
dbi_conn dcon = qe->conn;
dbi_result result;
const gchar *dbname = dbi_conn_get_option( dcon, "dbname" );
+ gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
+
+ if (!gnc_dbi_transaction_begin(qbe, dcon))
+ {
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ qof_backend_set_message( qbe, "SQL Backend lock database failed, couldn't obtain a transaction." );
+ return FALSE;
+ }
/* Create the table if it doesn't exist */
result = dbi_conn_get_table_list( dcon, dbname, lock_table);
if (!( result && dbi_result_get_numrows( result ) ))
@@ -707,16 +715,8 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
dbi_result_free( result );
result = NULL;
}
- /* Protect everything with a single transaction to prevent races */
- if (gnc_dbi_transaction_begin(qbe, dcon))
- {
+
/* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */
- gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
result = dbi_conn_queryf( dcon, "SELECT * FROM %s", lock_table );
if ( result && dbi_result_get_numrows( result ) )
{
@@ -765,12 +765,12 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
return TRUE;
/* Uh-oh, abort! */
gnc_dbi_transaction_rollback(qbe, dcon);
- }
- /* Couldn't get a transaction (probably couldn't get a lock) so fail */
+ /* Couldn't commit the transaction, database isn't locked! */
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
- qof_backend_set_message( qbe, "SQL Backend failed to get a transaction");
+ qof_backend_set_message( qbe, "SQL Backend failed to lock the database, it was unable to commit the SQL transaction." );
return FALSE;
}
+
static void
gnc_dbi_unlock( QofBackend *qbe )
{
@@ -778,6 +778,7 @@ gnc_dbi_unlock( QofBackend *qbe )
dbi_conn dcon = qe->conn;
dbi_result result;
const gchar *dbname = NULL;
+ gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
g_return_if_fail( dcon != NULL );
g_return_if_fail( dbi_conn_error( dcon, NULL ) == 0 );
@@ -806,7 +807,6 @@ gnc_dbi_unlock( QofBackend *qbe )
}
/* Delete the entry if it's our hostname and PID */
- gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
memset( hostname, 0, sizeof(hostname) );
gethostname( hostname, GNC_HOST_NAME_MAX );
result = dbi_conn_queryf( dcon, "SELECT * FROM %s WHERE Hostname = '%s' AND PID = '%d'", lock_table, hostname, (int)GETPID() );
@@ -1134,7 +1134,6 @@ gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session,
gnc_sql_connection_dispose( be->sql_be.conn );
}
be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_MYSQL, qbe, be->conn );
- }
be->sql_be.timespec_format = MYSQL_TIMESPEC_STR_FORMAT;
/* We should now have a proper session set up.
@@ -1143,7 +1142,7 @@ gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session,
translog_path = gnc_build_translog_path (basename);
xaccLogSetBaseName (translog_path);
PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
-
+ }
exit:
g_free( protocol );
g_free( host );
@@ -1486,7 +1485,6 @@ gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session,
gnc_sql_connection_dispose( be->sql_be.conn );
}
be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_PGSQL, qbe, be->conn );
- }
be->sql_be.timespec_format = PGSQL_TIMESPEC_STR_FORMAT;
/* We should now have a proper session set up.
@@ -1495,7 +1493,7 @@ gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session,
translog_path = gnc_build_translog_path (basename);
xaccLogSetBaseName (translog_path);
PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
-
+ }
exit:
g_free( protocol );
g_free( host );
commit 979e6397c15290550231d3c8ce18a1097157ccc0
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Feb 4 09:50:50 2017 -0800
Add SAVEPOINT support to enable nested gnc_dbi_transaction calls.
diff --git a/src/backend/dbi/gnc-backend-dbi.c b/src/backend/dbi/gnc-backend-dbi.c
index 26fa9e1..4ef104d 100644
--- a/src/backend/dbi/gnc-backend-dbi.c
+++ b/src/backend/dbi/gnc-backend-dbi.c
@@ -220,12 +220,20 @@ gnc_dbi_verify_conn( GncDbiSqlConnection* dbi_conn )
return dbi_conn->conn_ok;
}
+static unsigned int sql_savepoint = 0;
static gboolean
gnc_dbi_transaction_begin(QofBackend* qbe, dbi_conn conn)
{
dbi_result result;
+ if (sql_savepoint == 0)
result = dbi_conn_queryf(conn, "BEGIN");
+ else
+ {
+ char *savepoint = g_strdup_printf("savepoint_%d", sql_savepoint);
+ result = dbi_conn_queryf(conn, "SAVEPOINT %s", savepoint);
+ g_free(savepoint);
+ }
if (result != NULL)
{
if(dbi_result_free(result) != 0)
@@ -233,6 +241,7 @@ gnc_dbi_transaction_begin(QofBackend* qbe, dbi_conn conn)
PERR( "Error in dbi_result_free() result\n" );
qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
}
+ ++sql_savepoint;
return TRUE;
}
PERR( "BEGIN transaction failed()\n" );
@@ -244,7 +253,16 @@ static gboolean
gnc_dbi_transaction_commit(QofBackend *qbe, dbi_conn conn)
{
dbi_result result;
+ g_return_val_if_fail(sql_savepoint > 0, FALSE);
+ if (sql_savepoint == 1)
result = dbi_conn_queryf(conn, "COMMIT");
+ else
+ {
+ char *savepoint = g_strdup_printf("savepoint_%d", sql_savepoint - 1);
+ result = dbi_conn_queryf(conn, "RELEASE SAVEPOINT %s",
+ savepoint);
+ g_free(savepoint);
+ }
if (result != NULL)
{
if(dbi_result_free(result) != 0)
@@ -252,6 +270,7 @@ gnc_dbi_transaction_commit(QofBackend *qbe, dbi_conn conn)
PERR( "Error in dbi_result_free() result\n" );
qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
}
+ --sql_savepoint;
return TRUE;
}
PERR( "COMMIT transaction failed()\n" );
@@ -264,7 +283,16 @@ gnc_dbi_transaction_rollback(QofBackend *qbe, dbi_conn conn)
{
dbi_result result;
DEBUG( "ROLLBACK\n" );
+ g_return_val_if_fail(sql_savepoint > 0, FALSE);
+ if (sql_savepoint == 1)
result = dbi_conn_queryf(conn, "ROLLBACK");
+ else
+ {
+ char *savepoint = g_strdup_printf("savepoint_%d", sql_savepoint - 1);
+ result = dbi_conn_queryf(conn, "ROLLBACK TO SAVEPOINT %s",
+ savepoint);
+ g_free(savepoint);
+ }
if (result != NULL)
{
if(dbi_result_free(result) != 0)
@@ -272,6 +300,7 @@ gnc_dbi_transaction_rollback(QofBackend *qbe, dbi_conn conn)
PERR( "Error in dbi_result_free() result\n" );
qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR );
}
+ --sql_savepoint;
return TRUE;
}
PERR( "ROLLBACK transaction failed()\n" );
commit db73c39bf182904d01b3531524481aa274a2462a
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Feb 4 09:41:45 2017 -0800
Extract static gnc_dbi_transaction functions.
To enable local calls (as opposed to just virtual calls via
GncDbiSqlConnection) and replace all direct transaction queries with the
new functions.
diff --git a/src/backend/dbi/gnc-backend-dbi.c b/src/backend/dbi/gnc-backend-dbi.c
index a716ad2..26fa9e1 100644
--- a/src/backend/dbi/gnc-backend-dbi.c
+++ b/src/backend/dbi/gnc-backend-dbi.c
@@ -221,6 +221,64 @@ gnc_dbi_verify_conn( GncDbiSqlConnection* dbi_conn )
return dbi_conn->conn_ok;
}
+static gboolean
+gnc_dbi_transaction_begin(QofBackend* qbe, dbi_conn conn)
+{
+ dbi_result result;
+ result = dbi_conn_queryf(conn, "BEGIN");
+ if (result != NULL)
+ {
+ if(dbi_result_free(result) != 0)
+ {
+ PERR( "Error in dbi_result_free() result\n" );
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
+ }
+ return TRUE;
+ }
+ PERR( "BEGIN transaction failed()\n" );
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
+ return FALSE;
+}
+
+static gboolean
+gnc_dbi_transaction_commit(QofBackend *qbe, dbi_conn conn)
+{
+ dbi_result result;
+ result = dbi_conn_queryf(conn, "COMMIT");
+ if (result != NULL)
+ {
+ if(dbi_result_free(result) != 0)
+ {
+ PERR( "Error in dbi_result_free() result\n" );
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
+ }
+ return TRUE;
+ }
+ PERR( "COMMIT transaction failed()\n" );
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR);
+ return FALSE;
+}
+
+static gboolean
+gnc_dbi_transaction_rollback(QofBackend *qbe, dbi_conn conn)
+{
+ dbi_result result;
+ DEBUG( "ROLLBACK\n" );
+ result = dbi_conn_queryf(conn, "ROLLBACK");
+ if (result != NULL)
+ {
+ if(dbi_result_free(result) != 0)
+ {
+ PERR( "Error in dbi_result_free() result\n" );
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR );
+ }
+ return TRUE;
+ }
+ PERR( "ROLLBACK transaction failed()\n" );
+ qof_backend_set_error(qbe, ERR_BACKEND_SERVER_ERR );
+ return FALSE;
+}
+
/* ================================================================= */
static void
@@ -620,9 +678,8 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
dbi_result_free( result );
result = NULL;
}
-
/* Protect everything with a single transaction to prevent races */
- if ( (result = dbi_conn_query( dcon, "BEGIN" )) )
+ if (gnc_dbi_transaction_begin(qbe, dcon))
{
/* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */
gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
@@ -640,7 +697,7 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
{
qof_backend_set_error( qbe, ERR_BACKEND_LOCKED );
/* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */
- dbi_conn_query( dcon, "ROLLBACK" );
+ gnc_dbi_transaction_rollback(qbe, dcon);
return FALSE;
}
result = dbi_conn_queryf( dcon, "DELETE FROM %s", lock_table );
@@ -648,12 +705,7 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
{
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
qof_backend_set_message( qbe, "Failed to delete lock record" );
- result = dbi_conn_query( dcon, "ROLLBACK" );
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
+ gnc_dbi_transaction_rollback(qbe, dcon);
return FALSE;
}
if (result)
@@ -672,12 +724,7 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
{
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
qof_backend_set_message( qbe, "Failed to create lock record" );
- result = dbi_conn_query( dcon, "ROLLBACK" );
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
+ gnc_dbi_transaction_rollback(qbe, dcon);
return FALSE;
}
if (result)
@@ -685,22 +732,14 @@ gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
dbi_result_free( result );
result = NULL;
}
- result = dbi_conn_query( dcon, "COMMIT" );
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
- return TRUE;
+ if (gnc_dbi_transaction_commit(qbe, dcon))
+ return TRUE;
+ /* Uh-oh, abort! */
+ gnc_dbi_transaction_rollback(qbe, dcon);
}
- /* Couldn't get a transaction (probably couldn't get a lock), so fail */
+ /* Couldn't get a transaction (probably couldn't get a lock) so fail */
qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
- qof_backend_set_message( qbe, "SQL Backend failed to obtain a transaction" );
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
+ qof_backend_set_message( qbe, "SQL Backend failed to get a transaction");
return FALSE;
}
static void
@@ -729,67 +768,49 @@ gnc_dbi_unlock( QofBackend *qbe )
return;
}
dbi_result_free( result );
+ result = NULL;
- result = dbi_conn_query( dcon, "BEGIN" );
- if ( result )
+ if (!gnc_dbi_transaction_begin(qbe, dcon))
{
- /* Delete the entry if it's our hostname and PID */
- gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
+ PWARN("Unable to get a lock on LOCK, so failed to clear the lock entry.");
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ }
- dbi_result_free( result );
- result = NULL;
- memset( hostname, 0, sizeof(hostname) );
- gethostname( hostname, GNC_HOST_NAME_MAX );
- result = dbi_conn_queryf( dcon, "SELECT * FROM %s WHERE Hostname = '%s' AND PID = '%d'", lock_table, hostname, (int)GETPID() );
- if ( result && dbi_result_get_numrows( result ) )
+ /* Delete the entry if it's our hostname and PID */
+ gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
+ memset( hostname, 0, sizeof(hostname) );
+ gethostname( hostname, GNC_HOST_NAME_MAX );
+ result = dbi_conn_queryf( dcon, "SELECT * FROM %s WHERE Hostname = '%s' AND PID = '%d'", lock_table, hostname, (int)GETPID() );
+ if ( result && dbi_result_get_numrows( result ) )
+ {
+ if (result)
{
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
- result = dbi_conn_queryf( dcon, "DELETE FROM %s", lock_table );
- if ( !result)
- {
- PERR("Failed to delete the lock entry");
- qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
- result = dbi_conn_query( dcon, "ROLLBACK" );
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
- return;
- }
- else
- {
- dbi_result_free( result );
- result = NULL;
- }
- result = dbi_conn_query( dcon, "COMMIT" );
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
- return;
+ dbi_result_free( result );
+ result = NULL;
}
- result = dbi_conn_query( dcon, "ROLLBACK" );
- if (result)
+ result = dbi_conn_queryf( dcon, "DELETE FROM %s", lock_table );
+ if ( !result)
+ {
+ PERR("Failed to delete the lock entry");
+ qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+ gnc_dbi_transaction_rollback(qbe, dcon);
+ }
+ else
{
dbi_result_free( result );
result = NULL;
}
- PWARN("There was no lock entry in the Lock table");
+ if (!gnc_dbi_transaction_commit(qbe, dcon))
+ {
+ gnc_dbi_transaction_rollback(qbe, dcon);
+ PWARN("Failed to unlock the database, unable to commit the SQL transaction.");
+ }
return;
}
- if (result)
- {
- dbi_result_free( result );
- result = NULL;
- }
- PWARN("Unable to get a lock on LOCK, so failed to clear the lock entry.");
- qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
+
+ gnc_dbi_transaction_rollback(qbe, dcon);
+ PWARN("There was no lock entry in the Lock table");
+ return;
}
#define SQL_OPTION_TO_REMOVE "NO_ZERO_DATE"
@@ -2543,39 +2564,23 @@ static gboolean
conn_begin_transaction( /*@ unused @*/ GncSqlConnection* conn )
{
GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
- dbi_result result;
gint status;
gboolean success = FALSE;
DEBUG( "BEGIN\n" );
-
if ( !gnc_dbi_verify_conn (dbi_conn) )
{
PERR( "gnc_dbi_verify_conn() failed\n" );
qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
return FALSE;
}
-
do
{
gnc_dbi_init_error( dbi_conn );
- result = dbi_conn_queryf( dbi_conn->conn, "BEGIN" );
+ success = gnc_dbi_transaction_begin(dbi_conn->qbe, dbi_conn->conn);
}
while ( dbi_conn->retry );
- success = ( result != NULL );
- status = dbi_result_free( result );
- if ( status < 0 )
- {
- PERR( "Error in dbi_result_free() result\n" );
- qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
- }
- if ( !success )
- {
- PERR( "BEGIN transaction failed()\n" );
- qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
- }
-
return success;
}
@@ -2583,54 +2588,14 @@ static gboolean
conn_rollback_transaction( /*@ unused @*/ GncSqlConnection* conn )
{
GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
- dbi_result result;
- gint status;
- gboolean success = FALSE;
-
- DEBUG( "ROLLBACK\n" );
- result = dbi_conn_queryf( dbi_conn->conn, "ROLLBACK" );
- success = ( result != NULL );
-
- status = dbi_result_free( result );
- if ( status < 0 )
- {
- PERR( "Error in dbi_result_free() result\n" );
- qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
- }
- if ( !success )
- {
- PERR( "Error in conn_rollback_transaction()\n" );
- qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
- }
-
- return success;
+ return gnc_dbi_transaction_rollback(dbi_conn->qbe, dbi_conn->conn);
}
static gboolean
conn_commit_transaction( /*@ unused @*/ GncSqlConnection* conn )
{
GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
- dbi_result result;
- gint status;
- gboolean success = FALSE;
-
- DEBUG( "COMMIT\n" );
- result = dbi_conn_queryf( dbi_conn->conn, "COMMIT" );
- success = ( result != NULL );
-
- status = dbi_result_free( result );
- if ( status < 0 )
- {
- PERR( "Error in dbi_result_free() result\n" );
- qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
- }
- if ( !success )
- {
- PERR( "Error in conn_commit_transaction()\n" );
- qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
- }
-
- return success;
+ return gnc_dbi_transaction_commit(dbi_conn->qbe, dbi_conn->conn);
}
static /*@ null @*/ gchar*
Summary of changes:
src/backend/dbi/gnc-backend-dbi-priv.h | 2 +
src/backend/dbi/gnc-backend-dbi.c | 603 ++++++++++++++++++++-------------
2 files changed, 363 insertions(+), 242 deletions(-)
More information about the gnucash-changes
mailing list