XMMS2
collquery.c
Go to the documentation of this file.
1/* XMMS2 - X Music Multiplexer System
2 * Copyright (C) 2003-2011 XMMS2 Team
3 *
4 * PLUGINS ARE NOT CONSIDERED TO BE DERIVED WORK !!!
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 */
16
17
18/** @file
19 * Functions to build an SQL query from a collection.
20 */
21
22#include <string.h>
23#include <glib.h>
24
26#include "xmms/xmms_log.h"
27
28
29/* Query structures */
30
31typedef struct {
32 guint limit_start;
33 guint limit_len;
34 xmmsv_t *order;
35 xmmsv_t *fetch;
36 xmmsv_t *group;
37} coll_query_params_t;
38
39typedef enum {
43
44typedef struct {
46 guint id;
47 gboolean optional;
48} coll_query_alias_t;
49
50typedef struct {
51 GHashTable *aliases;
52 guint alias_count;
53 gchar *alias_base;
54 GString *conditions;
55 coll_query_params_t *params;
56} coll_query_t;
57
58typedef enum {
63
64static coll_query_t* init_query (coll_query_params_t *params);
65static void add_fetch_group_aliases (coll_query_t *query, coll_query_params_t *params);
66static void destroy_query (coll_query_t* query);
67static GString* xmms_collection_gen_query (coll_query_t *query);
68static void xmms_collection_append_to_query (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, coll_query_t *query);
69
70static void query_append_int (coll_query_t *query, gint i);
71static void query_append_string (coll_query_t *query, const gchar *s);
72static void query_append_protect_string (coll_query_t *query, gchar *s);
73static void query_append_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsv_coll_t *coll);
74static void query_append_intersect_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsv_coll_t *coll);
75static void query_append_filter (coll_query_t *query, xmmsv_coll_type_t type, gchar *key, gchar *value, gboolean case_sens);
76static void query_string_append_joins (gpointer key, gpointer val, gpointer udata);
77static void query_string_append_alias_list (coll_query_t *query, GString *qstring, xmmsv_t *fields);
78static void query_string_append_fetch (coll_query_t *query, GString *qstring);
79static void query_string_append_alias (GString *qstring, coll_query_alias_t *alias, coll_query_value_type_t type);
80
81static const gchar *canonical_field_name (const gchar *field);
82static gboolean operator_is_allmedia (xmmsv_coll_t *op);
83static coll_query_alias_t *query_make_alias (coll_query_t *query, const gchar *field, gboolean optional);
84static coll_query_alias_t *query_get_alias (coll_query_t *query, const gchar *field);
85
86
87
88/** @defgroup CollectionQuery CollectionQuery
89 * @ingroup XMMSServer
90 * @brief This module generates queries from collections.
91 *
92 * @{
93 */
94
95/* Generate a query string from a collection and query parameters. */
96GString*
98 guint limit_start, guint limit_len,
99 xmmsv_t *order, xmmsv_t *fetch, xmmsv_t *group)
100{
101 GString *qstring;
102 coll_query_t *query;
103 coll_query_params_t params = { limit_start, limit_len, order, fetch, group };
104
105 query = init_query (&params);
106 xmms_collection_append_to_query (dag, coll, query);
107 add_fetch_group_aliases (query, &params);
108
109 qstring = xmms_collection_gen_query (query);
110
111 destroy_query (query);
112
113 return qstring;
114}
115
116
117/* Initialize a query structure */
118static coll_query_t*
119init_query (coll_query_params_t *params)
120{
121 coll_query_t *query;
122
123 query = g_new (coll_query_t, 1);
124 if (query == NULL) {
125 return NULL;
126 }
127
128 query->aliases = g_hash_table_new_full (g_str_hash, g_str_equal,
129 g_free, g_free);
130
131 query->alias_count = 1;
132 query->alias_base = NULL;
133 query->conditions = g_string_new (NULL);
134 query->params = params;
135
136 return query;
137}
138
139static void
140append_each_alias (xmmsv_t *value, void *udata)
141{
142 const gchar *name;
143 coll_query_t *query = (coll_query_t *) udata;
144 xmmsv_get_string (value, &name);
145 query_make_alias (query, name, TRUE);
146}
147
148static void
149add_fetch_group_aliases (coll_query_t *query, coll_query_params_t *params)
150{
151 /* Prepare aliases for the group/fetch fields */
152 xmmsv_list_foreach (query->params->group, append_each_alias, query);
153 xmmsv_list_foreach (query->params->fetch, append_each_alias, query);
154}
155
156/* Free a coll_query_t object */
157static void
158destroy_query (coll_query_t* query)
159{
160 g_hash_table_destroy (query->aliases);
161 g_string_free (query->conditions, TRUE);
162 g_free (query);
163}
164
165
166/* Generate a query string from a query structure. */
167static GString*
168xmms_collection_gen_query (coll_query_t *query)
169{
170 GString *qstring;
171
172 /* If no alias base yet (m0), select the default base property */
173 if (query->alias_base == NULL) {
174 query_make_alias (query, XMMS_COLLQUERY_DEFAULT_BASE, FALSE);
175 } else {
176 /* We are actually interested in the property of m0...
177 Let's make sure it comes from a good source. */
178 if (query->conditions->len > 0) {
179 g_string_append (query->conditions, " AND ");
180 }
181 g_string_append_printf (query->conditions,
182 "xmms_source_pref (m0.source) = "
183 "(SELECT MIN (xmms_source_pref (n.source)) FROM Media AS n "
184 "WHERE n.id = m0.id AND n.key = '%s')",
185 query->alias_base);
186 }
187
188 /* Append select and joins */
189 qstring = g_string_new ("SELECT DISTINCT ");
190 query_string_append_fetch (query, qstring);
191 g_string_append (qstring, " FROM Media AS m0");
192 g_hash_table_foreach (query->aliases, query_string_append_joins, qstring);
193
194 /* Append conditions */
195 g_string_append_printf (qstring, " WHERE m0.key='%s'", query->alias_base);
196 if (query->conditions->len > 0) {
197 g_string_append_printf (qstring, " AND %s", query->conditions->str);
198 }
199
200 /* Append grouping */
201 if (xmmsv_list_get_size (query->params->group) > 0) {
202 g_string_append (qstring, " GROUP BY ");
203 query_string_append_alias_list (query, qstring, query->params->group);
204 }
205
206 /* Append ordering */
207 /* FIXME: Ordering is Teh Broken (source?) */
208 if (xmmsv_list_get_size (query->params->order) > 0) {
209 g_string_append (qstring, " ORDER BY ");
210 query_string_append_alias_list (query, qstring, query->params->order);
211 }
212
213 /* Append limit */
214 if (query->params->limit_len != 0) {
215 if (query->params->limit_start ) {
216 g_string_append_printf (qstring, " LIMIT %u,%u",
217 query->params->limit_start,
218 query->params->limit_len);
219 } else {
220 g_string_append_printf (qstring, " LIMIT %u",
221 query->params->limit_len);
222 }
223 }
224
225 return qstring;
226}
227
228/* Recursively append conditions corresponding to the given collection to the query. */
229static void
230xmms_collection_append_to_query (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
231 coll_query_t *query)
232{
233 xmmsv_coll_t *op;
235 gchar *attr1, *attr2, *attr3;
236 gboolean case_sens;
237 xmmsv_list_iter_t *iter;
238 xmmsv_t *tmp;
239 gboolean first;
240
242 switch (type) {
244 if (!operator_is_allmedia (coll)) {
245 query_append_operand (query, dag, coll);
246 } else {
247 /* FIXME: Hackish solution to append a ref to All Media */
248 query_append_string (query, "1");
249 }
250 break;
251
254 first = TRUE;
255 query_append_string (query, "(");
256
258
259 for (xmmsv_list_iter_first (iter);
261 xmmsv_list_iter_next (iter)) {
262 if (first) {
263 first = FALSE;
264 } else {
265 if (type == XMMS_COLLECTION_TYPE_UNION)
266 query_append_string (query, " OR ");
267 else
268 query_append_string (query, " AND ");
269 }
270 xmmsv_list_iter_entry (iter, &tmp);
271 xmmsv_get_coll (tmp, &op);
272 xmms_collection_append_to_query (dag, op, query);
273 }
275
276 query_append_string (query, ")");
277 break;
278
280 query_append_string (query, "NOT ");
281 query_append_operand (query, dag, coll);
282 break;
283
289 xmmsv_coll_attribute_get (coll, "field", &attr1);
290 xmmsv_coll_attribute_get (coll, "value", &attr2);
291 xmmsv_coll_attribute_get (coll, "case-sensitive", &attr3);
292 case_sens = (attr3 != NULL && strcmp (attr3, "true") == 0);
293
294 query_append_string (query, "(");
295 query_append_filter (query, type, attr1, attr2, case_sens);
296
297 query_append_intersect_operand (query, dag, coll);
298 query_append_string (query, ")");
299 break;
300
304 first = TRUE;
305 query_append_string (query, "m0.id IN (");
306
308 for (xmmsv_list_iter_first (iter);
310 xmmsv_list_iter_next (iter)) {
311
312 if (first) {
313 first = FALSE;
314 } else {
315 query_append_string (query, ",");
316 }
317
318 xmmsv_list_iter_entry_int (iter, &entry);
319 query_append_int (query, entry);
320 }
322
323 query_append_string (query, ")");
324 break;
325
326 /* invalid type */
327 default:
328 XMMS_DBG ("Cannot append invalid collection operator!");
329 g_assert_not_reached ();
330 break;
331 }
332
333}
334
335
336/** Register a (unique) field alias in the query structure and return
337 * the corresponding alias pointer.
338 *
339 * @param query The query object to insert the alias in.
340 * @param field The name of the property that will correspond to the alias.
341 * @param optional Whether the property can be optional (i.e. LEFT JOIN)
342 * @return The alias pointer.
343 */
344static coll_query_alias_t *
345query_make_alias (coll_query_t *query, const gchar *field, gboolean optional)
346{
347 coll_query_alias_t *alias;
348 alias = g_hash_table_lookup (query->aliases, field);
349
350 /* Insert in the hashtable */
351 if (alias == NULL) {
352 gchar *fieldkey = g_strdup (field);
353
354 alias = g_new (coll_query_alias_t, 1);
355 alias->optional = optional;
356 alias->id = 0;
357
358 if (strcmp (field, "id") == 0) {
359 alias->type = XMMS_QUERY_ALIAS_ID;
360 } else {
361 alias->type = XMMS_QUERY_ALIAS_PROP;
362
363 /* Found a base */
364 if (query->alias_base == NULL &&
365 (!optional || strcmp (field, XMMS_COLLQUERY_DEFAULT_BASE) == 0)) {
366 alias->id = 0;
367 query->alias_base = fieldkey;
368 } else {
369 alias->id = query->alias_count;
370 query->alias_count++;
371 }
372 }
373
374 g_hash_table_insert (query->aliases, fieldkey, alias);
375
376 /* If was not optional but now is, update */
377 } else if (!alias->optional && optional) {
378 alias->optional = optional;
379 }
380
381 return alias;
382}
383
384static coll_query_alias_t *
385query_get_alias (coll_query_t *query, const gchar *field)
386{
387 return g_hash_table_lookup (query->aliases, field);
388}
389
390/* Find the canonical name of a field (strip flags, if any) */
391static const gchar *
392canonical_field_name (const gchar *field) {
393 if (*field == '-') {
394 field++;
395 } else if (*field == '~') {
396 field = NULL;
397 }
398 return field;
399}
400
401
402/* Determine whether the given operator is a reference to "All Media" */
403static gboolean
404operator_is_allmedia (xmmsv_coll_t *op)
405{
406 gchar *target_name;
407 xmmsv_coll_attribute_get (op, "reference", &target_name);
408 return (target_name != NULL && strcmp (target_name, "All Media") == 0);
409}
410
411static void
412query_append_int (coll_query_t *query, gint i)
413{
414 g_string_append_printf (query->conditions, "%d", i);
415}
416
417static void
418query_append_string (coll_query_t *query, const gchar *s)
419{
420 g_string_append (query->conditions, s);
421}
422
423static void
424query_append_protect_string (coll_query_t *query, gchar *s)
425{
426 gchar *preps;
427 if ((preps = sqlite_prepare_string (s)) != NULL) { /* FIXME: Return oom error */
428 query_append_string (query, preps);
429 g_free (preps);
430 }
431}
432
433static void
434query_append_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsv_coll_t *coll)
435{
436 xmmsv_coll_t *op = NULL;
437 gchar *target_name;
438 gchar *target_ns;
439 guint target_nsid;
440
441 if (!xmmsv_list_get_coll (xmmsv_coll_operands_get (coll), 0, &op)) {
442
443 /* Ref'd coll not saved as operand, look for it */
444 if (xmmsv_coll_attribute_get (coll, "reference", &target_name) &&
445 xmmsv_coll_attribute_get (coll, "namespace", &target_ns)) {
446
447 target_nsid = xmms_collection_get_namespace_id (target_ns);
448 op = xmms_collection_get_pointer (dag, target_name, target_nsid);
449 }
450 }
451
452 /* Append reference operator */
453 if (op != NULL) {
454 xmms_collection_append_to_query (dag, op, query);
455
456 /* Cannot find reference, append dummy TRUE */
457 } else {
458 query_append_string (query, "1");
459 }
460}
461
462static void
463query_append_intersect_operand (coll_query_t *query, xmms_coll_dag_t *dag,
464 xmmsv_coll_t *coll)
465{
466 xmmsv_coll_t *op;
467 xmmsv_t *tmp;
468
469 if (xmmsv_list_get (xmmsv_coll_operands_get (coll), 0, &tmp)) {
470 xmmsv_get_coll (tmp, &op);
471
472 if (!operator_is_allmedia (op)) {
473 query_append_string (query, " AND ");
474 xmms_collection_append_to_query (dag, op, query);
475 }
476 }
477}
478
479/* Append a filtering clause on the field value, depending on the operator type. */
480static void
481query_append_filter (coll_query_t *query, xmmsv_coll_type_t type,
482 gchar *key, gchar *value, gboolean case_sens)
483{
484 coll_query_alias_t *alias;
485 gboolean optional;
486 gchar *temp;
487 gint i;
488
489 if (type == XMMS_COLLECTION_TYPE_HAS) {
490 optional = TRUE;
491 } else {
492 optional = FALSE;
493 }
494
495 alias = query_make_alias (query, key, optional);
496
497 switch (type) {
498 /* escape strings */
501 if (case_sens) {
502 query_string_append_alias (query->conditions, alias,
504 } else {
505 query_append_string (query, "(");
506 query_string_append_alias (query->conditions, alias,
508 query_append_string (query, " COLLATE NOCASE)");
509 }
510
511 if (type == XMMS_COLLECTION_TYPE_EQUALS) {
512 query_append_string (query, "=");
513 } else {
514 if (case_sens) {
515 query_append_string (query, " GLOB ");
516 } else {
517 query_append_string (query, " LIKE ");
518 }
519 }
520
521 if (type == XMMS_COLLECTION_TYPE_MATCH && !case_sens) {
522 temp = g_strdup(value);
523 for (i = 0; temp[i]; i++) {
524 switch (temp[i]) {
525 case '*': temp[i] = '%'; break;
526 case '?': temp[i] = '_'; break;
527 default : break;
528 }
529 }
530 query_append_protect_string (query, temp);
531 g_free(temp);
532 } else {
533 query_append_protect_string (query, value);
534 }
535 break;
536
537 /* do not escape numerical values */
540 query_string_append_alias (query->conditions, alias,
542 if (type == XMMS_COLLECTION_TYPE_SMALLER) {
543 query_append_string (query, " < ");
544 } else {
545 query_append_string (query, " > ");
546 }
547 query_append_string (query, value);
548 break;
549
551 query_string_append_alias (query->conditions, alias,
553 query_append_string (query, " is not null");
554 break;
555
556 /* Called with invalid type? */
557 default:
558 g_assert_not_reached ();
559 break;
560 }
561}
562
563/* Append SELECT joins to the argument string for each alias of the hashtable. */
564static void
565query_string_append_joins (gpointer key, gpointer val, gpointer udata)
566{
567 gchar *field;
568 GString *qstring;
569 coll_query_alias_t *alias;
570
571 field = key;
572 qstring = (GString*)udata;
573 alias = (coll_query_alias_t*)val;
574
575 if ((alias->id > 0) && (alias->type == XMMS_QUERY_ALIAS_PROP)) {
576 if (alias->optional) {
577 g_string_append_printf (qstring, " LEFT");
578 }
579
580 g_string_append_printf (qstring,
581 " JOIN Media AS m%u ON m0.id=m%u.id AND m%u.key='%s' AND"
582 " xmms_source_pref (m%u.source) = "
583 "(SELECT MIN (xmms_source_pref (n.source)) FROM Media AS n"
584 " WHERE n.id = m0.id AND n.key = '%s')",
585 alias->id, alias->id, alias->id, field, alias->id, field);
586 }
587}
588
589/* Given a list of fields, append the corresponding aliases to the argument string. */
590static void
591query_string_append_alias_list (coll_query_t *query, GString *qstring,
592 xmmsv_t *fields)
593{
594 coll_query_alias_t *alias;
596 xmmsv_t *valstr;
597 gboolean first = TRUE;
598
599 for (xmmsv_get_list_iter (fields, &it);
602
603 /* extract string from cmdval_t */
604 const gchar *field, *canon_field;
605 xmmsv_list_iter_entry (it, &valstr);
606 xmmsv_get_string (valstr, &field);
607 canon_field = canonical_field_name (field);
608
609 if (first) first = FALSE;
610 else {
611 g_string_append (qstring, ", ");
612 }
613
614 if (canon_field != NULL) {
615 alias = query_get_alias (query, canon_field);
616 if (alias != NULL) {
617 query_string_append_alias (qstring, alias,
619 } else {
620 if (*field != '~') {
621 if (strcmp(canon_field, "id") == 0) {
622 g_string_append (qstring, "m0.id");
623 } else {
624 g_string_append_printf (qstring,
625 "(SELECT IFNULL (intval, value) "
626 "FROM Media WHERE id = m0.id AND key='%s' AND "
627 "xmms_source_pref (source) = "
628 "(SELECT MIN (xmms_source_pref (n.source)) "
629 "FROM Media AS n WHERE n.id = m0.id AND "
630 "n.key = '%s'))",
631 canon_field, canon_field);
632 }
633 }
634 }
635 }
636
637 /* special prefix for ordering */
638 if (*field == '-') {
639 g_string_append (qstring, " DESC");
640 } else if (*field == '~') {
641 /* FIXME: Temporary hack to allow custom ordering functions */
642 g_string_append (qstring, field + 1);
643 }
644 }
645}
646
647static void
648query_string_append_fetch (coll_query_t *query, GString *qstring)
649{
650 coll_query_alias_t *alias;
652 xmmsv_t *valstr;
653 gboolean first = TRUE;
654 const gchar *name;
655
656 for (xmmsv_get_list_iter (query->params->fetch, &it);
659
660 /* extract string from cmdval_t */
661 xmmsv_list_iter_entry (it, &valstr);
662 xmmsv_get_string (valstr, &name);
663 alias = query_make_alias (query, name, TRUE);
664
665 if (first) first = FALSE;
666 else {
667 g_string_append (qstring, ", ");
668 }
669
670 query_string_append_alias (qstring, alias,
672 g_string_append_printf (qstring, " AS %s", name);
673 }
674}
675
676static void
677query_string_append_alias (GString *qstring, coll_query_alias_t *alias,
679{
680 switch (alias->type) {
682 switch (type) {
684 g_string_append_printf (qstring, "m%u.value", alias->id);
685 break;
687 g_string_append_printf (qstring, "m%u.intval", alias->id);
688 break;
690 g_string_append_printf (qstring, "IFNULL (m%u.intval, m%u.value)",
691 alias->id, alias->id);
692 break;
693 }
694 break;
695
697 g_string_append (qstring, "m0.id");
698 break;
699
700 default:
701 break;
702 }
703}
704
705/**
706 * @}
707 */
xmms_collection_namespace_id_t xmms_collection_get_namespace_id(const gchar *namespace)
Find the namespace id corresponding to a namespace string.
coll_query_alias_type_t
Definition collquery.c:39
@ XMMS_QUERY_ALIAS_ID
Definition collquery.c:40
@ XMMS_QUERY_ALIAS_PROP
Definition collquery.c:41
coll_query_value_type_t
Definition collquery.c:58
@ COLL_QUERY_VALUE_TYPE_STRING
Definition collquery.c:59
@ COLL_QUERY_VALUE_TYPE_BOTH
Definition collquery.c:61
@ COLL_QUERY_VALUE_TYPE_INT
Definition collquery.c:60
GString * xmms_collection_get_query(xmms_coll_dag_t *dag, xmmsv_coll_t *coll, guint limit_start, guint limit_len, xmmsv_t *order, xmmsv_t *fetch, xmmsv_t *group)
Definition collquery.c:97
struct xmmsv_St * xmmsv_coll_idlist_get(xmmsv_coll_t *coll)
Return the list of ids stored in the collection.
Definition coll.c:429
xmmsv_coll_type_t xmmsv_coll_get_type(xmmsv_coll_t *coll)
Return the type of the collection.
Definition coll.c:367
struct xmmsv_St * xmmsv_coll_operands_get(xmmsv_coll_t *coll)
Definition coll.c:437
int xmmsv_coll_attribute_get(xmmsv_coll_t *coll, const char *key, char **value)
Retrieve the value of the attribute of the given collection.
Definition coll.c:498
xmmsv_coll_t * xmms_collection_get_pointer(xmms_coll_dag_t *dag, const gchar *collname, guint nsid)
Find the collection structure corresponding to the given name in the given namespace.
Definition collection.c:873
int xmmsv_get_list_iter(const xmmsv_t *val, xmmsv_list_iter_t **it)
Retrieves a list iterator from a list xmmsv_t.
Definition value.c:926
void xmmsv_list_iter_next(xmmsv_list_iter_t *it)
Advance the iterator to the next element in the list.
Definition value.c:1553
int xmmsv_list_foreach(xmmsv_t *listv, xmmsv_list_foreach_func func, void *user_data)
Apply a function to each element in the list, in sequential order.
Definition value.c:1375
struct xmmsv_list_iter_St xmmsv_list_iter_t
Definition xmmsv_list.h:69
int xmmsv_list_iter_entry(xmmsv_list_iter_t *it, xmmsv_t **val)
Get the element currently pointed at by the iterator.
Definition value.c:1495
void xmmsv_list_iter_first(xmmsv_list_iter_t *it)
Rewind the iterator to the start of the list.
Definition value.c:1523
void xmmsv_list_iter_explicit_destroy(xmmsv_list_iter_t *it)
Explicitly free list iterator.
Definition value.c:1478
int xmmsv_list_iter_valid(xmmsv_list_iter_t *it)
Check whether the iterator is valid and points to a valid element.
Definition value.c:1512
int xmmsv_list_iter_entry_int(xmmsv_list_iter_t *it, int32_t *val)
int xmmsv_list_get(xmmsv_t *listv, int pos, xmmsv_t **val)
Get the element at the given position in the list xmmsv_t.
Definition value.c:1218
int xmmsv_list_get_size(xmmsv_t *listv)
Return the size of the list.
Definition value.c:1403
int xmmsv_list_get_coll(xmmsv_t *v, int pos, xmmsv_coll_t **val)
gchar * sqlite_prepare_string(const gchar *input)
Definition sqlite.c:809
int xmmsv_get_coll(const xmmsv_t *val, xmmsv_coll_t **coll)
Retrieves a collection from the value.
Definition value.c:883
int xmmsv_get_string(const xmmsv_t *val, const char **r)
Retrieves a string from the value.
Definition value.c:863
struct xmmsv_St xmmsv_t
#define XMMS_DBG(fmt,...)
Definition xmms_log.h:32
G_BEGIN_DECLS typedef gint32 xmms_medialib_entry_t
struct xmms_coll_dag_St xmms_coll_dag_t
#define XMMS_COLLQUERY_DEFAULT_BASE
xmmsv_coll_type_t
@ XMMS_COLLECTION_TYPE_UNION
@ XMMS_COLLECTION_TYPE_IDLIST
@ XMMS_COLLECTION_TYPE_INTERSECTION
@ XMMS_COLLECTION_TYPE_GREATER
@ XMMS_COLLECTION_TYPE_COMPLEMENT
@ XMMS_COLLECTION_TYPE_MATCH
@ XMMS_COLLECTION_TYPE_REFERENCE
@ XMMS_COLLECTION_TYPE_SMALLER
@ XMMS_COLLECTION_TYPE_EQUALS
@ XMMS_COLLECTION_TYPE_HAS
@ XMMS_COLLECTION_TYPE_QUEUE
@ XMMS_COLLECTION_TYPE_PARTYSHUFFLE
struct xmmsv_coll_St xmmsv_coll_t
Definition xmmsv_coll.h:28