Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
rhythmdb-query.c:243:13 | clang-analyzer | Value stored to 'new' during its initialization is never read | ||
rhythmdb-query.c:243:13 | clang-analyzer | Value stored to 'new' during its initialization is never read | ||
rhythmdb-query.c:553:4 | clang-analyzer | Value stored to 'subnode' is never read | ||
rhythmdb-query.c:553:4 | clang-analyzer | Value stored to 'subnode' is never read |
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003,2004 Colin Walters <walters@gnome.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include <config.h>
30
31 #include <string.h>
32
33 #include <glib.h>
34 #include <glib-object.h>
35 #include <gobject/gvaluecollector.h>
36
37 #include "rhythmdb.h"
38 #include "rhythmdb-private.h"
39 #include "rb-util.h"
40
41 #define RB_PARSE_CONJ (xmlChar *) "conjunction"
42 #define RB_PARSE_SUBQUERY (xmlChar *) "subquery"
43 #define RB_PARSE_LIKE (xmlChar *) "like"
44 #define RB_PARSE_PROP (xmlChar *) "prop"
45 #define RB_PARSE_NOT_LIKE (xmlChar *) "not-like"
46 #define RB_PARSE_PREFIX (xmlChar *) "prefix"
47 #define RB_PARSE_SUFFIX (xmlChar *) "suffix"
48 #define RB_PARSE_EQUALS (xmlChar *) "equals"
49 #define RB_PARSE_NOT_EQUAL (xmlChar *) "not-equal"
50 #define RB_PARSE_DISJ (xmlChar *) "disjunction"
51 #define RB_PARSE_GREATER (xmlChar *) "greater"
52 #define RB_PARSE_LESS (xmlChar *) "less"
53 #define RB_PARSE_CURRENT_TIME_WITHIN (xmlChar *) "current-time-within"
54 #define RB_PARSE_CURRENT_TIME_NOT_WITHIN (xmlChar *) "current-time-not-within"
55 #define RB_PARSE_YEAR_EQUALS RB_PARSE_EQUALS
56 #define RB_PARSE_YEAR_NOT_EQUAL RB_PARSE_NOT_EQUAL
57 #define RB_PARSE_YEAR_GREATER RB_PARSE_GREATER
58 #define RB_PARSE_YEAR_LESS RB_PARSE_LESS
59
60 /**
61 * rhythmdb_query_copy:
62 * @array: the query to copy.
63 *
64 * Creates a copy of a query.
65 *
66 * Return value: (transfer full): a copy of the passed query.
67 * It must be freed with rhythmdb_query_free()
68 **/
69 GPtrArray *
70 rhythmdb_query_copy (GPtrArray *array)
71 {
72 GPtrArray *ret;
73
74 if (!array)
75 return NULL;
76
77 ret = g_ptr_array_sized_new (array->len);
78 rhythmdb_query_concatenate (ret, array);
79
80 return ret;
81 }
82
83 /**
84 * rhythmdb_query_concatenate:
85 * @query1: query to append to
86 * @query2: query to append
87 *
88 * Appends @query2 to @query1.
89 */
90 void
91 rhythmdb_query_concatenate (GPtrArray *query1, GPtrArray *query2)
92 {
93 guint i;
94
95 g_assert (query2);
96 if (!query2)
97 return;
98
99 for (i = 0; i < query2->len; i++) {
100 RhythmDBQueryData *data = g_ptr_array_index (query2, i);
101 RhythmDBQueryData *new_data = g_new0 (RhythmDBQueryData, 1);
102 new_data->type = data->type;
103 new_data->propid = data->propid;
104 if (data->val) {
105 new_data->val = g_new0 (GValue, 1);
106 g_value_init (new_data->val, G_VALUE_TYPE (data->val));
107 g_value_copy (data->val, new_data->val);
108 }
109 if (data->subquery)
110 new_data->subquery = rhythmdb_query_copy (data->subquery);
111 g_ptr_array_add (query1, new_data);
112 }
113 }
114
115 /**
116 * rhythmdb_query_parse_valist:
117 * @db: the #RhythmDB
118 * @args: the arguments to parse
119 *
120 * Converts a va_list into a parsed query in the form of a @GPtrArray.
121 * See @rhythmdb_query_parse for more information on the parsing process.
122 *
123 * Return value: converted query
124 */
125 GPtrArray *
126 rhythmdb_query_parse_valist (RhythmDB *db, va_list args)
127 {
128 RhythmDBQueryType query;
129 GPtrArray *ret = g_ptr_array_new ();
130 char *error;
131
132 while ((query = va_arg (args, RhythmDBQueryType)) != RHYTHMDB_QUERY_END) {
133 RhythmDBQueryData *data = g_new0 (RhythmDBQueryData, 1);
134 data->type = query;
135 switch (query) {
136 case RHYTHMDB_QUERY_DISJUNCTION:
137 break;
138 case RHYTHMDB_QUERY_SUBQUERY:
139 data->subquery = rhythmdb_query_copy (va_arg (args, GPtrArray *));
140 break;
141 case RHYTHMDB_QUERY_PROP_EQUALS:
142 case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
143 case RHYTHMDB_QUERY_PROP_LIKE:
144 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
145 case RHYTHMDB_QUERY_PROP_PREFIX:
146 case RHYTHMDB_QUERY_PROP_SUFFIX:
147 case RHYTHMDB_QUERY_PROP_GREATER:
148 case RHYTHMDB_QUERY_PROP_LESS:
149 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
150 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
151 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
152 case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
153 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
154 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
155 data->propid = va_arg (args, guint);
156 data->val = g_new0 (GValue, 1);
157 g_value_init (data->val, rhythmdb_get_property_type (db, data->propid));
158 G_VALUE_COLLECT (data->val, args, 0, &error);
159 break;
160 case RHYTHMDB_QUERY_END:
161 g_assert_not_reached ();
162 break;
163 }
164 g_ptr_array_add (ret, data);
165 }
166 return ret;
167 }
168
169 /**
170 * rhythmdb_query_parse:
171 * @db: a #RhythmDB instance
172 * @Varargs: query criteria to parse
173 *
174 * Creates a query from a list of criteria.
175 *
176 * Most criteria consists of an operator (#RhythmDBQueryType),
177 * a property (#RhythmDBPropType) and the data to compare with. An entry
178 * matches a criteria if the operator returns true with the value of the
179 * entries property as the first argument, and the given data as the second
180 * argument.
181 *
182 * Three types criteria are special. Passing RHYTHMDB_QUERY_END indicates the
183 * end of the list of criteria, and must be the last passes parameter.
184 *
185 * The second special criteria is a subquery which is defined by passing
186 * RHYTHMDB_QUERY_SUBQUERY, followed by a query (#GPtrArray). An entry will
187 * match a subquery criteria if it matches all criteria in the subquery.
188 *
189 * The third special criteria is a disjunction which is defined by passing
190 * RHYTHMDB_QUERY_DISJUNCTION, which will make an entry match the query if
191 * it matches the criteria before the disjunction, the criteria after the
192 * disjunction, or both.
193 *
194 * Example:
195 * rhythmdb_query_parse (db,
196 * RHYTHMDB_QUERY_SUBQUERY, subquery,
197 * RHYTHMDB_QUERY_DISJUNCTION
198 * RHYTHMDB_QUERY_PROP_LIKE, RHYTHMDB_PROP_TITLE, "cat",
199 * RHYTHMDB_QUERY_DISJUNCTION
200 * RHYTHMDB_QUERY_PROP_GREATER, RHYTHMDB_PROP_RATING, 2.5,
201 * RHYTHMDB_QUERY_PROP_LESS, RHYTHMDB_PROP_PLAY_COUNT, 10,
202 * RHYTHMDB_QUERY_END);
203 *
204 * will create a query that matches entries:
205 * a) that match the query "subquery", or
206 * b) that have "cat" in their title, or
207 * c) have a rating of at least 2.5, and a play count of at most 10
208 *
209 * Returns: (transfer full): a the newly created query.
210 * It must be freed with rhythmdb_query_free()
211 **/
212 GPtrArray *
213 rhythmdb_query_parse (RhythmDB *db, ...)
214 {
215 GPtrArray *ret;
216 va_list args;
217
218 va_start (args, db);
219
220 ret = rhythmdb_query_parse_valist (db, args);
221
222 va_end (args);
223
224 return ret;
225 }
226
227 /**
228 * rhythmdb_query_append:
229 * @db: a #RhythmDB instance
230 * @query: a query.
231 * @Varargs: query criteria to append
232 *
233 * Appends new criteria to the query @query.
234 *
235 * The list of criteria must be in the same format as for rhythmdb_query_parse,
236 * and ended by RHYTHMDB_QUERY_END.
237 **/
238 void
239 rhythmdb_query_append (RhythmDB *db, GPtrArray *query, ...)
240 {
241 va_list args;
242 guint i;
243 GPtrArray *new = g_ptr_array_new ();
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
244
245 va_start (args, query);
246
247 new = rhythmdb_query_parse_valist (db, args);
248
249 for (i = 0; i < new->len; i++)
250 g_ptr_array_add (query, g_ptr_array_index (new, i));
251
252 g_ptr_array_free (new, TRUE);
253
254 va_end (args);
255 }
256
257 /**
258 * rhythmdb_query_append_params:
259 * @db: the #RhythmDB
260 * @query: the query to append to
261 * @type: query type
262 * @prop: query property
263 * @value: query value
264 *
265 * Appends a new query term to @query.
266 */
267 void
268 rhythmdb_query_append_params (RhythmDB *db, GPtrArray *query,
269 RhythmDBQueryType type, RhythmDBPropType prop, const GValue *value)
270 {
271 RhythmDBQueryData *data = g_new0 (RhythmDBQueryData, 1);
272
273 data->type = type;
274 switch (type) {
275 case RHYTHMDB_QUERY_END:
276 g_assert_not_reached ();
277 break;
278 case RHYTHMDB_QUERY_DISJUNCTION:
279 break;
280 case RHYTHMDB_QUERY_SUBQUERY:
281 data->subquery = rhythmdb_query_copy (g_value_get_pointer (value));
282 break;
283 case RHYTHMDB_QUERY_PROP_EQUALS:
284 case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
285 case RHYTHMDB_QUERY_PROP_LIKE:
286 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
287 case RHYTHMDB_QUERY_PROP_PREFIX:
288 case RHYTHMDB_QUERY_PROP_SUFFIX:
289 case RHYTHMDB_QUERY_PROP_GREATER:
290 case RHYTHMDB_QUERY_PROP_LESS:
291 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
292 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
293 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
294 case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
295 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
296 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
297 data->propid = prop;
298 data->val = g_new0 (GValue, 1);
299 g_value_init (data->val, rhythmdb_get_property_type (db, data->propid));
300 g_value_copy (value, data->val);
301 break;
302 }
303
304 g_ptr_array_add (query, data);
305 }
306
307 /**
308 * rhythmdb_query_free:
309 * @query: a query.
310 *
311 * Frees the query @query
312 */
313 void
314 rhythmdb_query_free (GPtrArray *query)
315 {
316 guint i;
317
318 if (query == NULL)
319 return;
320
321 for (i = 0; i < query->len; i++) {
322 RhythmDBQueryData *data = g_ptr_array_index (query, i);
323 switch (data->type) {
324 case RHYTHMDB_QUERY_DISJUNCTION:
325 break;
326 case RHYTHMDB_QUERY_SUBQUERY:
327 rhythmdb_query_free (data->subquery);
328 break;
329 case RHYTHMDB_QUERY_PROP_EQUALS:
330 case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
331 case RHYTHMDB_QUERY_PROP_LIKE:
332 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
333 case RHYTHMDB_QUERY_PROP_PREFIX:
334 case RHYTHMDB_QUERY_PROP_SUFFIX:
335 case RHYTHMDB_QUERY_PROP_GREATER:
336 case RHYTHMDB_QUERY_PROP_LESS:
337 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
338 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
339 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
340 case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
341 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
342 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
343 g_value_unset (data->val);
344 g_free (data->val);
345 break;
346 case RHYTHMDB_QUERY_END:
347 g_assert_not_reached ();
348 break;
349 }
350 g_free (data);
351 }
352
353 g_ptr_array_free (query, TRUE);
354 }
355
356 static char *
357 prop_gvalue_to_string (RhythmDB *db,
358 RhythmDBPropType propid,
359 GValue *val)
360 {
361 /* special-case some properties */
362 switch (propid) {
363 case RHYTHMDB_PROP_TYPE:
364 {
365 RhythmDBEntryType *type = g_value_get_object (val);
366 return g_strdup (rhythmdb_entry_type_get_name (type));
367 }
368 break;
369 default:
370 break;
371 }
372
373 /* otherwise just convert numbers to strings */
374 switch (G_VALUE_TYPE (val)) {
375 case G_TYPE_STRING:
376 return g_value_dup_string (val);
377 case G_TYPE_BOOLEAN:
378 return g_strdup_printf ("%d", g_value_get_boolean (val));
379 case G_TYPE_INT:
380 return g_strdup_printf ("%d", g_value_get_int (val));
381 case G_TYPE_LONG:
382 return g_strdup_printf ("%ld", g_value_get_long (val));
383 case G_TYPE_ULONG:
384 return g_strdup_printf ("%lu", g_value_get_ulong (val));
385 case G_TYPE_UINT64:
386 return g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (val));
387 case G_TYPE_FLOAT:
388 return g_strdup_printf ("%f", g_value_get_float (val));
389 case G_TYPE_DOUBLE:
390 return g_strdup_printf ("%f", g_value_get_double (val));
391 default:
392 g_assert_not_reached ();
393 return NULL;
394 }
395 }
396
397 static void
398 write_encoded_gvalue (RhythmDB *db,
399 xmlNodePtr node,
400 RhythmDBPropType propid,
401 GValue *val)
402 {
403 char *strval = NULL;
404 xmlChar *quoted;
405
406 strval = prop_gvalue_to_string (db, propid, val);
407 quoted = xmlEncodeEntitiesReentrant (NULL, BAD_CAST strval);
408 g_free (strval);
409
410 xmlNodeSetContent (node, quoted);
411 g_free (quoted);
412 }
413
414 /**
415 * rhythmdb_read_encoded_property:
416 * @db: the #RhythmDB
417 * @content: encoded property value
418 * @propid: property ID
419 * @val: returns the property value
420 *
421 * Converts a string containing a property value into a #GValue
422 * containing the native form of the property value. For boolean
423 * and numeric properties, this converts the string to a number.
424 * For #RHYTHMDB_PROP_TYPE, this looks up the entry type by name.
425 * For strings, no conversion is required.
426 */
427 void
428 rhythmdb_read_encoded_property (RhythmDB *db,
429 const char *content,
430 RhythmDBPropType propid,
431 GValue *val)
432 {
433 g_value_init (val, rhythmdb_get_property_type (db, propid));
434
435 switch (G_VALUE_TYPE (val)) {
436 case G_TYPE_STRING:
437 g_value_set_string (val, content);
438 break;
439 case G_TYPE_BOOLEAN:
440 g_value_set_boolean (val, g_ascii_strtoull (content, NULL, 10));
441 break;
442 case G_TYPE_ULONG:
443 g_value_set_ulong (val, g_ascii_strtoull (content, NULL, 10));
444 break;
445 case G_TYPE_UINT64:
446 g_value_set_uint64 (val, g_ascii_strtoull (content, NULL, 10));
447 break;
448 case G_TYPE_DOUBLE:
449 {
450 gdouble d;
451 char *end;
452
453 d = g_ascii_strtod (content, &end);
454 if (*end != '\0') {
455 /* conversion wasn't entirely successful.
456 * try locale-aware strtod().
457 */
458 d = strtod (content, NULL);
459 }
460 g_value_set_double (val, d);
461 }
462 break;
463 case G_TYPE_OBJECT: /* hm, really? */
464 if (propid == RHYTHMDB_PROP_TYPE) {
465 RhythmDBEntryType *entry_type;
466 entry_type = rhythmdb_entry_type_get_by_name (db, content);
467 if (entry_type != NULL) {
468 g_value_set_object (val, entry_type);
469 break;
470 } else {
471 g_warning ("Unexpected entry type");
472 /* Fall through */
473 }
474 }
475 /* Falling through on purpose to get an assert for unexpected
476 * cases
477 */
478 default:
479 g_warning ("Attempt to read '%s' of unhandled type %s",
480 rhythmdb_nice_elt_name_from_propid (db, propid),
481 g_type_name (G_VALUE_TYPE (val)));
482 g_assert_not_reached ();
483 break;
484 }
485 }
486
487 /**
488 * rhythmdb_query_serialize:
489 * @db: the #RhythmDB
490 * @query: query to serialize
491 * @parent: XML node to attach the query to
492 *
493 * Converts @query into XML form as a child of @parent. It can be converted
494 * back into a query by passing @parent to @rhythmdb_query_deserialize.
495 */
496 void
497 rhythmdb_query_serialize (RhythmDB *db, GPtrArray *query,
498 xmlNodePtr parent)
499 {
500 guint i;
501 xmlNodePtr node = xmlNewChild (parent, NULL, RB_PARSE_CONJ, NULL);
502 xmlNodePtr subnode;
503
504 for (i = 0; i < query->len; i++) {
505 RhythmDBQueryData *data = g_ptr_array_index (query, i);
506
507 switch (data->type) {
508 case RHYTHMDB_QUERY_SUBQUERY:
509 subnode = xmlNewChild (node, NULL, RB_PARSE_SUBQUERY, NULL);
510 rhythmdb_query_serialize (db, data->subquery, subnode);
511 break;
512 case RHYTHMDB_QUERY_PROP_LIKE:
513 subnode = xmlNewChild (node, NULL, RB_PARSE_LIKE, NULL);
514 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
515 write_encoded_gvalue (db, subnode, data->propid, data->val);
516 break;
517 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
518 subnode = xmlNewChild (node, NULL, RB_PARSE_NOT_LIKE, NULL);
519 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
520 write_encoded_gvalue (db, subnode, data->propid, data->val);
521 break;
522 case RHYTHMDB_QUERY_PROP_PREFIX:
523 subnode = xmlNewChild (node, NULL, RB_PARSE_PREFIX, NULL);
524 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
525 write_encoded_gvalue (db, subnode, data->propid, data->val);
526 break;
527 case RHYTHMDB_QUERY_PROP_SUFFIX:
528 subnode = xmlNewChild (node, NULL, RB_PARSE_SUFFIX, NULL);
529 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
530 write_encoded_gvalue (db, subnode, data->propid, data->val);
531 break;
532 case RHYTHMDB_QUERY_PROP_EQUALS:
533 subnode = xmlNewChild (node, NULL, RB_PARSE_EQUALS, NULL);
534 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
535 write_encoded_gvalue (db, subnode, data->propid, data->val);
536 break;
537 case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
538 subnode = xmlNewChild (node, NULL, RB_PARSE_NOT_EQUAL, NULL);
539 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
540 write_encoded_gvalue (db, subnode, data->propid, data->val);
541 break;
542 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
543 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_EQUALS, NULL);
544 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
545 write_encoded_gvalue (db, subnode, data->propid, data->val);
546 break;
547 case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
548 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_NOT_EQUAL, NULL);
549 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
550 write_encoded_gvalue (db, subnode, data->propid, data->val);
551 break;
552 case RHYTHMDB_QUERY_DISJUNCTION:
553 subnode = xmlNewChild (node, NULL, RB_PARSE_DISJ, NULL);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
554 break;
555 case RHYTHMDB_QUERY_END:
556 break;
557 case RHYTHMDB_QUERY_PROP_GREATER:
558 subnode = xmlNewChild (node, NULL, RB_PARSE_GREATER, NULL);
559 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
560 write_encoded_gvalue (db, subnode, data->propid, data->val);
561 break;
562 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
563 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_GREATER, NULL);
564 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
565 write_encoded_gvalue (db, subnode, data->propid, data->val);
566 break;
567 case RHYTHMDB_QUERY_PROP_LESS:
568 subnode = xmlNewChild (node, NULL, RB_PARSE_LESS, NULL);
569 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
570 write_encoded_gvalue (db, subnode, data->propid, data->val);
571 break;
572 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
573 subnode = xmlNewChild (node, NULL, RB_PARSE_YEAR_LESS, NULL);
574 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
575 write_encoded_gvalue (db, subnode, data->propid, data->val);
576 break;
577 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
578 subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_WITHIN, NULL);
579 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
580 write_encoded_gvalue (db, subnode, data->propid, data->val);
581 break;
582 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
583 subnode = xmlNewChild (node, NULL, RB_PARSE_CURRENT_TIME_NOT_WITHIN, NULL);
584 xmlSetProp (subnode, RB_PARSE_PROP, rhythmdb_nice_elt_name_from_propid (db, data->propid));
585 write_encoded_gvalue (db, subnode, data->propid, data->val);
586 break;
587 }
588 }
589 }
590
591 /**
592 * rhythmdb_query_deserialize:
593 * @db: the #RhythmDB
594 * @parent: parent XML node of serialized query
595 *
596 * Converts a serialized query back into a @GPtrArray query.
597 *
598 * Return value: (transfer full): deserialized query.
599 */
600 GPtrArray *
601 rhythmdb_query_deserialize (RhythmDB *db, xmlNodePtr parent)
602 {
603 GPtrArray *query = g_ptr_array_new ();
604 xmlNodePtr child;
605
606 g_assert (!xmlStrcmp (parent->name, RB_PARSE_CONJ));
607
608 for (child = parent->children; child; child = child->next) {
609 RhythmDBQueryData *data;
610
611 if (xmlNodeIsText (child))
612 continue;
613
614 data = g_new0 (RhythmDBQueryData, 1);
615
616 if (!xmlStrcmp (child->name, RB_PARSE_SUBQUERY)) {
617 xmlNodePtr subquery;
618 data->type = RHYTHMDB_QUERY_SUBQUERY;
619 subquery = child->children;
620 while (xmlNodeIsText (subquery))
621 subquery = subquery->next;
622
623 data->subquery = rhythmdb_query_deserialize (db, subquery);
624 } else if (!xmlStrcmp (child->name, RB_PARSE_DISJ)) {
625 data->type = RHYTHMDB_QUERY_DISJUNCTION;
626 } else if (!xmlStrcmp (child->name, RB_PARSE_LIKE)) {
627 data->type = RHYTHMDB_QUERY_PROP_LIKE;
628 } else if (!xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)) {
629 data->type = RHYTHMDB_QUERY_PROP_NOT_LIKE;
630 } else if (!xmlStrcmp (child->name, RB_PARSE_PREFIX)) {
631 data->type = RHYTHMDB_QUERY_PROP_PREFIX;
632 } else if (!xmlStrcmp (child->name, RB_PARSE_SUFFIX)) {
633 data->type = RHYTHMDB_QUERY_PROP_SUFFIX;
634 } else if (!xmlStrcmp (child->name, RB_PARSE_EQUALS)) {
635 xmlChar* prop;
636
637 prop = xmlGetProp(child, RB_PARSE_PROP);
638 if (!xmlStrcmp(prop, (xmlChar *)"date"))
639 data->type = RHYTHMDB_QUERY_PROP_YEAR_EQUALS;
640 else
641 data->type = RHYTHMDB_QUERY_PROP_EQUALS;
642 xmlFree (prop);
643 } else if (!xmlStrcmp (child->name, RB_PARSE_NOT_EQUAL)) {
644 xmlChar* prop;
645
646 prop = xmlGetProp(child, RB_PARSE_PROP);
647 if (!xmlStrcmp(prop, (xmlChar *)"date"))
648 data->type = RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL;
649 else
650 data->type = RHYTHMDB_QUERY_PROP_NOT_EQUAL;
651 xmlFree (prop);
652 } else if (!xmlStrcmp (child->name, RB_PARSE_GREATER)) {
653 xmlChar* prop;
654
655 prop = xmlGetProp(child, RB_PARSE_PROP);
656 if (!xmlStrcmp(prop, (xmlChar *)"date"))
657 data->type = RHYTHMDB_QUERY_PROP_YEAR_GREATER;
658 else
659 data->type = RHYTHMDB_QUERY_PROP_GREATER;
660 xmlFree (prop);
661 } else if (!xmlStrcmp (child->name, RB_PARSE_LESS)) {
662 xmlChar* prop;
663
664 prop = xmlGetProp(child, RB_PARSE_PROP);
665 if (!xmlStrcmp(prop, (xmlChar *)"date"))
666 data->type = RHYTHMDB_QUERY_PROP_YEAR_LESS;
667 else
668 data->type = RHYTHMDB_QUERY_PROP_LESS;
669 xmlFree (prop);
670 } else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)) {
671 data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN;
672 } else if (!xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
673 data->type = RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN;
674 } else
675 g_assert_not_reached ();
676
677 if (!xmlStrcmp (child->name, RB_PARSE_LIKE)
678 || !xmlStrcmp (child->name, RB_PARSE_NOT_LIKE)
679 || !xmlStrcmp (child->name, RB_PARSE_PREFIX)
680 || !xmlStrcmp (child->name, RB_PARSE_SUFFIX)
681 || !xmlStrcmp (child->name, RB_PARSE_EQUALS)
682 || !xmlStrcmp (child->name, RB_PARSE_NOT_EQUAL)
683 || !xmlStrcmp (child->name, RB_PARSE_GREATER)
684 || !xmlStrcmp (child->name, RB_PARSE_LESS)
685 || !xmlStrcmp (child->name, RB_PARSE_YEAR_EQUALS)
686 || !xmlStrcmp (child->name, RB_PARSE_YEAR_GREATER)
687 || !xmlStrcmp (child->name, RB_PARSE_YEAR_LESS)
688 || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_WITHIN)
689 || !xmlStrcmp (child->name, RB_PARSE_CURRENT_TIME_NOT_WITHIN)) {
690 char *content;
691 xmlChar *propstr = xmlGetProp (child, RB_PARSE_PROP);
692 gint propid = rhythmdb_propid_from_nice_elt_name (db, propstr);
693 g_free (propstr);
694
695 g_assert (propid >= 0 && propid < RHYTHMDB_NUM_PROPERTIES);
696
697 data->propid = propid;
698 data->val = g_new0 (GValue, 1);
699
700 content = (char *)xmlNodeGetContent (child);
701 rhythmdb_read_encoded_property (db, content, data->propid, data->val);
702 g_free (content);
703 }
704
705 g_ptr_array_add (query, data);
706 }
707
708 return query;
709 }
710
711 /*
712 * This is used to "process" queries, before using them. It is mainly used to two things:
713 *
714 * 1) performing expensive data transformations once per query, rather than
715 * once per entry we try to match against. e.g. RHYTHMDB_PROP_SEARCH_MATCH
716 *
717 * 2) defining criteria in terms of other lower-level ones that the db backend
718 * actually implements. e.g. RHYTHMDB_QUERY_YEAR_*
719 */
720
721 /**
722 * rhythmdb_query_preprocess:
723 * @db: the #RhythmDB
724 * @query: query to preprocess
725 *
726 * Preprocesses a query to prepare it for execution. This has two main
727 * roles: to perform expensive data transformations once per query, rather
728 * than once per entry, and converting criteria to lower-level forms that
729 * are implemented by the database backend.
730 *
731 * For RHYTHMDB_PROP_SEARCH_MATCH, this converts the search terms into
732 * an array of case-folded words.
733 *
734 * When matching against case-folded properties such as
735 * #RHYTHMDB_PROP_TITLE_FOLDED, this case-folds the query value.
736 *
737 * When performing year-based criteria such as #RHYTHMDB_QUERY_PROP_YEAR_LESS,
738 * it converts the year into the Julian date such that a simple numeric
739 * comparison will work.
740 */
741 void
742 rhythmdb_query_preprocess (RhythmDB *db, GPtrArray *query)
743 {
744 int i;
745
746 if (query == NULL)
747 return;
748
749 for (i = 0; i < query->len; i++) {
750 RhythmDBQueryData *data = g_ptr_array_index (query, i);
751 gboolean restart_criteria = FALSE;
752
753 if (data->subquery) {
754 rhythmdb_query_preprocess (db, data->subquery);
755 } else switch (data->propid) {
756 case RHYTHMDB_PROP_TITLE_FOLDED:
757 case RHYTHMDB_PROP_GENRE_FOLDED:
758 case RHYTHMDB_PROP_ARTIST_FOLDED:
759 case RHYTHMDB_PROP_ALBUM_FOLDED:
760 {
761 /* as we are matching against a folded property, the string needs to also be folded */
762 const char *orig = g_value_get_string (data->val);
763 char *folded = rb_search_fold (orig);
764
765 g_value_reset (data->val);
766 g_value_take_string (data->val, folded);
767 break;
768 }
769
770 case RHYTHMDB_PROP_SEARCH_MATCH:
771 {
772 const char *orig = g_value_get_string (data->val);
773 char *folded = rb_search_fold (orig);
774 char **words = rb_string_split_words (folded);
775
776 g_free (folded);
777 g_value_unset (data->val);
778 g_value_init (data->val, G_TYPE_STRV);
779 g_value_take_boxed (data->val, words);
780 break;
781 }
782
783 case RHYTHMDB_PROP_DATE:
784 {
785 GDate date = {0,};
786 gulong search_date;
787 gulong begin;
788 gulong end;
789 gulong year;
790
791 search_date = g_value_get_ulong (data->val);
792
793 /* GDate functions don't handle Year="0", so we need to special case this */
794 if (search_date != 0) {
795 g_date_set_julian (&date, search_date);
796 year = g_date_get_year (&date);
797 g_date_clear (&date, 1);
798
799 /* get Julian dates for beginning and end of year */
800 g_date_set_dmy (&date, 1, G_DATE_JANUARY, year);
801 begin = g_date_get_julian (&date);
802 g_date_clear (&date, 1);
803
804 /* and the day before the beginning of the next year */
805 g_date_set_dmy (&date, 1, G_DATE_JANUARY, year + 1);
806 end = g_date_get_julian (&date) - 1;
807 } else {
808 begin = 0;
809 end = 0;
810 }
811
812 switch (data->type)
813 {
814 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
815 restart_criteria = TRUE;
816 data->type = RHYTHMDB_QUERY_SUBQUERY;
817 data->subquery = rhythmdb_query_parse (db,
818 RHYTHMDB_QUERY_PROP_GREATER, data->propid, begin,
819 RHYTHMDB_QUERY_PROP_LESS, data->propid, end,
820 RHYTHMDB_QUERY_END);
821 break;
822 case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
823 restart_criteria = TRUE;
824 data->type = RHYTHMDB_QUERY_SUBQUERY;
825 data->subquery = rhythmdb_query_parse (db,
826 RHYTHMDB_QUERY_PROP_LESS, data->propid, begin-1,
827 RHYTHMDB_QUERY_DISJUNCTION,
828 RHYTHMDB_QUERY_PROP_GREATER, data->propid, end+1,
829 RHYTHMDB_QUERY_END);
830 break;
831
832 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
833 restart_criteria = TRUE;
834 data->type = RHYTHMDB_QUERY_PROP_LESS;
835 g_value_set_ulong (data->val, end);
836 break;
837
838 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
839 restart_criteria = TRUE;
840 data->type = RHYTHMDB_QUERY_PROP_GREATER;
841 g_value_set_ulong (data->val, begin);
842 break;
843
844 default:
845 break;
846 }
847
848 break;
849 }
850
851 default:
852 break;
853 }
854
855 /* re-do this criteria, in case it needs further transformation */
856 if (restart_criteria)
857 i--;
858 }
859 }
860
861 /**
862 * rhythmdb_query_append_prop_multiple:
863 * @db: the #RhythmDB
864 * @query: the query to append to
865 * @propid: property ID to match
866 * @items: (element-type GObject.Value): #GList of values to match against
867 *
868 * Appends a set of criteria to a query to match against any of the values
869 * listed in @items.
870 */
871 void
872 rhythmdb_query_append_prop_multiple (RhythmDB *db, GPtrArray *query, RhythmDBPropType propid, GList *items)
873 {
874 GPtrArray *subquery;
875
876 if (items == NULL)
877 return;
878
879 if (items->next == NULL) {
880 rhythmdb_query_append (db,
881 query,
882 RHYTHMDB_QUERY_PROP_EQUALS,
883 propid,
884 items->data,
885 RHYTHMDB_QUERY_END);
886 return;
887 }
888
889 subquery = g_ptr_array_new ();
890
891 rhythmdb_query_append (db,
892 subquery,
893 RHYTHMDB_QUERY_PROP_EQUALS,
894 propid,
895 items->data,
896 RHYTHMDB_QUERY_END);
897 items = items->next;
898 while (items) {
899 rhythmdb_query_append (db,
900 subquery,
901 RHYTHMDB_QUERY_DISJUNCTION,
902 RHYTHMDB_QUERY_PROP_EQUALS,
903 propid,
904 items->data,
905 RHYTHMDB_QUERY_END);
906 items = items->next;
907 }
908 rhythmdb_query_append (db, query, RHYTHMDB_QUERY_SUBQUERY, subquery,
909 RHYTHMDB_QUERY_END);
910 }
911
912 /**
913 * rhythmdb_query_is_time_relative:
914 * @db: the #RhythmDB
915 * @query: the query to check
916 *
917 * Checks if a query contains any time-relative criteria.
918 *
919 * Return value: %TRUE if time-relative criteria found
920 */
921 gboolean
922 rhythmdb_query_is_time_relative (RhythmDB *db, GPtrArray *query)
923 {
924 int i;
925 if (query == NULL)
926 return FALSE;
927
928 for (i=0; i < query->len; i++) {
929 RhythmDBQueryData *data = g_ptr_array_index (query, i);
930
931 if (data->subquery) {
932 if (rhythmdb_query_is_time_relative (db, data->subquery))
933 return TRUE;
934 else
935 continue;
936 }
937
938 switch (data->type) {
939 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
940 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
941 return TRUE;
942 default:
943 break;
944 }
945 }
946
947 return FALSE;
948 }
949
950 /**
951 * rhythmdb_query_to_string:
952 * @db: a #RhythmDB instance
953 * @query: a query.
954 *
955 * Returns a supposedly human-readable form of the query.
956 * This is only intended for debug usage.
957 *
958 * Returns: allocated string form of the query
959 **/
960 char *
961 rhythmdb_query_to_string (RhythmDB *db, GPtrArray *query)
962 {
963 GString *buf;
964 int i;
965
966 buf = g_string_sized_new (100);
967 for (i = 0; i < query->len; i++) {
968 char *fmt = NULL;
969 RhythmDBQueryData *data = g_ptr_array_index (query, i);
970
971 switch (data->type) {
972 case RHYTHMDB_QUERY_SUBQUERY:
973 {
974 char *s;
975
976 s = rhythmdb_query_to_string (db, data->subquery);
977 g_string_append_printf (buf, "{ %s }", s);
978 g_free (s);
979 }
980 break;
981 case RHYTHMDB_QUERY_PROP_LIKE:
982 fmt = "(%s =~ %s)";
983 break;
984 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
985 fmt = "(%s !~ %s)";
986 break;
987 case RHYTHMDB_QUERY_PROP_PREFIX:
988 fmt = "(%s |< %s)";
989 break;
990 case RHYTHMDB_QUERY_PROP_SUFFIX:
991 fmt = "(%s >| %s)";
992 break;
993 case RHYTHMDB_QUERY_PROP_EQUALS:
994 fmt = "(%s == %s)";
995 break;
996 case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
997 fmt = "(%s != %s)";
998 break;
999 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
1000 fmt = "(year(%s) == %s)";
1001 break;
1002 case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
1003 fmt = "(year(%s) != %s)";
1004 break;
1005 case RHYTHMDB_QUERY_DISJUNCTION:
1006 g_string_append_printf (buf, " || ");
1007 break;
1008 case RHYTHMDB_QUERY_END:
1009 break;
1010 case RHYTHMDB_QUERY_PROP_GREATER:
1011 fmt = "(%s > %s)";
1012 break;
1013 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
1014 fmt = "(year(%s) > %s)";
1015 break;
1016 case RHYTHMDB_QUERY_PROP_LESS:
1017 fmt = "(%s < %s)";
1018 break;
1019 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
1020 fmt = "(year(%s) < %s)";
1021 break;
1022 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1023 fmt = "(%s <> %s)";
1024 break;
1025 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1026 fmt = "(%s >< %s)";
1027 break;
1028 }
1029
1030 if (fmt) {
1031 char *value;
1032
1033 value = prop_gvalue_to_string (db, data->propid, data->val);
1034 g_string_append_printf (buf, fmt,
1035 rhythmdb_nice_elt_name_from_propid (db, data->propid),
1036 value);
1037 g_free (value);
1038 fmt = NULL;
1039 }
1040 }
1041
1042 return g_string_free (buf, FALSE);
1043 }
1044
1045 GType
1046 rhythmdb_query_get_type (void)
1047 {
1048 static GType type = 0;
1049
1050 if (G_UNLIKELY (type == 0)) {
1051 type = g_boxed_type_register_static ("RhythmDBQuery",
1052 (GBoxedCopyFunc)rhythmdb_query_copy,
1053 (GBoxedFreeFunc)rhythmdb_query_free);
1054 }
1055
1056 return type;
1057 }