add more update handling

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@2332 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Linas Vepstas 2000-05-15 17:52:39 +00:00
parent 19d7265ab1
commit 2590d21bcc

View File

@ -23,8 +23,12 @@
static short module = MOD_BACKEND;
/* ============================================================= */
/* The SEND_QUERY macro sends the sql statement off to the server.
* It performs a minimal check to see that the send succeeded.
*/
#define SEND_QUERY(be) { \
#define SEND_QUERY(be) \
{ \
int rc; \
rc = PQsendQuery (be->connection, be->buff); \
if (!rc) \
@ -37,7 +41,15 @@ static short module = MOD_BACKEND;
} \
}
#define FLUSH(conn) { \
/* --------------------------------------------------------------- */
/* The FINISH_QUERY macro makes sure that the previously sent
* query complete with no errors. It assumes that the query
* is does not produce any results; if it did, those results are
* discarded (only error conditions are checked for).
*/
#define FINISH_QUERY(conn) \
{ \
PGresult *result; \
/* complete/commit the transaction, check the status */ \
do { \
@ -55,6 +67,71 @@ static short module = MOD_BACKEND;
} while (result); \
}
/* --------------------------------------------------------------- */
/* The GET_RESULTS macro grabs the result of an sSQL query off the
* wire, and makes sure that no errors occured. Results are left
* in the result buffer.
*/
#define GET_RESULTS(conn,result) \
{ \
ExecStatusType status; \
result = PQgetResult (conn); \
if (!result) break; \
status = PQresultStatus(result); \
if ((PGRES_COMMAND_OK != status) && \
(PGRES_TUPLES_OK != status)) \
{ \
PERR ("failed to get result to query\n"); \
PQclear (result); \
/* hack alert need gentler, kinder error recovery */ \
PQfinish (conn); \
break; \
} \
}
/* --------------------------------------------------------------- */
/* The IF_ONE_ROW macro counts the number of rows returned by
* a query, reports an error if there is more than one row, and
* conditionally executes a block for the first row.
*/
#define IF_ONE_ROW(result,nrows,loopcounter) \
{ \
int ncols = PQnfields (result); \
nrows += PQntuples (result); \
PINFO ("query result %d has %d rows and %d cols\n", \
loopcounter, nrows, ncols); \
} \
if (1 < nrows) { \
PERR ("unexpected duplicate records\n"); \
break; \
} else if (1 == nrows)
/* --------------------------------------------------------------- */
/* Some utility macros for comparing values returned from the
* database to values in the engine structs.
*/
#define GET_DB_VAL(str) (PQgetvalue (result, 0, PQfnumber (result, str)))
#define COMP_STR(sqlname,fun,ndiffs) { \
if (strcmp (GET_DB_VAL(sqlname),fun)) { \
PINFO("%s sql='%s', eng='%s'\n", sqlname, \
GET_DB_VAL (sqlname), fun); \
ndiffs++; \
} \
}
#define COMP_GUID(sqlname,fun, ndiffs) { \
const char *tmp = guid_to_string(fun); \
if (strcmp (GET_DB_VAL(sqlname),tmp)) { \
PINFO("%s sql='%s', eng='%s'\n", sqlname, \
GET_DB_VAL(sqlname), tmp); \
ndiffs++; \
} \
free ((char *) tmp); \
}
/* ============================================================= */
/* This routine stores the indicated group structure into the database.
* It does *not* chase pointers, traverse the tree, etc.
@ -95,7 +172,7 @@ pgendStoreOneGroupOnly (PGBackend *be, AccountGroup *grp)
SEND_QUERY(be);
/* complete/commit the transaction, check the status */
FLUSH(be->connection);
FINISH_QUERY(be->connection);
}
free ((char *) grp_guid);
free ((char *) parent_guid);
@ -110,7 +187,7 @@ pgendStoreOneGroupOnly (PGBackend *be, AccountGroup *grp)
*/
static void
pgendStoreOneAccountOnly (PGBackend *be, Account *acct)
pgendStoreOneAccountOnly (PGBackend *be, Account *acct, int update)
{
const char *acct_guid, *parent_guid, *child_guid;
ENTER ("be=%p, acct=%p\n", be, acct);
@ -120,26 +197,47 @@ pgendStoreOneAccountOnly (PGBackend *be, Account *acct)
parent_guid = guid_to_string(xaccGroupGetGUID (xaccAccountGetParent(acct)));
child_guid = guid_to_string(xaccGroupGetGUID (xaccAccountGetChildren(acct)));
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"INSERT INTO gncAccount "
"(accountGuid, parentGuid, childrenGuid, "
"accountName, accountCode, description, notes, "
"type, currency, security)"
" values "
"('%s', '%s', '%s', '%s', '%s', '%s', '%s', "
"%d, '%s', '%s');",
acct_guid,
parent_guid,
child_guid,
xaccAccountGetName (acct),
xaccAccountGetCode (acct),
xaccAccountGetDescription (acct),
xaccAccountGetNotes (acct),
xaccAccountGetType (acct),
xaccAccountGetCurrency (acct),
xaccAccountGetSecurity (acct)
);
if (update) {
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"UPDATE gncAccount SET "
"parentGuid='%s', childrenGuid='%s', "
"accountName='%s', accountCode='%s', description='%s', "
"notes='%s', type=%d, currency='%s', security='%s' "
"WHERE accountGuid='%s';",
parent_guid,
child_guid,
xaccAccountGetName (acct),
xaccAccountGetCode (acct),
xaccAccountGetDescription (acct),
xaccAccountGetNotes (acct),
xaccAccountGetType (acct),
xaccAccountGetCurrency (acct),
xaccAccountGetSecurity (acct),
acct_guid
);
} else {
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"INSERT INTO gncAccount "
"(accountGuid, parentGuid, childrenGuid, "
"accountName, accountCode, description, notes, "
"type, currency, security)"
" values "
"('%s', '%s', '%s', '%s', '%s', '%s', '%s', "
"%d, '%s', '%s');",
acct_guid,
parent_guid,
child_guid,
xaccAccountGetName (acct),
xaccAccountGetCode (acct),
xaccAccountGetDescription (acct),
xaccAccountGetNotes (acct),
xaccAccountGetType (acct),
xaccAccountGetCurrency (acct),
xaccAccountGetSecurity (acct)
);
}
free ((char *) acct_guid);
free ((char *) parent_guid);
free ((char *) child_guid);
@ -147,11 +245,75 @@ pgendStoreOneAccountOnly (PGBackend *be, Account *acct)
SEND_QUERY (be);
/* complete/commit the transaction, check the status */
FLUSH(be->connection);
FINISH_QUERY(be->connection);
LEAVE ("\n");
}
/* ============================================================= */
/* this routine routine returns non-zero if the indicated acount
* differs from that in the SQL database.
* this routine grabs no locks.
*/
static int
pgendCompareOneAccountOnly (PGBackend *be, Account *acct)
{
const char *acct_guid;
PGresult *result;
int i=0, nrows=0, ndiffs=0;
ENTER ("be=%p, acct=%p\n", be, acct);
if (!be || !acct) return;
acct_guid = guid_to_string(xaccAccountGetGUID (acct));
/* try to find this account in the database */
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"SELECT parentGuid, childrenGuid, accountName, accountCode, "
"description, notes, type, currency, security "
"FROM gncAccount "
"WHERE accountGuid = '%s';",
acct_guid
);
free ((char *) acct_guid);
/* hack alert -- if error occurs here, what is the return value ???? */
SEND_QUERY (be);
i=0; nrows=0;
do {
GET_RESULTS (be->connection, result);
IF_ONE_ROW (result, nrows, i) {
/* compared queried values to input values */
COMP_STR ("accountName", xaccAccountGetName(acct), ndiffs);
COMP_STR ("description", xaccAccountGetDescription(acct), ndiffs);
COMP_STR ("notes", xaccAccountGetNotes(acct), ndiffs);
COMP_STR ("currency", xaccAccountGetCurrency(acct), ndiffs);
COMP_STR ("security", xaccAccountGetSecurity(acct), ndiffs);
COMP_GUID ("parentGuid",
xaccGroupGetGUID (xaccAccountGetParent(acct)), ndiffs);
COMP_GUID ("childrenGuid",
xaccGroupGetGUID (xaccAccountGetChildren(acct)), ndiffs);
/* hack alert -- need to also do the account type */
/* hack alert -- need to also do additional acct info for
the specialty account types */
}
PQclear (result);
i++;
} while (result);
if (0 == nrows) ndiffs = -1;
LEAVE ("\n");
return ndiffs;
}
/* ============================================================= */
/* This routine stores the indicated transaction structure into the database.
* It does *not* chase pointers, traverse the tree, etc.
@ -159,7 +321,7 @@ pgendStoreOneAccountOnly (PGBackend *be, Account *acct)
*/
static void
pgendStoreOneTransactionOnly (PGBackend *be, Transaction *trans)
pgendStoreOneTransactionOnly (PGBackend *be, Transaction *trans, int update)
{
const char * trans_guid;
@ -167,27 +329,93 @@ pgendStoreOneTransactionOnly (PGBackend *be, Transaction *trans)
if (!be || !trans) return;
trans_guid = guid_to_string(xaccTransGetGUID (trans));
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"INSERT INTO gncTransaction "
"(transGuid, num, description)"
" values "
"('%s', '%s', '%s');",
trans_guid,
xaccTransGetNum (trans),
xaccTransGetDescription (trans)
);
if (update) {
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"UPDATE gncTransaction SET "
"num='%s', description='%s' "
"WHERE transGuid='%s';",
xaccTransGetNum (trans),
xaccTransGetDescription (trans),
trans_guid
);
} else {
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"INSERT INTO gncTransaction "
"(transGuid, num, description)"
" values "
"('%s', '%s', '%s');",
trans_guid,
xaccTransGetNum (trans),
xaccTransGetDescription (trans)
);
}
free ((char *) trans_guid);
SEND_QUERY (be);
/* complete/commit the transaction, check the status */
FLUSH(be->connection);
FINISH_QUERY(be->connection);
LEAVE ("\n");
}
/* ============================================================= */
/* this routine routine returns non-zero if the indicated transaction
* differs from that in the SQL database.
* this routine grabs no locks.
*/
static int
pgendCompareOneTransactionOnly (PGBackend *be, Transaction *trans)
{
const char *trans_guid;
PGresult *result;
int i=0, nrows=0, ndiffs=0;
ENTER ("be=%p, trans=%p\n", be, trans);
if (!be || !trans) return;
trans_guid = guid_to_string(xaccTransGetGUID (trans));
/* try to find this transaction in the database */
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"SELECT transGuid, date_entered, date_posted, num, description "
"FROM gncTransaction "
"WHERE transGuid = '%s';",
trans_guid
);
free ((char *) trans_guid);
/* hack alert -- if error occurs here, what is the return value ???? */
SEND_QUERY (be);
i=0; nrows=0;
do {
GET_RESULTS (be->connection, result);
IF_ONE_ROW (result, nrows, i) {
/* compared queried values to input values */
COMP_STR ("num", xaccTransGetNum(trans), ndiffs);
COMP_STR ("description", xaccTransGetDescription(trans), ndiffs);
/* hack alert -- need to also do the dates */
}
PQclear (result);
i++;
} while (result);
if (0 == nrows) ndiffs = -1;
LEAVE ("\n");
return ndiffs;
}
/* ============================================================= */
/* this routine stores the indicated split in the database
*/
@ -211,7 +439,7 @@ pgendStoreOneSplitOnly (PGBackend *be, Split *split, int update)
if (update) {
/* hack alert -- values should be escaped so that no '' apear in them */
snprintf (be->buff, be->bufflen,
"UPDATE gncEntry SET"
"UPDATE gncEntry SET "
"accountGuid='%s', transGuid='%s', memo='%s', action='%s', "
"reconciled='%c', amount=%g, share_price=%g "
"WHERE entryGuid='%s';",
@ -249,14 +477,14 @@ pgendStoreOneSplitOnly (PGBackend *be, Split *split, int update)
SEND_QUERY (be);
/* complete/commit the transaction, check the status */
FLUSH(be->connection);
FINISH_QUERY(be->connection);
LEAVE ("\n");
}
/* ============================================================= */
/* this routine routine returns non-zero if the indicated split
* differs fropm that in the SQL database.
* differs from that in the SQL database.
* this routine grabs no locks.
*/
@ -265,7 +493,7 @@ pgendCompareOneSplitOnly (PGBackend *be, Split *split)
{
const char *split_guid;
PGresult *result;
int i, nrows, ncols, ndiffs=0;
int i=0, nrows=0, ndiffs=0;
ENTER ("be=%p, split=%p\n", be, split);
if (!be || !split) return;
@ -283,58 +511,31 @@ pgendCompareOneSplitOnly (PGBackend *be, Split *split)
);
free ((char *) split_guid);
/* hack alert -- if error occurs here, what is the return value ???? */
SEND_QUERY (be);
i=0; nrows=0;
do {
ExecStatusType status;
GET_RESULTS (be->connection, result);
IF_ONE_ROW (result, nrows, i) {
result = PQgetResult (be->connection);
if (!result) break;
status = PQresultStatus(result);
if ((PGRES_COMMAND_OK != status) &&
(PGRES_TUPLES_OK != status))
{
PERR ("failed to get result to query\n");
PQclear (result);
/* hack alert need gentler, kinder error recovery */
PQfinish (be->connection);
break;
}
nrows += PQntuples (result);
ncols = PQnfields (result);
PINFO ("query result %d has %d rows and %d cols\n",
i, nrows, ncols);
if (1 < nrows) {
PERR ("unexpected duplicate records\n");
break;
} else if (1 == nrows) {
#define GETV(str) (PQgetvalue (result, 0, PQfnumber (result, str)))
#define COMP(sqlname,fun) if (strcmp (GETV(sqlname),fun)) { \
PINFO("%s sql='%s', split='%s'\n", sqlname, GETV(sqlname), fun); \
ndiffs++; }
#define GCOMP(sqlname,fun) { \
const char *tmp = guid_to_string(fun); \
if (strcmp (GETV(sqlname),tmp)) { \
PINFO("%s sql='%s', split='%s'\n", sqlname, GETV(sqlname), tmp); \
ndiffs++; } \
free ((char *) tmp); }
/* compared queried values to input values */
COMP ("memo", xaccSplitGetMemo(split));
COMP ("action", xaccSplitGetAction(split));
GCOMP ("accountGuid",
xaccAccountGetGUID (xaccSplitGetAccount(split)));
GCOMP ("transGuid",
xaccTransGetGUID (xaccSplitGetParent(split)));
COMP_STR ("memo", xaccSplitGetMemo(split), ndiffs);
COMP_STR ("action", xaccSplitGetAction(split), ndiffs);
COMP_GUID ("accountGuid",
xaccAccountGetGUID (xaccSplitGetAccount(split)), ndiffs);
COMP_GUID ("transGuid",
xaccTransGetGUID (xaccSplitGetParent(split)), ndiffs);
/* hack alert -- need to also compare recconcile flag, amount, and price */
PINFO ("recn=%s amt=%s price=%s\n", GETV("reconciled"), GETV("amount"),
GETV("share_price"));
/*
xaccSplitGetReconcile(split),
xaccSplitGetShareAmount(split),
xaccSplitGetSharePrice(split)
*/
}
PQclear (result);
@ -343,17 +544,79 @@ GETV("share_price"));
if (0 == nrows) ndiffs = -1;
/*
xaccSplitGetReconcile(split),
xaccSplitGetShareAmount(split),
xaccSplitGetSharePrice(split)
*/
LEAVE ("\n");
return ndiffs;
}
/* ============================================================= */
/* This routine traverses the account structure and stores/updates
* it in the database. If checks the account parents as well,
* updating those.
*/
static void
pgendStoreAccount (PGBackend *be, Account *acct)
{
int i, ndiffs, nsplits;
if (!be || !acct) return;
/* hack alert -- because this query is potentially quite
heavy-hitting to the database, and also because it will probably be
performed redundantly, we should use a check-flag to mark this accountas
having been checked already. The check-flag should be zeroed after
things get unlocked */
ndiffs = pgendCompareOneAccountOnly (be, acct);
/* update account if there are differences ... */
if (0<ndiffs) pgendStoreOneAccountOnly (be, acct, 1);
/* insert account if it doesn't exist */
if (0>ndiffs) pgendStoreOneAccountOnly (be, acct, 0);
/* hack alert -- walk over tree of parents, children */
}
/* ============================================================= */
/* This routine traverses the transaction structure and stores/updates
* it in the database. If checks the transaction splits as well,
* updating those. Finally, it makes sure that each account is present
* as well.
*/
static void
pgendStoreTransaction (PGBackend *be, Transaction *trans)
{
int i, ndiffs, nsplits;
if (!be || !trans) return;
ndiffs = pgendCompareOneTransOnly (be, trans);
/* update transaction if there are differences ... */
if (0<ndiffs) pgendStoreOneTransOnly (be, trans, 1);
/* insert trans if it doesn't exist */
if (0>ndiffs) pgendStoreOneTransOnly (be, trans, 0);
/* walk over the list of splits */
nsplits = xaccTransCountSplits (trans);
for (i=0; i<nsplits; i++) {
Split * s = xaccTransGetSplit (trans, i);
Account *acct = xaccSplitGetAccount (s);
ndiffs = pgendCompareOneSplitOnly (be, s);
/* update split if there are differences ... */
if (0<ndiffs) pgendStoreOneSplitOnly (be, s, 1);
/* insert split if it doesn't exist */
if (0>ndiffs) pgendStoreOneSplitOnly (be, s, 0);
/* check to see if the account that this split references is in
* storage; if not, add it */
pgendStoreAccount (be, acct);
}
}
/* ============================================================= */
/* This routine traverses the group structure and stores it into
* the database. The NoLock version doesn't lock up the tables.
@ -362,24 +625,7 @@ GETV("share_price"));
static int
traverse_cb (Transaction *trans, void *cb_data)
{
PGBackend *be = (PGBackend *) cb_data;
int i, nsplits;
if (!be || !trans) return;
pgendStoreOneTransactionOnly (be, trans);
/* walk over the list of splits */
nsplits = xaccTransCountSplits (trans);
for (i=0; i<nsplits; i++) {
Split * s = xaccTransGetSplit (trans, i);
int ndiffs = pgendCompareOneSplitOnly (be, s);
/* update split if there are differences ... */
if (0<ndiffs) pgendStoreOneSplitOnly (be, s, 1);
/* insert split if it doesn't exist */
if (0>ndiffs) pgendStoreOneSplitOnly (be, s, 0);
}
pgendStoreTransaction ((PGBackend *) cb_data, trans);
return 0;
}
@ -399,7 +645,12 @@ pgendStoreGroupNoLock (PGBackend *be, AccountGroup *grp)
for (i=0; i<nacc; i++) {
AccountGroup *subgrp;
Account *acc = xaccGroupGetAccount(grp, i);
pgendStoreOneAccountOnly (be, acc);
int ndiffs = pgendCompareOneAccountOnly (be, acc);
/* update account if there are differences ... */
if (0<ndiffs) pgendStoreOneAccountOnly (be, acc, 1);
/* insert account if it doesn't exist */
if (0>ndiffs) pgendStoreOneAccountOnly (be, acc, 0);
/* recursively walk to child accounts */
subgrp = xaccAccountGetChildren (acc);
@ -417,7 +668,7 @@ pgendStoreGroup (PGBackend *be, AccountGroup *grp)
/* lock it up so that we store atomically */
snprintf (be->buff, be->bufflen, "BEGIN;");
SEND_QUERY (be);
FLUSH(be->connection);
FINISH_QUERY(be->connection);
/* reset the write flags. We use this to amek sure we don't
* get caught in infinite recursion */
@ -429,7 +680,7 @@ pgendStoreGroup (PGBackend *be, AccountGroup *grp)
snprintf (be->buff, be->bufflen, "COMMIT;");
SEND_QUERY (be);
FLUSH(be->connection);
FINISH_QUERY(be->connection);
}
/* ============================================================= */