r20123 - gnucash/trunk/src/backend - Enhanced database version handling.

John Ralls jralls at code.gnucash.org
Tue Jan 18 18:42:49 EST 2011


Author: jralls
Date: 2011-01-18 18:42:48 -0500 (Tue, 18 Jan 2011)
New Revision: 20123
Trac: http://svn.gnucash.org/trac/changeset/20123

Modified:
   gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c
   gnucash/trunk/src/backend/dbi/test/test-dbi-basic.c
   gnucash/trunk/src/backend/dbi/test/test-dbi-stuff.c
   gnucash/trunk/src/backend/sql/gnc-backend-sql.c
   gnucash/trunk/src/backend/sql/gnc-backend-sql.h
Log:
Enhanced database version handling.

* Add a macro GNC_RESAVE_VERSION which holds an svn revision number. 
* When fully saving a database put this number in versions with 
  table_name Gnucash-Resave.
* On database load, compare the current GNC_RESAVE_VERSION with the 
  Gnucash revision and Gnucash-Resave revision saved when the database 
  was created. 
* If the current GNC_RESAVE_VERSION > the saved Gnucash 
  version, then emit ERR_SQL_DB_TOO_OLD.
* If GNC_RESAVE_VERSION < the saved Gnucash-Resave, emit ERR_SQL_DB_TOO_NEW.



Modified: gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c
===================================================================
--- gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c	2011-01-18 19:27:23 UTC (rev 20122)
+++ gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c	2011-01-18 23:42:48 UTC (rev 20123)
@@ -1190,6 +1190,24 @@
 
     gnc_sql_load( &be->sql_be, book, loadType );
 
+    if ( GNC_RESAVE_VERSION > gnc_sql_get_table_version( &be->sql_be, "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 ( GNC_RESAVE_VERSION < gnc_sql_get_table_version( &be->sql_be,
+							      "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( "" );
 }
 

Modified: gnucash/trunk/src/backend/dbi/test/test-dbi-basic.c
===================================================================
--- gnucash/trunk/src/backend/dbi/test/test-dbi-basic.c	2011-01-18 19:27:23 UTC (rev 20122)
+++ gnucash/trunk/src/backend/dbi/test/test-dbi-basic.c	2011-01-18 23:42:48 UTC (rev 20123)
@@ -114,6 +114,7 @@
     test_dbi_store_and_reload( "sqlite3", session_1, filename );
     session_1 = create_session();
     test_dbi_safe_save( "sqlite3", filename );
+    test_dbi_version_control( "sqlite3", filename );
 #ifdef TEST_MYSQL_URL
     printf( "TEST_MYSQL_URL='%s'\n", TEST_MYSQL_URL );
     if ( strlen( TEST_MYSQL_URL ) > 0 )
@@ -121,7 +122,8 @@
         session_1 = create_session();
         test_dbi_store_and_reload( "mysql", session_1, TEST_MYSQL_URL );
 	session_1 = create_session();
-	test_dbi_safe_save( "msql", filename );
+	test_dbi_safe_save( "mysql", filename );
+	test_dbi_version_control( "mysql", filename );
     }
 #endif
 #ifdef TEST_PGSQL_URL
@@ -132,6 +134,7 @@
         test_dbi_store_and_reload( "pgsql", session_1, TEST_PGSQL_URL );
 	session_1 = create_session();
 	test_dbi_safe_save( "pgsql", filename );
+	test_dbi_version_control( "pgsql", filename );
     }
 #endif
     print_test_results();

Modified: gnucash/trunk/src/backend/dbi/test/test-dbi-stuff.c
===================================================================
--- gnucash/trunk/src/backend/dbi/test/test-dbi-stuff.c	2011-01-18 19:27:23 UTC (rev 20122)
+++ gnucash/trunk/src/backend/dbi/test/test-dbi-stuff.c	2011-01-18 23:42:48 UTC (rev 20123)
@@ -25,6 +25,7 @@
 
 #include "config.h"
 #include "qof.h"
+#include "qofsession-p.h"
 #include "cashobjects.h"
 #include "test-engine-stuff.h"
 #include "test-stuff.h"
@@ -159,7 +160,9 @@
     test_conn_get_index_list( be );
 }
 
-
+/* Given a synthetic session, use the same logic as
+ * QofSession::save_as to save it to a specified sql url, then load it
+ * back and compare. */
 void
 test_dbi_store_and_reload( const gchar* driver, QofSession* session_1, const gchar* url )
 {
@@ -213,6 +216,13 @@
     qof_session_destroy( session_3 );
 }
 
+/* Given an already-created url (yeah, bad testing practice: Should
+ * start fresh from a synthetic session) load and safe-save it, then
+ * load it again into a new session and compare the two. Since
+ * safe-save is a more-or-less atomic function call, there's no way to
+ * be sure that it's actually doing what it's supposed to without
+ * running this test in a debugger and stopping in the middle of the
+ * safe-save and inspecting the database. */
 void
 test_dbi_safe_save( const gchar* driver,  const gchar* url )
 {
@@ -261,3 +271,64 @@
     qof_session_destroy( session_1 );
     return;
 }
