Coverage Report

Created: 2025-11-24 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libxmlb/src/xb-silo-query.c
Line
Count
Source
1
/*
2
 * Copyright 2018 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "XbSilo"
8
9
#include "config.h"
10
11
#include <gio/gio.h>
12
#include <string.h>
13
14
#include "xb-node-private.h"
15
#include "xb-opcode-private.h"
16
#include "xb-opcode.h"
17
#include "xb-query-private.h"
18
#include "xb-silo-node.h"
19
#include "xb-silo-query-private.h"
20
#include "xb-stack-private.h"
21
#include "xb-value-bindings-private.h"
22
23
static gboolean
24
xb_silo_query_node_matches(XbSilo *self,
25
         XbMachine *machine,
26
         XbSiloNode *sn,
27
         XbQuerySection *section,
28
         XbSiloQueryData *query_data,
29
         XbValueBindings *bindings,
30
         guint bindings_offset,
31
         guint *bindings_offset_end_out,
32
         gboolean *result,
33
         GError **error)
34
0
{
35
  /* we have an index into the string table */
36
0
  if (section->element_idx != sn->element_name &&
37
0
      section->kind != XB_SILO_QUERY_KIND_WILDCARD) {
38
0
    *result = FALSE;
39
0
    return TRUE;
40
0
  }
41
42
  /* for section */
43
0
  query_data->position += 1;
44
45
  /* check predicates */
46
0
  if (section->predicates != NULL) {
47
0
    for (guint i = 0; i < section->predicates->len; i++) {
48
0
      XbStack *opcodes = g_ptr_array_index(section->predicates, i);
49
0
      g_auto(XbValueBindings) predicate_bindings = XB_VALUE_BINDINGS_INIT();
50
0
      guint predicate_bindings_idx = 0;
51
0
      XbValueBindings *predicate_bindings_ptr = NULL;
52
53
0
      if (bindings != NULL)
54
0
        predicate_bindings_ptr = &predicate_bindings;
55
56
      /* set up the bindings for this predicate */
57
0
      for (guint k = 0; bindings != NULL && k < xb_stack_get_size(opcodes); k++) {
58
0
        XbOpcode *op = xb_stack_peek(opcodes, k);
59
0
        if (xb_opcode_is_binding(op)) {
60
          /* ignore errors as they’ll be caught by xb_machine_run() */
61
0
          xb_value_bindings_copy_binding(bindings,
62
0
                       bindings_offset +
63
0
                     predicate_bindings_idx,
64
0
                       &predicate_bindings,
65
0
                       predicate_bindings_idx);
66
0
          predicate_bindings_idx++;
67
0
        }
68
0
      }
69
70
      /* run the predicate; pass NULL for the bindings iff
71
       * (bindings == NULL), as that means we’ve been called
72
       * with pre-0.3.0-style pre-bound values */
73
0
      if (!xb_machine_run_with_bindings(machine,
74
0
                opcodes,
75
0
                predicate_bindings_ptr,
76
0
                result,
77
0
                query_data,
78
0
                error))
79
0
        return FALSE;
80
81
0
      bindings_offset += predicate_bindings_idx;
82
0
    }
83
0
  }
84
85
0
  if (bindings_offset_end_out != NULL)
86
0
    *bindings_offset_end_out = bindings_offset;
87
88
  /* success */
89
0
  return TRUE;
90
0
}
91
92
/**
93
 * XbSiloQueryHelperFlags:
94
 * @XB_SILO_QUERY_HELPER_NONE: No flags set.
95
 * @XB_SILO_QUERY_HELPER_USE_SN: Return #XbSiloNodes as results, rather than
96
 *    wrapping them in #XbNode. This assumes that they’ll be wrapped later.
97
 * @XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE: Always cache the #XbNode objects
98
 *
99
 * Flags for #XbSiloQueryHelper.
100
 *
101
 * Since: 0.2.0
102
 */
