Coverage Report

Created: 2026-04-12 06:29

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tinysparql/src/libtinysparql/remote/tracker-remote-statement.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2021, 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
#include "config.h"
23
24
#include "tracker-remote-statement.h"
25
26
#include <tracker-common.h>
27
28
#include "core/tracker-sparql-grammar.h"
29
#include "core/tracker-sparql-parser.h"
30
#include "tracker-private.h"
31
32
struct _TrackerRemoteStatement
33
{
34
  TrackerSparqlStatement parent_instance;
35
  TrackerNodeTree *parser_tree;
36
  GHashTable *bindings;
37
};
38
39
0
G_DEFINE_TYPE (TrackerRemoteStatement,
40
0
               tracker_remote_statement,
41
0
               TRACKER_TYPE_SPARQL_STATEMENT)
42
0
43
0
static void
44
0
tracker_remote_statement_finalize (GObject *object)
45
0
{
46
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (object);
47
48
0
  if (remote_stmt->parser_tree)
49
0
    tracker_node_tree_free (remote_stmt->parser_tree);
50
0
  g_hash_table_unref (remote_stmt->bindings);
51
52
0
  G_OBJECT_CLASS (tracker_remote_statement_parent_class)->finalize (object);
53
0
}
54
55
static void
56
tracker_remote_statement_bind_int (TrackerSparqlStatement *stmt,
57
                                   const gchar            *name,
58
                                   gint64                  value)
59
0
{
60
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
61
0
  GValue *val;
62
63
0
  val = g_new0 (GValue, 1);
64
0
  g_value_init (val, G_TYPE_INT64);
65
0
  g_value_set_int64 (val, value);
66
67
0
  g_hash_table_insert (remote_stmt->bindings,
68
0
                       g_strdup (name),
69
0
                       val);
70
0
}
71
72
static void
73
tracker_remote_statement_bind_boolean (TrackerSparqlStatement *stmt,
74
                                       const gchar            *name,
75
                                       gboolean                value)
76
0
{
77
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
78
0
  GValue *val;
79
80
0
  val = g_new0 (GValue, 1);
81
0
  g_value_init (val, G_TYPE_BOOLEAN);
82
0
  g_value_set_boolean (val, value);
83
84
0
  g_hash_table_insert (remote_stmt->bindings,
85
0
                       g_strdup (name),
86
0
                       val);
87
0
}
88
89
static void
90
tracker_remote_statement_bind_string (TrackerSparqlStatement *stmt,
91
                                      const gchar            *name,
92
                                      const gchar            *value)
93
0
{
94
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
95
0
  GValue *val;
96
97
0
  val = g_new0 (GValue, 1);
98
0
  g_value_init (val, G_TYPE_STRING);
99
0
  g_value_set_string (val, value);
100
101
0
  g_hash_table_insert (remote_stmt->bindings,
102
0
                       g_strdup (name),
103
0
                       val);
104
0
}
105
106
static void
107
tracker_remote_statement_bind_double (TrackerSparqlStatement *stmt,
108
                                      const gchar            *name,
109
                                      gdouble                 value)
110
0
{
111
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
112
0
  GValue *val;
113
114
0
  val = g_new0 (GValue, 1);
115
0
  g_value_init (val, G_TYPE_DOUBLE);
116
0
  g_value_set_double (val, value);
117
118
0
  g_hash_table_insert (remote_stmt->bindings,
119
0
                       g_strdup (name),
120
0
                       val);
121
0
}
122
123
static void
124
tracker_remote_statement_bind_datetime (TrackerSparqlStatement *stmt,
125
                                        const gchar            *name,
126
                                        GDateTime              *value)
127
0
{
128
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
129
0
  GValue *val;
130
131
0
  val = g_new0 (GValue, 1);
132
0
  g_value_init (val, G_TYPE_DATE_TIME);
133
0
  g_value_set_boxed (val, value);
134
135
0
  g_hash_table_insert (remote_stmt->bindings,
136
0
                       g_strdup (name),
137
0
                       val);
138
0
}
139
140
static void
141
tracker_remote_statement_bind_langstring (TrackerSparqlStatement *stmt,
142
                                          const gchar            *name,
143
                                          const gchar            *value,
144
                                          const gchar            *langtag)
145
0
{
146
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
147
0
  GValue *val;
148
149
0
  val = g_new0 (GValue, 1);
150
0
  g_value_init (val, G_TYPE_BYTES);
151
0
  g_value_take_boxed (val, tracker_sparql_make_langstring (value, langtag));
152
153
0
  g_hash_table_insert (remote_stmt->bindings,
154
0
                       g_strdup (name),
155
0
                       val);
156
0
}
157
158
static void
159
append_gvalue (GString *str,
160
               const GValue  *value)
