/********************************************************************\ * gncquery.c : Convert gnucash engine Query into an SQL Query * * Copyright (C) 2001 Linas Vepstas * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License* * along with this program; if not, contact: * * * * Free Software Foundation Voice: +1-617-542-5942 * * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * * Boston, MA 02111-1307, USA gnu@gnu.org * \********************************************************************/ /* * FILE: * gncquery.c * * FUNCTION: * Convert gnucash engine Query (a la Query.h) into an SQL Query * * The gnc engine query consists of doubly nested list of * query terms. The inner list consists of terms that need to be * AND'ed together; the outer list OR's together the inner lists. * * HISTORY: * Linas Vepstas January 2001 */ #define _GNU_SOURCE #include #include #include "escape.h" #include "gnc-engine-util.h" #include "gncquery.h" static short module = MOD_BACKEND; struct _gnc_query { char * q_base; char * pq; size_t buflen; sqlEscape *escape; }; /* =========================================================== */ #define INITIAL_BUFSZ 16300 sqlQuery * sqlQuery_new(void) { sqlQuery *sq = g_new (sqlQuery, 1); sq->q_base = g_malloc (INITIAL_BUFSZ); sq->buflen = INITIAL_BUFSZ; sq->pq = sq->q_base; sq->escape = sqlEscape_new (); return sq; } void sql_Query_destroy (sqlQuery *sq) { if (!sq) return; g_free(sq->q_base); sqlEscape_destroy (sq->escape); g_free(sq); } /* =========================================================== */ /* Macro for PD_STRING query types * Note that postgres supports both case-sensitive and * case-insensitve string searches, and it also supports * regex! yahooo! */ #define STRING_TERM(fieldname) \ { \ const char *tmp; \ \ if (0 == pd->str.sense) \ { \ sq->pq = stpcpy (sq->pq, "NOT ("); \ } \ sq->pq = stpcpy(sq->pq, fieldname " ~"); \ if (0 == pd->str.case_sens) { \ sq->pq = stpcpy(sq->pq, "*"); \ } \ sq->pq = stpcpy(sq->pq, " '"); \ tmp = sqlEscapeString (sq->escape, pd->str.matchstring); \ sq->pq = stpcpy(sq->pq, tmp); \ sq->pq = stpcpy(sq->pq, "'"); \ if (0 == pd->str.sense) \ { \ sq->pq = stpcpy (sq->pq, ") "); \ } \ } /* =========================================================== */ /* Macro for PD_AMOUNT type terms. The logic used here in the * SQL exactly matches that used in the Query.c code. If * that code is incorrect or has changed, then the code below is * broken as well. */ #define AMOUNT_TERM(fieldname) \ { \ if (0 == pd->amount.sense) \ { \ sq->pq = stpcpy (sq->pq, "NOT ("); \ } \ switch(pd->amount.amt_sgn) \ { \ case AMT_SGN_MATCH_CREDIT: \ sq->pq = stpcpy(sq->pq, fieldname " <= 0 AND "); \ break; \ case AMT_SGN_MATCH_DEBIT: \ sq->pq = stpcpy(sq->pq, fieldname " >= 0 AND "); \ break; \ default: \ break; \ } \ switch(pd->amount.how) \ { \ case AMT_MATCH_ATLEAST: \ sq->pq = stpcpy(sq->pq, \ "abs(" fieldname ") >= gncCommodity.fraction * float8"); \ sq->pq += sprintf (sq->pq, "(%22.18g)", pd->amount.amount); \ break; \ case AMT_MATCH_ATMOST: \ sq->pq = stpcpy(sq->pq, \ "abs(" fieldname ") <= gncCommodity.fraction * float8"); \ sq->pq += sprintf (sq->pq, "(%22.18g)", pd->amount.amount); \ break; \ case AMT_MATCH_EXACTLY: \ sq->pq = stpcpy(sq->pq, \ "abs(abs(" fieldname ") - gncCommodity.fraction * float8"); \ sq->pq += sprintf (sq->pq, "(%22.18g)", pd->amount.amount); \ sq->pq = stpcpy(sq->pq, ") < 1"); \ break; \ } \ if (0 == pd->amount.sense) \ { \ sq->pq = stpcpy (sq->pq, ") "); \ } \ } /* =========================================================== */ /* Macro for PR_CLEARED term */ #define CLR_TERM(howie,flagchar) \ { \ if (pd->cleared.how & howie) \ { \ if (got_one) \ { \ sq->pq = stpcpy(sq->pq, "OR "); \ } \ sq->pq = stpcpy(sq->pq, "gncEntry.reconciled = '"); \ *(sq->pq) = flagchar; (sq->pq) ++; \ sq->pq = stpcpy(sq->pq, "' "); \ got_one = 1; \ } \ } /* =========================================================== */ const char * sqlQuery_build (sqlQuery*sq, Query *q) { GList *il, *jl, *qterms, *andterms; QueryTerm *qt; PredicateData *pd; int more_or = 0; int more_and = 0; int max_rows; if (!sq || !q) return NULL; /* reset the buffer pointers */ sq->pq = sq->q_base; sq->pq = stpcpy(sq->pq, "SELECT DISTINCT gncEntry.transGuid " " FROM gncEntry, gncTransaction, gncAccount, gncCommodity " " WHERE gncEntry.transGuid = gncTransaction.transGuid AND ( "); qterms = xaccQueryGetTerms (q); /* 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 mustbe 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 = (QueryTerm *)jl->data; pd = &qt->data; switch (pd->base.term_type) { case PR_ACCOUNT: { int got_more = 0; GList *acct; PINFO("term is PR_ACCOUNT"); for (acct = pd->acct.account_guids; acct; acct=acct->next) { if (got_more) sq->pq = stpcpy(sq->pq, " AND "); got_more = 1; if ((0 == pd->acct.sense && ACCT_MATCH_NONE != pd->acct.how) || (1 == pd->acct.sense && ACCT_MATCH_NONE == pd->acct.how)) { sq->pq = stpcpy (sq->pq, "NOT "); } sq->pq = stpcpy(sq->pq, "gncEntry.accountGuid='"); sq->pq = guid_to_string_buff ((GUID*) acct->data, sq->pq); sq->pq = stpcpy(sq->pq, "'"); } break; } case PR_ACTION: PINFO("term is PR_ACTION"); STRING_TERM ("gncEntry.action"); break; case PR_AMOUNT: { PINFO("term is PR_AMOUNT"); sq->pq = stpcpy(sq->pq, "gncTransaction.currency = gncCommodity.commodity AND "); AMOUNT_TERM ("gncEntry.value"); break; } case PR_BALANCE: { PINFO("term is PR_BALANCE"); PWARN("PR_BALANCE query term not properly implemented"); if (0 == pd->balance.sense) { sq->pq = stpcpy (sq->pq, "NOT ("); } if (pd->balance.how & BALANCE_BALANCED) { sq->pq = stpcpy(sq->pq, "TRUE "); } else { sq->pq = stpcpy(sq->pq, "FALSE "); } if (0 == pd->balance.sense) { sq->pq = stpcpy (sq->pq, ") "); } break; } case PR_CLEARED: { int got_one = 0; PINFO("term is PR_CLEARED"); if (0 == pd->cleared.sense) { sq->pq = stpcpy (sq->pq, "NOT "); } sq->pq = stpcpy (sq->pq, "("); CLR_TERM (CLEARED_NO, NREC); CLR_TERM (CLEARED_CLEARED, CREC); CLR_TERM (CLEARED_RECONCILED, YREC); CLR_TERM (CLEARED_FROZEN, FREC); sq->pq = stpcpy (sq->pq, ") "); break; } case PR_DATE: { PINFO("term is PR_DATE"); if (0 == pd->date.sense) { sq->pq = stpcpy (sq->pq, "NOT ("); } if (pd->date.use_start) { sq->pq = stpcpy(sq->pq, "gncTransaction.date_posted >= '"); sq->pq = gnc_timespec_to_iso8601_buff (pd->date.start, sq->pq); sq->pq = stpcpy(sq->pq, "' "); } if (pd->date.use_end) { if (pd->date.use_start) { sq->pq = stpcpy(sq->pq, "AND "); } sq->pq = stpcpy(sq->pq, "gncTransaction.date_posted <= '"); sq->pq = gnc_timespec_to_iso8601_buff (pd->date.end, sq->pq); sq->pq = stpcpy(sq->pq, "' "); } if (!pd->date.use_start && !pd->date.use_end) { sq->pq = stpcpy(sq->pq, "TRUE "); } if (0 == pd->date.sense) { sq->pq = stpcpy (sq->pq, ") "); } break; } case PR_DESC: PINFO("term is PR_DESC"); STRING_TERM ("gncTransaction.description"); break; case PR_GUID: { PINFO("term is PR_GUID"); if (0 == pd->guid.sense) { sq->pq = stpcpy (sq->pq, "NOT ("); } switch (xaccGUIDType (&pd->guid.guid)) { case GNC_ID_NONE: case GNC_ID_NULL: default: sq->pq = stpcpy(sq->pq, "FALSE "); break; case GNC_ID_ACCOUNT: sq->pq = stpcpy(sq->pq, "gncAccount.accountGuid = '"); sq->pq = guid_to_string_buff (&pd->guid.guid, sq->pq); sq->pq = stpcpy(sq->pq, "' "); break; case GNC_ID_TRANS: sq->pq = stpcpy(sq->pq, "gncTransaction.transGuid = '"); sq->pq = guid_to_string_buff (&pd->guid.guid, sq->pq); sq->pq = stpcpy(sq->pq, "' "); break; case GNC_ID_SPLIT: sq->pq = stpcpy(sq->pq, "gncEntry.entryGuid = '"); sq->pq = guid_to_string_buff (&pd->guid.guid, sq->pq); sq->pq = stpcpy(sq->pq, "' "); break; } if (0 == pd->guid.sense) { sq->pq = stpcpy (sq->pq, ") "); } break; } case PR_MEMO: PINFO("term is PR_MEMO"); STRING_TERM ("gncEntry.memo"); break; case PR_MISC: PINFO("term is PR_MISC"); sq->pq = stpcpy(sq->pq, "TRUE "); break; case PR_NUM: PINFO("term is PR_NUM"); STRING_TERM ("gncTransaction.num"); break; case PR_PRICE: { PINFO("term is PR_PRICE"); if (0 == pd->amount.sense) { sq->pq = stpcpy (sq->pq, "NOT ("); } switch(pd->amount.amt_sgn) { case AMT_SGN_MATCH_CREDIT: sq->pq = stpcpy(sq->pq, "gncEntry.value / gncEntry.amount <= 0 AND "); break; case AMT_SGN_MATCH_DEBIT: sq->pq = stpcpy(sq->pq, "gncEntry.value / gncEntry.amount >= 0 AND "); break; default: break; } switch(pd->amount.how) { case AMT_MATCH_ATLEAST: sq->pq = stpcpy(sq->pq, "gncHelperPrVal(gncEntry) >= gncHelperPrAmt(gncEntry) * float8"); sq->pq += sprintf (sq->pq, "(%22.18g)", pd->amount.amount); break; case AMT_MATCH_ATMOST: sq->pq = stpcpy(sq->pq, "gncHelperPrVal(gncEntry) <= gncHelperPrAmt(gncEntry) * float8"); sq->pq += sprintf (sq->pq, "(%22.18g)", pd->amount.amount); break; case AMT_MATCH_EXACTLY: sq->pq = stpcpy(sq->pq, "abs(gncHelperPrVal(gncEntry) - gncHelperPrAmt(gncEntry) * float8"); sq->pq += sprintf (sq->pq, "(%22.18g)", pd->amount.amount); sq->pq = stpcpy(sq->pq, ") < 1"); break; } if (0 == pd->amount.sense) { sq->pq = stpcpy (sq->pq, ") "); } break; } case PR_SHRS: { PINFO("term is PR_SHRS"); sq->pq = stpcpy(sq->pq, "gncEntry.accountGuid = gncAccount.accountGuid AND " "gncAccount.commodity = gncCommodity.commodity AND "); AMOUNT_TERM ("gncEntry.amount"); break; } default: PERR ("unkown query term type %d", pd->base.term_type); } } /* if there were and terms, close the brace */ if (il->data) sq->pq = stpcpy(sq->pq, ")"); } sq->pq = stpcpy(sq->pq, ")"); /* limit the query result to a finite numbe of rows */ max_rows = xaccQueryGetMaxSplits (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; } /* ========================== END OF FILE ==================== */