redesign for atomic operations

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@773 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Linas Vepstas 1998-04-04 08:50:50 +00:00
parent cb34c2bc00
commit 30837b6bb7
3 changed files with 156 additions and 185 deletions

View File

@ -23,6 +23,7 @@
* Huntington Beach, CA 92648-4632 *
\********************************************************************/
#include <assert.h>
#include <string.h>
#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; i<num; i++) {
(trans->dest_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));
}
/********************************************************************\

View File

@ -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 *);

View File

@ -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__ */