XMMS2
collquery.c
Go to the documentation of this file.
1 /* XMMS2 - X Music Multiplexer System
2  * Copyright (C) 2003-2009 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 
31 typedef 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 
39 typedef enum {
43 
44 typedef struct {
46  guint id;
47  gboolean optional;
48 } coll_query_alias_t;
49 
50 typedef 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 
58 typedef enum {
63 
64 static coll_query_t* init_query (coll_query_params_t *params);
65 static void add_fetch_group_aliases (coll_query_t *query, coll_query_params_t *params);
66 static void destroy_query (coll_query_t* query);
67 static GString* xmms_collection_gen_query (coll_query_t *query);
68 static void xmms_collection_append_to_query (xmms_coll_dag_t *dag, xmmsv_coll_t *coll, coll_query_t *query);
69 
70 static void query_append_uint (coll_query_t *query, guint i);
71 static void query_append_string (coll_query_t *query, const gchar *s);
72 static void query_append_protect_string (coll_query_t *query, gchar *s);
73 static void query_append_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsv_coll_t *coll);
74 static void query_append_intersect_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsv_coll_t *coll);
75 static void query_append_filter (coll_query_t *query, xmmsv_coll_type_t type, gchar *key, gchar *value, gboolean case_sens);
76 static void query_string_append_joins (gpointer key, gpointer val, gpointer udata);
77 static void query_string_append_alias_list (coll_query_t *query, GString *qstring, xmmsv_t *fields);
78 static void query_string_append_fetch (coll_query_t *query, GString *qstring);
79 static void query_string_append_alias (GString *qstring, coll_query_alias_t *alias, coll_query_value_type_t type);
80 
81 static const gchar *canonical_field_name (const gchar *field);
82 static gboolean operator_is_allmedia (xmmsv_coll_t *op);
83 static coll_query_alias_t *query_make_alias (coll_query_t *query, const gchar *field, gboolean optional);
84 static 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. */
96 GString*
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 */
118 static coll_query_t*
119 init_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 
139 static void
140 append_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 
148 static void
149 add_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 */
157 static void
158 destroy_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. */
167 static GString*
168 xmms_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. */
229 static void
230 xmms_collection_append_to_query (xmms_coll_dag_t *dag, xmmsv_coll_t *coll,
231  coll_query_t *query)
232 {
233  gint i;
234  xmmsv_coll_t *op;
235  guint *idlist;
236  gchar *attr1, *attr2, *attr3;
237  gboolean case_sens;
238  xmmsv_list_iter_t *iter;
239  xmmsv_t *tmp;
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  i = 0;
255  query_append_string (query, "(");
256 
258 
259  for (xmmsv_list_iter_first (iter);
260  xmmsv_list_iter_valid (iter);
261  xmmsv_list_iter_next (iter)) {
262  if (i != 0) {
263  if (type == XMMS_COLLECTION_TYPE_UNION)
264  query_append_string (query, " OR ");
265  else
266  query_append_string (query, " AND ");
267  } else {
268  i = 1;
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  idlist = xmmsv_coll_get_idlist (coll);
305  query_append_string (query, "m0.id IN (");
306  for (i = 0; idlist[i] != 0; ++i) {
307  if (i != 0) {
308  query_append_string (query, ",");
309  }
310  query_append_uint (query, idlist[i]);
311  }
312  query_append_string (query, ")");
313  break;
314 
315  /* invalid type */
316  default:
317  XMMS_DBG ("Cannot append invalid collection operator!");
318  g_assert_not_reached ();
319  break;
320  }
321 
322 }
323 
324 
325 /** Register a (unique) field alias in the query structure and return
326  * the corresponding alias pointer.
327  *
328  * @param query The query object to insert the alias in.
329  * @param field The name of the property that will correspond to the alias.
330  * @param optional Whether the property can be optional (i.e. LEFT JOIN)
331  * @return The alias pointer.
332  */
333 static coll_query_alias_t *
334 query_make_alias (coll_query_t *query, const gchar *field, gboolean optional)
335 {
336  coll_query_alias_t *alias;
337  alias = g_hash_table_lookup (query->aliases, field);
338 
339  /* Insert in the hashtable */
340  if (alias == NULL) {
341  gchar *fieldkey = g_strdup (field);
342 
343  alias = g_new (coll_query_alias_t, 1);
344  alias->optional = optional;
345  alias->id = 0;
346 
347  if (strcmp (field, "id") == 0) {
348  alias->type = XMMS_QUERY_ALIAS_ID;
349  } else {
350  alias->type = XMMS_QUERY_ALIAS_PROP;
351 
352  /* Found a base */
353  if (query->alias_base == NULL &&
354  (!optional || strcmp (field, XMMS_COLLQUERY_DEFAULT_BASE) == 0)) {
355  alias->id = 0;
356  query->alias_base = fieldkey;
357  } else {
358  alias->id = query->alias_count;
359  query->alias_count++;
360  }
361  }
362 
363  g_hash_table_insert (query->aliases, fieldkey, alias);
364 
365  /* If was not optional but now is, update */
366  } else if (!alias->optional && optional) {
367  alias->optional = optional;
368  }
369 
370  return alias;
371 }
372 
373 static coll_query_alias_t *
374 query_get_alias (coll_query_t *query, const gchar *field)
375 {
376  return g_hash_table_lookup (query->aliases, field);
377 }
378 
379 /* Find the canonical name of a field (strip flags, if any) */
380 static const gchar *
381 canonical_field_name (const gchar *field) {
382  if (*field == '-') {
383  field++;
384  } else if (*field == '~') {
385  field = NULL;
386  }
387  return field;
388 }
389 
390 
391 /* Determine whether the given operator is a reference to "All Media" */
392 static gboolean
393 operator_is_allmedia (xmmsv_coll_t *op)
394 {
395  gchar *target_name;
396  xmmsv_coll_attribute_get (op, "reference", &target_name);
397  return (target_name != NULL && strcmp (target_name, "All Media") == 0);
398 }
399 
400 static void
401 query_append_uint (coll_query_t *query, guint i)
402 {
403  g_string_append_printf (query->conditions, "%u", i);
404 }
405 
406 static void
407 query_append_string (coll_query_t *query, const gchar *s)
408 {
409  g_string_append (query->conditions, s);
410 }
411 
412 static void
413 query_append_protect_string (coll_query_t *query, gchar *s)
414 {
415  gchar *preps;
416  if ((preps = sqlite_prepare_string (s)) != NULL) { /* FIXME: Return oom error */
417  query_append_string (query, preps);
418  g_free (preps);
419  }
420 }
421 
422 static void
423 query_append_operand (coll_query_t *query, xmms_coll_dag_t *dag, xmmsv_coll_t *coll)
424 {
425  xmmsv_coll_t *op = NULL;
426  gchar *target_name;
427  gchar *target_ns;
428  guint target_nsid;
429 
430  if (!xmmsv_list_get_coll (xmmsv_coll_operands_get (coll), 0, &op)) {
431 
432  /* Ref'd coll not saved as operand, look for it */
433  if (xmmsv_coll_attribute_get (coll, "reference", &target_name) &&
434  xmmsv_coll_attribute_get (coll, "namespace", &target_ns)) {
435 
436  target_nsid = xmms_collection_get_namespace_id (target_ns);
437  op = xmms_collection_get_pointer (dag, target_name, target_nsid);
438  }
439  }
440 
441  /* Append reference operator */
442  if (op != NULL) {
443  xmms_collection_append_to_query (dag, op, query);
444 
445  /* Cannot find reference, append dummy TRUE */
446  } else {
447  query_append_string (query, "1");
448  }
449 }
450 
451 static void
452 query_append_intersect_operand (coll_query_t *query, xmms_coll_dag_t *dag,
453  xmmsv_coll_t *coll)
454 {
455  xmmsv_coll_t *op;
456  xmmsv_t *tmp;
457 
458  if (xmmsv_list_get (xmmsv_coll_operands_get (coll), 0, &tmp)) {
459  xmmsv_get_coll (tmp, &op);
460 
461  if (!operator_is_allmedia (op)) {
462  query_append_string (query, " AND ");
463  xmms_collection_append_to_query (dag, op, query);
464  }
465  }
466 }
467 
468 /* Append a filtering clause on the field value, depending on the operator type. */
469 static void
470 query_append_filter (coll_query_t *query, xmmsv_coll_type_t type,
471  gchar *key, gchar *value, gboolean case_sens)
472 {
473  coll_query_alias_t *alias;
474  gboolean optional;
475  gchar *temp;
476  gint i;
477 
478  if (type == XMMS_COLLECTION_TYPE_HAS) {
479  optional = TRUE;
480  } else {
481  optional = FALSE;
482  }
483 
484  alias = query_make_alias (query, key, optional);
485 
486  switch (type) {
487  /* escape strings */
490  if (case_sens) {
491  query_string_append_alias (query->conditions, alias,
493  } else {
494  query_append_string (query, "(");
495  query_string_append_alias (query->conditions, alias,
497  query_append_string (query, " COLLATE NOCASE)");
498  }
499 
500  if (type == XMMS_COLLECTION_TYPE_EQUALS) {
501  query_append_string (query, "=");
502  } else {
503  if (case_sens) {
504  query_append_string (query, " GLOB ");
505  } else {
506  query_append_string (query, " LIKE ");
507  }
508  }
509 
510  if (type == XMMS_COLLECTION_TYPE_MATCH && !case_sens) {
511  temp = g_strdup(value);
512  for (i = 0; temp[i]; i++) {
513  switch (temp[i]) {
514  case '*': temp[i] = '%'; break;
515  case '?': temp[i] = '_'; break;
516  default : break;
517  }
518  }
519  query_append_protect_string (query, temp);
520  g_free(temp);
521  } else {
522  query_append_protect_string (query, value);
523  }
524  break;
525 
526  /* do not escape numerical values */
529  query_string_append_alias (query->conditions, alias,
531  if (type == XMMS_COLLECTION_TYPE_SMALLER) {
532  query_append_string (query, " < ");
533  } else {
534  query_append_string (query, " > ");
535  }
536  query_append_string (query, value);
537  break;
538 
540  query_string_append_alias (query->conditions, alias,
542  query_append_string (query, " is not null");
543  break;
544 
545  /* Called with invalid type? */
546  default:
547  g_assert_not_reached ();
548  break;
549  }
550 }
551 
552 /* Append SELECT joins to the argument string for each alias of the hashtable. */
553 static void
554 query_string_append_joins (gpointer key, gpointer val, gpointer udata)
555 {
556  gchar *field;
557  GString *qstring;
558  coll_query_alias_t *alias;
559 
560  field = key;
561  qstring = (GString*)udata;
562  alias = (coll_query_alias_t*)val;
563 
564  if ((alias->id > 0) && (alias->type == XMMS_QUERY_ALIAS_PROP)) {
565  if (alias->optional) {
566  g_string_append_printf (qstring, " LEFT");
567  }
568 
569  g_string_append_printf (qstring,
570  " JOIN Media AS m%u ON m0.id=m%u.id AND m%u.key='%s' AND"
571  " xmms_source_pref (m%u.source) = "
572  "(SELECT MIN (xmms_source_pref (n.source)) FROM Media AS n"
573  " WHERE n.id = m0.id AND n.key = '%s')",
574  alias->id, alias->id, alias->id, field, alias->id, field);
575  }
576 }
577 
578 /* Given a list of fields, append the corresponding aliases to the argument string. */
579 static void
580 query_string_append_alias_list (coll_query_t *query, GString *qstring,
581  xmmsv_t *fields)
582 {
583  coll_query_alias_t *alias;
584  xmmsv_list_iter_t *it;
585  xmmsv_t *valstr;
586  gboolean first = TRUE;
587 
588  for (xmmsv_get_list_iter (fields, &it);
590  xmmsv_list_iter_next (it)) {
591 
592  /* extract string from cmdval_t */
593  const gchar *field, *canon_field;
594  xmmsv_list_iter_entry (it, &valstr);
595  xmmsv_get_string (valstr, &field);
596  canon_field = canonical_field_name (field);
597 
598  if (first) first = FALSE;
599  else {
600  g_string_append (qstring, ", ");
601  }
602 
603  if (canon_field != NULL) {
604  alias = query_get_alias (query, canon_field);
605  if (alias != NULL) {
606  query_string_append_alias (qstring, alias,
608  } else {
609  if (*field != '~') {
610  if (strcmp(canon_field, "id") == 0) {
611  g_string_append (qstring, "m0.id");
612  } else {
613  g_string_append_printf (qstring,
614  "(SELECT IFNULL (intval, value) "
615  "FROM Media WHERE id = m0.id AND key='%s' AND "
616  "xmms_source_pref (source) = "
617  "(SELECT MIN (xmms_source_pref (n.source)) "
618  "FROM Media AS n WHERE n.id = m0.id AND "
619  "n.key = '%s'))",
620  canon_field, canon_field);
621  }
622  }
623  }
624  }
625 
626  /* special prefix for ordering */
627  if (*field == '-') {
628  g_string_append (qstring, " DESC");
629  } else if (*field == '~') {
630  /* FIXME: Temporary hack to allow custom ordering functions */
631  g_string_append (qstring, field + 1);
632  }
633  }
634 }
635 
636 static void
637 query_string_append_fetch (coll_query_t *query, GString *qstring)
638 {
639  coll_query_alias_t *alias;
640  xmmsv_list_iter_t *it;
641  xmmsv_t *valstr;
642  gboolean first = TRUE;
643  const gchar *name;
644 
645  for (xmmsv_get_list_iter (query->params->fetch, &it);
647  xmmsv_list_iter_next (it)) {
648 
649  /* extract string from cmdval_t */
650  xmmsv_list_iter_entry (it, &valstr);
651  xmmsv_get_string (valstr, &name);
652  alias = query_make_alias (query, name, TRUE);
653 
654  if (first) first = FALSE;
655  else {
656  g_string_append (qstring, ", ");
657  }
658 
659  query_string_append_alias (qstring, alias,
661  g_string_append_printf (qstring, " AS %s", name);
662  }
663 }
664 
665 static void
666 query_string_append_alias (GString *qstring, coll_query_alias_t *alias,
668 {
669  switch (alias->type) {
671  switch (type) {
673  g_string_append_printf (qstring, "m%u.value", alias->id);
674  break;
676  g_string_append_printf (qstring, "m%u.intval", alias->id);
677  break;
679  g_string_append_printf (qstring, "IFNULL (m%u.intval, m%u.value)",
680  alias->id, alias->id);
681  break;
682  }
683  break;
684 
685  case XMMS_QUERY_ALIAS_ID:
686  g_string_append (qstring, "m0.id");
687  break;
688 
689  default:
690  break;
691  }
692 }
693 
694 /**
695  * @}
696  */
struct xmmsv_St xmmsv_t
Definition: xmmsv.h:51
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:550
xmms_collection_namespace_id_t xmms_collection_get_namespace_id(const gchar *namespace)
Find the namespace id corresponding to a namespace string.
Definition: collection.c:1353
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:1210
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:918
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:1487
void xmmsv_list_iter_first(xmmsv_list_iter_t *it)
Rewind the iterator to the start of the list.
Definition: value.c:1515
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:927
coll_query_alias_type_t
Definition: collquery.c:39
xmmsv_coll_type_t
coll_query_value_type_t
Definition: collquery.c:58
struct xmmsv_St * xmmsv_coll_operands_get(xmmsv_coll_t *coll)
Definition: coll.c:489
int xmmsv_list_get_coll(xmmsv_t *v, int pos, xmmsv_coll_t **val)
void xmmsv_list_iter_explicit_destroy(xmmsv_list_iter_t *it)
Explicitly free list iterator.
Definition: value.c:1470
int xmmsv_get_coll(const xmmsv_t *val, xmmsv_coll_t **coll)
Retrieves a collection from the value.
Definition: value.c:875
struct xmmsv_coll_St xmmsv_coll_t
Definition: xmmsv_coll.h:28
int xmmsv_list_get_size(xmmsv_t *listv)
Return the size of the list.
Definition: value.c:1395
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
void xmmsv_list_iter_next(xmmsv_list_iter_t *it)
Advance the iterator to the next element in the list.
Definition: value.c:1545
gchar * sqlite_prepare_string(const gchar *input)
Definition: sqlite.c:809
#define XMMS_DBG(fmt,...)
Definition: xmms_log.h:32
int xmmsv_get_string(const xmmsv_t *val, const char **r)
Retrieves a string from the value.
Definition: value.c:855
uint32_t * xmmsv_coll_get_idlist(xmmsv_coll_t *coll)
Return the list of ids stored in the collection.
Definition: coll.c:481
#define XMMS_COLLQUERY_DEFAULT_BASE
xmmsv_coll_type_t xmmsv_coll_get_type(xmmsv_coll_t *coll)
Return the type of the collection.
Definition: coll.c:464
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:1367
struct xmmsv_list_iter_St xmmsv_list_iter_t
Definition: xmmsv.h:53
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:1504
struct xmms_coll_dag_St xmms_coll_dag_t