Coverage Report

Created: 2026-05-30 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tinysparql/src/libtinysparql/tracker-deserializer-json.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2022, Red Hat Inc.
3
 *
4
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * This library is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with this library; if not, write to the
16
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
 * Boston, MA  02110-1301, USA.
18
 *
19
 * Author: Carlos Garnacho <carlosg@gnome.org>
20
 */
21
22
/* Deserialization to cursors for the JSON format defined at:
23
 *  https://www.w3.org/TR/sparql11-results-json/
24
 */
25
26
#include "config.h"
27
28
#include "tracker-deserializer-json.h"
29
30
#include <json-glib/json-glib.h>
31
32
typedef struct {
33
  TrackerSparqlValueType type;
34
  const gchar *str;
35
  const gchar *langtag;
36
} ColumnData;
37
38
struct _TrackerDeserializerJson {
39
  TrackerDeserializer parent_instance;
40
  GArray *columns;
41
  JsonParser *parser;
42
  JsonArray *vars;
43
  JsonArray *results;
44
  JsonObject *current_row;
45
  guint idx;
46
  gboolean started;
47
  GError *init_error;
48
};
49
50
0
G_DEFINE_TYPE (TrackerDeserializerJson,
51
0
               tracker_deserializer_json,
52
0
               TRACKER_TYPE_DESERIALIZER)
53
0
54
0
static void
55
0
tracker_deserializer_json_finalize (GObject *object)
56
0
{
57
0
  TrackerDeserializerJson *deserializer =
58
0
    TRACKER_DESERIALIZER_JSON (object);
59
60
0
  g_clear_object (&deserializer->parser);
61
0
  g_array_unref (deserializer->columns);
62
63
0
  G_OBJECT_CLASS (tracker_deserializer_json_parent_class)->finalize (object);
64
0
}
65
66
static void
67
tracker_deserializer_json_constructed (GObject *object)
68
0
{
69
0
  TrackerDeserializerJson *deserializer =
70
0
    TRACKER_DESERIALIZER_JSON (object);
71
0
  GInputStream *stream;
72
0
  JsonNode *root_node;
73
0
  JsonObject *root, *head, *results;
74
75
0
  G_OBJECT_CLASS (tracker_deserializer_json_parent_class)->constructed (object);
76
77
0
  stream = tracker_deserializer_get_stream (TRACKER_DESERIALIZER (object));
78
79
0
  if (json_parser_load_from_stream (deserializer->parser,
80
0
                                    stream,
81
0
                                    NULL,
82
0
                                    &deserializer->init_error)) {
83
0
    root_node = json_parser_get_root (deserializer->parser);
84
0
    root = json_node_get_object (root_node);
85
86
0
    head = json_object_get_object_member (root, "head");
87
0
    deserializer->vars = json_object_get_array_member (head, "vars");
88
89
0
    results = json_object_get_object_member (root, "results");
90
0
    deserializer->results = json_object_get_array_member (results, "bindings");
91
0
  }
92
0
}
93
94
static gint
95
tracker_deserializer_json_get_n_columns (TrackerSparqlCursor  *cursor)
96
0
{
97
0
  TrackerDeserializerJson *deserializer =
98
0
    TRACKER_DESERIALIZER_JSON (cursor);
99
100
0
  return json_array_get_length (deserializer->vars);
101
0
}
102
103
static TrackerSparqlValueType
104
tracker_deserializer_json_get_value_type (TrackerSparqlCursor  *cursor,
105
                                          gint                  column)
106
0
{
107
0
  TrackerDeserializerJson *deserializer =
108
0
    TRACKER_DESERIALIZER_JSON (cursor);
109
0
  ColumnData *col;
110
111
0
  if (column < 0 || column >= (gint) deserializer->columns->len)
112
0
    return TRACKER_SPARQL_VALUE_TYPE_UNBOUND;
113
114
0
  col = &g_array_index (deserializer->columns, ColumnData, column);
115
116
0
  return col->type;
117
0
}
118
119
static const gchar *
120
tracker_deserializer_json_get_variable_name (TrackerSparqlCursor  *cursor,
121
                                             gint                  column)
122
0
{
123
0
  TrackerDeserializerJson *deserializer =
124
0
    TRACKER_DESERIALIZER_JSON (cursor);
125
126
0
  if (column < 0 || (guint) column >= json_array_get_length (deserializer->vars))
127
0
    return NULL;
128
129
0
  return json_array_get_string_element (deserializer->vars, column);
130
0
}
131
132
static const gchar *
133
tracker_deserializer_json_get_string (TrackerSparqlCursor   *cursor,
134
                                      gint                   column,
135
                                      const gchar          **langtag,
136
                                      glong                 *length)
