|
GnuCash 2.4.99
|
00001 /******************************************************************** 00002 * gnc-backend-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 <stdlib.h> 00030 #include "config.h" 00031 00032 #include <errno.h> 00033 #include <glib.h> 00034 #include <glib/gi18n.h> 00035 #include <glib/gstdio.h> 00036 00037 #include "qof.h" 00038 #include "qofquery-p.h" 00039 #include "qofquerycore-p.h" 00040 #include "Account.h" 00041 #include "TransLog.h" 00042 #include "gnc-engine.h" 00043 #include "SX-book.h" 00044 #include "Recurrence.h" 00045 #include "gncBillTerm.h" 00046 #include "gncTaxTable.h" 00047 #include "gncInvoice.h" 00048 00049 #include "gnc-gconf-utils.h" 00050 00051 #include "gnc-backend-sql.h" 00052 00053 #include "gnc-account-sql.h" 00054 #include "gnc-book-sql.h" 00055 #include "gnc-budget-sql.h" 00056 #include "gnc-commodity-sql.h" 00057 #include "gnc-lots-sql.h" 00058 #include "gnc-price-sql.h" 00059 #include "gnc-pricedb.h" 00060 #include "gnc-recurrence-sql.h" 00061 #include "gnc-schedxaction-sql.h" 00062 #include "gnc-slots-sql.h" 00063 #include "gnc-transaction-sql.h" 00064 00065 #include "gnc-address-sql.h" 00066 #include "gnc-bill-term-sql.h" 00067 #include "gnc-customer-sql.h" 00068 #include "gnc-employee-sql.h" 00069 #include "gnc-entry-sql.h" 00070 #include "gnc-invoice-sql.h" 00071 #include "gnc-job-sql.h" 00072 #include "gnc-order-sql.h" 00073 #include "gnc-owner-sql.h" 00074 #include "gnc-tax-table-sql.h" 00075 #include "gnc-vendor-sql.h" 00076 00077 #include "gnc-main.h" 00078 00079 #if defined( S_SPLINT_S ) 00080 #include "splint-defs.h" 00081 #endif 00082 00083 #if 0 00084 static const gchar* convert_search_obj( QofIdType objType ); 00085 #endif 00086 static void gnc_sql_init_object_handlers( void ); 00087 static void update_progress( GncSqlBackend* be ); 00088 static void finish_progress( GncSqlBackend* be ); 00089 static void register_standard_col_type_handlers( void ); 00090 static gboolean reset_version_info( GncSqlBackend* be ); 00091 /*@ null @*/ 00092 static GncSqlStatement* build_insert_statement( GncSqlBackend* be, 00093 const gchar* table_name, 00094 QofIdTypeConst obj_name, gpointer pObject, 00095 const GncSqlColumnTableEntry* table ); 00096 /*@ null @*/ 00097 static GncSqlStatement* build_update_statement( GncSqlBackend* be, 00098 const gchar* table_name, 00099 QofIdTypeConst obj_name, gpointer pObject, 00100 const GncSqlColumnTableEntry* table ); 00101 /*@ null @*/ 00102 static GncSqlStatement* build_delete_statement( GncSqlBackend* be, 00103 const gchar* table_name, 00104 QofIdTypeConst obj_name, gpointer pObject, 00105 const GncSqlColumnTableEntry* table ); 00106 00107 #define TRANSACTION_NAME "trans" 00108 00109 typedef struct 00110 { 00111 /*@ dependent @*/ QofIdType searchObj; 00112 /*@ dependent @*/ 00113 gpointer pCompiledQuery; 00114 } gnc_sql_query_info; 00115 00116 /* callback structure */ 00117 typedef struct 00118 { 00119 gboolean is_known; 00120 gboolean is_ok; 00121 /*@ dependent @*/ 00122 GncSqlBackend* be; 00123 /*@ dependent @*/ 00124 QofInstance* inst; 00125 /*@ dependent @*/ 00126 QofQuery* pQuery; 00127 /*@ dependent @*/ 00128 gpointer pCompiledQuery; 00129 /*@ owned @*/ 00130 gnc_sql_query_info* pQueryInfo; 00131 } sql_backend; 00132 00133 static QofLogModule log_module = G_LOG_DOMAIN; 00134 00135 #define SQLITE_PROVIDER_NAME "SQLite" 00136 00137 /* ================================================================= */ 00138 00139 void 00140 gnc_sql_init( /*@ unused @*/ GncSqlBackend* be ) 00141 { 00142 static gboolean initialized = FALSE; 00143 00144 if ( !initialized ) 00145 { 00146 register_standard_col_type_handlers(); 00147 gnc_sql_init_object_handlers(); 00148 initialized = TRUE; 00149 } 00150 } 00151 00152 /* ================================================================= */ 00153 00154 static void 00155 create_tables_cb( const gchar* type, gpointer data_p, gpointer be_p ) 00156 { 00157 GncSqlObjectBackend* pData = data_p; 00158 GncSqlBackend* be = be_p; 00159 00160 g_return_if_fail( type != NULL && data_p != NULL && be_p != NULL ); 00161 g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION ); 00162 00163 if ( pData->create_tables != NULL ) 00164 { 00165 update_progress( be ); 00166 (pData->create_tables)( be ); 00167 } 00168 } 00169 00170 /* ================================================================= */ 00171 00172 /* Main object load order */ 00173 static const gchar* fixed_load_order[] = 00174 { GNC_ID_BOOK, GNC_ID_COMMODITY, GNC_ID_ACCOUNT, GNC_ID_LOT, NULL }; 00175 00176 /* Load order for objects from other modules */ 00177 static const gchar** other_load_order = NULL; 00178 00179 void 00180 gnc_sql_set_load_order( const gchar** load_order ) 00181 { 00182 other_load_order = load_order; 00183 } 00184 00185 static void 00186 initial_load_cb( const gchar* type, gpointer data_p, gpointer be_p ) 00187 { 00188 GncSqlObjectBackend* pData = data_p; 00189 GncSqlBackend* be = be_p; 00190 gint i; 00191 00192 g_return_if_fail( type != NULL && data_p != NULL && be_p != NULL ); 00193 g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION ); 00194 00195 // Don't need to load anything if it has already been loaded with the fixed order 00196 for ( i = 0; fixed_load_order[i] != NULL; i++ ) 00197 { 00198 update_progress( be ); 00199 if ( g_ascii_strcasecmp( type, fixed_load_order[i] ) == 0 ) return; 00200 } 00201 if ( other_load_order != NULL ) 00202 { 00203 for ( i = 0; other_load_order[i] != NULL; i++ ) 00204 { 00205 update_progress( be ); 00206 if ( g_ascii_strcasecmp( type, other_load_order[i] ) == 0 ) return; 00207 } 00208 } 00209 00210 if ( pData->initial_load != NULL ) 00211 { 00212 (pData->initial_load)( be ); 00213 } 00214 } 00215 00216 void 00217 gnc_sql_load( GncSqlBackend* be, /*@ dependent @*/ QofBook *book, QofBackendLoadType loadType ) 00218 { 00219 GncSqlObjectBackend* pData; 00220 gint i; 00221 Account* root; 00222 00223 g_return_if_fail( be != NULL ); 00224 g_return_if_fail( book != NULL ); 00225 00226 ENTER( "be=%p, book=%p", be, book ); 00227 00228 be->loading = TRUE; 00229 00230 if ( loadType == LOAD_TYPE_INITIAL_LOAD ) 00231 { 00232 g_assert( be->book == NULL ); 00233 be->book = book; 00234 00235 /* Load any initial stuff. Some of this needs to happen in a certain order */ 00236 for ( i = 0; fixed_load_order[i] != NULL; i++ ) 00237 { 00238 pData = qof_object_lookup_backend( fixed_load_order[i], GNC_SQL_BACKEND ); 00239 if ( pData->initial_load != NULL ) 00240 { 00241 update_progress( be ); 00242 (pData->initial_load)( be ); 00243 } 00244 } 00245 if ( other_load_order != NULL ) 00246 { 00247 for ( i = 0; other_load_order[i] != NULL; i++ ) 00248 { 00249 pData = qof_object_lookup_backend( other_load_order[i], GNC_SQL_BACKEND ); 00250 if ( pData->initial_load != NULL ) 00251 { 00252 update_progress( be ); 00253 (pData->initial_load)( be ); 00254 } 00255 } 00256 } 00257 00258 root = gnc_book_get_root_account( book ); 00259 gnc_account_foreach_descendant( root, (AccountCb)xaccAccountBeginEdit, NULL ); 00260 00261 qof_object_foreach_backend( GNC_SQL_BACKEND, initial_load_cb, be ); 00262 00263 gnc_account_foreach_descendant( root, (AccountCb)xaccAccountCommitEdit, NULL ); 00264 } 00265 else if ( loadType == LOAD_TYPE_LOAD_ALL ) 00266 { 00267 // Load all transactions 00268 gnc_sql_transaction_load_all_tx( be ); 00269 } 00270 00271 be->loading = FALSE; 00272 00273 /* Mark the sessoion as clean -- though it should never be marked 00274 * dirty with this backend 00275 */ 00276 qof_book_mark_session_saved( book ); 00277 finish_progress( be ); 00278 00279 LEAVE( "" ); 00280 } 00281 00282 /* ================================================================= */ 00283 00284 #if 0 00285 static gint 00286 compare_namespaces(gconstpointer a, gconstpointer b) 00287 { 00288 const gchar *sa = (const gchar *) a; 00289 const gchar *sb = (const gchar *) b; 00290 00291 return( safe_strcmp( sa, sb ) ); 00292 } 00293 00294 static gint 00295 compare_commodity_ids(gconstpointer a, gconstpointer b) 00296 { 00297 const gnc_commodity *ca = (const gnc_commodity *) a; 00298 const gnc_commodity *cb = (const gnc_commodity *) b; 00299 00300 return( safe_strcmp( gnc_commodity_get_mnemonic( ca ), 00301 gnc_commodity_get_mnemonic( cb ) ) ); 00302 } 00303 00304 static void 00305 write_commodities( GncSqlBackend* be, QofBook* book ) 00306 { 00307 gnc_commodity_table* tbl; 00308 GList* namespaces; 00309 GList* lp; 00310 00311 g_return_if_fail( be != NULL ); 00312 g_return_if_fail( book != NULL ); 00313 00314 tbl = gnc_commodity_table_get_table( book ); 00315 namespaces = gnc_commodity_table_get_namespaces( tbl ); 00316 if ( namespaces != NULL ) 00317 { 00318 namespaces = g_list_sort( namespaces, compare_namespaces ); 00319 } 00320 for ( lp = namespaces; lp != NULL; lp = lp->next ) 00321 { 00322 GList* comms; 00323 GList* lp2; 00324 00325 comms = gnc_commodity_table_get_commodities( tbl, lp->data ); 00326 comms = g_list_sort( comms, compare_commodity_ids ); 00327 00328 for ( lp2 = comms; lp2 != NULL; lp2 = lp2->next ) 00329 { 00330 gnc_sql_save_commodity( be, GNC_COMMODITY(lp2->data) ); 00331 } 00332 } 00333 update_progress( be ); 00334 } 00335 #endif 00336 00337 static gboolean 00338 write_account_tree( GncSqlBackend* be, Account* root ) 00339 { 00340 GList* descendants; 00341 /*@ dependent @*/ 00342 GList* node; 00343 gboolean is_ok = TRUE; 00344 00345 g_return_val_if_fail( be != NULL, FALSE ); 00346 g_return_val_if_fail( root != NULL, FALSE ); 00347 00348 is_ok = gnc_sql_save_account( be, QOF_INSTANCE(root) ); 00349 if ( is_ok ) 00350 { 00351 descendants = gnc_account_get_descendants( root ); 00352 for ( node = descendants; node != NULL && is_ok; node = g_list_next(node) ) 00353 { 00354 is_ok = gnc_sql_save_account( be, QOF_INSTANCE(GNC_ACCOUNT(node->data)) ); 00355 if ( !is_ok ) break; 00356 } 00357 g_list_free( descendants ); 00358 } 00359 update_progress( be ); 00360 00361 return is_ok; 00362 } 00363 00364 static gboolean 00365 write_accounts( GncSqlBackend* be ) 00366 { 00367 gboolean is_ok; 00368 00369 g_return_val_if_fail( be != NULL, FALSE ); 00370 00371 update_progress( be ); 00372 is_ok = write_account_tree( be, gnc_book_get_root_account( be->book ) ); 00373 if ( is_ok ) 00374 { 00375 update_progress( be ); 00376 is_ok = write_account_tree( be, gnc_book_get_template_root( be->book ) ); 00377 } 00378 00379 return is_ok; 00380 } 00381 00382 static int 00383 write_tx( Transaction* tx, gpointer data ) 00384 { 00385 write_objects_t* s = (write_objects_t*)data; 00386 00387 g_return_val_if_fail( tx != NULL, 0 ); 00388 g_return_val_if_fail( data != NULL, 0 ); 00389 00390 s->is_ok = gnc_sql_save_transaction( s->be, QOF_INSTANCE(tx) ); 00391 update_progress( s->be ); 00392 00393 if ( s->is_ok ) 00394 { 00395 return 0; 00396 } 00397 else 00398 { 00399 return 1; 00400 } 00401 } 00402 00403 static gboolean 00404 write_transactions( GncSqlBackend* be ) 00405 { 00406 write_objects_t data; 00407 00408 g_return_val_if_fail( be != NULL, FALSE ); 00409 00410 data.be = be; 00411 data.is_ok = TRUE; 00412 (void)xaccAccountTreeForEachTransaction( 00413 gnc_book_get_root_account( be->book ), write_tx, &data ); 00414 update_progress( be ); 00415 return data.is_ok; 00416 } 00417 00418 static gboolean 00419 write_template_transactions( GncSqlBackend* be ) 00420 { 00421 Account* ra; 00422 write_objects_t data; 00423 00424 g_return_val_if_fail( be != NULL, FALSE ); 00425 00426 data.is_ok = TRUE; 00427 data.be = be; 00428 ra = gnc_book_get_template_root( be->book ); 00429 if ( gnc_account_n_descendants( ra ) > 0 ) 00430 { 00431 (void)xaccAccountTreeForEachTransaction( ra, write_tx, &data ); 00432 update_progress( be ); 00433 } 00434 00435 return data.is_ok; 00436 } 00437 00438 static gboolean 00439 write_schedXactions( GncSqlBackend* be ) 00440 { 00441 GList* schedXactions; 00442 SchedXaction* tmpSX; 00443 gboolean is_ok = TRUE; 00444 00445 g_return_val_if_fail( be != NULL, FALSE ); 00446 00447 schedXactions = gnc_book_get_schedxactions( be->book )->sx_list; 00448 00449 for ( ; schedXactions != NULL && is_ok; schedXactions = schedXactions->next ) 00450 { 00451 tmpSX = schedXactions->data; 00452 is_ok = gnc_sql_save_schedxaction( be, QOF_INSTANCE( tmpSX ) ); 00453 } 00454 update_progress( be ); 00455 00456 return is_ok; 00457 } 00458 00459 static void 00460 write_cb( const gchar* type, gpointer data_p, gpointer be_p ) 00461 { 00462 GncSqlObjectBackend* pData = data_p; 00463 GncSqlBackend* be = (GncSqlBackend*)be_p; 00464 00465 g_return_if_fail( type != NULL && data_p != NULL && be_p != NULL ); 00466 g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION ); 00467 00468 if ( pData->write != NULL ) 00469 { 00470 (void)(pData->write)( be ); 00471 update_progress( be ); 00472 } 00473 } 00474 00475 static void 00476 update_progress( GncSqlBackend* be ) 00477 { 00478 if ( be->be.percentage != NULL ) 00479 (be->be.percentage)( NULL, 101.0 ); 00480 } 00481 00482 static void 00483 finish_progress( GncSqlBackend* be ) 00484 { 00485 if ( be->be.percentage != NULL ) 00486 (be->be.percentage)( NULL, -1.0 ); 00487 } 00488 00489 void 00490 gnc_sql_sync_all( GncSqlBackend* be, /*@ dependent @*/ QofBook *book ) 00491 { 00492 gboolean is_ok; 00493 00494 g_return_if_fail( be != NULL ); 00495 g_return_if_fail( book != NULL ); 00496 00497 ENTER( "book=%p, be->book=%p", book, be->book ); 00498 update_progress( be ); 00499 (void)reset_version_info( be ); 00500 gnc_sql_set_table_version( be, "Gnucash", gnc_get_long_version() ); 00501 gnc_sql_set_table_version( be, "Gnucash-Resave", GNUCASH_RESAVE_VERSION ); 00502 00503 /* Create new tables */ 00504 be->is_pristine_db = TRUE; 00505 qof_object_foreach_backend( GNC_SQL_BACKEND, create_tables_cb, be ); 00506 00507 /* Save all contents */ 00508 be->book = book; 00509 be->obj_total = 0; 00510 be->obj_total += 1 + gnc_account_n_descendants( gnc_book_get_root_account( book ) ); 00511 be->obj_total += gnc_book_count_transactions( book ); 00512 be->operations_done = 0; 00513 00514 is_ok = gnc_sql_connection_begin_transaction( be->conn ); 00515 00516 // FIXME: should write the set of commodities that are used 00517 //write_commodities( be, book ); 00518 if ( is_ok ) 00519 { 00520 is_ok = gnc_sql_save_book( be, QOF_INSTANCE(book) ); 00521 } 00522 if ( is_ok ) 00523 { 00524 is_ok = write_accounts( be ); 00525 } 00526 if ( is_ok ) 00527 { 00528 is_ok = write_transactions( be ); 00529 } 00530 if ( is_ok ) 00531 { 00532 is_ok = write_template_transactions( be ); 00533 } 00534 if ( is_ok ) 00535 { 00536 is_ok = write_schedXactions( be ); 00537 } 00538 if ( is_ok ) 00539 { 00540 qof_object_foreach_backend( GNC_SQL_BACKEND, write_cb, be ); 00541 } 00542 if ( is_ok ) 00543 { 00544 is_ok = gnc_sql_connection_commit_transaction( be->conn ); 00545 } 00546 if ( is_ok ) 00547 { 00548 be->is_pristine_db = FALSE; 00549 00550 /* Mark the session as clean -- though it shouldn't ever get 00551 * marked dirty with this backend 00552 */ 00553 qof_book_mark_session_saved( book ); 00554 } 00555 else 00556 { 00557 qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_SERVER_ERR ); 00558 is_ok = gnc_sql_connection_rollback_transaction( be->conn ); 00559 } 00560 finish_progress( be ); 00561 LEAVE( "book=%p", book ); 00562 } 00563 00564 /* ================================================================= */ 00565 /* Routines to deal with the creation of multiple books. */ 00566 00567 void 00568 gnc_sql_begin_edit( GncSqlBackend *be, QofInstance *inst ) 00569 { 00570 g_return_if_fail( be != NULL ); 00571 g_return_if_fail( inst != NULL ); 00572 00573 ENTER( " " ); 00574 LEAVE( "" ); 00575 } 00576 00577 void 00578 gnc_sql_rollback_edit( GncSqlBackend *be, QofInstance *inst ) 00579 { 00580 g_return_if_fail( be != NULL ); 00581 g_return_if_fail( inst != NULL ); 00582 00583 ENTER( " " ); 00584 LEAVE( "" ); 00585 } 00586 00587 static void 00588 commit_cb( const gchar* type, gpointer data_p, gpointer be_data_p ) 00589 { 00590 GncSqlObjectBackend* pData = data_p; 00591 sql_backend* be_data = be_data_p; 00592 00593 g_return_if_fail( type != NULL && pData != NULL && be_data != NULL ); 00594 g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION ); 00595 00596 /* If this has already been handled, or is not the correct handler, return */ 00597 if ( strcmp( pData->type_name, be_data->inst->e_type ) != 0 ) return; 00598 if ( be_data->is_known ) return; 00599 00600 if ( pData->commit != NULL ) 00601 { 00602 be_data->is_ok = (pData->commit)( be_data->be, be_data->inst ); 00603 be_data->is_known = TRUE; 00604 } 00605 } 00606 00607 /* Commit_edit handler - find the correct backend handler for this object 00608 * type and call its commit handler 00609 */ 00610 void 00611 gnc_sql_commit_edit( GncSqlBackend *be, QofInstance *inst ) 00612 { 00613 sql_backend be_data; 00614 gboolean is_dirty; 00615 gboolean is_destroying; 00616 gboolean is_infant; 00617 00618 g_return_if_fail( be != NULL ); 00619 g_return_if_fail( inst != NULL ); 00620 00621 if ( qof_book_is_readonly( be->book ) ) 00622 { 00623 qof_backend_set_error( (QofBackend*)be, ERR_BACKEND_READONLY ); 00624 (void)gnc_sql_connection_rollback_transaction( be->conn ); 00625 return; 00626 } 00627 /* During initial load where objects are being created, don't commit 00628 anything, but do mark the object as clean. */ 00629 if ( be->loading ) 00630 { 00631 qof_instance_mark_clean( inst ); 00632 return; 00633 } 00634 00635 // The engine has a PriceDB object but it isn't in the database 00636 if ( strcmp( inst->e_type, "PriceDB" ) == 0 ) 00637 { 00638 qof_instance_mark_clean( inst ); 00639 qof_book_mark_session_saved( be->book ); 00640 return; 00641 } 00642 00643 ENTER( " " ); 00644 00645 is_dirty = qof_instance_get_dirty_flag( inst ); 00646 is_destroying = qof_instance_get_destroying( inst ); 00647 is_infant = qof_instance_get_infant( inst ); 00648 00649 DEBUG( "%s dirty = %d, do_free = %d, infant = %d\n", 00650 (inst->e_type ? inst->e_type : "(null)"), 00651 is_dirty, is_destroying, is_infant ); 00652 00653 if ( !is_dirty && !is_destroying ) 00654 { 00655 LEAVE( "!dirty OR !destroying" ); 00656 return; 00657 } 00658 00659 if ( !gnc_sql_connection_begin_transaction( be->conn ) ) 00660 { 00661 PERR( "gnc_sql_commit_edit(): begin_transaction failed\n" ); 00662 LEAVE( "Rolled back - database transaction begin error" ); 00663 return; 00664 } 00665 00666 be_data.is_known = FALSE; 00667 be_data.be = be; 00668 be_data.inst = inst; 00669 be_data.is_ok = TRUE; 00670 00671 qof_object_foreach_backend( GNC_SQL_BACKEND, commit_cb, &be_data ); 00672 00673 if ( !be_data.is_known ) 00674 { 00675 PERR( "gnc_sql_commit_edit(): Unknown object type '%s'\n", inst->e_type ); 00676 (void)gnc_sql_connection_rollback_transaction( be->conn ); 00677 00678 // Don't let unknown items still mark the book as being dirty 00679 qof_book_mark_session_saved( be->book ); 00680 qof_instance_mark_clean(inst); 00681 LEAVE( "Rolled back - unknown object type" ); 00682 return; 00683 } 00684 if ( !be_data.is_ok ) 00685 { 00686 // Error - roll it back 00687 (void)gnc_sql_connection_rollback_transaction( be->conn ); 00688 00689 // This *should* leave things marked dirty 00690 LEAVE( "Rolled back - database error" ); 00691 return; 00692 } 00693 00694 (void)gnc_sql_connection_commit_transaction( be->conn ); 00695 00696 qof_book_mark_session_saved( be->book ); 00697 qof_instance_mark_clean(inst); 00698 00699 LEAVE( "" ); 00700 } 00701 /* ---------------------------------------------------------------------- */ 00702 00703 /* Query processing */ 00704 static void 00705 handle_and_term( QofQueryTerm* pTerm, GString* sql ) 00706 { 00707 GSList* pParamPath; 00708 QofQueryPredData* pPredData; 00709 gboolean isInverted; 00710 GSList* name; 00711 gchar val[G_ASCII_DTOSTR_BUF_SIZE]; 00712 00713 g_return_if_fail( pTerm != NULL ); 00714 g_return_if_fail( sql != NULL ); 00715 00716 pParamPath = qof_query_term_get_param_path( pTerm ); 00717 pPredData = qof_query_term_get_pred_data( pTerm ); 00718 isInverted = qof_query_term_is_inverted( pTerm ); 00719 00720 if ( strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 ) 00721 { 00722 query_guid_t guid_data = (query_guid_t)pPredData; 00723 GList* guid_entry; 00724 00725 for ( name = pParamPath; name != NULL; name = name->next ) 00726 { 00727 if ( name != pParamPath ) g_string_append( sql, "." ); 00728 g_string_append( sql, name->data ); 00729 } 00730 00731 if ( guid_data->options == QOF_GUID_MATCH_ANY ) 00732 { 00733 if ( isInverted ) g_string_append( sql, " NOT " ); 00734 g_string_append( sql, " IN (" ); 00735 } 00736 for ( guid_entry = guid_data->guids; guid_entry != NULL; guid_entry = guid_entry->next ) 00737 { 00738 if ( guid_entry != guid_data->guids ) g_string_append( sql, "." ); 00739 (void)guid_to_string_buff( guid_entry->data, val ); 00740 g_string_append( sql, "'" ); 00741 g_string_append( sql, val ); 00742 g_string_append( sql, "'" ); 00743 } 00744 if ( guid_data->options == QOF_GUID_MATCH_ANY ) 00745 { 00746 g_string_append( sql, ")" ); 00747 } 00748 } 00749 00750 g_string_append( sql, "(" ); 00751 if ( isInverted ) 00752 { 00753 g_string_append( sql, "!" ); 00754 } 00755 00756 for ( name = pParamPath; name != NULL; name = name->next ) 00757 { 00758 if ( name != pParamPath ) g_string_append( sql, "." ); 00759 g_string_append( sql, name->data ); 00760 } 00761 00762 if ( pPredData->how == QOF_COMPARE_LT ) 00763 { 00764 g_string_append( sql, "<" ); 00765 } 00766 else if ( pPredData->how == QOF_COMPARE_LTE ) 00767 { 00768 g_string_append( sql, "<=" ); 00769 } 00770 else if ( pPredData->how == QOF_COMPARE_EQUAL ) 00771 { 00772 g_string_append( sql, "=" ); 00773 } 00774 else if ( pPredData->how == QOF_COMPARE_GT ) 00775 { 00776 g_string_append( sql, ">" ); 00777 } 00778 else if ( pPredData->how == QOF_COMPARE_GTE ) 00779 { 00780 g_string_append( sql, ">=" ); 00781 } 00782 else if ( pPredData->how == QOF_COMPARE_NEQ ) 00783 { 00784 g_string_append( sql, "~=" ); 00785 } 00786 else 00787 { 00788 g_string_append( sql, "??" ); 00789 } 00790 00791 if ( strcmp( pPredData->type_name, "string" ) == 0 ) 00792 { 00793 query_string_t pData = (query_string_t)pPredData; 00794 g_string_append( sql, "'" ); 00795 g_string_append( sql, pData->matchstring ); 00796 g_string_append( sql, "'" ); 00797 } 00798 else if ( strcmp( pPredData->type_name, "date" ) == 0 ) 00799 { 00800 query_date_t pData = (query_date_t)pPredData; 00801 00802 (void)gnc_timespec_to_iso8601_buff( pData->date, val ); 00803 g_string_append( sql, "'" ); 00804 //g_string_append( sql, val, 4+1+2+1+2 ); 00805 g_string_append( sql, "'" ); 00806 } 00807 else if ( strcmp( pPredData->type_name, "numeric" ) == 0 ) 00808 { 00809 query_numeric_t pData = (query_numeric_t)pPredData; 00810 00811 g_string_append( sql, "numeric" ); 00812 } 00813 else if ( strcmp( pPredData->type_name, QOF_TYPE_GUID ) == 0 ) 00814 { 00815 } 00816 else if ( strcmp( pPredData->type_name, "gint32" ) == 0 ) 00817 { 00818 query_int32_t pData = (query_int32_t)pPredData; 00819 00820 sprintf( val, "%d", pData->val ); 00821 g_string_append( sql, val ); 00822 } 00823 else if ( strcmp( pPredData->type_name, "gint64" ) == 0 ) 00824 { 00825 query_int64_t pData = (query_int64_t)pPredData; 00826 00827 sprintf( val, "%" G_GINT64_FORMAT, pData->val ); 00828 g_string_append( sql, val ); 00829 } 00830 else if ( strcmp( pPredData->type_name, "double" ) == 0 ) 00831 { 00832 query_double_t pData = (query_double_t)pPredData; 00833 00834 g_ascii_dtostr( val, sizeof(val), pData->val ); 00835 g_string_append( sql, val ); 00836 } 00837 else if ( strcmp( pPredData->type_name, "boolean" ) == 0 ) 00838 { 00839 query_boolean_t pData = (query_boolean_t)pPredData; 00840 00841 sprintf( val, "%d", pData->val ); 00842 g_string_append( sql, val ); 00843 } 00844 else 00845 { 00846 g_assert( FALSE ); 00847 } 00848 00849 g_string_append( sql, ")" ); 00850 } 00851 00852 static void 00853 compile_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p ) 00854 { 00855 GncSqlObjectBackend* pData = data_p; 00856 sql_backend* be_data = be_data_p; 00857 00858 g_return_if_fail( type != NULL && pData != NULL && be_data != NULL ); 00859 g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION ); 00860 00861 // Is this the right item? 00862 if ( strcmp( type, be_data->pQueryInfo->searchObj ) != 0 ) return; 00863 if ( be_data->is_ok ) return; 00864 00865 if ( pData->compile_query != NULL ) 00866 { 00867 be_data->pQueryInfo->pCompiledQuery = (pData->compile_query)( 00868 be_data->be, 00869 be_data->pQuery ); 00870 be_data->is_ok = TRUE; 00871 } 00872 } 00873 00874 gchar* gnc_sql_compile_query_to_sql( GncSqlBackend* be, QofQuery* query ); 00875 00876 /*@ null @*/ 00877 gpointer 00878 gnc_sql_compile_query( QofBackend* pBEnd, QofQuery* pQuery ) 00879 { 00880 GncSqlBackend *be = (GncSqlBackend*)pBEnd; 00881 QofIdType searchObj; 00882 sql_backend be_data; 00883 gnc_sql_query_info* pQueryInfo; 00884 00885 g_return_val_if_fail( pBEnd != NULL, NULL ); 00886 g_return_val_if_fail( pQuery != NULL, NULL ); 00887 00888 ENTER( " " ); 00889 00890 //gnc_sql_compile_query_to_sql( be, pQuery ); 00891 searchObj = qof_query_get_search_for( pQuery ); 00892 00893 pQueryInfo = g_malloc( (gsize)sizeof( gnc_sql_query_info ) ); 00894 g_assert( pQueryInfo != NULL ); 00895 pQueryInfo->pCompiledQuery = NULL; 00896 pQueryInfo->searchObj = searchObj; 00897 00898 // Try various objects first 00899 be_data.is_ok = FALSE; 00900 be_data.be = be; 00901 be_data.pQuery = pQuery; 00902 be_data.pQueryInfo = pQueryInfo; 00903 00904 qof_object_foreach_backend( GNC_SQL_BACKEND, compile_query_cb, &be_data ); 00905 if ( be_data.is_ok ) 00906 { 00907 LEAVE( "" ); 00908 return be_data.pQueryInfo; 00909 } 00910 00911 LEAVE( "" ); 00912 00913 return pQueryInfo; 00914 } 00915 00916 static const gchar* 00917 convert_search_obj( QofIdType objType ) 00918 { 00919 return (gchar*)objType; 00920 } 00921 00922 gchar* 00923 gnc_sql_compile_query_to_sql( GncSqlBackend* be, QofQuery* query ) 00924 { 00925 QofIdType searchObj; 00926 GList* bookList; 00927 GString* sql; 00928 00929 g_return_val_if_fail( be != NULL, NULL ); 00930 g_return_val_if_fail( query != NULL, NULL ); 00931 00932 searchObj = qof_query_get_search_for( query ); 00933 bookList = qof_query_get_books( query ); 00934 00935 /* Convert search object type to table name */ 00936 sql = g_string_new( "" ); 00937 g_string_append( sql, "SELECT * FROM " ); 00938 g_string_append( sql, convert_search_obj( searchObj ) ); 00939 if ( !qof_query_has_terms( query ) ) 00940 { 00941 g_string_append( sql, ";" ); 00942 } 00943 else 00944 { 00945 GList* orterms = qof_query_get_terms( query ); 00946 GList* orTerm; 00947 00948 g_string_append( sql, " WHERE " ); 00949 00950 for ( orTerm = orterms; orTerm != NULL; orTerm = orTerm->next ) 00951 { 00952 GList* andterms = (GList*)orTerm->data; 00953 GList* andTerm; 00954 00955 if ( orTerm != orterms ) g_string_append( sql, " OR " ); 00956 g_string_append( sql, "(" ); 00957 for ( andTerm = andterms; andTerm != NULL; andTerm = andTerm->next ) 00958 { 00959 if ( andTerm != andterms ) g_string_append( sql, " AND " ); 00960 handle_and_term( (QofQueryTerm*)andTerm->data, sql ); 00961 } 00962 g_string_append( sql, ")" ); 00963 } 00964 } 00965 00966 DEBUG( "Compiled: %s\n", sql->str ); 00967 return g_string_free( sql, FALSE ); 00968 } 00969 00970 static void 00971 free_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p ) 00972 { 00973 GncSqlObjectBackend* pData = data_p; 00974 sql_backend* be_data = be_data_p; 00975 00976 g_return_if_fail( type != NULL && pData != NULL && be_data != NULL ); 00977 g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION ); 00978 if ( be_data->is_ok ) return; 00979 if ( strcmp( type, be_data->pQueryInfo->searchObj ) != 0 ) return; 00980 00981 if ( pData->free_query != NULL ) 00982 { 00983 (pData->free_query)( be_data->be, be_data->pCompiledQuery ); 00984 be_data->is_ok = TRUE; 00985 } 00986 } 00987 00988 void 00989 gnc_sql_free_query( QofBackend* pBEnd, gpointer pQuery ) 00990 { 00991 GncSqlBackend *be = (GncSqlBackend*)pBEnd; 00992 gnc_sql_query_info* pQueryInfo = (gnc_sql_query_info*)pQuery; 00993 sql_backend be_data; 00994 00995 g_return_if_fail( pBEnd != NULL ); 00996 g_return_if_fail( pQuery != NULL ); 00997 00998 ENTER( " " ); 00999 01000 // Try various objects first 01001 be_data.is_ok = FALSE; 01002 be_data.be = be; 01003 be_data.pCompiledQuery = pQuery; 01004 be_data.pQueryInfo = pQueryInfo; 01005 01006 qof_object_foreach_backend( GNC_SQL_BACKEND, free_query_cb, &be_data ); 01007 if ( be_data.is_ok ) 01008 { 01009 LEAVE( "" ); 01010 return; 01011 } 01012 01013 if ( pQueryInfo->pCompiledQuery != NULL ) 01014 { 01015 DEBUG( "%s\n", (gchar*)pQueryInfo->pCompiledQuery ); 01016 g_free( pQueryInfo->pCompiledQuery ); 01017 } 01018 g_free( pQueryInfo ); 01019 01020 LEAVE( "" ); 01021 } 01022 01023 static void 01024 run_query_cb( const gchar* type, gpointer data_p, gpointer be_data_p ) 01025 { 01026 GncSqlObjectBackend* pData = data_p; 01027 sql_backend* be_data = be_data_p; 01028 01029 g_return_if_fail( type != NULL && pData != NULL && be_data != NULL ); 01030 g_return_if_fail( pData->version == GNC_SQL_BACKEND_VERSION ); 01031 if ( be_data->is_ok ) return; 01032 01033 // Is this the right item? 01034 if ( strcmp( type, be_data->pQueryInfo->searchObj ) != 0 ) return; 01035 01036 if ( pData->run_query != NULL ) 01037 { 01038 (pData->run_query)( be_data->be, be_data->pCompiledQuery ); 01039 be_data->is_ok = TRUE; 01040 } 01041 } 01042 01043 void 01044 gnc_sql_run_query( QofBackend* pBEnd, gpointer pQuery ) 01045 { 01046 GncSqlBackend *be = (GncSqlBackend*)pBEnd; 01047 gnc_sql_query_info* pQueryInfo = (gnc_sql_query_info*)pQuery; 01048 sql_backend be_data; 01049 01050 g_return_if_fail( pBEnd != NULL ); 01051 g_return_if_fail( pQuery != NULL ); 01052 g_return_if_fail( !be->in_query ); 01053 01054 ENTER( " " ); 01055 01056 be->loading = TRUE; 01057 be->in_query = TRUE; 01058 01059 qof_event_suspend(); 01060 01061 // Try various objects first 01062 be_data.is_ok = FALSE; 01063 be_data.be = be; 01064 be_data.pCompiledQuery = pQueryInfo->pCompiledQuery; 01065 be_data.pQueryInfo = pQueryInfo; 01066 01067 qof_object_foreach_backend( GNC_SQL_BACKEND, run_query_cb, &be_data ); 01068 be->loading = FALSE; 01069 be->in_query = FALSE; 01070 qof_event_resume(); 01071 // if( be_data.is_ok ) { 01072 // LEAVE( "" ); 01073 // return; 01074 // } 01075 01076 // Mark the book as clean 01077 qof_instance_mark_clean( QOF_INSTANCE(be->book) ); 01078 01079 // DEBUG( "%s\n", (gchar*)pQueryInfo->pCompiledQuery ); 01080 01081 LEAVE( "" ); 01082 } 01083 01084 /* ================================================================= */ 01085 /* Order in which business objects need to be loaded */ 01086 static const gchar* business_fixed_load_order[] = 01087 { GNC_ID_BILLTERM, GNC_ID_TAXTABLE, GNC_ID_INVOICE, NULL }; 01088 01089 static void 01090 business_core_sql_init(void) 01091 { 01092 /* Initialize our pointers into the backend subsystem */ 01093 gnc_address_sql_initialize(); 01094 gnc_billterm_sql_initialize(); 01095 gnc_customer_sql_initialize(); 01096 gnc_employee_sql_initialize(); 01097 gnc_entry_sql_initialize(); 01098 gnc_invoice_sql_initialize(); 01099 gnc_job_sql_initialize(); 01100 gnc_order_sql_initialize(); 01101 gnc_owner_sql_initialize(); 01102 gnc_taxtable_sql_initialize(); 01103 gnc_vendor_sql_initialize(); 01104 01105 gnc_sql_set_load_order( business_fixed_load_order ); 01106 } 01107 01108 static void 01109 gnc_sql_init_object_handlers( void ) 01110 { 01111 gnc_sql_init_book_handler(); 01112 gnc_sql_init_commodity_handler(); 01113 gnc_sql_init_account_handler(); 01114 gnc_sql_init_budget_handler(); 01115 gnc_sql_init_price_handler(); 01116 gnc_sql_init_transaction_handler(); 01117 gnc_sql_init_slots_handler(); 01118 gnc_sql_init_recurrence_handler(); 01119 gnc_sql_init_schedxaction_handler(); 01120 gnc_sql_init_lot_handler(); 01121 01122 /* And the business objects */ 01123 business_core_sql_init(); 01124 } 01125 01126 /* ================================================================= */ 01127 01128 gint64 01129 gnc_sql_get_integer_value( const GValue* value ) 01130 { 01131 g_return_val_if_fail( value != NULL, 0 ); 01132 01133 if ( G_VALUE_HOLDS_INT(value) ) 01134 { 01135 return (gint64)g_value_get_int( value ); 01136 } 01137 else if ( G_VALUE_HOLDS_UINT(value) ) 01138 { 01139 return (gint64)g_value_get_uint( value ); 01140 } 01141 else if ( G_VALUE_HOLDS_LONG(value) ) 01142 { 01143 return (gint64)g_value_get_long( value ); 01144 } 01145 else if ( G_VALUE_HOLDS_ULONG(value) ) 01146 { 01147 return (gint64)g_value_get_ulong( value ); 01148 } 01149 else if ( G_VALUE_HOLDS_INT64(value) ) 01150 { 01151 return g_value_get_int64( value ); 01152 } 01153 else if ( G_VALUE_HOLDS_UINT64(value) ) 01154 { 01155 return (gint64)g_value_get_uint64( value ); 01156 } 01157 else if ( G_VALUE_HOLDS_STRING( value ) ) 01158 { 01159 return g_ascii_strtoll( g_value_get_string( value ), NULL, 10 ); 01160 } 01161 else 01162 { 01163 PWARN( "Unknown type: %s", G_VALUE_TYPE_NAME( value ) ); 01164 } 01165 01166 return 0; 01167 } 01168 01169 /* ----------------------------------------------------------------- */ 01170 /*@ null @*/ static gpointer 01171 get_autoinc_id() 01172 { 01173 // Just need a 0 to force a new autoinc value 01174 return (gpointer)0; 01175 } 01176 01177 static void 01178 set_autoinc_id() 01179 { 01180 // Nowhere to put the ID 01181 } 01182 01183 /*@ null @*/ QofAccessFunc 01184 gnc_sql_get_getter( QofIdTypeConst obj_name, const GncSqlColumnTableEntry* table_row ) 01185 { 01186 QofAccessFunc getter; 01187 01188 g_return_val_if_fail( obj_name != NULL, NULL ); 01189 g_return_val_if_fail( table_row != NULL, NULL ); 01190 01191 if ( (table_row->flags & COL_AUTOINC) != 0 ) 01192 { 01193 getter = get_autoinc_id; 01194 } 01195 else if ( table_row->qof_param_name != NULL ) 01196 { 01197 getter = qof_class_get_parameter_getter( obj_name, 01198 table_row->qof_param_name ); 01199 } 01200 else 01201 { 01202 getter = table_row->getter; 01203 } 01204 01205 return getter; 01206 } 01207 01208 /* ----------------------------------------------------------------- */ 01209 void 01210 gnc_sql_add_colname_to_list( const GncSqlColumnTableEntry* table_row, GList** pList ) 01211 { 01212 (*pList) = g_list_append( (*pList), g_strdup( table_row->col_name ) ); 01213 } 01214 01215 /* ----------------------------------------------------------------- */ 01216 void 01217 gnc_sql_add_subtable_colnames_to_list( const GncSqlColumnTableEntry* table_row, const GncSqlColumnTableEntry* subtable, 01218 GList** pList ) 01219 { 01220 const GncSqlColumnTableEntry* subtable_row; 01221 gchar* buf; 01222 01223 for ( subtable_row = subtable; subtable_row->col_name != NULL; subtable_row++ ) 01224 { 01225 buf = g_strdup_printf( "%s_%s", table_row->col_name, subtable_row->col_name ); 01226 (*pList) = g_list_append( (*pList), buf ); 01227 } 01228 } 01229 01230 static GncSqlColumnInfo* 01231 create_column_info( const GncSqlColumnTableEntry* table_row, GncSqlBasicColumnType type, 01232 gint size, gboolean is_unicode ) 01233 { 01234 GncSqlColumnInfo* info; 01235 01236 info = g_new0( GncSqlColumnInfo, 1 ); 01237 g_assert( info != NULL ); 01238 info->name = g_strdup( table_row->col_name ); 01239 info->type = type; 01240 info->size = size; 01241 info->is_primary_key = ((table_row->flags & COL_PKEY) != 0) ? TRUE : FALSE; 01242 info->null_allowed = ((table_row->flags & COL_NNUL) != 0) ? FALSE : TRUE; 01243 info->is_unicode = is_unicode; 01244 info->is_autoinc = ((table_row->flags & COL_AUTOINC) != 0) ? TRUE : FALSE; 01245 01246 return info; 01247 } 01248 01249 /* ----------------------------------------------------------------- */ 01250 static void 01251 load_string( const GncSqlBackend* be, GncSqlRow* row, 01252 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 01253 const GncSqlColumnTableEntry* table_row ) 01254 { 01255 const GValue* val; 01256 const gchar* s; 01257 01258 g_return_if_fail( be != NULL ); 01259 g_return_if_fail( row != NULL ); 01260 g_return_if_fail( pObject != NULL ); 01261 g_return_if_fail( table_row != NULL ); 01262 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 01263 01264 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 01265 g_return_if_fail( val != NULL ); 01266 s = g_value_get_string( val ); 01267 if ( table_row->gobj_param_name != NULL ) 01268 { 01269 g_object_set( pObject, table_row->gobj_param_name, s, NULL ); 01270 } 01271 else 01272 { 01273 g_return_if_fail( setter != NULL ); 01274 (*setter)( pObject, (const gpointer)s ); 01275 } 01276 } 01277 01278 static void 01279 add_string_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 01280 GList** pList ) 01281 { 01282 GncSqlColumnInfo* info; 01283 01284 g_return_if_fail( be != NULL ); 01285 g_return_if_fail( table_row != NULL ); 01286 g_return_if_fail( pList != NULL ); 01287 01288 info = create_column_info( table_row, BCT_STRING, table_row->size, TRUE ); 01289 01290 *pList = g_list_append( *pList, info ); 01291 } 01292 01293 static void 01294 add_gvalue_string_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 01295 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 01296 { 01297 QofAccessFunc getter; 01298 gchar* s = NULL; 01299 GValue* value; 01300 01301 g_return_if_fail( be != NULL ); 01302 g_return_if_fail( obj_name != NULL ); 01303 g_return_if_fail( pObject != NULL ); 01304 g_return_if_fail( table_row != NULL ); 01305 g_return_if_fail( pList != NULL ); 01306 01307 value = g_new0( GValue, 1 ); 01308 g_assert( value != NULL ); 01309 memset( value, 0, sizeof( GValue ) ); 01310 if ( table_row->gobj_param_name != NULL ) 01311 { 01312 g_object_get( pObject, table_row->gobj_param_name, &s, NULL ); 01313 } 01314 else 01315 { 01316 getter = gnc_sql_get_getter( obj_name, table_row ); 01317 if ( getter != NULL ) 01318 { 01319 s = (gchar*)(*getter)( pObject, NULL ); 01320 if ( s != NULL ) 01321 { 01322 s = g_strdup( s ); 01323 } 01324 } 01325 } 01326 (void)g_value_init( value, G_TYPE_STRING ); 01327 if ( s ) 01328 { 01329 g_value_take_string( value, s ); 01330 } 01331 01332 (*pList) = g_slist_append( (*pList), value ); 01333 } 01334 01335 static GncSqlColumnTypeHandler string_handler 01336 = 01337 { 01338 load_string, 01339 add_string_col_info_to_list, 01340 gnc_sql_add_colname_to_list, 01341 add_gvalue_string_to_slist 01342 }; 01343 /* ----------------------------------------------------------------- */ 01344 typedef gint (*IntAccessFunc)( const gpointer ); 01345 typedef void (*IntSetterFunc)( const gpointer, gint ); 01346 01347 static void 01348 load_int( const GncSqlBackend* be, GncSqlRow* row, 01349 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 01350 const GncSqlColumnTableEntry* table_row ) 01351 { 01352 const GValue* val; 01353 gint int_value; 01354 IntSetterFunc i_setter; 01355 01356 g_return_if_fail( be != NULL ); 01357 g_return_if_fail( row != NULL ); 01358 g_return_if_fail( pObject != NULL ); 01359 g_return_if_fail( table_row != NULL ); 01360 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 01361 01362 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 01363 if ( val == NULL ) 01364 { 01365 int_value = 0; 01366 } 01367 else 01368 { 01369 int_value = (gint)gnc_sql_get_integer_value( val ); 01370 } 01371 if ( table_row->gobj_param_name != NULL ) 01372 { 01373 g_object_set( pObject, table_row->gobj_param_name, int_value, NULL ); 01374 } 01375 else 01376 { 01377 g_return_if_fail( setter != NULL ); 01378 i_setter = (IntSetterFunc)setter; 01379 (*i_setter)( pObject, int_value ); 01380 } 01381 } 01382 01383 static void 01384 add_int_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 01385 GList** pList ) 01386 { 01387 GncSqlColumnInfo* info; 01388 01389 g_return_if_fail( be != NULL ); 01390 g_return_if_fail( table_row != NULL ); 01391 g_return_if_fail( pList != NULL ); 01392 01393 info = create_column_info( table_row, BCT_INT, 0, FALSE ); 01394 01395 *pList = g_list_append( *pList, info ); 01396 } 01397 01398 static void 01399 add_gvalue_int_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 01400 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 01401 { 01402 gint int_value = 0; 01403 IntAccessFunc i_getter; 01404 GValue* value; 01405 01406 g_return_if_fail( be != NULL ); 01407 g_return_if_fail( obj_name != NULL ); 01408 g_return_if_fail( pObject != NULL ); 01409 g_return_if_fail( table_row != NULL ); 01410 g_return_if_fail( pList != NULL ); 01411 01412 value = g_new0( GValue, 1 ); 01413 g_assert( value != NULL ); 01414 (void)g_value_init( value, G_TYPE_INT ); 01415 01416 if ( table_row->gobj_param_name != NULL ) 01417 { 01418 g_object_get_property( pObject, table_row->gobj_param_name, value ); 01419 } 01420 else 01421 { 01422 i_getter = (IntAccessFunc)gnc_sql_get_getter( obj_name, table_row ); 01423 if ( i_getter != NULL ) 01424 { 01425 int_value = (*i_getter)( pObject ); 01426 } 01427 g_value_set_int( value, int_value ); 01428 } 01429 01430 (*pList) = g_slist_append( (*pList), value ); 01431 } 01432 01433 static GncSqlColumnTypeHandler int_handler 01434 = 01435 { 01436 load_int, 01437 add_int_col_info_to_list, 01438 gnc_sql_add_colname_to_list, 01439 add_gvalue_int_to_slist 01440 }; 01441 /* ----------------------------------------------------------------- */ 01442 typedef gboolean (*BooleanAccessFunc)( const gpointer ); 01443 typedef void (*BooleanSetterFunc)( const gpointer, gboolean ); 01444 01445 static void 01446 load_boolean( const GncSqlBackend* be, GncSqlRow* row, 01447 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 01448 const GncSqlColumnTableEntry* table_row ) 01449 { 01450 const GValue* val; 01451 gint int_value; 01452 BooleanSetterFunc b_setter; 01453 01454 g_return_if_fail( be != NULL ); 01455 g_return_if_fail( row != NULL ); 01456 g_return_if_fail( pObject != NULL ); 01457 g_return_if_fail( table_row != NULL ); 01458 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 01459 01460 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 01461 if ( val == NULL ) 01462 { 01463 int_value = 0; 01464 } 01465 else 01466 { 01467 int_value = (gint)gnc_sql_get_integer_value( val ); 01468 } 01469 if ( table_row->gobj_param_name != NULL ) 01470 { 01471 g_object_set( pObject, table_row->gobj_param_name, int_value, NULL ); 01472 } 01473 else 01474 { 01475 g_return_if_fail( setter != NULL ); 01476 b_setter = (BooleanSetterFunc)setter; 01477 (*b_setter)( pObject, (int_value != 0) ? TRUE : FALSE ); 01478 } 01479 } 01480 01481 static void 01482 add_boolean_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 01483 GList** pList ) 01484 { 01485 GncSqlColumnInfo* info; 01486 01487 g_return_if_fail( be != NULL ); 01488 g_return_if_fail( table_row != NULL ); 01489 g_return_if_fail( pList != NULL ); 01490 01491 info = create_column_info( table_row, BCT_INT, 0, FALSE ); 01492 01493 *pList = g_list_append( *pList, info ); 01494 } 01495 01496 static void 01497 add_gvalue_boolean_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 01498 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 01499 { 01500 gint int_value = 0; 01501 BooleanAccessFunc b_getter; 01502 GValue* value; 01503 01504 g_return_if_fail( be != NULL ); 01505 g_return_if_fail( obj_name != NULL ); 01506 g_return_if_fail( pObject != NULL ); 01507 g_return_if_fail( table_row != NULL ); 01508 g_return_if_fail( pList != NULL ); 01509 01510 value = g_new0( GValue, 1 ); 01511 g_assert( value != NULL ); 01512 01513 if ( table_row->gobj_param_name != NULL ) 01514 { 01515 g_object_get( pObject, table_row->gobj_param_name, &int_value, NULL ); 01516 } 01517 else 01518 { 01519 b_getter = (BooleanAccessFunc)gnc_sql_get_getter( obj_name, table_row ); 01520 if ( b_getter != NULL ) 01521 { 01522 int_value = ((*b_getter)( pObject )) ? 1 : 0; 01523 } 01524 } 01525 (void)g_value_init( value, G_TYPE_INT ); 01526 g_value_set_int( value, int_value ); 01527 01528 (*pList) = g_slist_append( (*pList), value ); 01529 } 01530 01531 static GncSqlColumnTypeHandler boolean_handler 01532 = 01533 { 01534 load_boolean, 01535 add_boolean_col_info_to_list, 01536 gnc_sql_add_colname_to_list, 01537 add_gvalue_boolean_to_slist 01538 }; 01539 /* ----------------------------------------------------------------- */ 01540 typedef gint64 (*Int64AccessFunc)( const gpointer ); 01541 typedef void (*Int64SetterFunc)( const gpointer, gint64 ); 01542 01543 static void 01544 load_int64( const GncSqlBackend* be, GncSqlRow* row, 01545 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 01546 const GncSqlColumnTableEntry* table_row ) 01547 { 01548 const GValue* val; 01549 gint64 i64_value = 0; 01550 Int64SetterFunc i64_setter = (Int64SetterFunc)setter; 01551 01552 g_return_if_fail( be != NULL ); 01553 g_return_if_fail( row != NULL ); 01554 g_return_if_fail( table_row != NULL ); 01555 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 01556 01557 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 01558 if ( val != NULL ) 01559 { 01560 i64_value = gnc_sql_get_integer_value( val ); 01561 } 01562 if ( table_row->gobj_param_name != NULL ) 01563 { 01564 g_object_set( pObject, table_row->gobj_param_name, i64_value, NULL ); 01565 } 01566 else 01567 { 01568 (*i64_setter)( pObject, i64_value ); 01569 } 01570 } 01571 01572 static void 01573 add_int64_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 01574 GList** pList ) 01575 { 01576 GncSqlColumnInfo* info; 01577 01578 g_return_if_fail( be != NULL ); 01579 g_return_if_fail( table_row != NULL ); 01580 g_return_if_fail( pList != NULL ); 01581 01582 info = create_column_info( table_row, BCT_INT64, 0, FALSE ); 01583 01584 *pList = g_list_append( *pList, info ); 01585 } 01586 01587 static void 01588 add_gvalue_int64_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 01589 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 01590 { 01591 gint64 i64_value = 0; 01592 Int64AccessFunc getter; 01593 GValue* value; 01594 01595 g_return_if_fail( be != NULL ); 01596 g_return_if_fail( obj_name != NULL ); 01597 g_return_if_fail( pObject != NULL ); 01598 g_return_if_fail( table_row != NULL ); 01599 g_return_if_fail( pList != NULL ); 01600 01601 value = g_new0( GValue, 1 ); 01602 g_assert( value != NULL ); 01603 if ( table_row->gobj_param_name != NULL ) 01604 { 01605 g_object_get( pObject, table_row->gobj_param_name, &i64_value, NULL ); 01606 } 01607 else 01608 { 01609 getter = (Int64AccessFunc)gnc_sql_get_getter( obj_name, table_row ); 01610 if ( getter != NULL ) 01611 { 01612 i64_value = (*getter)( pObject ); 01613 } 01614 } 01615 (void)g_value_init( value, G_TYPE_INT64 ); 01616 g_value_set_int64( value, i64_value ); 01617 01618 (*pList) = g_slist_append( (*pList), value ); 01619 } 01620 01621 static GncSqlColumnTypeHandler int64_handler 01622 = 01623 { 01624 load_int64, 01625 add_int64_col_info_to_list, 01626 gnc_sql_add_colname_to_list, 01627 add_gvalue_int64_to_slist 01628 }; 01629 /* ----------------------------------------------------------------- */ 01630 01631 static void 01632 load_double( const GncSqlBackend* be, GncSqlRow* row, 01633 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 01634 const GncSqlColumnTableEntry* table_row ) 01635 { 01636 const GValue* val; 01637 gdouble d_value; 01638 01639 g_return_if_fail( be != NULL ); 01640 g_return_if_fail( row != NULL ); 01641 g_return_if_fail( pObject != NULL ); 01642 g_return_if_fail( table_row != NULL ); 01643 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 01644 01645 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 01646 if ( val == NULL ) 01647 { 01648 (*setter)( pObject, (gpointer)NULL ); 01649 } 01650 else 01651 { 01652 if ( G_VALUE_HOLDS(val, G_TYPE_INT) ) 01653 { 01654 d_value = (gdouble)g_value_get_int( val ); 01655 } 01656 else if ( G_VALUE_HOLDS(val, G_TYPE_FLOAT) ) 01657 { 01658 d_value = g_value_get_float( val ); 01659 } 01660 else if (G_VALUE_HOLDS(val, G_TYPE_DOUBLE) ) 01661 { 01662 d_value = g_value_get_double( val ); 01663 } 01664 else 01665 { 01666 PWARN( "Unknown float value type: %s\n", g_type_name( G_VALUE_TYPE(val) ) ); 01667 d_value = 0; 01668 } 01669 if ( table_row->gobj_param_name != NULL ) 01670 { 01671 g_object_set( pObject, table_row->gobj_param_name, d_value, NULL ); 01672 } 01673 else 01674 { 01675 (*setter)( pObject, (gpointer)&d_value ); 01676 } 01677 } 01678 } 01679 01680 static void 01681 add_double_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 01682 GList** pList ) 01683 { 01684 GncSqlColumnInfo* info; 01685 01686 g_return_if_fail( be != NULL ); 01687 g_return_if_fail( table_row != NULL ); 01688 g_return_if_fail( pList != NULL ); 01689 01690 info = create_column_info( table_row, BCT_DOUBLE, 0, FALSE ); 01691 01692 *pList = g_list_append( *pList, info ); 01693 } 01694 01695 static void 01696 add_gvalue_double_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 01697 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 01698 { 01699 QofAccessFunc getter; 01700 gdouble* pDouble = NULL; 01701 gdouble d_value; 01702 GValue* value; 01703 01704 g_return_if_fail( be != NULL ); 01705 g_return_if_fail( obj_name != NULL ); 01706 g_return_if_fail( pObject != NULL ); 01707 g_return_if_fail( table_row != NULL ); 01708 01709 value = g_new0( GValue, 1 ); 01710 g_assert( value != NULL ); 01711 getter = gnc_sql_get_getter( obj_name, table_row ); 01712 if ( getter != NULL ) 01713 { 01714 pDouble = (*getter)( pObject, NULL ); 01715 } 01716 if ( pDouble != NULL ) 01717 { 01718 d_value = *pDouble; 01719 (void)g_value_init( value, G_TYPE_DOUBLE ); 01720 g_value_set_double( value, d_value ); 01721 } 01722 else 01723 { 01724 (void)g_value_init( value, G_TYPE_DOUBLE ); 01725 g_value_set_double( value, 0.0 ); 01726 } 01727 01728 (*pList) = g_slist_append( (*pList), value ); 01729 } 01730 01731 static GncSqlColumnTypeHandler double_handler 01732 = 01733 { 01734 load_double, 01735 add_double_col_info_to_list, 01736 gnc_sql_add_colname_to_list, 01737 add_gvalue_double_to_slist 01738 }; 01739 /* ----------------------------------------------------------------- */ 01740 01741 static void 01742 load_guid( const GncSqlBackend* be, GncSqlRow* row, 01743 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 01744 const GncSqlColumnTableEntry* table_row ) 01745 { 01746 const GValue* val; 01747 GncGUID guid; 01748 const GncGUID* pGuid; 01749 01750 g_return_if_fail( be != NULL ); 01751 g_return_if_fail( row != NULL ); 01752 g_return_if_fail( pObject != NULL ); 01753 g_return_if_fail( table_row != NULL ); 01754 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 01755 01756 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 01757 if ( val == NULL || g_value_get_string( val ) == NULL ) 01758 { 01759 pGuid = NULL; 01760 } 01761 else 01762 { 01763 (void)string_to_guid( g_value_get_string( val ), &guid ); 01764 pGuid = &guid; 01765 } 01766 if ( pGuid != NULL ) 01767 { 01768 if ( table_row->gobj_param_name != NULL ) 01769 { 01770 g_object_set( pObject, table_row->gobj_param_name, pGuid, NULL ); 01771 } 01772 else 01773 { 01774 g_return_if_fail( setter != NULL ); 01775 (*setter)( pObject, (const gpointer)pGuid ); 01776 } 01777 } 01778 } 01779 01780 static void 01781 add_guid_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 01782 GList** pList ) 01783 { 01784 GncSqlColumnInfo* info; 01785 01786 g_return_if_fail( be != NULL ); 01787 g_return_if_fail( table_row != NULL ); 01788 g_return_if_fail( pList != NULL ); 01789 01790 info = create_column_info( table_row, BCT_STRING, GUID_ENCODING_LENGTH, FALSE ); 01791 01792 *pList = g_list_append( *pList, info ); 01793 } 01794 01795 static void 01796 add_gvalue_guid_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 01797 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 01798 { 01799 QofAccessFunc getter; 01800 GncGUID* guid = NULL; 01801 gchar guid_buf[GUID_ENCODING_LENGTH+1]; 01802 GValue* value; 01803 gboolean free_guid = FALSE; 01804 01805 g_return_if_fail( be != NULL ); 01806 g_return_if_fail( obj_name != NULL ); 01807 g_return_if_fail( pObject != NULL ); 01808 g_return_if_fail( table_row != NULL ); 01809 01810 value = g_new0( GValue, 1 ); 01811 g_assert( value != NULL ); 01812 if ( table_row->gobj_param_name != NULL ) 01813 { 01814 g_object_get( pObject, table_row->gobj_param_name, &guid, NULL ); 01815 free_guid = TRUE; 01816 } 01817 else 01818 { 01819 getter = gnc_sql_get_getter( obj_name, table_row ); 01820 if ( getter != NULL ) 01821 { 01822 guid = (*getter)( pObject, NULL ); 01823 } 01824 } 01825 (void)g_value_init( value, G_TYPE_STRING ); 01826 if ( guid != NULL ) 01827 { 01828 (void)guid_to_string_buff( guid, guid_buf ); 01829 g_value_set_string( value, guid_buf ); 01830 } 01831 01832 (*pList) = g_slist_append( (*pList), value ); 01833 01834 #if 0 01835 if ( free_guid ) 01836 { 01837 g_free( guid ); 01838 } 01839 #endif 01840 } 01841 01842 static GncSqlColumnTypeHandler guid_handler 01843 = 01844 { 01845 load_guid, 01846 add_guid_col_info_to_list, 01847 gnc_sql_add_colname_to_list, 01848 add_gvalue_guid_to_slist 01849 }; 01850 /* ----------------------------------------------------------------- */ 01851 01852 void 01853 gnc_sql_add_gvalue_objectref_guid_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 01854 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 01855 { 01856 QofAccessFunc getter; 01857 const GncGUID* guid = NULL; 01858 gchar guid_buf[GUID_ENCODING_LENGTH+1]; 01859 QofInstance* inst = NULL; 01860 GValue* value; 01861 01862 g_return_if_fail( be != NULL ); 01863 g_return_if_fail( obj_name != NULL ); 01864 g_return_if_fail( pObject != NULL ); 01865 g_return_if_fail( table_row != NULL ); 01866 01867 value = g_new0( GValue, 1 ); 01868 g_assert( value != NULL ); 01869 if ( table_row->gobj_param_name != NULL ) 01870 { 01871 g_object_get( pObject, table_row->gobj_param_name, &inst, NULL ); 01872 } 01873 else 01874 { 01875 getter = gnc_sql_get_getter( obj_name, table_row ); 01876 if ( getter != NULL ) 01877 { 01878 inst = (*getter)( pObject, NULL ); 01879 } 01880 } 01881 if ( inst != NULL ) 01882 { 01883 guid = qof_instance_get_guid( inst ); 01884 } 01885 (void)g_value_init( value, G_TYPE_STRING ); 01886 if ( guid != NULL ) 01887 { 01888 (void)guid_to_string_buff( guid, guid_buf ); 01889 g_value_set_string( value, guid_buf ); 01890 } 01891 01892 (*pList) = g_slist_append( (*pList), value ); 01893 } 01894 01895 void 01896 gnc_sql_add_objectref_guid_col_info_to_list( const GncSqlBackend* be, 01897 const GncSqlColumnTableEntry* table_row, 01898 GList** pList ) 01899 { 01900 add_guid_col_info_to_list( be, table_row, pList ); 01901 } 01902 01903 /* ----------------------------------------------------------------- */ 01904 typedef Timespec (*TimespecAccessFunc)( const gpointer ); 01905 typedef void (*TimespecSetterFunc)( const gpointer, Timespec ); 01906 01907 #define TIMESPEC_STR_FORMAT "%04d%02d%02d%02d%02d%02d" 01908 #define TIMESPEC_COL_SIZE (4+2+2+2+2+2) 01909 01910 gchar* 01911 gnc_sql_convert_timespec_to_string( const GncSqlBackend* be, Timespec ts ) 01912 { 01913 time_t time; 01914 struct tm* tm; 01915 gint year; 01916 gchar* datebuf; 01917 01918 time = timespecToTime_t( ts ); 01919 tm = gmtime( &time ); 01920 01921 if ( tm->tm_year < 60 ) year = tm->tm_year + 2000; 01922 else year = tm->tm_year + 1900; 01923 01924 datebuf = g_strdup_printf( be->timespec_format, 01925 year, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec ); 01926 return datebuf; 01927 } 01928 01929 static void 01930 load_timespec( const GncSqlBackend* be, GncSqlRow* row, 01931 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 01932 const GncSqlColumnTableEntry* table_row ) 01933 { 01934 const GValue* val; 01935 Timespec ts = {0, 0}; 01936 TimespecSetterFunc ts_setter; 01937 gboolean isOK = FALSE; 01938 01939 g_return_if_fail( be != NULL ); 01940 g_return_if_fail( row != NULL ); 01941 g_return_if_fail( pObject != NULL ); 01942 g_return_if_fail( table_row != NULL ); 01943 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 01944 01945 ts_setter = (TimespecSetterFunc)setter; 01946 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 01947 if ( val == NULL ) 01948 { 01949 isOK = TRUE; 01950 } 01951 else 01952 { 01953 if ( G_VALUE_HOLDS_STRING( val ) ) 01954 { 01955 const gchar* s = g_value_get_string( val ); 01956 if ( s != NULL ) 01957 { 01958 gchar* buf; 01959 buf = g_strdup_printf( "%c%c%c%c-%c%c-%c%c %c%c:%c%c:%c%c", 01960 s[0], s[1], s[2], s[3], 01961 s[4], s[5], 01962 s[6], s[7], 01963 s[8], s[9], 01964 s[10], s[11], 01965 s[12], s[13] ); 01966 ts = gnc_iso8601_to_timespec_gmt( buf ); 01967 g_free( buf ); 01968 isOK = TRUE; 01969 } 01970 01971 } 01972 else 01973 { 01974 PWARN( "Unknown timespec type: %s", G_VALUE_TYPE_NAME( val ) ); 01975 } 01976 } 01977 if ( isOK ) 01978 { 01979 if (table_row->gobj_param_name != NULL) 01980 { 01981 g_object_set( pObject, table_row->gobj_param_name, &ts, NULL ); 01982 } 01983 else 01984 { 01985 (*ts_setter)( pObject, ts ); 01986 } 01987 } 01988 } 01989 01990 static void 01991 add_timespec_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 01992 GList** pList ) 01993 { 01994 GncSqlColumnInfo* info; 01995 01996 g_return_if_fail( be != NULL ); 01997 g_return_if_fail( table_row != NULL ); 01998 g_return_if_fail( pList != NULL ); 01999 02000 info = create_column_info( table_row, BCT_DATETIME, TIMESPEC_COL_SIZE, FALSE ); 02001 02002 *pList = g_list_append( *pList, info ); 02003 } 02004 02005 static void 02006 add_gvalue_timespec_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 02007 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 02008 { 02009 TimespecAccessFunc ts_getter; 02010 Timespec ts; 02011 gchar* datebuf; 02012 GValue* value; 02013 02014 g_return_if_fail( be != NULL ); 02015 g_return_if_fail( obj_name != NULL ); 02016 g_return_if_fail( pObject != NULL ); 02017 g_return_if_fail( table_row != NULL ); 02018 g_return_if_fail( pList != NULL ); 02019 02020 if ( table_row->gobj_param_name != NULL ) 02021 { 02022 Timespec* pts; 02023 g_object_get( pObject, table_row->gobj_param_name, &pts, NULL ); 02024 ts = *pts; 02025 } 02026 else 02027 { 02028 ts_getter = (TimespecAccessFunc)gnc_sql_get_getter( obj_name, table_row ); 02029 g_return_if_fail( ts_getter != NULL ); 02030 ts = (*ts_getter)( pObject ); 02031 } 02032 02033 value = g_new0( GValue, 1 ); 02034 g_assert( value != NULL ); 02035 (void)g_value_init( value, G_TYPE_STRING ); 02036 if ( ts.tv_sec != 0 || ts.tv_nsec != 0 ) 02037 { 02038 datebuf = gnc_sql_convert_timespec_to_string( be, ts ); 02039 g_value_take_string( value, datebuf ); 02040 } 02041 02042 (*pList) = g_slist_append( (*pList), value ); 02043 } 02044 02045 static GncSqlColumnTypeHandler timespec_handler 02046 = 02047 { 02048 load_timespec, 02049 add_timespec_col_info_to_list, 02050 gnc_sql_add_colname_to_list, 02051 add_gvalue_timespec_to_slist 02052 }; 02053 /* ----------------------------------------------------------------- */ 02054 #define DATE_COL_SIZE 8 02055 02056 static void 02057 load_date( const GncSqlBackend* be, GncSqlRow* row, 02058 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 02059 const GncSqlColumnTableEntry* table_row ) 02060 { 02061 const GValue* val; 02062 GDate* date; 02063 02064 g_return_if_fail( be != NULL ); 02065 g_return_if_fail( row != NULL ); 02066 g_return_if_fail( pObject != NULL ); 02067 g_return_if_fail( table_row != NULL ); 02068 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 02069 02070 val = gnc_sql_row_get_value_at_col_name( row, table_row->col_name ); 02071 if ( val != NULL ) 02072 { 02073 if ( G_VALUE_HOLDS_STRING( val ) ) 02074 { 02075 // Format of date is YYYYMMDD 02076 const gchar* s = g_value_get_string( val ); 02077 02078 if ( s != NULL ) 02079 { 02080 gchar buf[5]; 02081 GDateDay day; 02082 guint month; 02083 GDateYear year; 02084 02085 strncpy( buf, &s[0], 4 ); 02086 buf[4] = '\0'; 02087 year = (GDateYear)atoi( buf ); 02088 strncpy( buf, &s[4], 2 ); 02089 buf[2] = '\0'; 02090 month = (guint)atoi( buf ); 02091 strncpy( buf, &s[6], 2 ); 02092 day = (GDateDay)atoi( buf ); 02093 02094 if ( year != 0 || month != 0 || day != (GDateDay)0 ) 02095 { 02096 date = g_date_new_dmy( day, month, year ); 02097 if ( table_row->gobj_param_name != NULL ) 02098 { 02099 g_object_set( pObject, table_row->gobj_param_name, date, NULL ); 02100 } 02101 else 02102 { 02103 (*setter)( pObject, date ); 02104 } 02105 g_date_free( date ); 02106 } 02107 } 02108 } 02109 else 02110 { 02111 PWARN( "Unknown date type: %s", G_VALUE_TYPE_NAME( val ) ); 02112 } 02113 } 02114 } 02115 02116 static void 02117 add_date_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 02118 GList** pList ) 02119 { 02120 GncSqlColumnInfo* info; 02121 02122 g_return_if_fail( be != NULL ); 02123 g_return_if_fail( table_row != NULL ); 02124 g_return_if_fail( pList != NULL ); 02125 02126 info = create_column_info( table_row, BCT_DATE, DATE_COL_SIZE, FALSE ); 02127 02128 *pList = g_list_append( *pList, info ); 02129 } 02130 02131 static void 02132 add_gvalue_date_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 02133 const gpointer pObject, 02134 const GncSqlColumnTableEntry* table_row, GSList** pList ) 02135 { 02136 GDate* date = NULL; 02137 QofAccessFunc getter; 02138 gchar* buf; 02139 GValue* value; 02140 02141 g_return_if_fail( be != NULL ); 02142 g_return_if_fail( obj_name != NULL ); 02143 g_return_if_fail( pObject != NULL ); 02144 g_return_if_fail( table_row != NULL ); 02145 02146 value = g_new0( GValue, 1 ); 02147 g_assert( value != NULL ); 02148 (void)g_value_init( value, G_TYPE_STRING ); 02149 if ( table_row->gobj_param_name != NULL ) 02150 { 02151 g_object_get( pObject, table_row->gobj_param_name, &date, NULL ); 02152 } 02153 else 02154 { 02155 getter = gnc_sql_get_getter( obj_name, table_row ); 02156 if ( getter != NULL ) 02157 { 02158 date = (GDate*)(*getter)( pObject, NULL ); 02159 } 02160 } 02161 if ( date && g_date_valid( date ) ) 02162 { 02163 buf = g_strdup_printf( "%04d%02d%02d", 02164 g_date_get_year( date ), g_date_get_month( date ), g_date_get_day( date ) ); 02165 g_value_take_string( value, buf ); 02166 } 02167 02168 (*pList) = g_slist_append( (*pList), value ); 02169 } 02170 02171 static GncSqlColumnTypeHandler date_handler 02172 = 02173 { 02174 load_date, 02175 add_date_col_info_to_list, 02176 gnc_sql_add_colname_to_list, 02177 add_gvalue_date_to_slist 02178 }; 02179 /* ----------------------------------------------------------------- */ 02180 typedef gnc_numeric (*NumericGetterFunc)( const gpointer ); 02181 typedef void (*NumericSetterFunc)( gpointer, gnc_numeric ); 02182 02183 static const GncSqlColumnTableEntry numeric_col_table[] = 02184 { 02185 /*@ -full_init_block @*/ 02186 { "num", CT_INT64, 0, COL_NNUL, "guid" }, 02187 { "denom", CT_INT64, 0, COL_NNUL, "guid" }, 02188 { NULL } 02189 /*@ +full_init_block @*/ 02190 }; 02191 02192 static void 02193 load_numeric( const GncSqlBackend* be, GncSqlRow* row, 02194 /*@ null @*/ QofSetterFunc setter, gpointer pObject, 02195 const GncSqlColumnTableEntry* table_row ) 02196 { 02197 const GValue* val; 02198 gchar* buf; 02199 gint64 num, denom; 02200 gnc_numeric n; 02201 gboolean isNull = FALSE; 02202 02203 g_return_if_fail( be != NULL ); 02204 g_return_if_fail( row != NULL ); 02205 g_return_if_fail( pObject != NULL ); 02206 g_return_if_fail( table_row != NULL ); 02207 g_return_if_fail( table_row->gobj_param_name != NULL || setter != NULL ); 02208 02209 buf = g_strdup_printf( "%s_num", table_row->col_name ); 02210 val = gnc_sql_row_get_value_at_col_name( row, buf ); 02211 g_free( buf ); 02212 if ( val == NULL ) 02213 { 02214 isNull = TRUE; 02215 num = 0; 02216 } 02217 else 02218 { 02219 num = gnc_sql_get_integer_value( val ); 02220 } 02221 buf = g_strdup_printf( "%s_denom", table_row->col_name ); 02222 val = gnc_sql_row_get_value_at_col_name( row, buf ); 02223 g_free( buf ); 02224 if ( val == NULL ) 02225 { 02226 isNull = TRUE; 02227 denom = 1; 02228 } 02229 else 02230 { 02231 denom = gnc_sql_get_integer_value( val ); 02232 } 02233 n = gnc_numeric_create( num, denom ); 02234 if ( !isNull ) 02235 { 02236 if ( table_row->gobj_param_name != NULL ) 02237 { 02238 g_object_set( pObject, table_row->gobj_param_name, &n, NULL ); 02239 } 02240 else 02241 { 02242 NumericSetterFunc n_setter = (NumericSetterFunc)setter; 02243 (*n_setter)( pObject, n ); 02244 } 02245 } 02246 } 02247 02248 static void 02249 add_numeric_col_info_to_list( const GncSqlBackend* be, const GncSqlColumnTableEntry* table_row, 02250 GList** pList ) 02251 { 02252 GncSqlColumnInfo* info; 02253 gchar* buf; 02254 const GncSqlColumnTableEntry* subtable_row; 02255 02256 g_return_if_fail( be != NULL ); 02257 g_return_if_fail( table_row != NULL ); 02258 g_return_if_fail( pList != NULL ); 02259 02260 for ( subtable_row = numeric_col_table; subtable_row->col_name != NULL; subtable_row++ ) 02261 { 02262 buf = g_strdup_printf( "%s_%s", table_row->col_name, subtable_row->col_name ); 02263 info = g_new0( GncSqlColumnInfo, 1 ); 02264 g_assert( info != NULL ); 02265 info->name = buf; 02266 info->type = BCT_INT64; 02267 info->is_primary_key = ((table_row->flags & COL_PKEY) != 0) ? TRUE : FALSE; 02268 info->null_allowed = ((table_row->flags & COL_NNUL) != 0) ? FALSE : TRUE; 02269 info->is_unicode = FALSE; 02270 *pList = g_list_append( *pList, info ); 02271 } 02272 } 02273 02274 static void 02275 add_numeric_colname_to_list( const GncSqlColumnTableEntry* table_row, GList** pList ) 02276 { 02277 gnc_sql_add_subtable_colnames_to_list( table_row, numeric_col_table, pList ); 02278 } 02279 02280 static void 02281 add_gvalue_numeric_to_slist( const GncSqlBackend* be, QofIdTypeConst obj_name, 02282 const gpointer pObject, const GncSqlColumnTableEntry* table_row, GSList** pList ) 02283 { 02284 NumericGetterFunc getter; 02285 gnc_numeric n; 02286 GValue* num_value; 02287 GValue* denom_value; 02288 02289 g_return_if_fail( be != NULL ); 02290 g_return_if_fail( obj_name != NULL ); 02291 g_return_if_fail( pObject != NULL ); 02292 g_return_if_fail( table_row != NULL ); 02293 02294 if ( table_row->gobj_param_name != NULL ) 02295 { 02296 gnc_numeric *s; 02297 g_object_get( pObject, table_row->gobj_param_name, &s, NULL ); 02298 n = *s; 02299 } 02300 else 02301 { 02302 getter = (NumericGetterFunc)gnc_sql_get_getter( obj_name, table_row ); 02303 if ( getter != NULL ) 02304 { 02305 n = (*getter)( pObject ); 02306 } 02307 else 02308 { 02309 n = gnc_numeric_zero(); 02310 } 02311 } 02312 02313 num_value = g_new0( GValue, 1 ); 02314 g_assert( num_value != NULL ); 02315 (void)g_value_init( num_value, G_TYPE_INT64 ); 02316 g_value_set_int64( num_value, gnc_numeric_num( n ) ); 02317 denom_value = g_new0( GValue, 1 ); 02318 g_assert( denom_value != NULL ); 02319 (void)g_value_init( denom_value, G_TYPE_INT64 ); 02320 g_value_set_int64( denom_value, gnc_numeric_denom( n ) ); 02321 02322 (*pList) = g_slist_append( (*pList), num_value ); 02323 (*pList) = g_slist_append( (*pList), denom_value ); 02324 } 02325 02326 static GncSqlColumnTypeHandler numeric_handler 02327 = { load_numeric, 02328 add_numeric_col_info_to_list, 02329 add_numeric_colname_to_list, 02330 add_gvalue_numeric_to_slist 02331 }; 02332 /* ================================================================= */ 02333 02334 static /*@ null @*//*@ only @*/ GHashTable* g_columnTypeHash = NULL; 02335 02336 void 02337 gnc_sql_register_col_type_handler( const gchar* colType, const GncSqlColumnTypeHandler* handler ) 02338 { 02339 g_return_if_fail( colType != NULL ); 02340 g_return_if_fail( handler != NULL ); 02341 02342 if ( g_columnTypeHash == NULL ) 02343 { 02344 g_columnTypeHash = g_hash_table_new( g_str_hash, g_str_equal ); 02345 g_assert( g_columnTypeHash != NULL ); 02346 } 02347 02348 DEBUG( "Col type %s registered\n", colType ); 02349 g_hash_table_insert( g_columnTypeHash, (gpointer)colType, (gpointer)handler ); 02350 } 02351 02352 /*@ dependent @*//*@ null @*/ static GncSqlColumnTypeHandler* 02353 get_handler( const GncSqlColumnTableEntry* table_row ) 02354 { 02355 GncSqlColumnTypeHandler* pHandler; 02356 02357 g_return_val_if_fail( table_row != NULL, NULL ); 02358 g_return_val_if_fail( table_row->col_type != NULL, NULL ); 02359 02360 if ( g_columnTypeHash != NULL ) 02361 { 02362 pHandler = g_hash_table_lookup( g_columnTypeHash, table_row->col_type ); 02363 g_assert( pHandler != NULL ); 02364 } 02365 else 02366 { 02367 pHandler = NULL; 02368 } 02369 02370 return pHandler; 02371 } 02372 02373 static void 02374 register_standard_col_type_handlers( void ) 02375 { 02376 gnc_sql_register_col_type_handler( CT_STRING, &string_handler ); 02377 gnc_sql_register_col_type_handler( CT_BOOLEAN, &boolean_handler ); 02378 gnc_sql_register_col_type_handler( CT_INT, &int_handler ); 02379 gnc_sql_register_col_type_handler( CT_INT64, &int64_handler ); 02380 gnc_sql_register_col_type_handler( CT_DOUBLE, &double_handler ); 02381 gnc_sql_register_col_type_handler( CT_GUID, &guid_handler ); 02382 gnc_sql_register_col_type_handler( CT_TIMESPEC, ×pec_handler ); 02383 gnc_sql_register_col_type_handler( CT_GDATE, &date_handler ); 02384 gnc_sql_register_col_type_handler( CT_NUMERIC, &numeric_handler ); 02385 } 02386 02387 void 02388 _retrieve_guid_( gpointer pObject, /*@ null @*/ gpointer pValue ) 02389 { 02390 GncGUID* pGuid = (GncGUID*)pObject; 02391 GncGUID* guid = (GncGUID*)pValue; 02392 02393 g_return_if_fail( pObject != NULL ); 02394 g_return_if_fail( pValue != NULL ); 02395 02396 memcpy( pGuid, guid, sizeof( GncGUID ) ); 02397 } 02398 02399 02400 // Table to retrieve just the guid 02401 static GncSqlColumnTableEntry guid_table[] = 02402 { 02403 /*@ -full_init_block @*/ 02404 { "guid", CT_GUID, 0, 0, NULL, NULL, NULL, _retrieve_guid_ }, 02405 { NULL } 02406 /*@ +full_init_block @*/ 02407 }; 02408 02409 /*@ null @*/ 02410 const GncGUID* 02411 gnc_sql_load_guid( const GncSqlBackend* be, GncSqlRow* row ) 02412 { 02413 static GncGUID guid; 02414 02415 g_return_val_if_fail( be != NULL, NULL ); 02416 g_return_val_if_fail( row != NULL, NULL ); 02417 02418 gnc_sql_load_object( be, row, NULL, &guid, guid_table ); 02419 02420 return &guid; 02421 } 02422 02423 // Table to retrieve just the guid 02424 static GncSqlColumnTableEntry tx_guid_table[] = 02425 { 02426 /*@ -full_init_block @*/ 02427 { "tx_guid", CT_GUID, 0, 0, NULL, NULL, NULL, _retrieve_guid_ }, 02428 { NULL } 02429 /*@ +full_init_block @*/ 02430 }; 02431 02432 /*@ null @*//*@ dependent @*/ 02433 const GncGUID* 02434 gnc_sql_load_tx_guid( const GncSqlBackend* be, GncSqlRow* row ) 02435 { 02436 static GncGUID guid; 02437 02438 g_return_val_if_fail( be != NULL, NULL ); 02439 g_return_val_if_fail( row != NULL, NULL ); 02440 02441 gnc_sql_load_object( be, row, NULL, &guid, tx_guid_table ); 02442 02443 return &guid; 02444 } 02445 02446 void 02447 gnc_sql_load_object( const GncSqlBackend* be, GncSqlRow* row, 02448 /*@ null @*/ QofIdTypeConst obj_name, gpointer pObject, 02449 const GncSqlColumnTableEntry* table ) 02450 { 02451 QofSetterFunc setter; 02452 GncSqlColumnTypeHandler* pHandler; 02453 const GncSqlColumnTableEntry* table_row; 02454 02455 g_return_if_fail( be != NULL ); 02456 g_return_if_fail( row != NULL ); 02457 g_return_if_fail( pObject != NULL ); 02458 g_return_if_fail( table != NULL ); 02459 02460 for ( table_row = table; table_row->col_name != NULL; table_row++ ) 02461 { 02462 if ( (table_row->flags & COL_AUTOINC) != 0 ) 02463 { 02464 setter = set_autoinc_id; 02465 } 02466 else if ( table_row->qof_param_name != NULL ) 02467 { 02468 g_assert( obj_name != NULL ); 02469 setter = qof_class_get_parameter_setter( obj_name, 02470 table_row->qof_param_name ); 02471 } 02472 else 02473 { 02474 setter = table_row->setter; 02475 } 02476 pHandler = get_handler( table_row ); 02477 g_assert( pHandler != NULL ); 02478 pHandler->load_fn( be, row, setter, pObject, table_row ); 02479 } 02480 } 02481 02482 /* ================================================================= */ 02483 /*@ null @*/ GncSqlStatement* 02484 gnc_sql_create_select_statement( GncSqlBackend* be, const gchar* table_name ) 02485 { 02486 gchar* sql; 02487 GncSqlStatement* stmt; 02488 02489 g_return_val_if_fail( be != NULL, NULL ); 02490 g_return_val_if_fail( table_name != NULL, NULL ); 02491 02492 sql = g_strdup_printf( "SELECT * FROM %s", table_name ); 02493 stmt = gnc_sql_create_statement_from_sql( be, sql ); 02494 g_free( sql ); 02495 return stmt; 02496 } 02497 02498 /*@ null @*/ static GncSqlStatement* 02499 create_single_col_select_statement( GncSqlBackend* be, 02500 const gchar* table_name, 02501 const GncSqlColumnTableEntry* table_row ) 02502 { 02503 gchar* sql; 02504 GncSqlStatement* stmt; 02505 02506 g_return_val_if_fail( be != NULL, NULL ); 02507 g_return_val_if_fail( table_name != NULL, NULL ); 02508 g_return_val_if_fail( table_row != NULL, NULL ); 02509 02510 sql = g_strdup_printf( "SELECT %s FROM %s", table_row->col_name, table_name ); 02511 stmt = gnc_sql_create_statement_from_sql( be, sql ); 02512 g_free( sql ); 02513 return stmt; 02514 } 02515 02516 /* ================================================================= */ 02517 02518 /*@ null @*/ GncSqlResult* 02519 gnc_sql_execute_select_statement( GncSqlBackend* be, GncSqlStatement* stmt ) 02520 { 02521 GncSqlResult* result; 02522 02523 g_return_val_if_fail( be != NULL, NULL ); 02524 g_return_val_if_fail( stmt != NULL, NULL ); 02525 02526 result = gnc_sql_connection_execute_select_statement( be->conn, stmt ); 02527 if ( result == NULL ) 02528 { 02529 PERR( "SQL error: %s\n", gnc_sql_statement_to_sql( stmt ) ); 02530 qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR ); 02531 } 02532 02533 return result; 02534 } 02535 02536 /*@ null @*/ GncSqlStatement* 02537 gnc_sql_create_statement_from_sql( GncSqlBackend* be, const gchar* sql ) 02538 { 02539 GncSqlStatement* stmt; 02540 02541 g_return_val_if_fail( be != NULL, NULL ); 02542 g_return_val_if_fail( sql != NULL, NULL ); 02543 02544 stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sql ); 02545 if ( stmt == NULL ) 02546 { 02547 PERR( "SQL error: %s\n", sql ); 02548 qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR ); 02549 } 02550 02551 return stmt; 02552 } 02553 02554 /*@ null @*/ GncSqlResult* 02555 gnc_sql_execute_select_sql( GncSqlBackend* be, const gchar* sql ) 02556 { 02557 GncSqlStatement* stmt; 02558 GncSqlResult* result = NULL; 02559 02560 g_return_val_if_fail( be != NULL, NULL ); 02561 g_return_val_if_fail( sql != NULL, NULL ); 02562 02563 stmt = gnc_sql_create_statement_from_sql( be, sql ); 02564 if ( stmt == NULL ) 02565 { 02566 return NULL; 02567 } 02568 result = gnc_sql_connection_execute_select_statement( be->conn, stmt ); 02569 gnc_sql_statement_dispose( stmt ); 02570 if ( result == NULL ) 02571 { 02572 PERR( "SQL error: %s\n", sql ); 02573 qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR ); 02574 } 02575 02576 return result; 02577 } 02578 02579 gint 02580 gnc_sql_execute_nonselect_sql( GncSqlBackend* be, const gchar* sql ) 02581 { 02582 GncSqlStatement* stmt; 02583 gint result; 02584 02585 g_return_val_if_fail( be != NULL, 0 ); 02586 g_return_val_if_fail( sql != NULL, 0 ); 02587 02588 stmt = gnc_sql_create_statement_from_sql( be, sql ); 02589 if ( stmt == NULL ) 02590 { 02591 return -1; 02592 } 02593 result = gnc_sql_connection_execute_nonselect_statement( be->conn, stmt ); 02594 gnc_sql_statement_dispose( stmt ); 02595 return result; 02596 } 02597 02598 static guint 02599 execute_statement_get_count( GncSqlBackend* be, GncSqlStatement* stmt ) 02600 { 02601 GncSqlResult* result; 02602 guint count = 0; 02603 02604 g_return_val_if_fail( be != NULL, 0 ); 02605 g_return_val_if_fail( stmt != NULL, 0 ); 02606 02607 result = gnc_sql_execute_select_statement( be, stmt ); 02608 if ( result != NULL ) 02609 { 02610 count = gnc_sql_result_get_num_rows( result ); 02611 gnc_sql_result_dispose( result ); 02612 } 02613 02614 return count; 02615 } 02616 02617 guint 02618 gnc_sql_append_guid_list_to_sql( GString* sql, GList* list, guint maxCount ) 02619 { 02620 gchar guid_buf[GUID_ENCODING_LENGTH+1]; 02621 gboolean first_guid = TRUE; 02622 guint count; 02623 02624 g_return_val_if_fail( sql != NULL, 0 ); 02625 02626 if ( list == NULL ) return 0; 02627 02628 for ( count = 0; list != NULL && count < maxCount; list = list->next, count++ ) 02629 { 02630 QofInstance* inst = QOF_INSTANCE(list->data); 02631 (void)guid_to_string_buff( qof_instance_get_guid( inst ), guid_buf ); 02632 02633 if ( !first_guid ) 02634 { 02635 (void)g_string_append( sql, "," ); 02636 } 02637 (void)g_string_append( sql, "'" ); 02638 (void)g_string_append( sql, guid_buf ); 02639 (void)g_string_append( sql, "'" ); 02640 first_guid = FALSE; 02641 } 02642 02643 return count; 02644 } 02645 /* ================================================================= */ 02646 02647 gboolean 02648 gnc_sql_object_is_it_in_db( GncSqlBackend* be, const gchar* table_name, 02649 QofIdTypeConst obj_name, gpointer pObject, 02650 const GncSqlColumnTableEntry* table ) 02651 { 02652 GncSqlStatement* sqlStmt; 02653 guint count; 02654 GncSqlColumnTypeHandler* pHandler; 02655 GSList* list = NULL; 02656 02657 g_return_val_if_fail( be != NULL, FALSE ); 02658 g_return_val_if_fail( table_name != NULL, FALSE ); 02659 g_return_val_if_fail( obj_name != NULL, FALSE ); 02660 g_return_val_if_fail( pObject != NULL, FALSE ); 02661 g_return_val_if_fail( table != NULL, FALSE ); 02662 02663 /* SELECT * FROM */ 02664 sqlStmt = create_single_col_select_statement( be, table_name, table ); 02665 g_assert( sqlStmt != NULL ); 02666 02667 /* WHERE */ 02668 pHandler = get_handler( table ); 02669 g_assert( pHandler != NULL ); 02670 pHandler->add_gvalue_to_slist_fn( be, obj_name, pObject, table, &list ); 02671 g_assert( list != NULL ); 02672 gnc_sql_statement_add_where_cond( sqlStmt, obj_name, pObject, &table[0], (GValue*)(list->data) ); 02673 02674 count = execute_statement_get_count( be, sqlStmt ); 02675 gnc_sql_statement_dispose( sqlStmt ); 02676 if ( count == 0 ) 02677 { 02678 return FALSE; 02679 } 02680 else 02681 { 02682 return TRUE; 02683 } 02684 } 02685 02686 gboolean 02687 gnc_sql_do_db_operation( GncSqlBackend* be, 02688 E_DB_OPERATION op, 02689 const gchar* table_name, 02690 QofIdTypeConst obj_name, gpointer pObject, 02691 const GncSqlColumnTableEntry* table ) 02692 { 02693 GncSqlStatement* stmt = NULL; 02694 gboolean ok = FALSE; 02695 02696 g_return_val_if_fail( be != NULL, FALSE ); 02697 g_return_val_if_fail( table_name != NULL, FALSE ); 02698 g_return_val_if_fail( obj_name != NULL, FALSE ); 02699 g_return_val_if_fail( pObject != NULL, FALSE ); 02700 g_return_val_if_fail( table != NULL, FALSE ); 02701 02702 if ( op == OP_DB_INSERT ) 02703 { 02704 stmt = build_insert_statement( be, table_name, obj_name, pObject, table ); 02705 } 02706 else if ( op == OP_DB_UPDATE ) 02707 { 02708 stmt = build_update_statement( be, table_name, obj_name, pObject, table ); 02709 } 02710 else if ( op == OP_DB_DELETE ) 02711 { 02712 stmt = build_delete_statement( be, table_name, obj_name, pObject, table ); 02713 } 02714 else 02715 { 02716 g_assert( FALSE ); 02717 } 02718 if ( stmt != NULL ) 02719 { 02720 gint result; 02721 02722 result = gnc_sql_connection_execute_nonselect_statement( be->conn, stmt ); 02723 if ( result == -1 ) 02724 { 02725 PERR( "SQL error: %s\n", gnc_sql_statement_to_sql( stmt ) ); 02726 qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR ); 02727 } 02728 else 02729 { 02730 ok = TRUE; 02731 } 02732 gnc_sql_statement_dispose( stmt ); 02733 } 02734 02735 return ok; 02736 } 02737 02738 static GSList* 02739 create_gslist_from_values( GncSqlBackend* be, 02740 QofIdTypeConst obj_name, gpointer pObject, 02741 const GncSqlColumnTableEntry* table ) 02742 { 02743 GSList* list = NULL; 02744 GncSqlColumnTypeHandler* pHandler; 02745 const GncSqlColumnTableEntry* table_row; 02746 02747 for ( table_row = table; table_row->col_name != NULL; table_row++ ) 02748 { 02749 if (( table_row->flags & COL_AUTOINC ) == 0 ) 02750 { 02751 pHandler = get_handler( table_row ); 02752 g_assert( pHandler != NULL ); 02753 pHandler->add_gvalue_to_slist_fn( be, obj_name, pObject, table_row, &list ); 02754 } 02755 } 02756 02757 g_assert( list != NULL ); 02758 return list; 02759 } 02760 02761 gchar* 02762 gnc_sql_get_sql_value( const GncSqlConnection* conn, const GValue* value ) 02763 { 02764 if ( value != NULL && G_IS_VALUE( value ) ) 02765 { 02766 GType type = G_VALUE_TYPE(value); 02767 02768 if ( G_VALUE_HOLDS_STRING(value) ) 02769 { 02770 if ( g_value_get_string( value ) != NULL ) 02771 { 02772 gchar* before_str; 02773 gchar* after_str; 02774 before_str = g_value_dup_string( value ); 02775 after_str = gnc_sql_connection_quote_string( conn, before_str ); 02776 g_free( before_str ); 02777 return after_str; 02778 } 02779 else 02780 { 02781 return g_strdup( "NULL" ); 02782 } 02783 } 02784 else if ( type == G_TYPE_INT64 ) 02785 { 02786 return g_strdup_printf( "%" G_GINT64_FORMAT, g_value_get_int64( value ) ); 02787 02788 } 02789 else if ( type == G_TYPE_INT ) 02790 { 02791 return g_strdup_printf( "%d", g_value_get_int( value ) ); 02792 02793 } 02794 else if ( type == G_TYPE_DOUBLE ) 02795 { 02796 gchar doublestr[G_ASCII_DTOSTR_BUF_SIZE]; 02797 g_ascii_dtostr( doublestr, sizeof(doublestr), 02798 g_value_get_double( value )); 02799 return g_strdup( doublestr ); 02800 02801 } 02802 else if ( g_value_type_transformable( type, G_TYPE_STRING ) ) 02803 { 02804 GValue* string; 02805 gchar* str; 02806 02807 string = g_new0( GValue, 1 ); 02808 g_assert( string != NULL ); 02809 (void)g_value_init( string, G_TYPE_STRING ); 02810 (void)g_value_transform( value, string ); 02811 str = g_value_dup_string( string ); 02812 g_value_unset( string ); 02813 g_free( string ); 02814 PWARN( "using g_value_transform(), gtype = '%s'\n", g_type_name( type ) ); 02815 return str; 02816 } 02817 else 02818 { 02819 PWARN( "not transformable, gtype = '%s'\n", g_type_name( type ) ); 02820 return g_strdup( "$$$" ); 02821 } 02822 } 02823 else 02824 { 02825 PWARN( "value is NULL or not G_IS_VALUE()\n" ); 02826 return g_strdup( "" ); 02827 } 02828 } 02829 02830 static void 02831 free_gvalue_list( GSList* list ) 02832 { 02833 GSList* node; 02834 GValue* value; 02835 02836 for ( node = list; node != NULL; node = node->next ) 02837 { 02838 value = (GValue*)node->data; 02839 02840 g_value_unset( value ); 02841 g_free( value ); 02842 } 02843 g_slist_free( list ); 02844 } 02845 02846 /*@ null @*/ static GncSqlStatement* 02847 build_insert_statement( GncSqlBackend* be, 02848 const gchar* table_name, 02849 QofIdTypeConst obj_name, gpointer pObject, 02850 const GncSqlColumnTableEntry* table ) 02851 { 02852 GncSqlStatement* stmt; 02853 GString* sql; 02854 GSList* values; 02855 GSList* node; 02856 gchar* sqlbuf; 02857 GList* colnames = NULL; 02858 GList* colname; 02859 const GncSqlColumnTableEntry* table_row; 02860 02861 g_return_val_if_fail( be != NULL, NULL ); 02862 g_return_val_if_fail( table_name != NULL, NULL ); 02863 g_return_val_if_fail( obj_name != NULL, NULL ); 02864 g_return_val_if_fail( pObject != NULL, NULL ); 02865 g_return_val_if_fail( table != NULL, NULL ); 02866 02867 sqlbuf = g_strdup_printf( "INSERT INTO %s(", table_name ); 02868 sql = g_string_new( sqlbuf ); 02869 g_free( sqlbuf ); 02870 02871 // Get all col names and all values 02872 for ( table_row = table; table_row->col_name != NULL; table_row++ ) 02873 { 02874 if (( table_row->flags & COL_AUTOINC ) == 0 ) 02875 { 02876 GncSqlColumnTypeHandler* pHandler; 02877 02878 // Add col names to the list 02879 pHandler = get_handler( table_row ); 02880 g_assert( pHandler != NULL ); 02881 pHandler->add_colname_to_list_fn( table_row, &colnames ); 02882 } 02883 } 02884 g_assert( colnames != NULL ); 02885 02886 for ( colname = colnames; colname != NULL; colname = colname->next ) 02887 { 02888 if ( colname != colnames ) 02889 { 02890 g_string_append( sql, "," ); 02891 } 02892 g_string_append( sql, (gchar*)colname->data ); 02893 g_free( colname->data ); 02894 } 02895 g_list_free( colnames ); 02896 02897 g_string_append( sql, ") VALUES(" ); 02898 values = create_gslist_from_values( be, obj_name, pObject, table ); 02899 for ( node = values; node != NULL; node = node->next ) 02900 { 02901 GValue* value = (GValue*)node->data; 02902 gchar* value_str; 02903 if ( node != values ) 02904 { 02905 (void)g_string_append( sql, "," ); 02906 } 02907 value_str = gnc_sql_get_sql_value( be->conn, value ); 02908 (void)g_string_append( sql, value_str ); 02909 g_free( value_str ); 02910 (void)g_value_reset( value ); 02911 } 02912 free_gvalue_list( values ); 02913 (void)g_string_append( sql, ")" ); 02914 02915 stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sql->str ); 02916 (void)g_string_free( sql, TRUE ); 02917 02918 return stmt; 02919 } 02920 02921 /*@ null @*/ static GncSqlStatement* 02922 build_update_statement( GncSqlBackend* be, 02923 const gchar* table_name, 02924 QofIdTypeConst obj_name, gpointer pObject, 02925 const GncSqlColumnTableEntry* table ) 02926 { 02927 GncSqlStatement* stmt; 02928 GString* sql; 02929 GSList* values; 02930 GList* colnames = NULL; 02931 GSList* value; 02932 GList* colname; 02933 gboolean firstCol; 02934 const GncSqlColumnTableEntry* table_row; 02935 gchar* sqlbuf; 02936 02937 g_return_val_if_fail( be != NULL, NULL ); 02938 g_return_val_if_fail( table_name != NULL, NULL ); 02939 g_return_val_if_fail( obj_name != NULL, NULL ); 02940 g_return_val_if_fail( pObject != NULL, NULL ); 02941 g_return_val_if_fail( table != NULL, NULL ); 02942 02943 // Get all col names and all values 02944 for ( table_row = table; table_row->col_name != NULL; table_row++ ) 02945 { 02946 if (( table_row->flags & COL_AUTOINC ) == 0 ) 02947 { 02948 GncSqlColumnTypeHandler* pHandler; 02949 02950 // Add col names to the list 02951 pHandler = get_handler( table_row ); 02952 g_assert( pHandler != NULL ); 02953 pHandler->add_colname_to_list_fn( table_row, &colnames ); 02954 } 02955 } 02956 g_assert( colnames != NULL ); 02957 values = create_gslist_from_values( be, obj_name, pObject, table ); 02958 02959 // Create the SQL statement 02960 sqlbuf = g_strdup_printf( "UPDATE %s SET ", table_name ); 02961 sql = g_string_new( sqlbuf ); 02962 g_free( sqlbuf ); 02963 02964 firstCol = TRUE; 02965 for ( colname = colnames->next, value = values->next; 02966 colname != NULL && value != NULL; 02967 colname = colname->next, value = value->next ) 02968 { 02969 gchar* value_str; 02970 if ( !firstCol ) 02971 { 02972 (void)g_string_append( sql, "," ); 02973 } 02974 (void)g_string_append( sql, (gchar*)colname->data ); 02975 (void)g_string_append( sql, "=" ); 02976 value_str = gnc_sql_get_sql_value( be->conn, (GValue*)(value->data) ); 02977 (void)g_string_append( sql, value_str ); 02978 g_free( value_str ); 02979 firstCol = FALSE; 02980 } 02981 for ( colname = colnames; colname != NULL; colname = colname->next ) 02982 { 02983 g_free( colname->data ); 02984 } 02985 g_list_free( colnames ); 02986 if ( value != NULL || colname != NULL ) 02987 { 02988 PERR( "Mismatch in number of column names and values" ); 02989 } 02990 02991 stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sql->str ); 02992 gnc_sql_statement_add_where_cond( stmt, obj_name, pObject, &table[0], (GValue*)(values->data) ); 02993 free_gvalue_list( values ); 02994 (void)g_string_free( sql, TRUE ); 02995 02996 return stmt; 02997 } 02998 02999 /*@ null @*/ static GncSqlStatement* 03000 build_delete_statement( GncSqlBackend* be, 03001 const gchar* table_name, 03002 QofIdTypeConst obj_name, gpointer pObject, 03003 const GncSqlColumnTableEntry* table ) 03004 { 03005 GncSqlStatement* stmt; 03006 GncSqlColumnTypeHandler* pHandler; 03007 GSList* list = NULL; 03008 gchar* sqlbuf; 03009 03010 g_return_val_if_fail( be != NULL, NULL ); 03011 g_return_val_if_fail( table_name != NULL, NULL ); 03012 g_return_val_if_fail( obj_name != NULL, NULL ); 03013 g_return_val_if_fail( pObject != NULL, NULL ); 03014 g_return_val_if_fail( table != NULL, NULL ); 03015 03016 sqlbuf = g_strdup_printf( "DELETE FROM %s ", table_name ); 03017 stmt = gnc_sql_connection_create_statement_from_sql( be->conn, sqlbuf ); 03018 g_free( sqlbuf ); 03019 03020 /* WHERE */ 03021 pHandler = get_handler( table ); 03022 g_assert( pHandler != NULL ); 03023 pHandler->add_gvalue_to_slist_fn( be, obj_name, pObject, table, &list ); 03024 g_assert( list != NULL ); 03025 gnc_sql_statement_add_where_cond( stmt, obj_name, pObject, &table[0], (GValue*)(list->data) ); 03026 free_gvalue_list( list ); 03027 03028 return stmt; 03029 } 03030 03031 /* ================================================================= */ 03032 gboolean 03033 gnc_sql_commit_standard_item( GncSqlBackend* be, QofInstance* inst, const gchar* tableName, 03034 QofIdTypeConst obj_name, const GncSqlColumnTableEntry* col_table ) 03035 { 03036 const GncGUID* guid; 03037 gboolean is_infant; 03038 gint op; 03039 gboolean is_ok; 03040 03041 is_infant = qof_instance_get_infant( inst ); 03042 if ( qof_instance_get_destroying( inst ) ) 03043 { 03044 op = OP_DB_DELETE; 03045 } 03046 else if ( be->is_pristine_db || is_infant ) 03047 { 03048 op = OP_DB_INSERT; 03049 } 03050 else 03051 { 03052 op = OP_DB_UPDATE; 03053 } 03054 is_ok = gnc_sql_do_db_operation( be, op, tableName, obj_name, inst, col_table ); 03055 03056 if ( is_ok ) 03057 { 03058 // Now, commit any slots 03059 guid = qof_instance_get_guid( inst ); 03060 if ( !qof_instance_get_destroying(inst) ) 03061 { 03062 is_ok = gnc_sql_slots_save( be, guid, is_infant, qof_instance_get_slots( inst ) ); 03063 } 03064 else 03065 { 03066 is_ok = gnc_sql_slots_delete( be, guid ); 03067 } 03068 } 03069 03070 return is_ok; 03071 } 03072 03073 /* ================================================================= */ 03074 03075 static gboolean 03076 do_create_table( const GncSqlBackend* be, const gchar* table_name, 03077 const GncSqlColumnTableEntry* col_table ) 03078 { 03079 GList* col_info_list = NULL; 03080 gboolean ok = FALSE; 03081 03082 g_return_val_if_fail( be != NULL, FALSE ); 03083 g_return_val_if_fail( table_name != NULL, FALSE ); 03084 g_return_val_if_fail( col_table != NULL, FALSE ); 03085 03086 for ( ; col_table->col_name != NULL; col_table++ ) 03087 { 03088 GncSqlColumnTypeHandler* pHandler; 03089 03090 pHandler = get_handler( col_table ); 03091 g_assert( pHandler != NULL ); 03092 pHandler->add_col_info_to_list_fn( be, col_table, &col_info_list ); 03093 } 03094 g_assert( col_info_list != NULL ); 03095 ok = gnc_sql_connection_create_table( be->conn, table_name, col_info_list ); 03096 return ok; 03097 } 03098 03099 gboolean 03100 gnc_sql_create_table( GncSqlBackend* be, const gchar* table_name, 03101 gint table_version, const GncSqlColumnTableEntry* col_table ) 03102 { 03103 gboolean ok; 03104 03105 g_return_val_if_fail( be != NULL, FALSE ); 03106 g_return_val_if_fail( table_name != NULL, FALSE ); 03107 g_return_val_if_fail( col_table != NULL, FALSE ); 03108 03109 DEBUG( "Creating %s table\n", table_name ); 03110 03111 ok = do_create_table( be, table_name, col_table ); 03112 if ( ok ) 03113 { 03114 ok = gnc_sql_set_table_version( be, table_name, table_version ); 03115 } 03116 return ok; 03117 } 03118 03119 gboolean 03120 gnc_sql_create_temp_table( const GncSqlBackend* be, const gchar* table_name, 03121 const GncSqlColumnTableEntry* col_table ) 03122 { 03123 g_return_val_if_fail( be != NULL, FALSE ); 03124 g_return_val_if_fail( table_name != NULL, FALSE ); 03125 g_return_val_if_fail( col_table != NULL, FALSE ); 03126 03127 return do_create_table( be, table_name, col_table ); 03128 } 03129 03130 gboolean 03131 gnc_sql_create_index( const GncSqlBackend* be, const gchar* index_name, 03132 const gchar* table_name, 03133 const GncSqlColumnTableEntry* col_table ) 03134 { 03135 gboolean ok; 03136 03137 g_return_val_if_fail( be != NULL, FALSE ); 03138 g_return_val_if_fail( index_name != NULL, FALSE ); 03139 g_return_val_if_fail( table_name != NULL, FALSE ); 03140 g_return_val_if_fail( col_table != NULL, FALSE ); 03141 03142 ok = gnc_sql_connection_create_index( be->conn, index_name, table_name, 03143 col_table ); 03144 return ok; 03145 } 03146 03147 gint 03148 gnc_sql_get_table_version( const GncSqlBackend* be, const gchar* table_name ) 03149 { 03150 g_return_val_if_fail( be != NULL, 0 ); 03151 g_return_val_if_fail( table_name != NULL, 0 ); 03152 03153 /* If the db is pristine because it's being saved, the table does not exist. */ 03154 if ( be->is_pristine_db ) 03155 { 03156 return 0; 03157 } 03158 03159 return GPOINTER_TO_INT(g_hash_table_lookup( be->versions, table_name )); 03160 } 03161 03162 /* Create a temporary table, copy the data from the old table, delete the 03163 old table, then rename the new one. */ 03164 void 03165 gnc_sql_upgrade_table( GncSqlBackend* be, const gchar* table_name, 03166 const GncSqlColumnTableEntry* col_table ) 03167 { 03168 gchar* sql; 03169 gchar* temp_table_name; 03170 03171 g_return_if_fail( be != NULL ); 03172 g_return_if_fail( table_name != NULL ); 03173 g_return_if_fail( col_table != NULL ); 03174 03175 DEBUG( "Upgrading %s table\n", table_name ); 03176 03177 temp_table_name = g_strdup_printf( "%s_new", table_name ); 03178 (void)gnc_sql_create_temp_table( be, temp_table_name, col_table ); 03179 sql = g_strdup_printf( "INSERT INTO %s SELECT * FROM %s", 03180 temp_table_name, table_name ); 03181 (void)gnc_sql_execute_nonselect_sql( be, sql ); 03182 g_free( sql ); 03183 03184 sql = g_strdup_printf( "DROP TABLE %s", table_name ); 03185 (void)gnc_sql_execute_nonselect_sql( be, sql ); 03186 g_free( sql ); 03187 03188 sql = g_strdup_printf( "ALTER TABLE %s RENAME TO %s", temp_table_name, table_name ); 03189 (void)gnc_sql_execute_nonselect_sql( be, sql ); 03190 g_free( sql ); 03191 g_free( temp_table_name ); 03192 } 03193 03194 /* Adds one or more columns to an existing table. */ 03195 gboolean gnc_sql_add_columns_to_table( GncSqlBackend* be, const gchar* table_name, 03196 const GncSqlColumnTableEntry* new_col_table ) 03197 { 03198 GList* col_info_list = NULL; 03199 gboolean ok = FALSE; 03200 03201 g_return_val_if_fail( be != NULL, FALSE ); 03202 g_return_val_if_fail( table_name != NULL, FALSE ); 03203 g_return_val_if_fail( new_col_table != NULL, FALSE ); 03204 03205 for ( ; new_col_table->col_name != NULL; new_col_table++ ) 03206 { 03207 GncSqlColumnTypeHandler* pHandler; 03208 03209 pHandler = get_handler( new_col_table ); 03210 g_assert( pHandler != NULL ); 03211 pHandler->add_col_info_to_list_fn( be, new_col_table, &col_info_list ); 03212 } 03213 g_assert( col_info_list != NULL ); 03214 ok = gnc_sql_connection_add_columns_to_table( be->conn, table_name, col_info_list ); 03215 return ok; 03216 } 03217 03218 /* ================================================================= */ 03219 #define VERSION_TABLE_NAME "versions" 03220 #define MAX_TABLE_NAME_LEN 50 03221 #define TABLE_COL_NAME "table_name" 03222 #define VERSION_COL_NAME "table_version" 03223 03224 static GncSqlColumnTableEntry version_table[] = 03225 { 03226 /*@ -full_init_block @*/ 03227 { TABLE_COL_NAME, CT_STRING, MAX_TABLE_NAME_LEN, COL_PKEY | COL_NNUL }, 03228 { VERSION_COL_NAME, CT_INT, 0, COL_NNUL }, 03229 { NULL } 03230 /*@ +full_init_block @*/ 03231 }; 03232 03239 void 03240 gnc_sql_init_version_info( GncSqlBackend* be ) 03241 { 03242 g_return_if_fail( be != NULL ); 03243 03244 if ( be->versions != NULL ) 03245 { 03246 g_hash_table_destroy( be->versions ); 03247 } 03248 be->versions = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL ); 03249 03250 if ( gnc_sql_connection_does_table_exist( be->conn, VERSION_TABLE_NAME ) ) 03251 { 03252 GncSqlResult* result; 03253 gchar* sql; 03254 03255 sql = g_strdup_printf( "SELECT * FROM %s", VERSION_TABLE_NAME ); 03256 result = gnc_sql_execute_select_sql( be, sql ); 03257 g_free( sql ); 03258 if ( result != NULL ) 03259 { 03260 const GValue* name; 03261 const GValue* version; 03262 GncSqlRow* row; 03263 03264 row = gnc_sql_result_get_first_row( result ); 03265 while ( row != NULL ) 03266 { 03267 name = gnc_sql_row_get_value_at_col_name( row, TABLE_COL_NAME ); 03268 version = gnc_sql_row_get_value_at_col_name( row, VERSION_COL_NAME ); 03269 g_hash_table_insert( be->versions, 03270 g_strdup( g_value_get_string( name ) ), 03271 GINT_TO_POINTER((gint)g_value_get_int64( version )) ); 03272 row = gnc_sql_result_get_next_row( result ); 03273 } 03274 gnc_sql_result_dispose( result ); 03275 } 03276 } 03277 else 03278 { 03279 gboolean ok; 03280 03281 ok = do_create_table( be, VERSION_TABLE_NAME, version_table ); 03282 } 03283 } 03284 03292 static gboolean 03293 reset_version_info( GncSqlBackend* be ) 03294 { 03295 gboolean ok; 03296 03297 g_return_val_if_fail( be != NULL, FALSE ); 03298 03299 ok = do_create_table( be, VERSION_TABLE_NAME, version_table ); 03300 if ( be->versions == NULL ) 03301 { 03302 be->versions = g_hash_table_new_full( g_str_hash, g_str_equal, g_free, NULL ); 03303 } 03304 else 03305 { 03306 g_hash_table_remove_all( be->versions ); 03307 } 03308 03309 return ok; 03310 } 03311 03317 void 03318 gnc_sql_finalize_version_info( GncSqlBackend* be ) 03319 { 03320 g_return_if_fail( be != NULL ); 03321 03322 if ( be->versions != NULL ) 03323 { 03324 g_hash_table_destroy( be->versions ); 03325 be->versions = NULL; 03326 } 03327 } 03328 03338 gboolean 03339 gnc_sql_set_table_version( GncSqlBackend* be, const gchar* table_name, gint version ) 03340 { 03341 gchar* sql; 03342 gint cur_version; 03343 gint status; 03344 03345 g_return_val_if_fail( be != NULL, FALSE ); 03346 g_return_val_if_fail( table_name != NULL, FALSE ); 03347 g_return_val_if_fail( version > 0, FALSE ); 03348 03349 cur_version = gnc_sql_get_table_version( be, table_name ); 03350 if ( cur_version != version ) 03351 { 03352 if ( cur_version == 0 ) 03353 { 03354 sql = g_strdup_printf( "INSERT INTO %s VALUES('%s',%d)", VERSION_TABLE_NAME, 03355 table_name, version ); 03356 } 03357 else 03358 { 03359 sql = g_strdup_printf( "UPDATE %s SET %s=%d WHERE %s='%s'", VERSION_TABLE_NAME, 03360 VERSION_COL_NAME, version, 03361 TABLE_COL_NAME, table_name ); 03362 } 03363 status = gnc_sql_execute_nonselect_sql( be, sql ); 03364 if ( status == -1 ) 03365 { 03366 PERR( "SQL error: %s\n", sql ); 03367 qof_backend_set_error( &be->be, ERR_BACKEND_SERVER_ERR ); 03368 } 03369 g_free( sql ); 03370 } 03371 03372 g_hash_table_insert( be->versions, g_strdup( table_name ), GINT_TO_POINTER(version) ); 03373 03374 return TRUE; 03375 } 03376 03377 /* ========================== END OF FILE ===================== */
1.7.4