|
GnuCash 2.4.99
|
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 ===================== */
1.7.4