mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
restructure to improve multi-user performance
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@5037 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
parent
27fa0eac94
commit
fc06934106
@ -291,77 +291,125 @@ static const char *table_drop_str =
|
|||||||
static gpointer
|
static gpointer
|
||||||
query_cb (PGBackend *be, PGresult *result, int j, gpointer data)
|
query_cb (PGBackend *be, PGresult *result, int j, gpointer data)
|
||||||
{
|
{
|
||||||
GHashTable *xaction_hash = (GHashTable *) data;
|
GList *xaction_list = (GList *) data;
|
||||||
GUID *trans_guid;
|
GUID trans_guid;
|
||||||
Transaction *trans;
|
Transaction *trans;
|
||||||
|
gnc_commodity *currency;
|
||||||
|
Timespec ts;
|
||||||
|
|
||||||
/* find the transaction this goes into */
|
/* find the transaction this goes into */
|
||||||
trans_guid = xaccGUIDMalloc();
|
trans_guid = nullguid; /* just in case the read fails ... */
|
||||||
*trans_guid = nullguid; /* just in case the read fails ... */
|
string_to_guid (DB_GET_VAL("transGUID",j), &trans_guid);
|
||||||
string_to_guid (DB_GET_VAL("transGUID",j), trans_guid);
|
|
||||||
|
|
||||||
/* use markers to avoid redundant traversals of transactions we've
|
/* use markers to avoid redundant traversals of transactions we've
|
||||||
* already checked recently. */
|
* already checked recently. */
|
||||||
trans = xaccTransLookup (trans_guid);
|
trans = xaccTransLookup (&trans_guid);
|
||||||
if (NULL != trans && 0 != trans->marker)
|
if (NULL != trans)
|
||||||
{
|
{
|
||||||
xaccGUIDFree (trans_guid);
|
if (0 != trans->marker)
|
||||||
return xaction_hash;
|
{
|
||||||
|
return xaction_list;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gint32 db_version, cache_version;
|
||||||
|
db_version = atoi (DB_GET_VAL("version",j));
|
||||||
|
cache_version = xaccTransGetVersion (trans);
|
||||||
|
if (db_version <= cache_version) {
|
||||||
|
return xaction_list;
|
||||||
|
}
|
||||||
|
xaccTransBeginEdit (trans);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trans = xaccMallocTransaction();
|
||||||
|
xaccTransBeginEdit (trans);
|
||||||
|
xaccTransSetGUID (trans, &trans_guid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* don't put transaction into the list more than once ... */
|
xaccTransSetNum (trans, DB_GET_VAL("num",j));
|
||||||
if (g_hash_table_lookup (xaction_hash, trans_guid))
|
xaccTransSetDescription (trans, DB_GET_VAL("description",j));
|
||||||
{
|
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_posted",j));
|
||||||
xaccGUIDFree (trans_guid);
|
xaccTransSetDatePostedTS (trans, &ts);
|
||||||
return xaction_hash;
|
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_entered",j));
|
||||||
}
|
xaccTransSetDateEnteredTS (trans, &ts);
|
||||||
g_hash_table_insert (xaction_hash, trans_guid, 0);
|
xaccTransSetVersion (trans, atoi(DB_GET_VAL("version",j)));
|
||||||
|
|
||||||
return xaction_hash;
|
currency = gnc_string_to_commodity (DB_GET_VAL("currency",j));
|
||||||
|
xaccTransSetCurrency (trans, currency);
|
||||||
|
|
||||||
|
trans->marker = 1;
|
||||||
|
xaction_list = g_list_prepend (xaction_list, trans);
|
||||||
|
|
||||||
|
return xaction_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct _ctxt {
|
|
||||||
PGBackend *be;
|
|
||||||
GList *acct_list;
|
|
||||||
} ctxt;
|
|
||||||
|
|
||||||
typedef struct acct_earliest {
|
typedef struct acct_earliest {
|
||||||
Account *acct;
|
Account *acct;
|
||||||
Timespec ts;
|
Timespec ts;
|
||||||
} AcctEarliest;
|
} AcctEarliest;
|
||||||
|
|
||||||
|
static int ncalls = 0;
|
||||||
|
|
||||||
static void
|
static void
|
||||||
for_each_txn (gpointer key, gpointer value, gpointer user_data)
|
pgendFillOutToCheckpoint (PGBackend *be, const char *query_string)
|
||||||
{
|
{
|
||||||
GUID *trans_guid = (GUID *)key;
|
int call_count = ncalls;
|
||||||
ctxt *ct = (ctxt *) user_data;
|
int nact=0;
|
||||||
PGBackend *be = ct->be;
|
GList *xaction_list = NULL;
|
||||||
GList *anode, *acct_list = ct->acct_list;
|
GList *node, *anode, *acct_list = NULL;
|
||||||
|
|
||||||
Transaction *trans;
|
ENTER (" ");
|
||||||
int engine_data_is_newer;
|
if (!be) return;
|
||||||
|
|
||||||
/* use markers to avoid redundant traversals of transactions we've
|
if (0 == ncalls) {
|
||||||
* already checked recently. */
|
START_CLOCK (9, "starting at level 0");
|
||||||
trans = xaccTransLookup (trans_guid);
|
|
||||||
if (NULL == trans || 0 == trans->marker)
|
|
||||||
{
|
|
||||||
engine_data_is_newer = pgendCopyTransactionToEngine (be, trans_guid);
|
|
||||||
trans = xaccTransLookup (trans_guid);
|
|
||||||
trans->marker = 1;
|
|
||||||
PINFO ("copy result=%d", engine_data_is_newer);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
PINFO ("avoided scan");
|
REPORT_CLOCK (9, "call count %d", call_count);
|
||||||
engine_data_is_newer = 1;
|
}
|
||||||
|
ncalls ++;
|
||||||
|
|
||||||
|
SEND_QUERY (be, query_string, );
|
||||||
|
xaction_list = pgendGetResults (be, query_cb, NULL);
|
||||||
|
REPORT_CLOCK (9, "fetched results at call %d", call_count);
|
||||||
|
|
||||||
|
/* restore the splits for these transactions */
|
||||||
|
for (node=xaction_list; node; node=node->next)
|
||||||
|
{
|
||||||
|
Transaction *trans = (Transaction *) node->data;
|
||||||
|
GList *engine_splits, *snode;
|
||||||
|
pgendCopySplitsToEngine (be, trans);
|
||||||
|
xaccTransCommitEdit (trans);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* if we restored this transaction from the db, scan over the accounts
|
#if 0
|
||||||
* it affects and see how far back the data goes.
|
/* hack alert !! deal with kvp later -- huge sucking sound ! */
|
||||||
*/
|
/* restore any kvp data associated with the transaction and splits */
|
||||||
if (0 > engine_data_is_newer)
|
for (node=xaction_list; node; node=node->next)
|
||||||
{
|
{
|
||||||
|
Transaction *trans = (Transaction *) node->data;
|
||||||
|
GList *engine_splits, *snode;
|
||||||
|
|
||||||
|
trans->kvp_data = pgendKVPFetch (be, &(trans->guid), trans->kvp_data);
|
||||||
|
|
||||||
|
engine_splits = xaccTransGetSplitList(trans);
|
||||||
|
for (snode = engine_splits; snode; snode=snode->next)
|
||||||
|
{
|
||||||
|
Split *s = snode->data;
|
||||||
|
s->kvp_data = pgendKVPFetch (be, &(s->guid), s->kvp_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
xaccTransCommitEdit (trans);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* run the fill-out algorithm */
|
||||||
|
for (node=xaction_list; node; node=node->next)
|
||||||
|
{
|
||||||
|
Transaction *trans = (Transaction *) node->data;
|
||||||
GList *split_list, *snode;
|
GList *split_list, *snode;
|
||||||
Timespec ts;
|
Timespec ts;
|
||||||
|
|
||||||
@ -379,7 +427,7 @@ for_each_txn (gpointer key, gpointer value, gpointer user_data)
|
|||||||
Account *acc = xaccSplitGetAccount (s);
|
Account *acc = xaccSplitGetAccount (s);
|
||||||
|
|
||||||
/* lets see if we have a record of this account already */
|
/* lets see if we have a record of this account already */
|
||||||
for (anode = ct->acct_list; anode; anode = anode->next)
|
for (anode = acct_list; anode; anode = anode->next)
|
||||||
{
|
{
|
||||||
AcctEarliest * ae = (AcctEarliest *) anode->data;
|
AcctEarliest * ae = (AcctEarliest *) anode->data;
|
||||||
if (ae->acct == acc)
|
if (ae->acct == acc)
|
||||||
@ -399,55 +447,12 @@ for_each_txn (gpointer key, gpointer value, gpointer user_data)
|
|||||||
AcctEarliest * ae = g_new (AcctEarliest, 1);
|
AcctEarliest * ae = g_new (AcctEarliest, 1);
|
||||||
ae->acct = acc;
|
ae->acct = acc;
|
||||||
ae->ts = ts;
|
ae->ts = ts;
|
||||||
ct->acct_list = g_list_prepend (ct->acct_list, ae);
|
acct_list = g_list_prepend (acct_list, ae);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
g_list_free (xaction_list);
|
||||||
|
|
||||||
static gboolean
|
|
||||||
for_each_remove (gpointer key, gpointer value, gpointer user_data)
|
|
||||||
{
|
|
||||||
GUID *trans_guid = (GUID *)key;
|
|
||||||
xaccGUIDFree (trans_guid);
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int ncalls = 0;
|
|
||||||
|
|
||||||
static void
|
|
||||||
pgendFillOutToCheckpoint (PGBackend *be, const char *query_string)
|
|
||||||
{
|
|
||||||
int call_count = ncalls;
|
|
||||||
int nact=0;
|
|
||||||
GHashTable *xaction_hash = NULL;
|
|
||||||
GList *node, *anode, *acct_list = NULL;
|
|
||||||
ctxt ct;
|
|
||||||
|
|
||||||
ENTER (" ");
|
|
||||||
if (!be) return;
|
|
||||||
|
|
||||||
if (0 == ncalls) {
|
|
||||||
START_CLOCK (9, "starting at level 0");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
REPORT_CLOCK (9, "call count %d", call_count);
|
|
||||||
}
|
|
||||||
ncalls ++;
|
|
||||||
|
|
||||||
SEND_QUERY (be, query_string, );
|
|
||||||
xaction_hash = g_hash_table_new (g_direct_hash, (GCompareFunc) guid_equal);
|
|
||||||
pgendGetResults (be, query_cb, xaction_hash);
|
|
||||||
REPORT_CLOCK (9, "fetched results at call %d", call_count);
|
|
||||||
|
|
||||||
/* restore the transactions */
|
|
||||||
ct.be = be;
|
|
||||||
ct.acct_list = NULL;
|
|
||||||
g_hash_table_foreach (xaction_hash, for_each_txn, &ct);
|
|
||||||
g_hash_table_foreach_remove (xaction_hash, for_each_remove, NULL);
|
|
||||||
g_hash_table_destroy (xaction_hash);
|
|
||||||
acct_list = ct.acct_list;
|
|
||||||
|
|
||||||
REPORT_CLOCK (9, "done gathering at call %d", call_count);
|
REPORT_CLOCK (9, "done gathering at call %d", call_count);
|
||||||
if (NULL == acct_list) return;
|
if (NULL == acct_list) return;
|
||||||
@ -468,8 +473,8 @@ pgendFillOutToCheckpoint (PGBackend *be, const char *query_string)
|
|||||||
* GetBalance goes to less-then-or-equal-to because of the BETWEEN
|
* GetBalance goes to less-then-or-equal-to because of the BETWEEN
|
||||||
* that appears in the gncSubTotalBalance sql function. */
|
* that appears in the gncSubTotalBalance sql function. */
|
||||||
p = be->buff; *p = 0;
|
p = be->buff; *p = 0;
|
||||||
p = stpcpy (p, "SELECT DISTINCT gncEntry.transGuid from gncEntry, gncTransaction WHERE "
|
p = stpcpy (p, "SELECT DISTINCT gncTransaction.* from gncEntry, gncTransaction WHERE "
|
||||||
" gncEntry.transGuid = gncTransaction.transGuid AND accountGuid='");
|
" gncEntry.transGuid = gncTransaction.transGuid AND gncEntry.accountGuid='");
|
||||||
p = guid_to_string_buff(xaccAccountGetGUID(ae->acct), p);
|
p = guid_to_string_buff(xaccAccountGetGUID(ae->acct), p);
|
||||||
p = stpcpy (p, "' AND gncTransaction.date_posted > '");
|
p = stpcpy (p, "' AND gncTransaction.date_posted > '");
|
||||||
p = gnc_timespec_to_iso8601_buff (ae->ts, p);
|
p = gnc_timespec_to_iso8601_buff (ae->ts, p);
|
||||||
|
@ -218,7 +218,7 @@ In 'single-user-update' mode, data loads from the sql database
|
|||||||
should be 1.5x faster than comparable loads from the XML flat file,
|
should be 1.5x faster than comparable loads from the XML flat file,
|
||||||
at least for medium datasets (measured at 3.5 seconds on a 700MHz
|
at least for medium datasets (measured at 3.5 seconds on a 700MHz
|
||||||
Athalon a dataset with 3K transactions and 150 accounts, vs. 4.8
|
Athalon a dataset with 3K transactions and 150 accounts, vs. 4.8
|
||||||
seconds loading from file).
|
seconds loading from file; postgres version 7.1.2).
|
||||||
|
|
||||||
Hitting the 'save' button is a no-op and takes no cpu cycles.
|
Hitting the 'save' button is a no-op and takes no cpu cycles.
|
||||||
(date is saved as its modified, so a global save is not needed).
|
(date is saved as its modified, so a global save is not needed).
|
||||||
@ -228,6 +228,10 @@ issued. The 'vacuum' reclaims storage, and 'analyze' does some
|
|||||||
performance tuning. Doing this regularly improves performance
|
performance tuning. Doing this regularly improves performance
|
||||||
about 20% in the cases I looked at.
|
about 20% in the cases I looked at.
|
||||||
|
|
||||||
|
---------
|
||||||
|
Accounts are restored roughly at the rate of 50-75 per second
|
||||||
|
(for above hardware/software config).
|
||||||
|
|
||||||
---------
|
---------
|
||||||
Performance in multi-user mode is still a can of worms, and will
|
Performance in multi-user mode is still a can of worms, and will
|
||||||
be a good bit slower for now. The working assumptions are that
|
be a good bit slower for now. The working assumptions are that
|
||||||
|
@ -560,7 +560,7 @@ sqlQuery_build (sqlQuery *sq, Query *q)
|
|||||||
/* reset the buffer pointers */
|
/* reset the buffer pointers */
|
||||||
sq->pq = sq->q_base;
|
sq->pq = sq->q_base;
|
||||||
sq->pq = stpcpy(sq->pq,
|
sq->pq = stpcpy(sq->pq,
|
||||||
"SELECT DISTINCT gncTransaction.transGuid ");
|
"SELECT DISTINCT gncTransaction.* ");
|
||||||
|
|
||||||
/* For SELECT DISTINCT, ORDER BY expressions must appear in target list */
|
/* For SELECT DISTINCT, ORDER BY expressions must appear in target list */
|
||||||
sq->pq = sql_sort_distinct (sq->pq, xaccQueryGetPrimarySortOrder(q));
|
sq->pq = sql_sort_distinct (sq->pq, xaccQueryGetPrimarySortOrder(q));
|
||||||
|
@ -199,7 +199,7 @@ pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans,
|
|||||||
|
|
||||||
/* If this trans is marked for deletetion, use the 'orig' values
|
/* If this trans is marked for deletetion, use the 'orig' values
|
||||||
* as the base for recording the audit. This wouldn't be normally
|
* as the base for recording the audit. This wouldn't be normally
|
||||||
* reqquired, except that otherwise one gets a trashed currency
|
* required, except that otherwise one gets a trashed currency
|
||||||
* value.
|
* value.
|
||||||
*/
|
*/
|
||||||
pgendStoreAuditTransaction (be, trans->orig, SQL_DELETE);
|
pgendStoreAuditTransaction (be, trans->orig, SQL_DELETE);
|
||||||
@ -340,160 +340,23 @@ pgendStoreAllTransactions (PGBackend *be, AccountGroup *grp)
|
|||||||
* probably be fixed.
|
* probably be fixed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int
|
void
|
||||||
pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
|
pgendCopySplitsToEngine (PGBackend *be, Transaction *trans)
|
||||||
{
|
{
|
||||||
char *pbuff;
|
char *pbuff;
|
||||||
Transaction *trans;
|
|
||||||
PGresult *result;
|
|
||||||
Account *acc, *previous_acc=NULL;
|
|
||||||
gboolean do_set_guid=FALSE;
|
|
||||||
int engine_data_is_newer = 0;
|
|
||||||
int i, j, nrows;
|
int i, j, nrows;
|
||||||
|
PGresult *result;
|
||||||
int save_state = 1;
|
int save_state = 1;
|
||||||
|
const GUID *trans_guid;
|
||||||
|
Account *acc, *previous_acc=NULL;
|
||||||
GList *node, *db_splits=NULL, *engine_splits, *delete_splits=NULL;
|
GList *node, *db_splits=NULL, *engine_splits, *delete_splits=NULL;
|
||||||
gnc_commodity *currency = NULL;
|
gnc_commodity *currency = NULL;
|
||||||
gint64 trans_frac = 0;
|
gint64 trans_frac = 0;
|
||||||
|
|
||||||
ENTER ("be=%p", be);
|
trans_guid = xaccTransGetGUID (trans);
|
||||||
if (!be || !trans_guid) return 0;
|
currency = xaccTransGetCurrency (trans);
|
||||||
|
|
||||||
/* disable callbacks into the backend, and events to GUI */
|
|
||||||
gnc_engine_suspend_events();
|
|
||||||
pgendDisable(be);
|
|
||||||
|
|
||||||
/* first, see if we already have such a transaction */
|
|
||||||
trans = xaccTransLookup (trans_guid);
|
|
||||||
if (!trans)
|
|
||||||
{
|
|
||||||
trans = xaccMallocTransaction();
|
|
||||||
do_set_guid=TRUE;
|
|
||||||
engine_data_is_newer = -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* save some performance, don't go to the backend if the data is recent. */
|
|
||||||
if (MAX_VERSION_AGE >= be->version_check - trans->version_check)
|
|
||||||
{
|
|
||||||
PINFO ("fresh data, skip check");
|
|
||||||
pgendEnable(be);
|
|
||||||
gnc_engine_resume_events();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build the sql query to get the transaction */
|
|
||||||
pbuff = be->buff;
|
|
||||||
pbuff[0] = 0;
|
|
||||||
pbuff = stpcpy (pbuff,
|
|
||||||
"SELECT * FROM gncTransaction WHERE transGuid='");
|
|
||||||
pbuff = guid_to_string_buff(trans_guid, pbuff);
|
|
||||||
pbuff = stpcpy (pbuff, "';");
|
|
||||||
|
|
||||||
SEND_QUERY (be,be->buff, 0);
|
|
||||||
i=0; nrows=0;
|
|
||||||
do {
|
|
||||||
GET_RESULTS (be->connection, result);
|
|
||||||
{
|
|
||||||
int jrows;
|
|
||||||
int ncols = PQnfields (result);
|
|
||||||
jrows = PQntuples (result);
|
|
||||||
nrows += jrows;
|
|
||||||
PINFO ("query result %d has %d rows and %d cols",
|
|
||||||
i, nrows, ncols);
|
|
||||||
|
|
||||||
j = 0;
|
|
||||||
if (0 == nrows)
|
|
||||||
{
|
|
||||||
PQclear (result);
|
|
||||||
/* I beleive its a programming error to get this case.
|
|
||||||
* Print a warning for now... */
|
|
||||||
PERR ("no such transaction in the database. This is unexpected ...\n");
|
|
||||||
xaccBackendSetError (&be->be, ERR_SQL_MISSING_DATA);
|
|
||||||
pgendEnable(be);
|
|
||||||
gnc_engine_resume_events();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (1 < nrows)
|
|
||||||
{
|
|
||||||
/* since the guid is primary key, this error is totally
|
|
||||||
* and completely impossible, theoretically ... */
|
|
||||||
PERR ("!!!!!!!!!!!SQL database is corrupt!!!!!!!\n"
|
|
||||||
"too many transactions with GUID=%s\n",
|
|
||||||
guid_to_string (trans_guid));
|
|
||||||
if (jrows != nrows) xaccTransCommitEdit (trans);
|
|
||||||
xaccBackendSetError (&be->be, ERR_BACKEND_DATA_CORRUPT);
|
|
||||||
pgendEnable(be);
|
|
||||||
gnc_engine_resume_events();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* First order of business is to determine whose data is
|
|
||||||
* newer: the engine cache, or the database. If the
|
|
||||||
* database has newer stuff, we update the engine. If the
|
|
||||||
* engine is equal or newer, we do nothing in this routine.
|
|
||||||
* Of course, we know the database has newer data if this
|
|
||||||
* transaction doesn't exist in the engine yet.
|
|
||||||
*/
|
|
||||||
if (!do_set_guid)
|
|
||||||
{
|
|
||||||
gint32 db_version, cache_version;
|
|
||||||
db_version = atoi (DB_GET_VAL("version",j));
|
|
||||||
cache_version = xaccTransGetVersion (trans);
|
|
||||||
if (db_version == cache_version) {
|
|
||||||
engine_data_is_newer = 0;
|
|
||||||
} else
|
|
||||||
if (db_version < cache_version) {
|
|
||||||
engine_data_is_newer = +1;
|
|
||||||
} else {
|
|
||||||
engine_data_is_newer = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* if the DB data is newer, copy it to engine */
|
|
||||||
if (0 > engine_data_is_newer)
|
|
||||||
{
|
|
||||||
Timespec ts;
|
|
||||||
|
|
||||||
xaccTransBeginEdit (trans);
|
|
||||||
if (do_set_guid) xaccTransSetGUID (trans, trans_guid);
|
|
||||||
xaccTransSetNum (trans, DB_GET_VAL("num",j));
|
|
||||||
xaccTransSetDescription (trans, DB_GET_VAL("description",j));
|
|
||||||
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_posted",j));
|
|
||||||
xaccTransSetDatePostedTS (trans, &ts);
|
|
||||||
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_entered",j));
|
|
||||||
xaccTransSetDateEnteredTS (trans, &ts);
|
|
||||||
xaccTransSetVersion (trans, atoi(DB_GET_VAL("version",j)));
|
|
||||||
|
|
||||||
currency = gnc_string_to_commodity (DB_GET_VAL("currency",j));
|
|
||||||
trans_frac = gnc_commodity_get_fraction (currency);
|
trans_frac = gnc_commodity_get_fraction (currency);
|
||||||
|
|
||||||
xaccTransSetCurrency
|
|
||||||
(trans, gnc_string_to_commodity (DB_GET_VAL("currency",j)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PQclear (result);
|
|
||||||
i++;
|
|
||||||
} while (result);
|
|
||||||
|
|
||||||
/* set timestamp as 'recent' for this data */
|
|
||||||
trans->version_check = be->version_check;
|
|
||||||
|
|
||||||
/* if engine data was newer, we are done */
|
|
||||||
if (0 <= engine_data_is_newer)
|
|
||||||
{
|
|
||||||
pgendEnable(be);
|
|
||||||
gnc_engine_resume_events();
|
|
||||||
return engine_data_is_newer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------- */
|
|
||||||
/* If we are here, then the sql database contains data that is
|
|
||||||
* newer than what we have in the engine. And so, below,
|
|
||||||
* we finish the job of yanking data out of the db.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* build the sql query the splits */
|
/* build the sql query the splits */
|
||||||
pbuff = be->buff;
|
pbuff = be->buff;
|
||||||
pbuff[0] = 0;
|
pbuff[0] = 0;
|
||||||
@ -502,7 +365,7 @@ pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
|
|||||||
pbuff = guid_to_string_buff(trans_guid, pbuff);
|
pbuff = guid_to_string_buff(trans_guid, pbuff);
|
||||||
pbuff = stpcpy (pbuff, "';");
|
pbuff = stpcpy (pbuff, "';");
|
||||||
|
|
||||||
SEND_QUERY (be,be->buff, 0);
|
SEND_QUERY (be,be->buff, );
|
||||||
i=0; nrows=0;
|
i=0; nrows=0;
|
||||||
do {
|
do {
|
||||||
GET_RESULTS (be->connection, result);
|
GET_RESULTS (be->connection, result);
|
||||||
@ -628,6 +491,156 @@ pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
|
|||||||
g_list_free (delete_splits);
|
g_list_free (delete_splits);
|
||||||
g_list_free (db_splits);
|
g_list_free (db_splits);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
|
||||||
|
{
|
||||||
|
char *pbuff;
|
||||||
|
Transaction *trans;
|
||||||
|
PGresult *result;
|
||||||
|
gboolean do_set_guid=FALSE;
|
||||||
|
int engine_data_is_newer = 0;
|
||||||
|
int i, j, nrows;
|
||||||
|
GList *node, *engine_splits;
|
||||||
|
|
||||||
|
ENTER ("be=%p", be);
|
||||||
|
if (!be || !trans_guid) return 0;
|
||||||
|
|
||||||
|
/* disable callbacks into the backend, and events to GUI */
|
||||||
|
gnc_engine_suspend_events();
|
||||||
|
pgendDisable(be);
|
||||||
|
|
||||||
|
/* first, see if we already have such a transaction */
|
||||||
|
trans = xaccTransLookup (trans_guid);
|
||||||
|
if (!trans)
|
||||||
|
{
|
||||||
|
trans = xaccMallocTransaction();
|
||||||
|
do_set_guid=TRUE;
|
||||||
|
engine_data_is_newer = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* save some performance, don't go to the backend if the data is recent. */
|
||||||
|
if (MAX_VERSION_AGE >= be->version_check - trans->version_check)
|
||||||
|
{
|
||||||
|
PINFO ("fresh data, skip check");
|
||||||
|
pgendEnable(be);
|
||||||
|
gnc_engine_resume_events();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* build the sql query to get the transaction */
|
||||||
|
pbuff = be->buff;
|
||||||
|
pbuff[0] = 0;
|
||||||
|
pbuff = stpcpy (pbuff,
|
||||||
|
"SELECT * FROM gncTransaction WHERE transGuid='");
|
||||||
|
pbuff = guid_to_string_buff(trans_guid, pbuff);
|
||||||
|
pbuff = stpcpy (pbuff, "';");
|
||||||
|
|
||||||
|
SEND_QUERY (be,be->buff, 0);
|
||||||
|
i=0; nrows=0;
|
||||||
|
do {
|
||||||
|
GET_RESULTS (be->connection, result);
|
||||||
|
{
|
||||||
|
int jrows;
|
||||||
|
int ncols = PQnfields (result);
|
||||||
|
jrows = PQntuples (result);
|
||||||
|
nrows += jrows;
|
||||||
|
PINFO ("query result %d has %d rows and %d cols",
|
||||||
|
i, nrows, ncols);
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
if (0 == nrows)
|
||||||
|
{
|
||||||
|
PQclear (result);
|
||||||
|
/* I beleive its a programming error to get this case.
|
||||||
|
* Print a warning for now... */
|
||||||
|
PERR ("no such transaction in the database. This is unexpected ...\n");
|
||||||
|
xaccBackendSetError (&be->be, ERR_SQL_MISSING_DATA);
|
||||||
|
pgendEnable(be);
|
||||||
|
gnc_engine_resume_events();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 < nrows)
|
||||||
|
{
|
||||||
|
/* since the guid is primary key, this error is totally
|
||||||
|
* and completely impossible, theoretically ... */
|
||||||
|
PERR ("!!!!!!!!!!!SQL database is corrupt!!!!!!!\n"
|
||||||
|
"too many transactions with GUID=%s\n",
|
||||||
|
guid_to_string (trans_guid));
|
||||||
|
if (jrows != nrows) xaccTransCommitEdit (trans);
|
||||||
|
xaccBackendSetError (&be->be, ERR_BACKEND_DATA_CORRUPT);
|
||||||
|
pgendEnable(be);
|
||||||
|
gnc_engine_resume_events();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* First order of business is to determine whose data is
|
||||||
|
* newer: the engine cache, or the database. If the
|
||||||
|
* database has newer stuff, we update the engine. If the
|
||||||
|
* engine is equal or newer, we do nothing in this routine.
|
||||||
|
* Of course, we know the database has newer data if this
|
||||||
|
* transaction doesn't exist in the engine yet.
|
||||||
|
*/
|
||||||
|
if (!do_set_guid)
|
||||||
|
{
|
||||||
|
gint32 db_version, cache_version;
|
||||||
|
db_version = atoi (DB_GET_VAL("version",j));
|
||||||
|
cache_version = xaccTransGetVersion (trans);
|
||||||
|
if (db_version == cache_version) {
|
||||||
|
engine_data_is_newer = 0;
|
||||||
|
} else
|
||||||
|
if (db_version < cache_version) {
|
||||||
|
engine_data_is_newer = +1;
|
||||||
|
} else {
|
||||||
|
engine_data_is_newer = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if the DB data is newer, copy it to engine */
|
||||||
|
if (0 > engine_data_is_newer)
|
||||||
|
{
|
||||||
|
Timespec ts;
|
||||||
|
gnc_commodity *currency;
|
||||||
|
|
||||||
|
xaccTransBeginEdit (trans);
|
||||||
|
if (do_set_guid) xaccTransSetGUID (trans, trans_guid);
|
||||||
|
xaccTransSetNum (trans, DB_GET_VAL("num",j));
|
||||||
|
xaccTransSetDescription (trans, DB_GET_VAL("description",j));
|
||||||
|
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_posted",j));
|
||||||
|
xaccTransSetDatePostedTS (trans, &ts);
|
||||||
|
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_entered",j));
|
||||||
|
xaccTransSetDateEnteredTS (trans, &ts);
|
||||||
|
xaccTransSetVersion (trans, atoi(DB_GET_VAL("version",j)));
|
||||||
|
currency = gnc_string_to_commodity (DB_GET_VAL("currency",j));
|
||||||
|
xaccTransSetCurrency (trans, currency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PQclear (result);
|
||||||
|
i++;
|
||||||
|
} while (result);
|
||||||
|
|
||||||
|
/* set timestamp as 'recent' for this data */
|
||||||
|
trans->version_check = be->version_check;
|
||||||
|
|
||||||
|
/* if engine data was newer, we are done */
|
||||||
|
if (0 <= engine_data_is_newer)
|
||||||
|
{
|
||||||
|
pgendEnable(be);
|
||||||
|
gnc_engine_resume_events();
|
||||||
|
return engine_data_is_newer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ------------------------------------------------- */
|
||||||
|
/* If we are here, then the sql database contains data that is
|
||||||
|
* newer than what we have in the engine. And so, below,
|
||||||
|
* we finish the job of yanking data out of the db.
|
||||||
|
*/
|
||||||
|
pgendCopySplitsToEngine (be, trans);
|
||||||
|
|
||||||
/* ------------------------------------------------- */
|
/* ------------------------------------------------- */
|
||||||
/* restore any kvp data associated with the transaction and splits */
|
/* restore any kvp data associated with the transaction and splits */
|
||||||
|
|
||||||
|
@ -43,6 +43,8 @@
|
|||||||
#include "PostgresBackend.h"
|
#include "PostgresBackend.h"
|
||||||
|
|
||||||
int pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid);
|
int pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid);
|
||||||
|
void pgendCopySplitsToEngine (PGBackend *be, Transaction *trans);
|
||||||
|
|
||||||
void pgendStoreAllTransactions (PGBackend *be, AccountGroup *grp);
|
void pgendStoreAllTransactions (PGBackend *be, AccountGroup *grp);
|
||||||
void pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans, gboolean do_check_version);
|
void pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans, gboolean do_check_version);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user