udapted vi.po
[rhythmbox.git] / rhythmdb / rhythmdb-query.c
blobb058cb06066132b1d5af223f42d2346358fc935f
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:
353 gdouble d;
354 char *end;
356 d = g_ascii_strtod (content, &end);
357 if (*end != '\0') {
358 /* conversion wasn't entirely successful.
359 * try locale-aware strtod().
361 d = strtod (content, NULL);
363 g_value_set_double (val, d);
365 break;
366 case G_TYPE_POINTER:
367 if (propid == RHYTHMDB_PROP_TYPE) {
368 RhythmDBEntryType entry_type;
369 entry_type = rhythmdb_entry_type_get_by_name (db, content);
370 if (entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) {
371 g_value_set_pointer (val, entry_type);
372 break;
373 } else {
374 g_warning ("Unexpected entry type");
375 /* Fall through */
378 /* Falling through on purpose to get an assert for unexpected
379 * cases
381 default:
382 g_warning ("Attempt to read '%s' of unhandled type %s",
383 rhythmdb_nice_elt_name_from_propid (db, propid),
384 g_type_name (G_VALUE_TYPE (val)));
385 g_assert_not_reached ();
386 break;
390 void
391 rhythmdb_query_serialize (RhythmDB *db, GPtrArray *query,
392 xmlNodePtr parent)
394 guint i;
395 xmlNodePtr node = xmlNewChild (parent, NULL, RB_PARSE_CONJ, NULL);
396 xmlNodePtr subnode;
398 for (i = 0; i < query->len; i++) {
399 RhythmDBQueryData *data = g_ptr_array_index (query, i);
401 switch (data->type) {
402 case RHYTHMDB_QUERY_SUBQUERY:
403 subnode = xmlNewChild (node, NULL, RB_PARSE_SUBQUERY, NULL);
404 rhythmdb_query_serialize (db, data->subquery, subnode);
405 break;
406 case RHYTHMDB_QUERY_PROP_LIKE:
407 subnode = xmlNewChild (node, NULL, RB_PARSE_LIKE, NULL);
408 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
409 write_encoded_gvalue (db, subnode, data->propid, data->val);
410 break;
411 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
412 subnode = xmlNewChild (node, NULL, RB_PARSE_NOT_LIKE, NULL);
413 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
414 write_encoded_gvalue (db, subnode, data->propid, data->val);
415 break;
416 case RHYTHMDB_QUERY_PROP_PREFIX:
417 subnode = xmlNewChild (node, NULL, RB_PARSE_PREFIX, NULL);
418 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
419 write_encoded_gvalue (db, subnode, data->propid, data->val);
420 break;
421 case RHYTHMDB_QUERY_PROP_SUFFIX:
422 subnode = xmlNewChild (node, NULL, RB_PARSE_SUFFIX, NULL);
423 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
424 write_encoded_gvalue (db, subnode, data->propid, data->val);
425 break;
426 case RHYTHMDB_QUERY_PROP_EQUALS:
427 subnode = xmlNewChild (node, NULL, RB_PARSE_EQUALS, NULL);
428 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
429 write_encoded_gvalue (db, subnode, data->propid, data->val);
430 break;
431 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
432 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_EQUALS, NULL);
433 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
434 write_encoded_gvalue (db, subnode, data->propid, data->val);
435 break;
436 case RHYTHMDB_QUERY_DISJUNCTION:
437 subnode = xmlNewChild (node, NULL, RB_PARSE_DISJ, NULL);
438 break;
439 case RHYTHMDB_QUERY_END:
440 break;
441 case RHYTHMDB_QUERY_PROP_GREATER:
442 subnode = xmlNewChild (node, NULL, RB_PARSE_GREATER, NULL);
443 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
444 write_encoded_gvalue (db, subnode, data->propid, data->val);
445 break;
446 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
447 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_GREATER, NULL);
448 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
449 write_encoded_gvalue (db, subnode, data->propid, data->val);
450 break;
451 case RHYTHMDB_QUERY_PROP_LESS:
452 subnode = xmlNewChild (node, NULL, RB_PARSE_LESS, NULL);
453 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
454 write_encoded_gvalue (db, subnode, data->propid, data->val);
455 break;
456 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
457 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_LESS, NULL);
458 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
459 write_encoded_gvalue (db, subnode, data->propid, data->val);
460 break;
461 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
462 subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_WITHIN, NULL);
463 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
464 write_encoded_gvalue (db, subnode, data->propid, data->val);
465 break;
466 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
467 subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_NOT_WITHIN, NULL);
468 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
469 write_encoded_gvalue (db, subnode, data->propid, data->val);
470 break;
475 GPtrArray *
476 rhythmdb_query_deserialize (RhythmDB *db, xmlNodePtr parent)
478 GPtrArray *query = g_ptr_array_new ();
479 xmlNodePtr child;
481 g_assert (!xmlStrcmp (parent->name, RB_PARSE_CONJ));
483 for (child = parent->children; child; child = child->next) {
484 RhythmDBQueryData *data;
486 if (xmlNodeIsText (child))
487 continue;
489 data = g_new0 (RhythmDBQueryData, 1);
491 if (!xmlStrcmp (child->name, RB_PARSE_SUBQUERY)) {
492 xmlNodePtr subquery;
493 data->type = RHYTHMDB_QUERY_SUBQUERY;
494 subquery = child->children;
495 while (xmlNodeIsText (subquery))
496 subquery = subquery->next;
498 data->subquery = rhythmdb_query_deserialize (db, subquery);
499 } else if (!xmlStrcmp (child->name, RB_PARSE_DISJ)) {
500 data->type = RHYTHMDB_QUERY_DISJUNCTION;
501 } else if (!xmlStrcmp (child->name, RB_PARSE_LIKE)) {
502 data->type = RHYTHMDB_QUERY_PROP_LIKE;
503 } else if (!xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)) {
504 data->type = RHYTHMDB_QUERY_PROP_NOT_LIKE;
505 } else if (!xmlStrcmp (child->name, RB_PARSE_PREFIX)) {
506 data->type = RHYTHMDB_QUERY_PROP_PREFIX;
507 } else if (!xmlStrcmp (child->name, RB_PARSE_SUFFIX)) {
508 data->type = RHYTHMDB_QUERY_PROP_SUFFIX;
509 } else if (!xmlStrcmp (child->name, RB_PARSE_EQUALS)) {
510 xmlChar* prop;
512 prop = xmlGetProp(child, RB_PARSE_PROP);
513 if (!xmlStrcmp(prop, (xmlChar *)"date"))
514 data->type = RHYTHMDB_QUERY_PROP_YEAR_EQUALS;
515 else
516 data->type = RHYTHMDB_QUERY_PROP_EQUALS;
517 xmlFree (prop);
518 } else if (!xmlStrcmp (child->name, RB_PARSE_GREATER)) {
519 xmlChar* prop;
521 prop = xmlGetProp(child, RB_PARSE_PROP);
522 if (!xmlStrcmp(prop, (xmlChar *)"date"))
523 data->type = RHYTHMDB_QUERY_PROP_YEAR_GREATER;
524 else
525 data->type = RHYTHMDB_QUERY_PROP_GREATER;
526 xmlFree (prop);
527 } else if (!xmlStrcmp (child->name, RB_PARSE_LESS)) {
528 xmlChar* prop;
530 prop = xmlGetProp(child, RB_PARSE_PROP);
531 if (!xmlStrcmp(prop, (xmlChar *)"date"))
532 data->type = RHYTHMDB_QUERY_PROP_YEAR_LESS;
533 else
534 data->type = RHYTHMDB_QUERY_PROP_LESS;
535 xmlFree (prop);
536 } else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)) {
537 data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN;
538 } else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
539 data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN;
540 } else
541 g_assert_not_reached ();
543 if (!xmlStrcmp (child->name, RB_PARSE_LIKE)
544 || !xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)
545 || !xmlStrcmp (child->name, RB_PARSE_PREFIX)
546 || !xmlStrcmp (child->name, RB_PARSE_SUFFIX)
547 || !xmlStrcmp (child->name, RB_PARSE_EQUALS)
548 || !xmlStrcmp (child->name, RB_PARSE_GREATER)
549 || !xmlStrcmp (child->name, RB_PARSE_LESS)
550 || !xmlStrcmp (child->name, RB_PARSE_YEAR_EQUALS)
551 || !xmlStrcmp (child->name, RB_PARSE_YEAR_GREATER)
552 || !xmlStrcmp (child->name, RB_PARSE_YEAR_LESS)
553 || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)
554 || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
555 char *content;
556 xmlChar *propstr = xmlGetProp (child, RB_PARSE_PROP);
557 gint propid = rhythmdb_propid_from_nice_elt_name (db, propstr);
558 g_free (propstr);
560 g_assert (propid >= 0 && propid < RHYTHMDB_NUM_PROPERTIES);
562 data->propid = propid;
563 data->val = g_new0 (GValue, 1);
565 content = (char *)xmlNodeGetContent (child);
566 rhythmdb_read_encoded_property (db, content, data->propid, data->val);
567 g_free (content);
570 g_ptr_array_add (query, data);
573 return query;
577 * This is used to "process" queries, before using them. It is mainly used to two things:
579 * 1) performing expensive data transformations once per query, rather than
580 * once per entry we try to match against. e.g. RHYTHMDB_PROP_SEARCH_MATCH
582 * 2) defining criteria in terms of other lower-level ones that the db backend
583 * actually implements. e.g. RHYTHMDB_QUERY_YEAR_*
586 void
587 rhythmdb_query_preprocess (RhythmDB *db, GPtrArray *query)
589 int i;
591 if (query == NULL)
592 return;
594 for (i = 0; i < query->len; i++) {
595 RhythmDBQueryData *data = g_ptr_array_index (query, i);
596 gboolean restart_criteria = FALSE;
598 if (data->subquery) {
599 rhythmdb_query_preprocess (db, data->subquery);
600 } else switch (data->propid) {
601 case RHYTHMDB_PROP_TITLE_FOLDED:
602 case RHYTHMDB_PROP_GENRE_FOLDED:
603 case RHYTHMDB_PROP_ARTIST_FOLDED:
604 case RHYTHMDB_PROP_ALBUM_FOLDED:
606 /* as we are matching against a folded property, the string needs to also be folded */
607 const char *orig = g_value_get_string (data->val);
608 char *folded = rb_search_fold (orig);
610 g_value_reset (data->val);
611 g_value_take_string (data->val, folded);
612 break;
615 case RHYTHMDB_PROP_SEARCH_MATCH:
617 const char *orig = g_value_get_string (data->val);
618 char *folded = rb_search_fold (orig);
619 char **words = rb_string_split_words (folded);
621 g_free (folded);
622 g_value_unset (data->val);
623 g_value_init (data->val, G_TYPE_STRV);
624 g_value_take_boxed (data->val, words);
625 break;
628 case RHYTHMDB_PROP_DATE:
630 GDate date = {0,};
631 gulong search_date;
632 gulong begin;
633 gulong end;
634 gulong year;
636 search_date = g_value_get_ulong (data->val);
638 /* GDate functions don't handle Year="0", so we need to special case this */
639 if (search_date != 0) {
640 g_date_set_julian (&date, search_date);
641 year = g_date_get_year (&date);
642 g_date_clear (&date, 1);
644 /* get Julian dates for beginning and end of year */
645 g_date_set_dmy (&date, 1, G_DATE_JANUARY, year);
646 begin = g_date_get_julian (&date);
647 g_date_clear (&date, 1);
649 /* and the day before the beginning of the next year */
650 g_date_set_dmy (&date, 1, G_DATE_JANUARY, year + 1);
651 end = g_date_get_julian (&date) - 1;
652 } else {
653 begin = 0;
654 end = 0;
657 switch (data->type)
659 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
660 restart_criteria = TRUE;
661 data->type = RHYTHMDB_QUERY_SUBQUERY;
662 data->subquery = rhythmdb_query_parse (db,
663 RHYTHMDB_QUERY_PROP_GREATER, data->propid, begin,
664 RHYTHMDB_QUERY_PROP_LESS, data->propid, end,
665 RHYTHMDB_QUERY_END);
666 break;
668 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
669 restart_criteria = TRUE;
670 data->type = RHYTHMDB_QUERY_PROP_LESS;
671 g_value_set_ulong (data->val, end);
672 break;
674 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
675 restart_criteria = TRUE;
676 data->type = RHYTHMDB_QUERY_PROP_GREATER;
677 g_value_set_ulong (data->val, begin);
678 break;
680 default:
681 break;
684 break;
687 default:
688 break;
691 /* re-do this criteria, in case it needs further transformation */
692 if (restart_criteria)
693 i--;
697 void
698 rhythmdb_query_append_prop_multiple (RhythmDB *db, GPtrArray *query, RhythmDBPropType propid, GList *items)
700 GPtrArray *subquery;
702 if (items == NULL)
703 return;
705 if (items->next == NULL) {
706 rhythmdb_query_append (db,
707 query,
708 RHYTHMDB_QUERY_PROP_EQUALS,
709 propid,
710 items->data,
711 RHYTHMDB_QUERY_END);
712 return;
715 subquery = g_ptr_array_new ();
717 rhythmdb_query_append (db,
718 subquery,
719 RHYTHMDB_QUERY_PROP_EQUALS,
720 propid,
721 items->data,
722 RHYTHMDB_QUERY_END);
723 items = items->next;
724 while (items) {
725 rhythmdb_query_append (db,
726 subquery,
727 RHYTHMDB_QUERY_DISJUNCTION,
728 RHYTHMDB_QUERY_PROP_EQUALS,
729 propid,
730 items->data,
731 RHYTHMDB_QUERY_END);
732 items = items->next;
734 rhythmdb_query_append (db, query, RHYTHMDB_QUERY_SUBQUERY, subquery,
735 RHYTHMDB_QUERY_END);
738 gboolean
739 rhythmdb_query_is_time_relative (RhythmDB *db, GPtrArray *query)
741 int i;
742 if (query == NULL)
743 return FALSE;
745 for (i=0; i < query->len; i++) {
746 RhythmDBQueryData *data = g_ptr_array_index (query, i);
748 if (data->subquery) {
749 if (rhythmdb_query_is_time_relative (db, data->subquery))
750 return TRUE;
751 else
752 continue;
755 switch (data->type) {
756 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
757 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
758 return TRUE;
759 default:
760 break;
764 return FALSE;
768 * rhythmdb_query_to_string:
769 * @db: a #RhythmDB instance
770 * @query: a query.
772 * Returns a supposedly human-readable form of the query.
773 * This is only intended for debug usage.
775 char *
776 rhythmdb_query_to_string (RhythmDB *db, GPtrArray *query)
778 GString *buf;
779 int i;
781 buf = g_string_sized_new (100);
782 for (i = 0; i < query->len; i++) {
783 char *fmt = NULL;
784 RhythmDBQueryData *data = g_ptr_array_index (query, i);
786 switch (data->type) {
787 case RHYTHMDB_QUERY_SUBQUERY:
789 char *s;
791 s = rhythmdb_query_to_string (db, data->subquery);
792 g_string_append_printf (buf, "{ %s }", s);
793 g_free (s);
795 break;
796 case RHYTHMDB_QUERY_PROP_LIKE:
797 fmt = "(%s =~ %s)";
798 break;
799 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
800 fmt = "(%s !~ %s)";
801 break;
802 case RHYTHMDB_QUERY_PROP_PREFIX:
803 fmt = "(%s |< %s)";
804 break;
805 case RHYTHMDB_QUERY_PROP_SUFFIX:
806 fmt = "(%s >| %s)";
807 break;
808 case RHYTHMDB_QUERY_PROP_EQUALS:
809 fmt = "(%s == %s)";
810 break;
811 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
812 fmt = "(year(%s) == %s)";
813 break;
814 case RHYTHMDB_QUERY_DISJUNCTION:
815 g_string_append_printf (buf, " || ");
816 break;
817 case RHYTHMDB_QUERY_END:
818 break;
819 case RHYTHMDB_QUERY_PROP_GREATER:
820 fmt = "(%s > %s)";
821 break;
822 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
823 fmt = "(year(%s) > %s)";
824 break;
825 case RHYTHMDB_QUERY_PROP_LESS:
826 fmt = "(%s < %s)";
827 break;
828 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
829 fmt = "(year(%s) < %s)";
830 break;
831 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
832 fmt = "(%s <> %s)";
833 break;
834 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
835 fmt = "(%s >< %s)";
836 break;
839 if (fmt) {
840 char *value;
842 value = prop_gvalue_to_string (db, data->propid, data->val);
843 g_string_append_printf (buf, fmt,
844 rhythmdb_nice_elt_name_from_propid (db, data->propid),
845 value);
846 g_free (value);
847 fmt = NULL;
851 return g_string_free (buf, FALSE);
854 GType
855 rhythmdb_query_get_type (void)
857 static GType type = 0;
859 if (G_UNLIKELY (type == 0)) {
860 type = g_boxed_type_register_static ("RhythmDBQuery",
861 (GBoxedCopyFunc)rhythmdb_query_copy,
862 (GBoxedFreeFunc)rhythmdb_query_free);
865 return type;