137
0
{
138
0
  TrackerDeserializerJson *deserializer =
139
0
    TRACKER_DESERIALIZER_JSON (cursor);
140
0
  ColumnData *col;
141
142
0
  if (length)
143
0
    *length = 0;
144
0
  if (langtag)
145
0
    *langtag = NULL;
146
147
0
  if (column < 0 || column >= (gint) deserializer->columns->len)
148
0
    return NULL;
149
150
0
  col = &g_array_index (deserializer->columns, ColumnData, column);
151
152
0
  if (length)
153
0
    *length = strlen (col->str);
154
0
  if (langtag)
155
0
    *langtag = col->langtag;
156
157
0
  return col->str;
158
0
}
159
160
static gboolean
161
parse_column_type (JsonObject              *column,
162
                   TrackerSparqlValueType  *value,
163
                   GError                 **error)
164
0
{
165
0
  const gchar *type;
166
167
0
  if (!json_object_has_member (column, "type")) {
168
0
    g_set_error (error,
169
0
                 TRACKER_SPARQL_ERROR,
170
0
                 TRACKER_SPARQL_ERROR_PARSE,
171
0
                 "Column object does not have 'type' member");
172
0
    return FALSE;
173
0
  }
174
175
0
  type = json_object_get_string_member (column, "type");
176
177
0
  if (g_str_equal (type, "uri")) {
178
0
    *value = TRACKER_SPARQL_VALUE_TYPE_URI;
179
0
  } else if (g_str_equal (type, "bnode")) {
180
0
    *value = TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
181
0
  } else if (g_str_equal (type, "literal")) {
182
0
    const gchar *datatype, *suffix;
183
184
0
    if (json_object_has_member (column, "datatype"))
185
0
      datatype = json_object_get_string_member (column, "datatype");
186
0
    else
187
0
      datatype = TRACKER_PREFIX_XSD "string";
188
189
0
    if (!g_str_has_prefix (datatype, TRACKER_PREFIX_XSD)) {
190
0
      *value = TRACKER_SPARQL_VALUE_TYPE_STRING;
191
0
      return TRUE;
192
0
    }
193
194
0
    suffix = &datatype[strlen (TRACKER_PREFIX_XSD)];
195
196
0
    if (g_str_equal (suffix, "byte") ||
197
0
        g_str_equal (suffix, "int") ||
198
0
        g_str_equal (suffix, "integer") ||
199
0
        g_str_equal (suffix, "long"))
200
0
      *value = TRACKER_SPARQL_VALUE_TYPE_INTEGER;
201
0
    else if (g_str_equal (suffix, "decimal") ||
202
0
             g_str_equal (suffix, "double"))
203
0
      *value = TRACKER_SPARQL_VALUE_TYPE_DOUBLE;
204
0
    else if (g_str_equal (suffix, "date") ||
205
0
             g_str_equal (suffix, "dateTime"))
206
0
      *value = TRACKER_SPARQL_VALUE_TYPE_DATETIME;
207
0
    else if (g_str_equal (suffix, "boolean"))
208
0
      *value = TRACKER_SPARQL_VALUE_TYPE_BOOLEAN;
209
0
    else
210
0
      *value = TRACKER_SPARQL_VALUE_TYPE_STRING;
211
0
  } else {
212
0
    g_set_error (error,
213
0
                 TRACKER_SPARQL_ERROR,
214
0
                 TRACKER_SPARQL_ERROR_PARSE,
215
0
                 "Unknown type '%s'", type);
216
0
    return FALSE;
217
0
  }
218
219
0
  return TRUE;
220
0
}
221
222
static gboolean
223
parse_column_string (JsonObject   *column,
224
                     const gchar **str,
225
                     GError      **error)
226
0
{
227
0
  if (!json_object_has_member (column, "value")) {
228
0
    g_set_error (error,
229
0
                 TRACKER_SPARQL_ERROR,
230
0
                 TRACKER_SPARQL_ERROR_PARSE,
231
0
                 "Column object does not have 'value' member");
232
0
    return FALSE;
233
0
  }
234
235
0
  *str = json_object_get_string_member (column, "value");
236
0
  return TRUE;
237
0
}
238
239
static const gchar *
240
parse_column_langtag (JsonObject *column)
241
0
{
242
0
  if (!json_object_has_member (column, "xml:lang"))
243
0
    return NULL;
244
245
0
  return json_object_get_string_member (column, "xml:lang");
246
0
}
247
248
static gboolean
249
parse_row (TrackerDeserializerJson  *deserializer,
250
           GError                  **error)
251
0
{
252
0
  TrackerSparqlCursor *cursor = TRACKER_SPARQL_CURSOR (deserializer);
253
0
  const gchar *var_name;
254
0
  JsonObject *column;
255
0
  gint n_columns, i;
256
257
0
  g_array_set_size (deserializer->columns, 0);
258
0
  n_columns = tracker_sparql_cursor_get_n_columns (cursor);
259
260
0
  for (i = 0; i < n_columns; i++) {
261
0
    ColumnData col = { 0 };
262
263
0
    var_name = tracker_sparql_cursor_get_variable_name (cursor, i);
264
265
0
    if (json_object_has_member (deserializer->current_row, var_name)) {
266
0
      column = json_object_get_object_member (deserializer->current_row,
267
0
                                              var_name);
268
0
      if (column) {
269
0
        if (!parse_column_string (column, &col.str, error) ||
270
0
            !parse_column_type (column, &col.type, error))
271
0
          return FALSE;
272
273
0
        col.langtag = parse_column_langtag (column);
274
0
        g_array_append_val (deserializer->columns, col);
275
0
        continue;
276
0
      }
277
0
    }
278
279
0
    col = (ColumnData) { TRACKER_SPARQL_VALUE_TYPE_UNBOUND, NULL };
280
0
    g_array_append_val (deserializer->columns, col);
281
0
  }
282
283
0
  return TRUE;
284
0
}
285
286
static gboolean
287
tracker_deserializer_json_next (TrackerSparqlCursor  *cursor,
288
                                GCancellable         *cancellable,
289
                                GError              **error)
