/* * Copyright (C) 2018 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #define G_LOG_DOMAIN "XbSilo" #include "config.h" #include #include "xb-machine.h" #include "xb-opcode-private.h" #include "xb-query-private.h" #include "xb-silo-private.h" #include "xb-stack-private.h" typedef struct { GObject parent_instance; GPtrArray *sections; /* of XbQuerySection */ XbSilo *silo; XbQueryFlags flags; gchar *xpath; guint limit; } XbQueryPrivate; G_DEFINE_TYPE_WITH_PRIVATE (XbQuery, xb_query, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_query_get_instance_private (o)) /** * xb_query_get_sections: * @self: a #XbQuery * * Gets the sections that make up the query. * * Returns: (transfer none) (element-type XbQuerySection): sections * * Since: 0.1.4 **/ GPtrArray * xb_query_get_sections (XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_QUERY (self), NULL); return priv->sections; } /** * xb_query_get_xpath: * @self: a #XbQuery * * Gets the XPath string that created the query. * * Returns: string * * Since: 0.1.4 **/ const gchar * xb_query_get_xpath (XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_QUERY (self), NULL); return priv->xpath; } static gchar * xb_query_section_to_string (XbQuerySection *sect) { GString *str = g_string_new (NULL); if (sect->kind == XB_SILO_QUERY_KIND_PARENT) g_string_append (str, ".."); else if (sect->kind == XB_SILO_QUERY_KIND_WILDCARD) g_string_append (str, "*"); else g_string_append (str, sect->element); if (sect->predicates != NULL && sect->predicates->len > 0) { g_string_append (str, "["); for (guint j = 0; j < sect->predicates->len; j++) { XbStack *stack = g_ptr_array_index (sect->predicates, j); g_autofree gchar *tmp = xb_stack_to_string (stack); g_string_append (str, tmp); } g_string_append (str, "]"); } return g_string_free (str, FALSE); } /** * xb_query_to_string: * @self: a #XbQuery * * Gets the XPath that was used for the query. * * Returns: string * * Since: 0.1.13 **/ gchar * xb_query_to_string (XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE (self); GString *str = g_string_new (NULL); for (guint i = 0; i < priv->sections->len; i++) { XbQuerySection *sect = g_ptr_array_index (priv->sections, i); g_autofree gchar *tmp = xb_query_section_to_string (sect); g_string_append (str, tmp); if (i != priv->sections->len - 1) g_string_append (str, "/"); } return g_string_free (str, FALSE); } /** * xb_query_get_limit: * @self: a #XbQuery * * Gets the results limit on this query, where 0 is 'all'. * * Returns: integer, default 0 * * Since: 0.1.4 **/ guint xb_query_get_limit (XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_QUERY (self), 0); return priv->limit; } /** * xb_query_set_limit: * @self: a #XbQuery * @limit: integer * * Sets the results limit on this query, where 0 is 'all'. * * Since: 0.1.4 **/ void xb_query_set_limit (XbQuery *self, guint limit) { XbQueryPrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_QUERY (self)); priv->limit = limit; } /** * xb_query_get_flags: * @self: a #XbQuery * * Gets the flags used for this query. * * Returns: #XbQueryFlags, default %XB_QUERY_FLAG_NONE * * Since: 0.1.15 **/ XbQueryFlags xb_query_get_flags (XbQuery *self) { XbQueryPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_QUERY (self), 0); return priv->flags; } /** * xb_query_set_flags: * @self: a #XbQuery * @flags: a #XbQueryFlags, e.g. %XB_QUERY_FLAG_USE_INDEXES * * Sets the flags to use for this query. * * Since: 0.1.15 **/ void xb_query_set_flags (XbQuery *self, XbQueryFlags flags) { XbQueryPrivate *priv = GET_PRIVATE (self); g_return_if_fail (XB_IS_QUERY (self)); priv->flags = flags; } static XbOpcode * xb_query_get_bound_opcode (XbQuery *self, guint idx) { XbQueryPrivate *priv = GET_PRIVATE (self); guint idx_cnt = 0; for (guint i = 0; i < priv->sections->len; i++) { XbQuerySection *section = g_ptr_array_index (priv->sections, i); if (section->predicates == NULL) continue; for (guint j = 0; j < section->predicates->len; j++) { XbStack *stack = g_ptr_array_index (section->predicates, j); for (guint k = 0; k < xb_stack_get_size (stack); k++) { XbOpcode *op = xb_stack_peek (stack, k); if (xb_opcode_is_bound (op)) { if (idx == idx_cnt++) return op; } } } } return NULL; } /** * xb_query_bind_str: * @self: a #XbQuery * @idx: an integer index * @str: string to assign to the bound variable * @error: a #GError, or %NULL * * Assigns a string to a bound value specified using `?`. * * Returns: %TRUE if the @idx existed * * Since: 0.1.4 **/ gboolean xb_query_bind_str (XbQuery *self, guint idx, const gchar *str, GError **error) { XbOpcode *op; g_return_val_if_fail (XB_IS_QUERY (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* get the correct opcode */ op = xb_query_get_bound_opcode (self, idx); if (op == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no bound opcode with index %u", idx); return FALSE; } xb_opcode_bind_str (op, g_strdup (str), g_free); return TRUE; } /** * xb_query_bind_val: * @self: a #XbQuery * @idx: an integer index * @val: value to assign to the bound variable * @error: a #GError, or %NULL * * Assigns a string to a bound value specified using `?`. * * Returns: %TRUE if the @idx existed * * Since: 0.1.4 **/ gboolean xb_query_bind_val (XbQuery *self, guint idx, guint32 val, GError **error) { XbOpcode *op; g_return_val_if_fail (XB_IS_QUERY (self), FALSE); g_return_val_if_fail (error == NULL || *error == NULL, FALSE); /* get the correct opcode */ op = xb_query_get_bound_opcode (self, idx); if (op == NULL) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "no bound opcode with index %u", idx); return FALSE; } xb_opcode_bind_val (op, val); return TRUE; } static void xb_query_section_free (XbQuerySection *section) { if (section->predicates != NULL) g_ptr_array_unref (section->predicates); g_free (section->element); g_slice_free (XbQuerySection, section); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(XbQuerySection, xb_query_section_free) static gboolean xb_query_repair_opcode_texi (XbQuery *self, XbOpcode *op, GError **error) { XbQueryPrivate *priv = GET_PRIVATE (self); if (xb_opcode_get_val (op) == XB_SILO_UNSET) { const gchar *tmp = xb_opcode_get_str (op); guint32 val = xb_silo_strtab_index_lookup (priv->silo, tmp); if (val == XB_SILO_UNSET) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "indexed string '%s' was unfound", tmp); return FALSE; } xb_opcode_set_val (op, val); } return TRUE; } static gboolean xb_query_parse_predicate (XbQuery *self, XbQuerySection *section, const gchar *text, gssize text_len, GError **error) { XbQueryPrivate *priv = GET_PRIVATE (self); XbMachineParseFlags machine_flags = XB_MACHINE_PARSE_FLAG_NONE; g_autoptr(XbStack) opcodes = NULL; /* set flags */ if (priv->flags & XB_QUERY_FLAG_OPTIMIZE) machine_flags |= XB_MACHINE_PARSE_FLAG_OPTIMIZE; /* parse */ opcodes = xb_machine_parse_full (xb_silo_get_machine (priv->silo), text, text_len, machine_flags, error); if (opcodes == NULL) return FALSE; /* repair or convert the indexed strings */ if (priv->flags & XB_QUERY_FLAG_USE_INDEXES) { for (guint i = 0; i < xb_stack_get_size (opcodes); i++) { XbOpcode *op = xb_stack_peek (opcodes, i); if (xb_opcode_get_kind (op) != XB_OPCODE_KIND_INDEXED_TEXT) continue; if (!xb_query_repair_opcode_texi (self, op, error)) return FALSE; } } else { for (guint i = 0; i < xb_stack_get_size (opcodes); i++) { XbOpcode *op = xb_stack_peek (opcodes, i); if (xb_opcode_get_kind (op) == XB_OPCODE_KIND_INDEXED_TEXT) xb_opcode_set_kind (op, XB_OPCODE_KIND_TEXT); } } /* create array if it does not exist */ if (section->predicates == NULL) section->predicates = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_stack_unref); g_ptr_array_add (section->predicates, g_steal_pointer (&opcodes)); return TRUE; } static XbQuerySection * xb_query_parse_section (XbQuery *self, const gchar *xpath, GError **error) { XbQueryPrivate *priv = GET_PRIVATE (self); g_autoptr(XbQuerySection) section = g_slice_new0 (XbQuerySection); guint start = 0; /* common XPath sections */ if (g_strcmp0 (xpath, "parent::*") == 0 || g_strcmp0 (xpath, "..") == 0) { section->kind = XB_SILO_QUERY_KIND_PARENT; return g_steal_pointer (§ion); } /* parse element and predicate */ for (guint i = 0; xpath[i] != '\0'; i++) { if (start == 0 && xpath[i] == '[') { if (section->element == NULL) section->element = g_strndup (xpath, i); start = i; continue; } if (start > 0 && xpath[i] == ']') { if (!xb_query_parse_predicate (self, section, xpath + start + 1, i - start - 1, error)) { return NULL; } start = 0; continue; } } /* incomplete predicate */ if (start != 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "predicate %s was unfinished, missing ']'", xpath + start); return NULL; } if (section->element == NULL) section->element = g_strdup (xpath); if (g_strcmp0 (section->element, "child::*") == 0 || g_strcmp0 (section->element, "*") == 0) { section->kind = XB_SILO_QUERY_KIND_WILDCARD; return g_steal_pointer (§ion); } section->element_idx = xb_silo_get_strtab_idx (priv->silo, section->element); if (section->element_idx == XB_SILO_UNSET) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "element name %s is unknown in silo", section->element); return NULL; } return g_steal_pointer (§ion); } static gboolean xb_query_parse (XbQuery *self, const gchar *xpath, GError **error) { XbQueryPrivate *priv = GET_PRIVATE (self); XbQuerySection *section; g_autoptr(GString) acc = g_string_new (NULL); // g_debug ("parsing XPath %s", xpath); for (gsize i = 0; xpath[i] != '\0'; i++) { /* escaped chars */ if (xpath[i] == '\\') { if (xpath[i+1] == '/' || xpath[i+1] == 't' || xpath[i+1] == 'n') { g_string_append_c (acc, xpath[i+1]); i += 1; continue; } } /* split */ if (xpath[i] == '/') { if (acc->len == 0) { g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, "xpath section empty"); return FALSE; } section = xb_query_parse_section (self, acc->str, error); if (section == NULL) return FALSE; g_ptr_array_add (priv->sections, section); g_string_truncate (acc, 0); continue; } g_string_append_c (acc, xpath[i]); } /* add any remaining section */ section = xb_query_parse_section (self, acc->str, error); if (section == NULL) return FALSE; g_ptr_array_add (priv->sections, section); return TRUE; } /** * xb_query_new_full: * @silo: a #XbSilo * @xpath: The XPath query * @flags: some #XbQueryFlags, e.g. #XB_QUERY_FLAG_USE_INDEXES * @error: the #GError, or %NULL * * Creates a query to be used by @silo. It may be quicker to create a query * manually and re-use it multiple times. * * Returns: (transfer full): a #XbQuery * * Since: 0.1.6 **/ XbQuery * xb_query_new_full (XbSilo *silo, const gchar *xpath, XbQueryFlags flags, GError **error) { g_autoptr(XbQuery) self = g_object_new (XB_TYPE_QUERY, NULL); XbQueryPrivate *priv = GET_PRIVATE (self); g_return_val_if_fail (XB_IS_SILO (silo), NULL); g_return_val_if_fail (error == NULL || *error == NULL, NULL); /* create */ priv->silo = g_object_ref (silo); priv->xpath = g_strdup (xpath); priv->flags = flags; priv->sections = g_ptr_array_new_with_free_func ((GDestroyNotify) xb_query_section_free); /* add each section */ if (!xb_query_parse (self, xpath, error)) return NULL; /* nothing here! */ if (priv->sections->len == 0) { g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "No query sections for '%s'", xpath); return NULL; } /* success */ return g_steal_pointer (&self); } /** * xb_query_new: * @silo: a #XbSilo * @xpath: The XPath query * @error: the #GError, or %NULL * * Creates a query to be used by @silo. It may be quicker to create a query * manually and re-use it multiple times. * * Returns: (transfer full): a #XbQuery * * Since: 0.1.4 **/ XbQuery * xb_query_new (XbSilo *silo, const gchar *xpath, GError **error) { return xb_query_new_full (silo, xpath, XB_QUERY_FLAG_OPTIMIZE | XB_QUERY_FLAG_USE_INDEXES, error); } static void xb_query_init (XbQuery *self) { } static void xb_query_finalize (GObject *obj) { XbQuery *self = XB_QUERY (obj); XbQueryPrivate *priv = GET_PRIVATE (self); g_object_unref (priv->silo); g_ptr_array_unref (priv->sections); g_free (priv->xpath); G_OBJECT_CLASS (xb_query_parent_class)->finalize (obj); } static void xb_query_class_init (XbQueryClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = xb_query_finalize; }