diff options
author | Sam Thursfield <sam@afuera.me.uk> | 2022-02-05 14:25:48 +0000 |
---|---|---|
committer | Sam Thursfield <sam@afuera.me.uk> | 2022-02-05 14:25:48 +0000 |
commit | 54119deb3ac487d867041b09ef351c386547f4cd (patch) | |
tree | 91195112224cbf18a6f987eccb8817266a9df06b | |
parent | 87736aa6f41cbe4de9aa7cdb3aee8cb97741f978 (diff) | |
parent | c63f9e633f05cc60150811f6307272873eb8bfa4 (diff) | |
download | tracker-54119deb3ac487d867041b09ef351c386547f4cd.tar.gz |
Merge branch 'wip/carlosg/serialize-api' into 'master'
Add API to serialize to RDF formats
See merge request GNOME/tracker!476
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 (); +} |