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