mirror of https://github.com/Gnucash/gnucash
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.
487 lines
15 KiB
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 ==================== */
|
|
|