diff --git a/ChangeLog b/ChangeLog index e24f01d054..d151c50cdc 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,60 @@ +2003-01-29 Matthew Vanecek + + * src/backend/postgres/Makefile.am: Changed the .sql.c target to + not echo the beginning and ending quotes. This is part of the + gcc 3.x compatibility changes. + + * src/backend/postgres/PostgresBackend.c: Made some whitespace changes + for readability. + - (pgend_book_load_poll): Commented out a PWARN about the old book + list not being empty. See added comments for why. + - (pgend_book_load_poll): added a gnc_session_set_book() call so that + the session would have the correct book loaded (i.e., the book + that's stored in the DB). + + * src/backend/postgres/*.sql: Enclosed each line in a set of + quotes "..". The "multi-line literals" were causing compile errors + for gcc 3.x. + + * src/backend/postgres/gncquery.c: ran indent on the file. + - (STRING_TERM): Changed the comparison on STRING_MATCH_NORMAL to + STRING_MATCH_CASEINSENSITIVE for purposes of adding a '*' to the + comparison operator "~". The code was writing a "=*" which is + invalid, but "~*" is valid for case-insensitive regex searches. + - (sqlQuery_build): Added a "more_and = 0" to the final if statement + of the "for (il = qterms; il; il = il->next)" loop. This will + prevent a stray "AND" being appended to the end of the "WHERE" clause + of a dynamic query. + + * src/backend/postgres/kvp-sql (store_cb): In "case KVP_TYPE_TIMESPEC", + changed the 'cb_data->stype = "timespec"' to 'cb_data->stype = "time"'. + The destination field in the database is defined as 4 characters, and + the extra chars in stype were causing an insertion error. + + * src/backend/postgres/putil.h (FINISH_QUERY): Changed the error to use + PQresultErrorMessage() instead of PQerrorMessage(). + + * src/backend/postgres/test/db-control.sh (our_pg_ctl): Changed the + script to return -1 if pg_ctl was not found in PATH. + + * src/backend/postgres/test/run-tests.sh: If db-control.sh returns + failure, the attempt to connect to $PGHOST on $PGPORT. + + * src/backend/postgres/test/test-db.c: Included QueryNew.h. Added the + _dbinfo struct to pass host, port, dbname, and mode information + around. Everywhere that used db_name/mode is now using DbInfor->*. + - Ran indent on the file. + - Added a drop_database function to call dropdb on the database used + for the tests. This eventually needs to be changed so that every + "return FALSE" first calls gnc_session_destroy(session), before this + new function is called. + - (db_file_url): Changed the function to handle TCP connections as well + as socket connections. + - (load_db_file): Added a PGBackend object from which to get a PGconn + connection to store in DbInfo. + - (test_raw_query): Added a call to gncQueryPrint() if + gnc_should_log(MOD_TEST, GNC_LOG_DETAIL) + 2003-01-29 Herbert Thoma * src/report/standard-reports/cash-flow.scm: only asset accounts diff --git a/src/backend/postgres/Makefile.am b/src/backend/postgres/Makefile.am index d65f8c7f43..6793776758 100644 --- a/src/backend/postgres/Makefile.am +++ b/src/backend/postgres/Makefile.am @@ -108,8 +108,6 @@ $(M4_SRC): table.m4 m4 -I ${srcdir} $< > $@ .sql.c: - echo \" > $@ - echo "-- DO NOT EDIT THIS FILE. IT IS AUTOGENERATED." >> $@ + echo "\"-- DO NOT EDIT THIS FILE. IT IS AUTOGENERATED.\"" > $@ cat $< >> $@ - echo \" >> $@ diff --git a/src/backend/postgres/PostgresBackend.c b/src/backend/postgres/PostgresBackend.c index 13b2595fd6..4472d776dd 100644 --- a/src/backend/postgres/PostgresBackend.c +++ b/src/backend/postgres/PostgresBackend.c @@ -1473,7 +1473,6 @@ pgend_session_end (Backend *bend) * the poll & event style load, where only the accounts, * and never the transactions, need to be loaded. */ - static void pgend_book_load_poll (Backend *bend, GNCBook *book) { @@ -1493,12 +1492,20 @@ pgend_book_load_poll (Backend *bend, GNCBook *book) if (be->blist) { /* XXX not clear what this means ... should we free old books ?? */ - PWARN ("old book list not empty "); + /* The old book list is set by the session when the session is + * created. It is an empty book, and should be discarded in favor + * of the Book retrieved from the database. + * PWARN ("old book list not empty "); + */ g_list_free (be->blist); be->blist = NULL; } pgend_set_book (be, book); pgendGetBook (be, book); + gnc_session_set_book(be->session, book); + + PINFO("Book GUID = %s\n", + guid_to_string(gnc_book_get_guid(book))); pgendGetAllAccountsInBook (be, book); @@ -2246,13 +2253,13 @@ pgend_session_begin (Backend *backend, { case MODE_SINGLE_FILE: pgendEnable(be); - be->be.load = pgend_do_load_single; - be->be.begin = pgend_do_begin; - be->be.commit = pgend_do_commit; - be->be.rollback = pgend_do_rollback; + be->be.load = pgend_do_load_single; + be->be.begin = pgend_do_begin; + be->be.commit = pgend_do_commit; + be->be.rollback = pgend_do_rollback; - be->be.compile_query = NULL; - be->be.free_query = NULL; + be->be.compile_query = NULL; + be->be.free_query = NULL; be->be.run_query = NULL; be->be.price_lookup = NULL; @@ -2268,15 +2275,14 @@ pgend_session_begin (Backend *backend, case MODE_SINGLE_UPDATE: pgendEnable(be); - be->be.load = pgend_do_load_single; - be->be.begin = pgend_do_begin; - be->be.commit = pgend_do_commit; - be->be.rollback = pgend_do_rollback; - - be->be.compile_query = NULL; - be->be.free_query = NULL; - be->be.run_query = NULL; - be->be.price_lookup = NULL; + be->be.load = pgend_do_load_single; + be->be.begin = pgend_do_begin; + be->be.commit = pgend_do_commit; + be->be.rollback = pgend_do_rollback; + be->be.compile_query = NULL; + be->be.free_query = NULL; + be->be.run_query = NULL; + be->be.price_lookup = NULL; be->be.sync = pgendDoSync; be->be.export = NULL; @@ -2290,13 +2296,13 @@ pgend_session_begin (Backend *backend, case MODE_POLL: pgendEnable(be); - be->be.load = pgend_book_load_poll; + be->be.load = pgend_book_load_poll; be->be.begin = pgend_do_begin; be->be.commit = pgend_do_commit; be->be.rollback = pgend_do_rollback; - be->be.compile_query = pgendCompileQuery; - be->be.free_query = pgendFreeQuery; + be->be.compile_query = pgendCompileQuery; + be->be.free_query = pgendFreeQuery; be->be.run_query = pgendRunQuery; be->be.price_lookup = pgendPriceFind; @@ -2317,21 +2323,21 @@ pgend_session_begin (Backend *backend, pgendSessionGetPid (be); pgendSessionSetupNotifies (be); - be->be.load = pgend_book_load_poll; - be->be.begin = pgend_do_begin; - be->be.commit = pgend_do_commit; - be->be.rollback = pgend_do_rollback; + be->be.load = pgend_book_load_poll; + be->be.begin = pgend_do_begin; + be->be.commit = pgend_do_commit; + be->be.rollback = pgend_do_rollback; - be->be.compile_query = pgendCompileQuery; - be->be.free_query = pgendFreeQuery; - be->be.run_query = pgendRunQuery; - be->be.price_lookup = pgendPriceFind; + be->be.compile_query = pgendCompileQuery; + be->be.free_query = pgendFreeQuery; + be->be.run_query = pgendRunQuery; + be->be.price_lookup = pgendPriceFind; - be->be.sync = pgendDoSync; - be->be.export = NULL; - be->be.percentage = NULL; - be->be.events_pending = pgendEventsPending; - be->be.process_events = pgendProcessEvents; + be->be.sync = pgendDoSync; + be->be.export = NULL; + be->be.percentage = NULL; + be->be.events_pending = pgendEventsPending; + be->be.process_events = pgendProcessEvents; PWARN ("mode=multi-user is beta -- \n" "we've fixed all known bugs but that doesn't mean\n" diff --git a/src/backend/postgres/functions.sql b/src/backend/postgres/functions.sql index cb307b2d78..3410ba0d21 100644 --- a/src/backend/postgres/functions.sql +++ b/src/backend/postgres/functions.sql @@ -1,69 +1,69 @@ --- --- FILE: --- functions.sql --- --- FUNCTION: --- Define assorted utility functions. --- --- HISTORY: --- Copyright (C) 2001 Linas Vepstas --- - - --- utility functions to compute checkpoint balance subtotals - -CREATE FUNCTION gncSubtotalBalance (CHAR(32), TIMESTAMP, TIMESTAMP) - RETURNS INT8 - AS 'SELECT INT8(sum(gncEntry.amount)) - FROM gncEntry, gncTransaction - WHERE - gncEntry.accountGuid = $1 AND - gncEntry.transGuid = gncTransaction.transGuid AND - gncTransaction.date_posted BETWEEN $2 AND $3' - LANGUAGE 'sql'; - -CREATE FUNCTION gncSubtotalClearedBalance (CHAR(32), TIMESTAMP, TIMESTAMP) - RETURNS INT8 - AS 'SELECT INT8(sum(gncEntry.amount)) - FROM gncEntry, gncTransaction - WHERE - gncEntry.accountGuid = $1 AND - gncEntry.transGuid = gncTransaction.transGuid AND - gncTransaction.date_posted BETWEEN $2 AND $3 AND - gncEntry.reconciled <> \\'n\\'' - LANGUAGE 'sql'; - -CREATE FUNCTION gncSubtotalReconedBalance (CHAR(32), TIMESTAMP, TIMESTAMP) - RETURNS INT8 - AS 'SELECT INT8(sum(gncEntry.amount)) - FROM gncEntry, gncTransaction - WHERE - gncEntry.accountGuid = $1 AND - gncEntry.transGuid = gncTransaction.transGuid AND - gncTransaction.date_posted BETWEEN $2 AND $3 AND - (gncEntry.reconciled = \\'y\\' OR - gncEntry.reconciled = \\'f\\')' - LANGUAGE 'sql'; - --- helper functions. These intentionally use the 'wrong' fraction. --- This is because value_frac * amount * price = value * amount_frac - -CREATE FUNCTION gncHelperPrVal (gncEntry) - RETURNS INT8 - AS 'SELECT abs($1 . value * gncCommodity.fraction) - FROM gncEntry, gncAccount, gncCommodity - WHERE - $1 . accountGuid = gncAccount.accountGuid AND - gncAccount.commodity = gncCommodity.commodity' - LANGUAGE 'sql'; - -CREATE FUNCTION gncHelperPrAmt (gncEntry) - RETURNS INT8 - AS 'SELECT abs($1 . amount * gncCommodity.fraction) - FROM gncEntry, gncTransaction, gncCommodity - WHERE - $1 . transGuid = gncTransaction.transGuid AND - gncTransaction.currency = gncCommodity.commodity' - LANGUAGE 'sql'; - --- end of file +"-- \n" +"-- FILE: \n" +"-- functions.sql \n" +"-- \n" +"-- FUNCTION: \n" +"-- Define assorted utility functions. \n" +"-- \n" +"-- HISTORY: \n" +"-- Copyright (C) 2001 Linas Vepstas \n" +"-- \n" +" \n" +" \n" +"-- utility functions to compute checkpoint balance subtotals \n" +" \n" +"CREATE FUNCTION gncSubtotalBalance (CHAR(32), TIMESTAMP, TIMESTAMP) \n" +" RETURNS INT8 \n" +" AS 'SELECT INT8(sum(gncEntry.amount)) \n" +" FROM gncEntry, gncTransaction \n" +" WHERE \n" +" gncEntry.accountGuid = $1 AND \n" +" gncEntry.transGuid = gncTransaction.transGuid AND \n" +" gncTransaction.date_posted BETWEEN $2 AND $3' \n" +" LANGUAGE 'sql'; \n" +" \n" +"CREATE FUNCTION gncSubtotalClearedBalance (CHAR(32), TIMESTAMP, TIMESTAMP) \n" +" RETURNS INT8 \n" +" AS 'SELECT INT8(sum(gncEntry.amount)) \n" +" FROM gncEntry, gncTransaction \n" +" WHERE \n" +" gncEntry.accountGuid = $1 AND \n" +" gncEntry.transGuid = gncTransaction.transGuid AND \n" +" gncTransaction.date_posted BETWEEN $2 AND $3 AND \n" +" gncEntry.reconciled <> \\'n\\'' \n" +" LANGUAGE 'sql'; \n" +" \n" +"CREATE FUNCTION gncSubtotalReconedBalance (CHAR(32), TIMESTAMP, TIMESTAMP) \n" +" RETURNS INT8 \n" +" AS 'SELECT INT8(sum(gncEntry.amount)) \n" +" FROM gncEntry, gncTransaction \n" +" WHERE \n" +" gncEntry.accountGuid = $1 AND \n" +" gncEntry.transGuid = gncTransaction.transGuid AND \n" +" gncTransaction.date_posted BETWEEN $2 AND $3 AND \n" +" (gncEntry.reconciled = \\'y\\' OR \n" +" gncEntry.reconciled = \\'f\\')' \n" +" LANGUAGE 'sql'; \n" +" \n" +"-- helper functions. These intentionally use the 'wrong' fraction. \n" +"-- This is because value_frac * amount * price = value * amount_frac \n" +" \n" +"CREATE FUNCTION gncHelperPrVal (gncEntry) \n" +" RETURNS INT8 \n" +" AS 'SELECT abs($1 . value * gncCommodity.fraction) \n" +" FROM gncEntry, gncAccount, gncCommodity \n" +" WHERE \n" +" $1 . accountGuid = gncAccount.accountGuid AND \n" +" gncAccount.commodity = gncCommodity.commodity' \n" +" LANGUAGE 'sql'; \n" +" \n" +"CREATE FUNCTION gncHelperPrAmt (gncEntry) \n" +" RETURNS INT8 \n" +" AS 'SELECT abs($1 . amount * gncCommodity.fraction) \n" +" FROM gncEntry, gncTransaction, gncCommodity \n" +" WHERE \n" +" $1 . transGuid = gncTransaction.transGuid AND \n" +" gncTransaction.currency = gncCommodity.commodity' \n" +" LANGUAGE 'sql'; \n" +" \n" +"-- end of file"; diff --git a/src/backend/postgres/gncquery.c b/src/backend/postgres/gncquery.c index 00f08cf016..779995b688 100644 --- a/src/backend/postgres/gncquery.c +++ b/src/backend/postgres/gncquery.c @@ -56,10 +56,10 @@ static short module = MOD_BACKEND; struct _gnc_query { - char * q_base; - char * pq; - size_t buflen; - sqlEscape *escape; + char *q_base; + char *pq; + size_t buflen; + sqlEscape *escape; }; @@ -70,23 +70,24 @@ struct _gnc_query { sqlQuery * sqlQuery_new(void) { - sqlQuery *sq = g_new (sqlQuery, 1); + sqlQuery *sq = g_new(sqlQuery, 1); - sq->q_base = g_malloc (INITIAL_BUFSZ); - sq->buflen = INITIAL_BUFSZ; + sq->q_base = g_malloc(INITIAL_BUFSZ); + sq->buflen = INITIAL_BUFSZ; - sq->pq = sq->q_base; - sq->escape = sqlEscape_new (); - return sq; + sq->pq = sq->q_base; + sq->escape = sqlEscape_new(); + return sq; } -void -sql_Query_destroy (sqlQuery *sq) +void +sql_Query_destroy(sqlQuery * sq) { - if (!sq) return; - g_free(sq->q_base); - sqlEscape_destroy (sq->escape); - g_free(sq); + if (!sq) + return; + g_free(sq->q_base); + sqlEscape_destroy(sq->escape); + g_free(sq); } /* @@ -94,64 +95,64 @@ sql_Query_destroy (sqlQuery *sq) * sort_order and sort_distinct into one place */ static char * -sql_sort_get_type (char *p, GSList *path) +sql_sort_get_type(char *p, GSList * path) { - if (!safe_strcmp (path->data, SPLIT_TRANS)) { - path = path->next; - if (!path) - PERR ("AIEE -- sorting on the Transaction???"); - - if (!safe_strcmp (path->data, TRANS_DATE_POSTED)) { - p = stpcpy (p, "gncTransaction.date_posted"); - } else if (!safe_strcmp (path->data, TRANS_DATE_ENTERED)) { - p = stpcpy (p, "gncTransaction.date_entered"); - } else if (!safe_strcmp (path->data, TRANS_NUM)) { - p = stpcpy (p, "gncTransaction.num"); - } else if (!safe_strcmp (path->data, TRANS_DESCRIPTION)) { - p = stpcpy (p, "gncTransaction.description"); + if (!safe_strcmp(path->data, SPLIT_TRANS)) { + path = path->next; + if (!path) + PERR("AIEE -- sorting on the Transaction???"); + + if (!safe_strcmp(path->data, TRANS_DATE_POSTED)) { + p = stpcpy(p, "gncTransaction.date_posted"); + } else if (!safe_strcmp(path->data, TRANS_DATE_ENTERED)) { + p = stpcpy(p, "gncTransaction.date_entered"); + } else if (!safe_strcmp(path->data, TRANS_NUM)) { + p = stpcpy(p, "gncTransaction.num"); + } else if (!safe_strcmp(path->data, TRANS_DESCRIPTION)) { + p = stpcpy(p, "gncTransaction.description"); + } else { + PERR("Unknown Transaction parameter: %s", (char *)(path->data)); + } + + } else if (!safe_strcmp(path->data, SPLIT_ACCOUNT)) { + path = path->next; + if (!path) + PERR("AIEE -- sorting on the Account??"); + + if (!safe_strcmp(path->data, ACCOUNT_CODE_)) { + /* XXX hack alert FIXME implement this */ + PERR("BY_ACCOUNT_CODE badly implemented"); + p = stpcpy(p, "gncAccount.accountCode"); + } else { + PERR("Unknown Account parameter: %s", (char *)(path->data)); + } + + } else if (!safe_strcmp(path->data, SPLIT_DATE_RECONCILED)) { + p = stpcpy(p, "gncEntry.date_reconciled"); + } else if (!safe_strcmp(path->data, SPLIT_MEMO)) { + p = stpcpy(p, "gncEntry.memo"); + } else if (!safe_strcmp(path->data, SPLIT_RECONCILE)) { + p = stpcpy(p, "gncEntry.reconciled"); + } else if (!safe_strcmp(path->data, SPLIT_VALUE)) { + p = stpcpy(p, "gncEntry.amount"); + } else if (!safe_strcmp(path->data, SPLIT_ACCT_FULLNAME)) { + /* XXX hack alert FIXME implement this */ + PERR("BY_ACCOUNT_FULL_NAME badly implemented"); + p = stpcpy(p, "gncAccount.accountName"); + } else if (!safe_strcmp(path->data, SPLIT_CORR_ACCT_NAME)) { + /* XXX hack alert FIXME implement this */ + PERR("BY_CORR_ACCOUNT_FULL_NAME not implemented"); + p = stpcpy(p, "gncAccount.accountName"); + } else if (!safe_strcmp(path->data, SPLIT_CORR_ACCT_CODE)) { + /* XXX hack alert FIXME implement this */ + PERR("BY_CORR_ACCOUNT_CODE not implemented"); + p = stpcpy(p, "gncAccount.accountCode"); + } else { - PERR ("Unknown Transaction parameter: %s", (char*)(path->data)); + PERR("Unknown Split parameter: %s", (char *)(path->data)); } - } else if (!safe_strcmp (path->data, SPLIT_ACCOUNT)) { - path = path->next; - if (!path) - PERR ("AIEE -- sorting on the Account??"); - - if (!safe_strcmp (path->data, ACCOUNT_CODE_)) { - /* XXX hack alert FIXME implement this */ - PERR ("BY_ACCOUNT_CODE badly implemented"); - p = stpcpy (p, "gncAccount.accountCode"); - } else { - PERR ("Unknown Account parameter: %s", (char*)(path->data)); - } - - } else if (!safe_strcmp (path->data, SPLIT_DATE_RECONCILED)) { - p = stpcpy (p, "gncEntry.date_reconciled"); - } else if (!safe_strcmp (path->data, SPLIT_MEMO)) { - p = stpcpy (p, "gncEntry.memo"); - } else if (!safe_strcmp (path->data, SPLIT_RECONCILE)) { - p = stpcpy (p, "gncEntry.reconciled"); - } else if (!safe_strcmp (path->data, SPLIT_VALUE)) { - p = stpcpy (p, "gncEntry.amount"); - } else if (!safe_strcmp (path->data, SPLIT_ACCT_FULLNAME)) { - /* XXX hack alert FIXME implement this */ - PERR ("BY_ACCOUNT_FULL_NAME badly implemented"); - p = stpcpy (p, "gncAccount.accountName"); - } else if (!safe_strcmp (path->data, SPLIT_CORR_ACCT_NAME)) { - /* XXX hack alert FIXME implement this */ - PERR ("BY_CORR_ACCOUNT_FULL_NAME not implemented"); - p = stpcpy (p, "gncAccount.accountName"); - } else if (!safe_strcmp (path->data, SPLIT_CORR_ACCT_CODE)) { - /* XXX hack alert FIXME implement this */ - PERR ("BY_CORR_ACCOUNT_CODE not implemented"); - p = stpcpy (p, "gncAccount.accountCode"); - - } else { - PERR ("Unknown Split parameter: %s", (char*)(path->data)); - } - - return p; + return p; } /* =========================================================== */ @@ -162,153 +163,147 @@ sql_sort_get_type (char *p, GSList *path) * done by the Query C code. */ static char * -sql_sort_order (char *p, QueryNewSort_t sort) +sql_sort_order(char *p, QueryNewSort_t sort) { - GSList *path; - gboolean increasing; + GSList *path; + gboolean increasing; - increasing = gncQuerySortGetIncreasing (sort); + increasing = gncQuerySortGetIncreasing(sort); - ENTER ("incr=%d", increasing); + ENTER("incr=%d", increasing); - path = gncQuerySortGetParamPath (sort); - - if (path == NULL) { - /* Ok, we're not sorting on anything here. */ - ; - } else if (!safe_strcmp (path->data, QUERY_DEFAULT_SORT)) { - if (TRUE == increasing) { - p = stpcpy (p, - "gncTransaction.date_posted DESC, " - "gncTransaction.num DESC, " - "gncTransaction.date_entered DESC, " - "gncTransaction.description"); - } else { - p = stpcpy (p, - "gncTransaction.date_posted ASC, " - "gncTransaction.num ASC, " - "gncTransaction.date_entered ASC, " - "gncTransaction.description"); - } + path = gncQuerySortGetParamPath(sort); - } else { - p = sql_sort_get_type (p, path); - } + if (path == NULL) { + /* Ok, we're not sorting on anything here. */ + ; + } else if (!safe_strcmp(path->data, QUERY_DEFAULT_SORT)) { + if (TRUE == increasing) { + p = stpcpy(p, + "gncTransaction.date_posted DESC, " + "gncTransaction.num DESC, " + "gncTransaction.date_entered DESC, " + "gncTransaction.description"); + } else { + p = stpcpy(p, + "gncTransaction.date_posted ASC, " + "gncTransaction.num ASC, " + "gncTransaction.date_entered ASC, " + "gncTransaction.description"); + } - if (TRUE == increasing) - { - p = stpcpy (p, " DESC "); - } - else - { - p = stpcpy (p, " ASC "); - } + } else { + p = sql_sort_get_type(p, path); + } - return p; + if (TRUE == increasing) { + p = stpcpy(p, " DESC "); + } else { + p = stpcpy(p, " ASC "); + } + + return p; } - + /* =========================================================== */ /* distinct clauses */ static char * -sql_sort_distinct (char *p, QueryNewSort_t sort) +sql_sort_distinct(char *p, QueryNewSort_t sort) { - GSList *path; - ENTER ("."); + GSList *path; + ENTER("."); - path = gncQuerySortGetParamPath (sort); + path = gncQuerySortGetParamPath(sort); - if (NULL != path) - { - p = stpcpy (p, ", "); - } + if (NULL != path) { + p = stpcpy(p, ", "); + } - if (path == NULL) { - ; + if (path == NULL) { + ; - } else if (!safe_strcmp (path->data, QUERY_DEFAULT_SORT)) { - p = stpcpy (p, "gncTransaction.date_posted, gncTransaction.num, " - "gncTransaction.date_entered, gncTransaction.description"); - } else { - p = sql_sort_get_type (p, path); - } + } else if (!safe_strcmp(path->data, QUERY_DEFAULT_SORT)) { + p = stpcpy(p, "gncTransaction.date_posted, gncTransaction.num, " + "gncTransaction.date_entered, gncTransaction.description"); + } else { + p = sql_sort_get_type(p, path); + } - return p; + return p; } - + /* =========================================================== */ /* does sorting require a reference to this particular table? */ -static gboolean -sql_sort_sort_need_account (QueryNewSort_t sort) +static gboolean +sql_sort_sort_need_account(QueryNewSort_t sort) { - gboolean need_account = FALSE; - GSList *path; - ENTER ("."); + gboolean need_account = FALSE; + GSList *path; + ENTER("."); - path = gncQuerySortGetParamPath (sort); + path = gncQuerySortGetParamPath(sort); - if (path) - if (!safe_strcmp (path->data, SPLIT_ACCT_FULLNAME) || - !safe_strcmp (path->data, SPLIT_CORR_ACCT_NAME) || - !safe_strcmp (path->data, SPLIT_CORR_ACCT_CODE) || - !safe_strcmp (path->data, SPLIT_ACCOUNT)) - need_account = TRUE; + if (path) + if (!safe_strcmp(path->data, SPLIT_ACCT_FULLNAME) || + !safe_strcmp(path->data, SPLIT_CORR_ACCT_NAME) || + !safe_strcmp(path->data, SPLIT_CORR_ACCT_CODE) || + !safe_strcmp(path->data, SPLIT_ACCOUNT)) + need_account = TRUE; - return need_account; + return need_account; } - -static gboolean -sql_sort_need_account (Query *q) + +static gboolean +sql_sort_need_account(Query * q) { - gboolean need_account = FALSE; - QueryNewSort_t s1, s2, s3; + gboolean need_account = FALSE; + QueryNewSort_t s1, s2, s3; - gncQueryGetSorts (q, &s1, &s2, &s3); + gncQueryGetSorts(q, &s1, &s2, &s3); - need_account = sql_sort_sort_need_account (s1) || - sql_sort_sort_need_account (s2) || - sql_sort_sort_need_account (s3); + need_account = sql_sort_sort_need_account(s1) || + sql_sort_sort_need_account(s2) || sql_sort_sort_need_account(s3); - return need_account; + return need_account; } /* =========================================================== */ /* does sorting require a reference to this particular table? */ -static gboolean -sql_sort_sort_need_entry (QueryNewSort_t sort) +static gboolean +sql_sort_sort_need_entry(QueryNewSort_t sort) { - gboolean need_entry = FALSE; - GSList *path; - ENTER ("."); + gboolean need_entry = FALSE; + GSList *path; + ENTER("."); - path = gncQuerySortGetParamPath (sort); + path = gncQuerySortGetParamPath(sort); - if (path) - if (!safe_strcmp (path->data, SPLIT_VALUE) || - !safe_strcmp (path->data, SPLIT_DATE_RECONCILED) || - !safe_strcmp (path->data, SPLIT_MEMO) || - !safe_strcmp (path->data, SPLIT_RECONCILE)) - need_entry = TRUE; - - return need_entry; + if (path) + if (!safe_strcmp(path->data, SPLIT_VALUE) || + !safe_strcmp(path->data, SPLIT_DATE_RECONCILED) || + !safe_strcmp(path->data, SPLIT_MEMO) || + !safe_strcmp(path->data, SPLIT_RECONCILE)) + need_entry = TRUE; + + return need_entry; } - -static gboolean -sql_sort_need_entry (Query *q) + +static gboolean +sql_sort_need_entry(Query * q) { - gboolean need_entry = FALSE; - QueryNewSort_t s1, s2, s3; + gboolean need_entry = FALSE; + QueryNewSort_t s1, s2, s3; - gncQueryGetSorts (q, &s1, &s2, &s3); + gncQueryGetSorts(q, &s1, &s2, &s3); - need_entry = sql_sort_sort_need_entry (s1) || - sql_sort_sort_need_entry (s2) || - sql_sort_sort_need_entry (s3); + need_entry = sql_sort_sort_need_entry(s1) || + sql_sort_sort_need_entry(s2) || sql_sort_sort_need_entry(s3); - return need_entry; + return need_entry; } /* =========================================================== */ @@ -330,7 +325,7 @@ sql_sort_need_entry (Query *q) sq->pq = stpcpy(sq->pq, fieldname " ~"); \ else \ sq->pq = stpcpy(sq->pq, fieldname " ="); \ - if (pdata->options == STRING_MATCH_NORMAL) { \ + if (pdata->options == STRING_MATCH_CASEINSENSITIVE) { \ sq->pq = stpcpy(sq->pq, "*"); \ } \ sq->pq = stpcpy(sq->pq, " '"); \ @@ -424,259 +419,251 @@ sql_sort_need_entry (Query *q) /* =========================================================== */ static const char * -kvp_table_name (kvp_value_t value_t) +kvp_table_name(kvp_value_t value_t) { - switch (value_t) - { - case KVP_TYPE_GINT64: - return "gnckvpvalue_int64"; + switch (value_t) { + case KVP_TYPE_GINT64: + return "gnckvpvalue_int64"; - case KVP_TYPE_DOUBLE: - return "gnckvpvalue_dbl"; + case KVP_TYPE_DOUBLE: + return "gnckvpvalue_dbl"; - case KVP_TYPE_NUMERIC: - return "gnckvpvalue_numeric"; + case KVP_TYPE_NUMERIC: + return "gnckvpvalue_numeric"; - case KVP_TYPE_STRING: - return "gnckvpvalue_str"; + case KVP_TYPE_STRING: + return "gnckvpvalue_str"; - case KVP_TYPE_GUID: - return "gnckvpvalue_guid"; + case KVP_TYPE_GUID: + return "gnckvpvalue_guid"; - case KVP_TYPE_TIMESPEC: - return "gnckvpvalue_timespec"; + case KVP_TYPE_TIMESPEC: + return "gnckvpvalue_timespec"; - default: - PWARN ("kvp value not supported"); - return NULL; - } + default: + PWARN("kvp value not supported"); + return NULL; + } } static char * -kvp_path_name (GSList *path) +kvp_path_name(GSList * path) { - GString *s = g_string_new (NULL); - char *name; + GString *s = g_string_new(NULL); + char *name; - for ( ; path; path = path->next) - { - g_string_append_c (s, '/'); - g_string_append (s, path->data); - } + for (; path; path = path->next) { + g_string_append_c(s, '/'); + g_string_append(s, path->data); + } - name = s->str; - g_string_free (s, FALSE); + name = s->str; + g_string_free(s, FALSE); - return name; + return name; } static const char * -compare_op_name (query_compare_t how) +compare_op_name(query_compare_t how) { - switch (how) - { - case COMPARE_LT: - return " < "; + switch (how) { + case COMPARE_LT: + return " < "; - case COMPARE_LTE: - return " <= "; + case COMPARE_LTE: + return " <= "; - case COMPARE_EQUAL: - return " = "; + case COMPARE_EQUAL: + return " = "; - case COMPARE_GTE: - return " >= "; + case COMPARE_GTE: + return " >= "; - case COMPARE_GT: - return " > "; + case COMPARE_GT: + return " > "; - case COMPARE_NEQ: - return " != "; + case COMPARE_NEQ: + return " != "; - default: - return NULL; - } + default: + return NULL; + } } static char * -kvp_left_operand (kvp_value *value) +kvp_left_operand(kvp_value * value) { - kvp_value_t value_t; - const char *kvptable; + kvp_value_t value_t; + const char *kvptable; - g_return_val_if_fail (value, NULL); + g_return_val_if_fail(value, NULL); - value_t = kvp_value_get_type (value); + value_t = kvp_value_get_type(value); - kvptable = kvp_table_name (value_t); + kvptable = kvp_table_name(value_t); - switch (value_t) - { - case KVP_TYPE_GINT64: - case KVP_TYPE_DOUBLE: - case KVP_TYPE_GUID: - case KVP_TYPE_TIMESPEC: - case KVP_TYPE_STRING: - return g_strdup_printf ("%s.data", kvptable); + switch (value_t) { + case KVP_TYPE_GINT64: + case KVP_TYPE_DOUBLE: + case KVP_TYPE_GUID: + case KVP_TYPE_TIMESPEC: + case KVP_TYPE_STRING: + return g_strdup_printf("%s.data", kvptable); - case KVP_TYPE_NUMERIC: { - gnc_numeric n = kvp_value_get_numeric (value); - return g_strdup_printf ("(%lld::int8 * %s.num::int8)", - (long long int) n.denom, kvptable); + case KVP_TYPE_NUMERIC:{ + gnc_numeric n = kvp_value_get_numeric(value); + return g_strdup_printf("(%lld::int8 * %s.num::int8)", + (long long int)n.denom, kvptable); + } + + default: + return NULL; } - - default: - return NULL; - } } static char * -kvp_right_operand (sqlQuery *sq, kvp_value *value) +kvp_right_operand(sqlQuery * sq, kvp_value * value) { - kvp_value_t value_t; - const char *kvptable; + kvp_value_t value_t; + const char *kvptable; - g_return_val_if_fail (value, NULL); + g_return_val_if_fail(value, NULL); - value_t = kvp_value_get_type (value); + value_t = kvp_value_get_type(value); - kvptable = kvp_table_name (value_t); + kvptable = kvp_table_name(value_t); - switch (value_t) - { - case KVP_TYPE_GINT64: - return g_strdup_printf ("%lld", - (long long int) kvp_value_get_gint64 (value)); + switch (value_t) { + case KVP_TYPE_GINT64: + return g_strdup_printf("%lld", + (long long int) + kvp_value_get_gint64(value)); - case KVP_TYPE_DOUBLE: - return g_strdup_printf (SQL_DBL_FMT, kvp_value_get_double (value)); + case KVP_TYPE_DOUBLE: + return g_strdup_printf(SQL_DBL_FMT, kvp_value_get_double(value)); - case KVP_TYPE_GUID: { - char *guid = guid_to_string (kvp_value_get_guid (value)); - char *s = g_strdup_printf ("'%s'", guid); - g_free (guid); - return s; + case KVP_TYPE_GUID:{ + char *guid = guid_to_string(kvp_value_get_guid(value)); + char *s = g_strdup_printf("'%s'", guid); + g_free(guid); + return s; + } + + case KVP_TYPE_TIMESPEC:{ + char s[80]; + gnc_timespec_to_iso8601_buff(kvp_value_get_timespec(value), s); + return g_strdup_printf("'%s'", s); + } + + case KVP_TYPE_STRING:{ + const char *s = sqlEscapeString(sq->escape, + kvp_value_get_string(value)); + return g_strdup_printf("'%s'", s); + } + + case KVP_TYPE_NUMERIC:{ + gnc_numeric n = kvp_value_get_numeric(value); + return g_strdup_printf("(%lld::int8 * %s.denom::int8)", + (long long int)n.num, kvptable); + } + + default: + return NULL; } - - case KVP_TYPE_TIMESPEC: { - char s[80]; - gnc_timespec_to_iso8601_buff (kvp_value_get_timespec (value), s); - return g_strdup_printf ("'%s'", s); - } - - case KVP_TYPE_STRING: { - const char *s = sqlEscapeString (sq->escape, - kvp_value_get_string (value)); - return g_strdup_printf ("'%s'", s); - } - - case KVP_TYPE_NUMERIC: { - gnc_numeric n = kvp_value_get_numeric (value); - return g_strdup_printf ("(%lld::int8 * %s.denom::int8)", - (long long int) n.num, kvptable); - } - - default: - return NULL; - } } static void -add_kvp_clause (sqlQuery *sq, const char *kvptable, const char *entity_table, - const char *left, const char *op, const char *right) +add_kvp_clause(sqlQuery * sq, const char *kvptable, const char *entity_table, + const char *left, const char *op, const char *right) { - sq->pq = stpcpy (sq->pq, " ( "); + sq->pq = stpcpy(sq->pq, " ( "); - sq->pq = stpcpy (sq->pq, "gncPathCache.ipath = "); - sq->pq = stpcpy (sq->pq, kvptable); - sq->pq = stpcpy (sq->pq, ".ipath"); + sq->pq = stpcpy(sq->pq, "gncPathCache.ipath = "); + sq->pq = stpcpy(sq->pq, kvptable); + sq->pq = stpcpy(sq->pq, ".ipath"); - sq->pq = stpcpy (sq->pq, " AND "); + sq->pq = stpcpy(sq->pq, " AND "); - sq->pq = stpcpy (sq->pq, entity_table); - sq->pq = stpcpy (sq->pq, ".iguid"); - sq->pq = stpcpy (sq->pq, " = "); - sq->pq = stpcpy (sq->pq, kvptable); - sq->pq = stpcpy (sq->pq, ".iguid"); + sq->pq = stpcpy(sq->pq, entity_table); + sq->pq = stpcpy(sq->pq, ".iguid"); + sq->pq = stpcpy(sq->pq, " = "); + sq->pq = stpcpy(sq->pq, kvptable); + sq->pq = stpcpy(sq->pq, ".iguid"); - sq->pq = stpcpy (sq->pq, " AND "); + sq->pq = stpcpy(sq->pq, " AND "); - sq->pq = stpcpy (sq->pq, left); - sq->pq = stpcpy (sq->pq, op); - sq->pq = stpcpy (sq->pq, right); + sq->pq = stpcpy(sq->pq, left); + sq->pq = stpcpy(sq->pq, op); + sq->pq = stpcpy(sq->pq, right); - sq->pq = stpcpy (sq->pq, " ) "); + sq->pq = stpcpy(sq->pq, " ) "); } static void -sqlQuery_kvp_build (sqlQuery *sq, GSList *sort_path, query_compare_t how, - gboolean invert, query_kvp_t kpd) +sqlQuery_kvp_build(sqlQuery * sq, GSList * sort_path, query_compare_t how, + gboolean invert, query_kvp_t kpd) { - kvp_value_t value_t; - const char *kvptable; - const char *op; - GList *list; - GList *node; - char *right; - char *left; - char *path; + kvp_value_t value_t; + const char *kvptable; + const char *op; + GList *list; + GList *node; + char *right; + char *left; + char *path; - g_return_if_fail (sq && kpd && kpd->path && kpd->value); + g_return_if_fail(sq && kpd && kpd->path && kpd->value); - if (safe_strcmp (sort_path->data, SPLIT_KVP) && - ((safe_strcmp (sort_path->data, SPLIT_ACCOUNT) && - safe_strcmp (sort_path->data, SPLIT_TRANS)) || - safe_strcmp (sort_path->next->data, SPLIT_KVP))) - return; + if (safe_strcmp(sort_path->data, SPLIT_KVP) && + ((safe_strcmp(sort_path->data, SPLIT_ACCOUNT) && + safe_strcmp(sort_path->data, SPLIT_TRANS)) || + safe_strcmp(sort_path->next->data, SPLIT_KVP))) + return; - value_t = kvp_value_get_type (kpd->value); + value_t = kvp_value_get_type(kpd->value); - if (value_t == KVP_TYPE_GUID && how != COMPARE_EQUAL) - { - PWARN ("guid non-equality comparison not supported"); - return; - } + if (value_t == KVP_TYPE_GUID && how != COMPARE_EQUAL) { + PWARN("guid non-equality comparison not supported"); + return; + } - kvptable = kvp_table_name (value_t); - if (!kvptable) - return; + kvptable = kvp_table_name(value_t); + if (!kvptable) + return; - path = kvp_path_name (kpd->path); - op = compare_op_name (how); - left = kvp_left_operand (kpd->value); - right = kvp_right_operand (sq, kpd->value); + path = kvp_path_name(kpd->path); + op = compare_op_name(how); + left = kvp_left_operand(kpd->value); + right = kvp_right_operand(sq, kpd->value); - list = NULL; - if (!safe_strcmp (sort_path->data, SPLIT_KVP)) - list = g_list_prepend (list, "gncEntry"); - else if (!safe_strcmp (sort_path->data, SPLIT_TRANS)) - list = g_list_prepend (list, "gncTransaction"); - else - list = g_list_prepend (list, "gncAccount"); + list = NULL; + if (!safe_strcmp(sort_path->data, SPLIT_KVP)) + list = g_list_prepend(list, "gncEntry"); + else if (!safe_strcmp(sort_path->data, SPLIT_TRANS)) + list = g_list_prepend(list, "gncTransaction"); + else + list = g_list_prepend(list, "gncAccount"); - if (invert) - sq->pq = stpcpy (sq->pq, "NOT "); + if (invert) + sq->pq = stpcpy(sq->pq, "NOT "); - sq->pq = stpcpy (sq->pq, - " EXISTS ( SELECT true " - " WHERE "); + sq->pq = stpcpy(sq->pq, " EXISTS ( SELECT true " " WHERE "); - sq->pq = stpcpy (sq->pq, "gncPathCache.path = '"); - sq->pq = stpcpy (sq->pq, sqlEscapeString (sq->escape, path)); - sq->pq = stpcpy (sq->pq, "'"); + sq->pq = stpcpy(sq->pq, "gncPathCache.path = '"); + sq->pq = stpcpy(sq->pq, sqlEscapeString(sq->escape, path)); + sq->pq = stpcpy(sq->pq, "'"); - for (node = list; node; node = node->next) - { - sq->pq = stpcpy (sq->pq, " AND "); - add_kvp_clause (sq, kvptable, node->data, left, op, right); - } + for (node = list; node; node = node->next) { + sq->pq = stpcpy(sq->pq, " AND "); + add_kvp_clause(sq, kvptable, node->data, left, op, right); + } - sq->pq = stpcpy (sq->pq, " ) "); + sq->pq = stpcpy(sq->pq, " ) "); - g_free (path); - g_free (left); - g_free (right); - g_list_free (list); + g_free(path); + g_free(left); + g_free(right); + g_list_free(list); } /* =========================================================== */ @@ -688,7 +675,7 @@ sqlQuery_kvp_build (sqlQuery *sq, GSList *sort_path, query_compare_t how, * performance tuning, I discovered that gratuitously listing a * table that is not referenced (e.g. gncAccount when the query * doesn't involve accounts) is not gratuitous: it can hurt - * performance by factors of ten(!!!). Thus, there is a *lot* + * performance by factors of ten(!!!). Thus, there is a *lot* * (hundreds of lines of code) of extra complexity here to make * sure that only the needed tables get listed, and no more. * @@ -713,425 +700,428 @@ sqlQuery_kvp_build (sqlQuery *sq, GSList *sort_path, query_compare_t how, */ const char * -sqlQuery_build (sqlQuery *sq, Query *q) +sqlQuery_build(sqlQuery * sq, Query * q) { - GList *il, *jl, *qterms, *andterms; - GList *tables = NULL; - GSList *path; - QueryNewTerm_t qt; - QueryPredData_t pd; - QueryNewSort_t s1, s2, s3; - int more_or = 0; - int more_and = 0; - int max_rows; - gboolean invert; - gboolean need_account_commodity = FALSE; - gboolean need_trans_commodity = FALSE; - gboolean need_account = FALSE; - gboolean need_entry = FALSE; - - if (!sq || !q) return NULL; - - /* Only Split searches are allowed */ - if (safe_strcmp (gncQueryGetSearchFor (q), GNC_ID_SPLIT)) { - PERR ("Only SPLITs are supported, not %s", gncQueryGetSearchFor (q)); - return NULL; - } - - /* Determine whether the query will need to reference certain - * tables. See note above for details. */ - qterms = gncQueryGetTerms (q); - - for (il=qterms; il; il=il->next) - { - /* andterms is GList of query terms that must be anded */ - andterms = il->data; - - for (jl=andterms; jl; jl=jl->next) - { - qt = (QueryNewTerm_t)jl->data; - pd = gncQueryTermGetPredData (qt); - path = gncQueryTermGetParamPath (qt); - - g_assert (path); - - if (!safe_strcmp (path->data, SPLIT_ACTION) || - !safe_strcmp (path->data, SPLIT_RECONCILE) || - !safe_strcmp (path->data, SPLIT_MEMO) || - !safe_strcmp (path->data, SPLIT_SHARE_PRICE)) { - need_entry = TRUE; - } else if (!safe_strcmp (path->data, SPLIT_VALUE)) { - need_entry = TRUE; - need_trans_commodity = TRUE; - } else if (!safe_strcmp (pd->type_name, QUERYCORE_GUID)) { - if (!safe_strcmp (path->data, QUERY_PARAM_GUID)) - need_entry = TRUE; - else if (!safe_strcmp (path->data, SPLIT_ACCOUNT)) { - need_account = TRUE; - } - } else if (!safe_strcmp (pd->type_name, QUERYCORE_KVP)) { - if (!safe_strcmp (path->data, SPLIT_KVP)) - need_entry = TRUE; - else if (!safe_strcmp (path->data, SPLIT_ACCOUNT)) - need_account = TRUE; - } else if (!safe_strcmp (path->data, SPLIT_AMOUNT)) { - need_entry = TRUE; - need_account_commodity = TRUE; - need_account = TRUE; - } - } - } - - /* determine whether the requested sort order needs these tables */ - need_entry = need_entry || sql_sort_need_entry (q); - need_account = need_account || sql_sort_need_account (q); - - /* reset the buffer pointers */ - sq->pq = sq->q_base; - sq->pq = stpcpy(sq->pq, - "SELECT DISTINCT gncTransaction.* "); - - gncQueryGetSorts (q, &s1, &s2, &s3); - - /* For SELECT DISTINCT, ORDER BY expressions must appear in target list */ - sq->pq = sql_sort_distinct (sq->pq, s1); - sq->pq = sql_sort_distinct (sq->pq, s2); - sq->pq = sql_sort_distinct (sq->pq, s3); - - /* add needed explicit tables. postgres can figure out the rest. */ - if (need_account_commodity) - tables = g_list_prepend (tables, "gncCommodity account_com"); - - if (need_trans_commodity) - tables = g_list_prepend (tables, "gncCommodity trans_com"); - - if (tables) - { - GList *node; - - sq->pq = stpcpy(sq->pq, " FROM "); - - for (node = tables; node; node = node->next) - { - sq->pq = stpcpy(sq->pq, node->data); - if (node->next) - sq->pq = stpcpy(sq->pq, ", "); - } - - g_list_free (tables); - } - - sq->pq = stpcpy(sq->pq, " WHERE "); - - if (need_entry || need_account) - { - sq->pq = stpcpy(sq->pq, - " gncEntry.transGuid = gncTransaction.transGuid AND "); - } - - if (need_account) - { - sq->pq = stpcpy(sq->pq, - " gncEntry.accountGuid = gncAccount.accountGuid AND "); - } - - sq->pq = stpcpy(sq->pq, " ( "); - - /* qterms is a list of lists: outer list is a set of terms - * that must be OR'ed together, inner lists are a set of terms - * that must be anded. Out strategy is to build the sql query - * of the AND terms first, and OR these together ... - */ - for (il=qterms; il; il=il->next) - { - /* andterms is GList of query terms that must be anded */ - andterms = il->data; - - /* if there are andterms, open a brace */ - if (andterms) - { - /* concatenate additional OR terms */ - if (more_or) sq->pq = stpcpy (sq->pq, " OR "); - more_or = 1; - sq->pq = stpcpy(sq->pq, "("); - } - - more_and = 0; - for (jl=andterms; jl; jl=jl->next) - { - /* concatencate more terms together */ - if(more_and) sq->pq = stpcpy(sq->pq, " AND "); - more_and = 1; - - qt = (QueryNewTerm_t )jl->data; - pd = gncQueryTermGetPredData (qt); - path = gncQueryTermGetParamPath (qt); - invert = gncQueryTermIsInverted (qt); - - if (!safe_strcmp (pd->type_name, QUERYCORE_GUID)) { - query_guid_t pdata = (query_guid_t) pd; - GList *node; - char * field = NULL; - - PINFO("term is QUERYCORE_GUID"); - - if (!safe_strcmp (path->data, QUERY_PARAM_GUID)) { - field = "gncEntry.entryGUID"; - g_assert (pdata->options != GUID_MATCH_ALL); - - } else if (!safe_strcmp (path->data, SPLIT_TRANS) && - !safe_strcmp (path->next->data, QUERY_PARAM_GUID)) { - field = "gncEntry.transGUID"; - g_assert (pdata->options != GUID_MATCH_ALL); - - } else if (!safe_strcmp (path->data, SPLIT_ACCOUNT) && - !safe_strcmp (path->next->data, QUERY_PARAM_GUID)) { - field = "gncEntry.accountGUID"; - g_assert (pdata->options != GUID_MATCH_ALL); - - } else if (!safe_strcmp (path->data, SPLIT_TRANS) && - !safe_strcmp (path->next->data, TRANS_SPLITLIST) && - !safe_strcmp (path->next->next->data, SPLIT_ACCOUNT_GUID)) { - field = "gncEntry.accountGUID"; - g_assert (pdata->options == GUID_MATCH_ALL); - - } else { - PINFO ("Unknown GUID parameter, %s", (char*)path->data); - - /* XXX: Need to support the Book GUID? (gncAccount.bookGUID) */ - } - - if (field != NULL) { - - if (invert) - sq->pq = stpcpy (sq->pq, "NOT "); - - sq->pq = stpcpy(sq->pq, "("); - - for (node = pdata->guids; node; node=node->next) { - - switch (pdata->options) { - case GUID_MATCH_NONE: - sq->pq = stpcpy (sq->pq, "NOT "); - /* fall through */ - - case GUID_MATCH_ANY: - sq->pq = stpcpy (sq->pq, field); - sq->pq = stpcpy (sq->pq, "='"); - sq->pq = guid_to_string_buff ((GUID*) node->data, sq->pq); - sq->pq = stpcpy(sq->pq, "'"); - break; - - case GUID_MATCH_ALL: - sq->pq = stpcpy (sq->pq, - " EXISTS ( SELECT true FROM gncEntry e" - " WHERE " - "e.transGuid = gncTransaction.transGuid" - " AND " - "e.accountGuid='"); - sq->pq = guid_to_string_buff ((GUID*) node->data, sq->pq); - sq->pq = stpcpy(sq->pq, "')"); - - break; - - default: - PERR ("unexpected guid match type: %d", pdata->options); - break; - } - - if (node->next) { - switch (pdata->options) { - case GUID_MATCH_ANY: - sq->pq = stpcpy(sq->pq, " OR "); - break; - - case GUID_MATCH_ALL: - case GUID_MATCH_NONE: - sq->pq = stpcpy(sq->pq, " AND "); - break; - - default: - PERR ("unexpected account match type: %d", pdata->options); - break; - } - } - } - - sq->pq = stpcpy(sq->pq, ")"); - - } else { - /* Empty field -- nothing to "AND" in... */ - more_and = 0; - } - - } else if (!safe_strcmp (pd->type_name, QUERYCORE_STRING)) { - query_string_t pdata = (query_string_t) pd; + GList *il, *jl, *qterms, *andterms; + GList *tables = NULL; + GSList *path; + QueryNewTerm_t qt; + QueryPredData_t pd; + QueryNewSort_t s1, s2, s3; + int more_or = 0; + int more_and = 0; + int max_rows; + gboolean invert; + gboolean need_account_commodity = FALSE; + gboolean need_trans_commodity = FALSE; + gboolean need_account = FALSE; + gboolean need_entry = FALSE; + + if (!sq || !q) + return NULL; + + /* Only Split searches are allowed */ + if (safe_strcmp(gncQueryGetSearchFor(q), GNC_ID_SPLIT)) { + PERR("Only SPLITs are supported, not %s", gncQueryGetSearchFor(q)); + return NULL; + } + + /* Determine whether the query will need to reference certain + * tables. See note above for details. */ + qterms = gncQueryGetTerms(q); + + for (il = qterms; il; il = il->next) { + /* andterms is GList of query terms that must be anded */ + andterms = il->data; + + for (jl = andterms; jl; jl = jl->next) { + qt = (QueryNewTerm_t) jl->data; + pd = gncQueryTermGetPredData(qt); + path = gncQueryTermGetParamPath(qt); + + g_assert(path); + + if (!safe_strcmp(path->data, SPLIT_ACTION) || + !safe_strcmp(path->data, SPLIT_RECONCILE) || + !safe_strcmp(path->data, SPLIT_MEMO) || + !safe_strcmp(path->data, SPLIT_SHARE_PRICE)) { + need_entry = TRUE; + } else if (!safe_strcmp(path->data, SPLIT_VALUE)) { + need_entry = TRUE; + need_trans_commodity = TRUE; + } else if (!safe_strcmp(pd->type_name, QUERYCORE_GUID)) { + if (!safe_strcmp(path->data, QUERY_PARAM_GUID)) + need_entry = TRUE; + else if (!safe_strcmp(path->data, SPLIT_ACCOUNT)) { + need_account = TRUE; + } + } else if (!safe_strcmp(pd->type_name, QUERYCORE_KVP)) { + if (!safe_strcmp(path->data, SPLIT_KVP)) + need_entry = TRUE; + else if (!safe_strcmp(path->data, SPLIT_ACCOUNT)) + need_account = TRUE; + } else if (!safe_strcmp(path->data, SPLIT_AMOUNT)) { + need_entry = TRUE; + need_account_commodity = TRUE; + need_account = TRUE; + } + } + } + + /* determine whether the requested sort order needs these tables */ + need_entry = need_entry || sql_sort_need_entry(q); + need_account = need_account || sql_sort_need_account(q); + + /* reset the buffer pointers */ + sq->pq = sq->q_base; + sq->pq = stpcpy(sq->pq, "SELECT DISTINCT gncTransaction.* "); + + gncQueryGetSorts(q, &s1, &s2, &s3); + + /* For SELECT DISTINCT, ORDER BY expressions must appear in target list */ + sq->pq = sql_sort_distinct(sq->pq, s1); + sq->pq = sql_sort_distinct(sq->pq, s2); + sq->pq = sql_sort_distinct(sq->pq, s3); + + /* add needed explicit tables. postgres can figure out the rest. */ + if (need_account_commodity) + tables = g_list_prepend(tables, "gncCommodity account_com"); + + if (need_trans_commodity) + tables = g_list_prepend(tables, "gncCommodity trans_com"); + + if (tables) { + GList *node; + + sq->pq = stpcpy(sq->pq, " FROM "); + + for (node = tables; node; node = node->next) { + sq->pq = stpcpy(sq->pq, node->data); + if (node->next) + sq->pq = stpcpy(sq->pq, ", "); + } + + g_list_free(tables); + } + + sq->pq = stpcpy(sq->pq, " WHERE "); + + if (need_entry || need_account) { + sq->pq = stpcpy(sq->pq, + " gncEntry.transGuid = gncTransaction.transGuid AND "); + } + + if (need_account) { + sq->pq = stpcpy(sq->pq, + " gncEntry.accountGuid = gncAccount.accountGuid AND "); + } + + sq->pq = stpcpy(sq->pq, " ( "); + + /* qterms is a list of lists: outer list is a set of terms + * that must be OR'ed together, inner lists are a set of terms + * that must be anded. Out strategy is to build the sql query + * of the AND terms first, and OR these together ... + */ + for (il = qterms; il; il = il->next) { + /* andterms is GList of query terms that must be anded */ + andterms = il->data; + + /* if there are andterms, open a brace */ + if (andterms) { + /* concatenate additional OR terms */ + if (more_or) + sq->pq = stpcpy(sq->pq, " OR "); + more_or = 1; + sq->pq = stpcpy(sq->pq, "("); + } + + more_and = 0; + for (jl = andterms; jl; jl = jl->next) { + /* concatencate more terms together */ + if (more_and) { + sq->pq = stpcpy(sq->pq, " AND "); + } + more_and = 1; + + qt = (QueryNewTerm_t) jl->data; + pd = gncQueryTermGetPredData(qt); + path = gncQueryTermGetParamPath(qt); + invert = gncQueryTermIsInverted(qt); + + if (!safe_strcmp(pd->type_name, QUERYCORE_GUID)) { + query_guid_t pdata = (query_guid_t) pd; + GList *node; + char *field = NULL; + + PINFO("term is QUERYCORE_GUID"); + + if (!safe_strcmp(path->data, QUERY_PARAM_GUID)) { + field = "gncEntry.entryGUID"; + g_assert(pdata->options != GUID_MATCH_ALL); + + } else if (!safe_strcmp(path->data, SPLIT_TRANS) && + !safe_strcmp(path->next->data, QUERY_PARAM_GUID)) { + field = "gncEntry.transGUID"; + g_assert(pdata->options != GUID_MATCH_ALL); + + } else if (!safe_strcmp(path->data, SPLIT_ACCOUNT) && + !safe_strcmp(path->next->data, QUERY_PARAM_GUID)) { + field = "gncEntry.accountGUID"; + g_assert(pdata->options != GUID_MATCH_ALL); + + } else if (!safe_strcmp(path->data, SPLIT_TRANS) && + !safe_strcmp(path->next->data, TRANS_SPLITLIST) && + !safe_strcmp(path->next->next->data, + SPLIT_ACCOUNT_GUID)) { + field = "gncEntry.accountGUID"; + g_assert(pdata->options == GUID_MATCH_ALL); + + } else if (!safe_strcmp(path->data, QUERY_PARAM_BOOK) && + !safe_strcmp(path->next->data, QUERY_PARAM_GUID)) { + /* XXX: Need to support the Book GUID? (gncAccount.bookGUID) */ + field = "gncAccount.bookGUID"; + g_assert(pdata->options != GUID_MATCH_ALL); + } else { + PINFO("Unknown GUID parameter, %s", (char *)path->data); + } + + if (field != NULL) { + if (invert) + sq->pq = stpcpy(sq->pq, "NOT "); + + sq->pq = stpcpy(sq->pq, "("); + + for (node = pdata->guids; node; node = node->next) { + + switch (pdata->options) { + case GUID_MATCH_NONE: + sq->pq = stpcpy(sq->pq, "NOT "); + /* fall through */ + + case GUID_MATCH_ANY: + sq->pq = stpcpy(sq->pq, field); + sq->pq = stpcpy(sq->pq, "='"); + sq->pq = + guid_to_string_buff((GUID *) node->data, + sq->pq); + sq->pq = stpcpy(sq->pq, "'"); + break; + + case GUID_MATCH_ALL: + sq->pq = stpcpy(sq->pq, + " EXISTS ( SELECT true FROM gncEntry e" + " WHERE " + "e.transGuid = gncTransaction.transGuid" + " AND " "e.accountGuid='"); + sq->pq = + guid_to_string_buff((GUID *) node->data, + sq->pq); + sq->pq = stpcpy(sq->pq, "')"); + + break; + + default: + PERR("unexpected guid match type: %d", + pdata->options); + break; + } + + if (node->next) { + switch (pdata->options) { + case GUID_MATCH_ANY: + sq->pq = stpcpy(sq->pq, " OR "); + break; + + case GUID_MATCH_ALL: + case GUID_MATCH_NONE: + sq->pq = stpcpy(sq->pq, " AND "); + break; + + default: + PERR("unexpected account match type: %d", + pdata->options); + break; + } + } + } + + sq->pq = stpcpy(sq->pq, ")"); + + } else { + /* Empty field -- nothing to "AND" in... */ + more_and = 0; + } + + } else if (!safe_strcmp(pd->type_name, QUERYCORE_STRING)) { + query_string_t pdata = (query_string_t) pd; + + if (!safe_strcmp(path->data, SPLIT_ACTION)) { + PINFO("term is PR_ACTION"); + STRING_TERM("gncEntry.action"); + + } else if (!safe_strcmp(path->data, SPLIT_MEMO)) { + PINFO("term is PR_MEMO"); + STRING_TERM("gncEntry.memo"); + + } else if (!safe_strcmp(path->data, SPLIT_TRANS)) { + path = path->next; + + if (!path) { + PINFO("invalid transaction parameter"); + + } else if (!safe_strcmp(path->data, TRANS_DESCRIPTION)) { + PINFO("term is PR_DESC"); + STRING_TERM("gncTransaction.description"); + + } else if (!safe_strcmp(path->data, TRANS_NUM)) { + PINFO("term is PR_NUM"); + STRING_TERM("gncTransaction.num"); + } else + PINFO("unknown string (transaction) parameter: %s", + (char *)(path->data)); + } else + PINFO("unknown string (split) parameter: %s", + (char *)(path->data)); - if (!safe_strcmp (path->data, SPLIT_ACTION)) { - PINFO("term is PR_ACTION"); - STRING_TERM ("gncEntry.action"); - - } else if (!safe_strcmp (path->data, SPLIT_MEMO)) { - PINFO("term is PR_MEMO"); - STRING_TERM ("gncEntry.memo"); - - } else if (!safe_strcmp (path->data, SPLIT_TRANS)) { - path = path->next; - - if (!path) { - PINFO ("invalid transaction parameter"); - - } else if (!safe_strcmp (path->data, TRANS_DESCRIPTION)) { - PINFO("term is PR_DESC"); - STRING_TERM ("gncTransaction.description"); - - } else if (!safe_strcmp (path->data, TRANS_NUM)) { - PINFO("term is PR_NUM"); - STRING_TERM ("gncTransaction.num"); - } else - PINFO ("unknown string (transaction) parameter: %s", - (char*)(path->data)); - } else - PINFO ("unknown string (split) parameter: %s", - (char*)(path->data)); + + } else if (!safe_strcmp(pd->type_name, QUERYCORE_NUMERIC)) { + query_numeric_t pdata = (query_numeric_t) pd; - } else if (!safe_strcmp (pd->type_name, QUERYCORE_NUMERIC)) { - query_numeric_t pdata = (query_numeric_t) pd; + if (!safe_strcmp(path->data, SPLIT_AMOUNT)) { + PINFO("term is PR_SHRS"); + sq->pq = stpcpy(sq->pq, + "gncAccount.commodity = account_com.commodity AND "); + AMOUNT_TERM("gncEntry.amount", "abs(gncEntry.amount)", + "account_com.fraction"); + + } else if (!safe_strcmp(path->data, SPLIT_VALUE)) { + + PINFO("term is PR_VALUE"); + sq->pq = stpcpy(sq->pq, + "gncTransaction.currency = trans_com.commodity AND "); + AMOUNT_TERM("gncEntry.value", "abs(gncEntry.value)", + "trans_com.fraction"); + } else if (!safe_strcmp(path->data, SPLIT_SHARE_PRICE)) { - if (!safe_strcmp (path->data, SPLIT_AMOUNT)) { - PINFO("term is PR_SHRS"); - sq->pq = stpcpy(sq->pq, - "gncAccount.commodity = account_com.commodity AND "); - AMOUNT_TERM ("gncEntry.amount", "abs(gncEntry.amount)", - "account_com.fraction"); - - } else if (!safe_strcmp (path->data, SPLIT_VALUE)) { - - PINFO("term is PR_VALUE"); - sq->pq = stpcpy(sq->pq, - "gncTransaction.currency = trans_com.commodity AND "); - AMOUNT_TERM ("gncEntry.value", "abs(gncEntry.value)", - "trans_com.fraction"); - - } else if (!safe_strcmp (path->data, SPLIT_SHARE_PRICE)) { - - PINFO("term is PR_PRICE"); - - AMOUNT_TERM ("gncEntry.value / gncEntry.amount", - "gncHelperPrVal(gncEntry)", - "gncHelperPrAmt(gncEntry)"); - } else { - - PINFO ("Unknown NUMERIC: %s", (char*)(path->data)); - } - - } else if (!safe_strcmp (pd->type_name, QUERYCORE_DATE)) { - query_date_t pdata = (query_date_t) pd; - char * field = NULL; - const char * op = NULL; + PINFO("term is PR_PRICE"); - PINFO("term is PR_DATE"); - if (invert) - sq->pq = stpcpy (sq->pq, "NOT ("); + AMOUNT_TERM("gncEntry.value / gncEntry.amount", + "gncHelperPrVal(gncEntry)", + "gncHelperPrAmt(gncEntry)"); + } else { - if (!safe_strcmp (path->data, SPLIT_TRANS) && - !safe_strcmp (path->next->data, TRANS_DATE_POSTED)) - field = "gncTransaction.date_posted"; + PINFO("Unknown NUMERIC: %s", (char *)(path->data)); + } - op = compare_op_name (pd->how); + } else if (!safe_strcmp(pd->type_name, QUERYCORE_DATE)) { + query_date_t pdata = (query_date_t) pd; + char *field = NULL; + const char *op = NULL; - if (field) { - sq->pq = stpcpy (sq->pq, field); - sq->pq = stpcpy (sq->pq, op); - sq->pq = stpcpy (sq->pq, "'"); - sq->pq = gnc_timespec_to_iso8601_buff (pdata->date, sq->pq); - sq->pq = stpcpy(sq->pq, "' "); - } + PINFO("term is PR_DATE"); + if (invert) + sq->pq = stpcpy(sq->pq, "NOT ("); - if (invert) - sq->pq = stpcpy (sq->pq, ") "); + if (!safe_strcmp(path->data, SPLIT_TRANS) && + !safe_strcmp(path->next->data, TRANS_DATE_POSTED)) + field = "gncTransaction.date_posted"; - } else if (!safe_strcmp (pd->type_name, QUERYCORE_CHAR)) { - query_char_t pdata = (query_char_t) pd; - int got_one = 0; + op = compare_op_name(pd->how); - if (!safe_strcmp (path->data, SPLIT_RECONCILE)) { + if (field) { + sq->pq = stpcpy(sq->pq, field); + sq->pq = stpcpy(sq->pq, op); + sq->pq = stpcpy(sq->pq, "'"); + sq->pq = + gnc_timespec_to_iso8601_buff(pdata->date, sq->pq); + sq->pq = stpcpy(sq->pq, "' "); + } - PINFO("term is CHAR (Reconcile)"); + if (invert) + sq->pq = stpcpy(sq->pq, ") "); - if (invert) - sq->pq = stpcpy (sq->pq, "NOT ("); - - if (pdata->options == CHAR_MATCH_NONE) - sq->pq = stpcpy (sq->pq, "NOT "); + } else if (!safe_strcmp(pd->type_name, QUERYCORE_CHAR)) { + query_char_t pdata = (query_char_t) pd; + int got_one = 0; - sq->pq = stpcpy (sq->pq, "("); - - while (pdata->char_list[got_one] != '\0') - CLR_TERM (pdata->char_list[got_one]); - - sq->pq = stpcpy (sq->pq, ") "); + if (!safe_strcmp(path->data, SPLIT_RECONCILE)) { - if (invert) - sq->pq = stpcpy (sq->pq, ") "); + PINFO("term is CHAR (Reconcile)"); - } else - PINFO ("Unknown CHAR type, %s", (char*)(path->data)); + if (invert) + sq->pq = stpcpy(sq->pq, "NOT ("); - } else if (!safe_strcmp (pd->type_name, QUERYCORE_KVP)) { - query_kvp_t pdata = (query_kvp_t)pd; + if (pdata->options == CHAR_MATCH_NONE) + sq->pq = stpcpy(sq->pq, "NOT "); - PINFO("term is a KVP"); - sqlQuery_kvp_build (sq, path, pd->how, invert, pdata); + sq->pq = stpcpy(sq->pq, "("); - } else { - PINFO ("Unsupported Query Type: %s", pd->type_name); - } - } + while (pdata->char_list[got_one] != '\0') + CLR_TERM(pdata->char_list[got_one]); - /* if there were and terms, close the brace */ - if (il->data) sq->pq = stpcpy(sq->pq, ")"); - } + sq->pq = stpcpy(sq->pq, ") "); - sq->pq = stpcpy(sq->pq, ") "); + if (invert) + sq->pq = stpcpy(sq->pq, ") "); - /* ---------------------------------------------------- */ - /* implement sorting order as well; bad sorts lead to bad data - * if the limit is set to a finite number of rows. - */ + } else + PINFO("Unknown CHAR type, %s", (char *)(path->data)); - if (gncQuerySortGetParamPath (s1) != NULL) - { - sq->pq = stpcpy(sq->pq, "ORDER BY "); - sq->pq = sql_sort_order (sq->pq, s1); + } else if (!safe_strcmp(pd->type_name, QUERYCORE_KVP)) { + query_kvp_t pdata = (query_kvp_t) pd; - if (gncQuerySortGetParamPath (s2) != NULL) - { - sq->pq = stpcpy(sq->pq, ", "); - sq->pq = sql_sort_order (sq->pq, s2); + PINFO("term is a KVP"); + sqlQuery_kvp_build(sq, path, pd->how, invert, pdata); - if (gncQuerySortGetParamPath (s3) != NULL) - { + } else { + PINFO("Unsupported Query Type: %s", pd->type_name); + } + } + + /* if there were and terms, close the brace */ + if (il->data) { + sq->pq = stpcpy(sq->pq, ")"); + } else { + more_and = 0; + } + } + + sq->pq = stpcpy(sq->pq, ") "); + + /* ---------------------------------------------------- */ + /* implement sorting order as well; bad sorts lead to bad data + * if the limit is set to a finite number of rows. + */ + + if (gncQuerySortGetParamPath(s1) != NULL) { + sq->pq = stpcpy(sq->pq, "ORDER BY "); + sq->pq = sql_sort_order(sq->pq, s1); + + if (gncQuerySortGetParamPath(s2) != NULL) { sq->pq = stpcpy(sq->pq, ", "); - sq->pq = sql_sort_order (sq->pq, s3); - } - } - } + sq->pq = sql_sort_order(sq->pq, s2); - /* ---------------------------------------------------- */ - /* limit the query result to a finite number of rows */ - max_rows = gncQueryGetMaxResults (q); - if (0 <= max_rows) - { - sq->pq = stpcpy(sq->pq, " LIMIT "); - sq->pq += snprintf (sq->pq, 30, "%d", max_rows); - } + if (gncQuerySortGetParamPath(s3) != NULL) { + sq->pq = stpcpy(sq->pq, ", "); + sq->pq = sql_sort_order(sq->pq, s3); + } + } + } - sq->pq = stpcpy(sq->pq, ";"); - - return sq->q_base; + /* ---------------------------------------------------- */ + /* limit the query result to a finite number of rows */ + max_rows = gncQueryGetMaxResults(q); + if (0 <= max_rows) { + sq->pq = stpcpy(sq->pq, " LIMIT "); + sq->pq += snprintf(sq->pq, 30, "%d", max_rows); + } + + sq->pq = stpcpy(sq->pq, ";"); + + return sq->q_base; } diff --git a/src/backend/postgres/kvp-sql.c b/src/backend/postgres/kvp-sql.c index c62d1a3d72..6ae52a2227 100644 --- a/src/backend/postgres/kvp-sql.c +++ b/src/backend/postgres/kvp-sql.c @@ -286,7 +286,7 @@ store_cb (const char *key, kvp_value *val, gpointer p) case KVP_TYPE_TIMESPEC: { PINFO ("path=%s type=timespec", cb_data->path); - cb_data->stype = "timespec"; + cb_data->stype = "time"; cb_data->u.ts = kvp_value_get_timespec (val); pgendPutOneKVPtimespecOnly (be, cb_data); } diff --git a/src/backend/postgres/putil.h b/src/backend/postgres/putil.h index d92ff63cc3..444fe3d4eb 100644 --- a/src/backend/postgres/putil.h +++ b/src/backend/postgres/putil.h @@ -108,30 +108,30 @@ int finishQuery(PGBackend *be); * discarded (only error conditions are checked for). */ -#define FINISH_QUERY(conn) \ -{ \ - int i=0; \ - PGresult *result; \ - /* complete/commit the transaction, check the status */ \ - do { \ +#define FINISH_QUERY(conn) \ +{ \ + int i=0; \ + PGresult *result; \ + /* complete/commit the transaction, check the status */ \ + do { \ gchar *msg = NULL; \ - 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); \ - xaccBackendSetMessage (&be->be, msg); \ + ExecStatusType status; \ + result = PQgetResult((conn)); \ + if (!result) break; \ + PINFO ("clearing result %d", i); \ + status = PQresultStatus(result); \ + if (PGRES_COMMAND_OK != status) { \ + msg = PQresultErrorMessage(result); \ + PERR("finish query failed:\n\t%s", msg); \ + PQclear(result); \ + xaccBackendSetMessage (&be->be, msg); \ xaccBackendSetError (&be->be, ERR_BACKEND_SERVER_ERR); \ - break; \ - } \ - PQclear(result); \ - i++; \ - } while (result); \ -} + break; \ + } \ + PQclear(result); \ + i++; \ + } while (result); \ +} \ /* --------------------------------------------------------------- */ /* The GET_RESULTS macro grabs the result of an pgSQL query off the diff --git a/src/backend/postgres/table-audit.sql b/src/backend/postgres/table-audit.sql index 9c33d59e6f..7a13775e3c 100644 --- a/src/backend/postgres/table-audit.sql +++ b/src/backend/postgres/table-audit.sql @@ -1,174 +1,174 @@ --- --- FILE: --- table-audit.sql --- --- FUNCTION: --- Define the audit trail tables --- Note that these tables must be kept manually in sync with those --- in table-create.sql --- --- HISTORY: --- Copyright (C) 2000, 2001 Linas Vepstas --- --- The change field may be 'a' -- add, 'd' -- delete/drop, 'm' -- modify --- --- The objtype field may be 'a' -- account, 'c' -- commodity, 'e' -- entry, --- 'k' -- kvp, 'p' -- price, 't' -- transaction --- --- The objtype is the class type of the child class. --- --- Note that SQL is only partially object-oriented. Thus, we use the --- gncAuditTrail table to define the parent class; the children inherit --- from it. Since SQL doesn't tell the parent who the child was, we use --- the 'objtype' field to store the class type of the child. --- --- Thus, we should really enforce a constraint on this field: --- CREATE TABLE gncAccountTrail ( --- objtype CHAR NOT NULL CHECK (objtype == 'a') --- and so on. - -CREATE TABLE gncAuditTrail ( - sessionGuid CHAR(32) NOT NULL, -- who changed it - date_changed TIMESTAMP, -- when they changed it - change CHAR NOT NULL, - objtype CHAR NOT NULL -); - --- would love to inherit, but can't because this wrecks the primary key - -CREATE TABLE gncAccountTrail ( - accountGuid CHAR(32) NOT NULL, -- override, not a primary key anymore - parentGuid CHAR(32) NOT NULL, - bookGuid CHAR(32) NOT NULL, - accountName TEXT NOT NULL CHECK (accountName <> ''), - accountCode TEXT, - description TEXT, - type TEXT NOT NULL, - commodity TEXT NOT NULL CHECK (commodity <>''), - version INT4 NOT NULL, - iguid INT4 DEFAULT 0 -) INHERITS (gncAuditTrail); - -CREATE INDEX gncAccountTrail_account_idx ON gncAccountTrail (accountGuid); - -CREATE TABLE gncBookTrail ( - bookGuid CHAR(32) NOT NULL, - book_open CHAR DEFAULT 'n', - version INT4 NOT NULL, - iguid INT4 DEFAULT 0 -) INHERITS (gncAuditTrail); - -CREATE INDEX gncBookTrail_book_idx ON gncBookTrail (bookGuid); - -CREATE TABLE gncCommodityTrail ( - commodity TEXT NOT NULL, -- override, not a primary key anymore - fullname TEXT, - namespace TEXT NOT NULL, - mnemonic TEXT NOT NULL, - code TEXT, - fraction INT DEFAULT '100' -) INHERITS (gncAuditTrail); - -CREATE INDEX gncCommodityTrail_commodity_idx ON gncCommodityTrail (commodity); - -CREATE TABLE gncEntryTrail ( - entryGuid CHAR(32) NOT NULL, -- override, not a primary key anymore - accountGuid CHAR(32) NOT NULL, - transGuid CHAR(32) NOT NULL, - memo TEXT, - action TEXT, - reconciled CHAR DEFAULT 'n', - date_reconciled TIMESTAMP, - amount INT8 DEFAULT '0', - value INT8 DEFAULT '0', - iguid INT4 DEFAULT 0 -) INHERITS (gncAuditTrail); - -CREATE INDEX gncEntryTrail_entry_idx ON gncEntryTrail (entryGuid); - -CREATE TABLE gncPriceTrail ( - priceGuid CHAR(32) NOT NULL, -- override, not a primary key anymore - commodity TEXT NOT NULL CHECK (commodity <>''), - currency TEXT NOT NULL CHECK (commodity <>''), - time TIMESTAMP, - source TEXT, - type TEXT, - valueNum INT8 DEFAULT '0', - valueDenom INT4 DEFAULT '100', - version INT4 NOT NULL, - bookGuid CHAR(32) NOT NULL -) INHERITS (gncAuditTrail); - -CREATE INDEX gncPriceTrail_price_idx ON gncPriceTrail (priceGuid); - -CREATE TABLE gncTransactionTrail ( - transGuid CHAR(32) NOT NULL, -- override, not a primary key anymore - last_modified TIMESTAMP DEFAULT 'NOW', - date_entered TIMESTAMP, - date_posted TIMESTAMP, - num TEXT, - description TEXT, - currency TEXT NOT NULL CHECK (currency <> ''), - version INT4 NOT NULL, - iguid INT4 DEFAULT 0 -) INHERITS (gncAuditTrail); - -CREATE INDEX gncTransactionTrail_trans_idx ON gncTransactionTrail (transGuid); - -CREATE TABLE gncKVPvalueTrail ( - iguid INT4, - ipath INT4, - type char(4) -) INHERITS (gncAuditTrail); - -CREATE TABLE gncKVPvalue_int64Trail ( - iguid INT4, - ipath INT4, - type char(4), - data INT8 -) INHERITS (gncAuditTrail); - -CREATE TABLE gncKVPvalue_dblTrail ( - iguid INT4, - ipath INT4, - type char(4), - data FLOAT8 -) INHERITS (gncAuditTrail); - -CREATE TABLE gncKVPvalue_numericTrail ( - iguid INT4, - ipath INT4, - type char(4), - num INT8, - denom INT8 -) INHERITS (gncAuditTrail); - -CREATE TABLE gncKVPvalue_strTrail ( - iguid INT4, - ipath INT4, - type char(4), - data TEXT -) INHERITS (gncAuditTrail); - -CREATE TABLE gncKVPvalue_guidTrail ( - iguid INT4, - ipath INT4, - type char(4), - data CHAR(32) -) INHERITS (gncAuditTrail); - -CREATE TABLE gncKVPvalue_timespecTrail ( - iguid INT4, - ipath INT4, - type char(4), - data TIMESTAMP -) INHERITS (gncAuditTrail); - -CREATE TABLE gncKVPvalue_listTrail ( - iguid INT4, - ipath INT4, - type char(4), - data TEXT[] -) INHERITS (gncAuditTrail); - --- end of file +"-- \n" +"-- FILE: \n" +"-- table-audit.sql \n" +"-- \n" +"-- FUNCTION: \n" +"-- Define the audit trail tables \n" +"-- Note that these tables must be kept manually in sync with those \n" +"-- in table-create.sql \n" +"-- \n" +"-- HISTORY: \n" +"-- Copyright (C) 2000, 2001 Linas Vepstas \n" +"-- \n" +"-- The change field may be 'a' -- add, 'd' -- delete/drop, 'm' -- modify \n" +"-- \n" +"-- The objtype field may be 'a' -- account, 'c' -- commodity, 'e' -- entry, \n" +"-- 'k' -- kvp, 'p' -- price, 't' -- transaction \n" +"-- \n" +"-- The objtype is the class type of the child class. \n" +"-- \n" +"-- Note that SQL is only partially object-oriented. Thus, we use the \n" +"-- gncAuditTrail table to define the parent class; the children inherit \n" +"-- from it. Since SQL doesn't tell the parent who the child was, we use \n" +"-- the 'objtype' field to store the class type of the child. \n" +"-- \n" +"-- Thus, we should really enforce a constraint on this field: \n" +"-- CREATE TABLE gncAccountTrail ( \n" +"-- objtype CHAR NOT NULL CHECK (objtype == 'a') \n" +"-- and so on. \n" +" \n" +"CREATE TABLE gncAuditTrail ( \n" +" sessionGuid CHAR(32) NOT NULL, -- who changed it \n" +" date_changed TIMESTAMP, -- when they changed it \n" +" change CHAR NOT NULL, \n" +" objtype CHAR NOT NULL \n" +"); \n" +" \n" +"-- would love to inherit, but can't because this wrecks the primary key \n" +" \n" +"CREATE TABLE gncAccountTrail ( \n" +" accountGuid CHAR(32) NOT NULL, -- override, not a primary key anymore \n" +" parentGuid CHAR(32) NOT NULL, \n" +" bookGuid CHAR(32) NOT NULL, \n" +" accountName TEXT NOT NULL CHECK (accountName <> ''), \n" +" accountCode TEXT, \n" +" description TEXT, \n" +" type TEXT NOT NULL, \n" +" commodity TEXT NOT NULL CHECK (commodity <>''), \n" +" version INT4 NOT NULL, \n" +" iguid INT4 DEFAULT 0 \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE INDEX gncAccountTrail_account_idx ON gncAccountTrail (accountGuid); \n" +" \n" +"CREATE TABLE gncBookTrail ( \n" +" bookGuid CHAR(32) NOT NULL, \n" +" book_open CHAR DEFAULT 'n', \n" +" version INT4 NOT NULL, \n" +" iguid INT4 DEFAULT 0 \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE INDEX gncBookTrail_book_idx ON gncBookTrail (bookGuid); \n" +" \n" +"CREATE TABLE gncCommodityTrail ( \n" +" commodity TEXT NOT NULL, -- override, not a primary key anymore \n" +" fullname TEXT, \n" +" namespace TEXT NOT NULL, \n" +" mnemonic TEXT NOT NULL, \n" +" code TEXT, \n" +" fraction INT DEFAULT '100' \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE INDEX gncCommodityTrail_commodity_idx ON gncCommodityTrail (commodity); \n" +" \n" +"CREATE TABLE gncEntryTrail ( \n" +" entryGuid CHAR(32) NOT NULL, -- override, not a primary key anymore \n" +" accountGuid CHAR(32) NOT NULL, \n" +" transGuid CHAR(32) NOT NULL, \n" +" memo TEXT, \n" +" action TEXT, \n" +" reconciled CHAR DEFAULT 'n', \n" +" date_reconciled TIMESTAMP, \n" +" amount INT8 DEFAULT '0', \n" +" value INT8 DEFAULT '0', \n" +" iguid INT4 DEFAULT 0 \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE INDEX gncEntryTrail_entry_idx ON gncEntryTrail (entryGuid); \n" +" \n" +"CREATE TABLE gncPriceTrail ( \n" +" priceGuid CHAR(32) NOT NULL, -- override, not a primary key anymore \n" +" commodity TEXT NOT NULL CHECK (commodity <>''), \n" +" currency TEXT NOT NULL CHECK (commodity <>''), \n" +" time TIMESTAMP, \n" +" source TEXT, \n" +" type TEXT, \n" +" valueNum INT8 DEFAULT '0', \n" +" valueDenom INT4 DEFAULT '100', \n" +" version INT4 NOT NULL, \n" +" bookGuid CHAR(32) NOT NULL \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE INDEX gncPriceTrail_price_idx ON gncPriceTrail (priceGuid); \n" +" \n" +"CREATE TABLE gncTransactionTrail ( \n" +" transGuid CHAR(32) NOT NULL, -- override, not a primary key anymore \n" +" last_modified TIMESTAMP DEFAULT 'NOW', \n" +" date_entered TIMESTAMP, \n" +" date_posted TIMESTAMP, \n" +" num TEXT, \n" +" description TEXT, \n" +" currency TEXT NOT NULL CHECK (currency <> ''), \n" +" version INT4 NOT NULL, \n" +" iguid INT4 DEFAULT 0 \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE INDEX gncTransactionTrail_trans_idx ON gncTransactionTrail (transGuid); \n" +" \n" +"CREATE TABLE gncKVPvalueTrail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4) \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE TABLE gncKVPvalue_int64Trail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" data INT8 \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE TABLE gncKVPvalue_dblTrail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" data FLOAT8 \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE TABLE gncKVPvalue_numericTrail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" num INT8, \n" +" denom INT8 \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE TABLE gncKVPvalue_strTrail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" data TEXT \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE TABLE gncKVPvalue_guidTrail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" data CHAR(32) \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE TABLE gncKVPvalue_timespecTrail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" data TIMESTAMP \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"CREATE TABLE gncKVPvalue_listTrail ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" data TEXT[] \n" +") INHERITS (gncAuditTrail); \n" +" \n" +"-- end of file"; diff --git a/src/backend/postgres/table-create.sql b/src/backend/postgres/table-create.sql index b916c0e075..d1cdbb63d7 100644 --- a/src/backend/postgres/table-create.sql +++ b/src/backend/postgres/table-create.sql @@ -1,246 +1,246 @@ --- --- FILE: --- table-create.sql --- --- FUNCTION: --- Define the tables needed to initialize a new GnuCash database --- --- These tables roughly mirror the c structs in --- TransactionP.h, AccountP.h, gnc-commodity.c --- Please refer to the C files to get the right level of documentation. --- --- If these tables are changed or added to, a correspionding --- audit-trail table (in table-audit.sql) must be updated as well. --- --- These tables are specifically designed for the --- postgres database server, but are hopefull relatively portable. --- --- These tables are hand-built, but maybe they should be --- auto-built with the m4 macros ... --- --- HISTORY: --- Copyright (C) 2000, 2001 Linas Vepstas --- - -CREATE TABLE gncVersion ( - major INT NOT NULL, - minor INT NOT NULL, - rev INT DEFAULT '0', - name TEXT UNIQUE NOT NULL CHECK (name <> ''), - date TIMESTAMP DEFAULT 'NOW' -); - --- Commodity structure --- Store currency, security types. Namespace includes --- ISO4217 for currencies, NASDAQ, AMEX, NYSE, EUREX for --- stocks. See the C documentation for details. - -CREATE TABLE gncCommodity ( - commodity TEXT PRIMARY KEY, - fullname TEXT, - namespace TEXT NOT NULL, - mnemonic TEXT NOT NULL, - code TEXT, - fraction INT DEFAULT '100' -); - -CREATE TABLE gncBook ( - bookGuid CHAR(32) PRIMARY KEY, - book_open CHAR DEFAULT 'n', - version INT4 NOT NULL, - iguid INT4 DEFAULT 0 -); - --- Account structure -- parentGUID points to parent account --- guid. There is no supports for Groups in this schema. --- (there seems to be no strong need to have groups in the DB.) - -CREATE TABLE gncAccount ( - accountGuid CHAR(32) PRIMARY KEY, - parentGuid CHAR(32) NOT NULL, - bookGuid CHAR(32) NOT NULL, - accountName TEXT NOT NULL CHECK (accountName <> ''), - accountCode TEXT, - description TEXT, - type TEXT NOT NULL, - commodity TEXT NOT NULL CHECK (commodity <>''), - version INT4 NOT NULL, - iguid INT4 DEFAULT 0 -); - --- CREATE INDEX gncAccount_pg_idx ON gncAccount (parentGuid); - -CREATE TABLE gncTransaction ( - transGuid CHAR(32) PRIMARY KEY, - last_modified TIMESTAMP DEFAULT 'NOW', - date_entered TIMESTAMP, - date_posted TIMESTAMP, - num TEXT, - description TEXT, - currency TEXT NOT NULL CHECK (currency <> ''), - version INT4 NOT NULL, - iguid INT4 DEFAULT 0 -); - -CREATE INDEX gncTransaction_posted_idx ON gncTransaction (date_posted); - --- a gncEntry is what we call 'Split' elsewhere in the engine --- Here, we call it a 'journal entry' - -CREATE TABLE gncEntry ( - entryGuid CHAR(32) PRIMARY KEY, - accountGuid CHAR(32) NOT NULL, - transGuid CHAR(32) NOT NULL, - memo TEXT, - action TEXT, - reconciled CHAR DEFAULT 'n', - date_reconciled TIMESTAMP, - amount INT8 DEFAULT '0', - value INT8 DEFAULT '0', - iguid INT4 DEFAULT 0 -); - -CREATE INDEX gncEntry_acc_idx ON gncEntry (accountGuid); -CREATE INDEX gncEntry_trn_idx ON gncEntry (transGuid); - --- The checkpoint table provides balance information --- The balance is provided in the indicated currency; --- this allows the potential of maintaining balance information --- in multiple currencies. --- (e.g. report stock account balances in shares of stock, --- and in dollars) --- the 'type' field indicates what type of balance this is --- (simple, FIFO, LIFO, or other accounting method) - -CREATE TABLE gncCheckpoint ( - accountGuid CHAR(32) NOT NULL, - date_start TIMESTAMP NOT NULL, - date_end TIMESTAMP NOT NULL, - commodity TEXT NOT NULL CHECK (commodity <>''), - type TEXT DEFAULT 'simple', - balance INT8 DEFAULT '0', - cleared_balance INT8 DEFAULT '0', - reconciled_balance INT8 DEFAULT '0', - - PRIMARY KEY (accountGuid, date_start, commodity) -); - --- The price table stores the price of 'commodity' valued --- in units of 'currency' -CREATE TABLE gncPrice ( - priceGuid CHAR(32) PRIMARY KEY, - commodity TEXT NOT NULL CHECK (commodity <>''), - currency TEXT NOT NULL CHECK (commodity <>''), - time TIMESTAMP, - source TEXT, - type TEXT, - valueNum INT8 DEFAULT '0', - valueDenom INT4 DEFAULT '100', - version INT4 NOT NULL, - bookGuid CHAR(32) NOT NULL -); - - --- The session directory serves several purposes. First and formost, --- it notes the database access type. There are three modes: --- o 'Single User' -- Only one user can have access to the database --- at a time. --- o 'Multi-User Polled' -- multiple users --- o 'Muilti-User Event Driven' --- See Design.txt for more info. --- Note that a client can lie about its identity, sign-on time, etc. --- so these records aren't really sufficient for a true audit. - -CREATE TABLE gncSession ( - sessionGuid CHAR(32) PRIMARY KEY, - session_mode CHAR(16) NOT NULL, - hostname TEXT, - login_name TEXT, - gecos TEXT, - time_on TIMESTAMP NOT NULL, - time_off TIMESTAMP NOT NULL DEFAULT 'INFINITY' -); - - --- The kvp path-cache replaces a long path name with a single unique --- number. The guid-cache replaces a 32-byte guid with a shorter --- 4-byte identifier. The KVP Value table stores the actual values. - -CREATE TABLE gncPathCache ( - ipath SERIAL PRIMARY KEY, - path TEXT -); - -CREATE SEQUENCE gnc_iguid_seq START 1; - -CREATE TABLE gncKVPvalue ( - iguid INT4, - ipath INT4, - type char(4), - - PRIMARY KEY (iguid, ipath) -); - --- CREATE INDEX gncKVPvalue_iguid_idx ON gncKVPvalue (iguid); - --- Add primary keys to each kvp table ... because key inheritance --- is ambiguously defined and thus not implemented in postgres. --- Note, however, adding these keys degrades performance by 20% --- (even after a vacuum analyze), and adding indexes degrades --- an additional 15% !! I find this result surprising, so I --- simply leave these commented out ... (as of postgres 7.1.2) --- Note, indexex on the main, non-inherited tables *are* important --- for ensuring good performance, so this effect seems to be related --- to inheritance - -CREATE TABLE gncKVPvalue_int64 ( - data INT8 --- PRIMARY KEY (iguid, ipath) -) INHERITS (gncKVPvalue); - --- CREATE INDEX gncKVPvalue_int64_iguid_idx ON gncKVPvalue_int64 (iguid); - -CREATE TABLE gncKVPvalue_dbl ( - data FLOAT8 --- PRIMARY KEY (iguid, ipath) -) INHERITS (gncKVPvalue); - --- CREATE INDEX gncKVPvalue_dbl_iguid_idx ON gncKVPvalue_dbl (iguid); - -CREATE TABLE gncKVPvalue_numeric ( - num INT8, - denom INT8 --- PRIMARY KEY (iguid, ipath) -) INHERITS (gncKVPvalue); - --- CREATE INDEX gncKVPvalue_numeric_iguid_idx ON gncKVPvalue_numeric (iguid); - -CREATE TABLE gncKVPvalue_str ( - data TEXT --- PRIMARY KEY (iguid, ipath) -) INHERITS (gncKVPvalue); - --- CREATE INDEX gncKVPvalue_str_iguid_idx ON gncKVPvalue_str (iguid); - -CREATE TABLE gncKVPvalue_guid ( - data CHAR(32) --- PRIMARY KEY (iguid, ipath) -) INHERITS (gncKVPvalue); - --- CREATE INDEX gncKVPvalue_guid_iguid_idx ON gncKVPvalue_guid (iguid); - -CREATE TABLE gncKVPvalue_timespec ( - data TIMESTAMP --- PRIMARY KEY (iguid, ipath) -) INHERITS (gncKVPvalue); - --- CREATE INDEX gncKVPvalue_timespec_iguid_idx ON gncKVPvalue_timespec (iguid); - -CREATE TABLE gncKVPvalue_list ( - data TEXT[] --- PRIMARY KEY (iguid, ipath) -) INHERITS (gncKVPvalue); - --- CREATE INDEX gncKVPvalue_list_iguid_idx ON gncKVPvalue_list (iguid); - --- end of file +"-- \n" +"-- FILE: \n" +"-- table-create.sql \n" +"-- \n" +"-- FUNCTION: \n" +"-- Define the tables needed to initialize a new GnuCash database \n" +"-- \n" +"-- These tables roughly mirror the c structs in \n" +"-- TransactionP.h, AccountP.h, gnc-commodity.c \n" +"-- Please refer to the C files to get the right level of documentation. \n" +"-- \n" +"-- If these tables are changed or added to, a correspionding \n" +"-- audit-trail table (in table-audit.sql) must be updated as well. \n" +"-- \n" +"-- These tables are specifically designed for the \n" +"-- postgres database server, but are hopefull relatively portable. \n" +"-- \n" +"-- These tables are hand-built, but maybe they should be \n" +"-- auto-built with the m4 macros ... \n" +"-- \n" +"-- HISTORY: \n" +"-- Copyright (C) 2000, 2001 Linas Vepstas \n" +"-- \n" +" \n" +"CREATE TABLE gncVersion ( \n" +" major INT NOT NULL, \n" +" minor INT NOT NULL, \n" +" rev INT DEFAULT '0', \n" +" name TEXT UNIQUE NOT NULL CHECK (name <> ''), \n" +" date TIMESTAMP DEFAULT 'NOW' \n" +"); \n" +" \n" +"-- Commodity structure \n" +"-- Store currency, security types. Namespace includes \n" +"-- ISO4217 for currencies, NASDAQ, AMEX, NYSE, EUREX for \n" +"-- stocks. See the C documentation for details. \n" +" \n" +"CREATE TABLE gncCommodity ( \n" +" commodity TEXT PRIMARY KEY, \n" +" fullname TEXT, \n" +" namespace TEXT NOT NULL, \n" +" mnemonic TEXT NOT NULL, \n" +" code TEXT, \n" +" fraction INT DEFAULT '100' \n" +"); \n" +" \n" +"CREATE TABLE gncBook ( \n" +" bookGuid CHAR(32) PRIMARY KEY, \n" +" book_open CHAR DEFAULT 'n', \n" +" version INT4 NOT NULL, \n" +" iguid INT4 DEFAULT 0 \n" +"); \n" +" \n" +"-- Account structure -- parentGUID points to parent account \n" +"-- guid. There is no supports for Groups in this schema. \n" +"-- (there seems to be no strong need to have groups in the DB.) \n" +" \n" +"CREATE TABLE gncAccount ( \n" +" accountGuid CHAR(32) PRIMARY KEY, \n" +" parentGuid CHAR(32) NOT NULL, \n" +" bookGuid CHAR(32) NOT NULL, \n" +" accountName TEXT NOT NULL CHECK (accountName <> ''), \n" +" accountCode TEXT, \n" +" description TEXT, \n" +" type TEXT NOT NULL, \n" +" commodity TEXT NOT NULL CHECK (commodity <>''), \n" +" version INT4 NOT NULL, \n" +" iguid INT4 DEFAULT 0 \n" +"); \n" +" \n" +"-- CREATE INDEX gncAccount_pg_idx ON gncAccount (parentGuid); \n" +" \n" +"CREATE TABLE gncTransaction ( \n" +" transGuid CHAR(32) PRIMARY KEY, \n" +" last_modified TIMESTAMP DEFAULT 'NOW', \n" +" date_entered TIMESTAMP, \n" +" date_posted TIMESTAMP, \n" +" num TEXT, \n" +" description TEXT, \n" +" currency TEXT NOT NULL CHECK (currency <> ''), \n" +" version INT4 NOT NULL, \n" +" iguid INT4 DEFAULT 0 \n" +"); \n" +" \n" +"CREATE INDEX gncTransaction_posted_idx ON gncTransaction (date_posted); \n" +" \n" +"-- a gncEntry is what we call 'Split' elsewhere in the engine \n" +"-- Here, we call it a 'journal entry' \n" +" \n" +"CREATE TABLE gncEntry ( \n" +" entryGuid CHAR(32) PRIMARY KEY, \n" +" accountGuid CHAR(32) NOT NULL, \n" +" transGuid CHAR(32) NOT NULL, \n" +" memo TEXT, \n" +" action TEXT, \n" +" reconciled CHAR DEFAULT 'n', \n" +" date_reconciled TIMESTAMP, \n" +" amount INT8 DEFAULT '0', \n" +" value INT8 DEFAULT '0', \n" +" iguid INT4 DEFAULT 0 \n" +"); \n" +" \n" +"CREATE INDEX gncEntry_acc_idx ON gncEntry (accountGuid); \n" +"CREATE INDEX gncEntry_trn_idx ON gncEntry (transGuid); \n" +" \n" +"-- The checkpoint table provides balance information \n" +"-- The balance is provided in the indicated currency; \n" +"-- this allows the potential of maintaining balance information \n" +"-- in multiple currencies. \n" +"-- (e.g. report stock account balances in shares of stock, \n" +"-- and in dollars) \n" +"-- the 'type' field indicates what type of balance this is \n" +"-- (simple, FIFO, LIFO, or other accounting method) \n" +" \n" +"CREATE TABLE gncCheckpoint ( \n" +" accountGuid CHAR(32) NOT NULL, \n" +" date_start TIMESTAMP NOT NULL, \n" +" date_end TIMESTAMP NOT NULL, \n" +" commodity TEXT NOT NULL CHECK (commodity <>''), \n" +" type TEXT DEFAULT 'simple', \n" +" balance INT8 DEFAULT '0', \n" +" cleared_balance INT8 DEFAULT '0', \n" +" reconciled_balance INT8 DEFAULT '0', \n" +" \n" +" PRIMARY KEY (accountGuid, date_start, commodity) \n" +"); \n" +" \n" +"-- The price table stores the price of 'commodity' valued \n" +"-- in units of 'currency' \n" +"CREATE TABLE gncPrice ( \n" +" priceGuid CHAR(32) PRIMARY KEY, \n" +" commodity TEXT NOT NULL CHECK (commodity <>''), \n" +" currency TEXT NOT NULL CHECK (commodity <>''), \n" +" time TIMESTAMP, \n" +" source TEXT, \n" +" type TEXT, \n" +" valueNum INT8 DEFAULT '0', \n" +" valueDenom INT4 DEFAULT '100', \n" +" version INT4 NOT NULL, \n" +" bookGuid CHAR(32) NOT NULL \n" +"); \n" +" \n" +" \n" +"-- The session directory serves several purposes. First and formost, \n" +"-- it notes the database access type. There are three modes: \n" +"-- o 'Single User' -- Only one user can have access to the database \n" +"-- at a time. \n" +"-- o 'Multi-User Polled' -- multiple users \n" +"-- o 'Muilti-User Event Driven' \n" +"-- See Design.txt for more info. \n" +"-- Note that a client can lie about its identity, sign-on time, etc. \n" +"-- so these records aren't really sufficient for a true audit. \n" +" \n" +"CREATE TABLE gncSession ( \n" +" sessionGuid CHAR(32) PRIMARY KEY, \n" +" session_mode CHAR(16) NOT NULL, \n" +" hostname TEXT, \n" +" login_name TEXT, \n" +" gecos TEXT, \n" +" time_on TIMESTAMP NOT NULL, \n" +" time_off TIMESTAMP NOT NULL DEFAULT 'INFINITY' \n" +"); \n" +" \n" +" \n" +"-- The kvp path-cache replaces a long path name with a single unique \n" +"-- number. The guid-cache replaces a 32-byte guid with a shorter \n" +"-- 4-byte identifier. The KVP Value table stores the actual values. \n" +" \n" +"CREATE TABLE gncPathCache ( \n" +" ipath SERIAL PRIMARY KEY, \n" +" path TEXT \n" +"); \n" +" \n" +"CREATE SEQUENCE gnc_iguid_seq START 1; \n" +" \n" +"CREATE TABLE gncKVPvalue ( \n" +" iguid INT4, \n" +" ipath INT4, \n" +" type char(4), \n" +" \n" +" PRIMARY KEY (iguid, ipath) \n" +"); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_iguid_idx ON gncKVPvalue (iguid); \n" +" \n" +"-- Add primary keys to each kvp table ... because key inheritance \n" +"-- is ambiguously defined and thus not implemented in postgres. \n" +"-- Note, however, adding these keys degrades performance by 20% \n" +"-- (even after a vacuum analyze), and adding indexes degrades \n" +"-- an additional 15% !! I find this result surprising, so I \n" +"-- simply leave these commented out ... (as of postgres 7.1.2) \n" +"-- Note, indexex on the main, non-inherited tables *are* important \n" +"-- for ensuring good performance, so this effect seems to be related \n" +"-- to inheritance \n" +" \n" +"CREATE TABLE gncKVPvalue_int64 ( \n" +" data INT8 \n" +"-- PRIMARY KEY (iguid, ipath) \n" +") INHERITS (gncKVPvalue); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_int64_iguid_idx ON gncKVPvalue_int64 (iguid); \n" +" \n" +"CREATE TABLE gncKVPvalue_dbl ( \n" +" data FLOAT8 \n" +"-- PRIMARY KEY (iguid, ipath) \n" +") INHERITS (gncKVPvalue); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_dbl_iguid_idx ON gncKVPvalue_dbl (iguid); \n" +" \n" +"CREATE TABLE gncKVPvalue_numeric ( \n" +" num INT8, \n" +" denom INT8 \n" +"-- PRIMARY KEY (iguid, ipath) \n" +") INHERITS (gncKVPvalue); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_numeric_iguid_idx ON gncKVPvalue_numeric (iguid); \n" +" \n" +"CREATE TABLE gncKVPvalue_str ( \n" +" data TEXT \n" +"-- PRIMARY KEY (iguid, ipath) \n" +") INHERITS (gncKVPvalue); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_str_iguid_idx ON gncKVPvalue_str (iguid); \n" +" \n" +"CREATE TABLE gncKVPvalue_guid ( \n" +" data CHAR(32) \n" +"-- PRIMARY KEY (iguid, ipath) \n" +") INHERITS (gncKVPvalue); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_guid_iguid_idx ON gncKVPvalue_guid (iguid); \n" +" \n" +"CREATE TABLE gncKVPvalue_timespec ( \n" +" data TIMESTAMP \n" +"-- PRIMARY KEY (iguid, ipath) \n" +") INHERITS (gncKVPvalue); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_timespec_iguid_idx ON gncKVPvalue_timespec (iguid); \n" +" \n" +"CREATE TABLE gncKVPvalue_list ( \n" +" data TEXT[] \n" +"-- PRIMARY KEY (iguid, ipath) \n" +") INHERITS (gncKVPvalue); \n" +" \n" +"-- CREATE INDEX gncKVPvalue_list_iguid_idx ON gncKVPvalue_list (iguid); \n" +" \n" +"-- end of file"; diff --git a/src/backend/postgres/table-drop.sql b/src/backend/postgres/table-drop.sql index 3ba44ad6f3..9bdd6b05b5 100644 --- a/src/backend/postgres/table-drop.sql +++ b/src/backend/postgres/table-drop.sql @@ -1,26 +1,26 @@ --- --- FILE: --- table-drop.sql --- --- FUNCTION: --- Drop the tables needed to run GnuCash database - - -DROP TABLE gncCommodity; -DROP TABLE gncAccount; -DROP TABLE gncTransaction; -DROP TABLE gncEntry; -DROP TABLE gncCheckpoint; -DROP TABLE gncSession; --- -DROP TABLE gncPathCache; -DROP TABLE gncGUIDCache; -DROP TABLE gncKVPvalue; -DROP TABLE gncKVPvalue_int64; -DROP TABLE gncKVPvalue_dbl; -DROP TABLE gncKVPvalue_numeric; -DROP TABLE gncKVPvalue_str; -DROP TABLE gncKVPvalue_guid; -DROP TABLE gncKVPvalue_timespec; -DROP TABLE gncKVPvalue_list; -DROP TABLE gncKVPvalue_frame; +"-- \n" +"-- FILE: \n" +"-- table-drop.sql \n" +"-- \n" +"-- FUNCTION: \n" +"-- Drop the tables needed to run GnuCash database \n" +" \n" +" \n" +"DROP TABLE gncCommodity; \n" +"DROP TABLE gncAccount; \n" +"DROP TABLE gncTransaction; \n" +"DROP TABLE gncEntry; \n" +"DROP TABLE gncCheckpoint; \n" +"DROP TABLE gncSession; \n" +"-- \n" +"DROP TABLE gncPathCache; \n" +"DROP TABLE gncGUIDCache; \n" +"DROP TABLE gncKVPvalue; \n" +"DROP TABLE gncKVPvalue_int64; \n" +"DROP TABLE gncKVPvalue_dbl; \n" +"DROP TABLE gncKVPvalue_numeric; \n" +"DROP TABLE gncKVPvalue_str; \n" +"DROP TABLE gncKVPvalue_guid; \n" +"DROP TABLE gncKVPvalue_timespec; \n" +"DROP TABLE gncKVPvalue_list; \n" +"DROP TABLE gncKVPvalue_frame;"; diff --git a/src/backend/postgres/table-version.sql b/src/backend/postgres/table-version.sql index 56f63ada99..12b1fbaf13 100644 --- a/src/backend/postgres/table-version.sql +++ b/src/backend/postgres/table-version.sql @@ -1,19 +1,19 @@ --- --- FILE: --- table-version.sql --- --- FUNCTION: --- Insert the latest version information into the gncVersion table. --- --- Inserting in the same query as creating the table does not --- work under Postgres 7.0 --- --- HISTORY: --- Copyright (C) 2001 Linux Developers Group --- - -INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,0,0,'Version Table'); -INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,1,1,'iGUID in Main Tables'); -INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,2,1,'Fix gncSubtotalReconedBalance'); -INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,3,1,'Add kvp_timespec tables'); -INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,4,1,'Add support for multiple books'); +"-- \n" +"-- FILE: \n" +"-- table-version.sql \n" +"-- \n" +"-- FUNCTION: \n" +"-- Insert the latest version information into the gncVersion table. \n" +"-- \n" +"-- Inserting in the same query as creating the table does not \n" +"-- work under Postgres 7.0 \n" +"-- \n" +"-- HISTORY: \n" +"-- Copyright (C) 2001 Linux Developers Group \n" +"-- \n" +" \n" +"INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,0,0,'Version Table'); \n" +"INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,1,1,'iGUID in Main Tables'); \n" +"INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,2,1,'Fix gncSubtotalReconedBalance'); \n" +"INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,3,1,'Add kvp_timespec tables'); \n" +"INSERT INTO gncVersion (major,minor,rev,name) VALUES (1,4,1,'Add support for multiple books');"; diff --git a/src/backend/postgres/test/db-control.sh b/src/backend/postgres/test/db-control.sh index 051eb3784a..a53979aaf8 100755 --- a/src/backend/postgres/test/db-control.sh +++ b/src/backend/postgres/test/db-control.sh @@ -3,30 +3,35 @@ EXIT_VALUE=0 PATH=/usr/lib/postgresql/bin:$PATH +PGCTL=`which pg_ctl 2> /dev/null` DB=$PWD/gnc_test_db SOCKDIR=$PWD/gnc_test_db_sock SOCKNUM=7777 + # I couldn't get this to work -- the shell seems to think "'-k" is an # argument after it finishes expanding ${PG_CTL}... # PG_CTL="pg_ctl -D "${DB}" -o '-k ${SOCKDIR} -p ${SOCKNUM}'" our_pg_ctl () { + if [ ${PGCTL}X == X ]; then + exit -1 + fi pg_ctl -D "${DB}" -o "-k ${SOCKDIR} -p ${SOCKNUM}" "$@"; } case $1 in create) - our_pg_ctl status | grep "pid" && our_pg_ctl stop && sleep 1 + our_pg_ctl status | grep "pid" && our_pg_ctl stop && sleep 1 || exit -1 rm -rf ${DB} rm -rf ${SOCKDIR} initdb ${DB} || EXIT_VALUE=1 mkdir ${SOCKDIR} || EXIT_VALUE=1 ;; destroy) - our_pg_ctl status | grep "pid" && our_pg_ctl stop && sleep 1 + our_pg_ctl status | grep "pid" && our_pg_ctl stop && sleep 1 || exit -1 rm -rf ${DB} rm -rf ${SOCKDIR} ;; @@ -34,13 +39,13 @@ case $1 in our_pg_ctl start ;; stop) - pg_ctl -D ${DB} -o '-k ${SOCKDIR} -p 7777' stop + pg_ctl -D ${DB} -o '-k ${SOCKDIR} -p 7777' stop || exit -1 ;; status) our_pg_ctl status ;; connect) - our_pg_ctl status | grep "not running" && our_pg_ctl start && sleep 1 + our_pg_ctl status | grep "not running" && our_pg_ctl start && sleep 1 || exit -1 psql -h ${SOCKDIR} -p ${SOCKNUM} $2 ;; *) diff --git a/src/backend/postgres/test/run-tests.sh b/src/backend/postgres/test/run-tests.sh index 7543f9d403..ce7b8afde2 100755 --- a/src/backend/postgres/test/run-tests.sh +++ b/src/backend/postgres/test/run-tests.sh @@ -2,17 +2,25 @@ EXIT_VALUE=0 -rm -f test_file_* -./db-control.sh create - -./db-control.sh start # .libs/test-db || EXIT_VALUE=1 # gdb .libs/test-db -./test-db || EXIT_VALUE=1 -./db-control.sh stop + +rm -f test_file_* +if ./db-control.sh create; then + ./db-control.sh start + ./test-db localhost 7777 || EXIT_VALUE=1 + ./db-control.sh stop + ./db-control.sh destroy +elif [ "${PGHOST}X" != "X" ]; then +# This expects the logged in user to have authority +# to create databases. + if [ "${PGPORT}X" == "X" ]; then + export PGPORT=5432 + fi + ./test-db $PGHOST $PGPORT || EXIT_VALUE=1 +fi if test $EXIT_VALUE != 0; then exit $EXIT_VALUE; fi -./db-control.sh destroy exit $EXIT_VALUE diff --git a/src/backend/postgres/test/test-db.c b/src/backend/postgres/test/test-db.c index be7c5ee832..45a57c5421 100644 --- a/src/backend/postgres/test/test-db.c +++ b/src/backend/postgres/test/test-db.c @@ -17,1069 +17,1121 @@ #include "gnc-module.h" #include "gnc-session-p.h" #include "gncquery.h" - +#include "QueryNew.h" #include "test-stuff.h" #include "test-engine-stuff.h" -/* Prevent compiler warnings. Uncomment if LEAVE/WARN/et al get used*/ -/* static short module = MOD_TEST; */ +static short module = MOD_TEST; + +struct _dbinfo { + char *host; + char *port; + char *dbname; + char *mode; + PGconn *conn; +}; + +typedef struct _dbinfo DbInfo; static void -save_xml_file (GNCSession *session, const char *filename_base) +save_xml_file(GNCSession * session, const char *filename_base) { - GNCBackendError io_err; - char cwd[1024]; - char *filename; + GNCBackendError io_err; + char cwd[1024]; + char *filename; - g_return_if_fail (session && filename_base); + g_return_if_fail(session && filename_base); - getcwd (cwd, sizeof (cwd)); + getcwd(cwd, sizeof(cwd)); - filename = g_strdup_printf ("file:/%s/%s", cwd, filename_base); + filename = g_strdup_printf("file:/%s/%s", cwd, filename_base); - gnc_session_begin (session, filename, FALSE, TRUE); + gnc_session_begin(session, filename, FALSE, TRUE); - io_err = gnc_session_get_error (session); - g_return_if_fail (io_err == ERR_BACKEND_NO_ERR); + io_err = gnc_session_get_error(session); + g_return_if_fail(io_err == ERR_BACKEND_NO_ERR); - gnc_session_save (session, NULL); - io_err = gnc_session_get_error (session); - g_return_if_fail (io_err == ERR_BACKEND_NO_ERR); + gnc_session_save(session, NULL); + io_err = gnc_session_get_error(session); + g_return_if_fail(io_err == ERR_BACKEND_NO_ERR); - gnc_session_end (session); - io_err = gnc_session_get_error (session); - g_return_if_fail (io_err == ERR_BACKEND_NO_ERR); + gnc_session_end(session); + io_err = gnc_session_get_error(session); + g_return_if_fail(io_err == ERR_BACKEND_NO_ERR); - g_free (filename); + g_free(filename); } static void -save_xml_files (GNCSession *session_1, GNCSession *session_2) +save_xml_files(GNCSession * session_1, GNCSession * session_2) { - g_return_if_fail (session_1 && session_2); + g_return_if_fail(session_1 && session_2); - save_xml_file (session_1, "test_file_1"); - save_xml_file (session_2, "test_file_2"); + save_xml_file(session_1, "test_file_1"); + save_xml_file(session_2, "test_file_2"); } static char * -db_file_url (const char *db_name, const char *mode) +db_file_url(DbInfo *dbinfo) { - char *db_socket_dir; + char *db_socket_dir; + gchar *url; - g_return_val_if_fail (db_name && mode, NULL); + g_return_val_if_fail(dbinfo->dbname && dbinfo->mode, NULL); - /* TEST_DB_SOCKET_DIR must be an absolute path */ - db_socket_dir = getenv("TEST_DB_SOCKET_DIR"); - if(! db_socket_dir) g_warning("Couldn't getenv TEST_DB_SOCKET_DIR"); - g_return_val_if_fail (db_socket_dir, NULL); - - return g_strdup_printf ("postgres://%s:7777/%s?mode=%s", - db_socket_dir, db_name, mode); + if ((!g_strncasecmp(dbinfo->port, "7777", 4)) && + (!g_strncasecmp(dbinfo->host, "localhost", 8))) { + /* TEST_DB_SOCKET_DIR must be an absolute path */ + db_socket_dir = getenv("TEST_DB_SOCKET_DIR"); + if (!db_socket_dir) + g_warning("Couldn't getenv TEST_DB_SOCKET_DIR"); + g_return_val_if_fail(db_socket_dir, NULL); + url = g_strdup_printf("postgres://%s:7777/%s?mode=%s", + db_socket_dir, dbinfo->dbname, dbinfo->mode); + } else { + url = g_strdup_printf("postgres://%s:%s/%s?mode=%s", + dbinfo->host, dbinfo->port, + dbinfo->dbname, dbinfo->mode); + } + return url; } static gboolean -save_db_file (GNCSession *session, const char *db_name, const char *mode) +save_db_file(GNCSession * session, DbInfo *dbinfo) { - GNCBackendError io_err; - char *filename; + GNCBackendError io_err; + char *filename; - g_return_val_if_fail (session && db_name && mode, FALSE); + g_return_val_if_fail(session && dbinfo->dbname && dbinfo->mode, FALSE); - filename = db_file_url (db_name, mode); + filename = db_file_url(dbinfo); + gnc_session_begin(session, filename, FALSE, TRUE); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Beginning db session", + __FILE__, __LINE__, + "can't begin session for %s in mode %s", dbinfo->dbname, dbinfo->mode)) + return FALSE; - gnc_session_begin (session, filename, FALSE, TRUE); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Beginning db session", - __FILE__, __LINE__, - "can't begin session for %s in mode %s", - db_name, mode)) - return FALSE; + gnc_session_save(session, NULL); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Saving db session", + __FILE__, __LINE__, + "can't save session for %s in mode %s", dbinfo->dbname, dbinfo->mode)) + return FALSE; - gnc_session_save (session, NULL); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Saving db session", - __FILE__, __LINE__, - "can't save session for %s in mode %s", - db_name, mode)) - return FALSE; + gnc_session_end(session); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Ending db session", + __FILE__, __LINE__, + "can't end session for %s in mode %s", dbinfo->dbname, dbinfo->mode)) + return FALSE; - gnc_session_end (session); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Ending db session", - __FILE__, __LINE__, - "can't end session for %s in mode %s", - db_name, mode)) - return FALSE; + do_test(gnc_session_get_url(session) == NULL, "session url not NULL"); - do_test (gnc_session_get_url (session) == NULL, "session url not NULL"); + g_free(filename); - g_free (filename); - - return TRUE; + return TRUE; } static gboolean -load_db_file (GNCSession *session, const char *db_name, const char *mode, - gboolean end_session) +load_db_file(GNCSession * session, DbInfo *dbinfo, gboolean end_session) { - GNCBackendError io_err; - char *filename; + GNCBackendError io_err; + PGBackend *be; + char *filename; - g_return_val_if_fail (session && db_name && mode, FALSE); + g_return_val_if_fail(session && dbinfo->dbname && dbinfo->mode, FALSE); - filename = db_file_url (db_name, mode); + filename = db_file_url(dbinfo); - gnc_session_begin (session, filename, FALSE, FALSE); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Beginning db session", - __FILE__, __LINE__, - "can't begin session for %s in mode %s", - db_name, mode)) - return FALSE; + gnc_session_begin(session, filename, FALSE, FALSE); + + be = (PGBackend *)gnc_session_get_backend(session); + dbinfo->conn = be->connection; - gnc_session_load (session, NULL); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Loading db session", - __FILE__, __LINE__, - "can't load session for %s in mode %s", - db_name, mode)) - return FALSE; + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Beginning db session", + __FILE__, __LINE__, + "can't begin session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; - if (end_session) - { - gnc_session_end (session); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Ending db session", - __FILE__, __LINE__, - "can't end session for %s in mode %s", - db_name, mode)) - return FALSE; + gnc_session_load(session, NULL); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Loading db session", + __FILE__, __LINE__, + "can't load session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; - do_test (gnc_session_get_url (session) == NULL, "session url not NULL"); - } + if (end_session) { + gnc_session_end(session); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Ending db session", + __FILE__, __LINE__, + "can't end session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; - g_free (filename); + do_test(gnc_session_get_url(session) == NULL, "session url not NULL"); + } - return TRUE; + g_free(filename); + + return TRUE; } static gboolean -test_access (const char *db_name, const char *mode, gboolean multi_user) +test_access(DbInfo *dbinfo, gboolean multi_user) { - GNCBackendError io_err; - GNCSession *session_1; - GNCSession *session_2; - char *filename; + GNCBackendError io_err; + GNCSession *session_1; + GNCSession *session_2; + char *filename; - g_return_val_if_fail (db_name && mode, FALSE); + g_return_val_if_fail(dbinfo->dbname && dbinfo->mode, FALSE); - filename = db_file_url (db_name, mode); + filename = db_file_url(dbinfo); - session_1 = gnc_session_new (); + session_1 = gnc_session_new(); - gnc_session_begin (session_1, filename, FALSE, FALSE); - io_err = gnc_session_get_error (session_1); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Beginning db session", - __FILE__, __LINE__, - "can't begin session for %s in mode %s", - db_name, mode)) - return FALSE; + gnc_session_begin(session_1, filename, FALSE, FALSE); + io_err = gnc_session_get_error(session_1); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Beginning db session", + __FILE__, __LINE__, + "can't begin session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; - session_2 = gnc_session_new (); + session_2 = gnc_session_new(); - gnc_session_begin (session_2, filename, FALSE, FALSE); - io_err = gnc_session_get_error (session_2); + gnc_session_begin(session_2, filename, FALSE, FALSE); + io_err = gnc_session_get_error(session_2); - if (multi_user) - { - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Beginning second multi-user db session", - __FILE__, __LINE__, - "can't begin second session for %s in mode %s", - db_name, mode)) - return FALSE; - } - else - { - if (!do_test_args (io_err != ERR_BACKEND_NO_ERR, - "Beginning second single-user db session", - __FILE__, __LINE__, - "began second session for %s in mode %s", - db_name, mode)) - return FALSE; - } + if (multi_user) { + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Beginning second multi-user db session", + __FILE__, __LINE__, + "can't begin second session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; + } else { + if (!do_test_args(io_err != ERR_BACKEND_NO_ERR, + "Beginning second single-user db session", + __FILE__, __LINE__, + "began second session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; + } - gnc_session_destroy (session_1); - gnc_session_destroy (session_2); + gnc_session_destroy(session_1); + gnc_session_destroy(session_2); - return TRUE; + return TRUE; } static gpointer -mark_account_commodities (Account *a, gpointer data) +mark_account_commodities(Account * a, gpointer data) { - GHashTable *hash = data; + GHashTable *hash = data; - g_hash_table_insert (hash, xaccAccountGetCommodity (a), hash); + g_hash_table_insert(hash, xaccAccountGetCommodity(a), hash); - return NULL; + return NULL; } static int -mark_transaction_commodities (Transaction *t, void *data) +mark_transaction_commodities(Transaction * t, void *data) { - GHashTable *hash = data; + GHashTable *hash = data; - g_hash_table_insert (hash, xaccTransGetCurrency (t), hash); + g_hash_table_insert(hash, xaccTransGetCurrency(t), hash); - return TRUE; + return TRUE; } static gboolean -mark_price_commodities (GNCPrice *p, gpointer data) +mark_price_commodities(GNCPrice * p, gpointer data) { - GHashTable *hash = data; + GHashTable *hash = data; - g_hash_table_insert (hash, gnc_price_get_commodity (p), hash); - g_hash_table_insert (hash, gnc_price_get_currency (p), hash); + g_hash_table_insert(hash, gnc_price_get_commodity(p), hash); + g_hash_table_insert(hash, gnc_price_get_currency(p), hash); - return TRUE; + return TRUE; } -typedef struct -{ - GHashTable *hash; - GList *to_delete; +typedef struct { + GHashTable *hash; + GList *to_delete; } CommodityDeleteInfo; static gboolean -add_commodity_to_delete (gnc_commodity *com, gpointer data) +add_commodity_to_delete(gnc_commodity * com, gpointer data) { - CommodityDeleteInfo *cdi = data; + CommodityDeleteInfo *cdi = data; - if (!g_hash_table_lookup (cdi->hash, com) && - safe_strcmp (gnc_commodity_get_namespace (com), - GNC_COMMODITY_NS_ISO) != 0) - cdi->to_delete = g_list_prepend (cdi->to_delete, com); + if (!g_hash_table_lookup(cdi->hash, com) && + safe_strcmp(gnc_commodity_get_namespace(com), + GNC_COMMODITY_NS_ISO) != 0) + cdi->to_delete = g_list_prepend(cdi->to_delete, com); - return TRUE; + return TRUE; } static void -remove_unneeded_commodities (GNCSession *session) +remove_unneeded_commodities(GNCSession * session) { - CommodityDeleteInfo cdi; - GNCBook *book; - GList *node; + CommodityDeleteInfo cdi; + GNCBook *book; + GList *node; - g_return_if_fail (session); + g_return_if_fail(session); - cdi.hash = g_hash_table_new (g_direct_hash, g_direct_equal); + cdi.hash = g_hash_table_new(g_direct_hash, g_direct_equal); - book = gnc_session_get_book (session); + book = gnc_session_get_book(session); - xaccGroupForEachAccount (gnc_book_get_group (book), - mark_account_commodities, - cdi.hash, TRUE); + xaccGroupForEachAccount(gnc_book_get_group(book), + mark_account_commodities, cdi.hash, TRUE); - xaccGroupForEachTransaction (gnc_book_get_group (book), - mark_transaction_commodities, - cdi.hash); + xaccGroupForEachTransaction(gnc_book_get_group(book), + mark_transaction_commodities, cdi.hash); - gnc_pricedb_foreach_price (gnc_book_get_pricedb (book), - mark_price_commodities, - cdi.hash, FALSE); + gnc_pricedb_foreach_price(gnc_book_get_pricedb(book), + mark_price_commodities, cdi.hash, FALSE); - cdi.to_delete = NULL; + cdi.to_delete = NULL; - gnc_commodity_table_foreach_commodity (gnc_book_get_commodity_table (book), - add_commodity_to_delete, &cdi); + gnc_commodity_table_foreach_commodity(gnc_book_get_commodity_table(book), + add_commodity_to_delete, &cdi); - for (node = cdi.to_delete; node; node = node->next) - gnc_commodity_table_remove (gnc_book_get_commodity_table (book), - node->data); + for (node = cdi.to_delete; node; node = node->next) + gnc_commodity_table_remove(gnc_book_get_commodity_table(book), + node->data); - g_list_free (cdi.to_delete); - g_hash_table_destroy (cdi.hash); + g_list_free(cdi.to_delete); + g_hash_table_destroy(cdi.hash); } static Query * -make_get_all_query (GNCSession * session) +make_get_all_query(GNCSession * session) { - Query *q; + Query *q; - g_return_val_if_fail (session, NULL); + g_return_val_if_fail(session, NULL); - q = xaccMallocQuery (); + q = xaccMallocQuery(); - xaccQuerySetBook (q, gnc_session_get_book (session)); + xaccQuerySetBook(q, gnc_session_get_book(session)); - xaccQueryAddClearedMatch (q, - CLEARED_NO | - CLEARED_CLEARED | - CLEARED_RECONCILED | - CLEARED_FROZEN | - CLEARED_VOIDED, - QUERY_AND); + xaccQueryAddClearedMatch(q, + CLEARED_NO | + CLEARED_CLEARED | + CLEARED_RECONCILED | + CLEARED_FROZEN | CLEARED_VOIDED, QUERY_AND); - return q; + return q; } static void -multi_user_get_everything (GNCSession *session, GNCSession *base) +multi_user_get_everything(GNCSession * session, GNCSession * base) { - Query *q; + Query *q; - g_return_if_fail (session); + g_return_if_fail(session); - q = make_get_all_query (session); + q = make_get_all_query(session); - xaccQueryGetSplits (q); + xaccQueryGetSplits(q); - xaccFreeQuery (q); + xaccFreeQuery(q); - /* load in prices from base */ - if (base) - gnc_pricedb_equal (gnc_book_get_pricedb (gnc_session_get_book (base)), - gnc_book_get_pricedb (gnc_session_get_book (session))); + /* load in prices from base */ + if (base) + gnc_pricedb_equal(gnc_book_get_pricedb(gnc_session_get_book(base)), + gnc_book_get_pricedb(gnc_session_get_book + (session))); } static gboolean -test_updates (GNCSession *session, const char *db_name, const char *mode, - gboolean multi_user) +test_updates(GNCSession * session, DbInfo *dbinfo, gboolean multi_user) { - GNCBackendError io_err; - GNCSession *session_2; - char *filename; - gboolean ok; + GNCBackendError io_err; + GNCSession *session_2; + char *filename; + gboolean ok; - g_return_val_if_fail (session && db_name && mode, FALSE); + g_return_val_if_fail(session && dbinfo->dbname && dbinfo->mode, FALSE); - filename = db_file_url (db_name, mode); + filename = db_file_url(dbinfo); - gnc_session_begin (session, filename, FALSE, FALSE); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Beginning db update session", - __FILE__, __LINE__, - "can't begin session for %s in mode %s", - db_name, mode)) - return FALSE; + gnc_session_begin(session, filename, FALSE, FALSE); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Beginning db update session", + __FILE__, __LINE__, + "can't begin session for %s in mode %s", dbinfo->dbname, dbinfo->mode)) + return FALSE; - make_random_changes_to_session (session); + make_random_changes_to_session(session); - if (!multi_user) - { - gnc_session_end (session); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Ending db session", - __FILE__, __LINE__, - "can't end session for %s in mode %s", - db_name, mode)) - return FALSE; - } + if (!multi_user) { + gnc_session_end(session); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Ending db session", + __FILE__, __LINE__, + "can't end session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; + } - session_2 = gnc_session_new (); + session_2 = gnc_session_new(); - if (!load_db_file (session_2, db_name, mode, !multi_user)) - return FALSE; + if (!load_db_file(session_2, dbinfo, !multi_user)) + return FALSE; - if (multi_user) - multi_user_get_everything (session_2, session); + if (multi_user) + multi_user_get_everything(session_2, session); - remove_unneeded_commodities (session); - remove_unneeded_commodities (session_2); + remove_unneeded_commodities(session); + remove_unneeded_commodities(session_2); - ok = gnc_book_equal (gnc_session_get_book (session), - gnc_session_get_book (session_2)); + ok = gnc_book_equal(gnc_session_get_book(session), + gnc_session_get_book(session_2)); - do_test_args (ok, "Books equal after update", __FILE__, __LINE__, - "Books not equal for session %s in mode %s", - db_name, mode); + do_test_args(ok, "Books equal after update", __FILE__, __LINE__, + "Books not equal for session %s in mode %si\n" + "book 1: %s,\nbook 2: %s", + dbinfo->dbname, dbinfo->mode, + guid_to_string(gnc_book_get_guid(gnc_session_get_book(session))), + guid_to_string(gnc_book_get_guid(gnc_session_get_book(session_2)))); - if (multi_user) - { - gnc_session_end (session); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Ending db session", - __FILE__, __LINE__, - "can't end session for %s in mode %s", - db_name, mode)) - return FALSE; + if (multi_user) { + gnc_session_end(session); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Ending db session", + __FILE__, __LINE__, + "can't end session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; - gnc_session_end (session_2); - io_err = gnc_session_get_error (session_2); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Ending db session", - __FILE__, __LINE__, - "can't end session for %s in mode %s", - db_name, mode)) - return FALSE; - } + gnc_session_end(session_2); + io_err = gnc_session_get_error(session_2); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Ending db session", + __FILE__, __LINE__, + "can't end session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; + } - if (!ok) - { - save_xml_files (session, session_2); - return FALSE; - } + if (!ok) { + save_xml_files(session, session_2); + return FALSE; + } - gnc_session_destroy (session_2); - g_free (filename); + gnc_session_destroy(session_2); + g_free(filename); - return TRUE; + return TRUE; } static gboolean -num_trans_helper (Transaction *trans, gpointer data) +num_trans_helper(Transaction * trans, gpointer data) { - int *num = data; + int *num = data; - *num += 1; + *num += 1; - return TRUE; + return TRUE; } static int -session_num_trans (GNCSession *session) +session_num_trans(GNCSession * session) { - AccountGroup *group; - GNCBook *book; - int num = 0; + AccountGroup *group; + GNCBook *book; + int num = 0; - g_return_val_if_fail (session, 0); + g_return_val_if_fail(session, 0); - book = gnc_session_get_book (session); - group = gnc_book_get_group (book); + book = gnc_session_get_book(session); + group = gnc_book_get_group(book); - xaccGroupForEachTransaction (group, num_trans_helper, &num); + xaccGroupForEachTransaction(group, num_trans_helper, &num); - return num; + return num; } -typedef struct -{ - GNCSession *session_base; - const char *db_name; - const char *mode; - gint loaded; - gint total; +typedef struct { + GNCSession *session_base; + DbInfo *dbinfo; + gint loaded; + gint total; } QueryTestData; static gboolean -test_raw_query (GNCSession *session, Query *q) +test_raw_query(GNCSession * session, Query * q) { - const char *sql_query_string; - PGresult *result; - PGBackend *be; - sqlQuery *sq; - gboolean ok; + const char *sql_query_string; + PGresult *result; + PGBackend *be; + sqlQuery *sq; + gboolean ok; + QueryNew *qn = q; - g_return_val_if_fail (session && q, FALSE); + g_return_val_if_fail(session && q, FALSE); - be = (PGBackend *) gnc_session_get_backend(session); + be = (PGBackend *) gnc_session_get_backend(session); - sq = sqlQuery_new(); - sql_query_string = sqlQuery_build (sq, q); + if (gnc_should_log(module, GNC_LOG_DETAIL)) + gncQueryPrint(qn); - result = PQexec (be->connection, sql_query_string); + sq = sqlQuery_new(); + sql_query_string = sqlQuery_build(sq, q); - ok = (result && PQresultStatus (result) == PGRES_TUPLES_OK); - if (!ok) - { - failure ("raw query failed"); - } - else - { - ok = ok && (PQntuples (result) == 1); - if (!ok) - failure_args ("number returned test", - __FILE__, __LINE__, - "query returned %d tuples", - PQntuples (result)); - } + result = PQexec(be->connection, sql_query_string); - if (ok) - { - success ("raw query succeeded"); - } - - PQclear (result); - sql_Query_destroy (sq); - - return ok; -} - -static gboolean -test_trans_query (Transaction *trans, gpointer data) -{ - QueryTestData *qtd = data; - GNCBackendError io_err; - GNCSession *session; - char *filename; - GNCBook *book; - GList *list; - Query *q; - - filename = db_file_url (qtd->db_name, qtd->mode); - - session = gnc_session_new (); - - gnc_session_begin (session, filename, FALSE, FALSE); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Beginning db session", + ok = (result && PQresultStatus(result) == PGRES_TUPLES_OK); + if (!ok) { + failure_args("Raw query failed", __FILE__, __LINE__, - "can't begin session for %s", - filename)) - return FALSE; + "Error: %s\nQuery: %s", + PQresultErrorMessage(result), + sql_query_string); + /* failure("raw query failed: %s", sql_query_string); */ + } else { + ok = ok && (PQntuples(result) == 1); + if (!ok) + failure_args("number returned test", + __FILE__, __LINE__, + "query returned %d tuples", PQntuples(result)); + } - gnc_session_load (session, NULL); - io_err = gnc_session_get_error (session); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Loading db session", - __FILE__, __LINE__, - "can't load session for %s", - filename)) - return FALSE; + if (ok) { + success("raw query succeeded"); + } - book = gnc_session_get_book (session); + PQclear(result); + sql_Query_destroy(sq); - q = make_trans_query (trans, get_random_query_type () | GUID_QT); - xaccQuerySetBook (q, book); - - if (!test_raw_query (session, q)) - { - failure ("raw query failed"); - return FALSE; - } - - list = xaccQueryGetTransactions (q, QUERY_TXN_MATCH_ANY); - if (g_list_length (list) != 1) - { - failure_args ("test num returned", __FILE__, __LINE__, - "number of matching transactions %d not 1", - g_list_length (list)); - g_list_free (list); - return FALSE; - } - - qtd->loaded += session_num_trans (session); - qtd->total += session_num_trans (qtd->session_base); - - if (!xaccTransEqual (trans, list->data, TRUE, TRUE)) - { - failure ("matching transaction is wrong"); - g_list_free (list); - return FALSE; - } - - success ("found right transaction"); - - xaccFreeQuery (q); - gnc_session_destroy (session); - g_free (filename); - g_list_free (list); - - return TRUE; + return ok; } static gboolean -compare_balances (GNCSession *session_1, GNCSession *session_2) +test_trans_query(Transaction * trans, gpointer data) { - GNCBook * book_1 = gnc_session_get_book (session_1); - GNCBook * book_2 = gnc_session_get_book (session_2); - GList * list; - GList * node; - gboolean ok; + QueryTestData *qtd = data; + GNCBackendError io_err; + GNCSession *session; + char *filename; + GNCBook *book; + GList *list; + Query *q; - g_return_val_if_fail (session_1, FALSE); - g_return_val_if_fail (session_2, FALSE); + filename = db_file_url(qtd->dbinfo); - ok = TRUE; + session = gnc_session_new(); - list = xaccGroupGetSubAccounts (gnc_book_get_group (book_1)); - for (node = list; node; node = node->next) - { - Account * account_1 = node->data; - Account * account_2; + gnc_session_begin(session, filename, FALSE, FALSE); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Beginning db session", + __FILE__, __LINE__, + "can't begin session for %s", filename)) + return FALSE; - account_2 = xaccAccountLookup (xaccAccountGetGUID (account_1), book_2); - if (!account_2) - { - failure_args ("", __FILE__, __LINE__, - "session_1 has account %s but not session_2", - guid_to_string (xaccAccountGetGUID (account_1))); - return FALSE; + gnc_session_load(session, NULL); + io_err = gnc_session_get_error(session); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Loading db session", + __FILE__, __LINE__, + "can't load session for %s", filename)) + return FALSE; + + book = gnc_session_get_book(session); + + q = make_trans_query(trans, get_random_query_type() | GUID_QT); + xaccQuerySetBook(q, book); + + if (!test_raw_query(session, q)) { + failure("raw query failed"); + return FALSE; } - if (!gnc_numeric_equal (xaccAccountGetBalance (account_1), - xaccAccountGetBalance (account_2))) - { - failure_args ("", __FILE__, __LINE__, - "balances not equal for account %s", - guid_to_string (xaccAccountGetGUID (account_1))); - ok = FALSE; + list = xaccQueryGetTransactions(q, QUERY_TXN_MATCH_ANY); + if (g_list_length(list) != 1) { + failure_args("test num returned", __FILE__, __LINE__, + "number of matching transactions %d not 1", + g_list_length(list)); + g_list_free(list); + return FALSE; } - if (!gnc_numeric_equal (xaccAccountGetClearedBalance (account_1), - xaccAccountGetClearedBalance (account_2))) - { - failure_args ("", __FILE__, __LINE__, - "cleared balances not equal for account %s", - guid_to_string (xaccAccountGetGUID (account_1))); - ok = FALSE; + qtd->loaded += session_num_trans(session); + qtd->total += session_num_trans(qtd->session_base); + + if (!xaccTransEqual(trans, list->data, TRUE, TRUE)) { + failure("matching transaction is wrong"); + g_list_free(list); + return FALSE; } - if (!gnc_numeric_equal (xaccAccountGetReconciledBalance (account_1), - xaccAccountGetReconciledBalance (account_2))) - { - failure_args ("", __FILE__, __LINE__, - "reconciled balances not equal for account %s", - guid_to_string (xaccAccountGetGUID (account_1))); - ok = FALSE; - } + success("found right transaction"); - if (!ok) - break; + xaccFreeQuery(q); + gnc_session_destroy(session); + g_free(filename); + g_list_free(list); - success ("balances equal"); - } - g_list_free (list); - - return ok; + return TRUE; } static gboolean -test_queries (GNCSession *session_base, const char *db_name, const char *mode) +compare_balances(GNCSession * session_1, GNCSession * session_2) { - QueryTestData qtd; - AccountGroup *group; - GNCBook *book; - gboolean ok; + GNCBook *book_1 = gnc_session_get_book(session_1); + GNCBook *book_2 = gnc_session_get_book(session_2); + GList *list; + GList *node; + gboolean ok; - g_return_val_if_fail (db_name && mode, FALSE); + g_return_val_if_fail(session_1, FALSE); + g_return_val_if_fail(session_2, FALSE); - book = gnc_session_get_book (session_base); - group = gnc_book_get_group (book); + ok = TRUE; - qtd.session_base = session_base; - qtd.db_name = db_name; - qtd.mode = mode; - qtd.loaded = 0; - qtd.total = 0; + list = xaccGroupGetSubAccounts(gnc_book_get_group(book_1)); + for (node = list; node; node = node->next) { + Account *account_1 = node->data; + Account *account_2; - ok = xaccGroupForEachTransaction (group, test_trans_query, &qtd); + account_2 = xaccAccountLookup(xaccAccountGetGUID(account_1), book_2); + if (!account_2) { + failure_args("", __FILE__, __LINE__, + "session_1 has account %s but not session_2", + guid_to_string(xaccAccountGetGUID(account_1))); + return FALSE; + } + + if (!gnc_numeric_equal(xaccAccountGetBalance(account_1), + xaccAccountGetBalance(account_2))) { + failure_args("", __FILE__, __LINE__, + "balances not equal for account %s", + guid_to_string(xaccAccountGetGUID(account_1))); + ok = FALSE; + } + + if (!gnc_numeric_equal(xaccAccountGetClearedBalance(account_1), + xaccAccountGetClearedBalance(account_2))) { + failure_args("", __FILE__, __LINE__, + "cleared balances not equal for account %s", + guid_to_string(xaccAccountGetGUID(account_1))); + ok = FALSE; + } + + if (!gnc_numeric_equal(xaccAccountGetReconciledBalance(account_1), + xaccAccountGetReconciledBalance(account_2))) { + failure_args("", __FILE__, __LINE__, + "reconciled balances not equal for account %s", + guid_to_string(xaccAccountGetGUID(account_1))); + ok = FALSE; + } + + if (!ok) + break; + + success("balances equal"); + } + g_list_free(list); + + return ok; +} + +static gboolean +test_queries(GNCSession * session_base, DbInfo *dbinfo) +{ + QueryTestData qtd; + AccountGroup *group; + GNCBook *book; + gboolean ok; + + g_return_val_if_fail(dbinfo->dbname && dbinfo->mode, FALSE); + + book = gnc_session_get_book(session_base); + group = gnc_book_get_group(book); + + qtd.session_base = session_base; + qtd.dbinfo = dbinfo; + qtd.loaded = 0; + qtd.total = 0; + + ok = xaccGroupForEachTransaction(group, test_trans_query, &qtd); #if 0 - g_warning ("average percentage loaded = %3.2f%%", - (qtd.loaded / (double) qtd.total) * 100.0); + g_warning("average percentage loaded = %3.2f%%", + (qtd.loaded / (double)qtd.total) * 100.0); #endif - return ok; + return ok; } -typedef struct -{ - GNCSession * session_1; - GNCSession * session_2; +typedef struct { + GNCSession *session_1; + GNCSession *session_2; - GNCBook * book_1; - GNCBook * book_2; + GNCBook *book_1; + GNCBook *book_2; - AccountGroup * group_1; - AccountGroup * group_2; + AccountGroup *group_1; + AccountGroup *group_2; - GList * accounts_1; - GList * accounts_2; + GList *accounts_1; + GList *accounts_2; } UpdateTestData; static gboolean -test_trans_update (Transaction *trans, gpointer data) +test_trans_update(Transaction * trans, gpointer data) { - UpdateTestData *td = data; - GNCBackendError io_err; - Transaction * trans_2; - GNCBook * book_1; - GNCBook * book_2; - GUID guid; - gboolean ok; + UpdateTestData *td = data; + GNCBackendError io_err; + Transaction *trans_2; + GNCBook *book_1; + GNCBook *book_2; + GUID guid; + gboolean ok; - /* FIXME remove */ - return TRUE; + /* FIXME remove */ + return TRUE; - book_1 = gnc_session_get_book (td->session_1); - book_2 = gnc_session_get_book (td->session_2); + book_1 = gnc_session_get_book(td->session_1); + book_2 = gnc_session_get_book(td->session_2); - guid = *xaccTransGetGUID (trans); + guid = *xaccTransGetGUID(trans); - xaccTransBeginEdit (trans); - make_random_changes_to_transaction_and_splits (book_1, trans, - td->accounts_1); - xaccTransCommitEdit (trans); + xaccTransBeginEdit(trans); + make_random_changes_to_transaction_and_splits(book_1, trans, + td->accounts_1); + xaccTransCommitEdit(trans); - io_err = gnc_session_get_error (td->session_1); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "changing transaction in session 1", - __FILE__, __LINE__, - "error changing transaction: %d", io_err)) - return FALSE; + io_err = gnc_session_get_error(td->session_1); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "changing transaction in session 1", + __FILE__, __LINE__, + "error changing transaction: %d", io_err)) + return FALSE; - trans = xaccTransLookup (&guid, book_1); - trans_2 = xaccTransLookup (&guid, book_2); + trans = xaccTransLookup(&guid, book_1); + trans_2 = xaccTransLookup(&guid, book_2); - /* This should get rolled back. */ - if (trans_2) - { - xaccTransBeginEdit (trans_2); - make_random_changes_to_transaction_and_splits (book_2, trans_2, - td->accounts_2); - xaccTransCommitEdit (trans_2); - } + /* This should get rolled back. */ + if (trans_2) { + xaccTransBeginEdit(trans_2); + make_random_changes_to_transaction_and_splits(book_2, trans_2, + td->accounts_2); + xaccTransCommitEdit(trans_2); + } - trans_2 = xaccTransLookup (&guid, book_2); + trans_2 = xaccTransLookup(&guid, book_2); - ok = xaccTransEqual (trans, trans_2, TRUE, TRUE); - if (trans && trans_2) - ok = ok && (trans->version == trans_2->version); + ok = xaccTransEqual(trans, trans_2, TRUE, TRUE); + if (trans && trans_2) + ok = ok && (trans->version == trans_2->version); - /* - ok = ok && (gnc_session_get_error (td->session_2) == ERR_BACKEND_MODIFIED); - */ + /* + ok = ok && (gnc_session_get_error (td->session_2) == ERR_BACKEND_MODIFIED); + */ - if (!do_test_args (ok, - "test trans rollback", - __FILE__, __LINE__, - "transaction not rolled back properly")) - return FALSE; + if (!do_test_args(ok, + "test trans rollback", + __FILE__, __LINE__, + "transaction not rolled back properly")) + return FALSE; - return TRUE; + return TRUE; } static gboolean -add_trans_helper (Transaction *trans, gpointer data) +add_trans_helper(Transaction * trans, gpointer data) { - GList **list = data; + GList **list = data; - *list = g_list_prepend (*list, trans); + *list = g_list_prepend(*list, trans); - return TRUE; + return TRUE; } static gboolean -test_updates_2 (GNCSession *session_base, - const char *db_name, const char *mode) +drop_database(DbInfo *dbinfo) { - UpdateTestData td; - char * filename; - GList * transes; - GList * node; - gboolean ok; + gchar *dropdb = NULL; + int rc; - g_return_val_if_fail (session_base && db_name && mode, FALSE); + if (!g_strncasecmp(dbinfo->port, "7777", 4)) { + dropdb = g_strdup_printf("dropdb -p %s %s", + dbinfo->port, dbinfo->dbname); + } else { + dropdb = g_strdup_printf("dropdb -p %s -h %s %s", + dbinfo->port, dbinfo->host, + dbinfo->dbname); + } - filename = db_file_url (db_name, mode); + /* Make sure everything is logged off */ + sleep(5); + rc = system(dropdb); + printf("Executed %s,\nreturn code was %d\n", dropdb, rc); + if (rc) { + printf("Please run the command\n" + "\t%s\nwhen this process completes\n", dropdb); + } + g_free(dropdb); + dropdb = NULL; + return rc == 0 ? TRUE : FALSE; +} + +static gboolean +test_updates_2(GNCSession * session_base, DbInfo *dbinfo) +{ + UpdateTestData td; + char *filename; + GList *transes; + GList *node; + gboolean ok; - if (!load_db_file (session_base, db_name, mode, FALSE)) - return FALSE; + g_return_val_if_fail(session_base && dbinfo->dbname && dbinfo->mode, FALSE); - multi_user_get_everything (session_base, NULL); + filename = db_file_url(dbinfo); - td.session_1 = session_base; - td.book_1 = gnc_session_get_book (session_base); - td.group_1 = gnc_book_get_group (td.book_1); - td.accounts_1 = xaccGroupGetSubAccounts (td.group_1); + if (!load_db_file(session_base, dbinfo, FALSE)) + return FALSE; - td.session_2 = gnc_session_new (); + multi_user_get_everything(session_base, NULL); - if (!load_db_file (td.session_2, db_name, mode, FALSE)) - return FALSE; + td.session_1 = session_base; + td.book_1 = gnc_session_get_book(session_base); + td.group_1 = gnc_book_get_group(td.book_1); + td.accounts_1 = xaccGroupGetSubAccounts(td.group_1); - multi_user_get_everything (td.session_2, NULL); + td.session_2 = gnc_session_new(); - td.book_2 = gnc_session_get_book (td.session_2); - td.group_2 = gnc_book_get_group (td.book_2); - td.accounts_2 = xaccGroupGetSubAccounts (td.group_2); + if (!load_db_file(td.session_2, dbinfo, FALSE)) + return FALSE; - ok = TRUE; - transes = NULL; - xaccGroupForEachTransaction (td.group_1, add_trans_helper, &transes); - for (node = transes; node; node = node->next) - { - ok = test_trans_update (node->data, &td); - if (!ok) - return FALSE; - } - g_list_free (transes); + multi_user_get_everything(td.session_2, NULL); + + td.book_2 = gnc_session_get_book(td.session_2); + td.group_2 = gnc_book_get_group(td.book_2); + td.accounts_2 = xaccGroupGetSubAccounts(td.group_2); + + ok = TRUE; + transes = NULL; + xaccGroupForEachTransaction(td.group_1, add_trans_helper, &transes); + for (node = transes; node; node = node->next) { + ok = test_trans_update(node->data, &td); + if (!ok) + return FALSE; + } + g_list_free(transes); #if 0 - for (node = td.accounts_1; node; node = node->next) - { - Account * account_1 = node->data; - Account * account_2 = - xaccAccountLookup (xaccAccountGetGUID (account_1), td.book_2); + for (node = td.accounts_1; node; node = node->next) { + Account *account_1 = node->data; + Account *account_2 = + xaccAccountLookup(xaccAccountGetGUID(account_1), td.book_2); - make_random_changes_to_account (td.book_1, account_1); - make_random_changes_to_account (td.book_2, account_2); + make_random_changes_to_account(td.book_1, account_1); + make_random_changes_to_account(td.book_2, account_2); - ok = xaccAccountEqual (account_1, account_2, TRUE); - if (account_1 && account_2) - ok = ok && (account_1->version == account_2->version); + ok = xaccAccountEqual(account_1, account_2, TRUE); + if (account_1 && account_2) + ok = ok && (account_1->version == account_2->version); - ok = ok && (gnc_session_get_error (td.session_2) == ERR_BACKEND_MODIFIED); + ok = ok + && (gnc_session_get_error(td.session_2) == ERR_BACKEND_MODIFIED); - if (!do_test_args (ok, - "test account rollback", - __FILE__, __LINE__, - "account not rolled back properly")) - return FALSE; - } + if (!do_test_args(ok, + "test account rollback", + __FILE__, __LINE__, + "account not rolled back properly")) + return FALSE; + } #endif - { - Account * account = get_random_account (td.book_1); - Account * child = get_random_account (td.book_1); - Transaction * trans = get_random_transaction (td.book_1); - Query * q = xaccMallocQuery (); - int count = 0; - - xaccAccountBeginEdit (account); - xaccAccountBeginEdit (child); - xaccGroupInsertAccount (td.group_1, account); - xaccAccountInsertSubAccount (account, child); - xaccAccountCommitEdit (child); - xaccAccountCommitEdit (account); - - xaccTransBeginEdit (trans); - for (node = xaccTransGetSplitList (trans); node; node = node->next) { - Split * split = node->data; + Account *account = get_random_account(td.book_1); + Account *child = get_random_account(td.book_1); + Transaction *trans = get_random_transaction(td.book_1); + Query *q = xaccMallocQuery(); + int count = 0; - xaccAccountInsertSplit (child, split); - count++; - } - xaccTransCommitEdit (trans); + xaccAccountBeginEdit(account); + xaccAccountBeginEdit(child); + xaccGroupInsertAccount(td.group_1, account); + xaccAccountInsertSubAccount(account, child); + xaccAccountCommitEdit(child); + xaccAccountCommitEdit(account); - xaccQueryAddGUIDMatch (q, xaccAccountGetGUID (child), - GNC_ID_ACCOUNT, QUERY_AND); + xaccTransBeginEdit(trans); + for (node = xaccTransGetSplitList(trans); node; node = node->next) { + Split *split = node->data; - xaccQuerySetBook (q, td.book_2); + xaccAccountInsertSplit(child, split); + count++; + } + xaccTransCommitEdit(trans); - ok = (g_list_length (xaccQueryGetSplits (q)) == count); + xaccQueryAddGUIDMatch(q, xaccAccountGetGUID(child), + GNC_ID_ACCOUNT, QUERY_AND); - xaccFreeQuery (q); + xaccQuerySetBook(q, td.book_2); - if (ok) - { - Transaction * trans_2; - Account * account_2; - Account * child_2; + ok = (g_list_length(xaccQueryGetSplits(q)) == count); - trans_2 = xaccTransLookup (xaccTransGetGUID (trans), td.book_2); - account_2 = xaccAccountLookup (xaccAccountGetGUID (account), td.book_2); - child_2 = xaccAccountLookup (xaccAccountGetGUID (child), td.book_2); + xaccFreeQuery(q); - ok = ok && xaccTransEqual (trans, trans_2, TRUE, TRUE); - ok = ok && xaccAccountEqual (account, account_2, TRUE); - ok = ok && xaccAccountEqual (child, child_2, TRUE); + if (ok) { + Transaction *trans_2; + Account *account_2; + Account *child_2; + + trans_2 = xaccTransLookup(xaccTransGetGUID(trans), td.book_2); + account_2 = + xaccAccountLookup(xaccAccountGetGUID(account), td.book_2); + child_2 = xaccAccountLookup(xaccAccountGetGUID(child), td.book_2); + + ok = ok && xaccTransEqual(trans, trans_2, TRUE, TRUE); + ok = ok && xaccAccountEqual(account, account_2, TRUE); + ok = ok && xaccAccountEqual(child, child_2, TRUE); + } + + if (!do_test_args(ok, + "test new account", + __FILE__, __LINE__, + "new accounts not loaded properly")) + return FALSE; } - if (!do_test_args (ok, - "test new account", - __FILE__, __LINE__, - "new accounts not loaded properly")) - return FALSE; - } + gnc_session_end(td.session_1); + gnc_session_end(td.session_2); + gnc_session_destroy(td.session_2); - gnc_session_end (td.session_1); - gnc_session_end (td.session_2); - gnc_session_destroy (td.session_2); + g_list_free(td.accounts_1); + g_list_free(td.accounts_2); - g_list_free (td.accounts_1); - g_list_free (td.accounts_2); - - return ok; + return ok; } static gboolean -test_mode (const char *db_name, const char *mode, - gboolean updates, gboolean multi_user) +test_mode(DbInfo *dbinfo, gboolean updates, gboolean multi_user) { - GNCSession *session; - GNCSession *session_db; - gboolean ok; + GNCSession *session; + GNCSession *session_db; + gboolean ok; + gchar *modesave = dbinfo->mode; + gchar *sumode = "single-update"; - session = get_random_session (); + session = get_random_session(); - add_random_transactions_to_book (gnc_session_get_book(session), 20); + add_random_transactions_to_book(gnc_session_get_book(session), 20); - if (!save_db_file (session, db_name, "single-update")) - return FALSE; + dbinfo->mode = sumode; + if (!save_db_file(session, dbinfo)) + return FALSE; + dbinfo->mode = modesave; - session_db = gnc_session_new (); + session_db = gnc_session_new(); - if (!load_db_file (session_db, db_name, mode, !multi_user)) - return FALSE; + if (!load_db_file(session_db, dbinfo, !multi_user)) + return FALSE; - if (multi_user) - { - if (!compare_balances (session, session_db)) - return FALSE; + if (multi_user) { + if (!compare_balances(session, session_db)) + return FALSE; - multi_user_get_everything (session_db, session); - } + multi_user_get_everything(session_db, session); + } - ok = gnc_book_equal (gnc_session_get_book (session), - gnc_session_get_book (session_db)); + ok = gnc_book_equal(gnc_session_get_book(session), + gnc_session_get_book(session_db)); - do_test_args (ok, "Books equal", __FILE__, __LINE__, - "Books not equal for session %s in mode %s", - db_name, mode); + do_test_args(ok, "Books equal", __FILE__, __LINE__, + "Books not equal for session %s in mode %s", + dbinfo->dbname, dbinfo->mode); - if (multi_user) - { - GNCBackendError io_err; + if (multi_user) { + GNCBackendError io_err; - gnc_session_end (session_db); - io_err = gnc_session_get_error (session_db); - if (!do_test_args (io_err == ERR_BACKEND_NO_ERR, - "Ending db session", - __FILE__, __LINE__, - "can't end session for %s in mode %s", - db_name, mode)) - return FALSE; - } + gnc_session_end(session_db); + io_err = gnc_session_get_error(session_db); + if (!do_test_args(io_err == ERR_BACKEND_NO_ERR, + "Ending db session", + __FILE__, __LINE__, + "can't end session for %s in mode %s", + dbinfo->dbname, dbinfo->mode)) + return FALSE; + } - if (!ok) - { - save_xml_files (session, session_db); - return FALSE; - } + if (!ok) { + save_xml_files(session, session_db); + return FALSE; + } - if (!test_access (db_name, mode, multi_user)) - return FALSE; + if (!test_access(dbinfo, multi_user)) + return FALSE; - if (updates && !test_updates (session_db, db_name, mode, multi_user)) - return FALSE; + if (updates && !test_updates(session_db, dbinfo, multi_user)) + return FALSE; - if (multi_user && !test_queries (session_db, db_name, mode)) - return FALSE; + if (multi_user && !test_queries(session_db, dbinfo)) + return FALSE; - if (updates && !test_updates_2 (session_db, db_name, mode)) - return FALSE; + if (updates && !test_updates_2(session_db, dbinfo)) + return FALSE; - gnc_session_destroy (session); - gnc_session_destroy (session_db); + gnc_session_destroy(session); + gnc_session_destroy(session_db); - return ok; + return ok; } static void -run_test (void) +run_test(DbInfo *dbinfo) { + #if 0 - if (!test_mode ("single_file", "single-file", FALSE, FALSE)) - return; + if (!test_mode("single_file", "single-file", FALSE, FALSE)) + return; #endif #if 0 - if (!test_mode ("single_update", "single-update", TRUE, FALSE)) - return; + if (!test_mode("single_update", "single-update", TRUE, FALSE)) + return; #endif #if 0 - if (!test_mode ("multi_user", "multi-user", TRUE, TRUE)) - return; + if (!test_mode("multi_user", "multi-user", TRUE, TRUE)) + return; #endif #if 1 - if (!test_mode ("multi_user_poll", "multi-user-poll", TRUE, TRUE)) - return; + dbinfo->dbname = "multi_user_poll"; + dbinfo->mode = "multi-user-poll"; + /** Account for previous failed checks */ + drop_database(dbinfo); + test_mode(dbinfo, TRUE, TRUE); #endif + drop_database(dbinfo); + } #if 0 static void -test_performance (const char *db_name, const char *mode) +test_performance(DbInfo *dbinfo) { - GNCSession *session; + GNCSession *session; + gchar *modesave = dbinfo->mode; + gchar *sumode = "single-update"; - session = get_random_session (); + session = get_random_session(); - gnc_set_log_level (MOD_TEST, GNC_LOG_WARNING); + gnc_set_log_level(MOD_TEST, GNC_LOG_WARNING); - START_CLOCK (0, "Starting to save session"); - if (!save_db_file (session, db_name, "single-update")) - return; - REPORT_CLOCK (0, "Finished saving session"); + dbinfo->mode = sumode; + START_CLOCK(0, "Starting to save session"); + if (!save_db_file(session, dbinfo)) + return; + REPORT_CLOCK(0, "Finished saving session"); + dbinfo->mode = modesave; + + gnc_session_destroy(session); + session = gnc_session_new(); - gnc_session_destroy (session); - session = gnc_session_new (); + if (!load_db_file(session, dbinfo, FALSE)) + return; - if (!load_db_file (session, db_name, mode, FALSE)) - return; + gnc_set_log_level(MOD_TEST, GNC_LOG_INFO); - gnc_set_log_level (MOD_TEST, GNC_LOG_INFO); + START_CLOCK(0, "Starting to save transactions"); + add_random_transactions_to_book(gnc_session_get_book(session), 100); + REPORT_CLOCK(0, "Finished saving transactions"); - START_CLOCK (0, "Starting to save transactions"); - add_random_transactions_to_book (gnc_session_get_book(session), 100); - REPORT_CLOCK (0, "Finished saving transactions"); + REPORT_CLOCK_TOTAL(0, "total"); + REPORT_CLOCK_TOTAL(1, "deleting kvp"); + REPORT_CLOCK_TOTAL(2, "storing kvp"); - REPORT_CLOCK_TOTAL (0, "total"); - REPORT_CLOCK_TOTAL (1, "deleting kvp"); - REPORT_CLOCK_TOTAL (2, "storing kvp"); - - gnc_session_end (session); - gnc_session_destroy (session); + gnc_session_end(session); + gnc_session_destroy(session); } #endif static void -guile_main (int argc, char **argv) +guile_main(int argc, char **argv) { - gnc_module_system_init (); - gnc_module_load ("gnucash/engine", 0); + DbInfo *dbinfo; + + GNCModule *mod; + gnc_module_system_init(); + mod = gnc_module_load("gnucash/engine", 0); - /* g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); */ + dbinfo = g_new0(DbInfo, 1); + + if (argc >= 2) + dbinfo->host = argv[1]; + else { + dbinfo->host = getenv("PGHOST"); + if (!dbinfo->host) + dbinfo->host = "localhost"; + } - kvp_exclude_type (KVP_TYPE_BINARY); - kvp_exclude_type (KVP_TYPE_GLIST); + if (argc >= 3) + dbinfo->port = argv[2]; + else { + dbinfo->port = getenv("PGPORT"); + if (!dbinfo->port) + dbinfo->port = "5432"; + } - /* The random double generator is making values - * that postgres doesn't like. */ - kvp_exclude_type (KVP_TYPE_DOUBLE); + /* g_log_set_always_fatal (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING); */ - set_max_kvp_depth (3); - set_max_kvp_frame_elements (3); + kvp_exclude_type(KVP_TYPE_BINARY); + kvp_exclude_type(KVP_TYPE_GLIST); - set_max_group_depth (3); - set_max_group_accounts (3); + /* The random double generator is making values + * that postgres doesn't like. */ + kvp_exclude_type(KVP_TYPE_DOUBLE); - random_timespec_zero_nsec (TRUE); + set_max_kvp_depth(3); + set_max_kvp_frame_elements(3); - /* Querying on exact prices is problematic. */ - trans_query_include_price (FALSE); + set_max_group_depth(3); + set_max_group_accounts(3); - xaccLogDisable (); + random_timespec_zero_nsec(TRUE); - run_test (); + /* Querying on exact prices is problematic. */ + trans_query_include_price(FALSE); - print_test_results (); - exit (get_rv ()); + xaccLogDisable(); + + run_test(dbinfo); + + print_test_results(); + exit(get_rv()); } int -main (int argc, char ** argv) +main(int argc, char **argv) { - gh_enter (argc, argv, guile_main); - return 0; + gh_enter(argc, argv, guile_main); + return 0; }