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.
28 #include <glib-object.h>
29 #include <gobject/gvaluecollector.h>
32 #include "rhythmdb-private.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
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()
60 rhythmdb_query_copy (GPtrArray
*array
)
67 ret
= g_ptr_array_sized_new (array
->len
);
68 rhythmdb_query_concatenate (ret
, array
);
74 rhythmdb_query_concatenate (GPtrArray
*query1
, GPtrArray
*query2
)
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
;
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
);
93 new_data
->subquery
= rhythmdb_query_copy (data
->subquery
);
94 g_ptr_array_add (query1
, new_data
);
99 rhythmdb_query_parse_valist (RhythmDB
*db
, va_list args
)
101 RhythmDBQueryType query
;
102 GPtrArray
*ret
= g_ptr_array_new ();
105 while ((query
= va_arg (args
, RhythmDBQueryType
)) != RHYTHMDB_QUERY_END
) {
106 RhythmDBQueryData
*data
= g_new0 (RhythmDBQueryData
, 1);
109 case RHYTHMDB_QUERY_DISJUNCTION
:
111 case RHYTHMDB_QUERY_SUBQUERY
:
112 data
->subquery
= rhythmdb_query_copy (va_arg (args
, GPtrArray
*));
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
);
131 case RHYTHMDB_QUERY_END
:
132 g_assert_not_reached ();
135 g_ptr_array_add (ret
, data
);
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
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.
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()
182 rhythmdb_query_parse (RhythmDB
*db
, ...)
189 ret
= rhythmdb_query_parse_valist (db
, args
);
197 * rhythmdb_query_append:
198 * @db: a #RhythmDB instance
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.
207 rhythmdb_query_append (RhythmDB
*db
, GPtrArray
*query
, ...)
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
);
226 * rhythmdb_query_free:
229 * Frees the query @query
232 rhythmdb_query_free (GPtrArray
*query
)
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
:
244 case RHYTHMDB_QUERY_SUBQUERY
:
245 rhythmdb_query_free (data
->subquery
);
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
);
262 case RHYTHMDB_QUERY_END
:
263 g_assert_not_reached ();
269 g_ptr_array_free (query
, TRUE
);
273 prop_gvalue_to_string (RhythmDB
*db
,
274 RhythmDBPropType propid
,
277 /* special-case some properties */
279 case RHYTHMDB_PROP_TYPE
:
281 RhythmDBEntryType type
= g_value_get_pointer (val
);
282 return g_strdup (type
->name
);
289 /* otherwise just convert numbers to strings */
290 switch (G_VALUE_TYPE (val
)) {
292 return g_value_dup_string (val
);
294 return g_strdup_printf ("%d", g_value_get_boolean (val
));
296 return g_strdup_printf ("%d", g_value_get_int (val
));
298 return g_strdup_printf ("%ld", g_value_get_long (val
));
300 return g_strdup_printf ("%lu", g_value_get_ulong (val
));
302 return g_strdup_printf ("%" G_GUINT64_FORMAT
, g_value_get_uint64 (val
));
304 return g_strdup_printf ("%f", g_value_get_float (val
));
306 return g_strdup_printf ("%f", g_value_get_double (val
));
308 g_assert_not_reached ();
314 write_encoded_gvalue (RhythmDB
*db
,
316 RhythmDBPropType propid
,
322 strval
= prop_gvalue_to_string (db
, propid
, val
);
323 quoted
= xmlEncodeEntitiesReentrant (NULL
, BAD_CAST strval
);
326 xmlNodeSetContent (node
, quoted
);
331 rhythmdb_read_encoded_property (RhythmDB
*db
,
333 RhythmDBPropType propid
,
336 g_value_init (val
, rhythmdb_get_property_type (db
, propid
));
338 switch (G_VALUE_TYPE (val
)) {
340 g_value_set_string (val
, content
);
343 g_value_set_boolean (val
, g_ascii_strtoull (content
, NULL
, 10));
346 g_value_set_ulong (val
, g_ascii_strtoull (content
, NULL
, 10));
349 g_value_set_uint64 (val
, g_ascii_strtoull (content
, NULL
, 10));
356 d
= g_ascii_strtod (content
, &end
);
358 /* conversion wasn't entirely successful.
359 * try locale-aware strtod().
361 d
= strtod (content
, NULL
);
363 g_value_set_double (val
, d
);
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
);
374 g_warning ("Unexpected entry type");
378 /* Falling through on purpose to get an assert for unexpected
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 ();
391 rhythmdb_query_serialize (RhythmDB
*db
, GPtrArray
*query
,
395 xmlNodePtr node
= xmlNewChild (parent
, NULL
, RB_PARSE_CONJ
, NULL
);
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
);
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
);
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
);
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
);
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
);
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
);
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
);
436 case RHYTHMDB_QUERY_DISJUNCTION
:
437 subnode
= xmlNewChild (node
, NULL
, RB_PARSE_DISJ
, NULL
);
439 case RHYTHMDB_QUERY_END
:
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
);
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
);
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
);
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
);
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
);
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
);
476 rhythmdb_query_deserialize (RhythmDB
*db
, xmlNodePtr parent
)
478 GPtrArray
*query
= g_ptr_array_new ();
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
))
489 data
= g_new0 (RhythmDBQueryData
, 1);
491 if (!xmlStrcmp (child
->name
, RB_PARSE_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
)) {
512 prop
= xmlGetProp(child
, RB_PARSE_PROP
);
513 if (!xmlStrcmp(prop
, (xmlChar
*)"date"))
514 data
->type
= RHYTHMDB_QUERY_PROP_YEAR_EQUALS
;
516 data
->type
= RHYTHMDB_QUERY_PROP_EQUALS
;
518 } else if (!xmlStrcmp (child
->name
, RB_PARSE_GREATER
)) {
521 prop
= xmlGetProp(child
, RB_PARSE_PROP
);
522 if (!xmlStrcmp(prop
, (xmlChar
*)"date"))
523 data
->type
= RHYTHMDB_QUERY_PROP_YEAR_GREATER
;
525 data
->type
= RHYTHMDB_QUERY_PROP_GREATER
;
527 } else if (!xmlStrcmp (child
->name
, RB_PARSE_LESS
)) {
530 prop
= xmlGetProp(child
, RB_PARSE_PROP
);
531 if (!xmlStrcmp(prop
, (xmlChar
*)"date"))
532 data
->type
= RHYTHMDB_QUERY_PROP_YEAR_LESS
;
534 data
->type
= RHYTHMDB_QUERY_PROP_LESS
;
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
;
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
)) {
556 xmlChar
*propstr
= xmlGetProp (child
, RB_PARSE_PROP
);
557 gint propid
= rhythmdb_propid_from_nice_elt_name (db
, 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
);
570 g_ptr_array_add (query
, data
);
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_*
587 rhythmdb_query_preprocess (RhythmDB
*db
, GPtrArray
*query
)
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
);
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
);
622 g_value_unset (data
->val
);
623 g_value_init (data
->val
, G_TYPE_STRV
);
624 g_value_take_boxed (data
->val
, words
);
628 case RHYTHMDB_PROP_DATE
:
636 search_date
= g_value_get_ulong (data
->val
);
637 g_date_set_julian (&date
, search_date
);
638 year
= g_date_get_year (&date
);
639 g_date_clear (&date
, 1);
641 /* get Julian dates for beginning and end of year */
642 g_date_set_dmy (&date
, 1, G_DATE_JANUARY
, year
);
643 begin
= g_date_get_julian (&date
);
644 g_date_clear (&date
, 1);
646 /* and the day before the beginning of the next year */
647 g_date_set_dmy (&date
, 1, G_DATE_JANUARY
, year
+ 1);
648 end
= g_date_get_julian (&date
) - 1;
652 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS
:
653 restart_criteria
= TRUE
;
654 data
->type
= RHYTHMDB_QUERY_SUBQUERY
;
655 data
->subquery
= rhythmdb_query_parse (db
,
656 RHYTHMDB_QUERY_PROP_GREATER
, data
->propid
, begin
,
657 RHYTHMDB_QUERY_PROP_LESS
, data
->propid
, end
,
661 case RHYTHMDB_QUERY_PROP_YEAR_LESS
:
662 restart_criteria
= TRUE
;
663 data
->type
= RHYTHMDB_QUERY_PROP_LESS
;
664 g_value_set_ulong (data
->val
, end
);
667 case RHYTHMDB_QUERY_PROP_YEAR_GREATER
:
668 restart_criteria
= TRUE
;
669 data
->type
= RHYTHMDB_QUERY_PROP_GREATER
;
670 g_value_set_ulong (data
->val
, begin
);
684 /* re-do this criteria, in case it needs further transformation */
685 if (restart_criteria
)
691 rhythmdb_query_append_prop_multiple (RhythmDB
*db
, GPtrArray
*query
, RhythmDBPropType propid
, GList
*items
)
698 if (items
->next
== NULL
) {
699 rhythmdb_query_append (db
,
701 RHYTHMDB_QUERY_PROP_EQUALS
,
708 subquery
= g_ptr_array_new ();
710 rhythmdb_query_append (db
,
712 RHYTHMDB_QUERY_PROP_EQUALS
,
718 rhythmdb_query_append (db
,
720 RHYTHMDB_QUERY_DISJUNCTION
,
721 RHYTHMDB_QUERY_PROP_EQUALS
,
727 rhythmdb_query_append (db
, query
, RHYTHMDB_QUERY_SUBQUERY
, subquery
,
732 rhythmdb_query_is_time_relative (RhythmDB
*db
, GPtrArray
*query
)
738 for (i
=0; i
< query
->len
; i
++) {
739 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
741 if (data
->subquery
) {
742 if (rhythmdb_query_is_time_relative (db
, data
->subquery
))
748 switch (data
->type
) {
749 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
:
750 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN
:
761 * rhythmdb_query_to_string:
762 * @db: a #RhythmDB instance
765 * Returns a supposedly human-readable form of the query.
766 * This is only intended for debug usage.
769 rhythmdb_query_to_string (RhythmDB
*db
, GPtrArray
*query
)
774 buf
= g_string_sized_new (100);
775 for (i
= 0; i
< query
->len
; i
++) {
777 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
779 switch (data
->type
) {
780 case RHYTHMDB_QUERY_SUBQUERY
:
784 s
= rhythmdb_query_to_string (db
, data
->subquery
);
785 g_string_append_printf (buf
, "{ %s }", s
);
789 case RHYTHMDB_QUERY_PROP_LIKE
:
792 case RHYTHMDB_QUERY_PROP_NOT_LIKE
:
795 case RHYTHMDB_QUERY_PROP_PREFIX
:
798 case RHYTHMDB_QUERY_PROP_SUFFIX
:
801 case RHYTHMDB_QUERY_PROP_EQUALS
:
804 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS
:
805 fmt
= "(year(%s) == %s)";
807 case RHYTHMDB_QUERY_DISJUNCTION
:
808 g_string_append_printf (buf
, " || ");
810 case RHYTHMDB_QUERY_END
:
812 case RHYTHMDB_QUERY_PROP_GREATER
:
815 case RHYTHMDB_QUERY_PROP_YEAR_GREATER
:
816 fmt
= "(year(%s) > %s)";
818 case RHYTHMDB_QUERY_PROP_LESS
:
821 case RHYTHMDB_QUERY_PROP_YEAR_LESS
:
822 fmt
= "(year(%s) < %s)";
824 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
:
827 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN
:
835 value
= prop_gvalue_to_string (db
, data
->propid
, data
->val
);
836 g_string_append_printf (buf
, fmt
,
837 rhythmdb_nice_elt_name_from_propid (db
, data
->propid
),
844 return g_string_free (buf
, FALSE
);
848 rhythmdb_query_get_type (void)
850 static GType type
= 0;
852 if (G_UNLIKELY (type
== 0)) {
853 type
= g_boxed_type_register_static ("RhythmDBQuery",
854 (GBoxedCopyFunc
)rhythmdb_query_copy
,
855 (GBoxedFreeFunc
)rhythmdb_query_free
);