You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gnucash/src/engine/sql/gncquery.c

487 lines
15 KiB

/********************************************************************\
* gncquery.c : Convert gnucash engine Query into an SQL Query *
* Copyright (C) 2001 Linas Vepstas <linas@linas.org> *
* *
* 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 <glib.h>
#include <string.h>
#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 ==================== */