/*
* Copyright (C) 2018 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "XbSilo"
#include "config.h"
#include <gio/gio.h>
#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;
}