161
0
{
162
0
  if (G_VALUE_HOLDS_BOOLEAN (value)) {
163
0
    g_string_append_printf (str, "%s",
164
0
                            g_value_get_boolean (value) ?
165
0
                            "true" : "false");
166
0
  } else if (G_VALUE_HOLDS_INT64 (value)) {
167
0
    g_string_append_printf (str, "%" G_GINT64_FORMAT,
168
0
                            g_value_get_int64 (value));
169
0
  } else if (G_VALUE_HOLDS_DOUBLE (value)) {
170
0
    gchar buf[G_ASCII_DTOSTR_BUF_SIZE + 1];
171
172
0
    g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value));
173
0
    g_string_append (str, buf);
174
0
  } else if (G_VALUE_TYPE (value) == G_TYPE_DATE_TIME) {
175
0
    GDateTime *datetime;
176
0
    gchar *datetime_str;
177
178
0
    datetime = g_value_get_boxed (value);
179
0
    datetime_str = tracker_date_format_iso8601 (datetime);
180
0
    g_string_append_printf (str, "\"%s\"", datetime_str);
181
0
    g_free (datetime_str);
182
0
  } else if (G_VALUE_TYPE (value) == G_TYPE_BYTES) {
183
0
    GBytes *bytes;
184
0
    const gchar *data;
185
0
    gsize len, str_len;
186
187
0
    bytes = g_value_get_boxed (value);
188
0
    data = g_bytes_get_data (bytes, &len);
189
0
    str_len = strlen (data);
190
0
    g_string_append_printf (str, "\"%s\"", data);
191
0
    if (str_len < len) {
192
0
      const gchar *langtag;
193
0
      langtag = &data[str_len + 1];
194
0
      g_string_append_printf (str, "@%s", langtag);
195
0
    }
196
0
  } else if (G_VALUE_HOLDS_STRING (value)) {
197
0
    const gchar *val = g_value_get_string (value);
198
0
    int len = strlen (val);
199
0
    gchar *end;
200
0
    gboolean is_number = FALSE;
201
202
    /* Try to detect numbers anyway, since we use to allow
203
     * loose typing in other connection types.
204
     */
205
0
    g_ascii_strtoll (val, &end, 10);
206
0
    is_number = (end == &val[len]);
207
208
0
    if (!is_number) {
209
0
      g_ascii_strtod (val, &end);
210
0
      is_number = (end == &val[len]);
211
0
    }
212
213
0
    if (is_number)
214
0
      g_string_append (str, val);
215
0
    else
216
0
      g_string_append_printf (str, "\"%s\"", val);
217
0
  }
218
0
}
219
220
static gchar *
221
apply_bindings (TrackerSparqlStatement  *stmt,
222
                GHashTable              *bindings,
223
                GError                 **error)
224
0
{
225
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
226
0
  const gchar *query = tracker_sparql_statement_get_sparql (stmt);
227
0
  GString *str = g_string_new (NULL);
228
0
  TrackerParserNode *node = tracker_node_tree_get_root (remote_stmt->parser_tree);
229
230
0
  for (node = tracker_sparql_parser_tree_find_first (node, TRUE);
231
0
       node;
232
0
       node = tracker_sparql_parser_tree_find_next (node, TRUE)) {
233
0
    const TrackerGrammarRule *rule;
234
0
    gssize start, end;
235
236
0
    if (!tracker_parser_node_get_extents (node, &start, &end)) {
237
      /* Skip over 0-len nodes */
238
0
      continue;
239
0
    }
240
241
0
    rule = tracker_parser_node_get_rule (node);
242
243
0
    if (tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL,
244
0
                                   TERMINAL_TYPE_PARAMETERIZED_VAR)) {
245
0
      gchar *param_name;
246
0
      const GValue *value;
247
248
0
      param_name = g_strndup (&query[start], end - start);
249
0
      value = g_hash_table_lookup (bindings, &param_name[1]);
250
0
      if (!value) {
251
0
        g_set_error (error,
252
0
                     TRACKER_SPARQL_ERROR,
253
0
                     TRACKER_SPARQL_ERROR_PARSE,
254
0
                     "No binding found for variable %s",
255
0
                     param_name);
256
0
        g_string_free (str, TRUE);
257
0
        g_free (param_name);
258
0
        return NULL;
259
0
      }
260
261
0
      append_gvalue (str, value);
262
0
      g_free (param_name);
263
0
    } else {
264
0
      g_string_append_len (str,
265
0
                           &query[start],
266
0
                           end - start);
267
0
    }
268
269
0
    g_string_append_c (str, ' ');
270
0
  }
271
272
0
  return g_string_free (str, FALSE);
