summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam@afuera.me.uk>2020-03-02 19:06:28 +0000
committerSam Thursfield <sam@afuera.me.uk>2020-03-02 19:06:28 +0000
commit63736da59ac0d676cac4a78bd49ca2577dd1b486 (patch)
tree7392308eaf2d98006538b702ceb67d24eba05d36
parent05b18cc5d17fd8d4348d238cd8d876b6fb882396 (diff)
parented204139a1af9a2b539b0b07429bb7df6096eab7 (diff)
downloadtracker-63736da59ac0d676cac4a78bd49ca2577dd1b486.tar.gz
Merge branch 'wip/carlosg/bus-statements' into 'master'
Implement TrackerSparqlStatement for bus connections Closes #179 See merge request GNOME/tracker!190
-rw-r--r--src/libtracker-bus/meson.build1
-rw-r--r--src/libtracker-bus/tracker-bus-statement.vala93
-rw-r--r--src/libtracker-bus/tracker-bus.vala52
-rw-r--r--src/libtracker-data/tracker-db-interface-sqlite.c12
-rw-r--r--src/libtracker-data/tracker-sparql.c5
-rw-r--r--src/libtracker-sparql/libtracker-sparql-intermediate-c.vapi1
-rw-r--r--src/libtracker-sparql/tracker-endpoint-dbus.c159
-rw-r--r--tests/libtracker-sparql/meson.build11
-rw-r--r--tests/libtracker-sparql/statement/filter.out1
-rw-r--r--tests/libtracker-sparql/statement/filter.rq5
-rw-r--r--tests/libtracker-sparql/statement/object-iri.out1
-rw-r--r--tests/libtracker-sparql/statement/object-iri.rq3
-rw-r--r--tests/libtracker-sparql/statement/object.out1
-rw-r--r--tests/libtracker-sparql/statement/object.rq3
-rw-r--r--tests/libtracker-sparql/statement/simple-error.rq1
-rw-r--r--tests/libtracker-sparql/statement/simple.out1
-rw-r--r--tests/libtracker-sparql/statement/simple.rq1
-rw-r--r--tests/libtracker-sparql/statement/subject-2.out1
-rw-r--r--tests/libtracker-sparql/statement/subject.out1
-rw-r--r--tests/libtracker-sparql/statement/subject.rq3
-rw-r--r--tests/libtracker-sparql/tracker-statement-test.c291
21 files changed, 591 insertions, 56 deletions
diff --git a/src/libtracker-bus/meson.build b/src/libtracker-bus/meson.build
index 775afc73f..91fd74c34 100644
--- a/src/libtracker-bus/meson.build
+++ b/src/libtracker-bus/meson.build
@@ -2,6 +2,7 @@ libtracker_bus = static_library('tracker-bus',
'tracker-bus.vala',
'tracker-namespace.vala',
'tracker-bus-fd-cursor.vala',
+ 'tracker-bus-statement.vala',
'../libtracker-common/libtracker-common.vapi',
tracker_common_enum_header,
c_args: tracker_c_args + [
diff --git a/src/libtracker-bus/tracker-bus-statement.vala b/src/libtracker-bus/tracker-bus-statement.vala
new file mode 100644
index 000000000..e5d78b933
--- /dev/null
+++ b/src/libtracker-bus/tracker-bus-statement.vala
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020, Red Hat Ltd.
+ *
+ * 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>
+ */
+
+public class Tracker.Bus.Statement : Tracker.Sparql.Statement {
+ private DBusConnection bus;
+ private string query;
+ private string dbus_name;
+ private string object_path;
+ private HashTable<string,GLib.Variant> arguments;
+
+ private const string ENDPOINT_IFACE = "org.freedesktop.Tracker1.Endpoint";
+
+ public Statement (DBusConnection bus, string dbus_name, string object_path, string query) {
+ Object ();
+ this.bus = bus;
+ this.dbus_name = dbus_name;
+ this.object_path = object_path;
+ this.query = query;
+ this.arguments = new HashTable<string, GLib.Variant> (str_hash, str_equal);
+ }
+
+ public override void bind_boolean (string name, bool value) {
+ this.arguments.insert (name, new GLib.Variant.boolean (value));
+ }
+
+ public override void bind_double (string name, double value) {
+ this.arguments.insert (name, new GLib.Variant.double (value));
+ }
+
+ public override void bind_int (string name, int64 value) {
+ this.arguments.insert (name, new GLib.Variant.int64 (value));
+ }
+
+ public override void bind_string (string name, string value) {
+ this.arguments.insert (name, new GLib.Variant.string (value));
+ }
+
+ public override void clear_bindings () {
+ this.arguments.remove_all ();
+ }
+
+ private VariantBuilder? get_arguments () {
+ if (this.arguments.size () == 0)
+ return null;
+
+ VariantBuilder builder = new VariantBuilder (new VariantType ("a{sv}"));
+ HashTableIter<string, Variant> iter = HashTableIter<string, Variant> (this.arguments);
+ unowned string arg;
+ unowned GLib.Variant value;
+
+ while (iter.next (out arg, out value))
+ builder.add ("{sv}", arg, value);
+
+ return builder;
+ }
+
+ public override Sparql.Cursor execute (GLib.Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError {
+ // use separate main context for sync operation
+ var context = new MainContext ();
+ var loop = new MainLoop (context, false);
+ context.push_thread_default ();
+ AsyncResult async_res = null;
+ execute_async.begin (cancellable, (o, res) => {
+ async_res = res;
+ loop.quit ();
+ });
+ loop.run ();
+ context.pop_thread_default ();
+ return execute_async.end (async_res);
+ }
+
+ public async override Sparql.Cursor execute_async (GLib.Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError {
+ return yield Tracker.Bus.Connection.perform_query_call (bus, dbus_name, object_path, query, get_arguments (), cancellable);
+ }
+}
diff --git a/src/libtracker-bus/tracker-bus.vala b/src/libtracker-bus/tracker-bus.vala
index 03b3030f1..1a0d01db4 100644
--- a/src/libtracker-bus/tracker-bus.vala
+++ b/src/libtracker-bus/tracker-bus.vala
@@ -34,7 +34,7 @@ public class Tracker.Bus.Connection : Tracker.Sparql.Connection {
new Sparql.Error.INTERNAL ("");
}
- void pipe (out UnixInputStream input, out UnixOutputStream output) throws IOError {
+ static void pipe (out UnixInputStream input, out UnixOutputStream output) throws IOError {
int pipefd[2];
if (Posix.pipe (pipefd) < 0) {
throw new IOError.FAILED ("Pipe creation failed");
@@ -43,7 +43,7 @@ public class Tracker.Bus.Connection : Tracker.Sparql.Connection {
output = new UnixOutputStream (pipefd[1], true);
}
- void handle_error_reply (DBusMessage message) throws Sparql.Error, IOError, DBusError {
+ static void handle_error_reply (DBusMessage message) throws Sparql.Error, IOError, DBusError {
try {
message.to_gerror ();
} catch (IOError e_io) {
@@ -57,31 +57,16 @@ public class Tracker.Bus.Connection : Tracker.Sparql.Connection {
}
}
- void send_query (string sparql, UnixOutputStream output, Cancellable? cancellable, AsyncReadyCallback? callback) throws GLib.IOError, GLib.Error {
+ static void send_query (DBusConnection bus, string dbus_name, string object_path, string sparql, VariantBuilder? arguments, UnixOutputStream output, Cancellable? cancellable, AsyncReadyCallback? callback) throws GLib.IOError, GLib.Error {
var message = new DBusMessage.method_call (dbus_name, object_path, ENDPOINT_IFACE, "Query");
var fd_list = new UnixFDList ();
- message.set_body (new Variant ("(sh)", sparql, fd_list.append (output.fd)));
+ message.set_body (new Variant ("(sha{sv})", sparql, fd_list.append (output.fd), arguments));
message.set_unix_fd_list (fd_list);
bus.send_message_with_reply.begin (message, DBusSendMessageFlags.NONE, int.MAX, null, cancellable, callback);
}
- public override Sparql.Cursor query (string sparql, Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, DBusError {
- // use separate main context for sync operation
- var context = new MainContext ();
- var loop = new MainLoop (context, false);
- context.push_thread_default ();
- AsyncResult async_res = null;
- query_async.begin (sparql, cancellable, (o, res) => {
- async_res = res;
- loop.quit ();
- });
- loop.run ();
- context.pop_thread_default ();
- return query_async.end (async_res);
- }
-
- public async override Sparql.Cursor query_async (string sparql, Cancellable? cancellable = null) throws Sparql.Error, GLib.Error, GLib.IOError, DBusError {
+ public static async Sparql.Cursor perform_query_call (DBusConnection bus, string dbus_name, string object_path, string sparql, VariantBuilder? arguments, Cancellable? cancellable) throws GLib.IOError, GLib.Error {
UnixInputStream input;
UnixOutputStream output;
pipe (out input, out output);
@@ -89,10 +74,10 @@ public class Tracker.Bus.Connection : Tracker.Sparql.Connection {
// send D-Bus request
AsyncResult dbus_res = null;
bool received_result = false;
- send_query (sparql, output, cancellable, (o, res) => {
+ send_query (bus, dbus_name, object_path, sparql, arguments, output, cancellable, (o, res) => {
dbus_res = res;
if (received_result) {
- query_async.callback ();
+ perform_query_call.callback ();
}
});
@@ -119,6 +104,29 @@ public class Tracker.Bus.Connection : Tracker.Sparql.Connection {
return new FDCursor (mem_stream.steal_data (), mem_stream.data_size, variable_names);
}
+ public override Sparql.Cursor query (string sparql, Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, DBusError {
+ // use separate main context for sync operation
+ var context = new MainContext ();
+ var loop = new MainLoop (context, false);
+ context.push_thread_default ();
+ AsyncResult async_res = null;
+ query_async.begin (sparql, cancellable, (o, res) => {
+ async_res = res;
+ loop.quit ();
+ });
+ loop.run ();
+ context.pop_thread_default ();
+ return query_async.end (async_res);
+ }
+
+ public async override Sparql.Cursor query_async (string sparql, Cancellable? cancellable = null) throws Sparql.Error, GLib.Error, GLib.IOError, DBusError {
+ return yield perform_query_call (bus, dbus_name, object_path, sparql, null, cancellable);
+ }
+
+ public override Sparql.Statement? query_statement (string sparql, GLib.Cancellable? cancellable = null) throws Sparql.Error {
+ return new Bus.Statement (bus, dbus_name, object_path, sparql);
+ }
+
void send_update (string method, UnixInputStream input, Cancellable? cancellable, AsyncReadyCallback? callback) throws GLib.Error, GLib.IOError {
var message = new DBusMessage.method_call (dbus_name, object_path, ENDPOINT_IFACE, method);
var fd_list = new UnixFDList ();
diff --git a/src/libtracker-data/tracker-db-interface-sqlite.c b/src/libtracker-data/tracker-db-interface-sqlite.c
index 074bf40f1..3dbfcc233 100644
--- a/src/libtracker-data/tracker-db-interface-sqlite.c
+++ b/src/libtracker-data/tracker-db-interface-sqlite.c
@@ -131,7 +131,8 @@ struct TrackerDBStatement {
GInitiallyUnowned parent_instance;
TrackerDBInterface *db_interface;
sqlite3_stmt *stmt;
- gboolean stmt_is_used;
+ guint stmt_is_used : 1;
+ guint stmt_is_owned : 1;
TrackerDBStatement *next;
TrackerDBStatement *prev;
};
@@ -2572,9 +2573,9 @@ tracker_db_interface_lru_lookup (TrackerDBInterface *db_interface,
/* a) Cached */
- if (stmt && stmt->stmt_is_used) {
+ if (stmt && stmt->stmt_is_owned) {
/* c) Forced non-cached
- * prepared statement is still in use, create new uncached one
+ * prepared statement is owned somewhere else, create new uncached one
*/
stmt = NULL;
/* Make sure to set cache_type here, to avoid replacing
@@ -2741,6 +2742,7 @@ tracker_db_interface_create_statement (TrackerDBInterface *db_interfac
stmt);
}
+ stmt->stmt_is_owned = TRUE;
g_free (full_query);
tracker_db_interface_unlock (db_interface);
@@ -2929,6 +2931,7 @@ static TrackerDBStatement *
tracker_db_statement_sqlite_grab (TrackerDBStatement *stmt)
{
g_assert (!stmt->stmt_is_used);
+ g_assert (stmt->stmt_is_owned);
stmt->stmt_is_used = TRUE;
g_object_ref (stmt->db_interface);
return g_object_ref (stmt);
@@ -2939,8 +2942,9 @@ tracker_db_statement_sqlite_release (TrackerDBStatement *stmt)
{
TrackerDBInterface *iface = stmt->db_interface;
- g_assert (stmt->stmt_is_used);
+ g_assert (stmt->stmt_is_owned);
stmt->stmt_is_used = FALSE;
+ stmt->stmt_is_owned = FALSE;
tracker_db_statement_sqlite_reset (stmt);
g_object_unref (stmt);
g_object_unref (iface);
diff --git a/src/libtracker-data/tracker-sparql.c b/src/libtracker-data/tracker-sparql.c
index a69f1a66b..9c5bde454 100644
--- a/src/libtracker-data/tracker-sparql.c
+++ b/src/libtracker-data/tracker-sparql.c
@@ -8076,9 +8076,8 @@ translate_RDFLiteral (TrackerSparql *sparql,
sparql->current_state.expression_type = type;
tracker_binding_set_data_type (binding, type);
- if (!is_parameter &&
- (sparql->current_state.type == TRACKER_SPARQL_TYPE_SELECT ||
- sparql->current_state.type == TRACKER_SPARQL_TYPE_CONSTRUCT)) {
+ if (sparql->current_state.type == TRACKER_SPARQL_TYPE_SELECT ||
+ sparql->current_state.type == TRACKER_SPARQL_TYPE_CONSTRUCT) {
tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
TRACKER_LITERAL_BINDING (binding));
}
diff --git a/src/libtracker-sparql/libtracker-sparql-intermediate-c.vapi b/src/libtracker-sparql/libtracker-sparql-intermediate-c.vapi
index bc513d2e6..74eeca38c 100644
--- a/src/libtracker-sparql/libtracker-sparql-intermediate-c.vapi
+++ b/src/libtracker-sparql/libtracker-sparql-intermediate-c.vapi
@@ -102,6 +102,7 @@ namespace Tracker {
public abstract void bind_boolean (string name, bool value);
public abstract void bind_string (string name, string value);
public abstract void bind_double (string name, double value);
+ public abstract void clear_bindings ();
public abstract Cursor execute (GLib.Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError;
public async abstract Cursor execute_async (GLib.Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError;
diff --git a/src/libtracker-sparql/tracker-endpoint-dbus.c b/src/libtracker-sparql/tracker-endpoint-dbus.c
index c9ef380b0..1668aa97e 100644
--- a/src/libtracker-sparql/tracker-endpoint-dbus.c
+++ b/src/libtracker-sparql/tracker-endpoint-dbus.c
@@ -36,6 +36,7 @@ static const gchar introspection_xml[] =
" <method name='Query'>"
" <arg type='s' name='query' direction='in' />"
" <arg type='h' name='output_stream' direction='in' />"
+ " <arg type='a{sv}' name='arguments' direction='in' />"
" <arg type='as' name='result' direction='out' />"
" </method>"
" <method name='Update'>"
@@ -206,33 +207,21 @@ update_request_free (UpdateRequest *request)
g_free (request);
}
-static void
-query_cb (GObject *object,
- GAsyncResult *res,
- gpointer user_data)
+static gboolean
+write_cursor (QueryRequest *request,
+ TrackerSparqlCursor *cursor,
+ GError **error)
{
- QueryRequest *request = user_data;
- TrackerSparqlCursor *cursor;
- GError *error = NULL;
const gchar **values = NULL;
- const gchar **variable_names = NULL;
glong *offsets = NULL;
gint i, n_columns = 0;
-
- cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (object),
- res, &error);
- if (!cursor)
- goto error;
+ GError *inner_error = NULL;
n_columns = tracker_sparql_cursor_get_n_columns (cursor);
- variable_names = g_new0 (const gchar *, n_columns + 1);
values = g_new0 (const char *, n_columns);
offsets = g_new0 (glong, n_columns);
- for (i = 0; i < n_columns; i++)
- variable_names[i] = tracker_sparql_cursor_get_variable_name (cursor, i);
-
- while (tracker_sparql_cursor_next (cursor, NULL, &error)) {
+ while (tracker_sparql_cursor_next (cursor, NULL, &inner_error)) {
glong cur_offset = -1;
g_data_output_stream_put_int32 (request->data_stream, n_columns, NULL, NULL);
@@ -262,17 +251,71 @@ query_cb (GObject *object,
}
}
-error:
+ g_free (values);
+ g_free (offsets);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+/* Takes ownership on both cursor and error */
+static void
+handle_cursor_reply (QueryRequest *request,
+ TrackerSparqlCursor *cursor,
+ GError *error)
+{
+ const gchar **variable_names = NULL;
+ gint i, n_columns;
+
+ if (!error) {
+ n_columns = tracker_sparql_cursor_get_n_columns (cursor);
+ variable_names = g_new0 (const gchar *, n_columns + 1);
+ for (i = 0; i < n_columns; i++)
+ variable_names[i] = tracker_sparql_cursor_get_variable_name (cursor, i);
+
+ write_cursor (request, cursor, &error);
+ }
+
if (error)
g_dbus_method_invocation_return_gerror (request->invocation, error);
else
g_dbus_method_invocation_return_value (request->invocation, g_variant_new ("(^as)", variable_names));
g_free (variable_names);
- g_free (values);
- g_free (offsets);
g_clear_object (&cursor);
+}
+static void
+query_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ QueryRequest *request = user_data;
+ TrackerSparqlCursor *cursor;
+ GError *error = NULL;
+
+ cursor = tracker_sparql_connection_query_finish (TRACKER_SPARQL_CONNECTION (object),
+ res, &error);
+ handle_cursor_reply (request, cursor, error);
+ query_request_free (request);
+}
+
+static void
+stmt_execute_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ QueryRequest *request = user_data;
+ TrackerSparqlCursor *cursor;
+ GError *error = NULL;
+
+ cursor = tracker_sparql_statement_execute_finish (TRACKER_SPARQL_STATEMENT (object),
+ res, &error);
+ handle_cursor_reply (request, cursor, error);
query_request_free (request);
}
@@ -373,6 +416,47 @@ read_update_blank_cb (GObject *object,
request);
}
+static TrackerSparqlStatement *
+create_statement (TrackerSparqlConnection *conn,
+ const gchar *query,
+ GVariantIter *arguments,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerSparqlStatement *stmt;
+ GVariant *value;
+ const gchar *arg;
+
+ stmt = tracker_sparql_connection_query_statement (conn,
+ query,
+ cancellable,
+ error);
+ if (!stmt)
+ return NULL;
+
+ while (g_variant_iter_loop (arguments, "{sv}", &arg, &value)) {
+ if (g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
+ tracker_sparql_statement_bind_string (stmt, arg,
+ g_variant_get_string (value, NULL));
+ } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_DOUBLE)) {
+ tracker_sparql_statement_bind_double (stmt, arg,
+ g_variant_get_double (value));
+ } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_INT64)) {
+ tracker_sparql_statement_bind_double (stmt, arg,
+ g_variant_get_int64 (value));
+ } else if (g_variant_is_of_type (value, G_VARIANT_TYPE_BOOLEAN)) {
+ tracker_sparql_statement_bind_double (stmt, arg,
+ g_variant_get_boolean (value));
+ } else {
+ g_warning ("Unhandled type '%s' for argument %s",
+ g_variant_get_type_string (value),
+ arg);
+ }
+ }
+
+ return stmt;
+}
+
static void
endpoint_dbus_iface_method_call (GDBusConnection *connection,
const gchar *sender,
@@ -387,6 +471,7 @@ endpoint_dbus_iface_method_call (GDBusConnection *connection,
TrackerSparqlConnection *conn;
GUnixFDList *fd_list;
GError *error = NULL;
+ GVariantIter *arguments;
gchar *query;
gint handle, fd = -1;
@@ -394,7 +479,7 @@ endpoint_dbus_iface_method_call (GDBusConnection *connection,
fd_list = g_dbus_message_get_unix_fd_list (g_dbus_method_invocation_get_message (invocation));
if (g_strcmp0 (method_name, "Query") == 0) {
- g_variant_get (parameters, "(sh)", &query, &handle);
+ g_variant_get (parameters, "(sha{sv})", &query, &handle, &arguments);
if (fd_list)
fd = g_unix_fd_list_get (fd_list, handle, &error);
@@ -408,11 +493,31 @@ endpoint_dbus_iface_method_call (GDBusConnection *connection,
QueryRequest *request;
request = query_request_new (endpoint_dbus, invocation, fd);
- tracker_sparql_connection_query_async (conn,
- query,
- endpoint_dbus->cancellable,
- query_cb,
- request);
+
+ if (arguments) {
+ TrackerSparqlStatement *stmt;
+
+ stmt = create_statement (conn, query, arguments,
+ endpoint_dbus->cancellable,
+ &error);
+ if (stmt) {
+ tracker_sparql_statement_execute_async (stmt,
+ endpoint_dbus->cancellable,
+ stmt_execute_cb,
+ request);
+ /* Statements are single use here... */
+ g_object_unref (stmt);
+ } else {
+ g_dbus_method_invocation_return_gerror (invocation,
+ error);
+ }
+ } else {
+ tracker_sparql_connection_query_async (conn,
+ query,
+ endpoint_dbus->cancellable,
+ query_cb,
+ request);
+ }
}
g_free (query);
diff --git a/tests/libtracker-sparql/meson.build b/tests/libtracker-sparql/meson.build
index 153ce221c..d4f5c556d 100644
--- a/tests/libtracker-sparql/meson.build
+++ b/tests/libtracker-sparql/meson.build
@@ -40,3 +40,14 @@ tests += {
'exe': tracker_fd_test,
'suite': ['sparql'],
}
+
+tracker_statement_test = executable('tracker-statement-test',
+ 'tracker-statement-test.c',
+ dependencies: [tracker_common_dep, tracker_sparql_dep],
+ c_args: libtracker_sparql_test_c_args + test_c_args)
+
+tests += {
+ 'name': 'statement',
+ 'exe': tracker_statement_test,
+ 'suite': ['sparql'],
+}
diff --git a/tests/libtracker-sparql/statement/filter.out b/tests/libtracker-sparql/statement/filter.out
new file mode 100644
index 000000000..826861551
--- /dev/null
+++ b/tests/libtracker-sparql/statement/filter.out
@@ -0,0 +1 @@
+"true"
diff --git a/tests/libtracker-sparql/statement/filter.rq b/tests/libtracker-sparql/statement/filter.rq
new file mode 100644
index 000000000..1b3186bc9
--- /dev/null
+++ b/tests/libtracker-sparql/statement/filter.rq
@@ -0,0 +1,5 @@
+ASK {
+ ~arg1 a rdfs:Class ;
+ rdfs:label ?label .
+ FILTER (?label = ~arg2)
+}
diff --git a/tests/libtracker-sparql/statement/object-iri.out b/tests/libtracker-sparql/statement/object-iri.out
new file mode 100644
index 000000000..826861551
--- /dev/null
+++ b/tests/libtracker-sparql/statement/object-iri.out
@@ -0,0 +1 @@
+"true"
diff --git a/tests/libtracker-sparql/statement/object-iri.rq b/tests/libtracker-sparql/statement/object-iri.rq
new file mode 100644
index 000000000..2b86b3602
--- /dev/null
+++ b/tests/libtracker-sparql/statement/object-iri.rq
@@ -0,0 +1,3 @@
+ASK {
+ nmm:MusicAlbum rdfs:subClassOf ~arg1
+}
diff --git a/tests/libtracker-sparql/statement/object.out b/tests/libtracker-sparql/statement/object.out
new file mode 100644
index 000000000..826861551
--- /dev/null
+++ b/tests/libtracker-sparql/statement/object.out
@@ -0,0 +1 @@
+"true"
diff --git a/tests/libtracker-sparql/statement/object.rq b/tests/libtracker-sparql/statement/object.rq
new file mode 100644
index 000000000..49b328dfd
--- /dev/null
+++ b/tests/libtracker-sparql/statement/object.rq
@@ -0,0 +1,3 @@
+ASK {
+ nmm:MusicAlbum rdfs:label ~arg1 .
+}
diff --git a/tests/libtracker-sparql/statement/simple-error.rq b/tests/libtracker-sparql/statement/simple-error.rq
new file mode 100644
index 000000000..2019497b8
--- /dev/null
+++ b/tests/libtracker-sparql/statement/simple-error.rq
@@ -0,0 +1 @@
+SELECT ~arg1 { }
diff --git a/tests/libtracker-sparql/statement/simple.out b/tests/libtracker-sparql/statement/simple.out
new file mode 100644
index 000000000..3580093b9
--- /dev/null
+++ b/tests/libtracker-sparql/statement/simple.out
@@ -0,0 +1 @@
+"hello"
diff --git a/tests/libtracker-sparql/statement/simple.rq b/tests/libtracker-sparql/statement/simple.rq
new file mode 100644
index 000000000..2019497b8
--- /dev/null
+++ b/tests/libtracker-sparql/statement/simple.rq
@@ -0,0 +1 @@
+SELECT ~arg1 { }
diff --git a/tests/libtracker-sparql/statement/subject-2.out b/tests/libtracker-sparql/statement/subject-2.out
new file mode 100644
index 000000000..d24842c0c
--- /dev/null
+++ b/tests/libtracker-sparql/statement/subject-2.out
@@ -0,0 +1 @@
+"false"
diff --git a/tests/libtracker-sparql/statement/subject.out b/tests/libtracker-sparql/statement/subject.out
new file mode 100644
index 000000000..826861551
--- /dev/null
+++ b/tests/libtracker-sparql/statement/subject.out
@@ -0,0 +1 @@
+"true"
diff --git a/tests/libtracker-sparql/statement/subject.rq b/tests/libtracker-sparql/statement/subject.rq
new file mode 100644
index 000000000..dae307aff
--- /dev/null
+++ b/tests/libtracker-sparql/statement/subject.rq
@@ -0,0 +1,3 @@
+ASK {
+ ~arg1 a rdfs:Class .
+}
diff --git a/tests/libtracker-sparql/tracker-statement-test.c b/tests/libtracker-sparql/tracker-statement-test.c
new file mode 100644
index 000000000..86e884c0e
--- /dev/null
+++ b/tests/libtracker-sparql/tracker-statement-test.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2020, Red Hat Ltd.
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <libtracker-sparql/tracker-sparql.h>
+
+typedef struct {
+ const gchar *test_name;
+ const gchar *query_file;
+ const gchar *output_file;
+ const gchar *arg1;
+ const gchar *arg2;
+ const gchar *arg3;
+ TrackerSparqlConnection *conn;
+} TestInfo;
+
+TestInfo tests[] = {
+ { "simple", "statement/simple.rq", "statement/simple.out", "hello" },
+ { "simple-error", "statement/simple-error.rq" },
+ { "object", "statement/object.rq", "statement/object.out", "Music album" },
+ { "object-iri", "statement/object-iri.rq", "statement/object-iri.out", "http://www.semanticdesktop.org/ontologies/2007/03/22/nfo#MediaList" },
+ { "subject", "statement/subject.rq", "statement/subject.out", "http://www.tracker-project.org/temp/nmm#MusicAlbum" },
+ { "subject-2", "statement/subject.rq", "statement/subject-2.out", "urn:nonexistent" },
+ { "filter", "statement/filter.rq", "statement/filter.out", "http://www.tracker-project.org/temp/nmm#MusicAlbum", "Music album" },
+};
+
+typedef struct {
+ TrackerSparqlConnection *direct;
+ GDBusConnection *dbus_conn;
+} StartupData;
+
+static gboolean started = FALSE;
+
+static void
+check_result (TrackerSparqlCursor *cursor,
+ const gchar *results_filename)
+{
+ GString *test_results;
+ gchar *results;
+ GError *nerror = NULL;
+ GError *error = NULL;
+ gint col;
+
+ g_file_get_contents (results_filename, &results, NULL, &nerror);
+ g_assert_no_error (nerror);
+ g_clear_error (&nerror);
+
+ /* compare results with reference output */
+
+ test_results = g_string_new ("");
+
+ while (tracker_sparql_cursor_next (cursor, NULL, &error)) {
+ GString *row_str = g_string_new (NULL);
+
+ for (col = 0; col < tracker_sparql_cursor_get_n_columns (cursor); col++) {
+ const gchar *str;
+
+ if (col > 0) {
+ g_string_append (row_str, "\t");
+ }
+
+ str = tracker_sparql_cursor_get_string (cursor, col, NULL);
+
+ /* Hack to avoid misc properties that might tamper with
+ * test reproduceability in DESCRIBE and other unrestricted
+ * queries.
+ */
+ if (g_strcmp0 (str, TRACKER_PREFIX_TRACKER "modified") == 0 ||
+ g_strcmp0 (str, TRACKER_PREFIX_TRACKER "added") == 0) {
+ g_string_free (row_str, TRUE);
+ row_str = NULL;
+ break;
+ }
+
+ if (str != NULL) {
+ /* bound variable */
+ g_string_append_printf (row_str, "\"%s\"", str);
+ }
+ }
+
+ if (row_str) {
+ g_string_append (test_results, row_str->str);
+ g_string_free (row_str, TRUE);
+ g_string_append (test_results, "\n");
+ }
+ }
+
+ g_assert_no_error (error);
+
+ if (strcmp (results, test_results->str) != 0) {
+ /* print result difference */
+ gchar *quoted_results;
+ gchar *command_line;
+ gchar *quoted_command_line;
+ gchar *shell;
+ gchar *diff;
+
+ quoted_results = g_shell_quote (test_results->str);
+ command_line = g_strdup_printf ("echo -n %s | diff -u %s -", quoted_results, results_filename);
+ quoted_command_line = g_shell_quote (command_line);
+ shell = g_strdup_printf ("sh -c %s", quoted_command_line);
+ g_spawn_command_line_sync (shell, &diff, NULL, NULL, &error);
+ g_assert_no_error (error);
+
+ g_error ("%s", diff);
+
+ g_free (quoted_results);
+ g_free (command_line);
+ g_free (quoted_command_line);
+ g_free (shell);
+ g_free (diff);
+ }
+
+ g_string_free (test_results, TRUE);
+ g_free (results);
+}
+
+static void
+setup (TestInfo *test_info,
+ gconstpointer context)
+{
+ const TestInfo *test = context;
+
+ *test_info = *test;
+}
+
+static void
+query_statement (TestInfo *test_info,
+ gconstpointer context)
+{
+ TrackerSparqlStatement *stmt;
+ TrackerSparqlCursor *cursor;
+ GError *error = NULL;
+ gchar *path, *query;
+
+ path = g_build_filename (TOP_SRCDIR, "tests", "libtracker-sparql",
+ test_info->query_file, NULL);
+ g_file_get_contents (path, &query, NULL, &error);
+ g_assert_no_error (error);
+ g_free (path);
+
+ stmt = tracker_sparql_connection_query_statement (test_info->conn, query,
+ NULL, &error);
+ g_free (query);
+ g_assert_no_error (error);
+
+ if (test_info->arg1)
+ tracker_sparql_statement_bind_string (stmt, "arg1", test_info->arg1);
+ if (test_info->arg2)
+ tracker_sparql_statement_bind_string (stmt, "arg2", test_info->arg2);
+ if (test_info->arg3)
+ tracker_sparql_statement_bind_string (stmt, "arg3", test_info->arg3);
+
+ cursor = tracker_sparql_statement_execute (stmt, NULL, &error);
+
+ if (test_info->output_file) {
+ g_assert_no_error (error);
+
+ path = g_build_filename (TOP_SRCDIR, "tests", "libtracker-sparql",
+ test_info->output_file, NULL);
+ check_result (cursor, path);
+ g_free (path);
+ } else {
+ g_assert_nonnull (error);
+ }
+}
+
+TrackerSparqlConnection *
+create_local_connection (GError **error)
+{
+ TrackerSparqlConnection *conn;
+ GFile *ontology;
+
+ ontology = g_file_new_for_path (TEST_ONTOLOGIES_DIR);
+
+ conn = tracker_sparql_connection_new (0, NULL, ontology, NULL, error);
+ g_object_unref (ontology);
+
+ return conn;
+}
+
+static gpointer
+thread_func (gpointer user_data)
+{
+ StartupData *data = user_data;;
+ TrackerEndpointDBus *endpoint;
+ GMainContext *context;
+ GMainLoop *main_loop;
+
+ context = g_main_context_new ();
+ g_main_context_push_thread_default (context);
+
+ main_loop = g_main_loop_new (context, FALSE);
+
+ endpoint = tracker_endpoint_dbus_new (data->direct, data->dbus_conn, NULL, NULL, NULL);
+ if (!endpoint)
+ return NULL;
+
+ started = TRUE;
+ g_main_loop_run (main_loop);
+
+ return NULL;
+}
+
+static gboolean
+create_connections (TrackerSparqlConnection **dbus,
+ TrackerSparqlConnection **direct,
+ GError **error)
+{
+ StartupData data;
+
+ data.direct = create_local_connection (NULL);
+ if (!data.direct)
+ return FALSE;
+ data.dbus_conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
+ if (!data.dbus_conn)
+ return FALSE;
+
+ g_thread_new (NULL, thread_func, &data);
+
+ while (!started)
+ g_usleep (100);
+
+ *dbus = tracker_sparql_connection_bus_new (g_dbus_connection_get_unique_name (data.dbus_conn),
+ NULL, data.dbus_conn, error);
+ *direct = data.direct;
+
+ return TRUE;
+}
+
+static void
+add_tests (TrackerSparqlConnection *conn,
+ const gchar *name)
+{
+ gint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tests); i++) {
+ gchar *testpath;
+
+ tests[i].conn = conn;
+ testpath = g_strconcat ("/libtracker-sparql/statement/", name, "/", tests[i].test_name, NULL);
+ g_test_add (testpath, TestInfo, &tests[i], setup, query_statement, NULL);
+ g_free (testpath);
+ }
+}
+
+gint
+main (gint argc, gchar **argv)
+{
+ TrackerSparqlConnection *dbus = NULL, *direct = NULL;
+ GError *error = NULL;
+
+ g_test_init (&argc, &argv, NULL);
+
+ /* g_test_init() enables verbose logging by default, but Tracker is too
+ * verbose. To make the logs managable, we hide DEBUG and INFO messages
+ * unless TRACKER_TESTS_VERBOSE is set.
+ */
+ if (! g_getenv ("TRACKER_TESTS_VERBOSE")) {
+ g_log_set_handler ("Tracker", G_LOG_LEVEL_DEBUG | G_LOG_LEVEL_INFO, g_log_default_handler, NULL);
+ }
+
+ g_assert_true (create_connections (&dbus, &direct, &error));
+ g_assert_no_error (error);
+
+ add_tests (direct, "direct");
+ add_tests (dbus, "dbus");
+
+ return g_test_run ();
+}