checkpoint.c

00001 /********************************************************************\
00002  * checkpoint.c : computes account balance checkpoints              *
00003  * Copyright (C) 2001 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 /* 
00024  * FILE:
00025  * checkpoint.c
00026  *
00027  * FUNCTION:
00028  * Account balance checkpointing.
00029  * Not used in single-user mode; vital for multi-user mode.
00030  *
00031  * HISTORY:
00032  * Copyright (c) 2000, 2001 Linas Vepstas
00033  * 
00034  */
00035 
00036 #include "config.h"
00037 #include <glib.h>
00038 #include <stdio.h>
00039 #include <stdlib.h>
00040 #include <string.h>  
00041 #include <sys/types.h>  
00042 
00043 #include <libpq-fe.h>  
00044 
00045 #include "Account.h"
00046 #include "AccountP.h"
00047 #include "qof.h"
00048 #include "gnc-commodity.h"
00049 
00050 #include "builder.h"
00051 #include "checkpoint.h"
00052 #include "escape.h"
00053 
00054 #include "putil.h"
00055 
00056 static QofLogModule log_module = GNC_MOD_BACKEND; 
00057 
00058 /* ============================================================= */
00059 /* include autogenerated code */
00060 
00061 #include "check-autogen.h"
00062 #include "check-autogen.c"
00063 
00064 /* ============================================================= */
00065 /* recompute *all* checkpoints for the account */
00066 
00067 static void
00068 pgendAccountRecomputeAllCheckpoints (PGBackend *be, const GUID *acct_guid)
00069 {
00070    Timespec this_ts, next_ts;
00071 #ifndef HAVE_GLIB29
00072    GMemChunk *chunk;
00073 #endif
00074    GList *node, *checkpoints = NULL;
00075    PGresult *result;
00076    Checkpoint *bp;
00077    char *p;
00078    int i;
00079    int nck;
00080    Account *acc;
00081    const char *commodity_name, *guid_string;
00082 
00083    if (!be) return;
00084    ENTER("be=%p", be);
00085 
00086    guid_string = guid_to_string (acct_guid);
00087    acc = pgendAccountLookup (be, acct_guid);
00088    commodity_name =
00089      gnc_commodity_get_unique_name (xaccAccountGetCommodity(acc));
00090 
00091 #ifndef HAVE_GLIB29
00092    chunk = g_mem_chunk_create (Checkpoint, 300, G_ALLOC_ONLY);
00093 #endif
00094 
00095    /* prevent others from inserting any splits while we recompute 
00096     * the checkpoints. (hack alert -verify that this is the correct
00097     * lock) */
00098    p = "BEGIN WORK;\n"
00099        "LOCK TABLE gncCheckpoint IN ACCESS EXCLUSIVE MODE;\n"
00100        "LOCK TABLE gncSplit IN SHARE MODE;\n";
00101    SEND_QUERY (be,p, );
00102    FINISH_QUERY(be->connection);
00103 
00104    /* Blow all the old checkpoints for this account out of the water.
00105     * This should help ensure against accidental corruption.
00106     */
00107    p = be->buff; *p = 0;
00108    p = stpcpy (p, "DELETE FROM gncCheckpoint WHERE accountGuid='");
00109    p = guid_to_string_buff (acct_guid, p);
00110    p = stpcpy (p, "';");
00111    SEND_QUERY (be,be->buff, );
00112    FINISH_QUERY(be->connection);
00113 
00114    /* malloc a new checkpoint, set it to the dawn of unix time ... */
00115 #ifdef HAVE_GLIB29
00116    bp = g_slice_alloc0 (sizeof(Checkpoint));
00117 #else
00118    bp = g_chunk_new0 (Checkpoint, chunk);
00119 #endif
00120    checkpoints = g_list_prepend (checkpoints, bp);
00121    this_ts = gnc_iso8601_to_timespec_gmt (CK_EARLIEST_DATE);
00122    bp->date_start = this_ts;
00123    bp->account_guid = acct_guid;
00124    bp->commodity = commodity_name;
00125 
00126    /* loop over entries, creating a set of evenly-spaced checkpoints */
00127    nck = MIN_CHECKPOINT_COUNT;
00128    while (1)
00129    {
00130       p = be->buff; *p = 0;
00131       p = stpcpy (p, "SELECT gncTransaction.date_posted"
00132                      "    FROM gncTransaction, gncSplit"
00133                      "    WHERE"
00134                      "        gncSplit.transguid = gncTransaction.transguid AND"
00135                      "        gncSplit.accountguid='");
00136       p = stpcpy (p, guid_string);
00137       p = stpcpy (p, "'"
00138                      "    ORDER BY gncTransaction.date_posted ASC"
00139                      "    LIMIT 2 OFFSET ");
00140       p += sprintf (p, "%d", nck);
00141       p = stpcpy (p, ";");
00142       SEND_QUERY (be,be->buff, );
00143 
00144       i=0; 
00145       do {
00146          GET_RESULTS (be->connection, result);
00147          {
00148             int jrows;
00149             int ncols = PQnfields (result);
00150             jrows = PQntuples (result);
00151             PINFO ("query result %d has %d rows and %d cols",
00152                i, jrows, ncols);
00153 
00154             if (0 == jrows) {
00155                 FINISH_QUERY(be->connection);
00156                 goto done; 
00157             }
00158 
00159             if (0 == i) this_ts = gnc_iso8601_to_timespec_gmt (DB_GET_VAL("date_posted",0));
00160             if (2 == jrows) {
00161                next_ts = gnc_iso8601_to_timespec_gmt (DB_GET_VAL("date_posted",1));
00162             } else if (1 == i) {
00163                next_ts = gnc_iso8601_to_timespec_gmt (DB_GET_VAL("date_posted",0));
00164             } 
00165             PQclear (result);
00166             i++;
00167          }
00168       } while (result);
00169 
00170       /* lets see if its time to start a new checkpoint */
00171       /* look for splits that occur at least ten seconds apart */
00172       this_ts.tv_sec += 10;
00173       if (timespec_cmp (&this_ts, &next_ts) < 0)
00174       {
00175          /* Set checkpoint five seconds back. This is safe,
00176           * because we looked for a 10 second gap above */
00177          this_ts.tv_sec -= 5;
00178          bp->date_end = this_ts;
00179 
00180          /* and build a new checkpoint */
00181 #ifdef HAVE_GLIB29
00182          bp = g_slice_alloc0 (sizeof(Checkpoint));
00183 #else
00184          bp = g_chunk_new0 (Checkpoint, chunk);
00185 #endif
00186          checkpoints = g_list_prepend (checkpoints, bp);
00187          bp->date_start = this_ts;
00188          bp->account_guid = acct_guid;
00189          bp->commodity = commodity_name;
00190          nck += MIN_CHECKPOINT_COUNT;
00191       }
00192       else 
00193       {
00194          /* step one at a time until we find at least a ten-second gap */
00195          nck += 1;
00196       }
00197    }
00198 
00199 done:
00200 
00201    /* set the timestamp on the final checkpoint into the distant future */
00202    this_ts = gnc_iso8601_to_timespec_gmt (CK_LAST_DATE);
00203    bp->date_end = this_ts;
00204 
00205    /* now store the checkpoints */
00206    for (node = checkpoints; node; node = node->next)
00207    {
00208       bp = (Checkpoint *) node->data;
00209       pgendStoreOneCheckpointOnly (be, bp, SQL_INSERT);
00210    }
00211 
00212    g_list_free (checkpoints);
00213 #ifndef HAVE_GLIB29
00214    g_mem_chunk_destroy (chunk);
00215 #endif
00216 
00217    /* finally, let the sql server do the heavy lifting of computing the 
00218     * subtotal balances */
00219    p = be->buff; *p = 0;
00220    p = stpcpy (p, "UPDATE gncCheckpoint SET "
00221           "   balance            = (gncsubtotalbalance        (accountGuid, date_start, date_end )),"
00222           "   cleared_balance    = (gncsubtotalclearedbalance (accountGuid, date_start, date_end )),"
00223           "   reconciled_balance = (gncsubtotalreconedbalance (accountGuid, date_start, date_end )) "
00224           " WHERE accountGuid='");
00225    p = stpcpy (p, guid_string);
00226    p = stpcpy (p, "';\n");
00227    p = stpcpy (p, "COMMIT WORK;\n"
00228                   "NOTIFY gncCheckpoint;\n");
00229    SEND_QUERY (be,be->buff, );
00230    FINISH_QUERY(be->connection);
00231 }
00232 
00233 /* ============================================================= */
00234 /* recompute fresh balance checkpoints for every account */
00235 
00236 void
00237 pgendAccountTreeRecomputeAllCheckpoints (PGBackend *be, Account *parent)
00238 {
00239    GList *acclist, *node;
00240 
00241    pgendAccountRecomputeAllCheckpoints (be, xaccAccountGetGUID(parent));
00242 
00243    acclist = gnc_account_get_descendants(parent);
00244    for (node = acclist; node; node=node->next)
00245    {
00246       Account *acc = (Account *) node->data;
00247       pgendAccountRecomputeAllCheckpoints (be, xaccAccountGetGUID(acc));
00248    }
00249    g_list_free (acclist);
00250 }
00251 
00252 /* ============================================================= */
00253 /* recompute *one* checkpoint for the account */
00254 
00255 void
00256 pgendAccountRecomputeOneCheckpoint (PGBackend *be, Account *acc, Timespec ts)
00257 {
00258    char *p, dbuf[80];
00259 
00260    gnc_timespec_to_iso8601_buff (ts, dbuf);
00261 
00262    p = be->buff; *p = 0;
00263    p = stpcpy (p, "BEGIN WORK;\n"
00264                   "LOCK TABLE gncCheckpoint IN ACCESS EXCLUSIVE MODE;\n"
00265                   "LOCK TABLE gncSplit IN SHARE MODE;\n"
00266                   "UPDATE gncCheckpoint SET "
00267           "   balance            = (gncsubtotalbalance        (accountGuid, date_start, date_end )),"
00268           "   cleared_balance    = (gncsubtotalclearedbalance (accountGuid, date_start, date_end )),"
00269           "   reconciled_balance = (gncsubtotalreconedbalance (accountGuid, date_start, date_end )) "
00270           " WHERE accountGuid='");
00271    p = guid_to_string_buff (xaccAccountGetGUID(acc), p);
00272    p = stpcpy (p, "' AND date_start <= '");
00273    p = stpcpy (p, dbuf);
00274    p = stpcpy (p, "' AND date_end > '");
00275    p = stpcpy (p, dbuf);
00276    p = stpcpy (p, "';\n");
00277 
00278    p = stpcpy (p, "COMMIT WORK;\n"
00279                   "NOTIFY gncCheckpoint;\n");
00280    SEND_QUERY (be,be->buff, );
00281    FINISH_QUERY(be->connection);
00282 }
00283 
00284 /* ============================================================= */
00285 /* recompute all checkpoints affected by this transaction */
00286 
00287 void
00288 pgendTransactionRecomputeCheckpoints (PGBackend *be, Transaction *trans)
00289 {
00290    char *p;
00291 
00292    p = be->buff; *p = 0;
00293    p = stpcpy (p, "BEGIN WORK;\n"
00294                   "LOCK TABLE gncCheckpoint IN ACCESS EXCLUSIVE MODE;\n"
00295                   "LOCK TABLE gncTransaction IN SHARE MODE;\n"
00296                   "LOCK TABLE gncSplit IN SHARE MODE;\n"
00297                   "UPDATE gncCheckpoint SET "
00298    "  balance            = (gncsubtotalbalance        (gncSplit.accountGuid, date_start, date_end )),"
00299    "  cleared_balance    = (gncsubtotalclearedbalance (gncSplit.accountGuid, date_start, date_end )),"
00300    "  reconciled_balance = (gncsubtotalreconedbalance (gncSplit.accountGuid, date_start, date_end )) "
00301    " WHERE gncSplit.transGuid = '");
00302    p = guid_to_string_buff (xaccTransGetGUID(trans), p);
00303    p = stpcpy (p, "' AND gncTransaction.transGuid = gncSplit.transGuid "
00304                   "  AND gncCheckpoint.accountGuid = gncSplit.accountGuid "
00305                   "  AND date_start <= gncTransaction.date_posted "
00306                   "  AND date_end > gncTransaction.date_posted;\n"
00307                   "COMMIT WORK;\n"
00308                   "NOTIFY gncCheckpoint;\n");
00309    SEND_QUERY (be,be->buff, );
00310    FINISH_QUERY(be->connection);
00311 }
00312 
00313 /* ============================================================= */
00314 /* get checkpoint value for the account 
00315  * We find the checkpoint which matches the account and commodity,
00316  * for the first date immediately preceeding the date.  
00317  * Then we fill in the balance fields for the returned query.
00318  */
00319 
00320 static gpointer 
00321 get_checkpoint_cb (PGBackend *be, PGresult *result, int j, gpointer data)
00322 {
00323    Checkpoint *chk = (Checkpoint *) data;
00324    chk->balance = strtoll(DB_GET_VAL("baln", j), NULL, 0);
00325    chk->cleared_balance = strtoll(DB_GET_VAL("cleared_baln", j), NULL, 0);
00326    chk->reconciled_balance = strtoll(DB_GET_VAL("reconed_baln", j), NULL, 0);
00327    return data;
00328 }
00329 
00330 static gpointer 
00331 get_checkpoint_date_cb (PGBackend *be, PGresult *result, int j, gpointer data)
00332 {
00333    Checkpoint *chk = (Checkpoint *) data;
00334    chk->date_start = gnc_iso8601_to_timespec_gmt (DB_GET_VAL("date_start", j));
00335    return data;
00336 }
00337 
00338 static void
00339 pgendAccountGetCheckpoint (PGBackend *be, Checkpoint *chk)
00340 {
00341    sqlEscape *escape;
00342    char guid_str[80], end_str[80];
00343    char * p;
00344 
00345    if (!be || !chk) return;
00346    ENTER("be=%p", be);
00347 
00348    escape = sqlEscape_new ();
00349 
00350    guid_to_string_buff (chk->account_guid, guid_str);
00351    gnc_timespec_to_iso8601_buff (chk->date_end, end_str);
00352 
00353    /* sum up the total of all the checpoints before this date */
00354    p = be->buff; *p = 0;
00355    p = stpcpy (p, "SELECT sum(balance) AS baln, "
00356                   "       sum(cleared_balance) AS cleared_baln, "
00357                   "       sum(reconciled_balance) AS reconed_baln "
00358                   "    FROM gncCheckpoint "
00359                   "    WHERE accountGuid='");
00360    p = stpcpy (p, guid_str);
00361    p = stpcpy (p, "'   AND commodity='");
00362    p = stpcpy (p, sqlEscapeString (escape, chk->commodity));
00363    p = stpcpy (p, "'   AND date_end <'");
00364    p = stpcpy (p, end_str);
00365    p = stpcpy (p, "';");
00366    SEND_QUERY (be,be->buff, );
00367 
00368    sqlEscape_destroy (escape);
00369    escape = NULL;
00370 
00371    pgendGetResults (be, get_checkpoint_cb, chk);
00372 
00373    /* now get the ending date of the last checkpoint,
00374     * aka the starting date of the next checkpoint */
00375    p = be->buff; *p = 0;
00376    p = stpcpy (p, "SELECT date_start FROM gncCheckpoint "
00377                   "    WHERE accountGuid='");
00378    p = stpcpy (p, guid_str);
00379    p = stpcpy (p, "'   AND date_start < '");
00380    p = stpcpy (p, end_str);
00381    p = stpcpy (p, "'   ORDER BY date_start DESC LIMIT 1;");
00382    SEND_QUERY (be,be->buff, );
00383 
00384    /* provide default value, in case there are no checkpoints */
00385    chk->date_start = gnc_iso8601_to_timespec_gmt (CK_EARLIEST_DATE);
00386    pgendGetResults (be, get_checkpoint_date_cb, chk);
00387 
00388    LEAVE("be=%p", be);
00389 }
00390 
00391 /* ============================================================= */
00392 /* get partial balance for an account */
00393 
00394 static void
00395 pgendAccountGetPartialBalance (PGBackend *be, Checkpoint *chk)
00396 {
00397    char guid_str[80], start_str[80], end_str[80];
00398    char * p;
00399 
00400    if (!be || !chk) return;
00401    ENTER("be=%p", be);
00402 
00403    guid_to_string_buff (chk->account_guid, guid_str);
00404    gnc_timespec_to_iso8601_buff (chk->date_start, start_str);
00405    gnc_timespec_to_iso8601_buff (chk->date_end, end_str);
00406    
00407    /* create the query we need */
00408    p = be->buff; *p = 0;
00409    p = stpcpy (p, "SELECT gncSubtotalBalance ('");
00410    p = stpcpy (p, guid_str);
00411    p = stpcpy (p, "', '");
00412    p = stpcpy (p, start_str);
00413    p = stpcpy (p, "', '");
00414    p = stpcpy (p, end_str);
00415    p = stpcpy (p, "') AS baln, "
00416                   " gncSubtotalClearedBalance ('");
00417    p = stpcpy (p, guid_str);
00418    p = stpcpy (p, "', '");
00419    p = stpcpy (p, start_str);
00420    p = stpcpy (p, "', '");
00421    p = stpcpy (p, end_str);
00422    p = stpcpy (p, "') AS cleared_baln, "
00423                   " gncSubtotalReconedBalance ('");
00424    p = stpcpy (p, guid_str);
00425    p = stpcpy (p, "', '");
00426    p = stpcpy (p, start_str);
00427    p = stpcpy (p, "', '");
00428    p = stpcpy (p, end_str);
00429    p = stpcpy (p, "') AS reconed_baln;");
00430 
00431    SEND_QUERY (be,be->buff, );
00432 
00433    pgendGetResults (be, get_checkpoint_cb, chk);
00434 
00435    LEAVE("be=%p", be);
00436 }
00437 
00438 /* ============================================================= */
00439 /* get checkpoint value for one accounts */
00440 
00441 void
00442 pgendAccountGetBalance (PGBackend *be, Account *acc, Timespec as_of_date)
00443 {
00444    Checkpoint chk;
00445    const gnc_commodity *com;
00446    gint64 b, cl_b, rec_b, deno;
00447    gnc_numeric baln;
00448    gnc_numeric cleared_baln;
00449    gnc_numeric reconciled_baln;
00450 
00451    if (!be || !acc) return;
00452    ENTER("be=%p", be);
00453 
00454    /* setup what we will match for */
00455    chk.date_end = as_of_date;
00456 
00457    com = xaccAccountGetCommodity(acc);
00458    if (!com)
00459    {
00460         PERR("account %s has no commodity",
00461              guid_to_string (xaccAccountGetGUID (acc)));
00462         return;
00463    }
00464 
00465    chk.commodity = gnc_commodity_get_unique_name(com);
00466    chk.account_guid = xaccAccountGetGUID (acc);
00467    chk.balance = 0;
00468    chk.cleared_balance = 0;
00469    chk.reconciled_balance = 0;
00470 
00471    /* get the checkpoint */
00472    pgendAccountGetCheckpoint (be, &chk);
00473 
00474    b = chk.balance;
00475    cl_b = chk.cleared_balance;
00476    rec_b = chk.reconciled_balance;
00477    deno = gnc_commodity_get_fraction (com);
00478 
00479    {
00480       char buf[80];
00481       gnc_timespec_to_iso8601_buff (chk.date_start, buf);
00482       DEBUG("%s balance to %s baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT " clr=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT " rcn=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, 
00483             xaccAccountGetDescription (acc), buf,
00484             b, deno, cl_b, deno, rec_b, deno);
00485    }
00486 
00487    /* add up loose entries since the checkpoint */
00488    pgendAccountGetPartialBalance (be, &chk);
00489 
00490    b += chk.balance;
00491    cl_b += chk.cleared_balance;
00492    rec_b += chk.reconciled_balance;
00493 
00494    /* set the account balances */
00495    baln = gnc_numeric_create (b, deno);
00496    cleared_baln = gnc_numeric_create (cl_b, deno);
00497    reconciled_baln = gnc_numeric_create (rec_b, deno);
00498 
00499    g_object_set(acc,
00500                 "start-balance", &baln,
00501                 "start-cleared-balance", &cleared_baln,
00502                 "start-reconcoled-balance", &reconciled_baln,
00503                 NULL);
00504 
00505    {
00506         char buf[80];
00507         gnc_timespec_to_iso8601_buff (as_of_date, buf);
00508         LEAVE("be=%p %s %s baln=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT " clr=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT " rcn=%" G_GINT64_FORMAT "/%" G_GINT64_FORMAT, be, 
00509               xaccAccountGetDescription (acc), buf,
00510               b, deno, cl_b, deno, rec_b, deno);
00511    }
00512 }
00513 
00514 /* ============================================================= */
00515 /* get checkpoint value for all accounts */
00516 
00517 void
00518 pgendAccountTreeGetAllBalances (PGBackend *be, Account *root, 
00519                           Timespec as_of_date)
00520 {
00521    GList *acclist, *node;
00522 
00523    if (!be || !root) return;
00524    ENTER("be=%p", be);
00525 
00526    /* loop over all accounts */
00527    acclist = gnc_account_get_descendants (root);
00528    for (node=acclist; node; node=node->next)
00529    {
00530       Account *acc = (Account *) node->data;
00531       pgendAccountGetBalance (be, acc, as_of_date);
00532    }
00533 
00534    g_list_free (acclist);
00535    LEAVE("be=%p", be);
00536 }
00537 
00538 /* ======================== END OF FILE ======================== */

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