diff options
author | Sam Thursfield <sam@afuera.me.uk> | 2020-03-02 19:06:28 +0000 |
---|---|---|
committer | Sam Thursfield <sam@afuera.me.uk> | 2020-03-02 19:06:28 +0000 |
commit | 63736da59ac0d676cac4a78bd49ca2577dd1b486 (patch) | |
tree | 7392308eaf2d98006538b702ceb67d24eba05d36 | |
parent | 05b18cc5d17fd8d4348d238cd8d876b6fb882396 (diff) | |
parent | ed204139a1af9a2b539b0b07429bb7df6096eab7 (diff) | |
download | tracker-63736da59ac0d676cac4a78bd49ca2577dd1b486.tar.gz |
Merge branch 'wip/carlosg/bus-statements' into 'master'
Implement TrackerSparqlStatement for bus connections
Closes #179
See merge request GNOME/tracker!190
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 (); +} |