[gom] sorting: Support ordering queries
- From: Mathieu Bridon <mbridon src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gom] sorting: Support ordering queries
- Date: Wed, 8 Jul 2015 13:32:21 +0000 (UTC)
commit 423e1dc364c4c031129a41f4f665eea4e9627125
Author: Mathieu Bridon <bochecha daitauha fr>
Date: Sat May 16 15:28:07 2015 +0200
sorting: Support ordering queries
https://bugzilla.gnome.org/show_bug.cgi?id=730581
.gitignore | 1 +
gom/Makefile.include | 2 +
gom/gom-command-builder.c | 48 +++++
gom/gom-repository.c | 36 ++++
gom/gom-repository.h | 6 +
gom/gom-resource-group.c | 44 +++++
gom/gom-sorting.c | 226 ++++++++++++++++++++++
gom/gom-sorting.h | 68 +++++++
gom/gom.h | 1 +
tests/Makefile.include | 6 +
tests/test-gom-sorting.c | 453 +++++++++++++++++++++++++++++++++++++++++++++
11 files changed, 891 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 635a89e..4b3a1dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,7 @@ test-gom-transform
test-gom-migration
test-gom-constraints
test-gom-update
+test-gom-sorting
*.typelib
*.gmo
*.pot
diff --git a/gom/Makefile.include b/gom/Makefile.include
index eeb2162..4eef987 100644
--- a/gom/Makefile.include
+++ b/gom/Makefile.include
@@ -16,6 +16,7 @@ INST_H_FILES += gom/gom-repository.h
INST_H_FILES += gom/gom-resource-group.h
INST_H_FILES += gom/gom-resource.h
INST_H_FILES += gom/gom-autocleanups.h
+INST_H_FILES += gom/gom-sorting.h
NOINST_H_FILES = gom/gom-resource-priv.h
@@ -31,6 +32,7 @@ libgom_1_0_la_SOURCES += gom/gom-filter.c
libgom_1_0_la_SOURCES += gom/gom-repository.c
libgom_1_0_la_SOURCES += gom/gom-resource.c
libgom_1_0_la_SOURCES += gom/gom-resource-group.c
+libgom_1_0_la_SOURCES += gom/gom-sorting.c
libgom_1_0_la_CPPFLAGS =
libgom_1_0_la_CPPFLAGS += '-DG_LOG_DOMAIN="Gom"'
diff --git a/gom/gom-command-builder.c b/gom/gom-command-builder.c
index 393aa13..4534246 100644
--- a/gom/gom-command-builder.c
+++ b/gom/gom-command-builder.c
@@ -24,6 +24,7 @@
#include "gom-filter.h"
#include "gom-resource.h"
#include "gom-resource-priv.h"
+#include "gom-sorting.h"
G_DEFINE_TYPE(GomCommandBuilder, gom_command_builder, G_TYPE_OBJECT)
@@ -31,6 +32,7 @@ struct _GomCommandBuilderPrivate
{
GomAdapter *adapter;
GomFilter *filter;
+ GomSorting *sorting;
GType resource_type;
guint limit;
guint offset;
@@ -43,6 +45,7 @@ enum
PROP_0,
PROP_ADAPTER,
PROP_FILTER,
+ PROP_SORTING,
PROP_LIMIT,
PROP_M2M_TABLE,
PROP_M2M_TYPE,
@@ -350,6 +353,32 @@ add_where (GString *str,
}
static void
+add_order_by (GString *str,
+ GType m2m_type,
+ const gchar *m2m_table,
+ GomSorting *sorting)
+{
+ GHashTable *table_map = NULL;
+ gchar *sql;
+
+ if (sorting) {
+ if (m2m_type) {
+ table_map = g_hash_table_new_full(g_str_hash, g_str_equal,
+ g_free, g_free);
+ build_map(table_map, m2m_type, m2m_table);
+ }
+
+ sql = gom_sorting_get_sql(sorting, table_map);
+ g_string_append_printf(str, " ORDER BY %s ", sql);
+ g_free(sql);
+
+ if (table_map) {
+ g_hash_table_destroy(table_map);
+ }
+ }
+}
+
+static void
add_limit (GString *str,
guint limit)
{
@@ -511,6 +540,7 @@ gom_command_builder_build_select (GomCommandBuilder *builder)
add_joins(str, klass);
add_m2m(str, klass, priv->m2m_table, priv->m2m_type);
add_where(str, priv->m2m_type, priv->m2m_table, priv->filter);
+ add_order_by(str, priv->m2m_type, priv->m2m_table, priv->sorting);
add_limit(str, priv->limit);
add_offset(str, priv->offset);
@@ -869,6 +899,7 @@ gom_command_builder_finalize (GObject *object)
g_clear_object(&priv->adapter);
g_clear_object(&priv->filter);
+ g_clear_object(&priv->sorting);
g_free(priv->m2m_table);
G_OBJECT_CLASS(gom_command_builder_parent_class)->finalize(object);
@@ -898,6 +929,9 @@ gom_command_builder_get_property (GObject *object,
case PROP_FILTER:
g_value_set_object(value, builder->priv->filter);
break;
+ case PROP_SORTING:
+ g_value_set_object(value, builder->priv->sorting);
+ break;
case PROP_LIMIT:
g_value_set_uint(value, builder->priv->limit);
break;
@@ -945,6 +979,11 @@ gom_command_builder_set_property (GObject *object,
builder->priv->filter = g_value_dup_object(value);
g_object_notify_by_pspec(object, pspec);
break;
+ case PROP_SORTING:
+ g_clear_object(&builder->priv->sorting);
+ builder->priv->sorting = g_value_dup_object(value);
+ g_object_notify_by_pspec(object, pspec);
+ break;
case PROP_LIMIT:
builder->priv->limit = g_value_get_uint(value);
g_object_notify_by_pspec(object, pspec);
@@ -1005,6 +1044,15 @@ gom_command_builder_class_init (GomCommandBuilderClass *klass)
g_object_class_install_property(object_class, PROP_FILTER,
gParamSpecs[PROP_FILTER]);
+ gParamSpecs[PROP_SORTING] =
+ g_param_spec_object("sorting",
+ _("Sorting"),
+ _("The sorting for the command."),
+ GOM_TYPE_SORTING,
+ G_PARAM_READWRITE);
+ g_object_class_install_property(object_class, PROP_SORTING,
+ gParamSpecs[PROP_SORTING]);
+
gParamSpecs[PROP_LIMIT] =
g_param_spec_uint("limit",
_("Limit"),
diff --git a/gom/gom-repository.c b/gom/gom-repository.c
index 28e723c..ae396ae 100644
--- a/gom/gom-repository.c
+++ b/gom/gom-repository.c
@@ -468,6 +468,7 @@ gom_repository_find_cb (GomAdapter *adapter,
GomCommand *command;
GomCursor *cursor;
GomFilter *filter;
+ GomSorting *sorting;
GError *error = NULL;
GType resource_type;
GAsyncQueue *queue;
@@ -485,12 +486,16 @@ gom_repository_find_cb (GomAdapter *adapter,
filter = g_object_get_data(G_OBJECT(simple), "filter");
g_assert(!filter || GOM_IS_FILTER(filter));
+ sorting = g_object_get_data(G_OBJECT(simple), "sorting");
+ g_assert(!sorting || GOM_IS_SORTING(sorting));
+
queue = g_object_get_data(G_OBJECT(simple), "queue");
builder = g_object_new(GOM_TYPE_COMMAND_BUILDER,
"adapter", adapter,
"resource-type", resource_type,
"filter", filter,
+ "sorting", sorting,
NULL);
command = gom_command_builder_build_count(builder);
@@ -511,6 +516,7 @@ gom_repository_find_cb (GomAdapter *adapter,
ret = g_object_new(GOM_TYPE_RESOURCE_GROUP,
"count", count,
"filter", filter,
+ "sorting", sorting,
"repository", repository,
"resource-type", resource_type,
NULL);
@@ -546,6 +552,32 @@ gom_repository_find_sync (GomRepository *repository,
GomFilter *filter,
GError **error)
{
+ return gom_repository_find_sorted_sync(repository, resource_type, filter,
+ NULL, error);
+}
+
+/**
+ * gom_repository_find_sorted_sync:
+ * @repository: (in): A #GomRepository.
+ * @resource_type: (in): The #GType of the resources to query.
+ * @filter: (in) (allow-none): An optional filter for the query.
+ * @sorting: (in) (allow-none): An optional #GomSorting to order the query
+ * results.
+ * @error: (out): A location for a #GError, or %NULL.
+ *
+ * Synchronously queries the #GomRepository for objects matching the
+ * requested query. This must only be run from a callback provided to
+ * gom_adapter_queue_read().
+ *
+ * Returns: (transfer full): A #GomResourceGroup or %NULL.
+ */
+GomResourceGroup *
+gom_repository_find_sorted_sync (GomRepository *repository,
+ GType resource_type,
+ GomFilter *filter,
+ GomSorting *sorting,
+ GError **error)
+{
GomRepositoryPrivate *priv;
GSimpleAsyncResult *simple;
GomResourceGroup *ret;
@@ -555,6 +587,7 @@ gom_repository_find_sync (GomRepository *repository,
g_return_val_if_fail(g_type_is_a(resource_type, GOM_TYPE_RESOURCE), NULL);
g_return_val_if_fail(resource_type != GOM_TYPE_RESOURCE, NULL);
g_return_val_if_fail(!filter || GOM_IS_FILTER(filter), NULL);
+ g_return_val_if_fail(!sorting || GOM_IS_SORTING(sorting), NULL);
priv = repository->priv;
@@ -567,6 +600,9 @@ gom_repository_find_sync (GomRepository *repository,
g_object_set_data_full(G_OBJECT(simple), "filter",
filter ? g_object_ref(filter) : NULL,
filter ? g_object_unref : NULL);
+ g_object_set_data_full(G_OBJECT(simple), "sorting",
+ sorting ? g_object_ref(sorting) : NULL,
+ sorting ? g_object_unref : NULL);
g_object_set_data(G_OBJECT(simple), "queue", queue);
gom_adapter_queue_read(priv->adapter, gom_repository_find_cb, simple);
diff --git a/gom/gom-repository.h b/gom/gom-repository.h
index 3f7e55b..113cccd 100644
--- a/gom/gom-repository.h
+++ b/gom/gom-repository.h
@@ -24,6 +24,7 @@
#include "gom-adapter.h"
#include "gom-filter.h"
#include "gom-resource-group.h"
+#include "gom-sorting.h"
G_BEGIN_DECLS
@@ -106,6 +107,11 @@ GomResourceGroup *gom_repository_find_sync (GomRepository *reposi
GType resource_type,
GomFilter *filter,
GError **error);
+GomResourceGroup *gom_repository_find_sorted_sync (GomRepository *repository,
+ GType resource_type,
+ GomFilter *filter,
+ GomSorting *sorting,
+ GError **error);
void gom_repository_find_async (GomRepository *repository,
GType resource_type,
GomFilter *filter,
diff --git a/gom/gom-resource-group.c b/gom/gom-resource-group.c
index f6bbfa3..ad83b23 100644
--- a/gom/gom-resource-group.c
+++ b/gom/gom-resource-group.c
@@ -27,6 +27,7 @@
#include "gom-resource.h"
#include "gom-resource-priv.h"
#include "gom-resource-group.h"
+#include "gom-sorting.h"
G_DEFINE_TYPE(GomResourceGroup, gom_resource_group, G_TYPE_OBJECT)
@@ -37,6 +38,7 @@ struct _GomResourceGroupPrivate
/* Read group */
guint count;
GomFilter *filter;
+ GomSorting *sorting;
GType resource_type;
GHashTable *items;
gchar *m2m_table;
@@ -52,6 +54,7 @@ enum
PROP_0,
PROP_COUNT,
PROP_FILTER,
+ PROP_SORTING,
PROP_M2M_TABLE,
PROP_M2M_TYPE,
PROP_RESOURCE_TYPE,
@@ -438,6 +441,26 @@ gom_resource_group_set_filter (GomResourceGroup *group,
}
}
+static GomSorting *
+gom_resource_group_get_sorting (GomResourceGroup *group)
+{
+ g_return_val_if_fail(GOM_IS_RESOURCE_GROUP(group), NULL);
+ return group->priv->sorting;
+}
+
+static void
+gom_resource_group_set_sorting (GomResourceGroup *group,
+ GomSorting *sorting)
+{
+ g_return_if_fail(GOM_IS_RESOURCE_GROUP(group));
+ g_return_if_fail(!sorting || GOM_IS_SORTING(sorting));
+
+ if (sorting) {
+ group->priv->sorting = g_object_ref(sorting);
+ g_object_notify_by_pspec(G_OBJECT(group), gParamSpecs[PROP_SORTING]);
+ }
+}
+
guint
gom_resource_group_get_count (GomResourceGroup *group)
{
@@ -640,6 +663,7 @@ gom_resource_group_fetch_cb (GomAdapter *adapter,
GomCommand *command = NULL;
GomCursor *cursor = NULL;
GomFilter *filter = NULL;
+ GomSorting *sorting = NULL;
GError *error = NULL;
GType resource_type;
gchar *m2m_table = NULL;
@@ -655,6 +679,7 @@ gom_resource_group_fetch_cb (GomAdapter *adapter,
group = GOM_RESOURCE_GROUP(g_async_result_get_source_object(G_ASYNC_RESULT(simple)));
g_object_get(group,
"filter", &filter,
+ "sorting", &sorting,
"m2m-table", &m2m_table,
"m2m-type", &m2m_type,
"repository", &repository,
@@ -662,6 +687,7 @@ gom_resource_group_fetch_cb (GomAdapter *adapter,
NULL);
g_assert(GOM_IS_ADAPTER(adapter));
g_assert(!filter || GOM_IS_FILTER(filter));
+ g_assert(!sorting || GOM_IS_SORTING(sorting));
g_assert(GOM_IS_REPOSITORY(repository));
g_assert(g_type_is_a(resource_type, GOM_TYPE_RESOURCE));
@@ -672,6 +698,7 @@ gom_resource_group_fetch_cb (GomAdapter *adapter,
builder = g_object_new(GOM_TYPE_COMMAND_BUILDER,
"adapter", gom_repository_get_adapter(repository),
"filter", filter,
+ "sorting", sorting,
"limit", limit,
"m2m-table", m2m_table,
"m2m-type", m2m_type,
@@ -717,6 +744,7 @@ out:
g_clear_object(&command);
g_clear_object(&cursor);
g_clear_object(&filter);
+ g_clear_object(&sorting);
g_clear_object(&repository);
if (!queue)
g_simple_async_result_complete_in_idle(simple);
@@ -865,6 +893,7 @@ gom_resource_group_finalize (GObject *object)
g_clear_object(&priv->repository);
g_clear_object(&priv->filter);
+ g_clear_object(&priv->sorting);
g_clear_pointer(&priv->items, g_hash_table_unref);
g_clear_pointer(&priv->to_write, g_ptr_array_unref);
@@ -895,6 +924,9 @@ gom_resource_group_get_property (GObject *object,
case PROP_FILTER:
g_value_set_object(value, gom_resource_group_get_filter(group));
break;
+ case PROP_SORTING:
+ g_value_set_object(value, gom_resource_group_get_sorting(group));
+ break;
case PROP_M2M_TABLE:
g_value_set_string(value, gom_resource_group_get_m2m_table(group));
break;
@@ -939,6 +971,9 @@ gom_resource_group_set_property (GObject *object,
case PROP_FILTER:
gom_resource_group_set_filter(group, g_value_get_object(value));
break;
+ case PROP_SORTING:
+ gom_resource_group_set_sorting(group, g_value_get_object(value));
+ break;
case PROP_M2M_TABLE:
gom_resource_group_set_m2m_table(group, g_value_get_string(value));
break;
@@ -996,6 +1031,15 @@ gom_resource_group_class_init (GomResourceGroupClass *klass)
g_object_class_install_property(object_class, PROP_FILTER,
gParamSpecs[PROP_FILTER]);
+ gParamSpecs[PROP_SORTING] =
+ g_param_spec_object("sorting",
+ _("Sorting"),
+ _("The query sorting."),
+ GOM_TYPE_SORTING,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+ g_object_class_install_property(object_class, PROP_SORTING,
+ gParamSpecs[PROP_SORTING]);
+
gParamSpecs[PROP_M2M_TABLE] =
g_param_spec_string("m2m-table",
_("Many-to-Many Table"),
diff --git a/gom/gom-sorting.c b/gom/gom-sorting.c
new file mode 100644
index 0000000..915a0d5
--- /dev/null
+++ b/gom/gom-sorting.c
@@ -0,0 +1,226 @@
+/* gom-sorting.c
+ *
+ * Copyright (C) 2015 Mathieu Bridon <bochecha daitauha fr>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdarg.h>
+
+#include <glib/gi18n.h>
+
+#include "gom-sorting.h"
+#include "gom-resource.h"
+
+G_DEFINE_TYPE(GomSorting, gom_sorting, G_TYPE_INITIALLY_UNOWNED)
+
+struct _GomSortingPrivate
+{
+ GQueue *order_by_terms;
+};
+
+typedef struct GomOrderByTerm
+{
+ GType resource_type;
+ gchar *property_name;
+ GomSortingMode mode;
+} GomOrderByTerm;
+
+static void
+gom_sorting_finalize (GObject *object)
+{
+ GomSortingPrivate *priv = GOM_SORTING(object)->priv;
+
+ if (priv->order_by_terms != NULL)
+ g_queue_free_full(priv->order_by_terms, g_free);
+
+ G_OBJECT_CLASS(gom_sorting_parent_class)->finalize(object);
+}
+
+static void
+gom_sorting_class_init (GomSortingClass *klass)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = gom_sorting_finalize;
+
+ g_type_class_add_private(object_class, sizeof(GomSortingPrivate));
+}
+
+static void
+gom_sorting_init (GomSorting *sorting)
+{
+ sorting->priv = G_TYPE_INSTANCE_GET_PRIVATE(sorting, GOM_TYPE_SORTING,
+ GomSortingPrivate);
+}
+
+GType
+gom_sorting_mode_get_type (void)
+{
+ static GType g_type = 0;
+ static gsize initialized = FALSE;
+ static const GEnumValue values[] = {
+ { GOM_SORTING_ASCENDING, "GOM_SORTING_ASCENDING", "" },
+ { GOM_SORTING_DESCENDING, "GOM_SORTING_DESCENDING", "DESC" },
+ { 0 }
+ };
+
+ if (g_once_init_enter(&initialized)) {
+ g_type = g_enum_register_static("GomSortingMode", values);
+ g_once_init_leave(&initialized, TRUE);
+ }
+
+ return g_type;
+}
+
+static gchar *
+get_table (GType type,
+ GHashTable *table_map)
+{
+ GomResourceClass *klass;
+ gchar *table;
+ gchar *key;
+
+ g_return_val_if_fail(g_type_is_a(type, GOM_TYPE_RESOURCE), NULL);
+
+ klass = g_type_class_ref(type);
+ key = g_strdup_printf("%s.%s", g_type_name(type), klass->table);
+ if (table_map && (table = g_hash_table_lookup(table_map, key))) {
+ table = g_strdup(table);
+ } else {
+ table = g_strdup(klass->table);
+ }
+ g_free(key);
+ g_type_class_unref(klass);
+
+ return table;
+}
+
+/**
+ * gom_sorting_new: (constructor)
+ * @first_resource_type: A subclass of #GomResource.
+ * @first_property_name: A pointer to a const gchar.
+ * @first_sorting_mode: A GomSortingMode.
+ * @...: Additional triples of resource_type/property_name/sorting_mode,
+ * followed by %NULL.
+ *
+ * Creates a new #GomSorting to instance.
+ *
+ * This is useful to sort query results, as #GomSorting knows how to return
+ * the proper "ORDER BY" SQL statements.
+ *
+ * Example:
+ *
+ * GomSorting *sorting = gom_sorting_new(EPISODE_TYPE_RESOURCE,
+ * "season-number",
+ * GOM_SORTING_DESCENDING,
+ * EPISODE_TYPE_RESOURCE,
+ * "episode-number",
+ * GOM_SORTING_ASCENDING);
+ *
+ * The above example maps to the following SQL statement:
+ *
+ * ORDER BY 'episodes'.'season-number' DESC, 'episodes'.'episode-number'
+ *
+ * Returns: (transfer full): A #GomSorting.
+ */
+GomSorting *
+gom_sorting_new (GType first_resource_type,
+ const gchar *first_property_name,
+ GomSortingMode first_sorting_mode,
+ ...)
+{
+ GomSorting *sorting;
+ va_list args;
+ GType resource_type;
+ const gchar *property_name;
+ GomSortingMode sorting_mode;
+
+ g_return_val_if_fail(g_type_is_a(first_resource_type, GOM_TYPE_RESOURCE),
+ NULL);
+
+ sorting = g_object_new(GOM_TYPE_SORTING, NULL);
+ sorting->priv->order_by_terms = g_queue_new();
+
+ resource_type = first_resource_type;
+ property_name = first_property_name;
+ sorting_mode = first_sorting_mode;
+
+ va_start(args, first_sorting_mode);
+
+ while TRUE {
+ GomOrderByTerm *o = g_new(GomOrderByTerm, 1);
+
+ g_return_val_if_fail(g_type_is_a(resource_type, GOM_TYPE_RESOURCE),
+ NULL);
+ g_return_val_if_fail(property_name != NULL, NULL);
+ g_return_val_if_fail(sorting_mode, NULL);
+
+ o->resource_type = resource_type;
+ o->property_name = strdup(property_name);
+ o->mode = sorting_mode;
+ g_queue_push_tail(sorting->priv->order_by_terms, o);
+
+ resource_type = va_arg(args, GType);
+
+ if (!resource_type)
+ break;
+
+ property_name = va_arg(args, const gchar*);
+ sorting_mode = va_arg(args, GomSortingMode);
+ }
+
+ va_end(args);
+
+ return sorting;
+}
+
+/**
+ * gom_sorting_get_sql:
+ * @sorting: (in): A #GomSorting.
+ * @table_map: (in): A #GHashTable.
+ *
+ * Returns: (transfer full): A string containing the SQL query corresponding
+ * to this @sorting.
+ */
+gchar *
+gom_sorting_get_sql (GomSorting *sorting,
+ GHashTable *table_map)
+{
+ GomSortingPrivate *priv;
+ gchar *table;
+ gchar **sqls;
+ gint i, len;
+ gchar *ret;
+
+ g_return_val_if_fail(GOM_IS_SORTING(sorting), NULL);
+
+ priv = sorting->priv;
+ len = g_queue_get_length(priv->order_by_terms);
+ sqls = g_new(gchar *, len + 1);
+
+ for (i = 0; i < len; i++) {
+ GomOrderByTerm *o = g_queue_peek_nth(priv->order_by_terms, i);
+ table = get_table(o->resource_type, table_map);
+
+ sqls[i] = g_strdup_printf("'%s'.'%s'%s", table, o->property_name, o->mode == GOM_SORTING_DESCENDING ?
" DESC" : "");
+ }
+ sqls[i] = NULL;
+
+ ret = g_strjoinv(", ", sqls);
+ g_strfreev(sqls);
+
+ return ret;
+}
diff --git a/gom/gom-sorting.h b/gom/gom-sorting.h
new file mode 100644
index 0000000..87fb1db
--- /dev/null
+++ b/gom/gom-sorting.h
@@ -0,0 +1,68 @@
+/* gom-sorting.h
+ *
+ * Copyright (C) 2015 Mathieu Bridon <bochecha daitauha fr>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GOM_SORTING_H
+#define GOM_SORTING_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GOM_TYPE_SORTING (gom_sorting_get_type())
+#define GOM_TYPE_SORTING_MODE (gom_sorting_mode_get_type())
+#define GOM_SORTING(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOM_TYPE_SORTING, GomSorting))
+#define GOM_SORTING_CONST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GOM_TYPE_SORTING, GomSorting const))
+#define GOM_SORTING_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GOM_TYPE_SORTING, GomSortingClass))
+#define GOM_IS_SORTING(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GOM_TYPE_SORTING))
+#define GOM_IS_SORTING_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GOM_TYPE_SORTING))
+#define GOM_SORTING_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GOM_TYPE_SORTING, GomSortingClass))
+
+typedef struct _GomSorting GomSorting;
+typedef struct _GomSortingClass GomSortingClass;
+typedef struct _GomSortingPrivate GomSortingPrivate;
+typedef enum _GomSortingMode GomSortingMode;
+
+struct _GomSorting
+{
+ GObject parent;
+ GomSortingPrivate *priv;
+};
+
+struct _GomSortingClass
+{
+ GObjectClass parent_class;
+};
+
+enum _GomSortingMode
+{
+ GOM_SORTING_ASCENDING = 1,
+ GOM_SORTING_DESCENDING
+};
+
+GType gom_sorting_get_type (void) G_GNUC_CONST;
+GType gom_sorting_mode_get_type (void) G_GNUC_CONST;
+gchar *gom_sorting_get_sql (GomSorting *sorting,
+ GHashTable *table_map);
+GomSorting *gom_sorting_new (GType first_resource_type,
+ const gchar *first_property_name,
+ GomSortingMode first_sorting_mode,
+ ...);
+
+G_END_DECLS
+
+#endif /* GOM_SORTING_H */
diff --git a/gom/gom.h b/gom/gom.h
index 9b67da7..c8cb85b 100644
--- a/gom/gom.h
+++ b/gom/gom.h
@@ -34,6 +34,7 @@ G_BEGIN_DECLS
#include "gom-repository.h"
#include "gom-resource-group.h"
#include "gom-resource.h"
+#include "gom-sorting.h"
#include "gom-autocleanups.h"
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 28b6c4c..f0d25c7 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -9,6 +9,7 @@ noinst_PROGRAMS += test-gom-migration
noinst_PROGRAMS += test-gom-constraints
noinst_PROGRAMS += test-gom-insert
noinst_PROGRAMS += test-gom-update
+noinst_PROGRAMS += test-gom-sorting
TEST_PROGS += test-gom-adapter
TEST_PROGS += test-gom-repository
@@ -20,6 +21,7 @@ TEST_PROGS += test-gom-migration
TEST_PROGS += test-gom-constraints
TEST_PROGS += test-gom-insert
TEST_PROGS += test-gom-update
+TEST_PROGS += test-gom-sorting
test_gom_adapter_SOURCES = tests/test-gom-adapter.c
test_gom_adapter_CPPFLAGS = $(GIO_CFLAGS) $(GOBJECT_CFLAGS) $(WARN_CFLAGS)
@@ -61,4 +63,8 @@ test_gom_update_SOURCES = tests/test-gom-update.c
test_gom_update_CPPFLAGS = $(GIO_CFLAGS) $(GOBJECT_CFLAGS) $(WARN_CFLAGS)
test_gom_update_LDADD = $(GIO_LIBS) $(GOBJECT_LIBS) $(top_builddir)/libgom-1.0.la
+test_gom_sorting_SOURCES = tests/test-gom-sorting.c
+test_gom_sorting_CPPFLAGS = $(GIO_CFLAGS) $(GOBJECT_CFLAGS) $(WARN_CFLAGS)
+test_gom_sorting_LDADD = $(GIO_LIBS) $(GOBJECT_LIBS) $(top_builddir)/libgom-1.0.la
+
EXTRA_DIST += tests/grl-bookmarks.db tests/gnome.png
diff --git a/tests/test-gom-sorting.c b/tests/test-gom-sorting.c
new file mode 100644
index 0000000..2b8eb19
--- /dev/null
+++ b/tests/test-gom-sorting.c
@@ -0,0 +1,453 @@
+#include <gom/gom.h>
+
+
+static GMainLoop *gMainLoop;
+
+
+#define EPISODE_TYPE_RESOURCE (episode_resource_get_type())
+#define EPISODE_TYPE_TYPE (episode_type_get_type())
+#define EPISODE_RESOURCE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EPISODE_TYPE_RESOURCE,
EpisodeResource))
+#define EPISODE_RESOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EPISODE_TYPE_RESOURCE,
EpisodeResourceClass))
+#define EPISODE_IS_RESOURCE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EPISODE_TYPE_RESOURCE))
+#define EPISODE_IS_RESOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EPISODE_TYPE_RESOURCE))
+#define EPISODE_RESOURCE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EPISODE_TYPE_RESOURCE,
EpisodeResourceClass))
+
+typedef struct {
+ gint64 db_id;
+ gchar *series_id;
+ gchar *imdb_id;
+ guint8 season_number;
+ guint8 episode_number;
+ gchar *episode_name;
+} EpisodeResourcePrivate;
+
+typedef struct
+{
+ GomResource parent;
+ EpisodeResourcePrivate *priv;
+} EpisodeResource;
+
+typedef struct
+{
+ GomResourceClass parent_class;
+} EpisodeResourceClass;
+
+GType episode_resource_get_type(void);
+
+G_DEFINE_TYPE(EpisodeResource, episode_resource, GOM_TYPE_RESOURCE)
+
+enum {
+ PROP_0,
+ PROP_DB_ID,
+ PROP_SERIES_ID,
+ PROP_IMDB_ID,
+ PROP_SEASON_NUMBER,
+ PROP_EPISODE_NUMBER,
+ PROP_EPISODE_NAME,
+ LAST_PROP
+};
+
+static GParamSpec *specs[LAST_PROP];
+
+static struct {
+ const gchar *series_id;
+ const gchar *imdb_id;
+ guint8 season_number;
+ guint8 episode_number;
+ const gchar *episode_name;
+} values[] = {
+ { "84947", "tt2483070", 4, 1, "New York Sour" },
+ { "84947", "tt2778300", 4, 2, "Resignation" },
+ { "84947", "tt3216480", 5, 1, "Golden Days for Boys and Girls" },
+ { "84947", "tt3767076", 5, 2, "The Good Listener" }
+};
+
+static void
+episode_resource_finalize (GObject *object)
+{
+ EpisodeResourcePrivate *priv = EPISODE_RESOURCE(object)->priv;
+
+ g_clear_pointer(&priv->series_id, g_free);
+ g_clear_pointer(&priv->imdb_id, g_free);
+ g_clear_pointer(&priv->episode_name, g_free);
+
+ G_OBJECT_CLASS(episode_resource_parent_class)->finalize(object);
+}
+
+static void
+episode_resource_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ EpisodeResource *resource = EPISODE_RESOURCE(object);
+
+ switch (prop_id) {
+ case PROP_DB_ID:
+ g_value_set_int64(value, resource->priv->db_id);
+ break;
+ case PROP_SERIES_ID:
+ g_value_set_string(value, resource->priv->series_id);
+ break;
+ case PROP_IMDB_ID:
+ g_value_set_string(value, resource->priv->imdb_id);
+ break;
+ case PROP_SEASON_NUMBER:
+ g_value_set_uchar(value, resource->priv->season_number);
+ break;
+ case PROP_EPISODE_NUMBER:
+ g_value_set_uchar(value, resource->priv->episode_number);
+ break;
+ case PROP_EPISODE_NAME:
+ g_value_set_string(value, resource->priv->episode_name);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+episode_resource_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ EpisodeResource *resource = EPISODE_RESOURCE(object);
+
+ switch (prop_id) {
+ case PROP_DB_ID:
+ resource->priv->db_id = g_value_get_int64(value);
+ break;
+ case PROP_SERIES_ID:
+ g_clear_pointer(&resource->priv->series_id, g_free);
+ resource->priv->series_id = g_value_dup_string(value);
+ break;
+ case PROP_IMDB_ID:
+ g_clear_pointer(&resource->priv->imdb_id, g_free);
+ resource->priv->imdb_id = g_value_dup_string(value);
+ break;
+ case PROP_SEASON_NUMBER:
+ resource->priv->season_number = g_value_get_uchar(value);
+ break;
+ case PROP_EPISODE_NUMBER:
+ resource->priv->episode_number = g_value_get_uchar(value);
+ break;
+ case PROP_EPISODE_NAME:
+ g_clear_pointer(&resource->priv->episode_name, g_free);
+ resource->priv->episode_name = g_value_dup_string(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+episode_resource_class_init (EpisodeResourceClass *klass)
+{
+ GObjectClass *object_class;
+ GomResourceClass *resource_class;
+
+ object_class = G_OBJECT_CLASS(klass);
+ object_class->finalize = episode_resource_finalize;
+ object_class->get_property = episode_resource_get_property;
+ object_class->set_property = episode_resource_set_property;
+ g_type_class_add_private(object_class, sizeof(EpisodeResourcePrivate));
+
+ resource_class = GOM_RESOURCE_CLASS(klass);
+ gom_resource_class_set_table(resource_class, "episodes");
+
+ specs[PROP_DB_ID] = g_param_spec_int64("id", NULL, NULL, 0, G_MAXINT64,
+ 0, G_PARAM_READWRITE);
+ g_object_class_install_property(object_class, PROP_DB_ID,
+ specs[PROP_DB_ID]);
+ gom_resource_class_set_primary_key(resource_class, "id");
+
+ specs[PROP_SERIES_ID] = g_param_spec_string("series-id", NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property(object_class, PROP_SERIES_ID,
+ specs[PROP_SERIES_ID]);
+
+ specs[PROP_IMDB_ID] = g_param_spec_string("imdb-id", NULL, NULL, NULL,
+ G_PARAM_READWRITE);
+ g_object_class_install_property(object_class, PROP_IMDB_ID,
+ specs[PROP_IMDB_ID]);
+
+ specs[PROP_SEASON_NUMBER] = g_param_spec_uchar("season-number", NULL, NULL,
+ 0, G_MAXUINT8, 0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property(object_class, PROP_SEASON_NUMBER,
+ specs[PROP_SEASON_NUMBER]);
+
+ specs[PROP_EPISODE_NUMBER] = g_param_spec_uchar("episode-number", NULL,
+ NULL, 0, G_MAXUINT8, 0,
+ G_PARAM_READWRITE);
+ g_object_class_install_property(object_class, PROP_EPISODE_NUMBER,
+ specs[PROP_EPISODE_NUMBER]);
+
+ specs[PROP_EPISODE_NAME] = g_param_spec_string("episode-name", NULL, NULL,
+ NULL, G_PARAM_READWRITE);
+ g_object_class_install_property(object_class, PROP_EPISODE_NAME,
+ specs[PROP_EPISODE_NAME]);
+
+}
+
+static void
+episode_resource_init (EpisodeResource *resource)
+{
+ resource->priv = G_TYPE_INSTANCE_GET_PRIVATE(resource,
+ EPISODE_TYPE_RESOURCE,
+ EpisodeResourcePrivate);
+}
+
+static void
+create_memory_db (GomAdapter **adapter,
+ GomRepository **repository)
+{
+ gboolean ret;
+ GError *error = NULL;
+ GList *object_types;
+ EpisodeResource *eres;
+ guint i;
+
+ *adapter = gom_adapter_new();
+ ret = gom_adapter_open_sync(*adapter, ":memory:", &error);
+ g_assert_no_error(error);
+ g_assert(ret);
+
+ *repository = gom_repository_new(*adapter);
+
+ object_types = g_list_prepend(NULL,
+ GINT_TO_POINTER(EPISODE_TYPE_RESOURCE));
+ ret = gom_repository_automatic_migrate_sync(*repository, 1, object_types,
+ &error);
+ g_assert_no_error(error);
+ g_assert(ret);
+
+ for (i = 0; i < G_N_ELEMENTS(values); i++) {
+ eres = g_object_new(EPISODE_TYPE_RESOURCE, "repository", *repository,
+ "series-id", values[i].series_id,
+ "imdb-id", values[i].imdb_id,
+ "season-number", values[i].season_number,
+ "episode-number", values[i].episode_number,
+ "episode-name", values[i].episode_name,
+ NULL);
+ ret = gom_resource_save_sync(GOM_RESOURCE(eres), &error);
+ g_assert(ret);
+ g_assert_no_error(error);
+ g_object_unref(eres);
+ }
+}
+
+static void
+free_memory_db (GomAdapter *adapter,
+ GomRepository *repository)
+{
+ gboolean ret;
+ GError *error = NULL;
+
+ ret = gom_adapter_close_sync(adapter, &error);
+ g_assert_no_error(error);
+ g_assert(ret);
+
+ g_object_unref(repository);
+ g_object_unref(adapter);
+}
+
+
+static void
+find_order_by_asc (void)
+{
+ GomAdapter *adapter;
+ GomRepository *repository;
+ GomFilter *filter;
+ GomSorting *sorting;
+ GValue value = { 0, };
+ GError *error = NULL;
+ GomResourceGroup *group;
+ EpisodeResource *eres;
+ guint count;
+ guint8 i;
+
+ create_memory_db(&adapter, &repository);
+
+ /* Filter on season number */
+ g_value_init(&value, G_TYPE_INT64);
+ g_value_set_int64(&value, 4);
+ filter = gom_filter_new_eq(EPISODE_TYPE_RESOURCE, "season-number", &value);
+ g_value_unset(&value);
+
+ /* Order by episode */
+ sorting = gom_sorting_new(EPISODE_TYPE_RESOURCE, "episode-number",
+ GOM_SORTING_ASCENDING, NULL);
+
+ group = gom_repository_find_sorted_sync(repository, EPISODE_TYPE_RESOURCE,
+ filter, sorting, &error);
+ g_assert_no_error(error);
+ g_object_unref(filter);
+ g_object_unref(sorting);
+
+ count = gom_resource_group_get_count(group);
+ g_assert_cmpuint(count, ==, 2);
+
+ gom_resource_group_fetch_sync(group, 0, count, &error);
+ g_assert_no_error(error);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 0));
+ g_assert(eres);
+ g_object_get(eres, "episode-number", &i, NULL);
+ g_assert_cmpuint(i, ==, 1);
+ g_object_unref(eres);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 1));
+ g_assert(eres);
+ g_object_get(eres, "episode-number", &i, NULL);
+ g_assert_cmpuint(i, ==, 2);
+ g_object_unref(eres);
+
+ free_memory_db(adapter, repository);
+}
+
+static void
+find_order_by_desc (void)
+{
+ GomAdapter *adapter;
+ GomRepository *repository;
+ GomFilter *filter;
+ GomSorting *sorting;
+ GValue value = { 0, };
+ GError *error = NULL;
+ GomResourceGroup *group;
+ EpisodeResource *eres;
+ guint count;
+ guint8 i;
+
+ create_memory_db(&adapter, &repository);
+
+ /* Filter on season number */
+ g_value_init(&value, G_TYPE_INT64);
+ g_value_set_int64(&value, 4);
+ filter = gom_filter_new_eq(EPISODE_TYPE_RESOURCE, "season-number", &value);
+ g_value_unset(&value);
+
+ /* Order by episode */
+ sorting = gom_sorting_new(EPISODE_TYPE_RESOURCE, "episode-number",
+ GOM_SORTING_DESCENDING, NULL);
+
+ group = gom_repository_find_sorted_sync(repository, EPISODE_TYPE_RESOURCE,
+ filter, sorting, &error);
+ g_assert_no_error(error);
+ g_object_unref(filter);
+ g_object_unref(sorting);
+
+ count = gom_resource_group_get_count(group);
+ g_assert_cmpuint(count, ==, 2);
+
+ gom_resource_group_fetch_sync(group, 0, count, &error);
+ g_assert_no_error(error);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 0));
+ g_assert(eres);
+ g_object_get(eres, "episode-number", &i, NULL);
+ g_assert_cmpuint(i, ==, 2);
+ g_object_unref(eres);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 1));
+ g_assert(eres);
+ g_object_get(eres, "episode-number", &i, NULL);
+ g_assert_cmpuint(i, ==, 1);
+ g_object_unref(eres);
+
+ free_memory_db(adapter, repository);
+}
+
+static void
+find_order_by_complex (void)
+{
+ GomAdapter *adapter;
+ GomRepository *repository;
+ GomFilter *filter;
+ GomSorting *sorting;
+ GomResourceGroup *group;
+ GValue value = { 0, };
+ GError *error = NULL;
+ EpisodeResource *eres;
+ guint count;
+ gchar *id;
+ guint8 season, episode;
+
+ create_memory_db(&adapter, &repository);
+
+ /* Select only the episode for a single show */
+ g_value_init(&value, G_TYPE_STRING);
+ g_value_set_string(&value, "84947");
+ filter = gom_filter_new_eq(EPISODE_TYPE_RESOURCE, "series-id", &value);
+ g_value_unset(&value);
+
+ /* Order by season, then by episode */
+ sorting = gom_sorting_new(EPISODE_TYPE_RESOURCE, "season-number",
+ GOM_SORTING_DESCENDING,
+ EPISODE_TYPE_RESOURCE, "episode-number",
+ GOM_SORTING_ASCENDING,
+ NULL);
+
+ group = gom_repository_find_sorted_sync(repository, EPISODE_TYPE_RESOURCE,
+ filter, sorting, &error);
+ g_assert_no_error(error);
+ g_object_unref(sorting);
+
+ count = gom_resource_group_get_count(group);
+ g_assert_cmpuint(count, ==, 4);
+
+ gom_resource_group_fetch_sync(group, 0, count, &error);
+ g_assert_no_error(error);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 0));
+ g_assert(eres);
+ g_object_get(eres, "series-id", &id, "season-number", &season,
+ "episode-number", &episode, NULL);
+ g_assert_cmpstr(id, ==, "84947");
+ g_assert_cmpuint(season, ==, 5);
+ g_assert_cmpuint(episode, ==, 1);
+ g_object_unref(eres);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 1));
+ g_assert(eres);
+ g_object_get(eres, "series-id", &id, "season-number", &season,
+ "episode-number", &episode, NULL);
+ g_assert_cmpstr(id, ==, "84947");
+ g_assert_cmpuint(season, ==, 5);
+ g_assert_cmpuint(episode, ==, 2);
+ g_object_unref(eres);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 2));
+ g_assert(eres);
+ g_object_get(eres, "series-id", &id, "season-number", &season,
+ "episode-number", &episode, NULL);
+ g_assert_cmpstr(id, ==, "84947");
+ g_assert_cmpuint(season, ==, 4);
+ g_assert_cmpuint(episode, ==, 1);
+ g_object_unref(eres);
+
+ eres = EPISODE_RESOURCE(gom_resource_group_get_index(group, 3));
+ g_assert(eres);
+ g_object_get(eres, "series-id", &id, "season-number", &season,
+ "episode-number", &episode, NULL);
+ g_assert_cmpstr(id, ==, "84947");
+ g_assert_cmpuint(season, ==, 4);
+ g_assert_cmpuint(episode, ==, 2);
+ g_object_unref(eres);
+
+ free_memory_db(adapter, repository);
+}
+
+gint
+main (gint argc, gchar *argv[])
+{
+ g_test_init(&argc, &argv, NULL);
+ g_test_add_func("/GomRepository/find-order-by-asc", find_order_by_asc);
+ g_test_add_func("/GomRepository/find-order-by-desc", find_order_by_desc);
+ g_test_add_func("/GomRepository/find-order-by-complex",
+ find_order_by_complex);
+ gMainLoop = g_main_loop_new(NULL, FALSE);
+ return g_test_run();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]