103
typedef enum {
104
  XB_SILO_QUERY_HELPER_NONE = 0,
105
  XB_SILO_QUERY_HELPER_USE_SN = 1 << 0,
106
  XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE = 1 << 1,
107
} XbSiloQueryHelperFlags;
108
109
typedef struct {
110
  GPtrArray *sections; /* of XbQuerySection */
111
  GPtrArray *results;  /* of XbNode or XbSiloNode (see @flags) */
112
  XbValueBindings *bindings;
113
  GHashTable *results_hash; /* of sn:1 */
114
  guint limit;
115
  XbSiloQueryHelperFlags flags;
116
  XbSiloQueryData *query_data;
117
} XbSiloQueryHelper;
118
119
static gboolean
120
xb_silo_query_section_add_result(XbSilo *self, XbSiloQueryHelper *helper, XbSiloNode *sn)
121
0
{
122
0
  if (g_hash_table_lookup(helper->results_hash, sn) != NULL)
123
0
    return FALSE;
124
0
  if (helper->flags & XB_SILO_QUERY_HELPER_USE_SN) {
125
0
    g_ptr_array_add(helper->results, sn);
126
0
  } else {
127
0
    gboolean force_node_cache =
128
0
        (helper->flags & XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE) > 0;
129
0
    g_ptr_array_add(helper->results, xb_silo_create_node(self, sn, force_node_cache));
130
0
  }
131
0
  g_hash_table_add(helper->results_hash, sn);
132
0
  return helper->results->len == helper->limit;
133
0
}
134
135
/*
136
 * @parent: (allow-none)
137
 */
138
static gboolean
139
xb_silo_query_section_root(XbSilo *self,
140
         XbSiloNode *sn,
141
         guint i,
142
         guint bindings_offset,
143
         XbSiloQueryHelper *helper,
144
         GError **error)
145
0
{
146
0
  XbMachine *machine = xb_silo_get_machine(self);
147
0
  XbSiloQueryData *query_data = helper->query_data;
148
0
  XbQuerySection *section = g_ptr_array_index(helper->sections, i);
149
150
  /* handle parent */
151
0
  if (section->kind == XB_SILO_QUERY_KIND_PARENT) {
152
0
    XbSiloNode *parent;
153
0
    if (sn == NULL) {
154
0
      g_set_error_literal(error,
155
0
              G_IO_ERROR,
156
0
              G_IO_ERROR_INVALID_ARGUMENT,
157
0
              "cannot obtain parent for root");
158
0
      return FALSE;
159
0
    }
160
0
    parent = xb_silo_get_parent_node(self, sn, error);
161
0
    if (parent == NULL)
162
0
      return FALSE;
163
0
    if (i == helper->sections->len - 1) {
164
0
      xb_silo_query_section_add_result(self, helper, parent);
165
0
      return TRUE;
166
0
    }
167
0
    return xb_silo_query_section_root(self,
168
0
              parent,
169
0
              i + 1,
170
0
              bindings_offset,
171
0
              helper,
172
0
              error);
173
0
  }
174
175
  /* no node means root */
176
0
  if (sn == NULL) {
177
0
    sn = xb_silo_get_root_node(self, error);
178
0
    if (sn == NULL)
179
0
      return FALSE;
180
0
  } else {
181
0
    g_autoptr(GError) error_local = NULL;
182
0
    sn = xb_silo_get_child_node(self, sn, &error_local);
183
0
    if (sn == NULL) {
184
0
      if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT))
185
0
        return TRUE;
186
0
      g_propagate_error(error, g_steal_pointer(&error_local));
187
0
      return FALSE;
188
0
    }
189
0
  }
190
191
  /* set up level pointer */
192
0
  query_data->position = 0;
193
194
  /* continue matching children ".." */
195
0
  do {
196
0
    XbSiloNode *sn_new;
197
0
    gboolean result = TRUE;
198
0
    guint bindings_offset_end = 0;
199
0
    query_data->sn = sn;
200
0
    if (!xb_silo_query_node_matches(self,
201
0
            machine,
202
0
            sn,
203
0
            section,
204
0
            query_data,
205
0
            helper->bindings,
206
0
            bindings_offset,
207
0
            &bindings_offset_end,
208
0
            &result,
209
0
            error))
210
0
      return FALSE;
211
0
    if (result) {
212
0
      if (i == helper->sections->len - 1) {
213
0
        if (xb_silo_query_section_add_result(self, helper, sn))
214
0
          break;
215
0
      } else {
216
0
        if (!xb_silo_query_section_root(self,
217
0
                sn,
218
0
                i + 1,
219
0
                bindings_offset_end,
220
0
                helper,
221
0
                error))
222
0
          return FALSE;
223
0
        if (helper->results->len > 0 &&
224
0
            helper->results->len == helper->limit)
225
0
          break;
226
0
      }
227
0
    }
228
0
    if (sn->next == 0x0)
229
0
      break;
230
0
    sn_new = xb_silo_get_node(self, sn->next, error);
231
0
    if (sn_new == NULL)
232
0
      return FALSE;
233
0
    if (sn_new <= sn) {
234
0
      g_set_error(error,
235
0
            G_IO_ERROR,
236
0
            G_IO_ERROR_INVALID_DATA,
237
0
            "silo node was invalid: %p -> %p",
238
0
            sn,
239
0
            sn_new);
240
0
      return FALSE;
241
0
    }
242
0
    sn = sn_new;
243
0
  } while (TRUE);
