account.c

00001 /********************************************************************\
00002  * account.c -- implements account handling for postgres backend    *
00003  * Copyright (c) 2000, 2001, 2002 Linas Vepstas <linas@linas.org>   *
00004  *                                                                  *
00005  * This program is free software; you can redistribute it and/or    *
00006  * modify it under the terms of the GNU General Public License as   *
00007  * published by the Free Software Foundation; either version 2 of   *
00008  * the License, or (at your option) any later version.              *
00009  *                                                                  *
00010  * This program is distributed in the hope that it will be useful,  *
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00013  * GNU General Public License for more details.                     *
00014  *                                                                  *
00015  * You should have received a copy of the GNU General Public License*
00016  * along with this program; if not, contact:                        *
00017  *                                                                  *
00018  * Free Software Foundation           Voice:  +1-617-542-5942       *
00019  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00020  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00021 \********************************************************************/
00022 
00023 #include "config.h"
00024 
00025 #include <glib.h>
00026 #include <stdlib.h>  
00027 #include <string.h>  
00028 
00029 #include <libpq-fe.h>  
00030  
00031 #include "AccountP.h"
00032 #include "qof.h"
00033 #include "gnc-commodity.h"
00034 #include "gnc-pricedb.h"
00035 
00036 #include "account.h"
00037 #include "book.h"
00038 #include "base-autogen.h"
00039 #include "kvp-sql.h"
00040 #include "PostgresBackend.h"
00041 #include "price.h"
00042 
00043 static QofLogModule log_module = GNC_MOD_BACKEND; 
00044 
00045 #include "putil.h"
00046 
00047 /* ============================================================= */
00048 /* ============================================================= */
00049 /*                    ACCOUNT STUFF                              */
00050 /*      (UTILITIES FIRST, THEN SETTERS, THEN GETTERS)            */
00051 /* ============================================================= */
00052 /* ============================================================= */
00053 
00054 /* ============================================================= */
00055 /* the pgendStoreAccount() routine stores an account to the 
00056  * database.  That is, the engine data is written out to the 
00057  * database.  It does not do any of the account children; nor 
00058  * does it handle any of the splits or transactions associated 
00059  * with the account.  It does, however, store the associated 
00060  * commodity. 
00061  *
00062  * If do_mark is set to TRUE, then this routine sets a mark
00063  * to terminate recursion.  That is,  it will only store the
00064  * account once; a second call on a marked account will simply 
00065  * return.  Be sure to clear the mark when done!
00066  *
00067  * If the do_check_version flag is set, then this routine
00068  * will compare the engine and sql db version numbrs, and
00069  * perform the store only if the engine version is equal 
00070  * or newer than the sql version.
00071  *
00072  * This routine doesn't perform any locks, and shouldn't be 
00073  * used outside of locks,
00074  */
00075 
00076 static void
00077 pgendStoreAccountNoLock (PGBackend *be, Account *acct,
00078                          gboolean do_mark, 
00079                          gboolean do_check_version)
00080 {
00081    const gnc_commodity *com;
00082    guint32 a_idata;
00083 
00084    if (!be || !acct) return;
00085    if ((FALSE == do_mark) && (!qof_instance_get_dirty_flag(acct)))
00086      return;
00087 
00088    ENTER ("acct=%p, mark=%d", acct, do_mark);
00089 
00090    if (do_mark) 
00091    { 
00092       /* Check to see if we've processed this account recently.
00093        * If so, then return.  The goal here is to avoid excess
00094        * hits to the database, leading to poor performance.
00095        * Note that this marking makes this routine unsafe to use 
00096        * outside a lock (since we never clear the mark)
00097        */
00098       if (xaccAccountGetMark (acct)) return;
00099       xaccAccountSetMark (acct, 1);
00100    }
00101 
00102    if (do_check_version)
00103    {
00104      if (0 < pgendAccountCompareVersion (be, acct)) return;
00105    }
00106   /* be sure to update the version !! */
00107    qof_instance_increment_version(acct, be->version_check);
00108 
00109    a_idata = qof_instance_get_idata(acct);
00110    if ((0 == a_idata) &&
00111        (FALSE == kvp_frame_is_empty (xaccAccountGetSlots(acct))))
00112    {
00113       a_idata = pgendNewGUIDidx(be);
00114       qof_instance_set_idata(acct, a_idata);
00115    }
00116 
00117    pgendPutOneAccountOnly (be, acct);
00118 
00119    /* make sure the account's commodity is in the commodity table */
00120 
00121    /* XXX hack alert FIXME -- it would be more efficient to do 
00122     * this elsewhere, and not here. Furthermore, with this method
00123     * the transaction currencies must be stored in the same way,
00124     * as the transactions are traversed individually, and that
00125     * is even more inefficient.
00126     *
00127     * See StoreAllPrices for an example of how to do this. 
00128     */
00129    com = xaccAccountGetCommodity (acct);
00130    pgendPutOneCommodityOnly (be, (gnc_commodity *) com);
00131 
00132    if (a_idata)
00133    {
00134       pgendKVPDelete (be, a_idata);
00135       pgendKVPStore (be, a_idata, acct->inst.kvp_data);
00136    }
00137    LEAVE(" ");
00138 }
00139 
00140 /* ============================================================= */
00141 /* The pgendStoreAccountTree() routine stores the account hierarchy
00142  * to the sql database.  That is, it stores not oonly the top-level
00143  * accounts, but all of thier children too.   It also stores the
00144  * commodities associated with the accounts.  It does *not* store
00145  * any of the transactions.
00146  *
00147  * Note that it checks the version numbers, and only stores 
00148  * those accounts whose version number is equal or newer than 
00149  * what's in the DB.
00150  *
00151  * The NoLock version doesn't lock up the tables.
00152  */
00153 
00154 void
00155 pgendStoreAccountTreeNoLock (PGBackend *be, Account *root, 
00156                        gboolean do_mark, gboolean do_check_version)
00157 {
00158    GList *descendants, *node;
00159 
00160    if (!be || !root) return;
00161    ENTER("root=%p mark=%d", root, do_mark);
00162 
00163    /* walk the account tree, and store subaccounts */
00164    pgendStoreAccountNoLock (be, root, do_mark, do_check_version);
00165    descendants = gnc_account_get_descendants (root);
00166    for (node=descendants; node; node=node->next) 
00167       pgendStoreAccountNoLock (be, node->data, do_mark, do_check_version);
00168    g_list_free(descendants);
00169    LEAVE(" ");
00170 }
00171 
00172 
00173 void
00174 pgendStoreAccountTree (PGBackend *be, Account *root)
00175 {
00176    char *p;
00177    ENTER ("be=%p, root=%p", be, root);
00178    if (!be || !root) return;
00179 
00180    /* lock it up so that we store atomically */
00181    p = "BEGIN;\n"
00182        "LOCK TABLE gncAccount IN EXCLUSIVE MODE;\n"
00183        "LOCK TABLE gncCommodity IN EXCLUSIVE MODE;\n";
00184    SEND_QUERY (be,p, );
00185    FINISH_QUERY(be->connection);
00186 
00187    /* Clear the account marks; this is used to avoid visiting
00188     * the same account more than once. */
00189    xaccClearMarkDown (root, 0);
00190 
00191    pgendStoreAccountTreeNoLock (be, root, TRUE, TRUE);
00192 
00193    /* reset the write flags again */
00194    xaccClearMarkDown (root, 0);
00195 
00196    p = "COMMIT;\n"
00197        "NOTIFY gncAccount;";
00198    SEND_QUERY (be,p, );
00199    FINISH_QUERY(be->connection);
00200    LEAVE(" ");
00201 }
00202 
00203 /* ============================================================= */
00204 /*        ACCOUNT GETTERS (SETTERS ARE ABOVE)                    */
00205 /* ============================================================= */
00206 
00207 /* ============================================================= */
00208 /* This routine walks the account tree, gets all KVP values */
00209 
00210 static void
00211 restore_cb (Account *acc, void * cb_data)
00212 {
00213    PGBackend *be = (PGBackend *) cb_data;
00214    guint32 a_idata = qof_instance_get_idata(acc);
00215    if (0 == a_idata) return;
00216    acc->inst.kvp_data = pgendKVPFetch (be, a_idata, acc->inst.kvp_data);
00217 }
00218 
00219 static void 
00220 pgendGetAllAccountKVP (PGBackend *be, Account *root)
00221 {
00222    if (!root) return;
00223 
00224    restore_cb(root, NULL);
00225    gnc_account_foreach_descendant(root, restore_cb, be);
00226 }
00227 
00228 /* ============================================================= */
00229 /* The pgendGetAllAccounts() routine restores the account hierarchy 
00230  * of *all* accounts in the DB.  Each account is stuffed into
00231  * its corresponding book.
00232  *
00233  * The pgendGetAllAccountsInBook() routine only fetches the accounts
00234  * for the indicated book.
00235  */
00236 
00237 typedef struct
00238 {
00239   Account * account;
00240 
00241   char * commodity_string; /* If non-NULL, need to load commodity */
00242   gboolean need_parent;    /* If TRUE, need to load parent */
00243   GUID parent_guid;        /* GUID of parent */
00244 } AccountResolveInfo;
00245 
00246 typedef struct
00247 {
00248   QofBook * book;
00249   GList * resolve_info;
00250 } GetAccountData;
00251 
00252 static AccountResolveInfo *
00253 get_resolve_info (AccountResolveInfo * ri)
00254 {
00255   if (ri) return ri;
00256   return g_new0 (AccountResolveInfo, 1);
00257 }
00258 
00259 static gpointer
00260 get_account_cb (PGBackend *be, PGresult *result, int j, gpointer data)
00261 {
00262    GetAccountData * gad = data;
00263    QofBook * book = gad->book;
00264    Account *parent;
00265    Account *acc;
00266    GUID acct_guid;
00267    char * commodity_string;
00268    gnc_commodity * commodity;
00269    AccountResolveInfo *ri = NULL;
00270 
00271    PINFO ("account GUID=%s", DB_GET_VAL("accountGUID",j));
00272 
00273    FIND_BOOK (book);
00274 
00275    /* Next, lets see if we've already got this account */
00276    acct_guid = nullguid;  /* just in case the read fails ... */
00277    string_to_guid (DB_GET_VAL("accountGUID",j), &acct_guid);
00278 
00279    acc = xaccAccountLookup (&acct_guid, book);
00280    if (!acc)
00281    {
00282       acc = xaccMallocAccount(book);
00283       xaccAccountBeginEdit(acc);
00284       xaccAccountSetGUID(acc, &acct_guid);
00285    }
00286    else
00287    {
00288       xaccAccountBeginEdit(acc);
00289    }
00290 
00291    commodity_string = DB_GET_VAL("commodity",j);
00292    commodity = gnc_string_to_commodity (commodity_string, book);
00293    if (!commodity)
00294    {
00295      ri = get_resolve_info (ri);
00296 
00297      ri->account = acc;
00298      ri->commodity_string = g_strdup (commodity_string);
00299    }
00300 
00301    xaccAccountSetName(acc, DB_GET_VAL("accountName",j));
00302    xaccAccountSetDescription(acc, DB_GET_VAL("description",j));
00303    xaccAccountSetCode(acc, DB_GET_VAL("accountCode",j));
00304    xaccAccountSetType(acc, xaccAccountStringToEnum(DB_GET_VAL("type",j)));
00305    if (commodity)
00306      xaccAccountSetCommodity(acc, commodity);
00307    qof_instance_set_version(acc, atoi(DB_GET_VAL("version",j)));
00308    qof_instance_set_idata(acc, atoi(DB_GET_VAL("iguid",j)));
00309 
00310    /* try to find the parent account */
00311    PINFO ("parent GUID=%s", DB_GET_VAL("parentGUID",j));
00312    acct_guid = nullguid;  /* just in case the read fails ... */
00313    string_to_guid (DB_GET_VAL("parentGUID",j), &acct_guid);
00314    if (guid_equal(guid_null(), &acct_guid)) 
00315    {
00316       /* if the parent guid is null, then this
00317        * account belongs in the top level */
00318       gnc_account_append_child (gnc_book_get_root_account(book), acc);
00319    }
00320    else
00321    {
00322       /* if we haven't restored the parent account, create
00323        * a resolution node for it */
00324       parent = xaccAccountLookup (&acct_guid, book);
00325       if (!parent)
00326       {
00327          ri = get_resolve_info (ri);
00328 
00329          ri->account = acc;
00330          ri->need_parent = TRUE;
00331          ri->parent_guid = acct_guid;
00332       }
00333       else
00334       {
00335          xaccAccountBeginEdit(parent);
00336          gnc_account_append_child(parent, acc);
00337          xaccAccountCommitEdit(parent);
00338       }
00339    }
00340 
00341    xaccAccountCommitEdit(acc);
00342 
00343    if (ri)
00344      gad->resolve_info = g_list_prepend (gad->resolve_info, ri);
00345 
00346    return data;
00347 }
00348 
00349 static void
00350 pgendGetAccounts (PGBackend *be, QofBook *book)
00351 {
00352   GetAccountData gad;
00353 
00354   gad.book = book ? book : be->book;
00355   gad.resolve_info = NULL;
00356 
00357   pgendGetResults (be, get_account_cb, &gad);
00358 
00359   while (gad.resolve_info)
00360   {
00361     AccountResolveInfo *ri = gad.resolve_info->data;
00362 
00363     gad.resolve_info = g_list_remove (gad.resolve_info, ri);
00364 
00365     xaccAccountBeginEdit (ri->account);
00366 
00367     if (ri->commodity_string)
00368     {
00369       gnc_commodity * commodity;
00370 
00371       pgendGetCommodity (be, ri->commodity_string);
00372       commodity = gnc_string_to_commodity (ri->commodity_string,
00373                                            gnc_account_get_book(ri->account));
00374 
00375       if (commodity)
00376       {
00377         xaccAccountSetCommodity (ri->account, commodity);
00378       }
00379       else
00380       {
00381         PERR ("Can't find commodity %s", ri->commodity_string);
00382       }
00383 
00384       g_free (ri->commodity_string);
00385       ri->commodity_string = NULL;
00386     }
00387 
00388     if (ri->need_parent)
00389     {
00390       Account * parent;
00391 
00392       /* parent could have been pulled in after node was inserted */
00393       parent = xaccAccountLookup (&ri->parent_guid, gad.book);
00394 
00395       if (!parent)
00396         parent = pgendCopyAccountToEngine (be, &ri->parent_guid);
00397 
00398       if (parent)
00399       {
00400         xaccAccountBeginEdit(parent);
00401         gnc_account_append_child(parent, ri->account);
00402         xaccAccountCommitEdit(parent);
00403       }
00404       else
00405       {
00406         PERR ("no such account: %s", guid_to_string (&ri->parent_guid));
00407       }
00408     }
00409 
00410     xaccAccountCommitEdit (ri->account);
00411 
00412     g_free (ri);
00413   }
00414 }
00415 
00416 void
00417 pgendGetAllAccounts (PGBackend *be)
00418 {
00419    QofBookList *node;
00420    char * bufp;
00421 
00422    ENTER ("be=%p", be);
00423    if (!be) return;
00424 
00425    /* get all the books in the database */
00426    pgendGetAllBooks (be, be->blist);
00427 
00428    /* Make sure commodities table is up to date */
00429    pgendGetAllCommodities (be);
00430 
00431    /* Get them ALL */
00432    bufp = "SELECT * FROM gncAccount;";
00433    SEND_QUERY (be, bufp, );
00434    pgendGetAccounts (be, NULL);
00435 
00436    for (node=be->blist; node; node=node->next)
00437    {
00438       QofBook *book = node->data;
00439       Account *root = gnc_book_get_root_account(book);
00440       pgendGetAllAccountKVP (be, root);
00441    }
00442 
00443    LEAVE (" ");
00444 }
00445 
00446 void
00447 pgendGetAllAccountsInBook (PGBackend *be, QofBook *book)
00448 {
00449    char *p, buff[400];
00450    Account *root;
00451 
00452    ENTER ("be=%p", be);
00453    if (!be || !book) return;
00454 
00455    /* first, make sure commodities table is up to date */
00456    pgendGetAllCommodities (be);
00457 
00458    /* Get everything for this book */
00459 
00460    p = buff;
00461    p = stpcpy (p, "SELECT * FROM gncAccount WHERE bookGuid='");
00462    p = guid_to_string_buff (qof_book_get_guid(book), p);
00463    p = stpcpy (p, "';");
00464    SEND_QUERY (be, buff, );
00465    pgendGetAccounts (be, book);
00466 
00467    root = gnc_book_get_root_account(book);
00468    pgendGetAllAccountKVP (be, root);
00469 
00470    LEAVE (" ");
00471 }
00472 
00473 
00474 /* ============================================================= */
00475 
00476 Account *
00477 pgendCopyAccountToEngine (PGBackend *be, const GUID *acct_guid)
00478 {
00479    char *pbuff;
00480    Account *acc = NULL;
00481    int engine_data_is_newer = 0;
00482    guint32 a_idata;
00483 
00484    ENTER ("be=%p", be);
00485    if (!be || !acct_guid) return 0;
00486 
00487    /* disable callbacks into the backend, and events to GUI */
00488    qof_event_suspend();
00489    pgendDisable(be);
00490 
00491    /* First, see if we already have such an account */
00492    acc = pgendAccountLookup (be, acct_guid);
00493 
00494    if (!acc)
00495    {
00496       engine_data_is_newer = -1;
00497    }
00498    else
00499    {
00500       /* save some performance, don't go to the
00501        * backend if the data is recent. */
00502       guint32 value = qof_instance_get_version_check(acc);
00503       if (MAX_VERSION_AGE >= be->version_check - value) 
00504       {
00505          PINFO ("fresh data, skip check");
00506          engine_data_is_newer = 0;
00507       }
00508       else
00509       {
00510          engine_data_is_newer = - pgendAccountCompareVersion (be, acc);
00511       }
00512    }
00513 
00514    if (0 > engine_data_is_newer)
00515    { 
00516       /* build the sql query to get the account */
00517       pbuff = be->buff;
00518       pbuff[0] = 0;
00519       pbuff = stpcpy (pbuff,
00520                       "SELECT * FROM gncAccount WHERE accountGuid='");
00521       pbuff = guid_to_string_buff(acct_guid, pbuff);
00522       pbuff = stpcpy (pbuff, "';");
00523 
00524       SEND_QUERY (be,be->buff, 0);
00525       pgendGetAccounts (be, NULL);
00526       acc = pgendAccountLookup (be, acct_guid);
00527 
00528       /* restore any kvp data associated with the transaction and splits */
00529       if (acc)
00530       {
00531          a_idata = qof_instance_get_idata(acc);
00532          if (a_idata)
00533          {
00534             acc->inst.kvp_data = pgendKVPFetch (be, a_idata, acc->inst.kvp_data);
00535          }
00536 
00537          qof_instance_set_version_check(acc, be->version_check);
00538       }
00539    }
00540 
00541    /* re-enable events to the backend and GUI */
00542    pgendEnable(be);
00543    qof_event_resume();
00544 
00545    LEAVE (" ");
00546    return acc;
00547 }
00548 
00549 /* ============================================================= */
00550 /* ============================================================= */
00551 /*         HIGHER LEVEL ROUTINES AND BACKEND PROPER              */
00552 /* ============================================================= */
00553 /* ============================================================= */
00554 
00555 void
00556 pgend_account_commit_edit (QofBackend * bend, 
00557                            Account * acct)
00558 {
00559    char *p;
00560    QofBackendError err;
00561    PGBackend *be = (PGBackend *)bend;
00562 
00563    ENTER ("be=%p, acct=%p", be, acct);
00564    if (!be || !acct) return;
00565 
00566    if (!qof_instance_get_dirty_flag(acct))
00567    {
00568       LEAVE ("account not written because not dirty");
00569       return;
00570    }
00571 
00572    /* lock it up so that we query and store atomically */
00573    /* its not at all clear to me that this isn't rife with deadlocks. */
00574    p = "BEGIN;\n"
00575        "LOCK TABLE gncAccount IN EXCLUSIVE MODE;\n"
00576        "LOCK TABLE gncCommodity IN EXCLUSIVE MODE;\n";
00577 
00578    SEND_QUERY (be,p,);
00579    FINISH_QUERY(be->connection);
00580 
00581    /* check to see that the engine version is equal or newer than 
00582     * whats in the database.  It its not, then some other user has 
00583     * made changes, and we must roll back. */
00584    if (0 < pgendAccountCompareVersion (be, acct))
00585    {
00586       qof_instance_set_destroying(acct, FALSE);
00587       p = "ROLLBACK;";
00588       SEND_QUERY (be,p,);
00589       FINISH_QUERY(be->connection);
00590 
00591       /* hack alert -- we should restore the account data from the 
00592        * sql back end at this point ! !!! */
00593       PWARN(" account data in engine is newer\n"
00594             " account must be rolled back.  This function\n"
00595             " is not completely implemented !! \n");
00596       qof_backend_set_error (&be->be, ERR_BACKEND_MODIFIED);
00597       LEAVE ("rolled back");
00598       return;
00599    }
00600    /* be sure to update the version !! */
00601    qof_instance_increment_version(acct, be->version_check);
00602 
00603    if (qof_instance_get_destroying(acct))
00604    {
00605       const GUID *guid = xaccAccountGetGUID(acct);
00606       pgendKVPDelete (be, qof_instance_get_idata(acct));
00607 
00608       p = be->buff; *p = 0;
00609       p = stpcpy (p, "DELETE FROM gncAccount WHERE accountGuid='");
00610       p = guid_to_string_buff (guid, p);
00611       p = stpcpy (p, "';");
00612       err = sendQuery (be,be->buff);
00613       if (err == ERR_BACKEND_NO_ERR) {
00614           err = finishQuery(be);
00615           if (err > 0) /* if the number of rows deleted is 0 */
00616               pgendStoreAuditAccount (be, acct, SQL_DELETE);
00617       }
00618    }
00619    else
00620    {
00621       pgendStoreAccountNoLock (be, acct, FALSE, FALSE);
00622    }
00623 
00624    p = "COMMIT;\n"
00625        "NOTIFY gncAccount;";
00626    SEND_QUERY (be,p,);
00627    FINISH_QUERY(be->connection);
00628 
00629    LEAVE ("commited");
00630    return;
00631 }
00632 
00633 /* ======================== END OF FILE ======================== */

Generated on Thu Jul 3 05:06:28 2008 for GnuCash by  doxygen 1.5.2