mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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:
parent
55d3b6c5d8
commit
82e3d5368f
@ -258,7 +258,7 @@ void
|
|||||||
sqlBuild_Set_Double (sqlBuilder *b, const char *tag, double flt)
|
sqlBuild_Set_Double (sqlBuilder *b, const char *tag, double flt)
|
||||||
{
|
{
|
||||||
char buf[120];
|
char buf[120];
|
||||||
snprintf (buf, 120, "%24.18g", flt);
|
snprintf (buf, 120, SQL_DBL_FMT, flt);
|
||||||
sqlBuild_Set_Str (b, tag, buf);
|
sqlBuild_Set_Str (b, tag, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,8 @@
|
|||||||
#include "date.h"
|
#include "date.h"
|
||||||
#include "guid.h"
|
#include "guid.h"
|
||||||
|
|
||||||
|
#define SQL_DBL_FMT "%24.18g"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
SQL_UPDATE = 'm', /* m == modify */
|
SQL_UPDATE = 'm', /* m == modify */
|
||||||
SQL_INSERT = 'a', /* a == add */
|
SQL_INSERT = 'a', /* a == add */
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "builder.h"
|
||||||
#include "escape.h"
|
#include "escape.h"
|
||||||
#include "gnc-engine-util.h"
|
#include "gnc-engine-util.h"
|
||||||
#include "gncquery.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 *
|
const char *
|
||||||
@ -486,22 +729,20 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
GList *il, *jl, *qterms, *andterms;
|
GList *il, *jl, *qterms, *andterms;
|
||||||
QueryTerm *qt;
|
QueryTerm *qt;
|
||||||
PredicateData *pd;
|
PredicateData *pd;
|
||||||
|
GList *tables = NULL;
|
||||||
int more_or = 0;
|
int more_or = 0;
|
||||||
int more_and = 0;
|
int more_and = 0;
|
||||||
int max_rows;
|
int max_rows;
|
||||||
gboolean need_account = FALSE;
|
|
||||||
gboolean need_account_commodity = FALSE;
|
gboolean need_account_commodity = FALSE;
|
||||||
gboolean need_trans_commodity = FALSE;
|
gboolean need_trans_commodity = FALSE;
|
||||||
|
gboolean need_account = FALSE;
|
||||||
gboolean need_entry = FALSE;
|
gboolean need_entry = FALSE;
|
||||||
sort_type_t sorter;
|
sort_type_t sorter;
|
||||||
|
|
||||||
if (!sq || !q || !session) return NULL;
|
if (!sq || !q || !session) return NULL;
|
||||||
|
|
||||||
/* determine whther the query will need to reference the account
|
/* determine whether the query will need to reference certain
|
||||||
* or commodity tables. If it doesn't need them, then we can gain
|
* tables. */
|
||||||
* a significant performance improvement by not specifying them.
|
|
||||||
* The exact reason why this affects performance is a bit of a
|
|
||||||
* mystery to me ... */
|
|
||||||
qterms = xaccQueryGetTerms (q);
|
qterms = xaccQueryGetTerms (q);
|
||||||
|
|
||||||
for (il=qterms; il; il=il->next)
|
for (il=qterms; il; il=il->next)
|
||||||
@ -513,6 +754,7 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
{
|
{
|
||||||
qt = (QueryTerm *)jl->data;
|
qt = (QueryTerm *)jl->data;
|
||||||
pd = &qt->data;
|
pd = &qt->data;
|
||||||
|
|
||||||
switch (pd->base.term_type)
|
switch (pd->base.term_type)
|
||||||
{
|
{
|
||||||
case PR_BALANCE:
|
case PR_BALANCE:
|
||||||
@ -521,6 +763,7 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
case PR_MISC:
|
case PR_MISC:
|
||||||
case PR_NUM:
|
case PR_NUM:
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PR_ACCOUNT:
|
case PR_ACCOUNT:
|
||||||
case PR_ACTION:
|
case PR_ACTION:
|
||||||
case PR_CLEARED:
|
case PR_CLEARED:
|
||||||
@ -528,40 +771,42 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
case PR_PRICE:
|
case PR_PRICE:
|
||||||
need_entry = TRUE;
|
need_entry = TRUE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PR_AMOUNT:
|
case PR_AMOUNT:
|
||||||
need_entry = TRUE;
|
need_entry = TRUE;
|
||||||
need_trans_commodity = TRUE;
|
need_trans_commodity = TRUE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PR_GUID:
|
case PR_GUID:
|
||||||
switch (xaccGUIDType (&pd->guid.guid, session))
|
if (!guid_equal (&pd->guid.guid, xaccGUIDNULL ()))
|
||||||
{
|
{
|
||||||
case GNC_ID_ACCOUNT:
|
need_account = TRUE;
|
||||||
need_account = TRUE;
|
need_entry = 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;
|
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:
|
case PR_SHRS:
|
||||||
need_entry = TRUE;
|
need_entry = TRUE;
|
||||||
need_account_commodity = TRUE;
|
need_account_commodity = TRUE;
|
||||||
need_account = TRUE;
|
need_account = TRUE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* determine whether the requested sort order needs this table */
|
/* determine whether the requested sort order needs these tables */
|
||||||
need_account = need_account || sql_sort_need_account (q);
|
|
||||||
need_entry = need_entry || sql_sort_need_entry (q);
|
need_entry = need_entry || sql_sort_need_entry (q);
|
||||||
|
need_account = need_account || sql_sort_need_account (q);
|
||||||
|
|
||||||
/* reset the buffer pointers */
|
/* reset the buffer pointers */
|
||||||
sq->pq = sq->q_base;
|
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, xaccQueryGetSecondarySortOrder(q));
|
||||||
sq->pq = sql_sort_distinct (sq->pq, xaccQueryGetTertiarySortOrder(q));
|
sq->pq = sql_sort_distinct (sq->pq, xaccQueryGetTertiarySortOrder(q));
|
||||||
|
|
||||||
sq->pq = stpcpy(sq->pq, " FROM gncTransaction");
|
/* add needed explicit tables. postgres can figure out the rest. */
|
||||||
|
|
||||||
/* add additional search tables, as needed for performance */
|
|
||||||
if (need_account)
|
|
||||||
{
|
|
||||||
sq->pq = stpcpy(sq->pq, ", gncAccount");
|
|
||||||
}
|
|
||||||
if (need_account_commodity)
|
if (need_account_commodity)
|
||||||
{
|
tables = g_list_prepend (tables, "gncCommodity account_com");
|
||||||
sq->pq = stpcpy(sq->pq, ", gncCommodity account_com");
|
|
||||||
}
|
|
||||||
if (need_trans_commodity)
|
if (need_trans_commodity)
|
||||||
|
tables = g_list_prepend (tables, "gncCommodity trans_com");
|
||||||
|
|
||||||
|
if (tables)
|
||||||
{
|
{
|
||||||
sq->pq = stpcpy(sq->pq, ", gncCommodity trans_com");
|
GList *node;
|
||||||
}
|
|
||||||
if (need_entry)
|
sq->pq = stpcpy(sq->pq, " FROM ");
|
||||||
{
|
|
||||||
sq->pq = stpcpy(sq->pq, ", gncEntry");
|
for (node = tables; node; node = node->next)
|
||||||
|
{
|
||||||
|
sq->pq = stpcpy(sq->pq, node->data);
|
||||||
|
if (node->next)
|
||||||
|
sq->pq = stpcpy(sq->pq, ", ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sq->pq = stpcpy(sq->pq, " WHERE ");
|
sq->pq = stpcpy(sq->pq, " WHERE ");
|
||||||
if (need_entry)
|
|
||||||
|
if (need_entry || need_account)
|
||||||
{
|
{
|
||||||
sq->pq = stpcpy(sq->pq,
|
sq->pq = stpcpy(sq->pq,
|
||||||
" gncEntry.transGuid = gncTransaction.transGuid AND ");
|
" 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
|
/* 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 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;
|
pd = &qt->data;
|
||||||
switch (pd->base.term_type)
|
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:
|
case PR_ACCOUNT:
|
||||||
{
|
{
|
||||||
int got_more = 0;
|
int got_more = 0;
|
||||||
@ -722,7 +979,8 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
if (pd->date.use_start)
|
if (pd->date.use_start)
|
||||||
{
|
{
|
||||||
sq->pq = stpcpy(sq->pq, "gncTransaction.date_posted >= '");
|
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, "' ");
|
sq->pq = stpcpy(sq->pq, "' ");
|
||||||
}
|
}
|
||||||
if (pd->date.use_end)
|
if (pd->date.use_end)
|
||||||
@ -756,42 +1014,42 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
PINFO("term is PR_GUID");
|
PINFO("term is PR_GUID");
|
||||||
if (0 == pd->guid.sense)
|
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, "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, " (");
|
||||||
|
|
||||||
|
if (guid_equal (&pd->guid.guid, xaccGUIDNULL ()))
|
||||||
{
|
{
|
||||||
sq->pq = stpcpy (sq->pq, ") ");
|
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, "' ");
|
||||||
|
sq->pq = stpcpy(sq->pq, " OR ");
|
||||||
|
|
||||||
|
sq->pq = stpcpy(sq->pq, "gncTransaction.transGuid = '");
|
||||||
|
sq->pq = guid_to_string_buff (&pd->guid.guid, sq->pq);
|
||||||
|
sq->pq = stpcpy(sq->pq, "' ");
|
||||||
|
sq->pq = stpcpy(sq->pq, " OR ");
|
||||||
|
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case PR_KVP:
|
||||||
|
PINFO("term is PR_KVP");
|
||||||
|
sqlQuery_kvp_build (sq, &pd->kvp);
|
||||||
|
break;
|
||||||
|
|
||||||
case PR_MEMO:
|
case PR_MEMO:
|
||||||
PINFO("term is PR_MEMO");
|
PINFO("term is PR_MEMO");
|
||||||
STRING_TERM ("gncEntry.memo");
|
STRING_TERM ("gncEntry.memo");
|
||||||
@ -854,7 +1112,6 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
case PR_SHRS: {
|
case PR_SHRS: {
|
||||||
PINFO("term is PR_SHRS");
|
PINFO("term is PR_SHRS");
|
||||||
sq->pq = stpcpy(sq->pq,
|
sq->pq = stpcpy(sq->pq,
|
||||||
"gncEntry.accountGuid = gncAccount.accountGuid AND "
|
|
||||||
"gncAccount.commodity = account_com.commodity AND ");
|
"gncAccount.commodity = account_com.commodity AND ");
|
||||||
AMOUNT_TERM ("gncEntry.amount","account_com");
|
AMOUNT_TERM ("gncEntry.amount","account_com");
|
||||||
break;
|
break;
|
||||||
@ -871,7 +1128,7 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
if (il->data) sq->pq = stpcpy(sq->pq, ")");
|
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
|
/* 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)
|
if (BY_NONE != sorter)
|
||||||
{
|
{
|
||||||
sq->pq = stpcpy(sq->pq, "ORDER BY ");
|
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);
|
sorter = xaccQueryGetSecondarySortOrder(q);
|
||||||
if (BY_NONE != sorter)
|
if (BY_NONE != sorter)
|
||||||
{
|
{
|
||||||
sq->pq = stpcpy(sq->pq, ", ");
|
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);
|
sorter = xaccQueryGetTertiarySortOrder(q);
|
||||||
if (BY_NONE != sorter)
|
if (BY_NONE != sorter)
|
||||||
{
|
{
|
||||||
sq->pq = stpcpy(sq->pq, ", ");
|
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);
|
max_rows = xaccQueryGetMaxSplits (q);
|
||||||
if (0 <= max_rows)
|
if (0 <= max_rows)
|
||||||
{
|
{
|
||||||
@ -914,4 +1174,3 @@ sqlQuery_build (sqlQuery *sq, Query *q, GNCSession *session)
|
|||||||
|
|
||||||
|
|
||||||
/* ========================== END OF FILE ==================== */
|
/* ========================== END OF FILE ==================== */
|
||||||
|
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <guile/gh.h>
|
#include <guile/gh.h>
|
||||||
|
#include <libpq-fe.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "Backend.h"
|
#include "Backend.h"
|
||||||
|
#include "PostgresBackend.h"
|
||||||
#include "TransLog.h"
|
#include "TransLog.h"
|
||||||
#include "gnc-engine.h"
|
#include "gnc-engine.h"
|
||||||
#include "gnc-engine-util.h"
|
#include "gnc-engine-util.h"
|
||||||
#include "gnc-module.h"
|
#include "gnc-module.h"
|
||||||
#include "gnc-session.h"
|
#include "gnc-session-p.h"
|
||||||
|
#include "gncquery.h"
|
||||||
|
|
||||||
#include "test-stuff.h"
|
#include "test-stuff.h"
|
||||||
#include "test-engine-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);
|
gnc_session_destroy (session_2);
|
||||||
g_free (filename);
|
g_free (filename);
|
||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,6 +450,68 @@ typedef struct
|
|||||||
gint total;
|
gint total;
|
||||||
} QueryTestData;
|
} 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
|
static gboolean
|
||||||
test_trans_query (Transaction *trans, gpointer data)
|
test_trans_query (Transaction *trans, gpointer data)
|
||||||
{
|
{
|
||||||
@ -483,9 +549,15 @@ test_trans_query (Transaction *trans, gpointer data)
|
|||||||
book = gnc_session_get_book (session);
|
book = gnc_session_get_book (session);
|
||||||
group = gnc_book_get_group (book);
|
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);
|
xaccQuerySetGroup (q, group);
|
||||||
|
|
||||||
|
if (!test_raw_query (session, q))
|
||||||
|
{
|
||||||
|
failure ("raw query failed");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
list = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);
|
list = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);
|
||||||
if (g_list_length (list) != 1)
|
if (g_list_length (list) != 1)
|
||||||
{
|
{
|
||||||
|
@ -1548,82 +1548,116 @@ trans_query_include_price (gboolean include_price_in)
|
|||||||
include_price = 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 *
|
Query *
|
||||||
make_trans_query (Transaction *trans)
|
make_trans_query (Transaction *trans, TestQueryTypes query_types)
|
||||||
{
|
{
|
||||||
Account *a;
|
Account *a;
|
||||||
double d;
|
double d;
|
||||||
Query *q;
|
Query *q;
|
||||||
Split *s;
|
Split *s;
|
||||||
|
|
||||||
|
if (query_types == RANDOM_QT)
|
||||||
|
query_types = get_random_query_type ();
|
||||||
|
|
||||||
q = xaccMallocQuery ();
|
q = xaccMallocQuery ();
|
||||||
|
|
||||||
s = xaccTransGetSplit (trans, 0);
|
s = xaccTransGetSplit (trans, 0);
|
||||||
a = xaccSplitGetAccount (s);
|
a = xaccSplitGetAccount (s);
|
||||||
|
|
||||||
xaccQueryAddSingleAccountMatch (q, xaccSplitGetAccount (s), QUERY_AND);
|
if (query_types & SIMPLE_QT)
|
||||||
|
|
||||||
xaccQueryAddDescriptionMatch (q, xaccTransGetDescription (trans),
|
|
||||||
TRUE, FALSE, QUERY_AND);
|
|
||||||
|
|
||||||
xaccQueryAddNumberMatch (q, xaccTransGetNum (trans), 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,
|
|
||||||
AMT_MATCH_EXACTLY, QUERY_AND);
|
|
||||||
|
|
||||||
d = gnc_numeric_to_double (xaccSplitGetAmount (s));
|
|
||||||
DxaccQueryAddSharesMatch (q, d, AMT_MATCH_EXACTLY, QUERY_AND);
|
|
||||||
|
|
||||||
if (include_price)
|
|
||||||
{
|
{
|
||||||
d = gnc_numeric_to_double (xaccSplitGetSharePrice (s));
|
xaccQueryAddSingleAccountMatch (q, xaccSplitGetAccount (s), QUERY_AND);
|
||||||
DxaccQueryAddSharePriceMatch (q, d, AMT_MATCH_EXACTLY, QUERY_AND);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
xaccQueryAddDescriptionMatch (q, xaccTransGetDescription (trans),
|
||||||
Timespec ts;
|
TRUE, FALSE, QUERY_AND);
|
||||||
|
|
||||||
xaccTransGetDatePostedTS (trans, &ts);
|
xaccQueryAddNumberMatch (q, xaccTransGetNum (trans),
|
||||||
xaccQueryAddDateMatchTS (q, TRUE, ts, TRUE, ts, QUERY_AND);
|
TRUE, FALSE, QUERY_AND);
|
||||||
}
|
|
||||||
|
|
||||||
xaccQueryAddMemoMatch (q, xaccSplitGetMemo (s), TRUE, FALSE, QUERY_AND);
|
xaccQueryAddActionMatch (q, xaccSplitGetAction (s),
|
||||||
|
TRUE, FALSE, QUERY_AND);
|
||||||
|
|
||||||
{
|
d = gnc_numeric_to_double (xaccSplitGetValue (s));
|
||||||
cleared_match_t how;
|
DxaccQueryAddAmountMatch (q, d, AMT_SGN_MATCH_EITHER,
|
||||||
|
AMT_MATCH_EXACTLY, QUERY_AND);
|
||||||
|
|
||||||
switch (xaccSplitGetReconcile (s))
|
d = gnc_numeric_to_double (xaccSplitGetAmount (s));
|
||||||
|
DxaccQueryAddSharesMatch (q, d, AMT_MATCH_EXACTLY, QUERY_AND);
|
||||||
|
|
||||||
|
if (include_price)
|
||||||
{
|
{
|
||||||
case NREC:
|
d = gnc_numeric_to_double (xaccSplitGetSharePrice (s));
|
||||||
how = CLEARED_NO;
|
DxaccQueryAddSharePriceMatch (q, d, AMT_MATCH_EXACTLY, QUERY_AND);
|
||||||
break;
|
|
||||||
case CREC:
|
|
||||||
how = CLEARED_CLEARED;
|
|
||||||
break;
|
|
||||||
case YREC:
|
|
||||||
how = CLEARED_RECONCILED;
|
|
||||||
break;
|
|
||||||
case FREC:
|
|
||||||
how = CLEARED_FROZEN;
|
|
||||||
break;
|
|
||||||
case VREC:
|
|
||||||
how = CLEARED_VOIDED;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
failure ("bad reconcile flag");
|
|
||||||
xaccFreeQuery (q);
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xaccQueryAddClearedMatch (q, how, QUERY_AND);
|
{
|
||||||
|
Timespec ts;
|
||||||
|
|
||||||
|
xaccTransGetDatePostedTS (trans, &ts);
|
||||||
|
xaccQueryAddDateMatchTS (q, TRUE, ts, TRUE, ts, QUERY_AND);
|
||||||
|
}
|
||||||
|
|
||||||
|
xaccQueryAddMemoMatch (q, xaccSplitGetMemo (s), TRUE, FALSE, QUERY_AND);
|
||||||
|
|
||||||
|
{
|
||||||
|
cleared_match_t how;
|
||||||
|
|
||||||
|
switch (xaccSplitGetReconcile (s))
|
||||||
|
{
|
||||||
|
case NREC:
|
||||||
|
how = CLEARED_NO;
|
||||||
|
break;
|
||||||
|
case CREC:
|
||||||
|
how = CLEARED_CLEARED;
|
||||||
|
break;
|
||||||
|
case YREC:
|
||||||
|
how = CLEARED_RECONCILED;
|
||||||
|
break;
|
||||||
|
case FREC:
|
||||||
|
how = CLEARED_FROZEN;
|
||||||
|
break;
|
||||||
|
case VREC:
|
||||||
|
how = CLEARED_VOIDED;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
failure ("bad reconcile flag");
|
||||||
|
xaccFreeQuery (q);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
xaccQueryAddClearedMatch (q, how, QUERY_AND);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
add_kvp_query (q, xaccSplitGetSlots (s), KVP_MATCH_SPLIT);
|
if (query_types & GUID_QT)
|
||||||
add_kvp_query (q, xaccTransGetSlots (trans), KVP_MATCH_TRANS);
|
{
|
||||||
add_kvp_query (q, xaccAccountGetSlots (a), KVP_MATCH_ACCOUNT);
|
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;
|
return q;
|
||||||
}
|
}
|
||||||
|
@ -51,8 +51,20 @@ Transaction* get_random_transaction_with_currency(GNCSession *session,
|
|||||||
gnc_commodity* get_random_commodity(GNCSession *session);
|
gnc_commodity* get_random_commodity(GNCSession *session);
|
||||||
const char *get_random_commodity_namespace(void);
|
const char *get_random_commodity_namespace(void);
|
||||||
|
|
||||||
Query* get_random_query(void);
|
typedef enum
|
||||||
Query * make_trans_query (Transaction *trans);
|
{
|
||||||
|
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);
|
void trans_query_include_price (gboolean include_amounts);
|
||||||
|
|
||||||
GNCBook * get_random_book (GNCSession *session);
|
GNCBook * get_random_book (GNCSession *session);
|
||||||
|
@ -22,7 +22,7 @@ test_trans_query (Transaction *trans, gpointer data)
|
|||||||
book = gnc_session_get_book (session);
|
book = gnc_session_get_book (session);
|
||||||
group = gnc_book_get_group (book);
|
group = gnc_book_get_group (book);
|
||||||
|
|
||||||
q = make_trans_query (trans);
|
q = make_trans_query (trans, ALL_QT);
|
||||||
xaccQuerySetGroup (q, group);
|
xaccQuerySetGroup (q, group);
|
||||||
|
|
||||||
list = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);
|
list = xaccQueryGetTransactions (q, QUERY_MATCH_ANY);
|
||||||
|
Loading…
Reference in New Issue
Block a user