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

Generated on Sun Sep 4 18:07:33 2005 for GnuCash by  doxygen 1.4.3-20050530