From 30837b6bb72862d235d7a4c1c6991972e16ccdcd Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Sat, 4 Apr 1998 08:50:50 +0000 Subject: [PATCH] redesign for atomic operations git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@773 57a11ea4-9604-0410-9ed3-97b8803252fd --- src/engine/Transaction.c | 220 +++++++++++++++----------------------- src/engine/Transaction.h | 61 +++-------- src/engine/TransactionP.h | 60 +++++++++-- 3 files changed, 156 insertions(+), 185 deletions(-) diff --git a/src/engine/Transaction.c b/src/engine/Transaction.c index 84cc5d4c85..08a2b8cb0e 100644 --- a/src/engine/Transaction.c +++ b/src/engine/Transaction.c @@ -23,6 +23,7 @@ * Huntington Beach, CA 92648-4632 * \********************************************************************/ +#include #include #include "config.h" @@ -94,20 +95,8 @@ xaccMallocSplit( void ) void xaccFreeSplit( Split *split ) { - Transaction *trans; - if (!split) return; - /* free a split only if it is not claimed - * by any accounts. */ - if (split->acc) return; - - /* free the split only if its not a source split */ - trans = split->parent; - if (&(trans->source_split) == split) return; - - xaccTransRemoveSplit (trans, split); - free(split->memo); free(split->action); @@ -132,12 +121,10 @@ xaccFreeSplit( Split *split ) static void MarkChanged (Transaction *trans) { - MARK_SPLIT (&(trans->source_split)); - - if (trans->dest_splits) { + if (trans->splits) { int i=0; - while (trans->dest_splits[i]) { - MARK_SPLIT (trans->dest_splits[i]); + while (trans->splits[i]) { + MARK_SPLIT (trans->splits[i]); i++; } } @@ -244,11 +231,8 @@ xaccInitTransaction( Transaction * trans ) trans->num = strdup(""); trans->description = strdup(""); - trans->dest_splits = (Split **) _malloc (sizeof (Split *)); - trans->dest_splits[0] = NULL; - - xaccInitSplit ( &(trans->source_split)); - trans->source_split.parent = trans; + trans->splits = (Split **) _malloc (sizeof (Split *)); + trans->splits[0] = NULL; /* create at least one destination split */ dsplit = xaccMallocSplit (); @@ -284,38 +268,25 @@ xaccFreeTransaction( Transaction *trans ) /* free a transaction only if it is not claimed * by any accounts. */ - if (trans->source_split.acc) return; i = 0; - s = trans->dest_splits[i]; + s = trans->splits[i]; while (s) { if (s->acc) return; i++; - s = trans->dest_splits[i]; + s = trans->splits[i]; } /* free up the destination splits */ i = 0; - s = trans->dest_splits[i]; + s = trans->splits[i]; while (s) { xaccFreeSplit (s); i++; - s = trans->dest_splits[i]; + s = trans->splits[i]; } - _free (trans->dest_splits); - - - /* free up the source-split related stuff. */ - free(trans->source_split.memo); - free(trans->source_split.action); - - /* just in case someone looks up freed memory ... */ - trans->source_split.memo = 0x0; - trans->source_split.reconciled = NREC; - trans->source_split.damount = 0.0; - trans->source_split.share_price = 1.0; - trans->source_split.parent = NULL; + _free (trans->splits); /* free up transaction strings */ free(trans->num); @@ -357,7 +328,7 @@ xaccTransCommitEdit (Transaction *trans) void xaccTransRebalance (Transaction * trans) { - xaccSplitRebalance (&(trans->source_split)); + xaccSplitRebalance (trans->splits[0]); } void @@ -368,31 +339,33 @@ xaccSplitRebalance (Split *split) int i = 0; double value = 0.0; - trans = split->parent; + assert (trans); + assert (trans->splits); + assert (trans->splits[0]); - if (&(trans->source_split) == split) { + if (split == trans->splits[0]) { /* The indicated split is the source split. * Pick a destination split (by default, * the first destination split), and force * the total on it. */ - s = trans->dest_splits[0]; + s = trans->splits[1]; if (s) { /* first, add the source split */ value = split->share_price * split->damount; /* now add in the sum of the destination splits */ - i = 0; + i = 1; while (s) { value += s->share_price * s->damount; i++; - s = trans->dest_splits[i]; + s = trans->splits[i]; } /* subtract the first destination split */ - s = trans->dest_splits[0]; + s = trans->splits[1]; value -= (s->share_price) * (s->damount); /* the new value of the destination split @@ -409,6 +382,8 @@ xaccSplitRebalance (Split *split) * to be created. */ +/* hack alert -- I think this is broken */ +#ifdef HACK_ALERT if (force_double_entry) { value = split->share_price * split->damount; @@ -426,23 +401,25 @@ xaccSplitRebalance (Split *split) xaccAccountInsertSplit (split->acc, s); MARK_SPLIT (s); } +#endif + } } else { /* The indicated split is a destination split. - * Compute grand total of all distination splits, + * Compute grand total of all destination splits, * and force the source split to blanace. */ - i = 0; - s = trans->dest_splits[i]; + i = 1; + s = trans->splits[i]; value = 0.0; while (s) { value += s->share_price * s->damount; i++; - s = trans->dest_splits[i]; + s = trans->splits[i]; } - s = &(trans->source_split); + s = trans->splits[0]; s -> damount = - (value / (s->share_price)); MARK_SPLIT (s); } @@ -470,15 +447,15 @@ xaccTransAppendSplit (Transaction *trans, Split *split) /* first, insert the split into the array */ split->parent = trans; - num = xaccCountSplits (trans->dest_splits); + num = xaccCountSplits (trans->splits); - oldarray = trans->dest_splits; - trans->dest_splits = (Split **) _malloc ((num+2)*sizeof(Split *)); + oldarray = trans->splits; + trans->splits = (Split **) _malloc ((num+2)*sizeof(Split *)); for (i=0; idest_splits)[i] = oldarray[i]; + (trans->splits)[i] = oldarray[i]; } - trans->dest_splits[num] = split; - trans->dest_splits[num+1] = NULL; + trans->splits[num] = split; + trans->splits[num+1] = NULL; if (oldarray) _free (oldarray); @@ -499,15 +476,15 @@ xaccTransRemoveSplit (Transaction *trans, Split *split) if (!trans) return; split->parent = NULL; - s = trans->dest_splits[0]; + s = trans->splits[0]; while (s) { - trans->dest_splits[i] = trans->dest_splits[n]; + trans->splits[i] = trans->splits[n]; if (split == s) { i--; } i++; n++; - s = trans->dest_splits[n]; + s = trans->splits[n]; } - trans->dest_splits[i] = NULL; + trans->splits[i] = NULL; /* force double entry to always be consistent */ xaccTransRebalance (trans); @@ -659,6 +636,7 @@ xaccTransSetDate (Transaction *trans, int day, int mon, int year) { Split *split; Account *acc; + int i=0; trans->date.year = year; trans->date.month = mon; @@ -672,24 +650,18 @@ xaccTransSetDate (Transaction *trans, int day, int mon, int year) * order. */ - split = &(trans->source_split); - acc = (Account *) split->acc; - xaccAccountRemoveSplit (acc, split); - xaccAccountInsertSplit (acc, split); - xaccRecomputeBalance (acc); + assert (trans->splits); - if (trans->dest_splits) { - int i=0; - split = trans->dest_splits[i]; - while (split) { - acc = (Account *) split->acc; - xaccAccountRemoveSplit (acc, split); - xaccAccountInsertSplit (acc, split); - xaccRecomputeBalance (acc); + i=0; + split = trans->splits[i]; + while (split) { + acc = (Account *) split->acc; + xaccAccountRemoveSplit (acc, split); + xaccAccountInsertSplit (acc, split); + xaccRecomputeBalance (acc); - i++; - split = trans->dest_splits[i]; - } + i++; + split = trans->splits[i]; } } @@ -733,67 +705,55 @@ xaccTransSetDescription (Transaction *trans, const char *desc) MarkChanged (trans); } -void -xaccTransSetMemo (Transaction *trans, const char *memo) -{ - char * tmp = strdup (memo); - if (trans->source_split.memo) free (trans->source_split.memo); - trans->source_split.memo = tmp; - MARK_SPLIT (&(trans->source_split)); +#define SET_TRANS_FIELD(trans,field,value) \ +{ \ + if (!trans) return; \ + \ + /* the engine *must* always be internally consistent */ \ + assert (trans->splits); \ + \ + if (force_double_entry) { \ + assert (trans->splits[0]); \ + assert (trans->splits[1]); \ + } \ + \ + if (0x0 != trans->splits[0]) { \ + char * tmp = strdup (value); \ + free (trans->splits[0]->field); \ + trans->splits[0]->field = tmp; \ + MARK_SPLIT (trans->splits[0]); \ + \ + /* if there are just two splits, then keep them in sync. */\ + if (0x0 != trans->splits[1]) { \ + if (0x0 == trans->splits[2]) { \ + free (trans->splits[1]->field); \ + trans->splits[1]->field = strdup (tmp); \ + MARK_SPLIT (trans->splits[1]); \ + } \ + } \ + } \ +} - /* if there is only one split, then keep memos in sync. */ - if (trans->dest_splits) { - if (0x0 != trans->dest_splits[0]) { - if (0x0 == trans->dest_splits[1]) { - free (trans->dest_splits[0]->memo); - trans->dest_splits[0]->memo = strdup (tmp); - MARK_SPLIT (trans->dest_splits[0]); - } - } - } +void +xaccTransSetMemo (Transaction *trans, const char *mimeo) +{ + SET_TRANS_FIELD (trans, memo, mimeo); } void xaccTransSetAction (Transaction *trans, const char *actn) { - char * tmp = strdup (actn); - - if (trans->source_split.action) free (trans->source_split.action); - trans->source_split.action = tmp; - MARK_SPLIT (&(trans->source_split)); - - /* if there is only one split, then keep action in sync. */ - if (trans->dest_splits) { - if (0x0 != trans->dest_splits[0]) { - if (0x0 == trans->dest_splits[1]) { - free (trans->dest_splits[0]->action); - trans->dest_splits[0]->action = strdup (tmp); - MARK_SPLIT (trans->dest_splits[0]); - } - } - } -} - -void -xaccTransSetReconcile (Transaction *trans, char recn) -{ - trans->source_split.reconciled = recn; - MARK_SPLIT (&(trans->source_split)); + SET_TRANS_FIELD (trans, action, actn); } /********************************************************************\ \********************************************************************/ -Split * -xaccTransGetSourceSplit (Transaction *trans) -{ - return (&(trans->source_split)); -} Split * -xaccTransGetDestSplit (Transaction *trans, int i) +xaccTransGetSplit (Transaction *trans, int i) { - if (trans->dest_splits) { - return (trans->dest_splits[i]); + if (trans->splits) { + return (trans->splits[i]); } return NULL; } @@ -827,13 +787,7 @@ xaccTransGetDateStr (Transaction *trans) int xaccTransCountSplits (Transaction *trans) { - return (xaccCountSplits (trans->dest_splits)); -} - -int -xaccTransIsSource (Transaction *trans, Split *split) -{ - return (split == &(trans->source_split)); + return (xaccCountSplits (trans->splits)); } /********************************************************************\ diff --git a/src/engine/Transaction.h b/src/engine/Transaction.h index 3e89f2bab8..8efebe5eb2 100644 --- a/src/engine/Transaction.h +++ b/src/engine/Transaction.h @@ -64,10 +64,6 @@ typedef struct _transaction Transaction; Transaction * xaccMallocTransaction (void); /* mallocs and inits */ void xaccInitTransaction (Transaction *);/* clears a trans struct */ -/* freeTransaction only does so if the transaction is not part of an - * account. (i.e. if none of the member splits are in an account). */ -void xaccFreeTransaction (Transaction *); - void xaccTransBeginEdit (Transaction *); void xaccTransCommitEdit (Transaction *); @@ -81,64 +77,39 @@ void xaccTransSetNum (Transaction *, const char *); void xaccTransSetDescription (Transaction *, const char *); void xaccTransSetMemo (Transaction *, const char *); void xaccTransSetAction (Transaction *, const char *); -void xaccTransSetReconcile (Transaction *, char); void xaccTransAppendSplit (Transaction *, Split *); -void xaccTransRemoveSplit (Transaction *, Split *); /* - * HACK ALERT *** this algorithm is wrong. Needs fixing. - * The xaccSplitRebalance() routine is an important routine for - * maintaining and ensuring that double-entries balance properly. - * This routine forces the sum-total of the values of all the - * splits in a transaction to total up to exactly zero. + * The xaccSplitDestroy() method will update its parent account and + * transaction in a consistent maner, resulting in the complete + * unlinking of the split, and the freeing of it's associated memory. + * The goal of this routine is to perform the removal and destruction + * of the split in an atomic fashion, with no chance of accidentally + * leaving the accounting structure out-of-balance or otherwise + * inconsistent. * - * It is worthwhile to understand the algorithm that this routine - * uses to acheive balance. It goes like this: - * If the indicated split is a destination split, then the - * total value of the destination splits is computed, and the - * value of the source split is adjusted to be minus this amount. - * (the share price of the source split is not changed). - * If the indicated split is the source split, then the value - * of the very first destination split is adjusted so that - * the blanace is zero. If there is not destination split, - * one of two outcomes are possible, depending on whether - * "forced_double_entry" is enabled or disabled. - * (1) if forced-double-entry is disabled, the fact that - * the destination is missing is ignored. - * (2) if force-double-entry is enabled, then a destination - * split that exactly mirrors the ource split is created, - * and credited to the same account as the source split. - * Hopefully, the user will notice this, and reparent the - * destination split properly. - * - * The xaccTransRebalance() routine merely calls xaccSplitRebalance() - * on the source split. + * If the parent transaction of the split has three or more splits + * in it, then only this one split is unlinked. If the parent + * transaction has only two splits in it (and thus, this is one of + * them), then both splits and the transaction are destroyed. */ - -void xaccSplitRebalance (Split *); -void xaccTransRebalance (Transaction *); +void xaccSplitDestroy (Split *); /* ------------- gets --------------- */ -/* return pointer to the source split */ -Split * xaccTransGetSourceSplit (Transaction *); -Split * xaccTransGetDestSplit (Transaction *trans, int i); +/* return pointer to each of the splits */ +Split * xaccTransGetSplit (Transaction *trans, int i); char * xaccTransGetNum (Transaction *); -char * xaccTransGetDescription (Transaction *trans); +char * xaccTransGetDescription (Transaction *); Date * xaccTransGetDate (Transaction *); char * xaccTransGetDateStr (Transaction *); -/* return the number of destination splits */ +/* return the number of splits */ int xaccTransCountSplits (Transaction *trans); -/* returns non-zero value if split is source split */ -int xaccTransIsSource (Transaction *, Split *); - - Split * xaccMallocSplit (void); void xaccInitSplit (Split *); /* clears a split struct */ -void xaccFreeSplit (Split *); /* frees memory */ int xaccCountSplits (Split **sarray); void xaccSplitSetMemo (Split *, const char *); diff --git a/src/engine/TransactionP.h b/src/engine/TransactionP.h index c3308a6b6b..8454c0dba3 100644 --- a/src/engine/TransactionP.h +++ b/src/engine/TransactionP.h @@ -8,6 +8,13 @@ * outside of the engine should *never* access any of the structure * members directly. * + * Note that this header file also defines prototypes for various + * routines that perform sub-atomic updates of the accounting + * structures. If these routines are not used properly, they + * can result in inconsistent, unbalanced accounting structures. + * In other words, thier use is dangerous, and thier use outside + * of the scope of the engine is forbidden. + * */ /********************************************************************\ @@ -44,14 +51,17 @@ /** STRUCTS *********************************************************/ -/* The debit & credit pointers are used to implement a double-entry - * accounting system. Basically, the idea with double entry is that - * there is always an account that is debited, and another that is - * credited. These two pointers identify the two accounts. +/* + * Double-entry is forced by having at least two splits in every + * transaction. By convention, (and only by convention, not by + * any inate requirement), the first split is considered to be + * the source split or the crediting split, and the others are + * the destination, or debiting splits. The grand total of all + * of the splits must always be kept zero. */ /* A split transaction is one which shows up as a credit (or debit) in - * one account, and peices of it show up as debits (or credits) in other + * one account, and pieces of it show up as debits (or credits) in other * accounts. Thus, a single credit-card transaction might be split * between "dining", "tips" and "taxes" categories. */ @@ -85,8 +95,7 @@ struct _transaction Date date; /* transaction date */ char * description; - Split source_split; /* source (creidted) account */ - Split **dest_splits; /* list of splits, null terminated */ + Split **splits; /* list of splits, null terminated */ char write_flag; /* used only during file IO */ @@ -96,4 +105,41 @@ struct _transaction }; +/* freeTransaction only does so if the transaction is not part of an + * account. (i.e. if none of the member splits are in an account). */ +void xaccFreeTransaction (Transaction *); + +void xaccFreeSplit (Split *); /* frees memory */ + + +/* + * The xaccSplitRebalance() routine is an important routine for + * maintaining and ensuring that double-entries balance properly. + * This routine forces the sum-total of the values of all the + * splits in a transaction to total up to exactly zero. + * + * It is worthwhile to understand the algorithm that this routine + * uses to acheive balance. It goes like this: + * If the indicated split is a destination split (i.e. is not + * the first split), then the total value of the destination + * splits is computed, and the value of the source split (ie. + * the first split) is adjusted to be minus this amount. + * (the share price of the source split is not changed). + * If the indicated split is the source split, then the value + * of the very first destination split is adjusted so that + * the blanace is zero. If there is not destination split, + * one of two outcomes are possible, depending on whether + * "forced_double_entry" is enabled or disabled. + * (1) if forced-double-entry is disabled, the fact that + * the destination is missing is ignored. + * (2) if force-double-entry is enabled, then a destination + * split that exactly mirrors the source split is created, + * and credited to the same account as the source split. + * Hopefully, the user will notice this, and reparent the + * destination split properly. + */ + +void xaccSplitRebalance (Split *); + + #endif /* __XACC_TRANSACTION_P_H__ */