244
0
  return TRUE;
245
0
}
246
247
static gboolean
248
xb_silo_query_part(XbSilo *self,
249
       XbSiloNode *sroot,
250
       GPtrArray *results,
251
       GHashTable *results_hash,
252
       XbQuery *query,
253
       XbQueryContext *context,
254
       gboolean first_result_only,
255
       XbSiloQueryData *query_data,
256
       XbSiloQueryHelperFlags flags,
257
       GError **error)
258
0
{
259
0
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
260
0
  XbSiloQueryHelper helper = {
261
0
      .results = results,
262
0
      .bindings = (context != NULL) ? xb_query_context_get_bindings(context) : NULL,
263
0
      .limit = first_result_only   ? 1
264
0
         : (context != NULL) ? xb_query_context_get_limit(context)
265
0
           : xb_query_get_limit(query),
266
0
      .flags = flags,
267
0
      .results_hash = results_hash,
268
0
      .query_data = query_data,
269
0
  };
270
0
  XbQueryFlags query_flags = (context != NULL) ? xb_query_context_get_flags(context)
271
0
                 : xb_query_get_flags(query);
272
0
  G_GNUC_END_IGNORE_DEPRECATIONS
273
274
  /* find each section */
275
0
  helper.sections = xb_query_get_sections(query);
276
0
  if (query_flags & XB_QUERY_FLAG_FORCE_NODE_CACHE)
277
0
    helper.flags |= XB_SILO_QUERY_HELPER_FORCE_NODE_CACHE;
278
0
  return xb_silo_query_section_root(self, sroot, 0, 0, &helper, error);
279
0
}
280
281
/* Returns an array with (element-type XbSiloNode) if
282
 * %XB_SILO_QUERY_HELPER_USE_SN is set, and (element-type XbNode) otherwise. */
283
static GPtrArray *
284
silo_query_with_root(XbSilo *self,
285
         XbNode *n,
286
         const gchar *xpath,
287
         guint limit,
288
         XbSiloQueryHelperFlags flags,
289
         GError **error)
290
0
{
291
0
  XbSiloNode *sn = NULL;
292
0
  g_auto(GStrv) split = NULL;
293
0
  g_autoptr(GHashTable) results_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
294
0
  g_autoptr(GPtrArray) results = NULL;
295
0
  g_autoptr(GTimer) timer = xb_silo_start_profile(self);
296
0
  XbSiloQueryData query_data = {
297
0
      .sn = NULL,
298
0
      .position = 0,
299
0
  };
300
301
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
302
0
  g_return_val_if_fail(xpath != NULL, NULL);
303
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
304
305
  /* empty silo */
306
0
  if (xb_silo_is_empty(self)) {
307
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "silo has no data");
308
0
    return NULL;
309
0
  }
310
311
0
  if (flags & XB_SILO_QUERY_HELPER_USE_SN)
312
0
    results = g_ptr_array_new_with_free_func(NULL);
313
0
  else
314
0
    results = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
315
316
  /* subtree query */
317
0
  if (n != NULL) {
318
0
    sn = xb_node_get_sn(n);
319
0
    if (xpath[0] == '/') {
320
0
      g_set_error_literal(error,
321
0
              G_IO_ERROR,
322
0
              G_IO_ERROR_NOT_SUPPORTED,
323
0
              "XPath node query not supported");
324
0
      return NULL;
325
0
    }
326
0
  } else {
327
    /* assume it's just a root query */
328
0
    if (xpath[0] == '/')
329
0
      xpath++;
330
0
  }
331
332
  /* do 'or' searches */
333
0
  split = g_strsplit(xpath, "|", -1);
334
0
  for (guint i = 0; split[i] != NULL; i++) {
335
0
    g_autoptr(GError) error_local = NULL;
336
0
    g_autoptr(XbQuery) query = xb_query_new(self, split[i], &error_local);
337
0
    g_auto(XbQueryContext) context = XB_QUERY_CONTEXT_INIT();
338
339
0
    if (query == NULL) {
340
0
      if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) &&
341
0
          (split[i + 1] != NULL || results->len > 0)) {
342
0
        if (xb_silo_get_profile_flags(self) & XB_SILO_PROFILE_FLAG_DEBUG) {
343
0
          g_debug("ignoring for OR statement: %s",
344
0
            error_local->message);
345
0
        }
346
0
        continue;
347
0
      }
348
0
      g_propagate_prefixed_error(error,
349
0
               g_steal_pointer(&error_local),
350
0
               "failed to process %s: ",
351
0
               xpath);
352
0
      return NULL;
353
0
    }
354
355
0
    xb_query_context_set_limit(&context, limit);
356
0
    if (!xb_silo_query_part(self,
357
0
          sn,
358
0
          results,
359
0
          results_hash,
360
0
          query,
361
0
          &context,
362
0
          FALSE,
363
0
          &query_data,
364
0
          flags,
365
0
          error)) {
366
0
      return NULL;
367
0
    }
368
0
  }
369
370
  /* profile */
371
0
  if (xb_silo_get_profile_flags(self) & XB_SILO_PROFILE_FLAG_XPATH) {
372
0
    xb_silo_add_profile(self,
373
0
            timer,
374
0
            "query on %s with `%s` limit=%u -> %u results",
375
0
            n != NULL ? xb_node_get_element(n) : "/",
376
0
            xpath,
377
0
            limit,
378
0
            results->len);
379
0
  }
380
381
  /* nothing found */
382
0
  if (results->len == 0) {
383
0
    g_set_error(error,
384
0
          G_IO_ERROR,
385
0
          G_IO_ERROR_NOT_FOUND,
386
0
          "no results for XPath query '%s'",
387
0
          xpath);
388
0
    return NULL;
389
0
  }
390
0
  return g_steal_pointer(&results);
391
0
}
392
393
/**
394
 * xb_silo_query_with_root: (skip)
395
 * @self: a #XbSilo
396
 * @n: (allow-none): a #XbNode
397
 * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
398
 * @limit: maximum number of results to return, or 0 for "all"
399
 * @error: the #GError, or %NULL
400
 *
401
 * Searches the silo using an XPath query, returning up to @limit results.
402
 *
403
 * It is safe to call this function from a different thread to the one that
404
 * created the #XbSilo.
405
 *
406
 * Please note: Only a subset of XPath is supported.
407
 *
408
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
409
 *
410
 * Since: 0.1.0
411
 **/
412
GPtrArray *
413
xb_silo_query_with_root(XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error)
414
0
{
415
0
  return silo_query_with_root(self, n, xpath, limit, XB_SILO_QUERY_HELPER_NONE, error);
416
0
}
417
418
/**
419
 * xb_silo_query_sn_with_root: (skip)
420
 * @self: a #XbSilo
421
 * @n: (allow-none): a #XbNode
422
 * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
423
 * @limit: maximum number of results to return, or 0 for "all"
424
 * @error: the #GError, or %NULL
425
 *
426
 * A version of xb_silo_query_with_root() which returns results as #XbSiloNodes,
427
 * rather than as #XbNodes. This is intended to be used internally to save on
428
 * intermediate #XbNode allocations.
429
 *
430
 * Returns: (transfer container) (element-type XbSiloNode): results, or %NULL if unfound
431
 *
432
 * Since: 0.2.0
433
 **/
434
GPtrArray *
435
xb_silo_query_sn_with_root(XbSilo *self, XbNode *n, const gchar *xpath, guint limit, GError **error)
436
0
{
437
0
  return silo_query_with_root(self, n, xpath, limit, XB_SILO_QUERY_HELPER_USE_SN, error);
438
0
}
439
440
static void
441
_g_ptr_array_reverse(GPtrArray *array)
442
0
{
443
0
  guint last_idx = array->len - 1;
444
0
  for (guint i = 0; i < array->len / 2; i++) {
445
0
    gpointer tmp = array->pdata[i];
446
0
    array->pdata[i] = array->pdata[last_idx - i];
447
0
    array->pdata[last_idx - i] = tmp;
448
0
  }
449
0
}
450
451
/**
452
 * xb_silo_query_with_root_full: (skip)
453
 * @self: a #XbSilo
454
 * @n: (allow-none): a #XbNode
455
 * @query: an #XbQuery
456
 * @context: (nullable) (transfer none): context including values bound to opcodes of type
457
 *     %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if
458
 *     the query doesn’t need any context
459
 * @first_result_only: %TRUE if only the first result is going to be used; this
460
 *     overrides the limit set in @context, and may perform other optimisations
461
 * @error: the #GError, or %NULL
462
 *
463
 * Searches the silo using an XPath query, returning up to @limit results.
464
 *
465
 * It is safe to call this function from a different thread to the one that
466
 * created the #XbSilo.
467
 *
468
 * Please note: Only a subset of XPath is supported.
469
 *
470
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
471
 *
472
 * Since: 0.3.0
473
 **/
474
GPtrArray *
475
xb_silo_query_with_root_full(XbSilo *self,
476
           XbNode *n,
477
           XbQuery *query,
478
           XbQueryContext *context,
479
           gboolean first_result_only,
480
           GError **error)
481
0
{
482
0
  XbSiloNode *sn = NULL;
483
0
  g_autoptr(GHashTable) results_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
484
0
  g_autoptr(GPtrArray) results =
485
0
      g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
486
0
  g_autoptr(GTimer) timer = xb_silo_start_profile(self);
487
0
  XbSiloQueryData query_data = {
488
0
      .sn = NULL,
489
0
      .position = 0,
490
0
  };
491
0
  G_GNUC_BEGIN_IGNORE_DEPRECATIONS
492
0
  XbQueryFlags query_flags = (context != NULL) ? xb_query_context_get_flags(context)
493
0
                 : xb_query_get_flags(query);
494
0
  G_GNUC_END_IGNORE_DEPRECATIONS
495
496
  /* convert the XB_OPCODE_KIND_BOUND_TEXT into a XB_OPCODE_KIND_BOUND_INDEXED_TEXT */
497
0
  if (context != NULL && query_flags & XB_QUERY_FLAG_USE_INDEXES) {
498
0
    XbValueBindings *bindings = xb_query_context_get_bindings(context);
499
0
    if (!xb_value_bindings_indexed_text_lookup(bindings, self, error))
500
0
      return NULL;
501
0
  }
502
503
  /* empty silo */
504
0
  if (xb_silo_is_empty(self)) {
505
0
    g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "silo has no data");
506
0
    return NULL;
507
0
  }
508
509
  /* subtree query */
510
0
  if (n != NULL)
511
0
    sn = xb_node_get_sn(n);
512
513
  /* only one query allowed */
514
0
  if (!xb_silo_query_part(self,
515
0
        sn,
516
0
        results,
517
0
        results_hash,
518
0
        query,
519
0
        context,
520
0
        first_result_only,
521
0
        &query_data,
522
0
        XB_SILO_QUERY_HELPER_NONE,
523
0
        error))
524
0
    return NULL;
525
526
  /* profile */
527
0
  if (xb_silo_get_profile_flags(self) & XB_SILO_PROFILE_FLAG_XPATH) {
528
0
    g_autofree gchar *tmp = xb_query_to_string(query);
529
0
    g_autofree gchar *bindings_str = NULL;
530
0
    G_GNUC_BEGIN_IGNORE_DEPRECATIONS
531
0
    guint limit = first_result_only    ? 1
532
0
            : (context != NULL) ? xb_query_context_get_limit(context)
533
0
              : xb_query_get_limit(query);
534
0
    G_GNUC_END_IGNORE_DEPRECATIONS
535
536
0
    if (context != NULL) {
537
0
      XbValueBindings *bindings = xb_query_context_get_bindings(context);
538
0
      bindings_str = xb_value_bindings_to_string(bindings);
539
0
    }
540
541
0
    xb_silo_add_profile(self,
542
0
            timer,
543
0
            "query on %s with `%s` [%s] limit=%u -> %u results",
544
0
            n != NULL ? xb_node_get_element(n) : "/",
545
0
            tmp,
546
0
            bindings_str != NULL ? bindings_str : "",
547
0
            limit,
548
0
            results->len);
549
0
  }
550
551
  /* nothing found */
552
0
  if (results->len == 0) {
553
0
    if (error != NULL) {
554
0
      g_autofree gchar *tmp = xb_query_to_string(query);
555
0
      g_set_error(error,
556
0
            G_IO_ERROR,
557
0
            G_IO_ERROR_NOT_FOUND,
558
0
            "no results for XPath query '%s'",
559
0
            tmp);
560
0
    }
561
0
    return NULL;
562
0
  }
563
564
  /* reverse order */
565
0
  if (query_flags & XB_QUERY_FLAG_REVERSE)
566
0
    _g_ptr_array_reverse(results);
567
568
0
  return g_steal_pointer(&results);
569
0
}
570
571
/**
572
 * xb_silo_query_full:
573
 * @self: a #XbSilo
574
 * @query: an #XbQuery
575
 * @error: the #GError, or %NULL
576
 *
577
 * Searches the silo using an XPath query.
578
 *
579
 * It is safe to call this function from a different thread to the one that
580
 * created the #XbSilo.
581
 *
582
 * Please note: Only a subset of XPath is supported.
583
 *
584
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
585
 *
586
 * Since: 0.1.13
587
 **/
588
GPtrArray *
589
xb_silo_query_full(XbSilo *self, XbQuery *query, GError **error)
590
0
{
591
0
  return xb_silo_query_with_context(self, query, NULL, error);
592
0
}
593
594
/**
595
 * xb_silo_query_with_context:
596
 * @self: a #XbSilo
597
 * @query: an #XbQuery
598
 * @context: (nullable) (transfer none): context including values bound to opcodes of type
599
 *     %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if
600
 *     the query doesn’t need any context
601
 * @error: the #GError, or %NULL
602
 *
603
 * Searches the silo using an XPath query.
604
 *
605
 * It is safe to call this function from a different thread to the one that
606
 * created the #XbSilo.
607
 *
608
 * Please note: Only a subset of XPath is supported.
609
 *
610
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
611
 *
612
 * Since: 0.3.0
613
 **/
614
GPtrArray *
615
xb_silo_query_with_context(XbSilo *self, XbQuery *query, XbQueryContext *context, GError **error)
616
0
{
617
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
618
0
  g_return_val_if_fail(XB_IS_QUERY(query), NULL);
619
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
620
0
  return xb_silo_query_with_root_full(self, NULL, query, context, FALSE, error);
621
0
}
622
623
/**
624
 * xb_silo_query_first_full:
625
 * @self: a #XbSilo
626
 * @query: an #XbQuery
627
 * @error: the #GError, or %NULL
628
 *
629
 * Searches the silo using an XPath query, returning up to one result.
630
 *
631
 * It is safe to call this function from a different thread to the one that
632
 * created the #XbSilo.
633
 *
634
 * Please note: Only a tiny subset of XPath 1.0 is supported.
635
 *
636
 * Returns: (transfer full): a #XbNode, or %NULL if unfound
637
 *
638
 * Since: 0.1.13
639
 **/
640
XbNode *
641
xb_silo_query_first_full(XbSilo *self, XbQuery *query, GError **error)
642
0
{
643
0
  return xb_silo_query_first_with_context(self, query, NULL, error);
644
0
}
645
646
/**
647
 * xb_silo_query_first_with_context:
648
 * @self: a #XbSilo
649
 * @query: an #XbQuery
650
 * @context: (nullable) (transfer none): context including values bound to opcodes of type
651
 *     %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if
652
 *     the query doesn’t need any context
653
 * @error: the #GError, or %NULL
654
 *
655
 * Searches the silo using an XPath query, returning up to one result.
656
 *
657
 * It is safe to call this function from a different thread to the one that
658
 * created the #XbSilo.
659
 *
660
 * Please note: Only a tiny subset of XPath 1.0 is supported.
661
 *
662
 * Returns: (transfer full): a #XbNode, or %NULL if unfound
663
 *
664
 * Since: 0.3.0
665
 **/
666
XbNode *
667
xb_silo_query_first_with_context(XbSilo *self,
668
         XbQuery *query,
669
         XbQueryContext *context,
670
         GError **error)
671
0
{
672
0
  g_autoptr(GPtrArray) results = NULL;
673
674
0
  results = xb_silo_query_with_root_full(self, NULL, query, context, TRUE, error);
675
676
0
  if (results == NULL)
677
0
    return NULL;
678
0
  return g_object_ref(g_ptr_array_index(results, 0));
679
0
}
680
681
/**
682
 * xb_silo_query:
683
 * @self: a #XbSilo
684
 * @xpath: an XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
685
 * @limit: maximum number of results to return, or 0 for "all"
686
 * @error: the #GError, or %NULL
687
 *
688
 * Searches the silo using an XPath query, returning up to @limit results.
689
 *
690
 * It is safe to call this function from a different thread to the one that
691
 * created the #XbSilo.
692
 *
693
 * Please note: Only a subset of XPath is supported.
694
 *
695
 * Returns: (transfer container) (element-type XbNode): results, or %NULL if unfound
696
 *
697
 * Since: 0.1.0
698
 **/
699
GPtrArray *
700
xb_silo_query(XbSilo *self, const gchar *xpath, guint limit, GError **error)
701
0
{
702
0
  g_return_val_if_fail(XB_IS_SILO(self), NULL);
703
0
  g_return_val_if_fail(xpath != NULL, NULL);
704
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
705
0
  return xb_silo_query_with_root(self, NULL, xpath, limit, error);
706
0
}
707
708
/**
709
 * xb_silo_query_first:
710
 * @self: a #XbSilo
711
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
712
 * @error: the #GError, or %NULL
713
 *
714
 * Searches the silo using an XPath query, returning up to one result.
715
 *
716
 * It is safe to call this function from a different thread to the one that
717
 * created the #XbSilo.
718
 *
719
 * Please note: Only a tiny subset of XPath 1.0 is supported.
720
 *
721
 * Returns: (transfer full): a #XbNode, or %NULL if unfound
722
 *
723
 * Since: 0.1.0
724
 **/
725
XbNode *
726
xb_silo_query_first(XbSilo *self, const gchar *xpath, GError **error)
727
0
{
728
0
  g_autoptr(GPtrArray) results = NULL;
729
0
  results = xb_silo_query_with_root(self, NULL, xpath, 1, error);
730
0
  if (results == NULL)
731
0
    return NULL;
732
0
  return g_object_ref(g_ptr_array_index(results, 0));
733
0
}
734
735
/**
736
 * xb_silo_query_build_index:
737
 * @self: a #XbSilo
738
 * @xpath: An XPath, e.g. `/components/component[@type=desktop]/id[abe.desktop]`
739
 * @attr: (nullable): Attribute name, e.g. `type`, or NULL
740
 * @error: the #GError, or %NULL
741
 *
742
 * Adds the `attr()` or `text()` results of a query to the index.
743
 *
744
 * Returns: %TRUE for success
745
 *
746
 * Since: 0.1.4
747
 **/
748
gboolean
749
xb_silo_query_build_index(XbSilo *self, const gchar *xpath, const gchar *attr, GError **error)
750
0
{
751
0
  g_autoptr(GError) error_local = NULL;
752
0
  g_autoptr(GPtrArray) array = NULL;
753
754
0
  g_return_val_if_fail(XB_IS_SILO(self), FALSE);
755
0
  g_return_val_if_fail(xpath != NULL, FALSE);
756
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
757
758
  /* do the query */
759
0
  array =
760
0
      silo_query_with_root(self, NULL, xpath, 0, XB_SILO_QUERY_HELPER_USE_SN, &error_local);
761
0
  if (array == NULL) {
762
0
    if (g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT) ||
763
0
        g_error_matches(error_local, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
764
0
      g_debug("ignoring index: %s", error_local->message);
765
0
      return TRUE;
766
0
    }
767
0
    g_propagate_error(error, g_steal_pointer(&error_local));
768
0
    return FALSE;
769
0
  }
770
771
  /* add each attribute name AND value */
772
0
  for (guint i = 0; i < array->len; i++) {
773
0
    XbSiloNode *sn = g_ptr_array_index(array, i);
774
0
    if (attr != NULL) {
775
0
      guint8 attr_count = xb_silo_node_get_attr_count(sn);
776
0
      for (guint8 j = 0; j < attr_count; j++) {
777
0
        XbSiloNodeAttr *a = xb_silo_node_get_attr(sn, j);
778
0
        if (!xb_silo_strtab_index_insert(self, a->attr_name, error))
779
0
          return FALSE;
780
0
        if (!xb_silo_strtab_index_insert(self, a->attr_value, error))
781
0
          return FALSE;
782
0
      }
783
0
    } else if (xb_silo_node_get_text_idx(sn) != XB_SILO_UNSET) {
784
0
      if (!xb_silo_strtab_index_insert(self,
785
0
               xb_silo_node_get_text_idx(sn),
786
0
               error))
787
0
        return FALSE;
788
0
    }
789
0
  }
790
791
  /* success */
792
0
  return TRUE;
793
0
}