+
+/* Test the gnc_dbi_load logic that forces a newer database to be
+ * opened read-only and an older one to be safe-saved. Again, it would
+ * be better to do this starting from a fresh file, but instead we're
+ * being lazy and using an existing one. */
+void
+test_dbi_version_control( const gchar* driver,  const gchar* url )
+{
+
+    QofSession *sess;
+    QofBook *book;
+    QofBackend *qbe;
+    QofBackendError err;
+    gint ourversion = gnc_get_svn_version();
+
+    printf( "Testing safe save %s\n", driver );
+
+    // Load the session data
+    sess = qof_session_new();
+    qof_session_begin( sess, url, TRUE, FALSE, FALSE );
+    if (sess && qof_session_get_error(sess) != ERR_BACKEND_NO_ERR)
+    {
+        g_warning("Session Error: %d, %s", qof_session_get_error(sess),
+		  qof_session_get_error_message(sess));
+	do_test( FALSE, "DB Session Creation Failed");
+	goto cleanup;
+    }
+    qof_session_load( sess, NULL );
+    qbe = qof_session_get_backend( sess );
+    book = qof_session_get_book( sess );
+    qof_book_begin_edit( book );
+    gnc_sql_set_table_version( (GncSqlBackend*)qbe,
+			       "Gnucash", GNC_RESAVE_VERSION - 1 );
+    qof_book_commit_edit( book );
+    qof_session_end( sess );
+    qof_session_destroy( sess );
+    sess = qof_session_new();
+    qof_session_begin( sess, url, TRUE, FALSE, FALSE );
+    qof_session_load( sess, NULL );
+    err = qof_session_pop_error( sess );
+    do_test( err == ERR_SQL_DB_TOO_OLD, "DB Failed to flag too old" );
+    qbe = qof_session_get_backend( sess );
+    book = qof_session_get_book( sess );
+    qof_book_begin_edit( book );
+    gnc_sql_set_table_version( (GncSqlBackend*)qbe,
+			       "Gnucash", ourversion );
+    gnc_sql_set_table_version( (GncSqlBackend*)qbe,
+			       "Gnucash-Resave", ourversion + 1 );
+    qof_book_commit_edit( book );
+    qof_session_end( sess );
+    qof_session_destroy( sess );
+    sess = qof_session_new();
+    qof_session_begin( sess, url, TRUE, FALSE, FALSE );
+    qof_session_load( sess, NULL );
+    qof_session_ensure_all_data_loaded( sess );
+    err = qof_session_pop_error( sess );
+    do_test( err == ERR_SQL_DB_TOO_NEW, "DB Failed to flag too new" );
+cleanup:
+    qof_session_end( sess );
+    qof_session_destroy( sess );
+}

Modified: gnucash/trunk/src/backend/sql/gnc-backend-sql.c
===================================================================
--- gnucash/trunk/src/backend/sql/gnc-backend-sql.c	2011-01-18 19:27:23 UTC (rev 20122)
+++ gnucash/trunk/src/backend/sql/gnc-backend-sql.c	2011-01-18 23:42:48 UTC (rev 20123)
@@ -485,6 +485,7 @@
 
     (void)reset_version_info( be );
     gnc_sql_set_table_version( be, "Gnucash", gnc_get_svn_version() );
+    gnc_sql_set_table_version( be, "Gnucash-Resave", GNC_RESAVE_VERSION );
 
     /* Create new tables */
     be->is_pristine_db = TRUE;
@@ -538,6 +539,7 @@
     }
     else
     {
+	qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_SERVER_ERR );
         is_ok = gnc_sql_connection_rollback_transaction( be->conn );
     }
     LEAVE( "book=%p", book );

Modified: gnucash/trunk/src/backend/sql/gnc-backend-sql.h
===================================================================
--- gnucash/trunk/src/backend/sql/gnc-backend-sql.h	2011-01-18 19:27:23 UTC (rev 20122)
+++ gnucash/trunk/src/backend/sql/gnc-backend-sql.h	2011-01-18 23:42:48 UTC (rev 20123)
@@ -43,6 +43,25 @@
 #include "qofbackend-p.h"
 #include <gmodule.h>
 
+/**
+ * \def GNC_RESAVE_VERSION
+ *
+ * Defines the oldest svn revision of Gnucash which stores data in a
+ * way compatible with the current version. Data stored with an older
+ * version (or with no version indicated) of Gnucash will cause all
+ * tables to be moved aside, new tables saved with the current storage
+ * routines, and the old tables dropped. Any failures will trigger a
+ * rollback to the original tables.
+ *
+ * Encountering a database with a newer resave version will put the
+ * database in "read only" mode; a "save as" will be required to
+ * obtain a new database for storing from this instance, and the user
+ * will be warned of data loss.
+ *
+ */
+
+#define GNC_RESAVE_VERSION 19920
+
 typedef struct GncSqlConnection GncSqlConnection;
 
 /**
@@ -152,7 +171,7 @@
     GncSqlResult* (*executeSelectStatement)( GncSqlConnection*, GncSqlStatement* ); /**< Returns NULL if error */
     gint (*executeNonSelectStatement)( GncSqlConnection*, GncSqlStatement* ); /**< Returns -1 if error */
     GncSqlStatement* (*createStatementFromSql)( /*@ observer @*/ GncSqlConnection*, const gchar* );
-    gboolean (*doesTableExist)( GncSqlConnection*, const gchar* );
+    gboolean (*doesTableExist)( GncSqlConnection*, const gchar* );  /**< Returns true if successful */
     gboolean (*beginTransaction)( GncSqlConnection* ); /**< Returns TRUE if successful, FALSE if error */
     gboolean (*rollbackTransaction)( GncSqlConnection* ); /**< Returns TRUE if successful, FALSE if error */
     gboolean (*commitTransaction)( GncSqlConnection* ); /**< Returns TRUE if successful, FALSE if error */



More information about the gnucash-changes mailing list