273
0
}
274
275
static TrackerSparqlCursor *
276
execute_statement (TrackerSparqlStatement  *stmt,
277
                   GHashTable              *bindings,
278
                   GCancellable            *cancellable,
279
                   GError                 **error)
280
0
{
281
0
  TrackerSparqlCursor *cursor;
282
0
  gchar *rewritten_query = NULL;
283
284
0
  if (g_hash_table_size (bindings) > 0) {
285
0
    rewritten_query = apply_bindings (stmt, bindings, error);
286
0
    if (!rewritten_query)
287
0
      return NULL;
288
0
  }
289
290
0
  cursor = tracker_sparql_connection_query (tracker_sparql_statement_get_connection (stmt),
291
0
                                            rewritten_query ? rewritten_query :
292
0
                                            tracker_sparql_statement_get_sparql (stmt),
293
0
                                            cancellable,
294
0
                                            error);
295
0
  g_free (rewritten_query);
296
297
0
  return cursor;
298
0
}
299
300
TrackerSparqlCursor *
301
tracker_remote_statement_execute (TrackerSparqlStatement  *stmt,
302
                                  GCancellable            *cancellable,
303
                                  GError                 **error)
304
0
{
305
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
306
307
0
  return execute_statement (stmt, remote_stmt->bindings, cancellable, error);
308
0
}
309
310
static void
311
execute_in_thread (GTask        *task,
312
                   gpointer      object,
313
                   gpointer      task_data,
314
                   GCancellable *cancellable)
315
0
{
316
0
  TrackerSparqlCursor *cursor;
317
0
  GHashTable *bindings = task_data;
318
0
  GError *error = NULL;
319
320
0
  if (g_task_return_error_if_cancelled (task))
321
0
    return;
322
323
0
  cursor = execute_statement (object,
324
0
                              bindings,
325
0
                              g_task_get_cancellable (task),
326
0
                              &error);
327
0
  if (error)
328
0
    g_task_return_error (task, error);
329
0
  else
330
0
    g_task_return_pointer (task, cursor, g_object_unref);
331
332
0
  g_object_unref (task);
333
0
}
334
335
static void
336
free_gvalue (gpointer data)
337
0
{
338
0
  g_value_unset (data);
339
0
  g_free (data);
340
0
}
341
342
static GHashTable *
343
create_bindings_ht (void)
344
0
{
345
0
  return g_hash_table_new_full (g_str_hash, g_str_equal,
346
0
              g_free, free_gvalue);
347
0
}
348
349
static GHashTable *
350
copy_values_deep (GHashTable *values)
351
0
{
352
0
  GHashTable *copy;
353
0
  GHashTableIter iter;
354
0
  gpointer key, val;
355
356
0
  copy = create_bindings_ht ();
357
0
  g_hash_table_iter_init (&iter, values);
358
359
0
  while (g_hash_table_iter_next (&iter, &key, &val)) {
360
0
    GValue *copy_value;
361
362
0
    copy_value = g_new0 (GValue, 1);
363
0
    g_value_init (copy_value, G_VALUE_TYPE (val));
364
0
    g_value_copy (val, copy_value);
365
366
0
    g_hash_table_insert (copy, g_strdup (key), copy_value);
367
0
  }
368
369
0
  return copy;
370
0
}
371
372
static void
373
tracker_remote_statement_execute_async (TrackerSparqlStatement *stmt,
374
                                        GCancellable           *cancellable,
375
                                        GAsyncReadyCallback     callback,
376
                                        gpointer                user_data)
377
0
{
378
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
379
0
  GHashTable *bindings;
380
0
  GTask *task;
381
382
0
  bindings = copy_values_deep (remote_stmt->bindings);
383
384
0
  task = g_task_new (stmt, cancellable, callback, user_data);
385
0
  g_task_set_task_data (task, bindings, (GDestroyNotify) g_hash_table_unref);
386
0
  g_task_run_in_thread (task, execute_in_thread);
387
0
}
388
389
static TrackerSparqlCursor *
390
tracker_remote_statement_execute_finish (TrackerSparqlStatement  *stmt,
391
                                         GAsyncResult            *res,
392
                                         GError                 **error)
393
0
{
394
0
  return g_task_propagate_pointer (G_TASK (res), error);
395
0
}
396
397
static void
398
tracker_remote_statement_clear_bindings (TrackerSparqlStatement *stmt)
399
0
{
400
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
401
402
0
  g_hash_table_remove_all (remote_stmt->bindings);
403
0
}
404
405
static void
406
serialize_cb (GObject      *source,
407
              GAsyncResult *res,
408
              gpointer      user_data)