290
0
{
291
0
  TrackerDeserializerJson *deserializer =
292
0
    TRACKER_DESERIALIZER_JSON (cursor);
293
294
0
  g_array_set_size (deserializer->columns, 0);
295
296
0
  if (deserializer->init_error) {
297
0
    GError *init_error;
298
299
0
    init_error = g_steal_pointer (&deserializer->init_error);
300
0
    g_propagate_error (error, init_error);
301
0
    return FALSE;
302
0
  }
303
304
0
  if (deserializer->started)
305
0
    deserializer->idx++;
306
307
0
  if (deserializer->idx >= json_array_get_length (deserializer->results))
308
0
    return FALSE;
309
310
0
  if (g_cancellable_set_error_if_cancelled (cancellable, error))
311
0
    return FALSE;
312
313
0
  deserializer->current_row =
314
0
    json_array_get_object_element (deserializer->results,
315
0
                                   deserializer->idx);
316
0
  deserializer->started = TRUE;
317
318
0
  return parse_row (deserializer, error);
319
0
}
320
321
static void
322
tracker_deserializer_json_next_async (TrackerSparqlCursor  *cursor,
323
                                      GCancellable         *cancellable,
324
                                      GAsyncReadyCallback   cb,
325
                                      gpointer              user_data)
326
0
{
327
0
  GError *error = NULL;
328
0
  GTask *task;
329
330
0
  task = g_task_new (cursor, cancellable, cb, user_data);
331
332
0
  if (tracker_sparql_cursor_next (cursor, cancellable, &error))
333
0
    g_task_return_boolean (task, TRUE);
334
0
  else if (!error)
335
0
    g_task_return_boolean (task, FALSE);
336
0
  else
337
0
    g_task_return_error (task, error);
338
339
0
  g_object_unref (task);
340
0
}
341
342
static gboolean
343
tracker_deserializer_json_next_finish (TrackerSparqlCursor  *cursor,
344
                                       GAsyncResult         *res,
345
                                       GError              **error)
346
0
{
347
0
  return g_task_propagate_boolean (G_TASK (res), error);
348
0
}
349
350
gboolean
351
tracker_deserializer_json_get_parser_location (TrackerDeserializer  *deserializer,
352
                                               const char          **name,
353
                                               goffset              *line_no,
354
                                               goffset              *column_no)
355
0
{
356
0
  if (name)
357
0
    *name = tracker_deserializer_get_name (deserializer);
358
359
0
  return FALSE;
360
0
}
361
362
static void
363
tracker_deserializer_json_class_init (TrackerDeserializerJsonClass *klass)
364
0
{
365
0
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
366
0
  TrackerSparqlCursorClass *cursor_class =
367
0
    TRACKER_SPARQL_CURSOR_CLASS (klass);
368
0
  TrackerDeserializerClass *deserializer_class =
369
0
    TRACKER_DESERIALIZER_CLASS (klass);
370
371
0
  object_class->finalize = tracker_deserializer_json_finalize;
372
0
  object_class->constructed = tracker_deserializer_json_constructed;
373
374
0
  cursor_class->get_n_columns = tracker_deserializer_json_get_n_columns;
375
0
  cursor_class->get_value_type = tracker_deserializer_json_get_value_type;
376
0
  cursor_class->get_variable_name = tracker_deserializer_json_get_variable_name;
377
0
  cursor_class->get_string = tracker_deserializer_json_get_string;
378
0
  cursor_class->next = tracker_deserializer_json_next;
379
0
  cursor_class->next_async = tracker_deserializer_json_next_async;
380
0
  cursor_class->next_finish = tracker_deserializer_json_next_finish;
381
382
0
  deserializer_class->get_parser_location =
383
0
    tracker_deserializer_json_get_parser_location;
384
0
}
385
386
static void
387
tracker_deserializer_json_init (TrackerDeserializerJson *deserializer)
388
0
{
389
0
  deserializer->parser = json_parser_new ();
390
0
  deserializer->columns = g_array_new (FALSE, FALSE, sizeof (ColumnData));
391
0
}
392
393
TrackerSparqlCursor *
394
tracker_deserializer_json_new (GInputStream            *stream,
395
                               TrackerNamespaceManager *namespaces)
396
0
{
397
0
  return g_object_new (TRACKER_TYPE_DESERIALIZER_JSON,
398
0
                       "stream", stream,
399
0
                       "namespace-manager", namespaces,
400
                       NULL);
401
0
}