mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
bug fixes
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@3611 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
parent
75cf84a107
commit
cc8044c8fc
@ -2,9 +2,12 @@
|
|||||||
# Build the postgres backend as its own loadable shared object.
|
# Build the postgres backend as its own loadable shared object.
|
||||||
|
|
||||||
lib_LTLIBRARIES = libgnc_postgres.la
|
lib_LTLIBRARIES = libgnc_postgres.la
|
||||||
|
libgnc_postgres_la_LDFLAGS = -version-info 5:2:5
|
||||||
|
|
||||||
|
|
||||||
libgnc_postgres_la_SOURCES = \
|
libgnc_postgres_la_SOURCES = \
|
||||||
builder.c \
|
builder.c \
|
||||||
|
checkpoint.c \
|
||||||
gncquery.c \
|
gncquery.c \
|
||||||
PostgresBackend.c
|
PostgresBackend.c
|
||||||
|
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
* PostgressBackend.c
|
* PostgressBackend.c
|
||||||
*
|
*
|
||||||
* FUNCTION:
|
* FUNCTION:
|
||||||
* Implements the callbacks for the postgress backend.
|
* Implements the callbacks for the Postgres backend.
|
||||||
* this is code kinda usually works.
|
* The SINGLE modes mostly work and are mostly feature complete.
|
||||||
* it needs review and design checking
|
* The multi-user modes are mostly in disrepair, and marginally
|
||||||
|
* functional.
|
||||||
*
|
*
|
||||||
* HISTORY:
|
* HISTORY:
|
||||||
* Copyright (c) 2000, 2001 Linas Vepstas
|
* Copyright (c) 2000, 2001 Linas Vepstas
|
||||||
@ -40,6 +41,8 @@
|
|||||||
#include "gncquery.h"
|
#include "gncquery.h"
|
||||||
#include "PostgresBackend.h"
|
#include "PostgresBackend.h"
|
||||||
|
|
||||||
|
#include "putil.h"
|
||||||
|
|
||||||
static short module = MOD_BACKEND;
|
static short module = MOD_BACKEND;
|
||||||
|
|
||||||
static void pgendDisable (PGBackend *be);
|
static void pgendDisable (PGBackend *be);
|
||||||
@ -55,206 +58,6 @@ static const char * pgendSessionGetMode (PGBackend *be);
|
|||||||
*/
|
*/
|
||||||
#define QBUFSIZE 16350
|
#define QBUFSIZE 16350
|
||||||
|
|
||||||
|
|
||||||
/* hack alert -- calling PQFinish() is quite harsh, since all
|
|
||||||
* subsequent sql queries will fail. On the other hand, killing
|
|
||||||
* anything that follows *is* a way of minimizing data corruption
|
|
||||||
* due to subsequent mishaps ... so anyway, error handling in these
|
|
||||||
* routines needs to be rethought.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* ============================================================= */
|
|
||||||
/* 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,buff,retval) \
|
|
||||||
{ \
|
|
||||||
int rc; \
|
|
||||||
rc = PQsendQuery (be->connection, buff); \
|
|
||||||
if (!rc) \
|
|
||||||
{ \
|
|
||||||
/* hack alert -- we need kinder, gentler error handling */\
|
|
||||||
PERR("send query failed:\n" \
|
|
||||||
"\t%s", PQerrorMessage(be->connection)); \
|
|
||||||
PQfinish (be->connection); \
|
|
||||||
xaccBackendSetError (&be->be, ERR_SQL_SEND_QUERY_FAILED); \
|
|
||||||
return retval; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------- */
|
|
||||||
/* 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) \
|
|
||||||
{ \
|
|
||||||
int i=0; \
|
|
||||||
PGresult *result; \
|
|
||||||
/* complete/commit the transaction, check the status */ \
|
|
||||||
do { \
|
|
||||||
ExecStatusType status; \
|
|
||||||
result = PQgetResult((conn)); \
|
|
||||||
if (!result) break; \
|
|
||||||
PINFO ("clearing result %d", i); \
|
|
||||||
status = PQresultStatus(result); \
|
|
||||||
if (PGRES_COMMAND_OK != status) { \
|
|
||||||
PERR("finish query failed:\n" \
|
|
||||||
"\t%s", PQerrorMessage((conn))); \
|
|
||||||
PQclear(result); \
|
|
||||||
PQfinish ((conn)); \
|
|
||||||
xaccBackendSetError (&be->be, ERR_SQL_FINISH_QUERY_FAILED); \
|
|
||||||
} \
|
|
||||||
PQclear(result); \
|
|
||||||
i++; \
|
|
||||||
} while (result); \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------- */
|
|
||||||
/* The GET_RESULTS macro grabs the result of an pgSQL 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" \
|
|
||||||
"\t%s", PQerrorMessage((conn))); \
|
|
||||||
PQclear (result); \
|
|
||||||
PQfinish (conn); \
|
|
||||||
xaccBackendSetError (&be->be, ERR_SQL_GET_RESULT_FAILED); \
|
|
||||||
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", \
|
|
||||||
loopcounter, nrows, ncols); \
|
|
||||||
} \
|
|
||||||
if (1 < nrows) { \
|
|
||||||
PERR ("unexpected duplicate records"); \
|
|
||||||
xaccBackendSetError (&be->be, ERR_SQL_CORRUPT_DB); \
|
|
||||||
break; \
|
|
||||||
} else if (1 == nrows)
|
|
||||||
|
|
||||||
/* --------------------------------------------------------------- */
|
|
||||||
/* Some utility macros for comparing values returned from the
|
|
||||||
* database to values in the engine structs. These macros
|
|
||||||
* all take three arguments:
|
|
||||||
* -- sqlname -- input -- the name of the field in the sql table
|
|
||||||
* -- fun -- input -- a subroutine returning a value
|
|
||||||
* -- ndiffs -- input/output -- integer, incremented if the
|
|
||||||
* value ofthe field and the value returned by
|
|
||||||
* the subroutine differ.
|
|
||||||
*
|
|
||||||
* The different macros compare different field types.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define DB_GET_VAL(str,n) (PQgetvalue (result, n, PQfnumber (result, str)))
|
|
||||||
|
|
||||||
/* compare string types. null strings and emty strings are
|
|
||||||
* considered to be equal */
|
|
||||||
#define COMP_STR(sqlname,fun,ndiffs) { \
|
|
||||||
if (null_strcmp (DB_GET_VAL(sqlname,0),fun)) { \
|
|
||||||
PINFO("mis-match: %s sql='%s', eng='%s'", sqlname, \
|
|
||||||
DB_GET_VAL (sqlname,0), fun); \
|
|
||||||
ndiffs++; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* compare guids */
|
|
||||||
#define COMP_GUID(sqlname,fun, ndiffs) { \
|
|
||||||
char guid_str[GUID_ENCODING_LENGTH+1]; \
|
|
||||||
guid_to_string_buff(fun, guid_str); \
|
|
||||||
if (null_strcmp (DB_GET_VAL(sqlname,0),guid_str)) { \
|
|
||||||
PINFO("mis-match: %s sql='%s', eng='%s'", sqlname, \
|
|
||||||
DB_GET_VAL(sqlname,0), guid_str); \
|
|
||||||
ndiffs++; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* comapre one char only */
|
|
||||||
#define COMP_CHAR(sqlname,fun, ndiffs) { \
|
|
||||||
if (tolower((DB_GET_VAL(sqlname,0))[0]) != tolower(fun)) { \
|
|
||||||
PINFO("mis-match: %s sql=%c eng=%c", sqlname, \
|
|
||||||
tolower((DB_GET_VAL(sqlname,0))[0]), tolower(fun)); \
|
|
||||||
ndiffs++; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Compare dates.
|
|
||||||
* Assumes the datestring is in ISO-8601 format
|
|
||||||
* i.e. looks like 1998-07-17 11:00:00.68-05
|
|
||||||
* hack-alert doesn't compare nano-seconds ..
|
|
||||||
* this is intentional, its because I suspect
|
|
||||||
* the sql db round nanoseconds off ...
|
|
||||||
*/
|
|
||||||
#define COMP_DATE(sqlname,fun,ndiffs) { \
|
|
||||||
Timespec eng_time = fun; \
|
|
||||||
Timespec sql_time = gnc_iso8601_to_timespec_local( \
|
|
||||||
DB_GET_VAL(sqlname,0)); \
|
|
||||||
if (eng_time.tv_sec != sql_time.tv_sec) { \
|
|
||||||
char buff[80]; \
|
|
||||||
gnc_timespec_to_iso8601_buff(eng_time, buff); \
|
|
||||||
PINFO("mis-match: %s sql='%s' eng=%s", sqlname, \
|
|
||||||
DB_GET_VAL(sqlname,0), buff); \
|
|
||||||
ndiffs++; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Compare the date of last modification.
|
|
||||||
* This is a special date comp to make the m4 macros simpler.
|
|
||||||
*/
|
|
||||||
#define COMP_NOW(sqlname,fun,ndiffs) { \
|
|
||||||
Timespec eng_time = xaccTransRetDateEnteredTS(ptr); \
|
|
||||||
Timespec sql_time = gnc_iso8601_to_timespec_local( \
|
|
||||||
DB_GET_VAL(sqlname,0)); \
|
|
||||||
if (eng_time.tv_sec != sql_time.tv_sec) { \
|
|
||||||
char buff[80]; \
|
|
||||||
gnc_timespec_to_iso8601_buff(eng_time, buff); \
|
|
||||||
PINFO("mis-match: %s sql='%s' eng=%s", sqlname, \
|
|
||||||
DB_GET_VAL(sqlname,0), buff); \
|
|
||||||
ndiffs++; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Compare long-long integers */
|
|
||||||
#define COMP_INT64(sqlname,fun,ndiffs) { \
|
|
||||||
if (atoll (DB_GET_VAL(sqlname,0)) != fun) { \
|
|
||||||
PINFO("mis-match: %s sql='%s', eng='%lld'", sqlname, \
|
|
||||||
DB_GET_VAL (sqlname,0), fun); \
|
|
||||||
ndiffs++; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* compare 32-bit ints */
|
|
||||||
#define COMP_INT32(sqlname,fun,ndiffs) { \
|
|
||||||
if (atol (DB_GET_VAL(sqlname,0)) != fun) { \
|
|
||||||
PINFO("mis-match: %s sql='%s', eng='%d'", sqlname, \
|
|
||||||
DB_GET_VAL (sqlname,0), fun); \
|
|
||||||
ndiffs++; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================= */
|
/* ============================================================= */
|
||||||
/* misc bogus utility routines */
|
/* misc bogus utility routines */
|
||||||
|
|
||||||
@ -344,7 +147,7 @@ pgendStoreAccountNoLock (PGBackend *be, Account *acct,
|
|||||||
const gnc_commodity *com;
|
const gnc_commodity *com;
|
||||||
|
|
||||||
if (!be || !acct) return;
|
if (!be || !acct) return;
|
||||||
if (FALSE == acct->core_dirty) return;
|
if ((FALSE == do_mark) && (FALSE == acct->core_dirty)) return;
|
||||||
|
|
||||||
ENTER ("acct=%p, mark=%d", acct, do_mark);
|
ENTER ("acct=%p, mark=%d", acct, do_mark);
|
||||||
|
|
||||||
@ -364,7 +167,7 @@ pgendStoreAccountNoLock (PGBackend *be, Account *acct,
|
|||||||
|
|
||||||
/* make sure the account's commodity is in the commodity table */
|
/* make sure the account's commodity is in the commodity table */
|
||||||
/* hack alert -- it would be more efficient to do this elsewhere,
|
/* hack alert -- it would be more efficient to do this elsewhere,
|
||||||
* and not here. */
|
* and not here. Or put a mark on it ... */
|
||||||
com = xaccAccountGetCommodity (acct);
|
com = xaccAccountGetCommodity (acct);
|
||||||
pgendPutOneCommodityOnly (be, (gnc_commodity *) com);
|
pgendPutOneCommodityOnly (be, (gnc_commodity *) com);
|
||||||
|
|
||||||
@ -382,11 +185,10 @@ static void
|
|||||||
pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans,
|
pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans,
|
||||||
gboolean do_mark)
|
gboolean do_mark)
|
||||||
{
|
{
|
||||||
GUID nullguid = *(xaccGUIDNULL());
|
GList *start, *deletelist=NULL, *node;
|
||||||
GList *deletelist=NULL, *node;
|
|
||||||
PGresult *result;
|
PGresult *result;
|
||||||
char * p;
|
char * p;
|
||||||
int i, nrows, nsplits;
|
int i, nrows;
|
||||||
|
|
||||||
if (!be || !trans) return;
|
if (!be || !trans) return;
|
||||||
ENTER ("trans=%p, mark=%d", trans, do_mark);
|
ENTER ("trans=%p, mark=%d", trans, do_mark);
|
||||||
@ -417,7 +219,6 @@ pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans,
|
|||||||
{
|
{
|
||||||
GUID guid = nullguid;
|
GUID guid = nullguid;
|
||||||
string_to_guid (DB_GET_VAL ("entryGuid", j), &guid);
|
string_to_guid (DB_GET_VAL ("entryGuid", j), &guid);
|
||||||
|
|
||||||
/* If the database has splits that the engine doesn't,
|
/* If the database has splits that the engine doesn't,
|
||||||
* collect 'em up & we'll have to delete em */
|
* collect 'em up & we'll have to delete em */
|
||||||
if (NULL == xaccLookupEntity (&guid, GNC_ID_SPLIT))
|
if (NULL == xaccLookupEntity (&guid, GNC_ID_SPLIT))
|
||||||
@ -433,41 +234,44 @@ pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans,
|
|||||||
|
|
||||||
|
|
||||||
/* delete those that don't belong */
|
/* delete those that don't belong */
|
||||||
|
p = be->buff; *p = 0;
|
||||||
for (node=deletelist; node; node=node->next)
|
for (node=deletelist; node; node=node->next)
|
||||||
{
|
{
|
||||||
p = be->buff; *p = 0;
|
|
||||||
p = stpcpy (p, "DELETE FROM gncEntry WHERE entryGuid='");
|
p = stpcpy (p, "DELETE FROM gncEntry WHERE entryGuid='");
|
||||||
p = stpcpy (p, node->data);
|
p = stpcpy (p, node->data);
|
||||||
p = stpcpy (p, "';");
|
p = stpcpy (p, "';\n");
|
||||||
|
g_free (node->data);
|
||||||
|
}
|
||||||
|
if (p != be->buff)
|
||||||
|
{
|
||||||
|
PINFO ("%s", be->buff);
|
||||||
SEND_QUERY (be,be->buff, );
|
SEND_QUERY (be,be->buff, );
|
||||||
FINISH_QUERY(be->connection);
|
FINISH_QUERY(be->connection);
|
||||||
g_free (node->data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update the rest */
|
/* Update the rest */
|
||||||
nsplits = xaccTransCountSplits (trans);
|
start = xaccTransGetSplitList(trans);
|
||||||
|
|
||||||
if ((nsplits) && !(trans->open & BEING_DESTROYED))
|
if ((start) && !(trans->open & BEING_DESTROYED))
|
||||||
{
|
{
|
||||||
for (i=0; i<nsplits; i++) {
|
for (node=start; node; node=node->next)
|
||||||
Split * s = xaccTransGetSplit (trans, i);
|
{
|
||||||
|
Split * s = node->data;
|
||||||
pgendPutOneSplitOnly (be, s);
|
pgendPutOneSplitOnly (be, s);
|
||||||
}
|
}
|
||||||
pgendPutOneTransactionOnly (be, trans);
|
pgendPutOneTransactionOnly (be, trans);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (i=0; i<nsplits; i++) {
|
p = be->buff; *p = 0;
|
||||||
Split * s = xaccTransGetSplit (trans, i);
|
for (node=start; node; node=node->next)
|
||||||
p = be->buff; *p = 0;
|
{
|
||||||
|
Split * s = node->data;
|
||||||
p = stpcpy (p, "DELETE FROM gncEntry WHERE entryGuid='");
|
p = stpcpy (p, "DELETE FROM gncEntry WHERE entryGuid='");
|
||||||
p = guid_to_string_buff (xaccSplitGetGUID(s), p);
|
p = guid_to_string_buff (xaccSplitGetGUID(s), p);
|
||||||
p = stpcpy (p, "';");
|
p = stpcpy (p, "';\n");
|
||||||
PINFO ("%s\n", be->buff);
|
|
||||||
SEND_QUERY (be,be->buff, );
|
|
||||||
FINISH_QUERY(be->connection);
|
|
||||||
}
|
}
|
||||||
p = be->buff; *p = 0;
|
p = be->buff;
|
||||||
p = stpcpy (p, "DELETE FROM gncTransaction WHERE transGuid='");
|
p = stpcpy (p, "DELETE FROM gncTransaction WHERE transGuid='");
|
||||||
p = guid_to_string_buff (xaccTransGetGUID(trans), p);
|
p = guid_to_string_buff (xaccTransGetGUID(trans), p);
|
||||||
p = stpcpy (p, "';");
|
p = stpcpy (p, "';");
|
||||||
@ -520,17 +324,17 @@ static void
|
|||||||
pgendStoreGroupNoLock (PGBackend *be, AccountGroup *grp,
|
pgendStoreGroupNoLock (PGBackend *be, AccountGroup *grp,
|
||||||
gboolean do_mark)
|
gboolean do_mark)
|
||||||
{
|
{
|
||||||
int i, nacc;
|
GList *start, *node;
|
||||||
|
|
||||||
if (!be || !grp) return;
|
if (!be || !grp) return;
|
||||||
ENTER("grp=%p mark=%d", grp, do_mark);
|
ENTER("grp=%p mark=%d", grp, do_mark);
|
||||||
|
|
||||||
/* walk the account tree, and store subaccounts */
|
/* walk the account tree, and store subaccounts */
|
||||||
nacc = xaccGroupGetNumAccounts(grp);
|
start = xaccGroupGetAccountList (grp);
|
||||||
|
for (node=start; node; node=node->next)
|
||||||
for (i=0; i<nacc; i++) {
|
{
|
||||||
AccountGroup *subgrp;
|
AccountGroup *subgrp;
|
||||||
Account *acc = xaccGroupGetAccount(grp, i);
|
Account *acc = node->data;
|
||||||
|
|
||||||
pgendStoreAccountNoLock (be, acc, do_mark);
|
pgendStoreAccountNoLock (be, acc, do_mark);
|
||||||
|
|
||||||
@ -550,6 +354,7 @@ pgendStoreGroup (PGBackend *be, AccountGroup *grp)
|
|||||||
if (!be || !grp) return;
|
if (!be || !grp) return;
|
||||||
|
|
||||||
/* lock it up so that we store atomically */
|
/* lock it up so that we store atomically */
|
||||||
|
/* hack alert ---- we need to lock a bunch of tables, right??!! */
|
||||||
bufp = "BEGIN;";
|
bufp = "BEGIN;";
|
||||||
SEND_QUERY (be,bufp, );
|
SEND_QUERY (be,bufp, );
|
||||||
FINISH_QUERY(be->connection);
|
FINISH_QUERY(be->connection);
|
||||||
@ -575,289 +380,6 @@ pgendStoreGroup (PGBackend *be, AccountGroup *grp)
|
|||||||
LEAVE(" ");
|
LEAVE(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================================= */
|
|
||||||
/* recompute *all* checkpoints for the account */
|
|
||||||
|
|
||||||
static void
|
|
||||||
pgendAccountRecomputeAllCheckpoints (PGBackend *be, const GUID *acct_guid)
|
|
||||||
{
|
|
||||||
Timespec this_ts, prev_ts;
|
|
||||||
GMemChunk *chunk;
|
|
||||||
GList *node, *checkpoints = NULL;
|
|
||||||
PGresult *result;
|
|
||||||
Checkpoint *bp;
|
|
||||||
char *p;
|
|
||||||
int i, nrows, nsplits;
|
|
||||||
Account *acc;
|
|
||||||
const char *commodity_name;
|
|
||||||
|
|
||||||
if (!be) return;
|
|
||||||
ENTER("be=%p", be);
|
|
||||||
|
|
||||||
acc = xaccLookupEntity (acct_guid, GNC_ID_ACCOUNT);
|
|
||||||
commodity_name = gnc_commodity_get_unique_name (xaccAccountGetCommodity(acc));
|
|
||||||
|
|
||||||
chunk = g_mem_chunk_create (Checkpoint, 300, G_ALLOC_ONLY);
|
|
||||||
|
|
||||||
/* prevent others from inserting any splits while we recompute
|
|
||||||
* the checkpoints. (hack alert -verify that this is the correct
|
|
||||||
* lock) */
|
|
||||||
p = "BEGIN WORK; "
|
|
||||||
"LOCK TABLE gncEntry IN SHARE MODE; "
|
|
||||||
"LOCK TABLE gncCheckpoint IN ACCESS EXCLUSIVE MODE; ";
|
|
||||||
SEND_QUERY (be,p, );
|
|
||||||
FINISH_QUERY(be->connection);
|
|
||||||
|
|
||||||
/* Blow all the old checkpoints for this account out of the water.
|
|
||||||
* This should help ensure against accidental corruption.
|
|
||||||
*/
|
|
||||||
p = be->buff; *p = 0;
|
|
||||||
p = stpcpy (p, "DELETE FROM gncCheckpoint WHERE accountGuid='");
|
|
||||||
p = guid_to_string_buff (acct_guid, p);
|
|
||||||
p = stpcpy (p, "';");
|
|
||||||
SEND_QUERY (be,be->buff, );
|
|
||||||
FINISH_QUERY(be->connection);
|
|
||||||
|
|
||||||
/* and now, fetch *all* of the splits in this account */
|
|
||||||
p = be->buff; *p = 0;
|
|
||||||
p = stpcpy (p, "SELECT gncEntry.amountNum AS amountNum, "
|
|
||||||
" gncEntry.reconciled AS reconciled,"
|
|
||||||
" gncTransaction.date_posted AS date_posted "
|
|
||||||
"FROM gncEntry, gncTransaction "
|
|
||||||
"WHERE gncEntry.transGuid = gncTransaction.transGuid "
|
|
||||||
"AND accountGuid='");
|
|
||||||
p = guid_to_string_buff (acct_guid, p);
|
|
||||||
p = stpcpy (p, "' ORDER BY gncTransaction.date_posted ASC;");
|
|
||||||
SEND_QUERY (be,be->buff, );
|
|
||||||
|
|
||||||
/* malloc a new checkpoint, set it to the dawn of AD time ... */
|
|
||||||
bp = g_chunk_new0 (Checkpoint, chunk);
|
|
||||||
checkpoints = g_list_prepend (checkpoints, bp);
|
|
||||||
this_ts = gnc_iso8601_to_timespec_local ("1970-04-15 08:35:46.00");
|
|
||||||
bp->datetime = this_ts;
|
|
||||||
bp->account_guid = acct_guid;
|
|
||||||
bp->commodity = commodity_name;
|
|
||||||
|
|
||||||
/* malloc a new checkpoint ... */
|
|
||||||
nsplits = 0;
|
|
||||||
bp = g_chunk_new0 (Checkpoint, chunk);
|
|
||||||
checkpoints = g_list_prepend (checkpoints, bp);
|
|
||||||
bp->account_guid = acct_guid;
|
|
||||||
bp->commodity = commodity_name;
|
|
||||||
|
|
||||||
/* start adding up balances */
|
|
||||||
i=0; nrows=0;
|
|
||||||
do {
|
|
||||||
GET_RESULTS (be->connection, result);
|
|
||||||
{
|
|
||||||
int j, jrows;
|
|
||||||
int ncols = PQnfields (result);
|
|
||||||
jrows = PQntuples (result);
|
|
||||||
nrows += jrows;
|
|
||||||
PINFO ("query result %d has %d rows and %d cols",
|
|
||||||
i, nrows, ncols);
|
|
||||||
|
|
||||||
for (j=0; j<jrows; j++)
|
|
||||||
{
|
|
||||||
gint64 amt;
|
|
||||||
char recn;
|
|
||||||
|
|
||||||
/* lets see if its time to start a new checkpoint */
|
|
||||||
/* look for splits that occur at least ten seconds apart */
|
|
||||||
prev_ts = this_ts;
|
|
||||||
prev_ts.tv_sec += 10;
|
|
||||||
this_ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_posted",j));
|
|
||||||
if ((MIN_CHECKPOINT_COUNT < nsplits) &&
|
|
||||||
(timespec_cmp (&prev_ts, &this_ts) < 0))
|
|
||||||
{
|
|
||||||
Checkpoint *next_bp;
|
|
||||||
|
|
||||||
/* Set checkpoint five seconds back. This is safe,
|
|
||||||
* because we looked for a 10 second gap above */
|
|
||||||
this_ts.tv_sec -= 5;
|
|
||||||
bp->datetime = this_ts;
|
|
||||||
|
|
||||||
/* and now, build a new checkpoint */
|
|
||||||
nsplits = 0;
|
|
||||||
next_bp = g_chunk_new0 (Checkpoint, chunk);
|
|
||||||
checkpoints = g_list_prepend (checkpoints, next_bp);
|
|
||||||
*next_bp = *bp;
|
|
||||||
bp = next_bp;
|
|
||||||
bp->account_guid = acct_guid;
|
|
||||||
bp->commodity = commodity_name;
|
|
||||||
}
|
|
||||||
nsplits ++;
|
|
||||||
|
|
||||||
/* accumulate balances */
|
|
||||||
amt = atoll (DB_GET_VAL("amountNum",j));
|
|
||||||
recn = (DB_GET_VAL("reconciled",j))[0];
|
|
||||||
bp->balance += amt;
|
|
||||||
if (NREC != recn)
|
|
||||||
{
|
|
||||||
bp->cleared_balance += amt;
|
|
||||||
}
|
|
||||||
if (YREC == recn)
|
|
||||||
{
|
|
||||||
bp->reconciled_balance += amt;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PQclear (result);
|
|
||||||
i++;
|
|
||||||
} while (result);
|
|
||||||
|
|
||||||
/* set the timestamp on the final checkpoint,
|
|
||||||
* 8 seconds past the very last split */
|
|
||||||
this_ts.tv_sec += 8;
|
|
||||||
bp->datetime = this_ts;
|
|
||||||
|
|
||||||
/* now store the checkpoints */
|
|
||||||
for (node = checkpoints; node; node = node->next)
|
|
||||||
{
|
|
||||||
bp = (Checkpoint *) node->data;
|
|
||||||
pgendStoreOneCheckpointOnly (be, bp, SQL_INSERT);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_list_free (checkpoints);
|
|
||||||
g_mem_chunk_destroy (chunk);
|
|
||||||
|
|
||||||
p = "COMMIT WORK;";
|
|
||||||
SEND_QUERY (be,p, );
|
|
||||||
FINISH_QUERY(be->connection);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================= */
|
|
||||||
/* recompute fresh balance checkpoints for every account */
|
|
||||||
|
|
||||||
static void
|
|
||||||
pgendGroupRecomputeAllCheckpoints (PGBackend *be, AccountGroup *grp)
|
|
||||||
{
|
|
||||||
GList *acclist, *node;
|
|
||||||
|
|
||||||
acclist = xaccGroupGetSubAccounts(grp);
|
|
||||||
for (node = acclist; node; node=node->next)
|
|
||||||
{
|
|
||||||
Account *acc = (Account *) node->data;
|
|
||||||
pgendAccountRecomputeAllCheckpoints (be, xaccAccountGetGUID(acc));
|
|
||||||
}
|
|
||||||
g_list_free (acclist);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================= */
|
|
||||||
/* get checkpoint value for the account
|
|
||||||
* We find the checkpoint which matches the account and commodity,
|
|
||||||
* for the first date immediately preceeding the date.
|
|
||||||
* Then we fill in the balance fields for the returned query.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void
|
|
||||||
pgendAccountGetCheckpoint (PGBackend *be, Checkpoint *chk)
|
|
||||||
{
|
|
||||||
PGresult *result;
|
|
||||||
int i, nrows;
|
|
||||||
char * p;
|
|
||||||
|
|
||||||
if (!be || !chk) return;
|
|
||||||
ENTER("be=%p", be);
|
|
||||||
|
|
||||||
/* create the query we need */
|
|
||||||
p = be->buff; *p = 0;
|
|
||||||
p = stpcpy (p, "SELECT balance, cleared_balance, reconciled_balance "
|
|
||||||
"FROM gncCheckpoint "
|
|
||||||
"WHERE accountGuid='");
|
|
||||||
p = guid_to_string_buff (chk->account_guid, p);
|
|
||||||
p = stpcpy (p, "' AND commodity='");
|
|
||||||
p = stpcpy (p, chk->commodity);
|
|
||||||
p = stpcpy (p, "' AND date_xpoint <'");
|
|
||||||
p = gnc_timespec_to_iso8601_buff (chk->datetime, p);
|
|
||||||
p = stpcpy (p, "' ORDER BY date_xpoint DESC LIMIT 1;");
|
|
||||||
SEND_QUERY (be,be->buff, );
|
|
||||||
|
|
||||||
i=0; nrows=0;
|
|
||||||
do {
|
|
||||||
GET_RESULTS (be->connection, result);
|
|
||||||
{
|
|
||||||
int j=0, jrows;
|
|
||||||
int ncols = PQnfields (result);
|
|
||||||
jrows = PQntuples (result);
|
|
||||||
nrows += jrows;
|
|
||||||
PINFO ("query result %d has %d rows and %d cols",
|
|
||||||
i, nrows, ncols);
|
|
||||||
|
|
||||||
if (1 < nrows)
|
|
||||||
{
|
|
||||||
PERR ("excess data");
|
|
||||||
PQclear (result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
chk->balance = atoll(DB_GET_VAL("balance", j));
|
|
||||||
chk->cleared_balance = atoll(DB_GET_VAL("cleared_balance", j));
|
|
||||||
chk->reconciled_balance = atoll(DB_GET_VAL("reconciled_balance", j));
|
|
||||||
}
|
|
||||||
|
|
||||||
PQclear (result);
|
|
||||||
i++;
|
|
||||||
} while (result);
|
|
||||||
|
|
||||||
LEAVE("be=%p", be);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================= */
|
|
||||||
/* get checkpoint value for all accounts */
|
|
||||||
|
|
||||||
static void
|
|
||||||
pgendGroupGetAllCheckpoints (PGBackend *be, AccountGroup*grp)
|
|
||||||
{
|
|
||||||
Checkpoint chk;
|
|
||||||
GList *acclist, *node;
|
|
||||||
|
|
||||||
if (!be || !grp) return;
|
|
||||||
ENTER("be=%p", be);
|
|
||||||
|
|
||||||
chk.datetime.tv_sec = time(0);
|
|
||||||
chk.datetime.tv_nsec = 0;
|
|
||||||
|
|
||||||
acclist = xaccGroupGetSubAccounts (grp);
|
|
||||||
|
|
||||||
/* loop over all accounts */
|
|
||||||
for (node=acclist; node; node=node->next)
|
|
||||||
{
|
|
||||||
Account *acc;
|
|
||||||
const gnc_commodity *com;
|
|
||||||
gint64 deno;
|
|
||||||
gnc_numeric baln;
|
|
||||||
gnc_numeric cleared_baln;
|
|
||||||
gnc_numeric reconciled_baln;
|
|
||||||
|
|
||||||
/* setupwhat we will match for */
|
|
||||||
acc = (Account *) node->data;
|
|
||||||
com = xaccAccountGetCommodity(acc);
|
|
||||||
chk.commodity = gnc_commodity_get_unique_name(com);
|
|
||||||
chk.account_guid = xaccAccountGetGUID (acc);
|
|
||||||
chk.balance = 0;
|
|
||||||
chk.cleared_balance = 0;
|
|
||||||
chk.reconciled_balance = 0;
|
|
||||||
|
|
||||||
/* get the checkpoint */
|
|
||||||
pgendAccountGetCheckpoint (be, &chk);
|
|
||||||
|
|
||||||
/* set the account balances */
|
|
||||||
deno = gnc_commodity_get_fraction (com);
|
|
||||||
baln = gnc_numeric_create (chk.balance, deno);
|
|
||||||
cleared_baln = gnc_numeric_create (chk.cleared_balance, deno);
|
|
||||||
reconciled_baln = gnc_numeric_create (chk.reconciled_balance, deno);
|
|
||||||
|
|
||||||
xaccAccountSetStartingBalance (acc, baln,
|
|
||||||
cleared_baln, reconciled_baln);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_list_free (acclist);
|
|
||||||
LEAVE("be=%p", be);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ============================================================= */
|
/* ============================================================= */
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -939,7 +461,6 @@ static gboolean
|
|||||||
pgendCopyTransactionToEngine (PGBackend *be, GUID *trans_guid)
|
pgendCopyTransactionToEngine (PGBackend *be, GUID *trans_guid)
|
||||||
{
|
{
|
||||||
const gnc_commodity *modity=NULL;
|
const gnc_commodity *modity=NULL;
|
||||||
GUID nullguid = *(xaccGUIDNULL());
|
|
||||||
char *pbuff;
|
char *pbuff;
|
||||||
Transaction *trans;
|
Transaction *trans;
|
||||||
PGresult *result;
|
PGresult *result;
|
||||||
@ -1284,7 +805,6 @@ IsGuidInList (GList *list, GUID *guid)
|
|||||||
static void
|
static void
|
||||||
pgendRunQueryHelper (PGBackend *be, const char *qstring)
|
pgendRunQueryHelper (PGBackend *be, const char *qstring)
|
||||||
{
|
{
|
||||||
GUID nullguid = *(xaccGUIDNULL());
|
|
||||||
PGresult *result;
|
PGresult *result;
|
||||||
int i, nrows;
|
int i, nrows;
|
||||||
GList *node, *xact_list = NULL;
|
GList *node, *xact_list = NULL;
|
||||||
@ -1444,7 +964,6 @@ pgendGetAllAccounts (PGBackend *be)
|
|||||||
AccountGroup *topgrp;
|
AccountGroup *topgrp;
|
||||||
char * bufp;
|
char * bufp;
|
||||||
int i, nrows, iacc;
|
int i, nrows, iacc;
|
||||||
GUID nullguid = *(xaccGUIDNULL());
|
|
||||||
|
|
||||||
ENTER ("be=%p", be);
|
ENTER ("be=%p", be);
|
||||||
if (!be) return NULL;
|
if (!be) return NULL;
|
||||||
@ -1590,8 +1109,9 @@ pgend_trans_commit_edit (Backend * bend,
|
|||||||
Transaction * trans,
|
Transaction * trans,
|
||||||
Transaction * oldtrans)
|
Transaction * oldtrans)
|
||||||
{
|
{
|
||||||
|
GList *start, *node;
|
||||||
char * bufp;
|
char * bufp;
|
||||||
int i, ndiffs, nsplits, rollback=0;
|
int ndiffs, rollback=0;
|
||||||
PGBackend *be = (PGBackend *)bend;
|
PGBackend *be = (PGBackend *)bend;
|
||||||
|
|
||||||
ENTER ("be=%p, trans=%p", be, trans);
|
ENTER ("be=%p, trans=%p", be, trans);
|
||||||
@ -1626,9 +1146,10 @@ pgend_trans_commit_edit (Backend * bend,
|
|||||||
if (0 < ndiffs) rollback++;
|
if (0 < ndiffs) rollback++;
|
||||||
|
|
||||||
/* be sure to check the old splits as well ... */
|
/* be sure to check the old splits as well ... */
|
||||||
nsplits = xaccTransCountSplits (oldtrans);
|
start = xaccTransGetSplitList (oldtrans);
|
||||||
for (i=0; i<nsplits; i++) {
|
for (node=start; node; node=node->next)
|
||||||
Split * s = xaccTransGetSplit (oldtrans, i);
|
{
|
||||||
|
Split * s = node->data;
|
||||||
ndiffs = pgendCompareOneSplitOnly (be, s);
|
ndiffs = pgendCompareOneSplitOnly (be, s);
|
||||||
if (0 < ndiffs) rollback++;
|
if (0 < ndiffs) rollback++;
|
||||||
}
|
}
|
||||||
@ -2322,6 +1843,9 @@ pgendEnable (PGBackend *be)
|
|||||||
static void
|
static void
|
||||||
pgendInit (PGBackend *be)
|
pgendInit (PGBackend *be)
|
||||||
{
|
{
|
||||||
|
/* initialize global variable */
|
||||||
|
nullguid = *(xaccGUIDNULL());
|
||||||
|
|
||||||
/* access mode */
|
/* access mode */
|
||||||
be->session_mode = MODE_NONE;
|
be->session_mode = MODE_NONE;
|
||||||
be->sessionGuid = NULL;
|
be->sessionGuid = NULL;
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#ifndef __POSTGRES_BACKEND_H__
|
||||||
|
#define __POSTGRES_BACKEND_H__
|
||||||
|
|
||||||
#include <pgsql/libpq-fe.h>
|
#include <pgsql/libpq-fe.h>
|
||||||
#include "BackendP.h"
|
#include "BackendP.h"
|
||||||
|
|
||||||
@ -82,3 +85,32 @@ typedef struct _checkpoint {
|
|||||||
gint64 reconciled_balance;
|
gint64 reconciled_balance;
|
||||||
} Checkpoint;
|
} Checkpoint;
|
||||||
|
|
||||||
|
/* -------------------------------------------------------- */
|
||||||
|
/* the following prototypes belong in a 'checkpoint.h' file */
|
||||||
|
|
||||||
|
void pgendGroupRecomputeAllCheckpoints (PGBackend *be, AccountGroup *grp);
|
||||||
|
void pgendGroupGetAllCheckpoints (PGBackend *be, AccountGroup*grp);
|
||||||
|
|
||||||
|
/* -------------------------------------------------------- */
|
||||||
|
/* the following protypes belong in a 'autogen.h' file */
|
||||||
|
|
||||||
|
void pgendStoreOneAccountOnly (PGBackend *be, Account *ptr,
|
||||||
|
sqlBuild_QType update);
|
||||||
|
|
||||||
|
void pgendStoreOneCheckpointOnly (PGBackend *be, Checkpoint *ptr,
|
||||||
|
sqlBuild_QType update);
|
||||||
|
|
||||||
|
void pgendStoreOneCommodityOnly (PGBackend *be, gnc_commodity *ptr,
|
||||||
|
sqlBuild_QType update);
|
||||||
|
|
||||||
|
void pgendStoreOneSessionOnly (PGBackend *be, void *ptr,
|
||||||
|
sqlBuild_QType update);
|
||||||
|
|
||||||
|
void pgendStoreOneSplitOnly (PGBackend *be, Split *ptr,
|
||||||
|
sqlBuild_QType update);
|
||||||
|
|
||||||
|
void pgendStoreOneTransactionOnly (PGBackend *be, Transaction *ptr,
|
||||||
|
sqlBuild_QType update);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* __POSTGRES_BACKEND_H__ */
|
||||||
|
@ -7,7 +7,7 @@ problems.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
postgres install instructions
|
Postgres Install Instructions
|
||||||
-----------------------------
|
-----------------------------
|
||||||
1) Install postgresql server, client and devel packages.
|
1) Install postgresql server, client and devel packages.
|
||||||
2) if installed from redhat, then running /etc/rc.d/init.d/postgresql
|
2) if installed from redhat, then running /etc/rc.d/init.d/postgresql
|
||||||
@ -18,6 +18,32 @@ postgres install instructions
|
|||||||
5) as yourself (i.e. your unix login), run 'createdb gnucash'
|
5) as yourself (i.e. your unix login), run 'createdb gnucash'
|
||||||
|
|
||||||
|
|
||||||
|
GnuCash Build Instructions
|
||||||
|
--------------------------
|
||||||
|
Same as usual, but you want to specify the flag '--enable-sql' i.e.
|
||||||
|
./configure --enable-sql
|
||||||
|
and then 'make'.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
How To Use This Thing
|
||||||
|
---------------------
|
||||||
|
a) Open your favorite datafile in the usual fashion.
|
||||||
|
b) Click on 'Save As'
|
||||||
|
c) enter the following URL instead of a filename in the file picker:
|
||||||
|
postgres://localhost/some-name-you-pick
|
||||||
|
|
||||||
|
The above steps will copy your data into that database. You can
|
||||||
|
then restart gnucash (or keep working) and type in the same URL
|
||||||
|
in the file open dialogs. Or try it on the commandline:
|
||||||
|
|
||||||
|
/usr/local/bin/gnucash postgres://localhost/whatever
|
||||||
|
|
||||||
|
Note that you *must* use 'localhost' for your hostname, and
|
||||||
|
not some other hostname. This is because anything else requires
|
||||||
|
an sql username & password, and we don't have the gui dialogs for
|
||||||
|
that yet.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
To Be Done
|
To Be Done
|
||||||
@ -34,11 +60,14 @@ Core bugs/features that still need work:
|
|||||||
-- bug: group sync doesn't pull in newer data from the db ...
|
-- bug: group sync doesn't pull in newer data from the db ...
|
||||||
(related to the 'save as' above)
|
(related to the 'save as' above)
|
||||||
|
|
||||||
-- allow user to enter URL in GUI dialog
|
-- allow user to enter URL in GUI dialog, get GUI to remember the URL
|
||||||
|
|
||||||
-- Implement GUI to ask user for username/password to log onto the
|
-- Implement GUI to ask user for username/password to log onto the
|
||||||
server.
|
server.
|
||||||
|
|
||||||
|
-- fix the annoying postgres:,,localhost,asdf file syntax: needs
|
||||||
|
mods to gnc-book to keep it happy about lock files & such.
|
||||||
|
|
||||||
|
|
||||||
To Be Done, Part II
|
To Be Done, Part II
|
||||||
-------------------
|
-------------------
|
||||||
@ -47,10 +76,11 @@ This list only affects the multi-user and advanced/optional features.
|
|||||||
-- implement account commit edit (actually, the check&rollback part)
|
-- implement account commit edit (actually, the check&rollback part)
|
||||||
(need to check the account version number beofer the commit happens)
|
(need to check the account version number beofer the commit happens)
|
||||||
|
|
||||||
-- fix excessive use of account commit by engine
|
-- use version numbers for accounts, commodities, splits & transactions,
|
||||||
|
as this will provide a far more efficient 'compare' for
|
||||||
|
user changes.
|
||||||
|
|
||||||
-- provide support for more query types in gncquery.c
|
-- provide support for more query types in gncquery.c
|
||||||
-- optimize for quantity of SQL traffic -- there's a lot of it,
|
|
||||||
much of it probably un-needed.
|
|
||||||
|
|
||||||
-- Implement logging history in the SQL server. i.e. save the old
|
-- Implement logging history in the SQL server. i.e. save the old
|
||||||
copies of stuff in log tables. Make the username part of the
|
copies of stuff in log tables. Make the username part of the
|
||||||
|
324
src/engine/sql/checkpoint.c
Normal file
324
src/engine/sql/checkpoint.c
Normal file
@ -0,0 +1,324 @@
|
|||||||
|
/*
|
||||||
|
* FILE:
|
||||||
|
* checkpoint.c
|
||||||
|
*
|
||||||
|
* FUNCTION:
|
||||||
|
* account balance checkpointing.
|
||||||
|
*
|
||||||
|
* HISTORY:
|
||||||
|
* Copyright (c) 2000, 2001 Linas Vepstas
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#include <glib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <pgsql/libpq-fe.h>
|
||||||
|
|
||||||
|
#include "Account.h"
|
||||||
|
#include "AccountP.h"
|
||||||
|
#include "Backend.h"
|
||||||
|
#include "BackendP.h"
|
||||||
|
#include "Group.h"
|
||||||
|
#include "gnc-commodity.h"
|
||||||
|
// #include "gnc-engine.h"
|
||||||
|
#include "gnc-engine-util.h"
|
||||||
|
// #include "gnc-event.h"
|
||||||
|
#include "guid.h"
|
||||||
|
#include "GNCId.h"
|
||||||
|
#include "GNCIdP.h"
|
||||||
|
|
||||||
|
#include "builder.h"
|
||||||
|
#include "PostgresBackend.h"
|
||||||
|
|
||||||
|
#include "putil.h"
|
||||||
|
|
||||||
|
static short module = MOD_BACKEND;
|
||||||
|
|
||||||
|
/* ============================================================= */
|
||||||
|
/* recompute *all* checkpoints for the account */
|
||||||
|
|
||||||
|
static void
|
||||||
|
pgendAccountRecomputeAllCheckpoints (PGBackend *be, const GUID *acct_guid)
|
||||||
|
{
|
||||||
|
Timespec this_ts, prev_ts;
|
||||||
|
GMemChunk *chunk;
|
||||||
|
GList *node, *checkpoints = NULL;
|
||||||
|
PGresult *result;
|
||||||
|
Checkpoint *bp;
|
||||||
|
char *p;
|
||||||
|
int i, nrows, nsplits;
|
||||||
|
Account *acc;
|
||||||
|
const char *commodity_name;
|
||||||
|
|
||||||
|
if (!be) return;
|
||||||
|
ENTER("be=%p", be);
|
||||||
|
|
||||||
|
acc = xaccLookupEntity (acct_guid, GNC_ID_ACCOUNT);
|
||||||
|
commodity_name = gnc_commodity_get_unique_name (xaccAccountGetCommodity(acc));
|
||||||
|
|
||||||
|
chunk = g_mem_chunk_create (Checkpoint, 300, G_ALLOC_ONLY);
|
||||||
|
|
||||||
|
/* prevent others from inserting any splits while we recompute
|
||||||
|
* the checkpoints. (hack alert -verify that this is the correct
|
||||||
|
* lock) */
|
||||||
|
p = "BEGIN WORK; "
|
||||||
|
"LOCK TABLE gncEntry IN SHARE MODE; "
|
||||||
|
"LOCK TABLE gncCheckpoint IN ACCESS EXCLUSIVE MODE; ";
|
||||||
|
SEND_QUERY (be,p, );
|
||||||
|
FINISH_QUERY(be->connection);
|
||||||
|
|
||||||
|
/* Blow all the old checkpoints for this account out of the water.
|
||||||
|
* This should help ensure against accidental corruption.
|
||||||
|
*/
|
||||||
|
p = be->buff; *p = 0;
|
||||||
|
p = stpcpy (p, "DELETE FROM gncCheckpoint WHERE accountGuid='");
|
||||||
|
p = guid_to_string_buff (acct_guid, p);
|
||||||
|
p = stpcpy (p, "';");
|
||||||
|
SEND_QUERY (be,be->buff, );
|
||||||
|
FINISH_QUERY(be->connection);
|
||||||
|
|
||||||
|
/* and now, fetch *all* of the splits in this account */
|
||||||
|
p = be->buff; *p = 0;
|
||||||
|
p = stpcpy (p, "SELECT gncEntry.amountNum AS amountNum, "
|
||||||
|
" gncEntry.reconciled AS reconciled,"
|
||||||
|
" gncTransaction.date_posted AS date_posted "
|
||||||
|
"FROM gncEntry, gncTransaction "
|
||||||
|
"WHERE gncEntry.transGuid = gncTransaction.transGuid "
|
||||||
|
"AND accountGuid='");
|
||||||
|
p = guid_to_string_buff (acct_guid, p);
|
||||||
|
p = stpcpy (p, "' ORDER BY gncTransaction.date_posted ASC;");
|
||||||
|
SEND_QUERY (be,be->buff, );
|
||||||
|
|
||||||
|
/* malloc a new checkpoint, set it to the dawn of AD time ... */
|
||||||
|
bp = g_chunk_new0 (Checkpoint, chunk);
|
||||||
|
checkpoints = g_list_prepend (checkpoints, bp);
|
||||||
|
this_ts = gnc_iso8601_to_timespec_local ("1970-04-15 08:35:46.00");
|
||||||
|
bp->datetime = this_ts;
|
||||||
|
bp->account_guid = acct_guid;
|
||||||
|
bp->commodity = commodity_name;
|
||||||
|
|
||||||
|
/* malloc a new checkpoint ... */
|
||||||
|
nsplits = 0;
|
||||||
|
bp = g_chunk_new0 (Checkpoint, chunk);
|
||||||
|
checkpoints = g_list_prepend (checkpoints, bp);
|
||||||
|
bp->account_guid = acct_guid;
|
||||||
|
bp->commodity = commodity_name;
|
||||||
|
|
||||||
|
/* start adding up balances */
|
||||||
|
i=0; nrows=0;
|
||||||
|
do {
|
||||||
|
GET_RESULTS (be->connection, result);
|
||||||
|
{
|
||||||
|
int j, jrows;
|
||||||
|
int ncols = PQnfields (result);
|
||||||
|
jrows = PQntuples (result);
|
||||||
|
nrows += jrows;
|
||||||
|
PINFO ("query result %d has %d rows and %d cols",
|
||||||
|
i, nrows, ncols);
|
||||||
|
|
||||||
|
for (j=0; j<jrows; j++)
|
||||||
|
{
|
||||||
|
gint64 amt;
|
||||||
|
char recn;
|
||||||
|
|
||||||
|
/* lets see if its time to start a new checkpoint */
|
||||||
|
/* look for splits that occur at least ten seconds apart */
|
||||||
|
prev_ts = this_ts;
|
||||||
|
prev_ts.tv_sec += 10;
|
||||||
|
this_ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_posted",j));
|
||||||
|
if ((MIN_CHECKPOINT_COUNT < nsplits) &&
|
||||||
|
(timespec_cmp (&prev_ts, &this_ts) < 0))
|
||||||
|
{
|
||||||
|
Checkpoint *next_bp;
|
||||||
|
|
||||||
|
/* Set checkpoint five seconds back. This is safe,
|
||||||
|
* because we looked for a 10 second gap above */
|
||||||
|
this_ts.tv_sec -= 5;
|
||||||
|
bp->datetime = this_ts;
|
||||||
|
|
||||||
|
/* and now, build a new checkpoint */
|
||||||
|
nsplits = 0;
|
||||||
|
next_bp = g_chunk_new0 (Checkpoint, chunk);
|
||||||
|
checkpoints = g_list_prepend (checkpoints, next_bp);
|
||||||
|
*next_bp = *bp;
|
||||||
|
bp = next_bp;
|
||||||
|
bp->account_guid = acct_guid;
|
||||||
|
bp->commodity = commodity_name;
|
||||||
|
}
|
||||||
|
nsplits ++;
|
||||||
|
|
||||||
|
/* accumulate balances */
|
||||||
|
amt = atoll (DB_GET_VAL("amountNum",j));
|
||||||
|
recn = (DB_GET_VAL("reconciled",j))[0];
|
||||||
|
bp->balance += amt;
|
||||||
|
if (NREC != recn)
|
||||||
|
{
|
||||||
|
bp->cleared_balance += amt;
|
||||||
|
}
|
||||||
|
if (YREC == recn)
|
||||||
|
{
|
||||||
|
bp->reconciled_balance += amt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear (result);
|
||||||
|
i++;
|
||||||
|
} while (result);
|
||||||
|
|
||||||
|
/* set the timestamp on the final checkpoint,
|
||||||
|
* 8 seconds past the very last split */
|
||||||
|
this_ts.tv_sec += 8;
|
||||||
|
bp->datetime = this_ts;
|
||||||
|
|
||||||
|
/* now store the checkpoints */
|
||||||
|
for (node = checkpoints; node; node = node->next)
|
||||||
|
{
|
||||||
|
bp = (Checkpoint *) node->data;
|
||||||
|
pgendStoreOneCheckpointOnly (be, bp, SQL_INSERT);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_list_free (checkpoints);
|
||||||
|
g_mem_chunk_destroy (chunk);
|
||||||
|
|
||||||
|
p = "COMMIT WORK;";
|
||||||
|
SEND_QUERY (be,p, );
|
||||||
|
FINISH_QUERY(be->connection);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================= */
|
||||||
|
/* recompute fresh balance checkpoints for every account */
|
||||||
|
|
||||||
|
void
|
||||||
|
pgendGroupRecomputeAllCheckpoints (PGBackend *be, AccountGroup *grp)
|
||||||
|
{
|
||||||
|
GList *acclist, *node;
|
||||||
|
|
||||||
|
acclist = xaccGroupGetSubAccounts(grp);
|
||||||
|
for (node = acclist; node; node=node->next)
|
||||||
|
{
|
||||||
|
Account *acc = (Account *) node->data;
|
||||||
|
pgendAccountRecomputeAllCheckpoints (be, xaccAccountGetGUID(acc));
|
||||||
|
}
|
||||||
|
g_list_free (acclist);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================= */
|
||||||
|
/* get checkpoint value for the account
|
||||||
|
* We find the checkpoint which matches the account and commodity,
|
||||||
|
* for the first date immediately preceeding the date.
|
||||||
|
* Then we fill in the balance fields for the returned query.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
pgendAccountGetCheckpoint (PGBackend *be, Checkpoint *chk)
|
||||||
|
{
|
||||||
|
PGresult *result;
|
||||||
|
int i, nrows;
|
||||||
|
char * p;
|
||||||
|
|
||||||
|
if (!be || !chk) return;
|
||||||
|
ENTER("be=%p", be);
|
||||||
|
|
||||||
|
/* create the query we need */
|
||||||
|
p = be->buff; *p = 0;
|
||||||
|
p = stpcpy (p, "SELECT balance, cleared_balance, reconciled_balance "
|
||||||
|
"FROM gncCheckpoint "
|
||||||
|
"WHERE accountGuid='");
|
||||||
|
p = guid_to_string_buff (chk->account_guid, p);
|
||||||
|
p = stpcpy (p, "' AND commodity='");
|
||||||
|
p = stpcpy (p, chk->commodity);
|
||||||
|
p = stpcpy (p, "' AND date_xpoint <'");
|
||||||
|
p = gnc_timespec_to_iso8601_buff (chk->datetime, p);
|
||||||
|
p = stpcpy (p, "' ORDER BY date_xpoint DESC LIMIT 1;");
|
||||||
|
SEND_QUERY (be,be->buff, );
|
||||||
|
|
||||||
|
i=0; nrows=0;
|
||||||
|
do {
|
||||||
|
GET_RESULTS (be->connection, result);
|
||||||
|
{
|
||||||
|
int j=0, jrows;
|
||||||
|
int ncols = PQnfields (result);
|
||||||
|
jrows = PQntuples (result);
|
||||||
|
nrows += jrows;
|
||||||
|
PINFO ("query result %d has %d rows and %d cols",
|
||||||
|
i, nrows, ncols);
|
||||||
|
|
||||||
|
if (1 < nrows)
|
||||||
|
{
|
||||||
|
PERR ("excess data");
|
||||||
|
PQclear (result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chk->balance = atoll(DB_GET_VAL("balance", j));
|
||||||
|
chk->cleared_balance = atoll(DB_GET_VAL("cleared_balance", j));
|
||||||
|
chk->reconciled_balance = atoll(DB_GET_VAL("reconciled_balance", j));
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear (result);
|
||||||
|
i++;
|
||||||
|
} while (result);
|
||||||
|
|
||||||
|
LEAVE("be=%p", be);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================================= */
|
||||||
|
/* get checkpoint value for all accounts */
|
||||||
|
|
||||||
|
void
|
||||||
|
pgendGroupGetAllCheckpoints (PGBackend *be, AccountGroup*grp)
|
||||||
|
{
|
||||||
|
Checkpoint chk;
|
||||||
|
GList *acclist, *node;
|
||||||
|
|
||||||
|
if (!be || !grp) return;
|
||||||
|
ENTER("be=%p", be);
|
||||||
|
|
||||||
|
chk.datetime.tv_sec = time(0);
|
||||||
|
chk.datetime.tv_nsec = 0;
|
||||||
|
|
||||||
|
acclist = xaccGroupGetSubAccounts (grp);
|
||||||
|
|
||||||
|
/* loop over all accounts */
|
||||||
|
for (node=acclist; node; node=node->next)
|
||||||
|
{
|
||||||
|
Account *acc;
|
||||||
|
const gnc_commodity *com;
|
||||||
|
gint64 deno;
|
||||||
|
gnc_numeric baln;
|
||||||
|
gnc_numeric cleared_baln;
|
||||||
|
gnc_numeric reconciled_baln;
|
||||||
|
|
||||||
|
/* setupwhat we will match for */
|
||||||
|
acc = (Account *) node->data;
|
||||||
|
com = xaccAccountGetCommodity(acc);
|
||||||
|
chk.commodity = gnc_commodity_get_unique_name(com);
|
||||||
|
chk.account_guid = xaccAccountGetGUID (acc);
|
||||||
|
chk.balance = 0;
|
||||||
|
chk.cleared_balance = 0;
|
||||||
|
chk.reconciled_balance = 0;
|
||||||
|
|
||||||
|
/* get the checkpoint */
|
||||||
|
pgendAccountGetCheckpoint (be, &chk);
|
||||||
|
|
||||||
|
/* set the account balances */
|
||||||
|
deno = gnc_commodity_get_fraction (com);
|
||||||
|
baln = gnc_numeric_create (chk.balance, deno);
|
||||||
|
cleared_baln = gnc_numeric_create (chk.cleared_balance, deno);
|
||||||
|
reconciled_baln = gnc_numeric_create (chk.reconciled_balance, deno);
|
||||||
|
|
||||||
|
xaccAccountSetStartingBalance (acc, baln,
|
||||||
|
cleared_baln, reconciled_baln);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_list_free (acclist);
|
||||||
|
LEAVE("be=%p", be);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ======================== END OF FILE ======================== */
|
258
src/engine/sql/putil.h
Normal file
258
src/engine/sql/putil.h
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* FILE:
|
||||||
|
* putil.h
|
||||||
|
*
|
||||||
|
* FUNCTION:
|
||||||
|
* Postgres backend utility macros
|
||||||
|
*
|
||||||
|
* HISTORY:
|
||||||
|
* Copyright (c) 2000, 2001 Linas Vepstas
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __P_UTIL_H__
|
||||||
|
#define __P_UTIL_H__
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
#include <pgsql/libpq-fe.h>
|
||||||
|
|
||||||
|
#include "Backend.h"
|
||||||
|
#include "BackendP.h"
|
||||||
|
#include "gnc-engine-util.h"
|
||||||
|
#include "guid.h"
|
||||||
|
#include "GNCId.h"
|
||||||
|
|
||||||
|
#include "PostgresBackend.h"
|
||||||
|
|
||||||
|
|
||||||
|
extern GUID nullguid;
|
||||||
|
|
||||||
|
/* hack alert -- calling PQFinish() is quite harsh, since all
|
||||||
|
* subsequent sql queries will fail. On the other hand, killing
|
||||||
|
* anything that follows *is* a way of minimizing data corruption
|
||||||
|
* due to subsequent mishaps ... so anyway, error handling in these
|
||||||
|
* routines needs to be rethought.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ============================================================= */
|
||||||
|
/* 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,buff,retval) \
|
||||||
|
{ \
|
||||||
|
int rc; \
|
||||||
|
rc = PQsendQuery (be->connection, buff); \
|
||||||
|
if (!rc) \
|
||||||
|
{ \
|
||||||
|
/* hack alert -- we need kinder, gentler error handling */\
|
||||||
|
PERR("send query failed:\n" \
|
||||||
|
"\t%s", PQerrorMessage(be->connection)); \
|
||||||
|
PQfinish (be->connection); \
|
||||||
|
xaccBackendSetError (&be->be, ERR_SQL_SEND_QUERY_FAILED); \
|
||||||
|
return retval; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------- */
|
||||||
|
/* 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) \
|
||||||
|
{ \
|
||||||
|
int i=0; \
|
||||||
|
PGresult *result; \
|
||||||
|
/* complete/commit the transaction, check the status */ \
|
||||||
|
do { \
|
||||||
|
ExecStatusType status; \
|
||||||
|
result = PQgetResult((conn)); \
|
||||||
|
if (!result) break; \
|
||||||
|
PINFO ("clearing result %d", i); \
|
||||||
|
status = PQresultStatus(result); \
|
||||||
|
if (PGRES_COMMAND_OK != status) { \
|
||||||
|
PERR("finish query failed:\n" \
|
||||||
|
"\t%s", PQerrorMessage((conn))); \
|
||||||
|
PQclear(result); \
|
||||||
|
PQfinish ((conn)); \
|
||||||
|
xaccBackendSetError (&be->be, ERR_SQL_FINISH_QUERY_FAILED); \
|
||||||
|
} \
|
||||||
|
PQclear(result); \
|
||||||
|
i++; \
|
||||||
|
} while (result); \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------- */
|
||||||
|
/* The GET_RESULTS macro grabs the result of an pgSQL 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" \
|
||||||
|
"\t%s", PQerrorMessage((conn))); \
|
||||||
|
PQclear (result); \
|
||||||
|
PQfinish (conn); \
|
||||||
|
xaccBackendSetError (&be->be, ERR_SQL_GET_RESULT_FAILED); \
|
||||||
|
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", \
|
||||||
|
loopcounter, nrows, ncols); \
|
||||||
|
} \
|
||||||
|
if (1 < nrows) { \
|
||||||
|
PERR ("unexpected duplicate records"); \
|
||||||
|
xaccBackendSetError (&be->be, ERR_SQL_CORRUPT_DB); \
|
||||||
|
break; \
|
||||||
|
} else if (1 == nrows)
|
||||||
|
|
||||||
|
/* --------------------------------------------------------------- */
|
||||||
|
/* Some utility macros for comparing values returned from the
|
||||||
|
* database to values in the engine structs. These macros
|
||||||
|
* all take three arguments:
|
||||||
|
* -- sqlname -- input -- the name of the field in the sql table
|
||||||
|
* -- fun -- input -- a subroutine returning a value
|
||||||
|
* -- ndiffs -- input/output -- integer, incremented if the
|
||||||
|
* value ofthe field and the value returned by
|
||||||
|
* the subroutine differ.
|
||||||
|
*
|
||||||
|
* The different macros compare different field types.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DB_GET_VAL(str,n) (PQgetvalue (result, n, PQfnumber (result, str)))
|
||||||
|
|
||||||
|
/* Compare string types. Null strings and empty strings are
|
||||||
|
* considered to be equal */
|
||||||
|
#define COMP_STR(sqlname,fun,ndiffs) { \
|
||||||
|
if (null_strcmp (DB_GET_VAL(sqlname,0),fun)) { \
|
||||||
|
PINFO("mis-match: %s sql='%s', eng='%s'", sqlname, \
|
||||||
|
DB_GET_VAL (sqlname,0), fun); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compare commodities. This routine is almost identical to
|
||||||
|
* COMP_STR, except that a NULL currency from the engine
|
||||||
|
* is allowed to match any currency in the sql DB. This is
|
||||||
|
* used to facilitate deletion, where the currency has been
|
||||||
|
* nulled out .. */
|
||||||
|
#define COMP_COMMODITY(sqlname,fun,ndiffs) { \
|
||||||
|
const char *com = fun; \
|
||||||
|
if (com) { \
|
||||||
|
if (null_strcmp (DB_GET_VAL(sqlname,0),com)) { \
|
||||||
|
PINFO("mis-match: %s sql='%s', eng='%s'", sqlname, \
|
||||||
|
DB_GET_VAL (sqlname,0), fun); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compare guids. A NULL GUID from the engine is considered to
|
||||||
|
* match any value of a GUID in teh sql database. This is
|
||||||
|
* equality is used to enable deletion, where the GUID may have
|
||||||
|
* already been set to NULL in the engine, but not yet in the DB.
|
||||||
|
*/
|
||||||
|
#define COMP_GUID(sqlname,fun, ndiffs) { \
|
||||||
|
char guid_str[GUID_ENCODING_LENGTH+1]; \
|
||||||
|
const GUID *guid = fun; \
|
||||||
|
if (!guid_equal (guid, &nullguid)) { \
|
||||||
|
guid_to_string_buff(guid, guid_str); \
|
||||||
|
if (null_strcmp (DB_GET_VAL(sqlname,0),guid_str)) { \
|
||||||
|
PINFO("mis-match: %s sql='%s', eng='%s'", sqlname, \
|
||||||
|
DB_GET_VAL(sqlname,0), guid_str); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comapre one char only */
|
||||||
|
#define COMP_CHAR(sqlname,fun, ndiffs) { \
|
||||||
|
if (tolower((DB_GET_VAL(sqlname,0))[0]) != tolower(fun)) { \
|
||||||
|
PINFO("mis-match: %s sql=%c eng=%c", sqlname, \
|
||||||
|
tolower((DB_GET_VAL(sqlname,0))[0]), tolower(fun)); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compare dates.
|
||||||
|
* Assumes the datestring is in ISO-8601 format
|
||||||
|
* i.e. looks like 1998-07-17 11:00:00.68-05
|
||||||
|
* hack-alert doesn't compare nano-seconds ..
|
||||||
|
* this is intentional, its because I suspect
|
||||||
|
* the sql db round nanoseconds off ...
|
||||||
|
*/
|
||||||
|
#define COMP_DATE(sqlname,fun,ndiffs) { \
|
||||||
|
Timespec eng_time = fun; \
|
||||||
|
Timespec sql_time = gnc_iso8601_to_timespec_local( \
|
||||||
|
DB_GET_VAL(sqlname,0)); \
|
||||||
|
if (eng_time.tv_sec != sql_time.tv_sec) { \
|
||||||
|
char buff[80]; \
|
||||||
|
gnc_timespec_to_iso8601_buff(eng_time, buff); \
|
||||||
|
PINFO("mis-match: %s sql='%s' eng=%s", sqlname, \
|
||||||
|
DB_GET_VAL(sqlname,0), buff); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compare the date of last modification.
|
||||||
|
* This is a special date comp to make the m4 macros simpler.
|
||||||
|
*/
|
||||||
|
#define COMP_NOW(sqlname,fun,ndiffs) { \
|
||||||
|
Timespec eng_time = xaccTransRetDateEnteredTS(ptr); \
|
||||||
|
Timespec sql_time = gnc_iso8601_to_timespec_local( \
|
||||||
|
DB_GET_VAL(sqlname,0)); \
|
||||||
|
if (eng_time.tv_sec != sql_time.tv_sec) { \
|
||||||
|
char buff[80]; \
|
||||||
|
gnc_timespec_to_iso8601_buff(eng_time, buff); \
|
||||||
|
PINFO("mis-match: %s sql='%s' eng=%s", sqlname, \
|
||||||
|
DB_GET_VAL(sqlname,0), buff); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Compare long-long integers */
|
||||||
|
#define COMP_INT64(sqlname,fun,ndiffs) { \
|
||||||
|
if (atoll (DB_GET_VAL(sqlname,0)) != fun) { \
|
||||||
|
PINFO("mis-match: %s sql='%s', eng='%lld'", sqlname, \
|
||||||
|
DB_GET_VAL (sqlname,0), fun); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* compare 32-bit ints */
|
||||||
|
#define COMP_INT32(sqlname,fun,ndiffs) { \
|
||||||
|
if (atol (DB_GET_VAL(sqlname,0)) != fun) { \
|
||||||
|
PINFO("mis-match: %s sql='%s', eng='%d'", sqlname, \
|
||||||
|
DB_GET_VAL (sqlname,0), fun); \
|
||||||
|
ndiffs++; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* __P_UTIL_H__ */
|
||||||
|
|
||||||
|
/* ======================== END OF FILE ======================== */
|
@ -41,7 +41,7 @@ define(`split', `gncEntry, Split, Split,
|
|||||||
define(`transaction', `gncTransaction, Transaction, Transaction,
|
define(`transaction', `gncTransaction, Transaction, Transaction,
|
||||||
num, , char *, xaccTransGetNum(ptr),
|
num, , char *, xaccTransGetNum(ptr),
|
||||||
description, , char *, xaccTransGetDescription(ptr),
|
description, , char *, xaccTransGetDescription(ptr),
|
||||||
currency, , char *, gnc_commodity_get_unique_name(xaccTransGetCurrency(ptr)),
|
currency, , commod, gnc_commodity_get_unique_name(xaccTransGetCurrency(ptr)),
|
||||||
date_entered, , now, "NOW",
|
date_entered, , now, "NOW",
|
||||||
date_posted, , Timespec, xaccTransRetDatePostedTS(ptr),
|
date_posted, , Timespec, xaccTransRetDatePostedTS(ptr),
|
||||||
transGUID, KEY, GUID *, xaccTransGetGUID(ptr),
|
transGUID, KEY, GUID *, xaccTransGetGUID(ptr),
|
||||||
@ -97,6 +97,7 @@ define(`sql_setter', `ifelse($2, `KEY',
|
|||||||
$2, ,
|
$2, ,
|
||||||
`ifelse($1, `char *', sqlBuild_Set_Str,
|
`ifelse($1, `char *', sqlBuild_Set_Str,
|
||||||
$1, `now', sqlBuild_Set_Str,
|
$1, `now', sqlBuild_Set_Str,
|
||||||
|
$1, `commod', sqlBuild_Set_Str,
|
||||||
$1, `int32', sqlBuild_Set_Int32,
|
$1, `int32', sqlBuild_Set_Int32,
|
||||||
$1, `int64', sqlBuild_Set_Int64,
|
$1, `int64', sqlBuild_Set_Int64,
|
||||||
$1, `GUID *', sqlBuild_Set_GUID,
|
$1, `GUID *', sqlBuild_Set_GUID,
|
||||||
@ -114,12 +115,15 @@ define(`set_fields', `set_fields_r(firstrec($@))')
|
|||||||
|
|
||||||
/* -------- */
|
/* -------- */
|
||||||
/* macros to compare a query result */
|
/* macros to compare a query result */
|
||||||
|
/* the commod type behaves just like a string, except it
|
||||||
|
* has its one compre function. */
|
||||||
|
|
||||||
define(`cmp_value', `ifelse($1, `char *', COMP_STR,
|
define(`cmp_value', `ifelse($1, `char *', COMP_STR,
|
||||||
$1, `now', COMP_NOW,
|
$1, `now', COMP_NOW,
|
||||||
$1, `int32', COMP_INT32,
|
$1, `int32', COMP_INT32,
|
||||||
$1, `int64', COMP_INT64,
|
$1, `int64', COMP_INT64,
|
||||||
$1, `GUID *', COMP_GUID,
|
$1, `GUID *', COMP_GUID,
|
||||||
|
$1, `commod', COMP_COMMODITY,
|
||||||
$1, `Timespec', COMP_DATE,
|
$1, `Timespec', COMP_DATE,
|
||||||
$1, `char', COMP_CHAR)')
|
$1, `char', COMP_CHAR)')
|
||||||
|
|
||||||
@ -143,7 +147,7 @@ define(`store_one_only',
|
|||||||
* It just pokes the data in
|
* It just pokes the data in
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static void
|
void
|
||||||
pgendStoreOne`'func_name($@)`'Only (PGBackend *be,
|
pgendStoreOne`'func_name($@)`'Only (PGBackend *be,
|
||||||
xacc_type($@) *ptr,
|
xacc_type($@) *ptr,
|
||||||
sqlBuild_QType update)
|
sqlBuild_QType update)
|
||||||
|
Loading…
Reference in New Issue
Block a user