[tracker/wip/carlosg/sparql-parser-ng: 14/56] libtracker-data: Add TrackerSparql



commit f36fc9ed0bd7e2d6894ba817a0268f6a0de1ad06
Author: Carlos Garnacho <carlosg gnome org>
Date:   Fri Feb 23 19:13:57 2018 +0100

    libtracker-data: Add TrackerSparql

 src/libtracker-data/meson.build            |    2 +
 src/libtracker-data/tracker-data-manager.c |    1 +
 src/libtracker-data/tracker-data.h         |    1 +
 src/libtracker-data/tracker-sparql-types.c |  841 ++++
 src/libtracker-data/tracker-sparql-types.h |  335 ++
 src/libtracker-data/tracker-sparql.c       | 5826 ++++++++++++++++++++++++++++
 src/libtracker-data/tracker-sparql.h       |   41 +
 7 files changed, 7047 insertions(+)
---
diff --git a/src/libtracker-data/meson.build b/src/libtracker-data/meson.build
index 2d60879c5..88c2be95b 100644
--- a/src/libtracker-data/meson.build
+++ b/src/libtracker-data/meson.build
@@ -60,6 +60,8 @@ libtracker_data = library('tracker-data',
     'tracker-property.c',
     'tracker-string-builder.c',
     'tracker-sparql-parser.c',
+    'tracker-sparql-types.c',
+    'tracker-sparql.c',
     tracker_common_enum_header,
     tracker_data_enums[0],
     tracker_data_enums[1],
diff --git a/src/libtracker-data/tracker-data-manager.c b/src/libtracker-data/tracker-data-manager.c
index 87c9b486f..c467096c5 100644
--- a/src/libtracker-data/tracker-data-manager.c
+++ b/src/libtracker-data/tracker-data-manager.c
@@ -48,6 +48,7 @@
 #include "tracker-property.h"
 #include "tracker-sparql-query.h"
 #include "tracker-data-query.h"
+#include "tracker-sparql-parser.h"
 
 #define RDF_PROPERTY                    TRACKER_PREFIX_RDF "Property"
 #define RDF_TYPE                        TRACKER_PREFIX_RDF "type"
diff --git a/src/libtracker-data/tracker-data.h b/src/libtracker-data/tracker-data.h
index e25feb3f7..5ed5a7c4b 100644
--- a/src/libtracker-data/tracker-data.h
+++ b/src/libtracker-data/tracker-data.h
@@ -41,6 +41,7 @@
 #include "tracker-ontologies.h"
 #include "tracker-property.h"
 #include "tracker-sparql-query.h"
+#include "tracker-sparql.h"
 
 #undef __LIBTRACKER_DATA_INSIDE__
 
diff --git a/src/libtracker-data/tracker-sparql-types.c b/src/libtracker-data/tracker-sparql-types.c
new file mode 100644
index 000000000..d48ed886e
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql-types.c
@@ -0,0 +1,841 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, Red Hat Inc.
+ *
+ * This library 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 library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include "tracker-sparql-types.h"
+
+enum {
+       TOKEN_TYPE_NONE,
+       TOKEN_TYPE_LITERAL,
+       TOKEN_TYPE_VARIABLE
+};
+
+/* Helper structs */
+static TrackerDataTable *
+tracker_data_table_new (const gchar *tablename,
+                       const gchar *subject,
+                        gint         idx)
+{
+       TrackerDataTable *table;
+
+       table = g_new0 (TrackerDataTable, 1);
+       table->subject = g_strdup (subject);
+       table->sql_db_tablename = g_strdup (tablename);
+       table->sql_query_tablename = g_strdup_printf ("%s%d", tablename, idx);
+
+       return table;
+}
+
+static void
+tracker_data_table_free (TrackerDataTable *table)
+{
+       g_free (table->subject);
+       g_free (table->sql_db_tablename);
+       g_free (table->sql_query_tablename);
+       g_free (table);
+}
+
+void
+tracker_data_table_set_predicate_variable (TrackerDataTable         *table,
+                                           TrackerPredicateVariable *variable)
+{
+       table->predicate_variable = variable;
+}
+
+TrackerPredicateVariable *
+tracker_predicate_variable_new (void)
+{
+       return g_new0 (TrackerPredicateVariable, 1);
+}
+
+static void
+tracker_predicate_variable_free (TrackerPredicateVariable *pred_var)
+{
+       g_clear_object (&pred_var->domain);
+       g_free (pred_var->subject);
+       g_free (pred_var->object);
+       g_free (pred_var);
+}
+
+void
+tracker_predicate_variable_set_domain (TrackerPredicateVariable *pred_var,
+                                       TrackerClass             *domain)
+{
+       g_set_object (&pred_var->domain, domain);
+}
+
+void
+tracker_predicate_variable_set_triple_details (TrackerPredicateVariable *pred_var,
+                                               const gchar              *subject,
+                                               const gchar              *object,
+                                               gboolean                  return_graph)
+{
+       g_free (pred_var->subject);
+       pred_var->subject = g_strdup (subject);
+       g_free (pred_var->object);
+       pred_var->object = g_strdup (object);
+       pred_var->return_graph = !!return_graph;
+}
+
+static TrackerVariable *
+tracker_variable_new (const gchar *sql_prefix,
+                     const gchar *name)
+{
+       TrackerVariable *variable;
+
+       variable = g_new0 (TrackerVariable, 1);
+       variable->name = g_strdup (name);
+       variable->sql_expression = g_strdup_printf ("\"%s_%s\"", sql_prefix, name);
+
+       return variable;
+}
+
+static void
+tracker_variable_free (TrackerVariable *variable)
+{
+       g_clear_object (&variable->binding);
+       g_free (variable->sql_expression);
+       g_free (variable->name);
+       g_free (variable);
+}
+
+void
+tracker_variable_set_sql_expression (TrackerVariable *variable,
+                                     const gchar     *sql_expression)
+{
+       g_free (variable->sql_expression);
+       variable->sql_expression = g_strdup (sql_expression);
+}
+
+const gchar *
+tracker_variable_get_sql_expression (TrackerVariable *variable)
+{
+       return variable->sql_expression;
+}
+
+gchar *
+tracker_variable_get_extra_sql_expression (TrackerVariable *variable,
+                                          const gchar     *suffix)
+{
+       return g_strdup_printf ("%s:%s", variable->sql_expression, suffix);
+}
+
+gboolean
+tracker_variable_has_bindings (TrackerVariable *variable)
+{
+       return variable->binding != NULL;
+}
+
+void
+tracker_variable_set_sample_binding (TrackerVariable        *variable,
+                                     TrackerVariableBinding *binding)
+{
+       g_set_object (&variable->binding, binding);
+}
+
+TrackerVariableBinding *
+tracker_variable_get_sample_binding (TrackerVariable *variable)
+{
+       return variable->binding;
+}
+
+guint
+tracker_variable_hash (gconstpointer data)
+{
+       const TrackerVariable *variable = data;
+       return g_str_hash (variable->name);
+}
+
+gboolean
+tracker_variable_equal (gconstpointer data1,
+                        gconstpointer data2)
+{
+       const TrackerVariable *var1 = data1, *var2 = data2;
+       return g_str_equal (var1->name, var2->name);
+}
+
+void
+tracker_token_literal_init (TrackerToken *token,
+                            const gchar  *literal)
+{
+       token->type = TOKEN_TYPE_LITERAL;
+       token->content.literal = g_strdup (literal);
+}
+
+void
+tracker_token_variable_init (TrackerToken    *token,
+                             TrackerVariable *variable)
+{
+       token->type = TOKEN_TYPE_VARIABLE;
+       token->content.var = variable;
+}
+
+void
+tracker_token_unset (TrackerToken *token)
+{
+       if (token->type == TOKEN_TYPE_LITERAL)
+               g_clear_pointer (&token->content.literal, g_free);
+       token->type = TOKEN_TYPE_NONE;
+}
+
+gboolean
+tracker_token_is_empty (TrackerToken *token)
+{
+       return token->type == TOKEN_TYPE_NONE;
+}
+
+const gchar *
+tracker_token_get_literal (TrackerToken *token)
+{
+       if (token->type == TOKEN_TYPE_LITERAL)
+               return token->content.literal;
+       return NULL;
+}
+
+TrackerVariable *
+tracker_token_get_variable (TrackerToken *token)
+{
+       if (token->type == TOKEN_TYPE_VARIABLE)
+               return token->content.var;
+       return NULL;
+}
+
+const gchar *
+tracker_token_get_idstring (TrackerToken *token)
+{
+       if (token->type == TOKEN_TYPE_LITERAL)
+               return token->content.literal;
+       else if (token->type == TOKEN_TYPE_VARIABLE)
+               return token->content.var->sql_expression;
+       else
+               return NULL;
+}
+
+/* Solution */
+TrackerSolution *
+tracker_solution_new (guint n_cols)
+{
+       TrackerSolution *solution;
+
+       solution = g_new0 (TrackerSolution, 1);
+       solution->n_cols = n_cols;
+       solution->columns = g_ptr_array_new_with_free_func (g_free);
+       solution->values = g_ptr_array_new_with_free_func (g_free);
+       solution->solution_index = -1;
+
+       return solution;
+}
+
+void
+tracker_solution_add_column_name (TrackerSolution *solution,
+                                  const gchar     *name)
+{
+       g_ptr_array_add (solution->columns, g_strdup (name));
+}
+
+void
+tracker_solution_add_value (TrackerSolution *solution,
+                            const gchar     *str)
+{
+       g_ptr_array_add (solution->values, g_strdup (str));
+}
+
+gboolean
+tracker_solution_next (TrackerSolution *solution)
+{
+       solution->solution_index++;
+       return solution->solution_index * solution->n_cols < solution->values->len;
+}
+
+void
+tracker_solution_rewind (TrackerSolution *solution)
+{
+       solution->solution_index = -1;
+}
+
+void
+tracker_solution_free (TrackerSolution *solution)
+{
+       g_ptr_array_unref (solution->columns);
+       g_ptr_array_unref (solution->values);
+       g_free (solution);
+}
+
+GHashTable *
+tracker_solution_get_bindings (TrackerSolution *solution)
+{
+       GHashTable *ht;
+       gint i;
+
+       ht = g_hash_table_new (g_str_hash, g_str_equal);
+
+       for (i = 0; i < solution->columns->len; i++) {
+               gint values_pos = solution->solution_index * solution->n_cols + i;
+               gchar *name, *value;
+
+               if (values_pos >= solution->values->len)
+                       break;
+
+               name = g_ptr_array_index (solution->columns, i);
+               value = g_ptr_array_index (solution->values, values_pos);
+               g_hash_table_insert (ht, name, value);
+       }
+
+       return ht;
+}
+
+/* Data binding */
+G_DEFINE_ABSTRACT_TYPE (TrackerBinding, tracker_binding, G_TYPE_OBJECT)
+
+static void
+tracker_binding_finalize (GObject *object)
+{
+       TrackerBinding *binding = TRACKER_BINDING (object);
+
+       g_free (binding->sql_db_column_name);
+       g_free (binding->sql_expression);
+
+       G_OBJECT_CLASS (tracker_binding_parent_class)->finalize (object);
+}
+
+static void
+tracker_binding_class_init (TrackerBindingClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_binding_finalize;
+}
+
+static void
+tracker_binding_init (TrackerBinding *binding)
+{
+}
+
+TrackerDataTable *
+tracker_binding_get_table (TrackerBinding *binding)
+{
+       return binding->table;
+}
+
+void
+tracker_binding_set_db_column_name (TrackerBinding *binding,
+                                   const gchar    *column_name)
+{
+       g_free (binding->sql_db_column_name);
+       binding->sql_db_column_name = g_strdup (column_name);
+}
+
+void
+tracker_binding_set_sql_expression (TrackerBinding *binding,
+                                   const gchar    *sql_expression)
+{
+       g_free (binding->sql_expression);
+       binding->sql_expression = g_strdup (sql_expression);
+}
+
+const gchar *
+tracker_binding_get_sql_expression (TrackerBinding *binding)
+{
+       if (!binding->sql_expression && binding->table) {
+               binding->sql_expression =  g_strdup_printf ("\"%s\".\"%s\"",
+                                                           binding->table->sql_query_tablename,
+                                                           binding->sql_db_column_name);
+       }
+
+       return binding->sql_expression;
+}
+
+gchar *
+tracker_binding_get_extra_sql_expression (TrackerBinding *binding,
+                                          const gchar    *suffix)
+{
+       return g_strdup_printf ("\"%s\".\"%s:%s\"",
+                               binding->table->sql_query_tablename,
+                               binding->sql_db_column_name,
+                               suffix);
+}
+
+void
+tracker_binding_set_data_type (TrackerBinding      *binding,
+                               TrackerPropertyType  property_type)
+{
+       binding->data_type = property_type;
+}
+
+/* Literal binding */
+G_DEFINE_TYPE (TrackerLiteralBinding, tracker_literal_binding, TRACKER_TYPE_BINDING)
+
+static void
+tracker_literal_binding_finalize (GObject *object)
+{
+       TrackerLiteralBinding *binding = TRACKER_LITERAL_BINDING (object);
+
+       g_free (binding->literal);
+
+       G_OBJECT_CLASS (tracker_literal_binding_parent_class)->finalize (object);
+}
+
+static void
+tracker_literal_binding_class_init (TrackerLiteralBindingClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_literal_binding_finalize;
+}
+
+static void
+tracker_literal_binding_init (TrackerLiteralBinding *binding)
+{
+}
+
+TrackerBinding *
+tracker_literal_binding_new (const gchar      *literal,
+                            TrackerDataTable *table)
+{
+       TrackerBinding *binding;
+
+       binding = g_object_new (TRACKER_TYPE_LITERAL_BINDING, NULL);
+       binding->table = table;
+       TRACKER_LITERAL_BINDING (binding)->literal = g_strdup (literal);
+
+       return binding;
+}
+
+/* Variable binding */
+G_DEFINE_TYPE (TrackerVariableBinding, tracker_variable_binding, TRACKER_TYPE_BINDING)
+
+static void
+tracker_variable_binding_class_init (TrackerVariableBindingClass *klass)
+{
+}
+
+static void
+tracker_variable_binding_init (TrackerVariableBinding *binding)
+{
+}
+
+TrackerBinding *
+tracker_variable_binding_new (TrackerVariable  *variable,
+                             TrackerClass     *type,
+                             TrackerDataTable *table)
+{
+       TrackerBinding *binding;
+
+       binding = g_object_new (TRACKER_TYPE_VARIABLE_BINDING, NULL);
+       binding->table = table;
+       TRACKER_VARIABLE_BINDING (binding)->type = type;
+       TRACKER_VARIABLE_BINDING (binding)->variable = variable;
+
+       return binding;
+}
+
+void
+tracker_variable_binding_set_nullable (TrackerVariableBinding *binding,
+                                      gboolean                nullable)
+{
+       binding->nullable = !!nullable;
+}
+
+gboolean
+tracker_variable_binding_get_nullable (TrackerVariableBinding *binding)
+{
+       return binding->nullable;
+}
+
+TrackerVariable *
+tracker_variable_binding_get_variable (TrackerVariableBinding *binding)
+{
+       return binding->variable;
+}
+
+TrackerClass *
+tracker_variable_binding_get_class (TrackerVariableBinding *binding)
+{
+       return binding->type;
+}
+
+/* Context */
+G_DEFINE_TYPE (TrackerContext, tracker_context, G_TYPE_INITIALLY_UNOWNED)
+
+static void
+tracker_context_finalize (GObject *object)
+{
+       TrackerContext *context = (TrackerContext *) object;
+
+       while (context->children) {
+               g_object_unref (context->children->data);
+               context->children = g_list_delete_link (context->children,
+                                                       context->children);
+       }
+
+       if (context->variable_set)
+               g_hash_table_unref (context->variable_set);
+
+       G_OBJECT_CLASS (tracker_context_parent_class)->finalize (object);
+}
+
+static void
+tracker_context_class_init (TrackerContextClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_context_finalize;
+}
+
+static void
+tracker_context_init (TrackerContext *context)
+{
+       context->variable_set = g_hash_table_new (tracker_variable_hash,
+                                                 tracker_variable_equal);
+}
+
+TrackerContext *
+tracker_context_new (void)
+{
+       return g_object_new (TRACKER_TYPE_CONTEXT, NULL);
+}
+
+void
+tracker_context_set_parent (TrackerContext *context,
+                            TrackerContext *parent)
+{
+       g_assert (context->parent == NULL);
+
+       context->parent = parent;
+       parent->children = g_list_append (parent->children,
+                                         g_object_ref_sink (context));
+}
+
+TrackerContext *
+tracker_context_get_parent (TrackerContext *context)
+{
+       return context->parent;
+}
+
+void
+tracker_context_add_variable_ref (TrackerContext  *context,
+                                 TrackerVariable *variable)
+{
+       g_hash_table_add (context->variable_set, variable);
+}
+
+gboolean
+tracker_context_lookup_variable_ref (TrackerContext  *context,
+                                     TrackerVariable *variable)
+{
+       return g_hash_table_lookup (context->variable_set, variable) != NULL;
+}
+
+void
+tracker_context_propagate_variables (TrackerContext *context)
+{
+       GHashTableIter iter;
+       gpointer key;
+
+       g_assert (context->parent != NULL);
+       g_hash_table_iter_init (&iter, context->variable_set);
+
+       while (g_hash_table_iter_next (&iter, &key, NULL))
+               g_hash_table_add (context->parent->variable_set, key);
+}
+
+/* Select context */
+G_DEFINE_TYPE (TrackerSelectContext, tracker_select_context, TRACKER_TYPE_CONTEXT)
+
+static void
+tracker_select_context_finalize (GObject *object)
+{
+       TrackerSelectContext *context = TRACKER_SELECT_CONTEXT (object);
+
+       g_clear_pointer (&context->variables, g_hash_table_unref);
+       g_clear_pointer (&context->predicate_variables, g_hash_table_unref);
+       g_clear_pointer (&context->generated_variables, g_ptr_array_unref);
+       g_clear_pointer (&context->literal_bindings, g_ptr_array_unref);
+
+       G_OBJECT_CLASS (tracker_select_context_parent_class)->finalize (object);
+}
+
+static void
+tracker_select_context_class_init (TrackerSelectContextClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_select_context_finalize;
+}
+
+static void
+tracker_select_context_init (TrackerSelectContext *context)
+{
+}
+
+TrackerContext *
+tracker_select_context_new (void)
+{
+       return g_object_new (TRACKER_TYPE_SELECT_CONTEXT, NULL);
+}
+
+TrackerVariable *
+tracker_select_context_ensure_variable (TrackerSelectContext *context,
+                                        const gchar          *name)
+{
+       TrackerVariable *variable;
+
+       /* All variables are reserved to the root context */
+       g_assert (TRACKER_CONTEXT (context)->parent == NULL);
+
+       if (!context->variables) {
+               context->variables =
+                       g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+                                              (GDestroyNotify) tracker_variable_free);
+       }
+
+       variable = g_hash_table_lookup (context->variables, name);
+
+       if (!variable) {
+               variable = tracker_variable_new ("v", name);
+               g_hash_table_insert (context->variables, variable->name, variable);
+       }
+
+       return variable;
+}
+
+TrackerVariable *
+tracker_select_context_add_generated_variable (TrackerSelectContext *context)
+{
+       TrackerVariable *variable;
+       gchar *name;
+
+       /* All variables are reserved to the root context */
+       g_assert (TRACKER_CONTEXT (context)->parent == NULL);
+
+       if (!context->generated_variables) {
+               context->generated_variables =
+                       g_ptr_array_new_with_free_func ((GDestroyNotify) tracker_variable_free);
+       }
+
+       name = g_strdup_printf ("%d", context->generated_variables->len + 1);
+       variable = tracker_variable_new ("g", name);
+       g_free (name);
+
+       g_ptr_array_add (context->generated_variables, variable);
+
+       return variable;
+}
+
+TrackerPredicateVariable *
+tracker_select_context_lookup_predicate_variable (TrackerSelectContext *context,
+                                                  TrackerVariable      *variable)
+{
+       if (!context->predicate_variables)
+               return NULL;
+       return g_hash_table_lookup (context->predicate_variables, variable);
+}
+
+void
+tracker_select_context_add_predicate_variable (TrackerSelectContext     *context,
+                                               TrackerVariable          *variable,
+                                               TrackerPredicateVariable *pred_var)
+{
+       if (!context->predicate_variables) {
+               context->predicate_variables =
+                       g_hash_table_new_full (tracker_variable_hash,
+                                              tracker_variable_equal, NULL,
+                                              (GDestroyNotify) tracker_predicate_variable_free);
+       }
+
+       g_hash_table_insert (context->predicate_variables, variable, pred_var);
+}
+
+void
+tracker_select_context_add_literal_binding (TrackerSelectContext  *context,
+                                            TrackerLiteralBinding *binding)
+{
+       /* Literal bindings are reserved to the root context */
+       g_assert (TRACKER_CONTEXT (context)->parent == NULL);
+
+       if (!context->literal_bindings)
+               context->literal_bindings = g_ptr_array_new_with_free_func (g_object_unref);
+
+       g_ptr_array_add (context->literal_bindings, g_object_ref (binding));
+}
+
+guint
+tracker_select_context_get_literal_binding_index (TrackerSelectContext  *context,
+                                                  TrackerLiteralBinding *binding)
+{
+       guint i;
+
+       for (i = 0; i < context->literal_bindings->len; i++) {
+               if (binding == g_ptr_array_index (context->literal_bindings, i))
+                       return i;
+       }
+
+       g_assert_not_reached ();
+       return -1;
+}
+
+/* Triple context */
+G_DEFINE_TYPE (TrackerTripleContext, tracker_triple_context, TRACKER_TYPE_CONTEXT)
+
+static void
+tracker_triple_context_finalize (GObject *object)
+{
+       TrackerTripleContext *context = TRACKER_TRIPLE_CONTEXT (object);
+
+       g_ptr_array_unref (context->sql_tables);
+       g_ptr_array_unref (context->literal_bindings);
+       g_hash_table_unref (context->variable_bindings);
+
+       G_OBJECT_CLASS (tracker_triple_context_parent_class)->finalize (object);
+}
+
+static void
+tracker_triple_context_class_init (TrackerTripleContextClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_triple_context_finalize;
+}
+
+static void
+tracker_triple_context_init (TrackerTripleContext *context)
+{
+       context->sql_tables = g_ptr_array_new_with_free_func ((GDestroyNotify) tracker_data_table_free);
+       context->literal_bindings = g_ptr_array_new_with_free_func (g_object_unref);
+       context->variable_bindings =
+               g_hash_table_new_full (tracker_variable_hash,
+                                      tracker_variable_equal, NULL,
+                                      (GDestroyNotify) g_ptr_array_unref);
+}
+
+TrackerContext *
+tracker_triple_context_new (void)
+{
+       return g_object_new (TRACKER_TYPE_TRIPLE_CONTEXT, NULL);
+}
+
+TrackerDataTable *
+tracker_triple_context_lookup_table (TrackerTripleContext *context,
+                                     const gchar          *subject,
+                                     const gchar          *tablename)
+{
+       TrackerDataTable *table = NULL;
+       guint i;
+
+       for (i = 0; i < context->sql_tables->len; i++) {
+               TrackerDataTable *table;
+
+               table = g_ptr_array_index (context->sql_tables, i);
+
+               if (g_strcmp0 (table->subject, subject) == 0 &&
+                   g_strcmp0 (table->sql_db_tablename, tablename) == 0)
+                       return table;
+       }
+
+       return table;
+}
+
+TrackerDataTable *
+tracker_triple_context_add_table (TrackerTripleContext *context,
+                                  const gchar          *subject,
+                                  const gchar          *tablename)
+{
+       TrackerDataTable *table;
+
+       table = tracker_data_table_new (tablename, subject, ++context->table_counter);
+       g_ptr_array_add (context->sql_tables, table);
+
+       return table;
+}
+
+void
+tracker_triple_context_add_literal_binding (TrackerTripleContext  *context,
+                                            TrackerLiteralBinding *binding)
+{
+       g_ptr_array_add (context->literal_bindings, g_object_ref (binding));
+}
+
+GPtrArray *
+tracker_triple_context_lookup_variable_binding_list (TrackerTripleContext *context,
+                                                    TrackerVariable      *variable)
+{
+       return g_hash_table_lookup (context->variable_bindings, variable);
+}
+
+GPtrArray *
+tracker_triple_context_get_variable_binding_list (TrackerTripleContext *context,
+                                                  TrackerVariable      *variable)
+{
+       GPtrArray *binding_list = NULL;
+
+       binding_list = g_hash_table_lookup (context->variable_bindings, variable);
+
+       if (!binding_list) {
+               TrackerContext *current_context = (TrackerContext *) context;
+               TrackerContext *parent_context;
+
+               binding_list = g_ptr_array_new_with_free_func (g_object_unref);
+               g_hash_table_insert (context->variable_bindings, variable, binding_list);
+
+               if (tracker_variable_has_bindings (variable)) {
+                       /* might be in scalar subquery: check variables of outer queries */
+                       while (current_context) {
+                               parent_context = tracker_context_get_parent (current_context);
+
+                               /* only allow access to variables of immediate parent context of the subquery
+                                * allowing access to other variables leads to invalid SQL or wrong results
+                                */
+                               if (TRACKER_IS_SELECT_CONTEXT (current_context) &&
+                                   tracker_context_get_parent (current_context) &&
+                                   g_hash_table_lookup (parent_context->variable_set, variable)) {
+                                       TrackerVariableBinding *sample;
+                                       TrackerBinding *binding;
+
+                                       sample = tracker_variable_get_sample_binding (variable);
+                                       binding = tracker_variable_binding_new (variable, sample->type,
+                                                                               tracker_binding_get_table 
(TRACKER_BINDING (sample)));
+                                       tracker_binding_set_sql_expression (binding,
+                                                                           
tracker_variable_get_sql_expression (variable));
+                                       tracker_binding_set_data_type (binding,
+                                                                      TRACKER_BINDING (sample)->data_type);
+                                       g_ptr_array_add (binding_list, binding);
+                                       break;
+                               }
+
+                               current_context = parent_context;
+                       }
+               }
+       }
+
+       return binding_list;
+}
+
+void
+tracker_triple_context_add_variable_binding (TrackerTripleContext   *context,
+                                             TrackerVariable        *variable,
+                                             TrackerVariableBinding *binding)
+{
+       GPtrArray *binding_list;
+
+       binding_list = tracker_triple_context_get_variable_binding_list (context,
+                                                                        variable);
+       g_ptr_array_add (binding_list, g_object_ref (binding));
+}
diff --git a/src/libtracker-data/tracker-sparql-types.h b/src/libtracker-data/tracker-sparql-types.h
new file mode 100644
index 000000000..6fdd0cbe7
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql-types.h
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, Red Hat Inc.
+ *
+ * This library 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 library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TRACKER_SPARQL_TYPES_H__
+#define __TRACKER_SPARQL_TYPES_H__
+
+#include "tracker-ontologies.h"
+
+#define TRACKER_TYPE_BINDING  (tracker_binding_get_type ())
+#define TRACKER_BINDING(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_BINDING, TrackerBinding))
+#define TRACKER_IS_BINDING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_BINDING))
+
+#define TRACKER_TYPE_LITERAL_BINDING  (tracker_literal_binding_get_type ())
+#define TRACKER_LITERAL_BINDING(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_LITERAL_BINDING, 
TrackerLiteralBinding))
+#define TRACKER_IS_LITERAL_BINDING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_LITERAL_BINDING))
+
+#define TRACKER_TYPE_VARIABLE_BINDING  (tracker_variable_binding_get_type ())
+#define TRACKER_VARIABLE_BINDING(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_VARIABLE_BINDING, 
TrackerVariableBinding))
+#define TRACKER_IS_VARIABLE_BINDING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_VARIABLE_BINDING))
+
+#define TRACKER_TYPE_CONTEXT  (tracker_context_get_type ())
+#define TRACKER_CONTEXT(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_CONTEXT, TrackerContext))
+#define TRACKER_IS_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_CONTEXT))
+
+#define TRACKER_TYPE_SELECT_CONTEXT  (tracker_select_context_get_type ())
+#define TRACKER_SELECT_CONTEXT(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_SELECT_CONTEXT, 
TrackerSelectContext))
+#define TRACKER_IS_SELECT_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_SELECT_CONTEXT))
+
+#define TRACKER_TYPE_TRIPLE_CONTEXT (tracker_triple_context_get_type ())
+#define TRACKER_TRIPLE_CONTEXT(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_TRIPLE_CONTEXT, 
TrackerTripleContext))
+#define TRACKER_IS_TRIPLE_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_TRIPLE_CONTEXT))
+
+typedef struct _TrackerBinding TrackerBinding;
+typedef struct _TrackerBindingClass TrackerBindingClass;
+typedef struct _TrackerLiteralBinding TrackerLiteralBinding;
+typedef struct _TrackerLiteralBindingClass TrackerLiteralBindingClass;
+typedef struct _TrackerVariableBinding TrackerVariableBinding;
+typedef struct _TrackerVariableBindingClass TrackerVariableBindingClass;
+typedef struct _TrackerContext TrackerContext;
+typedef struct _TrackerContextClass TrackerContextClass;
+typedef struct _TrackerSelectContext TrackerSelectContext;
+typedef struct _TrackerSelectContextClass TrackerSelectContextClass;
+typedef struct _TrackerTripleContext TrackerTripleContext;
+typedef struct _TrackerTripleContextClass TrackerTripleContextClass;
+
+typedef struct _TrackerDataTable TrackerDataTable;
+typedef struct _TrackerVariable TrackerVariable;
+typedef struct _TrackerToken TrackerToken;
+typedef struct _TrackerSolution TrackerSolution;
+typedef struct _TrackerPredicateVariable TrackerPredicateVariable;
+
+struct _TrackerDataTable {
+       gchar *subject; /* Subject this table is pulled from */
+       gchar *sql_db_tablename; /* as in db schema */
+       gchar *sql_query_tablename; /* temp. name, generated */
+       TrackerPredicateVariable *predicate_variable;
+};
+
+struct _TrackerBinding {
+       GObject parent_instance;
+       TrackerPropertyType data_type;
+       TrackerDataTable *table;
+       gchar *sql_db_column_name;
+       gchar *sql_expression;
+};
+
+struct _TrackerBindingClass {
+       GObjectClass parent_class;
+};
+
+/* Represents a mapping of a SPARQL literal to a SQL table and column */
+struct _TrackerLiteralBinding {
+       TrackerBinding parent_instance;
+       gchar *literal;
+};
+
+struct _TrackerLiteralBindingClass {
+       TrackerBindingClass parent_class;
+};
+
+/* Represents a mapping of a SPARQL variable to a SQL table and column */
+struct _TrackerVariableBinding {
+       TrackerBinding parent_instance;
+       TrackerVariable *variable;
+       TrackerClass *type;
+       guint nullable : 1;
+};
+
+struct _TrackerVariableBindingClass {
+       TrackerBindingClass parent_class;
+};
+
+struct _TrackerVariable {
+       gchar *name;
+       gchar *sql_expression;
+       TrackerVariableBinding *binding;
+};
+
+struct _TrackerToken {
+       guint type;
+       union {
+               gchar *literal;
+               TrackerVariable *var;
+       } content;
+};
+
+struct _TrackerPredicateVariable {
+       gchar *subject;
+       gchar *object;
+       TrackerClass *domain;
+
+       guint return_graph : 1;
+};
+
+struct _TrackerSolution {
+       GPtrArray *columns;
+       GPtrArray *values;
+       int solution_index;
+       int n_cols;
+};
+
+struct _TrackerContext {
+       GInitiallyUnowned parent_instance;
+       TrackerContext *parent;
+       GList *children;
+
+       /* Variables used in this context, these will be owned by the
+        * root select context.
+        */
+       GHashTable *variable_set;
+};
+
+struct _TrackerContextClass {
+       GInitiallyUnownedClass parent_class;
+};
+
+struct _TrackerSelectContext {
+       TrackerContext parent_instance;
+
+       /* Variables used as predicates */
+       GHashTable *predicate_variables; /* TrackerVariable -> TrackerPredicateVariable */
+
+       /* All variables declared from this context. All these TrackerVariables
+        * are shared with children contexts. Only the root context has contents
+        * here.
+        */
+       GHashTable *variables; /* string -> TrackerVariable */
+
+       GPtrArray *generated_variables;
+
+       /* SPARQL literals. Content is TrackerLiteralBinding */
+       GPtrArray *literal_bindings;
+
+       /* Counter for sqlite3_stmt query bindings */
+       gint binding_counter;
+
+       /* Type to propagate upwards */
+       TrackerPropertyType type;
+};
+
+struct _TrackerSelectContextClass {
+       TrackerContextClass parent_class;
+};
+
+struct _TrackerTripleContext {
+       TrackerContext parent_instance;
+
+       /* Data tables pulled by the bindings below */
+       GPtrArray *sql_tables;
+
+       /* SPARQL literals. Content is TrackerLiteralBinding */
+       GPtrArray *literal_bindings;
+
+       /* SPARQL variables. */
+       GHashTable *variable_bindings; /* TrackerVariable -> GPtrArray(TrackerVariableBinding) */
+
+       /* Counter for disambiguating table names in queries */
+       gint table_counter;
+};
+
+struct _TrackerTripleContextClass {
+       TrackerContextClass parent_class;
+};
+
+/* Data table */
+void tracker_data_table_set_predicate_variable (TrackerDataTable         *table,
+                                               TrackerPredicateVariable *variable);
+
+/* Binding */
+GType              tracker_binding_get_type (void) G_GNUC_CONST;
+TrackerDataTable * tracker_binding_get_table (TrackerBinding   *binding);
+
+void tracker_binding_set_db_column_name (TrackerBinding *binding,
+                                        const gchar    *column_name);
+
+void tracker_binding_set_sql_expression (TrackerBinding *binding,
+                                        const gchar    *sql_expression);
+
+void tracker_binding_set_data_type (TrackerBinding      *binding,
+                                   TrackerPropertyType  type);
+
+const gchar * tracker_binding_get_sql_expression (TrackerBinding *binding);
+gchar * tracker_binding_get_extra_sql_expression (TrackerBinding *binding,
+                                                 const gchar    *suffix);
+
+/* Literal binding */
+GType            tracker_literal_binding_get_type (void) G_GNUC_CONST;
+TrackerBinding * tracker_literal_binding_new (const gchar      *literal,
+                                             TrackerDataTable *table);
+
+/* Variable binding */
+GType            tracker_variable_binding_get_type (void) G_GNUC_CONST;
+TrackerBinding * tracker_variable_binding_new (TrackerVariable  *variable,
+                                              TrackerClass     *class,
+                                              TrackerDataTable *table);
+void tracker_variable_binding_set_nullable (TrackerVariableBinding *binding,
+                                           gboolean                nullable);
+gboolean tracker_variable_binding_get_nullable (TrackerVariableBinding *binding);
+TrackerVariable * tracker_variable_binding_get_variable (TrackerVariableBinding *binding);
+
+/* Variable */
+void    tracker_variable_set_sql_expression       (TrackerVariable *variable,
+                                                  const gchar     *sql_expression);
+const gchar * tracker_variable_get_sql_expression (TrackerVariable *variable);
+gchar * tracker_variable_get_extra_sql_expression (TrackerVariable *variable,
+                                                  const gchar     *suffix);
+
+gboolean tracker_variable_has_bindings            (TrackerVariable        *variable);
+void    tracker_variable_set_sample_binding       (TrackerVariable        *variable,
+                                                  TrackerVariableBinding *binding);
+TrackerVariableBinding * tracker_variable_get_sample_binding (TrackerVariable *variable);
+
+/* Token */
+void tracker_token_literal_init  (TrackerToken    *token,
+                                  const gchar     *literal);
+void tracker_token_variable_init (TrackerToken    *token,
+                                  TrackerVariable *variable);
+void tracker_token_unset         (TrackerToken *token);
+
+gboolean           tracker_token_is_empty     (TrackerToken *token);
+const gchar      * tracker_token_get_literal  (TrackerToken *token);
+TrackerVariable  * tracker_token_get_variable (TrackerToken *token);
+const gchar      * tracker_token_get_idstring (TrackerToken *token);
+
+/* Predicate variable */
+TrackerPredicateVariable *tracker_predicate_variable_new (void);
+
+void tracker_predicate_variable_set_domain (TrackerPredicateVariable *pred_var,
+                                            TrackerClass             *domain);
+void tracker_predicate_variable_set_triple_details (TrackerPredicateVariable *pred_var,
+                                                    const gchar              *subject,
+                                                    const gchar              *object,
+                                                    gboolean                  return_graph);
+
+/* Solution */
+TrackerSolution * tracker_solution_new       (guint            n_cols);
+void              tracker_solution_free      (TrackerSolution *solution);
+gboolean          tracker_solution_next      (TrackerSolution *solution);
+void              tracker_solution_rewind    (TrackerSolution *solution);
+
+void              tracker_solution_add_column_name (TrackerSolution *solution,
+                                                    const gchar     *str);
+void              tracker_solution_add_value       (TrackerSolution *solution,
+                                                    const gchar     *str);
+GHashTable      * tracker_solution_get_bindings    (TrackerSolution *solution);
+
+
+/* Context */
+GType            tracker_context_get_type   (void) G_GNUC_CONST;
+TrackerContext * tracker_context_new        (void);
+void             tracker_context_set_parent (TrackerContext *context,
+                                             TrackerContext *parent);
+TrackerContext * tracker_context_get_parent (TrackerContext *context);
+
+void tracker_context_propagate_variables (TrackerContext *context);
+void tracker_context_add_variable_ref    (TrackerContext  *context,
+                                         TrackerVariable *variable);
+gboolean tracker_context_lookup_variable_ref (TrackerContext  *context,
+                                              TrackerVariable *variable);
+
+/* Select context */
+GType            tracker_select_context_get_type (void) G_GNUC_CONST;
+TrackerContext * tracker_select_context_new (void);
+TrackerVariable * tracker_select_context_ensure_variable (TrackerSelectContext *context,
+                                                         const gchar          *name);
+TrackerVariable * tracker_select_context_add_generated_variable (TrackerSelectContext *context);
+
+TrackerPredicateVariable * tracker_select_context_lookup_predicate_variable (TrackerSelectContext *context,
+                                                                            TrackerVariable      *variable);
+void tracker_select_context_add_predicate_variable (TrackerSelectContext     *context,
+                                                   TrackerVariable          *variable,
+                                                   TrackerPredicateVariable *pred_var);
+void tracker_select_context_add_literal_binding (TrackerSelectContext  *context,
+                                                 TrackerLiteralBinding *binding);
+guint tracker_select_context_get_literal_binding_index (TrackerSelectContext  *context,
+                                                        TrackerLiteralBinding *binding);
+
+/* Triple context */
+GType            tracker_triple_context_get_type (void) G_GNUC_CONST;
+TrackerContext * tracker_triple_context_new (void);
+
+TrackerDataTable * tracker_triple_context_lookup_table (TrackerTripleContext *context,
+                                                        const gchar          *subject,
+                                                        const gchar          *table);
+TrackerDataTable * tracker_triple_context_add_table    (TrackerTripleContext *context,
+                                                        const gchar          *subject,
+                                                        const gchar          *table);
+void tracker_triple_context_add_literal_binding  (TrackerTripleContext   *context,
+                                                 TrackerLiteralBinding  *binding);
+void tracker_triple_context_add_variable_binding (TrackerTripleContext   *context,
+                                                 TrackerVariable        *variable,
+                                                 TrackerVariableBinding *binding);
+GPtrArray * tracker_triple_context_lookup_variable_binding_list (TrackerTripleContext *context,
+                                                                TrackerVariable      *variable);
+GPtrArray * tracker_triple_context_get_variable_binding_list (TrackerTripleContext *context,
+                                                             TrackerVariable      *variable);
+
+#endif /* __TRACKER_SPARQL_TYPES_H__ */
diff --git a/src/libtracker-data/tracker-sparql.c b/src/libtracker-data/tracker-sparql.c
new file mode 100644
index 000000000..84e545f2b
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql.c
@@ -0,0 +1,5826 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, Red Hat Inc.
+ *
+ * This library 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 library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include "tracker-data-query.h"
+#include "tracker-string-builder.h"
+#include "tracker-sparql.h"
+#include "tracker-sparql-types.h"
+#include "tracker-sparql-parser.h"
+#include "tracker-sparql-grammar.h"
+#include "tracker-collation.h"
+#include "tracker-db-interface-sqlite.h"
+
+#define TRACKER_NS "http://www.tracker-project.org/ontologies/tracker#";
+#define RDF_NS "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
+#define RDFS_NS "http://www.w3.org/2000/01/rdf-schema#";
+#define FTS_NS "http://www.tracker-project.org/ontologies/fts#";
+#define XSD_NS "http://www.w3.org/2001/XMLSchema#";
+#define FN_NS "http://www.w3.org/2005/xpath-functions#";
+
+enum {
+       TIME_FORMAT_SECONDS,
+       TIME_FORMAT_MINUTES,
+       TIME_FORMAT_HOURS
+};
+
+static inline gboolean _call_rule_func (TrackerSparql            *sparql,
+                                        TrackerGrammarNamedRule   rule,
+                                        GError                  **error);
+static gboolean handle_function_call (TrackerSparql  *sparql,
+                                      GError        **error);
+static gboolean helper_translate_date (TrackerSparql  *sparql,
+                                       const gchar    *format,
+                                       GError        **error);
+static gboolean helper_translate_time (TrackerSparql  *sparql,
+                                       guint           format,
+                                       GError        **error);
+static inline TrackerVariable * _ensure_variable (TrackerSparql *sparql,
+                                                  const gchar   *name);
+
+#define _raise(v,s,sub)   \
+       G_STMT_START { \
+       g_set_error (error, TRACKER_SPARQL_ERROR, \
+                    TRACKER_SPARQL_ERROR_##v, \
+                    s " '%s'", sub); \
+       return FALSE; \
+       } G_STMT_END
+
+#define _unimplemented(s) _raise(UNSUPPORTED, "Unsupported syntax", s)
+
+/* Added for control flow simplicity. All processing will be stopped
+ * whenever any rule sets an error and returns FALSE.
+ */
+#define _call_rule(c,r,e) \
+       G_STMT_START { \
+       if (!_call_rule_func(c, r, e)) \
+               return FALSE; \
+       } G_STMT_END
+
+typedef gboolean (* RuleTranslationFunc) (TrackerSparql  *sparql,
+                                          GError        **error);
+
+struct _TrackerSparql
+{
+       GObject parent_instance;
+       TrackerDataManager *data_manager;
+       const gchar *sparql;
+
+       TrackerNodeTree *tree;
+       GError *parser_error;
+
+       TrackerContext *context;
+       TrackerStringBuilder *sql;
+
+       GHashTable *prefix_map;
+       GList *filter_clauses;
+
+       GPtrArray *var_names;
+       GArray *var_types;
+
+       struct {
+               TrackerContext *context;
+               TrackerContext *select_context;
+               TrackerStringBuilder *sql;
+               TrackerParserNode *node;
+               TrackerParserNode *prev_node;
+
+               TrackerToken graph;
+               TrackerToken subject;
+               TrackerToken predicate;
+               TrackerToken object;
+
+               TrackerToken *token;
+
+               const gchar *expression_list_separator;
+               TrackerPropertyType expression_type;
+       } current_state;
+};
+
+G_DEFINE_TYPE (TrackerSparql, tracker_sparql, G_TYPE_OBJECT)
+
+static void
+tracker_sparql_finalize (GObject *object)
+{
+       TrackerSparql *sparql = TRACKER_SPARQL (object);
+
+       g_object_unref (sparql->data_manager);
+       g_hash_table_destroy (sparql->prefix_map);
+
+       if (sparql->sql)
+               tracker_string_builder_free (sparql->sql);
+       if (sparql->tree)
+               tracker_node_tree_free (sparql->tree);
+
+       g_clear_object (&sparql->context);
+
+       /* Unset all possible current state (eg. after error) */
+       tracker_token_unset (&sparql->current_state.graph);
+       tracker_token_unset (&sparql->current_state.subject);
+       tracker_token_unset (&sparql->current_state.predicate);
+       tracker_token_unset (&sparql->current_state.object);
+
+       g_ptr_array_unref (sparql->var_names);
+       g_array_unref (sparql->var_types);
+
+       G_OBJECT_CLASS (tracker_sparql_parent_class)->finalize (object);
+}
+
+static inline void
+tracker_sparql_push_context (TrackerSparql  *sparql,
+                             TrackerContext *context)
+{
+       if (sparql->current_state.context)
+               tracker_context_set_parent (context, sparql->current_state.context);
+       sparql->current_state.context = context;
+}
+
+static inline void
+tracker_sparql_pop_context (TrackerSparql *sparql,
+                            gboolean       propagate_variables)
+{
+       TrackerContext *parent;
+
+       g_assert (sparql->current_state.context);
+
+       parent = tracker_context_get_parent (sparql->current_state.context);
+
+       if (parent && propagate_variables)
+               tracker_context_propagate_variables (sparql->current_state.context);
+
+       sparql->current_state.context = parent;
+}
+
+static inline TrackerStringBuilder *
+tracker_sparql_swap_builder (TrackerSparql        *sparql,
+                             TrackerStringBuilder *string)
+{
+       TrackerStringBuilder *old;
+
+       old = sparql->current_state.sql;
+       sparql->current_state.sql = string;
+
+       return old;
+}
+
+static inline const gchar *
+tracker_sparql_swap_current_expression_list_separator (TrackerSparql *sparql,
+                                                       const gchar   *sep)
+{
+       const gchar *old;
+
+       old = sparql->current_state.expression_list_separator;
+       sparql->current_state.expression_list_separator = sep;
+
+       return old;
+}
+
+static inline gchar *
+tracker_sparql_expand_prefix (TrackerSparql *sparql,
+                              const gchar   *term)
+{
+       const gchar *sep;
+       gchar *ns, *expanded_ns;
+
+       sep = strchr (term, ':');
+
+       if (sep) {
+               ns = g_strndup (term, sep - term);
+               sep++;
+       } else {
+               ns = g_strdup (term);
+       }
+
+       expanded_ns = g_hash_table_lookup (sparql->prefix_map, ns);
+
+       if (!expanded_ns && g_strcmp0 (ns, "fn") == 0)
+               expanded_ns = FN_NS;
+
+       if (!expanded_ns) {
+               TrackerOntologies *ontologies;
+               TrackerNamespace **namespaces;
+               guint n_namespaces, i;
+
+               ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+               namespaces = tracker_ontologies_get_namespaces (ontologies, &n_namespaces);
+
+               for (i = 0; i < n_namespaces; i++) {
+                       if (!g_str_equal (ns, tracker_namespace_get_prefix (namespaces[i])))
+                               continue;
+
+                       expanded_ns = g_strdup (tracker_namespace_get_uri (namespaces[i]));
+                       g_hash_table_insert (sparql->prefix_map, g_strdup (ns), expanded_ns);
+               }
+
+               if (!expanded_ns)
+                       return NULL;
+       }
+
+       g_free (ns);
+
+       if (sep) {
+               return g_strdup_printf ("%s%s", expanded_ns, sep);
+       } else {
+               return g_strdup (expanded_ns);
+       }
+}
+
+static inline void
+tracker_sparql_iter_next (TrackerSparql *sparql)
+{
+       sparql->current_state.prev_node = sparql->current_state.node;
+       sparql->current_state.node =
+               tracker_sparql_parser_tree_find_next (sparql->current_state.node, FALSE);
+}
+
+static inline gboolean
+_check_in_rule (TrackerSparql           *sparql,
+                TrackerGrammarNamedRule  named_rule)
+{
+       TrackerParserNode *node = sparql->current_state.node;
+       const TrackerGrammarRule *rule;
+
+       g_assert (named_rule < N_NAMED_RULES);
+
+       if (!node)
+               return FALSE;
+
+       rule = tracker_parser_node_get_rule (node);
+
+       return tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, named_rule);
+}
+
+static inline TrackerGrammarNamedRule
+_current_rule (TrackerSparql *sparql)
+{
+       TrackerParserNode *parser_node = sparql->current_state.node;
+       const TrackerGrammarRule *rule;
+
+       if (!parser_node)
+               return -1;
+       rule = tracker_parser_node_get_rule (parser_node);
+       if (rule->type != RULE_TYPE_RULE)
+               return -1;
+
+       return rule->data.rule;
+}
+
+static inline gboolean
+_accept (TrackerSparql          *sparql,
+         TrackerGrammarRuleType  type,
+         guint                   value)
+{
+       TrackerParserNode *parser_node = sparql->current_state.node;
+       const TrackerGrammarRule *rule;
+
+       if (!parser_node)
+               return FALSE;
+
+       rule = tracker_parser_node_get_rule (parser_node);
+
+       if (tracker_grammar_rule_is_a (rule, type, value)) {
+               tracker_sparql_iter_next (sparql);
+               return TRUE;
+       }
+
+       return FALSE;
+}
+
+static inline void
+_expect (TrackerSparql          *sparql,
+         TrackerGrammarRuleType  type,
+         guint                   value)
+{
+       if (!_accept (sparql, type, value)) {
+               TrackerParserNode *parser_node = sparql->current_state.node;
+               const TrackerGrammarRule *rule = NULL;
+
+               if (parser_node)
+                       rule = tracker_parser_node_get_rule (parser_node);
+
+               if (type == RULE_TYPE_LITERAL) {
+                       if (rule) {
+                               g_error ("Parser expects literal '%s'. Got rule %d, value %d(%s)", 
literals[value],
+                                        rule->type, rule->data.literal, rule->string ? rule->string : 
"Unknown");
+                       } else {
+                               g_error ("Parser expects literal '%s'. Got EOF", literals[value]);
+                       }
+               } else {
+                       if (rule) {
+                               g_error ("Parser expects rule %d (%d). Got rule %d, value %d(%s)", type, 
value,
+                                        rule->type, rule->data.literal, rule->string ? rule->string : 
"Unknown");
+                       } else {
+                               g_error ("Parser expects rule %d (%d). Got EOF", type, value);
+                       }
+               }
+       }
+}
+
+static inline void
+_step (TrackerSparql *sparql)
+{
+       tracker_sparql_iter_next (sparql);
+}
+
+static inline void
+_prepend_string (TrackerSparql *sparql,
+                 const gchar   *str)
+{
+       tracker_string_builder_prepend (sparql->current_state.sql, str, -1);
+}
+
+static inline TrackerStringBuilder *
+_prepend_placeholder (TrackerSparql *sparql)
+{
+       return tracker_string_builder_prepend_placeholder (sparql->current_state.sql);
+}
+
+static inline void
+_append_string (TrackerSparql *sparql,
+                const gchar   *str)
+{
+       tracker_string_builder_append (sparql->current_state.sql, str, -1);
+}
+
+static inline void
+_append_string_printf (TrackerSparql *sparql,
+                       const gchar   *format,
+                       ...)
+{
+       va_list varargs;
+
+       va_start (varargs, format);
+       tracker_string_builder_append_valist (sparql->current_state.sql, format, varargs);
+       va_end (varargs);
+}
+
+static inline TrackerStringBuilder *
+_append_placeholder (TrackerSparql *sparql)
+{
+       return tracker_string_builder_append_placeholder (sparql->current_state.sql);
+}
+
+static inline void
+_append_literal_sql (TrackerSparql         *sparql,
+                     TrackerLiteralBinding *binding)
+{
+       guint idx;
+
+       idx = tracker_select_context_get_literal_binding_index (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                               binding);
+
+       if (TRACKER_BINDING (binding)->data_type == TRACKER_PROPERTY_TYPE_RESOURCE) {
+               _append_string_printf (sparql,
+                                      "COALESCE ((SELECT ID FROM Resource WHERE Uri = ?%d), 0) ",
+                                      idx + 1);
+       } else {
+               _append_string_printf (sparql, "?%d ", idx + 1);
+       }
+
+       if (TRACKER_BINDING (binding)->data_type == TRACKER_PROPERTY_TYPE_STRING)
+               _append_string (sparql, "COLLATE " TRACKER_COLLATION_NAME " ");
+}
+
+static void
+_append_variable_sql (TrackerSparql   *sparql,
+                      TrackerVariable *variable)
+{
+       TrackerBinding *binding;
+
+       binding = TRACKER_BINDING (tracker_variable_get_sample_binding (variable));
+
+       if (binding &&
+           binding->data_type == TRACKER_PROPERTY_TYPE_DATETIME) {
+               TrackerVariable *local_time;
+               gchar *name;
+
+               name = g_strdup_printf ("%s:local", variable->name);
+               local_time = _ensure_variable (sparql, name);
+               g_free (name);
+
+               _append_string_printf (sparql, "%s ",
+                                      tracker_variable_get_sql_expression (local_time));
+       } else {
+               _append_string_printf (sparql, "%s ",
+                                      tracker_variable_get_sql_expression (variable));
+       }
+}
+
+static inline gchar *
+_extract_node_string (TrackerParserNode *node,
+                      TrackerSparql     *sparql)
+{
+       const TrackerGrammarRule *rule;
+       gchar *str = NULL;
+       gssize start, end;
+
+       if (!tracker_parser_node_get_extents (node, &start, &end))
+               return NULL;
+
+       rule = tracker_parser_node_get_rule (node);
+
+       if (rule->type == RULE_TYPE_LITERAL) {
+               switch (rule->data.literal) {
+               case LITERAL_A:
+                       str = g_strdup (RDF_NS "type");
+                       break;
+               default:
+                       str = g_strndup (&sparql->sparql[start], end - start);
+                       break;
+               }
+       } else if (rule->type == RULE_TYPE_TERMINAL) {
+               const gchar *terminal_start, *terminal_end;
+               gssize add_start = 0, subtract_end = 0;
+               gboolean compress = FALSE;
+
+               terminal_start = &sparql->sparql[start];
+               terminal_end = &sparql->sparql[end];
+               rule = tracker_parser_node_get_rule (node);
+
+               switch (rule->data.terminal) {
+               case TERMINAL_TYPE_VAR1:
+               case TERMINAL_TYPE_VAR2:
+                       add_start = 1;
+                       break;
+               case TERMINAL_TYPE_STRING_LITERAL1:
+               case TERMINAL_TYPE_STRING_LITERAL2:
+                       add_start = subtract_end = 1;
+                       compress = TRUE;
+                       break;
+               case TERMINAL_TYPE_STRING_LITERAL_LONG1:
+               case TERMINAL_TYPE_STRING_LITERAL_LONG2:
+                       add_start = subtract_end = 3;
+                       compress = TRUE;
+                       break;
+               case TERMINAL_TYPE_IRIREF:
+                       add_start = subtract_end = 1;
+                       break;
+               case TERMINAL_TYPE_BLANK_NODE_LABEL:
+                       add_start = 2;
+                       break;
+               case TERMINAL_TYPE_PNAME_NS:
+                       subtract_end = 1;
+                       /* Fall through */
+               case TERMINAL_TYPE_PNAME_LN: {
+                       gchar *unexpanded;
+
+                       unexpanded = g_strndup (terminal_start + add_start,
+                                               terminal_end - terminal_start - subtract_end);
+                       str = tracker_sparql_expand_prefix (sparql, unexpanded);
+                       g_free (unexpanded);
+                       break;
+               }
+               default:
+                       break;
+               }
+
+               terminal_start += add_start;
+               terminal_end -= subtract_end;
+               g_assert (terminal_end >= terminal_start);
+
+               if (!str)
+                       str = g_strndup (terminal_start, terminal_end - terminal_start);
+
+               if (compress) {
+                       gchar *tmp = str;
+
+                       str = g_strcompress (tmp);
+                       g_free (tmp);
+               }
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return str;
+}
+
+static inline gchar *
+_dup_last_string (TrackerSparql *sparql)
+{
+       return _extract_node_string (sparql->current_state.prev_node, sparql);
+}
+
+static inline TrackerBinding *
+_convert_terminal (TrackerSparql *sparql)
+{
+       TrackerBinding *binding;
+       gchar *str;
+
+       str = _dup_last_string (sparql);
+       g_assert (str != NULL);
+
+       binding = tracker_literal_binding_new (str, NULL);
+       tracker_binding_set_data_type (binding, sparql->current_state.expression_type);
+       g_free (str);
+
+       return binding;
+}
+
+static void
+_add_binding (TrackerSparql  *sparql,
+             TrackerBinding *binding)
+{
+       TrackerTripleContext *context;
+
+       context = TRACKER_TRIPLE_CONTEXT (sparql->current_state.context);
+
+       if (TRACKER_IS_LITERAL_BINDING (binding)) {
+               tracker_triple_context_add_literal_binding (context,
+                                                           TRACKER_LITERAL_BINDING (binding));
+
+               /* Also add on the root SelectContext right away */
+               tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                           TRACKER_LITERAL_BINDING (binding));
+       } else if (TRACKER_IS_VARIABLE_BINDING (binding)) {
+               TrackerVariableBinding *variable_binding = TRACKER_VARIABLE_BINDING (binding);
+               TrackerVariable *variable;
+
+               variable = tracker_variable_binding_get_variable (variable_binding);
+               tracker_triple_context_add_variable_binding (context,
+                                                            variable,
+                                                            variable_binding);
+
+               if (!tracker_variable_has_bindings (variable))
+                       tracker_variable_set_sample_binding (variable, variable_binding);
+       } else {
+               g_assert_not_reached ();
+       }
+}
+
+static inline TrackerVariable *
+_ensure_variable (TrackerSparql *sparql,
+                 const gchar   *name)
+{
+       TrackerVariable *var;
+
+       var = tracker_select_context_ensure_variable (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                     name);
+       tracker_context_add_variable_ref (sparql->current_state.context, var);
+
+       return var;
+}
+
+static inline TrackerVariable *
+_extract_node_variable (TrackerParserNode *node,
+                       TrackerSparql     *sparql)
+{
+       const TrackerGrammarRule *rule = tracker_parser_node_get_rule (node);
+       TrackerVariable *variable;
+       gchar *str;
+
+       if (!tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1) &&
+           !tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2))
+               return NULL;
+
+       str = _extract_node_string (node, sparql);
+       variable = _ensure_variable (sparql, str);
+       g_free (str);
+
+       return variable;
+}
+
+static inline TrackerVariable *
+_last_node_variable (TrackerSparql *sparql)
+{
+       return _extract_node_variable (sparql->current_state.prev_node, sparql);
+}
+
+static void
+_init_token (TrackerToken      *token,
+             TrackerParserNode *node,
+             TrackerSparql     *sparql)
+{
+       const TrackerGrammarRule *rule = tracker_parser_node_get_rule (node);
+       TrackerVariable *var;
+       gchar *str;
+
+       str = _extract_node_string (node, sparql);
+
+       if (tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1) ||
+           tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2)) {
+               var = _ensure_variable (sparql, str);
+               tracker_token_variable_init (token, var);
+       } else {
+               tracker_token_literal_init (token, str);
+       }
+
+       g_free (str);
+}
+
+static inline gboolean
+_accept_token (TrackerParserNode      **node,
+               TrackerGrammarRuleType   type,
+               guint                    value,
+               TrackerParserNode      **prev)
+{
+       const TrackerGrammarRule *rule;
+
+       g_assert (node != NULL && *node != NULL);
+       rule = tracker_parser_node_get_rule (*node);
+
+       if (!tracker_grammar_rule_is_a (rule, type, value))
+               return FALSE;
+
+       if (prev)
+               *prev = *node;
+
+       *node = tracker_sparql_parser_tree_find_next (*node, TRUE);
+       return TRUE;
+}
+
+static gboolean
+extract_fts_snippet_parameters (TrackerSparql      *sparql,
+                                TrackerParserNode  *node,
+                                gchar             **match_start,
+                                gchar             **match_end,
+                                gchar             **ellipsis,
+                                gchar             **num_tokens,
+                                GError            **error)
+{
+       TrackerParserNode *val = NULL;
+
+       if (_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+               if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1, &val) ||
+                   _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2, &val)) {
+                       *match_start = _extract_node_string (val, sparql);
+               } else {
+                       _raise (PARSE, "«Match start» argument expects string", "fts:snippet");
+               }
+
+               if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+                       _raise (PARSE, "Both «Match start» and «Match end» arguments expected", 
"fts:snippet");
+               }
+
+               if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1, &val) ||
+                   _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2, &val)) {
+                       *match_end = _extract_node_string (val, sparql);
+               } else {
+                       _raise (PARSE, "«Match end» argument expects string", "fts:snippet");
+               }
+       }
+
+       if (_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+               if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1, &val) ||
+                   _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2, &val)) {
+                       *ellipsis = _extract_node_string (val, sparql);
+               } else {
+                       _raise (PARSE, "«Ellipsis» argument expects string", "fts:snippet");
+               }
+       }
+
+       if (_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+               if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER, &val) ||
+                   _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER_POSITIVE, &val)) {
+                       *num_tokens = _extract_node_string (val, sparql);
+               } else {
+                       _raise (PARSE, "«Num. tokens» argument expects integer", "fts:snippet");
+               }
+       }
+
+       if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS, NULL)) {
+               _raise (PARSE, "Unexpected number of parameters", "fts:snippet");
+       }
+
+       return TRUE;
+}
+
+static gboolean
+introspect_fts_snippet (TrackerSparql         *sparql,
+                        TrackerVariable       *subject,
+                        TrackerDataTable      *table,
+                        TrackerTripleContext  *triple_context,
+                        GError               **error)
+{
+       TrackerParserNode *node = tracker_node_tree_get_root (sparql->tree);
+
+       for (node = tracker_sparql_parser_tree_find_first (node, TRUE);
+            node;
+            node = tracker_sparql_parser_tree_find_next (node, TRUE)) {
+               gchar *match_start = NULL, *match_end = NULL, *ellipsis = NULL, *num_tokens = NULL;
+               gchar *str, *var_name, *sql_expression;
+               const TrackerGrammarRule *rule;
+               TrackerBinding *binding;
+               TrackerVariable *var;
+
+               rule = tracker_parser_node_get_rule (node);
+               if (!tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL,
+                                               TERMINAL_TYPE_PNAME_LN))
+                       continue;
+
+               str = _extract_node_string (node, sparql);
+
+               if (g_str_equal (str, FTS_NS "snippet")) {
+                       g_free (str);
+                       node = tracker_sparql_parser_tree_find_next (node, TRUE);
+               } else {
+                       g_free (str);
+                       continue;
+               }
+
+               if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS, NULL)) {
+                       _raise (PARSE, "Expected open parens", "fts:snippet");
+               }
+
+               var = _extract_node_variable (node, sparql);
+               if (var != subject)
+                       continue;
+
+               node = tracker_sparql_parser_tree_find_next (node, TRUE);
+
+               if (!extract_fts_snippet_parameters (sparql, node,
+                                                    &match_start,
+                                                    &match_end,
+                                                    &ellipsis,
+                                                    &num_tokens,
+                                                    error)) {
+                       g_free (match_start);
+                       g_free (match_end);
+                       g_free (ellipsis);
+                       g_free (num_tokens);
+                       return FALSE;
+               }
+
+               var_name = g_strdup_printf ("%s:ftsSnippet", subject->name);
+               var = _ensure_variable (sparql, var_name);
+               g_free (var_name);
+
+               sql_expression = g_strdup_printf ("snippet(\"%s\".\"fts5\", -1, '%s', '%s', '%s', %s)",
+                                                 table->sql_query_tablename,
+                                                 match_start ? match_start : "",
+                                                 match_end ? match_end : "",
+                                                 ellipsis ? ellipsis : "…",
+                                                 num_tokens ? num_tokens : "5");
+
+               binding = tracker_variable_binding_new (var, NULL, NULL);
+               tracker_binding_set_sql_expression (binding, sql_expression);
+               _add_binding (sparql, binding);
+               g_object_unref (binding);
+
+               g_free (sql_expression);
+               g_free (match_start);
+               g_free (match_end);
+               g_free (ellipsis);
+               g_free (num_tokens);
+               break;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+_add_quad (TrackerSparql  *sparql,
+           TrackerToken   *graph,
+          TrackerToken   *subject,
+          TrackerToken   *predicate,
+          TrackerToken   *object,
+          GError        **error)
+{
+       TrackerSelectContext *select_context;
+       TrackerTripleContext *triple_context;
+       TrackerOntologies *ontologies;
+       TrackerDataTable *table = NULL;
+       TrackerVariable *variable;
+       TrackerBinding *binding;
+       TrackerProperty *property = NULL;
+       TrackerClass *subject_type = NULL;
+       gboolean new_table = FALSE, is_fts = FALSE, is_rdf_type = FALSE;
+
+       select_context = TRACKER_SELECT_CONTEXT (sparql->current_state.select_context);
+       triple_context = TRACKER_TRIPLE_CONTEXT (sparql->current_state.context);
+       ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+
+       if (!tracker_token_get_variable (predicate)) {
+               gboolean share_table = TRUE;
+               const gchar *db_table;
+
+               property = tracker_ontologies_get_property_by_uri (ontologies,
+                                                                  tracker_token_get_literal (predicate));
+
+               if (tracker_token_is_empty (graph) &&
+                   !tracker_token_get_variable (object) &&
+                   g_strcmp0 (tracker_token_get_literal (predicate), RDF_NS "type") == 0) {
+                       /* rdf:type query */
+                       subject_type = tracker_ontologies_get_class_by_uri (ontologies,
+                                                                           tracker_token_get_literal 
(object));
+                       if (!subject_type) {
+                               g_set_error (error, TRACKER_SPARQL_ERROR,
+                                            TRACKER_SPARQL_ERROR_UNKNOWN_CLASS,
+                                            "Unknown class '%s'",
+                                            tracker_token_get_literal (object));
+                               return FALSE;
+                       }
+
+                       is_rdf_type = TRUE;
+                       db_table = tracker_class_get_name (subject_type);
+               } else if (g_strcmp0 (tracker_token_get_literal (predicate), FTS_NS "match") == 0) {
+                       db_table = "fts5";
+                       share_table = FALSE;
+                       is_fts = TRUE;
+               } else if (property != NULL) {
+                       db_table = tracker_property_get_table_name (property);
+
+                       if (tracker_token_get_variable (subject)) {
+                               GPtrArray *binding_list;
+
+                               variable = tracker_token_get_variable (subject);
+
+                               if (!tracker_token_get_variable (object) &&
+                                   g_strcmp0 (tracker_token_get_literal (predicate), RDFS_NS "domain") == 0) 
{
+                                       TrackerPredicateVariable *pred_var;
+                                       TrackerClass *domain;
+
+                                       /* rdfs:domain */
+                                       domain = tracker_ontologies_get_class_by_uri (ontologies,
+                                                                                     
tracker_token_get_literal (object));
+                                       if (!domain) {
+                                               g_set_error (error, TRACKER_SPARQL_ERROR,
+                                                            TRACKER_SPARQL_ERROR_UNKNOWN_CLASS,
+                                                            "Unknown class '%s'",
+                                                            tracker_token_get_literal (object));
+                                               return FALSE;
+                                       }
+
+                                       pred_var = tracker_select_context_lookup_predicate_variable 
(select_context,
+                                                                                                    
variable);
+                                       if (!pred_var) {
+                                               pred_var = tracker_predicate_variable_new ();
+                                               tracker_select_context_add_predicate_variable (select_context,
+                                                                                              variable,
+                                                                                              pred_var);
+                                       }
+
+                                       tracker_predicate_variable_set_domain (pred_var, domain);
+                               }
+
+                               binding_list = tracker_triple_context_lookup_variable_binding_list 
(triple_context,
+                                                                                                   variable);
+                               /* Domain specific index might be a possibility, let's check */
+                               if (binding_list) {
+                                       TrackerClass *domain_index = NULL;
+                                       TrackerClass **classes;
+                                       gint i = 0, j;
+
+                                       classes = tracker_property_get_domain_indexes (property);
+
+                                       while (!domain_index && classes[i]) {
+                                               for (j = 0; j < binding_list->len; j++) {
+                                                       TrackerVariableBinding *list_binding;
+
+                                                       list_binding = g_ptr_array_index (binding_list, j);
+                                                       if (list_binding->type != classes[i])
+                                                               continue;
+
+                                                       domain_index = classes[i];
+                                                       break;
+                                               }
+
+                                               i++;
+                                       }
+
+                                       if (domain_index)
+                                               db_table = tracker_class_get_name (domain_index);
+                               }
+                       }
+
+                       /* We can never share the table with multiple triples for
+                        * multi value properties as a property may consist of multiple rows.
+                        */
+                       share_table = !tracker_property_get_multiple_values (property);
+
+                       subject_type = tracker_property_get_domain (property);
+               } else if (property == NULL) {
+                       g_set_error (error, TRACKER_SPARQL_ERROR,
+                                    TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
+                                    "Unknown property '%s'",
+                                    tracker_token_get_literal (predicate));
+                       return FALSE;
+               }
+
+               if (share_table) {
+                       table = tracker_triple_context_lookup_table (triple_context,
+                                                                    tracker_token_get_idstring (subject),
+                                                                    db_table);
+               }
+
+               if (!table) {
+                       table = tracker_triple_context_add_table (triple_context,
+                                                                 tracker_token_get_idstring (subject),
+                                                                 db_table);
+                       new_table = TRUE;
+               }
+       } else {
+               TrackerPredicateVariable *pred_var;
+
+               /* Variable in predicate */
+               variable = tracker_token_get_variable (predicate);
+               table = tracker_triple_context_add_table (triple_context,
+                                                         variable->name, variable->name);
+               new_table = TRUE;
+
+               pred_var = tracker_select_context_lookup_predicate_variable (select_context,
+                                                                            variable);
+               if (!pred_var) {
+                       pred_var = tracker_predicate_variable_new ();
+                       tracker_predicate_variable_set_triple_details (pred_var,
+                                                                      tracker_token_get_literal (subject),
+                                                                      tracker_token_get_literal (object),
+                                                                      !tracker_token_is_empty (graph));
+
+                       tracker_select_context_add_predicate_variable (select_context,
+                                                                      variable,
+                                                                      pred_var);
+               }
+
+               tracker_data_table_set_predicate_variable (table, pred_var);
+
+               /* Add to binding list */
+               binding = tracker_variable_binding_new (variable, NULL, table);
+               tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
+               tracker_binding_set_db_column_name (binding, "predicate");
+               _add_binding (sparql, binding);
+               g_object_unref (binding);
+       }
+
+       if (new_table) {
+               if (tracker_token_get_variable (subject)) {
+                       variable = tracker_token_get_variable (subject);
+                       binding = tracker_variable_binding_new (variable, subject_type, table);
+               } else {
+                       binding = tracker_literal_binding_new (tracker_token_get_literal (subject),
+                                                              table);
+               }
+
+               tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
+               tracker_binding_set_db_column_name (binding, is_fts ? "ROWID" : "ID");
+               _add_binding (sparql, binding);
+               g_object_unref (binding);
+       }
+
+       if (is_rdf_type) {
+               /* The type binding is already implicit in the data table */
+               return TRUE;
+       }
+
+       if (tracker_token_get_variable (object)) {
+               variable = tracker_token_get_variable (object);
+               binding = tracker_variable_binding_new (variable,
+                                                       property ? tracker_property_get_range (property) : 
NULL,
+                                                       table);
+
+               if (tracker_token_get_variable (predicate)) {
+                       tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_STRING);
+                       tracker_binding_set_db_column_name (binding, "object");
+                       tracker_variable_binding_set_nullable (TRACKER_VARIABLE_BINDING (binding), TRUE);
+               } else {
+                       g_assert (property != NULL);
+                       tracker_binding_set_data_type (binding, tracker_property_get_data_type (property));
+                       tracker_binding_set_db_column_name (binding, tracker_property_get_name (property));
+
+                       if (!tracker_property_get_multiple_values (property)) {
+                               /* For single value properties, row may have NULL
+                                * in any column except the ID column
+                                */
+                               tracker_variable_binding_set_nullable (TRACKER_VARIABLE_BINDING (binding), 
TRUE);
+                       }
+
+                       if (tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME) {
+                               gchar *date_var, *sql_expression, *local_date, *local_time;
+                               TrackerBinding *local_time_binding;
+
+                               /* Merge localDate/localTime into $var:local */
+                               date_var = g_strdup_printf ("%s:local", variable->name);
+                               variable = _ensure_variable (sparql, date_var);
+
+                               local_date = tracker_binding_get_extra_sql_expression (binding, "localDate");
+                               local_time = tracker_binding_get_extra_sql_expression (binding, "localTime");
+                               sql_expression = g_strdup_printf ("((%s * 24 * 3600) + %s)",
+                                                                 local_date, local_time);
+
+                               local_time_binding = tracker_variable_binding_new (variable, NULL, NULL);
+                               tracker_binding_set_sql_expression (local_time_binding,
+                                                                   sql_expression);
+                               _add_binding (sparql, local_time_binding);
+                               g_object_unref (local_time_binding);
+
+                               g_free (sql_expression);
+                               g_free (local_date);
+                               g_free (local_time);
+                               g_free (date_var);
+                       }
+               }
+
+               _add_binding (sparql, binding);
+               g_object_unref (binding);
+       } else if (is_fts) {
+               binding = tracker_literal_binding_new (tracker_token_get_literal (object), table);
+               tracker_binding_set_db_column_name (binding, "fts5");
+               _add_binding (sparql, binding);
+               g_object_unref (binding);
+
+               if (tracker_token_get_variable (subject)) {
+                       gchar *var_name, *sql_expression;
+                       TrackerVariable *fts_var;
+
+                       variable = tracker_token_get_variable (subject);
+
+                       /* FTS rank */
+                       var_name = g_strdup_printf ("%s:ftsRank", variable->name);
+                       fts_var = _ensure_variable (sparql, var_name);
+                       g_free (var_name);
+
+                       binding = tracker_variable_binding_new (fts_var, NULL, table);
+                       tracker_binding_set_db_column_name (binding, "rank");
+                       _add_binding (sparql, binding);
+                       g_object_unref (binding);
+
+                       /* FTS offsets */
+                       var_name = g_strdup_printf ("%s:ftsOffsets", variable->name);
+                       fts_var = _ensure_variable (sparql, var_name);
+                       g_free (var_name);
+
+                       sql_expression = g_strdup_printf ("tracker_offsets(\"%s\".\"fts5\")",
+                                                         table->sql_query_tablename);
+                       binding = tracker_variable_binding_new (fts_var, NULL, NULL);
+                       tracker_binding_set_sql_expression (binding, sql_expression);
+                       _add_binding (sparql, binding);
+                       g_object_unref (binding);
+                       g_free (sql_expression);
+
+                       /* FTS snippet */
+                       if (!introspect_fts_snippet (sparql, variable,
+                                                    table, triple_context, error)) {
+                               return FALSE;
+                       }
+               }
+       } else {
+               binding = tracker_literal_binding_new (tracker_token_get_literal (object),
+                                                      table);
+
+               if (tracker_token_get_variable (predicate)) {
+                       tracker_binding_set_db_column_name (binding, "object");
+               } else {
+                       g_assert (property != NULL);
+                       tracker_binding_set_data_type (binding, tracker_property_get_data_type (property));
+                       tracker_binding_set_db_column_name (binding, tracker_property_get_name (property));
+               }
+
+               _add_binding (sparql, binding);
+               g_object_unref (binding);
+       }
+
+       if (!tracker_token_is_empty (graph)) {
+               if (tracker_token_get_variable (graph)) {
+                       variable = tracker_token_get_variable (graph);
+                       binding = tracker_variable_binding_new (variable, NULL, table);
+                       tracker_variable_binding_set_nullable (TRACKER_VARIABLE_BINDING (binding), TRUE);
+               } else {
+                       binding = tracker_literal_binding_new (tracker_token_get_literal (graph), table);
+               }
+
+               tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
+
+               if (tracker_token_get_variable (predicate)) {
+                       tracker_binding_set_db_column_name (binding, "graph");
+               } else {
+                       gchar *column_name;
+
+                       g_assert (property != NULL);
+                       column_name = g_strdup_printf ("%s:graph", tracker_property_get_name (property));
+                       tracker_binding_set_db_column_name (binding, column_name);
+                       g_free (column_name);
+               }
+
+               _add_binding (sparql, binding);
+               g_object_unref (binding);
+       }
+
+       return TRUE;
+}
+
+static TrackerParserNode *
+_skip_rule (TrackerSparql *sparql,
+            guint          named_rule)
+{
+       TrackerParserNode *current, *iter, *next = NULL;
+
+       g_assert (_check_in_rule (sparql, named_rule));
+       current = iter = sparql->current_state.node;
+
+       while (iter) {
+               next = (TrackerParserNode *) g_node_next_sibling ((GNode *) iter);
+               if (next) {
+                       next = tracker_sparql_parser_tree_find_first (next, FALSE);
+                       break;
+               }
+
+               iter = (TrackerParserNode *) ((GNode *) iter)->parent;
+       }
+
+       sparql->current_state.node = next;
+
+       return current;
+}
+
+static void
+convert_expression_to_string (TrackerSparql       *sparql,
+                              TrackerPropertyType  type)
+{
+       switch (type) {
+       case TRACKER_PROPERTY_TYPE_STRING:
+       case TRACKER_PROPERTY_TYPE_INTEGER:
+               /* Nothing to convert. Do not use CAST to convert integers to
+                * strings as this breaks use of index when sorting by variable
+                * introduced in select expression
+                */
+               break;
+       case TRACKER_PROPERTY_TYPE_RESOURCE:
+               /* ID => Uri */
+               _prepend_string (sparql, "(SELECT Uri FROM Resource WHERE ID = ");
+               _append_string (sparql, ") ");
+               break;
+       case TRACKER_PROPERTY_TYPE_BOOLEAN:
+               _prepend_string (sparql, "CASE ");
+               _append_string (sparql, " WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE NULL END ");
+               break;
+       case TRACKER_PROPERTY_TYPE_DATE:
+               /* ISO 8601 format */
+               _prepend_string (sparql, "strftime (\"%%Y-%%m-%%d\", ");
+               _append_string (sparql, ", \"unixepoch\") ");
+               break;
+       case TRACKER_PROPERTY_TYPE_DATETIME:
+               /* ISO 8601 format */
+               _prepend_string (sparql, "SparqlFormatTime (");
+               _append_string (sparql, ") ");
+       default:
+               /* Let sqlite convert the expression to string */
+               _prepend_string (sparql, "CAST (");
+               _append_string (sparql, " AS TEXT) ");
+               break;
+       }
+}
+
+static TrackerClass **
+lookup_resource_types (TrackerSparql  *sparql,
+                      const gchar    *resource,
+                      gint           *n_types,
+                      GError        **error)
+{
+       TrackerOntologies *ontologies;
+       TrackerDBInterface *iface;
+       TrackerDBStatement *stmt;
+       TrackerDBCursor *cursor = NULL;
+       GError *inner_error = NULL;
+       GPtrArray *types;
+       gint resource_id;
+
+       if (n_types)
+               *n_types = 0;
+
+       ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+       iface = tracker_data_manager_get_writable_db_interface (sparql->data_manager);
+       resource_id = tracker_data_query_resource_id (sparql->data_manager,
+                                                     iface, resource);
+
+       /* This is not an error condition, query might refer to an unknown resource */
+       if (resource_id <= 0)
+               return NULL;
+
+       stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, 
&inner_error,
+                                                     "SELECT (SELECT Uri FROM Resource WHERE ID = 
\"rdf:type\") "
+                                                     "FROM \"rdfs:Resource_rdf:type\" WHERE ID = ?");
+       if (!stmt) {
+               g_propagate_error (error, inner_error);
+               return NULL;
+       }
+
+       tracker_db_statement_bind_int (stmt, 0, resource_id);
+       cursor = tracker_db_statement_start_cursor (stmt, error);
+       g_object_unref (stmt);
+
+       if (!cursor) {
+               g_propagate_error (error, inner_error);
+               return NULL;
+       }
+
+       types = g_ptr_array_new ();
+
+       while (tracker_db_cursor_iter_next (cursor, NULL, &inner_error)) {
+               TrackerClass *type;
+
+               type = tracker_ontologies_get_class_by_uri (ontologies,
+                                                           tracker_db_cursor_get_string (cursor, 0, NULL));
+               g_ptr_array_add (types, type);
+       }
+
+       g_object_unref (cursor);
+
+       if (inner_error) {
+               g_propagate_error (error, inner_error);
+               g_ptr_array_unref (types);
+               return NULL;
+       }
+
+       if (n_types)
+               *n_types = types->len;
+
+       return (TrackerClass **) g_ptr_array_free (types, FALSE);
+}
+
+static gboolean
+append_predicate_variable_query (TrackerSparql             *sparql,
+                                TrackerPredicateVariable  *pred_var,
+                                GError                   **error)
+{
+       TrackerOntologies *ontologies;
+       TrackerProperty **properties;
+       TrackerStringBuilder *str, *old;
+       TrackerClass **types;
+       TrackerBinding *binding = NULL;
+       gint n_properties, n_types, i, j;
+       GError *inner_error = NULL;
+       gboolean first = TRUE;
+
+       ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+       properties = tracker_ontologies_get_properties (ontologies, &n_properties);
+
+       if (pred_var->subject) {
+               /* <s> ?p ?o */
+               types = lookup_resource_types (sparql, pred_var->subject, &n_types, &inner_error);
+               if (inner_error) {
+                       g_propagate_error (error, inner_error);
+                       return FALSE;
+               }
+
+               for (i = 0; i < n_types; i++) {
+                       for (j = 0; j < n_properties; j++) {
+                               if (types[i] != tracker_property_get_domain (properties[j]))
+                                       continue;
+
+                               if (!first)
+                                       _append_string (sparql, "UNION ALL ");
+
+                               first = FALSE;
+                               _append_string_printf (sparql,
+                                                      "SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') 
AS \"predicate\", ",
+                                                      tracker_property_get_uri (properties[j]));
+
+                               str = _append_placeholder (sparql);
+                               old = tracker_sparql_swap_builder (sparql, str);
+                               _append_string_printf (sparql, "\"%s\" ", tracker_property_get_name 
(properties[j]));
+                               convert_expression_to_string (sparql, tracker_property_get_data_type 
(properties[j]));
+                               tracker_sparql_swap_builder (sparql, old);
+
+                               _append_string (sparql, " AS \"object\" ");
+
+                               if (pred_var->return_graph) {
+                                       _append_string_printf (sparql, ", \"%s:graph\" AS \"graph\" ",
+                                                              tracker_property_get_name (properties[j]));
+                               }
+
+                               _append_string_printf (sparql, "FROM \"%s\" WHERE ID = ",
+                                                      tracker_property_get_table_name (properties[j]));
+
+                               if (!binding) {
+                                       binding = tracker_literal_binding_new (pred_var->subject, NULL);
+                                       tracker_binding_set_data_type (binding, 
TRACKER_PROPERTY_TYPE_RESOURCE);
+                                       tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT 
(sparql->context),
+                                                                                   TRACKER_LITERAL_BINDING 
(binding));
+                               }
+
+                               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+                       }
+               }
+
+               if (first) {
+                       /* No match */
+                       _append_string (sparql,
+                                       "SELECT NULL AS ID, NULL AS \"predicate\", NULL AS \"object\", NULL 
AS \"graph\"");
+               }
+
+               g_free (types);
+       } else if (pred_var->object) {
+               /* ?s ?p <o> */
+               types = lookup_resource_types (sparql, pred_var->object, &n_types, &inner_error);
+               if (inner_error) {
+                       g_propagate_error (error, inner_error);
+                       return FALSE;
+               }
+
+               for (i = 0; i < n_types; i++) {
+                       for (j = 0; j < n_properties; j++) {
+                               if (types[i] != tracker_property_get_range (properties[j]))
+                                       continue;
+
+                               if (!first)
+                                       _append_string (sparql, "UNION ALL ");
+
+                               first = FALSE;
+                               _append_string_printf (sparql,
+                                                      "SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') 
AS \"predicate\", ",
+                                                      tracker_property_get_uri (properties[j]));
+
+                               str = _append_placeholder (sparql);
+                               old = tracker_sparql_swap_builder (sparql, str);
+                               _append_string_printf (sparql, "\"%s\" ", tracker_property_get_name 
(properties[j]));
+                               convert_expression_to_string (sparql, tracker_property_get_data_type 
(properties[j]));
+                               tracker_sparql_swap_builder (sparql, old);
+
+                               _append_string (sparql, " AS \"object\" ");
+
+                               if (pred_var->return_graph) {
+                                       _append_string_printf (sparql,
+                                                              ", \"%s:graph\" AS \"graph\" ",
+                                                              tracker_property_get_name (properties[j]));
+                               }
+
+                               _append_string_printf (sparql, " FROM \"%s\" ",
+                                                      tracker_property_get_table_name (properties[j]));
+                       }
+               }
+
+               if (first) {
+                       /* No match */
+                       _append_string (sparql,
+                                       "SELECT NULL AS ID, NULL AS \"predicate\", NULL AS \"object\", NULL 
AS \"graph\" ");
+               }
+
+               g_free (types);
+       } else if (pred_var->domain) {
+               /* Any subject, predicates limited to a specific domain */
+
+               for (j = 0; j < n_properties; j++) {
+                       if (pred_var->domain != tracker_property_get_domain (properties[j]))
+                               continue;
+
+                       if (!first)
+                               _append_string (sparql, "UNION ALL ");
+
+                       first = FALSE;
+                       _append_string_printf (sparql,
+                                              "SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') AS 
\"predicate\", ",
+                                              tracker_property_get_uri (properties[j]));
+
+                       str = _append_placeholder (sparql);
+                       old = tracker_sparql_swap_builder (sparql, str);
+                       _append_string_printf (sparql, "\"%s\" ", tracker_property_get_name (properties[j]));
+                       convert_expression_to_string (sparql, tracker_property_get_data_type (properties[j]));
+                       tracker_sparql_swap_builder (sparql, old);
+
+                       _append_string (sparql, " AS \"object\" ");
+
+                       if (pred_var->return_graph) {
+                               _append_string_printf (sparql,
+                                                      ", \"%s:graph\" AS \"graph\" ",
+                                                      tracker_property_get_name (properties[j]));
+                       }
+
+                       _append_string_printf (sparql, "FROM \"%s\" ",
+                                              tracker_property_get_table_name (properties[j]));
+               }
+       } else {
+               /* ?s ?p ?o */
+               _unimplemented ("Unrestricted predicate variables are not supported");
+       }
+
+       return TRUE;
+}
+
+static TrackerContext *
+_begin_triples_block (TrackerSparql *sparql)
+{
+       TrackerContext *context;
+
+       context = tracker_triple_context_new ();
+       tracker_sparql_push_context (sparql, context);
+
+       return context;
+}
+
+static gboolean
+_end_triples_block (TrackerSparql  *sparql,
+                   GError        **error)
+{
+       TrackerTripleContext *triple_context;
+       TrackerStringBuilder *where_placeholder;
+       TrackerVariable *var;
+       TrackerContext *context;
+       GHashTableIter iter;
+       gboolean first = TRUE;
+       gint i;
+
+       context = sparql->current_state.context;
+       g_assert (TRACKER_IS_TRIPLE_CONTEXT (context));
+       triple_context = (TrackerTripleContext *) context;
+
+       /* Triple is empty */
+       if (triple_context->sql_tables->len == 0) {
+               tracker_sparql_pop_context (sparql, TRUE);
+               return TRUE;
+       }
+
+       _append_string (sparql, "SELECT ");
+       g_hash_table_iter_init (&iter, triple_context->variable_bindings);
+
+       /* Add select variables */
+       while (g_hash_table_iter_next (&iter, (gpointer *) &var, NULL)) {
+               TrackerBinding *binding;
+               GPtrArray *binding_list;
+
+               binding_list = tracker_triple_context_get_variable_binding_list (triple_context,
+                                                                                var);
+               if (!binding_list)
+                       continue;
+
+               if (!first)
+                       _append_string (sparql, ", ");
+
+               first = FALSE;
+               binding = g_ptr_array_index (binding_list, 0);
+               _append_string_printf (sparql, "%s AS %s ",
+                                      tracker_binding_get_sql_expression (binding),
+                                      tracker_variable_get_sql_expression (var));
+       }
+
+       if (first)
+               _append_string (sparql, "1 ");
+
+       _append_string (sparql, "FROM ");
+       first = TRUE;
+
+       /* Add tables */
+       for (i = 0; i < triple_context->sql_tables->len; i++) {
+               TrackerDataTable *table = g_ptr_array_index (triple_context->sql_tables, i);
+
+               if (!first)
+                       _append_string (sparql, ", ");
+
+               if (table->predicate_variable) {
+                       _append_string (sparql, "(");
+
+                       if (!append_predicate_variable_query (sparql,
+                                                             table->predicate_variable,
+                                                             error))
+                               return FALSE;
+
+                       _append_string (sparql, ") ");
+               } else {
+                       _append_string_printf (sparql, "\"%s\" ", table->sql_db_tablename);
+               }
+
+               _append_string_printf (sparql, "AS \"%s\" ", table->sql_query_tablename);
+               first = FALSE;
+       }
+
+       g_hash_table_iter_init (&iter, triple_context->variable_bindings);
+
+       where_placeholder = _append_placeholder (sparql);
+       first = TRUE;
+
+       /* Add variable bindings */
+       while (g_hash_table_iter_next (&iter, (gpointer *) &var, NULL)) {
+               GPtrArray *binding_list;
+               gboolean nullable = TRUE;
+               guint i;
+
+               binding_list = tracker_triple_context_lookup_variable_binding_list (triple_context,
+                                                                                   var);
+               if (!binding_list)
+                       continue;
+
+               for (i = 0; i < binding_list->len; i++) {
+                       const gchar *expression1, *expression2;
+                       TrackerBinding *binding1, *binding2;
+
+                       binding1 = g_ptr_array_index (binding_list, i);
+                       if (!tracker_variable_binding_get_nullable (TRACKER_VARIABLE_BINDING (binding1)))
+                               nullable = FALSE;
+
+                       if (i + 1 >= binding_list->len)
+                               break;
+
+                       if (!first)
+                               _append_string (sparql, "AND ");
+
+                       /* Concatenate each binding with the next */
+                       binding2 = g_ptr_array_index (binding_list, i + 1);
+                       expression1 = tracker_binding_get_sql_expression (binding1);
+                       expression2 = tracker_binding_get_sql_expression (binding2);
+
+                       if (binding1->data_type == TRACKER_PROPERTY_TYPE_STRING &&
+                           binding2->data_type == TRACKER_PROPERTY_TYPE_RESOURCE) {
+                               _append_string_printf (sparql,
+                                                      "(SELECT ID FROM Resource WHERE Uri = %s) ",
+                                                      expression1);
+                       } else {
+                               _append_string_printf (sparql, "%s ", expression1);
+                       }
+
+                       _append_string (sparql, "= ");
+
+                       if (binding1->data_type == TRACKER_PROPERTY_TYPE_RESOURCE &&
+                           binding2->data_type == TRACKER_PROPERTY_TYPE_STRING) {
+                               _append_string_printf (sparql,
+                                                      "(SELECT ID FROM Resource WHERE Uri = %s) ",
+                                                      expression2);
+                       } else {
+                               _append_string_printf (sparql, "%s ", expression2);
+                       }
+
+                       if (!tracker_variable_binding_get_nullable (TRACKER_VARIABLE_BINDING (binding1)) ||
+                           !tracker_variable_binding_get_nullable (TRACKER_VARIABLE_BINDING (binding2)))
+                               nullable = FALSE;
+
+                       first = FALSE;
+               }
+
+               if (nullable) {
+                       if (!first)
+                               _append_string (sparql, "AND ");
+                       _append_string_printf (sparql, "%s IS NOT NULL ",
+                                              tracker_variable_get_sql_expression (var));
+                       first = FALSE;
+               }
+       }
+
+       /* Add literal bindings */
+       for (i = 0; i < triple_context->literal_bindings->len; i++) {
+               TrackerBinding *binding;
+
+               if (!first)
+                       _append_string (sparql, "AND ");
+
+               first = FALSE;
+               binding = g_ptr_array_index (triple_context->literal_bindings, i);
+               _append_string_printf (sparql, "%s = ", tracker_binding_get_sql_expression (binding));
+               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+       }
+
+       /* If we had any where clauses, prepend the 'WHERE' literal */
+       if (!first)
+               tracker_string_builder_append (where_placeholder, "WHERE ", -1);
+
+       tracker_sparql_pop_context (sparql, TRUE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_Query (TrackerSparql  *sparql,
+                 GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* Query ::= Prologue
+        *           ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery )
+        *           ValuesClause
+        */
+       _call_rule (sparql, NAMED_RULE_Prologue, error);
+
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_SelectQuery:
+       case NAMED_RULE_AskQuery:
+       case NAMED_RULE_ConstructQuery:
+       case NAMED_RULE_DescribeQuery:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       _call_rule (sparql, NAMED_RULE_ValuesClause, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_Update (TrackerSparql  *sparql,
+                  GError        **error)
+{
+       /* Update ::= Prologue ( Update1 ( ';' Update )? )?
+        */
+       _call_rule (sparql, NAMED_RULE_Prologue, error);
+
+       if (_check_in_rule (sparql, NAMED_RULE_Update1)) {
+               _call_rule (sparql, NAMED_RULE_Update1, error);
+
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON)) {
+                       _call_rule (sparql, NAMED_RULE_Update, error);
+               }
+       }
+
+       return TRUE;
+}
+
+static void
+tracker_sparql_add_select_var (TrackerSparql       *sparql,
+                              const gchar         *name,
+                              TrackerPropertyType  type)
+{
+       g_ptr_array_add (sparql->var_names, g_strdup (name));
+       g_array_append_val (sparql->var_types, type);
+}
+
+static gboolean
+translate_SelectClause (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       TrackerSelectContext *select_context;
+       TrackerStringBuilder *str, *old;
+       gboolean first = TRUE;
+
+       /* SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ 
| '*' )
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_SELECT);
+       _append_string (sparql, "SELECT ");
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT)) {
+               _append_string (sparql, "DISTINCT ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_REDUCED)) {
+               /* REDUCED is allowed to return the same amount of elements, so... *shrug* */
+       }
+
+       select_context = TRACKER_SELECT_CONTEXT (sparql->current_state.select_context);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GLOB)) {
+               TrackerVariable *var;
+               GHashTableIter iter;
+
+               g_hash_table_iter_init (&iter, select_context->variables);
+
+               while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &var)) {
+                       if (!first)
+                               _append_string (sparql, ", ");
+
+                       str = _append_placeholder (sparql);
+                       old = tracker_sparql_swap_builder (sparql, str);
+
+                       _append_string_printf (sparql, "%s ",
+                                              tracker_variable_get_sql_expression (var));
+
+                       if (sparql->current_state.select_context == sparql->context) {
+                               TrackerPropertyType prop_type;
+
+                               prop_type = TRACKER_BINDING (tracker_variable_get_sample_binding 
(var))->data_type;
+                               convert_expression_to_string (sparql, prop_type);
+                       }
+
+                       if (sparql->current_state.select_context == sparql->context)
+                               _append_string_printf (sparql, "AS \"%s\" ", var->name);
+
+                       tracker_sparql_swap_builder (sparql, old);
+                       first = FALSE;
+               }
+       } else {
+               do {
+                       TrackerVariable *var;
+                       TrackerBinding *binding;
+
+                       if (_check_in_rule (sparql, NAMED_RULE_Var)) {
+                               if (!first)
+                                       _append_string (sparql, ", ");
+
+                               _call_rule (sparql, NAMED_RULE_Var, error);
+                               var = _last_node_variable (sparql);
+
+                               if (!tracker_variable_has_bindings (var)) {
+                                       _raise (PARSE, "Undefined variable", var->name);
+                               }
+
+                               binding = TRACKER_BINDING (tracker_variable_get_sample_binding (var));
+
+                               str = _append_placeholder (sparql);
+                               old = tracker_sparql_swap_builder (sparql, str);
+
+                               _append_string_printf (sparql, "%s ",
+                                                      tracker_variable_get_sql_expression (var));
+
+                               select_context->type = binding->data_type;
+
+                               if (sparql->current_state.select_context == sparql->context) {
+                                       convert_expression_to_string (sparql, binding->data_type);
+                                       tracker_sparql_add_select_var (sparql, var->name, binding->data_type);
+                               }
+
+                               tracker_sparql_swap_builder (sparql, old);
+                       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+                               if (!first)
+                                       _append_string (sparql, ", ");
+
+                               str = _append_placeholder (sparql);
+                               old = tracker_sparql_swap_builder (sparql, str);
+                               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+                               if (sparql->current_state.select_context == sparql->context) {
+                                       convert_expression_to_string (sparql, 
sparql->current_state.expression_type);
+                                       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+                               }
+
+                               select_context->type = sparql->current_state.expression_type;
+
+                               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_AS);
+                               _call_rule (sparql, NAMED_RULE_Var, error);
+                               var = _last_node_variable (sparql);
+
+                               if (sparql->current_state.select_context == sparql->context) {
+                                       _append_string_printf (sparql, "AS \"%s\" ", var->name);
+                               } else {
+                                       binding = tracker_variable_binding_new (var, NULL, NULL);
+                                       tracker_binding_set_data_type (binding, 
sparql->current_state.expression_type);
+                                       tracker_variable_set_sample_binding (var, TRACKER_VARIABLE_BINDING 
(binding));
+                                       _append_string_printf (sparql, "AS %s ",
+                                                              tracker_variable_get_sql_expression (var));
+                               }
+
+                               tracker_sparql_swap_builder (sparql, old);
+                               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+                       } else {
+                               break;
+                       }
+
+                       first = FALSE;
+               } while (TRUE);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_Prologue (TrackerSparql  *sparql,
+                    GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* Prologue ::= ( BaseDecl | PrefixDecl )*
+        */
+       rule = _current_rule (sparql);
+
+       while (rule == NAMED_RULE_BaseDecl || rule == NAMED_RULE_PrefixDecl) {
+               _call_rule (sparql, rule, error);
+               rule = _current_rule (sparql);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_BaseDecl (TrackerSparql  *sparql,
+                    GError        **error)
+{
+       /* BaseDecl ::= 'BASE' IRIREF
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BASE);
+
+       /* FIXME: BASE is unimplemented, and we never raised an error */
+
+       _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_IRIREF);
+
+       return TRUE;
+}
+
+static gboolean
+translate_PrefixDecl (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       gchar *ns, *uri;
+
+       /* PrefixDecl ::= 'PREFIX' PNAME_NS IRIREF
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_PREFIX);
+
+       _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PNAME_NS);
+       ns = _dup_last_string (sparql);
+
+       _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_IRIREF);
+       uri = _dup_last_string (sparql);
+
+       g_hash_table_insert (sparql->prefix_map, ns, uri);
+
+       return TRUE;
+}
+
+static gboolean
+_check_undefined_variables (TrackerSparql         *sparql,
+                            TrackerSelectContext  *context,
+                            GError               **error)
+{
+       TrackerVariable *variable;
+       GHashTableIter iter;
+
+       if (!context->variables)
+               return TRUE;
+
+       g_hash_table_iter_init (&iter, context->variables);
+
+       while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &variable)) {
+               if (!tracker_variable_has_bindings (variable)) {
+                       _raise (PARSE, "Use of undefined variable", variable->name);
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+_postprocess_rule (TrackerSparql         *sparql,
+                   TrackerParserNode     *node,
+                   TrackerStringBuilder  *str,
+                   GError               **error)
+{
+       TrackerStringBuilder *old_str;
+       TrackerParserNode *old_node;
+       const TrackerGrammarRule *rule;
+
+       old_node = sparql->current_state.node;
+       sparql->current_state.node = node;
+       if (str)
+               old_str = tracker_sparql_swap_builder (sparql, str);
+
+       rule = tracker_parser_node_get_rule (node);
+       g_assert (rule->type == RULE_TYPE_RULE);
+       _call_rule (sparql, rule->data.rule, error);
+       sparql->current_state.node = old_node;
+
+       if (str)
+               tracker_sparql_swap_builder (sparql, old_str);
+
+       return TRUE;
+}
+
+static gboolean
+translate_SelectQuery (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       TrackerParserNode *select_clause;
+       TrackerStringBuilder *str;
+
+       /* SelectQuery ::= SelectClause DatasetClause* WhereClause SolutionModifier
+        */
+       sparql->context = g_object_ref_sink (tracker_select_context_new ());
+       sparql->current_state.select_context = sparql->context;
+       tracker_sparql_push_context (sparql, sparql->context);
+
+       /* Skip select clause here */
+       str = _append_placeholder (sparql);
+       select_clause = _skip_rule (sparql, NAMED_RULE_SelectClause);
+
+       while (_check_in_rule (sparql, NAMED_RULE_DatasetClause)) {
+               _call_rule (sparql, NAMED_RULE_DatasetClause, error);
+       }
+
+       _call_rule (sparql, NAMED_RULE_WhereClause, error);
+
+       if (!_check_undefined_variables (sparql, TRACKER_SELECT_CONTEXT (sparql->context), error))
+               return FALSE;
+
+       /* Now that we have all variable/binding information available,
+        * process the select clause.
+        */
+       if (!_postprocess_rule (sparql, select_clause, str, error))
+               return FALSE;
+
+       _call_rule (sparql, NAMED_RULE_SolutionModifier, error);
+
+       tracker_sparql_pop_context (sparql, FALSE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_SubSelect (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       TrackerContext *context, *prev;
+       TrackerStringBuilder *str;
+       TrackerParserNode *select_clause;
+
+       /* SubSelect ::= SelectClause WhereClause SolutionModifier ValuesClause
+        */
+       context = tracker_select_context_new ();
+       prev = sparql->current_state.select_context;
+       sparql->current_state.select_context = context;
+       tracker_sparql_push_context (sparql, context);
+
+       /* Skip select clause here */
+       str = _append_placeholder (sparql);
+       select_clause = _skip_rule (sparql, NAMED_RULE_SelectClause);
+
+       _call_rule (sparql, NAMED_RULE_WhereClause, error);
+
+       /* Now that we have all variable/binding information available,
+        * process the select clause.
+        */
+       if (!_postprocess_rule (sparql, select_clause, str, error))
+               return FALSE;
+
+       _call_rule (sparql, NAMED_RULE_SolutionModifier, error);
+       _call_rule (sparql, NAMED_RULE_ValuesClause, error);
+
+       sparql->current_state.expression_type = TRACKER_SELECT_CONTEXT (context)->type;
+       tracker_sparql_pop_context (sparql, FALSE);
+       sparql->current_state.select_context = prev;
+
+       return TRUE;
+}
+
+static gboolean
+translate_ConstructQuery (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       _unimplemented ("CONSTRUCT");
+}
+
+static gboolean
+translate_DescribeQuery (TrackerSparql  *sparql,
+                         GError        **error)
+{
+       _unimplemented ("DESCRIBE");
+}
+
+static gboolean
+translate_AskQuery (TrackerSparql  *sparql,
+                    GError        **error)
+{
+       /* AskQuery ::= 'ASK' DatasetClause* WhereClause SolutionModifier
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_ASK);
+
+       sparql->context = g_object_ref_sink (tracker_select_context_new ());
+       sparql->current_state.select_context = sparql->context;
+       tracker_sparql_push_context (sparql, sparql->context);
+
+       _append_string (sparql, "SELECT CASE EXISTS (SELECT 1 ");
+
+       while (_check_in_rule (sparql, NAMED_RULE_DatasetClause)) {
+               _call_rule (sparql, NAMED_RULE_DatasetClause, error);
+       }
+
+       _call_rule (sparql, NAMED_RULE_WhereClause, error);
+       _call_rule (sparql, NAMED_RULE_SolutionModifier, error);
+
+       tracker_sparql_pop_context (sparql, FALSE);
+
+       _append_string (sparql, ") WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE NULL END");
+
+       return TRUE;
+}
+
+static gboolean
+translate_DatasetClause (TrackerSparql  *sparql,
+                         GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* DatasetClause ::= 'FROM' ( DefaultGraphClause | NamedGraphClause )
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_FROM);
+
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_DefaultGraphClause:
+       case NAMED_RULE_NamedGraphClause:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_DefaultGraphClause (TrackerSparql  *sparql,
+                              GError        **error)
+{
+       /* DefaultGraphClause ::= SourceSelector
+        */
+       _call_rule (sparql, NAMED_RULE_SourceSelector, error);
+
+       /* FIXME: FROM <graph> is unimplemented, and we never raised an error */
+
+       return TRUE;
+}
+
+static gboolean
+translate_NamedGraphClause (TrackerSparql  *sparql,
+                            GError        **error)
+{
+       /* NamedGraphClause ::= 'NAMED' SourceSelector
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_NAMED);
+       _call_rule (sparql, NAMED_RULE_SourceSelector, error);
+
+       /* FIXME: FROM NAMED <graph> is unimplemented, and we never raised an error */
+
+       return TRUE;
+}
+
+static gboolean
+translate_SourceSelector (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* SourceSelector ::= iri
+        */
+       _call_rule (sparql, NAMED_RULE_iri, error);
+       return TRUE;
+}
+
+static gboolean
+translate_WhereClause (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       TrackerStringBuilder *child, *old;
+
+       /* WhereClause ::= 'WHERE'? GroupGraphPattern
+        */
+       child = _append_placeholder (sparql);
+       old = tracker_sparql_swap_builder (sparql, child);
+       _accept (sparql, RULE_TYPE_LITERAL, LITERAL_WHERE);
+       _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+       if (!tracker_string_builder_is_empty (child)) {
+               _prepend_string (sparql, "FROM (");
+               _append_string (sparql, ") ");
+       }
+
+       tracker_sparql_swap_builder (sparql, old);
+
+       return TRUE;
+}
+
+static gboolean
+translate_SolutionModifier (TrackerSparql  *sparql,
+                            GError        **error)
+{
+       /* SolutionModifier ::= GroupClause? HavingClause? OrderClause? LimitOffsetClauses?
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_GroupClause)) {
+               _call_rule (sparql, NAMED_RULE_GroupClause, error);
+       }
+
+       if (_check_in_rule (sparql, NAMED_RULE_HavingClause)) {
+               _call_rule (sparql, NAMED_RULE_HavingClause, error);
+       }
+
+       if (_check_in_rule (sparql, NAMED_RULE_OrderClause)) {
+               _call_rule (sparql, NAMED_RULE_OrderClause, error);
+       }
+
+       if (_check_in_rule (sparql, NAMED_RULE_LimitOffsetClauses)) {
+               _call_rule (sparql, NAMED_RULE_LimitOffsetClauses, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_GroupClause (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       /* GroupClause ::= 'GROUP' 'BY' GroupCondition+
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GROUP);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BY);
+       _append_string (sparql, "GROUP BY ");
+
+       while (_check_in_rule (sparql, NAMED_RULE_GroupCondition)) {
+               _call_rule (sparql, NAMED_RULE_GroupCondition, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_GroupCondition (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* GroupCondition ::= BuiltInCall | FunctionCall | '(' Expression ( 'AS' Var )? ')' | Var
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_AS)) {
+                       _unimplemented ("AS in GROUP BY");
+               }
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       } else {
+               TrackerGrammarNamedRule rule;
+               TrackerVariable *variable;
+
+               rule = _current_rule (sparql);
+
+               switch (rule) {
+               case NAMED_RULE_Var:
+                       _call_rule (sparql, rule, error);
+                       variable = _last_node_variable (sparql);
+                       _append_variable_sql (sparql, variable);
+                       break;
+               case NAMED_RULE_BuiltInCall:
+               case NAMED_RULE_FunctionCall:
+                       _call_rule (sparql, rule, error);
+                       break;
+               default:
+                       g_assert_not_reached ();
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_HavingClause (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* HavingClause ::= 'HAVING' HavingCondition+
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_HAVING);
+       _append_string (sparql, "HAVING ");
+
+       while (_check_in_rule (sparql, NAMED_RULE_HavingCondition)) {
+               _call_rule (sparql, NAMED_RULE_HavingCondition, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_HavingCondition (TrackerSparql  *sparql,
+                           GError        **error)
+{
+       /* HavingCondition ::= Constraint
+        */
+       _call_rule (sparql, NAMED_RULE_Constraint, error);
+       return TRUE;
+}
+
+static gboolean
+translate_OrderClause (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       gboolean first = TRUE;
+
+       /* OrderClause ::= 'ORDER' 'BY' OrderCondition+
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_ORDER);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BY);
+       _append_string (sparql, "ORDER BY ");
+
+       while (_check_in_rule (sparql, NAMED_RULE_OrderCondition)) {
+               if (!first)
+                       _append_string (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_OrderCondition, error);
+               first = FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_OrderCondition (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       TrackerStringBuilder *str, *old;
+       const gchar *order_str = NULL;
+
+       str = _append_placeholder (sparql);
+       old = tracker_sparql_swap_builder (sparql, str);
+
+       /* OrderCondition ::= ( ( 'ASC' | 'DESC' ) BrackettedExpression )
+        *                    | ( Constraint | Var )
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ASC)) {
+               _call_rule (sparql, NAMED_RULE_BrackettedExpression, error);
+               order_str = "ASC ";
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DESC)) {
+               _call_rule (sparql, NAMED_RULE_BrackettedExpression, error);
+               order_str = "DESC ";
+       } else if (_check_in_rule (sparql, NAMED_RULE_Constraint)) {
+               _call_rule (sparql, NAMED_RULE_Constraint, error);
+       } else if (_check_in_rule (sparql, NAMED_RULE_Var)) {
+               TrackerVariableBinding *binding;
+               TrackerVariable *variable;
+
+               _call_rule (sparql, NAMED_RULE_Var, error);
+
+               variable = _last_node_variable (sparql);
+               _append_variable_sql (sparql, variable);
+
+               binding = tracker_variable_get_sample_binding (variable);
+               sparql->current_state.expression_type = TRACKER_BINDING (binding)->data_type;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       if (sparql->current_state.expression_type == TRACKER_PROPERTY_TYPE_STRING)
+               _append_string (sparql, "COLLATE " TRACKER_COLLATION_NAME " ");
+       else if (sparql->current_state.expression_type == TRACKER_PROPERTY_TYPE_RESOURCE)
+               convert_expression_to_string (sparql, sparql->current_state.expression_type);
+
+       tracker_sparql_swap_builder (sparql, old);
+
+       if (order_str)
+               _append_string (sparql, order_str);
+
+       return TRUE;
+}
+
+static gboolean
+translate_LimitOffsetClauses (TrackerSparql  *sparql,
+                              GError        **error)
+{
+       TrackerBinding *limit = NULL, *offset = NULL;
+
+       /* LimitOffsetClauses ::= LimitClause OffsetClause? | OffsetClause LimitClause?
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_LimitClause)) {
+               _call_rule (sparql, NAMED_RULE_LimitClause, error);
+               limit = _convert_terminal (sparql);
+
+               if (_check_in_rule (sparql, NAMED_RULE_OffsetClause)) {
+                       _call_rule (sparql, NAMED_RULE_OffsetClause, error);
+                       offset = _convert_terminal (sparql);
+               }
+       } else if (_check_in_rule (sparql, NAMED_RULE_OffsetClause)) {
+               _call_rule (sparql, NAMED_RULE_OffsetClause, error);
+               offset = _convert_terminal (sparql);
+
+               if (_check_in_rule (sparql, NAMED_RULE_LimitClause)) {
+                       _call_rule (sparql, NAMED_RULE_LimitClause, error);
+                       limit = _convert_terminal (sparql);
+               }
+       } else {
+               g_assert_not_reached ();
+       }
+
+       if (limit) {
+               _append_string (sparql, "LIMIT ");
+               tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                           TRACKER_LITERAL_BINDING (limit));
+               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (limit));
+               g_object_unref (limit);
+       }
+
+       if (offset) {
+               _append_string (sparql, "OFFSET ");
+               tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                           TRACKER_LITERAL_BINDING (offset));
+               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (offset));
+               g_object_unref (offset);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_LimitClause (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       /* LimitClause ::= 'LIMIT' INTEGER
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_LIMIT);
+       _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER);
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+
+       return TRUE;
+}
+
+static gboolean
+translate_OffsetClause (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* OffsetClause ::= 'OFFSET' INTEGER
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OFFSET);
+       _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER);
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+
+       return TRUE;
+}
+
+static gboolean
+translate_ValuesClause (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* ValuesClause ::= ( 'VALUES' DataBlock )?
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_VALUES)) {
+               _unimplemented ("VALUES");
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_Update1 (TrackerSparql  *sparql,
+                   GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* Update1 ::= Load | Clear | Drop | Add | Move | Copy | Create | InsertData | DeleteData | 
DeleteWhere | Modify
+        */
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_Load:
+       case NAMED_RULE_Clear:
+       case NAMED_RULE_Drop:
+       case NAMED_RULE_Add:
+       case NAMED_RULE_Move:
+       case NAMED_RULE_Copy:
+       case NAMED_RULE_Create:
+       case NAMED_RULE_InsertData:
+       case NAMED_RULE_DeleteData:
+       case NAMED_RULE_DeleteWhere:
+       case NAMED_RULE_Modify:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_Load (TrackerSparql  *sparql,
+                GError        **error)
+{
+       _unimplemented ("LOAD");
+}
+
+static gboolean
+translate_Clear (TrackerSparql  *sparql,
+                 GError        **error)
+{
+       _unimplemented ("CLEAR");
+}
+
+static gboolean
+translate_Drop (TrackerSparql  *sparql,
+                GError        **error)
+{
+       _unimplemented ("DROP");
+}
+
+static gboolean
+translate_Create (TrackerSparql  *sparql,
+                  GError        **error)
+{
+       _unimplemented ("CREATE");
+}
+
+static gboolean
+translate_Add (TrackerSparql  *sparql,
+               GError        **error)
+{
+       _unimplemented ("ADD");
+}
+
+static gboolean
+translate_Move (TrackerSparql  *sparql,
+                GError        **error)
+{
+       _unimplemented ("MOVE");
+}
+
+static gboolean
+translate_Copy (TrackerSparql  *sparql,
+                GError        **error)
+{
+       _unimplemented ("COPY");
+}
+
+static gboolean
+translate_InsertData (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* InsertData ::= 'INSERT DATA' QuadData
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_INSERT);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DATA);
+
+       _call_rule (sparql, NAMED_RULE_QuadData, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_DeleteData (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* DeleteData ::= 'DELETE DATA' QuadData
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DELETE);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DATA);
+
+       _call_rule (sparql, NAMED_RULE_QuadData, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_DeleteWhere (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       /* DeleteWhere ::= 'DELETE WHERE' QuadPattern
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DELETE);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_WHERE);
+
+       _call_rule (sparql, NAMED_RULE_QuadPattern, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_Modify (TrackerSparql  *sparql,
+                  GError        **error)
+{
+       /* Modify ::= ( 'WITH' iri )? ( DeleteClause InsertClause? | InsertClause ) UsingClause* 'WHERE' 
GroupGraphPattern
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_WITH)) {
+               _call_rule (sparql, NAMED_RULE_iri, error);
+       }
+
+       if (_check_in_rule (sparql, NAMED_RULE_DeleteClause)) {
+               _call_rule (sparql, NAMED_RULE_DeleteClause, error);
+       }
+
+       if (_check_in_rule (sparql, NAMED_RULE_InsertClause)) {
+               _call_rule (sparql, NAMED_RULE_InsertClause, error);
+       }
+
+       while (_check_in_rule (sparql, NAMED_RULE_UsingClause)) {
+               _call_rule (sparql, NAMED_RULE_UsingClause, error);
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_WHERE);
+
+       _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_DeleteClause (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* DeleteClause ::= 'DELETE' QuadPattern
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DELETE);
+       _call_rule (sparql, NAMED_RULE_QuadPattern, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_InsertClause (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* InsertClause ::= 'INSERT' QuadPattern
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_INSERT);
+       _call_rule (sparql, NAMED_RULE_QuadPattern, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_UsingClause (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       /* UsingClause ::= 'USING' ( iri | 'NAMED' iri )
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_USING);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_NAMED)) {
+       }
+
+       _call_rule (sparql, NAMED_RULE_iri, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_GraphOrDefault (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* GraphOrDefault ::= 'DEFAULT' | 'GRAPH'? iri
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DEFAULT)) {
+
+       } else {
+               _accept (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+               _call_rule (sparql, NAMED_RULE_iri, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_GraphRefAll (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       /* GraphRefAll ::= GraphRef | 'DEFAULT' | 'NAMED' | 'ALL'
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DEFAULT) ||
+           _accept (sparql, RULE_TYPE_LITERAL, LITERAL_NAMED) ||
+           _accept (sparql, RULE_TYPE_LITERAL, LITERAL_ALL)) {
+       } else {
+               _call_rule (sparql, NAMED_RULE_GraphRef, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_GraphRef (TrackerSparql  *sparql,
+                    GError        **error)
+{
+       /* GraphRef ::= 'GRAPH' iri
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+       _call_rule (sparql, NAMED_RULE_iri, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_QuadPattern (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       /* QuadPattern ::= '{' Quads '}'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+       _call_rule (sparql, NAMED_RULE_Quads, error);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_QuadData (TrackerSparql  *sparql,
+                    GError        **error)
+{
+       /* QuadData ::= '{' Quads '}'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+       _call_rule (sparql, NAMED_RULE_Quads, error);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_Quads (TrackerSparql  *sparql,
+                 GError        **error)
+{
+       /* Quads ::= TriplesTemplate? ( QuadsNotTriples '.'? TriplesTemplate? )*
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+               _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+       }
+
+       while (_check_in_rule (sparql, NAMED_RULE_QuadsNotTriples)) {
+               _call_rule (sparql, NAMED_RULE_QuadsNotTriples, error);
+
+               _accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT);
+
+               if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+                       _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_QuadsNotTriples (TrackerSparql  *sparql,
+                           GError        **error)
+{
+       TrackerToken old_graph;
+
+       /* QuadsNotTriples ::= 'GRAPH' VarOrIri '{' TriplesTemplate? '}'
+        */
+       old_graph = sparql->current_state.graph;
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+
+       _call_rule (sparql, NAMED_RULE_VarOrIri, error);
+       _init_token (&sparql->current_state.graph,
+                    sparql->current_state.prev_node, sparql);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+       if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+               _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+       tracker_token_unset (&sparql->current_state.graph);
+       sparql->current_state.graph = old_graph;
+
+       return TRUE;
+}
+
+static gboolean
+translate_TriplesTemplate (TrackerSparql  *sparql,
+                           GError        **error)
+{
+       /* TriplesTemplate ::= TriplesSameSubject ( '.' TriplesTemplate? )?
+        */
+       _call_rule (sparql, NAMED_RULE_TriplesSameSubject, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT)) {
+               if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+                       _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_GroupGraphPatternSub (TrackerSparql  *sparql,
+                                GError        **error)
+{
+       TrackerStringBuilder *child, *old;
+       TrackerParserNode *root;
+
+       /* GroupGraphPatternSub ::= TriplesBlock? ( GraphPatternNotTriples '.'? TriplesBlock? )*
+        */
+       root = (TrackerParserNode *) ((GNode *) sparql->current_state.node)->parent;
+       child = _append_placeholder (sparql);
+       old = tracker_sparql_swap_builder (sparql, child);
+
+       if (_check_in_rule (sparql, NAMED_RULE_TriplesBlock)) {
+               _begin_triples_block (sparql);
+               _call_rule (sparql, NAMED_RULE_TriplesBlock, error);
+               if (!_end_triples_block (sparql, error))
+                       return FALSE;
+       }
+
+       while (_check_in_rule (sparql, NAMED_RULE_GraphPatternNotTriples)) {
+               /* XXX: In the older code there was a minor optimization for
+                * simple OPTIONAL {} clauses. Two cases where handled where the
+                * optional is added inside the triples block:
+                *
+                * 1) OPTIONAL { ?u <p> ?o }, where ?u would be already bound
+                *    in the non optional part, <p> is a single-valued property,
+                *    and ?o is unbound. The binding representing pred/obj would
+                *    be folded into the previous triples block, simply without:
+                *
+                *    [AND] "var" IS NOT NULL
+                *
+                * 2) OPTIONAL { ?u <p> ?o }, where ?o is bound in the non optional
+                *    part, <p> is an InverseFunctionalProperty and ?u is unbound.
+                *    The previous triples block select clause would contain:
+                *
+                *    SELECT ...,
+                *           (SELECT ID FROM "$prop_table" WHERE "$prop" = "$table_in_from_clause"."$prop") 
AS ...,
+                *           ...
+                *
+                *    i.e. the resource ID is obtained in a subquery.
+                *
+                *    The first one could be useful way more frequently than the
+                *    second, and both involved substantial complications to SQL
+                *    query preparation, so they have been left out at the moment.
+                */
+               _call_rule (sparql, NAMED_RULE_GraphPatternNotTriples, error);
+               _accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT);
+
+               if (_check_in_rule (sparql, NAMED_RULE_TriplesBlock)) {
+                       gboolean do_join;
+
+                       do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+                       if (do_join) {
+                               _prepend_string (sparql, "SELECT * FROM (");
+                               _append_string (sparql, ") NATURAL INNER JOIN (");
+                       }
+
+                       _begin_triples_block (sparql);
+                       _call_rule (sparql, NAMED_RULE_TriplesBlock, error);
+                       if (!_end_triples_block (sparql, error))
+                               return FALSE;
+
+                       if (do_join)
+                               _append_string (sparql, ") ");
+               }
+       }
+
+       /* Handle filters last, they apply to the pattern as a whole */
+       if (sparql->filter_clauses) {
+               GList *filters = sparql->filter_clauses;
+               gboolean first = TRUE;
+
+               while (filters) {
+                       TrackerParserNode *filter_node = filters->data;
+                       GList *elem = filters;
+
+                       filters = filters->next;
+
+                       if (!g_node_is_ancestor ((GNode *) root, (GNode *) filter_node))
+                               continue;
+
+                       if (first) {
+                               if (tracker_string_builder_is_empty (sparql->current_state.sql)) {
+                                       _prepend_string (sparql, "SELECT 1 ");
+                                       _append_string (sparql, "WHERE ");
+                               } else {
+                                       _prepend_string (sparql, "SELECT * FROM (");
+                                       _append_string (sparql, ") WHERE ");
+                               }
+                               first = FALSE;
+                       } else {
+                               _append_string (sparql, "AND ");
+                       }
+
+                       if (!_postprocess_rule (sparql, filter_node,
+                                               NULL, error))
+                               return FALSE;
+
+                       sparql->filter_clauses =
+                               g_list_delete_link (sparql->filter_clauses, elem);
+               }
+       }
+
+       tracker_sparql_swap_builder (sparql, old);
+
+       return TRUE;
+}
+
+static gboolean
+translate_TriplesBlock (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* TriplesBlock ::= TriplesSameSubjectPath ( '.' TriplesBlock? )?
+        */
+       _call_rule (sparql, NAMED_RULE_TriplesSameSubjectPath, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT)) {
+               if (_check_in_rule (sparql, NAMED_RULE_TriplesBlock)) {
+                       _call_rule (sparql, NAMED_RULE_TriplesBlock, error);
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_GraphPatternNotTriples (TrackerSparql  *sparql,
+                                  GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* GraphPatternNotTriples ::= GroupOrUnionGraphPattern | OptionalGraphPattern | MinusGraphPattern | 
GraphGraphPattern | ServiceGraphPattern | Filter | Bind | InlineData
+        */
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_GroupOrUnionGraphPattern:
+       case NAMED_RULE_OptionalGraphPattern:
+       case NAMED_RULE_MinusGraphPattern:
+       case NAMED_RULE_GraphGraphPattern:
+       case NAMED_RULE_ServiceGraphPattern:
+       case NAMED_RULE_Filter:
+       case NAMED_RULE_Bind:
+       case NAMED_RULE_InlineData:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_OptionalGraphPattern (TrackerSparql  *sparql,
+                                GError        **error)
+{
+       gboolean do_join;
+
+       /* OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern
+        */
+       do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPTIONAL);
+
+       if (do_join) {
+               _prepend_string (sparql, "SELECT * FROM (");
+               _append_string (sparql, ") NATURAL LEFT JOIN (");
+       }
+
+       _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+       if (do_join)
+               _append_string (sparql, ") ");
+
+       return TRUE;
+}
+
+static gboolean
+translate_GraphGraphPattern (TrackerSparql  *sparql,
+                             GError        **error)
+{
+       TrackerToken old_graph;
+       gboolean do_join;
+
+       /* GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern
+        */
+
+       do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+       if (do_join) {
+               _prepend_string (sparql, "SELECT * FROM (");
+               _append_string (sparql, ") NATURAL INNER JOIN (");
+       }
+
+       old_graph = sparql->current_state.graph;
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+       _call_rule (sparql, NAMED_RULE_VarOrIri, error);
+       _init_token (&sparql->current_state.graph,
+                    sparql->current_state.prev_node, sparql);
+       _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+       tracker_token_unset (&sparql->current_state.graph);
+       sparql->current_state.graph = old_graph;
+
+       if (do_join)
+               _append_string (sparql, ") ");
+
+       return TRUE;
+}
+
+static gboolean
+translate_ServiceGraphPattern (TrackerSparql  *sparql,
+                               GError        **error)
+{
+       /* ServiceGraphPattern ::= 'SERVICE' 'SILENT'? VarOrIri GroupGraphPattern
+        */
+       _unimplemented ("SERVICE");
+}
+
+static gboolean
+translate_Bind (TrackerSparql  *sparql,
+                GError        **error)
+{
+       TrackerStringBuilder *str, *old;
+       TrackerVariable *variable;
+       TrackerBinding *binding;
+       TrackerPropertyType type;
+
+       /* Bind ::= 'BIND' '(' Expression 'AS' Var ')'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BIND);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+
+       str = _prepend_placeholder (sparql);
+       old = tracker_sparql_swap_builder (sparql, str);
+
+       _append_string (sparql, "SELECT *, ");
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+       type = sparql->current_state.expression_type;
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_AS);
+       _call_rule (sparql, NAMED_RULE_Var, error);
+
+       variable = _last_node_variable (sparql);
+
+       if (tracker_variable_has_bindings (variable))
+               _raise (PARSE, "Expected undefined variable", "BIND");
+
+       _append_string_printf (sparql, "AS %s FROM (",
+                              tracker_variable_get_sql_expression (variable));
+
+       binding = tracker_variable_binding_new (variable, NULL, NULL);
+       tracker_binding_set_data_type (binding, type);
+       tracker_variable_set_sample_binding (variable, TRACKER_VARIABLE_BINDING (binding));
+
+       tracker_sparql_swap_builder (sparql, old);
+       _append_string (sparql, ") ");
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+
+       return TRUE;
+}
+
+static gboolean
+translate_InlineData (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* InlineData ::= 'VALUES' DataBlock
+        */
+       _unimplemented ("VALUES");
+}
+
+static gboolean
+translate_DataBlock (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* DataBlock ::= InlineDataOneVar | InlineDataFull
+        */
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_InlineDataOneVar:
+       case NAMED_RULE_InlineDataFull:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_InlineDataOneVar (TrackerSparql  *sparql,
+                            GError        **error)
+{
+       /* InlineDataOneVar ::= Var '{' DataBlockValue* '}'
+        */
+       _call_rule (sparql, NAMED_RULE_Var, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+       while (_check_in_rule (sparql, NAMED_RULE_DataBlockValue)) {
+               _call_rule (sparql, NAMED_RULE_DataBlockValue, error);
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_InlineDataFull (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* InlineDataFull ::= ( NIL | '(' Var* ')' ) '{' ( '(' DataBlockValue* ')' | NIL )* '}'
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+               while (_check_in_rule (sparql, NAMED_RULE_Var)) {
+                       _call_rule (sparql, NAMED_RULE_Var, error);
+               }
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       } else {
+               g_assert_not_reached ();
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+       do {
+               if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+               } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+                       while (_check_in_rule (sparql, NAMED_RULE_DataBlockValue)) {
+                               _call_rule (sparql, NAMED_RULE_DataBlockValue, error);
+                       }
+
+                       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               } else {
+                       break;
+               }
+       } while (TRUE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_DataBlockValue (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* DataBlockValue ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | 'UNDEF'
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UNDEF)) {
+               return TRUE;
+       }
+
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_iri:
+       case NAMED_RULE_RDFLiteral:
+       case NAMED_RULE_NumericLiteral:
+       case NAMED_RULE_BooleanLiteral:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_MinusGraphPattern (TrackerSparql  *sparql,
+                             GError        **error)
+{
+       /* MinusGraphPattern ::= 'MINUS' GroupGraphPattern
+        */
+       _unimplemented ("MINUS");
+}
+
+static void
+append_union_select_vars (TrackerSparql  *sparql,
+                          TrackerContext *context,
+                         GList          *vars)
+{
+       GList *l;
+
+       _append_string (sparql, "SELECT ");
+
+       if (vars == NULL)
+               _append_string (sparql, "* ");
+
+       for (l = vars; l; l = l->next) {
+               TrackerVariable *variable = l->data;
+
+               if (l != vars)
+                       _append_string (sparql, ", ");
+
+               if (!tracker_context_lookup_variable_ref (context, variable))
+                       _append_string (sparql, "NULL AS ");
+
+               _append_string_printf (sparql, "%s ",
+                                      tracker_variable_get_sql_expression (variable));
+       }
+
+       _append_string (sparql, "FROM (");
+}
+
+static gboolean
+translate_GroupOrUnionGraphPattern (TrackerSparql  *sparql,
+                                    GError        **error)
+{
+       TrackerContext *context;
+       GPtrArray *placeholders;
+       GList *vars, *c;
+       gint idx = 0;
+       gboolean do_join;
+
+       /* GroupOrUnionGraphPattern ::= GroupGraphPattern ( 'UNION' GroupGraphPattern )*
+        */
+       do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+       if (do_join) {
+               _prepend_string (sparql, "SELECT * FROM (");
+               _append_string (sparql, ") NATURAL INNER JOIN (");
+       }
+
+       placeholders = g_ptr_array_new ();
+       context = tracker_context_new ();
+       tracker_sparql_push_context (sparql, context);
+
+       do {
+               g_ptr_array_add (placeholders, _append_placeholder (sparql));
+
+               if (!_call_rule_func (sparql, NAMED_RULE_GroupGraphPattern, error)) {
+                       g_ptr_array_unref (placeholders);
+                       return FALSE;
+               }
+       } while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UNION));
+
+       vars = g_hash_table_get_keys (context->variable_set);
+
+       if (placeholders->len > 1) {
+               /* We are performing an union of multiple GroupGraphPattern,
+                * we must fix up the junction between all nested selects so we
+                * do UNION ALL on a common set of variables in a fixed
+                * order.
+                *
+                * If a variable is unused in the current subcontext
+                * it is defined as NULL.
+                */
+               for (c = context->children; c; c = c->next) {
+                       TrackerStringBuilder *str, *old;
+
+                       g_assert (idx < placeholders->len);
+                       str = g_ptr_array_index (placeholders, idx);
+                       old = tracker_sparql_swap_builder (sparql, str);
+
+                       if (c != context->children)
+                               _append_string (sparql, ") UNION ALL ");
+
+                       append_union_select_vars (sparql, c->data, vars);
+                       tracker_sparql_swap_builder (sparql, old);
+                       idx++;
+               }
+
+               _append_string (sparql, ") ");
+       }
+
+       tracker_sparql_pop_context (sparql, TRUE);
+       g_ptr_array_unref (placeholders);
+       g_list_free (vars);
+
+       if (do_join)
+               _append_string (sparql, ") ");
+
+       return TRUE;
+}
+
+static gboolean
+translate_Filter (TrackerSparql  *sparql,
+                  GError        **error)
+{
+       TrackerParserNode *node;
+
+       /* Filter ::= 'FILTER' Constraint
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_FILTER);
+       node = _skip_rule (sparql, NAMED_RULE_Constraint);
+       /* Add constraints to list for later processing */
+       sparql->filter_clauses = g_list_prepend (sparql->filter_clauses, node);
+
+       return TRUE;
+}
+
+static gboolean
+translate_Constraint (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* Constraint ::= BrackettedExpression | BuiltInCall | FunctionCall
+        */
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_BrackettedExpression:
+       case NAMED_RULE_BuiltInCall:
+       case NAMED_RULE_FunctionCall:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_FunctionCall (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* FunctionCall ::= iri ArgList
+        */
+       _call_rule (sparql, NAMED_RULE_iri, error);
+       return handle_function_call (sparql, error);
+}
+
+static gboolean
+translate_ArgList (TrackerSparql  *sparql,
+                   GError        **error)
+{
+       /* ArgList ::= NIL | '(' 'DISTINCT'? Expression ( ',' Expression )* ')'
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT)) {
+                       _unimplemented ("DISTINCT in ArgList");
+               }
+
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+               while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+                       const gchar *separator = ", ";
+
+                       if (sparql->current_state.expression_list_separator)
+                               separator = sparql->current_state.expression_list_separator;
+
+                       _append_string (sparql, separator);
+                       _call_rule (sparql, NAMED_RULE_Expression, error);
+               }
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_ExpressionList (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* ExpressionList ::= NIL | '(' Expression ( ',' Expression )* ')'
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+               _append_string (sparql, "() ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+               _append_string (sparql, "(");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+               while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+                       _append_string (sparql,
+                                       sparql->current_state.expression_list_separator);
+                       _call_rule (sparql, NAMED_RULE_Expression, error);
+               }
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_ConstructTemplate (TrackerSparql  *sparql,
+                             GError        **error)
+{
+       /* ConstructTemplate ::= '{' ConstructTriples? '}'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+       if (_check_in_rule (sparql, NAMED_RULE_ConstructTriples)) {
+               _call_rule (sparql, NAMED_RULE_ConstructTriples, error);
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_ConstructTriples (TrackerSparql  *sparql,
+                            GError        **error)
+{
+       /* ConstructTriples ::= TriplesSameSubject ( '.' ConstructTriples? )?
+        */
+       _call_rule (sparql, NAMED_RULE_TriplesSameSubject, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT)) {
+               if (_check_in_rule (sparql, NAMED_RULE_ConstructTriples)) {
+                       _call_rule (sparql, NAMED_RULE_ConstructTriples, error);
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_TriplesSameSubject (TrackerSparql  *sparql,
+                              GError        **error)
+{
+       TrackerToken old_subject = sparql->current_state.subject;
+       TrackerGrammarNamedRule rule;
+
+       /* TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
+        */
+       rule = _current_rule (sparql);
+
+       if (rule == NAMED_RULE_VarOrTerm) {
+               sparql->current_state.token = &sparql->current_state.subject;
+               _call_rule (sparql, rule, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+               _call_rule (sparql, NAMED_RULE_PropertyListNotEmpty, error);
+       } else if (rule == NAMED_RULE_TriplesNode) {
+               sparql->current_state.token = &sparql->current_state.subject;
+               _call_rule (sparql, rule, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+               _call_rule (sparql, NAMED_RULE_PropertyList, error);
+       }
+
+       tracker_token_unset (&sparql->current_state.subject);
+       sparql->current_state.subject = old_subject;
+
+       return TRUE;
+}
+
+static gboolean
+translate_GroupGraphPattern (TrackerSparql  *sparql,
+                             GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+       TrackerContext *context;
+
+       /* GroupGraphPattern ::= '{' ( SubSelect | GroupGraphPatternSub ) '}'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+       context = tracker_context_new ();
+       tracker_sparql_push_context (sparql, context);
+
+       rule = _current_rule (sparql);
+
+       if (rule == NAMED_RULE_SubSelect) {
+               _append_string (sparql, "(");
+               _call_rule (sparql, rule, error);
+               _append_string (sparql, ") ");
+       } else if (rule == NAMED_RULE_GroupGraphPatternSub) {
+               _call_rule (sparql, rule, error);
+       }
+
+       tracker_sparql_pop_context (sparql, TRUE);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_PropertyList (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* PropertyList ::= PropertyListNotEmpty?
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_PropertyListNotEmpty)) {
+               _call_rule (sparql, NAMED_RULE_PropertyListNotEmpty, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_PropertyListNotEmpty (TrackerSparql  *sparql,
+                                GError        **error)
+{
+       TrackerToken old_pred;
+
+       old_pred = sparql->current_state.predicate;
+
+       /* PropertyListNotEmpty ::= Verb ObjectList ( ';' ( Verb ObjectList )? )*
+        */
+       _call_rule (sparql, NAMED_RULE_Verb, error);
+       _init_token (&sparql->current_state.predicate,
+                    sparql->current_state.prev_node, sparql);
+
+       _call_rule (sparql, NAMED_RULE_ObjectList, error);
+       tracker_token_unset (&sparql->current_state.predicate);
+
+       while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON)) {
+               if (!_check_in_rule (sparql, NAMED_RULE_Verb))
+                       break;
+
+               _call_rule (sparql, NAMED_RULE_Verb, error);
+               _init_token (&sparql->current_state.predicate,
+                            sparql->current_state.prev_node, sparql);
+
+               _call_rule (sparql, NAMED_RULE_ObjectList, error);
+
+               tracker_token_unset (&sparql->current_state.predicate);
+       }
+
+       sparql->current_state.predicate = old_pred;
+
+       return TRUE;
+}
+
+static gboolean
+translate_Verb (TrackerSparql  *sparql,
+                GError        **error)
+{
+       /* Verb ::= VarOrIri | 'a'
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_A)) {
+       } else {
+               _call_rule (sparql, NAMED_RULE_VarOrIri, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_ObjectList (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* ObjectList ::= Object ( ',' Object )*
+        */
+       _call_rule (sparql, NAMED_RULE_Object, error);
+
+       while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+               _call_rule (sparql, NAMED_RULE_Object, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_Object (TrackerSparql  *sparql,
+                  GError        **error)
+{
+       /* Object ::= GraphNode
+        */
+       _call_rule (sparql, NAMED_RULE_GraphNode, error);
+       return TRUE;
+}
+
+static gboolean
+translate_TriplesSameSubjectPath (TrackerSparql  *sparql,
+                                  GError        **error)
+{
+       TrackerToken old_subject = sparql->current_state.subject;
+       TrackerGrammarNamedRule rule;
+
+       /* TriplesSameSubjectPath ::= VarOrTerm PropertyListPathNotEmpty | TriplesNodePath PropertyListPath
+        */
+       rule = _current_rule (sparql);
+
+       if (rule == NAMED_RULE_VarOrTerm) {
+               sparql->current_state.token = &sparql->current_state.subject;
+               _call_rule (sparql, rule, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+
+               _call_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty, error);
+       } else if (rule == NAMED_RULE_TriplesNodePath) {
+               sparql->current_state.token = &sparql->current_state.subject;
+               _call_rule (sparql, rule, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+
+               _call_rule (sparql, NAMED_RULE_PropertyListPath, error);
+       }
+
+       tracker_token_unset (&sparql->current_state.subject);
+       sparql->current_state.subject = old_subject;
+
+       return TRUE;
+}
+
+static gboolean
+translate_PropertyListPath (TrackerSparql  *sparql,
+                            GError        **error)
+{
+       /* PropertyListPath ::= PropertyListPathNotEmpty?
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty)) {
+               _call_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_PropertyListPathNotEmpty (TrackerSparql  *sparql,
+                                    GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+       TrackerToken old_predicate;
+
+       /* PropertyListPathNotEmpty ::= ( VerbPath | VerbSimple ) ObjectListPath ( ';' ( ( VerbPath | 
VerbSimple ) ObjectList )? )*
+        */
+       rule = _current_rule (sparql);
+       old_predicate = sparql->current_state.predicate;
+
+       if (rule == NAMED_RULE_VerbPath || rule == NAMED_RULE_VerbSimple) {
+               _call_rule (sparql, rule, error);
+       } else {
+               g_assert_not_reached ();
+       }
+
+       _call_rule (sparql, NAMED_RULE_ObjectListPath, error);
+
+       tracker_token_unset (&sparql->current_state.predicate);
+
+       while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON)) {
+               rule = _current_rule (sparql);
+
+               if (rule == NAMED_RULE_VerbPath || rule == NAMED_RULE_VerbSimple) {
+                       _call_rule (sparql, rule, error);
+               } else {
+                       break;
+               }
+
+               _call_rule (sparql, NAMED_RULE_ObjectList, error);
+               tracker_token_unset (&sparql->current_state.predicate);
+       }
+
+       sparql->current_state.predicate = old_predicate;
+
+       return TRUE;
+}
+
+static gboolean
+translate_VerbPath (TrackerSparql  *sparql,
+                    GError        **error)
+{
+       /* VerbPath ::= Path
+        */
+       _call_rule (sparql, NAMED_RULE_Path, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_VerbSimple (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* VerbSimple ::= Var
+        */
+       _call_rule (sparql, NAMED_RULE_Var, error);
+       _init_token (&sparql->current_state.predicate,
+                    sparql->current_state.prev_node, sparql);
+
+       return TRUE;
+}
+
+static gboolean
+translate_ObjectListPath (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* ObjectListPath ::= ObjectPath ( ',' ObjectPath )*
+        */
+       _call_rule (sparql, NAMED_RULE_ObjectPath, error);
+
+       while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+               _call_rule (sparql, NAMED_RULE_ObjectPath, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_ObjectPath (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* ObjectPath ::= GraphNodePath
+        */
+       _call_rule (sparql, NAMED_RULE_GraphNodePath, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_Path (TrackerSparql  *sparql,
+                GError        **error)
+{
+       /* Path ::= PathAlternative
+        */
+       _call_rule (sparql, NAMED_RULE_PathAlternative, error);
+       return TRUE;
+}
+
+static gboolean
+translate_PathAlternative (TrackerSparql  *sparql,
+                           GError        **error)
+{
+       /* PathAlternative ::= PathSequence ( '|' PathSequence )*
+        */
+       _call_rule (sparql, NAMED_RULE_PathSequence, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_PATH_ALTERNATIVE)) {
+               _unimplemented ("Property paths");
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_PathSequence (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* PathSequence ::= PathEltOrInverse ( '/' PathEltOrInverse )*
+        */
+       _call_rule (sparql, NAMED_RULE_PathEltOrInverse, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_PATH_SEQUENCE)) {
+               _unimplemented ("Property paths");
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_PathEltOrInverse (TrackerSparql  *sparql,
+                            GError        **error)
+{
+       /* PathEltOrInverse ::= PathElt | '^' PathElt
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_PATH_INVERSE)) {
+               _unimplemented ("Property paths");
+       }
+
+       _call_rule (sparql, NAMED_RULE_PathElt, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_PathElt (TrackerSparql  *sparql,
+                   GError        **error)
+{
+       /* PathElt ::= PathPrimary PathMod?
+        */
+       _call_rule (sparql, NAMED_RULE_PathPrimary, error);
+       _init_token (&sparql->current_state.predicate,
+                    sparql->current_state.prev_node, sparql);
+
+       if (_check_in_rule (sparql, NAMED_RULE_PathMod)) {
+               _call_rule (sparql, NAMED_RULE_PathMod, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_PathMod (TrackerSparql  *sparql,
+                   GError        **error)
+{
+       /* PathMod ::= '?' | '*' | '+'
+        */
+       _unimplemented ("Property paths");
+}
+
+static gboolean
+translate_PathPrimary (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       /* PathPrimary ::= iri | 'a' | '!' PathNegatedPropertySet | '(' Path ')'
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_NEG)) {
+               _call_rule (sparql, NAMED_RULE_PathNegatedPropertySet, error);
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+               _call_rule (sparql, NAMED_RULE_Path, error);
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_A)) {
+
+       } else if (_check_in_rule (sparql, NAMED_RULE_iri)) {
+               _call_rule (sparql, NAMED_RULE_iri, error);
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_PathNegatedPropertySet (TrackerSparql  *sparql,
+                                  GError        **error)
+{
+       /* PathNegatedPropertySet ::= PathOneInPropertySet | '(' ( PathOneInPropertySet ( '|' 
PathOneInPropertySet )* )? ')'
+        */
+       _unimplemented ("Property paths");
+}
+
+static gboolean
+translate_PathOneInPropertySet (TrackerSparql  *sparql,
+                                GError        **error)
+{
+       /* PathOneInPropertySet ::= iri | 'a' | '^' ( iri | 'a' )
+        */
+       _unimplemented ("Property paths");
+}
+
+static gboolean
+translate_Integer (TrackerSparql  *sparql,
+                   GError        **error)
+{
+       /* Integer ::= INTEGER
+        */
+       _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER);
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+
+       return TRUE;
+}
+
+static gboolean
+translate_TriplesNode (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* TriplesNode ::= Collection | BlankNodePropertyList
+        */
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_Collection:
+       case NAMED_RULE_BlankNodePropertyList:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_BlankNodePropertyList (TrackerSparql  *sparql,
+                                 GError        **error)
+{
+       TrackerToken old_subject = sparql->current_state.subject;
+       TrackerVariable *var;
+
+       /* BlankNodePropertyList ::= '[' PropertyListNotEmpty ']'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACKET);
+
+       var = tracker_select_context_add_generated_variable (TRACKER_SELECT_CONTEXT (sparql->context));
+       tracker_token_variable_init (&sparql->current_state.subject, var);
+       _call_rule (sparql, NAMED_RULE_PropertyListNotEmpty, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACKET);
+
+       /* Return the blank node subject through the token, if token is already
+        * the subject, doesn't need changing.
+        */
+       g_assert (sparql->current_state.token != NULL);
+
+       if (sparql->current_state.token != &sparql->current_state.subject) {
+               *sparql->current_state.token = sparql->current_state.subject;
+               sparql->current_state.subject = old_subject;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_TriplesNodePath (TrackerSparql  *sparql,
+                           GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* TriplesNodePath ::= CollectionPath | BlankNodePropertyListPath
+        */
+       rule = _current_rule (sparql);
+
+       if (rule == NAMED_RULE_CollectionPath) {
+               _call_rule (sparql, rule, error);
+       } else if (rule == NAMED_RULE_BlankNodePropertyListPath) {
+               _call_rule (sparql, rule, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_BlankNodePropertyListPath (TrackerSparql  *sparql,
+                                     GError        **error)
+{
+       TrackerToken old_subject = sparql->current_state.subject;
+       TrackerToken *token_location = sparql->current_state.token;
+       TrackerVariable *var;
+
+       /* BlankNodePropertyListPath ::= '[' PropertyListPathNotEmpty ']'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACKET);
+
+       var = tracker_select_context_add_generated_variable (TRACKER_SELECT_CONTEXT (sparql->context));
+       tracker_token_variable_init (&sparql->current_state.subject, var);
+       _call_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACKET);
+
+       tracker_token_unset (&sparql->current_state.subject);
+       sparql->current_state.subject = old_subject;
+
+       /* Return the blank node subject through the token */
+       g_assert (sparql->current_state.token != NULL);
+       tracker_token_unset (token_location);
+       tracker_token_variable_init (token_location, var);
+
+       return TRUE;
+}
+
+static gboolean
+translate_Collection (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* Collection ::= '(' GraphNode+ ')'
+        */
+       _unimplemented ("Collections are not supported");
+}
+
+static gboolean
+translate_CollectionPath (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* CollectionPath ::= '(' GraphNodePath+ ')'
+        */
+       _unimplemented ("Collections are not supported");
+}
+
+static gboolean
+translate_GraphNode (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       /* GraphNode ::= VarOrTerm | TriplesNode
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_VarOrTerm)) {
+               sparql->current_state.token = &sparql->current_state.object;
+               _call_rule (sparql, NAMED_RULE_VarOrTerm, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+       } else if (_check_in_rule (sparql, NAMED_RULE_TriplesNode)) {
+               sparql->current_state.token = &sparql->current_state.object;
+               _call_rule (sparql, NAMED_RULE_TriplesNode, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+       } else {
+               g_assert_not_reached ();
+       }
+
+       if (!_add_quad (sparql,
+                       &sparql->current_state.graph,
+                       &sparql->current_state.subject,
+                       &sparql->current_state.predicate,
+                       &sparql->current_state.object,
+                       error))
+               return FALSE;
+
+       tracker_token_unset (&sparql->current_state.object);
+
+       return TRUE;
+}
+
+static gboolean
+translate_GraphNodePath (TrackerSparql  *sparql,
+                         GError        **error)
+{
+       /* GraphNodePath ::= VarOrTerm | TriplesNodePath
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_VarOrTerm)) {
+               sparql->current_state.token = &sparql->current_state.object;
+               _call_rule (sparql, NAMED_RULE_VarOrTerm, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+       } else if (_check_in_rule (sparql, NAMED_RULE_TriplesNodePath)) {
+               sparql->current_state.token = &sparql->current_state.object;
+               _call_rule (sparql, NAMED_RULE_TriplesNodePath, error);
+               g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+       } else {
+               g_assert_not_reached ();
+       }
+
+       if (!_add_quad (sparql,
+                       &sparql->current_state.graph,
+                       &sparql->current_state.subject,
+                       &sparql->current_state.predicate,
+                       &sparql->current_state.object,
+                       error))
+               return FALSE;
+
+       tracker_token_unset (&sparql->current_state.object);
+
+       return TRUE;
+}
+
+static gboolean
+translate_VarOrTerm (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* VarOrTerm ::= Var | GraphTerm
+        */
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_Var:
+               _call_rule (sparql, rule, error);
+               g_assert (sparql->current_state.token != NULL);
+               _init_token (sparql->current_state.token,
+                            sparql->current_state.prev_node, sparql);
+               break;
+       case NAMED_RULE_GraphTerm:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_VarOrIri (TrackerSparql  *sparql,
+                    GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* VarOrIri ::= Var | iri
+        */
+       rule = _current_rule (sparql);
+
+       if (rule == NAMED_RULE_Var) {
+               _call_rule (sparql, rule, error);
+       } else if (rule == NAMED_RULE_iri) {
+               _call_rule (sparql, rule, error);
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_Var (TrackerSparql  *sparql,
+               GError        **error)
+{
+       /* Var ::= VAR1 | VAR2
+        */
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1) ||
+           _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2)) {
+               TrackerVariableBinding *binding;
+               TrackerVariable *var;
+
+               /* Ensure the variable is referenced in the context */
+               var = _extract_node_variable (sparql->current_state.prev_node,
+                                             sparql);
+
+               binding = tracker_variable_get_sample_binding (var);
+
+               if (binding)
+                       sparql->current_state.expression_type = TRACKER_BINDING (binding)->data_type;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_GraphTerm (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* GraphTerm ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+               return TRUE;
+       }
+
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_iri:
+       case NAMED_RULE_RDFLiteral:
+       case NAMED_RULE_NumericLiteral:
+       case NAMED_RULE_BooleanLiteral:
+               _call_rule (sparql, rule, error);
+               g_assert (sparql->current_state.token != NULL);
+               _init_token (sparql->current_state.token,
+                            sparql->current_state.prev_node, sparql);
+               break;
+       case NAMED_RULE_BlankNode:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_Expression (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       /* Expression ::= ConditionalOrExpression
+        */
+       _call_rule (sparql, NAMED_RULE_ConditionalOrExpression, error);
+
+       return TRUE;
+
+}
+
+static gboolean
+translate_ConditionalOrExpression (TrackerSparql  *sparql,
+                                   GError        **error)
+{
+       /* ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
+        */
+       _call_rule (sparql, NAMED_RULE_ConditionalAndExpression, error);
+
+       while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_OR)) {
+               if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+                       _raise (PARSE, "Expected boolean expression", "||");
+
+               _append_string (sparql, " OR ");
+               _call_rule (sparql, NAMED_RULE_ConditionalAndExpression, error);
+
+               if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+                       _raise (PARSE, "Expected boolean expression", "||");
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_ConditionalAndExpression (TrackerSparql  *sparql,
+                                    GError        **error)
+{
+       /* ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
+        */
+       _call_rule (sparql, NAMED_RULE_ValueLogical, error);
+
+       while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_AND)) {
+               if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+                       _raise (PARSE, "Expected boolean expression", "&&");
+
+               _append_string (sparql, " AND ");
+               _call_rule (sparql, NAMED_RULE_ValueLogical, error);
+
+               if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+                       _raise (PARSE, "Expected boolean expression", "&&");
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_ValueLogical (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* ValueLogical ::= RelationalExpression
+        */
+       _call_rule (sparql, NAMED_RULE_RelationalExpression, error);
+
+       return TRUE;
+}
+
+static gboolean
+translate_RelationalExpression (TrackerSparql  *sparql,
+                                GError        **error)
+{
+       const gchar *old_sep;
+
+       /* RelationalExpression ::= NumericExpression ( '=' NumericExpression | '!=' NumericExpression | '<' 
NumericExpression | '>' NumericExpression | '<=' NumericExpression | '>=' NumericExpression | 'IN' 
ExpressionList | 'NOT' 'IN' ExpressionList )?
+        */
+       _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_IN)) {
+               _append_string (sparql, "IN ");
+               old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+               tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_NOT)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OP_IN);
+               _append_string (sparql, "NOT IN ");
+               old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+               tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_EQ)) {
+               _append_string (sparql, " = ");
+               _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_NE)) {
+               _append_string (sparql, " != ");
+               _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_LT)) {
+               _append_string (sparql, " < ");
+               _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_GT)) {
+               _append_string (sparql, " > ");
+               _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_LE)) {
+               _append_string (sparql, " <= ");
+               _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_GE)) {
+               _append_string (sparql, " >= ");
+               _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_NumericExpression (TrackerSparql  *sparql,
+                             GError        **error)
+{
+       /* NumericExpression ::= AdditiveExpression
+        */
+       _call_rule (sparql, NAMED_RULE_AdditiveExpression, error);
+
+       return TRUE;
+}
+
+static gboolean
+maybe_numeric (TrackerPropertyType prop_type)
+{
+       return (prop_type == TRACKER_PROPERTY_TYPE_INTEGER ||
+               prop_type == TRACKER_PROPERTY_TYPE_DOUBLE ||
+               prop_type == TRACKER_PROPERTY_TYPE_DATE ||
+               prop_type == TRACKER_PROPERTY_TYPE_DATETIME ||
+               prop_type == TRACKER_PROPERTY_TYPE_UNKNOWN);
+}
+
+static gboolean
+translate_AdditiveExpression (TrackerSparql  *sparql,
+                              GError        **error)
+{
+       /* AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' 
MultiplicativeExpression | ( NumericLiteralPositive | NumericLiteralNegative ) ( ( '*' UnaryExpression ) | ( 
'/' UnaryExpression ) )* )*
+        */
+       _call_rule (sparql, NAMED_RULE_MultiplicativeExpression, error);
+
+       do {
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_PLUS)) {
+                       if (!maybe_numeric (sparql->current_state.expression_type))
+                               _raise (PARSE, "Expected numeric operand", "+");
+
+                       _append_string (sparql, " + ");
+                       _call_rule (sparql, NAMED_RULE_MultiplicativeExpression, error);
+
+                       if (!maybe_numeric (sparql->current_state.expression_type))
+                               _raise (PARSE, "Expected numeric operand", "+");
+               } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MINUS)) {
+                       if (!maybe_numeric (sparql->current_state.expression_type))
+                               _raise (PARSE, "Expected numeric operand", "-");
+                       _append_string (sparql, " - ");
+                       _call_rule (sparql, NAMED_RULE_MultiplicativeExpression, error);
+
+                       if (!maybe_numeric (sparql->current_state.expression_type))
+                               _raise (PARSE, "Expected numeric operand", "+");
+               } else if (_check_in_rule (sparql, NAMED_RULE_NumericLiteralPositive) ||
+                          _check_in_rule (sparql, NAMED_RULE_NumericLiteralNegative)) {
+                       if (!maybe_numeric (sparql->current_state.expression_type))
+                               _raise (PARSE, "Expected numeric operand", "multiplication/division");
+
+                       _call_rule (sparql, _current_rule (sparql), error);
+
+                       do {
+                               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MULT)) {
+                                       _append_string (sparql, " * ");
+                               } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_DIV)) {
+                                       _append_string (sparql, " / ");
+                               } else {
+                                       break;
+                               }
+
+                               _call_rule (sparql, NAMED_RULE_UnaryExpression, error);
+                               if (!maybe_numeric (sparql->current_state.expression_type))
+                                       _raise (PARSE, "Expected numeric operand", "multiplication/division");
+                       } while (TRUE);
+               } else {
+                       break;
+               }
+       } while (TRUE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_MultiplicativeExpression (TrackerSparql  *sparql,
+                                    GError        **error)
+{
+       /* MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
+        */
+       _call_rule (sparql, NAMED_RULE_UnaryExpression, error);
+
+       do {
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MULT)) {
+                       _append_string (sparql, " * ");
+               } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_DIV)) {
+                       _append_string (sparql, " / ");
+               } else {
+                       break;
+               }
+
+               _call_rule (sparql, NAMED_RULE_UnaryExpression, error);
+       } while (TRUE);
+
+       return TRUE;
+}
+
+static gboolean
+translate_UnaryExpression (TrackerSparql  *sparql,
+                           GError        **error)
+{
+       /* UnaryExpression ::= '!' PrimaryExpression
+        *                     | '+' PrimaryExpression
+        *                     | '-' PrimaryExpression
+        *                     | PrimaryExpression
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_NEG)) {
+               _append_string (sparql, "NOT (");
+               _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+               _append_string (sparql, ") ");
+
+               if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN) {
+                       _raise (PARSE, "Expected boolean expression", "UnaryExpression");
+               }
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_PLUS)) {
+               _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MINUS)) {
+               _append_string (sparql, "-(");
+               _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+               _append_string (sparql, ") ");
+       } else {
+               _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_PrimaryExpression (TrackerSparql  *sparql,
+                             GError        **error)
+{
+       TrackerSelectContext *select_context;
+       TrackerGrammarNamedRule rule;
+       TrackerBinding *binding;
+       TrackerVariable *variable;
+
+       /* PrimaryExpression ::= BrackettedExpression | BuiltInCall | iriOrFunction | RDFLiteral | 
NumericLiteral | BooleanLiteral | Var
+        */
+       rule = _current_rule (sparql);
+       select_context = TRACKER_SELECT_CONTEXT (sparql->context);
+
+       switch (rule) {
+       case NAMED_RULE_NumericLiteral:
+       case NAMED_RULE_BooleanLiteral:
+               _call_rule (sparql, rule, error);
+               binding = _convert_terminal (sparql);
+               tracker_select_context_add_literal_binding (select_context,
+                                                           TRACKER_LITERAL_BINDING (binding));
+               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+               g_object_unref (binding);
+               break;
+       case NAMED_RULE_Var:
+               _call_rule (sparql, rule, error);
+               variable = _last_node_variable (sparql);
+               _append_variable_sql (sparql, variable);
+
+               /* If the variable is bound, propagate the binding data type */
+               if (tracker_variable_has_bindings (variable)) {
+                       binding = TRACKER_BINDING (tracker_variable_get_sample_binding (variable));
+                       sparql->current_state.expression_type = binding->data_type;
+               }
+               break;
+       case NAMED_RULE_RDFLiteral:
+               _call_rule (sparql, rule, error);
+               binding = g_ptr_array_index (select_context->literal_bindings,
+                                            select_context->literal_bindings->len - 1);
+               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+               break;
+       case NAMED_RULE_BrackettedExpression:
+       case NAMED_RULE_BuiltInCall:
+       case NAMED_RULE_iriOrFunction:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+handle_property_function (TrackerSparql    *sparql,
+                         TrackerProperty  *property,
+                         GError          **error)
+{
+       if (tracker_property_get_multiple_values (property)) {
+               TrackerStringBuilder *str, *old;
+
+               _append_string (sparql, "(SELECT GROUP_CONCAT (");
+               str = _append_placeholder (sparql);
+               old = tracker_sparql_swap_builder (sparql, str);
+               _append_string_printf (sparql, "\"%s\"", tracker_property_get_name (property));
+               convert_expression_to_string (sparql, tracker_property_get_data_type (property));
+               tracker_sparql_swap_builder (sparql, old);
+
+               _append_string_printf (sparql, ", ',') FROM \"%s\" WHERE ID = ",
+                                      tracker_property_get_table_name (property));
+
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else {
+               _append_string_printf (sparql,
+                                      "(SELECT \"%s\" FROM \"%s\" WHERE ID = ",
+                                      tracker_property_get_name (property),
+                                      tracker_property_get_table_name (property));
+
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               sparql->current_state.expression_type = tracker_property_get_data_type (property);
+       }
+
+       _append_string (sparql, ") ");
+
+       return TRUE;
+}
+
+static gboolean
+handle_type_cast (TrackerSparql  *sparql,
+                 const gchar    *function,
+                 GError        **error)
+{
+       if (g_str_equal (function, XSD_NS "string")) {
+               _append_string (sparql, "CAST (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, "AS TEXT) ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (g_str_equal (function, XSD_NS "integer")) {
+               _append_string (sparql, "CAST (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, "AS INTEGER) ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, XSD_NS "double")) {
+               _append_string (sparql, "CAST (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, "AS REAL) ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else {
+               _raise (PARSE, "Unhandled cast conversion", function);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+handle_xpath_function (TrackerSparql  *sparql,
+                       const gchar    *function,
+                       GError        **error)
+{
+       if (g_str_equal (function, FN_NS "lower-case")) {
+               _append_string (sparql, "SparqlLowerCase (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+       } else if (g_str_equal (function, FN_NS "upper-case")) {
+               _append_string (sparql, "SparqlUpperCase (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+       } else if (g_str_equal (function, FN_NS "contains")) {
+               /* contains('A','B') => 'A' GLOB '*B*' */
+               _step (sparql);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "(");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, " GLOB '*' || ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _append_string (sparql, " || '*') ");
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (g_str_equal (function, FN_NS "starts-with")) {
+               gchar buf[6] = { 0 };
+               TrackerParserNode *node;
+
+               /* strstarts('A','B') => 'A' BETWEEN 'B' AND 'B\u0010fffd'
+                * 0010fffd always sorts last.
+                */
+
+               _step (sparql);
+               _append_string (sparql, "( ");
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, "BETWEEN ");
+
+               node = sparql->current_state.node;
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _append_string (sparql, "AND ");
+
+               /* Evaluate the same expression node again */
+               sparql->current_state.node = node;
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+               g_unichar_to_utf8 (TRACKER_COLLATION_LAST_CHAR, buf);
+               _append_string_printf (sparql, "|| '%s') ", buf);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (g_str_equal (function, FN_NS "ends-with")) {
+               /* strends('A','B') => 'A' GLOB '*B' */
+               _step (sparql);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "(");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, " GLOB '*' || ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (g_str_equal (function, FN_NS "substring")) {
+               _append_string (sparql, "SUBSTR (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (g_str_equal (function, FN_NS "concat")) {
+               const gchar *old_sep;
+
+               old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, " || ");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (g_str_equal (function, FN_NS "string-join")) {
+               _append_string (sparql, "SparqlStringJoin (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (g_str_equal (function, FN_NS "replace")) {
+               _append_string (sparql, "SparqlReplace (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (g_str_equal (function, FN_NS "year-from-dateTime")) {
+               _step (sparql);
+               if (!helper_translate_date (sparql, "%%Y", error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, FN_NS "month-from-dateTime")) {
+               _step (sparql);
+               if (!helper_translate_date (sparql, "%%m", error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, FN_NS "day-from-dateTime")) {
+               _step (sparql);
+               if (!helper_translate_date (sparql, "%%d", error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, FN_NS "hours-from-dateTime")) {
+               _step (sparql);
+               if (!helper_translate_time (sparql, TIME_FORMAT_HOURS, error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, FN_NS "minutes-from-dateTime")) {
+               _step (sparql);
+               if (!helper_translate_time (sparql, TIME_FORMAT_MINUTES, error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, FN_NS "seconds-from-dateTime")) {
+               _step (sparql);
+               if (!helper_translate_time (sparql, TIME_FORMAT_SECONDS, error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, FN_NS "timezone-from-dateTime")) {
+               TrackerVariable *variable;
+
+               _step (sparql);
+               _append_string (sparql, "( ");
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               variable = _last_node_variable (sparql);
+
+               if (!variable) {
+                       _raise (PARSE, "Expected variable", "fn:timezone-from-dateTime");
+               } else {
+                       _append_string_printf (sparql, " - %s ",
+                                              tracker_variable_get_sql_expression (variable));
+               }
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else {
+               _raise (PARSE, "Unknown XPath function", function);
+       }
+
+       return TRUE;
+}
+
+static TrackerVariable *
+find_fts_variable (TrackerSparql     *sparql,
+                   TrackerParserNode *node,
+                  const gchar       *suffix)
+{
+       TrackerParserNode *var = NULL;
+
+       node = tracker_sparql_parser_tree_find_next (node, TRUE);
+
+       if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS, NULL))
+               return NULL;
+
+       if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1, &var) ||
+           _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2, &var)) {
+               TrackerVariable *variable;
+               gchar *node_var, *full;
+
+               node_var = _extract_node_string (var, sparql);
+               full = g_strdup_printf ("%s:%s", node_var, suffix);
+               variable = _ensure_variable (sparql, full);
+               g_free (full);
+               g_free (node_var);
+
+               return variable;
+       }
+
+       return NULL;
+}
+
+static gboolean
+handle_custom_function (TrackerSparql  *sparql,
+                       const gchar    *function,
+                       GError        **error)
+{
+       TrackerVariable *variable;
+       TrackerParserNode *node;
+
+       if (g_str_equal (function, TRACKER_NS "case-fold")) {
+               _append_string (sparql, "SparqlCaseFold (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+       } else if (g_str_equal (function, TRACKER_NS "title-order")) {
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, "COLLATE " TRACKER_TITLE_COLLATION_NAME " ");
+       } else if (g_str_equal (function, TRACKER_NS "ascii-lower-case")) {
+               _append_string (sparql, "lower (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+       } else if (g_str_equal (function, TRACKER_NS "normalize")) {
+               _append_string (sparql, "SparqlNormalize (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+       } else if (g_str_equal (function, TRACKER_NS "unaccent")) {
+               _append_string (sparql, "SparqlUnaccent (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+       } else if (g_str_equal (function, TRACKER_NS "id")) {
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+
+               if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_RESOURCE)
+                       _raise (PARSE, "Expected resource", "tracker:id");
+
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, TRACKER_NS "uri")) {
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+
+               if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_INTEGER)
+                       _raise (PARSE, "Expected integer ID", "tracker:uri");
+
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_RESOURCE;
+       } else if (g_str_equal (function, TRACKER_NS "cartesian-distance")) {
+               _append_string (sparql, "SparqlCartesianDistance (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else if (g_str_equal (function, TRACKER_NS "haversine-distance")) {
+               _append_string (sparql, "SparqlHaversineDistance (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else if (g_str_equal (function, TRACKER_NS "uri-is-parent")) {
+               _append_string (sparql, "SparqlUriIsParent (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (g_str_equal (function, TRACKER_NS "uri-is-descendant")) {
+               _append_string (sparql, "SparqlUriIsDescendant (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (g_str_equal (function, TRACKER_NS "string-from-filename")) {
+               _append_string (sparql, "SparqlStringFromFilename (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (g_str_equal (function, TRACKER_NS "coalesce")) {
+               _append_string (sparql, "COALESCE (");
+               _call_rule (sparql, NAMED_RULE_ArgList, error);
+               _append_string (sparql, ") ");
+       } else if (g_str_equal (function, FTS_NS "rank")) {
+               node = _skip_rule (sparql, NAMED_RULE_ArgList);
+               variable = find_fts_variable (sparql, node, "ftsRank");
+               if (!variable)
+                       _raise (PARSE, "Function expects single variable argument", "fts:rank");
+
+               _append_variable_sql (sparql, variable);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (g_str_equal (function, FTS_NS "offsets")) {
+               node = _skip_rule (sparql, NAMED_RULE_ArgList);
+               variable = find_fts_variable (sparql, node, "ftsOffsets");
+               if (!variable || !tracker_variable_has_bindings (variable))
+                       _raise (PARSE, "Function expects single variable argument", "fts:offsets");
+
+               _append_variable_sql (sparql, variable);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (g_str_equal (function, FTS_NS "snippet")) {
+               node = _skip_rule (sparql, NAMED_RULE_ArgList);
+               variable = find_fts_variable (sparql, node, "ftsSnippet");
+               if (!variable || !tracker_variable_has_bindings (variable))
+                       _raise (PARSE, "Function expects variable argument", "fts:snippet");
+
+               _append_variable_sql (sparql, variable);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else {
+               _raise (PARSE, "Unknown function", function);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+handle_function_call (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       gchar *function = _dup_last_string (sparql);
+       gboolean handled;
+
+       if (g_str_has_prefix (function, XSD_NS)) {
+               handled = handle_type_cast (sparql, function, error);
+       } else if (g_str_has_prefix (function, FN_NS)) {
+               handled = handle_xpath_function (sparql, function, error);
+       } else {
+               TrackerOntologies *ontologies;
+               TrackerProperty *property;
+
+               ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+               property = tracker_ontologies_get_property_by_uri (ontologies, function);
+
+               if (property) {
+                       handled = handle_property_function (sparql, property, error);
+               } else {
+                       handled = handle_custom_function (sparql, function, error);
+               }
+       }
+
+       g_free (function);
+
+       return handled;
+}
+
+static gboolean
+translate_iriOrFunction (TrackerSparql  *sparql,
+                         GError        **error)
+{
+       gboolean handled = TRUE;
+
+       /* iriOrFunction ::= iri ArgList?
+        */
+       _call_rule (sparql, NAMED_RULE_iri, error);
+
+       if (_check_in_rule (sparql, NAMED_RULE_ArgList)) {
+               handled = handle_function_call (sparql, error);
+       } else {
+               TrackerBinding *binding;
+
+               binding = _convert_terminal (sparql);
+               tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                           TRACKER_LITERAL_BINDING (binding));
+               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+               g_object_unref (binding);
+       }
+
+       return handled;
+}
+
+static gboolean
+translate_BrackettedExpression (TrackerSparql  *sparql,
+                                GError        **error)
+{
+       /* BrackettedExpression ::= '(' Expression ')'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+
+       return TRUE;
+}
+
+static gboolean
+helper_translate_date (TrackerSparql  *sparql,
+                       const gchar    *format,
+                       GError        **error)
+{
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+       _append_string_printf (sparql, "strftime (\"%s\", ", format);
+
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       _append_string (sparql, ", \"unixepoch\") ");
+
+       return TRUE;
+}
+
+static gboolean
+helper_translate_time (TrackerSparql  *sparql,
+                       guint           format,
+                       GError        **error)
+{
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+
+       switch (format) {
+       case TIME_FORMAT_SECONDS:
+               _append_string (sparql, " % 60 ");
+               break;
+       case TIME_FORMAT_MINUTES:
+               _append_string (sparql, " / 60 % 60 ");
+               break;
+       case TIME_FORMAT_HOURS:
+               _append_string (sparql, " / 3600 % 24 ");
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_BuiltInCall (TrackerSparql  *sparql,
+                       GError        **error)
+{
+       const gchar *old_sep;
+
+       if (_check_in_rule (sparql, NAMED_RULE_Aggregate)) {
+               _call_rule (sparql, NAMED_RULE_Aggregate, error);
+       } else if (_check_in_rule (sparql, NAMED_RULE_RegexExpression)) {
+               _call_rule (sparql, NAMED_RULE_RegexExpression, error);
+       } else if (_check_in_rule (sparql, NAMED_RULE_ExistsFunc)) {
+               _call_rule (sparql, NAMED_RULE_ExistsFunc, error);
+       } else if (_check_in_rule (sparql, NAMED_RULE_NotExistsFunc)) {
+               _call_rule (sparql, NAMED_RULE_NotExistsFunc, error);
+       } else if (_check_in_rule (sparql, NAMED_RULE_SubstringExpression)) {
+               _call_rule (sparql, NAMED_RULE_SubstringExpression, error);
+       } else if (_check_in_rule (sparql, NAMED_RULE_StrReplaceExpression)) {
+               _call_rule (sparql, NAMED_RULE_StrReplaceExpression, error);
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STR)) {
+               TrackerStringBuilder *str, *old;
+
+               str = _append_placeholder (sparql);
+               old = tracker_sparql_swap_builder (sparql, str);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+
+               convert_expression_to_string (sparql, sparql->current_state.expression_type);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+               tracker_sparql_swap_builder (sparql, old);
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DATATYPE)) {
+               _unimplemented ("DATATYPE");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_IRI)) {
+               _unimplemented ("IRI");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_URI)) {
+               _unimplemented ("URI");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ABS)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "ABS (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_CEIL)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlCeil (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_FLOOR)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlFloor (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ROUND)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "ROUND (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRLEN)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "LENGTH (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UCASE)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlUpperCase (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_LCASE)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlLowerCase (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ENCODE_FOR_URI)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlEncodeForUri (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_YEAR)) {
+               if (!helper_translate_date (sparql, "%%Y", error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_MONTH)) {
+               if (!helper_translate_date (sparql, "%%m", error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DAY)) {
+               if (!helper_translate_date (sparql, "%%d", error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_HOURS)) {
+               if (!helper_translate_time (sparql, TIME_FORMAT_HOURS, error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_MINUTES)) {
+               if (!helper_translate_time (sparql, TIME_FORMAT_MINUTES, error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SECONDS)) {
+               if (!helper_translate_time (sparql, TIME_FORMAT_SECONDS, error))
+                       return FALSE;
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_TIMEZONE)) {
+               _unimplemented ("TIMEZONE");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_TZ)) {
+               _unimplemented ("TZ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_MD5)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlChecksum (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ", \"md5\") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA1)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlChecksum (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ", \"sha1\") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA256)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlChecksum (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ", \"sha256\") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA384)) {
+               _unimplemented ("SHA384");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA512)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlChecksum (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ", \"sha512\") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISIRI) ||
+                  _accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISURI)) {
+               TrackerBinding *binding;
+               const gchar *str;
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+               str = (sparql->current_state.expression_type == TRACKER_PROPERTY_TYPE_RESOURCE) ? "1" : "0";
+
+               binding = tracker_literal_binding_new (str, NULL);
+               tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                           TRACKER_LITERAL_BINDING (binding));
+               _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISBLANK)) {
+               _unimplemented ("ISBLANK");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISLITERAL)) {
+               _unimplemented ("ISLITERAL");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISNUMERIC)) {
+               _unimplemented ("ISNUMERIC");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_LANGMATCHES)) {
+               _unimplemented ("LANGMATCHES");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_CONTAINS)) {
+               /* contains('A','B') => 'A' GLOB '*B*' */
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "(");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, " GLOB '*' || ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _append_string (sparql, " || '*') ");
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRSTARTS)) {
+               gchar buf[6] = { 0 };
+               TrackerParserNode *node;
+
+               /* strstarts('A','B') => 'A' BETWEEN 'B' AND 'B\u0010fffd'
+                * 0010fffd always sorts last.
+                */
+               _append_string (sparql, "( ");
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, "BETWEEN ");
+
+               node = sparql->current_state.node;
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _append_string (sparql, "AND ");
+
+               /* Evaluate the same expression node again */
+               sparql->current_state.node = node;
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+               g_unichar_to_utf8 (TRACKER_COLLATION_LAST_CHAR, buf);
+               _append_string_printf (sparql, "|| '%s') ", buf);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRENDS)) {
+               /* strends('A','B') => 'A' GLOB '*B' */
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "(");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, " GLOB '*' || ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRBEFORE)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlStringBefore (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRAFTER)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "SparqlStringAfter (");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRLANG)) {
+               _unimplemented ("STRLANG");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRDT)) {
+               _unimplemented ("STRDT");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SAMETERM)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, " ( ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, " = ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, " ) ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_IF)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "CASE ");
+
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, "WHEN 1 THEN ");
+
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+               _append_string (sparql, "WHEN 0 THEN ");
+
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, "ELSE NULL END ");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_BOUND)) {
+               TrackerVariable *var;
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "(");
+               _call_rule (sparql, NAMED_RULE_Var, error);
+
+               var = _last_node_variable (sparql);
+               _append_string_printf (sparql, "%s ",
+                                      tracker_variable_get_sql_expression (var));
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, "IS NOT NULL) ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_BNODE)) {
+               _unimplemented ("BNODE");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_RAND)) {
+               _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+               _append_string (sparql, "SparqlRand() ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_NOW)) {
+               _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+               _append_string (sparql, "strftime('%%s', 'now') ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DATETIME;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UUID)) {
+               _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+               _unimplemented ("UUID");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRUUID)) {
+               _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+               _unimplemented ("STRUUID");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_CONCAT)) {
+               old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, " || ");
+               _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+               tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COALESCE)) {
+               _append_string (sparql, "COALESCE ");
+               old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+               tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_RegexExpression (TrackerSparql  *sparql,
+                           GError        **error)
+{
+       TrackerStringBuilder *str, *old;
+
+       /* RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_REGEX);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+       _append_string (sparql, "SparqlRegex (");
+
+       str = _append_placeholder (sparql);
+       old = tracker_sparql_swap_builder (sparql, str);
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+       convert_expression_to_string (sparql, sparql->current_state.expression_type);
+       tracker_sparql_swap_builder (sparql, old);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+       _append_string (sparql, ", ");
+
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+               _append_string (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       _append_string (sparql, ") ");
+
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+
+       return TRUE;
+}
+
+static gboolean
+translate_SubstringExpression (TrackerSparql  *sparql,
+                               GError        **error)
+{
+       /* SubstringExpression ::= 'SUBSTR' '(' Expression ',' Expression ( ',' Expression )? ')'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_SUBSTR);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+       _append_string (sparql, "SUBSTR (");
+
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+       _append_string (sparql, ", ");
+
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+               _append_string (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       _append_string (sparql, ") ");
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+
+       return TRUE;
+}
+
+static gboolean
+translate_StrReplaceExpression (TrackerSparql  *sparql,
+                                GError        **error)
+{
+       /* StrReplaceExpression ::= 'REPLACE' '(' Expression ',' Expression ',' Expression ( ',' Expression 
)? ')'
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_REPLACE);
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+       _append_string (sparql, "SparqlReplace (");
+
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+       _append_string (sparql, ", ");
+
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+       _append_string (sparql, ", ");
+
+       _call_rule (sparql, NAMED_RULE_Expression, error);
+
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+               _append_string (sparql, ", ");
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+       }
+
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+       _append_string (sparql, ") ");
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+
+       return TRUE;
+}
+
+static gboolean
+translate_ExistsFunc (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       TrackerContext *context;
+
+       /* ExistsFunc ::= 'EXISTS' GroupGraphPattern
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_EXISTS);
+       _append_string (sparql, "EXISTS (");
+
+       context = tracker_select_context_new ();
+       tracker_sparql_push_context (sparql, context);
+
+       _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+       tracker_sparql_pop_context (sparql, FALSE);
+
+       if (!_check_undefined_variables (sparql, TRACKER_SELECT_CONTEXT (context), error))
+               return FALSE;
+
+       _append_string (sparql, ") ");
+
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+
+       return TRUE;
+}
+
+static gboolean
+translate_NotExistsFunc (TrackerSparql  *sparql,
+                         GError        **error)
+{
+       /* NotExistsFunc ::= 'NOT' 'EXISTS' GroupGraphPattern
+        */
+       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_NOT);
+       _append_string (sparql, "NOT ");
+
+       return translate_ExistsFunc (sparql, error);
+}
+
+static gboolean
+translate_Aggregate (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       /* Aggregate ::= 'COUNT' '(' 'DISTINCT'? ( '*' | Expression ) ')'
+        *               | 'SUM' '(' 'DISTINCT'? Expression ')'
+        *               | 'MIN' '(' 'DISTINCT'? Expression ')'
+        *               | 'MAX' '(' 'DISTINCT'? Expression ')'
+        *               | 'AVG' '(' 'DISTINCT'? Expression ')'
+        *               | 'SAMPLE' '(' 'DISTINCT'? Expression ')'
+        *               | 'GROUP_CONCAT' '(' 'DISTINCT'? Expression ( ';' 'SEPARATOR' '=' String )? ')'
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COUNT) ||
+           _accept (sparql, RULE_TYPE_LITERAL, LITERAL_SUM) ||
+           _accept (sparql, RULE_TYPE_LITERAL, LITERAL_MIN) ||
+           _accept (sparql, RULE_TYPE_LITERAL, LITERAL_MAX) ||
+           _accept (sparql, RULE_TYPE_LITERAL, LITERAL_AVG)) {
+               gchar *last_string = _dup_last_string (sparql);
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               /* Luckily the SQL literals are the same than Sparql's */
+               _append_string (sparql, last_string);
+               _append_string (sparql, "(");
+               g_free (last_string);
+
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT))
+                       _append_string (sparql, "DISTINCT ");
+
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GLOB)) {
+                       _append_string (sparql, "* ");
+               } else if (_check_in_rule (sparql, NAMED_RULE_Expression)) {
+                       _call_rule (sparql, NAMED_RULE_Expression, error);
+               }
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GROUP_CONCAT)) {
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+               _append_string (sparql, "GROUP_CONCAT(");
+
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT))
+                       _append_string (sparql, "DISTINCT ");
+
+               _call_rule (sparql, NAMED_RULE_Expression, error);
+
+               if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON)) {
+                       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_SEPARATOR);
+                       _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OP_EQ);
+                       _append_string (sparql, ", ");
+
+                       _call_rule (sparql, NAMED_RULE_String, error);
+               }
+
+               _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+               _append_string (sparql, ") ");
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SAMPLE)) {
+               _unimplemented ("SAMPLE");
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_RDFLiteral (TrackerSparql  *sparql,
+                      GError        **error)
+{
+       TrackerBinding *binding;
+
+       /* RDFLiteral ::= String ( LANGTAG | ( '^^' iri ) )?
+        */
+       _call_rule (sparql, NAMED_RULE_String, error);
+       binding = _convert_terminal (sparql);
+
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_LANGTAG)) {
+               g_object_unref (binding);
+               _unimplemented ("LANGTAG");
+       } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOUBLE_CIRCUMFLEX)) {
+               gchar *cast;
+
+               _call_rule (sparql, NAMED_RULE_iri, error);
+               cast = _dup_last_string (sparql);
+
+               if (g_str_equal (cast, XSD_NS "boolean")) {
+                       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+               } else if (g_str_equal (cast, XSD_NS "integer") ||
+                          g_str_equal (cast, XSD_NS "nonPositiveInteger") ||
+                          g_str_equal (cast, XSD_NS "negativeInteger") ||
+                          g_str_equal (cast, XSD_NS "long") ||
+                          g_str_equal (cast, XSD_NS "int") ||
+                          g_str_equal (cast, XSD_NS "short") ||
+                          g_str_equal (cast, XSD_NS "byte") ||
+                          g_str_equal (cast, XSD_NS "nonNegativeInteger") ||
+                          g_str_equal (cast, XSD_NS "unsignedLong") ||
+                          g_str_equal (cast, XSD_NS "unsignedInt") ||
+                          g_str_equal (cast, XSD_NS "unsignedShort") ||
+                          g_str_equal (cast, XSD_NS "unsignedByte") ||
+                          g_str_equal (cast, XSD_NS "positiveInteger")) {
+                       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+               } else if (g_str_equal (cast, XSD_NS "double")) {
+                       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+               } else if (g_str_equal (cast, XSD_NS "date")) {
+                       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DATE;
+               } else if (g_str_equal (cast, XSD_NS "dateTime")) {
+                       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DATETIME;
+               } else {
+                       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+               }
+
+               g_free (cast);
+       }
+
+       tracker_binding_set_data_type (binding, sparql->current_state.expression_type);
+       tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+                                                   TRACKER_LITERAL_BINDING (binding));
+
+       g_object_unref (binding);
+
+       return TRUE;
+}
+
+static gboolean
+translate_NumericLiteral (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       TrackerGrammarNamedRule rule;
+
+       /* NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
+        */
+       rule = _current_rule (sparql);
+
+       switch (rule) {
+       case NAMED_RULE_NumericLiteralUnsigned:
+       case NAMED_RULE_NumericLiteralPositive:
+       case NAMED_RULE_NumericLiteralNegative:
+               _call_rule (sparql, rule, error);
+               break;
+       default:
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_NumericLiteralUnsigned (TrackerSparql  *sparql,
+                                  GError        **error)
+{
+       /* NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DOUBLE) ||
+                  _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DECIMAL)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_NumericLiteralPositive (TrackerSparql  *sparql,
+                                  GError        **error)
+{
+       /* NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER_POSITIVE)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DECIMAL_POSITIVE) ||
+                  _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DOUBLE_POSITIVE)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_NumericLiteralNegative (TrackerSparql  *sparql,
+                                  GError        **error)
+{
+       /* NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER_NEGATIVE)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+       } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DECIMAL_NEGATIVE) ||
+                  _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DOUBLE_NEGATIVE)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_BooleanLiteral (TrackerSparql  *sparql,
+                          GError        **error)
+{
+       /* BooleanLiteral ::= 'true' | 'false'
+        */
+       if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_TRUE) ||
+           _accept (sparql, RULE_TYPE_LITERAL, LITERAL_FALSE)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+               return TRUE;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_String (TrackerSparql  *sparql,
+                  GError        **error)
+{
+       /* String ::= STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1) ||
+           _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2) ||
+           _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL_LONG1) ||
+           _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL_LONG2)) {
+               sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+               return TRUE;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_iri (TrackerSparql  *sparql,
+               GError        **error)
+{
+       /* iri ::= IRIREF | PrefixedName
+        */
+       if (_check_in_rule (sparql, NAMED_RULE_PrefixedName)) {
+               _call_rule (sparql, NAMED_RULE_PrefixedName, error);
+       } else {
+               _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_IRIREF);
+       }
+
+       sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_RESOURCE;
+
+       return TRUE;
+}
+
+static gboolean
+translate_PrefixedName (TrackerSparql  *sparql,
+                        GError        **error)
+{
+       /* PrefixedName ::= PNAME_LN | PNAME_NS
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PNAME_LN) ||
+           _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PNAME_NS)) {
+               return TRUE;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+static gboolean
+translate_BlankNode (TrackerSparql  *sparql,
+                     GError        **error)
+{
+       TrackerVariable *var;
+
+       /* BlankNode ::= BLANK_NODE_LABEL | ANON
+        */
+       if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_ANON)) {
+               g_assert (sparql->current_state.token != NULL);
+
+               var = tracker_select_context_add_generated_variable (TRACKER_SELECT_CONTEXT 
(sparql->context));
+               tracker_token_variable_init (sparql->current_state.token, var);
+       } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_BLANK_NODE_LABEL)) {
+               /* FIXME */
+               return TRUE;
+       } else {
+               g_assert_not_reached ();
+       }
+
+       return TRUE;
+}
+
+const RuleTranslationFunc rule_translation_funcs[N_NAMED_RULES] = {
+       NULL, /* Grammar parser entry points */
+       NULL,
+       translate_Query,
+       translate_Update,
+       translate_SelectClause,
+       translate_Prologue,
+       translate_BaseDecl,
+       translate_PrefixDecl,
+       translate_SelectQuery,
+       translate_SubSelect,
+       translate_ConstructQuery,
+       translate_DescribeQuery,
+       translate_AskQuery,
+       translate_DatasetClause,
+       translate_DefaultGraphClause,
+       translate_NamedGraphClause,
+       translate_SourceSelector,
+       translate_WhereClause,
+       translate_SolutionModifier,
+       translate_GroupClause,
+       translate_GroupCondition,
+       translate_HavingClause,
+       translate_HavingCondition,
+       translate_OrderClause,
+       translate_OrderCondition,
+       translate_LimitOffsetClauses,
+       translate_LimitClause,
+       translate_OffsetClause,
+       translate_ValuesClause,
+       translate_Update1,
+       translate_Load,
+       translate_Clear,
+       translate_Drop,
+       translate_Create,
+       translate_Add,
+       translate_Move,
+       translate_Copy,
+       translate_InsertData,
+       translate_DeleteData,
+       translate_DeleteWhere,
+       translate_Modify,
+       translate_DeleteClause,
+       translate_InsertClause,
+       translate_UsingClause,
+       translate_GraphOrDefault,
+       translate_GraphRefAll,
+       translate_GraphRef,
+       translate_QuadPattern,
+       translate_QuadData,
+       translate_Quads,
+       translate_QuadsNotTriples,
+       translate_TriplesTemplate,
+       translate_GroupGraphPatternSub,
+       translate_TriplesBlock,
+       translate_GraphPatternNotTriples,
+       translate_OptionalGraphPattern,
+       translate_GraphGraphPattern,
+       translate_ServiceGraphPattern,
+       translate_Bind,
+       translate_InlineData,
+       translate_DataBlock,
+       translate_InlineDataOneVar,
+       translate_InlineDataFull,
+       translate_DataBlockValue,
+       translate_MinusGraphPattern,
+       translate_GroupOrUnionGraphPattern,
+       translate_Filter,
+       translate_Constraint,
+       translate_FunctionCall,
+       translate_ArgList,
+       translate_ExpressionList,
+       translate_ConstructTemplate,
+       translate_ConstructTriples,
+       translate_TriplesSameSubject,
+       translate_GroupGraphPattern,
+       translate_PropertyList,
+       translate_PropertyListNotEmpty,
+       translate_Verb,
+       translate_ObjectList,
+       translate_Object,
+       translate_TriplesSameSubjectPath,
+       translate_PropertyListPath,
+       translate_PropertyListPathNotEmpty,
+       translate_VerbPath,
+       translate_VerbSimple,
+       translate_ObjectListPath,
+       translate_ObjectPath,
+       translate_Path,
+       translate_PathAlternative,
+       translate_PathSequence,
+       translate_PathEltOrInverse,
+       translate_PathElt,
+       translate_PathMod,
+       translate_PathPrimary,
+       translate_PathNegatedPropertySet,
+       translate_PathOneInPropertySet,
+       translate_Integer,
+       translate_TriplesNode,
+       translate_BlankNodePropertyList,
+       translate_TriplesNodePath,
+       translate_BlankNodePropertyListPath,
+       translate_Collection,
+       translate_CollectionPath,
+       translate_GraphNode,
+       translate_GraphNodePath,
+       translate_VarOrTerm,
+       translate_VarOrIri,
+       translate_Var,
+       translate_GraphTerm,
+       translate_Expression,
+       translate_ConditionalOrExpression,
+       translate_ConditionalAndExpression,
+       translate_ValueLogical,
+       translate_RelationalExpression,
+       translate_NumericExpression,
+       translate_AdditiveExpression,
+       translate_MultiplicativeExpression,
+       translate_UnaryExpression,
+       translate_PrimaryExpression,
+       translate_iriOrFunction,
+       translate_BrackettedExpression,
+       translate_BuiltInCall,
+       translate_RegexExpression,
+       translate_SubstringExpression,
+       translate_StrReplaceExpression,
+       translate_ExistsFunc,
+       translate_NotExistsFunc,
+       translate_Aggregate,
+       translate_RDFLiteral,
+       translate_NumericLiteral,
+       translate_NumericLiteralUnsigned,
+       translate_NumericLiteralPositive,
+       translate_NumericLiteralNegative,
+       translate_BooleanLiteral,
+       translate_String,
+       translate_iri,
+       translate_PrefixedName,
+       translate_BlankNode,
+};
+
+static inline gboolean
+_call_rule_func (TrackerSparql            *sparql,
+                 TrackerGrammarNamedRule   named_rule,
+                 GError                  **error)
+{
+       TrackerParserNode *parser_node = sparql->current_state.node;
+       const TrackerGrammarRule *rule;
+       GError *inner_error = NULL;
+       gboolean retval;
+
+       g_assert (named_rule < N_NAMED_RULES);
+       g_assert (rule_translation_funcs[named_rule]);
+
+       /* Empty rules pass */
+       if (!parser_node ||
+           !tracker_parser_node_get_extents (parser_node, NULL, NULL))
+               return TRUE;
+
+       rule = tracker_parser_node_get_rule (parser_node);
+
+       if (!tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, named_rule))
+               return TRUE;
+
+       tracker_sparql_iter_next (sparql);
+
+       retval = rule_translation_funcs[named_rule] (sparql, &inner_error);
+
+       if (!retval) {
+               if (!inner_error) {
+                       g_error ("Translation rule '%s' returns FALSE, but no error",
+                                rule->string);
+               }
+
+               g_assert (inner_error != NULL);
+               g_propagate_error (error, inner_error);
+       }
+
+       return retval;
+}
+
+static void
+tracker_sparql_class_init (TrackerSparqlClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_sparql_finalize;
+}
+
+static void
+tracker_sparql_init (TrackerSparql *sparql)
+{
+       sparql->prefix_map = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                   g_free, g_free);
+       sparql->var_names = g_ptr_array_new_with_free_func (g_free);
+       sparql->var_types = g_array_new (FALSE, FALSE, sizeof (TrackerPropertyType));
+}
+
+TrackerSparql*
+tracker_sparql_new (TrackerDataManager *manager,
+                    const gchar        *query)
+{
+       TrackerNodeTree *tree;
+       TrackerSparql *sparql;
+
+       g_return_val_if_fail (TRACKER_IS_DATA_MANAGER (manager), NULL);
+       g_return_val_if_fail (query != NULL, NULL);
+
+       sparql = g_object_new (TRACKER_TYPE_SPARQL, NULL);
+       sparql->data_manager = g_object_ref (manager);
+       sparql->sparql = query;
+
+       tree = tracker_sparql_parse_query (sparql->sparql, -1, NULL,
+                                          &sparql->parser_error);
+       if (tree) {
+               sparql->tree = tree;
+               sparql->sql = tracker_string_builder_new ();
+
+               sparql->current_state.node = tracker_node_tree_get_root (sparql->tree);
+               sparql->current_state.sql = sparql->sql;
+       }
+
+       return sparql;
+}
+
+static TrackerDBStatement *
+prepare_query (TrackerDBInterface    *iface,
+               TrackerStringBuilder  *str,
+               GPtrArray             *literals,
+               GError               **error)
+{
+       TrackerDBStatement *stmt;
+       gchar *query;
+       guint i;
+
+       query = tracker_string_builder_to_string (str);
+       stmt = tracker_db_interface_create_statement (iface,
+                                                     TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT,
+                                                     error, query);
+       g_free (query);
+
+       if (!stmt || !literals)
+               return stmt;
+
+       for (i = 0; i < literals->len; i++) {
+               TrackerLiteralBinding *binding;
+               TrackerPropertyType prop_type;
+
+               binding = g_ptr_array_index (literals, i);
+               prop_type = TRACKER_BINDING (binding)->data_type;
+
+               if (prop_type == TRACKER_PROPERTY_TYPE_BOOLEAN) {
+                       if (g_str_equal (binding->literal, "1") ||
+                           g_ascii_strcasecmp (binding->literal, "true") == 0) {
+                               tracker_db_statement_bind_int (stmt, i, 1);
+                       } else if (g_str_equal (binding->literal, "0") ||
+                                  g_ascii_strcasecmp (binding->literal, "false") == 0) {
+                               tracker_db_statement_bind_int (stmt, i, 0);
+                       } else {
+                               g_set_error (error, TRACKER_SPARQL_ERROR,
+                                            TRACKER_SPARQL_ERROR_TYPE,
+                                            "'%s' is not a valid boolean",
+                                            binding->literal);
+                               g_object_unref (stmt);
+                               return NULL;
+                       }
+               } else if (prop_type == TRACKER_PROPERTY_TYPE_DATE) {
+                       gchar *full_str;
+                       gdouble datetime;
+
+                       full_str = g_strdup_printf ("%sT00:00:00Z", binding->literal);
+                       datetime = tracker_string_to_date (full_str, NULL, error);
+                       g_free (full_str);
+
+                       if (datetime < 0) {
+                               g_object_unref (stmt);
+                               return NULL;
+                       }
+
+                       tracker_db_statement_bind_int (stmt, i, (int) datetime);
+               } else if (prop_type == TRACKER_PROPERTY_TYPE_DATETIME) {
+                       gdouble datetime;
+
+                       datetime = tracker_string_to_date (binding->literal, NULL, error);
+                       if (datetime < 0) {
+                               g_object_unref (stmt);
+                               return NULL;
+                       }
+
+                       tracker_db_statement_bind_double (stmt, i, datetime);
+               } else if (prop_type == TRACKER_PROPERTY_TYPE_INTEGER) {
+                       tracker_db_statement_bind_int (stmt, i, atoi (binding->literal));
+               } else {
+                       tracker_db_statement_bind_text (stmt, i, binding->literal);
+               }
+       }
+
+       return stmt;
+}
+
+TrackerSparqlCursor *
+tracker_sparql_execute_cursor (TrackerSparql  *sparql,
+                               GError        **error)
+{
+       TrackerDBStatement *stmt;
+       TrackerDBInterface *iface;
+       TrackerDBCursor *cursor;
+       TrackerPropertyType *types;
+       const gchar * const *names;
+       guint n_types, n_names;
+
+       if (sparql->parser_error) {
+               g_propagate_error (error, sparql->parser_error);
+               return NULL;
+       }
+
+       if (!_call_rule_func (sparql, NAMED_RULE_Query, error))
+               return NULL;
+
+       iface = tracker_data_manager_get_db_interface (sparql->data_manager);
+       stmt = prepare_query (iface, sparql->sql,
+                             TRACKER_SELECT_CONTEXT (sparql->context)->literal_bindings,
+                             error);
+       if (!stmt)
+               return NULL;
+
+       types = (TrackerPropertyType *) sparql->var_types->data;
+       n_types = sparql->var_types->len;
+       names = (const gchar * const *) sparql->var_names->pdata;
+       n_names = sparql->var_names->len;
+
+       cursor = tracker_db_statement_start_sparql_cursor (stmt,
+                                                          types, n_types,
+                                                          names, n_names,
+                                                          error);
+       g_object_unref (stmt);
+
+       return TRACKER_SPARQL_CURSOR (cursor);
+}
diff --git a/src/libtracker-data/tracker-sparql.h b/src/libtracker-data/tracker-sparql.h
new file mode 100644
index 000000000..74a52d660
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, Red Hat Inc.
+ *
+ * This library 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 library 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 Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+#ifndef __TRACKER_SPARQL_H__
+#define __TRACKER_SPARQL_H__
+
+#if !defined (__LIBTRACKER_DATA_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "only <libtracker-data/tracker-data.h> must be included directly."
+#endif
+
+#include <glib.h>
+#include "tracker-data-manager.h"
+
+#define TRACKER_TYPE_SPARQL (tracker_sparql_get_type ())
+G_DECLARE_FINAL_TYPE (TrackerSparql, tracker_sparql,
+                      TRACKER, SPARQL, GObject)
+
+TrackerSparql *       tracker_sparql_new (TrackerDataManager *manager,
+                                          const gchar        *sparql);
+
+TrackerSparqlCursor * tracker_sparql_execute_cursor (TrackerSparql  *sparql,
+                                                     GError        **error);
+
+#endif /* __TRACKER_SPARQL_H__ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]