Coverage Report

Created: 2026-05-30 06:50

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