r20147 - gnucash/trunk/src - Bug #611936: Test DBI backends for a dbi bug which causes 64-bit numbers

John Ralls jralls at code.gnucash.org
Sat Jan 22 18:04:01 EST 2011


Author: jralls
Date: 2011-01-22 18:04:00 -0500 (Sat, 22 Jan 2011)
New Revision: 20147
Trac: http://svn.gnucash.org/trac/changeset/20147

Modified:
   gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c
   gnucash/trunk/src/gnome-utils/gnc-file.c
   gnucash/trunk/src/libqof/qof/qofbackend.h
Log:
Bug #611936: Test DBI backends for a dbi bug which causes 64-bit numbers 
to not be stored on 32-bit systems, and prevent saving or loading with 
an informative error dialog box.



Modified: gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c
===================================================================
--- gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c	2011-01-22 22:59:42 UTC (rev 20146)
+++ gnucash/trunk/src/backend/dbi/gnc-backend-dbi.c	2011-01-22 23:04:00 UTC (rev 20147)
@@ -137,7 +137,7 @@
         const gchar* table_name,
         GList* col_info_list );
 static GncSqlConnection* create_dbi_connection( /*@ observer @*/ provider_functions_t* provider, /*@ observer @*/ QofBackend* qbe, /*@ observer @*/ dbi_conn conn );
-
+static gboolean conn_test_dbi_library( dbi_conn conn );
 #define GNC_DBI_PROVIDER_SQLITE (&provider_sqlite3)
 #define GNC_DBI_PROVIDER_MYSQL (&provider_mysql)
 #define GNC_DBI_PROVIDER_PGSQL (&provider_pgsql)
@@ -227,7 +227,7 @@
 
     (void)dbi_conn_error( conn, &msg );
     PERR( "DBI error: %s\n", msg );
-    gnc_dbi_set_error( dbi_conn, ERR_BACKEND_MISC, 0, FALSE );
+    gnc_dbi_set_error( conn, ERR_BACKEND_MISC, 0, FALSE );
 }
 
 static void
@@ -237,9 +237,11 @@
 {
     GncDbiBackend *be = (GncDbiBackend*)qbe;
     gint result;
-    gchar* dirname;
-    gchar* basename;
+    gchar* dirname = NULL;
+    gchar* basename = NULL;
     gchar *filepath = NULL;
+    gchar *msg = " ";
+    gboolean file_exists;
 
     g_return_if_fail( qbe != NULL );
     g_return_if_fail( session != NULL );
@@ -249,22 +251,20 @@
 
     /* Remove uri type if present */
     filepath = gnc_uri_get_path ( book_id );
-
-    if ( !create &&
-            !g_file_test( filepath, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS ) )
+    file_exists = g_file_test( filepath,
+			       G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS );
+    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);
-        LEAVE(" ");
-        return;
+	goto exit;
     }
 
-    if ( create && !force &&
-            g_file_test( filepath, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS ) )
+    if ( create && !force && file_exists )
     {
         qof_backend_set_error (qbe, ERR_BACKEND_STORE_EXISTS);
-        LEAVE("Might clobber, no force");
-        return;
+        msg = "Might clobber, no force";
+	goto exit;
     }
 
 
@@ -277,13 +277,11 @@
     {
         PERR( "Unable to create sqlite3 dbi connection\n" );
         qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
-        LEAVE( " " );
-        return;
+	goto exit;
     }
 
     dirname = g_path_get_dirname( filepath );
     basename = g_path_get_basename( filepath );
-    g_free ( filepath );
     dbi_conn_error_handler( be->conn, sqlite3_error_fn, be );
     /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */
     result = dbi_conn_set_option( be->conn, "host", "localhost" );
@@ -291,42 +289,48 @@
     {
         PERR( "Error setting 'host' option\n" );
         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
-        LEAVE( " " );
-        return;
+	goto exit;
     }
     result = dbi_conn_set_option( be->conn, "dbname", basename );
     if ( result < 0 )
     {
         PERR( "Error setting 'dbname' option\n" );
         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
-        LEAVE( " " );
-        return;
+	goto exit;
     }
     result = dbi_conn_set_option( be->conn, "sqlite3_dbdir", dirname );
     if ( result < 0 )
     {
         PERR( "Error setting 'sqlite3_dbdir' option\n" );
         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
-        LEAVE( " " );
-        return;
+	goto exit;
     }
     result = dbi_conn_connect( be->conn );
-    g_free( basename );
-    g_free( dirname );
-    /* Need some better error handling here. In particular, need to emit a QOF_ERROR_LOCKED if the database is in use by another process. */
+
     if ( result < 0 )
     {
         PERR( "Unable to connect to %s: %d\n", book_id, result );
         qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
-        LEAVE( " " );
-        return;
+	goto exit;
     }
 