409
0
{
410
0
  GInputStream *istream;
411
0
  GError *error = NULL;
412
0
  GTask *task = user_data;
413
414
0
  istream = tracker_sparql_connection_serialize_finish (TRACKER_SPARQL_CONNECTION (source),
415
0
                                                        res, &error);
416
0
  if (error)
417
0
    g_task_return_error (task, error);
418
0
  else
419
0
    g_task_return_pointer (task, istream, g_object_unref);
420
421
0
  g_object_unref (task);
422
0
}
423
424
static void
425
tracker_remote_statement_serialize_async (TrackerSparqlStatement *stmt,
426
                                          TrackerSerializeFlags   flags,
427
                                          TrackerRdfFormat        format,
428
                                          GCancellable           *cancellable,
429
                                          GAsyncReadyCallback     callback,
430
                                          gpointer                user_data)
431
0
{
432
0
  TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
433
0
  gchar *rewritten_query = NULL;
434
0
  GError *error = NULL;
435
0
  GTask *task;
436
437
0
  task = g_task_new (stmt, cancellable, callback, user_data);
438
439
0
  if (g_hash_table_size (remote_stmt->bindings) > 0) {
440
0
    rewritten_query = apply_bindings (stmt,
441
0
                                      remote_stmt->bindings,
442
0
                                      &error);
443
0
    if (!rewritten_query) {
444
0
      g_task_return_error (task, error);
445
0
      g_object_unref (task);
446
0
      return;
447
0
    }
448
0
  }
449
450
0
  tracker_sparql_connection_serialize_async (tracker_sparql_statement_get_connection (stmt),
451
0
                                             flags,
452
0
                                             format,
453
0
                                             rewritten_query ? rewritten_query :
454
0
                                             tracker_sparql_statement_get_sparql (stmt),
455
0
                                             cancellable,
456
0
                                             serialize_cb,
457
0
                                             task);
458
0
  g_free (rewritten_query);
459
0
}
460
461
static GInputStream *
462
tracker_remote_statement_serialize_finish (TrackerSparqlStatement  *stmt,
463
                                           GAsyncResult            *res,
464
                                           GError                 **error)
465
0
{
466
0
  return g_task_propagate_pointer (G_TASK (res), error);
467
0
}
468
469
static void
470
tracker_remote_statement_class_init (TrackerRemoteStatementClass *klass)
471
0
{
472
0
  TrackerSparqlStatementClass *stmt_class = TRACKER_SPARQL_STATEMENT_CLASS (klass);
473
0
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
474
475
0
  object_class->finalize = tracker_remote_statement_finalize;
476
477
0
  stmt_class->bind_int = tracker_remote_statement_bind_int;
478
0
  stmt_class->bind_boolean = tracker_remote_statement_bind_boolean;
479
0
  stmt_class->bind_string = tracker_remote_statement_bind_string;
480
0
  stmt_class->bind_double = tracker_remote_statement_bind_double;
481
0
  stmt_class->bind_datetime = tracker_remote_statement_bind_datetime;
482
0
  stmt_class->bind_langstring = tracker_remote_statement_bind_langstring;
483
0
  stmt_class->execute = tracker_remote_statement_execute;
484
0
  stmt_class->execute_async = tracker_remote_statement_execute_async;
485
0
  stmt_class->execute_finish = tracker_remote_statement_execute_finish;
486
0
  stmt_class->clear_bindings = tracker_remote_statement_clear_bindings;
487
0
  stmt_class->serialize_async = tracker_remote_statement_serialize_async;
488
0
  stmt_class->serialize_finish = tracker_remote_statement_serialize_finish;
489
0
}
490
491
static void
492
tracker_remote_statement_init (TrackerRemoteStatement *stmt)
493
0
{
494
0
  stmt->bindings = create_bindings_ht ();
495
0
}
496
497
TrackerSparqlStatement *
498
tracker_remote_statement_new (TrackerSparqlConnection  *conn,
499
                              const gchar              *query,
500
                              GError                  **error)
501
0
{
502
0
  TrackerRemoteStatement *remote_stmt;
503
0
  TrackerSparqlStatement *stmt;
504
505
0
  stmt = g_object_new (TRACKER_TYPE_REMOTE_STATEMENT,
506
0
                              "connection", conn,
507
0
                              "sparql", query,
508
0
                              NULL);
509
0
  remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
510
0
  remote_stmt->parser_tree =
511
0
    tracker_sparql_parse_query (TRACKER_SPARQL_PARSE_ALLOW_EXTENSIONS,
512
0
                                tracker_sparql_statement_get_sparql (stmt),
513
0
                                -1, NULL, error);
514
0
  if (!remote_stmt->parser_tree) {
515
0
    g_object_unref (stmt);
516
0
    return NULL;
517
0
  }
518
519
0
  return stmt;
520
0
}