static void
test_query_eval (RhythmDB *db, RhythmDBQuery *query, RhythmDBEntry *entry, gboolean expected, const char *what)
{
	RhythmDBQueryModel *model;
	RhythmDBQuery *processed;
	GtkTreeIter iter = {0,};

	/* direct evaluation - need to preprocess it first */
	processed = rhythmdb_query_copy (query);
	rhythmdb_query_preprocess (db, processed);
	fail_unless (rhythmdb_evaluate_query (db, processed, entry) == expected, what);
	rhythmdb_query_free (processed);

	/* query evaluation - query is preprocessed by rhythmdb */
	model = rhythmdb_query_model_new_empty (db);
	rhythmdb_do_full_query_parsed (db, RHYTHMDB_QUERY_RESULTS (model), query);
	fail_unless (rhythmdb_query_model_entry_to_iter (model, entry, &iter) == expected, what);
	g_object_unref (model);
}
static void
rebuild_child_model (RBLibraryBrowser *widget,
		     gint property_index,
		     gboolean query_pending)
{
	RBLibraryBrowserPrivate *priv = RB_LIBRARY_BROWSER_GET_PRIVATE (widget);
	RhythmDBPropertyModel *prop_model;
	RhythmDBQueryModel *base_model, *child_model;
	RBPropertyView *view;
	RhythmDBQuery *query;
	GList *selections;

	g_assert (property_index >= 0);
	g_assert (property_index < num_browser_properties);

	/* get the query model for the previous property view */
	view = g_hash_table_lookup (priv->property_views, (gpointer)browser_properties[property_index].type);
	prop_model = rb_property_view_get_model (view);
	g_object_get (prop_model, "query-model", &base_model, NULL);

	selections = g_hash_table_lookup (priv->selections, (gpointer)browser_properties[property_index].type);
	if (selections != NULL) {

		/* create a new query model based on it, filtered by
		 * the selections of the previous property view.
		 * we need the entry type query criteria to allow the
		 * backend to optimise the query.
		 */
		query = rhythmdb_query_parse (priv->db,
				              RHYTHMDB_QUERY_PROP_EQUALS, RHYTHMDB_PROP_TYPE, priv->entry_type,
					      RHYTHMDB_QUERY_END);
		rhythmdb_query_append_prop_multiple (priv->db,
						     query,
						     browser_properties[property_index].type,
						     selections);

		child_model = rhythmdb_query_model_new_empty (priv->db);
		if (query_pending) {
			rb_debug ("rebuilding child model for browser %d; query is pending", property_index);
			g_object_set (child_model,
				      "query", query,
				      "base-model", base_model,
				      NULL);
		} else {
			rb_debug ("rebuilding child model for browser %d; running new query", property_index);
			rhythmdb_query_model_chain (child_model, base_model, FALSE);
			rhythmdb_do_full_query_parsed (priv->db,
						       RHYTHMDB_QUERY_RESULTS (child_model),
						       query);
		}
		rhythmdb_query_free (query);
	} else {
		rb_debug ("no selection for browser %d - reusing parent model", property_index);
		child_model = g_object_ref (base_model);
	}

	/* If this is the last property, use the child model as the output model
	 * for the browser.  Otherwise, use it as the input for the next property
	 * view.
	 */
	if (property_index == num_browser_properties-1) {
		if (priv->output_model != NULL) {
			g_object_unref (priv->output_model);
		}

		priv->output_model = child_model;

		g_object_notify (G_OBJECT (widget), "output-model");

	} else {
		view = g_hash_table_lookup (priv->property_views, (gpointer)browser_properties[property_index+1].type);
		ignore_selection_changes (widget, view, TRUE);

		prop_model = rb_property_view_get_model (view);
		g_object_set (prop_model, "query-model", child_model, NULL);

		g_object_unref (child_model);

		rebuild_child_model (widget, property_index + 1, query_pending);
		restore_selection (widget, property_index + 1, query_pending);
	}

	g_object_unref (base_model);
}
static void
rb_iradio_source_do_query (RBIRadioSource *source)
{
	RhythmDBQueryModel *genre_query_model = NULL;
	RhythmDBQueryModel *station_query_model = NULL;
	RhythmDBPropertyModel *genre_model;
	GPtrArray *query;
	RhythmDBEntryType *entry_type;

	/* don't update the selection while we're rebuilding the query */
	source->priv->setting_new_query = TRUE;

	/* construct and run the query for the search box.
	 * this is used as the model for the genre view.
	 */

	g_object_get (source, "entry-type", &entry_type, NULL);
	query = rhythmdb_query_parse (source->priv->db,
				      RHYTHMDB_QUERY_PROP_EQUALS,
				      RHYTHMDB_PROP_TYPE,
				      entry_type,
				      RHYTHMDB_QUERY_END);
	g_object_unref (entry_type);

	if (source->priv->search_query != NULL) {
		rhythmdb_query_append (source->priv->db,
				       query,
				       RHYTHMDB_QUERY_SUBQUERY,
				       source->priv->search_query,
				       RHYTHMDB_QUERY_END);
	}

	genre_model = rb_property_view_get_model (source->priv->genres);

	genre_query_model = rhythmdb_query_model_new_empty (source->priv->db);
	g_object_set (genre_model, "query-model", genre_query_model, NULL);

	rhythmdb_do_full_query_parsed (source->priv->db,
				       RHYTHMDB_QUERY_RESULTS (genre_query_model),
				       query);

	rhythmdb_query_free (query);
	query = NULL;

	/* check the selected genre is still available, and if not, select 'all' */
	if (source->priv->selected_genre != NULL) {
		GList *sel = NULL;

		if (!rhythmdb_property_model_iter_from_string (genre_model,
							       source->priv->selected_genre,
							       NULL)) {
			g_free (source->priv->selected_genre);
			source->priv->selected_genre = NULL;
		}

		sel = g_list_prepend (sel, source->priv->selected_genre);
		rb_property_view_set_selection (source->priv->genres, sel);
		g_list_free (sel);
	}

	/* if a genre is selected, construct a new query for it, and create
	 * a new model based on the search box query model.  otherwise, just
	 * reuse the search box query model.
	 */

	if (source->priv->selected_genre != NULL) {
		rb_debug ("matching on genre \"%s\"", source->priv->selected_genre);

		station_query_model = rhythmdb_query_model_new_empty (source->priv->db);
		query = rhythmdb_query_parse (source->priv->db,
					      RHYTHMDB_QUERY_PROP_EQUALS,
					      RHYTHMDB_PROP_GENRE,
					      source->priv->selected_genre,
					      RHYTHMDB_QUERY_END);

		g_object_set (station_query_model,
			      "query", query,
			      "base-model", genre_query_model,
			      NULL);

		rhythmdb_query_free (query);
		query = NULL;
	} else {
		station_query_model = g_object_ref (genre_query_model);
	}

	rb_entry_view_set_model (source->priv->stations, station_query_model);
	g_object_set (source, "query-model", station_query_model, NULL);

	g_object_unref (genre_query_model);
	g_object_unref (station_query_model);

	source->priv->setting_new_query = FALSE;
}