[tracker/wip/carlosg/unrestricted-predicates: 1/6] libtracker-data: Add "triples" virtual table
- From: Carlos Garnacho <carlosg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [tracker/wip/carlosg/unrestricted-predicates: 1/6] libtracker-data: Add "triples" virtual table
- Date: Mon, 14 Jan 2019 10:19:16 +0000 (UTC)
commit 173a5cd3590d0946eddafc756d66f5bb6f429ff2
Author: Carlos Garnacho <carlosg gnome org>
Date: Sun Jan 6 19:12:11 2019 +0100
libtracker-data: Add "triples" virtual table
This eponymous virtual table is able to decompose the full database
in all its composing triples. This may be used to implement queries
with predicate variables in a generic way, and finally support the
kind of queries where we gave up (e.g. "select * { ?s ?p ?o }").
Internally it works by using the TrackerOntologies in order to
split the query into a set of queries for individual
properties/columns, some optimizations happen when specific matches
are given, and the SQLite engine does take care of the ones we don't
optimize, sorting, etc...
This virtual table will also be useful in the future when implementing
CONSTRUCT/DESCRIBE commands.
src/libtracker-data/meson.build | 1 +
src/libtracker-data/tracker-vtab-triples.c | 531 +++++++++++++++++++++++++++++
src/libtracker-data/tracker-vtab-triples.h | 30 ++
3 files changed, 562 insertions(+)
---
diff --git a/src/libtracker-data/meson.build b/src/libtracker-data/meson.build
index 1363228cc..4b120d593 100644
--- a/src/libtracker-data/meson.build
+++ b/src/libtracker-data/meson.build
@@ -60,6 +60,7 @@ libtracker_data = library('tracker-data',
'tracker-sparql-types.c',
'tracker-sparql.c',
'tracker-uuid.c',
+ 'tracker-vtab-triples.c',
tracker_common_enum_header,
tracker_data_enums[0],
tracker_data_enums[1],
diff --git a/src/libtracker-data/tracker-vtab-triples.c b/src/libtracker-data/tracker-vtab-triples.c
new file mode 100644
index 000000000..37210a7be
--- /dev/null
+++ b/src/libtracker-data/tracker-vtab-triples.c
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2019, Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlosg gnome org>
+ */
+#include "config.h"
+
+#include "tracker-vtab-triples.h"
+
+enum {
+ COL_ROWID,
+ COL_GRAPH,
+ COL_SUBJECT,
+ COL_PREDICATE,
+ COL_OBJECT,
+ N_COLS
+};
+
+enum {
+ IDX_COL_GRAPH = 1 << 0,
+ IDX_COL_SUBJECT = 1 << 1,
+ IDX_COL_PREDICATE = 1 << 2,
+ IDX_MATCH_GRAPH_NEG = 1 << 3,
+ IDX_MATCH_SUBJECT_NEG = 1 << 4,
+ IDX_MATCH_PREDICATE_NEG = 1 << 5,
+};
+
+typedef struct {
+ sqlite3 *db;
+ TrackerOntologies *ontologies;
+} TrackerTriplesModule;
+
+typedef struct {
+ struct sqlite3_vtab parent;
+ TrackerTriplesModule *module;
+ GList *cursors;
+} TrackerTriplesVTab;
+
+typedef struct {
+ struct sqlite3_vtab_cursor parent;
+ TrackerTriplesVTab *vtab;
+ struct sqlite3_stmt *stmt;
+
+ struct {
+ sqlite3_value *graph;
+ sqlite3_value *subject;
+ sqlite3_value *predicate;
+ sqlite3_value *object;
+ guint idxFlags;
+ } match;
+
+ GList *properties;
+
+ guint64 rowid;
+ guint finished : 1;
+} TrackerTriplesCursor;
+
+static void
+tracker_triples_module_free (gpointer data)
+{
+ TrackerTriplesModule *module = data;
+
+ g_free (module);
+}
+
+static void
+tracker_triples_vtab_free (gpointer data)
+{
+ TrackerTriplesVTab *vtab = data;
+
+ g_list_free (vtab->cursors);
+ g_free (vtab);
+}
+
+static void
+tracker_triples_cursor_free (gpointer data)
+{
+ TrackerTriplesCursor *cursor = data;
+
+ if (cursor->stmt)
+ sqlite3_finalize (cursor->stmt);
+
+ g_clear_pointer (&cursor->match.graph, sqlite3_value_free);
+ g_clear_pointer (&cursor->match.subject, sqlite3_value_free);
+ g_clear_pointer (&cursor->match.predicate, sqlite3_value_free);
+ g_list_free (cursor->properties);
+ g_free (cursor);
+}
+
+static int
+triples_connect (sqlite3 *db,
+ gpointer data,
+ int argc,
+ const char *const *argv,
+ sqlite3_vtab **vtab_out,
+ char **err_out)
+{
+ TrackerTriplesModule *module = data;
+ TrackerTriplesVTab *vtab;
+ int rc;
+
+ vtab = g_new0 (TrackerTriplesVTab, 1);
+ vtab->module = module;
+
+ rc = sqlite3_declare_vtab (module->db,
+ "CREATE TABLE x("
+ " ID INTEGER,"
+ " graph INTEGER,"
+ " subject INTEGER, "
+ " predicate INTEGER, "
+ " object INTEGER"
+ ")");
+
+ if (rc == SQLITE_OK) {
+ *vtab_out = &vtab->parent;
+ } else {
+ g_free (vtab);
+ }
+
+ return rc;
+}
+
+static int
+triples_best_index (sqlite3_vtab *vtab,
+ sqlite3_index_info *info)
+{
+ gboolean order_by_consumed = FALSE;
+ int i, argv_idx = 1, idx = 0;
+ char *idx_str;
+
+ idx_str = sqlite3_malloc (sizeof (char) * N_COLS);
+ bzero (idx_str, sizeof (char) * N_COLS);
+
+ for (i = 0; i < info->nConstraint; i++) {
+ struct {
+ int mask;
+ int negated_mask;
+ } masks [] = {
+ { IDX_COL_GRAPH, IDX_MATCH_GRAPH_NEG },
+ { IDX_COL_SUBJECT, IDX_MATCH_SUBJECT_NEG },
+ { IDX_COL_PREDICATE, IDX_MATCH_PREDICATE_NEG },
+ { 0, 0 },
+ };
+
+ if (!info->aConstraint[i].usable)
+ continue;
+
+ /* We let object be matched in upper layers, where proper
+ * translation to strings can be done.
+ */
+ if (info->aConstraint[i].iColumn == COL_OBJECT)
+ continue;
+
+ if (info->aConstraint[i].iColumn == COL_ROWID)
+ return SQLITE_ERROR;
+
+ /* We can only check for (in)equality */
+ if (info->aConstraint[i].op != SQLITE_INDEX_CONSTRAINT_EQ &&
+ info->aConstraint[i].op != SQLITE_INDEX_CONSTRAINT_NE &&
+ info->aConstraint[i].op != SQLITE_INDEX_CONSTRAINT_ISNULL &&
+ info->aConstraint[i].op != SQLITE_INDEX_CONSTRAINT_ISNOTNULL)
+ return SQLITE_ERROR;
+
+ /* idxNum encodes the used columns and their operators */
+ idx |= masks[info->aConstraint[i].iColumn - 1].mask;
+
+ if (info->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_NE ||
+ info->aConstraint[i].op == SQLITE_INDEX_CONSTRAINT_ISNOTNULL)
+ idx |= masks[info->aConstraint[i].iColumn - 1].negated_mask;
+
+ /* idxStr stores the mapping between columns and filter arguments */
+ idx_str[info->aConstraint[i].iColumn] = argv_idx - 1;
+ info->aConstraintUsage[i].argvIndex = argv_idx;
+ info->aConstraintUsage[i].omit = FALSE;
+ argv_idx++;
+ }
+
+ info->idxNum = idx;
+ info->orderByConsumed = order_by_consumed;
+ info->idxStr = idx_str;
+ info->needToFreeIdxStr = TRUE;
+
+ return SQLITE_OK;
+}
+
+static int
+triples_disconnect (sqlite3_vtab *vtab)
+{
+ return SQLITE_OK;
+}
+
+static int
+triples_destroy (sqlite3_vtab *vtab)
+{
+ tracker_triples_vtab_free (vtab);
+ return SQLITE_OK;
+}
+
+static int
+triples_open (sqlite3_vtab *vtab_sqlite,
+ sqlite3_vtab_cursor **cursor_ret)
+{
+ TrackerTriplesVTab *vtab = (TrackerTriplesVTab *) vtab_sqlite;
+ TrackerTriplesCursor *cursor;
+
+ cursor = g_new0 (TrackerTriplesCursor, 1);
+ cursor->vtab = vtab;
+ vtab->cursors = g_list_prepend (vtab->cursors, cursor);
+
+ *cursor_ret = &cursor->parent;
+ return SQLITE_OK;
+}
+
+static int
+triples_close (sqlite3_vtab_cursor *vtab_cursor)
+{
+ TrackerTriplesCursor *cursor = (TrackerTriplesCursor *) vtab_cursor;
+ TrackerTriplesVTab *vtab = cursor->vtab;
+
+ vtab->cursors = g_list_remove (vtab->cursors, cursor);
+ tracker_triples_cursor_free (cursor);
+ return SQLITE_OK;
+}
+
+static void
+collect_properties (TrackerTriplesCursor *cursor)
+{
+ TrackerProperty **properties;
+ guint n_properties, i;
+
+ properties = tracker_ontologies_get_properties (cursor->vtab->module->ontologies,
+ &n_properties);
+ for (i = 0; i < n_properties; i++) {
+ if (cursor->match.predicate) {
+ gboolean negated = !!(cursor->match.idxFlags & IDX_MATCH_PREDICATE_NEG);
+ gboolean equals =
+ (sqlite3_value_int64 (cursor->match.predicate) ==
+ tracker_property_get_id (properties[i]));
+
+ if (equals == negated)
+ continue;
+ }
+
+ cursor->properties = g_list_prepend (cursor->properties,
+ properties[i]);
+ }
+}
+
+static gchar *
+convert_to_string (const gchar *table_name,
+ TrackerPropertyType type)
+{
+ switch (type) {
+ case TRACKER_PROPERTY_TYPE_STRING:
+ case TRACKER_PROPERTY_TYPE_INTEGER:
+ return g_strdup_printf ("t.\"%s\"", table_name);
+ case TRACKER_PROPERTY_TYPE_RESOURCE:
+ return g_strdup_printf ("(SELECT Uri FROM Resource WHERE ID = t.\"%s\")",
+ table_name);
+ case TRACKER_PROPERTY_TYPE_BOOLEAN:
+ return g_strdup_printf ("CASE t.\"%s\" "
+ "WHEN 1 THEN 'true' "
+ "WHEN 0 THEN 'false' "
+ "ELSE NULL END",
+ table_name);
+ case TRACKER_PROPERTY_TYPE_DATE:
+ return g_strdup_printf ("strftime (\"%%Y-%%m-%%d\", t.\"%s\", \"unixepoch\")",
+ table_name);
+ case TRACKER_PROPERTY_TYPE_DATETIME:
+ return g_strdup_printf ("SparqlFormatTime (t.\"%s\")",
+ table_name);
+ default:
+ /* Let sqlite convert the expression to string */
+ return g_strdup_printf ("CAST (t.\"%s\" AS TEXT)",
+ table_name);
+ }
+}
+
+static void
+add_arg_check (GString *str,
+ sqlite3_value *value,
+ gboolean negated,
+ const gchar *var_name)
+{
+ if (sqlite3_value_type (value) == SQLITE_NULL) {
+ if (negated)
+ g_string_append (str, "IS NOT NULL ");
+ else
+ g_string_append (str, "IS NULL ");
+ } else {
+ if (negated)
+ g_string_append_printf (str, "!= %s ", var_name);
+ else
+ g_string_append_printf (str, "= %s ", var_name);
+ }
+}
+
+static void
+bind_arg (sqlite3_stmt *stmt,
+ sqlite3_value *value,
+ const gchar *var_name)
+{
+ gint idx;
+
+ if (sqlite3_value_type (value) == SQLITE_NULL)
+ return;
+
+ idx = sqlite3_bind_parameter_index (stmt, var_name);
+ if (idx == 0)
+ return;
+
+ sqlite3_bind_value (stmt, idx, value);
+}
+
+static int
+init_stmt (TrackerTriplesCursor *cursor)
+{
+ TrackerProperty *property;
+ GString *sql;
+ int rc;
+
+ while (cursor->properties) {
+ gchar *string_expr;
+
+ property = cursor->properties->data;
+ cursor->properties = g_list_remove (cursor->properties, property);
+
+ string_expr = convert_to_string (tracker_property_get_name (property),
+ tracker_property_get_data_type (property));
+
+ sql = g_string_new (NULL);
+ g_string_append_printf (sql,
+ "SELECT t.\"%s:graph\", t.ID, "
+ " (SELECT ID From Resource WHERE Uri = \"%s\"), "
+ " %s "
+ "FROM \"%s\" AS t "
+ "WHERE 1 ",
+ tracker_property_get_name (property),
+ tracker_property_get_uri (property),
+ string_expr,
+ tracker_property_get_table_name (property));
+
+ if (cursor->match.graph) {
+ g_string_append_printf (sql,
+ "AND t.\"%s:graph\" ",
+ tracker_property_get_name (property));
+ add_arg_check (sql, cursor->match.graph,
+ !!(cursor->match.idxFlags & IDX_MATCH_GRAPH_NEG),
+ "@g");
+ }
+
+ if (cursor->match.subject) {
+ g_string_append (sql, "AND t.ID ");
+ add_arg_check (sql, cursor->match.subject,
+ !!(cursor->match.idxFlags & IDX_MATCH_SUBJECT_NEG),
+ "@s");
+ }
+
+ rc = sqlite3_prepare_v2 (cursor->vtab->module->db,
+ sql->str, -1, &cursor->stmt, 0);
+ g_string_free (sql, TRUE);
+ g_free (string_expr);
+
+ if (rc == SQLITE_OK) {
+ if (cursor->match.graph)
+ bind_arg (cursor->stmt, cursor->match.graph, "@g");
+ if (cursor->match.subject)
+ bind_arg (cursor->stmt, cursor->match.subject, "@s");
+
+ rc = sqlite3_step (cursor->stmt);
+ }
+
+ if (rc != SQLITE_DONE)
+ return rc;
+
+ g_clear_pointer (&cursor->stmt, sqlite3_finalize);
+ }
+
+ return SQLITE_DONE;
+}
+
+static int
+triples_filter (sqlite3_vtab_cursor *vtab_cursor,
+ int idx,
+ const char *idx_str,
+ int argc,
+ sqlite3_value **argv)
+{
+ TrackerTriplesCursor *cursor = (TrackerTriplesCursor *) vtab_cursor;
+ int rc;
+
+ if (idx & IDX_COL_GRAPH) {
+ int idx = idx_str[COL_GRAPH];
+ cursor->match.graph = sqlite3_value_dup (argv[idx]);
+ }
+
+ if (idx & IDX_COL_SUBJECT) {
+ int idx = idx_str[COL_SUBJECT];
+ cursor->match.subject = sqlite3_value_dup (argv[idx]);
+ }
+
+ if (idx & IDX_COL_PREDICATE) {
+ int idx = idx_str[COL_PREDICATE];
+ cursor->match.predicate = sqlite3_value_dup (argv[idx]);
+ }
+
+ cursor->match.idxFlags = idx;
+
+ collect_properties (cursor);
+
+ rc = init_stmt (cursor);
+
+ if (rc == SQLITE_ROW)
+ return SQLITE_OK;
+
+ return rc;
+}
+
+static int
+triples_next (sqlite3_vtab_cursor *vtab_cursor)
+{
+ TrackerTriplesCursor *cursor = (TrackerTriplesCursor *) vtab_cursor;
+ int rc;
+
+ rc = sqlite3_step (cursor->stmt);
+
+ if (rc == SQLITE_DONE) {
+ g_clear_pointer (&cursor->stmt, sqlite3_finalize);
+ rc = init_stmt (cursor);
+ }
+
+ if (rc == SQLITE_ROW) {
+ cursor->rowid++;
+ } else {
+ cursor->finished = TRUE;
+ }
+
+ if (rc != SQLITE_ROW && rc != SQLITE_DONE)
+ return rc;
+
+ return SQLITE_OK;
+}
+
+static int
+triples_eof (sqlite3_vtab_cursor *vtab_cursor)
+{
+ TrackerTriplesCursor *cursor = (TrackerTriplesCursor *) vtab_cursor;
+
+ return cursor->finished;
+}
+
+static int
+triples_column (sqlite3_vtab_cursor *vtab_cursor,
+ sqlite3_context *context,
+ int n_col)
+{
+ TrackerTriplesCursor *cursor = (TrackerTriplesCursor *) vtab_cursor;
+ sqlite3_value *value;
+
+ if (n_col == COL_ROWID) {
+ sqlite3_result_int64 (context, cursor->rowid);
+ } else {
+ value = sqlite3_column_value (cursor->stmt, n_col - 1);
+ sqlite3_result_value (context, value);
+ }
+
+ return SQLITE_OK;
+}
+
+static int
+triples_rowid (sqlite3_vtab_cursor *vtab_cursor,
+ sqlite_int64 *rowid_out)
+{
+ TrackerTriplesCursor *cursor = (TrackerTriplesCursor *) vtab_cursor;
+
+ *rowid_out = cursor->rowid;
+ return SQLITE_OK;
+}
+
+void
+tracker_vtab_triples_init (sqlite3 *db,
+ TrackerOntologies *ontologies)
+{
+ TrackerTriplesModule *module;
+ static const sqlite3_module triples_module = {
+ 2, /* version */
+ NULL, /* create(), null because this is an eponymous-only table */
+ triples_connect,
+ triples_best_index,
+ triples_disconnect,
+ triples_destroy,
+ triples_open,
+ triples_close,
+ triples_filter,
+ triples_next,
+ triples_eof,
+ triples_column,
+ triples_rowid,
+ NULL, /* update */
+ NULL, /* begin */
+ NULL, /* sync */
+ NULL, /* commit */
+ NULL, /* rollback */
+ NULL, /* find function */
+ NULL, /* rename */
+ NULL, /* savepoint */
+ NULL, /* release */
+ NULL, /* rollback to */
+ };
+
+ module = g_new0 (TrackerTriplesModule, 1);
+ module->db = db;
+ g_set_object (&module->ontologies, ontologies);
+ sqlite3_create_module_v2 (db, "tracker_triples", &triples_module,
+ module, tracker_triples_module_free);
+}
diff --git a/src/libtracker-data/tracker-vtab-triples.h b/src/libtracker-data/tracker-vtab-triples.h
new file mode 100644
index 000000000..7ca124da9
--- /dev/null
+++ b/src/libtracker-data/tracker-vtab-triples.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019, Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Carlos Garnacho <carlosg gnome org>
+ */
+#include <sqlite3.h>
+#include "tracker-ontologies.h"
+
+#ifndef __TRACKER_VTAB_TRIPLES_H__
+#define __TRACKER_VTAB_TRIPLES_H__
+
+void tracker_vtab_triples_init (sqlite3 *db,
+ TrackerOntologies *ontologies);
+
+#endif /* __TRACKER_VTAB_TRIPLES_H__ */
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]