Implement kvp queries in postgres backend.

Fix PR_GUID queries in postgres backend.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@5804 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Dave Peticolas 2001-11-09 11:50:29 +00:00
parent 55d3b6c5d8
commit 82e3d5368f
7 changed files with 512 additions and 133 deletions

View File

@ -258,7 +258,7 @@ void
sqlBuild_Set_Double (sqlBuilder *b, const char *tag, double flt)
{
char buf[120];
snprintf (buf, 120, "%24.18g", flt);
snprintf (buf, 120, SQL_DBL_FMT, flt);
sqlBuild_Set_Str (b, tag, buf);
}

View File

@ -41,6 +41,8 @@
#include "date.h"
#include "guid.h"
#define SQL_DBL_FMT "%24.18g"
typedef enum {
SQL_UPDATE = 'm', /* m == modify */
SQL_INSERT = 'a', /* a == add */

View File

@ -41,6 +41,7 @@
#include <glib.h>
#include <string.h>
#include "builder.h"
#include "escape.h"
#include "gnc-engine-util.h"
#include "gncquery.h"
@ -478,6 +479,248 @@ sql_sort_need_entry (Query *q)
} \
}
/* =========================================================== */
static const char *
kvp_table_name (kvp_value_t value_t)
{
switch (value_t)
{
case KVP_TYPE_GINT64:
return "gnckvpvalue_int64";
case KVP_TYPE_DOUBLE:
return "gnckvpvalue_dbl";
case KVP_TYPE_NUMERIC:
return "gnckvpvalue_numeric";
case KVP_TYPE_STRING:
return "gnckvpvalue_str";
case KVP_TYPE_GUID:
return "gnckvpvalue_guid";
default:
PWARN ("kvp value not supported");
return NULL;
}
}
static char *
kvp_path_name (GSList *path)
{
GString *s = g_string_new (NULL);
char *name;
for ( ; path; path = path->next)
{
g_string_append_c (s, '/');
g_string_append (s, path->data);
}
name = s->str;
g_string_free (s, FALSE);
return name;
}
static const char *
kvp_op_name (kvp_match_t how)
{
switch (how)
{
case KVP_MATCH_LT:
return " < ";
case KVP_MATCH_LTE:
return " <= ";
case KVP_MATCH_EQ:
return " = ";
case KVP_MATCH_GTE:
return " >= ";
case KVP_MATCH_GT:
return " > ";
default:
return NULL;
}
}
static char *
kvp_left_operand (kvp_value *value)
{
kvp_value_t value_t;
const char *kvptable;
char *operand;
g_return_val_if_fail (value, NULL);
value_t = kvp_value_get_type (value);
kvptable = kvp_table_name (value_t);
switch (value_t)
{
case KVP_TYPE_GINT64:
case KVP_TYPE_DOUBLE:
case KVP_TYPE_GUID:
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);
}
default:
return NULL;
}
}
static char *
kvp_right_operand (sqlQuery *sq, kvp_value *value)
{
kvp_value_t value_t;
const char *kvptable;
char *operand;
g_return_val_if_fail (value, NULL);
value_t = kvp_value_get_type (value);
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));
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_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)
{
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, " 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, " AND ");
sq->pq = stpcpy (sq->pq, left);
sq->pq = stpcpy (sq->pq, op);
sq->pq = stpcpy (sq->pq, right);
sq->pq = stpcpy (sq->pq, " ) ");
}
static void
sqlQuery_kvp_build (sqlQuery *sq, KVPPredicateData *kpd)
{
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);
if (!(kpd->where & (KVP_MATCH_SPLIT | KVP_MATCH_TRANS | KVP_MATCH_ACCOUNT)))
return;
value_t = kvp_value_get_type (kpd->value);
if (value_t == KVP_TYPE_GUID && kpd->how != KVP_MATCH_EQ)
{
PWARN ("guid non-equality comparison not supported");
return;
}
kvptable = kvp_table_name (value_t);
if (!kvptable)
return;
path = kvp_path_name (kpd->path);
op = kvp_op_name (kpd->how);
left = kvp_left_operand (kpd->value);
right = kvp_right_operand (sq, kpd->value);
list = NULL;
if (kpd->where & KVP_MATCH_SPLIT)
list = g_list_prepend (list, "gncEntry");
if (kpd->where & KVP_MATCH_TRANS)
list = g_list_prepend (list, "gncTransaction");
if (kpd->where & KVP_MATCH_ACCOUNT)
list = g_list_prepend (list, "gncAccount");
if (!kpd->sense)
sq->pq = stpcpy (sq->pq, "NOT ");
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, "'");
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, " ) ");
g_free (path);
g_free (left);
g_free (right);
g_list_free (list);
}
/* =========================================================== */
const char *
@ -486,22 +729,20 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
GList *il, *jl, *qterms, *andterms;
QueryTerm *qt;
PredicateData *pd;
GList *tables = NULL;
int more_or = 0;
int more_and = 0;
int max_rows;
gboolean need_account = FALSE;
gboolean need_account_commodity = FALSE;
gboolean need_trans_commodity = FALSE;
gboolean need_account = FALSE;
gboolean need_entry = FALSE;
sort_type_t sorter;
if (!sq || !q || !session) return NULL;
/* determine whther the query will need to reference the account
* or commodity tables. If it doesn't need them, then we can gain
* a significant performance improvement by not specifying them.
* The exact reason why this affects performance is a bit of a
* mystery to me ... */
/* determine whether the query will need to reference certain
* tables. */
qterms = xaccQueryGetTerms (q);
for (il=qterms; il; il=il->next)
@ -513,6 +754,7 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
{
qt = (QueryTerm *)jl->data;
pd = &qt->data;
switch (pd->base.term_type)
{
case PR_BALANCE:
@ -521,6 +763,7 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
case PR_MISC:
case PR_NUM:
break;
case PR_ACCOUNT:
case PR_ACTION:
case PR_CLEARED:
@ -528,40 +771,42 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
case PR_PRICE:
need_entry = TRUE;
break;
case PR_AMOUNT:
need_entry = TRUE;
need_trans_commodity = TRUE;
break;
case PR_GUID:
switch (xaccGUIDType (&pd->guid.guid, session))
if (!guid_equal (&pd->guid.guid, xaccGUIDNULL ()))
{
case GNC_ID_ACCOUNT:
need_account = TRUE;
break;
case GNC_ID_SPLIT:
need_entry = TRUE;
break;
case GNC_ID_NONE:
case GNC_ID_NULL:
case GNC_ID_TRANS:
default:
break;
}
break;
case PR_KVP:
if (pd->kvp.where & KVP_MATCH_SPLIT)
need_entry = TRUE;
if (pd->kvp.where & KVP_MATCH_ACCOUNT)
need_account = TRUE;
break;
case PR_SHRS:
need_entry = TRUE;
need_account_commodity = TRUE;
need_account = TRUE;
break;
default:
break;
}
}
}
/* determine whether the requested sort order needs this table */
need_account = need_account || sql_sort_need_account (q);
/* 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;
@ -573,33 +818,42 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
sq->pq = sql_sort_distinct (sq->pq, xaccQueryGetSecondarySortOrder(q));
sq->pq = sql_sort_distinct (sq->pq, xaccQueryGetTertiarySortOrder(q));
sq->pq = stpcpy(sq->pq, " FROM gncTransaction");
/* add additional search tables, as needed for performance */
if (need_account)
{
sq->pq = stpcpy(sq->pq, ", gncAccount");
}
/* add needed explicit tables. postgres can figure out the rest. */
if (need_account_commodity)
{
sq->pq = stpcpy(sq->pq, ", gncCommodity account_com");
}
tables = g_list_prepend (tables, "gncCommodity account_com");
if (need_trans_commodity)
tables = g_list_prepend (tables, "gncCommodity trans_com");
if (tables)
{
sq->pq = stpcpy(sq->pq, ", gncCommodity trans_com");
}
if (need_entry)
GList *node;
sq->pq = stpcpy(sq->pq, " FROM ");
for (node = tables; node; node = node->next)
{
sq->pq = stpcpy(sq->pq, ", gncEntry");
sq->pq = stpcpy(sq->pq, node->data);
if (node->next)
sq->pq = stpcpy(sq->pq, ", ");
}
}
sq->pq = stpcpy(sq->pq, " WHERE ");
if (need_entry)
if (need_entry || need_account)
{
sq->pq = stpcpy(sq->pq,
" gncEntry.transGuid = gncTransaction.transGuid AND ");
}
sq->pq = stpcpy(sq->pq, " ( ");
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
@ -631,6 +885,9 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
pd = &qt->data;
switch (pd->base.term_type)
{
/* FIXME: this doesn't correctly implement ACCT_MATCH_ALL or
* ACCT_MATCH_ANY for account lists with more than one
* account. */
case PR_ACCOUNT:
{
int got_more = 0;
@ -722,7 +979,8 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
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 = gnc_timespec_to_iso8601_buff (pd->date.start,
sq->pq);
sq->pq = stpcpy(sq->pq, "' ");
}
if (pd->date.use_end)
@ -756,41 +1014,41 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
PINFO("term is PR_GUID");
if (0 == pd->guid.sense)
{
sq->pq = stpcpy (sq->pq, "NOT (");
sq->pq = stpcpy (sq->pq, "NOT ");
}
switch (xaccGUIDType (&pd->guid.guid, session))
{
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, " (");
if (guid_equal (&pd->guid.guid, xaccGUIDNULL ()))
{
sq->pq = stpcpy(sq->pq, "FALSE ");
}
else
{
sq->pq = stpcpy(sq->pq, "gncAccount.accountGuid = '");
sq->pq = guid_to_string_buff (&pd->guid.guid, sq->pq);
sq->pq = stpcpy(sq->pq, "' ");
break;
sq->pq = stpcpy(sq->pq, " OR ");
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;
sq->pq = stpcpy(sq->pq, " OR ");
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, "' ");
}
sq->pq = stpcpy (sq->pq, ") ");
break;
}
if (0 == pd->guid.sense)
{
sq->pq = stpcpy (sq->pq, ") ");
}
case PR_KVP:
PINFO("term is PR_KVP");
sqlQuery_kvp_build (sq, &pd->kvp);
break;
}
case PR_MEMO:
PINFO("term is PR_MEMO");
@ -854,7 +1112,6 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
case PR_SHRS: {
PINFO("term is PR_SHRS");
sq->pq = stpcpy(sq->pq,
"gncEntry.accountGuid = gncAccount.accountGuid AND "
"gncAccount.commodity = account_com.commodity AND ");
AMOUNT_TERM ("gncEntry.amount","account_com");
break;
@ -871,7 +1128,7 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
if (il->data) sq->pq = stpcpy(sq->pq, ")");
}
sq->pq = stpcpy(sq->pq, ")");
sq->pq = stpcpy(sq->pq, ") ");
/* ---------------------------------------------------- */
/* implement sorting order as well; bad sorts lead to bad data
@ -881,25 +1138,28 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
if (BY_NONE != sorter)
{
sq->pq = stpcpy(sq->pq, "ORDER BY ");
sq->pq = sql_sort_order (sq->pq, sorter, xaccQueryGetSortPrimaryIncreasing (q));
sq->pq = sql_sort_order (sq->pq, sorter,
xaccQueryGetSortPrimaryIncreasing (q));
sorter = xaccQueryGetSecondarySortOrder(q);
if (BY_NONE != sorter)
{
sq->pq = stpcpy(sq->pq, ", ");
sq->pq = sql_sort_order (sq->pq, sorter, xaccQueryGetSortSecondaryIncreasing (q));
sq->pq = sql_sort_order (sq->pq, sorter,
xaccQueryGetSortSecondaryIncreasing (q));
sorter = xaccQueryGetTertiarySortOrder(q);
if (BY_NONE != sorter)
{
sq->pq = stpcpy(sq->pq, ", ");
sq->pq = sql_sort_order (sq->pq, sorter, xaccQueryGetSortTertiaryIncreasing (q));
sq->pq = sql_sort_order (sq->pq, sorter,
xaccQueryGetSortTertiaryIncreasing (q));
}
}
}
/* ---------------------------------------------------- */
/* limit the query result to a finite numbe of rows */
/* limit the query result to a finite number of rows */
max_rows = xaccQueryGetMaxSplits (q);
if (0 <= max_rows)
{
@ -914,4 +1174,3 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
/* ========================== END OF FILE ==================== */

View File

@ -1,16 +1,19 @@
#include <glib.h>
#include <guile/gh.h>
#include <libpq-fe.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "Backend.h"
#include "PostgresBackend.h"
#include "TransLog.h"
#include "gnc-engine.h"
#include "gnc-engine-util.h"
#include "gnc-module.h"
#include "gnc-session.h"
#include "gnc-session-p.h"
#include "gncquery.h"
#include "test-stuff.h"
#include "test-engine-stuff.h"
@ -407,6 +410,7 @@ test_updates (GNCSession *session, const char *db_name, const char *mode,
gnc_session_destroy (session_2);
g_free (filename);
return TRUE;
}
@ -446,6 +450,68 @@ typedef struct
gint total;
} QueryTestData;
static Query *
make_little_trans_query (Transaction *trans)
{
Query *q;
q = xaccMallocQuery ();
xaccQueryAddDescriptionMatch (q, xaccTransGetDescription (trans),
TRUE, FALSE, QUERY_AND);
return q;
}
static gboolean
test_raw_query (GNCSession *session, Query *q)
{
const char *sql_query_string;
PGresult *result;
PGBackend *be;
sqlQuery *sq;
gboolean ok;
g_return_val_if_fail (session && q, FALSE);
be = (PGBackend *) session->backend;
sq = sqlQuery_new();
sql_query_string = sqlQuery_build (sq, q, session);
#if 0
fputs (sql_query_string, stderr);
fputs ("\n", stderr);
#endif
result = PQexec (be->connection, sql_query_string);
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));
}
if (ok)
{
success ("raw query succeeded");
}
PQclear (result);
sql_Query_destroy (sq);
return ok;
}
static gboolean
test_trans_query (Transaction *trans, gpointer data)
{
@ -483,9 +549,15 @@ test_trans_query (Transaction *trans, gpointer data)
book = gnc_session_get_book (session);
group = gnc_book_get_group (book);
q = make_trans_query (trans);
q = make_trans_query (trans, get_random_query_type () | GUID_QT);
xaccQuerySetGroup (q, group);
if (!test_raw_query (session, q))
{
failure ("raw query failed");
return FALSE;
}
list = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);
if (g_list_length (list) != 1)
{

View File

@ -1548,27 +1548,48 @@ trans_query_include_price (gboolean include_price_in)
include_price = include_price_in;
}
TestQueryTypes
get_random_query_type (void)
{
switch (get_random_int_in_range (0, 4))
{
case 0: return SIMPLE_QT;
case 1: return SPLIT_KVP_QT;
case 2: return TRANS_KVP_QT;
case 3: return ACCOUNT_KVP_QT;
case 4: return GUID_QT;
default: return SIMPLE_QT;
}
}
Query *
make_trans_query (Transaction *trans)
make_trans_query (Transaction *trans, TestQueryTypes query_types)
{
Account *a;
double d;
Query *q;
Split *s;
if (query_types == RANDOM_QT)
query_types = get_random_query_type ();
q = xaccMallocQuery ();
s = xaccTransGetSplit (trans, 0);
a = xaccSplitGetAccount (s);
if (query_types & SIMPLE_QT)
{
xaccQueryAddSingleAccountMatch (q, xaccSplitGetAccount (s), QUERY_AND);
xaccQueryAddDescriptionMatch (q, xaccTransGetDescription (trans),
TRUE, FALSE, QUERY_AND);
xaccQueryAddNumberMatch (q, xaccTransGetNum (trans), TRUE, FALSE, QUERY_AND);
xaccQueryAddNumberMatch (q, xaccTransGetNum (trans),
TRUE, FALSE, QUERY_AND);
xaccQueryAddActionMatch (q, xaccSplitGetAction (s), TRUE, FALSE, QUERY_AND);
xaccQueryAddActionMatch (q, xaccSplitGetAction (s),
TRUE, FALSE, QUERY_AND);
d = gnc_numeric_to_double (xaccSplitGetValue (s));
DxaccQueryAddAmountMatch (q, d, AMT_SGN_MATCH_EITHER,
@ -1620,9 +1641,22 @@ make_trans_query (Transaction *trans)
xaccQueryAddClearedMatch (q, how, QUERY_AND);
}
}
if (query_types & GUID_QT)
{
xaccQueryAddGUIDMatch (q, xaccSplitGetGUID (s), QUERY_AND);
xaccQueryAddGUIDMatch (q, xaccTransGetGUID (trans), QUERY_AND);
xaccQueryAddGUIDMatch (q, xaccAccountGetGUID (a), QUERY_AND);
}
if (query_types & SPLIT_KVP_QT)
add_kvp_query (q, xaccSplitGetSlots (s), KVP_MATCH_SPLIT);
if (query_types & TRANS_KVP_QT)
add_kvp_query (q, xaccTransGetSlots (trans), KVP_MATCH_TRANS);
if (query_types & ACCOUNT_KVP_QT)
add_kvp_query (q, xaccAccountGetSlots (a), KVP_MATCH_ACCOUNT);
return q;

View File

@ -51,8 +51,20 @@ Transaction* get_random_transaction_with_currency(GNCSession *session,
gnc_commodity* get_random_commodity(GNCSession *session);
const char *get_random_commodity_namespace(void);
Query* get_random_query(void);
Query * make_trans_query (Transaction *trans);
typedef enum
{
RANDOM_QT = 0,
SIMPLE_QT = 1 << 0,
SPLIT_KVP_QT = 1 << 1,
TRANS_KVP_QT = 1 << 2,
ACCOUNT_KVP_QT = 1 << 3,
GUID_QT = 1 << 4,
ALL_QT = (1 << 8) - 1
} TestQueryTypes;
Query * get_random_query(void);
Query * make_trans_query (Transaction *trans, TestQueryTypes query_types);
TestQueryTypes get_random_query_type (void);
void trans_query_include_price (gboolean include_amounts);
GNCBook * get_random_book (GNCSession *session);

View File

@ -22,7 +22,7 @@ test_trans_query (Transaction *trans, gpointer data)
book = gnc_session_get_book (session);
group = gnc_book_get_group (book);
q = make_trans_query (trans);
q = make_trans_query (trans, ALL_QT);
xaccQuerySetGroup (q, group);
list = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);