+    if ( !conn_test_dbi_library( be->conn ) ) {
+	qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
+	qof_backend_set_message( qbe, "DBI library fails large number test" );
+	if ( create && !file_exists ) /* File didn't exist before, but it */
+	{     			      /* does now, and we don't want to */
+	    dbi_conn_close( be->conn );/* leave it lying around. */
+	    be->conn = NULL;
+	    g_unlink( filepath );
+	}
+	msg = "Bad DBI Library";
+	goto exit;
+    }
     if ( !gnc_dbi_lock_database( qbe, ignore_lock ) )
     {
         qof_backend_set_error( qbe, ERR_BACKEND_LOCKED );
-        LEAVE( "Locked" );
-        return;
+        msg = "Locked";
+	goto exit;
     }
 
     if ( be->sql_be.conn != NULL )
@@ -335,8 +339,11 @@
     }
     be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_SQLITE, qbe, be->conn );
     be->sql_be.timespec_format = SQLITE3_TIMESPEC_STR_FORMAT;
-
-    LEAVE (" ");
+exit:
+    if ( filepath != NULL ) g_free ( filepath );
+    if ( basename != NULL ) g_free( basename );
+    if ( dirname != NULL ) g_free( dirname );
+    LEAVE ( "%s", msg );
 }
 
 static GSList*
@@ -628,6 +635,7 @@
 
     dbname = dbi_conn_get_option( dcon, "dbname" );
     /* Check if the lock table exists */
+    g_return_if_fail( dbname != NULL );
     result = dbi_conn_get_table_list( dcon, dbname, lock_table);
     if (!( result && dbi_result_get_numrows( result ) ))
     {
@@ -753,6 +761,12 @@
     result = dbi_conn_connect( be->conn );
     if ( result == 0 )
     {
+	if ( !conn_test_dbi_library( be->conn ) ) {
+	    qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
+	    qof_backend_set_message( qbe,
+				     "DBI library fails large number test" );
+	    goto exit;
+	}
         if (create && !force && save_may_clobber_data( qbe ) )
         {
             qof_backend_set_error ( qbe, ERR_BACKEND_STORE_EXISTS );
@@ -819,6 +833,13 @@
                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
                 goto exit;
             }
+	    if ( !conn_test_dbi_library( be->conn ) ) {
+		qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
+		qof_backend_set_message( qbe,
+					 "DBI library fails large number test" );
+		dbi_conn_queryf( be->conn, "DROP DATABASE %s", dbname );
+		goto exit;
+	    }
             success = gnc_dbi_lock_database ( qbe, ignore_lock );
         }
         else
@@ -1006,6 +1027,12 @@
     result = dbi_conn_connect( be->conn );
     if ( result == 0 )
     {
+	if ( !conn_test_dbi_library( be->conn ) ) {
+	    qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
+	    qof_backend_set_message( qbe,
+				     "DBI library fails large number test" );
+	    goto exit;
+	}
         if (create && !force && save_may_clobber_data( qbe ) )
         {
             qof_backend_set_error ( qbe, ERR_BACKEND_STORE_EXISTS );
@@ -1073,6 +1100,14 @@
                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
                 goto exit;
             }
+	    if ( !conn_test_dbi_library( be->conn ) ) {
+		qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
+		qof_backend_set_message( qbe,
+					 "DBI library fails large number test" );
+		dbi_conn_select_db( be->conn, "template1" );
+		dbi_conn_queryf( be->conn, "DROP DATABASE %s", dbnamelc );
+		goto exit;
+	    }
             success = gnc_dbi_lock_database ( qbe, ignore_lock );
         }
         else
@@ -1081,7 +1116,6 @@
             qof_backend_set_message( qbe, "Database %s not found", dbname );
         }
     }
-
     if ( success )
     {
         if ( be->sql_be.conn != NULL )
@@ -2809,6 +2843,80 @@
     return list;
 }
 
