GnuCash 2.4.99
gnc-transaction-sql.c
Go to the documentation of this file.
00001 /********************************************************************
00002  * gnc-transaction-sql.c: load and save data to SQL                 *
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 <glib/gi18n.h>
00032 
00033 #include "qof.h"
00034 #include "qofquery-p.h"
00035 #include "qofquerycore-p.h"
00036 
00037 #include "Account.h"
00038 #include "Transaction.h"
00039 #include "gnc-lot.h"
00040 #include "engine-helpers.h"
00041 
00042 #include "gnc-backend-sql.h"
00043 #include "gnc-transaction-sql.h"
00044 #include "gnc-commodity.h"
00045 #include "gnc-commodity-sql.h"
00046 #include "gnc-slots-sql.h"
00047 
00048 #include "gnc-engine.h"
00049 
00050 #include "escape.h"
00051 
00052 #ifdef S_SPLINT_S
00053 #include "splint-defs.h"
00054 #endif
00055 
00056 #define SIMPLE_QUERY_COMPILATION 1
00057 #define LOAD_TRANSACTIONS_AS_NEEDED 0
00058 
00059 static QofLogModule log_module = G_LOG_DOMAIN;
00060 
00061 #define TRANSACTION_TABLE "transactions"
00062 #define TX_TABLE_VERSION 3
00063 #define SPLIT_TABLE "splits"
00064 #define SPLIT_TABLE_VERSION 4
00065 
00066 typedef struct
00067 {
00068     /*@ dependent @*/ GncSqlBackend* be;
00069     /*@ dependent @*/
00070     const GncGUID* guid;
00071     gboolean is_ok;
00072 } split_info_t;
00073 
00074 #define TX_MAX_NUM_LEN 2048
00075 #define TX_MAX_DESCRIPTION_LEN 2048
00076 
00077 static const GncSqlColumnTableEntry tx_col_table[] =
00078 {
00079     /*@ -full_init_block @*/
00080     { "guid",          CT_GUID,           0,                      COL_NNUL | COL_PKEY, "guid" },
00081     { "currency_guid", CT_COMMODITYREF,   0,                      COL_NNUL,          "currency" },
00082     { "num",           CT_STRING,         TX_MAX_NUM_LEN,         COL_NNUL,          "num" },
00083     { "post_date",     CT_TIMESPEC,       0,                      0,                 "post-date" },
00084     { "enter_date",    CT_TIMESPEC,       0,                      0,                 "enter-date" },
00085     { "description",   CT_STRING,         TX_MAX_DESCRIPTION_LEN, 0,                 "description" },
00086     { NULL }
00087     /*@ +full_init_block @*/
00088 };
00089 
00090 static /*@ dependent @*//*@ null @*/ gpointer get_split_reconcile_state( gpointer pObject );
00091 static void set_split_reconcile_state( gpointer pObject, /*@ null @*/ gpointer pValue );
00092 static void set_split_reconcile_date( gpointer pObject, Timespec ts );
00093 static void set_split_lot( gpointer pObject, /*@ null @*/ gpointer pLot );
00094 
00095 #define SPLIT_MAX_MEMO_LEN 2048
00096 #define SPLIT_MAX_ACTION_LEN 2048
00097 
00098 static const GncSqlColumnTableEntry split_col_table[] =
00099 {
00100     /*@ -full_init_block @*/
00101     { "guid",            CT_GUID,         0,                    COL_NNUL | COL_PKEY, "guid" },
00102     { "tx_guid",         CT_TXREF,        0,                    COL_NNUL,          "transaction" },
00103     { "account_guid",    CT_ACCOUNTREF,   0,                    COL_NNUL,          "account" },
00104     { "memo",            CT_STRING,       SPLIT_MAX_MEMO_LEN,   COL_NNUL,          "memo" },
00105     { "action",          CT_STRING,       SPLIT_MAX_ACTION_LEN, COL_NNUL,          "action" },
00106     {
00107         "reconcile_state", CT_STRING,       1,                    COL_NNUL,          NULL, NULL,
00108         (QofAccessFunc)get_split_reconcile_state, set_split_reconcile_state
00109     },
00110     { "reconcile_date",  CT_TIMESPEC,     0,                    0,                 "reconcile-date" },
00111     { "value",           CT_NUMERIC,      0,                    COL_NNUL,          "value" },
00112     { "quantity",        CT_NUMERIC,      0,                    COL_NNUL,          "amount" },
00113     {
00114         "lot_guid",        CT_LOTREF,       0,                    0,                 NULL, NULL,
00115         (QofAccessFunc)xaccSplitGetLot, set_split_lot
00116     },
00117     { NULL }
00118     /*@ +full_init_block @*/
00119 };
00120 
00121 static const GncSqlColumnTableEntry post_date_col_table[] =
00122 {
00123     /*@ -full_init_block @*/
00124     { "post_date", CT_TIMESPEC, 0, 0, "post-date" },
00125     { NULL }
00126     /*@ +full_init_block @*/
00127 };
00128 
00129 static const GncSqlColumnTableEntry account_guid_col_table[] =
00130 {
00131     /*@ -full_init_block @*/
00132     { "account_guid", CT_ACCOUNTREF, 0, COL_NNUL, "account" },
00133     { NULL }
00134     /*@ +full_init_block @*/
00135 };
00136 
00137 static const GncSqlColumnTableEntry tx_guid_col_table[] =
00138 {
00139     /*@ -full_init_block @*/
00140     { "tx_guid", CT_GUID, 0, 0, "guid" },
00141     { NULL }
00142     /*@ +full_init_block @*/
00143 };
00144 
00145 /* ================================================================= */
00146 
00147 static /*@ dependent @*//*@ null @*/ gpointer
00148 get_split_reconcile_state( gpointer pObject )
00149 {
00150     static gchar c[2];
00151 
00152     g_return_val_if_fail( pObject != NULL, NULL );
00153     g_return_val_if_fail( GNC_IS_SPLIT(pObject), NULL );
00154 
00155     c[0] = xaccSplitGetReconcile( GNC_SPLIT(pObject) );
00156     c[1] = '\0';
00157     return (gpointer)c;
00158 }
00159 
00160 static void
00161 set_split_reconcile_state( gpointer pObject, /*@ null @*/ gpointer pValue )
00162 {
00163     const gchar* s = (const gchar*)pValue;
00164 
00165     g_return_if_fail( pObject != NULL );
00166     g_return_if_fail( GNC_IS_SPLIT(pObject) );
00167     g_return_if_fail( pValue != NULL );
00168 
00169     xaccSplitSetReconcile( GNC_SPLIT(pObject), s[0] );
00170 }
00171 
00172 static void
00173 set_split_reconcile_date( gpointer pObject, Timespec ts )
00174 {
00175     g_return_if_fail( pObject != NULL );
00176     g_return_if_fail( GNC_IS_SPLIT(pObject) );
00177 
00178     xaccSplitSetDateReconciledTS( GNC_SPLIT(pObject), &ts );
00179 }
00180 
00181 static void
00182 set_split_lot( gpointer pObject, /*@ null @*/ gpointer pLot )
00183 {
00184     GNCLot* lot;
00185     Split* split;
00186 
00187     g_return_if_fail( pObject != NULL );
00188     g_return_if_fail( GNC_IS_SPLIT(pObject) );
00189 
00190     if ( pLot == NULL ) return;
00191 
00192     g_return_if_fail( GNC_IS_LOT(pLot) );
00193 
00194     split = GNC_SPLIT(pObject);
00195     lot = GNC_LOT(pLot);
00196     gnc_lot_add_split( lot, split );
00197 }
00198 
00199 static /*@ null @*/ Split*
00200 load_single_split( GncSqlBackend* be, GncSqlRow* row )
00201 {
00202     const GncGUID* guid;
00203     GncGUID split_guid;
00204     Split* pSplit;
00205 
00206     g_return_val_if_fail( be != NULL, NULL );
00207     g_return_val_if_fail( row != NULL, NULL );
00208 
00209     guid = gnc_sql_load_guid( be, row );
00210     if ( guid == NULL ) return NULL;
00211     split_guid = *guid;
00212 
00213     pSplit = xaccSplitLookup( &split_guid, be->book );
00214     if ( pSplit == NULL )
00215     {
00216         pSplit = xaccMallocSplit( be->book );
00217     }
00218 
00219     /* If the split is dirty, don't overwrite it */
00220     if ( !qof_instance_is_dirty( QOF_INSTANCE(pSplit) ) )
00221     {
00222         gnc_sql_load_object( be, row, GNC_ID_SPLIT, pSplit, split_col_table );
00223     }
00224 
00225     /*# -ifempty */g_assert( pSplit == xaccSplitLookup( &split_guid, be->book ) );
00226 
00227     return pSplit;
00228 }
00229 
00230 static void
00231 load_splits_for_tx_list( GncSqlBackend* be, GList* list )
00232 {
00233     GString* sql;
00234     GncSqlResult* result;
00235 
00236     g_return_if_fail( be != NULL );
00237 
00238     if ( list == NULL ) return;
00239 
00240     sql = g_string_sized_new( 40 + (GUID_ENCODING_LENGTH + 3) * g_list_length( list ) );
00241     g_string_append_printf( sql, "SELECT * FROM %s WHERE %s IN (", SPLIT_TABLE, tx_guid_col_table[0].col_name );
00242     (void)gnc_sql_append_guid_list_to_sql( sql, list, G_MAXUINT );
00243     (void)g_string_append( sql, ")" );
00244 
00245     // Execute the query and load the splits
00246     result = gnc_sql_execute_select_sql( be, sql->str );
00247     if ( result != NULL )
00248     {
00249         GList* split_list = NULL;
00250         GncSqlRow* row;
00251 
00252         row = gnc_sql_result_get_first_row( result );
00253         while ( row != NULL )
00254         {
00255             Split* s;
00256             s = load_single_split( be, row );
00257             if ( s != NULL )
00258             {
00259                 split_list = g_list_prepend( split_list, s );
00260             }
00261             row = gnc_sql_result_get_next_row( result );
00262         }
00263 
00264         if ( split_list != NULL )
00265         {
00266             gnc_sql_slots_load_for_list( be, split_list );
00267             g_list_free( split_list );
00268         }
00269 
00270         gnc_sql_result_dispose( result );
00271     }
00272     (void)g_string_free( sql, TRUE );
00273 }
00274 
00275 static /*@ null @*/ Transaction*
00276 load_single_tx( GncSqlBackend* be, GncSqlRow* row )
00277 {
00278     const GncGUID* guid;
00279     GncGUID tx_guid;
00280     Transaction* pTx;
00281 
00282     g_return_val_if_fail( be != NULL, NULL );
00283     g_return_val_if_fail( row != NULL, NULL );
00284 
00285     guid = gnc_sql_load_guid( be, row );
00286     if ( guid == NULL ) return NULL;
00287     tx_guid = *guid;
00288 
00289     // Don't overwrite the transaction if it's already been loaded (and possibly modified).
00290     pTx = xaccTransLookup( &tx_guid, be->book );
00291     if ( pTx != NULL )
00292     {
00293         return NULL;
00294     }
00295 
00296     pTx = xaccMallocTransaction( be->book );
00297     xaccTransBeginEdit( pTx );
00298     gnc_sql_load_object( be, row, GNC_ID_TRANS, pTx, tx_col_table );
00299 
00300     g_assert( pTx == xaccTransLookup( &tx_guid, be->book ) );
00301 
00302     return pTx;
00303 }
00304 
00311 typedef struct
00312 {
00313     /*@ dependent @*/ Account* acc;
00314     gnc_numeric start_bal;
00315     gnc_numeric end_bal;
00316     gnc_numeric start_cleared_bal;
00317     gnc_numeric end_cleared_bal;
00318     gnc_numeric start_reconciled_bal;
00319     gnc_numeric end_reconciled_bal;
00320 } full_acct_balances_t;
00321 
00328 static void
00329 save_account_balances( Account* acc, gpointer pData )
00330 {
00331     GSList** pBal_list = (GSList**)pData;
00332     full_acct_balances_t* newbal;
00333     gnc_numeric* pstart;
00334     gnc_numeric* pend;
00335     gnc_numeric* pstart_c;
00336     gnc_numeric* pend_c;
00337     gnc_numeric* pstart_r;
00338     gnc_numeric* pend_r;
00339 
00340     newbal = g_malloc( (gsize)sizeof( full_acct_balances_t ) );
00341     g_assert( newbal != NULL );
00342 
00343     newbal->acc = acc;
00344     g_object_get( acc,
00345                   "start-balance", &pstart,
00346                   "end-balance", &pend,
00347                   "start-cleared-balance", &pstart_c,
00348                   "end-cleared-balance", &pend_c,
00349                   "start-reconciled-balance", &pstart_r,
00350                   "end-reconciled-balance", &pend_r,
00351                   NULL );
00352     newbal->start_bal = *pstart;
00353     newbal->end_bal = *pend;
00354     newbal->start_cleared_bal = *pstart_c;
00355     newbal->end_cleared_bal = *pend_c;
00356     newbal->start_reconciled_bal = *pstart_r;
00357     newbal->end_reconciled_bal = *pend_r;
00358     *pBal_list = g_slist_append( *pBal_list, newbal );
00359 
00360     g_free( pstart );
00361     g_free( pend );
00362     g_free( pstart_c );
00363     g_free( pend_c );
00364     g_free( pstart_r );
00365     g_free( pend_r );
00366 }
00367 
00375 static void
00376 query_transactions( GncSqlBackend* be, GncSqlStatement* stmt )
00377 {
00378     GncSqlResult* result;
00379 
00380     g_return_if_fail( be != NULL );
00381     g_return_if_fail( stmt != NULL );
00382 
00383     result = gnc_sql_execute_select_statement( be, stmt );
00384     if ( result != NULL )
00385     {
00386         GList* tx_list = NULL;
00387         GList* node;
00388         GncSqlRow* row;
00389         Transaction* tx;
00390         GSList* bal_list = NULL;
00391         GSList* nextbal;
00392         Account* root = gnc_book_get_root_account( be->book );
00393 
00394 #if LOAD_TRANSACTIONS_AS_NEEDED
00395         qof_event_suspend();
00396         xaccAccountBeginEdit( root );
00397 
00398         // Save the start/ending balances (balance, cleared and reconciled) for
00399         // every account.
00400         gnc_account_foreach_descendant( gnc_book_get_root_account( be->primary_book ),
00401                                         save_account_balances,
00402                                         &bal_list );
00403 #endif
00404 
00405         // Load the transactions
00406         row = gnc_sql_result_get_first_row( result );
00407         while ( row != NULL )
00408         {
00409             tx = load_single_tx( be, row );
00410             if ( tx != NULL )
00411             {
00412                 tx_list = g_list_prepend( tx_list, tx );
00413             }
00414             row = gnc_sql_result_get_next_row( result );
00415         }
00416         gnc_sql_result_dispose( result );
00417 
00418         // Load all splits and slots for the transactions
00419         if ( tx_list != NULL )
00420         {
00421             gnc_sql_slots_load_for_list( be, tx_list );
00422             load_splits_for_tx_list( be, tx_list );
00423         }
00424 
00425         // Commit all of the transactions
00426         for ( node = tx_list; node != NULL; node = node->next )
00427         {
00428             Transaction* pTx = GNC_TRANSACTION(node->data);
00429             xaccTransCommitEdit( pTx );
00430         }
00431         g_list_free( tx_list );
00432 
00433 #if LOAD_TRANSACTIONS_AS_NEEDED
00434         // Update the account balances based on the loaded splits.  If the end
00435         // balance has changed, update the start balance so that the end
00436         // balance is the same as it was before the splits were loaded.
00437         // Repeat for cleared and reconciled balances.
00438         for ( nextbal = bal_list; nextbal != NULL; nextbal = nextbal->next )
00439         {
00440             full_acct_balances_t* balns = (full_acct_balances_t*)nextbal->data;
00441             gnc_numeric* pnew_end_bal;
00442             gnc_numeric* pnew_end_c_bal;
00443             gnc_numeric* pnew_end_r_bal;
00444             gnc_numeric adj;
00445 
00446             g_object_get( balns->acc,
00447                           "end-balance", &pnew_end_bal,
00448                           "end-cleared-balance", &pnew_end_c_bal,
00449                           "end-reconciled-balance", &pnew_end_r_bal,
00450                           NULL );
00451 
00452             if ( !gnc_numeric_eq( *pnew_end_bal, balns->end_bal ) )
00453             {
00454                 adj = gnc_numeric_sub( balns->end_bal, *pnew_end_bal,
00455                                        GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
00456                 balns->start_bal = gnc_numeric_add( balns->start_bal, adj,
00457                                                     GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
00458                 g_object_set( balns->acc, "start-balance", &balns->start_bal, NULL );
00459             }
00460             if ( !gnc_numeric_eq( *pnew_end_c_bal, balns->end_cleared_bal ) )
00461             {
00462                 adj = gnc_numeric_sub( balns->end_cleared_bal, *pnew_end_c_bal,
00463                                        GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
00464                 balns->start_cleared_bal = gnc_numeric_add( balns->start_cleared_bal, adj,
00465                                            GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
00466                 g_object_set( balns->acc, "start-cleared-balance", &balns->start_cleared_bal, NULL );
00467             }
00468             if ( !gnc_numeric_eq( *pnew_end_r_bal, balns->end_reconciled_bal ) )
00469             {
00470                 adj = gnc_numeric_sub( balns->end_reconciled_bal, *pnew_end_r_bal,
00471                                        GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
00472                 balns->start_reconciled_bal = gnc_numeric_add( balns->start_reconciled_bal, adj,
00473                                               GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
00474                 g_object_set( balns->acc, "start-reconciled-balance", &balns->start_reconciled_bal, NULL );
00475             }
00476             xaccAccountRecomputeBalance( balns->acc );
00477             g_free( pnew_end_bal );
00478             g_free( pnew_end_c_bal );
00479             g_free( pnew_end_r_bal );
00480             g_free( balns );
00481         }
00482         if ( bal_list != NULL )
00483         {
00484             g_slist_free( bal_list );
00485         }
00486 
00487         xaccAccountCommitEdit( root );
00488         qof_event_resume();
00489 #endif
00490     }
00491 }
00492 
00493 /* ================================================================= */
00499 static void
00500 create_transaction_tables( GncSqlBackend* be )
00501 {
00502     gint version;
00503     gboolean ok;
00504 
00505     g_return_if_fail( be != NULL );
00506 
00507     version = gnc_sql_get_table_version( be, TRANSACTION_TABLE );
00508     if ( version == 0 )
00509     {
00510         (void)gnc_sql_create_table( be, TRANSACTION_TABLE, TX_TABLE_VERSION, tx_col_table );
00511         ok = gnc_sql_create_index( be, "tx_post_date_index", TRANSACTION_TABLE, post_date_col_table );
00512         if ( !ok )
00513         {
00514             PERR( "Unable to create index\n" );
00515         }
00516     }
00517     else if ( version < TX_TABLE_VERSION )
00518     {
00519         /* Upgrade:
00520             1->2: 64 bit int handling
00521                 2->3: allow dates to be NULL
00522         */
00523         gnc_sql_upgrade_table( be, TRANSACTION_TABLE, tx_col_table );
00524         (void)gnc_sql_set_table_version( be, TRANSACTION_TABLE, TX_TABLE_VERSION );
00525         PINFO("Transactions table upgraded from version %d to version %d\n", version, TX_TABLE_VERSION);
00526     }
00527 
00528     version = gnc_sql_get_table_version( be, SPLIT_TABLE );
00529     if ( version == 0 )
00530     {
00531         (void)gnc_sql_create_table( be, SPLIT_TABLE, SPLIT_TABLE_VERSION, split_col_table );
00532         ok = gnc_sql_create_index( be, "splits_tx_guid_index", SPLIT_TABLE, tx_guid_col_table );
00533         if ( !ok )
00534         {
00535             PERR( "Unable to create index\n" );
00536         }
00537         ok = gnc_sql_create_index( be, "splits_account_guid_index", SPLIT_TABLE, account_guid_col_table );
00538         if ( !ok )
00539         {
00540             PERR( "Unable to create index\n" );
00541         }
00542     }
00543     else if ( version < SPLIT_TABLE_VERSION )
00544     {
00545 
00546         /* Upgrade:
00547            1->2: 64 bit int handling
00548            3->4: Split reconcile date can be NULL */
00549         gnc_sql_upgrade_table( be, SPLIT_TABLE, split_col_table );
00550         ok = gnc_sql_create_index( be, "splits_tx_guid_index", SPLIT_TABLE, tx_guid_col_table );
00551         if ( !ok )
00552         {
00553             PERR( "Unable to create index\n" );
00554         }
00555         ok = gnc_sql_create_index( be, "splits_account_guid_index", SPLIT_TABLE, account_guid_col_table );
00556         if ( !ok )
00557         {
00558             PERR( "Unable to create index\n" );
00559         }
00560         (void)gnc_sql_set_table_version( be, SPLIT_TABLE, SPLIT_TABLE_VERSION );
00561         PINFO("Splits table upgraded from version %d to version %d\n", version, SPLIT_TABLE_VERSION);
00562     }
00563 }
00564 /* ================================================================= */
00571 static void
00572 delete_split_slots_cb( gpointer data, gpointer user_data )
00573 {
00574     split_info_t* split_info = (split_info_t*)user_data;
00575     Split* pSplit = GNC_SPLIT(data);
00576 
00577     g_return_if_fail( data != NULL );
00578     g_return_if_fail( GNC_IS_SPLIT(data) );
00579     g_return_if_fail( user_data != NULL );
00580 
00581     if ( split_info->is_ok )
00582     {
00583         split_info->is_ok = gnc_sql_slots_delete( split_info->be,
00584                             qof_instance_get_guid( QOF_INSTANCE(pSplit) ) );
00585     }
00586 }
00587 
00595 static gboolean
00596 delete_splits( GncSqlBackend* be, Transaction* pTx )
00597 {
00598     split_info_t split_info;
00599 
00600     g_return_val_if_fail( be != NULL, FALSE );
00601     g_return_val_if_fail( pTx != NULL, FALSE );
00602 
00603     if ( !gnc_sql_do_db_operation( be, OP_DB_DELETE, SPLIT_TABLE,
00604                                    SPLIT_TABLE, pTx, tx_guid_col_table ) )
00605     {
00606         return FALSE;
00607     }
00608     split_info.be = be;
00609     split_info.is_ok = TRUE;
00610 
00611     g_list_foreach( xaccTransGetSplitList( pTx ), delete_split_slots_cb, &split_info );
00612 
00613     return split_info.is_ok;
00614 }
00615 
00623 static gboolean
00624 commit_split( GncSqlBackend* be, QofInstance* inst )
00625 {
00626     gint op;
00627     gboolean is_infant;
00628     gboolean is_ok;
00629 
00630     g_return_val_if_fail( inst != NULL, FALSE );
00631     g_return_val_if_fail( be != NULL, FALSE );
00632 
00633     is_infant = qof_instance_get_infant( inst );
00634     if ( qof_instance_get_destroying( inst ) )
00635     {
00636         op = OP_DB_DELETE;
00637     }
00638     else if ( be->is_pristine_db || is_infant )
00639     {
00640         op = OP_DB_INSERT;
00641     }
00642     else
00643     {
00644         op = OP_DB_UPDATE;
00645     }
00646     is_ok = gnc_sql_do_db_operation( be, op, SPLIT_TABLE, GNC_ID_SPLIT, inst, split_col_table );
00647     if ( is_ok )
00648     {
00649         is_ok = gnc_sql_slots_save( be,
00650                                     qof_instance_get_guid( inst ),
00651                                     is_infant,
00652                                     qof_instance_get_slots( inst ) );
00653     }
00654 
00655     return is_ok;
00656 }
00657 
00658 static void
00659 save_split_cb( gpointer data, gpointer user_data )
00660 {
00661     split_info_t* split_info = (split_info_t*)user_data;
00662     Split* pSplit = GNC_SPLIT(data);
00663 
00664     g_return_if_fail( data != NULL );
00665     g_return_if_fail( GNC_IS_SPLIT(data) );
00666     g_return_if_fail( user_data != NULL );
00667 
00668     if ( split_info->is_ok )
00669     {
00670         split_info->is_ok = commit_split( split_info->be, QOF_INSTANCE(pSplit) );
00671     }
00672 }
00673 
00674 static gboolean
00675 save_splits( GncSqlBackend* be, const GncGUID* tx_guid, SplitList* pSplitList )
00676 {
00677     split_info_t split_info;
00678 
00679     g_return_val_if_fail( be != NULL, FALSE );
00680     g_return_val_if_fail( tx_guid != NULL, FALSE );
00681     g_return_val_if_fail( pSplitList != NULL, FALSE );
00682 
00683     split_info.be = be;
00684     split_info.guid = tx_guid;
00685     split_info.is_ok = TRUE;
00686     g_list_foreach( pSplitList, save_split_cb, &split_info );
00687 
00688     return split_info.is_ok;
00689 }
00690 
00691 static gboolean
00692 save_transaction( GncSqlBackend* be, Transaction* pTx, gboolean do_save_splits )
00693 {
00694     const GncGUID* guid;
00695     gint op;
00696     gboolean is_infant;
00697     QofInstance* inst;
00698     gboolean is_ok = TRUE;
00699     gchar* err = NULL;
00700 
00701     g_return_val_if_fail( be != NULL, FALSE );
00702     g_return_val_if_fail( pTx != NULL, FALSE );
00703 
00704     inst = QOF_INSTANCE(pTx);
00705     is_infant = qof_instance_get_infant( inst );
00706     if ( qof_instance_get_destroying( inst ) )
00707     {
00708         op = OP_DB_DELETE;
00709     }
00710     else if ( be->is_pristine_db || is_infant )
00711     {
00712         op = OP_DB_INSERT;
00713     }
00714     else
00715     {
00716         op = OP_DB_UPDATE;
00717     }
00718 
00719     if ( op != OP_DB_DELETE )
00720     {
00721         gnc_commodity *commodity = xaccTransGetCurrency( pTx );
00722         // Ensure the commodity is in the db
00723         is_ok = gnc_sql_save_commodity( be, commodity );
00724         if ( ! is_ok )
00725         {
00726             err = "Commodity save failed: Probably an invalid or missing currency";
00727             qof_backend_set_error( &be->be, ERR_BACKEND_DATA_CORRUPT);
00728         }
00729     }
00730 
00731     if ( is_ok )
00732     {
00733         is_ok = gnc_sql_do_db_operation( be, op, TRANSACTION_TABLE, GNC_ID_TRANS, pTx, tx_col_table );
00734         if ( ! is_ok )
00735         {
00736             err = "Transaction header save failed. Check trace log for SQL errors";
00737         }
00738     }
00739 
00740     if ( is_ok )
00741     {
00742         // Commit slots and splits
00743         guid = qof_instance_get_guid( inst );
00744         if ( !qof_instance_get_destroying(inst) )
00745         {
00746             is_ok = gnc_sql_slots_save( be, guid, is_infant, qof_instance_get_slots( inst ) );
00747             if ( ! is_ok )
00748             {
00749                 err = "Slots save failed. Check trace log for SQL errors";
00750             }
00751             if ( is_ok && do_save_splits )
00752             {
00753                 is_ok = save_splits( be, guid, xaccTransGetSplitList( pTx ) );
00754                 if ( ! is_ok )
00755                 {
00756                     err = "Split save failed. Check trace log for SQL errors";
00757                 }
00758             }
00759         }
00760         else
00761         {
00762             is_ok = gnc_sql_slots_delete( be, guid );
00763             if ( ! is_ok )
00764             {
00765                 err = "Slots delete failed. Check trace log for SQL errors";
00766             }
00767             if ( is_ok )
00768             {
00769                 is_ok = delete_splits( be, pTx );
00770                 if ( ! is_ok )
00771                 {
00772                     err = "Split delete failed. Check trace log for SQL errors";
00773                 }
00774             }
00775         }
00776     }
00777     if (! is_ok )
00778     {
00779         gchar *message1 = "Transaction %s dated %s in account %s not saved due to %s.%s";
00780         gchar *message2 = "\nDatabase may be corrupted, check your data carefully.";
00781         Split* split = xaccTransGetSplit( pTx, 0);
00782         Account *acc = xaccSplitGetAccount( split );
00783         /* FIXME: This needs to be implemented
00784         qof_error_format_secondary_text( GTK_MESSAGE_DIALOG( msg ),
00785                                                   message1,
00786                                                  xaccTransGetDescription( pTx ),
00787                                                   qof_print_date( xaccTransGetDate( pTx ) ),
00788                                                   xaccAccountGetName( acc ),
00789                                                   err,
00790                                                   message2 );
00791         */
00792         PERR( "Transaction %s dated %s in account %s not saved due to %s.\n",
00793               xaccTransGetDescription( pTx ),
00794               qof_print_date( xaccTransGetDate( pTx ) ),
00795               xaccAccountGetName( acc ),
00796               err );
00797     }
00798     return is_ok;
00799 }
00800 
00801 gboolean
00802 gnc_sql_save_transaction( GncSqlBackend* be, QofInstance* inst )
00803 {
00804     g_return_val_if_fail( be != NULL, FALSE );
00805     g_return_val_if_fail( inst != NULL, FALSE );
00806     g_return_val_if_fail( GNC_IS_TRANS(inst), FALSE );
00807 
00808     return save_transaction( be, GNC_TRANS(inst), /* do_save_splits */TRUE );
00809 }
00810 
00811 static gboolean
00812 commit_transaction( GncSqlBackend* be, QofInstance* inst )
00813 {
00814     g_return_val_if_fail( be != NULL, FALSE );
00815     g_return_val_if_fail( inst != NULL, FALSE );
00816     g_return_val_if_fail( GNC_IS_TRANS(inst), FALSE );
00817 
00818     return save_transaction( be, GNC_TRANS(inst), /* do_save_splits */FALSE );
00819 }
00820 
00821 /* ================================================================= */
00822 static /*@ dependent @*//*@ null @*/ const GncGUID*
00823 get_guid_from_query( QofQuery* pQuery )
00824 {
00825     GList* pOrTerms;
00826     GList* pAndTerms;
00827     GList* andTerm;
00828     QofQueryTerm* pTerm;
00829     QofQueryPredData* pPredData;
00830     GSList* pParamPath;
00831 
00832     g_return_val_if_fail( pQuery != NULL, NULL );
00833 
00834     pOrTerms = qof_query_get_terms( pQuery );
00835     pAndTerms = (GList*)pOrTerms->data;
00836     andTerm = pAndTerms->next;
00837     pTerm = (QofQueryTerm*)andTerm->data;
00838 
00839     pPredData = qof_query_term_get_pred_data( pTerm );
00840     pParamPath = qof_query_term_get_param_path( pTerm );
00841 
00842     if ( strcmp( pPredData->type_name, "guid" ) == 0 )
00843     {
00844         query_guid_t pData = (query_guid_t)pPredData;
00845         return pData->guids->data;
00846     }
00847     else
00848     {
00849         return NULL;
00850     }
00851 }
00852 
00859 void gnc_sql_transaction_load_tx_for_account( GncSqlBackend* be, Account* account )
00860 {
00861     const GncGUID* guid;
00862     gchar guid_buf[GUID_ENCODING_LENGTH+1];
00863     gchar* query_sql;
00864     GncSqlStatement* stmt;
00865 
00866     g_return_if_fail( be != NULL );
00867     g_return_if_fail( account != NULL );
00868 
00869     guid = qof_instance_get_guid( QOF_INSTANCE(account) );
00870     (void)guid_to_string_buff( guid, guid_buf );
00871     query_sql = g_strdup_printf(
00872                     "SELECT DISTINCT t.* FROM %s AS t, %s AS s WHERE s.tx_guid=t.guid AND s.account_guid ='%s'",
00873                     TRANSACTION_TABLE, SPLIT_TABLE, guid_buf );
00874     stmt = gnc_sql_create_statement_from_sql( be, query_sql );
00875     g_free( query_sql );
00876     if ( stmt != NULL )
00877     {
00878         query_transactions( be, stmt );
00879         gnc_sql_statement_dispose( stmt );
00880     }
00881 }
00882 
00889 void gnc_sql_transaction_load_all_tx( GncSqlBackend* be )
00890 {
00891     gchar* query_sql;
00892     GncSqlStatement* stmt;
00893 
00894     g_return_if_fail( be != NULL );
00895 
00896     query_sql = g_strdup_printf( "SELECT * FROM %s", TRANSACTION_TABLE );
00897     stmt = gnc_sql_create_statement_from_sql( be, query_sql );
00898     g_free( query_sql );
00899     if ( stmt != NULL )
00900     {
00901         query_transactions( be, stmt );
00902         gnc_sql_statement_dispose( stmt );
00903     }
00904 }
00905 
00906 static void
00907 convert_query_comparison_to_sql( QofQueryPredData* pPredData, gboolean isInverted, GString* sql )
00908 {
00909     if ( pPredData->how == QOF_COMPARE_LT
00910             || ( isInverted && pPredData->how == QOF_COMPARE_GTE ) )
00911     {
00912         g_string_append( sql, "<" );
00913     }
00914     else if ( pPredData->how == QOF_COMPARE_LTE
00915               || ( isInverted && pPredData->how == QOF_COMPARE_GT ) )
00916     {
00917         g_string_append( sql, "<=" );
00918     }
00919     else if ( pPredData->how == QOF_COMPARE_EQUAL
00920               || ( isInverted && pPredData->how == QOF_COMPARE_NEQ ) )
00921     {
00922         g_string_append( sql, "=" );
00923     }
00924     else if ( pPredData->how == QOF_COMPARE_GT
00925               || ( isInverted && pPredData->how == QOF_COMPARE_LTE ) )
00926     {
00927         g_string_append( sql, ">" );
00928     }
00929     else if ( pPredData->how == QOF_COMPARE_GTE
00930               || ( isInverted && pPredData->how == QOF_COMPARE_LT ) )
00931     {
00932         g_string_append( sql, ">=" );
00933     }
00934     else if ( pPredData->how == QOF_COMPARE_NEQ
00935               || ( isInverted && pPredData->how == QOF_COMPARE_EQUAL ) )
00936     {
00937         g_string_append( sql, "~=" );
00938     }
00939     else
00940     {
00941         PERR( "Unknown comparison type\n" );
00942         g_string_append( sql, "??" );
00943     }
00944 }
00945 
00946 static void
00947 convert_query_term_to_sql( const GncSqlBackend* be, const gchar* fieldName, QofQueryTerm* pTerm, GString* sql )
00948 {
00949     GSList* pParamPath;
00950     QofQueryPredData* pPredData;
00951     gboolean isInverted;
00952     GSList* name;
00953 
00954     g_return_if_fail( pTerm != NULL );
00955     g_return_if_fail( sql != NULL );
00956 
00957     pParamPath = qof_query_term_get_param_path( pTerm );
00958     pPredData = qof_query_term_get_pred_data( pTerm );
00959     isInverted = qof_query_term_is_inverted( pTerm );
00960 
00961     if ( safe_strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 )
00962     {
00963         query_guid_t guid_data = (query_guid_t)pPredData;
00964         GList* guid_entry;
00965 
00966         g_string_append( sql, "(" );
00967         g_string_append( sql, fieldName );
00968 
00969         switch ( guid_data->options )
00970         {
00971         case QOF_GUID_MATCH_ANY:
00972             if ( isInverted ) g_string_append( sql, " NOT IN (" );
00973             else g_string_append( sql, " IN (" );
00974             break;
00975 
00976         case QOF_GUID_MATCH_NONE:
00977             if ( isInverted ) g_string_append( sql, " IN (" );
00978             else g_string_append( sql, " NOT IN (" );
00979             break;
00980 
00981         default:
00982             PERR( "Unexpected GncGUID match type: %d\n", guid_data->options );
00983         }
00984 
00985         for ( guid_entry = guid_data->guids; guid_entry != NULL; guid_entry = guid_entry->next )
00986         {
00987             gchar guid_buf[GUID_ENCODING_LENGTH+1];
00988 
00989             if ( guid_entry != guid_data->guids ) g_string_append( sql, "," );
00990             (void)guid_to_string_buff( guid_entry->data, guid_buf );
00991             g_string_append_printf( sql, "'%s'", guid_buf );
00992         }
00993         g_string_append( sql, "))" );
00994 
00995     }
00996     else if ( safe_strcmp( pPredData->type_name, QOF_TYPE_CHAR ) == 0 )
00997     {
00998         query_char_t char_data = (query_char_t)pPredData;
00999         int i;
01000 
01001         if ( isInverted )
01002         {
01003             g_string_append( sql, "NOT(" );
01004         }
01005         if ( char_data->options == QOF_CHAR_MATCH_NONE )
01006         {
01007             g_string_append( sql, "NOT " );
01008         }
01009         g_string_append( sql, "(" );
01010         for ( i = 0; char_data->char_list[i] != '\0'; i++ )
01011         {
01012             if ( i != 0 )
01013             {
01014                 g_string_append( sql, " OR " );
01015             }
01016             g_string_append( sql, fieldName );
01017             g_string_append( sql, " = '" );
01018             g_string_append_c( sql, char_data->char_list[i] );
01019             g_string_append( sql, "'" );
01020         }
01021         g_string_append( sql, ") " );
01022         if ( isInverted )
01023         {
01024             g_string_append( sql, ") " );
01025         }
01026 
01027     }
01028     else if ( safe_strcmp( pPredData->type_name, QOF_TYPE_STRING ) == 0 )
01029     {
01030         query_string_t string_data = (query_string_t)pPredData;
01031         sqlEscape* escape = sqlEscape_new();
01032 
01033         if ( isInverted )
01034         {
01035             g_string_append( sql, "NOT(" );
01036         }
01037         if ( pPredData->how == QOF_COMPARE_NEQ )
01038         {
01039             g_string_append( sql, "NOT(" );
01040         }
01041         g_string_append( sql, fieldName );
01042         if ( string_data->is_regex || string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE )
01043         {
01044             PWARN( "String is_regex || option = QOF_STRING_MATCH_INSENSITIVE\n" );
01045         }
01046 //                      g_string_append( sql, " ~" );
01047 //              } else {
01048         g_string_append( sql, " =" );
01049 //              }
01050 //              if( string_data->options == QOF_STRING_MATCH_CASEINSENSITIVE ) {
01051 //                      g_string_append( sql, "*" );
01052 //              }
01053         g_string_append( sql, "'" );
01054         g_string_append( sql, sqlEscapeString( escape, string_data->matchstring ) );
01055         g_string_append( sql, "'" );
01056         if ( pPredData->how == QOF_COMPARE_NEQ )
01057         {
01058             g_string_append( sql, ")" );
01059         }
01060         if ( isInverted )
01061         {
01062             g_string_append( sql, ")" );
01063         }
01064         sqlEscape_destroy( escape );
01065 
01066     }
01067     else
01068     {
01069         g_string_append( sql, "(" );
01070         g_string_append( sql, fieldName );
01071         convert_query_comparison_to_sql( pPredData, isInverted, sql );
01072 
01073         if ( strcmp( pPredData->type_name, QOF_TYPE_NUMERIC ) == 0 )
01074         {
01075             query_numeric_t pData = (query_numeric_t)pPredData;
01076             double d = gnc_numeric_to_double( pData->amount );
01077 
01078             g_string_append_printf( sql, "%f", d );
01079 
01080         }
01081         else if ( safe_strcmp( pPredData->type_name, QOF_TYPE_DATE ) == 0 )
01082         {
01083             query_date_t date_data = (query_date_t)pPredData;
01084             gchar* datebuf;
01085 
01086             datebuf = gnc_sql_convert_timespec_to_string( be, date_data->date );
01087             g_string_append_printf( sql, "'%s'", datebuf );
01088 
01089         }
01090         else if ( strcmp( pPredData->type_name, QOF_TYPE_INT32 ) == 0 )
01091         {
01092             query_int32_t pData = (query_int32_t)pPredData;
01093 
01094             g_string_append_printf( sql, "%d", pData->val );
01095 
01096         }
01097         else if ( strcmp( pPredData->type_name, QOF_TYPE_INT64 ) == 0 )
01098         {
01099             query_int64_t pData = (query_int64_t)pPredData;
01100 
01101             g_string_append_printf( sql, "%" G_GINT64_FORMAT, pData->val );
01102 
01103         }
01104         else if ( strcmp( pPredData->type_name, QOF_TYPE_DOUBLE ) == 0 )
01105         {
01106             query_double_t pData = (query_double_t)pPredData;
01107 
01108             g_string_append_printf( sql, "%f", pData->val );
01109 
01110         }
01111         else if ( strcmp( pPredData->type_name, QOF_TYPE_BOOLEAN ) == 0 )
01112         {
01113             query_boolean_t pData = (query_boolean_t)pPredData;
01114 
01115             g_string_append_printf( sql, "%d", pData->val );
01116 
01117         }
01118         else
01119         {
01120             PERR( "Unknown query predicate type: %s\n", pPredData->type_name );
01121         }
01122 
01123         g_string_append( sql, ")" );
01124     }
01125 }
01126 
01127 typedef struct
01128 {
01129     GncSqlStatement* stmt;
01130     gboolean has_been_run;
01131 } split_query_info_t;
01132 
01133 static /*@ null @*/ gpointer
01134 compile_split_query( GncSqlBackend* be, QofQuery* query )
01135 {
01136     const GncGUID* acct_guid;
01137     gchar guid_buf[GUID_ENCODING_LENGTH+1];
01138     split_query_info_t* query_info = NULL;
01139     gchar* query_sql;
01140 
01141     g_return_val_if_fail( be != NULL, NULL );
01142     g_return_val_if_fail( query != NULL, NULL );
01143 
01144     query_info = g_malloc( (gsize)sizeof(split_query_info_t) );
01145     g_assert( query_info != NULL );
01146     query_info->has_been_run = FALSE;
01147 
01148     if ( qof_query_has_terms( query ) )
01149     {
01150         GList* orterms = qof_query_get_terms( query );
01151         GList* orTerm;
01152         GString* sql = g_string_new( "" );
01153         gboolean need_OR = FALSE;
01154 
01155         for ( orTerm = orterms; orTerm != NULL; orTerm = orTerm->next )
01156         {
01157             GList* andterms = (GList*)orTerm->data;
01158             GList* andTerm;
01159             gboolean need_AND = FALSE;
01160             gboolean has_tx_guid_check = FALSE;
01161 
01162             if ( need_OR )
01163             {
01164                 g_string_append( sql, " OR " );
01165             }
01166             g_string_append( sql, "(" );
01167             for ( andTerm = andterms; andTerm != NULL; andTerm = andTerm->next )
01168             {
01169                 QofQueryTerm* term;
01170                 GSList* paramPath;
01171                 gboolean unknownPath = FALSE;
01172 
01173                 term = (QofQueryTerm*)andTerm->data;
01174                 paramPath = qof_query_term_get_param_path( term );
01175 
01176                 if ( strcmp( paramPath->data, QOF_PARAM_BOOK ) == 0 ) continue;
01177 
01178 #if SIMPLE_QUERY_COMPILATION
01179                 if ( strcmp( paramPath->data, SPLIT_ACCOUNT ) != 0
01180                         || strcmp( paramPath->next->data, QOF_PARAM_GUID ) != 0 ) continue;
01181 #endif
01182 
01183                 if ( need_AND ) g_string_append( sql, " AND " );
01184 
01185                 if ( strcmp( paramPath->data, SPLIT_ACCOUNT ) == 0
01186                         && strcmp( paramPath->next->data, QOF_PARAM_GUID ) == 0 )
01187                 {
01188                     convert_query_term_to_sql( be, "s.account_guid", term, sql );
01189 #if SIMPLE_QUERY_COMPILATION
01190                     goto done_compiling_query;
01191 #endif
01192 
01193                 }
01194                 else if ( strcmp( paramPath->data, SPLIT_RECONCILE ) == 0 )
01195                 {
01196                     convert_query_term_to_sql( be, "s.reconcile_state", term, sql );
01197 
01198                 }
01199                 else if ( strcmp( paramPath->data, SPLIT_TRANS ) == 0 )
01200                 {
01201 #if 0
01202                     if ( !has_tx_guid_check )
01203                     {
01204                         g_string_append( sql, "(splits.tx_guid = transactions.guid) AND " );
01205                         has_tx_guid_check = TRUE;
01206                     }
01207 #endif
01208                     if ( strcmp( paramPath->next->data, TRANS_DATE_POSTED ) == 0 )
01209                     {
01210                         convert_query_term_to_sql( be, "t.post_date", term, sql );
01211                     }
01212                     else if ( strcmp( paramPath->next->data, TRANS_DESCRIPTION ) == 0 )
01213                     {
01214                         convert_query_term_to_sql( be, "t.description", term, sql );
01215                     }
01216                     else
01217                     {
01218                         unknownPath = TRUE;
01219                     }
01220 
01221                 }
01222                 else if ( strcmp( paramPath->data, SPLIT_VALUE ) == 0 )
01223                 {
01224                     convert_query_term_to_sql( be, "s.value_num/s.value_denom", term, sql );
01225 
01226                 }
01227                 else
01228                 {
01229                     unknownPath = TRUE;
01230                 }
01231 
01232                 if ( unknownPath )
01233                 {
01234                     GString* name = g_string_new( (gchar*)paramPath->data );
01235                     while ( paramPath->next != NULL )
01236                     {
01237                         g_string_append( name, "." );
01238                         g_string_append( name, paramPath->next->data );
01239                         paramPath = paramPath->next;
01240                     }
01241                     PERR( "Unknown SPLIT query field: %s\n", name->str );
01242                     g_string_free( name, TRUE );
01243                 }
01244                 need_AND = TRUE;
01245             }
01246 
01247             /* If the last char in the string is a '(', then for some reason, there were
01248                no terms added to the SQL.  If so, remove it and ignore the OR term. */
01249             if ( sql->str[sql->len-1] == '(' )
01250             {
01251                 g_string_truncate( sql, sql->len - 1 );
01252                 need_OR = FALSE;
01253             }
01254             else
01255             {
01256                 g_string_append( sql, ")" );
01257                 need_OR = TRUE;
01258             }
01259         }
01260 
01261 #if SIMPLE_QUERY_COMPILATION
01262 done_compiling_query:
01263 #endif
01264         if ( sql->len != 0 )
01265         {
01266 #if SIMPLE_QUERY_COMPILATION
01267             g_string_append( sql, ")" );
01268 #endif
01269             query_sql = g_strdup_printf(
01270                             "SELECT DISTINCT t.* FROM %s AS t, %s AS s WHERE s.tx_guid=t.guid AND %s",
01271                             TRANSACTION_TABLE, SPLIT_TABLE, sql->str );
01272         }
01273         else
01274         {
01275             query_sql = g_strdup_printf( "SELECT * FROM %s", TRANSACTION_TABLE );
01276         }
01277         query_info->stmt = gnc_sql_create_statement_from_sql( be, query_sql );
01278 
01279         g_string_free( sql, TRUE );
01280         g_free( query_sql );
01281 
01282     }
01283     else
01284     {
01285         query_sql = g_strdup_printf( "SELECT * FROM %s", TRANSACTION_TABLE );
01286         query_info->stmt = gnc_sql_create_statement_from_sql( be, query_sql );
01287         g_free( query_sql );
01288     }
01289 
01290     return query_info;
01291 }
01292 
01293 static void
01294 run_split_query( GncSqlBackend* be, gpointer pQuery )
01295 {
01296     split_query_info_t* query_info = (split_query_info_t*)pQuery;
01297 
01298     g_return_if_fail( be != NULL );
01299     g_return_if_fail( pQuery != NULL );
01300 
01301     if ( !query_info->has_been_run )
01302     {
01303         query_transactions( be, query_info->stmt );
01304         query_info->has_been_run = TRUE;
01305         gnc_sql_statement_dispose( query_info->stmt );
01306         query_info->stmt = NULL;
01307     }
01308 }
01309 
01310 static void
01311 free_split_query( GncSqlBackend* be, gpointer pQuery )
01312 {
01313     g_return_if_fail( be != NULL );
01314     g_return_if_fail( pQuery != NULL );
01315 
01316     g_free( pQuery );
01317 }
01318 
01319 /* ----------------------------------------------------------------- */
01320 typedef struct
01321 {
01322     /*@ dependent @*/ const GncSqlBackend* be;
01323     /*@ dependent @*/
01324     Account* acct;
01325     char reconcile_state;
01326     gnc_numeric balance;
01327 } single_acct_balance_t;
01328 
01329 static void
01330 set_acct_bal_account_from_guid( gpointer pObject, gpointer pValue )
01331 {
01332     single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
01333     const GncGUID* guid = (const GncGUID*)pValue;
01334 
01335     g_return_if_fail( pObject != NULL );
01336     g_return_if_fail( pValue != NULL );
01337 
01338     bal->acct = xaccAccountLookup( guid, bal->be->book );
01339 }
01340 
01341 static void
01342 set_acct_bal_reconcile_state( gpointer pObject, gpointer pValue )
01343 {
01344     single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
01345     const gchar* s = (const gchar*)pValue;
01346 
01347     g_return_if_fail( pObject != NULL );
01348     g_return_if_fail( pValue != NULL );
01349 
01350     bal->reconcile_state = s[0];
01351 }
01352 
01353 static void
01354 set_acct_bal_balance( gpointer pObject, gnc_numeric value )
01355 {
01356     single_acct_balance_t* bal = (single_acct_balance_t*)pObject;
01357 
01358     g_return_if_fail( pObject != NULL );
01359 
01360     bal->balance = value;
01361 }
01362 
01363 static const GncSqlColumnTableEntry acct_balances_col_table[] =
01364 {
01365     /*@ -full_init_block @*/
01366     { "account_guid",    CT_GUID,    0, 0, NULL, NULL, NULL, (QofSetterFunc)set_acct_bal_account_from_guid },
01367     { "reconcile_state", CT_STRING,  1, 0, NULL, NULL, NULL, (QofSetterFunc)set_acct_bal_reconcile_state },
01368     { "quantity",        CT_NUMERIC, 0, 0, NULL, NULL, NULL, (QofSetterFunc)set_acct_bal_balance },
01369     { NULL }
01370     /*@ +full_init_block @*/
01371 };
01372 
01373 static /*@ null @*/ single_acct_balance_t*
01374 load_single_acct_balances( const GncSqlBackend* be, GncSqlRow* row )
01375 {
01376     single_acct_balance_t* bal = NULL;
01377 
01378     g_return_val_if_fail( be != NULL, NULL );
01379     g_return_val_if_fail( row != NULL, NULL );
01380 
01381     bal = g_malloc( (gsize)sizeof(single_acct_balance_t) );
01382     g_assert( bal != NULL );
01383 
01384     bal->be = be;
01385     gnc_sql_load_object( be, row, NULL, bal, acct_balances_col_table );
01386 
01387     return bal;
01388 }
01389 
01390 /*@ null @*/ GSList*
01391 gnc_sql_get_account_balances_slist( GncSqlBackend* be )
01392 {
01393 #if LOAD_TRANSACTIONS_AS_NEEDED
01394     GncSqlResult* result;
01395     GncSqlStatement* stmt;
01396     gchar* buf;
01397     GSList* bal_slist = NULL;
01398 
01399     g_return_val_if_fail( be != NULL, NULL );
01400 
01401     buf = g_strdup_printf( "SELECT account_guid, reconcile_state, sum(quantity_num) as quantity_num, quantity_denom FROM %s GROUP BY account_guid, reconcile_state, quantity_denom ORDER BY account_guid, reconcile_state",
01402                            SPLIT_TABLE );
01403     stmt = gnc_sql_create_statement_from_sql( be, buf );
01404     g_assert( stmt != NULL );
01405     g_free( buf );
01406     result = gnc_sql_execute_select_statement( be, stmt );
01407     gnc_sql_statement_dispose( stmt );
01408     if ( result != NULL )
01409     {
01410         GncSqlRow* row;
01411         acct_balances_t* bal = NULL;
01412 
01413         row = gnc_sql_result_get_first_row( result );
01414         while ( row != NULL )
01415         {
01416             single_acct_balance_t* single_bal;
01417 
01418             // Get the next reconcile state balance and merge with other balances
01419             single_bal = load_single_acct_balances( be, row );
01420             if ( single_bal != NULL )
01421             {
01422                 if ( bal != NULL && bal->acct != single_bal->acct )
01423                 {
01424                     bal->cleared_balance = gnc_numeric_add( bal->cleared_balance, bal->reconciled_balance,
01425                                                             GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
01426                     bal->balance = gnc_numeric_add( bal->balance, bal->cleared_balance,
01427                                                     GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
01428                     bal_slist = g_slist_append( bal_slist, bal );
01429                     bal = NULL;
01430                 }
01431                 if ( bal == NULL )
01432                 {
01433                     bal = g_malloc( (gsize)sizeof(acct_balances_t) );
01434                     g_assert( bal != NULL );
01435 
01436                     bal->acct = single_bal->acct;
01437                     bal->balance = gnc_numeric_zero();
01438                     bal->cleared_balance = gnc_numeric_zero();
01439                     bal->reconciled_balance = gnc_numeric_zero();
01440                 }
01441                 if ( single_bal->reconcile_state == 'n' )
01442                 {
01443                     bal->balance = gnc_numeric_add( bal->balance, single_bal->balance,
01444                                                     GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
01445                 }
01446                 else if ( single_bal->reconcile_state == 'c' )
01447                 {
01448                     bal->cleared_balance = gnc_numeric_add( bal->cleared_balance, single_bal->balance,
01449                                                             GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
01450                 }
01451                 else if ( single_bal->reconcile_state == 'y' )
01452                 {
01453                     bal->reconciled_balance = gnc_numeric_add( bal->reconciled_balance, single_bal->balance,
01454                                               GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
01455                 }
01456                 g_free( single_bal );
01457             }
01458             row = gnc_sql_result_get_next_row( result );
01459         }
01460 
01461         // Add the final balance
01462         if ( bal != NULL )
01463         {
01464             bal->cleared_balance = gnc_numeric_add( bal->cleared_balance, bal->reconciled_balance,
01465                                                     GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
01466             bal->balance = gnc_numeric_add( bal->balance, bal->cleared_balance,
01467                                             GNC_DENOM_AUTO, GNC_HOW_DENOM_LCD );
01468             bal_slist = g_slist_append( bal_slist, bal );
01469         }
01470         gnc_sql_result_dispose( result );
01471     }
01472 
01473     return bal_slist;
01474 #else
01475     return NULL;
01476 #endif
01477 }
01478 
01479 /* ----------------------------------------------------------------- */
01480 static void
01481 load_tx_guid( const GncSqlBackend* be, GncSqlRow* row,
01482               /*@ null @*/ QofSetterFunc setter, gpointer pObject,
01483               const GncSqlColumnTableEntry* table_row )
01484 {
01485     const GValue* val;
01486     GncGUID guid;
01487     Transaction* tx;
01488     const gchar* guid_str;
01489 
01490     g_return_if_fail( be != NULL );
01491     g_return_if_fail( row != NULL );
01492     g_return_if_fail( pObject != NULL );
01493     g_return_if_fail( table_row != NULL );
01494 
01495     val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name );
01496     g_assert( val != NULL );
01497     guid_str = g_value_get_string(val);
01498     if ( guid_str != NULL )
01499     {
01500         (void)string_to_guid( guid_str, &guid );
01501         tx = xaccTransLookup( &guid, be->book );
01502 
01503         // If the transaction is not found, try loading it
01504         if ( tx == NULL )
01505         {
01506             gchar* buf;
01507             GncSqlStatement* stmt;
01508 
01509             buf = g_strdup_printf( "SELECT * FROM %s WHERE guid='%s'",
01510                                    TRANSACTION_TABLE, guid_str );
01511             stmt = gnc_sql_create_statement_from_sql( (GncSqlBackend*)be, buf );
01512             g_free( buf );
01513             query_transactions( (GncSqlBackend*)be, stmt );
01514             tx = xaccTransLookup( &guid, be->book );
01515         }
01516 
01517         if ( tx != NULL )
01518         {
01519             if ( table_row->gobj_param_name != NULL )
01520             {
01521                 g_object_set( pObject, table_row->gobj_param_name, tx, NULL );
01522             }
01523             else
01524             {
01525                 g_return_if_fail( setter != NULL );
01526                 (*setter)( pObject, (const gpointer)tx );
01527             }
01528         }
01529     }
01530 }
01531 
01532 static GncSqlColumnTypeHandler tx_guid_handler
01533 = { load_tx_guid,
01534     gnc_sql_add_objectref_guid_col_info_to_list,
01535     gnc_sql_add_colname_to_list,
01536     gnc_sql_add_gvalue_objectref_guid_to_slist
01537   };
01538 /* ================================================================= */
01539 void
01540 gnc_sql_init_transaction_handler( void )
01541 {
01542     static GncSqlObjectBackend be_data_tx =
01543     {
01544         GNC_SQL_BACKEND_VERSION,
01545         GNC_ID_TRANS,
01546         commit_transaction,          /* commit */
01547 #if LOAD_TRANSACTIONS_AS_NEEDED
01548         NULL,                        /* initial load */
01549 #else
01550         gnc_sql_transaction_load_all_tx,
01551 #endif
01552         create_transaction_tables,   /* create tables */
01553         NULL,                        /* compile_query */
01554         NULL,                        /* run_query */
01555         NULL,                        /* free_query */
01556         NULL                         /* write */
01557     };
01558     static GncSqlObjectBackend be_data_split =
01559     {
01560         GNC_SQL_BACKEND_VERSION,
01561         GNC_ID_SPLIT,
01562         commit_split,                /* commit */
01563         NULL,                        /* initial_load */
01564         NULL,                        /* create tables */
01565 #if LOAD_TRANSACTIONS_AS_NEEDED
01566         compile_split_query,
01567         run_split_query,
01568         free_split_query,
01569 #else
01570         NULL,                        /* compile_query */
01571         NULL,                        /* run_query */
01572         NULL,                        /* free_query */
01573 #endif
01574         NULL                         /* write */
01575     };
01576 
01577     (void)qof_object_register_backend( GNC_ID_TRANS, GNC_SQL_BACKEND, &be_data_tx );
01578     (void)qof_object_register_backend( GNC_ID_SPLIT, GNC_SQL_BACKEND, &be_data_split );
01579 
01580     gnc_sql_register_col_type_handler( CT_TXREF, &tx_guid_handler );
01581 }
01582 
01583 /* ========================== END OF FILE ===================== */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines