summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Thursfield <sam@afuera.me.uk>2022-02-05 14:25:48 +0000
committerSam Thursfield <sam@afuera.me.uk>2022-02-05 14:25:48 +0000
commit54119deb3ac487d867041b09ef351c386547f4cd (patch)
tree91195112224cbf18a6f987eccb8817266a9df06b
parent87736aa6f41cbe4de9aa7cdb3aee8cb97741f978 (diff)
parentc63f9e633f05cc60150811f6307272873eb8bfa4 (diff)
downloadtracker-54119deb3ac487d867041b09ef351c386547f4cd.tar.gz
Merge branch 'wip/carlosg/serialize-api' into 'master'
Add API to serialize to RDF formats See merge request GNOME/tracker!476
-rw-r--r--docs/reference/libtracker-sparql/limits.md7
-rw-r--r--docs/reference/libtracker-sparql/sparql-and-tracker.md13
-rw-r--r--src/libtracker-data/tracker-data-update.c18
-rw-r--r--src/libtracker-data/tracker-db-interface-sqlite.c195
-rw-r--r--src/libtracker-data/tracker-db-interface.h5
-rw-r--r--src/libtracker-data/tracker-sparql-types.h3
-rw-r--r--src/libtracker-data/tracker-sparql.c208
-rw-r--r--src/libtracker-data/tracker-sparql.h2
-rw-r--r--src/libtracker-sparql/bus/tracker-bus-statement.vala4
-rw-r--r--src/libtracker-sparql/bus/tracker-bus.vala20
-rw-r--r--src/libtracker-sparql/direct/tracker-direct-statement.c112
-rw-r--r--src/libtracker-sparql/direct/tracker-direct.c166
-rw-r--r--src/libtracker-sparql/meson.build2
-rw-r--r--src/libtracker-sparql/remote/tracker-remote-statement.c72
-rw-r--r--src/libtracker-sparql/remote/tracker-remote.vala30
-rw-r--r--src/libtracker-sparql/tracker-connection.c86
-rw-r--r--src/libtracker-sparql/tracker-connection.h23
-rw-r--r--src/libtracker-sparql/tracker-endpoint-dbus.c149
-rw-r--r--src/libtracker-sparql/tracker-endpoint-http.c13
-rw-r--r--src/libtracker-sparql/tracker-private.h21
-rw-r--r--src/libtracker-sparql/tracker-serializer-trig.c362
-rw-r--r--src/libtracker-sparql/tracker-serializer-trig.h36
-rw-r--r--src/libtracker-sparql/tracker-serializer-turtle.c339
-rw-r--r--src/libtracker-sparql/tracker-serializer-turtle.h36
-rw-r--r--src/libtracker-sparql/tracker-serializer.c22
-rw-r--r--src/libtracker-sparql/tracker-serializer.h2
-rw-r--r--src/libtracker-sparql/tracker-sparql.vapi14
-rw-r--r--src/libtracker-sparql/tracker-statement.c73
-rw-r--r--src/libtracker-sparql/tracker-statement.h13
-rw-r--r--src/tracker/tracker-export.c97
-rw-r--r--tests/libtracker-data/describe/describe-limit.out22
-rw-r--r--tests/libtracker-data/describe/describe-multiple.out44
-rw-r--r--tests/libtracker-data/describe/describe-pattern.out12
-rw-r--r--tests/libtracker-data/describe/describe-single.out10
-rw-r--r--tests/libtracker-sparql/meson.build11
-rw-r--r--tests/libtracker-sparql/serialize/construct-trig.out21
-rw-r--r--tests/libtracker-sparql/serialize/construct-trig.rq5
-rw-r--r--tests/libtracker-sparql/serialize/construct-ttl.out19
-rw-r--r--tests/libtracker-sparql/serialize/construct-ttl.rq5
-rw-r--r--tests/libtracker-sparql/serialize/describe-graph-trig.out47
-rw-r--r--tests/libtracker-sparql/serialize/describe-graph-trig.rq5
-rw-r--r--tests/libtracker-sparql/serialize/describe-graph-ttl.out29
-rw-r--r--tests/libtracker-sparql/serialize/describe-graph-ttl.rq5
-rw-r--r--tests/libtracker-sparql/serialize/describe-single-trig.out22
-rw-r--r--tests/libtracker-sparql/serialize/describe-single-trig.rq1
-rw-r--r--tests/libtracker-sparql/serialize/describe-single-ttl.out20
-rw-r--r--tests/libtracker-sparql/serialize/describe-single-ttl.rq1
-rw-r--r--tests/libtracker-sparql/serialize/describe-var-trig.out23
-rw-r--r--tests/libtracker-sparql/serialize/describe-var-trig.rq3
-rw-r--r--tests/libtracker-sparql/serialize/describe-var-ttl.out21
-rw-r--r--tests/libtracker-sparql/serialize/describe-var-ttl.rq3
-rw-r--r--tests/libtracker-sparql/tracker-serialize-test.c340
52 files changed, 2556 insertions, 256 deletions
diff --git a/docs/reference/libtracker-sparql/limits.md b/docs/reference/libtracker-sparql/limits.md
index 120b1c7c4..9d514b834 100644
--- a/docs/reference/libtracker-sparql/limits.md
+++ b/docs/reference/libtracker-sparql/limits.md
@@ -16,12 +16,17 @@ options.
Tracker can store by default up to 1 GiB in a text field by default. This
limit is controlled by `SQLITE_MAX_LENGTH`
-## Maximum number of properties
+## Maximum number of properties and select columns
The maximum amount of properties in the domain of a single class, and
the maximum number of global fulltext-search properties in the ontology
are limited by `SQLITE_MAX_COLUMN` (defaults to 2000).
+Each column in the result set of a SELECT query contains additional
+information included as an additional column, so the limit of columns
+that can be retrieved in a single SELECT query is effectively half
+of `SQLITE_MAX_COLUMN` (1000 with the default settings).
+
## Underlying parser limits
SQLite defines some limits to its parser. Maximum query length is 1 MiB,
diff --git a/docs/reference/libtracker-sparql/sparql-and-tracker.md b/docs/reference/libtracker-sparql/sparql-and-tracker.md
index dc6ba79c6..78a39932f 100644
--- a/docs/reference/libtracker-sparql/sparql-and-tracker.md
+++ b/docs/reference/libtracker-sparql/sparql-and-tracker.md
@@ -262,3 +262,16 @@ this full-text search mechanism, by toggling the
`tracker:fulltextIndexed` property on in the
text `rdf:Property` instances. See the documentation
on [defining ontologies](ontologies.html).
+
+## DESCRIBE queries
+
+The [SPARQL documentation](https://www.w3.org/TR/sparql11-query/#describe)
+says:
+
+```
+The DESCRIBE form returns a single result RDF graph containing RDF data about resources.
+```
+
+In order to allow serialization to RDF formats that allow expressing graph information
+(e.g. Trig), DESCRIBE resultsets have 4 columns for subject / predicate / object / graph
+information.
diff --git a/src/libtracker-data/tracker-data-update.c b/src/libtracker-data/tracker-data-update.c
index 91807f165..c090f63dc 100644
--- a/src/libtracker-data/tracker-data-update.c
+++ b/src/libtracker-data/tracker-data-update.c
@@ -764,6 +764,9 @@ statement_bind_gvalue (TrackerDBStatement *stmt,
case G_TYPE_STRING:
tracker_db_statement_bind_text (stmt, (*idx)++, g_value_get_string (value));
break;
+ case G_TYPE_INT:
+ tracker_db_statement_bind_int (stmt, (*idx)++, g_value_get_int (value));
+ break;
case G_TYPE_INT64:
tracker_db_statement_bind_int (stmt, (*idx)++, g_value_get_int64 (value));
break;
@@ -806,6 +809,9 @@ statement_bind_gvalue (TrackerDBStatement *stmt,
/* String with langtag */
tracker_db_statement_bind_bytes (stmt, (*idx)++, bytes);
}
+ } else if (g_strcmp0 (g_type_name (type), "TrackerUri") == 0) {
+ /* FIXME: We can't access TrackerUri GType here */
+ tracker_db_statement_bind_text (stmt, (*idx)++, g_value_get_string (value));
} else {
g_warning ("Unknown type for binding: %s\n", G_VALUE_TYPE_NAME (value));
}
@@ -2511,11 +2517,11 @@ tracker_data_insert_statement_with_uri (TrackerData *data,
return;
if (predicate == tracker_ontologies_get_rdf_type (ontologies)) {
- gchar *object_str = NULL;
+ const gchar *object_str = NULL;
gint64 object_id;
object_id = g_value_get_int64 (object);
- object_str = g_strdup (tracker_ontologies_get_uri_by_id (ontologies, object_id));
+ object_str = tracker_ontologies_get_uri_by_id (ontologies, object_id);
/* handle rdf:type statements specially to
cope with inference and insert blank rows */
@@ -2889,6 +2895,13 @@ tracker_data_load_turtle_file (TrackerData *data,
goto failed;
}
+ /* Skip nrl:added/nrl:modified when parsing */
+ if (g_strcmp0 (tracker_property_get_name (predicate),
+ "nrl:modified") == 0 ||
+ g_strcmp0 (tracker_property_get_name (predicate),
+ "nrl:added") == 0)
+ continue;
+
if (!tracker_data_query_string_to_value (data->manager,
object_str,
langtag,
@@ -3096,6 +3109,7 @@ update_resource_property (TrackerData *data,
break;
}
+ g_free (object_str);
value = &free_me;
} else {
value = v->data;
diff --git a/src/libtracker-data/tracker-db-interface-sqlite.c b/src/libtracker-data/tracker-db-interface-sqlite.c
index 13a7861a7..ac23beb70 100644
--- a/src/libtracker-data/tracker-db-interface-sqlite.c
+++ b/src/libtracker-data/tracker-db-interface-sqlite.c
@@ -115,10 +115,7 @@ struct TrackerDBCursor {
sqlite3_stmt *stmt;
TrackerDBStatement *ref_stmt;
gboolean finished;
- TrackerPropertyType *types;
- guint n_types;
- gchar **variable_names;
- guint n_variable_names;
+ guint n_columns;
};
struct TrackerDBCursorClass {
@@ -144,10 +141,7 @@ static TrackerDBStatement *tracker_db_statement_sqlite_new (TrackerDBIn
sqlite3_stmt *sqlite_stmt);
static void tracker_db_statement_sqlite_reset (TrackerDBStatement *stmt);
static TrackerDBCursor *tracker_db_cursor_sqlite_new (TrackerDBStatement *ref_stmt,
- TrackerPropertyType *types,
- guint n_types,
- const gchar * const *variable_names,
- guint n_variable_names);
+ guint n_columns);
static gboolean tracker_db_cursor_get_boolean (TrackerSparqlCursor *cursor,
guint column);
static gboolean db_cursor_iter_next (TrackerDBCursor *cursor,
@@ -457,7 +451,7 @@ function_sparql_format_time (sqlite3_context *context,
-1, g_free);
g_date_time_unref (datetime);
} else {
- result_context_function_error (context, fn, "Datetime conversion error");
+ sqlite3_result_null (context);
}
} else if (sqlite3_value_type (argv[0]) == SQLITE_TEXT) {
const gchar *str;
@@ -3129,19 +3123,11 @@ static void
tracker_db_cursor_finalize (GObject *object)
{
TrackerDBCursor *cursor;
- guint i;
cursor = TRACKER_DB_CURSOR (object);
tracker_db_cursor_close (cursor);
- g_free (cursor->types);
-
- for (i = 0; i < cursor->n_variable_names; i++) {
- g_free (cursor->variable_names[i]);
- }
- g_free (cursor->variable_names);
-
G_OBJECT_CLASS (tracker_db_cursor_parent_class)->finalize (object);
}
@@ -3230,10 +3216,7 @@ tracker_db_cursor_class_init (TrackerDBCursorClass *class)
static TrackerDBCursor *
tracker_db_cursor_sqlite_new (TrackerDBStatement *ref_stmt,
- TrackerPropertyType *types,
- guint n_types,
- const gchar * const *variable_names,
- guint n_variable_names)
+ guint n_columns)
{
TrackerDBCursor *cursor;
TrackerDBInterface *iface;
@@ -3260,30 +3243,11 @@ tracker_db_cursor_sqlite_new (TrackerDBStatement *ref_stmt,
cursor = g_object_new (TRACKER_TYPE_DB_CURSOR, NULL);
cursor->finished = FALSE;
+ cursor->n_columns = n_columns;
cursor->stmt = ref_stmt->stmt;
cursor->ref_stmt = tracker_db_statement_sqlite_grab (ref_stmt);
- if (types) {
- guint i;
-
- cursor->types = g_new (TrackerPropertyType, n_types);
- cursor->n_types = n_types;
- for (i = 0; i < n_types; i++) {
- cursor->types[i] = types[i];
- }
- }
-
- if (variable_names) {
- guint i;
-
- cursor->variable_names = g_new (gchar *, n_variable_names);
- cursor->n_variable_names = n_variable_names;
- for (i = 0; i < n_variable_names; i++) {
- cursor->variable_names[i] = g_strdup (variable_names[i]);
- }
- }
-
return cursor;
}
@@ -3497,7 +3461,21 @@ db_cursor_iter_next (TrackerDBCursor *cursor,
guint
tracker_db_cursor_get_n_columns (TrackerDBCursor *cursor)
{
- return sqlite3_column_count (cursor->stmt);
+ TrackerDBInterface *iface;
+ guint n_columns;
+
+ iface = cursor->ref_stmt->db_interface;
+
+ tracker_db_interface_lock (iface);
+
+ if (cursor->n_columns == 0)
+ n_columns = sqlite3_column_count (cursor->stmt);
+ else
+ n_columns = cursor->n_columns;
+
+ tracker_db_interface_unlock (iface);
+
+ return n_columns;
}
void
@@ -3538,6 +3516,9 @@ tracker_db_cursor_get_int (TrackerDBCursor *cursor,
TrackerDBInterface *iface;
gint64 result;
+ if (cursor->n_columns > 0 && column >= cursor->n_columns)
+ return 0;
+
iface = cursor->ref_stmt->db_interface;
tracker_db_interface_lock (iface);
@@ -3556,6 +3537,9 @@ tracker_db_cursor_get_double (TrackerDBCursor *cursor,
TrackerDBInterface *iface;
gdouble result;
+ if (cursor->n_columns > 0 && column >= cursor->n_columns)
+ return 0;
+
iface = cursor->ref_stmt->db_interface;
tracker_db_interface_lock (iface);
@@ -3575,16 +3559,82 @@ tracker_db_cursor_get_boolean (TrackerSparqlCursor *sparql_cursor,
return (g_strcmp0 (tracker_db_cursor_get_string (cursor, column, NULL), "true") == 0);
}
+static gboolean
+tracker_db_cursor_get_annotated_value_type (TrackerDBCursor *cursor,
+ guint column,
+ TrackerSparqlValueType *value_type)
+{
+ TrackerDBInterface *iface;
+ TrackerPropertyType property_type;
+ gboolean is_null;
+
+ if (cursor->n_columns == 0)
+ return FALSE;
+
+ iface = cursor->ref_stmt->db_interface;
+
+ tracker_db_interface_lock (iface);
+
+ /* The value type may be annotated in extra columns, one per
+ * user-visible column.
+ */
+ property_type = sqlite3_column_int64 (cursor->stmt, column + cursor->n_columns);
+ is_null = sqlite3_column_type (cursor->stmt, column) == SQLITE_NULL;
+
+ tracker_db_interface_unlock (iface);
+
+ if (is_null) {
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_UNBOUND;
+ return TRUE;
+ }
+
+ switch (property_type) {
+ case TRACKER_PROPERTY_TYPE_UNKNOWN:
+ return FALSE;
+ case TRACKER_PROPERTY_TYPE_STRING:
+ case TRACKER_PROPERTY_TYPE_LANGSTRING:
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_STRING;
+ return TRUE;
+ case TRACKER_PROPERTY_TYPE_BOOLEAN:
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_BOOLEAN;
+ return TRUE;
+ case TRACKER_PROPERTY_TYPE_INTEGER:
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_INTEGER;
+ return TRUE;
+ case TRACKER_PROPERTY_TYPE_DOUBLE:
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_DOUBLE;
+ return TRUE;
+ case TRACKER_PROPERTY_TYPE_DATE:
+ case TRACKER_PROPERTY_TYPE_DATETIME:
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_DATETIME;
+ return TRUE;
+ case TRACKER_PROPERTY_TYPE_RESOURCE:
+ if (g_str_has_prefix (tracker_db_cursor_get_string (cursor, column, NULL),
+ "urn:bnode:"))
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
+ else
+ *value_type = TRACKER_SPARQL_VALUE_TYPE_URI;
+
+ return TRUE;
+ };
+
+ g_assert_not_reached ();
+}
+
TrackerSparqlValueType
tracker_db_cursor_get_value_type (TrackerDBCursor *cursor,
guint column)
{
TrackerDBInterface *iface;
gint column_type;
- guint n_columns = sqlite3_column_count (cursor->stmt);
+ guint n_columns = tracker_db_cursor_get_n_columns (cursor);
+ TrackerSparqlValueType value_type;
g_return_val_if_fail (column < n_columns, TRACKER_SPARQL_VALUE_TYPE_UNBOUND);
+ if (tracker_db_cursor_get_annotated_value_type (cursor, column, &value_type))
+ return value_type;
+
iface = cursor->ref_stmt->db_interface;
tracker_db_interface_lock (iface);
@@ -3597,27 +3647,14 @@ tracker_db_cursor_get_value_type (TrackerDBCursor *cursor,
return TRACKER_SPARQL_VALUE_TYPE_UNBOUND;
}
- if (column < cursor->n_types) {
- switch (cursor->types[column]) {
- case TRACKER_PROPERTY_TYPE_RESOURCE:
- return TRACKER_SPARQL_VALUE_TYPE_URI;
- case TRACKER_PROPERTY_TYPE_INTEGER:
- return TRACKER_SPARQL_VALUE_TYPE_INTEGER;
- case TRACKER_PROPERTY_TYPE_DOUBLE:
- return TRACKER_SPARQL_VALUE_TYPE_DOUBLE;
- case TRACKER_PROPERTY_TYPE_DATETIME:
- return TRACKER_SPARQL_VALUE_TYPE_DATETIME;
- case TRACKER_PROPERTY_TYPE_BOOLEAN:
- return TRACKER_SPARQL_VALUE_TYPE_BOOLEAN;
- case TRACKER_PROPERTY_TYPE_DATE:
- case TRACKER_PROPERTY_TYPE_LANGSTRING:
- case TRACKER_PROPERTY_TYPE_STRING:
- case TRACKER_PROPERTY_TYPE_UNKNOWN:
- return TRACKER_SPARQL_VALUE_TYPE_STRING;
- }
+ switch (column_type) {
+ case SQLITE_INTEGER:
+ return TRACKER_SPARQL_VALUE_TYPE_INTEGER;
+ case SQLITE_FLOAT:
+ return TRACKER_SPARQL_VALUE_TYPE_DOUBLE;
+ default:
+ return TRACKER_SPARQL_VALUE_TYPE_STRING;
}
-
- return TRACKER_SPARQL_VALUE_TYPE_STRING;
}
const gchar*
@@ -3627,17 +3664,21 @@ tracker_db_cursor_get_variable_name (TrackerDBCursor *cursor,
TrackerDBInterface *iface;
const gchar *result;
+ if (cursor->n_columns > 0 && column >= cursor->n_columns)
+ return NULL;
+
iface = cursor->ref_stmt->db_interface;
tracker_db_interface_lock (iface);
+ result = sqlite3_column_name (cursor->stmt, column);
+ tracker_db_interface_unlock (iface);
- if (column < cursor->n_variable_names) {
- result = cursor->variable_names[column];
- } else {
- result = sqlite3_column_name (cursor->stmt, column);
- }
+ if (!result)
+ return NULL;
- tracker_db_interface_unlock (iface);
+ /* Weed out our own internal variable prefixes */
+ if (g_str_has_prefix (result, "v_"))
+ return &result[2];
return result;
}
@@ -3650,6 +3691,9 @@ tracker_db_cursor_get_string (TrackerDBCursor *cursor,
TrackerDBInterface *iface;
const gchar *result;
+ if (cursor->n_columns > 0 && column >= cursor->n_columns)
+ return NULL;
+
iface = cursor->ref_stmt->db_interface;
tracker_db_interface_lock (iface);
@@ -3686,21 +3730,18 @@ tracker_db_statement_start_cursor (TrackerDBStatement *stmt,
g_return_val_if_fail (TRACKER_IS_DB_STATEMENT (stmt), NULL);
g_return_val_if_fail (!stmt->stmt_is_used, NULL);
- return tracker_db_cursor_sqlite_new (stmt, NULL, 0, NULL, 0);
+ return tracker_db_cursor_sqlite_new (stmt, 0);
}
TrackerDBCursor *
-tracker_db_statement_start_sparql_cursor (TrackerDBStatement *stmt,
- TrackerPropertyType *types,
- guint n_types,
- const gchar * const *variable_names,
- guint n_variable_names,
- GError **error)
+tracker_db_statement_start_sparql_cursor (TrackerDBStatement *stmt,
+ guint n_columns,
+ GError **error)
{
g_return_val_if_fail (TRACKER_IS_DB_STATEMENT (stmt), NULL);
g_return_val_if_fail (!stmt->stmt_is_used, NULL);
- return tracker_db_cursor_sqlite_new (stmt, types, n_types, variable_names, n_variable_names);
+ return tracker_db_cursor_sqlite_new (stmt, n_columns);
}
static void
diff --git a/src/libtracker-data/tracker-db-interface.h b/src/libtracker-data/tracker-db-interface.h
index ca651126d..66aa31b63 100644
--- a/src/libtracker-data/tracker-db-interface.h
+++ b/src/libtracker-data/tracker-db-interface.h
@@ -143,10 +143,7 @@ void tracker_db_statement_execute (TrackerDBS
TrackerDBCursor * tracker_db_statement_start_cursor (TrackerDBStatement *stmt,
GError **error);
TrackerDBCursor * tracker_db_statement_start_sparql_cursor (TrackerDBStatement *stmt,
- TrackerPropertyType *types,
- guint n_types,
- const gchar * const *variable_names,
- guint n_variable_names,
+ guint n_columns,
GError **error);
/* Functions to deal with a cursor */
diff --git a/src/libtracker-data/tracker-sparql-types.h b/src/libtracker-data/tracker-sparql-types.h
index e676f3f02..3829fd1d6 100644
--- a/src/libtracker-data/tracker-sparql-types.h
+++ b/src/libtracker-data/tracker-sparql-types.h
@@ -214,6 +214,9 @@ struct _TrackerSelectContext {
/* Property path elements */
GPtrArray *path_elements;
+
+ /* Number of variables retrieved in the SELECT */
+ guint n_columns;
};
struct _TrackerSelectContextClass {
diff --git a/src/libtracker-data/tracker-sparql.c b/src/libtracker-data/tracker-sparql.c
index faddac119..4a504e939 100644
--- a/src/libtracker-data/tracker-sparql.c
+++ b/src/libtracker-data/tracker-sparql.c
@@ -174,8 +174,6 @@ struct _TrackerSparql
GHashTable *prefix_map;
GList *filter_clauses;
- GPtrArray *var_names;
- GArray *var_types;
GHashTable *cached_bindings;
GVariantBuilder *blank_nodes;
@@ -243,8 +241,6 @@ tracker_sparql_finalize (GObject *object)
g_ptr_array_unref (sparql->named_graphs);
g_ptr_array_unref (sparql->anon_graphs);
- g_ptr_array_unref (sparql->var_names);
- g_array_unref (sparql->var_types);
g_free (sparql->base);
g_clear_pointer (&sparql->policy.graphs, g_ptr_array_unref);
@@ -2790,11 +2786,7 @@ tracker_sparql_add_select_var (TrackerSparql *sparql,
const gchar *name,
TrackerPropertyType type)
{
- if (sparql->current_state->select_context == sparql->context) {
- /* Topmost select context */
- g_ptr_array_add (sparql->var_names, g_strdup (name));
- g_array_append_val (sparql->var_types, type);
- } else {
+ if (sparql->current_state->select_context != sparql->context) {
TrackerContext *parent;
TrackerVariable *var;
@@ -2831,12 +2823,32 @@ handle_as (TrackerSparql *sparql,
return TRUE;
}
+static void
+handle_value_type_column (TrackerSparql *sparql,
+ TrackerPropertyType prop_type,
+ TrackerVariable *var)
+{
+ TrackerVariable *type_var = NULL;
+
+ if (var)
+ type_var = lookup_subvariable (sparql, var, "type");
+
+ if (type_var) {
+ /* If a $var:type variable exists for this variable, use that */
+ _append_string_printf (sparql, ", %s ",
+ tracker_variable_get_sql_expression (type_var));
+ } else {
+ _append_string_printf (sparql, ", %d ", prop_type);
+ }
+}
+
static gboolean
translate_SelectClause (TrackerSparql *sparql,
GError **error)
{
TrackerSelectContext *select_context;
TrackerStringBuilder *str, *old;
+ TrackerStringBuilder *vars, *types;
gboolean first = TRUE;
/* SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ | '*' )
@@ -2855,6 +2867,8 @@ translate_SelectClause (TrackerSparql *sparql,
}
select_context = TRACKER_SELECT_CONTEXT (sparql->current_state->select_context);
+ vars = _append_placeholder (sparql);
+ types = _append_placeholder (sparql);
if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GLOB)) {
TrackerVariable *var;
@@ -2870,35 +2884,44 @@ translate_SelectClause (TrackerSparql *sparql,
g_hash_table_iter_init (&iter, select_context->variables);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &var)) {
+ TrackerPropertyType prop_type;
+
/* Skip our own internal variables */
if (strchr (var->name, ':'))
continue;
+ old = tracker_sparql_swap_builder (sparql, vars);
+
if (!first)
_append_string (sparql, ", ");
str = _append_placeholder (sparql);
- old = tracker_sparql_swap_builder (sparql, str);
+ tracker_sparql_swap_builder (sparql, str);
+ prop_type = TRACKER_BINDING (tracker_variable_get_sample_binding (var))->data_type;
_append_string_printf (sparql, "%s ",
tracker_variable_get_sql_expression (var));
if (sparql->current_state->select_context == sparql->context) {
- TrackerPropertyType prop_type;
-
- prop_type = TRACKER_BINDING (tracker_variable_get_sample_binding (var))->data_type;
convert_expression_to_string (sparql, prop_type);
}
- if (sparql->current_state->select_context == sparql->context)
- _append_string_printf (sparql, "AS \"%s\" ", var->name);
+ if (sparql->current_state->select_context == sparql->context) {
+ tracker_sparql_swap_builder (sparql, types);
+ handle_value_type_column (sparql, prop_type, var);
+ }
tracker_sparql_swap_builder (sparql, old);
+
first = FALSE;
+ select_context->n_columns++;
}
} else {
do {
- TrackerVariable *var;
+ TrackerVariable *var = NULL;
+ TrackerPropertyType prop_type;
+
+ old = tracker_sparql_swap_builder (sparql, vars);
if (_check_in_rule (sparql, NAMED_RULE_Var)) {
gchar *name;
@@ -2911,27 +2934,28 @@ translate_SelectClause (TrackerSparql *sparql,
name = _dup_last_string (sparql);
str = _append_placeholder (sparql);
- old = tracker_sparql_swap_builder (sparql, str);
+ tracker_sparql_swap_builder (sparql, str);
found = tracker_context_lookup_variable_by_name (sparql->current_state->context,
name);
var = _last_node_variable (sparql);
+ prop_type = sparql->current_state->expression_type;
if (found) {
_append_string_printf (sparql, "%s ",
tracker_variable_get_sql_expression (var));
if (sparql->current_state->select_context == sparql->context)
- convert_expression_to_string (sparql, sparql->current_state->expression_type);
+ convert_expression_to_string (sparql, prop_type);
- select_context->type = sparql->current_state->expression_type;
+ select_context->type = prop_type;
} else {
_append_string (sparql, "NULL ");
- select_context->type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+ select_context->type = prop_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
}
if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_AS)) {
- if (!handle_as (sparql, select_context->type, error)) {
+ if (!handle_as (sparql, prop_type, error)) {
g_free (name);
return FALSE;
}
@@ -2941,10 +2965,9 @@ translate_SelectClause (TrackerSparql *sparql,
tracker_variable_get_sql_expression (var));
}
- tracker_sparql_add_select_var (sparql, name, select_context->type);
+ tracker_sparql_add_select_var (sparql, name, prop_type);
}
- tracker_sparql_swap_builder (sparql, old);
g_free (name);
} else {
gboolean parens = FALSE;
@@ -2958,31 +2981,38 @@ translate_SelectClause (TrackerSparql *sparql,
_append_string (sparql, ", ");
str = _append_placeholder (sparql);
- old = tracker_sparql_swap_builder (sparql, str);
+ tracker_sparql_swap_builder (sparql, str);
_call_rule (sparql, NAMED_RULE_Expression, error);
+ prop_type = sparql->current_state->expression_type;
if (sparql->current_state->select_context == sparql->context)
- convert_expression_to_string (sparql, sparql->current_state->expression_type);
+ convert_expression_to_string (sparql, prop_type);
- select_context->type = sparql->current_state->expression_type;
+ select_context->type = prop_type;
if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_AS)) {
- if (!handle_as (sparql, sparql->current_state->expression_type, error))
+ if (!handle_as (sparql, prop_type, error))
return FALSE;
} else if (sparql->current_state->select_context == sparql->context) {
/* This is only allowed on the topmost context, an
* expression without AS in a subselect is meaningless
*/
- tracker_sparql_add_select_var (sparql, "", sparql->current_state->expression_type);
+ tracker_sparql_add_select_var (sparql, "", prop_type);
}
- tracker_sparql_swap_builder (sparql, old);
-
if (parens)
_expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
}
+ if (sparql->current_state->select_context == sparql->context) {
+ tracker_sparql_swap_builder (sparql, types);
+ handle_value_type_column (sparql, prop_type, var);
+ }
+
+ tracker_sparql_swap_builder (sparql, old);
+
first = FALSE;
+ select_context->n_columns++;
} while (TRUE);
}
@@ -3291,6 +3321,7 @@ translate_ConstructQuery (TrackerSparql *sparql,
GError **error)
{
TrackerParserNode *node = NULL;
+ TrackerSelectContext *select_context;
TrackerStringBuilder *old;
/* ConstructQuery ::= 'CONSTRUCT' ( ConstructTemplate DatasetClause* WhereClause SolutionModifier |
@@ -3349,6 +3380,9 @@ translate_ConstructQuery (TrackerSparql *sparql,
}
}
+ select_context = TRACKER_SELECT_CONTEXT (sparql->current_state->select_context);
+ select_context->n_columns = 3;
+
return TRUE;
}
@@ -3357,20 +3391,29 @@ translate_DescribeQuery (TrackerSparql *sparql,
GError **error)
{
TrackerStringBuilder *where_str = NULL;
+ TrackerSelectContext *select_context;
TrackerVariable *variable;
TrackerBinding *binding;
GList *resources = NULL, *l;
+ gboolean has_variables = FALSE;
gboolean glob = FALSE;
/* DescribeQuery ::= 'DESCRIBE' ( VarOrIri+ | '*' ) DatasetClause* WhereClause? SolutionModifier
*/
_expect (sparql, RULE_TYPE_LITERAL, LITERAL_DESCRIBE);
- _append_string (sparql,
- "SELECT "
- " (SELECT Uri FROM Resource WHERE ID = subject),"
- " (SELECT Uri FROM Resource WHERE ID = predicate),"
- " object "
- "FROM tracker_triples ");
+ _append_string_printf (sparql,
+ "SELECT "
+ " COALESCE((SELECT Uri FROM Resource WHERE ID = subject), 'urn:bnode:' || subject),"
+ " (SELECT Uri FROM Resource WHERE ID = predicate),"
+ " object, "
+ " (SELECT Uri FROM Resource WHERE ID = graph) ");
+
+ handle_value_type_column (sparql, TRACKER_PROPERTY_TYPE_RESOURCE, NULL);
+ handle_value_type_column (sparql, TRACKER_PROPERTY_TYPE_RESOURCE, NULL);
+ _append_string (sparql, ", object_type ");
+ handle_value_type_column (sparql, TRACKER_PROPERTY_TYPE_RESOURCE, NULL);
+
+ _append_string_printf (sparql, "FROM tracker_triples ");
if (sparql->policy.graphs) {
_append_graph_checks (sparql, "graph",
@@ -3382,7 +3425,7 @@ translate_DescribeQuery (TrackerSparql *sparql,
_append_string (sparql, "WHERE ");
}
_append_string (sparql,
- "object IS NOT NULL AND subject IN (");
+ "object IS NOT NULL ");
if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GLOB)) {
glob = TRUE;
@@ -3407,6 +3450,7 @@ translate_DescribeQuery (TrackerSparql *sparql,
variable = tracker_token_get_variable (&resource);
binding = tracker_variable_binding_new (variable, NULL, NULL);
+ has_variables = TRUE;
}
tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
@@ -3417,6 +3461,25 @@ translate_DescribeQuery (TrackerSparql *sparql,
tracker_sparql_pop_context (sparql, FALSE);
}
+ if (has_variables) {
+ /* If we have variables, we will likely have a WHERE clause
+ * that will return a moderately large set of results.
+ *
+ * Since the turning point is not that far where it is faster
+ * to query everything from the triples table and filter later
+ * than querying row by row, we soon want the former if there
+ * is a WHERE pattern.
+ *
+ * To hint this to SQLite query planner, use the unary plus
+ * operator to disqualify the term from constraining an index,
+ * (https://www.sqlite.org/optoverview.html#disqualifying_where_clause_terms_using_unary_)
+ * which is exactly what we are meaning to do here.
+ */
+ _append_string (sparql, "AND +subject IN (");
+ } else {
+ _append_string (sparql, "AND subject IN (");
+ }
+
while (_check_in_rule (sparql, NAMED_RULE_DatasetClause))
_call_rule (sparql, NAMED_RULE_DatasetClause, error);
@@ -3484,6 +3547,9 @@ translate_DescribeQuery (TrackerSparql *sparql,
g_list_free_full (resources, g_object_unref);
g_clear_pointer (&where_str, tracker_string_builder_free);
+ select_context = TRACKER_SELECT_CONTEXT (sparql->current_state->select_context);
+ select_context->n_columns = 4;
+
return TRUE;
}
@@ -3492,6 +3558,7 @@ translate_AskQuery (TrackerSparql *sparql,
GError **error)
{
TrackerStringBuilder *str, *old;
+ TrackerSelectContext *select_context;
/* AskQuery ::= 'ASK' DatasetClause* WhereClause SolutionModifier
*/
@@ -3511,6 +3578,10 @@ translate_AskQuery (TrackerSparql *sparql,
tracker_sparql_swap_builder (sparql, old);
_append_string (sparql, ") WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE NULL END");
+ handle_value_type_column (sparql, TRACKER_PROPERTY_TYPE_BOOLEAN, NULL);
+
+ select_context = TRACKER_SELECT_CONTEXT (sparql->current_state->select_context);
+ select_context->n_columns = 1;
return TRUE;
}
@@ -4617,7 +4688,7 @@ get_solution_for_pattern (TrackerSparql *sparql,
iface = tracker_data_manager_get_writable_db_interface (sparql->data_manager);
stmt = prepare_query (sparql, iface,
- TRACKER_SELECT_CONTEXT (sparql->context)->literal_bindings,
+ TRACKER_SELECT_CONTEXT (sparql->context)->literal_bindings,
NULL, TRUE,
error);
g_clear_object (&sparql->context);
@@ -4625,10 +4696,7 @@ get_solution_for_pattern (TrackerSparql *sparql,
if (!stmt)
return NULL;
- cursor = tracker_db_statement_start_sparql_cursor (stmt,
- NULL, 0,
- NULL, 0,
- error);
+ cursor = tracker_db_statement_start_sparql_cursor (stmt, 0, error);
g_object_unref (stmt);
if (!cursor)
@@ -9716,8 +9784,6 @@ tracker_sparql_init (TrackerSparql *sparql)
g_free, g_object_unref);
sparql->parameters = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_object_unref);
- sparql->var_names = g_ptr_array_new_with_free_func (g_free);
- sparql->var_types = g_array_new (FALSE, FALSE, sizeof (TrackerPropertyType));
sparql->anon_graphs = g_ptr_array_new_with_free_func (g_free);
sparql->named_graphs = g_ptr_array_new_with_free_func (g_free);
sparql->cacheable = TRUE;
@@ -9909,8 +9975,6 @@ tracker_sparql_reset_state (TrackerSparql *sparql)
g_clear_object (&sparql->context);
g_list_free (sparql->filter_clauses);
sparql->filter_clauses = NULL;
- g_ptr_array_set_size (sparql->var_names, 0);
- g_array_set_size (sparql->var_types, 0);
g_hash_table_remove_all (sparql->cached_bindings);
g_hash_table_remove_all (sparql->parameters);
g_ptr_array_set_size (sparql->anon_graphs, 0);
@@ -9925,9 +9989,7 @@ tracker_sparql_execute_cursor (TrackerSparql *sparql,
TrackerDBStatement *stmt;
TrackerDBInterface *iface = NULL;
TrackerDBCursor *cursor = NULL;
- TrackerPropertyType *types;
- const gchar * const *names;
- guint n_types, n_names;
+ TrackerSelectContext *select_context;
g_mutex_lock (&sparql->mutex);
@@ -9967,22 +10029,17 @@ tracker_sparql_execute_cursor (TrackerSparql *sparql,
if (!iface)
goto error;
+ select_context = TRACKER_SELECT_CONTEXT (sparql->context);
stmt = prepare_query (sparql, iface,
- TRACKER_SELECT_CONTEXT (sparql->context)->literal_bindings,
+ select_context->literal_bindings,
parameters,
sparql->cacheable,
error);
if (!stmt)
goto error;
- types = (TrackerPropertyType *) sparql->var_types->data;
- n_types = sparql->var_types->len;
- names = (const gchar * const *) sparql->var_names->pdata;
- n_names = sparql->var_names->len;
-
cursor = tracker_db_statement_start_sparql_cursor (stmt,
- types, n_types,
- names, n_names,
+ select_context->n_columns,
error);
g_object_unref (stmt);
@@ -10093,3 +10150,40 @@ tracker_sparql_make_langstring (const gchar *str,
return bytes;
}
+
+gboolean
+tracker_sparql_is_serializable (TrackerSparql *sparql)
+{
+ TrackerParserNode *node;
+
+ /* Updates are the other way around */
+ if (sparql->query_type == TRACKER_SPARQL_QUERY_UPDATE)
+ return FALSE;
+
+ if (!sparql->tree)
+ return FALSE;
+
+ node = tracker_node_tree_get_root (sparql->tree);
+
+ for (node = tracker_sparql_parser_tree_find_first (node, FALSE);
+ node;
+ node = tracker_sparql_parser_tree_find_next (node, FALSE)) {
+ const TrackerGrammarRule *rule;
+
+ rule = tracker_parser_node_get_rule (node);
+
+ /* Only DESCRIBE and CONSTRUCT queries apply, since these
+ * are guaranteed to return full RDF data.
+ */
+ if (tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, NAMED_RULE_DescribeQuery) ||
+ tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, NAMED_RULE_ConstructQuery))
+ return TRUE;
+
+ /* Early out in other query types */
+ if (tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, NAMED_RULE_SelectQuery) ||
+ tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, NAMED_RULE_AskQuery))
+ break;
+ }
+
+ return FALSE;
+}
diff --git a/src/libtracker-data/tracker-sparql.h b/src/libtracker-data/tracker-sparql.h
index 74297bd0a..29c881cc9 100644
--- a/src/libtracker-data/tracker-sparql.h
+++ b/src/libtracker-data/tracker-sparql.h
@@ -35,6 +35,8 @@ G_DECLARE_FINAL_TYPE (TrackerSparql, tracker_sparql,
TrackerSparql * tracker_sparql_new (TrackerDataManager *manager,
const gchar *sparql);
+gboolean tracker_sparql_is_serializable (TrackerSparql *sparql);
+
TrackerSparqlCursor * tracker_sparql_execute_cursor (TrackerSparql *sparql,
GHashTable *parameters,
GError **error);
diff --git a/src/libtracker-sparql/bus/tracker-bus-statement.vala b/src/libtracker-sparql/bus/tracker-bus-statement.vala
index 39af7c766..6c4dfa892 100644
--- a/src/libtracker-sparql/bus/tracker-bus-statement.vala
+++ b/src/libtracker-sparql/bus/tracker-bus-statement.vala
@@ -93,4 +93,8 @@ public class Tracker.Bus.Statement : Tracker.Sparql.Statement {
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);
}
+
+ public async override GLib.InputStream serialize_async (SerializeFlags flags, RdfFormat format, GLib.Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError {
+ return yield Tracker.Bus.Connection.perform_serialize (bus, dbus_name, object_path, flags, format, query, get_arguments (), cancellable);
+ }
}
diff --git a/src/libtracker-sparql/bus/tracker-bus.vala b/src/libtracker-sparql/bus/tracker-bus.vala
index f61c6226f..ffa6f265d 100644
--- a/src/libtracker-sparql/bus/tracker-bus.vala
+++ b/src/libtracker-sparql/bus/tracker-bus.vala
@@ -391,4 +391,24 @@ public class Tracker.Bus.Connection : Tracker.Sparql.Connection {
null);
return batch;
}
+
+ public static async GLib.InputStream perform_serialize (DBusConnection bus, string dbus_name, string object_path, SerializeFlags flags, RdfFormat format, string sparql, VariantBuilder? arguments, Cancellable? cancellable) throws GLib.IOError, GLib.Error {
+ UnixInputStream input;
+ UnixOutputStream output;
+ pipe (out input, out output);
+
+ var message = new DBusMessage.method_call (dbus_name, object_path, ENDPOINT_IFACE, "Serialize");
+ var fd_list = new UnixFDList ();
+ message.set_body (new Variant ("(shiia{sv})", sparql, fd_list.append (output.fd), flags, format, arguments));
+ message.set_unix_fd_list (fd_list);
+
+ var reply = yield bus.send_message_with_reply (message, DBusSendMessageFlags.NONE, int.MAX, null, cancellable);
+ handle_error_reply (reply);
+
+ return input;
+ }
+
+ public async override GLib.InputStream serialize_async (SerializeFlags flags, RdfFormat format, string sparql, GLib.Cancellable? cancellable = null) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError {
+ return yield perform_serialize (bus, dbus_name, object_path, flags, format, sparql, null, cancellable);
+ }
}
diff --git a/src/libtracker-sparql/direct/tracker-direct-statement.c b/src/libtracker-sparql/direct/tracker-direct-statement.c
index ee6b54d7c..86f58213b 100644
--- a/src/libtracker-sparql/direct/tracker-direct-statement.c
+++ b/src/libtracker-sparql/direct/tracker-direct-statement.c
@@ -21,6 +21,7 @@
#include "tracker-direct-statement.h"
#include "tracker-data.h"
#include "tracker-private.h"
+#include <libtracker-sparql/tracker-serializer.h>
typedef struct _TrackerDirectStatementPrivate TrackerDirectStatementPrivate;
@@ -30,6 +31,12 @@ struct _TrackerDirectStatementPrivate
GHashTable *values;
};
+typedef struct
+{
+ GHashTable *values;
+ TrackerRdfFormat format;
+} RdfSerializationData;
+
G_DEFINE_TYPE_WITH_PRIVATE (TrackerDirectStatement,
tracker_direct_statement,
TRACKER_TYPE_SPARQL_STATEMENT)
@@ -193,6 +200,71 @@ execute_in_thread (GTask *task,
}
static void
+rdf_serialization_data_free (RdfSerializationData *data)
+{
+ g_hash_table_unref (data->values);
+ g_free (data);
+}
+
+static TrackerSerializerFormat
+convert_format (TrackerRdfFormat format)
+{
+ switch (format) {
+ case TRACKER_RDF_FORMAT_TURTLE:
+ return TRACKER_SERIALIZER_FORMAT_TTL;
+ case TRACKER_RDF_FORMAT_TRIG:
+ return TRACKER_SERIALIZER_FORMAT_TRIG;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+serialize_in_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ TrackerSparqlConnection *conn;
+ TrackerDirectStatementPrivate *priv;
+ TrackerSparqlCursor *cursor = NULL;
+ RdfSerializationData *data = task_data;
+ GInputStream *istream = NULL;
+ GError *error = NULL;
+
+ if (g_task_return_error_if_cancelled (task))
+ return;
+
+ priv = tracker_direct_statement_get_instance_private (object);
+ if (!tracker_sparql_is_serializable (priv->sparql)) {
+ g_set_error (&error,
+ TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_PARSE,
+ "Query is not DESCRIBE or CONSTRUCT");
+ goto out;
+ }
+
+ cursor = tracker_sparql_execute_cursor (priv->sparql, data->values, &error);
+ if (!cursor)
+ goto out;
+
+ conn = tracker_sparql_statement_get_connection (object);
+ tracker_direct_connection_update_timestamp (TRACKER_DIRECT_CONNECTION (conn));
+ tracker_sparql_cursor_set_connection (cursor, conn);
+ istream = tracker_serializer_new (cursor, convert_format (data->format));
+
+ out:
+ g_clear_object (&cursor);
+
+ if (istream)
+ g_task_return_pointer (task, istream, g_object_unref);
+ else
+ g_task_return_error (task, error);
+
+ g_object_unref (task);
+}
+
+static void
free_gvalue (gpointer data)
{
g_value_unset (data);
@@ -269,6 +341,44 @@ tracker_direct_statement_execute_finish (TrackerSparqlStatement *stmt,
}
static void
+tracker_direct_statement_serialize_async (TrackerSparqlStatement *stmt,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TrackerDirectStatementPrivate *priv;
+ RdfSerializationData *data;
+ GTask *task;
+
+ priv = tracker_direct_statement_get_instance_private (TRACKER_DIRECT_STATEMENT (stmt));
+
+ data = g_new0 (RdfSerializationData, 1);
+ data->values = copy_values_deep (priv->values);
+ data->format = format;
+
+ task = g_task_new (stmt, cancellable, callback, user_data);
+ g_task_set_task_data (task, data, (GDestroyNotify) rdf_serialization_data_free);
+ g_task_run_in_thread (task, serialize_in_thread);
+}
+
+static GInputStream *
+tracker_direct_statement_serialize_finish (TrackerSparqlStatement *stmt,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ GInputStream *istream;
+
+ istream = g_task_propagate_pointer (G_TASK (res), &inner_error);
+ if (inner_error)
+ g_propagate_error (error, _translate_internal_error (inner_error));
+
+ return istream;
+}
+
+static void
tracker_direct_statement_class_init (TrackerDirectStatementClass *klass)
{
TrackerSparqlStatementClass *stmt_class = (TrackerSparqlStatementClass *) klass;
@@ -286,6 +396,8 @@ tracker_direct_statement_class_init (TrackerDirectStatementClass *klass)
stmt_class->execute = tracker_direct_statement_execute;
stmt_class->execute_async = tracker_direct_statement_execute_async;
stmt_class->execute_finish = tracker_direct_statement_execute_finish;
+ stmt_class->serialize_async = tracker_direct_statement_serialize_async;
+ stmt_class->serialize_finish = tracker_direct_statement_serialize_finish;
}
static void
diff --git a/src/libtracker-sparql/direct/tracker-direct.c b/src/libtracker-sparql/direct/tracker-direct.c
index a4a69d44c..8317f40d3 100644
--- a/src/libtracker-sparql/direct/tracker-direct.c
+++ b/src/libtracker-sparql/direct/tracker-direct.c
@@ -29,6 +29,7 @@
#include <libtracker-data/tracker-sparql.h>
#include <libtracker-sparql/tracker-notifier-private.h>
#include <libtracker-sparql/tracker-private.h>
+#include <libtracker-sparql/tracker-serializer.h>
typedef struct _TrackerDirectConnectionPrivate TrackerDirectConnectionPrivate;
@@ -61,6 +62,11 @@ typedef struct {
TrackerResource *resource;
} UpdateResource;
+typedef struct {
+ gchar *query;
+ TrackerRdfFormat format;
+} SerializeRdf;
+
enum {
PROP_0,
PROP_FLAGS,
@@ -78,6 +84,7 @@ typedef enum {
TASK_TYPE_UPDATE_RESOURCE,
TASK_TYPE_UPDATE_BATCH,
TASK_TYPE_RELEASE_MEMORY,
+ TASK_TYPE_SERIALIZE,
} TaskType;
typedef struct {
@@ -208,6 +215,7 @@ update_thread_func (gpointer data,
switch (task_data->type) {
case TASK_TYPE_QUERY:
+ case TASK_TYPE_SERIALIZE:
g_warning ("Queries don't go through this thread");
break;
case TASK_TYPE_UPDATE:
@@ -247,17 +255,87 @@ update_thread_func (gpointer data,
}
static void
+execute_query_in_thread (GTask *task,
+ TaskData *task_data)
+{
+ TrackerSparqlCursor *cursor;
+ GError *error = NULL;
+
+ cursor = tracker_sparql_connection_query (TRACKER_SPARQL_CONNECTION (g_task_get_source_object (task)),
+ task_data->data,
+ g_task_get_cancellable (task),
+ &error);
+ if (cursor)
+ g_task_return_pointer (task, cursor, g_object_unref);
+ else
+ g_task_return_error (task, error);
+}
+
+static TrackerSerializerFormat
+convert_format (TrackerRdfFormat format)
+{
+ switch (format) {
+ case TRACKER_RDF_FORMAT_TURTLE:
+ return TRACKER_SERIALIZER_FORMAT_TTL;
+ case TRACKER_RDF_FORMAT_TRIG:
+ return TRACKER_SERIALIZER_FORMAT_TRIG;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static void
+serialize_in_thread (GTask *task,
+ TaskData *task_data)
+{
+ TrackerDirectConnectionPrivate *priv;
+ TrackerDirectConnection *conn;
+ TrackerSparql *query = NULL;
+ TrackerSparqlCursor *cursor = NULL;
+ GInputStream *istream = NULL;
+ SerializeRdf *data = task_data->data;
+ GError *error = NULL;
+
+ conn = g_task_get_source_object (task);
+ priv = tracker_direct_connection_get_instance_private (conn);
+
+ g_mutex_lock (&priv->mutex);
+ query = tracker_sparql_new (priv->data_manager, data->query);
+ if (!tracker_sparql_is_serializable (query)) {
+ g_set_error (&error,
+ TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_PARSE,
+ "Query is not DESCRIBE or CONSTRUCT");
+ goto out;
+ }
+
+ cursor = tracker_sparql_execute_cursor (query, NULL, &error);
+ tracker_direct_connection_update_timestamp (conn);
+ if (!cursor)
+ goto out;
+
+ tracker_sparql_cursor_set_connection (cursor, TRACKER_SPARQL_CONNECTION (conn));
+ istream = tracker_serializer_new (cursor, convert_format (data->format));
+
+ out:
+ g_clear_object (&query);
+ g_clear_object (&cursor);
+ g_mutex_unlock (&priv->mutex);
+
+ if (istream)
+ g_task_return_pointer (task, istream, g_object_unref);
+ else
+ g_task_return_error (task, error);
+}
+
+static void
query_thread_pool_func (gpointer data,
gpointer user_data)
{
TrackerDirectConnection *conn = user_data;
TrackerDirectConnectionPrivate *priv;
- TrackerSparqlCursor *cursor;
GTask *task = data;
TaskData *task_data = g_task_get_task_data (task);
- GError *error = NULL;
-
- g_assert (task_data->type == TASK_TYPE_QUERY);
priv = tracker_direct_connection_get_instance_private (conn);
@@ -270,14 +348,16 @@ query_thread_pool_func (gpointer data,
return;
}
- cursor = tracker_sparql_connection_query (TRACKER_SPARQL_CONNECTION (g_task_get_source_object (task)),
- task_data->data,
- g_task_get_cancellable (task),
- &error);
- if (cursor)
- g_task_return_pointer (task, cursor, g_object_unref);
- else
- g_task_return_error (task, error);
+ switch (task_data->type) {
+ case TASK_TYPE_QUERY:
+ execute_query_in_thread (task, task_data);
+ break;
+ case TASK_TYPE_SERIALIZE:
+ serialize_in_thread (task, task_data);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
g_object_unref (task);
}
@@ -1227,6 +1307,66 @@ tracker_direct_connection_lookup_dbus_service (TrackerSparqlConnection *connect
return TRUE;
}
+static SerializeRdf *
+serialize_rdf_data_new (const gchar *query,
+ TrackerRdfFormat format)
+{
+ SerializeRdf *data;
+
+ data = g_new0 (SerializeRdf, 1);
+ data->query = g_strdup (query);
+ data->format = format;
+
+ return data;
+}
+
+static void
+serialize_rdf_data_free (gpointer user_data)
+{
+ SerializeRdf *data = user_data;
+
+ g_free (data->query);
+ g_free (data);
+}
+
+static void
+tracker_direct_connection_serialize_async (TrackerSparqlConnection *self,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ const gchar *query,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TrackerDirectConnectionPrivate *priv;
+ TrackerDirectConnection *conn;
+ GError *error = NULL;
+ GTask *task;
+
+ conn = TRACKER_DIRECT_CONNECTION (self);
+ priv = tracker_direct_connection_get_instance_private (conn);
+
+ task = g_task_new (self, cancellable, callback, user_data);
+ g_task_set_task_data (task,
+ task_data_query_new (TASK_TYPE_SERIALIZE,
+ serialize_rdf_data_new (query, format),
+ serialize_rdf_data_free),
+ (GDestroyNotify) task_data_free);
+
+ if (!g_thread_pool_push (priv->select_pool, task, &error)) {
+ g_task_return_error (task, _translate_internal_error (error));
+ g_object_unref (task);
+ }
+}
+
+static GInputStream *
+tracker_direct_connection_serialize_finish (TrackerSparqlConnection *connection,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
static void
tracker_direct_connection_class_init (TrackerDirectConnectionClass *klass)
{
@@ -1262,6 +1402,8 @@ tracker_direct_connection_class_init (TrackerDirectConnectionClass *klass)
sparql_connection_class->update_resource_finish = tracker_direct_connection_update_resource_finish;
sparql_connection_class->create_batch = tracker_direct_connection_create_batch;
sparql_connection_class->lookup_dbus_service = tracker_direct_connection_lookup_dbus_service;
+ sparql_connection_class->serialize_async = tracker_direct_connection_serialize_async;
+ sparql_connection_class->serialize_finish = tracker_direct_connection_serialize_finish;
props[PROP_FLAGS] =
g_param_spec_flags ("flags",
diff --git a/src/libtracker-sparql/meson.build b/src/libtracker-sparql/meson.build
index 3a64b1311..35fe7be9f 100644
--- a/src/libtracker-sparql/meson.build
+++ b/src/libtracker-sparql/meson.build
@@ -27,6 +27,8 @@ libtracker_sparql_c_sources = files(
'tracker-statement.c',
'tracker-serializer.c',
'tracker-serializer-json.c',
+ 'tracker-serializer-trig.c',
+ 'tracker-serializer-turtle.c',
'tracker-serializer-xml.c',
'tracker-uri.c',
'tracker-utils.c',
diff --git a/src/libtracker-sparql/remote/tracker-remote-statement.c b/src/libtracker-sparql/remote/tracker-remote-statement.c
index abd6570f0..beb94d2da 100644
--- a/src/libtracker-sparql/remote/tracker-remote-statement.c
+++ b/src/libtracker-sparql/remote/tracker-remote-statement.c
@@ -336,7 +336,7 @@ copy_values_deep (GHashTable *values)
return copy;
}
-void
+static void
tracker_remote_statement_execute_async (TrackerSparqlStatement *stmt,
GCancellable *cancellable,
GAsyncReadyCallback callback,
@@ -353,7 +353,7 @@ tracker_remote_statement_execute_async (TrackerSparqlStatement *stmt,
g_task_run_in_thread (task, execute_in_thread);
}
-TrackerSparqlCursor *
+static TrackerSparqlCursor *
tracker_remote_statement_execute_finish (TrackerSparqlStatement *stmt,
GAsyncResult *res,
GError **error)
@@ -361,7 +361,7 @@ tracker_remote_statement_execute_finish (TrackerSparqlStatement *stmt,
return g_task_propagate_pointer (G_TASK (res), error);
}
-void
+static void
tracker_remote_statement_clear_bindings (TrackerSparqlStatement *stmt)
{
TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
@@ -370,6 +370,70 @@ tracker_remote_statement_clear_bindings (TrackerSparqlStatement *stmt)
}
static void
+serialize_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GInputStream *istream;
+ GError *error = NULL;
+ GTask *task = user_data;
+
+ istream = tracker_sparql_connection_serialize_finish (TRACKER_SPARQL_CONNECTION (source),
+ res, &error);
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, istream, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+tracker_remote_statement_serialize_async (TrackerSparqlStatement *stmt,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TrackerRemoteStatement *remote_stmt = TRACKER_REMOTE_STATEMENT (stmt);
+ gchar *rewritten_query = NULL;
+ GError *error = NULL;
+ GTask *task;
+
+ task = g_task_new (stmt, cancellable, callback, user_data);
+
+ if (g_hash_table_size (remote_stmt->bindings) > 0) {
+ rewritten_query = apply_bindings (stmt,
+ remote_stmt->bindings,
+ &error);
+ if (!rewritten_query) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ tracker_sparql_connection_serialize_async (tracker_sparql_statement_get_connection (stmt),
+ flags,
+ format,
+ rewritten_query ? rewritten_query :
+ tracker_sparql_statement_get_sparql (stmt),
+ cancellable,
+ serialize_cb,
+ task);
+ g_free (rewritten_query);
+}
+
+static GInputStream *
+tracker_remote_statement_serialize_finish (TrackerSparqlStatement *stmt,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
tracker_remote_statement_class_init (TrackerRemoteStatementClass *klass)
{
TrackerSparqlStatementClass *stmt_class = TRACKER_SPARQL_STATEMENT_CLASS (klass);
@@ -386,6 +450,8 @@ tracker_remote_statement_class_init (TrackerRemoteStatementClass *klass)
stmt_class->execute_async = tracker_remote_statement_execute_async;
stmt_class->execute_finish = tracker_remote_statement_execute_finish;
stmt_class->clear_bindings = tracker_remote_statement_clear_bindings;
+ stmt_class->serialize_async = tracker_remote_statement_serialize_async;
+ stmt_class->serialize_finish = tracker_remote_statement_serialize_finish;
}
static void
diff --git a/src/libtracker-sparql/remote/tracker-remote.vala b/src/libtracker-sparql/remote/tracker-remote.vala
index f3be3147a..71a1d3965 100644
--- a/src/libtracker-sparql/remote/tracker-remote.vala
+++ b/src/libtracker-sparql/remote/tracker-remote.vala
@@ -28,6 +28,8 @@ public class Tracker.Remote.Connection : Tracker.Sparql.Connection {
const string XML_TYPE = "application/sparql-results+xml";
const string JSON_TYPE = "application/sparql-results+json";
+ const string TTL_TYPE = "text/turtle";
+ const string TRIG_TYPE = "application/trig";
const string USER_AGENT = "Tracker/" + PACKAGE_VERSION + " (https://gitlab.gnome.org/GNOME/tracker/issues/; tracker-list@lists.gnome.org) Tracker/" + PACKAGE_VERSION;
public Connection (string base_uri) {
@@ -36,6 +38,25 @@ public class Tracker.Remote.Connection : Tracker.Sparql.Connection {
_session = new Soup.Session ();
}
+ private Soup.Message create_describe_request (string sparql, RdfFormat format) {
+ var uri = _base_uri + "?query=" + GLib.Uri.escape_string (sparql, null, false);
+ var message = new Soup.Message ("GET", uri);
+#if SOUP2
+ var headers = message.request_headers;
+#else
+ var headers = message.get_request_headers();
+#endif
+
+ headers.append ("User-Agent", USER_AGENT);
+
+ if (format == RdfFormat.TURTLE)
+ headers.append ("Accept", TTL_TYPE);
+ else if (format == RdfFormat.TRIG)
+ headers.append ("Accept", TRIG_TYPE);
+
+ return message;
+ }
+
private Soup.Message create_request (string sparql) {
var uri = _base_uri + "?query=" + GLib.Uri.escape_string (sparql, null, false);
var message = new Soup.Message ("GET", uri);
@@ -120,4 +141,13 @@ public class Tracker.Remote.Connection : Tracker.Sparql.Connection {
public async override bool close_async () throws GLib.IOError {
return true;
}
+
+ public async override GLib.InputStream serialize_async (SerializeFlags flags, RdfFormat format, string sparql, GLib.Cancellable? cancellable = null) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError {
+ var message = create_describe_request (sparql, format);
+#if SOUP2
+ return yield _session.send_async (message, cancellable);
+#else
+ return yield _session.send_async (message, GLib.Priority.DEFAULT, cancellable);
+#endif
+ }
}
diff --git a/src/libtracker-sparql/tracker-connection.c b/src/libtracker-sparql/tracker-connection.c
index e2585d440..33042b0f9 100644
--- a/src/libtracker-sparql/tracker-connection.c
+++ b/src/libtracker-sparql/tracker-connection.c
@@ -60,6 +60,10 @@
#include "tracker-connection.h"
#include "tracker-private.h"
+#include "tracker-serializer-json.h"
+#include "tracker-serializer-trig.h"
+#include "tracker-serializer-turtle.h"
+#include "tracker-serializer-xml.h"
G_DEFINE_ABSTRACT_TYPE (TrackerSparqlConnection, tracker_sparql_connection,
G_TYPE_OBJECT)
@@ -83,6 +87,14 @@ tracker_sparql_connection_class_init (TrackerSparqlConnectionClass *klass)
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = tracker_sparql_connection_dispose;
+
+ /* Ensure serializer types, we want all of these initialized before
+ * the remote soup 2/3 modules gets to initialize them.
+ */
+ g_type_ensure (TRACKER_TYPE_SERIALIZER_XML);
+ g_type_ensure (TRACKER_TYPE_SERIALIZER_JSON);
+ g_type_ensure (TRACKER_TYPE_SERIALIZER_TURTLE);
+ g_type_ensure (TRACKER_TYPE_SERIALIZER_TRIG);
}
gboolean
@@ -858,3 +870,77 @@ tracker_sparql_connection_load_statement_from_gresource (TrackerSparqlConnection
return stmt;
}
+
+/**
+ * tracker_sparql_connection_serialize_async:
+ * @connection: a #TrackerSparqlConnection
+ * @flags: serialization flags
+ * @format: output RDF format
+ * @query: SPARQL query
+ * @cancellable: a #GCancellable
+ * @callback: the #GAsyncReadyCallback called when the operation completes
+ * @user_data: data passed to @callback
+ *
+ * Serializes data into the specified RDF format. @query must be either a
+ * `DESCRIBE` or `CONSTRUCT` query. This is an asynchronous operation,
+ * @callback will be invoked when the data is available for reading.
+ *
+ * The SPARQL endpoint may not support the specified format, in that case
+ * an error will be raised.
+ *
+ * The @flags argument is reserved for future expansions, currently
+ * %TRACKER_SERIALIZE_FLAGS_NONE must be passed.
+ *
+ * Since: 3.3
+ **/
+void
+tracker_sparql_connection_serialize_async (TrackerSparqlConnection *connection,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ const gchar *query,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (TRACKER_IS_SPARQL_CONNECTION (connection));
+ g_return_if_fail (flags == TRACKER_SERIALIZE_FLAGS_NONE);
+ g_return_if_fail (format < TRACKER_N_RDF_FORMATS);
+ g_return_if_fail (query != NULL);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (callback != NULL);
+
+ TRACKER_SPARQL_CONNECTION_GET_CLASS (connection)->serialize_async (connection,
+ flags,
+ format,
+ query,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * tracker_sparql_connection_serialize_finish:
+ * @connection: a #TrackerSparqlConnection
+ * @result: the #GAsyncResult
+ * @error: location for returned errors, or %NULL
+ *
+ * Finishes a tracker_sparql_connection_serialize_async() operation.
+ * In case of error, %NULL will be returned and @error will be set.
+ *
+ * Returns: (transfer full): a #GInputStream to read RDF content.
+ *
+ * Since: 3.3
+ **/
+GInputStream *
+tracker_sparql_connection_serialize_finish (TrackerSparqlConnection *connection,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (TRACKER_IS_SPARQL_CONNECTION (connection), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+ g_return_val_if_fail (!error || !*error, NULL);
+
+ return TRACKER_SPARQL_CONNECTION_GET_CLASS (connection)->serialize_finish (connection,
+ result,
+ error);
+}
diff --git a/src/libtracker-sparql/tracker-connection.h b/src/libtracker-sparql/tracker-connection.h
index b8ea4715a..edb185c84 100644
--- a/src/libtracker-sparql/tracker-connection.h
+++ b/src/libtracker-sparql/tracker-connection.h
@@ -54,6 +54,16 @@ typedef enum {
TRACKER_SPARQL_CONNECTION_FLAGS_ANONYMOUS_BNODES = 1 << 5,
} TrackerSparqlConnectionFlags;
+typedef enum {
+ TRACKER_RDF_FORMAT_TURTLE,
+ TRACKER_RDF_FORMAT_TRIG,
+ TRACKER_N_RDF_FORMATS
+} TrackerRdfFormat;
+
+typedef enum {
+ TRACKER_SERIALIZE_FLAGS_NONE = 0,
+} TrackerSerializeFlags;
+
/**
* TrackerSparqlConnection:
*
@@ -204,6 +214,19 @@ TrackerNotifier * tracker_sparql_connection_create_notifier (TrackerSparqlConnec
TRACKER_AVAILABLE_IN_ALL
void tracker_sparql_connection_close (TrackerSparqlConnection *connection);
+TRACKER_AVAILABLE_IN_3_3
+void tracker_sparql_connection_serialize_async (TrackerSparqlConnection *connection,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ const gchar *query,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+TRACKER_AVAILABLE_IN_3_3
+GInputStream * tracker_sparql_connection_serialize_finish (TrackerSparqlConnection *connection,
+ GAsyncResult *result,
+ GError **error);
+
TRACKER_AVAILABLE_IN_ALL
void tracker_sparql_connection_close_async (TrackerSparqlConnection *connection,
GCancellable *cancellable,
diff --git a/src/libtracker-sparql/tracker-endpoint-dbus.c b/src/libtracker-sparql/tracker-endpoint-dbus.c
index d022c6f04..fa840e0e3 100644
--- a/src/libtracker-sparql/tracker-endpoint-dbus.c
+++ b/src/libtracker-sparql/tracker-endpoint-dbus.c
@@ -60,6 +60,13 @@ static const gchar introspection_xml[] =
" <arg type='a{sv}' name='arguments' direction='in' />"
" <arg type='as' name='result' direction='out' />"
" </method>"
+ " <method name='Serialize'>"
+ " <arg type='s' name='query' direction='in' />"
+ " <arg type='h' name='output_stream' direction='in' />"
+ " <arg type='i' name='flags' direction='in' />"
+ " <arg type='i' name='format' direction='in' />"
+ " <arg type='a{sv}' name='arguments' direction='in' />"
+ " </method>"
" <method name='Update'>"
" <arg type='h' name='input_stream' direction='in' />"
" </method>"
@@ -507,6 +514,84 @@ stmt_execute_cb (GObject *object,
}
static void
+splice_rdf_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ QueryRequest *request = user_data;
+ GError *error = NULL;
+
+ g_output_stream_splice_finish (G_OUTPUT_STREAM (object),
+ res, &error);
+
+ if (error) {
+ /* The query request method invocations has been already replied */
+ g_warning ("Error splicing RDF data: %s", error->message);
+ g_error_free (error);
+ }
+
+ query_request_free (request);
+}
+
+static void
+stmt_serialize_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ QueryRequest *request = user_data;
+ GInputStream *istream;
+ GError *error = NULL;
+
+ istream = tracker_sparql_statement_serialize_finish (TRACKER_SPARQL_STATEMENT (object),
+ res, &error);
+ if (!istream) {
+ g_dbus_method_invocation_return_gerror (request->invocation, error);
+ g_error_free (error);
+ query_request_free (request);
+ return;
+ }
+
+ g_dbus_method_invocation_return_value (request->invocation, NULL);
+ g_output_stream_splice_async (G_OUTPUT_STREAM (request->data_stream),
+ istream,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ G_PRIORITY_DEFAULT,
+ request->global_cancellable,
+ splice_rdf_cb,
+ request);
+}
+
+static void
+serialize_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ QueryRequest *request = user_data;
+ GInputStream *istream;
+ GError *error = NULL;
+
+ istream = tracker_sparql_connection_serialize_finish (TRACKER_SPARQL_CONNECTION (object),
+ res, &error);
+ if (!istream) {
+ g_dbus_method_invocation_return_gerror (request->invocation, error);
+ g_error_free (error);
+ query_request_free (request);
+ return;
+ }
+
+ g_dbus_method_invocation_return_value (request->invocation, NULL);
+ g_output_stream_splice_async (G_OUTPUT_STREAM (request->data_stream),
+ istream,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ G_PRIORITY_DEFAULT,
+ request->global_cancellable,
+ splice_rdf_cb,
+ request);
+}
+
+static void
update_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
@@ -722,6 +807,70 @@ endpoint_dbus_iface_method_call (GDBusConnection *connection,
g_variant_iter_free (arguments);
g_free (query);
+ } else if (g_strcmp0 (method_name, "Serialize") == 0) {
+ TrackerSerializeFlags flags;
+ TrackerRdfFormat format;
+
+ if (tracker_endpoint_dbus_forbid_operation (endpoint_dbus,
+ invocation,
+ TRACKER_OPERATION_TYPE_SELECT)) {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_ACCESS_DENIED,
+ "Operation not allowed");
+ return;
+ }
+
+ g_variant_get (parameters, "(shiia{sv})", &query, &handle, &flags, &format, &arguments);
+
+ if (fd_list)
+ fd = g_unix_fd_list_get (fd_list, handle, &error);
+
+ if (fd < 0) {
+ g_dbus_method_invocation_return_error (invocation,
+ G_DBUS_ERROR,
+ G_DBUS_ERROR_INVALID_ARGS,
+ "Did not get a file descriptor");
+ } else {
+ QueryRequest *request;
+
+ query = tracker_endpoint_dbus_add_prologue (endpoint_dbus,
+ query);
+
+ request = query_request_new (endpoint_dbus, invocation, fd);
+
+ if (arguments) {
+ TrackerSparqlStatement *stmt;
+
+ stmt = create_statement (conn, query, arguments,
+ request->cancellable,
+ &error);
+ if (stmt) {
+ tracker_sparql_statement_serialize_async (stmt,
+ flags,
+ format,
+ request->cancellable,
+ stmt_serialize_cb,
+ request);
+ /* Statements are single use here... */
+ g_object_unref (stmt);
+ } else {
+ query_request_free (request);
+ g_dbus_method_invocation_return_gerror (invocation,
+ error);
+ }
+ } else {
+ tracker_sparql_connection_serialize_async (conn,
+ flags,
+ format,
+ query,
+ request->cancellable,
+ serialize_cb,
+ request);
+ }
+ }
+
+ g_free (query);
} else if (g_strcmp0 (method_name, "Update") == 0 ||
g_strcmp0 (method_name, "UpdateArray") == 0) {
if (tracker_endpoint_dbus_forbid_operation (endpoint_dbus,
diff --git a/src/libtracker-sparql/tracker-endpoint-http.c b/src/libtracker-sparql/tracker-endpoint-http.c
index 4ff89d6f9..03a7eb4ea 100644
--- a/src/libtracker-sparql/tracker-endpoint-http.c
+++ b/src/libtracker-sparql/tracker-endpoint-http.c
@@ -83,6 +83,8 @@ enum {
#define XML_TYPE "application/sparql-results+xml"
#define JSON_TYPE "application/sparql-results+json"
+#define TTL_TYPE "text/turtle"
+#define TRIG_TYPE "application/trig"
static GParamSpec *props[N_PROPS];
static guint signals[N_SIGNALS];
@@ -106,7 +108,7 @@ handle_request_in_thread (GTask *task,
GCancellable *cancellable)
{
Request *request = task_data;
- gchar *buffer[1000];
+ gchar buffer[1000];
SoupMessageBody *message_body;
GError *error = NULL;
gssize count;
@@ -202,6 +204,7 @@ query_async_cb (GObject *object,
request->task = g_task_new (endpoint_http, endpoint_http->cancellable,
request_finished_cb, request);
g_task_set_task_data (request->task, request, NULL);
+ g_object_unref (cursor);
g_task_run_in_thread (request->task, handle_request_in_thread);
}
@@ -234,6 +237,14 @@ pick_format (SoupMessage *message,
soup_message_headers_set_content_type (response_headers, XML_TYPE, NULL);
*format = TRACKER_SERIALIZER_FORMAT_XML;
return TRUE;
+ } else if (soup_message_headers_header_contains (request_headers, "Accept", TTL_TYPE)) {
+ soup_message_headers_set_content_type (response_headers, TTL_TYPE, NULL);
+ *format = TRACKER_SERIALIZER_FORMAT_TTL;
+ return TRUE;
+ } else if (soup_message_headers_header_contains (request_headers, "Accept", TRIG_TYPE)) {
+ soup_message_headers_set_content_type (response_headers, TRIG_TYPE, NULL);
+ *format = TRACKER_SERIALIZER_FORMAT_TRIG;
+ return TRUE;
} else {
return FALSE;
}
diff --git a/src/libtracker-sparql/tracker-private.h b/src/libtracker-sparql/tracker-private.h
index 448581acd..bad99bd47 100644
--- a/src/libtracker-sparql/tracker-private.h
+++ b/src/libtracker-sparql/tracker-private.h
@@ -110,6 +110,17 @@ struct _TrackerSparqlConnectionClass
const gchar *dbus_path,
gchar **name,
gchar **path);
+
+ void (* serialize_async) (TrackerSparqlConnection *connection,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ const gchar *query,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ GInputStream * (* serialize_finish) (TrackerSparqlConnection *connection,
+ GAsyncResult *res,
+ GError **error);
};
struct _TrackerSparqlCursorClass
@@ -222,6 +233,16 @@ struct _TrackerSparqlStatementClass
GAsyncResult *res,
GError **error);
void (* clear_bindings) (TrackerSparqlStatement *stmt);
+
+ void (* serialize_async) (TrackerSparqlStatement *stmt,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+ GInputStream * (* serialize_finish) (TrackerSparqlStatement *stmt,
+ GAsyncResult *res,
+ GError **error);
};
struct _TrackerNotifierClass {
diff --git a/src/libtracker-sparql/tracker-serializer-trig.c b/src/libtracker-sparql/tracker-serializer-trig.c
new file mode 100644
index 000000000..71952e8ff
--- /dev/null
+++ b/src/libtracker-sparql/tracker-serializer-trig.c
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2021, 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>
+ */
+
+/* Serialization of cursors to the TRIG format defined at:
+ * http://www.w3.org/TR/trig/
+ */
+
+#include "config.h"
+
+#include "tracker-serializer-trig.h"
+
+typedef struct _TrackerQuad TrackerQuad;
+
+struct _TrackerQuad
+{
+ gchar *subject;
+ gchar *predicate;
+ gchar *object;
+ gchar *graph;
+ TrackerSparqlValueType subject_type;
+ TrackerSparqlValueType object_type;
+};
+
+struct _TrackerSerializerTrig
+{
+ TrackerSerializer parent_instance;
+ TrackerQuad *last_quad;
+ GString *data;
+ guint stream_closed : 1;
+ guint cursor_started : 1;
+ guint cursor_finished : 1;
+ guint head_printed : 1;
+ guint has_quads : 1;
+};
+
+G_DEFINE_TYPE (TrackerSerializerTrig, tracker_serializer_trig,
+ TRACKER_TYPE_SERIALIZER)
+
+typedef enum
+{
+ TRACKER_QUAD_BREAK_NONE,
+ TRACKER_QUAD_BREAK_GRAPH,
+ TRACKER_QUAD_BREAK_SUBJECT,
+ TRACKER_QUAD_BREAK_PREDICATE,
+ TRACKER_QUAD_BREAK_OBJECT,
+} TrackerQuadBreak;
+
+static TrackerQuad *
+tracker_quad_new_from_cursor (TrackerSparqlCursor *cursor)
+{
+ TrackerQuad *quad;
+
+ if (tracker_sparql_cursor_get_n_columns (cursor) < 3)
+ return NULL;
+
+ quad = g_new0 (TrackerQuad, 1);
+ quad->subject_type = tracker_sparql_cursor_get_value_type (cursor, 0);
+ quad->object_type = tracker_sparql_cursor_get_value_type (cursor, 2);
+ quad->subject = g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+ quad->predicate = g_strdup (tracker_sparql_cursor_get_string (cursor, 1, NULL));
+ quad->object = g_strdup (tracker_sparql_cursor_get_string (cursor, 2, NULL));
+
+ if (tracker_sparql_cursor_get_n_columns (cursor) >= 4)
+ quad->graph = g_strdup (tracker_sparql_cursor_get_string (cursor, 3, NULL));
+
+ if (quad->subject_type == TRACKER_SPARQL_VALUE_TYPE_STRING) {
+ if (g_str_has_prefix (quad->subject, "urn:bnode:")) {
+ quad->subject_type = TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
+ } else {
+ quad->subject_type = TRACKER_SPARQL_VALUE_TYPE_URI;
+ }
+ }
+
+ if (quad->object_type == TRACKER_SPARQL_VALUE_TYPE_STRING) {
+ if (g_str_has_prefix (quad->object, "urn:bnode:")) {
+ quad->object_type = TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
+ }
+ }
+
+ return quad;
+}
+
+static void
+tracker_quad_free (TrackerQuad *quad)
+{
+ g_free (quad->subject);
+ g_free (quad->predicate);
+ g_free (quad->object);
+ g_free (quad->graph);
+ g_free (quad);
+}
+
+static TrackerQuadBreak
+tracker_quad_get_break (TrackerQuad *last,
+ TrackerQuad *cur)
+{
+ if (!last)
+ return TRACKER_QUAD_BREAK_NONE;
+
+ if (g_strcmp0 (last->graph, cur->graph) != 0)
+ return TRACKER_QUAD_BREAK_GRAPH;
+
+ if (g_strcmp0 (last->subject, cur->subject) != 0)
+ return TRACKER_QUAD_BREAK_SUBJECT;
+
+ if (g_strcmp0 (last->predicate, cur->predicate) != 0)
+ return TRACKER_QUAD_BREAK_PREDICATE;
+
+ return TRACKER_QUAD_BREAK_OBJECT;
+}
+
+static void
+tracker_serializer_trig_finalize (GObject *object)
+{
+ g_input_stream_close (G_INPUT_STREAM (object), NULL, NULL);
+
+ G_OBJECT_CLASS (tracker_serializer_trig_parent_class)->finalize (object);
+}
+
+static void
+print_value (GString *str,
+ const gchar *value,
+ TrackerSparqlValueType value_type,
+ TrackerNamespaceManager *namespaces)
+{
+ switch (value_type) {
+ case TRACKER_SPARQL_VALUE_TYPE_URI: {
+ gchar *shortname;
+
+ shortname = tracker_namespace_manager_compress_uri (namespaces, value);
+
+ if (shortname) {
+ g_string_append_printf (str, "%s", shortname);
+ } else {
+ g_string_append_printf (str, "<%s>", value);
+ }
+
+ g_free (shortname);
+ break;
+ }
+ case TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE: {
+ gchar *bnode_label;
+
+ bnode_label = g_strdelimit (g_strdup (value), ":", '_');
+ g_string_append_printf (str, "_:%s", bnode_label);
+ g_free (bnode_label);
+ break;
+ }
+ case TRACKER_SPARQL_VALUE_TYPE_STRING:
+ case TRACKER_SPARQL_VALUE_TYPE_DATETIME: {
+ gchar *escaped;
+
+ escaped = tracker_sparql_escape_string (value);
+ g_string_append_printf (str, "\"%s\"",
+ escaped);
+ g_free (escaped);
+ break;
+ }
+ case TRACKER_SPARQL_VALUE_TYPE_INTEGER:
+ case TRACKER_SPARQL_VALUE_TYPE_DOUBLE:
+ g_string_append (str, value);
+ break;
+ case TRACKER_SPARQL_VALUE_TYPE_BOOLEAN:
+ g_string_append (str,
+ (value[0] == 't' || value[0] == 'T') ?
+ "true" : "false");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+serialize_up_to_size (TrackerSerializerTrig *serializer_trig,
+ gsize size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerSparqlCursor *cursor;
+ TrackerNamespaceManager *namespaces;
+ TrackerSparqlConnection *conn;
+ GError *inner_error = NULL;
+ TrackerQuad *cur;
+
+ if (!serializer_trig->data)
+ serializer_trig->data = g_string_new (NULL);
+
+ cursor = tracker_serializer_get_cursor (TRACKER_SERIALIZER (serializer_trig));
+ conn = tracker_sparql_cursor_get_connection (cursor);
+ namespaces = tracker_sparql_connection_get_namespace_manager (conn);
+
+ if (!serializer_trig->head_printed) {
+ gchar *str;
+
+ str = tracker_namespace_manager_print_turtle (namespaces);
+
+ g_string_append_printf (serializer_trig->data, "%s\n", str);
+ g_free (str);
+ serializer_trig->head_printed = TRUE;
+ }
+
+ while (!serializer_trig->cursor_finished &&
+ serializer_trig->data->len < size) {
+ TrackerQuadBreak br;
+
+ if (!tracker_sparql_cursor_next (cursor, cancellable, &inner_error)) {
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ } else {
+ serializer_trig->cursor_finished = TRUE;
+ break;
+ }
+ } else {
+ serializer_trig->cursor_started = TRUE;
+ }
+
+ cur = tracker_quad_new_from_cursor (cursor);
+
+ if (!cur) {
+ g_set_error (error,
+ TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_INTERNAL,
+ "Cursor has no subject/predicate/object/graph columns");
+ return FALSE;
+ }
+
+ br = tracker_quad_get_break (serializer_trig->last_quad, cur);
+
+ if (br <= TRACKER_QUAD_BREAK_GRAPH) {
+ if (br == TRACKER_QUAD_BREAK_GRAPH)
+ g_string_append (serializer_trig->data, " .\n}\n\n");
+
+ if (cur->graph) {
+ g_string_append (serializer_trig->data, "GRAPH ");
+ print_value (serializer_trig->data, cur->graph,
+ TRACKER_SPARQL_VALUE_TYPE_URI, namespaces);
+ g_string_append_c (serializer_trig->data, ' ');
+ }
+
+ g_string_append (serializer_trig->data, "{\n ");
+ }
+
+ if (br <= TRACKER_QUAD_BREAK_SUBJECT) {
+ if (br == TRACKER_QUAD_BREAK_SUBJECT)
+ g_string_append (serializer_trig->data, " .\n\n ");
+ print_value (serializer_trig->data, cur->subject, cur->subject_type, namespaces);
+ }
+
+ if (br <= TRACKER_QUAD_BREAK_PREDICATE) {
+ if (br == TRACKER_QUAD_BREAK_PREDICATE)
+ g_string_append (serializer_trig->data, " ;\n ");
+ else
+ g_string_append_c (serializer_trig->data, ' ');
+
+ print_value (serializer_trig->data, cur->predicate,
+ TRACKER_SPARQL_VALUE_TYPE_URI, namespaces);
+ }
+
+ if (br <= TRACKER_QUAD_BREAK_OBJECT) {
+ if (br == TRACKER_QUAD_BREAK_OBJECT)
+ g_string_append (serializer_trig->data, ",");
+
+ g_string_append_c (serializer_trig->data, ' ');
+ print_value (serializer_trig->data, cur->object, cur->object_type, namespaces);
+ }
+
+ serializer_trig->has_quads = TRUE;
+ g_clear_pointer (&serializer_trig->last_quad, tracker_quad_free);
+ serializer_trig->last_quad = cur;
+ }
+
+ /* Close the last quad */
+ if (serializer_trig->cursor_finished &&
+ serializer_trig->has_quads)
+ g_string_append (serializer_trig->data, " .\n}\n");
+
+ return TRUE;
+}
+
+static gssize
+tracker_serializer_trig_read (GInputStream *istream,
+ gpointer buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerSerializerTrig *serializer_trig = TRACKER_SERIALIZER_TRIG (istream);
+ gsize bytes_copied;
+
+ if (serializer_trig->stream_closed ||
+ (serializer_trig->cursor_finished &&
+ serializer_trig->data->len == 0))
+ return 0;
+
+ if (!serialize_up_to_size (serializer_trig,
+ count,
+ cancellable,
+ error))
+ return -1;
+
+ bytes_copied = MIN (count, serializer_trig->data->len);
+
+ memcpy (buffer,
+ serializer_trig->data->str,
+ bytes_copied);
+ g_string_erase (serializer_trig->data, 0, bytes_copied);
+
+ return bytes_copied;
+}
+
+static gboolean
+tracker_serializer_trig_close (GInputStream *istream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerSerializerTrig *serializer_trig = TRACKER_SERIALIZER_TRIG (istream);
+
+ g_clear_pointer (&serializer_trig->last_quad, tracker_quad_free);
+
+ if (serializer_trig->data) {
+ g_string_free (serializer_trig->data, TRUE);
+ serializer_trig->data = NULL;
+ }
+
+ return TRUE;
+}
+
+static void
+tracker_serializer_trig_class_init (TrackerSerializerTrigClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass);
+
+ object_class->finalize = tracker_serializer_trig_finalize;
+
+ istream_class->read_fn = tracker_serializer_trig_read;
+ istream_class->close_fn = tracker_serializer_trig_close;
+}
+
+static void
+tracker_serializer_trig_init (TrackerSerializerTrig *serializer)
+{
+}
diff --git a/src/libtracker-sparql/tracker-serializer-trig.h b/src/libtracker-sparql/tracker-serializer-trig.h
new file mode 100644
index 000000000..a6ac6ad09
--- /dev/null
+++ b/src/libtracker-sparql/tracker-serializer-trig.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021, 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>
+ */
+
+#ifndef TRACKER_SERIALIZER_TRIG_H
+#define TRACKER_SERIALIZER_TRIG_H
+
+#include <libtracker-sparql/tracker-sparql.h>
+#include <libtracker-sparql/tracker-private.h>
+#include <libtracker-sparql/tracker-serializer.h>
+
+#define TRACKER_TYPE_SERIALIZER_TRIG (tracker_serializer_trig_get_type())
+
+G_DECLARE_FINAL_TYPE (TrackerSerializerTrig,
+ tracker_serializer_trig,
+ TRACKER, SERIALIZER_TRIG,
+ TrackerSerializer)
+
+#endif /* TRACKER_SERIALIZER_TRIG_H */
diff --git a/src/libtracker-sparql/tracker-serializer-turtle.c b/src/libtracker-sparql/tracker-serializer-turtle.c
new file mode 100644
index 000000000..b94fc85e7
--- /dev/null
+++ b/src/libtracker-sparql/tracker-serializer-turtle.c
@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2021, 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>
+ */
+
+/* Serialization of cursors to the turtle format defined at:
+ * https://www.w3.org/TR/turtle/
+ */
+
+#include "config.h"
+
+#include "tracker-serializer-turtle.h"
+
+typedef struct _TrackerTriple TrackerTriple;
+
+struct _TrackerTriple
+{
+ gchar *subject;
+ gchar *predicate;
+ gchar *object;
+ TrackerSparqlValueType subject_type;
+ TrackerSparqlValueType object_type;
+};
+
+struct _TrackerSerializerTurtle
+{
+ TrackerSerializer parent_instance;
+ TrackerTriple *last_triple;
+ GString *data;
+ guint stream_closed : 1;
+ guint cursor_started : 1;
+ guint cursor_finished : 1;
+ guint head_printed : 1;
+ guint has_triples : 1;
+};
+
+G_DEFINE_TYPE (TrackerSerializerTurtle, tracker_serializer_turtle,
+ TRACKER_TYPE_SERIALIZER)
+
+typedef enum
+{
+ TRACKER_TRIPLE_BREAK_NONE,
+ TRACKER_TRIPLE_BREAK_SUBJECT,
+ TRACKER_TRIPLE_BREAK_PREDICATE,
+ TRACKER_TRIPLE_BREAK_OBJECT,
+} TrackerTripleBreak;
+
+static TrackerTriple *
+tracker_triple_new_from_cursor (TrackerSparqlCursor *cursor)
+{
+ TrackerTriple *triple;
+
+ if (tracker_sparql_cursor_get_n_columns (cursor) < 3)
+ return NULL;
+
+ triple = g_new0 (TrackerTriple, 1);
+ triple->subject_type = tracker_sparql_cursor_get_value_type (cursor, 0);
+ triple->object_type = tracker_sparql_cursor_get_value_type (cursor, 2);
+ triple->subject = g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL));
+ triple->predicate = g_strdup (tracker_sparql_cursor_get_string (cursor, 1, NULL));
+ triple->object = g_strdup (tracker_sparql_cursor_get_string (cursor, 2, NULL));
+
+ if (triple->subject_type == TRACKER_SPARQL_VALUE_TYPE_STRING) {
+ if (g_str_has_prefix (triple->subject, "urn:bnode:")) {
+ triple->subject_type = TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
+ } else {
+ triple->subject_type = TRACKER_SPARQL_VALUE_TYPE_URI;
+ }
+ }
+
+ if (triple->object_type == TRACKER_SPARQL_VALUE_TYPE_STRING) {
+ if (g_str_has_prefix (triple->object, "urn:bnode:")) {
+ triple->object_type = TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE;
+ }
+ }
+
+ return triple;
+}
+
+static void
+tracker_triple_free (TrackerTriple *triple)
+{
+ g_free (triple->subject);
+ g_free (triple->predicate);
+ g_free (triple->object);
+ g_free (triple);
+}
+
+static TrackerTripleBreak
+tracker_triple_get_break (TrackerTriple *last,
+ TrackerTriple *cur)
+{
+ if (!last)
+ return TRACKER_TRIPLE_BREAK_NONE;
+
+ if (g_strcmp0 (last->subject, cur->subject) != 0)
+ return TRACKER_TRIPLE_BREAK_SUBJECT;
+
+ if (g_strcmp0 (last->predicate, cur->predicate) != 0)
+ return TRACKER_TRIPLE_BREAK_PREDICATE;
+
+ return TRACKER_TRIPLE_BREAK_OBJECT;
+}
+
+static void
+tracker_serializer_turtle_finalize (GObject *object)
+{
+ g_input_stream_close (G_INPUT_STREAM (object), NULL, NULL);
+
+ G_OBJECT_CLASS (tracker_serializer_turtle_parent_class)->finalize (object);
+}
+
+static void
+print_value (GString *str,
+ const gchar *value,
+ TrackerSparqlValueType value_type,
+ TrackerNamespaceManager *namespaces)
+{
+ switch (value_type) {
+ case TRACKER_SPARQL_VALUE_TYPE_URI: {
+ gchar *shortname;
+
+ shortname = tracker_namespace_manager_compress_uri (namespaces, value);
+
+ if (shortname) {
+ g_string_append_printf (str, "%s", shortname);
+ } else {
+ g_string_append_printf (str, "<%s>", value);
+ }
+
+ g_free (shortname);
+ break;
+ }
+ case TRACKER_SPARQL_VALUE_TYPE_BLANK_NODE: {
+ gchar *bnode_label;
+
+ bnode_label = g_strdelimit (g_strdup (value), ":", '_');
+ g_string_append_printf (str, "_:%s", bnode_label);
+ g_free (bnode_label);
+ break;
+ }
+ case TRACKER_SPARQL_VALUE_TYPE_STRING:
+ case TRACKER_SPARQL_VALUE_TYPE_DATETIME: {
+ gchar *escaped;
+
+ escaped = tracker_sparql_escape_string (value);
+ g_string_append_printf (str, "\"%s\"",
+ escaped);
+ g_free (escaped);
+ break;
+ }
+ case TRACKER_SPARQL_VALUE_TYPE_INTEGER:
+ case TRACKER_SPARQL_VALUE_TYPE_DOUBLE:
+ g_string_append (str, value);
+ break;
+ case TRACKER_SPARQL_VALUE_TYPE_BOOLEAN:
+ g_string_append (str,
+ (value[0] == 't' || value[0] == 'T') ?
+ "true" : "false");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static gboolean
+serialize_up_to_size (TrackerSerializerTurtle *serializer_ttl,
+ gsize size,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerSparqlCursor *cursor;
+ TrackerNamespaceManager *namespaces;
+ TrackerSparqlConnection *conn;
+ GError *inner_error = NULL;
+ TrackerTriple *cur;
+
+ if (!serializer_ttl->data)
+ serializer_ttl->data = g_string_new (NULL);
+
+ cursor = tracker_serializer_get_cursor (TRACKER_SERIALIZER (serializer_ttl));
+ conn = tracker_sparql_cursor_get_connection (cursor);
+ namespaces = tracker_sparql_connection_get_namespace_manager (conn);
+
+ if (!serializer_ttl->head_printed) {
+ gchar *str;
+
+ str = tracker_namespace_manager_print_turtle (namespaces);
+
+ g_string_append_printf (serializer_ttl->data, "%s\n", str);
+ g_free (str);
+ serializer_ttl->head_printed = TRUE;
+ }
+
+ while (!serializer_ttl->cursor_finished &&
+ serializer_ttl->data->len < size) {
+ TrackerTripleBreak br;
+
+ if (!tracker_sparql_cursor_next (cursor, cancellable, &inner_error)) {
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ } else {
+ serializer_ttl->cursor_finished = TRUE;
+ break;
+ }
+ } else {
+ serializer_ttl->cursor_started = TRUE;
+ }
+
+ cur = tracker_triple_new_from_cursor (cursor);
+
+ if (!cur) {
+ g_set_error (error,
+ TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_INTERNAL,
+ "Cursor has no subject/predicate/object columns");
+ return FALSE;
+ }
+
+ br = tracker_triple_get_break (serializer_ttl->last_triple, cur);
+
+ if (br <= TRACKER_TRIPLE_BREAK_SUBJECT) {
+ if (br == TRACKER_TRIPLE_BREAK_SUBJECT)
+ g_string_append (serializer_ttl->data, " .\n\n");
+ print_value (serializer_ttl->data, cur->subject, cur->subject_type, namespaces);
+ }
+
+ if (br <= TRACKER_TRIPLE_BREAK_PREDICATE) {
+ if (br == TRACKER_TRIPLE_BREAK_PREDICATE)
+ g_string_append (serializer_ttl->data, " ;\n ");
+ else
+ g_string_append_c (serializer_ttl->data, ' ');
+
+ print_value (serializer_ttl->data, cur->predicate,
+ TRACKER_SPARQL_VALUE_TYPE_URI, namespaces);
+ }
+
+ if (br <= TRACKER_TRIPLE_BREAK_OBJECT) {
+ if (br == TRACKER_TRIPLE_BREAK_OBJECT)
+ g_string_append (serializer_ttl->data, ",");
+
+ g_string_append_c (serializer_ttl->data, ' ');
+ print_value (serializer_ttl->data, cur->object, cur->object_type, namespaces);
+ }
+
+ serializer_ttl->has_triples = TRUE;
+ g_clear_pointer (&serializer_ttl->last_triple, tracker_triple_free);
+ serializer_ttl->last_triple = cur;
+ }
+
+ /* Print dot for the last triple */
+ if (serializer_ttl->cursor_finished &&
+ serializer_ttl->has_triples)
+ g_string_append (serializer_ttl->data, " .\n");
+
+ return TRUE;
+}
+
+static gssize
+tracker_serializer_turtle_read (GInputStream *istream,
+ gpointer buffer,
+ gsize count,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerSerializerTurtle *serializer_ttl = TRACKER_SERIALIZER_TURTLE (istream);
+ gsize bytes_copied;
+
+ if (serializer_ttl->stream_closed ||
+ (serializer_ttl->cursor_finished &&
+ serializer_ttl->data->len == 0))
+ return 0;
+
+ if (!serialize_up_to_size (serializer_ttl,
+ count,
+ cancellable,
+ error))
+ return -1;
+
+ bytes_copied = MIN (count, serializer_ttl->data->len);
+
+ memcpy (buffer,
+ serializer_ttl->data->str,
+ bytes_copied);
+ g_string_erase (serializer_ttl->data, 0, bytes_copied);
+
+ return bytes_copied;
+}
+
+static gboolean
+tracker_serializer_turtle_close (GInputStream *istream,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerSerializerTurtle *serializer_ttl = TRACKER_SERIALIZER_TURTLE (istream);
+
+ g_clear_pointer (&serializer_ttl->last_triple, tracker_triple_free);
+
+ if (serializer_ttl->data) {
+ g_string_free (serializer_ttl->data, TRUE);
+ serializer_ttl->data = NULL;
+ }
+
+ return TRUE;
+}
+
+static void
+tracker_serializer_turtle_class_init (TrackerSerializerTurtleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GInputStreamClass *istream_class = G_INPUT_STREAM_CLASS (klass);
+
+ object_class->finalize = tracker_serializer_turtle_finalize;
+
+ istream_class->read_fn = tracker_serializer_turtle_read;
+ istream_class->close_fn = tracker_serializer_turtle_close;
+}
+
+static void
+tracker_serializer_turtle_init (TrackerSerializerTurtle *serializer)
+{
+}
diff --git a/src/libtracker-sparql/tracker-serializer-turtle.h b/src/libtracker-sparql/tracker-serializer-turtle.h
new file mode 100644
index 000000000..3f22d3c7c
--- /dev/null
+++ b/src/libtracker-sparql/tracker-serializer-turtle.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021, 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>
+ */
+
+#ifndef TRACKER_SERIALIZER_TURTLE_H
+#define TRACKER_SERIALIZER_TURTLE_H
+
+#include <libtracker-sparql/tracker-sparql.h>
+#include <libtracker-sparql/tracker-private.h>
+#include <libtracker-sparql/tracker-serializer.h>
+
+#define TRACKER_TYPE_SERIALIZER_TURTLE (tracker_serializer_turtle_get_type())
+
+G_DECLARE_FINAL_TYPE (TrackerSerializerTurtle,
+ tracker_serializer_turtle,
+ TRACKER, SERIALIZER_TURTLE,
+ TrackerSerializer)
+
+#endif /* TRACKER_SERIALIZER_TURTLE_H */
diff --git a/src/libtracker-sparql/tracker-serializer.c b/src/libtracker-sparql/tracker-serializer.c
index 4109bfafe..4c343fb58 100644
--- a/src/libtracker-sparql/tracker-serializer.c
+++ b/src/libtracker-sparql/tracker-serializer.c
@@ -23,6 +23,8 @@
#include "tracker-serializer.h"
#include "tracker-serializer-json.h"
+#include "tracker-serializer-trig.h"
+#include "tracker-serializer-turtle.h"
#include "tracker-serializer-xml.h"
#include "tracker-private.h"
@@ -68,7 +70,7 @@ tracker_serializer_set_property (GObject *object,
switch (prop_id) {
case PROP_CURSOR:
- priv->cursor = g_value_get_object (value);
+ priv->cursor = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -133,10 +135,24 @@ tracker_serializer_new (TrackerSparqlCursor *cursor,
switch (format) {
case TRACKER_SERIALIZER_FORMAT_JSON:
- type = TRACKER_TYPE_SERIALIZER_JSON;
+ type = g_type_from_name ("TrackerSerializerJson");
+ if (type == 0)
+ type = TRACKER_TYPE_SERIALIZER_JSON;
break;
case TRACKER_SERIALIZER_FORMAT_XML:
- type = TRACKER_TYPE_SERIALIZER_XML;
+ type = g_type_from_name ("TrackerSerializerXml");
+ if (type == 0)
+ type = TRACKER_TYPE_SERIALIZER_XML;
+ break;
+ case TRACKER_SERIALIZER_FORMAT_TTL:
+ type = g_type_from_name ("TrackerSerializerTurtle");
+ if (type == 0)
+ type = TRACKER_TYPE_SERIALIZER_TURTLE;
+ break;
+ case TRACKER_SERIALIZER_FORMAT_TRIG:
+ type = g_type_from_name ("TrackerSerializerTrig");
+ if (type == 0)
+ type = TRACKER_TYPE_SERIALIZER_TRIG;
break;
default:
g_warn_if_reached ();
diff --git a/src/libtracker-sparql/tracker-serializer.h b/src/libtracker-sparql/tracker-serializer.h
index 03dcbbba4..9f6a87df8 100644
--- a/src/libtracker-sparql/tracker-serializer.h
+++ b/src/libtracker-sparql/tracker-serializer.h
@@ -35,6 +35,8 @@ typedef enum
{
TRACKER_SERIALIZER_FORMAT_JSON, /* application/sparql-results+json */
TRACKER_SERIALIZER_FORMAT_XML, /* application/sparql-results+xml */
+ TRACKER_SERIALIZER_FORMAT_TTL, /* text/turtle */
+ TRACKER_SERIALIZER_FORMAT_TRIG, /* application/trig */
} TrackerSerializerFormat;
GInputStream * tracker_serializer_new (TrackerSparqlCursor *cursor,
diff --git a/src/libtracker-sparql/tracker-sparql.vapi b/src/libtracker-sparql/tracker-sparql.vapi
index dec936737..74a0f66df 100644
--- a/src/libtracker-sparql/tracker-sparql.vapi
+++ b/src/libtracker-sparql/tracker-sparql.vapi
@@ -53,6 +53,17 @@ namespace Tracker {
BOOLEAN
}
+ [CCode (cheader_filename = "libtracker-sparql/tracker-connection.h")]
+ public enum RdfFormat {
+ TURTLE,
+ TRIG,
+ }
+
+ [CCode (cheader_filename = "libtracker-sparql/tracker-connection.h")]
+ public enum SerializeFlags {
+ NONE = 0,
+ }
+
namespace Sparql {
[CCode (cheader_filename = "libtracker-sparql/tracker-sparql.h")]
public static string escape_string (string literal);
@@ -101,6 +112,8 @@ namespace Tracker {
public virtual Notifier? create_notifier ();
public virtual void close ();
public async virtual bool close_async () throws GLib.IOError;
+
+ public async virtual GLib.InputStream serialize_async (SerializeFlags flags, RdfFormat format, string sparql, GLib.Cancellable? cancellable = null) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError;
}
[CCode (cheader_filename = "libtracker-sparql/tracker-sparql.h")]
@@ -117,6 +130,7 @@ namespace Tracker {
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;
+ public async abstract GLib.InputStream serialize_async (SerializeFlags flags, RdfFormat format, GLib.Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, GLib.DBusError;
}
[CCode (cheader_filename = "libtracker-sparql/tracker-sparql.h")]
diff --git a/src/libtracker-sparql/tracker-statement.c b/src/libtracker-sparql/tracker-statement.c
index 261265197..72c8604f6 100644
--- a/src/libtracker-sparql/tracker-statement.c
+++ b/src/libtracker-sparql/tracker-statement.c
@@ -398,3 +398,76 @@ tracker_sparql_statement_clear_bindings (TrackerSparqlStatement *stmt)
TRACKER_SPARQL_STATEMENT_GET_CLASS (stmt)->clear_bindings (stmt);
}
+
+/**
+ * tracker_sparql_statement_serialize_async:
+ * @stmt: a #TrackerSparqlStatement
+ * @flags: serialization flags
+ * @format: RDF format of the serialized data
+ * @cancellable: (nullable): a #GCancellable used to cancel the operation
+ * @callback: user-defined #GAsyncReadyCallback to be called when
+ * asynchronous operation is finished.
+ * @user_data: user-defined data to be passed to @callback
+ *
+ * Serializes data into the specified RDF format. The query @stmt was
+ * created from must be either a `DESCRIBE` or `CONSTRUCT` query, an
+ * error will be raised otherwise.
+ *
+ * This is an asynchronous operation, @callback will be invoked when the
+ * data is available for reading.
+ *
+ * The SPARQL endpoint may not support the specified format, in that case
+ * an error will be raised.
+ *
+ * The @flags argument is reserved for future expansions, currently
+ * %TRACKER_SERIALIZE_FLAGS_NONE must be passed.
+ *
+ * Since: 3.3
+ **/
+void
+tracker_sparql_statement_serialize_async (TrackerSparqlStatement *stmt,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_return_if_fail (TRACKER_IS_SPARQL_STATEMENT (stmt));
+ g_return_if_fail (flags == TRACKER_SERIALIZE_FLAGS_NONE);
+ g_return_if_fail (!cancellable || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (callback != NULL);
+
+ TRACKER_SPARQL_STATEMENT_GET_CLASS (stmt)->serialize_async (stmt,
+ flags,
+ format,
+ cancellable,
+ callback,
+ user_data);
+}
+
+/**
+ * tracker_sparql_statement_serialize_finish:
+ * @stmt: a #TrackerSparqlStatement
+ * @result: the #GAsyncResult
+ * @error: location for returned errors, or %NULL
+ *
+ * Finishes a tracker_sparql_statement_serialize_async() operation.
+ * In case of error, %NULL will be returned and @error will be set.
+ *
+ * Returns: (transfer full): a #GInputStream to read RDF content.
+ *
+ * Since: 3.3
+ **/
+GInputStream *
+tracker_sparql_statement_serialize_finish (TrackerSparqlStatement *stmt,
+ GAsyncResult *result,
+ GError **error)
+{
+ g_return_val_if_fail (TRACKER_IS_SPARQL_STATEMENT (stmt), NULL);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+ g_return_val_if_fail (!error || !*error, NULL);
+
+ return TRACKER_SPARQL_STATEMENT_GET_CLASS (stmt)->serialize_finish (stmt,
+ result,
+ error);
+}
diff --git a/src/libtracker-sparql/tracker-statement.h b/src/libtracker-sparql/tracker-statement.h
index ead440c78..6d24db5ba 100644
--- a/src/libtracker-sparql/tracker-statement.h
+++ b/src/libtracker-sparql/tracker-statement.h
@@ -91,6 +91,19 @@ TrackerSparqlCursor * tracker_sparql_statement_execute_finish (TrackerSparqlStat
GAsyncResult *res,
GError **error);
+TRACKER_AVAILABLE_IN_3_3
+void tracker_sparql_statement_serialize_async (TrackerSparqlStatement *stmt,
+ TrackerSerializeFlags flags,
+ TrackerRdfFormat format,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+TRACKER_AVAILABLE_IN_3_3
+GInputStream * tracker_sparql_statement_serialize_finish (TrackerSparqlStatement *stmt,
+ GAsyncResult *result,
+ GError **error);
+
TRACKER_AVAILABLE_IN_ALL
void tracker_sparql_statement_clear_bindings (TrackerSparqlStatement *stmt);
diff --git a/src/tracker/tracker-export.c b/src/tracker/tracker-export.c
index 38c05f941..9d9d42a83 100644
--- a/src/tracker/tracker-export.c
+++ b/src/tracker/tracker-export.c
@@ -26,6 +26,7 @@
#include <glib.h>
#include <glib/gi18n.h>
+#include <gio/gunixoutputstream.h>
#include "tracker-sparql.h"
#include "tracker-color.h"
@@ -136,15 +137,6 @@ format_urn (GHashTable *prefixes,
return urn_out;
}
-/* print a URI prefix in Turtle format */
-static void
-print_prefix (gpointer key,
- gpointer value,
- gpointer user_data)
-{
- g_print ("@prefix %s: <%s#> .\n", (gchar *) value, (gchar *) key);
-}
-
/* Print triples in Turtle format */
static void
print_turtle (TrackerSparqlCursor *cursor,
@@ -291,14 +283,38 @@ print_keyfile (TrackerSparqlCursor *cursor)
g_print ("%s\n", data);
}
+static void
+serialize_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GInputStream *istream;
+ GOutputStream *ostream;
+ GError *error = NULL;
+
+ istream = tracker_sparql_connection_serialize_finish (TRACKER_SPARQL_CONNECTION (object),
+ res, &error);
+ if (istream) {
+ ostream = g_unix_output_stream_new (STDOUT_FILENO, FALSE);
+ g_output_stream_splice (ostream, istream, G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);
+ g_output_stream_close (ostream, NULL, NULL);
+ g_object_unref (ostream);
+ }
+
+ if (error)
+ g_printerr ("%s\n", error ? error->message : _("No error given"));
+
+ g_object_unref (istream);
+ g_main_loop_quit (user_data);
+}
+
static int
export_run_default (void)
{
g_autoptr(TrackerSparqlConnection) connection = NULL;
- g_autoptr(TrackerSparqlCursor) cursor = NULL;
g_autoptr(GError) error = NULL;
- g_autoptr(GHashTable) prefixes = NULL;
g_autoptr(GString) query = NULL;
+ g_autoptr(GMainLoop) loop = NULL;
guint i;
connection = create_connection (&error);
@@ -310,56 +326,33 @@ export_run_default (void)
return EXIT_FAILURE;
}
- prefixes = tracker_sparql_get_prefixes (connection);
-
- query = g_string_new (NULL);
- g_string_append (query,
- "SELECT ?g ?u ?p ?v "
- " (EXISTS { ?p rdfs:range [ rdfs:subClassOf rdfs:Resource ] }) AS ?is_resource "
- "{ "
- " GRAPH ?g { "
- " ?u ?p ?v ");
+ query = g_string_new ("DESCRIBE ");
if (iris) {
- g_string_append (query, "FILTER (?u IN (");
-
- for (i = 0; iris[i]; i++) {
- if (i != 0)
- g_string_append_c (query, ',');
- g_string_append_printf (query, "<%s>", iris[i]);
- }
-
- g_string_append (query, "))");
+ for (i = 0; iris[i] != NULL; i++)
+ g_string_append_printf (query, "<%s> ", iris[i]);
} else {
g_string_append (query,
- "FILTER NOT EXISTS { ?u a rdf:Property } "
- "FILTER NOT EXISTS { ?u a rdfs:Class } "
- "FILTER NOT EXISTS { ?u a nrl:Namespace } ");
- }
-
- g_string_append (query,
- " } "
- "} ORDER BY ?g ?u");
-
- cursor = tracker_sparql_connection_query (connection, query->str, NULL, &error);
-
- if (error) {
- g_printerr ("%s, %s\n",
- _("Could not run query"),
- error->message);
- return EXIT_FAILURE;
+ "?u {"
+ " ?u a rdfs:Resource . "
+ " FILTER NOT EXISTS { ?u a rdf:Property } "
+ " FILTER NOT EXISTS { ?u a rdfs:Class } "
+ " FILTER NOT EXISTS { ?u a nrl:Namespace } "
+ "}");
}
tracker_term_pipe_to_pager ();
- g_hash_table_foreach (prefixes, (GHFunc) print_prefix, NULL);
- g_print ("\n");
+ loop = g_main_loop_new (NULL, FALSE);
- if (show_graphs) {
- print_trig (cursor, prefixes, FALSE);
- } else {
- print_turtle (cursor, prefixes, FALSE);
- }
+ tracker_sparql_connection_serialize_async (connection,
+ TRACKER_SERIALIZE_FLAGS_NONE,
+ show_graphs ?
+ TRACKER_RDF_FORMAT_TRIG :
+ TRACKER_RDF_FORMAT_TURTLE,
+ query->str,
+ NULL, serialize_cb, loop);
+ g_main_loop_run (loop);
tracker_term_pager_close ();
diff --git a/tests/libtracker-data/describe/describe-limit.out b/tests/libtracker-data/describe/describe-limit.out
index e983c2b48..b536c99e2 100644
--- a/tests/libtracker-data/describe/describe-limit.out
+++ b/tests/libtracker-data/describe/describe-limit.out
@@ -1,11 +1,11 @@
-"a" "http://example/number" "42"
-"a" "http://example/date" "2000-01-01T00:00:01Z"
-"a" "http://example/name" "nameA"
-"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
-"b" "http://example/relation" "z"
-"b" "http://example/number" "73"
-"b" "http://example/date" "2001-01-01T00:00:01Z"
-"b" "http://example/name" "nameB"
-"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
+"b" "http://example/relation" "z"
+"a" "http://example/number" "42"
+"b" "http://example/number" "73"
+"a" "http://example/date" "2000-01-01T00:00:01Z"
+"b" "http://example/date" "2001-01-01T00:00:01Z"
+"a" "http://example/name" "nameA"
+"b" "http://example/name" "nameB"
+"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
+"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
diff --git a/tests/libtracker-data/describe/describe-multiple.out b/tests/libtracker-data/describe/describe-multiple.out
index 034058637..8edc0fd45 100644
--- a/tests/libtracker-data/describe/describe-multiple.out
+++ b/tests/libtracker-data/describe/describe-multiple.out
@@ -1,22 +1,22 @@
-"b" "http://example/relation" "z"
-"b" "http://example/number" "73"
-"b" "http://example/date" "2001-01-01T00:00:01Z"
-"b" "http://example/name" "nameB"
-"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
-"z" "http://example/title" "titleZ"
-"z" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"z" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/B"
-"c" "http://example/relation" "x"
-"c" "http://example/number" "113"
-"c" "http://example/name" "nameC"
-"c" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"c" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
-"x" "http://example/title" "titleX"
-"x" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"x" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/B"
-"d" "http://example/relation" "z"
-"d" "http://example/date" "2002-01-01T00:00:01Z"
-"d" "http://example/name" "nameD"
-"d" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"d" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
+"b" "http://example/relation" "z"
+"c" "http://example/relation" "x"
+"d" "http://example/relation" "z"
+"z" "http://example/title" "titleZ"
+"x" "http://example/title" "titleX"
+"b" "http://example/number" "73"
+"c" "http://example/number" "113"
+"b" "http://example/date" "2001-01-01T00:00:01Z"
+"d" "http://example/date" "2002-01-01T00:00:01Z"
+"b" "http://example/name" "nameB"
+"c" "http://example/name" "nameC"
+"d" "http://example/name" "nameD"
+"z" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"z" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/B"
+"x" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"x" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/B"
+"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
+"c" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"c" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
+"d" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"d" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
diff --git a/tests/libtracker-data/describe/describe-pattern.out b/tests/libtracker-data/describe/describe-pattern.out
index e512b1d8c..84d8d9520 100644
--- a/tests/libtracker-data/describe/describe-pattern.out
+++ b/tests/libtracker-data/describe/describe-pattern.out
@@ -1,6 +1,6 @@
-"b" "http://example/relation" "z"
-"b" "http://example/number" "73"
-"b" "http://example/date" "2001-01-01T00:00:01Z"
-"b" "http://example/name" "nameB"
-"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
+"b" "http://example/relation" "z"
+"b" "http://example/number" "73"
+"b" "http://example/date" "2001-01-01T00:00:01Z"
+"b" "http://example/name" "nameB"
+"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"b" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
diff --git a/tests/libtracker-data/describe/describe-single.out b/tests/libtracker-data/describe/describe-single.out
index 0bb548ba0..b896021ee 100644
--- a/tests/libtracker-data/describe/describe-single.out
+++ b/tests/libtracker-data/describe/describe-single.out
@@ -1,5 +1,5 @@
-"a" "http://example/number" "42"
-"a" "http://example/date" "2000-01-01T00:00:01Z"
-"a" "http://example/name" "nameA"
-"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
-"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
+"a" "http://example/number" "42"
+"a" "http://example/date" "2000-01-01T00:00:01Z"
+"a" "http://example/name" "nameA"
+"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://www.w3.org/2000/01/rdf-schema#Resource"
+"a" "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" "http://example/A"
diff --git a/tests/libtracker-sparql/meson.build b/tests/libtracker-sparql/meson.build
index 17badcab9..9f6733f9e 100644
--- a/tests/libtracker-sparql/meson.build
+++ b/tests/libtracker-sparql/meson.build
@@ -54,3 +54,14 @@ tests += {
'exe': tracker_statement_test,
'suite': ['sparql'],
}
+
+tracker_serialize_test = executable('tracker-serialize-test',
+ 'tracker-serialize-test.c',
+ dependencies: [tracker_common_dep, tracker_sparql_dep],
+ c_args: libtracker_sparql_test_c_args + test_c_args)
+
+tests += {
+ 'name': 'serialize',
+ 'exe': tracker_serialize_test,
+ 'suite': ['sparql'],
+}
diff --git a/tests/libtracker-sparql/serialize/construct-trig.out b/tests/libtracker-sparql/serialize/construct-trig.out
new file mode 100644
index 000000000..65d6809e3
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/construct-trig.out
@@ -0,0 +1,21 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+{
+ <a> <http://example.com/#prop> "Foo" .
+
+ <b> <http://example.com/#prop> "Foo" .
+}
diff --git a/tests/libtracker-sparql/serialize/construct-trig.rq b/tests/libtracker-sparql/serialize/construct-trig.rq
new file mode 100644
index 000000000..6f9753b34
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/construct-trig.rq
@@ -0,0 +1,5 @@
+CONSTRUCT {
+ ?u <http://example.com/#prop> "Foo"
+} WHERE {
+ ?u a nmm:MusicPiece
+}
diff --git a/tests/libtracker-sparql/serialize/construct-ttl.out b/tests/libtracker-sparql/serialize/construct-ttl.out
new file mode 100644
index 000000000..432f87a3c
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/construct-ttl.out
@@ -0,0 +1,19 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+<a> <http://example.com/#prop> "Foo" .
+
+<b> <http://example.com/#prop> "Foo" .
diff --git a/tests/libtracker-sparql/serialize/construct-ttl.rq b/tests/libtracker-sparql/serialize/construct-ttl.rq
new file mode 100644
index 000000000..6f9753b34
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/construct-ttl.rq
@@ -0,0 +1,5 @@
+CONSTRUCT {
+ ?u <http://example.com/#prop> "Foo"
+} WHERE {
+ ?u a nmm:MusicPiece
+}
diff --git a/tests/libtracker-sparql/serialize/describe-graph-trig.out b/tests/libtracker-sparql/serialize/describe-graph-trig.out
new file mode 100644
index 000000000..07165da63
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-graph-trig.out
@@ -0,0 +1,47 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+GRAPH <B> {
+ <a> nmm:trackNumber 1 .
+
+ <b> nmm:beatsPerMinute 120 .
+}
+
+GRAPH <A> {
+ <a> nie:title "Aaa" ;
+ dc:title "Aaa" ;
+}
+
+GRAPH <B> {
+
+}
+
+GRAPH <A> {
+}
+
+GRAPH <B> {
+
+}
+
+GRAPH <A> {
+ <a> rdf:type rdfs:Resource, nie:InformationElement, nfo:Media, nmm:MusicPiece .
+}
+
+GRAPH <B> {
+ <a> rdf:type rdfs:Resource, nie:InformationElement, nfo:Media, nmm:MusicPiece .
+
+ <b> rdf:type rdfs:Resource, nie:InformationElement, nfo:Media, nmm:MusicPiece .
+}
diff --git a/tests/libtracker-sparql/serialize/describe-graph-trig.rq b/tests/libtracker-sparql/serialize/describe-graph-trig.rq
new file mode 100644
index 000000000..fc6ad9904
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-graph-trig.rq
@@ -0,0 +1,5 @@
+DESCRIBE ?u {
+ GRAPH ?g {
+ ?u a nmm:MusicPiece .
+ }
+}
diff --git a/tests/libtracker-sparql/serialize/describe-graph-ttl.out b/tests/libtracker-sparql/serialize/describe-graph-ttl.out
new file mode 100644
index 000000000..f6e9b5199
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-graph-ttl.out
@@ -0,0 +1,29 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+<a> nmm:trackNumber 1 .
+
+<b> nmm:beatsPerMinute 120 .
+
+<a> nie:title "Aaa" ;
+ dc:title "Aaa" ;
+
+
+
+
+<a> rdf:type rdfs:Resource, nie:InformationElement, nfo:Media, nmm:MusicPiece, rdfs:Resource, nie:InformationElement, nfo:Media, nmm:MusicPiece .
+
+<b> rdf:type rdfs:Resource, nie:InformationElement, nfo:Media, nmm:MusicPiece .
diff --git a/tests/libtracker-sparql/serialize/describe-graph-ttl.rq b/tests/libtracker-sparql/serialize/describe-graph-ttl.rq
new file mode 100644
index 000000000..b08eb84a1
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-graph-ttl.rq
@@ -0,0 +1,5 @@
+DESCRIBE ?u {
+ GRAPH ?g {
+ ?u a rdfs:Resource .
+ }
+}
diff --git a/tests/libtracker-sparql/serialize/describe-single-trig.out b/tests/libtracker-sparql/serialize/describe-single-trig.out
new file mode 100644
index 000000000..ec5667d31
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-single-trig.out
@@ -0,0 +1,22 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+{
+ rdfs:Resource nrl:classSpecification "https://www.w3.org/TR/rdf-schema/#ch_resource" ;
+ rdfs:label "All Resources" ;
+ rdfs:comment "All resources" ;
+ rdf:type rdfs:Resource, rdfs:Class .
+}
diff --git a/tests/libtracker-sparql/serialize/describe-single-trig.rq b/tests/libtracker-sparql/serialize/describe-single-trig.rq
new file mode 100644
index 000000000..2584aa749
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-single-trig.rq
@@ -0,0 +1 @@
+DESCRIBE rdfs:Resource
diff --git a/tests/libtracker-sparql/serialize/describe-single-ttl.out b/tests/libtracker-sparql/serialize/describe-single-ttl.out
new file mode 100644
index 000000000..78b8ce55a
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-single-ttl.out
@@ -0,0 +1,20 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+rdfs:Resource nrl:classSpecification "https://www.w3.org/TR/rdf-schema/#ch_resource" ;
+ rdfs:label "All Resources" ;
+ rdfs:comment "All resources" ;
+ rdf:type rdfs:Resource, rdfs:Class .
diff --git a/tests/libtracker-sparql/serialize/describe-single-ttl.rq b/tests/libtracker-sparql/serialize/describe-single-ttl.rq
new file mode 100644
index 000000000..2584aa749
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-single-ttl.rq
@@ -0,0 +1 @@
+DESCRIBE rdfs:Resource
diff --git a/tests/libtracker-sparql/serialize/describe-var-trig.out b/tests/libtracker-sparql/serialize/describe-var-trig.out
new file mode 100644
index 000000000..eb1ceda36
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-var-trig.out
@@ -0,0 +1,23 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+{
+ rdfs:Class nrl:classSpecification "https://www.w3.org/TR/rdf-schema/#ch_class" ;
+ rdfs:label "Class" ;
+ rdfs:comment "The class of classes" ;
+ rdfs:subClassOf rdfs:Resource ;
+ rdf:type rdfs:Resource, rdfs:Class .
+}
diff --git a/tests/libtracker-sparql/serialize/describe-var-trig.rq b/tests/libtracker-sparql/serialize/describe-var-trig.rq
new file mode 100644
index 000000000..20463c01d
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-var-trig.rq
@@ -0,0 +1,3 @@
+DESCRIBE ?u {
+ ?u rdfs:label ~arg1
+}
diff --git a/tests/libtracker-sparql/serialize/describe-var-ttl.out b/tests/libtracker-sparql/serialize/describe-var-ttl.out
new file mode 100644
index 000000000..8cd80c5d4
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-var-ttl.out
@@ -0,0 +1,21 @@
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix nao: <http://tracker.api.gnome.org/ontology/v3/nao#> .
+@prefix nrl: <http://tracker.api.gnome.org/ontology/v3/nrl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix fts: <http://tracker.api.gnome.org/ontology/v3/fts#> .
+@prefix nmm: <http://tracker.api.gnome.org/ontology/v3/nmm#> .
+@prefix slo: <http://tracker.api.gnome.org/ontology/v3/slo#> .
+@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> .
+@prefix dc: <http://purl.org/dc/elements/1.1/> .
+@prefix nfo: <http://tracker.api.gnome.org/ontology/v3/nfo#> .
+@prefix osinfo: <http://tracker.api.gnome.org/ontology/v3/osinfo#> .
+@prefix nco: <http://tracker.api.gnome.org/ontology/v3/nco#> .
+@prefix nie: <http://tracker.api.gnome.org/ontology/v3/nie#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix mfo: <http://tracker.api.gnome.org/ontology/v3/mfo#> .
+
+rdfs:Class nrl:classSpecification "https://www.w3.org/TR/rdf-schema/#ch_class" ;
+ rdfs:label "Class" ;
+ rdfs:comment "The class of classes" ;
+ rdfs:subClassOf rdfs:Resource ;
+ rdf:type rdfs:Resource, rdfs:Class .
diff --git a/tests/libtracker-sparql/serialize/describe-var-ttl.rq b/tests/libtracker-sparql/serialize/describe-var-ttl.rq
new file mode 100644
index 000000000..20463c01d
--- /dev/null
+++ b/tests/libtracker-sparql/serialize/describe-var-ttl.rq
@@ -0,0 +1,3 @@
+DESCRIBE ?u {
+ ?u rdfs:label ~arg1
+}
diff --git a/tests/libtracker-sparql/tracker-serialize-test.c b/tests/libtracker-sparql/tracker-serialize-test.c
new file mode 100644
index 000000000..f33a83b96
--- /dev/null
+++ b/tests/libtracker-sparql/tracker-serialize-test.c
@@ -0,0 +1,340 @@
+/*
+ * 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;
+ TrackerRdfFormat format;
+ const gchar *arg1;
+} TestInfo;
+
+TestInfo tests[] = {
+ { "ttl/var", "serialize/describe-var-ttl.rq", "serialize/describe-var-ttl.out", TRACKER_RDF_FORMAT_TURTLE, "Class" },
+ { "ttl/single", "serialize/describe-single-ttl.rq", "serialize/describe-single-ttl.out", TRACKER_RDF_FORMAT_TURTLE, NULL },
+ { "ttl/graph", "serialize/describe-graph-ttl.rq", "serialize/describe-graph-ttl.out", TRACKER_RDF_FORMAT_TURTLE, NULL },
+ { "ttl/construct", "serialize/construct-ttl.rq", "serialize/construct-ttl.out", TRACKER_RDF_FORMAT_TURTLE, NULL },
+ { "trig/var", "serialize/describe-var-trig.rq", "serialize/describe-var-trig.out", TRACKER_RDF_FORMAT_TRIG, "Class" },
+ { "trig/single", "serialize/describe-single-trig.rq", "serialize/describe-single-trig.out", TRACKER_RDF_FORMAT_TRIG, NULL },
+ { "trig/graph", "serialize/describe-graph-trig.rq", "serialize/describe-graph-trig.out", TRACKER_RDF_FORMAT_TRIG, NULL },
+ { "trig/construct", "serialize/construct-trig.rq", "serialize/construct-trig.out", TRACKER_RDF_FORMAT_TRIG, NULL },
+};
+
+typedef struct {
+ TestInfo *test;
+ TrackerSparqlConnection *conn;
+ GMainLoop *loop;
+ GInputStream *istream;
+} TestFixture;
+
+typedef struct {
+ TrackerSparqlConnection *direct;
+ GDBusConnection *dbus_conn;
+} StartupData;
+
+static gboolean started = FALSE;
+static const gchar *bus_name = NULL;
+
+static void
+check_result (GInputStream *istream,
+ const gchar *results_filename)
+{
+ gchar *results;
+ GError *nerror = NULL;
+ GError *error = NULL;
+ gchar *quoted_results;
+ gchar *command_line;
+ gchar *quoted_command_line;
+ gchar *shell;
+ gchar *diff;
+ gchar output[8096] = { 0 };
+
+ g_input_stream_read_all (istream,
+ output, sizeof (output),
+ NULL, NULL, &error);
+ g_assert_no_error (error);
+
+ g_file_get_contents (results_filename, &results, NULL, &nerror);
+ g_assert_no_error (nerror);
+ g_clear_error (&nerror);
+
+ /* compare results with reference output */
+ quoted_results = g_shell_quote (output);
+ command_line = g_strdup_printf ("echo -n %s | grep -v -e nrl:modified -e nrl:added | 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);
+
+ if (diff && *diff)
+ g_error ("%s", diff);
+
+ g_free (quoted_results);
+ g_free (command_line);
+ g_free (quoted_command_line);
+ g_free (shell);
+ g_free (diff);
+
+ g_free (results);
+}
+
+static void
+setup (TestFixture *fixture,
+ gconstpointer context)
+{
+ const TestFixture *test = context;
+
+ *fixture = *test;
+}
+
+static void
+serialize_stmt_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TestFixture *fixture = user_data;
+ GError *error = NULL;
+
+ fixture->istream =
+ tracker_sparql_statement_serialize_finish (TRACKER_SPARQL_STATEMENT (source),
+ res, &error);
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->loop);
+}
+
+
+static void
+serialize_cb (GObject *source,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ TestFixture *fixture = user_data;
+ GError *error = NULL;
+
+ fixture->istream =
+ tracker_sparql_connection_serialize_finish (TRACKER_SPARQL_CONNECTION (source),
+ res, &error);
+ g_assert_no_error (error);
+ g_main_loop_quit (fixture->loop);
+}
+
+static void
+serialize (TestFixture *test_fixture,
+ gconstpointer context)
+{
+ TrackerSparqlStatement *stmt;
+ GError *error = NULL;
+ gchar *path, *query;
+ TestInfo *test_info = test_fixture->test;
+
+ test_fixture->loop = g_main_loop_new (NULL, FALSE);
+
+ 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);
+
+ if (test_info->arg1) {
+ stmt = tracker_sparql_connection_query_statement (test_fixture->conn,
+ query,
+ NULL,
+ &error);
+ g_assert_no_error (error);
+
+ tracker_sparql_statement_bind_string (stmt, "arg1", test_info->arg1);
+
+ tracker_sparql_statement_serialize_async (stmt,
+ TRACKER_SERIALIZE_FLAGS_NONE,
+ test_info->format,
+ NULL,
+ serialize_stmt_cb,
+ test_fixture);
+ g_object_unref (stmt);
+ } else {
+ tracker_sparql_connection_serialize_async (test_fixture->conn,
+ TRACKER_SERIALIZE_FLAGS_NONE,
+ test_info->format,
+ query,
+ NULL,
+ serialize_cb,
+ test_fixture);
+ }
+
+ g_main_loop_run (test_fixture->loop);
+
+ g_assert_nonnull (test_fixture->istream);
+
+ path = g_build_filename (TOP_SRCDIR, "tests", "libtracker-sparql",
+ test_info->output_file, NULL);
+ check_result (test_fixture->istream, path);
+ g_input_stream_close (test_fixture->istream, NULL, NULL);
+ g_object_unref (test_fixture->istream);
+ g_free (path);
+}
+
+static void
+populate_data (TrackerSparqlConnection *conn)
+{
+ TrackerResource *res;
+ GError *error = NULL;
+
+ /* Add some test data in different graphs */
+ res = tracker_resource_new ("a");
+ tracker_resource_set_uri (res, "rdf:type", "nmm:MusicPiece");
+ tracker_resource_set_uri (res, "nie:title", "Aaa");
+ tracker_sparql_connection_update_resource (conn, "A", res, NULL, &error);
+ g_assert_no_error (error);
+ g_object_unref (res);
+
+ res = tracker_resource_new ("a");
+ tracker_resource_set_uri (res, "rdf:type", "nmm:MusicPiece");
+ tracker_resource_set_int (res, "nmm:trackNumber", 1);
+ tracker_sparql_connection_update_resource (conn, "B", res, NULL, &error);
+ g_assert_no_error (error);
+ g_object_unref (res);
+
+ res = tracker_resource_new ("b");
+ tracker_resource_set_uri (res, "rdf:type", "nmm:MusicPiece");
+ tracker_resource_set_int (res, "nmm:beatsPerMinute", 120);
+ tracker_sparql_connection_update_resource (conn, "B", res, NULL, &error);
+ g_assert_no_error (error);
+ g_object_unref (res);
+}
+
+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);
+
+ populate_data (conn);
+
+ return conn;
+}
+
+static gpointer
+thread_func (gpointer user_data)
+{
+ StartupData *data = user_data;
+ TrackerEndpointDBus *endpoint;
+ TrackerEndpointHttp *endpoint_http;
+ 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;
+
+ endpoint_http = tracker_endpoint_http_new (data->direct, 54322, NULL, NULL, NULL);
+ if (!endpoint_http)
+ return NULL;
+
+ started = TRUE;
+ g_main_loop_run (main_loop);
+
+ return NULL;
+}
+
+static gboolean
+create_connections (TrackerSparqlConnection **dbus,
+ TrackerSparqlConnection **direct,
+ TrackerSparqlConnection **remote,
+ GError **error)
+{
+ StartupData data;
+ GThread *thread;
+
+ 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;
+
+ thread = g_thread_new (NULL, thread_func, &data);
+
+ while (!started)
+ g_usleep (100);
+
+ bus_name = g_dbus_connection_get_unique_name (data.dbus_conn);
+ *dbus = tracker_sparql_connection_bus_new (bus_name,
+ NULL, data.dbus_conn, error);
+ *direct = create_local_connection (error);
+ *remote = tracker_sparql_connection_remote_new ("http://127.0.0.1:54322/sparql");
+ g_thread_unref (thread);
+
+ return TRUE;
+}
+
+static void
+add_tests (TrackerSparqlConnection *conn,
+ const gchar *name,
+ gboolean run_service_tests)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (tests); i++) {
+ TestFixture *fixture;
+ gchar *testpath;
+
+ fixture = g_new0 (TestFixture, 1);
+ fixture->conn = conn;
+ fixture->test = &tests[i];
+ testpath = g_strconcat ("/libtracker-sparql/serialize/", name, "/", tests[i].test_name, NULL);
+ g_test_add (testpath, TestFixture, fixture, setup, serialize, NULL);
+ g_free (testpath);
+ }
+}
+
+gint
+main (gint argc, gchar **argv)
+{
+ TrackerSparqlConnection *dbus = NULL, *direct = NULL, *remote = NULL;
+ GError *error = NULL;
+
+ g_test_init (&argc, &argv, NULL);
+
+ g_assert_true (create_connections (&dbus, &direct, &remote, &error));
+ g_assert_no_error (error);
+
+ add_tests (direct, "direct", TRUE);
+ add_tests (dbus, "dbus", FALSE);
+ add_tests (remote, "http", FALSE);
+
+ return g_test_run ();
+}