gnucash/libgnucash/engine/Query.c
Geert Janssens 1238b9d8cd Prevent gcc from searching config.h in the current directory
This will avoid a ninja-build from picking up a config.h generated by the autotools build
(in the root build directory). Picking up the wrong config.h may lead to all kinds of
subtle issues if the autotools run was done with different options than the cmake run.
2017-10-26 14:05:17 +02:00

711 lines
21 KiB
C

/********************************************************************\
* Query.c : api for finding transactions *
* Copyright (C) 2000 Bill Gribble <grib@billgribble.com> *
* Copyright (C) 2002 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 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA gnu@gnu.org *
\********************************************************************/
#include <config.h>
#include <platform.h>
#if PLATFORM(WINDOWS)
#include <windows.h>
#endif
#include <ctype.h>
#include <glib.h>
#include <math.h>
#include <string.h>
#include <sys/types.h>
#include <regex.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include "gnc-lot.h"
#include "Account.h"
#include "Query.h"
#include "Transaction.h"
#include "TransactionP.h"
static QofLogModule log_module = GNC_MOD_QUERY;
static GSList *
build_param_list_internal (const char *first, va_list rest)
{
GSList *list = NULL;
char const *param;
for (param = first; param; param = va_arg (rest, const char *))
list = g_slist_prepend (list, (gpointer)param);
return (g_slist_reverse (list));
}
/********************************************************************
* xaccQueryGetSplitsUniqueTrans
* Get splits but no more than one from a given transaction.
********************************************************************/
SplitList *
xaccQueryGetSplitsUniqueTrans(QofQuery *q)
{
GList * splits = qof_query_run(q);
GList * current;
GList * result = NULL;
GHashTable * trans_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
for (current = splits; current; current = current->next)
{
Split *split = current->data;
Transaction *trans = xaccSplitGetParent (split);
if (!g_hash_table_lookup (trans_hash, trans))
{
g_hash_table_insert (trans_hash, trans, trans);
result = g_list_prepend (result, split);
}
}
g_hash_table_destroy (trans_hash);
return g_list_reverse (result);
}
/********************************************************************
* xaccQueryGetTransactions
* Get transactions matching the query terms, specifying whether
* we require some or all splits to match
********************************************************************/
static void
query_match_all_filter_func(gpointer key, gpointer value, gpointer user_data)
{
Transaction * t = key;
int num_matches = GPOINTER_TO_INT(value);
GList ** matches = user_data;
if (num_matches == xaccTransCountSplits(t))
{
*matches = g_list_prepend(*matches, t);
}
}
static void
query_match_any_filter_func(gpointer key, gpointer value, gpointer user_data)
{
Transaction * t = key;
GList ** matches = user_data;
*matches = g_list_prepend(*matches, t);
}
TransList *
xaccQueryGetTransactions (QofQuery * q, query_txn_match_t runtype)
{
GList * splits = qof_query_run(q);
GList * current = NULL;
GList * retval = NULL;
GHashTable * trans_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
Transaction * trans = NULL;
gpointer val = NULL;
int count = 0;
/* iterate over matching splits, incrementing a match-count in
* the hash table */
for (current = splits; current; current = current->next)
{
trans = xaccSplitGetParent((Split *)(current->data));
/* don't waste time looking up unless we need the count
* information */
if (runtype == QUERY_TXN_MATCH_ALL)
{
val = g_hash_table_lookup(trans_hash, trans);
count = GPOINTER_TO_INT(val);
}
g_hash_table_insert(trans_hash, trans, GINT_TO_POINTER(count + 1));
}
/* now pick out the transactions that match */
if (runtype == QUERY_TXN_MATCH_ALL)
{
g_hash_table_foreach(trans_hash, query_match_all_filter_func,
&retval);
}
else
{
g_hash_table_foreach(trans_hash, query_match_any_filter_func,
&retval);
}
g_hash_table_destroy(trans_hash);
return retval;
}
/********************************************************************
* xaccQueryGetLots
* Get lots matching the query terms, specifying whether
* we require some or all splits to match
********************************************************************/
static void
query_match_all_lot_filter_func(gpointer key, gpointer value, gpointer user_data)
{
GNCLot * l = key;
int num_matches = GPOINTER_TO_INT(value);
GList ** matches = user_data;
if (num_matches == gnc_lot_count_splits(l))
{
*matches = g_list_prepend(*matches, l);
}
}
static void
query_match_any_lot_filter_func(gpointer key, gpointer value, gpointer user_data)
{
GNCLot * t = key;
GList ** matches = user_data;
*matches = g_list_prepend(*matches, t);
}
LotList *
xaccQueryGetLots (QofQuery * q, query_txn_match_t runtype)
{
GList * splits = qof_query_run(q);
GList * current = NULL;
GList * retval = NULL;
GHashTable * lot_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
GNCLot * lot = NULL;
gpointer val = NULL;
int count = 0;
/* iterate over matching splits, incrementing a match-count in
* the hash table */
for (current = splits; current; current = current->next)
{
lot = xaccSplitGetLot((Split *)(current->data));
/* don't waste time looking up unless we need the count
* information */
if (runtype == QUERY_TXN_MATCH_ALL)
{
val = g_hash_table_lookup(lot_hash, lot);
count = GPOINTER_TO_INT(val);
}
g_hash_table_insert(lot_hash, lot, GINT_TO_POINTER(count + 1));
}
/* now pick out the transactions that match */
if (runtype == QUERY_TXN_MATCH_ALL)
{
g_hash_table_foreach(lot_hash, query_match_all_lot_filter_func,
&retval);
}
else
{
g_hash_table_foreach(lot_hash, query_match_any_lot_filter_func,
&retval);
}
g_hash_table_destroy(lot_hash);
return retval;
}
/*******************************************************************
* match-adding API
*******************************************************************/
void
xaccQueryAddAccountMatch(QofQuery *q, AccountList *acct_list,
QofGuidMatch how, QofQueryOp op)
{
GList *list = NULL;
if (!q) return;
for (; acct_list; acct_list = acct_list->next)
{
Account *acc = acct_list->data;
const GncGUID *guid;
if (!acc)
{
PWARN ("acct_list has NULL account");
continue;
}
guid = qof_entity_get_guid (QOF_INSTANCE(acc));
if (!guid)
{
PWARN ("acct returns NULL GncGUID");
continue;
}
list = g_list_prepend (list, (gpointer)guid);
}
xaccQueryAddAccountGUIDMatch (q, list, how, op);
g_list_free (list);
}
void
xaccQueryAddAccountGUIDMatch(QofQuery *q, AccountGUIDList *guid_list,
QofGuidMatch how, QofQueryOp op)
{
QofQueryPredData *pred_data;
GSList *param_list = NULL;
if (!q) return;
if (!guid_list && how != QOF_GUID_MATCH_NULL)
{
g_warning("Got a NULL guid_list but the QofGuidMatch is not MATCH_NULL (but instead %d). In other words, the list of GUID matches is empty but it must contain something non-empty.", how);
/* qof_query_guid_predicate() would trigger a g_warning as well */
return;
}
pred_data = qof_query_guid_predicate (how, guid_list);
if (!pred_data)
return;
switch (how)
{
case QOF_GUID_MATCH_ANY:
case QOF_GUID_MATCH_NONE:
param_list = qof_query_build_param_list (SPLIT_ACCOUNT, QOF_PARAM_GUID, NULL);
break;
case QOF_GUID_MATCH_ALL:
param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_SPLITLIST,
SPLIT_ACCOUNT_GUID, NULL);
break;
default:
PERR ("Invalid match type: %d", how);
break;
}
qof_query_add_term (q, param_list, pred_data, op);
}
void
xaccQueryAddSingleAccountMatch(QofQuery *q, Account *acc, QofQueryOp op)
{
GList *list;
const GncGUID *guid;
if (!q || !acc)
return;
guid = qof_entity_get_guid (QOF_INSTANCE(acc));
g_return_if_fail (guid);
list = g_list_prepend (NULL, (gpointer)guid);
xaccQueryAddAccountGUIDMatch (q, list, QOF_GUID_MATCH_ANY, op);
g_list_free (list);
}
void
xaccQueryAddStringMatch (QofQuery* q, const char *matchstring,
gboolean case_sens, gboolean use_regexp,
QofQueryCompare how, QofQueryOp op,
const char * path, ...)
{
QofQueryPredData *pred_data;
GSList *param_list;
va_list ap;
if (!path || !q)
return;
pred_data = qof_query_string_predicate (how, (char *)matchstring,
(case_sens ? QOF_STRING_MATCH_NORMAL :
QOF_STRING_MATCH_CASEINSENSITIVE),
use_regexp);
if (!pred_data)
return;
va_start (ap, path);
param_list = build_param_list_internal (path, ap);
va_end (ap);
qof_query_add_term (q, param_list, pred_data, op);
}
void
xaccQueryAddNumericMatch (QofQuery *q, gnc_numeric amount, QofNumericMatch sign,
QofQueryCompare how, QofQueryOp op,
const char * path, ...)
{
QofQueryPredData *pred_data;
GSList *param_list;
va_list ap;
if (!q || !path)
return;
pred_data = qof_query_numeric_predicate (how, sign, amount);
if (!pred_data)
return;
va_start (ap, path);
param_list = build_param_list_internal (path, ap);
va_end (ap);
qof_query_add_term (q, param_list, pred_data, op);
}
/* The DateMatch queries match transactions whose posted date
* is in a date range. If use_start is TRUE, then a matching
* posted date will be greater than the start date. If
* use_end is TRUE, then a match occurs for posted dates earlier
* than the end date. If both flags are set, then *both*
* conditions must hold ('and'). If neither flag is set, then
* all transactions are matched.
*/
void
xaccQueryAddDateMatchTS (QofQuery * q,
gboolean use_start, Timespec sts,
gboolean use_end, Timespec ets,
QofQueryOp op)
{
QofQuery *tmp_q = NULL;
QofQueryPredData *pred_data;
GSList *param_list;
if (!q || (!use_start && !use_end))
return;
tmp_q = qof_query_create ();
if (use_start)
{
pred_data = qof_query_date_predicate (QOF_COMPARE_GTE, QOF_DATE_MATCH_NORMAL, sts);
if (!pred_data)
{
qof_query_destroy (tmp_q);
return;
}
param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_DATE_POSTED, NULL);
qof_query_add_term (tmp_q, param_list, pred_data, QOF_QUERY_AND);
}
if (use_end)
{
pred_data = qof_query_date_predicate (QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, ets);
if (!pred_data)
{
qof_query_destroy (tmp_q);
return;
}
param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_DATE_POSTED, NULL);
qof_query_add_term (tmp_q, param_list, pred_data, QOF_QUERY_AND);
}
qof_query_merge_in_place (q, tmp_q, op);
qof_query_destroy (tmp_q);
}
void
xaccQueryGetDateMatchTS (QofQuery * q,
Timespec * sts,
Timespec * ets)
{
QofQueryPredData *term_data;
GSList *param_list;
GSList *terms, *tmp;
sts->tv_sec = sts->tv_nsec = 0;
ets->tv_sec = ets->tv_nsec = 0;
param_list = qof_query_build_param_list (SPLIT_TRANS, TRANS_DATE_POSTED, NULL);
terms = qof_query_get_term_type (q, param_list);
g_slist_free(param_list);
for (tmp = terms; tmp; tmp = g_slist_next(tmp))
{
term_data = tmp->data;
if (term_data->how == QOF_COMPARE_GTE)
qof_query_date_predicate_get_date(term_data, sts);
if (term_data->how == QOF_COMPARE_LTE)
qof_query_date_predicate_get_date(term_data, ets);
}
g_slist_free(terms);
}
/********************************************************************
* xaccQueryAddDateMatch
* Add a date filter to an existing query.
********************************************************************/
void
xaccQueryAddDateMatch(QofQuery * q,
gboolean use_start, int sday, int smonth, int syear,
gboolean use_end, int eday, int emonth, int eyear,
QofQueryOp op)
{
/* gcc -O3 will auto-inline this function, avoiding a call overhead */
xaccQueryAddDateMatchTS (q, use_start,
gnc_dmy2timespec(sday, smonth, syear),
use_end,
gnc_dmy2timespec_end(eday, emonth, eyear),
op);
}
/********************************************************************
* xaccQueryAddDateMatchTT
* Add a date filter to an existing query.
********************************************************************/
void
xaccQueryAddDateMatchTT(QofQuery * q,
gboolean use_start,
time64 stt,
gboolean use_end,
time64 ett,
QofQueryOp op)
{
Timespec sts;
Timespec ets;
sts.tv_sec = stt;
sts.tv_nsec = 0;
ets.tv_sec = ett;
ets.tv_nsec = 0;
/* gcc -O3 will auto-inline this function, avoiding a call overhead */
xaccQueryAddDateMatchTS (q, use_start, sts,
use_end, ets, op);
}
void
xaccQueryGetDateMatchTT (QofQuery * q,
time64 * stt,
time64 * ett)
{
Timespec sts;
Timespec ets;
xaccQueryGetDateMatchTS (q, &sts, &ets);
*stt = sts.tv_sec;
*ett = ets.tv_sec;
}
void
xaccQueryAddClearedMatch(QofQuery * q, cleared_match_t how, QofQueryOp op)
{
QofQueryPredData *pred_data;
GSList *param_list;
char chars[6];
int i = 0;
if (!q)
return;
if (how & CLEARED_CLEARED)
chars[i++] = CREC;
if (how & CLEARED_RECONCILED)
chars[i++] = YREC;
if (how & CLEARED_FROZEN)
chars[i++] = FREC;
if (how & CLEARED_NO)
chars[i++] = NREC;
if (how & CLEARED_VOIDED)
chars[i++] = VREC;
chars[i] = '\0';
pred_data = qof_query_char_predicate (QOF_CHAR_MATCH_ANY, chars);
if (!pred_data)
return;
param_list = qof_query_build_param_list (SPLIT_RECONCILE, NULL);
qof_query_add_term (q, param_list, pred_data, op);
}
void
xaccQueryAddGUIDMatch(QofQuery * q, const GncGUID *guid,
QofIdType id_type, QofQueryOp op)
{
GSList *param_list = NULL;
if (!q || !guid || !id_type)
return;
if (!g_strcmp0 (id_type, GNC_ID_SPLIT))
param_list = qof_query_build_param_list (QOF_PARAM_GUID, NULL);
else if (!g_strcmp0 (id_type, GNC_ID_TRANS))
param_list = qof_query_build_param_list (SPLIT_TRANS, QOF_PARAM_GUID, NULL);
else if (!g_strcmp0 (id_type, GNC_ID_ACCOUNT))
param_list = qof_query_build_param_list (SPLIT_ACCOUNT, QOF_PARAM_GUID, NULL);
else
PERR ("Invalid match type: %s", id_type);
qof_query_add_guid_match (q, param_list, guid, op);
}
/********************************************************************
* xaccQueryAddClosingTransMatch
* Add a filter that matches book closing entries to an existing query.
********************************************************************/
void
xaccQueryAddClosingTransMatch(QofQuery *q, gboolean value, QofQueryOp op)
{
GSList *param_list;
param_list = qof_query_build_param_list(SPLIT_TRANS, TRANS_IS_CLOSING, NULL);
qof_query_add_boolean_match(q, param_list, value, op);
}
/*******************************************************************
* xaccQueryGetEarliestDateFound
*******************************************************************/
time64
xaccQueryGetEarliestDateFound(QofQuery * q)
{
GList * spl;
Split * sp;
time64 earliest;
if (!q) return 0;
spl = qof_query_last_run (q);
if (!spl) return 0;
/* Safe until 2038 on archs where time64 is 32bit */
sp = spl->data;
earliest = sp->parent->date_posted.tv_sec;
for (; spl; spl = spl->next)
{
sp = spl->data;
if (sp->parent->date_posted.tv_sec < earliest)
{
earliest = sp->parent->date_posted.tv_sec;
}
}
return earliest;
}
/*******************************************************************
* xaccQueryGetLatestDateFound
*******************************************************************/
time64
xaccQueryGetLatestDateFound(QofQuery * q)
{
Split * sp;
GList * spl;
time64 latest = 0;
if (!q) return 0;
spl = qof_query_last_run (q);
if (!spl) return 0;
for (; spl; spl = spl->next)
{
sp = spl->data;
if (sp->parent->date_posted.tv_sec > latest)
{
latest = sp->parent->date_posted.tv_sec;
}
}
return latest;
}
void
xaccQueryAddDescriptionMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
QofQueryCompare h, QofQueryOp o)
{
xaccQueryAddStringMatch ((q), (m), (c), (r), (h), (o), SPLIT_TRANS,
TRANS_DESCRIPTION, NULL);
}
void
xaccQueryAddNotesMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
QofQueryCompare h, QofQueryOp o)
{
xaccQueryAddStringMatch ((q), (m), (c), (r), (h), (o), SPLIT_TRANS,
TRANS_NOTES, NULL);
}
void
xaccQueryAddNumberMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
QofQueryCompare h, QofQueryOp o)
{
xaccQueryAddStringMatch ((q), (m), (c), (r), (h), (o), SPLIT_TRANS,
TRANS_NUM, NULL);
}
void
xaccQueryAddActionMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
QofQueryCompare h, QofQueryOp o)
{
xaccQueryAddStringMatch ((q), (m), (c), (r), (h), (o), SPLIT_ACTION, NULL);
}
void
xaccQueryAddMemoMatch(QofQuery *q, const char *m, gboolean c, gboolean r,
QofQueryCompare h, QofQueryOp o)
{
xaccQueryAddStringMatch ((q), (m), (c), (r), (h), (o), SPLIT_MEMO, NULL);
}
void
xaccQueryAddValueMatch(QofQuery *q, gnc_numeric amt, QofNumericMatch sgn,
QofQueryCompare how, QofQueryOp op)
{
xaccQueryAddNumericMatch ((q), (amt), (sgn), (how), (op),
SPLIT_VALUE, NULL);
}
void
xaccQueryAddSharePriceMatch(QofQuery *q, gnc_numeric amt, QofQueryCompare how,
QofQueryOp op)
{
xaccQueryAddNumericMatch ((q), (amt), QOF_NUMERIC_MATCH_ANY, (how), (op),
SPLIT_SHARE_PRICE, NULL);
}
void
xaccQueryAddSharesMatch(QofQuery *q, gnc_numeric amt, QofQueryCompare how,
QofQueryOp op)
{
xaccQueryAddNumericMatch ((q), (amt), QOF_NUMERIC_MATCH_ANY, (how), (op),
SPLIT_AMOUNT, NULL);
}
void
xaccQueryAddBalanceMatch(QofQuery *q, QofQueryCompare bal, QofQueryOp op)
{
xaccQueryAddNumericMatch(
(q), gnc_numeric_zero(), QOF_NUMERIC_MATCH_ANY,
((bal) ? QOF_COMPARE_EQUAL : QOF_COMPARE_NEQ), (op),
SPLIT_TRANS, TRANS_IMBALANCE, NULL);
}
/* ======================== END OF FILE ======================= */