+/** Users discovered a bug in some distributions of libdbi, where if
+ * it is compiled on certain versions of gcc with the -ffast-math
+ * compiler option it fails to correctly handle saving of 64-bit
+ * values. This function tests for the problem.
+ * @param: conn: The just-opened dbi_conn
+ * @returns: TRUE if the dbi library is safe to use, FALSE otherwise.
+ */
+static gboolean
+conn_test_dbi_library( dbi_conn conn )
+{
+    gint64 testlonglong = -9223372036854775807LL, resultlonglong = 0;
+    guint64 testulonglong = 9223372036854775807LLU, resultulonglong = 0;
+    gdouble testdouble = 1.7976921348623157E+307, resultdouble = 0.0;
+    dbi_result result;
+    gboolean retval = TRUE;
+
+    result = dbi_conn_query( conn, "CREATE TEMPORARY TABLE numtest "
+			     "( test_int BIGINT, test_unsigned BIGINT,"
+			     " test_double FLOAT8 )" );
+    if ( result == NULL )
+    {
+	PWARN("Test_DBI_Library: Create table failed");
+	return FALSE;
+    }
+    dbi_result_free( result );
+    result = dbi_conn_queryf( conn,
+			      "INSERT INTO numtest VALUES (%lld, %llu, %17e)",
+			      testlonglong, testulonglong, testdouble );
+    if ( result == NULL )
+    {
+	PWARN("Test_DBI_Library: Failed to insert test row into table" );
+	return FALSE;
+    }
+    dbi_result_free( result );
+    result = dbi_conn_query( conn, "SELECT * FROM numtest" );
+    if ( result == NULL )
+    {
+	const char *errmsg;
+	dbi_conn_error( conn, &errmsg );
+	PWARN("Test_DBI_Library: Failed to retrieve test row into table: %s",
+	      errmsg );
+	result = dbi_conn_query( conn, "DROP TABLE numtest" );
+	return FALSE;
+    }
+    while ( dbi_result_next_row( result ))
+    {
+	resultlonglong = dbi_result_get_longlong( result, "test_int" );
+	resultulonglong = dbi_result_get_ulonglong( result, "test_unsigned" );
+	resultdouble = dbi_result_get_double( result, "test_double" );
+    }
+    if ( testlonglong != resultlonglong )
+    {
+	PWARN( "Test_DBI_Library: LongLong Failed %lld != %lld",
+	       testlonglong, resultlonglong );
+	retval = FALSE;
+    }
+    if ( testulonglong != resultulonglong )
+    {
+	PWARN( "Test_DBI_Library: Unsigned longlong Failed %llu != %llu",
+	       testulonglong, resultulonglong );
+	retval = FALSE;
+    }
+    /* A bug in libdbi stores only 7 digits of precision */
+    if ( testdouble >= resultdouble + 0.000001e307 ||
+	 testdouble <= resultdouble - 0.000001e307 )
+    {
+	PWARN( "Test_DBI_Library: Double Failed %17e != %17e",
+	       testdouble, resultdouble );
+	retval = FALSE;
+    }
+    return retval;
+}
+
+
 static GncSqlConnection*
 create_dbi_connection( /*@ observer @*/ provider_functions_t* provider,
                                         /*@ observer @*/ QofBackend* qbe,

Modified: gnucash/trunk/src/gnome-utils/gnc-file.c
===================================================================
--- gnucash/trunk/src/gnome-utils/gnc-file.c	2011-01-22 22:59:42 UTC (rev 20146)
+++ gnucash/trunk/src/gnome-utils/gnc-file.c	2011-01-22 23:04:00 UTC (rev 20147)
@@ -428,6 +428,15 @@
         gnc_error_dialog (parent, "%s", fmt);
         break;
 
+    case ERR_SQL_BAD_DBI:
+	fmt = _("The libdbi installed on your system doesn't correctly store "
+		"large numbers. This is fatal, and Gnucash will not open or "
+		"save to SQL databases until it is fixed. Please see "
+		"https://bugzilla.gnome.org/show_bug.cgi?id=611936 for more "
+		"information.");
+	gnc_error_dialog (parent, "%s", fmt);
+	break;
+
     default:
         PERR("FIXME: Unhandled error %d", io_error);
         fmt = _("An unknown I/O error (%d) occurred.");

Modified: gnucash/trunk/src/libqof/qof/qofbackend.h
===================================================================
--- gnucash/trunk/src/libqof/qof/qofbackend.h	2011-01-22 22:59:42 UTC (rev 20146)
+++ gnucash/trunk/src/libqof/qof/qofbackend.h	2011-01-22 23:04:00 UTC (rev 20147)
@@ -109,6 +109,7 @@
     ERR_SQL_DB_TOO_OLD,           /**< database is old and needs upgrading */
     ERR_SQL_DB_TOO_NEW,		  /**< database is newer, we can't write to it */
     ERR_SQL_DB_BUSY,              /**< database is busy, cannot upgrade version */
+    ERR_SQL_BAD_DBI,		  /**< LibDBI has numeric errors */
 
     /* RPC errors */
     ERR_RPC_HOST_UNK = 4000,      /**< Host unknown */



More information about the gnucash-changes mailing list