/********************************************************************\ * qof_query.c -- Implement predicate API for searching for objects * * Copyright (C) 2002 Derek Atkins * * * * 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 * * * \********************************************************************/ extern "C" { #include #include #include #include #include #include } #include "qof.h" #include "qof-backend.hpp" #include "qofbook-p.h" #include "qofclass-p.h" #include "qofquery-p.h" #include "qofquerycore-p.h" static QofLogModule log_module = QOF_MOD_QUERY; struct _QofQueryTerm { QofQueryParamList * param_list; QofQueryPredData *pdata; gboolean invert; /* These values are filled in during "compilation" of the query * term, based upon the obj_name, param_name, and searched-for * object type. If conv_fcn is NULL, then we don't know how to * convert types. */ GSList * param_fcns; QofQueryPredicateFunc pred_fcn; }; struct _QofQuerySort { QofQueryParamList * param_list; gint options; gboolean increasing; /* These values are filled in during "compilation" of the query * term, based upon the obj_name, param_name, and searched-for * object type. If conv_fcn is NULL, then we don't know how to * convert types. */ gboolean use_default; GSList * param_fcns; /* Chain of parameters to walk */ QofSortFunc obj_cmp; /* In case you are comparing objects */ QofCompareFunc comp_fcn; /* When you are comparing core types */ }; /* The QUERY structure */ struct _QofQuery { /* The object type that we're searching for */ QofIdType search_for; /* terms is a list of the OR-terms in a sum-of-products * logical expression. */ GList * terms; /* sorting and chopping is independent of the search filter */ QofQuerySort primary_sort; QofQuerySort secondary_sort; QofQuerySort tertiary_sort; QofSortFunc defaultSort; /* <- Computed from search_for */ /* The maximum number of results to return */ gint max_results; /* list of books that will be participating in the query */ GList * books; /* a map of book to backend-compiled queries */ GHashTable* be_compiled; /* cache the results so we don't have to run the whole search * again until it's really necessary */ gint changed; GList * results; }; typedef struct _QofQueryCB { QofQuery * query; GList * list; gint count; } QofQueryCB; /* initial_term will be owned by the new Query */ static void query_init (QofQuery *q, QofQueryTerm *initial_term) { GList * _or_ = NULL; GList *_and_ = NULL; GHashTable *ht; if (initial_term) { _or_ = g_list_alloc (); _and_ = g_list_alloc (); _and_->data = initial_term; _or_->data = _and_; } if (q->terms) qof_query_clear (q); g_list_free (q->results); g_list_free (q->books); g_slist_free (q->primary_sort.param_list); g_slist_free (q->secondary_sort.param_list); g_slist_free (q->tertiary_sort.param_list); g_slist_free (q->primary_sort.param_fcns); g_slist_free (q->secondary_sort.param_fcns); g_slist_free (q->tertiary_sort.param_fcns); ht = q->be_compiled; memset (q, 0, sizeof (*q)); q->be_compiled = ht; q->terms = _or_; q->changed = 1; q->max_results = -1; q->primary_sort.param_list = g_slist_prepend (static_cast(NULL), static_cast(const_cast(QUERY_DEFAULT_SORT))); q->primary_sort.increasing = TRUE; q->secondary_sort.increasing = TRUE; q->tertiary_sort.increasing = TRUE; } static void swap_terms (QofQuery *q1, QofQuery *q2) { GList *g; if (!q1 || !q2) return; g = q1->terms; q1->terms = q2->terms; q2->terms = g; g = q1->books; q1->books = q2->books; q2->books = g; q1->changed = 1; q2->changed = 1; } static void free_query_term (QofQueryTerm *qt) { if (!qt) return; qof_query_core_predicate_free (qt->pdata); g_slist_free (qt->param_list); g_slist_free (qt->param_fcns); g_free (qt); } static QofQueryTerm * copy_query_term (const QofQueryTerm *qt) { QofQueryTerm *new_qt; if (!qt) return NULL; new_qt = g_new0 (QofQueryTerm, 1); memcpy (new_qt, qt, sizeof(QofQueryTerm)); new_qt->param_list = g_slist_copy (qt->param_list); new_qt->param_fcns = g_slist_copy (qt->param_fcns); new_qt->pdata = qof_query_core_predicate_copy (qt->pdata); return new_qt; } static GList * copy_and_terms (const GList *and_terms) { GList *_and_ = NULL; const GList *cur_and; for (cur_and = and_terms; cur_and; cur_and = cur_and->next) { _and_ = g_list_prepend(_and_, copy_query_term (static_cast(cur_and->data))); } return g_list_reverse(_and_); } static GList * copy_or_terms(const GList * or_terms) { GList * _or_ = NULL; const GList * cur_or; for (cur_or = or_terms; cur_or; cur_or = cur_or->next) { _or_ = g_list_prepend(_or_, copy_and_terms(static_cast(cur_or->data))); } return g_list_reverse(_or_); } static void copy_sort (QofQuerySort *dst, const QofQuerySort *src) { memcpy (dst, src, sizeof (*dst)); dst->param_list = g_slist_copy (src->param_list); dst->param_fcns = g_slist_copy (src->param_fcns); } static void free_sort (QofQuerySort *s) { g_slist_free (s->param_list); s->param_list = NULL; g_slist_free (s->param_fcns); s->param_fcns = NULL; } static void free_members (QofQuery *q) { GList * cur_or; if (q == NULL) return; for (cur_or = q->terms; cur_or; cur_or = cur_or->next) { GList * cur_and; for (cur_and = static_cast(cur_or->data); cur_and; cur_and = static_cast(cur_and->next)) { free_query_term(static_cast(cur_and->data)); cur_and->data = NULL; } g_list_free(static_cast(cur_or->data)); cur_or->data = NULL; } free_sort (&(q->primary_sort)); free_sort (&(q->secondary_sort)); free_sort (&(q->tertiary_sort)); g_list_free(q->terms); q->terms = NULL; g_list_free(q->books); q->books = NULL; g_list_free(q->results); q->results = NULL; } static int cmp_func (const QofQuerySort *sort, QofSortFunc default_sort, const gconstpointer a, const gconstpointer b) { QofParam *param = NULL; GSList *node; gpointer conva, convb; g_return_val_if_fail (sort, 0); /* See if this is a default sort */ if (sort->use_default) { if (default_sort) return default_sort (a, b); return 0; } /* If no parameters, consider them equal */ if (!sort->param_fcns) return 0; /* no compare function, consider the two objects equal */ if (!sort->comp_fcn && !sort->obj_cmp) return 0; /* Do the list of conversions */ conva = (gpointer)a; convb = (gpointer)b; for (node = static_cast(sort->param_fcns); node; node = static_cast(node->next)) { param = static_cast(node->data); /* The last term is really the "parameter getter", * unless we're comparing objects ;) */ if (!node->next && !sort->obj_cmp) break; /* Do the conversions */ conva = (param->param_getfcn) (conva, param); convb = (param->param_getfcn) (convb, param); } /* And now return the (appropriate) compare */ if (sort->comp_fcn) { int rc = sort->comp_fcn (conva, convb, sort->options, param); return rc; } return sort->obj_cmp (conva, convb); } static int sort_func (const gconstpointer a, const gconstpointer b, const gpointer q) { int retval; const QofQuery *sortQuery = static_cast(q); g_return_val_if_fail (sortQuery, 0); retval = cmp_func (&(sortQuery->primary_sort), sortQuery->defaultSort, a, b); if (retval == 0) { retval = cmp_func (&(sortQuery->secondary_sort), sortQuery->defaultSort, a, b); if (retval == 0) { retval = cmp_func (&(sortQuery->tertiary_sort), sortQuery->defaultSort, a, b); return sortQuery->tertiary_sort.increasing ? retval : -retval; } else { return sortQuery->secondary_sort.increasing ? retval : -retval; } } else { return sortQuery->primary_sort.increasing ? retval : -retval; } } /* ==================================================================== */ /* This is the main workhorse for performing the query. For each * object, it walks over all of the query terms to see if the * object passes the seive. */ static int check_object (const QofQuery *q, gpointer object) { const GList * and_ptr; const GList * or_ptr; const QofQueryTerm * qt; int and_terms_ok = 1; for (or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next) { and_terms_ok = 1; for (and_ptr = static_cast(or_ptr->data); and_ptr; and_ptr = static_cast(and_ptr->next)) { qt = (QofQueryTerm *)(and_ptr->data); if (qt->param_fcns && qt->pred_fcn) { const GSList *node; QofParam *param = NULL; gpointer conv_obj = object; /* iterate through the conversions */ for (node = qt->param_fcns; node; node = node->next) { param = static_cast(node->data); /* The last term is the actual parameter getter */ if (!node->next) break; conv_obj = param->param_getfcn (conv_obj, param); } if (((qt->pred_fcn)(conv_obj, param, qt->pdata)) == qt->invert) { and_terms_ok = 0; break; } } else { /* XXX: Don't know how to do this conversion -- do we care? */ } } if (and_terms_ok) { return 1; } } /* If there are no terms, assume a "match any" applies. * A query with no terms is still meaningful, since the user * may want to get all objects, but in a particular sorted * order. */ if (NULL == q->terms) return 1; return 0; } /* walk the list of parameters, starting with the given object, and * compile the list of parameter get-functions. Save the last valid * parameter definition in "final" and return the list of functions. * * returns NULL if the first parameter is bad (and final is unchanged). */ static GSList * compile_params (QofQueryParamList *param_list, QofIdType start_obj, QofParam const **final) { const QofParam *objDef = NULL; GSList *fcns = NULL; ENTER ("param_list=%p id=%s", param_list, start_obj); g_return_val_if_fail (param_list, NULL); g_return_val_if_fail (start_obj, NULL); g_return_val_if_fail (final, NULL); for (; param_list; param_list = param_list->next) { QofIdType param_name = static_cast(param_list->data); objDef = qof_class_get_parameter (start_obj, param_name); /* If it doesn't exist, then we've reached the end */ if (!objDef) break; /* Save off this parameter */ fcns = g_slist_prepend (fcns, (gpointer) objDef); /* Save this off, just in case */ *final = objDef; /* And reset for the next parameter */ start_obj = (QofIdType) objDef->param_type; } LEAVE ("fcns=%p", fcns); return (g_slist_reverse (fcns)); } static void compile_sort (QofQuerySort *sort, QofIdType obj) { const QofParam *resObj = NULL; ENTER ("sort=%p id=%s params=%p", sort, obj, sort->param_list); sort->use_default = FALSE; g_slist_free (sort->param_fcns); sort->param_fcns = NULL; sort->comp_fcn = NULL; sort->obj_cmp = NULL; /* An empty param_list implies "no sort" */ if (!sort->param_list) { LEAVE (" "); return; } /* Walk the parameter list of obtain the parameter functions */ sort->param_fcns = compile_params (sort->param_list, obj, &resObj); /* If we have valid parameters, grab the compare function, * If not, check if this is the default sort. */ if (sort->param_fcns) { /* First, check if this parameter has a sort function override. * if not then check if there's a global compare function for the type */ if (resObj->param_compfcn) sort->comp_fcn = resObj->param_compfcn; else sort->comp_fcn = qof_query_core_get_compare (resObj->param_type); /* Next, perhaps this is an object compare, not a core type compare? */ if (sort->comp_fcn == NULL) sort->obj_cmp = qof_class_get_default_sort (resObj->param_type); } else if (!g_strcmp0 (static_cast(sort->param_list->data), QUERY_DEFAULT_SORT)) { sort->use_default = TRUE; } LEAVE ("sort=%p id=%s", sort, obj); } static void compile_terms (QofQuery *q) { GList *or_ptr, *and_ptr, *node; ENTER (" query=%p", q); /* Find the specific functions for this Query. Note that the * Query's search_for should now be set to the new type. */ for (or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next) { for (and_ptr = static_cast(or_ptr->data); and_ptr; and_ptr = static_cast(and_ptr->next)) { QofQueryTerm* qt = static_cast(and_ptr->data); const QofParam* resObj = NULL; g_slist_free (qt->param_fcns); qt->param_fcns = NULL; /* Walk the parameter list of obtain the parameter functions */ qt->param_fcns = compile_params (qt->param_list, q->search_for, &resObj); /* If we have valid parameters, grab the predicate function, * If not, see if this is the default sort. */ if (qt->param_fcns) qt->pred_fcn = qof_query_core_get_predicate (resObj->param_type); else qt->pred_fcn = NULL; } } /* Update the sort functions */ compile_sort (&(q->primary_sort), q->search_for); compile_sort (&(q->secondary_sort), q->search_for); compile_sort (&(q->tertiary_sort), q->search_for); q->defaultSort = qof_class_get_default_sort (q->search_for); #ifdef QOF_BACKEND_QUERY /* Now compile the backend instances */ for (node = q->books; node; node = node->next) { QofBook* book = static_cast(node->data); QofBackend* be = book->backend; if (be && be->compile_query) { gpointer result = (be->compile_query)(be, q); if (result) g_hash_table_insert (q->be_compiled, book, result); } } #endif LEAVE (" query=%p", q); } static void check_item_cb (gpointer object, gpointer user_data) { QofQueryCB* ql = static_cast(user_data); if (!object || !ql) return; if (check_object (ql->query, object)) { ql->list = g_list_prepend (ql->list, object); ql->count++; } return; } static int param_list_cmp (const QofQueryParamList *l1, const QofQueryParamList *l2) { int ret; while (1) { /* Check the easy stuff */ if (!l1 && !l2) return 0; if (!l1 && l2) return -1; if (l1 && !l2) return 1; ret = g_strcmp0 (static_cast(l1->data), static_cast(l2->data)); if (ret) break; l1 = l1->next; l2 = l2->next; } return ret; } static GList * merge_books (GList *l1, GList *l2) { GList *res = NULL; GList *node; res = g_list_copy (l1); for (node = l2; node; node = node->next) { if (g_list_index (res, node->data) == -1) res = g_list_prepend (res, node->data); } return res; } static gboolean query_free_compiled (gpointer key, gpointer value, gpointer not_used) { #ifdef QOF_BACKEND_QUERY QofBook* book = static_cast(key); QofBackend* be = book->backend; if (be && be->free_query) (be->free_query)(be, value); #endif return TRUE; } /* clear out any cached query_compilations */ static void query_clear_compiles (QofQuery *q) { g_hash_table_foreach_remove (q->be_compiled, query_free_compiled, NULL); } /********************************************************************/ /* PUBLISHED API FUNCTIONS */ QofQueryParamList * qof_query_build_param_list (char const *param, ...) { QofQueryParamList *param_list = NULL; char const *this_param; va_list ap; if (!param) return NULL; va_start (ap, param); for (this_param = param; this_param; this_param = va_arg (ap, const char *)) param_list = g_slist_prepend (param_list, (gpointer)this_param); va_end (ap); return g_slist_reverse (param_list); } void qof_query_add_term (QofQuery *q, QofQueryParamList *param_list, QofQueryPredData *pred_data, QofQueryOp op) { QofQueryTerm *qt; QofQuery *qr, *qs; if (!q || !param_list || !pred_data) return; qt = g_new0 (QofQueryTerm, 1); qt->param_list = param_list; qt->pdata = pred_data; qs = qof_query_create (); query_init (qs, qt); if (qof_query_has_terms (q)) qr = qof_query_merge (q, qs, op); else qr = qof_query_merge (q, qs, QOF_QUERY_OR); swap_terms (q, qr); qof_query_destroy (qs); qof_query_destroy (qr); } void qof_query_purge_terms (QofQuery *q, QofQueryParamList *param_list) { QofQueryTerm *qt; GList *_or_, *_and_; if (!q || !param_list) return; for (_or_ = q->terms; _or_; _or_ = _or_->next) { for (_and_ = static_cast(_or_->data); _and_; _and_ = static_cast(_and_->next)) { qt = static_cast(_and_->data); if (!param_list_cmp (qt->param_list, param_list)) { if (g_list_length (static_cast(_or_->data)) == 1) { q->terms = g_list_remove_link (static_cast(q->terms), _or_); g_list_free_1 (_or_); _or_ = q->terms; break; } else { _or_->data = g_list_remove_link (static_cast(_or_->data), _and_); g_list_free_1 (_and_); _and_ = static_cast(_or_->data); if (!_and_) break; } q->changed = 1; free_query_term (qt); } } if (!_or_) break; } } static GList * qof_query_run_internal (QofQuery *q, void(*run_cb)(QofQueryCB*, gpointer), gpointer cb_arg) { GList *matching_objects = NULL; int object_count = 0; if (!q) return NULL; g_return_val_if_fail (q->search_for, NULL); g_return_val_if_fail (q->books, NULL); g_return_val_if_fail (run_cb, NULL); ENTER (" q=%p", q); /* XXX: Prioritize the query terms? */ /* prepare the Query for processing */ if (q->changed) { query_clear_compiles (q); compile_terms (q); } /* Maybe log this sucker */ if (qof_log_check (log_module, QOF_LOG_DEBUG)) qof_query_print (q); /* Now run the query over all the objects and save the results */ { QofQueryCB qcb; memset (&qcb, 0, sizeof (qcb)); qcb.query = q; /* Run the query callback */ run_cb(&qcb, cb_arg); matching_objects = qcb.list; object_count = qcb.count; } PINFO ("matching objects=%p count=%d", matching_objects, object_count); /* There is no absolute need to reverse this list, since it's being * sorted below. However, in the common case, we will be searching * in a confined location where the objects are already in order, * thus reversing will put us in the correct order we want and make * the sorting go much faster. */ matching_objects = g_list_reverse(matching_objects); /* Now sort the matching objects based on the search criteria */ if (q->primary_sort.comp_fcn || q->primary_sort.obj_cmp || (q->primary_sort.use_default && q->defaultSort)) { matching_objects = g_list_sort_with_data(matching_objects, sort_func, q); } /* Crop the list to limit the number of splits. */ if ((object_count > q->max_results) && (q->max_results > -1)) { if (q->max_results > 0) { GList *mptr; /* mptr is set to the first node of what will be the new list */ mptr = g_list_nth(matching_objects, object_count - q->max_results); /* mptr should not be NULL, but let's be safe */ if (mptr != NULL) { if (mptr->prev != NULL) mptr->prev->next = NULL; mptr->prev = NULL; } g_list_free(matching_objects); matching_objects = mptr; } else { /* q->max_results == 0 */ g_list_free(matching_objects); matching_objects = NULL; } object_count = q->max_results; } q->changed = 0; g_list_free(q->results); q->results = matching_objects; LEAVE (" q=%p", q); return matching_objects; } static void qof_query_run_cb(QofQueryCB* qcb, gpointer cb_arg) { GList *node; (void)cb_arg; /* unused */ g_return_if_fail(qcb); for (node = qcb->query->books; node; node = node->next) { QofBook* book = static_cast(node->data); #ifdef QOF_BACKEND_QUERY QofBackend* be = book->backend; if (be) { gpointer compiled_query = g_hash_table_lookup (qcb->query->be_compiled, book); if (compiled_query && be->run_query) { (be->run_query) (be, compiled_query); } } #endif /* And then iterate over all the objects */ qof_object_foreach (qcb->query->search_for, book, (QofInstanceForeachCB) check_item_cb, qcb); } } GList * qof_query_run (QofQuery *q) { /* Just a wrapper */ return qof_query_run_internal(q, qof_query_run_cb, NULL); } static void qof_query_run_subq_cb(QofQueryCB* qcb, gpointer cb_arg) { QofQuery* pq = static_cast(cb_arg); g_return_if_fail(pq); g_list_foreach(qof_query_last_run(pq), check_item_cb, qcb); } GList * qof_query_run_subquery (QofQuery *subq, const QofQuery* primaryq) { if (!subq) return NULL; if (!primaryq) return NULL; /* Make sure we're searching for the same thing */ g_return_val_if_fail (subq->search_for, NULL); g_return_val_if_fail (primaryq->search_for, NULL); g_return_val_if_fail(!g_strcmp0(subq->search_for, primaryq->search_for), NULL); /* Perform the subquery */ return qof_query_run_internal(subq, qof_query_run_subq_cb, (gpointer)primaryq); } GList * qof_query_last_run (QofQuery *query) { if (!query) return NULL; return query->results; } void qof_query_clear (QofQuery *query) { QofQuery *q2 = qof_query_create (); swap_terms (query, q2); qof_query_destroy (q2); g_list_free (query->books); query->books = NULL; g_list_free (query->results); query->results = NULL; query->changed = 1; } QofQuery * qof_query_create (void) { QofQuery *qp = g_new0 (QofQuery, 1); qp->be_compiled = g_hash_table_new (g_direct_hash, g_direct_equal); query_init (qp, NULL); return qp; } void qof_query_search_for (QofQuery *q, QofIdTypeConst obj_type) { if (!q || !obj_type) return; if (g_strcmp0 (q->search_for, obj_type)) { q->search_for = (QofIdType) obj_type; q->changed = 1; } } QofQuery * qof_query_create_for (QofIdTypeConst obj_type) { QofQuery *q; if (!obj_type) return NULL; q = qof_query_create (); qof_query_search_for (q, obj_type); return q; } int qof_query_has_terms (QofQuery *q) { if (!q) return 0; return g_list_length (q->terms); } int qof_query_num_terms (QofQuery *q) { GList *o; int n = 0; if (!q) return 0; for (o = q->terms; o; o = o->next) n += g_list_length(static_cast(o->data)); return n; } gboolean qof_query_has_term_type (QofQuery *q, QofQueryParamList *term_param) { GList *_or_; GList *_and_; if (!q || !term_param) return FALSE; for (_or_ = q->terms; _or_; _or_ = _or_->next) { for (_and_ = static_cast(_or_->data); _and_; _and_ = static_cast(_and_->next)) { QofQueryTerm* qt = static_cast(_and_->data); if (!param_list_cmp (term_param, qt->param_list)) return TRUE; } } return FALSE; } GSList * qof_query_get_term_type (QofQuery *q, QofQueryParamList *term_param) { GList *_or_; GList *_and_; GSList *results = NULL; if (!q || !term_param) return FALSE; for (_or_ = q->terms; _or_; _or_ = _or_->next) { for (_and_ = static_cast(_or_->data); _and_; _and_ = static_cast(_and_->next)) { QofQueryTerm *qt = static_cast(_and_->data); if (!param_list_cmp (term_param, qt->param_list)) results = g_slist_append(results, qt->pdata); } } return results; } void qof_query_destroy (QofQuery *q) { if (!q) return; free_members (q); query_clear_compiles (q); g_hash_table_destroy (q->be_compiled); g_free (q); } QofQuery * qof_query_copy (QofQuery *q) { QofQuery *copy; GHashTable *ht; if (!q) return NULL; copy = qof_query_create (); ht = copy->be_compiled; free_members (copy); memcpy (copy, q, sizeof (QofQuery)); copy->be_compiled = ht; copy->terms = copy_or_terms (q->terms); copy->books = g_list_copy (q->books); copy->results = g_list_copy (q->results); copy_sort (&(copy->primary_sort), &(q->primary_sort)); copy_sort (&(copy->secondary_sort), &(q->secondary_sort)); copy_sort (&(copy->tertiary_sort), &(q->tertiary_sort)); copy->changed = 1; return copy; } /* ******************************************************************* * qof_query_invert * return a newly-allocated Query object which is the * logical inverse of the original. ********************************************************************/ QofQuery * qof_query_invert (QofQuery *q) { QofQuery * retval; QofQuery * right, * left, * iright, * ileft; QofQueryTerm * qt; GList * aterms; GList * cur; GList * new_oterm; int num_or_terms; if (!q) return NULL; num_or_terms = g_list_length(q->terms); switch (num_or_terms) { case 0: retval = qof_query_create(); retval->max_results = q->max_results; break; /* This is the DeMorgan expansion for a single AND expression. */ /* !(abc) = !a + !b + !c */ case 1: retval = qof_query_create(); retval->max_results = q->max_results; retval->books = g_list_copy (q->books); retval->search_for = q->search_for; retval->changed = 1; aterms = static_cast(g_list_nth_data(q->terms, 0)); new_oterm = NULL; for (cur = aterms; cur; cur = cur->next) { qt = copy_query_term(static_cast(cur->data)); qt->invert = !(qt->invert); new_oterm = g_list_append(NULL, qt); retval->terms = g_list_prepend(retval->terms, new_oterm); } retval->terms = g_list_reverse(retval->terms); break; /* If there are multiple OR-terms, we just recurse by * breaking it down to !(a + b + c) = * !a * !(b + c) = !a * !b * !c. */ default: right = qof_query_create(); right->terms = copy_or_terms(g_list_nth(q->terms, 1)); left = qof_query_create(); left->terms = g_list_append(NULL, copy_and_terms(static_cast(g_list_nth_data(q->terms, 0)))); iright = qof_query_invert(right); ileft = qof_query_invert(left); retval = qof_query_merge(iright, ileft, QOF_QUERY_AND); retval->books = g_list_copy (q->books); retval->max_results = q->max_results; retval->search_for = q->search_for; retval->changed = 1; qof_query_destroy(iright); qof_query_destroy(ileft); qof_query_destroy(right); qof_query_destroy(left); break; } return retval; } /* ******************************************************************* * qof_query_merge * combine 2 Query objects by the logical operation in "op". ********************************************************************/ QofQuery * qof_query_merge(QofQuery *q1, QofQuery *q2, QofQueryOp op) { QofQuery * retval = NULL; QofQuery * i1, * i2; QofQuery * t1, * t2; GList * i, * j; QofIdType search_for; if (!q1) return q2; if (!q2) return q1; if (q1->search_for && q2->search_for) g_return_val_if_fail (g_strcmp0 (q1->search_for, q2->search_for) == 0, NULL); search_for = (q1->search_for ? q1->search_for : q2->search_for); /* Avoid merge surprises if op==QOF_QUERY_AND but q1 is empty. * The goal of this tweak is to allow the user to start with * an empty q1 and then append to it recursively * (and q1 (and q2 (and q3 (and q4 ....)))) * without bombing out because the append started with an * empty list. * We do essentially the same check in qof_query_add_term() * so that the first term added to an empty query doesn't screw up. */ if ((QOF_QUERY_AND == op) && ( (0 == qof_query_has_terms (q1)) || (0 == qof_query_has_terms (q2)) )) { op = QOF_QUERY_OR; } switch (op) { case QOF_QUERY_OR: retval = qof_query_create(); retval->terms = g_list_concat(copy_or_terms(q1->terms), copy_or_terms(q2->terms)); retval->books = merge_books (q1->books, q2->books); retval->max_results = q1->max_results; retval->changed = 1; break; case QOF_QUERY_AND: retval = qof_query_create(); retval->books = merge_books (q1->books, q2->books); retval->max_results = q1->max_results; retval->changed = 1; /* g_list_append() can take forever, so let's build the list in * reverse and then reverse it at the end, to deal better with * "large" queries. */ for (i = q1->terms; i; i = i->next) { for (j = q2->terms; j; j = j->next) { retval->terms = g_list_prepend(retval->terms, g_list_concat (copy_and_terms(static_cast(i->data)), copy_and_terms(static_cast(j->data)))); } } retval->terms = g_list_reverse(retval->terms); break; case QOF_QUERY_NAND: /* !(a*b) = (!a + !b) */ i1 = qof_query_invert(q1); i2 = qof_query_invert(q2); retval = qof_query_merge(i1, i2, QOF_QUERY_OR); qof_query_destroy(i1); qof_query_destroy(i2); break; case QOF_QUERY_NOR: /* !(a+b) = (!a*!b) */ i1 = qof_query_invert(q1); i2 = qof_query_invert(q2); retval = qof_query_merge(i1, i2, QOF_QUERY_AND); qof_query_destroy(i1); qof_query_destroy(i2); break; case QOF_QUERY_XOR: /* a xor b = (a * !b) + (!a * b) */ i1 = qof_query_invert(q1); i2 = qof_query_invert(q2); t1 = qof_query_merge(q1, i2, QOF_QUERY_AND); t2 = qof_query_merge(i1, q2, QOF_QUERY_AND); retval = qof_query_merge(t1, t2, QOF_QUERY_OR); qof_query_destroy(i1); qof_query_destroy(i2); qof_query_destroy(t1); qof_query_destroy(t2); break; } retval->search_for = search_for; return retval; } void qof_query_merge_in_place(QofQuery *q1, QofQuery *q2, QofQueryOp op) { QofQuery *tmp_q; if (!q1 || !q2) return; tmp_q = qof_query_merge (q1, q2, op); swap_terms (q1, tmp_q); qof_query_destroy (tmp_q); } void qof_query_set_sort_order (QofQuery *q, QofQueryParamList *params1, QofQueryParamList *params2, QofQueryParamList *params3) { if (!q) return; if (q->primary_sort.param_list) g_slist_free (q->primary_sort.param_list); q->primary_sort.param_list = params1; q->primary_sort.options = 0; if (q->secondary_sort.param_list) g_slist_free (q->secondary_sort.param_list); q->secondary_sort.param_list = params2; q->secondary_sort.options = 0; if (q->tertiary_sort.param_list) g_slist_free (q->tertiary_sort.param_list); q->tertiary_sort.param_list = params3; q->tertiary_sort.options = 0; q->changed = 1; } void qof_query_set_sort_options (QofQuery *q, gint prim_op, gint sec_op, gint tert_op) { if (!q) return; q->primary_sort.options = prim_op; q->secondary_sort.options = sec_op; q->tertiary_sort.options = tert_op; } void qof_query_set_sort_increasing (QofQuery *q, gboolean prim_inc, gboolean sec_inc, gboolean tert_inc) { if (!q) return; q->primary_sort.increasing = prim_inc; q->secondary_sort.increasing = sec_inc; q->tertiary_sort.increasing = tert_inc; } void qof_query_set_max_results (QofQuery *q, int n) { if (!q) return; q->max_results = n; } void qof_query_add_guid_list_match (QofQuery *q, QofQueryParamList *param_list, GList *guid_list, QofGuidMatch options, QofQueryOp op) { QofQueryPredData *pdata; if (!q || !param_list) return; if (!guid_list) g_return_if_fail (options == QOF_GUID_MATCH_NULL); pdata = qof_query_guid_predicate (options, guid_list); qof_query_add_term (q, param_list, pdata, op); } void qof_query_add_guid_match (QofQuery *q, QofQueryParamList *param_list, const GncGUID *guid, QofQueryOp op) { GList *g = NULL; if (!q || !param_list) return; if (guid) g = g_list_prepend (g, (gpointer)guid); qof_query_add_guid_list_match (q, param_list, g, g ? QOF_GUID_MATCH_ANY : QOF_GUID_MATCH_NULL, op); g_list_free (g); } void qof_query_set_book (QofQuery *q, QofBook *book) { QofQueryParamList *slist = NULL; if (!q || !book) return; /* Make sure this book is only in the list once */ if (g_list_index (q->books, book) == -1) q->books = g_list_prepend (q->books, book); slist = g_slist_prepend (slist, static_cast(const_cast(QOF_PARAM_GUID))); slist = g_slist_prepend (slist, static_cast(const_cast(QOF_PARAM_BOOK))); qof_query_add_guid_match (q, slist, qof_instance_get_guid(book), QOF_QUERY_AND); } GList * qof_query_get_books (QofQuery *q) { if (!q) return NULL; return q->books; } void qof_query_add_boolean_match (QofQuery *q, QofQueryParamList *param_list, gboolean value, QofQueryOp op) { QofQueryPredData *pdata; if (!q || !param_list) return; pdata = qof_query_boolean_predicate (QOF_COMPARE_EQUAL, value); qof_query_add_term (q, param_list, pdata, op); } /**********************************************************************/ /* PRIVATE PUBLISHED API FUNCTIONS */ void qof_query_init (void) { ENTER (" "); qof_query_core_init (); qof_class_init (); LEAVE ("Completed initialization of QofQuery"); } void qof_query_shutdown (void) { qof_class_shutdown (); qof_query_core_shutdown (); } int qof_query_get_max_results (const QofQuery *q) { if (!q) return 0; return q->max_results; } QofIdType qof_query_get_search_for (const QofQuery *q) { if (!q) return NULL; return q->search_for; } GList * qof_query_get_terms (const QofQuery *q) { if (!q) return NULL; return q->terms; } QofQueryParamList * qof_query_term_get_param_path (const QofQueryTerm *qt) { if (!qt) return NULL; return qt->param_list; } QofQueryPredData *qof_query_term_get_pred_data (const QofQueryTerm *qt) { if (!qt) return NULL; return qt->pdata; } gboolean qof_query_term_is_inverted (const QofQueryTerm *qt) { if (!qt) return FALSE; return qt->invert; } void qof_query_get_sorts (QofQuery *q, QofQuerySort **primary, QofQuerySort **secondary, QofQuerySort **tertiary) { if (!q) return; if (primary) *primary = &(q->primary_sort); if (secondary) *secondary = &(q->secondary_sort); if (tertiary) *tertiary = &(q->tertiary_sort); } QofQueryParamList * qof_query_sort_get_param_path (const QofQuerySort *qs) { if (!qs) return NULL; return qs->param_list; } gint qof_query_sort_get_sort_options (const QofQuerySort *qs) { if (!qs) return 0; return qs->options; } gboolean qof_query_sort_get_increasing (const QofQuerySort *qs) { if (!qs) return FALSE; return qs->increasing; } static gboolean qof_query_term_equal (const QofQueryTerm *qt1, const QofQueryTerm *qt2) { if (qt1 == qt2) return TRUE; if (!qt1 || !qt2) return FALSE; if (qt1->invert != qt2->invert) return FALSE; if (param_list_cmp (qt1->param_list, qt2->param_list)) return FALSE; return qof_query_core_predicate_equal (qt1->pdata, qt2->pdata); } static gboolean qof_query_sort_equal (const QofQuerySort* qs1, const QofQuerySort* qs2) { if (qs1 == qs2) return TRUE; if (!qs1 || !qs2) return FALSE; /* "Empty" sorts are equivalent, regardless of the flags */ if (!qs1->param_list && !qs2->param_list) return TRUE; if (qs1->options != qs2->options) return FALSE; if (qs1->increasing != qs2->increasing) return FALSE; return (param_list_cmp (qs1->param_list, qs2->param_list) == 0); } gboolean qof_query_equal (const QofQuery *q1, const QofQuery *q2) { GList *or1, *or2; if (q1 == q2) return TRUE; if (!q1 || !q2) return FALSE; if (g_list_length (q1->terms) != g_list_length (q2->terms)) return FALSE; if (q1->max_results != q2->max_results) return FALSE; for (or1 = q1->terms, or2 = q2->terms; or1; or1 = or1->next, or2 = or2->next) { GList *and1, *and2; and1 = static_cast(or1->data); and2 = static_cast(or2->data); if (g_list_length (and1) != g_list_length (and2)) return FALSE; for ( ; and1; and1 = and1->next, and2 = and2->next) if (!qof_query_term_equal (static_cast(and1->data), static_cast(and2->data))) return FALSE; } if (!qof_query_sort_equal (&(q1->primary_sort), &(q2->primary_sort))) return FALSE; if (!qof_query_sort_equal (&(q1->secondary_sort), &(q2->secondary_sort))) return FALSE; if (!qof_query_sort_equal (&(q1->tertiary_sort), &(q2->tertiary_sort))) return FALSE; return TRUE; } /* **************************************************************************/ /* Query Print functions for use with qof_log_set_level. */ /* Static prototypes */ static GList *qof_query_printSearchFor (QofQuery * query, GList * output); static GList *qof_query_printTerms (QofQuery * query, GList * output); static GList *qof_query_printSorts (QofQuerySort *s[], const gint numSorts, GList * output); static GList *qof_query_printAndTerms (GList * terms, GList * output); static const char *qof_query_printStringForHow (QofQueryCompare how); static const char *qof_query_printStringMatch (QofStringMatch s); static const char *qof_query_printDateMatch (QofDateMatch d); static const char *qof_query_printNumericMatch (QofNumericMatch n); static const char *qof_query_printGuidMatch (QofGuidMatch g); static const char *qof_query_printCharMatch (QofCharMatch c); static GList *qof_query_printPredData (QofQueryPredData *pd, GList *lst); static GString *qof_query_printParamPath (QofQueryParamList * parmList); static void qof_query_printValueForParam (QofQueryPredData *pd, GString * gs); static void qof_query_printOutput (GList * output); /** \deprecated access via qof_log instead. The query will be logged automatically if qof_log_set_default or qof_log_set_level(QOF_MOD_QUERY, ...) are set to QOF_LOG_DEBUG or higher. This function cycles through a QofQuery object, and prints out the values of the various members of the query */ void qof_query_print (QofQuery * query) { GList *output; GString *str; QofQuerySort *s[3]; gint maxResults = 0, numSorts = 3; ENTER (" "); if (!query) { LEAVE("query is (null)"); return; } output = NULL; str = NULL; maxResults = qof_query_get_max_results (query); output = qof_query_printSearchFor (query, output); output = qof_query_printTerms (query, output); qof_query_get_sorts (query, &s[0], &s[1], &s[2]); if (s[0]) { output = qof_query_printSorts (s, numSorts, output); } str = g_string_new (" "); g_string_printf (str, "Maximum number of results: %d", maxResults); output = g_list_append (output, str); qof_query_printOutput (output); LEAVE (" "); } static void qof_query_printOutput (GList * output) { GList *lst; for (lst = output; lst; lst = lst->next) { GString *line = (GString *) lst->data; DEBUG (" %s", line->str); g_string_free (line, TRUE); line = NULL; } } /* Get the search_for type--This is the type of Object we are searching for (SPLIT, TRANS, etc) */ static GList * qof_query_printSearchFor (QofQuery * query, GList * output) { QofIdType searchFor; GString *gs; searchFor = qof_query_get_search_for (query); gs = g_string_new ("Query Object Type: "); g_string_append (gs, (NULL == searchFor) ? "(null)" : searchFor); output = g_list_append (output, gs); return output; } /* qof_query_printSearchFor */ /* Run through the terms of the query. This is a outer-inner loop. The elements of the outer loop are ORed, and the elements of the inner loop are ANDed. */ static GList * qof_query_printTerms (QofQuery * query, GList * output) { GList *terms, *lst; terms = qof_query_get_terms (query); for (lst = terms; lst; lst = lst->next) { output = g_list_append (output, g_string_new ("OR and AND Terms:")); if (lst->data) { output = qof_query_printAndTerms (static_cast(lst->data), output); } else { output = g_list_append (output, g_string_new (" No data for AND terms")); } } return output; } /* qof_query_printTerms */ /* Process the sort parameters If this function is called, the assumption is that the first sort not null. */ static GList * qof_query_printSorts (QofQuerySort *s[], const gint numSorts, GList * output) { QofQueryParamList *gsl, *n = NULL; gint curSort; GString *gs = g_string_new ("Sort Parameters: "); for (curSort = 0; curSort < numSorts; curSort++) { gboolean increasing; if (!s[curSort]) { break; } increasing = qof_query_sort_get_increasing (s[curSort]); gsl = qof_query_sort_get_param_path (s[curSort]); if (gsl) g_string_append_printf (gs, " Param: "); for (n = gsl; n; n = n->next) { QofIdType param_name = static_cast(n->data); if (gsl != n) g_string_append_printf (gs, " "); g_string_append_printf (gs, "%s", param_name); } if (gsl) { g_string_append_printf (gs, " %s ", increasing ? "DESC" : "ASC"); g_string_append_printf (gs, " Options: 0x%x ", s[curSort]->options); } } output = g_list_append (output, gs); return output; } /* qof_query_printSorts */ /* Process the AND terms of the query. This is a GList of WHERE terms that will be ANDed */ static GList * qof_query_printAndTerms (GList * terms, GList * output) { const char *prefix = "AND Terms:"; QofQueryTerm *qt; QofQueryPredData *pd; QofQueryParamList *path; GList *lst; gboolean invert; output = g_list_append (output, g_string_new (prefix)); for (lst = terms; lst; lst = lst->next) { qt = (QofQueryTerm *) lst->data; pd = qof_query_term_get_pred_data (qt); path = qof_query_term_get_param_path (qt); invert = qof_query_term_is_inverted (qt); if (invert) output = g_list_append (output, g_string_new(" INVERT SENSE ")); output = g_list_append (output, qof_query_printParamPath (path)); output = qof_query_printPredData (pd, output); // output = g_list_append (output, g_string_new(" ")); } return output; } /* qof_query_printAndTerms */ /* Process the parameter types of the predicate data */ static GString * qof_query_printParamPath (QofQueryParamList * parmList) { QofQueryParamList *list = NULL; GString *gs = g_string_new ("Param List: "); g_string_append (gs, " "); for (list = parmList; list; list = list->next) { g_string_append (gs, (gchar *) list->data); if (list->next) g_string_append (gs, "->"); } return gs; } /* qof_query_printParamPath */ /* Process the PredData of the AND terms */ static GList * qof_query_printPredData (QofQueryPredData *pd, GList *lst) { GString *gs; gs = g_string_new ("Pred Data: "); g_string_append (gs, (gchar *) pd->type_name); /* Char Predicate and GncGUID predicate don't use the 'how' field. */ if (g_strcmp0 (pd->type_name, QOF_TYPE_CHAR) && g_strcmp0 (pd->type_name, QOF_TYPE_GUID)) { g_string_append_printf (gs, " how: %s", qof_query_printStringForHow (pd->how)); } lst = g_list_append(lst, gs); gs = g_string_new (""); qof_query_printValueForParam (pd, gs); lst = g_list_append(lst, gs); return lst; } /* qof_query_printPredData */ /* Get a string representation for the QofCompareFunc enum type. */ static const char * qof_query_printStringForHow (QofQueryCompare how) { switch (how) { case QOF_COMPARE_LT: return "QOF_COMPARE_LT"; case QOF_COMPARE_LTE: return "QOF_COMPARE_LTE"; case QOF_COMPARE_EQUAL: return "QOF_COMPARE_EQUAL"; case QOF_COMPARE_GT: return "QOF_COMPARE_GT"; case QOF_COMPARE_GTE: return "QOF_COMPARE_GTE"; case QOF_COMPARE_NEQ: return "QOF_COMPARE_NEQ"; case QOF_COMPARE_CONTAINS: return "QOF_COMPARE_CONTAINS"; case QOF_COMPARE_NCONTAINS: return "QOF_COMPARE_NCONTAINS"; } return "INVALID HOW"; } /* qncQueryPrintStringForHow */ static void qof_query_printValueForParam (QofQueryPredData *pd, GString * gs) { if (!g_strcmp0 (pd->type_name, QOF_TYPE_GUID)) { GList *node; query_guid_t pdata = (query_guid_t) pd; g_string_append_printf (gs, "Match type %s", qof_query_printGuidMatch (pdata->options)); for (node = pdata->guids; node; node = node->next) { gchar guidstr[GUID_ENCODING_LENGTH+1]; guid_to_string_buff ((GncGUID *) node->data,guidstr); g_string_append_printf (gs, ", guids: %s",guidstr); } return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_STRING)) { query_string_t pdata = (query_string_t) pd; g_string_append_printf (gs, " Match type %s", qof_query_printStringMatch (pdata->options)); g_string_append_printf (gs, " %s string: %s", pdata->is_regex ? "Regex" : "Not regex", pdata->matchstring); return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_NUMERIC)) { query_numeric_t pdata = (query_numeric_t) pd; g_string_append_printf (gs, " Match type %s", qof_query_printNumericMatch (pdata->options)); g_string_append_printf (gs, " gnc_numeric: %s", gnc_num_dbg_to_string (pdata->amount)); return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_INT64)) { query_int64_t pdata = (query_int64_t) pd; g_string_append_printf (gs, " int64: %" G_GINT64_FORMAT, pdata->val); return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_INT32)) { query_int32_t pdata = (query_int32_t) pd; g_string_append_printf (gs, " int32: %d", pdata->val); return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_DOUBLE)) { query_double_t pdata = (query_double_t) pd; g_string_append_printf (gs, " double: %.18g", pdata->val); return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_DATE)) { query_date_t pdata = (query_date_t) pd; char datebuff[MAX_DATE_LENGTH + 1]; memset (datebuff, 0, sizeof(datebuff)); qof_print_date_buff (datebuff, sizeof(datebuff), pdata->date); g_string_append_printf (gs, " Match type %s", qof_query_printDateMatch (pdata->options)); g_string_append_printf (gs, " query_date: %s", datebuff); return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_CHAR)) { query_char_t pdata = (query_char_t) pd; g_string_append_printf (gs, " Match type %s", qof_query_printCharMatch (pdata->options)); g_string_append_printf (gs, " char list: %s", pdata->char_list); return; } if (!g_strcmp0 (pd->type_name, QOF_TYPE_BOOLEAN)) { query_boolean_t pdata = (query_boolean_t) pd; g_string_append_printf (gs, " boolean: %s", pdata->val ? "TRUE" : "FALSE"); return; } /** \todo QOF_TYPE_COLLECT */ return; } /* qof_query_printValueForParam */ /* * Print out a string representation of the * QofStringMatch enum */ static const char * qof_query_printStringMatch (QofStringMatch s) { switch (s) { case QOF_STRING_MATCH_NORMAL: return "QOF_STRING_MATCH_NORMAL"; case QOF_STRING_MATCH_CASEINSENSITIVE: return "QOF_STRING_MATCH_CASEINSENSITIVE"; } return "UNKNOWN MATCH TYPE"; } /* qof_query_printStringMatch */ /* * Print out a string representation of the * QofDateMatch enum */ static const char * qof_query_printDateMatch (QofDateMatch d) { switch (d) { case QOF_DATE_MATCH_NORMAL: return "QOF_DATE_MATCH_NORMAL"; case QOF_DATE_MATCH_DAY: return "QOF_DATE_MATCH_DAY"; } return "UNKNOWN MATCH TYPE"; } /* qof_query_printDateMatch */ /* * Print out a string representation of the * QofNumericMatch enum */ static const char * qof_query_printNumericMatch (QofNumericMatch n) { switch (n) { case QOF_NUMERIC_MATCH_DEBIT: return "QOF_NUMERIC_MATCH_DEBIT"; case QOF_NUMERIC_MATCH_CREDIT: return "QOF_NUMERIC_MATCH_CREDIT"; case QOF_NUMERIC_MATCH_ANY: return "QOF_NUMERIC_MATCH_ANY"; } return "UNKNOWN MATCH TYPE"; } /* qof_query_printNumericMatch */ /* * Print out a string representation of the * QofGuidMatch enum */ static const char * qof_query_printGuidMatch (QofGuidMatch g) { switch (g) { case QOF_GUID_MATCH_ANY: return "QOF_GUID_MATCH_ANY"; case QOF_GUID_MATCH_ALL: return "QOF_GUID_MATCH_ALL"; case QOF_GUID_MATCH_NONE: return "QOF_GUID_MATCH_NONE"; case QOF_GUID_MATCH_NULL: return "QOF_GUID_MATCH_NULL"; case QOF_GUID_MATCH_LIST_ANY: return "QOF_GUID_MATCH_LIST_ANY"; } return "UNKNOWN MATCH TYPE"; } /* qof_query_printGuidMatch */ /* * Print out a string representation of the * QofCharMatch enum */ static const char * qof_query_printCharMatch (QofCharMatch c) { switch (c) { case QOF_CHAR_MATCH_ANY: return "QOF_CHAR_MATCH_ANY"; case QOF_CHAR_MATCH_NONE: return "QOF_CHAR_MATCH_NONE"; } return "UNKNOWN MATCH TYPE"; } /* qof_query_printGuidMatch */ /* ======================== END OF FILE =================== */