GnuCash 2.4.99
gnc-backend-dbi.c
Go to the documentation of this file.
00001 /********************************************************************
00002  * gnc-backend-dbi.c: load and save data to SQL via libdbi          *
00003  *                                                                  *
00004  * This program is free software; you can redistribute it and/or    *
00005  * modify it under the terms of the GNU General Public License as   *
00006  * published by the Free Software Foundation; either version 2 of   *
00007  * the License, or (at your option) any later version.              *
00008  *                                                                  *
00009  * This program is distributed in the hope that it will be useful,  *
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00012  * GNU General Public License for more details.                     *
00013  *                                                                  *
00014  * You should have received a copy of the GNU General Public License*
00015  * along with this program; if not, contact:                        *
00016  *                                                                  *
00017  * Free Software Foundation           Voice:  +1-617-542-5942       *
00018  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00019  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00020 \********************************************************************/
00029 #include "config.h"
00030 
00031 #include <errno.h>
00032 #include <glib.h>
00033 #include <glib/gstdio.h>
00034 #if !HAVE_GMTIME_R
00035 #include "gmtime_r.h"
00036 #endif
00037 
00038 #include "gnc-backend-dbi-priv.h"
00039 
00040 #include "qof.h"
00041 #include "qofquery-p.h"
00042 #include "qofquerycore-p.h"
00043 #include "Account.h"
00044 #include "TransLog.h"
00045 #include "gnc-engine.h"
00046 #include "SX-book.h"
00047 #include "Recurrence.h"
00048 
00049 #include "gnc-gconf-utils.h"
00050 #include "gnc-uri-utils.h"
00051 #include "gnc-filepath-utils.h"
00052 #include "gnc-locale-utils.h"
00053 
00054 #include "gnc-backend-dbi.h"
00055 
00056 #ifdef S_SPLINT_S
00057 #include "splint-defs.h"
00058 #endif
00059 
00060 #ifdef G_OS_WIN32
00061 #include <winsock2.h>
00062 #define GETPID() GetCurrentProcessId()
00063 #else
00064 #include <limits.h>
00065 #include <unistd.h>
00066 #define GETPID() getpid()
00067 #endif
00068 
00069 #define GNC_HOST_NAME_MAX 255
00070 #define TRANSACTION_NAME "trans"
00071 
00072 static QofLogModule log_module = G_LOG_DOMAIN;
00073 
00074 static gchar lock_table[] = "gnclock";
00075 
00076 #define FILE_URI_TYPE "file"
00077 #define FILE_URI_PREFIX (FILE_URI_TYPE "://")
00078 #define SQLITE3_URI_TYPE "sqlite3"
00079 #define SQLITE3_URI_PREFIX (SQLITE3_URI_TYPE "://")
00080 #define PGSQL_DEFAULT_PORT 5432
00081 
00082 static /*@ null @*/ gchar* conn_create_table_ddl_sqlite3( GncSqlConnection* conn,
00083         const gchar* table_name,
00084         const GList* col_info_list );
00085 static GSList* conn_get_table_list( dbi_conn conn, const gchar* dbname );
00086 static GSList* conn_get_table_list_sqlite3( dbi_conn conn, const gchar* dbname );
00087 static void append_sqlite3_col_def( GString* ddl, GncSqlColumnInfo* info );
00088 static GSList *conn_get_index_list_sqlite3( dbi_conn conn );
00089 static provider_functions_t provider_sqlite3 =
00090 {
00091     conn_create_table_ddl_sqlite3,
00092     conn_get_table_list_sqlite3,
00093     append_sqlite3_col_def,
00094     conn_get_index_list_sqlite3
00095 };
00096 #define SQLITE3_TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d"
00097 
00098 static /*@ null @*/ gchar* conn_create_table_ddl_mysql( GncSqlConnection* conn,
00099         const gchar* table_name,
00100         const GList* col_info_list );
00101 static void append_mysql_col_def( GString* ddl, GncSqlColumnInfo* info );
00102 static GSList *conn_get_index_list_mysql( dbi_conn conn );
00103 static provider_functions_t provider_mysql =
00104 {
00105     conn_create_table_ddl_mysql,
00106     conn_get_table_list,
00107     append_mysql_col_def,
00108     conn_get_index_list_mysql
00109 };
00110 #define MYSQL_TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d"
00111 
00112 static /*@ null @*/ gchar* conn_create_table_ddl_pgsql( GncSqlConnection* conn,
00113         const gchar* table_name,
00114         const GList* col_info_list );
00115 static GSList* conn_get_table_list_pgsql( dbi_conn conn, const gchar* dbname );
00116 static void append_pgsql_col_def( GString* ddl, GncSqlColumnInfo* info );
00117 static GSList *conn_get_index_list_pgsql( dbi_conn conn );
00118 
00119 static provider_functions_t provider_pgsql =
00120 {
00121     conn_create_table_ddl_pgsql,
00122     conn_get_table_list_pgsql,
00123     append_pgsql_col_def,
00124     conn_get_index_list_pgsql
00125 };
00126 #define PGSQL_TIMESPEC_STR_FORMAT "%04d%02d%02d %02d%02d%02d"
00127 
00128 static gboolean gnc_dbi_lock_database( QofBackend *qbe, gboolean ignore_lock );
00129 static void gnc_dbi_unlock( QofBackend *qbe );
00130 static gboolean save_may_clobber_data( QofBackend* qbe );
00131 
00132 static /*@ null @*/ gchar* create_index_ddl( GncSqlConnection* conn,
00133         const gchar* index_name,
00134         const gchar* table_name,
00135         const GncSqlColumnTableEntry* col_table );
00136 static /*@ null @*/ gchar* add_columns_ddl( GncSqlConnection* conn,
00137         const gchar* table_name,
00138         GList* col_info_list );
00139 static GncSqlConnection* create_dbi_connection( /*@ observer @*/ provider_functions_t* provider, /*@ observer @*/ QofBackend* qbe, /*@ observer @*/ dbi_conn conn );
00140 static GncDbiTestResult conn_test_dbi_library( dbi_conn conn );
00141 #define GNC_DBI_PROVIDER_SQLITE (&provider_sqlite3)
00142 #define GNC_DBI_PROVIDER_MYSQL (&provider_mysql)
00143 #define GNC_DBI_PROVIDER_PGSQL (&provider_pgsql)
00144 
00145 
00146 #define DBI_MAX_CONN_ATTEMPTS 5
00147 
00148 /* ================================================================= */
00149 
00150 /* Free the contents of a GSList, then free the list. Don't use this
00151  * if the elements of the list weren't created with g_new! */
00152 static void
00153 gnc_table_slist_free( GSList *table_list )
00154 {
00155     GSList *list;
00156     for ( list = table_list; list != NULL; list = g_slist_next( list ))
00157     {
00158         g_free( list->data );
00159     }
00160     g_slist_free( table_list );
00161 }
00162 
00163 static void
00164 gnc_dbi_set_error( GncDbiSqlConnection* dbi_conn, gint last_error,
00165                    gint error_repeat, gboolean retry )
00166 {
00167     g_return_if_fail( dbi_conn != NULL );
00168 
00169     dbi_conn->last_error = last_error;
00170     if ( error_repeat > 0 )
00171         dbi_conn->error_repeat = dbi_conn->error_repeat + error_repeat;
00172     else
00173         dbi_conn->error_repeat = 0;
00174     dbi_conn->retry = retry;
00175 }
00176 
00177 static void
00178 gnc_dbi_init_error( GncDbiSqlConnection* dbi_conn )
00179 {
00180     gnc_dbi_set_error( dbi_conn, ERR_BACKEND_NO_ERR, 0, FALSE );
00181 }
00182 
00183 /* Check if the dbi connection is valid. If not attempt to re-establish it
00184  * Returns TRUE is there is a valid connection in the end or FALSE otherwise
00185  */
00186 static gboolean
00187 gnc_dbi_verify_conn( GncDbiSqlConnection* dbi_conn )
00188 {
00189     if ( dbi_conn->conn_ok )
00190         return TRUE;
00191 
00192     /* We attempt to connect only once here. The error function will automatically
00193      * re-attempt up until DBI_MAX_CONN_ATTEMPTS time to connect if this call fails.
00194      * After all these attempts, conn_ok will indicate if there is a valid connection
00195      * or not.
00196      */
00197     gnc_dbi_init_error( dbi_conn );
00198     dbi_conn->conn_ok = TRUE;
00199     (void)dbi_conn_connect( dbi_conn->conn );
00200 
00201     return dbi_conn->conn_ok;
00202 }
00203 
00204 /* ================================================================= */
00205 
00206 static void
00207 create_tables_cb( const gchar* type, gpointer data_p, gpointer be_p )
00208 {
00209     GncSqlObjectBackend* pData = data_p;
00210     GncDbiBackend* be = be_p;
00211 
00212     g_return_if_fail( type != NULL && data_p != NULL && be_p != NULL );
00213     g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION );
00214 
00215     if ( pData->create_tables != NULL )
00216     {
00217         (pData->create_tables)( &be->sql_be );
00218     }
00219 }
00220 
00221 static void
00222 sqlite3_error_fn( dbi_conn conn, /*@ unused @*/ void* user_data )
00223 {
00224     GncDbiBackend *be = (GncDbiBackend*)user_data;
00225     GncDbiSqlConnection *dbi_conn = (GncDbiSqlConnection*)be->sql_be.conn;
00226     const gchar* msg;
00227 
00228     (void)dbi_conn_error( conn, &msg );
00229     PERR( "DBI error: %s\n", msg );
00230     gnc_dbi_set_error( conn, ERR_BACKEND_MISC, 0, FALSE );
00231 }
00232 
00233 static void
00234 gnc_dbi_sqlite3_session_begin( QofBackend *qbe, QofSession *session,
00235                                const gchar *book_id, gboolean ignore_lock,
00236                                gboolean create, gboolean force )
00237 {
00238     GncDbiBackend *be = (GncDbiBackend*)qbe;
00239     gint result;
00240     gchar* dirname = NULL;
00241     gchar* basename = NULL;
00242     gchar *filepath = NULL;
00243     gchar *msg = " ";
00244     gboolean file_exists;
00245     GncDbiTestResult dbi_test_result = GNC_DBI_PASS;
00246 
00247     g_return_if_fail( qbe != NULL );
00248     g_return_if_fail( session != NULL );
00249     g_return_if_fail( book_id != NULL );
00250 
00251     ENTER (" ");
00252 
00253     /* Remove uri type if present */
00254     filepath = gnc_uri_get_path ( book_id );
00255     file_exists = g_file_test( filepath,
00256                                G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS );
00257     if ( !create && !file_exists )
00258     {
00259         qof_backend_set_error( qbe, ERR_FILEIO_FILE_NOT_FOUND );
00260         qof_backend_set_message(qbe, "Sqlite3 file %s not found", filepath);
00261         PWARN ("Sqlite3 file %s not found", filepath);
00262         goto exit;
00263     }
00264 
00265     if ( create && !force && file_exists )
00266     {
00267         qof_backend_set_error (qbe, ERR_BACKEND_STORE_EXISTS);
00268         msg = "Might clobber, no force";
00269         PWARN ("%s", msg);
00270         goto exit;
00271     }
00272 
00273 
00274     if ( be->conn != NULL )
00275     {
00276         dbi_conn_close( be->conn );
00277     }
00278     be->conn = dbi_conn_new( "sqlite3" );
00279     if ( be->conn == NULL )
00280     {
00281         PERR( "Unable to create sqlite3 dbi connection\n" );
00282         qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
00283         goto exit;
00284     }
00285 
00286     dirname = g_path_get_dirname( filepath );
00287     basename = g_path_get_basename( filepath );
00288     dbi_conn_error_handler( be->conn, sqlite3_error_fn, be );
00289     /* dbi-sqlite3 documentation says that sqlite3 doesn't take a "host" option */
00290     result = dbi_conn_set_option( be->conn, "host", "localhost" );
00291     if ( result < 0 )
00292     {
00293         PERR( "Error setting 'host' option\n" );
00294         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00295         goto exit;
00296     }
00297     result = dbi_conn_set_option( be->conn, "dbname", basename );
00298     if ( result < 0 )
00299     {
00300         PERR( "Error setting 'dbname' option\n" );
00301         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00302         goto exit;
00303     }
00304     result = dbi_conn_set_option( be->conn, "sqlite3_dbdir", dirname );
00305     if ( result < 0 )
00306     {
00307         PERR( "Error setting 'sqlite3_dbdir' option\n" );
00308         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00309         goto exit;
00310     }
00311     result = dbi_conn_connect( be->conn );
00312 
00313     if ( result < 0 )
00314     {
00315         PERR( "Unable to connect to %s: %d\n", book_id, result );
00316         qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
00317         goto exit;
00318     }
00319 
00320     dbi_test_result = conn_test_dbi_library( be->conn );
00321     switch ( dbi_test_result )
00322     {
00323     case GNC_DBI_PASS:
00324         break;
00325 
00326     case GNC_DBI_FAIL_SETUP:
00327         qof_backend_set_error( qbe, ERR_SQL_DBI_UNTESTABLE );
00328         qof_backend_set_message( qbe,
00329                                  "SQLite3: Failed to setup for large number test" );
00330         break;
00331 
00332     case GNC_DBI_FAIL_TEST:
00333         qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
00334         qof_backend_set_message( qbe,
00335                                  "SQLite3 DBI library fails large number test" );
00336         break;
00337     }
00338     if ( dbi_test_result != GNC_DBI_PASS )
00339     {
00340         if ( create && !file_exists ) /* File didn't exist before, but it */
00341         {
00342             /* does now, and we don't want to */
00343             dbi_conn_close( be->conn );/* leave it lying around. */
00344             be->conn = NULL;
00345             g_unlink( filepath );
00346         }
00347         msg = "Bad DBI Library";
00348         goto exit;
00349     }
00350     if ( !gnc_dbi_lock_database( qbe, ignore_lock ) )
00351     {
00352         qof_backend_set_error( qbe, ERR_BACKEND_LOCKED );
00353         msg = "Locked";
00354         goto exit;
00355     }
00356 
00357     if ( be->sql_be.conn != NULL )
00358     {
00359         gnc_sql_connection_dispose( be->sql_be.conn );
00360     }
00361     be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_SQLITE, qbe, be->conn );
00362     be->sql_be.timespec_format = SQLITE3_TIMESPEC_STR_FORMAT;
00363 
00364     /* We should now have a proper session set up.
00365      * Let's start logging */
00366     xaccLogSetBaseName (filepath);
00367     PINFO ("logpath=%s", filepath ? filepath : "(null)");
00368 
00369 exit:
00370     if ( filepath != NULL ) g_free ( filepath );
00371     if ( basename != NULL ) g_free( basename );
00372     if ( dirname != NULL ) g_free( dirname );
00373     LEAVE ( "%s", msg );
00374 }
00375 
00376 static GSList*
00377 conn_get_index_list_sqlite3( dbi_conn conn )
00378 {
00379     GSList *list = NULL;
00380     const gchar *errmsg;
00381     dbi_result result = dbi_conn_query( conn, "SELECT name FROM sqlite_master WHERE type = 'index' AND name NOT LIKE 'sqlite_autoindex%'" );
00382     if ( dbi_conn_error( conn, &errmsg ) != DBI_ERROR_NONE )
00383     {
00384         g_print( "Index Table Retrieval Error: %s\n", errmsg );
00385         return NULL;
00386     }
00387     while ( dbi_result_next_row( result ) != 0 )
00388     {
00389         const gchar* index_name;
00390 
00391         index_name = dbi_result_get_string_idx( result, 1 );
00392         list = g_slist_prepend( list, strdup( index_name ) );
00393     }
00394     dbi_result_free( result );
00395     return list;
00396 }
00397 
00398 static void
00399 mysql_error_fn( dbi_conn conn, void* user_data )
00400 {
00401     GncDbiBackend *be = (GncDbiBackend*)user_data;
00402     GncDbiSqlConnection *dbi_conn = (GncDbiSqlConnection*)be->sql_be.conn;
00403     const gchar* msg;
00404     gint err_num;
00405 
00406     err_num = dbi_conn_error( conn, &msg );
00407 
00408     /* Note: the sql connection may not have been initialized yet
00409      *       so let's be careful with using it
00410      */
00411 
00412     /* Database doesn't exist. When this error is triggered the
00413      * GncDbiSqlConnection may not exist yet either, so don't use it here
00414      */
00415     if ( err_num == 1049 )          // Database doesn't exist
00416     {
00417         PINFO( "DBI error: %s\n", msg );
00418         be->exists = FALSE;
00419         return;
00420     }
00421 
00422     /* All the other error handling code assumes the GncDbiSqlConnection
00423      *  has been initialized. So let's assert it exits here, otherwise
00424      * simply return.
00425      */
00426     if (!dbi_conn)
00427     {
00428         PINFO( "DBI error: %s\n", msg );
00429         PINFO( "Note: GbcDbiSqlConnection not yet initialized. Skipping further error processing." );
00430         return;
00431     }
00432 
00433     /* Test for other errors */
00434     if ( err_num == 2006 )     // Server has gone away
00435     {
00436         PINFO( "DBI error: %s - Reconnecting...\n", msg );
00437         if (dbi_conn)
00438             gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CONN_LOST, 1, TRUE );
00439         dbi_conn->conn_ok = TRUE;
00440         (void)dbi_conn_connect( conn );
00441     }
00442     else if ( err_num == 2003 )     // Unable to connect
00443     {
00444         if (dbi_conn->error_repeat >= DBI_MAX_CONN_ATTEMPTS )
00445         {
00446             PERR( "DBI error: %s - Giving up after %d consecutive attempts.\n", msg, DBI_MAX_CONN_ATTEMPTS );
00447             if (dbi_conn)
00448                 gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CANT_CONNECT, 0, FALSE );
00449             dbi_conn->conn_ok = FALSE;
00450         }
00451         else
00452         {
00453             PINFO( "DBI error: %s - Reconnecting...\n", msg );
00454             if (dbi_conn)
00455                 gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CANT_CONNECT, 1, TRUE );
00456             dbi_conn->conn_ok = TRUE;
00457             (void)dbi_conn_connect( conn );
00458         }
00459     }
00460     else                            // Any other error
00461     {
00462         PERR( "DBI error: %s\n", msg );
00463         if (dbi_conn)
00464             gnc_dbi_set_error( dbi_conn, ERR_BACKEND_MISC, 0, FALSE );
00465     }
00466 }
00467 
00480 static gboolean
00481 set_standard_connection_options( QofBackend* qbe, dbi_conn conn, const gchar* host, int port,
00482                                  const gchar* dbname, const gchar* username, const gchar* password )
00483 {
00484     gint result;
00485 
00486     result = dbi_conn_set_option( conn, "host", host );
00487     if ( result < 0 )
00488     {
00489         PERR( "Error setting 'host' option\n" );
00490         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00491         return FALSE;
00492     }
00493     result = dbi_conn_set_option_numeric( conn, "port", port );
00494     if ( result < 0 )
00495     {
00496         PERR( "Error setting 'port' option\n" );
00497         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00498         return FALSE;
00499     }
00500     result = dbi_conn_set_option( conn, "dbname", dbname );
00501     if ( result < 0 )
00502     {
00503         PERR( "Error setting 'dbname' option\n" );
00504         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00505         return FALSE;
00506     }
00507     result = dbi_conn_set_option( conn, "username", username );
00508     if ( result < 0 )
00509     {
00510         PERR( "Error setting 'username' option\n" );
00511         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00512         return FALSE;
00513     }
00514     result = dbi_conn_set_option( conn, "password", password );
00515     if ( result < 0 )
00516     {
00517         PERR( "Error setting 'password' option\n" );
00518         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00519         return FALSE;
00520     }
00521 
00522     return TRUE;
00523 }
00524 
00525 
00526 static gboolean
00527 gnc_dbi_lock_database ( QofBackend* qbe, gboolean ignore_lock )
00528 {
00529 
00530     GncDbiBackend *qe = (GncDbiBackend*)qbe;
00531     dbi_conn dcon = qe->conn;
00532     dbi_result result;
00533     const gchar *dbname = dbi_conn_get_option( dcon, "dbname" );
00534     /* Create the table if it doesn't exist */
00535     result = dbi_conn_get_table_list( dcon, dbname, lock_table);
00536     if (!( result && dbi_result_get_numrows( result ) ))
00537     {
00538         if ( result )
00539         {
00540             dbi_result_free( result );
00541             result = NULL;
00542         }
00543         result = dbi_conn_queryf( dcon, "CREATE TABLE %s ( Hostname varchar(%d), PID int )", lock_table, GNC_HOST_NAME_MAX );
00544         if ( dbi_conn_error( dcon, NULL ) )
00545         {
00546             const gchar *errstr;
00547             dbi_conn_error( dcon, &errstr );
00548             PERR( "Error %s creating lock table", errstr );
00549             qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00550             if ( result )
00551             {
00552                 dbi_result_free( result );
00553                 result = NULL;
00554             }
00555             return FALSE;
00556         }
00557         if ( result )
00558         {
00559             dbi_result_free( result );
00560             result = NULL;
00561         }
00562     }
00563     if (result)
00564     {
00565         dbi_result_free( result );
00566         result = NULL;
00567     }
00568 
00569     /* Protect everything with a single transaction to prevent races */
00570     if ( (result = dbi_conn_query( dcon, "BEGIN" )) )
00571     {
00572         /* Check for an existing entry; delete it if ignore_lock is true, otherwise fail */
00573         gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
00574         if (result)
00575         {
00576             dbi_result_free( result );
00577             result = NULL;
00578         }
00579         result = dbi_conn_queryf( dcon, "SELECT * FROM %s", lock_table );
00580         if ( result && dbi_result_get_numrows( result ) )
00581         {
00582             dbi_result_free( result );
00583             result = NULL;
00584             if ( !ignore_lock )
00585             {
00586                 qof_backend_set_error( qbe, ERR_BACKEND_LOCKED );
00587                 /* FIXME: After enhancing the qof_backend_error mechanism, report in the dialog what is the hostname of the machine holding the lock. */
00588                 dbi_conn_query( dcon, "ROLLBACK" );
00589                 return FALSE;
00590             }
00591             result = dbi_conn_queryf( dcon, "DELETE FROM %s", lock_table );
00592             if ( !result)
00593             {
00594                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00595                 qof_backend_set_message( qbe, "Failed to delete lock record" );
00596                 result = dbi_conn_query( dcon, "ROLLBACK" );
00597                 if (result)
00598                 {
00599                     dbi_result_free( result );
00600                     result = NULL;
00601                 }
00602                 return FALSE;
00603             }
00604             if (result)
00605             {
00606                 dbi_result_free( result );
00607                 result = NULL;
00608             }
00609         }
00610         /* Add an entry and commit the transaction */
00611         memset( hostname, 0, sizeof(hostname) );
00612         gethostname( hostname, GNC_HOST_NAME_MAX );
00613         result = dbi_conn_queryf( dcon,
00614                                   "INSERT INTO %s VALUES ('%s', '%d')",
00615                                   lock_table, hostname, (int)GETPID() );
00616         if ( !result)
00617         {
00618             qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00619             qof_backend_set_message( qbe, "Failed to create lock record" );
00620             result = dbi_conn_query( dcon, "ROLLBACK" );
00621             if (result)
00622             {
00623                 dbi_result_free( result );
00624                 result = NULL;
00625             }
00626             return FALSE;
00627         }
00628         if (result)
00629         {
00630             dbi_result_free( result );
00631             result = NULL;
00632         }
00633         result = dbi_conn_query( dcon, "COMMIT" );
00634         if (result)
00635         {
00636             dbi_result_free( result );
00637             result = NULL;
00638         }
00639         return TRUE;
00640     }
00641     /* Couldn't get a transaction (probably couldn't get a lock), so fail */
00642     qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00643     qof_backend_set_message( qbe, "SQL Backend failed to obtain a transaction" );
00644     if (result)
00645     {
00646         dbi_result_free( result );
00647         result = NULL;
00648     }
00649     return FALSE;
00650 }
00651 static void
00652 gnc_dbi_unlock( QofBackend *qbe )
00653 {
00654     GncDbiBackend *qe = (GncDbiBackend*)qbe;
00655     dbi_conn dcon = qe->conn;
00656     dbi_result result;
00657     const gchar *dbname = NULL;
00658 
00659     g_return_if_fail( dcon != NULL );
00660     g_return_if_fail( dbi_conn_error( dcon, NULL ) == 0 );
00661 
00662     dbname = dbi_conn_get_option( dcon, "dbname" );
00663     /* Check if the lock table exists */
00664     g_return_if_fail( dbname != NULL );
00665     result = dbi_conn_get_table_list( dcon, dbname, lock_table);
00666     if (!( result && dbi_result_get_numrows( result ) ))
00667     {
00668         if (result)
00669         {
00670             dbi_result_free( result );
00671             result = NULL;
00672         }
00673         PWARN("No lock table in database, so not unlocking it.");
00674         return;
00675     }
00676     dbi_result_free( result );
00677 
00678     result = dbi_conn_query( dcon, "BEGIN" );
00679     if ( result )
00680     {
00681         /* Delete the entry if it's our hostname and PID */
00682         gchar hostname[ GNC_HOST_NAME_MAX + 1 ];
00683 
00684         dbi_result_free( result );
00685         result = NULL;
00686         memset( hostname, 0, sizeof(hostname) );
00687         gethostname( hostname, GNC_HOST_NAME_MAX );
00688         result = dbi_conn_queryf( dcon, "SELECT * FROM %s WHERE Hostname = '%s' AND PID = '%d'", lock_table, hostname, (int)GETPID() );
00689         if ( result && dbi_result_get_numrows( result ) )
00690         {
00691             if (result)
00692             {
00693                 dbi_result_free( result );
00694                 result = NULL;
00695             }
00696             result = dbi_conn_queryf( dcon, "DELETE FROM %s", lock_table );
00697             if ( !result)
00698             {
00699                 PERR("Failed to delete the lock entry");
00700                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00701                 result = dbi_conn_query( dcon, "ROLLBACK" );
00702                 if (result)
00703                 {
00704                     dbi_result_free( result );
00705                     result = NULL;
00706                 }
00707                 return;
00708             }
00709             else
00710             {
00711                 dbi_result_free( result );
00712                 result = NULL;
00713             }
00714             result = dbi_conn_query( dcon, "COMMIT" );
00715             if (result)
00716             {
00717                 dbi_result_free( result );
00718                 result = NULL;
00719             }
00720             return;
00721         }
00722         result = dbi_conn_query( dcon, "ROLLBACK" );
00723         if (result)
00724         {
00725             dbi_result_free( result );
00726             result = NULL;
00727         }
00728         PWARN("There was no lock entry in the Lock table");
00729         return;
00730     }
00731     if (result)
00732     {
00733         dbi_result_free( result );
00734         result = NULL;
00735     }
00736     PWARN("Unable to get a lock on LOCK, so failed to clear the lock entry.");
00737     qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00738 }
00739 
00740 static void
00741 gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session,
00742                              const gchar *book_id, gboolean ignore_lock,
00743                              gboolean create, gboolean force )
00744 {
00745     GncDbiBackend *be = (GncDbiBackend*)qbe;
00746     gchar* protocol = NULL;
00747     gchar* host = NULL;
00748     gchar* dbname = NULL;
00749     gchar* username = NULL;
00750     gchar* password = NULL;
00751     gchar* basename = NULL;
00752     gchar* translog_path = NULL;
00753     gint portnum = 0;
00754     gint result;
00755     gboolean success = FALSE;
00756     GncDbiTestResult dbi_test_result = GNC_DBI_PASS;
00757 
00758     g_return_if_fail( qbe != NULL );
00759     g_return_if_fail( session != NULL );
00760     g_return_if_fail( book_id != NULL );
00761 
00762     ENTER (" ");
00763 
00764     /* Split the book-id
00765      * Format is protocol://username:password@hostname:port/dbname
00766        where username, password and port are optional) */
00767     gnc_uri_get_components ( book_id, &protocol, &host, &portnum,
00768                              &username, &password, &dbname );
00769 
00770     // Try to connect to the db.  If it doesn't exist and the create
00771     // flag is TRUE, we'll need to connect to the 'mysql' db and execute the
00772     // CREATE DATABASE ddl statement there.
00773     if ( be->conn != NULL )
00774     {
00775         dbi_conn_close( be->conn );
00776     }
00777     be->conn = dbi_conn_new( "mysql" );
00778     if ( be->conn == NULL )
00779     {
00780         PERR( "Unable to create mysql dbi connection\n" );
00781         qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
00782         goto exit;
00783     }
00784     dbi_conn_error_handler( be->conn, mysql_error_fn, be );
00785     if ( !set_standard_connection_options( qbe, be->conn, host, portnum, dbname, username, password ) )
00786     {
00787         goto exit;
00788     }
00789     be->exists = TRUE;
00790     result = dbi_conn_connect( be->conn );
00791     if ( result == 0 )
00792     {
00793         dbi_test_result = conn_test_dbi_library( be->conn );
00794         switch ( dbi_test_result )
00795         {
00796         case GNC_DBI_PASS:
00797             break;
00798 
00799         case GNC_DBI_FAIL_SETUP:
00800             qof_backend_set_error( qbe, ERR_SQL_DBI_UNTESTABLE );
00801             qof_backend_set_message( qbe,
00802                                      "DBI library large number test incomplete" );
00803             break;
00804 
00805         case GNC_DBI_FAIL_TEST:
00806             qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
00807             qof_backend_set_message( qbe,
00808                                      "DBI library fails large number test" );
00809             break;
00810         }
00811         if ( GNC_DBI_PASS != dbi_test_result )
00812         {
00813             goto exit;
00814         }
00815         if (create && !force && save_may_clobber_data( qbe ) )
00816         {
00817             qof_backend_set_error ( qbe, ERR_BACKEND_STORE_EXISTS );
00818             PWARN("Databse already exists, Might clobber it.");
00819             goto exit;
00820         }
00821 
00822         success = gnc_dbi_lock_database ( qbe, ignore_lock );
00823     }
00824     else
00825     {
00826 
00827         if ( be->exists )
00828         {
00829             PERR( "Unable to connect to database '%s'\n", dbname );
00830             qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00831             goto exit;
00832         }
00833 
00834         // The db does not already exist.  Connect to the 'mysql' db and try to create it.
00835         if ( create )
00836         {
00837             dbi_result dresult;
00838             result = dbi_conn_set_option( be->conn, "dbname", "mysql" );
00839             if ( result < 0 )
00840             {
00841                 PERR( "Error setting 'dbname' option\n" );
00842                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00843                 goto exit;
00844             }
00845             result = dbi_conn_connect( be->conn );
00846             if ( result < 0 )
00847             {
00848                 PERR( "Unable to connect to 'mysql' database\n" );
00849                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00850                 goto exit;
00851             }
00852             dresult = dbi_conn_queryf( be->conn, "CREATE DATABASE %s CHARACTER SET utf8", dbname );
00853             if ( dresult == NULL )
00854             {
00855                 PERR( "Unable to create database '%s'\n", dbname );
00856                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00857                 goto exit;
00858             }
00859             dbi_conn_close( be->conn );
00860 
00861             // Try again to connect to the db
00862             be->conn = dbi_conn_new( "mysql" );
00863             if ( be->conn == NULL )
00864             {
00865                 PERR( "Unable to create mysql dbi connection\n" );
00866                 qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
00867                 goto exit;
00868             }
00869             dbi_conn_error_handler( be->conn, mysql_error_fn, be );
00870             if ( !set_standard_connection_options( qbe, be->conn, host, 0, dbname, username, password ) )
00871             {
00872                 goto exit;
00873             }
00874             result = dbi_conn_connect( be->conn );
00875             if ( result < 0 )
00876             {
00877                 PERR( "Unable to create database '%s'\n", dbname );
00878                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00879                 goto exit;
00880             }
00881             dbi_test_result = conn_test_dbi_library( be->conn );
00882             switch ( dbi_test_result )
00883             {
00884             case GNC_DBI_PASS:
00885                 break;
00886 
00887             case GNC_DBI_FAIL_SETUP:
00888                 qof_backend_set_error( qbe, ERR_SQL_DBI_UNTESTABLE );
00889                 qof_backend_set_message( qbe,
00890                                          "MySql: Failed to setup for large number test" );
00891                 break;
00892 
00893             case GNC_DBI_FAIL_TEST:
00894                 qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
00895                 qof_backend_set_message( qbe,
00896                                          "MySql DBI library fails large number test" );
00897                 break;
00898             }
00899             if ( dbi_test_result != GNC_DBI_PASS )
00900             {
00901                 dbi_conn_queryf( be->conn, "DROP DATABASE %s", dbname );
00902                 goto exit;
00903             }
00904             success = gnc_dbi_lock_database ( qbe, ignore_lock );
00905         }
00906         else
00907         {
00908             qof_backend_set_error( qbe, ERR_BACKEND_NO_SUCH_DB );
00909             qof_backend_set_message( qbe, "Database %s not found", dbname );
00910         }
00911     }
00912 
00913     if ( success )
00914     {
00915         dbi_result dresult;
00916 
00917         /* Set connection char set to utf8 */
00918         dresult = dbi_conn_queryf( be->conn, "SET NAMES 'utf8'" );
00919         if ( dresult == NULL )
00920         {
00921             PERR( "Unable to set connection char set" );
00922             qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
00923             goto exit;
00924         }
00925 
00926         if ( be->sql_be.conn != NULL )
00927         {
00928             gnc_sql_connection_dispose( be->sql_be.conn );
00929         }
00930         be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_MYSQL, qbe, be->conn );
00931     }
00932     be->sql_be.timespec_format = MYSQL_TIMESPEC_STR_FORMAT;
00933 
00934     /* We should now have a proper session set up.
00935      * Let's start logging */
00936     basename = g_strjoin("_", protocol, host, username, dbname, NULL);
00937     translog_path = gnc_build_translog_path (basename);
00938     xaccLogSetBaseName (translog_path);
00939     PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
00940 
00941 exit:
00942     g_free( protocol );
00943     g_free( host );
00944     g_free( username );
00945     g_free( password );
00946     g_free( basename );
00947     g_free( translog_path );
00948     g_free( dbname );
00949 
00950     LEAVE (" ");
00951 }
00952 
00953 static GSList*
00954 conn_get_index_list_mysql( dbi_conn conn )
00955 {
00956     GSList *index_list = NULL;
00957     dbi_result table_list;
00958     const char *errmsg;
00959     const gchar *dbname = dbi_conn_get_option( conn, "dbname" );
00960     g_return_val_if_fail( conn != NULL, NULL );
00961     table_list = dbi_conn_get_table_list( conn, dbname, NULL );
00962     if ( dbi_conn_error( conn, &errmsg ) != DBI_ERROR_NONE )
00963     {
00964         g_print( "Table Retrieval Error: %s\n", errmsg );
00965         return NULL;
00966     }
00967     while ( dbi_result_next_row( table_list ) != 0 )
00968     {
00969         dbi_result result;
00970         const gchar *table_name = dbi_result_get_string_idx( table_list, 1 );
00971         result = dbi_conn_queryf( conn,
00972                                   "SHOW INDEXES IN %s WHERE Key_name != 'PRIMARY'",
00973                                   table_name );
00974         if ( dbi_conn_error( conn, &errmsg ) != DBI_ERROR_NONE )
00975         {
00976             g_print( "Index Table Retrieval Error: %s\n", errmsg );
00977             continue;
00978         }
00979 
00980         while ( dbi_result_next_row( result ) != 0 )
00981         {
00982             const gchar*  index_name = dbi_result_get_string_idx( result, 3 );
00983             index_list = g_slist_prepend( index_list, strdup( index_name ) );
00984         }
00985         dbi_result_free( result );
00986     }
00987 
00988     return index_list;
00989 }
00990 
00991 static void
00992 pgsql_error_fn( dbi_conn conn, void* user_data )
00993 {
00994     GncDbiBackend *be = (GncDbiBackend*)user_data;
00995     GncDbiSqlConnection *dbi_conn = (GncDbiSqlConnection*)be->sql_be.conn;
00996     const gchar* msg;
00997 
00998     (void)dbi_conn_error( conn, &msg );
00999     if ( g_str_has_prefix( msg, "FATAL:  database" ) &&
01000             g_str_has_suffix( msg, "does not exist\n" ) )
01001     {
01002         PINFO( "DBI error: %s\n", msg );
01003         be->exists = FALSE;
01004         gnc_dbi_set_error( dbi_conn, ERR_BACKEND_NO_SUCH_DB, 0, FALSE );
01005     }
01006     else if ( g_strrstr( msg, "server closed the connection unexpectedly" ) ) // Connection lost
01007     {
01008         if ( dbi_conn == NULL )
01009         {
01010             PWARN( "DBI Error: Connection lost, connection pointer invalid");
01011             return;
01012         }
01013         PINFO( "DBI error: %s - Reconnecting...\n", msg );
01014         gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CONN_LOST, 1, TRUE );
01015         dbi_conn->conn_ok = TRUE;
01016         (void)dbi_conn_connect( conn );
01017     }
01018     else if ( dbi_conn &&
01019               ( g_str_has_prefix( msg, "connection pointer is NULL" ) ||
01020                 g_str_has_prefix(msg, "could not connect to server" ) ) )     // No connection
01021     {
01022         if (dbi_conn->error_repeat >= DBI_MAX_CONN_ATTEMPTS )
01023         {
01024             PERR( "DBI error: %s - Giving up after %d consecutive attempts.\n", msg, DBI_MAX_CONN_ATTEMPTS );
01025             gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CANT_CONNECT, 0, FALSE );
01026             dbi_conn->conn_ok = FALSE;
01027         }
01028         else
01029         {
01030             PINFO( "DBI error: %s - Reconnecting...\n", msg );
01031             gnc_dbi_set_error( dbi_conn, ERR_BACKEND_CANT_CONNECT, 1, TRUE );
01032             dbi_conn->conn_ok = TRUE;
01033             (void)dbi_conn_connect( conn );
01034         }
01035     }
01036     else
01037     {
01038         PERR( "DBI error: %s\n", msg );
01039         gnc_dbi_set_error( dbi_conn, ERR_BACKEND_MISC, 0, FALSE );
01040     }
01041 }
01042 
01043 static void
01044 gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session,
01045                                 const gchar *book_id, gboolean ignore_lock,
01046                                 gboolean create, gboolean force )
01047 {
01048     GncDbiBackend *be = (GncDbiBackend*)qbe;
01049     gint result = 0;
01050     gchar* protocol = NULL;
01051     gchar* host = NULL;
01052     gchar *dbname = NULL, *dbnamelc = NULL;
01053     gchar* username = NULL;
01054     gchar* password = NULL;
01055     gchar* basename = NULL;
01056     gchar* translog_path = NULL;
01057     gboolean success = FALSE;
01058     gint portnum = 0;
01059     GncDbiTestResult dbi_test_result = GNC_DBI_PASS;
01060 
01061     g_return_if_fail( qbe != NULL );
01062     g_return_if_fail( session != NULL );
01063     g_return_if_fail( book_id != NULL );
01064 
01065     ENTER (" ");
01066 
01067     /* Split the book-id
01068      * Format is protocol://username:password@hostname:port/dbname
01069        where username, password and port are optional) */
01070     gnc_uri_get_components ( book_id, &protocol, &host, &portnum,
01071                              &username, &password, &dbname );
01072     if ( portnum == 0 )
01073         portnum = PGSQL_DEFAULT_PORT;
01074     /* Postgres's SQL interface coerces identifiers to lower case, but the
01075      * C interface is case-sensitive. This results in a mixed-case dbname
01076      * being created (with a lower case name) but then dbi can't conect to
01077      * it. To work around this, coerce the name to lowercase first. */
01078     dbnamelc = g_utf8_strdown( dbname, -1 );
01079 
01080     // Try to connect to the db.  If it doesn't exist and the create
01081     // flag is TRUE, we'll need to connect to the 'postgres' db and execute the
01082     // CREATE DATABASE ddl statement there.
01083     if ( be->conn != NULL )
01084     {
01085         dbi_conn_close( be->conn );
01086     }
01087     be->conn = dbi_conn_new( "pgsql" );
01088     if ( be->conn == NULL )
01089     {
01090         PERR( "Unable to create pgsql dbi connection\n" );
01091         qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
01092         goto exit;
01093     }
01094     dbi_conn_error_handler( be->conn, pgsql_error_fn, be );
01095     if ( !set_standard_connection_options( qbe, be->conn, host, portnum, dbnamelc, username, password ) )
01096     {
01097         goto exit;
01098     }
01099     be->exists = TRUE;
01100     result = dbi_conn_connect( be->conn );
01101     if ( result == 0 )
01102     {
01103         dbi_test_result = conn_test_dbi_library( be->conn );
01104         switch ( dbi_test_result )
01105         {
01106         case GNC_DBI_PASS:
01107             break;
01108 
01109         case GNC_DBI_FAIL_SETUP:
01110             qof_backend_set_error( qbe, ERR_SQL_DBI_UNTESTABLE );
01111             qof_backend_set_message( qbe,
01112                                      "Postgresql: Failed to setup for large number test" );
01113             break;
01114 
01115         case GNC_DBI_FAIL_TEST:
01116             qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
01117             qof_backend_set_message( qbe,
01118                                      "Postgresql DBI library fails large number test" );
01119             break;
01120         }
01121         if ( dbi_test_result != GNC_DBI_PASS )
01122         {
01123             goto exit;
01124         }
01125         if (create && !force && save_may_clobber_data( qbe ) )
01126         {
01127             qof_backend_set_error ( qbe, ERR_BACKEND_STORE_EXISTS );
01128             PWARN("Databse already exists, Might clobber it.");
01129             goto exit;
01130         }
01131 
01132         success = gnc_dbi_lock_database ( qbe, ignore_lock );
01133     }
01134     else
01135     {
01136 
01137         if ( be->exists )
01138         {
01139             PERR( "Unable to connect to database '%s'\n", dbname );
01140             qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01141             goto exit;
01142         }
01143 
01144         // The db does not already exist.  Connect to the 'postgres' db and try to create it.
01145         if ( create )
01146         {
01147             dbi_result dresult;
01148             result = dbi_conn_set_option( be->conn, "dbname", "postgres" );
01149             if ( result < 0 )
01150             {
01151                 PERR( "Error setting 'dbname' option\n" );
01152                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01153                 goto exit;
01154             }
01155             result = dbi_conn_connect( be->conn );
01156             if ( result < 0 )
01157             {
01158                 PERR( "Unable to connect to 'postgres' database\n" );
01159                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01160                 goto exit;
01161             }
01162             dresult = dbi_conn_queryf( be->conn, "CREATE DATABASE %s WITH TEMPLATE template0 ENCODING 'UTF8'", dbnamelc );
01163             if ( dresult == NULL )
01164             {
01165                 PERR( "Unable to create database '%s'\n", dbname );
01166                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01167                 goto exit;
01168             }
01169             dbi_conn_queryf( be->conn, "ALTER DATABASE %s SET standard_conforming_strings TO on", dbnamelc );
01170             dbi_conn_close( be->conn );
01171 
01172             // Try again to connect to the db
01173             be->conn = dbi_conn_new( "pgsql" );
01174             if ( be->conn == NULL )
01175             {
01176                 PERR( "Unable to create pgsql dbi connection\n" );
01177                 qof_backend_set_error( qbe, ERR_BACKEND_BAD_URL );
01178                 goto exit;
01179             }
01180             dbi_conn_error_handler( be->conn, pgsql_error_fn, be );
01181             if ( !set_standard_connection_options( qbe, be->conn, host, PGSQL_DEFAULT_PORT, dbnamelc, username, password ) )
01182             {
01183                 goto exit;
01184             }
01185             result = dbi_conn_connect( be->conn );
01186             if ( result < 0 )
01187             {
01188                 PERR( "Unable to create database '%s'\n", dbname );
01189                 qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01190                 goto exit;
01191             }
01192             dbi_test_result = conn_test_dbi_library( be->conn );
01193             switch ( dbi_test_result )
01194             {
01195             case GNC_DBI_PASS:
01196                 break;
01197 
01198             case GNC_DBI_FAIL_SETUP:
01199                 qof_backend_set_error( qbe, ERR_SQL_DBI_UNTESTABLE );
01200                 qof_backend_set_message( qbe,
01201                                          "DBI library large number test incomplete" );
01202                 break;
01203 
01204             case GNC_DBI_FAIL_TEST:
01205                 qof_backend_set_error( qbe, ERR_SQL_BAD_DBI );
01206                 qof_backend_set_message( qbe,
01207                                          "DBI library fails large number test" );
01208                 break;
01209             }
01210             if ( GNC_DBI_PASS != dbi_test_result )
01211             {
01212                 dbi_conn_select_db( be->conn, "template1" );
01213                 dbi_conn_queryf( be->conn, "DROP DATABASE %s", dbnamelc );
01214                 goto exit;
01215             }
01216             success = gnc_dbi_lock_database ( qbe, ignore_lock );
01217         }
01218         else
01219         {
01220             qof_backend_set_error( qbe, ERR_BACKEND_NO_SUCH_DB );
01221             qof_backend_set_message( qbe, "Database %s not found", dbname );
01222         }
01223     }
01224     if ( success )
01225     {
01226         if ( be->sql_be.conn != NULL )
01227         {
01228             gnc_sql_connection_dispose( be->sql_be.conn );
01229         }
01230         be->sql_be.conn = create_dbi_connection( GNC_DBI_PROVIDER_PGSQL, qbe, be->conn );
01231     }
01232     be->sql_be.timespec_format = PGSQL_TIMESPEC_STR_FORMAT;
01233 
01234     /* We should now have a proper session set up.
01235      * Let's start logging */
01236     basename = g_strjoin("_", protocol, host, username, dbname, NULL);
01237     translog_path = gnc_build_translog_path (basename);
01238     xaccLogSetBaseName (translog_path);
01239     PINFO ("logpath=%s", translog_path ? translog_path : "(null)");
01240 
01241 exit:
01242     g_free( protocol );
01243     g_free( host );
01244     g_free( username );
01245     g_free( password );
01246     g_free( basename );
01247     g_free( translog_path );
01248     g_free( dbname );
01249     g_free( dbnamelc );
01250 
01251     LEAVE (" ");
01252 }
01253 
01254 static GSList*
01255 conn_get_index_list_pgsql( dbi_conn conn )
01256 {
01257     GSList *list = NULL;
01258     const gchar *errmsg;
01259     dbi_result result;
01260     g_print( "Retrieving postgres index list\n");
01261     result = dbi_conn_query( conn, "SELECT relname FROM pg_class AS a INNER JOIN pg_index AS b ON (b.indexrelid = a.oid) INNER JOIN pg_namespace AS c ON (a.relnamespace = c.oid) WHERE reltype = '0' AND indisprimary = 'f' AND nspname = 'public'" );
01262     if ( dbi_conn_error( conn, &errmsg ) != DBI_ERROR_NONE )
01263     {
01264         g_print( "Index Table Retrieval Error: %s\n", errmsg );
01265         return NULL;
01266     }
01267     while ( dbi_result_next_row( result ) != 0 )
01268     {
01269         const gchar* index_name;
01270 
01271         index_name = dbi_result_get_string_idx( result, 1 );
01272         list = g_slist_prepend( list, strdup( index_name ) );
01273     }
01274     dbi_result_free( result );
01275     return list;
01276 }
01277 
01278 
01279 /* ================================================================= */
01280 
01281 static void
01282 gnc_dbi_session_end( QofBackend *be_start )
01283 {
01284     GncDbiBackend *be = (GncDbiBackend*)be_start;
01285 
01286     g_return_if_fail( be_start != NULL );
01287 
01288     ENTER (" ");
01289 
01290     if ( be->conn != NULL )
01291     {
01292         gnc_dbi_unlock( be_start );
01293         dbi_conn_close( be->conn );
01294         be->conn = NULL;
01295     }
01296     if ( be->sql_be.conn != NULL )
01297     {
01298         gnc_sql_connection_dispose( be->sql_be.conn );
01299         be->sql_be.conn = NULL;
01300     }
01301     gnc_sql_finalize_version_info( &be->sql_be );
01302 
01303     LEAVE (" ");
01304 }
01305 
01306 static void
01307 gnc_dbi_destroy_backend( /*@ only @*/ QofBackend *be )
01308 {
01309     g_return_if_fail( be != NULL );
01310 
01311     /* Stop transaction logging */
01312     xaccLogSetBaseName (NULL);
01313 
01314     qof_backend_destroy( be );
01315 
01316     g_free( be );
01317 }
01318 
01319 /* ================================================================= */
01320 
01321 /* GNUCASH_RESAVE_VERSION indicates the earliest database version
01322  * compatible with this version of Gnucash; the stored value is the
01323  * earliest version of Gnucash conpatible with the database. If the
01324  * GNUCASH_RESAVE_VERSION for this Gnucash is newer than the Gnucash
01325  * version which created the database, a resave is offered. If the
01326  * version of this Gnucash is older than the saved resave version,
01327  * then the database will be loaded read-only. A resave will update
01328  * both values to match this version of Gnucash.
01329  */
01330 static void
01331 gnc_dbi_load( QofBackend* qbe, /*@ dependent @*/ QofBook *book, QofBackendLoadType loadType )
01332 {
01333     GncDbiBackend *be = (GncDbiBackend*)qbe;
01334 
01335     g_return_if_fail( qbe != NULL );
01336     g_return_if_fail( book != NULL );
01337 
01338     ENTER( "be=%p, book=%p", be, book );
01339 
01340     if ( loadType == LOAD_TYPE_INITIAL_LOAD )
01341     {
01342         g_assert( be->primary_book == NULL );
01343         be->primary_book = book;
01344 
01345         // Set up table version information
01346         gnc_sql_init_version_info( &be->sql_be );
01347 
01348         // Call all object backends to create any required tables
01349         qof_object_foreach_backend( GNC_SQL_BACKEND, create_tables_cb, be );
01350     }
01351 
01352     gnc_sql_load( &be->sql_be, book, loadType );
01353 
01354     if ( GNUCASH_RESAVE_VERSION > gnc_sql_get_table_version( &be->sql_be, "Gnucash" ) )
01355     {
01356         /* The database was loaded with an older database schema or
01357          * data semantics. In order to ensure consistency, the whole
01358          * thing needs to be saved anew. */
01359         qof_backend_set_error( qbe, ERR_SQL_DB_TOO_OLD );
01360     }
01361     else if ( GNUCASH_RESAVE_VERSION < gnc_sql_get_table_version( &be->sql_be,
01362               "Gnucash-Resave"))
01363     {
01364         /* Worse, the database was created with a newer version. We
01365          * can't safely write to this database, so the user will have
01366          * to do a "save as" to make one that we can write to.
01367          */
01368         qof_backend_set_error( qbe, ERR_SQL_DB_TOO_NEW );
01369     }
01370 
01371 
01372     LEAVE( "" );
01373 }
01374 
01375 /* ================================================================= */
01376 
01377 static gboolean
01378 save_may_clobber_data( QofBackend* qbe )
01379 {
01380     GncDbiBackend* be = (GncDbiBackend*)qbe;
01381     const gchar* dbname;
01382     dbi_result result;
01383     gboolean retval = FALSE;
01384 
01385     /* Data may be clobbered iff the number of tables != 0 */
01386     dbname = dbi_conn_get_option( be->conn, "dbname" );
01387     result = dbi_conn_get_table_list( be->conn, dbname, NULL );
01388     if ( result )
01389     {
01390         retval =  dbi_result_get_numrows( result ) > 0;
01391         dbi_result_free( result );
01392     }
01393     return retval;
01394 }
01395 
01396 static dbi_result
01397 conn_table_manage_backup (GncDbiSqlConnection *conn,
01398                           gchar *table_name, TableOpType op )
01399 {
01400     gchar *new_name = g_strdup_printf( "%s_%s", table_name, "back" );
01401     dbi_result result = NULL;
01402     switch ( op )
01403     {
01404     case backup:
01405         result = dbi_conn_queryf( conn->conn, "ALTER TABLE %s RENAME TO %s",
01406                                   table_name, new_name );
01407         break;
01408     case rollback:
01409         result = dbi_conn_queryf( conn->conn,
01410                                   "ALTER TABLE %s RENAME TO %s",
01411                                   new_name, table_name );
01412         break;
01413     case drop_backup:
01414         result = dbi_conn_queryf( conn->conn, "DROP TABLE %s",
01415                                   new_name );
01416     default:
01417         break;
01418     }
01419     g_free( new_name );
01420     return result;
01421 }
01422 
01448 static gboolean
01449 conn_table_operation( GncSqlConnection *sql_conn, GSList *table_name_list,
01450                       TableOpType op )
01451 {
01452     GSList* node;
01453     gboolean result = TRUE;
01454     GncDbiSqlConnection *conn = (GncDbiSqlConnection*)(sql_conn);
01455     GSList *full_table_name_list = NULL;
01456     const gchar *dbname = dbi_conn_get_option( conn->conn, "dbname" );
01457 
01458     g_return_val_if_fail( table_name_list != NULL, FALSE );
01459     if ( op == rollback )
01460         full_table_name_list =
01461             conn->provider->get_table_list( conn->conn, dbname );
01462 
01463     for ( node = table_name_list; node != NULL && result; node = node->next )
01464     {
01465         gchar* table_name = (gchar*)node->data;
01466         dbi_result result;
01467         /* Ignore the lock table */
01468         if ( g_strcmp0(table_name, lock_table) == 0)
01469         {
01470             continue;
01471         }
01472         do
01473         {
01474             gnc_dbi_init_error( conn );
01475             switch ( op )
01476             {
01477             case rollback:
01478                 if (g_slist_find(full_table_name_list, table_name))
01479                 {
01480                     result = dbi_conn_queryf( conn->conn, "DROP TABLE %s",
01481                                               table_name );
01482                     if ( result )
01483                         break;
01484                 }
01485                 /* Fall through */
01486             case backup:
01487             case drop_backup:
01488                 result = conn_table_manage_backup( conn, table_name, op );
01489                 break;
01490             case empty:
01491                 result = dbi_conn_queryf( conn->conn, "DELETE FROM TABLE %s",
01492                                           table_name );
01493                 break;
01494             case drop:
01495             default:
01496                 result = dbi_conn_queryf( conn->conn, "DROP TABLE %s",
01497                                           table_name );
01498                 break;
01499             }
01500         }
01501         while ( conn->retry );
01502         if ( result != NULL )
01503         {
01504             if ( dbi_result_free( result ) < 0 )
01505             {
01506                 PERR( "Error in dbi_result_free() result\n" );
01507                 result = FALSE;
01508             }
01509         }
01510     }
01511     gnc_table_slist_free( full_table_name_list );
01512     return result;
01513 }
01514 
01525 static void
01526 gnc_dbi_sync_all( QofBackend* qbe, /*@ dependent @*/ QofBook *book )
01527 {
01528     GncDbiBackend* be = (GncDbiBackend*)qbe;
01529     GncDbiSqlConnection *conn = (GncDbiSqlConnection*)(((GncSqlBackend*)be)->conn);
01530     GSList* table_name_list;
01531     const gchar* dbname;
01532     gint status;
01533 
01534     g_return_if_fail( be != NULL );
01535     g_return_if_fail( book != NULL );
01536 
01537     ENTER( "book=%p, primary=%p", book, be->primary_book );
01538 
01539     /* Destroy the current contents of the database */
01540     dbname = dbi_conn_get_option( be->conn, "dbname" );
01541     table_name_list = conn->provider->get_table_list( conn->conn, dbname );
01542     if ( !conn_table_operation( (GncSqlConnection*)conn, table_name_list,
01543                                 drop ) )
01544     {
01545         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01546         return;
01547     }
01548     gnc_table_slist_free( table_name_list );
01549     /* Save all contents */
01550     be->is_pristine_db = TRUE;
01551     be->primary_book = book;
01552     gnc_sql_sync_all( &be->sql_be, book );
01553 
01554     LEAVE( "book=%p", book );
01555 }
01556 
01566 static void
01567 gnc_dbi_safe_sync_all( QofBackend *qbe, QofBook *book )
01568 {
01569     GncDbiBackend *be = (GncDbiBackend*)qbe;
01570     GncDbiSqlConnection *conn = (GncDbiSqlConnection*)(((GncSqlBackend*)be)->conn);
01571     GSList *table_list, *index_list, *iter;
01572     const gchar* dbname = NULL;
01573     gint status;
01574 
01575     g_return_if_fail( be != NULL );
01576     g_return_if_fail( book != NULL );
01577 
01578     ENTER( "book=%p, primary=%p", book, be->primary_book );
01579     dbname = dbi_conn_get_option( be->conn, "dbname" );
01580     table_list = conn->provider->get_table_list( conn->conn, dbname );
01581     if ( !conn_table_operation( (GncSqlConnection*)conn, table_list,
01582                                 backup ) )
01583     {
01584         qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01585         conn_table_operation( (GncSqlConnection*)conn, table_list,
01586                               rollback );
01587         LEAVE( "Failed to rename tables" );
01588         gnc_table_slist_free( table_list );
01589         return;
01590     }
01591     index_list = conn->provider->get_index_list( conn->conn );
01592     for ( iter = index_list; iter != NULL; iter = g_slist_next( iter) )
01593     {
01594         const char *errmsg;
01595         dbi_result result =
01596             dbi_conn_queryf( conn->conn, "DROP INDEX %s", iter->data );
01597         if ( result )
01598             dbi_result_free( result );
01599         if ( DBI_ERROR_NONE != dbi_conn_error( conn->conn, &errmsg ) )
01600         {
01601             qof_backend_set_error( qbe, ERR_BACKEND_SERVER_ERR );
01602             gnc_table_slist_free( index_list );
01603             conn_table_operation( (GncSqlConnection*)conn, table_list,
01604                                   rollback );
01605             gnc_table_slist_free( table_list );
01606             LEAVE( "Failed to drop indexes %s", errmsg  );
01607             return;
01608         }
01609     }
01610     gnc_table_slist_free( index_list );
01611 
01612     be->is_pristine_db = TRUE;
01613     be->primary_book = book;
01614 
01615     gnc_sql_sync_all( &be->sql_be, book );
01616     if ( ERR_BACKEND_NO_ERR != qof_backend_get_error( qbe ) )
01617     {
01618         conn_table_operation( (GncSqlConnection*)conn, table_list,
01619                               rollback );
01620         LEAVE( "Failed to create new database tables" );
01621         return;
01622     }
01623     conn_table_operation( (GncSqlConnection*)conn, table_list,
01624                           drop_backup );
01625     gnc_table_slist_free( table_list );
01626     LEAVE("book=%p", book);
01627 }
01628 /* ================================================================= */
01629 static void
01630 gnc_dbi_begin_edit( QofBackend *qbe, QofInstance *inst )
01631 {
01632     GncDbiBackend* be = (GncDbiBackend*)qbe;
01633 
01634     g_return_if_fail( be != NULL );
01635     g_return_if_fail( inst != NULL );
01636 
01637     gnc_sql_begin_edit( &be->sql_be, inst );
01638 }
01639 
01640 static void
01641 gnc_dbi_rollback_edit( QofBackend *qbe, QofInstance *inst )
01642 {
01643     GncDbiBackend* be = (GncDbiBackend*)qbe;
01644 
01645     g_return_if_fail( be != NULL );
01646     g_return_if_fail( inst != NULL );
01647 
01648     gnc_sql_rollback_edit( &be->sql_be, inst );
01649 }
01650 
01651 static void
01652 gnc_dbi_commit_edit( QofBackend *qbe, QofInstance *inst )
01653 {
01654     GncDbiBackend* be = (GncDbiBackend*)qbe;
01655 
01656     g_return_if_fail( be != NULL );
01657     g_return_if_fail( inst != NULL );
01658 
01659     gnc_sql_commit_edit( &be->sql_be, inst );
01660 }
01661 
01662 /* ================================================================= */
01663 
01664 static void
01665 init_sql_backend( GncDbiBackend* dbi_be )
01666 {
01667     QofBackend* be;
01668 
01669     be = (QofBackend*)dbi_be;
01670 
01671     be->session_end = gnc_dbi_session_end;
01672     be->destroy_backend = gnc_dbi_destroy_backend;
01673 
01674     be->load = gnc_dbi_load;
01675 
01676     /* The gda backend treats accounting periods transactionally. */
01677     be->begin = gnc_dbi_begin_edit;
01678     be->commit = gnc_dbi_commit_edit;
01679     be->rollback = gnc_dbi_rollback_edit;
01680 
01681     /* The gda backend will not be multi-user (for now)... */
01682     be->events_pending = NULL;
01683     be->process_events = NULL;
01684 
01685     /* The SQL/DBI backend doesn't need to be synced until it is
01686      * configured for multiuser access. */
01687     be->sync = gnc_dbi_safe_sync_all;
01688     be->safe_sync = gnc_dbi_safe_sync_all;
01689     be->load_config = NULL;
01690     be->get_config = NULL;
01691 
01692     be->compile_query = gnc_sql_compile_query;
01693     be->run_query = gnc_sql_run_query;
01694     be->free_query = gnc_sql_free_query;
01695 
01696     be->export_fn = NULL;
01697 
01698     gnc_sql_init( &dbi_be->sql_be );
01699 
01700     dbi_be->sql_be.conn = NULL;
01701     dbi_be->sql_be.book = NULL;
01702 }
01703 
01704 static QofBackend*
01705 new_backend( void (*session_begin)( QofBackend *, QofSession *, const gchar *,
01706                                     /*@ unused @*/ gboolean,
01707                                     /*@ unused @*/ gboolean,
01708                                     /*@ unused @*/ gboolean ) )
01709 {
01710     GncDbiBackend *dbi_be;
01711     QofBackend *be;
01712 
01713     dbi_be = g_new0( GncDbiBackend, 1 );
01714     g_assert( dbi_be != NULL );
01715 
01716     be = (QofBackend*)dbi_be;
01717     qof_backend_init( be );
01718 
01719     be->session_begin = session_begin;
01720     init_sql_backend( dbi_be );
01721 
01722     return be;
01723 }
01724 
01725 static QofBackend*
01726 gnc_dbi_backend_sqlite3_new( void )
01727 {
01728     return new_backend( gnc_dbi_sqlite3_session_begin );
01729 }
01730 
01731 static QofBackend*
01732 gnc_dbi_backend_mysql_new( void )
01733 {
01734     return new_backend( gnc_dbi_mysql_session_begin );
01735 }
01736 
01737 static QofBackend*
01738 gnc_dbi_backend_postgres_new( void )
01739 {
01740     return new_backend( gnc_dbi_postgres_session_begin );
01741 }
01742 
01743 static void
01744 gnc_dbi_provider_free( /*@ only @*/ QofBackendProvider *prov )
01745 {
01746     g_return_if_fail( prov != NULL );
01747 
01748     g_free( prov );
01749 }
01750 
01751 /*
01752  * Checks to see whether the file is an sqlite file or not
01753  *
01754  */
01755 static gboolean
01756 gnc_dbi_check_sqlite3_file( const gchar *uri )
01757 {
01758     FILE* f;
01759     gchar buf[50];
01760     size_t chars_read;
01761     gint status;
01762     gchar *filename;
01763 
01764     // BAD if the path is null
01765     g_return_val_if_fail( uri != NULL, FALSE );
01766 
01767     filename = gnc_uri_get_path ( uri );
01768     f = g_fopen( filename, "r" );
01769     g_free ( filename );
01770 
01771     // OK if the file doesn't exist - new file
01772     if ( f == NULL )
01773     {
01774         PINFO( "doesn't exist (errno=%d) -> DBI", errno );
01775         return TRUE;
01776     }
01777 
01778     // OK if file has the correct header
01779     chars_read = fread( buf, sizeof(buf), 1, f );
01780     status = fclose( f );
01781     if ( status < 0 )
01782     {
01783         PERR( "Error in fclose(): %d\n", errno );
01784     }
01785     if ( g_str_has_prefix( buf, "SQLite format 3" ) )
01786     {
01787         PINFO( "has SQLite format string -> DBI" );
01788         return TRUE;
01789     }
01790     PINFO( "exists, does not have SQLite format string -> not DBI" );
01791 
01792     // Otherwise, BAD
01793     return FALSE;
01794 }
01795 
01796 void
01797 gnc_module_init_backend_dbi(void)
01798 {
01799     QofBackendProvider *prov;
01800     const gchar* driver_dir;
01801     int num_drivers;
01802     gboolean have_sqlite3_driver = FALSE;
01803     gboolean have_mysql_driver = FALSE;
01804     gboolean have_pgsql_driver = FALSE;
01805 
01806     /* Initialize libdbi and see which drivers are available.  Only register qof backends which
01807        have drivers available. */
01808     driver_dir = g_getenv( "GNC_DBD_DIR" );
01809     if ( driver_dir == NULL )
01810     {
01811         PINFO( "GNC_DBD_DIR not set: using libdbi built-in default\n");
01812     }
01813 
01814     /* dbi_initialize returns -1 in case of errors */
01815     num_drivers = dbi_initialize( driver_dir );
01816     if ( num_drivers <= 0 )
01817     {
01818         PWARN( "No DBD drivers found\n" );
01819     }
01820     else
01821     {
01822         dbi_driver driver = NULL;
01823         PINFO( "%d DBD drivers found\n", num_drivers );
01824 
01825         do
01826         {
01827             driver = dbi_driver_list( driver );
01828             if ( driver != NULL )
01829             {
01830                 const gchar* name = dbi_driver_get_name( driver );
01831 
01832                 PINFO( "Driver: %s\n", name );
01833                 if ( strcmp( name, "sqlite3" ) == 0 )
01834                 {
01835                     have_sqlite3_driver = TRUE;
01836                 }
01837                 else if ( strcmp( name, "mysql" ) == 0 )
01838                 {
01839                     have_mysql_driver = TRUE;
01840                 }
01841                 else if ( strcmp( name, "pgsql" ) == 0 )
01842                 {
01843                     have_pgsql_driver = TRUE;
01844                 }
01845             }
01846         }
01847         while ( driver != NULL );
01848     }
01849 
01850     if ( have_sqlite3_driver )
01851     {
01852         prov = g_new0( QofBackendProvider, 1 );
01853         g_assert( prov != NULL );
01854 
01855         prov->provider_name = "GnuCash Libdbi (SQLITE3) Backend";
01856         prov->access_method = FILE_URI_TYPE;
01857         prov->partial_book_supported = FALSE;
01858         prov->backend_new = gnc_dbi_backend_sqlite3_new;
01859         prov->provider_free = gnc_dbi_provider_free;
01860         prov->check_data_type = gnc_dbi_check_sqlite3_file;
01861         qof_backend_register_provider( prov );
01862 
01863         prov = g_new0( QofBackendProvider, 1 );
01864         g_assert( prov != NULL );
01865 
01866         prov->provider_name = "GnuCash Libdbi (SQLITE3) Backend";
01867         prov->access_method = SQLITE3_URI_TYPE;
01868         prov->partial_book_supported = FALSE;
01869         prov->backend_new = gnc_dbi_backend_sqlite3_new;
01870         prov->provider_free = gnc_dbi_provider_free;
01871         prov->check_data_type = gnc_dbi_check_sqlite3_file;
01872         qof_backend_register_provider( prov );
01873     }
01874 
01875     if ( have_mysql_driver )
01876     {
01877         prov = g_new0( QofBackendProvider, 1 );
01878         g_assert( prov != NULL );
01879 
01880         prov->provider_name = "GnuCash Libdbi (MYSQL) Backend";
01881         prov->access_method = "mysql";
01882         prov->partial_book_supported = FALSE;
01883         prov->backend_new = gnc_dbi_backend_mysql_new;
01884         prov->provider_free = gnc_dbi_provider_free;
01885         prov->check_data_type = NULL;
01886         qof_backend_register_provider( prov );
01887     }
01888 
01889     if ( have_pgsql_driver )
01890     {
01891         prov = g_new0( QofBackendProvider, 1 );
01892         g_assert( prov != NULL );
01893 
01894         prov->provider_name = "GnuCash Libdbi (POSTGRESQL) Backend";
01895         prov->access_method = "postgres";
01896         prov->partial_book_supported = FALSE;
01897         prov->backend_new = gnc_dbi_backend_postgres_new;
01898         prov->provider_free = gnc_dbi_provider_free;
01899         prov->check_data_type = NULL;
01900         qof_backend_register_provider( prov );
01901     }
01902 
01903     /* If needed, set log level to DEBUG so that SQl statements will be put into
01904        the gnucash.trace file. */
01905     /*    qof_log_set_level( log_module, QOF_LOG_DEBUG ); */
01906 }
01907 
01908 #ifndef GNC_NO_LOADABLE_MODULES
01909 G_MODULE_EXPORT void
01910 qof_backend_module_init( void )
01911 {
01912     gnc_module_init_backend_dbi();
01913 }
01914 
01915 G_MODULE_EXPORT void
01916 qof_backend_module_finalize( void )
01917 {
01918     gnc_module_finalize_backend_dbi();
01919 }
01920 #endif /* GNC_NO_LOADABLE_MODULES */
01921 
01922 void
01923 gnc_module_finalize_backend_dbi( void )
01924 {
01925     dbi_shutdown();
01926 }
01927 
01928 /* --------------------------------------------------------- */
01929 typedef struct
01930 {
01931     GncSqlRow base;
01932 
01933     /*@ dependent @*/
01934     dbi_result result;
01935     GList* gvalue_list;
01936 } GncDbiSqlRow;
01937 
01938 static void
01939 row_dispose( /*@ only @*/ GncSqlRow* row )
01940 {
01941     GncDbiSqlRow* dbi_row = (GncDbiSqlRow*)row;
01942     GList* node;
01943 
01944     if ( dbi_row->gvalue_list != NULL )
01945     {
01946         for ( node = dbi_row->gvalue_list; node != NULL; node = node->next )
01947         {
01948             GValue* value;
01949             if ( !G_IS_VALUE(node->data) )
01950                 continue;
01951             value = (GValue*)node->data;
01952             if ( G_VALUE_HOLDS_STRING(value) )
01953             {
01954                 g_free( (gpointer)g_value_get_string( value ) );
01955             }
01956             g_free( value );
01957         }
01958         g_list_free( dbi_row->gvalue_list );
01959     }
01960     g_free( dbi_row );
01961 }
01962 
01963 static /*@ null @*/ const GValue*
01964 row_get_value_at_col_name( GncSqlRow* row, const gchar* col_name )
01965 {
01966     GncDbiSqlRow* dbi_row = (GncDbiSqlRow*)row;
01967     gushort type;
01968     guint attrs;
01969     GValue* value;
01970     time_t time;
01971     struct tm tm_struct;
01972 
01973     type = dbi_result_get_field_type( dbi_row->result, col_name );
01974     attrs = dbi_result_get_field_attribs( dbi_row->result, col_name );
01975     value = g_new0( GValue, 1 );
01976     g_assert( value != NULL );
01977 
01978     switch ( type )
01979     {
01980     case DBI_TYPE_INTEGER:
01981         (void)g_value_init( value, G_TYPE_INT64 );
01982         g_value_set_int64( value, dbi_result_get_longlong( dbi_row->result, col_name ) );
01983         break;
01984     case DBI_TYPE_DECIMAL:
01985         gnc_push_locale( LC_NUMERIC, "C" );
01986         if ( (attrs & DBI_DECIMAL_SIZEMASK) == DBI_DECIMAL_SIZE4 )
01987         {
01988             (void)g_value_init( value, G_TYPE_FLOAT );
01989             g_value_set_float( value, dbi_result_get_float( dbi_row->result, col_name ) );
01990         }
01991         else if ( (attrs & DBI_DECIMAL_SIZEMASK) == DBI_DECIMAL_SIZE8 )
01992         {
01993             (void)g_value_init( value, G_TYPE_DOUBLE );
01994             g_value_set_double( value, dbi_result_get_double( dbi_row->result, col_name ) );
01995         }
01996         else
01997         {
01998             PERR( "Field %s: strange decimal length attrs=%d\n", col_name, attrs );
01999         }
02000         gnc_pop_locale( LC_NUMERIC );
02001         break;
02002     case DBI_TYPE_STRING:
02003         (void)g_value_init( value, G_TYPE_STRING );
02004         g_value_take_string( value, dbi_result_get_string_copy( dbi_row->result, col_name ) );
02005         break;
02006     case DBI_TYPE_DATETIME:
02007         if ( dbi_result_field_is_null( dbi_row->result, col_name ) )
02008         {
02009             return NULL;
02010         }
02011         else
02012         {
02013             time = dbi_result_get_datetime( dbi_row->result, col_name );
02014             (void)gmtime_r( &time, &tm_struct );
02015             (void)g_value_init( value, G_TYPE_STRING );
02016             g_value_take_string( value,
02017                                  g_strdup_printf( "%d%02d%02d%02d%02d%02d",
02018                                                   1900 + tm_struct.tm_year, tm_struct.tm_mon + 1, tm_struct.tm_mday,
02019                                                   tm_struct.tm_hour, tm_struct.tm_min, tm_struct.tm_sec ) );
02020         }
02021         break;
02022     default:
02023         PERR( "Field %s: unknown DBI_TYPE: %d\n", col_name, type );
02024         g_free( value );
02025         return NULL;
02026     }
02027 
02028     dbi_row->gvalue_list = g_list_prepend( dbi_row->gvalue_list, value );
02029     return value;
02030 }
02031 
02032 static GncSqlRow*
02033 create_dbi_row( /*@ dependent @*/ dbi_result result )
02034 {
02035     GncDbiSqlRow* row;
02036 
02037     row = g_new0( GncDbiSqlRow, 1 );
02038     g_assert( row != NULL );
02039 
02040     row->base.getValueAtColName = row_get_value_at_col_name;
02041     row->base.dispose = row_dispose;
02042     row->result = result;
02043 
02044     return (GncSqlRow*)row;
02045 }
02046 /* --------------------------------------------------------- */
02047 typedef struct
02048 {
02049     GncSqlResult base;
02050 
02051     /*@ observer @*/
02052     GncDbiSqlConnection* dbi_conn;
02053     /*@ owned @*/
02054     dbi_result result;
02055     guint num_rows;
02056     guint cur_row;
02057     GncSqlRow* row;
02058 } GncDbiSqlResult;
02059 
02060 static void
02061 result_dispose( /*@ only @*/ GncSqlResult* result )
02062 {
02063     GncDbiSqlResult* dbi_result = (GncDbiSqlResult*)result;
02064 
02065     if ( dbi_result->row != NULL )
02066     {
02067         gnc_sql_row_dispose( dbi_result->row );
02068     }
02069     if ( dbi_result->result != NULL )
02070     {
02071         gint status;
02072 
02073         status = dbi_result_free( dbi_result->result );
02074         if ( status < 0 )
02075         {
02076             PERR( "Error in dbi_result_free() result\n" );
02077             qof_backend_set_error( dbi_result->dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02078         }
02079     }
02080     g_free( result );
02081 }
02082 
02083 static guint
02084 result_get_num_rows( GncSqlResult* result )
02085 {
02086     GncDbiSqlResult* dbi_result = (GncDbiSqlResult*)result;
02087 
02088     return dbi_result->num_rows;
02089 }
02090 
02091 static /*@ null @*/ GncSqlRow*
02092 result_get_first_row( GncSqlResult* result )
02093 {
02094     GncDbiSqlResult* dbi_result = (GncDbiSqlResult*)result;
02095 
02096     if ( dbi_result->row != NULL )
02097     {
02098         gnc_sql_row_dispose( dbi_result->row );
02099         dbi_result->row = NULL;
02100     }
02101     if ( dbi_result->num_rows > 0 )
02102     {
02103         gint status = dbi_result_first_row( dbi_result->result );
02104         if ( status == 0 )
02105         {
02106             PERR( "Error in dbi_result_first_row()\n" );
02107             qof_backend_set_error( dbi_result->dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02108         }
02109         dbi_result->cur_row = 1;
02110         dbi_result->row = create_dbi_row( dbi_result->result );
02111         return dbi_result->row;
02112     }
02113     else
02114     {
02115         return NULL;
02116     }
02117 }
02118 
02119 static /*@ null @*/ GncSqlRow*
02120 result_get_next_row( GncSqlResult* result )
02121 {
02122     GncDbiSqlResult* dbi_result = (GncDbiSqlResult*)result;
02123 
02124     if ( dbi_result->row != NULL )
02125     {
02126         gnc_sql_row_dispose( dbi_result->row );
02127         dbi_result->row = NULL;
02128     }
02129     if ( dbi_result->cur_row < dbi_result->num_rows )
02130     {
02131         gint status = dbi_result_next_row( dbi_result->result );
02132         if ( status == 0 )
02133         {
02134             PERR( "Error in dbi_result_first_row()\n" );
02135             qof_backend_set_error( dbi_result->dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02136         }
02137         dbi_result->cur_row++;
02138         dbi_result->row = create_dbi_row( dbi_result->result );
02139         return dbi_result->row;
02140     }
02141     else
02142     {
02143         return NULL;
02144     }
02145 }
02146 
02147 static GncSqlResult*
02148 create_dbi_result( /*@ observer @*/ GncDbiSqlConnection* dbi_conn, /*@ owned @*/ dbi_result result )
02149 {
02150     GncDbiSqlResult* dbi_result;
02151 
02152     dbi_result = g_new0( GncDbiSqlResult, 1 );
02153     g_assert( dbi_result != NULL );
02154 
02155     dbi_result->base.dispose = result_dispose;
02156     dbi_result->base.getNumRows = result_get_num_rows;
02157     dbi_result->base.getFirstRow = result_get_first_row;
02158     dbi_result->base.getNextRow = result_get_next_row;
02159     dbi_result->result = result;
02160     dbi_result->num_rows = (guint)dbi_result_get_numrows( result );
02161     dbi_result->cur_row = 0;
02162     dbi_result->dbi_conn = dbi_conn;
02163 
02164     return (GncSqlResult*)dbi_result;
02165 }
02166 /* --------------------------------------------------------- */
02167 typedef struct
02168 {
02169     GncSqlStatement base;
02170 
02171     GString* sql;
02172     /*@ observer @*/
02173     GncSqlConnection* conn;
02174 } GncDbiSqlStatement;
02175 
02176 static void
02177 stmt_dispose( /*@ only @*/ GncSqlStatement* stmt )
02178 {
02179     GncDbiSqlStatement* dbi_stmt = (GncDbiSqlStatement*)stmt;
02180 
02181     if ( dbi_stmt->sql != NULL )
02182     {
02183         (void)g_string_free( dbi_stmt->sql, TRUE );
02184     }
02185     g_free( stmt );
02186 }
02187 
02188 static gchar*
02189 stmt_to_sql( GncSqlStatement* stmt )
02190 {
02191     GncDbiSqlStatement* dbi_stmt = (GncDbiSqlStatement*)stmt;
02192 
02193     return dbi_stmt->sql->str;
02194 }
02195 
02196 static void
02197 stmt_add_where_cond( GncSqlStatement* stmt, /*@ unused @*/ QofIdTypeConst type_name,
02198                      /*@ unused @*/ gpointer obj, const GncSqlColumnTableEntry* table_row, GValue* value )
02199 {
02200     GncDbiSqlStatement* dbi_stmt = (GncDbiSqlStatement*)stmt;
02201     gchar* buf;
02202     gchar* value_str;
02203 
02204     value_str = gnc_sql_get_sql_value( dbi_stmt->conn, value );
02205     buf = g_strdup_printf( " WHERE %s = %s", table_row->col_name,
02206                            value_str );
02207     g_free( value_str );
02208     (void)g_string_append( dbi_stmt->sql, buf );
02209     g_free( buf );
02210 }
02211 
02212 static GncSqlStatement*
02213 create_dbi_statement( /*@ observer @*/ GncSqlConnection* conn, const gchar* sql )
02214 {
02215     GncDbiSqlStatement* stmt;
02216 
02217     stmt = g_new0( GncDbiSqlStatement, 1 );
02218     g_assert( stmt != NULL );
02219 
02220     stmt->base.dispose = stmt_dispose;
02221     stmt->base.toSql = stmt_to_sql;
02222     stmt->base.addWhereCond = stmt_add_where_cond;
02223     stmt->sql = g_string_new( sql );
02224     stmt->conn = conn;
02225 
02226     return (GncSqlStatement*)stmt;
02227 }
02228 /* --------------------------------------------------------- */
02229 static void
02230 conn_dispose( /*@ only @*/ GncSqlConnection* conn )
02231 {
02232     //GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02233 
02234     g_free( conn );
02235 }
02236 
02237 static /*@ null @*/ GncSqlResult*
02238 conn_execute_select_statement( GncSqlConnection* conn, GncSqlStatement* stmt )
02239 {
02240     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02241     GncDbiSqlStatement* dbi_stmt = (GncDbiSqlStatement*)stmt;
02242     dbi_result result;
02243 
02244     DEBUG( "SQL: %s\n", dbi_stmt->sql->str );
02245     gnc_push_locale( LC_NUMERIC, "C" );
02246     do
02247     {
02248         gnc_dbi_init_error( dbi_conn );
02249         result = dbi_conn_query( dbi_conn->conn, dbi_stmt->sql->str );
02250     }
02251     while ( dbi_conn->retry );
02252     if ( result == NULL )
02253     {
02254         PERR( "Error executing SQL %s\n", dbi_stmt->sql->str );
02255         return NULL;
02256     }
02257     gnc_pop_locale( LC_NUMERIC );
02258     return create_dbi_result( dbi_conn, result );
02259 }
02260 
02261 static gint
02262 conn_execute_nonselect_statement( GncSqlConnection* conn, GncSqlStatement* stmt )
02263 {
02264     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02265     GncDbiSqlStatement* dbi_stmt = (GncDbiSqlStatement*)stmt;
02266     dbi_result result;
02267     gint num_rows;
02268     gint status;
02269 
02270     DEBUG( "SQL: %s\n", dbi_stmt->sql->str );
02271     do
02272     {
02273         gnc_dbi_init_error( dbi_conn );
02274         result = dbi_conn_query( dbi_conn->conn, dbi_stmt->sql->str );
02275     }
02276     while ( dbi_conn->retry );
02277     if ( result == NULL )
02278     {
02279         PERR( "Error executing SQL %s\n", dbi_stmt->sql->str );
02280         return -1;
02281     }
02282     num_rows = (gint)dbi_result_get_numrows_affected( result );
02283     status = dbi_result_free( result );
02284     if ( status < 0 )
02285     {
02286         PERR( "Error in dbi_result_free() result\n" );
02287         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02288     }
02289     return num_rows;
02290 }
02291 
02292 static GncSqlStatement*
02293 conn_create_statement_from_sql( /*@ observer @*/ GncSqlConnection* conn, const gchar* sql )
02294 {
02295     //GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02296 
02297     return create_dbi_statement( conn, sql );
02298 }
02299 
02300 static GValue*
02301 create_gvalue_from_string( /*@ only @*/ gchar* s )
02302 {
02303     GValue* s_gval;
02304 
02305     s_gval = g_new0( GValue, 1 );
02306     g_assert( s_gval != NULL );
02307 
02308     (void)g_value_init( s_gval, G_TYPE_STRING );
02309     g_value_take_string( s_gval, s );
02310 
02311     return s_gval;
02312 }
02313 
02314 static gboolean
02315 conn_does_table_exist( GncSqlConnection* conn, const gchar* table_name )
02316 {
02317     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02318     gint nTables;
02319     dbi_result tables;
02320     const gchar* dbname;
02321     gint status;
02322 
02323     g_return_val_if_fail( conn != NULL, FALSE );
02324     g_return_val_if_fail( table_name != NULL, FALSE );
02325 
02326     dbname = dbi_conn_get_option( dbi_conn->conn, "dbname" );
02327     tables = dbi_conn_get_table_list( dbi_conn->conn, dbname, table_name );
02328     nTables = (gint)dbi_result_get_numrows( tables );
02329     status = dbi_result_free( tables );
02330     if ( status < 0 )
02331     {
02332         PERR( "Error in dbi_result_free() result\n" );
02333         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02334     }
02335 
02336     if ( nTables == 1 )
02337     {
02338         return TRUE;
02339     }
02340     else
02341     {
02342         return FALSE;
02343     }
02344 }
02345 
02346 static gboolean
02347 conn_begin_transaction( /*@ unused @*/ GncSqlConnection* conn )
02348 {
02349     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02350     dbi_result result;
02351     gint status;
02352     gboolean success = FALSE;
02353 
02354     DEBUG( "BEGIN\n" );
02355 
02356     if ( !gnc_dbi_verify_conn (dbi_conn) )
02357     {
02358         PERR( "gnc_dbi_verify_conn() failed\n" );
02359         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02360         return FALSE;
02361     }
02362 
02363     do
02364     {
02365         gnc_dbi_init_error( dbi_conn );
02366         result = dbi_conn_queryf( dbi_conn->conn, "BEGIN" );
02367     }
02368     while ( dbi_conn->retry );
02369 
02370     success = ( result != NULL );
02371     status = dbi_result_free( result );
02372     if ( status < 0 )
02373     {
02374         PERR( "Error in dbi_result_free() result\n" );
02375         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02376     }
02377     if ( !success )
02378     {
02379         PERR( "BEGIN transaction failed()\n" );
02380         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02381     }
02382 
02383     return success;
02384 }
02385 
02386 static gboolean
02387 conn_rollback_transaction( /*@ unused @*/ GncSqlConnection* conn )
02388 {
02389     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02390     dbi_result result;
02391     gint status;
02392     gboolean success = FALSE;
02393 
02394     DEBUG( "ROLLBACK\n" );
02395     result = dbi_conn_queryf( dbi_conn->conn, "ROLLBACK" );
02396     success = ( result != NULL );
02397 
02398     status = dbi_result_free( result );
02399     if ( status < 0 )
02400     {
02401         PERR( "Error in dbi_result_free() result\n" );
02402         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02403     }
02404     if ( !success )
02405     {
02406         PERR( "Error in conn_rollback_transaction()\n" );
02407         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02408     }
02409 
02410     return success;
02411 }
02412 
02413 static gboolean
02414 conn_commit_transaction( /*@ unused @*/ GncSqlConnection* conn )
02415 {
02416     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02417     dbi_result result;
02418     gint status;
02419     gboolean success = FALSE;
02420 
02421     DEBUG( "COMMIT\n" );
02422     result = dbi_conn_queryf( dbi_conn->conn, "COMMIT" );
02423     success = ( result != NULL );
02424 
02425     status = dbi_result_free( result );
02426     if ( status < 0 )
02427     {
02428         PERR( "Error in dbi_result_free() result\n" );
02429         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02430     }
02431     if ( !success )
02432     {
02433         PERR( "Error in conn_commit_transaction()\n" );
02434         qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02435     }
02436 
02437     return success;
02438 }
02439 
02440 static /*@ null @*/ gchar*
02441 create_index_ddl( GncSqlConnection* conn,
02442                   const gchar* index_name,
02443                   const gchar* table_name,
02444                   const GncSqlColumnTableEntry* col_table )
02445 {
02446     GString* ddl;
02447     const GncSqlColumnTableEntry* table_row;
02448 
02449     g_return_val_if_fail( conn != NULL, NULL );
02450     g_return_val_if_fail( index_name != NULL, NULL );
02451     g_return_val_if_fail( table_name != NULL, NULL );
02452     g_return_val_if_fail( col_table != NULL, NULL );
02453 
02454     ddl = g_string_new( "" );
02455     g_string_printf( ddl, "CREATE INDEX %s ON %s (", index_name, table_name );
02456     for ( table_row = col_table; table_row->col_name != NULL; ++table_row )
02457     {
02458         if ( table_row != col_table )
02459         {
02460             (void)g_string_append( ddl, ", " );
02461         }
02462         g_string_append_printf( ddl, "%s", table_row->col_name );
02463     }
02464     (void)g_string_append( ddl, ")" );
02465 
02466     return g_string_free( ddl, FALSE );
02467 }
02468 
02469 static /*@ null @*/ gchar*
02470 add_columns_ddl( GncSqlConnection* conn,
02471                  const gchar* table_name,
02472                  GList* col_info_list )
02473 {
02474     GString* ddl;
02475     const GList* list_node;
02476     const GncSqlColumnTableEntry* table_row;
02477     guint col_num;
02478     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02479 
02480     g_return_val_if_fail( conn != NULL, NULL );
02481     g_return_val_if_fail( table_name != NULL, NULL );
02482     g_return_val_if_fail( col_info_list != NULL, NULL );
02483 
02484     ddl = g_string_new( "" );
02485     g_string_printf( ddl, "ALTER TABLE %s ", table_name );
02486     for ( list_node = col_info_list, col_num = 0; list_node != NULL;
02487             list_node = list_node->next, col_num++ )
02488     {
02489         GncSqlColumnInfo* info = (GncSqlColumnInfo*)(list_node->data);
02490 
02491         if ( col_num != 0 )
02492         {
02493             (void)g_string_append( ddl, ", " );
02494         }
02495         g_string_append( ddl, "ADD COLUMN " );
02496         dbi_conn->provider->append_col_def( ddl, info );
02497         g_free( info->name );
02498         g_free( info );
02499     }
02500 
02501     return g_string_free( ddl, FALSE );
02502 }
02503 
02504 static void
02505 append_sqlite3_col_def( GString* ddl, GncSqlColumnInfo* info )
02506 {
02507     gchar* type_name;
02508 
02509     if ( info->type == BCT_INT )
02510     {
02511         type_name = "integer";
02512     }
02513     else if ( info->type == BCT_INT64 )
02514     {
02515         type_name = "bigint";
02516     }
02517     else if ( info->type == BCT_DOUBLE )
02518     {
02519         type_name = "float8";
02520     }
02521     else if ( info->type == BCT_STRING || info->type == BCT_DATE
02522               || info->type == BCT_DATETIME )
02523     {
02524         type_name = "text";
02525     }
02526     else
02527     {
02528         PERR( "Unknown column type: %d\n", info->type );
02529         type_name = "";
02530     }
02531     g_string_append_printf( ddl, "%s %s", info->name, type_name );
02532     if ( info->size != 0 )
02533     {
02534         (void)g_string_append_printf( ddl, "(%d)", info->size );
02535     }
02536     if ( info->is_primary_key )
02537     {
02538         (void)g_string_append( ddl, " PRIMARY KEY" );
02539     }
02540     if ( info->is_autoinc )
02541     {
02542         (void)g_string_append( ddl, " AUTOINCREMENT" );
02543     }
02544     if ( !info->null_allowed )
02545     {
02546         (void)g_string_append( ddl, " NOT NULL" );
02547     }
02548 }
02549 
02550 static /*@ null @*/ gchar*
02551 conn_create_table_ddl_sqlite3( GncSqlConnection* conn,
02552                                const gchar* table_name,
02553                                const GList* col_info_list )
02554 {
02555     GString* ddl;
02556     const GList* list_node;
02557     guint col_num;
02558 
02559     g_return_val_if_fail( conn != NULL, NULL );
02560     g_return_val_if_fail( table_name != NULL, NULL );
02561     g_return_val_if_fail( col_info_list != NULL, NULL );
02562 
02563     ddl = g_string_new( "" );
02564     g_string_printf( ddl, "CREATE TABLE %s (", table_name );
02565     for ( list_node = col_info_list, col_num = 0; list_node != NULL;
02566             list_node = list_node->next, col_num++ )
02567     {
02568         GncSqlColumnInfo* info = (GncSqlColumnInfo*)(list_node->data);
02569 
02570         if ( col_num != 0 )
02571         {
02572             (void)g_string_append( ddl, ", " );
02573         }
02574         append_sqlite3_col_def( ddl, info );
02575         g_free( info->name );
02576         g_free( info );
02577     }
02578     (void)g_string_append( ddl, ")" );
02579 
02580     return g_string_free( ddl, FALSE );
02581 }
02582 
02583 static void
02584 append_mysql_col_def( GString* ddl, GncSqlColumnInfo* info )
02585 {
02586     gchar* type_name;
02587 
02588     if ( info->type == BCT_INT )
02589     {
02590         type_name = "integer";
02591     }
02592     else if ( info->type == BCT_INT64 )
02593     {
02594         type_name = "bigint";
02595     }
02596     else if ( info->type == BCT_DOUBLE )
02597     {
02598         type_name = "double";
02599     }
02600     else if ( info->type == BCT_STRING )
02601     {
02602         type_name = "varchar";
02603     }
02604     else if ( info->type == BCT_DATE )
02605     {
02606         info->size = 0;
02607         type_name = "date";
02608     }
02609     else if ( info->type == BCT_DATETIME )
02610     {
02611         info->size = 0;
02612         type_name = "TIMESTAMP NULL DEFAULT 0";
02613     }
02614     else
02615     {
02616         PERR( "Unknown column type: %d\n", info->type );
02617         type_name = "";
02618     }
02619     g_string_append_printf( ddl, "%s %s", info->name, type_name );
02620     if ( info->size != 0 )
02621     {
02622         g_string_append_printf( ddl, "(%d)", info->size );
02623     }
02624     if ( info->is_unicode )
02625     {
02626         (void)g_string_append( ddl, " CHARACTER SET utf8" );
02627     }
02628     if ( info->is_primary_key )
02629     {
02630         (void)g_string_append( ddl, " PRIMARY KEY" );
02631     }
02632     if ( info->is_autoinc )
02633     {
02634         (void)g_string_append( ddl, " AUTO_INCREMENT" );
02635     }
02636     if ( !info->null_allowed )
02637     {
02638         (void)g_string_append( ddl, " NOT NULL" );
02639     }
02640 }
02641 
02642 static /*@ null @*/ gchar*
02643 conn_create_table_ddl_mysql( GncSqlConnection* conn, const gchar* table_name,
02644                              const GList* col_info_list )
02645 {
02646     GString* ddl;
02647     const GList* list_node;
02648     guint col_num;
02649 
02650     g_return_val_if_fail( conn != NULL, NULL );
02651     g_return_val_if_fail( table_name != NULL, NULL );
02652     g_return_val_if_fail( col_info_list != NULL, NULL );
02653 
02654     ddl = g_string_new( "" );
02655     g_string_printf( ddl, "CREATE TABLE %s (", table_name );
02656     for ( list_node = col_info_list, col_num = 0; list_node != NULL;
02657             list_node = list_node->next, col_num++ )
02658     {
02659         GncSqlColumnInfo* info = (GncSqlColumnInfo*)(list_node->data);
02660 
02661         if ( col_num != 0 )
02662         {
02663             (void)g_string_append( ddl, ", " );
02664         }
02665         append_mysql_col_def( ddl, info );
02666         g_free( info->name );
02667         g_free( info );
02668     }
02669     (void)g_string_append( ddl, ")" );
02670 
02671     return g_string_free( ddl, FALSE );
02672 }
02673 
02674 static void
02675 append_pgsql_col_def( GString* ddl, GncSqlColumnInfo* info )
02676 {
02677     gchar* type_name;
02678 
02679     if ( info->type == BCT_INT )
02680     {
02681         if ( info->is_autoinc )
02682         {
02683             type_name = "serial";
02684         }
02685         else
02686         {
02687             type_name = "integer";
02688         }
02689     }
02690     else if ( info->type == BCT_INT64 )
02691     {
02692         type_name = "int8";
02693     }
02694     else if ( info->type == BCT_DOUBLE )
02695     {
02696         type_name = "double precision";
02697     }
02698     else if ( info->type == BCT_STRING )
02699     {
02700         type_name = "varchar";
02701     }
02702     else if ( info->type == BCT_DATE )
02703     {
02704         info->size = 0;
02705         type_name = "date";
02706     }
02707     else if ( info->type == BCT_DATETIME )
02708     {
02709         info->size = 0;
02710         type_name = "timestamp without time zone";
02711     }
02712     else
02713     {
02714         PERR( "Unknown column type: %d\n", info->type );
02715         type_name = "";
02716     }
02717     g_string_append_printf( ddl, "%s %s", info->name, type_name );
02718     if ( info->size != 0 )
02719     {
02720         g_string_append_printf( ddl, "(%d)", info->size );
02721     }
02722     if ( info->is_primary_key )
02723     {
02724         (void)g_string_append( ddl, " PRIMARY KEY" );
02725     }
02726     if ( !info->null_allowed )
02727     {
02728         (void)g_string_append( ddl, " NOT NULL" );
02729     }
02730 }
02731 
02732 static /*@ null @*/ gchar*
02733 conn_create_table_ddl_pgsql( GncSqlConnection* conn, const gchar* table_name,
02734                              const GList* col_info_list )
02735 {
02736     GString* ddl;
02737     const GList* list_node;
02738     guint col_num;
02739     gboolean is_unicode = FALSE;
02740 
02741     g_return_val_if_fail( conn != NULL, NULL );
02742     g_return_val_if_fail( table_name != NULL, NULL );
02743     g_return_val_if_fail( col_info_list != NULL, NULL );
02744 
02745     ddl = g_string_new( "" );
02746     g_string_printf( ddl, "CREATE TABLE %s (", table_name );
02747     for ( list_node = col_info_list, col_num = 0; list_node != NULL;
02748             list_node = list_node->next, col_num++ )
02749     {
02750         GncSqlColumnInfo* info = (GncSqlColumnInfo*)(list_node->data);
02751 
02752         if ( col_num != 0 )
02753         {
02754             (void)g_string_append( ddl, ", " );
02755         }
02756         append_pgsql_col_def( ddl, info );
02757         is_unicode = is_unicode || info->is_unicode;
02758         g_free( info->name );
02759         g_free( info );
02760     }
02761     (void)g_string_append( ddl, ")" );
02762     if ( is_unicode )
02763     {
02764     }
02765 
02766     return g_string_free( ddl, FALSE );
02767 }
02768 
02769 static gboolean
02770 conn_create_table( GncSqlConnection* conn, const gchar* table_name,
02771                    GList* col_info_list )
02772 {
02773     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02774     gchar* ddl;
02775     dbi_result result;
02776 
02777     g_return_val_if_fail( conn != NULL, FALSE );
02778     g_return_val_if_fail( table_name != NULL, FALSE );
02779     g_return_val_if_fail( col_info_list != NULL, FALSE );
02780 
02781 
02782     ddl = dbi_conn->provider->create_table_ddl( conn, table_name,
02783             col_info_list );
02784     g_list_free( col_info_list );
02785     if ( ddl != NULL )
02786     {
02787         gint status;
02788 
02789         DEBUG( "SQL: %s\n", ddl );
02790         result = dbi_conn_query( dbi_conn->conn, ddl );
02791         g_free( ddl );
02792         status = dbi_result_free( result );
02793         if ( status < 0 )
02794         {
02795             PERR( "Error in dbi_result_free() result\n" );
02796             qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02797         }
02798     }
02799     else
02800     {
02801         return FALSE;
02802     }
02803 
02804     return TRUE;
02805 }
02806 
02807 static gboolean
02808 conn_create_index( /*@ unused @*/ GncSqlConnection* conn, /*@ unused @*/ const gchar* index_name,
02809                                   /*@ unused @*/ const gchar* table_name, /*@ unused @*/ const GncSqlColumnTableEntry* col_table )
02810 {
02811     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02812     gchar* ddl;
02813     dbi_result result;
02814     gint status;
02815 
02816     g_return_val_if_fail( conn != NULL, FALSE );
02817     g_return_val_if_fail( index_name != NULL, FALSE );
02818     g_return_val_if_fail( table_name != NULL, FALSE );
02819     g_return_val_if_fail( col_table != NULL, FALSE );
02820 
02821     ddl = create_index_ddl( conn, index_name, table_name, col_table );
02822     if ( ddl != NULL )
02823     {
02824         gint status;
02825 
02826         DEBUG( "SQL: %s\n", ddl );
02827         result = dbi_conn_query( dbi_conn->conn, ddl );
02828         g_free( ddl );
02829         status = dbi_result_free( result );
02830         if ( status < 0 )
02831         {
02832             PERR( "Error in dbi_result_free() result\n" );
02833             qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02834         }
02835     }
02836     else
02837     {
02838         return FALSE;
02839     }
02840 
02841     return TRUE;
02842 }
02843 
02844 static gboolean
02845 conn_add_columns_to_table( /*@ unused @*/ GncSqlConnection* conn, /*@ unused @*/ const gchar* table_name,
02846         GList* col_info_list )
02847 {
02848     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02849     gchar* ddl;
02850     dbi_result result;
02851     gint status;
02852 
02853     g_return_val_if_fail( conn != NULL, FALSE );
02854     g_return_val_if_fail( table_name != NULL, FALSE );
02855     g_return_val_if_fail( col_info_list != NULL, FALSE );
02856 
02857     ddl = add_columns_ddl( conn, table_name, col_info_list );
02858     if ( ddl != NULL )
02859     {
02860         gint status;
02861 
02862         DEBUG( "SQL: %s\n", ddl );
02863         result = dbi_conn_query( dbi_conn->conn, ddl );
02864         g_free( ddl );
02865         status = dbi_result_free( result );
02866         if ( status < 0 )
02867         {
02868             PERR( "Error in dbi_result_free() result\n" );
02869             qof_backend_set_error( dbi_conn->qbe, ERR_BACKEND_SERVER_ERR );
02870         }
02871     }
02872     else
02873     {
02874         return FALSE;
02875     }
02876 
02877     return TRUE;
02878 }
02879 
02880 static /*@ null @*/ gchar*
02881 conn_quote_string( const GncSqlConnection* conn, gchar* unquoted_str )
02882 {
02883     GncDbiSqlConnection* dbi_conn = (GncDbiSqlConnection*)conn;
02884     gchar* quoted_str;
02885     size_t size;
02886 
02887     size = dbi_conn_quote_string_copy( dbi_conn->conn, unquoted_str,
02888                                        &quoted_str );
02889     if ( size != 0 )
02890     {
02891         return quoted_str;
02892     }
02893     else
02894     {
02895         return NULL;
02896     }
02897 }
02898 
02899 static GSList*
02900 conn_get_table_list( dbi_conn conn, const gchar* dbname )
02901 {
02902     dbi_result tables;
02903     GSList* list = NULL;
02904 
02905     tables = dbi_conn_get_table_list( conn, dbname, NULL );
02906     while ( dbi_result_next_row( tables ) != 0 )
02907     {
02908         const gchar* table_name;
02909         dbi_result result;
02910 
02911         table_name = dbi_result_get_string_idx( tables, 1 );
02912         list = g_slist_prepend( list, strdup( table_name ) );
02913     }
02914     dbi_result_free( tables );
02915     return list;
02916 }
02917 
02918 static GSList*
02919 conn_get_table_list_sqlite3( dbi_conn conn, const gchar* dbname )
02920 {
02921     gboolean change_made;
02922 
02923     /* Return the list, but remove the tables that sqlite3 adds for
02924      * its own use. */
02925     GSList* list = conn_get_table_list( conn, dbname );
02926     change_made = TRUE;
02927     while ( list != NULL && change_made )
02928     {
02929         GSList* node;
02930 
02931         change_made = FALSE;
02932         for ( node = list; node != NULL; node = node->next )
02933         {
02934             const gchar* table_name = (const gchar*)node->data;
02935 
02936             if ( strcmp( table_name, "sqlite_sequence" ) == 0 )
02937             {
02938                 g_free( node->data );
02939                 list = g_slist_delete_link( list, node );
02940                 change_made = TRUE;
02941                 break;
02942             }
02943         }
02944     }
02945     return list;
02946 }
02947 
02948 static GSList*
02949 conn_get_table_list_pgsql( dbi_conn conn, const gchar* dbname )
02950 {
02951     gboolean change_made;
02952 
02953     /* Return the list, but remove the tables that postgresql adds from the information schema. */
02954     GSList* list = conn_get_table_list( conn, dbname );
02955     change_made = TRUE;
02956     while ( list != NULL && change_made )
02957     {
02958         GSList* node;
02959 
02960         change_made = FALSE;
02961         for ( node = list; node != NULL; node = node->next )
02962         {
02963             const gchar* table_name = (const gchar*)node->data;
02964 
02965             if ( strcmp( table_name, "sql_features" ) == 0 ||
02966                     strcmp( table_name, "sql_implementation_info" ) == 0 ||
02967                     strcmp( table_name, "sql_languages" ) == 0 ||
02968                     strcmp( table_name, "sql_packages" ) == 0 ||
02969                     strcmp( table_name, "sql_parts" ) == 0 ||
02970                     strcmp( table_name, "sql_sizing" ) == 0 ||
02971                     strcmp( table_name, "sql_sizing_profiles" ) == 0 )
02972             {
02973                 g_free( node->data );
02974                 list = g_slist_delete_link( list, node );
02975                 change_made = TRUE;
02976                 break;
02977             }
02978         }
02979     }
02980     return list;
02981 }
02982 
02992 static GncDbiTestResult
02993 conn_test_dbi_library( dbi_conn conn )
02994 {
02995     gint64 testlonglong = -9223372036854775807LL, resultlonglong = 0;
02996     guint64 testulonglong = 9223372036854775807LLU, resultulonglong = 0;
02997     gdouble testdouble = 1.7976921348623157E+307, resultdouble = 0.0;
02998     dbi_result result;
02999     gchar doublestr[G_ASCII_DTOSTR_BUF_SIZE], *querystr;
03000     GncDbiTestResult retval = GNC_DBI_PASS;
03001     memset( doublestr, 0, sizeof(doublestr));
03002 
03003     result = dbi_conn_query( conn, "CREATE TEMPORARY TABLE numtest "
03004                              "( test_int BIGINT, test_unsigned BIGINT,"
03005                              " test_double FLOAT8 )" );
03006     if ( result == NULL )
03007     {
03008         PWARN("Test_DBI_Library: Create table failed");
03009         return GNC_DBI_FAIL_SETUP;
03010     }
03011     dbi_result_free( result );
03012     g_ascii_dtostr( doublestr, sizeof(doublestr), testdouble );
03013     querystr = g_strdup_printf( "INSERT INTO numtest VALUES (%" G_GINT64_FORMAT
03014                                 ", %" G_GUINT64_FORMAT ", %s)",
03015                                 testlonglong, testulonglong, doublestr );
03016     result = dbi_conn_query( conn, querystr );
03017     g_free( querystr );
03018     if ( result == NULL )
03019     {
03020         PWARN("Test_DBI_Library: Failed to insert test row into table" );
03021         return GNC_DBI_FAIL_SETUP;
03022     }
03023     dbi_result_free( result );
03024     gnc_push_locale( LC_NUMERIC, "C");
03025     result = dbi_conn_query( conn, "SELECT * FROM numtest" );
03026     if ( result == NULL )
03027     {
03028         const char *errmsg;
03029         dbi_conn_error( conn, &errmsg );
03030         PWARN("Test_DBI_Library: Failed to retrieve test row into table: %s",
03031               errmsg );
03032         result = dbi_conn_query( conn, "DROP TABLE numtest" );
03033         gnc_pop_locale( LC_NUMERIC );
03034         return GNC_DBI_FAIL_SETUP;
03035     }
03036     while ( dbi_result_next_row( result ))
03037     {
03038         resultlonglong = dbi_result_get_longlong( result, "test_int" );
03039         resultulonglong = dbi_result_get_ulonglong( result, "test_unsigned" );
03040         resultdouble = dbi_result_get_double( result, "test_double" );
03041     }
03042     gnc_pop_locale( LC_NUMERIC );
03043     if ( testlonglong != resultlonglong )
03044     {
03045         PWARN( "Test_DBI_Library: LongLong Failed %" G_GINT64_FORMAT " != % " G_GINT64_FORMAT,
03046                testlonglong, resultlonglong );
03047         retval = GNC_DBI_FAIL_TEST;
03048     }
03049     if ( testulonglong != resultulonglong )
03050     {
03051         PWARN( "Test_DBI_Library: Unsigned longlong Failed %" G_GUINT64_FORMAT " != %" G_GUINT64_FORMAT,
03052                testulonglong, resultulonglong );
03053         retval = GNC_DBI_FAIL_TEST;
03054     }
03055     /* A bug in libdbi stores only 7 digits of precision */
03056     if ( testdouble >= resultdouble + 0.000001e307 ||
03057             testdouble <= resultdouble - 0.000001e307 )
03058     {
03059         PWARN( "Test_DBI_Library: Double Failed %17e != %17e",
03060                testdouble, resultdouble );
03061         retval = GNC_DBI_FAIL_TEST;
03062     }
03063     return retval;
03064 }
03065 
03066 
03067 static GncSqlConnection*
03068 create_dbi_connection( /*@ observer @*/ provider_functions_t* provider,
03069                                         /*@ observer @*/ QofBackend* qbe,
03070                                         /*@ observer @*/ dbi_conn conn )
03071 {
03072     GncDbiSqlConnection* dbi_conn;
03073 
03074     dbi_conn = g_new0( GncDbiSqlConnection, 1 );
03075     g_assert( dbi_conn != NULL );
03076 
03077     dbi_conn->base.dispose = conn_dispose;
03078     dbi_conn->base.executeSelectStatement = conn_execute_select_statement;
03079     dbi_conn->base.executeNonSelectStatement = conn_execute_nonselect_statement;
03080     dbi_conn->base.createStatementFromSql = conn_create_statement_from_sql;
03081     dbi_conn->base.doesTableExist = conn_does_table_exist;
03082     dbi_conn->base.beginTransaction = conn_begin_transaction;
03083     dbi_conn->base.rollbackTransaction = conn_rollback_transaction;
03084     dbi_conn->base.commitTransaction = conn_commit_transaction;
03085     dbi_conn->base.createTable = conn_create_table;
03086     dbi_conn->base.createIndex = conn_create_index;
03087     dbi_conn->base.addColumnsToTable = conn_add_columns_to_table;
03088     dbi_conn->base.quoteString = conn_quote_string;
03089     dbi_conn->qbe = qbe;
03090     dbi_conn->conn = conn;
03091     dbi_conn->provider = provider;
03092     dbi_conn->conn_ok = TRUE;
03093     gnc_dbi_init_error(dbi_conn);
03094 
03095     return (GncSqlConnection*)dbi_conn;
03096 }
03097 
03098 /* ========================== END OF FILE ===================== */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines