[tracker/wip/carlosg/deserializer-cursors: 1/15] libtracker-sparql: Add JSON cursor deserializer




commit a79e1ff69bb238882c8388c07cbdaa38e8c334a8
Author: Carlos Garnacho <carlosg gnome org>
Date:   Wed Apr 27 17:30:13 2022 +0200

    libtracker-sparql: Add JSON cursor deserializer
    
    This is mostly a copy of the cursor implementation in
    libtracker-sparql/remote, but over TrackerDeserializer. Sadly,
    json-glib doesn't lend itself to serialization proper, so we still
    build a full JSON tree after reading the whole document.

 src/libtracker-sparql/meson.build                 |   1 +
 src/libtracker-sparql/tracker-deserializer-json.c | 386 ++++++++++++++++++++++
 src/libtracker-sparql/tracker-deserializer-json.h |  38 +++
 src/libtracker-sparql/tracker-deserializer.c      |   3 +
 4 files changed, 428 insertions(+)
---
diff --git a/src/libtracker-sparql/meson.build b/src/libtracker-sparql/meson.build
index 9c708becb..8c60f8b04 100644
--- a/src/libtracker-sparql/meson.build
+++ b/src/libtracker-sparql/meson.build
@@ -28,6 +28,7 @@ libtracker_sparql_c_sources = files(
     'tracker-deserializer.c',
     'tracker-deserializer-rdf.c',
     'tracker-deserializer-turtle.c',
+    'tracker-deserializer-json.c',
     'tracker-endpoint.c',
     'tracker-endpoint-dbus.c',
     'tracker-endpoint-http.c',
diff --git a/src/libtracker-sparql/tracker-deserializer-json.c 
b/src/libtracker-sparql/tracker-deserializer-json.c
new file mode 100644
index 000000000..27556df30
--- /dev/null
+++ b/src/libtracker-sparql/tracker-deserializer-json.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2022, 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>
+ */
+
+/* Deserialization to cursors for the JSON format defined at:
+ *  https://www.w3.org/TR/sparql11-results-json/
+ */
+
+#include "config.h"
+
+#include "tracker-deserializer-json.h"
+
+#include <json-glib/json-glib.h>
+
+typedef struct {
+       TrackerSparqlValueType type;
+       const gchar *str;
+} ColumnData;
+
+struct _TrackerDeserializerJson {
+       TrackerDeserializer parent_instance;
+       GArray *columns;
+       JsonParser *parser;
+       JsonArray *vars;
+       JsonArray *results;
+       JsonObject *current_row;
+       guint idx;
+       gboolean started;
+       GError *init_error;
+};
+
+G_DEFINE_TYPE (TrackerDeserializerJson,
+               tracker_deserializer_json,
+               TRACKER_TYPE_DESERIALIZER)
+
+static void
+tracker_deserializer_json_finalize (GObject *object)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (object);
+
+       g_clear_object (&deserializer->parser);
+       g_array_unref (deserializer->columns);
+
+       G_OBJECT_CLASS (tracker_deserializer_json_parent_class)->finalize (object);
+}
+
+static void
+tracker_deserializer_json_constructed (GObject *object)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (object);
+       GInputStream *stream;
+       JsonNode *root_node;
+       JsonObject *root, *head, *results;
+
+       G_OBJECT_CLASS (tracker_deserializer_json_parent_class)->constructed (object);
+
+       stream = tracker_deserializer_get_stream (TRACKER_DESERIALIZER (object));
+
+       if (json_parser_load_from_stream (deserializer->parser,
+                                         stream,
+                                         NULL,
+                                         &deserializer->init_error)) {
+               root_node = json_parser_get_root (deserializer->parser);
+               root = json_node_get_object (root_node);
+
+               head = json_object_get_object_member (root, "head");
+               deserializer->vars = json_object_get_array_member (head, "vars");
+
+               results = json_object_get_object_member (root, "results");
+               deserializer->results = json_object_get_array_member (results, "bindings");
+       }
+}
+
+static gint
+tracker_deserializer_json_get_n_columns (TrackerSparqlCursor  *cursor)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (cursor);
+
+       return json_array_get_length (deserializer->vars);
+}
+
+static TrackerSparqlValueType
+tracker_deserializer_json_get_value_type (TrackerSparqlCursor  *cursor,
+                                          gint                  column)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (cursor);
+       ColumnData *col;
+
+       if (column > (gint) deserializer->columns->len)
+               return TRACKER_SPARQL_VALUE_TYPE_UNBOUND;
+
+       col = &g_array_index (deserializer->columns, ColumnData, column);
+
+       return col->type;
+}
+
+static const gchar *
+tracker_deserializer_json_get_variable_name (TrackerSparqlCursor  *cursor,
+                                             gint                  column)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (cursor);
+
+       return json_array_get_string_element (deserializer->vars, column);
+}
+
+static const gchar *
+tracker_deserializer_json_get_string (TrackerSparqlCursor  *cursor,
+                                      gint                  column,
+                                      glong                *length)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (cursor);
+       ColumnData *col;
+
+       if (column > (gint) deserializer->columns->len)
+               return NULL;
+
+       col = &g_array_index (deserializer->columns, ColumnData, column);
+
+       return col->str;
+}
+
+static gboolean
+parse_column_type (JsonObject              *column,
+                   TrackerSparqlValueType  *value,
+                   GError                 **error)
+{
+       const gchar *type;
+
+       if (!json_object_has_member (column, "type")) {
+               g_set_error (error,
+                            TRACKER_SPARQL_ERROR,
+                            TRACKER_SPARQL_ERROR_PARSE,
+                            "Column object does not have 'type' member");
+               return FALSE;
+       }
+
+       type = json_object_get_string_member (column, "type");
+
+       if (g_str_equal (type, "uri")) {
+               *value = TRACKER_SPARQL_VALUE_TYPE_URI;
+       } else if (g_str_equal (type, "bnode")) {
+               *value = TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
+       } else if (g_str_equal (type, "literal")) {
+               const gchar *datatype, *suffix;
+
+               if (!json_object_has_member (column, "datatype")) {
+                       g_set_error (error,
+                                    TRACKER_SPARQL_ERROR,
+                                    TRACKER_SPARQL_ERROR_PARSE,
+                                    "Column object does not have 'datatype' member");
+                       return FALSE;
+               }
+
+               datatype = json_object_get_string_member (column, "datatype");
+
+               if (!g_str_has_prefix (datatype, TRACKER_PREFIX_XSD)) {
+                       *value = TRACKER_SPARQL_VALUE_TYPE_STRING;
+                       return TRUE;
+               }
+
+               suffix = &datatype[strlen (TRACKER_PREFIX_XSD)];
+
+               if (g_str_equal (suffix, "byte") ||
+                   g_str_equal (suffix, "int") ||
+                   g_str_equal (suffix, "integer") ||
+                   g_str_equal (suffix, "long"))
+                       *value = TRACKER_SPARQL_VALUE_TYPE_INTEGER;
+               else if (g_str_equal (suffix, "decimal") ||
+                        g_str_equal (suffix, "double"))
+                       *value = TRACKER_SPARQL_VALUE_TYPE_DOUBLE;
+               else if (g_str_equal (suffix, "date") ||
+                        g_str_equal (suffix, "dateTime"))
+                       *value = TRACKER_SPARQL_VALUE_TYPE_DATETIME;
+               else
+                       *value = TRACKER_SPARQL_VALUE_TYPE_STRING;
+       } else {
+               g_set_error (error,
+                            TRACKER_SPARQL_ERROR,
+                            TRACKER_SPARQL_ERROR_PARSE,
+                            "Unknown type '%s'", type);
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+static gboolean
+parse_column_string (JsonObject   *column,
+                     const gchar **str,
+                     GError      **error)
+{
+       if (!json_object_has_member (column, "value")) {
+               g_set_error (error,
+                            TRACKER_SPARQL_ERROR,
+                            TRACKER_SPARQL_ERROR_PARSE,
+                            "Column object does not have 'value' member");
+               return FALSE;
+       }
+
+       *str = json_object_get_string_member (column, "value");
+       return TRUE;
+}
+
+static gboolean
+parse_row (TrackerDeserializerJson  *deserializer,
+           GError                  **error)
+{
+       TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR (deserializer);
+       const gchar *var_name;
+       JsonObject *column;
+       gint n_columns, i;
+
+       g_array_set_size (deserializer->columns, 0);
+       n_columns = tracker_sparql_cursor_get_n_columns (cursor);
+
+       for (i = 0; i < n_columns; i++) {
+               ColumnData col = { 0 };
+
+               var_name = tracker_sparql_cursor_get_variable_name (cursor, i);
+
+               if (json_object_has_member (deserializer->current_row, var_name)) {
+                       column = json_object_get_object_member (deserializer->current_row,
+                                                               var_name);
+                       if (column) {
+                               if (!parse_column_string (column, &col.str, error) ||
+                                   !parse_column_type (column, &col.type, error))
+                                       return FALSE;
+
+                               g_array_append_val (deserializer->columns, col);
+                               continue;
+                       }
+               }
+
+               col = (ColumnData) { TRACKER_SPARQL_VALUE_TYPE_UNBOUND, NULL };
+               g_array_append_val (deserializer->columns, col);
+       }
+
+       return TRUE;
+}
+
+static gboolean
+tracker_deserializer_json_next (TrackerSparqlCursor  *cursor,
+                                GCancellable         *cancellable,
+                                GError              **error)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (cursor);
+
+       g_array_set_size (deserializer->columns, 0);
+
+       if (deserializer->init_error) {
+               GError *init_error;
+
+               init_error = g_steal_pointer (&deserializer->init_error);
+               g_propagate_error (error, init_error);
+               return FALSE;
+       }
+
+       if (deserializer->started)
+               deserializer->idx++;
+
+       if (deserializer->idx >= json_array_get_length (deserializer->results))
+               return FALSE;
+
+       if (g_cancellable_set_error_if_cancelled (cancellable, error))
+               return FALSE;
+
+       deserializer->current_row =
+               json_array_get_object_element (deserializer->results,
+                                              deserializer->idx);
+       deserializer->started = TRUE;
+
+       return parse_row (deserializer, error);
+}
+
+static void
+tracker_deserializer_json_next_async (TrackerSparqlCursor  *cursor,
+                                      GCancellable         *cancellable,
+                                      GAsyncReadyCallback   cb,
+                                      gpointer              user_data)
+{
+       GError *error = NULL;
+       GTask *task;
+
+       task = g_task_new (cursor, cancellable, cb, user_data);
+
+       if (tracker_sparql_cursor_next (cursor, cancellable, &error))
+               g_task_return_boolean (task, TRUE);
+       else if (!error)
+               g_task_return_boolean (task, FALSE);
+       else
+               g_task_return_error (task, error);
+
+       g_object_unref (task);
+}
+
+static gboolean
+tracker_deserializer_json_next_finish (TrackerSparqlCursor  *cursor,
+                                       GAsyncResult         *res,
+                                       GError              **error)
+{
+       return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+tracker_deserializer_json_rewind (TrackerSparqlCursor *cursor)
+{
+       TrackerDeserializerJson *deserializer =
+               TRACKER_DESERIALIZER_JSON (cursor);
+
+       deserializer->started = FALSE;
+       deserializer->idx = 0;
+}
+
+gboolean
+tracker_deserializer_json_get_parser_location (TrackerDeserializer *deserializer,
+                                               goffset             *line_no,
+                                               goffset             *column_no)
+{
+       return FALSE;
+}
+
+static void
+tracker_deserializer_json_class_init (TrackerDeserializerJsonClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       TrackerSparqlCursorClass *cursor_class =
+               TRACKER_SPARQL_CURSOR_CLASS (klass);
+       TrackerDeserializerClass *deserializer_class =
+               TRACKER_DESERIALIZER_CLASS (klass);
+
+       object_class->finalize = tracker_deserializer_json_finalize;
+       object_class->constructed = tracker_deserializer_json_constructed;
+
+       cursor_class->get_n_columns = tracker_deserializer_json_get_n_columns;
+       cursor_class->get_value_type = tracker_deserializer_json_get_value_type;
+       cursor_class->get_variable_name = tracker_deserializer_json_get_variable_name;
+       cursor_class->get_string = tracker_deserializer_json_get_string;
+       cursor_class->next = tracker_deserializer_json_next;
+       cursor_class->next_async = tracker_deserializer_json_next_async;
+       cursor_class->next_finish = tracker_deserializer_json_next_finish;
+       cursor_class->rewind = tracker_deserializer_json_rewind;
+
+       deserializer_class->get_parser_location =
+               tracker_deserializer_json_get_parser_location;
+}
+
+static void
+tracker_deserializer_json_init (TrackerDeserializerJson *deserializer)
+{
+       deserializer->parser = json_parser_new ();
+       deserializer->columns = g_array_new (FALSE, FALSE, sizeof (ColumnData));
+}
+
+TrackerSparqlCursor *
+tracker_deserializer_json_new (GInputStream            *stream,
+                               TrackerNamespaceManager *namespaces)
+{
+       return g_object_new (TRACKER_TYPE_DESERIALIZER_JSON,
+                            "stream", stream,
+                            "namespace-manager", namespaces,
+                            NULL);
+}
diff --git a/src/libtracker-sparql/tracker-deserializer-json.h 
b/src/libtracker-sparql/tracker-deserializer-json.h
new file mode 100644
index 000000000..607b6c391
--- /dev/null
+++ b/src/libtracker-sparql/tracker-deserializer-json.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2020, 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 "tracker-deserializer.h"
+
+#include <gio/gio.h>
+
+#ifndef __TRACKER_DESERIALIZER_JSON_H__
+#define __TRACKER_DESERIALIZER_JSON_H__
+
+#define TRACKER_TYPE_DESERIALIZER_JSON (tracker_deserializer_json_get_type ())
+G_DECLARE_FINAL_TYPE (TrackerDeserializerJson,
+                      tracker_deserializer_json,
+                      TRACKER, DESERIALIZER_JSON,
+                      TrackerDeserializer)
+
+TrackerSparqlCursor * tracker_deserializer_json_new (GInputStream            *stream,
+                                                     TrackerNamespaceManager *manager);
+
+#endif /* __TRACKER_DESERIALIZER_JSON_H__ */
diff --git a/src/libtracker-sparql/tracker-deserializer.c b/src/libtracker-sparql/tracker-deserializer.c
index ed8c37a97..8633298ad 100644
--- a/src/libtracker-sparql/tracker-deserializer.c
+++ b/src/libtracker-sparql/tracker-deserializer.c
@@ -23,6 +23,7 @@
 
 #include "tracker-deserializer.h"
 #include "tracker-deserializer-turtle.h"
+#include "tracker-deserializer-json.h"
 
 #include "tracker-private.h"
 
@@ -170,6 +171,8 @@ tracker_deserializer_new (GInputStream            *stream,
        g_return_val_if_fail (G_IS_INPUT_STREAM (stream), NULL);
 
        switch (format) {
+       case TRACKER_SERIALIZER_FORMAT_JSON:
+               return tracker_deserializer_json_new (stream, namespaces);
        case TRACKER_SERIALIZER_FORMAT_TTL:
                return tracker_deserializer_turtle_new (stream, namespaces);
        case TRACKER_SERIALIZER_FORMAT_TRIG:


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