1 /********************************************************************\
2  * qof_query.c -- Implement predicate API for searching for objects *
3  * Copyright (C) 2002 Derek Atkins <warlord@MIT.EDU>                *
4  * Copyright (C) 2006-2008 Neil Williams <linux@codehelp.co.uk>     *
5  *                                                                  *
6  * This program is free software; you can redistribute it and/or    *
7  * modify it under the terms of the GNU General Public License as   *
8  * published by the Free Software Foundation; either version 2 of   *
9  * the License, or (at your option) any later version.              *
10  *                                                                  *
11  * This program is distributed in the hope that it will be useful,  *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
14  * GNU General Public License for more details.                     *
15  *                                                                  *
16  * You should have received a copy of the GNU General Public License*
17  * along with this program; if not, contact:                        *
18  *                                                                  *
19  * Free Software Foundation           Voice:  +1-617-542-5942       *
20  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
21  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
22  *                                                                  *
23 \********************************************************************/
24 
25 #include "config.h"
26 
27 #include <sys/types.h>
28 #include <time.h>
29 #include <glib.h>
30 #include <regex.h>
31 #include <string.h>
32 
33 #include "qof.h"
34 #include "qofbackend-p.h"
35 #include "qofbook-p.h"
36 #include "qofclass-p.h"
37 #include "qofquery-p.h"
38 #include "qofquerycore-p.h"
39 
40 static QofLogModule log_module = QOF_MOD_QUERY;
41 
42 struct _QofQueryTerm
43 {
44 	GSList *param_list;
45 	QofQueryPredData *pdata;
46 	gboolean invert;
47 
48 	/* These values are filled in during "compilation" of the query
49 	 * term, based upon the obj_name, param_name, and searched-for
50 	 * object type.  If conv_fcn is NULL, then we don't know how to
51 	 * convert types.
52 	 */
53 	GSList *param_fcns;
54 	QofQueryPredicateFunc pred_fcn;
55 };
56 
57 struct _QofQuerySort
58 {
59 	GSList *param_list;
60 	gint options;
61 	gboolean increasing;
62 
63 	/* These values are filled in during "compilation" of the query
64 	 * term, based upon the obj_name, param_name, and searched-for
65 	 * object type.  If conv_fcn is NULL, then we don't know how to
66 	 * convert types.
67 	 */
68 	gboolean use_default;
69 	GSList *param_fcns;			/* Chain of paramters to walk */
70 	QofSortFunc obj_cmp;		/* In case you are comparing objects */
71 	QofCompareFunc comp_fcn;	/* When you are comparing core types */
72 };
73 
74 /* The QUERY structure */
75 struct _QofQuery
76 {
77 	/* The object type that we're searching for */
78 	QofIdType search_for;
79 
80 	/* terms is a list of the OR-terms in a sum-of-products
81 	 * logical expression. */
82 	GList *terms;
83 
84 	/* sorting and chopping is independent of the search filter */
85 
86 	QofQuerySort primary_sort;
87 	QofQuerySort secondary_sort;
88 	QofQuerySort tertiary_sort;
89 	QofSortFunc defaultSort;	/* <- Computed from search_for */
90 
91 	/* The maximum number of results to return */
92 	gint max_results;
93 
94 	/* list of books that will be participating in the query */
95 	GList *books;
96 
97 	/* a map of book to backend-compiled queries */
98 	GHashTable *be_compiled;
99 
100 	/* cache the results so we don't have to run the whole search
101 	 * again until it's really necessary */
102 	gint changed;
103 
104 	GList *results;
105 };
106 
107 typedef struct _QofQueryCB
108 {
109 	QofQuery *query;
110 	GList *list;
111 	gint count;
112 } QofQueryCB;
113 
114 /* initial_term will be owned by the new Query */
115 static void
query_init(QofQuery * q,QofQueryTerm * initial_term)116 query_init (QofQuery * q, QofQueryTerm * initial_term)
117 {
118 	GList *or = NULL;
119 	GList *and = NULL;
120 	GHashTable *ht;
121 
122 	if (initial_term)
123 	{
124 		or = g_list_alloc ();
125 		and = g_list_alloc ();
126 		and->data = initial_term;
127 		or->data = and;
128 	}
129 
130 	if (q->terms)
131 		qof_query_clear (q);
132 
133 	g_list_free (q->results);
134 	g_list_free (q->books);
135 
136 	g_slist_free (q->primary_sort.param_list);
137 	g_slist_free (q->secondary_sort.param_list);
138 	g_slist_free (q->tertiary_sort.param_list);
139 
140 	g_slist_free (q->primary_sort.param_fcns);
141 	g_slist_free (q->secondary_sort.param_fcns);
142 	g_slist_free (q->tertiary_sort.param_fcns);
143 
144 	ht = q->be_compiled;
145 	memset (q, 0, sizeof (*q));
146 	q->be_compiled = ht;
147 
148 	q->terms = or;
149 	q->changed = 1;
150 	q->max_results = -1;
151 
152 	q->primary_sort.param_list =
153 		g_slist_prepend (NULL, QUERY_DEFAULT_SORT);
154 	q->primary_sort.increasing = TRUE;
155 	q->secondary_sort.increasing = TRUE;
156 	q->tertiary_sort.increasing = TRUE;
157 }
158 
159 static void
swap_terms(QofQuery * q1,QofQuery * q2)160 swap_terms (QofQuery * q1, QofQuery * q2)
161 {
162 	GList *g;
163 
164 	if (!q1 || !q2)
165 		return;
166 
167 	g = q1->terms;
168 	q1->terms = q2->terms;
169 	q2->terms = g;
170 
171 	g = q1->books;
172 	q1->books = q2->books;
173 	q2->books = g;
174 
175 	q1->changed = 1;
176 	q2->changed = 1;
177 }
178 
179 static void
free_query_term(QofQueryTerm * qt)180 free_query_term (QofQueryTerm * qt)
181 {
182 	if (!qt)
183 		return;
184 
185 	qof_query_core_predicate_free (qt->pdata);
186 	g_slist_free (qt->param_list);
187 	g_slist_free (qt->param_fcns);
188 	g_free (qt);
189 }
190 
191 static QofQueryTerm *
copy_query_term(QofQueryTerm * qt)192 copy_query_term (QofQueryTerm * qt)
193 {
194 	QofQueryTerm *new_qt;
195 	if (!qt)
196 		return NULL;
197 
198 	new_qt = g_new0 (QofQueryTerm, 1);
199 	memcpy (new_qt, qt, sizeof (QofQueryTerm));
200 	new_qt->param_list = g_slist_copy (qt->param_list);
201 	new_qt->param_fcns = g_slist_copy (qt->param_fcns);
202 	new_qt->pdata = qof_query_core_predicate_copy (qt->pdata);
203 	return new_qt;
204 }
205 
206 static GList *
copy_and_terms(GList * and_terms)207 copy_and_terms (GList * and_terms)
208 {
209 	GList *and = NULL;
210 	GList *cur_and;
211 
212 	for (cur_and = and_terms; cur_and; cur_and = cur_and->next)
213 	{
214 		and = g_list_prepend (and, copy_query_term (cur_and->data));
215 	}
216 
217 	return g_list_reverse (and);
218 }
219 
220 static GList *
copy_or_terms(GList * or_terms)221 copy_or_terms (GList * or_terms)
222 {
223 	GList *or = NULL;
224 	GList *cur_or;
225 
226 	for (cur_or = or_terms; cur_or; cur_or = cur_or->next)
227 	{
228 		or = g_list_prepend (or, copy_and_terms (cur_or->data));
229 	}
230 
231 	return g_list_reverse (or);
232 }
233 
234 static void
copy_sort(QofQuerySort * dst,const QofQuerySort * src)235 copy_sort (QofQuerySort * dst, const QofQuerySort * src)
236 {
237 	memcpy (dst, src, sizeof (*dst));
238 	dst->param_list = g_slist_copy (src->param_list);
239 	dst->param_fcns = g_slist_copy (src->param_fcns);
240 }
241 
242 static void
free_sort(QofQuerySort * s)243 free_sort (QofQuerySort * s)
244 {
245 	g_slist_free (s->param_list);
246 	s->param_list = NULL;
247 
248 	g_slist_free (s->param_fcns);
249 	s->param_fcns = NULL;
250 }
251 
252 static void
free_members(QofQuery * q)253 free_members (QofQuery * q)
254 {
255 	GList *cur_or;
256 
257 	if (q == NULL)
258 		return;
259 
260 	for (cur_or = q->terms; cur_or; cur_or = cur_or->next)
261 	{
262 		GList *cur_and;
263 
264 		for (cur_and = cur_or->data; cur_and; cur_and = cur_and->next)
265 		{
266 			free_query_term (cur_and->data);
267 			cur_and->data = NULL;
268 		}
269 
270 		g_list_free (cur_or->data);
271 		cur_or->data = NULL;
272 	}
273 
274 	free_sort (&(q->primary_sort));
275 	free_sort (&(q->secondary_sort));
276 	free_sort (&(q->tertiary_sort));
277 
278 	g_list_free (q->terms);
279 	q->terms = NULL;
280 
281 	g_list_free (q->books);
282 	q->books = NULL;
283 
284 	g_list_free (q->results);
285 	q->results = NULL;
286 }
287 
288 static gint
cmp_func(QofQuerySort * sort,QofSortFunc default_sort,gconstpointer a,gconstpointer b)289 cmp_func (QofQuerySort * sort, QofSortFunc default_sort,
290 	gconstpointer a, gconstpointer b)
291 {
292 	QofParam *param = NULL;
293 	GSList *node;
294 	gpointer conva, convb;
295 
296 	g_return_val_if_fail (sort, 0);
297 
298 	/* See if this is a default sort */
299 	if (sort->use_default)
300 	{
301 		if (default_sort)
302 			return default_sort (a, b);
303 		return 0;
304 	}
305 
306 	/* If no parameters, consider them equal */
307 	if (!sort->param_fcns)
308 		return 0;
309 
310 	/* no compare function, consider the two objects equal */
311 	if (!sort->comp_fcn && !sort->obj_cmp)
312 		return 0;
313 
314 	/* Do the list of conversions */
315 	conva = (gpointer) a;
316 	convb = (gpointer) b;
317 	for (node = sort->param_fcns; node; node = node->next)
318 	{
319 		param = node->data;
320 
321 		/* The last term is really the "parameter getter",
322 		 * unless we're comparing objects ;) */
323 		if (!node->next && !sort->obj_cmp)
324 			break;
325 
326 		/* Do the converstions */
327 		conva = (param->param_getfcn) (conva, param);
328 		convb = (param->param_getfcn) (convb, param);
329 	}
330 
331 	/* And now return the (appropriate) compare */
332 	if (sort->comp_fcn)
333 	{
334 		gint rc = sort->comp_fcn (conva, convb, sort->options, param);
335 		return rc;
336 	}
337 
338 	return sort->obj_cmp (conva, convb);
339 }
340 
341 static QofQuery *sortQuery = NULL;
342 
343 static gint
sort_func(gconstpointer a,gconstpointer b)344 sort_func (gconstpointer a, gconstpointer b)
345 {
346 	gint retval;
347 
348 	g_return_val_if_fail (sortQuery, 0);
349 
350 	retval =
351 		cmp_func (&(sortQuery->primary_sort), sortQuery->defaultSort, a,
352 		b);
353 	if (retval == 0)
354 	{
355 		retval =
356 			cmp_func (&(sortQuery->secondary_sort),
357 			sortQuery->defaultSort,
358 			a, b);
359 		if (retval == 0)
360 		{
361 			retval =
362 				cmp_func (&(sortQuery->tertiary_sort),
363 				sortQuery->defaultSort, a, b);
364 			return sortQuery->tertiary_sort.increasing ?
365 				retval : -retval;
366 		}
367 		else
368 		{
369 			return sortQuery->secondary_sort.increasing ?
370 				retval : -retval;
371 		}
372 	}
373 	else
374 	{
375 		return sortQuery->primary_sort.increasing ? retval : -retval;
376 	}
377 }
378 
379 /* ==================================================================== */
380 /* This is the main workhorse for performing the query.  For each
381  * object, it walks over all of the query terms to see if the
382  * object passes the seive.
383  */
384 
385 static gint
check_object(QofQuery * q,gpointer object)386 check_object (QofQuery * q, gpointer object)
387 {
388 	GList *and_ptr;
389 	GList *or_ptr;
390 	QofQueryTerm *qt;
391 	gint and_terms_ok = 1;
392 
393 	for (or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next)
394 	{
395 		and_terms_ok = 1;
396 		for (and_ptr = or_ptr->data; and_ptr; and_ptr = and_ptr->next)
397 		{
398 			qt = (QofQueryTerm *) (and_ptr->data);
399 			if (qt->param_fcns && qt->pred_fcn)
400 			{
401 				GSList *node;
402 				QofParam *param = NULL;
403 				gpointer conv_obj = object;
404 
405 				/* iterate through the conversions */
406 				for (node = qt->param_fcns; node; node = node->next)
407 				{
408 					param = node->data;
409 
410 					/* The last term is the actual parameter getter */
411 					if (!node->next)
412 						break;
413 
414 					conv_obj = param->param_getfcn (conv_obj, param);
415 				}
416 
417 				if (((qt->pred_fcn) (conv_obj, param,
418 							qt->pdata)) == qt->invert)
419 				{
420 					and_terms_ok = 0;
421 					break;
422 				}
423 			}
424 			else
425 			{
426 				/* XXX: Don't know how to do this conversion -- do we care? */
427 			}
428 		}
429 		if (and_terms_ok)
430 		{
431 			return 1;
432 		}
433 	}
434 
435 	/* If there are no terms, assume a "match any" applies.
436 	 * A query with no terms is still meaningful, since the user
437 	 * may want to get all objects, but in a particular sorted
438 	 * order.
439 	 */
440 	if (NULL == q->terms)
441 		return 1;
442 	return 0;
443 }
444 
445 /* walk the list of parameters, starting with the given object, and
446  * compile the list of parameter get-functions.  Save the last valid
447  * parameter definition in "final" and return the list of functions.
448  *
449  * returns NULL if the first parameter is bad (and final is unchanged).
450  */
451 static GSList *
compile_params(GSList * param_list,QofIdType start_obj,QofParam const ** final)452 compile_params (GSList * param_list, QofIdType start_obj,
453 	QofParam const **final)
454 {
455 	const QofParam *objDef = NULL;
456 	GSList *fcns = NULL;
457 
458 	ENTER ("param_list=%p id=%s", param_list, start_obj);
459 	g_return_val_if_fail (param_list, NULL);
460 	g_return_val_if_fail (start_obj, NULL);
461 	g_return_val_if_fail (final, NULL);
462 
463 	for (; param_list; param_list = param_list->next)
464 	{
465 		QofIdType param_name = param_list->data;
466 		objDef = qof_class_get_parameter (start_obj, param_name);
467 
468 		/* If it doesn't exist, then we've reached the end */
469 		if (!objDef)
470 			break;
471 
472 		/* Save off this parameter */
473 		fcns = g_slist_prepend (fcns, (gpointer) objDef);
474 
475 		/* Save this off, just in case */
476 		*final = objDef;
477 
478 		/* And reset for the next parameter */
479 		start_obj = (QofIdType) objDef->param_type;
480 	}
481 
482 	LEAVE ("fcns=%p", fcns);
483 	return (g_slist_reverse (fcns));
484 }
485 
486 static void
compile_sort(QofQuerySort * sort,QofIdType obj)487 compile_sort (QofQuerySort * sort, QofIdType obj)
488 {
489 	const QofParam *resObj = NULL;
490 
491 	ENTER ("sort=%p id=%s params=%p", sort, obj, sort->param_list);
492 	sort->use_default = FALSE;
493 
494 	g_slist_free (sort->param_fcns);
495 	sort->param_fcns = NULL;
496 	sort->comp_fcn = NULL;
497 	sort->obj_cmp = NULL;
498 
499 	/* An empty param_list implies "no sort" */
500 	if (!sort->param_list)
501 	{
502 		LEAVE (" ");
503 		return;
504 	}
505 
506 	/* Walk the parameter list of obtain the parameter functions */
507 	sort->param_fcns = compile_params (sort->param_list, obj, &resObj);
508 
509 	/* If we have valid parameters, grab the compare function,
510 	 * If not, check if this is the default sort.
511 	 */
512 	if (sort->param_fcns)
513 	{
514 		sort->comp_fcn = qof_query_core_get_compare (resObj->param_type);
515 
516 		/* Hrm, perhaps this is an object compare, not a core compare? */
517 		if (sort->comp_fcn == NULL)
518 		{
519 			sort->obj_cmp =
520 				qof_class_get_default_sort (resObj->param_type);
521 		}
522 	}
523 	else if (!safe_strcmp (sort->param_list->data, QUERY_DEFAULT_SORT))
524 	{
525 		sort->use_default = TRUE;
526 	}
527 	LEAVE ("sort=%p id=%s", sort, obj);
528 }
529 
530 static void
compile_terms(QofQuery * q)531 compile_terms (QofQuery * q)
532 {
533 	GList *or_ptr, *and_ptr, *node;
534 
535 	ENTER (" query=%p", q);
536 	/* Find the specific functions for this Query.  Note that the
537 	 * Query's search_for should now be set to the new type.
538 	 */
539 	for (or_ptr = q->terms; or_ptr; or_ptr = or_ptr->next)
540 	{
541 		for (and_ptr = or_ptr->data; and_ptr; and_ptr = and_ptr->next)
542 		{
543 			QofQueryTerm *qt = and_ptr->data;
544 			const QofParam *resObj = NULL;
545 
546 			g_slist_free (qt->param_fcns);
547 			qt->param_fcns = NULL;
548 
549 			/* Walk the parameter list of obtain the parameter functions */
550 			qt->param_fcns = compile_params (qt->param_list, q->search_for,
551 				&resObj);
552 
553 			/* If we have valid parameters, grab the predicate function,
554 			 * If not, see if this is the default sort.
555 			 */
556 
557 			if (qt->param_fcns)
558 				qt->pred_fcn =
559 					qof_query_core_get_predicate (resObj->param_type);
560 			else
561 				qt->pred_fcn = NULL;
562 		}
563 	}
564 
565 	/* Update the sort functions */
566 	compile_sort (&(q->primary_sort), q->search_for);
567 	compile_sort (&(q->secondary_sort), q->search_for);
568 	compile_sort (&(q->tertiary_sort), q->search_for);
569 
570 	q->defaultSort = qof_class_get_default_sort (q->search_for);
571 
572 	/* Now compile the backend instances */
573 	for (node = q->books; node; node = node->next)
574 	{
575 		QofBook *book = node->data;
576 		QofBackend *be = book->backend;
577 
578 		if (be && be->compile_query)
579 		{
580 			gpointer result = (be->compile_query) (be, q);
581 			if (result)
582 				g_hash_table_insert (q->be_compiled, book, result);
583 		}
584 	}
585 	LEAVE (" query=%p", q);
586 }
587 
588 static void
check_item_cb(gpointer object,gpointer user_data)589 check_item_cb (gpointer object, gpointer user_data)
590 {
591 	QofQueryCB *ql = user_data;
592 
593 	if (!object || !ql)
594 		return;
595 
596 	if (check_object (ql->query, object))
597 	{
598 		ql->list = g_list_prepend (ql->list, object);
599 		ql->count++;
600 	}
601 	return;
602 }
603 
604 static int
param_list_cmp(GSList * l1,GSList * l2)605 param_list_cmp (GSList * l1, GSList * l2)
606 {
607 	while (1)
608 	{
609 		int ret;
610 
611 		/* Check the easy stuff */
612 		if (!l1 && !l2)
613 			return 0;
614 		if (!l1 && l2)
615 			return -1;
616 		if (l1 && !l2)
617 			return 1;
618 
619 		ret = safe_strcmp (l1->data, l2->data);
620 		if (ret)
621 			return ret;
622 
623 		l1 = l1->next;
624 		l2 = l2->next;
625 	}
626 }
627 
628 static GList *
merge_books(GList * l1,GList * l2)629 merge_books (GList * l1, GList * l2)
630 {
631 	GList *res = NULL;
632 	GList *node;
633 
634 	res = g_list_copy (l1);
635 
636 	for (node = l2; node; node = node->next)
637 	{
638 		if (g_list_index (res, node->data) == -1)
639 			res = g_list_prepend (res, node->data);
640 	}
641 
642 	return res;
643 }
644 
645 static gboolean
query_free_compiled(gpointer key,gpointer value,gpointer user_data)646 query_free_compiled (gpointer key, gpointer value,
647 		gpointer user_data __attribute__ ((unused)))
648 {
649 	QofBook *book = key;
650 	QofBackend *be = book->backend;
651 
652 	if (be && be->free_query)
653 		(be->free_query) (be, value);
654 
655 	return TRUE;
656 }
657 
658 /* clear out any cached query_compilations */
659 static void
query_clear_compiles(QofQuery * q)660 query_clear_compiles (QofQuery * q)
661 {
662 	g_hash_table_foreach_remove (q->be_compiled, query_free_compiled,
663 		NULL);
664 }
665 
666 /********************************************************************/
667 /* PUBLISHED API FUNCTIONS */
668 
669 GSList *
qof_query_build_param_list(gchar const * param,...)670 qof_query_build_param_list (gchar const *param, ...)
671 {
672 	GSList *param_list = NULL;
673 	gchar const *this_param;
674 	va_list ap;
675 
676 	if (!param)
677 		return NULL;
678 
679 	va_start (ap, param);
680 
681 	for (this_param = param; this_param;
682 		this_param = va_arg (ap, const gchar *))
683 		  param_list = g_slist_prepend (param_list, (gpointer) this_param);
684 
685 	va_end (ap);
686 
687 	return g_slist_reverse (param_list);
688 }
689 
690 void
qof_query_add_term(QofQuery * q,GSList * param_list,QofQueryPredData * pred_data,QofQueryOp op)691 qof_query_add_term (QofQuery * q, GSList * param_list,
692 	QofQueryPredData * pred_data, QofQueryOp op)
693 {
694 	QofQueryTerm *qt;
695 	QofQuery *qr, *qs;
696 
697 	if (!q || !param_list || !pred_data)
698 		return;
699 
700 	qt = g_new0 (QofQueryTerm, 1);
701 	qt->param_list = param_list;
702 	qt->pdata = pred_data;
703 	qs = qof_query_create ();
704 	query_init (qs, qt);
705 
706 	if (qof_query_has_terms (q))
707 		qr = qof_query_merge (q, qs, op);
708 	else
709 		qr = qof_query_merge (q, qs, QOF_QUERY_OR);
710 
711 	swap_terms (q, qr);
712 	qof_query_destroy (qs);
713 	qof_query_destroy (qr);
714 }
715 
716 void
qof_query_purge_terms(QofQuery * q,GSList * param_list)717 qof_query_purge_terms (QofQuery * q, GSList * param_list)
718 {
719 	QofQueryTerm *qt;
720 	GList *or, *and;
721 
722 	if (!q || !param_list)
723 		return;
724 
725 	for (or = q->terms; or; or = or->next)
726 	{
727 		for (and = or->data; and; and = and->next)
728 		{
729 			qt = and->data;
730 			if (!param_list_cmp (qt->param_list, param_list))
731 			{
732 				if (g_list_length (or->data) == 1)
733 				{
734 					q->terms = g_list_remove_link (q->terms, or);
735 					g_list_free_1 (or);
736 					or = q->terms;
737 					break;
738 				}
739 				else
740 				{
741 					or->data = g_list_remove_link (or->data, and);
742 					g_list_free_1 (and);
743 					and = or->data;
744 					if (!and)
745 						break;
746 				}
747 				q->changed = 1;
748 				free_query_term (qt);
749 			}
750 		}
751 		if (!or)
752 			break;
753 	}
754 }
755 
756 GList *
qof_query_run(QofQuery * q)757 qof_query_run (QofQuery * q)
758 {
759 	GList *matching_objects = NULL;
760 	GList *node;
761 	gint object_count = 0;
762 
763 	if (!q)
764 		return NULL;
765 	g_return_val_if_fail (q->search_for, NULL);
766 	g_return_val_if_fail (q->books, NULL);
767 	ENTER (" q=%p", q);
768 
769 	/* XXX: Prioritize the query terms? */
770 
771 	/* prepare the Query for processing */
772 	if (q->changed)
773 	{
774 		query_clear_compiles (q);
775 		compile_terms (q);
776 	}
777 
778 	/* Maybe log this sucker */
779 	if (qof_log_check (log_module, QOF_LOG_DETAIL))
780 		qof_query_print (q);
781 
782 	/* Now run the query over all the objects and save the results */
783 	{
784 		QofQueryCB qcb;
785 
786 		memset (&qcb, 0, sizeof (qcb));
787 		qcb.query = q;
788 
789 		/* For each book */
790 		for (node = q->books; node; node = node->next)
791 		{
792 			QofBook *book = node->data;
793 			QofBackend *be = book->backend;
794 
795 			/* run the query in the backend */
796 			if (be)
797 			{
798 				gpointer compiled_query =
799 					g_hash_table_lookup (q->be_compiled, book);
800 
801 				if (compiled_query && be->run_query)
802 				{
803 					(be->run_query) (be, compiled_query);
804 				}
805 			}
806 
807 			/* And then iterate over all the objects */
808 			qof_object_foreach (q->search_for, book,
809 				(QofEntityForeachCB) check_item_cb, &qcb);
810 		}
811 
812 		matching_objects = qcb.list;
813 		object_count = qcb.count;
814 	}
815 	PINFO ("matching objects=%p count=%d", matching_objects, object_count);
816 
817 	/* There is no absolute need to reverse this list, since it's being
818 	 * sorted below. However, in the common case, we will be searching
819 	 * in a confined location where the objects are already in order,
820 	 * thus reversing will put us in the correct order we want and make
821 	 * the sorting go much faster.
822 	 */
823 	matching_objects = g_list_reverse (matching_objects);
824 
825 	/* Now sort the matching objects based on the search criteria
826 	 * sortQuery is an unforgivable use of static global data...
827 	 * I just can't figure out how else to do this sanely.
828 	 */
829 	if (q->primary_sort.comp_fcn || q->primary_sort.obj_cmp ||
830 		(q->primary_sort.use_default && q->defaultSort))
831 	{
832 		sortQuery = q;
833 		matching_objects = g_list_sort (matching_objects, sort_func);
834 		sortQuery = NULL;
835 	}
836 
837 	/* Crop the list to limit the number of splits. */
838 	if ((object_count > q->max_results) && (q->max_results > -1))
839 	{
840 		if (q->max_results > 0)
841 		{
842 			GList *mptr;
843 
844 			/* mptr is set to the first node of what will be the new list */
845 			mptr =
846 				g_list_nth (matching_objects,
847 				object_count - q->max_results);
848 			/* mptr should not be NULL, but let's be safe */
849 			if (mptr != NULL)
850 			{
851 				if (mptr->prev != NULL)
852 					mptr->prev->next = NULL;
853 				mptr->prev = NULL;
854 			}
855 			g_list_free (matching_objects);
856 			matching_objects = mptr;
857 		}
858 		else
859 		{
860 			/* q->max_results == 0 */
861 			g_list_free (matching_objects);
862 			matching_objects = NULL;
863 		}
864 		object_count = q->max_results;
865 	}
866 
867 	q->changed = 0;
868 
869 	g_list_free (q->results);
870 	q->results = matching_objects;
871 
872 	LEAVE (" q=%p", q);
873 	return matching_objects;
874 }
875 
876 GList *
qof_query_last_run(QofQuery * query)877 qof_query_last_run (QofQuery * query)
878 {
879 	if (!query)
880 		return NULL;
881 
882 	return query->results;
883 }
884 
885 void
qof_query_clear(QofQuery * query)886 qof_query_clear (QofQuery * query)
887 {
888 	QofQuery *q2 = qof_query_create ();
889 	swap_terms (query, q2);
890 	qof_query_destroy (q2);
891 
892 	g_list_free (query->books);
893 	query->books = NULL;
894 	g_list_free (query->results);
895 	query->results = NULL;
896 	query->changed = 1;
897 }
898 
899 QofQuery *
qof_query_create(void)900 qof_query_create (void)
901 {
902 	QofQuery *qp = g_new0 (QofQuery, 1);
903 	qp->be_compiled = g_hash_table_new (g_direct_hash, g_direct_equal);
904 	query_init (qp, NULL);
905 	return qp;
906 }
907 
908 void
qof_query_search_for(QofQuery * q,QofIdTypeConst obj_type)909 qof_query_search_for (QofQuery * q, QofIdTypeConst obj_type)
910 {
911 	if (!q || !obj_type)
912 		return;
913 
914 	if (safe_strcmp (q->search_for, obj_type))
915 	{
916 		q->search_for = (QofIdType) obj_type;
917 		q->changed = 1;
918 	}
919 }
920 
921 QofQuery *
qof_query_create_for(QofIdTypeConst obj_type)922 qof_query_create_for (QofIdTypeConst obj_type)
923 {
924 	QofQuery *q;
925 	if (!obj_type)
926 		return NULL;
927 	q = qof_query_create ();
928 	qof_query_search_for (q, obj_type);
929 	return q;
930 }
931 
932 gint
qof_query_has_terms(QofQuery * q)933 qof_query_has_terms (QofQuery * q)
934 {
935 	if (!q)
936 		return 0;
937 	return g_list_length (q->terms);
938 }
939 
940 gint
qof_query_num_terms(QofQuery * q)941 qof_query_num_terms (QofQuery * q)
942 {
943 	GList *o;
944 	gint n = 0;
945 	if (!q)
946 		return 0;
947 	for (o = q->terms; o; o = o->next)
948 		n += g_list_length (o->data);
949 	return n;
950 }
951 
952 gboolean
qof_query_has_term_type(QofQuery * q,GSList * term_param)953 qof_query_has_term_type (QofQuery * q, GSList * term_param)
954 {
955 	GList *or;
956 	GList *and;
957 
958 	if (!q || !term_param)
959 		return FALSE;
960 
961 	for (or = q->terms; or; or = or->next)
962 	{
963 		for (and = or->data; and; and = and->next)
964 		{
965 			QofQueryTerm *qt = and->data;
966 			if (!param_list_cmp (term_param, qt->param_list))
967 				return TRUE;
968 		}
969 	}
970 
971 	return FALSE;
972 }
973 
974 GSList *
qof_query_get_term_type(QofQuery * q,GSList * term_param)975 qof_query_get_term_type (QofQuery * q, GSList * term_param)
976 {
977 	GList *or;
978 	GList *and;
979 	GSList *results = NULL;
980 
981 	if (!q || !term_param)
982 		return FALSE;
983 
984 	for (or = q->terms; or; or = or->next)
985 	{
986 		for (and = or->data; and; and = and->next)
987 		{
988 			QofQueryTerm *qt = and->data;
989 			if (!param_list_cmp (term_param, qt->param_list))
990 				results = g_slist_append (results, qt->pdata);
991 		}
992 	}
993 
994 	return results;
995 }
996 
997 void
qof_query_destroy(QofQuery * q)998 qof_query_destroy (QofQuery * q)
999 {
1000 	if (!q)
1001 		return;
1002 	free_members (q);
1003 	query_clear_compiles (q);
1004 	g_hash_table_destroy (q->be_compiled);
1005 	g_free (q);
1006 }
1007 
1008 QofQuery *
qof_query_copy(QofQuery * q)1009 qof_query_copy (QofQuery * q)
1010 {
1011 	QofQuery *copy;
1012 	GHashTable *ht;
1013 
1014 	if (!q)
1015 		return NULL;
1016 	copy = qof_query_create ();
1017 	ht = copy->be_compiled;
1018 	free_members (copy);
1019 
1020 	memcpy (copy, q, sizeof (QofQuery));
1021 
1022 	copy->be_compiled = ht;
1023 	copy->terms = copy_or_terms (q->terms);
1024 	copy->books = g_list_copy (q->books);
1025 	copy->results = g_list_copy (q->results);
1026 
1027 	copy_sort (&(copy->primary_sort), &(q->primary_sort));
1028 	copy_sort (&(copy->secondary_sort), &(q->secondary_sort));
1029 	copy_sort (&(copy->tertiary_sort), &(q->tertiary_sort));
1030 
1031 	copy->changed = 1;
1032 
1033 	return copy;
1034 }
1035 
1036 /* *******************************************************************
1037  * qof_query_invert
1038  * return a newly-allocated Query object which is the
1039  * logical inverse of the original.
1040  ********************************************************************/
1041 
1042 QofQuery *
qof_query_invert(QofQuery * q)1043 qof_query_invert (QofQuery * q)
1044 {
1045 	QofQuery *retval;
1046 	QofQuery *right, *left, *iright, *ileft;
1047 	QofQueryTerm *qt;
1048 	GList *aterms;
1049 	GList *cur;
1050 	GList *new_oterm;
1051 	gint num_or_terms;
1052 
1053 	if (!q)
1054 		return NULL;
1055 
1056 	num_or_terms = g_list_length (q->terms);
1057 
1058 	switch (num_or_terms)
1059 	{
1060 	case 0:
1061 		retval = qof_query_create ();
1062 		retval->max_results = q->max_results;
1063 		break;
1064 
1065 		/* This is the DeMorgan expansion for a single AND expression. */
1066 		/* !(abc) = !a + !b + !c */
1067 	case 1:
1068 		retval = qof_query_create ();
1069 		retval->max_results = q->max_results;
1070 		retval->books = g_list_copy (q->books);
1071 		retval->search_for = q->search_for;
1072 		retval->changed = 1;
1073 
1074 		aterms = g_list_nth_data (q->terms, 0);
1075 		new_oterm = NULL;
1076 		for (cur = aterms; cur; cur = cur->next)
1077 		{
1078 			qt = copy_query_term (cur->data);
1079 			qt->invert = !(qt->invert);
1080 			new_oterm = g_list_append (NULL, qt);
1081 
1082 			/* g_list_append() can take forever, so let's do this for speed
1083 			 * in "large" queries.
1084 			 */
1085 			retval->terms = g_list_reverse (retval->terms);
1086 			retval->terms = g_list_prepend (retval->terms, new_oterm);
1087 			retval->terms = g_list_reverse (retval->terms);
1088 		}
1089 		break;
1090 
1091 		/* If there are multiple OR-terms, we just recurse by
1092 		 * breaking it down to !(a + b + c) =
1093 		 * !a * !(b + c) = !a * !b * !c.  */
1094 	default:
1095 		right = qof_query_create ();
1096 		right->terms = copy_or_terms (g_list_nth (q->terms, 1));
1097 
1098 		left = qof_query_create ();
1099 		left->terms = g_list_append (NULL,
1100 			copy_and_terms (g_list_nth_data (q->terms, 0)));
1101 
1102 		iright = qof_query_invert (right);
1103 		ileft = qof_query_invert (left);
1104 
1105 		retval = qof_query_merge (iright, ileft, QOF_QUERY_AND);
1106 		retval->books = g_list_copy (q->books);
1107 		retval->max_results = q->max_results;
1108 		retval->search_for = q->search_for;
1109 		retval->changed = 1;
1110 
1111 		qof_query_destroy (iright);
1112 		qof_query_destroy (ileft);
1113 		qof_query_destroy (right);
1114 		qof_query_destroy (left);
1115 		break;
1116 	}
1117 
1118 	return retval;
1119 }
1120 
1121 /* *******************************************************************
1122  * qof_query_merge
1123  * combine 2 Query objects by the logical operation in "op".
1124  ********************************************************************/
1125 
1126 QofQuery *
qof_query_merge(QofQuery * q1,QofQuery * q2,QofQueryOp op)1127 qof_query_merge (QofQuery * q1, QofQuery * q2, QofQueryOp op)
1128 {
1129 
1130 	QofQuery *retval = NULL;
1131 	QofQuery *i1, *i2;
1132 	QofQuery *t1, *t2;
1133 	GList *i, *j;
1134 	QofIdType search_for;
1135 
1136 	if (!q1)
1137 		return q2;
1138 	if (!q2)
1139 		return q1;
1140 
1141 	if (q1->search_for && q2->search_for)
1142 		g_return_val_if_fail (safe_strcmp (q1->search_for,
1143 				q2->search_for) == 0, NULL);
1144 
1145 	search_for = (q1->search_for ? q1->search_for : q2->search_for);
1146 
1147 	/* Avoid merge surprises if op==QOF_QUERY_AND but q1 is empty.
1148 	 * The goal of this tweak is to all the user to start with
1149 	 * an empty q1 and then append to it recursively
1150 	 * (and q1 (and q2 (and q3 (and q4 ....))))
1151 	 * without bombing out because the append started with an
1152 	 * empty list.
1153 	 * We do essentially the same check in qof_query_add_term()
1154 	 * so that the first term added to an empty query doesn't screw up.
1155 	 */
1156 	if ((QOF_QUERY_AND == op) && (0 == qof_query_has_terms (q1)))
1157 	{
1158 		op = QOF_QUERY_OR;
1159 	}
1160 
1161 	switch (op)
1162 	{
1163 	case QOF_QUERY_OR:
1164 		retval = qof_query_create ();
1165 		retval->terms =
1166 			g_list_concat (copy_or_terms (q1->terms),
1167 			copy_or_terms (q2->terms));
1168 		retval->books = merge_books (q1->books, q2->books);
1169 		retval->max_results = q1->max_results;
1170 		retval->changed = 1;
1171 		break;
1172 
1173 	case QOF_QUERY_AND:
1174 		retval = qof_query_create ();
1175 		retval->books = merge_books (q1->books, q2->books);
1176 		retval->max_results = q1->max_results;
1177 		retval->changed = 1;
1178 
1179 		/* g_list_append() can take forever, so let's build the list in
1180 		 * reverse and then reverse it at the end, to deal better with
1181 		 * "large" queries.
1182 		 */
1183 		for (i = q1->terms; i; i = i->next)
1184 		{
1185 			for (j = q2->terms; j; j = j->next)
1186 			{
1187 				retval->terms =
1188 					g_list_prepend (retval->terms,
1189 					g_list_concat
1190 					(copy_and_terms (i->data), copy_and_terms (j->data)));
1191 			}
1192 		}
1193 		retval->terms = g_list_reverse (retval->terms);
1194 		break;
1195 
1196 	case QOF_QUERY_NAND:
1197 		/* !(a*b) = (!a + !b) */
1198 		i1 = qof_query_invert (q1);
1199 		i2 = qof_query_invert (q2);
1200 		retval = qof_query_merge (i1, i2, QOF_QUERY_OR);
1201 		qof_query_destroy (i1);
1202 		qof_query_destroy (i2);
1203 		break;
1204 
1205 	case QOF_QUERY_NOR:
1206 		/* !(a+b) = (!a*!b) */
1207 		i1 = qof_query_invert (q1);
1208 		i2 = qof_query_invert (q2);
1209 		retval = qof_query_merge (i1, i2, QOF_QUERY_AND);
1210 		qof_query_destroy (i1);
1211 		qof_query_destroy (i2);
1212 		break;
1213 
1214 	case QOF_QUERY_XOR:
1215 		/* a xor b = (a * !b) + (!a * b) */
1216 		i1 = qof_query_invert (q1);
1217 		i2 = qof_query_invert (q2);
1218 		t1 = qof_query_merge (q1, i2, QOF_QUERY_AND);
1219 		t2 = qof_query_merge (i1, q2, QOF_QUERY_AND);
1220 		retval = qof_query_merge (t1, t2, QOF_QUERY_OR);
1221 
1222 		qof_query_destroy (i1);
1223 		qof_query_destroy (i2);
1224 		qof_query_destroy (t1);
1225 		qof_query_destroy (t2);
1226 		break;
1227 	}
1228 
1229 	retval->search_for = search_for;
1230 	return retval;
1231 }
1232 
1233 void
qof_query_merge_in_place(QofQuery * q1,QofQuery * q2,QofQueryOp op)1234 qof_query_merge_in_place (QofQuery * q1, QofQuery * q2, QofQueryOp op)
1235 {
1236 	QofQuery *tmp_q;
1237 
1238 	if (!q1 || !q2)
1239 		return;
1240 
1241 	tmp_q = qof_query_merge (q1, q2, op);
1242 	swap_terms (q1, tmp_q);
1243 	qof_query_destroy (tmp_q);
1244 }
1245 
1246 void
qof_query_set_sort_order(QofQuery * q,GSList * params1,GSList * params2,GSList * params3)1247 qof_query_set_sort_order (QofQuery * q,
1248 	GSList * params1, GSList * params2, GSList * params3)
1249 {
1250 	if (!q)
1251 		return;
1252 	if (q->primary_sort.param_list)
1253 		g_slist_free (q->primary_sort.param_list);
1254 	q->primary_sort.param_list = params1;
1255 	q->primary_sort.options = 0;
1256 
1257 	if (q->secondary_sort.param_list)
1258 		g_slist_free (q->secondary_sort.param_list);
1259 	q->secondary_sort.param_list = params2;
1260 	q->secondary_sort.options = 0;
1261 
1262 	if (q->tertiary_sort.param_list)
1263 		g_slist_free (q->tertiary_sort.param_list);
1264 	q->tertiary_sort.param_list = params3;
1265 	q->tertiary_sort.options = 0;
1266 
1267 	q->changed = 1;
1268 }
1269 
1270 void
qof_query_set_sort_options(QofQuery * q,gint prim_op,gint sec_op,gint tert_op)1271 qof_query_set_sort_options (QofQuery * q, gint prim_op, gint sec_op,
1272 	gint tert_op)
1273 {
1274 	if (!q)
1275 		return;
1276 	q->primary_sort.options = prim_op;
1277 	q->secondary_sort.options = sec_op;
1278 	q->tertiary_sort.options = tert_op;
1279 }
1280 
1281 void
qof_query_set_sort_increasing(QofQuery * q,gboolean prim_inc,gboolean sec_inc,gboolean tert_inc)1282 qof_query_set_sort_increasing (QofQuery * q, gboolean prim_inc,
1283 	gboolean sec_inc, gboolean tert_inc)
1284 {
1285 	if (!q)
1286 		return;
1287 	q->primary_sort.increasing = prim_inc;
1288 	q->secondary_sort.increasing = sec_inc;
1289 	q->tertiary_sort.increasing = tert_inc;
1290 }
1291 
1292 void
qof_query_set_max_results(QofQuery * q,gint n)1293 qof_query_set_max_results (QofQuery * q, gint n)
1294 {
1295 	if (!q)
1296 		return;
1297 	q->max_results = n;
1298 }
1299 
1300 void
qof_query_add_guid_list_match(QofQuery * q,GSList * param_list,GList * guid_list,QofGuidMatch options,QofQueryOp op)1301 qof_query_add_guid_list_match (QofQuery * q, GSList * param_list,
1302 	GList * guid_list, QofGuidMatch options, QofQueryOp op)
1303 {
1304 	QofQueryPredData *pdata;
1305 
1306 	if (!q || !param_list)
1307 		return;
1308 
1309 	if (!guid_list)
1310 		g_return_if_fail (options == QOF_GUID_MATCH_NULL);
1311 
1312 	pdata = qof_query_guid_predicate (options, guid_list);
1313 	qof_query_add_term (q, param_list, pdata, op);
1314 }
1315 
1316 void
qof_query_add_guid_match(QofQuery * q,GSList * param_list,const GUID * guid,QofQueryOp op)1317 qof_query_add_guid_match (QofQuery * q, GSList * param_list,
1318 	const GUID * guid, QofQueryOp op)
1319 {
1320 	GList *g = NULL;
1321 
1322 	if (!q || !param_list)
1323 		return;
1324 
1325 	if (guid)
1326 		g = g_list_prepend (g, (gpointer) guid);
1327 
1328 	qof_query_add_guid_list_match (q, param_list, g,
1329 		g ? QOF_GUID_MATCH_ANY : QOF_GUID_MATCH_NULL, op);
1330 
1331 	g_list_free (g);
1332 }
1333 
1334 void
qof_query_set_book(QofQuery * q,QofBook * book)1335 qof_query_set_book (QofQuery * q, QofBook * book)
1336 {
1337 	GSList *slist = NULL;
1338 	if (!q || !book)
1339 		return;
1340 
1341 	/* Make sure this book is only in the list once */
1342 	if (g_list_index (q->books, book) == -1)
1343 		q->books = g_list_prepend (q->books, book);
1344 
1345 	slist = g_slist_prepend (slist, QOF_PARAM_GUID);
1346 	slist = g_slist_prepend (slist, QOF_PARAM_BOOK);
1347 	qof_query_add_guid_match (q, slist,
1348 		qof_entity_get_guid ((QofEntity*)book), QOF_QUERY_AND);
1349 }
1350 
1351 GList *
qof_query_get_books(QofQuery * q)1352 qof_query_get_books (QofQuery * q)
1353 {
1354 	if (!q)
1355 		return NULL;
1356 	return q->books;
1357 }
1358 
1359 void
qof_query_add_boolean_match(QofQuery * q,GSList * param_list,gboolean value,QofQueryOp op)1360 qof_query_add_boolean_match (QofQuery * q, GSList * param_list,
1361 	gboolean value, QofQueryOp op)
1362 {
1363 	QofQueryPredData *pdata;
1364 	if (!q || !param_list)
1365 		return;
1366 
1367 	pdata = qof_query_boolean_predicate (QOF_COMPARE_EQUAL, value);
1368 	qof_query_add_term (q, param_list, pdata, op);
1369 }
1370 
1371 /**********************************************************************/
1372 /* PRIVATE PUBLISHED API FUNCTIONS                                    */
1373 
1374 void
qof_query_init(void)1375 qof_query_init (void)
1376 {
1377 	ENTER (" ");
1378 	qof_query_core_init ();
1379 	qof_class_init ();
1380 	qof_date_init ();
1381 	LEAVE ("Completed initialization of QofQuery");
1382 }
1383 
1384 void
qof_query_shutdown(void)1385 qof_query_shutdown (void)
1386 {
1387 	qof_class_shutdown ();
1388 	qof_query_core_shutdown ();
1389 }
1390 
1391 gint
qof_query_get_max_results(QofQuery * q)1392 qof_query_get_max_results (QofQuery * q)
1393 {
1394 	if (!q)
1395 		return 0;
1396 	return q->max_results;
1397 }
1398 
1399 QofIdType
qof_query_get_search_for(QofQuery * q)1400 qof_query_get_search_for (QofQuery * q)
1401 {
1402 	if (!q)
1403 		return NULL;
1404 	return q->search_for;
1405 }
1406 
1407 GList *
qof_query_get_terms(QofQuery * q)1408 qof_query_get_terms (QofQuery * q)
1409 {
1410 	if (!q)
1411 		return NULL;
1412 	return q->terms;
1413 }
1414 
1415 GSList *
qof_query_term_get_param_path(QofQueryTerm * qt)1416 qof_query_term_get_param_path (QofQueryTerm * qt)
1417 {
1418 	if (!qt)
1419 		return NULL;
1420 	return qt->param_list;
1421 }
1422 
1423 QofQueryPredData *
qof_query_term_get_pred_data(QofQueryTerm * qt)1424 qof_query_term_get_pred_data (QofQueryTerm * qt)
1425 {
1426 	if (!qt)
1427 		return NULL;
1428 	return qt->pdata;
1429 }
1430 
1431 gboolean
qof_query_term_is_inverted(QofQueryTerm * qt)1432 qof_query_term_is_inverted (QofQueryTerm * qt)
1433 {
1434 	if (!qt)
1435 		return FALSE;
1436 	return qt->invert;
1437 }
1438 
1439 void
qof_query_get_sorts(QofQuery * q,QofQuerySort ** primary,QofQuerySort ** secondary,QofQuerySort ** tertiary)1440 qof_query_get_sorts (QofQuery * q, QofQuerySort ** primary,
1441 	QofQuerySort ** secondary, QofQuerySort ** tertiary)
1442 {
1443 	if (!q)
1444 		return;
1445 	if (primary)
1446 		*primary = &(q->primary_sort);
1447 	if (secondary)
1448 		*secondary = &(q->secondary_sort);
1449 	if (tertiary)
1450 		*tertiary = &(q->tertiary_sort);
1451 }
1452 
1453 GSList *
qof_query_sort_get_param_path(QofQuerySort * qs)1454 qof_query_sort_get_param_path (QofQuerySort * qs)
1455 {
1456 	if (!qs)
1457 		return NULL;
1458 	return qs->param_list;
1459 }
1460 
1461 gint
qof_query_sort_get_sort_options(QofQuerySort * qs)1462 qof_query_sort_get_sort_options (QofQuerySort * qs)
1463 {
1464 	if (!qs)
1465 		return 0;
1466 	return qs->options;
1467 }
1468 
1469 gboolean
qof_query_sort_get_increasing(QofQuerySort * qs)1470 qof_query_sort_get_increasing (QofQuerySort * qs)
1471 {
1472 	if (!qs)
1473 		return FALSE;
1474 	return qs->increasing;
1475 }
1476 
1477 static gboolean
qof_query_term_equal(QofQueryTerm * qt1,QofQueryTerm * qt2)1478 qof_query_term_equal (QofQueryTerm * qt1, QofQueryTerm * qt2)
1479 {
1480 	if (qt1 == qt2)
1481 		return TRUE;
1482 	if (!qt1 || !qt2)
1483 		return FALSE;
1484 
1485 	if (qt1->invert != qt2->invert)
1486 		return FALSE;
1487 	if (param_list_cmp (qt1->param_list, qt2->param_list))
1488 		return FALSE;
1489 	return qof_query_core_predicate_equal (qt1->pdata, qt2->pdata);
1490 }
1491 
1492 static gboolean
qof_query_sort_equal(QofQuerySort * qs1,QofQuerySort * qs2)1493 qof_query_sort_equal (QofQuerySort * qs1, QofQuerySort * qs2)
1494 {
1495 	if (qs1 == qs2)
1496 		return TRUE;
1497 	if (!qs1 || !qs2)
1498 		return FALSE;
1499 
1500 	/* "Empty" sorts are equivalent, regardless of the flags */
1501 	if (!qs1->param_list && !qs2->param_list)
1502 		return TRUE;
1503 
1504 	if (qs1->options != qs2->options)
1505 		return FALSE;
1506 	if (qs1->increasing != qs2->increasing)
1507 		return FALSE;
1508 	return (param_list_cmp (qs1->param_list, qs2->param_list) == 0);
1509 }
1510 
1511 gboolean
qof_query_equal(QofQuery * q1,QofQuery * q2)1512 qof_query_equal (QofQuery * q1, QofQuery * q2)
1513 {
1514 	GList *or1, *or2;
1515 
1516 	if (q1 == q2)
1517 		return TRUE;
1518 	if (!q1 || !q2)
1519 		return FALSE;
1520 
1521 	if (g_list_length (q1->terms) != g_list_length (q2->terms))
1522 		return FALSE;
1523 	if (q1->max_results != q2->max_results)
1524 		return FALSE;
1525 
1526 	for (or1 = q1->terms, or2 = q2->terms; or1;
1527 		or1 = or1->next, or2 = or2->next)
1528 	{
1529 		GList *and1, *and2;
1530 
1531 		and1 = or1->data;
1532 		and2 = or2->data;
1533 
1534 		if (g_list_length (and1) != g_list_length (and2))
1535 			return FALSE;
1536 
1537 		for (; and1; and1 = and1->next, and2 = and2->next)
1538 			if (!qof_query_term_equal (and1->data, and2->data))
1539 				return FALSE;
1540 	}
1541 
1542 	if (!qof_query_sort_equal (&(q1->primary_sort), &(q2->primary_sort)))
1543 		return FALSE;
1544 	if (!qof_query_sort_equal (&(q1->secondary_sort),
1545 			&(q2->secondary_sort)))
1546 		return FALSE;
1547 	if (!qof_query_sort_equal (&(q1->tertiary_sort), &(q2->tertiary_sort)))
1548 		return FALSE;
1549 
1550 	return TRUE;
1551 }
1552 
1553 /* **************************************************************************/
1554 /* Query Print functions for use with qof_log_set_level.
1555 */
1556 
1557 /* Static prototypes */
1558 static GList *qof_query_printSearchFor (QofQuery * query, GList * output);
1559 static GList *qof_query_printTerms (QofQuery * query, GList * output);
1560 static GList *qof_query_printSorts (QofQuerySort * s[],
1561 	const gint numSorts, GList * output);
1562 static GList *qof_query_printAndTerms (GList * terms, GList * output);
1563 static gchar *qof_query_printStringForHow (QofQueryCompare how);
1564 static gchar *qof_query_printStringMatch (QofStringMatch s);
1565 static gchar *qof_query_printDateMatch (QofDateMatch d);
1566 static gchar *qof_query_printNumericMatch (QofNumericMatch n);
1567 static gchar *qof_query_printGuidMatch (QofGuidMatch g);
1568 static gchar *qof_query_printCharMatch (QofCharMatch c);
1569 static GList *qof_query_printPredData (QofQueryPredData * pd, GList * lst);
1570 static GString *qof_query_printParamPath (GSList * parmList);
1571 static void qof_query_printValueForParam (QofQueryPredData * pd,
1572 	GString * gs);
1573 static void qof_query_printOutput (GList * output);
1574 
1575 /** \deprecated this will be private in libqof2. */
1576 void
qof_query_print(QofQuery * query)1577 qof_query_print (QofQuery * query)
1578 {
1579 	GList *output;
1580 	GString *str;
1581 	QofQuerySort *s[3];
1582 	gint maxResults = 0, numSorts = 3;
1583 
1584 	ENTER (" ");
1585 
1586 	if (!query)
1587 	{
1588 		LEAVE ("query is (null)");
1589 		return;
1590 	}
1591 
1592 	output = NULL;
1593 	str = NULL;
1594 	maxResults = qof_query_get_max_results (query);
1595 
1596 	output = qof_query_printSearchFor (query, output);
1597 	output = qof_query_printTerms (query, output);
1598 
1599 	qof_query_get_sorts (query, &s[0], &s[1], &s[2]);
1600 
1601 	if (s[0])
1602 	{
1603 		output = qof_query_printSorts (s, numSorts, output);
1604 	}
1605 
1606 	str = g_string_new (" ");
1607 	g_string_printf (str, "Maximum number of results: %d", maxResults);
1608 	output = g_list_append (output, str);
1609 
1610 	qof_query_printOutput (output);
1611 	LEAVE (" ");
1612 }
1613 
1614 static void
qof_query_printOutput(GList * output)1615 qof_query_printOutput (GList * output)
1616 {
1617 	GList *lst;
1618 
1619 	for (lst = output; lst; lst = lst->next)
1620 	{
1621 		GString *line = (GString *) lst->data;
1622 
1623 		DEBUG (" %s", line->str);
1624 		g_string_free (line, TRUE);
1625 		line = NULL;
1626 	}
1627 }
1628 
1629 /*
1630     Get the search_for type--This is the type of Object
1631     we are searching for (SPLIT, TRANS, etc)
1632 */
1633 static GList *
qof_query_printSearchFor(QofQuery * query,GList * output)1634 qof_query_printSearchFor (QofQuery * query, GList * output)
1635 {
1636 	QofIdType searchFor;
1637 	GString *gs;
1638 
1639 	searchFor = qof_query_get_search_for (query);
1640 	gs = g_string_new ("Query Object Type: ");
1641 	g_string_append (gs, (NULL == searchFor) ? "(null)" : searchFor);
1642 	output = g_list_append (output, gs);
1643 
1644 	return output;
1645 }	/* qof_query_printSearchFor */
1646 
1647 /*
1648     Run through the terms of the query.  This is a outer-inner
1649     loop.  The elements of the outer loop are ORed, and the
1650     elements of the inner loop are ANDed.
1651 */
1652 static GList *
qof_query_printTerms(QofQuery * query,GList * output)1653 qof_query_printTerms (QofQuery * query, GList * output)
1654 {
1655 
1656 	GList *terms, *lst;
1657 
1658 	terms = qof_query_get_terms (query);
1659 
1660 	for (lst = terms; lst; lst = lst->next)
1661 	{
1662 		output =
1663 			g_list_append (output, g_string_new ("OR Terms:"));
1664 
1665 		if (lst->data)
1666 		{
1667 			output = qof_query_printAndTerms (lst->data, output);
1668 		}
1669 		else
1670 		{
1671 			output =
1672 				g_list_append (output,
1673 				g_string_new ("  No data for AND terms"));
1674 		}
1675 	}
1676 
1677 	return output;
1678 }	/* qof_query_printTerms */
1679 
1680 /*
1681     Process the sort parameters
1682     If this function is called, the assumption is that the first sort
1683     not null.
1684 */
1685 static GList *
qof_query_printSorts(QofQuerySort * s[],const gint numSorts,GList * output)1686 qof_query_printSorts (QofQuerySort * s[], const gint numSorts,
1687 	GList * output)
1688 {
1689 	GSList *gsl, *n = NULL;
1690 	gint curSort;
1691 	GString *gs = g_string_new ("Sort Parameters:   ");
1692 
1693 	for (curSort = 0; curSort < numSorts; curSort++)
1694 	{
1695 		gboolean increasing;
1696 		if (!s[curSort])
1697 		{
1698 			break;
1699 		}
1700 		increasing = qof_query_sort_get_increasing (s[curSort]);
1701 
1702 		gsl = qof_query_sort_get_param_path (s[curSort]);
1703 		if (gsl)
1704 			g_string_append_printf (gs, " Param: ");
1705 		for (n = gsl; n; n = n->next)
1706 		{
1707 			QofIdType param_name = n->data;
1708 			if (gsl != n)
1709 				g_string_append_printf (gs, " ");
1710 			g_string_append_printf (gs, "%s", param_name);
1711 		}
1712 		if (gsl)
1713 		{
1714 			g_string_append_printf (gs, " %s ",
1715 				increasing ? "DESC" : "ASC");
1716 			g_string_append_printf (gs, " Options: 0x%x ",
1717 				s[curSort]->options);
1718 		}
1719 	}
1720 
1721 	output = g_list_append (output, gs);
1722 	return output;
1723 
1724 }		/* qof_query_printSorts */
1725 
1726 /*
1727     Process the AND terms of the query.  This is a GList
1728     of WHERE terms that will be ANDed
1729 */
1730 static GList *
qof_query_printAndTerms(GList * terms,GList * output)1731 qof_query_printAndTerms (GList * terms, GList * output)
1732 {
1733 	const gchar *prefix = "AND Terms:";
1734 	QofQueryTerm *qt;
1735 	QofQueryPredData *pd;
1736 	GSList *path;
1737 	GList *lst;
1738 	gboolean invert;
1739 
1740 	output = g_list_append (output, g_string_new (prefix));
1741 	for (lst = terms; lst; lst = lst->next)
1742 	{
1743 		qt = (QofQueryTerm *) lst->data;
1744 		pd = qof_query_term_get_pred_data (qt);
1745 		path = qof_query_term_get_param_path (qt);
1746 		invert = qof_query_term_is_inverted (qt);
1747 
1748 		if (invert)
1749 			output =
1750 				g_list_append (output, g_string_new (" INVERT SENSE "));
1751 		output = g_list_append (output, qof_query_printParamPath (path));
1752 		output = qof_query_printPredData (pd, output);
1753 //    output = g_list_append (output, g_string_new(" "));
1754 	}
1755 
1756 	return output;
1757 }	/* qof_query_printAndTerms */
1758 
1759 /*
1760     Process the parameter types of the predicate data
1761 */
1762 static GString *
qof_query_printParamPath(GSList * parmList)1763 qof_query_printParamPath (GSList * parmList)
1764 {
1765 	GSList *list = NULL;
1766 	GString *gs = g_string_new ("Param List: ");
1767 	g_string_append (gs, " ");
1768 	for (list = parmList; list; list = list->next)
1769 	{
1770 		g_string_append (gs, (gchar *) list->data);
1771 		if (list->next)
1772 			g_string_append (gs, ", ");
1773 	}
1774 
1775 	return gs;
1776 }	/* qof_query_printParamPath */
1777 
1778 /*
1779     Process the PredData of the AND terms
1780 */
1781 static GList *
qof_query_printPredData(QofQueryPredData * pd,GList * lst)1782 qof_query_printPredData (QofQueryPredData * pd, GList * lst)
1783 {
1784 	GString *gs;
1785 
1786 	gs = g_string_new ("Pred Data: ");
1787 	g_string_append (gs, (gchar *) pd->type_name);
1788 
1789 	/* Char Predicate and GUID predicate don't use the 'how' field. */
1790 	if (safe_strcmp (pd->type_name, QOF_TYPE_CHAR) &&
1791 		safe_strcmp (pd->type_name, QOF_TYPE_GUID))
1792 	{
1793 		g_string_append_printf (gs, " how: %s",
1794 			qof_query_printStringForHow (pd->how));
1795 	}
1796 	lst = g_list_append (lst, gs);
1797 	gs = g_string_new ("");
1798 	qof_query_printValueForParam (pd, gs);
1799 	lst = g_list_append (lst, gs);
1800 	return lst;
1801 }	/* qof_query_printPredData */
1802 
1803 /*
1804     Get a string representation for the
1805     QofCompareFunc enum type.
1806 */
1807 static gchar *
qof_query_printStringForHow(QofQueryCompare how)1808 qof_query_printStringForHow (QofQueryCompare how)
1809 {
1810 
1811 	switch (how)
1812 	{
1813 		AS_STRING_CASE(QOF_COMPARE_LT,)
1814 		AS_STRING_CASE(QOF_COMPARE_LTE,)
1815 		AS_STRING_CASE(QOF_COMPARE_EQUAL,)
1816 		AS_STRING_CASE(QOF_COMPARE_GT,)
1817 		AS_STRING_CASE(QOF_COMPARE_GTE,)
1818 		AS_STRING_CASE(QOF_COMPARE_NEQ,)
1819 	}
1820 	return "INVALID HOW";
1821 }	/* qncQueryPrintStringForHow */
1822 
1823 
1824 static void
qof_query_printValueForParam(QofQueryPredData * pd,GString * gs)1825 qof_query_printValueForParam (QofQueryPredData * pd, GString * gs)
1826 {
1827 
1828 	if (!safe_strcmp (pd->type_name, QOF_TYPE_GUID))
1829 	{
1830 		GList *node;
1831 		query_guid_t pdata = (query_guid_t) pd;
1832 		g_string_append_printf (gs, "Match type %s ",
1833 			qof_query_printGuidMatch (pdata->options));
1834 		for (node = pdata->guids; node; node = node->next)
1835 		{
1836 			/* THREAD-UNSAFE */
1837 			g_string_append_printf (gs, ", guids: %s",
1838 				guid_to_string ((GUID *) node->data));
1839 		}
1840 		return;
1841 	}
1842 	if (!safe_strcmp (pd->type_name, QOF_TYPE_STRING))
1843 	{
1844 		query_string_t pdata = (query_string_t) pd;
1845 		g_string_append_printf (gs, "Match type %s ",
1846 			qof_query_printStringMatch (pdata->options));
1847 		g_string_append_printf (gs, " %s string: %s",
1848 			pdata->is_regex ? "Regex" : "Not regex", pdata->matchstring);
1849 		return;
1850 	}
1851 	if (!safe_strcmp (pd->type_name, QOF_TYPE_NUMERIC))
1852 	{
1853 		query_numeric_t pdata = (query_numeric_t) pd;
1854 		g_string_append_printf (gs, "Match type %s ",
1855 			qof_query_printNumericMatch (pdata->options));
1856 		g_string_append_printf (gs, " numeric: %s",
1857 			qof_numeric_dbg_to_string (pdata->amount));
1858 		return;
1859 	}
1860 	if (!safe_strcmp (pd->type_name, QOF_TYPE_KVP))
1861 	{
1862 		GSList *node;
1863 		query_kvp_t pdata = (query_kvp_t) pd;
1864 		g_string_append_printf (gs, " kvp path: ");
1865 		for (node = pdata->path; node; node = node->next)
1866 		{
1867 			g_string_append_printf (gs, "/%s", (gchar *) node->data);
1868 		}
1869 		g_string_append_printf (gs, " kvp value: %s ",
1870 			kvp_value_to_string (pdata->value));
1871 		return;
1872 	}
1873 	if (!safe_strcmp (pd->type_name, QOF_TYPE_INT64))
1874 	{
1875 		query_int64_t pdata = (query_int64_t) pd;
1876 		g_string_append_printf (gs, " int64: %" G_GINT64_FORMAT,
1877 			pdata->val);
1878 		return;
1879 	}
1880 	if (!safe_strcmp (pd->type_name, QOF_TYPE_INT32))
1881 	{
1882 		query_int32_t pdata = (query_int32_t) pd;
1883 		g_string_append_printf (gs, " int32: %d", pdata->val);
1884 		return;
1885 	}
1886 	if (!safe_strcmp (pd->type_name, QOF_TYPE_DOUBLE))
1887 	{
1888 		query_double_t pdata = (query_double_t) pd;
1889 		g_string_append_printf (gs, " double: %.18g", pdata->val);
1890 		return;
1891 	}
1892 	if (!safe_strcmp (pd->type_name, QOF_TYPE_TIME))
1893 	{
1894 		query_time_t pdata;
1895 		QofDate *qd;
1896 
1897 		pdata = (query_time_t) pd;
1898 		qd = qof_date_from_qtime (pdata->qt);
1899 		g_string_append_printf (gs, "Match type %s " ,
1900 			qof_query_printDateMatch (pdata->options));
1901 		g_string_append_printf (gs, "query date: %s",
1902 			qof_date_print (qd, QOF_DATE_FORMAT_UTC));
1903 		qof_date_free (qd);
1904 	}
1905 	if (!safe_strcmp (pd->type_name, QOF_TYPE_CHAR))
1906 	{
1907 		query_char_t pdata = (query_char_t) pd;
1908 		g_string_append_printf (gs, "Match type %s ",
1909 			qof_query_printCharMatch (pdata->options));
1910 		g_string_append_printf (gs, " char list: %s",
1911 			pdata->char_list);
1912 		return;
1913 	}
1914 	if (!safe_strcmp (pd->type_name, QOF_TYPE_BOOLEAN))
1915 	{
1916 		query_boolean_t pdata = (query_boolean_t) pd;
1917 		g_string_append_printf (gs, " boolean: %s",
1918 			pdata->val ? "TRUE" : "FALSE");
1919 		return;
1920 	}
1921   /** \todo QOF_TYPE_COLLECT */
1922 	return;
1923 }	/* qof_query_printValueForParam */
1924 
1925 /*
1926  * Print out a string representation of the
1927  * QofStringMatch enum
1928  */
1929 static gchar *
qof_query_printStringMatch(QofStringMatch s)1930 qof_query_printStringMatch (QofStringMatch s)
1931 {
1932 	switch (s)
1933 	{
1934 		AS_STRING_CASE(QOF_STRING_MATCH_NORMAL,)
1935 		AS_STRING_CASE(QOF_STRING_MATCH_CASEINSENSITIVE,)
1936 	}
1937 	return "UNKNOWN MATCH TYPE";
1938 }	/* qof_query_printStringMatch */
1939 
1940 /*
1941  * Print out a string representation of the
1942  * QofDateMatch enum
1943  */
1944 static gchar *
qof_query_printDateMatch(QofDateMatch d)1945 qof_query_printDateMatch (QofDateMatch d)
1946 {
1947 	switch (d)
1948 	{
1949 		AS_STRING_CASE(QOF_DATE_MATCH_NORMAL,)
1950 		AS_STRING_CASE(QOF_DATE_MATCH_DAY,)
1951 	}
1952 	return "UNKNOWN MATCH TYPE";
1953 }	/* qof_query_printDateMatch */
1954 
1955 /*
1956  * Print out a string representation of the
1957  * QofNumericMatch enum
1958  */
1959 static gchar *
qof_query_printNumericMatch(QofNumericMatch n)1960 qof_query_printNumericMatch (QofNumericMatch n)
1961 {
1962 	switch (n)
1963 	{
1964 		AS_STRING_CASE(QOF_NUMERIC_MATCH_DEBIT,)
1965 		AS_STRING_CASE(QOF_NUMERIC_MATCH_CREDIT,)
1966 		AS_STRING_CASE(QOF_NUMERIC_MATCH_ANY,)
1967 	}
1968 	return "UNKNOWN MATCH TYPE";
1969 }	/* qof_query_printNumericMatch */
1970 
1971 /*
1972  * Print out a string representation of the
1973  * QofGuidMatch enum
1974  */
1975 static gchar *
qof_query_printGuidMatch(QofGuidMatch g)1976 qof_query_printGuidMatch (QofGuidMatch g)
1977 {
1978 	switch (g)
1979 	{
1980 		AS_STRING_CASE(QOF_GUID_MATCH_ANY,)
1981 		AS_STRING_CASE(QOF_GUID_MATCH_ALL,)
1982 		AS_STRING_CASE(QOF_GUID_MATCH_NONE,)
1983 		AS_STRING_CASE(QOF_GUID_MATCH_NULL,)
1984 		AS_STRING_CASE(QOF_GUID_MATCH_LIST_ANY,)
1985 	}
1986 
1987 	return "UNKNOWN MATCH TYPE";
1988 }	/* qof_query_printGuidMatch */
1989 
1990 /*
1991  * Print out a string representation of the
1992  * QofCharMatch enum
1993  */
1994 static gchar *
qof_query_printCharMatch(QofCharMatch c)1995 qof_query_printCharMatch (QofCharMatch c)
1996 {
1997 	switch (c)
1998 	{
1999 		AS_STRING_CASE(QOF_CHAR_MATCH_ANY,)
2000 		AS_STRING_CASE(QOF_CHAR_MATCH_NONE,)
2001 	}
2002 	return "UNKNOWN MATCH TYPE";
2003 }	/* qof_query_printGuidMatch */
2004 
2005 /* ======================== END OF FILE =================== */
2006