[tracker/wip/carlosg/statement-fixes: 1/2] libtracker-data: Add remote TrackerSparqlStatement implementation




commit b9e67c794df653eca5ee2c04f09a08e13f37d751
Author: Carlos Garnacho <carlosg gnome org>
Date:   Wed Nov 10 23:43:50 2021 +0200

    libtracker-data: Add remote TrackerSparqlStatement implementation
    
    Implement this object for remote connections to increase API consistency.
    Underneath, our very own SPARQL parser is used to do raw string replacements
    on the actual query being sent.
    
    Besides API consistence, this also has the benefit to make ~var bindings
    work with other SPARQL endpoints that are not Tracker's.

 src/libtracker-sparql/meson.build                  |   2 +
 .../remote/tracker-remote-statement.c              | 419 +++++++++++++++++++++
 .../remote/tracker-remote-statement.h              |  32 ++
 src/libtracker-sparql/remote/tracker-remote.vala   |   4 +
 src/libtracker-sparql/remote/tracker-remote.vapi   |   6 +
 5 files changed, 463 insertions(+)
---
diff --git a/src/libtracker-sparql/meson.build b/src/libtracker-sparql/meson.build
index 02a60b92e..3a64b1311 100644
--- a/src/libtracker-sparql/meson.build
+++ b/src/libtracker-sparql/meson.build
@@ -92,10 +92,12 @@ tracker_remote_dependencies = [json_glib, libxml2]
 
 libtracker_sparql_remote_c_sources = files (
     'tracker-endpoint-http.c',
+    'remote/tracker-remote-statement.c',
 )
 
 remote_sources = [
     libtracker_sparql_remote_c_sources,
+    'remote/tracker-remote.vapi',
     'remote/tracker-json-cursor.vala',
     'remote/tracker-xml-cursor.vala',
     'remote/tracker-remote.vala',
diff --git a/src/libtracker-sparql/remote/tracker-remote-statement.c 
b/src/libtracker-sparql/remote/tracker-remote-statement.c
new file mode 100644
index 000000000..abd6570f0
--- /dev/null
+++ b/src/libtracker-sparql/remote/tracker-remote-statement.c
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2021, 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.
+ *
+ * Author: Carlos Garnacho <carlosg gnome org>
+ */
+
+#include "config.h"
+
+#include "tracker-remote-statement.h"
+
+#include <libtracker-common/tracker-common.h>
+#include <libtracker-data/tracker-sparql-grammar.h>
+#include <libtracker-data/tracker-sparql-parser.h>
+#include <libtracker-sparql/tracker-private.h>
+
+struct _TrackerRemoteStatement
+{
+       TrackerSparqlStatement parent_instance;
+       TrackerNodeTree *parser_tree;
+       GHashTable *bindings;
+};
+
+G_DEFINE_TYPE (TrackerRemoteStatement,
+               tracker_remote_statement,
+               TRACKER_TYPE_SPARQL_STATEMENT)
+
+static void
+tracker_remote_statement_finalize (GObject *object)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (object);
+
+       if (remote_stmt->parser_tree)
+               tracker_node_tree_free (remote_stmt->parser_tree);
+       g_hash_table_unref (remote_stmt->bindings);
+
+       G_OBJECT_CLASS (tracker_remote_statement_parent_class)->finalize (object);
+}
+
+static void
+tracker_remote_statement_bind_int (TrackerSparqlStatement *stmt,
+                                   const gchar            *name,
+                                   gint64                  value)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       GValue *val;
+
+       val = g_new0 (GValue, 1);
+       g_value_init (val, G_TYPE_INT64);
+       g_value_set_int64 (val, value);
+
+       g_hash_table_insert (remote_stmt->bindings,
+                            g_strdup (name),
+                            val);
+}
+
+static void
+tracker_remote_statement_bind_boolean (TrackerSparqlStatement *stmt,
+                                       const gchar            *name,
+                                       gboolean                value)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       GValue *val;
+
+       val = g_new0 (GValue, 1);
+       g_value_init (val, G_TYPE_BOOLEAN);
+       g_value_set_boolean (val, value);
+
+       g_hash_table_insert (remote_stmt->bindings,
+                            g_strdup (name),
+                            val);
+}
+
+static void
+tracker_remote_statement_bind_string (TrackerSparqlStatement *stmt,
+                                      const gchar            *name,
+                                      const gchar            *value)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       GValue *val;
+
+       val = g_new0 (GValue, 1);
+       g_value_init (val, G_TYPE_STRING);
+       g_value_set_string (val, value);
+
+       g_hash_table_insert (remote_stmt->bindings,
+                            g_strdup (name),
+                            val);
+}
+
+static void
+tracker_remote_statement_bind_double (TrackerSparqlStatement *stmt,
+                                      const gchar            *name,
+                                      gdouble                 value)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       GValue *val;
+
+       val = g_new0 (GValue, 1);
+       g_value_init (val, G_TYPE_DOUBLE);
+       g_value_set_double (val, value);
+
+       g_hash_table_insert (remote_stmt->bindings,
+                            g_strdup (name),
+                            val);
+}
+
+static void
+tracker_remote_statement_bind_datetime (TrackerSparqlStatement *stmt,
+                                        const gchar            *name,
+                                        GDateTime              *value)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       GValue *val;
+
+       val = g_new0 (GValue, 1);
+       g_value_init (val, G_TYPE_DATE_TIME);
+       g_value_set_boxed (val, value);
+
+       g_hash_table_insert (remote_stmt->bindings,
+                            g_strdup (name),
+                            val);
+}
+
+static void
+append_gvalue (GString *str,
+               const GValue  *value)
+{
+       if (G_VALUE_HOLDS_BOOLEAN (value)) {
+               g_string_append_printf (str, "%s",
+                                       g_value_get_boolean (value) ?
+                                       "true" : "false");
+       } else if (G_VALUE_HOLDS_INT64 (value)) {
+               g_string_append_printf (str, "%" G_GINT64_FORMAT,
+                                       g_value_get_int64 (value));
+       } else if (G_VALUE_HOLDS_DOUBLE (value)) {
+               gchar buf[G_ASCII_DTOSTR_BUF_SIZE + 1];
+
+               g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value));
+               g_string_append (str, buf);
+       } else if (G_VALUE_TYPE (value) == G_TYPE_DATE_TIME) {
+               GDateTime *datetime;
+               gchar *datetime_str;
+
+               datetime = g_value_get_boxed (value);
+               datetime_str = tracker_date_format_iso8601 (datetime);
+               g_string_append_printf (str, "\"%s\"", datetime_str);
+               g_free (datetime_str);
+       } else if (G_VALUE_HOLDS_STRING (value)) {
+               const gchar *val = g_value_get_string (value);
+               int len = strlen (val);
+               gchar *end;
+               gboolean is_number = FALSE;
+
+               /* Try to detect numbers anyway, since we use to allow
+                * loose typing in other connection types.
+                */
+               g_ascii_strtoll (val, &end, 10);
+               is_number = (end == &val[len]);
+
+               if (!is_number) {
+                       g_ascii_strtod (val, &end);
+                       is_number = (end == &val[len]);
+               }
+
+               if (is_number)
+                       g_string_append (str, val);
+               else
+                       g_string_append_printf (str, "\"%s\"", val);
+       }
+}
+
+static gchar *
+apply_bindings (TrackerSparqlStatement  *stmt,
+                GHashTable              *bindings,
+                GError                 **error)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       const gchar *query = tracker_sparql_statement_get_sparql (stmt);
+       GString *str = g_string_new (NULL);
+       TrackerParserNode *node = tracker_node_tree_get_root (remote_stmt->parser_tree);
+
+       for (node = tracker_sparql_parser_tree_find_first (node, TRUE);
+            node;
+            node = tracker_sparql_parser_tree_find_next (node, TRUE)) {
+               const TrackerGrammarRule *rule;
+               gssize start, end;
+
+               if (!tracker_parser_node_get_extents (node, &start, &end)) {
+                       /* Skip over 0-len nodes */
+                       continue;
+               }
+
+               rule = tracker_parser_node_get_rule (node);
+
+               if (tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL,
+                                              TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+                       gchar *param_name;
+                       const GValue *value;
+
+                       param_name = g_strndup (&query[start], end - start);
+                       value = g_hash_table_lookup (bindings, &param_name[1]);
+                       if (!value) {
+                               g_set_error (error,
+                                            TRACKER_SPARQL_ERROR,
+                                            TRACKER_SPARQL_ERROR_PARSE,
+                                            "No binding found for variable %s",
+                                            param_name);
+                               g_string_free (str, TRUE);
+                               g_free (param_name);
+                               return NULL;
+                       }
+
+                       append_gvalue (str, value);
+                       g_free (param_name);
+               } else {
+                       g_string_append_len (str,
+                                            &query[start],
+                                            end - start);
+               }
+
+               g_string_append_c (str, ' ');
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+static TrackerSparqlCursor *
+execute_statement (TrackerSparqlStatement  *stmt,
+                   GHashTable              *bindings,
+                   GCancellable            *cancellable,
+                   GError                 **error)
+{
+       TrackerSparqlCursor *cursor;
+       gchar *rewritten_query = NULL;
+
+       if (g_hash_table_size (bindings) > 0) {
+               rewritten_query = apply_bindings (stmt, bindings, error);
+               if (!rewritten_query)
+                       return NULL;
+       }
+
+       cursor = tracker_sparql_connection_query (tracker_sparql_statement_get_connection (stmt),
+                                                 rewritten_query ? rewritten_query :
+                                                 tracker_sparql_statement_get_sparql (stmt),
+                                                 cancellable,
+                                                 error);
+       g_free (rewritten_query);
+
+       return cursor;
+}
+
+TrackerSparqlCursor *
+tracker_remote_statement_execute (TrackerSparqlStatement  *stmt,
+                                  GCancellable            *cancellable,
+                                  GError                 **error)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+
+       return execute_statement (stmt, remote_stmt->bindings, cancellable, error);
+}
+
+static void
+execute_in_thread (GTask        *task,
+                   gpointer      object,
+                   gpointer      task_data,
+                   GCancellable *cancellable)
+{
+       TrackerSparqlCursor *cursor;
+       GHashTable *bindings = task_data;
+       GError *error = NULL;
+
+       if (g_task_return_error_if_cancelled (task))
+               return;
+
+       cursor = execute_statement (object,
+                                   bindings,
+                                   g_task_get_cancellable (task),
+                                   &error);
+       if (error)
+               g_task_return_error (task, error);
+       else
+               g_task_return_pointer (task, cursor, g_object_unref);
+
+       g_object_unref (task);
+}
+
+static void
+free_gvalue (gpointer data)
+{
+       g_value_unset (data);
+       g_free (data);
+}
+
+static GHashTable *
+create_bindings_ht (void)
+{
+       return g_hash_table_new_full (g_str_hash, g_str_equal,
+                                     g_free, free_gvalue);
+}
+
+static GHashTable *
+copy_values_deep (GHashTable *values)
+{
+       GHashTable *copy;
+       GHashTableIter iter;
+       gpointer key, val;
+
+       copy = create_bindings_ht ();
+       g_hash_table_iter_init (&iter, values);
+
+       while (g_hash_table_iter_next (&iter, &key, &val)) {
+               GValue *copy_value;
+
+               copy_value = g_new0 (GValue, 1);
+               g_value_init (copy_value, G_VALUE_TYPE (val));
+               g_value_copy (val, copy_value);
+
+               g_hash_table_insert (copy, g_strdup (key), copy_value);
+       }
+
+       return copy;
+}
+
+void
+tracker_remote_statement_execute_async (TrackerSparqlStatement *stmt,
+                                        GCancellable           *cancellable,
+                                        GAsyncReadyCallback     callback,
+                                        gpointer                user_data)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       GHashTable *bindings;
+       GTask *task;
+
+       bindings = copy_values_deep (remote_stmt->bindings);
+
+       task = g_task_new (stmt, cancellable, callback, user_data);
+       g_task_set_task_data (task, bindings, (GDestroyNotify) g_hash_table_unref);
+       g_task_run_in_thread (task, execute_in_thread);
+}
+
+TrackerSparqlCursor *
+tracker_remote_statement_execute_finish (TrackerSparqlStatement  *stmt,
+                                         GAsyncResult            *res,
+                                         GError                 **error)
+{
+       return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+void
+tracker_remote_statement_clear_bindings (TrackerSparqlStatement *stmt)
+{
+       TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+
+       g_hash_table_remove_all (remote_stmt->bindings);
+}
+
+static void
+tracker_remote_statement_class_init (TrackerRemoteStatementClass *klass)
+{
+       TrackerSparqlStatementClass *stmt_class = TRACKER_SPARQL_STATEMENT_CLASS (klass);
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+       object_class->finalize = tracker_remote_statement_finalize;
+
+       stmt_class->bind_int = tracker_remote_statement_bind_int;
+       stmt_class->bind_boolean = tracker_remote_statement_bind_boolean;
+       stmt_class->bind_string = tracker_remote_statement_bind_string;
+       stmt_class->bind_double = tracker_remote_statement_bind_double;
+       stmt_class->bind_datetime = tracker_remote_statement_bind_datetime;
+       stmt_class->execute = tracker_remote_statement_execute;
+       stmt_class->execute_async = tracker_remote_statement_execute_async;
+       stmt_class->execute_finish = tracker_remote_statement_execute_finish;
+       stmt_class->clear_bindings = tracker_remote_statement_clear_bindings;
+}
+
+static void
+tracker_remote_statement_init (TrackerRemoteStatement *stmt)
+{
+       stmt->bindings = create_bindings_ht ();
+}
+
+TrackerSparqlStatement *
+tracker_remote_statement_new (TrackerSparqlConnection  *conn,
+                              const gchar              *query,
+                              GError                  **error)
+{
+       TrackerRemoteStatement *remote_stmt;
+       TrackerSparqlStatement *stmt;
+
+       stmt = g_object_new (TRACKER_TYPE_REMOTE_STATEMENT,
+                                   "connection", conn,
+                                   "sparql", query,
+                                   NULL);
+       remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+       remote_stmt->parser_tree =
+               tracker_sparql_parse_query (tracker_sparql_statement_get_sparql (stmt),
+                                           -1, NULL, error);
+       if (!remote_stmt->parser_tree) {
+               g_object_unref (stmt);
+               return NULL;
+       }
+
+       return stmt;
+}
diff --git a/src/libtracker-sparql/remote/tracker-remote-statement.h 
b/src/libtracker-sparql/remote/tracker-remote-statement.h
new file mode 100644
index 000000000..1cedf3811
--- /dev/null
+++ b/src/libtracker-sparql/remote/tracker-remote-statement.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021, Red Hat Ltd.
+ *
+ * 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.
+ *
+ * Author: Carlos Garnacho <carlosg gnome org>
+ */
+
+#include <libtracker-sparql/tracker-private.h>
+
+#define TRACKER_TYPE_REMOTE_STATEMENT (tracker_remote_statement_get_type ())
+G_DECLARE_FINAL_TYPE (TrackerRemoteStatement,
+                      tracker_remote_statement,
+                      TRACKER, REMOTE_STATEMENT,
+                      TrackerSparqlStatement)
+
+TrackerSparqlStatement * tracker_remote_statement_new (TrackerSparqlConnection  *conn,
+                                                       const gchar              *query,
+                                                       GError                  **error);
diff --git a/src/libtracker-sparql/remote/tracker-remote.vala 
b/src/libtracker-sparql/remote/tracker-remote.vala
index f29a989ad..f3be3147a 100644
--- a/src/libtracker-sparql/remote/tracker-remote.vala
+++ b/src/libtracker-sparql/remote/tracker-remote.vala
@@ -110,6 +110,10 @@ public class Tracker.Remote.Connection : Tracker.Sparql.Connection {
 #endif
        }
 
+       public override Sparql.Statement? query_statement (string sparql, GLib.Cancellable? cancellable = 
null) throws Sparql.Error {
+               return new Remote.Statement (this, sparql);
+       }
+
        public override void close () {
        }
 
diff --git a/src/libtracker-sparql/remote/tracker-remote.vapi 
b/src/libtracker-sparql/remote/tracker-remote.vapi
new file mode 100644
index 000000000..caf018b41
--- /dev/null
+++ b/src/libtracker-sparql/remote/tracker-remote.vapi
@@ -0,0 +1,6 @@
+namespace Tracker {
+       [CCode (cheader_filename = "libtracker-sparql/remote/tracker-remote-statement.h")]
+       class Remote.Statement : Sparql.Statement {
+               public Statement (Sparql.Connection conn, string query) throws Sparql.Error;
+       }
+}


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