mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
move prices, transactions to own file
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@4585 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
@@ -13,6 +13,7 @@ libgnc_postgres_la_SOURCES = \
|
||||
events.c \
|
||||
gncquery.c \
|
||||
kvp-sql.c \
|
||||
price.c \
|
||||
txn.c
|
||||
|
||||
noinst_HEADERS = \
|
||||
@@ -23,6 +24,7 @@ noinst_HEADERS = \
|
||||
events.h \
|
||||
gncquery.h \
|
||||
kvp-sql.h \
|
||||
price.h \
|
||||
putil.h \
|
||||
txn.h
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
#include "gncquery.h"
|
||||
#include "kvp-sql.h"
|
||||
#include "PostgresBackend.h"
|
||||
#include "price.h"
|
||||
#include "txn.h"
|
||||
|
||||
#include "putil.h"
|
||||
@@ -440,57 +441,6 @@ pgendGetAllAccountKVP (PGBackend *be, AccountGroup *grp)
|
||||
xaccGroupForEachAccount (grp, restore_cb, be, TRUE);
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* This routine restores all commodities in the database.
|
||||
*/
|
||||
|
||||
static gpointer
|
||||
get_commodities_cb (PGBackend *be, PGresult *result, int j, gpointer data)
|
||||
{
|
||||
gnc_commodity_table *comtab = (gnc_commodity_table *) data;
|
||||
gnc_commodity *com;
|
||||
|
||||
/* first, lets see if we've already got this one */
|
||||
com = gnc_commodity_table_lookup(comtab,
|
||||
DB_GET_VAL("namespace",j), DB_GET_VAL("mnemonic",j));
|
||||
|
||||
if (com) return comtab;
|
||||
|
||||
/* no we don't ... restore it */
|
||||
com = gnc_commodity_new (
|
||||
DB_GET_VAL("fullname",j),
|
||||
DB_GET_VAL("namespace",j),
|
||||
DB_GET_VAL("mnemonic",j),
|
||||
DB_GET_VAL("code",j),
|
||||
atoi(DB_GET_VAL("fraction",j)));
|
||||
|
||||
gnc_commodity_table_insert (comtab, com);
|
||||
return comtab;
|
||||
}
|
||||
|
||||
static void
|
||||
pgendGetAllCommodities (PGBackend *be)
|
||||
{
|
||||
gnc_commodity_table *comtab;
|
||||
char * p;
|
||||
if (!be) return;
|
||||
|
||||
ENTER ("be=%p, conn=%p", be, be->connection);
|
||||
|
||||
comtab = gnc_engine_commodities();
|
||||
if (!comtab) {
|
||||
PERR ("can't get global commodity table");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get them ALL */
|
||||
p = "SELECT * FROM gncCommodity;";
|
||||
SEND_QUERY (be, p, );
|
||||
pgendGetResults (be, get_commodities_cb, comtab);
|
||||
|
||||
LEAVE (" ");
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* The pgendGetAllAccounts() routine restores the account hierarchy
|
||||
* of *all* accounts in the DB.
|
||||
@@ -600,12 +550,6 @@ pgendGetAllAccounts (PGBackend *be, AccountGroup *topgrp)
|
||||
return topgrp;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* TRANSACTION STUFF */
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
|
||||
/* ============================================================= */
|
||||
/* QUERY STUFF */
|
||||
/* ============================================================= */
|
||||
@@ -868,298 +812,6 @@ pgendGetAllTransactions (PGBackend *be, AccountGroup *grp)
|
||||
gnc_engine_resume_events();
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* PRICE STUFF */
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* store just one price */
|
||||
|
||||
static void
|
||||
pgendStorePriceNoLock (PGBackend *be, GNCPrice *pr,
|
||||
gboolean do_check_version)
|
||||
{
|
||||
gnc_commodity *modity;
|
||||
|
||||
if (do_check_version)
|
||||
{
|
||||
if (0 < pgendPriceCompareVersion (be, pr)) return;
|
||||
}
|
||||
pr->version ++; /* be sure to update the version !! */
|
||||
|
||||
/* make sure that we've stored the commodity
|
||||
* and currency before we store the price.
|
||||
*/
|
||||
modity = gnc_price_get_commodity (pr);
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
|
||||
modity = gnc_price_get_currency (pr);
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
|
||||
pgendPutOnePriceOnly (be, pr);
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* store entire price database */
|
||||
|
||||
static gboolean
|
||||
foreach_price_cb (GNCPrice *pr, gpointer bend)
|
||||
{
|
||||
PGBackend *be = (PGBackend *) bend;
|
||||
gnc_commodity *modity;
|
||||
gint16 mark;
|
||||
|
||||
/* make sure that we've stored the commodity
|
||||
* and currency before we store the price.
|
||||
* We use marks to avoid redundant stores.
|
||||
*/
|
||||
modity = gnc_price_get_commodity (pr);
|
||||
mark = gnc_commodity_get_mark (modity);
|
||||
if (!mark) {
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
gnc_commodity_set_mark (modity, 1);
|
||||
}
|
||||
|
||||
modity = gnc_price_get_currency (pr);
|
||||
mark = gnc_commodity_get_mark (modity);
|
||||
if (!mark) {
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
gnc_commodity_set_mark (modity, 1);
|
||||
}
|
||||
|
||||
pgendPutOnePriceOnly (be, pr);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
commodity_mark_cb (gnc_commodity *cm, gpointer user_data)
|
||||
{
|
||||
gint32 v = ((gint32) user_data) & 0xffff;
|
||||
gnc_commodity_set_mark (cm, (gint16) v);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
pgendStorePriceDBNoLock (PGBackend *be, GNCPriceDB *prdb)
|
||||
{
|
||||
gnc_commodity_table *comtab = gnc_engine_commodities();
|
||||
|
||||
/* clear the marks on commodities -- we use this to mark
|
||||
* the thing as 'already stored', avoiding redundant stores */
|
||||
gnc_commodity_table_foreach_commodity (comtab, commodity_mark_cb, 0);
|
||||
|
||||
gnc_pricedb_foreach_price (prdb, foreach_price_cb,
|
||||
(gpointer) be, FALSE);
|
||||
|
||||
gnc_commodity_table_foreach_commodity (comtab, commodity_mark_cb, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
pgendStorePriceDB (PGBackend *be, GNCPriceDB *prdb)
|
||||
{
|
||||
char *p;
|
||||
ENTER ("be=%p, prdb=%p", be, prdb);
|
||||
if (!be || !prdb) return;
|
||||
|
||||
/* lock it up so that we store atomically */
|
||||
p = "BEGIN;\n"
|
||||
"LOCK TABLE gncPrice IN EXCLUSIVE MODE;\n";
|
||||
SEND_QUERY (be,p, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
pgendStorePriceDBNoLock (be, prdb);
|
||||
|
||||
p = "COMMIT;\n"
|
||||
"NOTIFY gncPrice;";
|
||||
SEND_QUERY (be,p, );
|
||||
FINISH_QUERY(be->connection);
|
||||
LEAVE(" ");
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* The pgendGetAllPrices() routine sucks *all* of the
|
||||
* prices out of the database. This is a potential
|
||||
* CPU and memory-burner; its use is not suggested for anything
|
||||
* but single-user mode.
|
||||
*/
|
||||
|
||||
static gpointer
|
||||
get_price_cb (PGBackend *be, PGresult *result, int j, gpointer data)
|
||||
{
|
||||
GNCPriceDB *prdb = (GNCPriceDB *) data;
|
||||
GNCPrice *pr;
|
||||
gint32 sql_vers, local_vers;
|
||||
Timespec ts;
|
||||
gint64 num, denom;
|
||||
gnc_numeric value;
|
||||
GUID guid = nullguid;
|
||||
int not_found = 0;
|
||||
|
||||
gnc_commodity * modity;
|
||||
|
||||
/* first, lets see if we've already got this one */
|
||||
string_to_guid (DB_GET_VAL ("priceGuid", j), &guid);
|
||||
pr = gnc_price_lookup (&guid);
|
||||
|
||||
if (!pr)
|
||||
{
|
||||
pr = gnc_price_create();
|
||||
gnc_price_begin_edit (pr);
|
||||
gnc_price_set_guid (pr, &guid);
|
||||
not_found = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
gnc_price_ref (pr);
|
||||
gnc_price_begin_edit (pr);
|
||||
not_found = 0;
|
||||
}
|
||||
|
||||
/* compare versions. Hack alert -- Not sure how to handle failures */
|
||||
sql_vers = atoi (DB_GET_VAL("version",j));
|
||||
local_vers = gnc_price_get_version(pr);
|
||||
if (sql_vers < local_vers) {
|
||||
PERR ("local price version is higher than db !!! local=%d sql=%d",
|
||||
local_vers, sql_vers);
|
||||
gnc_price_commit_edit (pr);
|
||||
gnc_price_unref (pr);
|
||||
return prdb;
|
||||
}
|
||||
gnc_price_set_version (pr, sql_vers);
|
||||
|
||||
modity = gnc_string_to_commodity (DB_GET_VAL("commodity",j));
|
||||
gnc_price_set_commodity (pr, modity);
|
||||
|
||||
modity = gnc_string_to_commodity (DB_GET_VAL("currency",j));
|
||||
gnc_price_set_currency (pr, modity);
|
||||
|
||||
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("time",j));
|
||||
gnc_price_set_time (pr, ts);
|
||||
|
||||
gnc_price_set_source (pr, DB_GET_VAL("source",j));
|
||||
gnc_price_set_type (pr, DB_GET_VAL("type",j));
|
||||
|
||||
num = atoll (DB_GET_VAL("valueNum", j));
|
||||
denom = atoll (DB_GET_VAL("valueDenom", j));
|
||||
value = gnc_numeric_create (num, denom);
|
||||
gnc_price_set_value (pr, value);
|
||||
|
||||
if (not_found) gnc_pricedb_add_price(prdb, pr);
|
||||
gnc_price_commit_edit (pr);
|
||||
gnc_price_unref (pr);
|
||||
|
||||
return prdb;
|
||||
}
|
||||
|
||||
|
||||
static GNCPriceDB *
|
||||
pgendGetAllPrices (PGBackend *be, GNCPriceDB *prdb)
|
||||
{
|
||||
char * p;
|
||||
|
||||
if (!be) return NULL;
|
||||
ENTER ("be=%p, conn=%p", be, be->connection);
|
||||
|
||||
if (!prdb) {
|
||||
prdb = gnc_pricedb_create();
|
||||
}
|
||||
|
||||
/* first, make sure commodities table is up to date */
|
||||
pgendGetAllCommodities (be);
|
||||
|
||||
/* Get them ALL */
|
||||
p = "SELECT * FROM gncPrice;";
|
||||
SEND_QUERY (be, p, prdb);
|
||||
pgendGetResults (be, get_price_cb, prdb);
|
||||
|
||||
LEAVE (" ");
|
||||
return prdb;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
static void
|
||||
pgendPriceLookup (Backend *bend, GNCPriceLookup *look)
|
||||
{
|
||||
PGBackend *be = (PGBackend *)bend;
|
||||
char * p;
|
||||
|
||||
ENTER ("be=%p, lookup=%p", be, look);
|
||||
if (!be || !look) return;
|
||||
|
||||
/* special case the two-way search in terms of more basic primitives */
|
||||
if (LOOKUP_NEAREST_IN_TIME == look->type)
|
||||
{
|
||||
look->type = LOOKUP_LATEST_BEFORE;
|
||||
pgendPriceLookup (bend, look);
|
||||
look->type = LOOKUP_EARLIEST_AFTER;
|
||||
pgendPriceLookup (bend, look);
|
||||
return;
|
||||
}
|
||||
|
||||
/* don't send events to GUI, don't accept callbacks to backend */
|
||||
gnc_engine_suspend_events();
|
||||
pgendDisable(be);
|
||||
|
||||
/* set up the common part of the query */
|
||||
p = be->buff; *p = 0;
|
||||
p = stpcpy (p, "SELECT * FROM gncPrice"
|
||||
" WHERE commodity='");
|
||||
p = stpcpy (p, gnc_commodity_get_unique_name(look->commodity));
|
||||
p = stpcpy (p, "' AND currency='");
|
||||
p = stpcpy (p, gnc_commodity_get_unique_name(look->currency));
|
||||
p = stpcpy (p, "' ");
|
||||
|
||||
switch (look->type)
|
||||
{
|
||||
case LOOKUP_LATEST:
|
||||
p = stpcpy (p, "ORDER BY time DESC LIMIT 1;");
|
||||
break;
|
||||
case LOOKUP_ALL:
|
||||
/* Get all prices for this commodity and currency */
|
||||
p = stpcpy (p, ";");
|
||||
break;
|
||||
case LOOKUP_AT_TIME:
|
||||
p = stpcpy (p, "AND time='");
|
||||
p = gnc_timespec_to_iso8601_buff (look->date, p);
|
||||
p = stpcpy (p, "';");
|
||||
break;
|
||||
case LOOKUP_NEAREST_IN_TIME:
|
||||
PERR ("this can't possibly happen but it did!!!");
|
||||
p = stpcpy (p, ";");
|
||||
break;
|
||||
case LOOKUP_LATEST_BEFORE:
|
||||
p = stpcpy (p, "AND time <= '");
|
||||
p = gnc_timespec_to_iso8601_buff (look->date, p);
|
||||
p = stpcpy (p, "' ORDER BY time DESC LIMIT 1;");
|
||||
break;
|
||||
case LOOKUP_EARLIEST_AFTER:
|
||||
p = stpcpy (p, "AND time >= '");
|
||||
p = gnc_timespec_to_iso8601_buff (look->date, p);
|
||||
p = stpcpy (p, "' ORDER BY time ASC LIMIT 1;");
|
||||
break;
|
||||
default:
|
||||
PERR ("unknown lookup type %d", look->type);
|
||||
/* re-enable events */
|
||||
pgendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
return;
|
||||
}
|
||||
|
||||
SEND_QUERY (be, be->buff, );
|
||||
pgendGetResults (be, get_price_cb, look->prdb);
|
||||
|
||||
/* insertion into the price db will mark it dirty;
|
||||
* but it really isn't at this point. */
|
||||
gnc_pricedb_mark_clean (look->prdb);
|
||||
|
||||
/* re-enable events */
|
||||
pgendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
@@ -1248,81 +900,6 @@ pgend_account_commit_edit (Backend * bend,
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
static int
|
||||
pgend_price_begin_edit (Backend * bend, GNCPrice *pr)
|
||||
{
|
||||
if (pr && pr->db && pr->db->dirty)
|
||||
{
|
||||
PERR ("price db is unexpectedly dirty");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
pgend_price_commit_edit (Backend * bend, GNCPrice *pr)
|
||||
{
|
||||
char * bufp;
|
||||
PGBackend *be = (PGBackend *)bend;
|
||||
|
||||
ENTER ("be=%p, price=%p", be, pr);
|
||||
if (!be || !pr) return 1; /* hack alert hardcode literal */
|
||||
|
||||
/* lock it up so that we query and store atomically */
|
||||
bufp = "BEGIN;\n"
|
||||
"LOCK TABLE gncPrice IN EXCLUSIVE MODE;\n";
|
||||
SEND_QUERY (be,bufp, 555);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* check to see that the engine version is equal or newer than
|
||||
* whats in the database. It its not, then some other user has
|
||||
* made changes, and we must roll back. */
|
||||
if (0 < pgendPriceCompareVersion (be, pr))
|
||||
{
|
||||
pr->do_free = FALSE;
|
||||
bufp = "ROLLBACK;";
|
||||
SEND_QUERY (be,bufp,444);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* hack alert -- we should restore the price data from the
|
||||
* sql back end at this point ! !!! */
|
||||
PWARN(" price data in engine is newer\n"
|
||||
" price must be rolled back. This function\n"
|
||||
" is not completely implemented !! \n");
|
||||
LEAVE ("rolled back");
|
||||
return 445;
|
||||
}
|
||||
pr->version ++; /* be sure to update the version !! */
|
||||
|
||||
if (pr->do_free)
|
||||
{
|
||||
pgendStoreAuditPrice (be, pr, SQL_DELETE);
|
||||
bufp = be->buff; *bufp = 0;
|
||||
bufp = stpcpy (bufp, "DELETE FROM gncPrice WHERE priceGuid='");
|
||||
bufp = guid_to_string_buff (gnc_price_get_guid(pr), bufp);
|
||||
bufp = stpcpy (bufp, "';");
|
||||
PINFO ("%s\n", be->buff ? be->buff : "(null)");
|
||||
SEND_QUERY (be,be->buff, 444);
|
||||
FINISH_QUERY(be->connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
pgendStorePriceNoLock (be, pr, FALSE);
|
||||
}
|
||||
|
||||
bufp = "COMMIT;\n"
|
||||
"NOTIFY gncPrice;";
|
||||
SEND_QUERY (be,bufp,335);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
if (pr->db) pr->db->dirty = FALSE;
|
||||
|
||||
LEAVE ("commited");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* hack alert -- the sane-ness of this algorithm should be reviewed.
|
||||
* I can't vouch that there aren't any subtle issues or race conditions
|
||||
|
||||
@@ -114,11 +114,16 @@ void pgendDisable (PGBackend *be);
|
||||
void pgendEnable (PGBackend *be);
|
||||
|
||||
void pgendStoreOneTransactionOnly (PGBackend *be, Transaction *ptr, sqlBuild_QType update);
|
||||
|
||||
void pgendPutOneCommodityOnly (PGBackend *be, gnc_commodity *ptr);
|
||||
void pgendPutOnePriceOnly (PGBackend *be, GNCPrice *ptr);
|
||||
void pgendPutOneSplitOnly (PGBackend *be, Split *ptr);
|
||||
void pgendPutOneTransactionOnly (PGBackend *be, Transaction *ptr);
|
||||
|
||||
int pgendPriceCompareVersion (PGBackend *be, GNCPrice *ptr);
|
||||
int pgendTransactionCompareVersion (PGBackend *be, Transaction *ptr);
|
||||
|
||||
void pgendStoreAuditPrice (PGBackend *be, GNCPrice *ptr, sqlBuild_QType update);
|
||||
void pgendStoreAuditSplit (PGBackend *be, Split *ptr, sqlBuild_QType update);
|
||||
void pgendStoreAuditTransaction (PGBackend *be, Transaction *ptr, sqlBuild_QType update);
|
||||
|
||||
|
||||
473
src/engine/sql/price.c
Normal file
473
src/engine/sql/price.c
Normal file
@@ -0,0 +1,473 @@
|
||||
/********************************************************************\
|
||||
* price.c -- implements price handling for the postgres backend *
|
||||
* Copyright (c) 2001 Linas Vepstas *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <string.h>
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#include "gnc-commodity.h"
|
||||
#include "gnc-engine.h"
|
||||
#include "gnc-engine-util.h"
|
||||
#include "gnc-event.h"
|
||||
#include "gnc-pricedb.h"
|
||||
#include "gnc-pricedb-p.h"
|
||||
#include "guid.h"
|
||||
|
||||
#include "PostgresBackend.h"
|
||||
#include "putil.h"
|
||||
|
||||
static short module = MOD_BACKEND;
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* COMMODITIES STUFF */
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
|
||||
/* This routine restores all commodities in the database.
|
||||
*/
|
||||
|
||||
static gpointer
|
||||
get_commodities_cb (PGBackend *be, PGresult *result, int j, gpointer data)
|
||||
{
|
||||
gnc_commodity_table *comtab = (gnc_commodity_table *) data;
|
||||
gnc_commodity *com;
|
||||
|
||||
/* first, lets see if we've already got this one */
|
||||
com = gnc_commodity_table_lookup(comtab,
|
||||
DB_GET_VAL("namespace",j), DB_GET_VAL("mnemonic",j));
|
||||
|
||||
if (com) return comtab;
|
||||
|
||||
/* no we don't ... restore it */
|
||||
com = gnc_commodity_new (
|
||||
DB_GET_VAL("fullname",j),
|
||||
DB_GET_VAL("namespace",j),
|
||||
DB_GET_VAL("mnemonic",j),
|
||||
DB_GET_VAL("code",j),
|
||||
atoi(DB_GET_VAL("fraction",j)));
|
||||
|
||||
gnc_commodity_table_insert (comtab, com);
|
||||
return comtab;
|
||||
}
|
||||
|
||||
void
|
||||
pgendGetAllCommodities (PGBackend *be)
|
||||
{
|
||||
gnc_commodity_table *comtab;
|
||||
char * p;
|
||||
if (!be) return;
|
||||
|
||||
ENTER ("be=%p, conn=%p", be, be->connection);
|
||||
|
||||
comtab = gnc_engine_commodities();
|
||||
if (!comtab) {
|
||||
PERR ("can't get global commodity table");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get them ALL */
|
||||
p = "SELECT * FROM gncCommodity;";
|
||||
SEND_QUERY (be, p, );
|
||||
pgendGetResults (be, get_commodities_cb, comtab);
|
||||
|
||||
LEAVE (" ");
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* PRICE STUFF */
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* store just one price */
|
||||
|
||||
static void
|
||||
pgendStorePriceNoLock (PGBackend *be, GNCPrice *pr,
|
||||
gboolean do_check_version)
|
||||
{
|
||||
gnc_commodity *modity;
|
||||
|
||||
if (do_check_version)
|
||||
{
|
||||
if (0 < pgendPriceCompareVersion (be, pr)) return;
|
||||
}
|
||||
pr->version ++; /* be sure to update the version !! */
|
||||
|
||||
/* make sure that we've stored the commodity
|
||||
* and currency before we store the price.
|
||||
*/
|
||||
modity = gnc_price_get_commodity (pr);
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
|
||||
modity = gnc_price_get_currency (pr);
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
|
||||
pgendPutOnePriceOnly (be, pr);
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* store entire price database */
|
||||
|
||||
static gboolean
|
||||
foreach_price_cb (GNCPrice *pr, gpointer bend)
|
||||
{
|
||||
PGBackend *be = (PGBackend *) bend;
|
||||
gnc_commodity *modity;
|
||||
gint16 mark;
|
||||
|
||||
/* make sure that we've stored the commodity
|
||||
* and currency before we store the price.
|
||||
* We use marks to avoid redundant stores.
|
||||
*/
|
||||
modity = gnc_price_get_commodity (pr);
|
||||
mark = gnc_commodity_get_mark (modity);
|
||||
if (!mark) {
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
gnc_commodity_set_mark (modity, 1);
|
||||
}
|
||||
|
||||
modity = gnc_price_get_currency (pr);
|
||||
mark = gnc_commodity_get_mark (modity);
|
||||
if (!mark) {
|
||||
pgendPutOneCommodityOnly (be, modity);
|
||||
gnc_commodity_set_mark (modity, 1);
|
||||
}
|
||||
|
||||
pgendPutOnePriceOnly (be, pr);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
commodity_mark_cb (gnc_commodity *cm, gpointer user_data)
|
||||
{
|
||||
gint32 v = ((gint32) user_data) & 0xffff;
|
||||
gnc_commodity_set_mark (cm, (gint16) v);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
pgendStorePriceDBNoLock (PGBackend *be, GNCPriceDB *prdb)
|
||||
{
|
||||
gnc_commodity_table *comtab = gnc_engine_commodities();
|
||||
|
||||
/* clear the marks on commodities -- we use this to mark
|
||||
* the thing as 'already stored', avoiding redundant stores */
|
||||
gnc_commodity_table_foreach_commodity (comtab, commodity_mark_cb, 0);
|
||||
|
||||
gnc_pricedb_foreach_price (prdb, foreach_price_cb,
|
||||
(gpointer) be, FALSE);
|
||||
|
||||
gnc_commodity_table_foreach_commodity (comtab, commodity_mark_cb, 0);
|
||||
}
|
||||
|
||||
void
|
||||
pgendStorePriceDB (PGBackend *be, GNCPriceDB *prdb)
|
||||
{
|
||||
char *p;
|
||||
ENTER ("be=%p, prdb=%p", be, prdb);
|
||||
if (!be || !prdb) return;
|
||||
|
||||
/* lock it up so that we store atomically */
|
||||
p = "BEGIN;\n"
|
||||
"LOCK TABLE gncPrice IN EXCLUSIVE MODE;\n";
|
||||
SEND_QUERY (be,p, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
pgendStorePriceDBNoLock (be, prdb);
|
||||
|
||||
p = "COMMIT;\n"
|
||||
"NOTIFY gncPrice;";
|
||||
SEND_QUERY (be,p, );
|
||||
FINISH_QUERY(be->connection);
|
||||
LEAVE(" ");
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* The pgendGetAllPrices() routine sucks *all* of the
|
||||
* prices out of the database. This is a potential
|
||||
* CPU and memory-burner; its use is not suggested for anything
|
||||
* but single-user mode.
|
||||
*/
|
||||
|
||||
static gpointer
|
||||
get_price_cb (PGBackend *be, PGresult *result, int j, gpointer data)
|
||||
{
|
||||
GNCPriceDB *prdb = (GNCPriceDB *) data;
|
||||
GNCPrice *pr;
|
||||
gint32 sql_vers, local_vers;
|
||||
Timespec ts;
|
||||
gint64 num, denom;
|
||||
gnc_numeric value;
|
||||
GUID guid = nullguid;
|
||||
int not_found = 0;
|
||||
|
||||
gnc_commodity * modity;
|
||||
|
||||
/* first, lets see if we've already got this one */
|
||||
string_to_guid (DB_GET_VAL ("priceGuid", j), &guid);
|
||||
pr = gnc_price_lookup (&guid);
|
||||
|
||||
if (!pr)
|
||||
{
|
||||
pr = gnc_price_create();
|
||||
gnc_price_begin_edit (pr);
|
||||
gnc_price_set_guid (pr, &guid);
|
||||
not_found = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
gnc_price_ref (pr);
|
||||
gnc_price_begin_edit (pr);
|
||||
not_found = 0;
|
||||
}
|
||||
|
||||
/* compare versions. Hack alert -- Not sure how to handle failures */
|
||||
sql_vers = atoi (DB_GET_VAL("version",j));
|
||||
local_vers = gnc_price_get_version(pr);
|
||||
if (sql_vers < local_vers) {
|
||||
PERR ("local price version is higher than db !!! local=%d sql=%d",
|
||||
local_vers, sql_vers);
|
||||
gnc_price_commit_edit (pr);
|
||||
gnc_price_unref (pr);
|
||||
return prdb;
|
||||
}
|
||||
gnc_price_set_version (pr, sql_vers);
|
||||
|
||||
modity = gnc_string_to_commodity (DB_GET_VAL("commodity",j));
|
||||
gnc_price_set_commodity (pr, modity);
|
||||
|
||||
modity = gnc_string_to_commodity (DB_GET_VAL("currency",j));
|
||||
gnc_price_set_currency (pr, modity);
|
||||
|
||||
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("time",j));
|
||||
gnc_price_set_time (pr, ts);
|
||||
|
||||
gnc_price_set_source (pr, DB_GET_VAL("source",j));
|
||||
gnc_price_set_type (pr, DB_GET_VAL("type",j));
|
||||
|
||||
num = atoll (DB_GET_VAL("valueNum", j));
|
||||
denom = atoll (DB_GET_VAL("valueDenom", j));
|
||||
value = gnc_numeric_create (num, denom);
|
||||
gnc_price_set_value (pr, value);
|
||||
|
||||
if (not_found) gnc_pricedb_add_price(prdb, pr);
|
||||
gnc_price_commit_edit (pr);
|
||||
gnc_price_unref (pr);
|
||||
|
||||
return prdb;
|
||||
}
|
||||
|
||||
|
||||
GNCPriceDB *
|
||||
pgendGetAllPrices (PGBackend *be, GNCPriceDB *prdb)
|
||||
{
|
||||
char * p;
|
||||
|
||||
if (!be) return NULL;
|
||||
ENTER ("be=%p, conn=%p", be, be->connection);
|
||||
|
||||
if (!prdb) {
|
||||
prdb = gnc_pricedb_create();
|
||||
}
|
||||
|
||||
/* first, make sure commodities table is up to date */
|
||||
pgendGetAllCommodities (be);
|
||||
|
||||
/* Get them ALL */
|
||||
p = "SELECT * FROM gncPrice;";
|
||||
SEND_QUERY (be, p, prdb);
|
||||
pgendGetResults (be, get_price_cb, prdb);
|
||||
|
||||
LEAVE (" ");
|
||||
return prdb;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
void
|
||||
pgendPriceLookup (Backend *bend, GNCPriceLookup *look)
|
||||
{
|
||||
PGBackend *be = (PGBackend *)bend;
|
||||
char * p;
|
||||
|
||||
ENTER ("be=%p, lookup=%p", be, look);
|
||||
if (!be || !look) return;
|
||||
|
||||
/* special case the two-way search in terms of more basic primitives */
|
||||
if (LOOKUP_NEAREST_IN_TIME == look->type)
|
||||
{
|
||||
look->type = LOOKUP_LATEST_BEFORE;
|
||||
pgendPriceLookup (bend, look);
|
||||
look->type = LOOKUP_EARLIEST_AFTER;
|
||||
pgendPriceLookup (bend, look);
|
||||
return;
|
||||
}
|
||||
|
||||
/* don't send events to GUI, don't accept callbacks to backend */
|
||||
gnc_engine_suspend_events();
|
||||
pgendDisable(be);
|
||||
|
||||
/* set up the common part of the query */
|
||||
p = be->buff; *p = 0;
|
||||
p = stpcpy (p, "SELECT * FROM gncPrice"
|
||||
" WHERE commodity='");
|
||||
p = stpcpy (p, gnc_commodity_get_unique_name(look->commodity));
|
||||
p = stpcpy (p, "' AND currency='");
|
||||
p = stpcpy (p, gnc_commodity_get_unique_name(look->currency));
|
||||
p = stpcpy (p, "' ");
|
||||
|
||||
switch (look->type)
|
||||
{
|
||||
case LOOKUP_LATEST:
|
||||
p = stpcpy (p, "ORDER BY time DESC LIMIT 1;");
|
||||
break;
|
||||
case LOOKUP_ALL:
|
||||
/* Get all prices for this commodity and currency */
|
||||
p = stpcpy (p, ";");
|
||||
break;
|
||||
case LOOKUP_AT_TIME:
|
||||
p = stpcpy (p, "AND time='");
|
||||
p = gnc_timespec_to_iso8601_buff (look->date, p);
|
||||
p = stpcpy (p, "';");
|
||||
break;
|
||||
case LOOKUP_NEAREST_IN_TIME:
|
||||
PERR ("this can't possibly happen but it did!!!");
|
||||
p = stpcpy (p, ";");
|
||||
break;
|
||||
case LOOKUP_LATEST_BEFORE:
|
||||
p = stpcpy (p, "AND time <= '");
|
||||
p = gnc_timespec_to_iso8601_buff (look->date, p);
|
||||
p = stpcpy (p, "' ORDER BY time DESC LIMIT 1;");
|
||||
break;
|
||||
case LOOKUP_EARLIEST_AFTER:
|
||||
p = stpcpy (p, "AND time >= '");
|
||||
p = gnc_timespec_to_iso8601_buff (look->date, p);
|
||||
p = stpcpy (p, "' ORDER BY time ASC LIMIT 1;");
|
||||
break;
|
||||
default:
|
||||
PERR ("unknown lookup type %d", look->type);
|
||||
/* re-enable events */
|
||||
pgendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
return;
|
||||
}
|
||||
|
||||
SEND_QUERY (be, be->buff, );
|
||||
pgendGetResults (be, get_price_cb, look->prdb);
|
||||
|
||||
/* insertion into the price db will mark it dirty;
|
||||
* but it really isn't at this point. */
|
||||
gnc_pricedb_mark_clean (look->prdb);
|
||||
|
||||
/* re-enable events */
|
||||
pgendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* HIGHER LEVEL ROUTINES AND BACKEND PROPER */
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
|
||||
int
|
||||
pgend_price_begin_edit (Backend * bend, GNCPrice *pr)
|
||||
{
|
||||
if (pr && pr->db && pr->db->dirty)
|
||||
{
|
||||
PERR ("price db is unexpectedly dirty");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
pgend_price_commit_edit (Backend * bend, GNCPrice *pr)
|
||||
{
|
||||
char * bufp;
|
||||
PGBackend *be = (PGBackend *)bend;
|
||||
|
||||
ENTER ("be=%p, price=%p", be, pr);
|
||||
if (!be || !pr) return 1; /* hack alert hardcode literal */
|
||||
|
||||
/* lock it up so that we query and store atomically */
|
||||
bufp = "BEGIN;\n"
|
||||
"LOCK TABLE gncPrice IN EXCLUSIVE MODE;\n";
|
||||
SEND_QUERY (be,bufp, 555);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* check to see that the engine version is equal or newer than
|
||||
* whats in the database. It its not, then some other user has
|
||||
* made changes, and we must roll back. */
|
||||
if (0 < pgendPriceCompareVersion (be, pr))
|
||||
{
|
||||
pr->do_free = FALSE;
|
||||
bufp = "ROLLBACK;";
|
||||
SEND_QUERY (be,bufp,444);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* hack alert -- we should restore the price data from the
|
||||
* sql back end at this point ! !!! */
|
||||
PWARN(" price data in engine is newer\n"
|
||||
" price must be rolled back. This function\n"
|
||||
" is not completely implemented !! \n");
|
||||
LEAVE ("rolled back");
|
||||
return 445;
|
||||
}
|
||||
pr->version ++; /* be sure to update the version !! */
|
||||
|
||||
if (pr->do_free)
|
||||
{
|
||||
pgendStoreAuditPrice (be, pr, SQL_DELETE);
|
||||
bufp = be->buff; *bufp = 0;
|
||||
bufp = stpcpy (bufp, "DELETE FROM gncPrice WHERE priceGuid='");
|
||||
bufp = guid_to_string_buff (gnc_price_get_guid(pr), bufp);
|
||||
bufp = stpcpy (bufp, "';");
|
||||
PINFO ("%s\n", be->buff ? be->buff : "(null)");
|
||||
SEND_QUERY (be,be->buff, 444);
|
||||
FINISH_QUERY(be->connection);
|
||||
}
|
||||
else
|
||||
{
|
||||
pgendStorePriceNoLock (be, pr, FALSE);
|
||||
}
|
||||
|
||||
bufp = "COMMIT;\n"
|
||||
"NOTIFY gncPrice;";
|
||||
SEND_QUERY (be,bufp,335);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
if (pr->db) pr->db->dirty = FALSE;
|
||||
|
||||
LEAVE ("commited");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ======================== END OF FILE ======================== */
|
||||
39
src/engine/sql/price.h
Normal file
39
src/engine/sql/price.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/********************************************************************\
|
||||
* price.h -- implements price & commodity handling for pg backend *
|
||||
* Copyright (c) 2000, 2001 Linas Vepstas *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
|
||||
|
||||
#ifndef __POSTGRES_PRICE_H__
|
||||
#define __POSTGRES_PRICE_H__
|
||||
|
||||
#include "PostgresBackend.h"
|
||||
|
||||
void pgendGetAllCommodities (PGBackend *be);
|
||||
void pgendStorePriceDB (PGBackend *be, GNCPriceDB *prdb);
|
||||
void pgendStorePriceDBNoLock (PGBackend *be, GNCPriceDB *prdb);
|
||||
GNCPriceDB * pgendGetAllPrices (PGBackend *be, GNCPriceDB *prdb);
|
||||
void pgendPriceLookup (Backend *bend, GNCPriceLookup *look);
|
||||
|
||||
|
||||
int pgend_price_begin_edit (Backend * bend, GNCPrice *pr);
|
||||
int pgend_price_commit_edit (Backend * bend, GNCPrice *pr);
|
||||
|
||||
#endif /* __POSTGRES_PRICE_H__ */
|
||||
904
src/engine/sql/txn.c
Normal file
904
src/engine/sql/txn.c
Normal file
@@ -0,0 +1,904 @@
|
||||
/********************************************************************\
|
||||
* txn.c -- implements transaction handlers for postgres backend *
|
||||
* Copyright (c) 2000, 2001 Linas Vepstas *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
|
||||
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <glib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <libpq-fe.h>
|
||||
|
||||
#include "Account.h"
|
||||
#include "AccountP.h"
|
||||
#include "Group.h"
|
||||
#include "GroupP.h"
|
||||
#include "gnc-commodity.h"
|
||||
#include "gnc-engine-util.h"
|
||||
#include "gnc-event.h"
|
||||
#include "guid.h"
|
||||
#include "Transaction.h"
|
||||
#include "TransactionP.h"
|
||||
|
||||
#include "checkpoint.h"
|
||||
#include "kvp-sql.h"
|
||||
#include "PostgresBackend.h"
|
||||
#include "txn.h"
|
||||
|
||||
#include "putil.h"
|
||||
|
||||
static short module = MOD_BACKEND;
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* TRANSACTION STUFF */
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* The is_trans_empty() routine returns TRUE if this appears to
|
||||
* be a fresh, 'null' transaction. It would be better if somehow
|
||||
* we could get the gui to mark this as a fresh transaction, rather
|
||||
* than having to scan a bunch of fields. But, oh well, this is
|
||||
* a minor quibble in the grand scheme of things.
|
||||
*/
|
||||
|
||||
static gboolean
|
||||
is_trans_empty (Transaction *trans)
|
||||
{
|
||||
Split *s;
|
||||
if (!trans) return TRUE;
|
||||
if (0 != (xaccTransGetDescription(trans))[0]) return FALSE;
|
||||
if (0 != (xaccTransGetNum(trans))[0]) return FALSE;
|
||||
if (1 != xaccTransCountSplits(trans)) return FALSE;
|
||||
|
||||
s = xaccTransGetSplit(trans, 0);
|
||||
if (TRUE != gnc_numeric_zero_p(xaccSplitGetShareAmount(s))) return FALSE;
|
||||
if (TRUE != gnc_numeric_zero_p(xaccSplitGetValue(s))) return FALSE;
|
||||
if ('n' != xaccSplitGetReconcile(s)) return FALSE;
|
||||
if (0 != (xaccSplitGetMemo(s))[0]) return FALSE;
|
||||
if (0 != (xaccSplitGetAction(s))[0]) return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* The pgendStoreTransactionNoLock() routine traverses the transaction
|
||||
* structure and stores/updates it in the database. If checks the
|
||||
* transaction splits as well, updating those. If the database
|
||||
* has splits which the transaction doesn't, those are deleted.
|
||||
* Then any new splits are poked into the database.
|
||||
*
|
||||
* If the do_check_version flag is set, then the database version
|
||||
* is compared to the engine version. If the database version is
|
||||
* newer, then the engine transaction is not stored.
|
||||
*
|
||||
* The pgendStoreTransaction() routine does the same, except that
|
||||
* it locks the tables appropriately.
|
||||
*/
|
||||
|
||||
static gpointer
|
||||
delete_list_cb (PGBackend *be, PGresult *result, int j, gpointer data)
|
||||
{
|
||||
GList * deletelist = (GList *) data;
|
||||
GUID guid = nullguid;
|
||||
|
||||
string_to_guid (DB_GET_VAL ("entryGuid", j), &guid);
|
||||
/* If the database has splits that the engine doesn't,
|
||||
* collect 'em up & we'll have to delete em */
|
||||
if (NULL == xaccSplitLookup (&guid))
|
||||
{
|
||||
deletelist = g_list_prepend (deletelist,
|
||||
g_strdup(DB_GET_VAL ("entryGuid", j)));
|
||||
}
|
||||
return deletelist;
|
||||
}
|
||||
|
||||
void
|
||||
pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans,
|
||||
gboolean do_check_version)
|
||||
{
|
||||
GList *start, *deletelist=NULL, *node;
|
||||
char * p;
|
||||
|
||||
if (!be || !trans) return;
|
||||
ENTER ("trans=%p", trans);
|
||||
|
||||
/* don't update the database if the database is newer ... */
|
||||
if (do_check_version)
|
||||
{
|
||||
if (0 < pgendTransactionCompareVersion (be, trans)) return;
|
||||
}
|
||||
trans->version ++; /* be sure to update the version !! */
|
||||
|
||||
/* first, we need to see which splits are in the database
|
||||
* since what is there may not match what we have cached in
|
||||
* the engine. */
|
||||
p = be->buff; *p = 0;
|
||||
p = stpcpy (p, "SELECT entryGuid FROM gncEntry WHERE transGuid='");
|
||||
p = guid_to_string_buff(xaccTransGetGUID(trans), p);
|
||||
p = stpcpy (p, "';");
|
||||
|
||||
SEND_QUERY (be,be->buff, );
|
||||
deletelist = pgendGetResults (be, delete_list_cb, deletelist);
|
||||
|
||||
/* delete those splits that don't belong */
|
||||
p = be->buff; *p = 0;
|
||||
for (node=deletelist; node; node=node->next)
|
||||
{
|
||||
Split *s;
|
||||
GUID guid;
|
||||
string_to_guid ((char *)(node->data), &guid);
|
||||
s = xaccSplitLookup(&guid);
|
||||
pgendStoreAuditSplit (be, s, SQL_DELETE);
|
||||
|
||||
p = stpcpy (p, "DELETE FROM gncEntry WHERE entryGuid='");
|
||||
p = stpcpy (p, node->data);
|
||||
p = stpcpy (p, "';\n");
|
||||
}
|
||||
if (p != be->buff)
|
||||
{
|
||||
PINFO ("%s", be->buff ? be->buff : "(null)");
|
||||
SEND_QUERY (be,be->buff, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* destroy any associated kvp data as well */
|
||||
for (node=deletelist; node; node=node->next)
|
||||
{
|
||||
pgendKVPDeleteStr (be, (char *)(node->data));
|
||||
g_free (node->data);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the rest */
|
||||
start = xaccTransGetSplitList(trans);
|
||||
|
||||
if ((start) && !(trans->do_free))
|
||||
{
|
||||
for (node=start; node; node=node->next)
|
||||
{
|
||||
Split * s = node->data;
|
||||
pgendPutOneSplitOnly (be, s);
|
||||
pgendKVPStore (be, &(s->guid), s->kvp_data);
|
||||
}
|
||||
pgendPutOneTransactionOnly (be, trans);
|
||||
pgendKVPStore (be, &(trans->guid), trans->kvp_data);
|
||||
}
|
||||
else
|
||||
{
|
||||
p = be->buff; *p = 0;
|
||||
for (node=start; node; node=node->next)
|
||||
{
|
||||
Split * s = node->data;
|
||||
pgendStoreAuditSplit (be, s, SQL_DELETE);
|
||||
p = stpcpy (p, "DELETE FROM gncEntry WHERE entryGuid='");
|
||||
p = guid_to_string_buff (xaccSplitGetGUID(s), p);
|
||||
p = stpcpy (p, "';\n");
|
||||
}
|
||||
|
||||
/* If this trans is marked for deletetion, use the 'orig' values
|
||||
* as the base for recording the audit. This wouldn't be normally
|
||||
* reqquired, except that otherwise one gets a trashed currency
|
||||
* value.
|
||||
*/
|
||||
pgendStoreAuditTransaction (be, trans->orig, SQL_DELETE);
|
||||
p = be->buff;
|
||||
p = stpcpy (p, "DELETE FROM gncTransaction WHERE transGuid='");
|
||||
p = guid_to_string_buff (xaccTransGetGUID(trans), p);
|
||||
p = stpcpy (p, "';");
|
||||
PINFO ("%s\n", be->buff ? be->buff : "(null)");
|
||||
SEND_QUERY (be,be->buff, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* destroy any associated kvp data as well */
|
||||
for (node=start; node; node=node->next)
|
||||
{
|
||||
Split * s = node->data;
|
||||
pgendKVPDelete (be, &(s->guid));
|
||||
}
|
||||
pgendKVPDelete (be, &(trans->guid));
|
||||
}
|
||||
|
||||
LEAVE(" ");
|
||||
}
|
||||
|
||||
#if 0
|
||||
/* This routine isn't used anywhere, and probably shouldn't
|
||||
* be, in part because its balance checkpointing algorithm
|
||||
* is wrong. */
|
||||
|
||||
static void
|
||||
pgendStoreTransaction (PGBackend *be, Transaction *trans)
|
||||
{
|
||||
char * bufp;
|
||||
if (!be || !trans) return;
|
||||
ENTER ("be=%p, trans=%p", be, trans);
|
||||
|
||||
/* lock it up so that we store atomically */
|
||||
bufp = "BEGIN;\n"
|
||||
"LOCK TABLE gncTransaction IN EXCLUSIVE MODE;\n"
|
||||
"LOCK TABLE gncEntry IN EXCLUSIVE MODE;\n";
|
||||
SEND_QUERY (be,bufp, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
pgendStoreTransactionNoLock (be, trans, TRUE);
|
||||
|
||||
bufp = "COMMIT;\n"
|
||||
"NOTIFY gncTransaction;";
|
||||
SEND_QUERY (be,bufp, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* If this is the multi-user mode, we need to update the
|
||||
* balances as well. */
|
||||
if ((MODE_POLL == be->session_mode) ||
|
||||
(MODE_EVENT == be->session_mode))
|
||||
{
|
||||
/* hack alert -- we should also recompute
|
||||
* the checkpoints for any accounts from which splits have
|
||||
* been deleted ... but we don't have these handy here ...
|
||||
* is this is actually kinda wrong ...
|
||||
*/
|
||||
pgendTransactionRecomputeCheckpoints (be, trans);
|
||||
}
|
||||
|
||||
LEAVE(" ");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ============================================================= */
|
||||
/* The pgendStoreAllTransactions() routine traverses through *all*
|
||||
* transactions in the account group, storing these to the database.
|
||||
* During the store, it checks the transaction version numbers,
|
||||
* and only stores those transactions that were newer in the engine.
|
||||
*/
|
||||
|
||||
static int
|
||||
trans_traverse_cb (Transaction *trans, void *cb_data)
|
||||
{
|
||||
pgendStoreTransactionNoLock ((PGBackend *) cb_data, trans, TRUE);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
pgendStoreAllTransactions (PGBackend *be, AccountGroup *grp)
|
||||
{
|
||||
char *p;
|
||||
ENTER ("be=%p, grp=%p", be, grp);
|
||||
if (!be || !grp) return;
|
||||
|
||||
/* lock it up so that we store atomically */
|
||||
p = "BEGIN;\n"
|
||||
"LOCK TABLE gncTransaction IN EXCLUSIVE MODE;\n"
|
||||
"LOCK TABLE gncEntry IN EXCLUSIVE MODE;\n";
|
||||
SEND_QUERY (be,p, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* Recursively walk transactions. Start by reseting the write
|
||||
* flags. We use this to avoid infinite recursion */
|
||||
xaccGroupBeginStagedTransactionTraversals(grp);
|
||||
xaccGroupStagedTransactionTraversal (grp, 1, trans_traverse_cb, be);
|
||||
|
||||
p = "COMMIT;\n"
|
||||
"NOTIFY gncTransaction;";
|
||||
SEND_QUERY (be,p, );
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* If this is the multi-user mode, we need to update the
|
||||
* balances as well. */
|
||||
if ((MODE_POLL == be->session_mode) ||
|
||||
(MODE_EVENT == be->session_mode))
|
||||
{
|
||||
pgendGroupRecomputeAllCheckpoints(be, grp);
|
||||
}
|
||||
LEAVE(" ");
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/*
|
||||
* The pgendCopyTransactionToEngine() routine 'copies' data out of
|
||||
* the SQL database and into the engine, for the indicated
|
||||
* Transaction GUID. It starts by looking for an existing
|
||||
* transaction in the engine with such a GUID. If found, then
|
||||
* it compares the version of last update to what's in the sql DB.
|
||||
* If the engine data is older, or the engine doesn't yet have
|
||||
* this transaction, then the full update happens. The full
|
||||
* update sets up the transaction structure, all of the splits
|
||||
* in the transaction, and makes sure that all of the splits
|
||||
* are in the proper accounts. If the pre-existing tranasaction
|
||||
* in the engine has more splits than what's in the DB, then these
|
||||
* are pruned so that the structure exactly matches what's in the
|
||||
* DB. This routine then returns -1.
|
||||
*
|
||||
* If this routine finds a pre-existing transaction in the engine,
|
||||
* and the version of last modification of this transaction is
|
||||
* equal to or *newer* then what the DB holds, then this routine
|
||||
* returns 0 if equal, and +1 if newer, and does *not* perform any
|
||||
* update. (Note that 0 is returned for various error conditions.
|
||||
* Thus, testing for 0 is a bad idea. This is a hack, and should
|
||||
* probably be fixed.
|
||||
*/
|
||||
|
||||
int
|
||||
pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
|
||||
{
|
||||
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 save_state = 1;
|
||||
GList *node, *db_splits=NULL, *engine_splits, *delete_splits=NULL;
|
||||
gnc_commodity *currency = NULL;
|
||||
gint64 trans_frac = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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 (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)));
|
||||
|
||||
/* hack alert -- don't set the transaction currency until
|
||||
* after all splits are restored. This hack is used to set
|
||||
* the reporting currency in an account. This hack will be
|
||||
* obsolete when reporting currencies are removed from the
|
||||
* account. */
|
||||
currency = gnc_string_to_commodity (DB_GET_VAL("currency",j));
|
||||
trans_frac = gnc_commodity_get_fraction (currency);
|
||||
#if 0
|
||||
xaccTransSetCurrency
|
||||
(trans, gnc_string_to_commodity (DB_GET_VAL("currency",j)));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
PQclear (result);
|
||||
i++;
|
||||
} while (result);
|
||||
|
||||
if (0 == nrows)
|
||||
{
|
||||
/* hack alert -- not sure how to handle this case; we'll just
|
||||
* punt 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 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 */
|
||||
pbuff = be->buff;
|
||||
pbuff[0] = 0;
|
||||
pbuff = stpcpy (pbuff,
|
||||
"SELECT * FROM gncEntry 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 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++)
|
||||
{
|
||||
Split *s;
|
||||
GUID guid;
|
||||
Timespec ts;
|
||||
gint64 num;
|
||||
gnc_numeric value, amount;
|
||||
|
||||
/* --------------------------------------------- */
|
||||
/* first, lets see if we've already got this one */
|
||||
PINFO ("split GUID=%s", DB_GET_VAL("entryGUID",j));
|
||||
guid = nullguid; /* just in case the read fails ... */
|
||||
string_to_guid (DB_GET_VAL("entryGUID",j), &guid);
|
||||
s = xaccSplitLookup (&guid);
|
||||
if (!s)
|
||||
{
|
||||
s = xaccMallocSplit();
|
||||
xaccSplitSetGUID(s, &guid);
|
||||
}
|
||||
|
||||
/* next, restore some split data */
|
||||
/* hack alert - not all split fields handled */
|
||||
xaccSplitSetMemo(s, DB_GET_VAL("memo",j));
|
||||
xaccSplitSetAction(s, DB_GET_VAL("action",j));
|
||||
ts = gnc_iso8601_to_timespec_local
|
||||
(DB_GET_VAL("date_reconciled",j));
|
||||
xaccSplitSetDateReconciledTS (s, &ts);
|
||||
|
||||
num = atoll (DB_GET_VAL("value", j));
|
||||
value = gnc_numeric_create (num, trans_frac);
|
||||
xaccSplitSetValue (s, value);
|
||||
|
||||
xaccSplitSetReconcile (s, (DB_GET_VAL("reconciled", j))[0]);
|
||||
|
||||
/* --------------------------------------------- */
|
||||
/* next, find the account that this split goes into */
|
||||
guid = nullguid; /* just in case the read fails ... */
|
||||
string_to_guid (DB_GET_VAL("accountGUID",j), &guid);
|
||||
acc = xaccAccountLookup (&guid);
|
||||
if (!acc)
|
||||
{
|
||||
PERR ("account not found, will delete this split\n"
|
||||
"\t(split with guid=%s\n"
|
||||
"\twants an acct with guid=%s)\n",
|
||||
DB_GET_VAL("entryGUID",j),
|
||||
DB_GET_VAL("accountGUID",j)
|
||||
);
|
||||
xaccSplitDestroy (s);
|
||||
}
|
||||
else
|
||||
{
|
||||
gnc_commodity *modity;
|
||||
gint64 acct_frac;
|
||||
num = atoll (DB_GET_VAL("amount", j));
|
||||
modity = xaccAccountGetCommodity (acc);
|
||||
acct_frac = gnc_commodity_get_fraction (modity);
|
||||
amount = gnc_numeric_create (num, acct_frac);
|
||||
xaccSplitSetShareAmount (s, amount);
|
||||
|
||||
xaccTransAppendSplit (trans, s);
|
||||
|
||||
if (acc != previous_acc)
|
||||
{
|
||||
xaccAccountCommitEdit (previous_acc);
|
||||
xaccAccountBeginEdit (acc);
|
||||
previous_acc = acc;
|
||||
}
|
||||
if (acc->parent) save_state = acc->parent->saved;
|
||||
xaccAccountInsertSplit(acc, s);
|
||||
if (acc->parent) acc->parent->saved = save_state;
|
||||
|
||||
/* finally tally them up; we use this below to
|
||||
* clean out deleted splits */
|
||||
db_splits = g_list_prepend (db_splits, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
PQclear (result);
|
||||
} while (result);
|
||||
|
||||
/* close out dangling edit session */
|
||||
xaccAccountCommitEdit (previous_acc);
|
||||
|
||||
/* ------------------------------------------------- */
|
||||
/* destroy any splits that the engine has that the DB didn't */
|
||||
|
||||
i=0; j=0;
|
||||
engine_splits = xaccTransGetSplitList(trans);
|
||||
for (node = engine_splits; node; node=node->next)
|
||||
{
|
||||
/* if not found, mark for deletion */
|
||||
if (NULL == g_list_find (db_splits, node->data))
|
||||
{
|
||||
delete_splits = g_list_prepend (delete_splits, node->data);
|
||||
j++;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
PINFO ("%d of %d splits marked for deletion", j, i);
|
||||
|
||||
/* now, delete them ... */
|
||||
for (node=delete_splits; node; node=node->next)
|
||||
{
|
||||
xaccSplitDestroy ((Split *) node->data);
|
||||
}
|
||||
g_list_free (delete_splits);
|
||||
g_list_free (db_splits);
|
||||
|
||||
/* ------------------------------------------------- */
|
||||
/* restore any kvp data associated with the transaction and splits */
|
||||
|
||||
trans->kvp_data = pgendKVPFetch (be, &(trans->guid), trans->kvp_data);
|
||||
|
||||
engine_splits = xaccTransGetSplitList(trans);
|
||||
for (node = engine_splits; node; node=node->next)
|
||||
{
|
||||
Split *s = node->data;
|
||||
s->kvp_data = pgendKVPFetch (be, &(s->guid), s->kvp_data);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------- */
|
||||
|
||||
/* see note above as to why we do this set here ... */
|
||||
xaccTransSetCurrency (trans, currency);
|
||||
|
||||
xaccTransCommitEdit (trans);
|
||||
|
||||
/* re-enable events to the backend and GUI */
|
||||
pgendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
|
||||
LEAVE (" ");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* This routine 'synchronizes' the Transaction structure
|
||||
* associated with the GUID. Data is pulled out of the database,
|
||||
* the versions are compared, and updates made, if needed.
|
||||
* The splits are handled as well ...
|
||||
*
|
||||
* hack alert unfinished, incomplete
|
||||
* hack alert -- philosophically speaking, not clear that this is the
|
||||
* right metaphor. Its OK to poke date into the engine, but writing
|
||||
* data out to the database should make use of versioning, and this
|
||||
* routine doesn't.
|
||||
*
|
||||
* THIS IS NOT USED ANYWHERE should probably go away. Although
|
||||
* this kind of a routine could be handy for resyncing after a lost
|
||||
* contact to the backend. Note, however, that it would
|
||||
* mangle balance checkpoints, and these would need to be
|
||||
* recomputed.
|
||||
*/
|
||||
|
||||
#if 0
|
||||
|
||||
static void
|
||||
pgendSyncTransaction (PGBackend *be, GUID *trans_guid)
|
||||
{
|
||||
Transaction *trans;
|
||||
int engine_data_is_newer = 0;
|
||||
|
||||
ENTER ("be=%p", be);
|
||||
if (!be || !trans_guid) return;
|
||||
|
||||
/* disable callbacks into the backend, and events to GUI */
|
||||
gnc_engine_suspend_events();
|
||||
pgendDisable(be);
|
||||
|
||||
engine_data_is_newer = pgendCopyTransactionToEngine (be, trans_guid);
|
||||
|
||||
/* if engine data was newer, we save to the db. */
|
||||
if (0 < engine_data_is_newer)
|
||||
{
|
||||
/* XXX hack alert -- fixme */
|
||||
PERR ("Data in the local cache is newer than the data in\n"
|
||||
"\tthe database. Thus, the local data will be sent\n"
|
||||
"\tto the database. This mode of operation is\n"
|
||||
"\tguarenteed to clobber other user's updates.\n");
|
||||
|
||||
trans = xaccTransLookup (trans_guid);
|
||||
|
||||
/* hack alert -- basically, we should use the pgend_commit_transaction
|
||||
* routine instead, and in fact, 'StoreTransaction'
|
||||
* pretty much shouldn't be allowed to exist in this
|
||||
* framework */
|
||||
pgendStoreTransaction (be, trans);
|
||||
|
||||
gnc_engine_resume_events();
|
||||
return;
|
||||
}
|
||||
|
||||
/* re-enable events to the backend and GUI */
|
||||
pgendEnable(be);
|
||||
gnc_engine_resume_events();
|
||||
|
||||
LEAVE (" ");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
/* HIGHER LEVEL ROUTINES AND BACKEND PROPER */
|
||||
/* ============================================================= */
|
||||
/* ============================================================= */
|
||||
|
||||
|
||||
/* ============================================================= */
|
||||
|
||||
int
|
||||
pgend_trans_commit_edit (Backend * bend,
|
||||
Transaction * trans,
|
||||
Transaction * oldtrans)
|
||||
{
|
||||
char * bufp;
|
||||
int rollback=0;
|
||||
PGBackend *be = (PGBackend *)bend;
|
||||
|
||||
ENTER ("be=%p, trans=%p", be, trans);
|
||||
if (!be || !trans) return 1; /* hack alert hardcode literal */
|
||||
|
||||
/* lock it up so that we query and store atomically */
|
||||
bufp = "BEGIN;\n"
|
||||
"LOCK TABLE gncTransaction IN EXCLUSIVE MODE;\n"
|
||||
"LOCK TABLE gncEntry IN EXCLUSIVE MODE;\n";
|
||||
SEND_QUERY (be,bufp, 555);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* Check to see if this is a 'new' transaction, or not.
|
||||
* The hallmark of a 'new' transaction is that all the
|
||||
* fields are empty. If its new, then we just go ahead
|
||||
* and commit. If its old, then we need some consistency
|
||||
* checks.
|
||||
*/
|
||||
if (FALSE == is_trans_empty (oldtrans))
|
||||
{
|
||||
/* See if the database is in the state that we last left it in.
|
||||
* Basically, the database should contain the 'old transaction'.
|
||||
* If it doesn't, then someone else has modified this transaction,
|
||||
* and thus, any further action on our part would be unsafe. It
|
||||
* is recommended that this be spit back at the GUI, and let a
|
||||
* human decide what to do next.
|
||||
*
|
||||
* We could directly compare all of the data ... but instead,
|
||||
* its more efficient to just compare the version number.
|
||||
*/
|
||||
|
||||
#ifdef COMPARE_ALL_TRANSACTION_DATA
|
||||
{
|
||||
int ndiffs;
|
||||
GList *start, *node;
|
||||
|
||||
ndiffs = pgendCompareOneTransactionOnly (be, oldtrans);
|
||||
if (0 < ndiffs) rollback++;
|
||||
|
||||
/* be sure to check the old splits as well ... */
|
||||
start = xaccTransGetSplitList (oldtrans);
|
||||
for (node=start; node; node=node->next)
|
||||
{
|
||||
Split * s = node->data;
|
||||
ndiffs = pgendCompareOneSplitOnly (be, s);
|
||||
if (0 < ndiffs) rollback++;
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* roll things back is sql version is newer */
|
||||
if (0 < pgendTransactionCompareVersion (be, oldtrans)) { rollback = 1; }
|
||||
|
||||
/* first, see if someone else has already deleted this transaction */
|
||||
if (-1 < pgendTransactionGetDeletedVersion (be, oldtrans))
|
||||
{
|
||||
if (rollback)
|
||||
{
|
||||
/* Although this situation should never happen, we'll try
|
||||
* to gracefully handle it anyway, because otherwuise the
|
||||
* transaction becomes un-modifiable, undeleteable.
|
||||
* (This situation might occur with the right combo of bugs
|
||||
* and crashes. We've fixed the bugs, but ...
|
||||
*/
|
||||
char buf[80];
|
||||
gnc_timespec_to_iso8601_buff (xaccTransRetDatePostedTS (trans), buf);
|
||||
PERR ("The impossible has happened, and thats not good!\n"
|
||||
"\tThe SQL database contains an active transaction that\n"
|
||||
"\talso appears in the audit trail as deleted !!\n"
|
||||
"\tWill try to delete transaction for good\n"
|
||||
"\ttransaction is '%s' %s\n",
|
||||
xaccTransGetDescription (trans), buf);
|
||||
rollback = 0;
|
||||
trans->do_free = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
rollback = 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rollback) {
|
||||
bufp = "ROLLBACK;";
|
||||
SEND_QUERY (be,bufp,444); /* hack alert hard coded literal */
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
PINFO ("old tranasction didn't match DB, edit rolled back)\n");
|
||||
|
||||
/* What happens here: We return to the engine with an
|
||||
* error code. This causes the engine to call
|
||||
* xaccTransRollback(), with then invokes our backend rollback
|
||||
* routine. Our rollback routine updates from the latest in
|
||||
* the sql database, and voila! we are good to go.
|
||||
*/
|
||||
return 666; /* hack alert- hard coded literal */
|
||||
}
|
||||
}
|
||||
|
||||
/* if we are here, we are good to go */
|
||||
pgendStoreTransactionNoLock (be, trans, FALSE);
|
||||
|
||||
bufp = "COMMIT;\n"
|
||||
"NOTIFY gncTransaction;";
|
||||
SEND_QUERY (be,bufp,334);
|
||||
FINISH_QUERY(be->connection);
|
||||
|
||||
/* If this is the multi-user mode, we need to update the
|
||||
* balances as well. */
|
||||
if ((MODE_POLL == be->session_mode) ||
|
||||
(MODE_EVENT == be->session_mode))
|
||||
{
|
||||
GList *node;
|
||||
|
||||
/* loop over the old accounts, as they used to be. */
|
||||
for (node = xaccTransGetSplitList(trans->orig); node; node=node->next)
|
||||
{
|
||||
Split *s = (Split *) node->data;
|
||||
Account *acc = xaccSplitGetAccount (s);
|
||||
pgendAccountRecomputeOneCheckpoint (be, acc, trans->orig->date_posted);
|
||||
}
|
||||
|
||||
/* set checkpoints for the new accounts */
|
||||
pgendTransactionRecomputeCheckpoints (be, trans);
|
||||
}
|
||||
|
||||
/* hack alert -- the following code will get rid of that annoying
|
||||
* message from the GUI about saving one's data. However, it doesn't
|
||||
* do the right thing if the connection to the backend was ever lost.
|
||||
* what should happen is the user should get a chance to
|
||||
* resynchronize thier data with the backend, before quiting out.
|
||||
*/
|
||||
{
|
||||
Split * s = xaccTransGetSplit (trans, 0);
|
||||
Account *acc = xaccSplitGetAccount (s);
|
||||
AccountGroup *top = xaccGetAccountRoot (acc);
|
||||
xaccGroupMarkSaved (top);
|
||||
}
|
||||
|
||||
LEAVE ("commited");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================= */
|
||||
/* transaction rollback routine. This routine can be invoked
|
||||
* in one of two ways: if the user canceled an edited transaction
|
||||
* by hand, from the gui, or automatically, due to a multi-user
|
||||
* edit conflict. In this latter case, the commit_edit routine
|
||||
* above failed, and returned to the engine. Then the engine
|
||||
* xaccTransRollback routine got invoked, which called us.
|
||||
* What we do here is to copy the transaction out of the dataabse
|
||||
* and into the engine. This will bring the local engine up
|
||||
* to sync from the changes that other users had made.
|
||||
*/
|
||||
|
||||
int
|
||||
pgend_trans_rollback_edit (Backend * bend, Transaction * trans)
|
||||
{
|
||||
PGBackend *be = (PGBackend *)bend;
|
||||
const GUID * trans_guid;
|
||||
|
||||
if (!be || !trans) return 0;
|
||||
ENTER ("be=%p, trans=%p", be, trans);
|
||||
|
||||
/* First, lets see if the other user had deleted this transaction.
|
||||
* If so, then we want to delete it from the local cache as well.
|
||||
*/
|
||||
if (-1 < pgendTransactionGetDeletedVersion (be, trans))
|
||||
{
|
||||
LEAVE ("destroyed");
|
||||
return BACKEND_ROLLBACK_DESTROY;
|
||||
}
|
||||
|
||||
trans_guid = xaccTransGetGUID (trans);
|
||||
pgendCopyTransactionToEngine (be, trans_guid);
|
||||
|
||||
LEAVE ("rolled back");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ======================== END OF FILE ======================== */
|
||||
55
src/engine/sql/txn.h
Normal file
55
src/engine/sql/txn.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/********************************************************************\
|
||||
* txn.h -- transaction handling routines for the postgres backend *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
|
||||
/*
|
||||
* FILE:
|
||||
* txn.h
|
||||
*
|
||||
* FUNCTION:
|
||||
* Implements the transaction handling callbacks for the postgres backend.
|
||||
*
|
||||
* HISTORY:
|
||||
* Copyright (c) 2000, 2001 Linas Vepstas
|
||||
*/
|
||||
|
||||
|
||||
#ifndef __POSTGRES_TXN_H__
|
||||
#define __POSTGRES_TXN_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include "Group.h"
|
||||
#include "guid.h"
|
||||
#include "Transaction.h"
|
||||
|
||||
#include "PostgresBackend.h"
|
||||
|
||||
int pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid);
|
||||
void pgendStoreAllTransactions (PGBackend *be, AccountGroup *grp);
|
||||
void pgendStoreTransactionNoLock (PGBackend *be, Transaction *trans, gboolean do_check_version);
|
||||
|
||||
int pgend_trans_commit_edit (Backend * bend, Transaction * trans, Transaction * oldtrans);
|
||||
int pgend_trans_rollback_edit (Backend * bend, Transaction * trans);
|
||||
|
||||
|
||||
|
||||
|
||||
#endif /* __POSTGRES_TXN_H__ */
|
||||
Reference in New Issue
Block a user