Updated Macedonian Translation <ufo@linux.net.mk>
[rhythmbox.git] / rhythmdb / rhythmdb-query.c
blob8702da92ed7f1095c0dc9fb31841461fe5f73d0b
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of RhythmDB - Rhythmbox backend queryable database
5 * Copyright (C) 2003,2004 Colin Walters <walters@gnome.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include <config.h>
25 #include <string.h>
27 #include <glib.h>
28 #include <glib-object.h>
29 #include <gobject/gvaluecollector.h>
31 #include "rhythmdb.h"
32 #include "rhythmdb-private.h"
33 #include "rb-util.h"
35 #define RB_PARSE_CONJ (xmlChar *) "conjunction"
36 #define RB_PARSE_SUBQUERY (xmlChar *) "subquery"
37 #define RB_PARSE_LIKE (xmlChar *) "like"
38 #define RB_PARSE_PROP (xmlChar *) "prop"
39 #define RB_PARSE_NOT_LIKE (xmlChar *) "not-like"
40 #define RB_PARSE_PREFIX (xmlChar *) "prefix"
41 #define RB_PARSE_SUFFIX (xmlChar *) "suffix"
42 #define RB_PARSE_EQUALS (xmlChar *) "equals"
43 #define RB_PARSE_DISJ (xmlChar *) "disjunction"
44 #define RB_PARSE_GREATER (xmlChar *) "greater"
45 #define RB_PARSE_LESS (xmlChar *) "less"
46 #define RB_PARSE_CURRENT_TIME_WITHIN (xmlChar *) "current-time-within"
47 #define RB_PARSE_CURRENT_TIME_NOT_WITHIN (xmlChar *) "current-time-not-within"
48 #define RB_PARSE_YEAR_EQUALS RB_PARSE_EQUALS
49 #define RB_PARSE_YEAR_GREATER RB_PARSE_GREATER
50 #define RB_PARSE_YEAR_LESS RB_PARSE_LESS
51 /**
52 * rhythmdb_query_copy:
53 * @array: the query to copy.
55 * Creates a copy of a query.
57 * Return value: a copy of the passed query. It must be freed with rhythmdb_query_free()
58 **/
59 GPtrArray *
60 rhythmdb_query_copy (GPtrArray *array)
62 GPtrArray *ret;
64 if (!array)
65 return NULL;
67 ret = g_ptr_array_sized_new (array->len);
68 rhythmdb_query_concatenate (ret, array);
70 return ret;
73 void
74 rhythmdb_query_concatenate (GPtrArray *query1, GPtrArray *query2)
76 guint i;
78 g_assert (query2);
79 if (!query2)
80 return;
82 for (i = 0; i < query2->len; i++) {
83 RhythmDBQueryData *data = g_ptr_array_index (query2, i);
84 RhythmDBQueryData *new_data = g_new0 (RhythmDBQueryData, 1);
85 new_data->type = data->type;
86 new_data->propid = data->propid;
87 if (data->val) {
88 new_data->val = g_new0 (GValue, 1);
89 g_value_init (new_data->val, G_VALUE_TYPE (data->val));
90 g_value_copy (data->val, new_data->val);
92 if (data->subquery)
93 new_data->subquery = rhythmdb_query_copy (data->subquery);
94 g_ptr_array_add (query1, new_data);
98 GPtrArray *
99 rhythmdb_query_parse_valist (RhythmDB *db, va_list args)
101 RhythmDBQueryType query;
102 GPtrArray *ret = g_ptr_array_new ();
103 char *error;
105 while ((query = va_arg (args, RhythmDBQueryType)) != RHYTHMDB_QUERY_END) {
106 RhythmDBQueryData *data = g_new0 (RhythmDBQueryData, 1);
107 data->type = query;
108 switch (query) {
109 case RHYTHMDB_QUERY_DISJUNCTION:
110 break;
111 case RHYTHMDB_QUERY_SUBQUERY:
112 data->subquery = rhythmdb_query_copy (va_arg (args, GPtrArray *));
113 break;
114 case RHYTHMDB_QUERY_PROP_EQUALS:
115 case RHYTHMDB_QUERY_PROP_LIKE:
116 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
117 case RHYTHMDB_QUERY_PROP_PREFIX:
118 case RHYTHMDB_QUERY_PROP_SUFFIX:
119 case RHYTHMDB_QUERY_PROP_GREATER:
120 case RHYTHMDB_QUERY_PROP_LESS:
121 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
122 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
123 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
124 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
125 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
126 data->propid = va_arg (args, guint);
127 data->val = g_new0 (GValue, 1);
128 g_value_init (data->val, rhythmdb_get_property_type (db, data->propid));
129 G_VALUE_COLLECT (data->val, args, 0, &error);
130 break;
131 case RHYTHMDB_QUERY_END:
132 g_assert_not_reached ();
133 break;
135 g_ptr_array_add (ret, data);
137 return ret;
141 * rhythmdb_query_parse:
142 * @db: a #RhythmDB instance
144 * Creates a query from a list of criteria.
146 * Most criteria consists of an operator (#RhythmDBQueryType),
147 * a property (#RhythmDBPropType) and the data to compare with. An entry
148 * matches a criteria if the operator returns true with the value of the
149 * entries property as the first argument, and the given data as the second
150 * argument.
152 * Three types criteria are special. Passing RHYTHMDB_QUERY_END indicates the
153 * end of the list of criteria, and must be the last passes parameter.
155 * The second special criteria is a subquery which is defined by passing
156 * RHYTHMDB_QUERY_SUBQUERY, followed by a query (#GPtrArray). An entry will
157 * match a subquery criteria if it matches all criteria in the subquery.
159 * The third special criteria is a disjunction which is defined by passing
160 * RHYTHMDB_QUERY_DISJUNCTION, which will make an entry match the query if
161 * it matches the criteria before the disjunction, the criteria after the
162 * disjunction, or both.
164 * Example:
165 * rhythmdb_query_parse (db,
166 * RHYTHMDB_QUERY_SUBQUERY, subquery,
167 * RHYTHMDB_QUERY_DISJUNCTION
168 * RHYTHMDB_QUERY_PROP_LIKE, RHYTHMDB_PROP_TITLE, "cat",
169 * RHYTHMDB_QUERY_DISJUNCTION
170 * RHYTHMDB_QUERY_PROP_GREATER, RHYTHMDB_PROP_RATING, 2.5,
171 * RHYTHMDB_QUERY_PROP_LESS, RHYTHMDB_PROP_PLAY_COUNT, 10,
172 * RHYTHMDB_QUERY_END);
174 * will create a query that matches entries:
175 * a) that match the query "subquery", or
176 * b) that have "cat" in their title, or
177 * c) have a rating of at least 2.5, and a play count of at most 10
179 * Returns: a the newly created query. It must be freed with rhythmdb_query_free()
181 GPtrArray *
182 rhythmdb_query_parse (RhythmDB *db, ...)
184 GPtrArray *ret;
185 va_list args;
187 va_start (args, db);
189 ret = rhythmdb_query_parse_valist (db, args);
191 va_end (args);
193 return ret;
197 * rhythmdb_query_append:
198 * @db: a #RhythmDB instance
199 * @query: a query.
201 * Appends new criteria to the query @query.
203 * The list of criteria must be in the same format as for rhythmdb_query_parse,
204 * and ended by RHYTHMDB_QUERY_END.
206 void
207 rhythmdb_query_append (RhythmDB *db, GPtrArray *query, ...)
209 va_list args;
210 guint i;
211 GPtrArray *new = g_ptr_array_new ();
213 va_start (args, query);
215 new = rhythmdb_query_parse_valist (db, args);
217 for (i = 0; i < new->len; i++)
218 g_ptr_array_add (query, g_ptr_array_index (new, i));
220 g_ptr_array_free (new, TRUE);
222 va_end (args);
226 * rhythmdb_query_free:
227 * @query: a query.
229 * Frees the query @query
231 void
232 rhythmdb_query_free (GPtrArray *query)
234 guint i;
236 if (query == NULL)
237 return;
239 for (i = 0; i < query->len; i++) {
240 RhythmDBQueryData *data = g_ptr_array_index (query, i);
241 switch (data->type) {
242 case RHYTHMDB_QUERY_DISJUNCTION:
243 break;
244 case RHYTHMDB_QUERY_SUBQUERY:
245 rhythmdb_query_free (data->subquery);
246 break;
247 case RHYTHMDB_QUERY_PROP_EQUALS:
248 case RHYTHMDB_QUERY_PROP_LIKE:
249 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
250 case RHYTHMDB_QUERY_PROP_PREFIX:
251 case RHYTHMDB_QUERY_PROP_SUFFIX:
252 case RHYTHMDB_QUERY_PROP_GREATER:
253 case RHYTHMDB_QUERY_PROP_LESS:
254 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
255 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
256 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
257 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
258 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
259 g_value_unset (data->val);
260 g_free (data->val);
261 break;
262 case RHYTHMDB_QUERY_END:
263 g_assert_not_reached ();
264 break;
266 g_free (data);
269 g_ptr_array_free (query, TRUE);
272 static char *
273 prop_gvalue_to_string (RhythmDB *db,
274 RhythmDBPropType propid,
275 GValue *val)
277 /* special-case some properties */
278 switch (propid) {
279 case RHYTHMDB_PROP_TYPE:
281 RhythmDBEntryType type = g_value_get_pointer (val);
282 return g_strdup (type->name);
284 break;
285 default:
286 break;
289 /* otherwise just convert numbers to strings */
290 switch (G_VALUE_TYPE (val)) {
291 case G_TYPE_STRING:
292 return g_value_dup_string (val);
293 case G_TYPE_BOOLEAN:
294 return g_strdup_printf ("%d", g_value_get_boolean (val));
295 case G_TYPE_INT:
296 return g_strdup_printf ("%d", g_value_get_int (val));
297 case G_TYPE_LONG:
298 return g_strdup_printf ("%ld", g_value_get_long (val));
299 case G_TYPE_ULONG:
300 return g_strdup_printf ("%lu", g_value_get_ulong (val));
301 case G_TYPE_UINT64:
302 return g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (val));
303 case G_TYPE_FLOAT:
304 return g_strdup_printf ("%f", g_value_get_float (val));
305 case G_TYPE_DOUBLE:
306 return g_strdup_printf ("%f", g_value_get_double (val));
307 default:
308 g_assert_not_reached ();
309 return NULL;
313 static void
314 write_encoded_gvalue (RhythmDB *db,
315 xmlNodePtr node,
316 RhythmDBPropType propid,
317 GValue *val)
319 char *strval = NULL;
320 xmlChar *quoted;
322 strval = prop_gvalue_to_string (db, propid, val);
323 quoted = xmlEncodeEntitiesReentrant (NULL, BAD_CAST strval);
324 g_free (strval);
326 xmlNodeSetContent (node, quoted);
327 g_free (quoted);
330 void
331 rhythmdb_read_encoded_property (RhythmDB *db,
332 const char *content,
333 RhythmDBPropType propid,
334 GValue *val)
336 g_value_init (val, rhythmdb_get_property_type (db, propid));
338 switch (G_VALUE_TYPE (val)) {
339 case G_TYPE_STRING:
340 g_value_set_string (val, content);
341 break;
342 case G_TYPE_BOOLEAN:
343 g_value_set_boolean (val, g_ascii_strtoull (content, NULL, 10));
344 break;
345 case G_TYPE_ULONG:
346 g_value_set_ulong (val, g_ascii_strtoull (content, NULL, 10));
347 break;
348 case G_TYPE_UINT64:
349 g_value_set_uint64 (val, g_ascii_strtoull (content, NULL, 10));
350 break;
351 case G_TYPE_DOUBLE:
352 g_value_set_double (val, g_ascii_strtod (content, NULL));
353 break;
354 case G_TYPE_POINTER:
355 if (propid == RHYTHMDB_PROP_TYPE) {
356 RhythmDBEntryType entry_type;
357 entry_type = rhythmdb_entry_type_get_by_name (db, content);
358 if (entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) {
359 g_value_set_pointer (val, entry_type);
360 break;
361 } else {
362 g_warning ("Unexpected entry type");
363 /* Fall through */
366 /* Falling through on purpose to get an assert for unexpected
367 * cases
369 default:
370 g_warning ("Attempt to read '%s' of unhandled type %s",
371 rhythmdb_nice_elt_name_from_propid (db, propid),
372 g_type_name (G_VALUE_TYPE (val)));
373 g_assert_not_reached ();
374 break;
378 void
379 rhythmdb_query_serialize (RhythmDB *db, GPtrArray *query,
380 xmlNodePtr parent)
382 guint i;
383 xmlNodePtr node = xmlNewChild (parent, NULL, RB_PARSE_CONJ, NULL);
384 xmlNodePtr subnode;
386 for (i = 0; i < query->len; i++) {
387 RhythmDBQueryData *data = g_ptr_array_index (query, i);
389 switch (data->type) {
390 case RHYTHMDB_QUERY_SUBQUERY:
391 subnode = xmlNewChild (node, NULL, RB_PARSE_SUBQUERY, NULL);
392 rhythmdb_query_serialize (db, data->subquery, subnode);
393 break;
394 case RHYTHMDB_QUERY_PROP_LIKE:
395 subnode = xmlNewChild (node, NULL, RB_PARSE_LIKE, NULL);
396 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
397 write_encoded_gvalue (db, subnode, data->propid, data->val);
398 break;
399 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
400 subnode = xmlNewChild (node, NULL, RB_PARSE_NOT_LIKE, NULL);
401 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
402 write_encoded_gvalue (db, subnode, data->propid, data->val);
403 break;
404 case RHYTHMDB_QUERY_PROP_PREFIX:
405 subnode = xmlNewChild (node, NULL, RB_PARSE_PREFIX, NULL);
406 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
407 write_encoded_gvalue (db, subnode, data->propid, data->val);
408 break;
409 case RHYTHMDB_QUERY_PROP_SUFFIX:
410 subnode = xmlNewChild (node, NULL, RB_PARSE_SUFFIX, NULL);
411 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
412 write_encoded_gvalue (db, subnode, data->propid, data->val);
413 break;
414 case RHYTHMDB_QUERY_PROP_EQUALS:
415 subnode = xmlNewChild (node, NULL, RB_PARSE_EQUALS, NULL);
416 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
417 write_encoded_gvalue (db, subnode, data->propid, data->val);
418 break;
419 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
420 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_EQUALS, NULL);
421 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
422 write_encoded_gvalue (db, subnode, data->propid, data->val);
423 break;
424 case RHYTHMDB_QUERY_DISJUNCTION:
425 subnode = xmlNewChild (node, NULL, RB_PARSE_DISJ, NULL);
426 break;
427 case RHYTHMDB_QUERY_END:
428 break;
429 case RHYTHMDB_QUERY_PROP_GREATER:
430 subnode = xmlNewChild (node, NULL, RB_PARSE_GREATER, NULL);
431 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
432 write_encoded_gvalue (db, subnode, data->propid, data->val);
433 break;
434 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
435 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_GREATER, NULL);
436 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
437 write_encoded_gvalue (db, subnode, data->propid, data->val);
438 break;
439 case RHYTHMDB_QUERY_PROP_LESS:
440 subnode = xmlNewChild (node, NULL, RB_PARSE_LESS, NULL);
441 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
442 write_encoded_gvalue (db, subnode, data->propid, data->val);
443 break;
444 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
445 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_LESS, NULL);
446 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
447 write_encoded_gvalue (db, subnode, data->propid, data->val);
448 break;
449 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
450 subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_WITHIN, NULL);
451 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
452 write_encoded_gvalue (db, subnode, data->propid, data->val);
453 break;
454 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
455 subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_NOT_WITHIN, NULL);
456 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
457 write_encoded_gvalue (db, subnode, data->propid, data->val);
458 break;
463 GPtrArray *
464 rhythmdb_query_deserialize (RhythmDB *db, xmlNodePtr parent)
466 GPtrArray *query = g_ptr_array_new ();
467 xmlNodePtr child;
469 g_assert (!xmlStrcmp (parent->name, RB_PARSE_CONJ));
471 for (child = parent->children; child; child = child->next) {
472 RhythmDBQueryData *data;
474 if (xmlNodeIsText (child))
475 continue;
477 data = g_new0 (RhythmDBQueryData, 1);
479 if (!xmlStrcmp (child->name, RB_PARSE_SUBQUERY)) {
480 xmlNodePtr subquery;
481 data->type = RHYTHMDB_QUERY_SUBQUERY;
482 subquery = child->children;
483 while (xmlNodeIsText (subquery))
484 subquery = subquery->next;
486 data->subquery = rhythmdb_query_deserialize (db, subquery);
487 } else if (!xmlStrcmp (child->name, RB_PARSE_DISJ)) {
488 data->type = RHYTHMDB_QUERY_DISJUNCTION;
489 } else if (!xmlStrcmp (child->name, RB_PARSE_LIKE)) {
490 data->type = RHYTHMDB_QUERY_PROP_LIKE;
491 } else if (!xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)) {
492 data->type = RHYTHMDB_QUERY_PROP_NOT_LIKE;
493 } else if (!xmlStrcmp (child->name, RB_PARSE_PREFIX)) {
494 data->type = RHYTHMDB_QUERY_PROP_PREFIX;
495 } else if (!xmlStrcmp (child->name, RB_PARSE_SUFFIX)) {
496 data->type = RHYTHMDB_QUERY_PROP_SUFFIX;
497 } else if (!xmlStrcmp (child->name, RB_PARSE_EQUALS)) {
498 xmlChar* prop;
500 prop = xmlGetProp(child, RB_PARSE_PROP);
501 if (!xmlStrcmp(prop, (xmlChar *)"date"))
502 data->type = RHYTHMDB_QUERY_PROP_YEAR_EQUALS;
503 else
504 data->type = RHYTHMDB_QUERY_PROP_EQUALS;
505 xmlFree (prop);
506 } else if (!xmlStrcmp (child->name, RB_PARSE_GREATER)) {
507 xmlChar* prop;
509 prop = xmlGetProp(child, RB_PARSE_PROP);
510 if (!xmlStrcmp(prop, (xmlChar *)"date"))
511 data->type = RHYTHMDB_QUERY_PROP_YEAR_GREATER;
512 else
513 data->type = RHYTHMDB_QUERY_PROP_GREATER;
514 xmlFree (prop);
515 } else if (!xmlStrcmp (child->name, RB_PARSE_LESS)) {
516 xmlChar* prop;
518 prop = xmlGetProp(child, RB_PARSE_PROP);
519 if (!xmlStrcmp(prop, (xmlChar *)"date"))
520 data->type = RHYTHMDB_QUERY_PROP_YEAR_LESS;
521 else
522 data->type = RHYTHMDB_QUERY_PROP_LESS;
523 xmlFree (prop);
524 } else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)) {
525 data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN;
526 } else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
527 data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN;
528 } else
529 g_assert_not_reached ();
531 if (!xmlStrcmp (child->name, RB_PARSE_LIKE)
532 || !xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)
533 || !xmlStrcmp (child->name, RB_PARSE_PREFIX)
534 || !xmlStrcmp (child->name, RB_PARSE_SUFFIX)
535 || !xmlStrcmp (child->name, RB_PARSE_EQUALS)
536 || !xmlStrcmp (child->name, RB_PARSE_GREATER)
537 || !xmlStrcmp (child->name, RB_PARSE_LESS)
538 || !xmlStrcmp (child->name, RB_PARSE_YEAR_EQUALS)
539 || !xmlStrcmp (child->name, RB_PARSE_YEAR_GREATER)
540 || !xmlStrcmp (child->name, RB_PARSE_YEAR_LESS)
541 || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)
542 || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
543 char *content;
544 xmlChar *propstr = xmlGetProp (child, RB_PARSE_PROP);
545 gint propid = rhythmdb_propid_from_nice_elt_name (db, propstr);
546 g_free (propstr);
548 g_assert (propid >= 0 && propid < RHYTHMDB_NUM_PROPERTIES);
550 data->propid = propid;
551 data->val = g_new0 (GValue, 1);
553 content = (char *)xmlNodeGetContent (child);
554 rhythmdb_read_encoded_property (db, content, data->propid, data->val);
555 g_free (content);
558 g_ptr_array_add (query, data);
561 return query;
565 * This is used to "process" queries, before using them. It is mainly used to two things:
567 * 1) performing expensive data transformations once per query, rather than
568 * once per entry we try to match against. e.g. RHYTHMDB_PROP_SEARCH_MATCH
570 * 2) defining criteria in terms of other lower-level ones that the db backend
571 * actually implements. e.g. RHYTHMDB_QUERY_YEAR_*
574 void
575 rhythmdb_query_preprocess (RhythmDB *db, GPtrArray *query)
577 int i;
579 if (query == NULL)
580 return;
582 for (i = 0; i < query->len; i++) {
583 RhythmDBQueryData *data = g_ptr_array_index (query, i);
584 gboolean restart_criteria = FALSE;
586 if (data->subquery) {
587 rhythmdb_query_preprocess (db, data->subquery);
588 } else switch (data->propid) {
589 case RHYTHMDB_PROP_TITLE_FOLDED:
590 case RHYTHMDB_PROP_GENRE_FOLDED:
591 case RHYTHMDB_PROP_ARTIST_FOLDED:
592 case RHYTHMDB_PROP_ALBUM_FOLDED:
594 /* as we are matching against a folded property, the string needs to also be folded */
595 const char *orig = g_value_get_string (data->val);
596 char *folded = rb_search_fold (orig);
598 g_value_reset (data->val);
599 g_value_take_string (data->val, folded);
600 break;
603 case RHYTHMDB_PROP_SEARCH_MATCH:
605 const char *orig = g_value_get_string (data->val);
606 char *folded = rb_search_fold (orig);
607 char **words = rb_string_split_words (folded);
609 g_free (folded);
610 g_value_unset (data->val);
611 g_value_init (data->val, G_TYPE_STRV);
612 g_value_take_boxed (data->val, words);
613 break;
616 case RHYTHMDB_PROP_DATE:
618 GDate date = {0,};
619 gulong search_date;
620 gulong begin;
621 gulong end;
622 gulong year;
624 search_date = g_value_get_ulong (data->val);
625 g_date_set_julian (&date, search_date);
626 year = g_date_get_year (&date);
627 g_date_clear (&date, 1);
629 /* get Julian dates for beginning and end of year */
630 g_date_set_dmy (&date, 1, G_DATE_JANUARY, year);
631 begin = g_date_get_julian (&date);
632 g_date_clear (&date, 1);
634 /* and the day before the beginning of the next year */
635 g_date_set_dmy (&date, 1, G_DATE_JANUARY, year + 1);
636 end = g_date_get_julian (&date) - 1;
638 switch (data->type)
640 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
641 restart_criteria = TRUE;
642 data->type = RHYTHMDB_QUERY_SUBQUERY;
643 data->subquery = rhythmdb_query_parse (db,
644 RHYTHMDB_QUERY_PROP_GREATER, data->propid, begin,
645 RHYTHMDB_QUERY_PROP_LESS, data->propid, end,
646 RHYTHMDB_QUERY_END);
647 break;
649 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
650 restart_criteria = TRUE;
651 data->type = RHYTHMDB_QUERY_PROP_LESS;
652 g_value_set_ulong (data->val, end);
653 break;
655 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
656 restart_criteria = TRUE;
657 data->type = RHYTHMDB_QUERY_PROP_GREATER;
658 g_value_set_ulong (data->val, begin);
659 break;
661 default:
662 break;
665 break;
668 default:
669 break;
672 /* re-do this criteria, in case it needs further transformation */
673 if (restart_criteria)
674 i--;
678 void
679 rhythmdb_query_append_prop_multiple (RhythmDB *db, GPtrArray *query, RhythmDBPropType propid, GList *items)
681 GPtrArray *subquery;
683 if (items == NULL)
684 return;
686 if (items->next == NULL) {
687 rhythmdb_query_append (db,
688 query,
689 RHYTHMDB_QUERY_PROP_EQUALS,
690 propid,
691 items->data,
692 RHYTHMDB_QUERY_END);
693 return;
696 subquery = g_ptr_array_new ();
698 rhythmdb_query_append (db,
699 subquery,
700 RHYTHMDB_QUERY_PROP_EQUALS,
701 propid,
702 items->data,
703 RHYTHMDB_QUERY_END);
704 items = items->next;
705 while (items) {
706 rhythmdb_query_append (db,
707 subquery,
708 RHYTHMDB_QUERY_DISJUNCTION,
709 RHYTHMDB_QUERY_PROP_EQUALS,
710 propid,
711 items->data,
712 RHYTHMDB_QUERY_END);
713 items = items->next;
715 rhythmdb_query_append (db, query, RHYTHMDB_QUERY_SUBQUERY, subquery,
716 RHYTHMDB_QUERY_END);
719 gboolean
720 rhythmdb_query_is_time_relative (RhythmDB *db, GPtrArray *query)
722 int i;
723 if (query == NULL)
724 return FALSE;
726 for (i=0; i < query->len; i++) {
727 RhythmDBQueryData *data = g_ptr_array_index (query, i);
729 if (data->subquery) {
730 if (rhythmdb_query_is_time_relative (db, data->subquery))
731 return TRUE;
732 else
733 continue;
736 switch (data->type) {
737 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
738 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
739 return TRUE;
740 default:
741 break;
745 return FALSE;
749 * rhythmdb_query_to_string:
750 * @db: a #RhythmDB instance
751 * @query: a query.
753 * Returns a supposedly human-readable form of the query.
754 * This is only intended for debug usage.
756 char *
757 rhythmdb_query_to_string (RhythmDB *db, GPtrArray *query)
759 GString *buf;
760 int i;
762 buf = g_string_sized_new (100);
763 for (i = 0; i < query->len; i++) {
764 char *fmt = NULL;
765 RhythmDBQueryData *data = g_ptr_array_index (query, i);
767 switch (data->type) {
768 case RHYTHMDB_QUERY_SUBQUERY:
769 g_string_append_printf (buf, "{ %s }",
770 rhythmdb_query_to_string (db, data->subquery));
771 break;
772 case RHYTHMDB_QUERY_PROP_LIKE:
773 fmt = "(%s =~ %s)";
774 break;
775 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
776 fmt = "(%s !~ %s)";
777 break;
778 case RHYTHMDB_QUERY_PROP_PREFIX:
779 fmt = "(%s |< %s)";
780 break;
781 case RHYTHMDB_QUERY_PROP_SUFFIX:
782 fmt = "(%s >| %s)";
783 break;
784 case RHYTHMDB_QUERY_PROP_EQUALS:
785 fmt = "(%s == %s)";
786 break;
787 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
788 fmt = "(year(%s) == %s)";
789 break;
790 case RHYTHMDB_QUERY_DISJUNCTION:
791 g_string_append_printf (buf, " || ");
792 break;
793 case RHYTHMDB_QUERY_END:
794 break;
795 case RHYTHMDB_QUERY_PROP_GREATER:
796 fmt = "(%s > %s)";
797 break;
798 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
799 fmt = "(year(%s) > %s)";
800 break;
801 case RHYTHMDB_QUERY_PROP_LESS:
802 fmt = "(%s < %s)";
803 break;
804 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
805 fmt = "(year(%s) < %s)";
806 break;
807 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
808 fmt = "(%s <> %s)";
809 break;
810 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
811 fmt = "(%s >< %s)";
812 break;
815 if (fmt) {
816 char *value;
818 value = prop_gvalue_to_string (db, data->propid, data->val);
819 g_string_append_printf (buf, fmt,
820 rhythmdb_nice_elt_name_from_propid (db, data->propid),
821 value);
822 g_free (value);
823 fmt = NULL;
827 return g_string_free (buf, FALSE);
830 GType
831 rhythmdb_query_get_type (void)
833 static GType type = 0;
835 if (G_UNLIKELY (type == 0)) {
836 type = g_boxed_type_register_static ("RhythmDBQuery",
837 (GBoxedCopyFunc)rhythmdb_query_copy,
838 (GBoxedFreeFunc)rhythmdb_query_free);
841 return type;