Commit fbfd3fa4 authored by Sam Thursfield's avatar Sam Thursfield

Merge branch 'wip/carlosg/sparql1.1' into 'master'

Fully implement sparql1.1 query/update recommendations

See merge request !131
parents e736434f c8e964cd
Pipeline #114458 passed with stage
in 3 minutes and 3 seconds
......@@ -53,6 +53,7 @@
<xi:include href="private-store.xml"/>
<xi:include href="examples.xml"/>
<xi:include href="migrating-1to2.xml"/>
<xi:include href="migrating-2to3.xml"/>
<index id="api-index-full">
<title>Index</title>
......
<?xml version='1.0'?>
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.3//EN"
"http://www.oasis-open.org/docbook/xml/4.3/docbookx.dtd" [
<!ENTITY % local.common.attrib "xmlns:xi CDATA #FIXED 'http://www.w3.org/2003/XInclude'">
]>
<chapter id="tracker-migrating-2-to-3">
<title>Migrating from libtracker-sparql 2.x to 3.0</title>
<para>
Tracker 3.0 is a new major version, containing some large
syntax and conceptual changes.
</para>
<section>
<title>Graph semantics</title>
<para>
One of the big features in 3.0 is full SPARQL 1.1 syntax
support. Besides the missing syntax additions, one conceptually
big change is the handling of graphs in the database.
</para>
<para>
In 2.x, there was a minimum concept of graphs, but it didn't
represent what is defined in the standard. You could attribute
a graph to a data triple, but a given triple could only reside
in one graph at a time. In other words, this yields the wrong
result:
</para>
<programlisting>
INSERT { GRAPH &lt;A&gt; { &lt;foo&gt; nie:title 'Hello' } }
INSERT { GRAPH &lt;B&gt; { &lt;foo&gt; nie:title 'Hola' } }
# We expect 2 rows, 2.x returns 1.
SELECT ?g ?t { GRAPH ?g { &lt;foo&gt; nie:title ?t } }
</programlisting>
<para>
3.0 implements the graph semantics as defined in the SPARQL 1.1
documents. So the SELECT query would return both graphs.
</para>
<para>
3.0 also honors properly the concept of «Unnamed graph». This
graph is always used whenever no graph is specified, and always
skipped if a GRAPH is requested or defined, e.g.:
</para>
<programlisting>
# Inserts element into the unnamed graph
INSERT { &lt;foo&gt; a nfo:FileDataObject }
# Inserts element into named graph A
INSERT { GRAPH &lt;A&gt; { &lt;bar&gt; a nfo:FileDataObject } }
# Queries from all named graphs, A in this case
SELECT ?g ?s { GRAPH ?g { ?s a nfo:FileDataObject } }
# Queries the default graph, which includes the unnamed graph
SELECT ?s { ?s a nfo:FileDataObject }
</programlisting>
<para>
3.0 defines the default graph to be the union of the unnamed
graph plus all known named graphs. So the last query in the
example above would return results from both the unnamed graph
and graph A. This behavior can be influenced with FROM/FROM NAMED
syntax (also newly handled in 3.0)
</para>
<para>
In contrast, 2.x does not distinguish between named and unnamed
graphs. The first SELECT query would return a row for the unnamed
graph, with ?g being NULL.
</para>
</section>
</chapter>
......@@ -26,6 +26,7 @@
/* Ontology classes */
#define RDFS_CLASS "http://www.w3.org/2000/01/rdf-schema#Class"
#define RDF_PROPERTY "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"
#define RDF_LIST "http://www.w3.org/1999/02/22-rdf-syntax-ns#List"
#define RDFS_SUBCLASSOF "http://www.w3.org/2000/01/rdf-schema#subClassOf"
#define RDFS_TYPE "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
#define RDFS_RANGE "http://www.w3.org/2000/01/rdf-schema#range"
......@@ -83,7 +84,8 @@ load_in_memory (Ontology *ontology,
if (!g_strcmp0 (turtle_predicate, RDFS_TYPE)) {
/* It is a definition of class or property */
if (!g_strcmp0 (turtle_object, RDFS_CLASS)) {
if (!g_strcmp0 (turtle_object, RDFS_CLASS) ||
!g_strcmp0 (turtle_object, RDF_LIST)) {
g_hash_table_insert (ontology->classes,
g_strdup (turtle_subject),
ttl_model_class_new (turtle_subject));
......
......@@ -241,8 +241,6 @@ endif
conf = configuration_data()
# Config that goes in config.h
conf.set('DISABLE_JOURNAL', get_option('journal') == false)
conf.set10('HAVE_TRACKER_FTS', enable_fts)
conf.set('HAVE_BUILTIN_FTS', sqlite3_has_builtin_fts5)
......@@ -289,15 +287,6 @@ conf.set('domain_ontologies_dir', join_paths('${datadir}', 'tracker', 'domain-on
conf.set('FUNCTIONAL_TESTS_ONTOLOGIES_DIR', join_paths(meson.current_source_dir(), 'tests', 'functional-tests', 'test-ontologies'))
conf.set('FUNCTIONAL_TESTS_TRACKER_STORE_PATH', join_paths(meson.current_build_dir(), 'src', 'tracker-store', 'tracker-store'))
# This is set in an awkward way for compatibility with Autoconf. Switch it
# to a normal boolean once we get rid of the Autotools build system. It's
# only used in tests/functional-tests/common/utils/configuration.py.in.
if get_option('journal')
conf.set('DISABLE_JOURNAL_TRUE', 'true')
else
conf.set('DISABLE_JOURNAL_TRUE', '')
endif
configure_file(input: 'config.h.meson.in',
output: 'config.h',
configuration: conf)
......
......@@ -201,14 +201,15 @@ tracker_string_to_date (const gchar *date_string,
}
gchar *
tracker_date_to_string (gdouble date_time)
tracker_date_to_string (gdouble date_time,
gint offset)
{
gchar buffer[30];
gchar buffer[35];
time_t seconds;
gint64 total_milliseconds;
gint milliseconds;
struct tm utc_time;
size_t count;
size_t count, size;
memset (buffer, '\0', sizeof (buffer));
memset (&utc_time, 0, sizeof (struct tm));
......@@ -218,7 +219,7 @@ tracker_date_to_string (gdouble date_time)
if (milliseconds < 0) {
milliseconds += 1000;
}
seconds = (time_t) ((total_milliseconds - milliseconds) / 1000);
seconds = (time_t) ((total_milliseconds - milliseconds) / 1000) + offset;
gmtime_r (&seconds, &utc_time);
/* Output is ISO 8601 format : "YYYY-MM-DDThh:mm:ss" */
......@@ -226,11 +227,25 @@ tracker_date_to_string (gdouble date_time)
/* Append milliseconds (if non-zero) and time zone */
if (milliseconds > 0) {
snprintf (buffer + count, sizeof (buffer) - count, ".%03dZ", milliseconds);
size = snprintf (buffer + count, sizeof (buffer) - count, ".%03d", milliseconds);
count += size;
}
if (offset != 0) {
gint hours, mins;
hours = ABS (offset) / 3600;
mins = (ABS (offset) % 3600) / 60;
size = snprintf (buffer + count, sizeof (buffer) - count, "%c%.2d:%.2d",
offset < 0 ? '-' : '+',
hours, mins);
count += size;
} else {
buffer[count] = 'Z';
buffer[count++] = 'Z';
}
g_assert (count <= sizeof (buffer));
return count > 0 ? g_strdup (buffer) : NULL;
}
......
......@@ -57,7 +57,8 @@ gint tracker_date_time_get_local_time (const GValue *value);
gdouble tracker_string_to_date (const gchar *date_string,
gint *offset,
GError **error);
gchar * tracker_date_to_string (gdouble date_time);
gchar * tracker_date_to_string (gdouble date_time,
gint offset);
G_END_DECLS
......
......@@ -323,3 +323,119 @@ tracker_unescape_unichars (const gchar *str,
return g_string_free (copy, FALSE);
}
gboolean
parse_abs_uri (const gchar *uri,
gchar **base,
const gchar **rel_path)
{
const gchar *loc, *end;
end = &uri[strlen (uri)];
loc = uri;
if (!g_ascii_isalpha (loc[0]))
return FALSE;
while (loc != end) {
if (loc[0] == ':')
break;
if (!g_ascii_isalpha (loc[0]) &&
loc[0] != '+' && loc[0] != '-' && loc[0] != '.')
return FALSE;
loc++;
}
if (loc == uri)
return FALSE;
if (strncmp (loc, "://", 3) == 0) {
/* Include authority in base */
loc += 3;
loc = strchr (loc, '/');
if (!loc)
loc = end;
}
*base = g_strndup (uri, loc - uri);
*rel_path = loc + 1;
return TRUE;
}
GPtrArray *
remove_dot_segments (gchar **uri_elems)
{
GPtrArray *array;
gint i;
array = g_ptr_array_new ();
for (i = 0; uri_elems[i] != NULL; i++) {
if (g_strcmp0 (uri_elems[i], ".") == 0) {
continue;
} else if (g_strcmp0 (uri_elems[i], "..") == 0) {
if (array->len > 0)
g_ptr_array_remove_index (array, array->len - 1);
continue;
} else if (*uri_elems[i] != '\0') {
/* NB: Not a copy */
g_ptr_array_add (array, uri_elems[i]);
}
}
return array;
}
gchar *
tracker_resolve_relative_uri (const gchar *base,
const gchar *rel_uri)
{
gchar **base_split, **rel_split, *host;
GPtrArray *base_norm, *rel_norm;
GString *str;
gint i;
/* Relative IRIs are combined with base IRIs with a simplified version
* of the algorithm described at RFC3986, Section 5.2. We don't care
* about query and fragment parts of an URI, and some simplifications
* are taken on base uri parsing and relative uri validation.
*/
rel_split = g_strsplit (rel_uri, "/", -1);
/* Rel uri is a full uri? */
if (strchr (rel_split[0], ':')) {
g_strfreev (rel_split);
return g_strdup (rel_uri);
}
if (!parse_abs_uri (base, &host, &base)) {
g_strfreev (rel_split);
return g_strdup (rel_uri);
}
base_split = g_strsplit (base, "/", -1);
base_norm = remove_dot_segments (base_split);
rel_norm = remove_dot_segments (rel_split);
for (i = 0; i < rel_norm->len; i++) {
g_ptr_array_add (base_norm,
g_ptr_array_index (rel_norm, i));
}
str = g_string_new (host);
for (i = 0; i < base_norm->len; i++) {
g_string_append_c (str, '/');
g_string_append (str,
g_ptr_array_index (base_norm, i));
}
g_ptr_array_unref (base_norm);
g_ptr_array_unref (rel_norm);
g_strfreev (base_split);
g_strfreev (rel_split);
g_free (host);
return g_string_free (str, FALSE);
}
......@@ -47,6 +47,8 @@ gchar * tracker_utf8_truncate (const gchar *str,
gsize max_size);
gchar * tracker_unescape_unichars (const gchar *str,
gssize len);
gchar * tracker_resolve_relative_uri (const gchar *base,
const gchar *rel_uri);
G_END_DECLS
......
......@@ -122,7 +122,6 @@ namespace Tracker {
public class Class : GLib.Object {
public string name { get; set; }
public string uri { get; set; }
public int count { get; set; }
[CCode (array_length = false, array_null_terminated = true)]
public unowned Class[] get_super_classes ();
public void transact_events ();
......@@ -192,11 +191,11 @@ namespace Tracker {
public void update_sparql (string update) throws Sparql.Error;
public GLib.Variant update_sparql_blank (string update) throws Sparql.Error;
public void load_turtle_file (GLib.File file) throws Sparql.Error;
public void delete_statement (string? graph, string subject, string predicate, string object) throws Sparql.Error, DateError;
public void update_statement (string? graph, string subject, string predicate, string? object) throws Sparql.Error, DateError;
public void insert_statement (string? graph, string subject, string predicate, string object) throws Sparql.Error, DateError;
public void insert_statement_with_uri (string? graph, string subject, string predicate, string object) throws Sparql.Error;
public void insert_statement_with_string (string? graph, string subject, string predicate, string object) throws Sparql.Error, DateError;
public void delete_statement (string? graph, string subject, string predicate, GLib.Bytes object) throws Sparql.Error, DateError;
public void update_statement (string? graph, string subject, string predicate, GLib.Bytes? object) throws Sparql.Error, DateError;
public void insert_statement (string? graph, string subject, string predicate, GLib.Bytes object) throws Sparql.Error, DateError;
public void insert_statement_with_uri (string? graph, string subject, string predicate, GLib.Bytes object) throws Sparql.Error;
public void insert_statement_with_string (string? graph, string subject, string predicate, GLib.Bytes object) throws Sparql.Error, DateError;
public void update_buffer_flush () throws DBInterfaceError;
public void update_buffer_might_flush () throws DBInterfaceError;
public void sync ();
......
......@@ -47,7 +47,6 @@ libtracker_data = library('tracker-data',
'tracker-db-interface.c',
'tracker-db-interface-sqlite.c',
'tracker-db-manager.c',
'tracker-db-journal.c',
'tracker-db-backup.c',
'tracker-namespace.c',
'tracker-ontology.c',
......@@ -58,6 +57,7 @@ libtracker_data = library('tracker-data',
'tracker-sparql-types.c',
'tracker-sparql.c',
'tracker-uuid.c',
'tracker-vtab-service.c',
'tracker-vtab-triples.c',
tracker_common_enum_header,
tracker_data_enums[0],
......
......@@ -34,7 +34,6 @@ typedef struct _TrackerClassPrivate TrackerClassPrivate;
struct _TrackerClassPrivate {
gchar *uri;
gchar *name;
gint count;
gint id;
gboolean is_new;
gboolean db_schema_changed;
......@@ -140,18 +139,6 @@ tracker_class_get_name (TrackerClass *service)
return priv->name;
}
gint
tracker_class_get_count (TrackerClass *service)
{
TrackerClassPrivate *priv;
g_return_val_if_fail (TRACKER_IS_CLASS (service), 0);
priv = tracker_class_get_instance_private (service);
return priv->count;
}
gint
tracker_class_get_id (TrackerClass *service)
{
......@@ -312,20 +299,6 @@ tracker_class_set_uri (TrackerClass *service,
}
}
void
tracker_class_set_count (TrackerClass *service,
gint value)
{
TrackerClassPrivate *priv;
g_return_if_fail (TRACKER_IS_CLASS (service));
priv = tracker_class_get_instance_private (service);
priv->count = value;
}
void
tracker_class_set_id (TrackerClass *service,
gint value)
......
......@@ -53,7 +53,6 @@ GType tracker_class_get_type (void) G_GNUC_CONST;
TrackerClass * tracker_class_new (gboolean use_gvdb);
const gchar * tracker_class_get_uri (TrackerClass *service);
const gchar * tracker_class_get_name (TrackerClass *service);
gint tracker_class_get_count (TrackerClass *service);
gint tracker_class_get_id (TrackerClass *service);
gboolean tracker_class_get_is_new (TrackerClass *service);
gboolean tracker_class_get_db_schema_changed (TrackerClass *service);
......@@ -66,8 +65,6 @@ TrackerClass **tracker_class_get_last_super_classes (TrackerClass *ser
void tracker_class_set_uri (TrackerClass *service,
const gchar *value);
void tracker_class_set_count (TrackerClass *service,
gint value);
void tracker_class_add_super_class (TrackerClass *service,
TrackerClass *value);
void tracker_class_add_domain_index (TrackerClass *service,
......
......@@ -29,7 +29,6 @@
#include "tracker-data-backup.h"
#include "tracker-data-manager.h"
#include "tracker-db-manager.h"
#include "tracker-db-journal.h"
#include "tracker-db-backup.h"
typedef struct {
......@@ -40,21 +39,6 @@ typedef struct {
GError *error;
} BackupSaveInfo;
#ifndef DISABLE_JOURNAL
typedef struct {
GPid pid;
guint stdout_watch_id;
guint stderr_watch_id;
GIOChannel *stdin_channel;
GIOChannel *stdout_channel;
GIOChannel *stderr_channel;
gpointer data;
GString *lines;
} ProcessContext;
#endif /* DISABLE_JOURNAL */
static void
free_backup_save_info (BackupSaveInfo *info)
{
......@@ -82,151 +66,6 @@ tracker_data_backup_error_quark (void)
return g_quark_from_static_string (TRACKER_DATA_BACKUP_ERROR_DOMAIN);
}
#ifndef DISABLE_JOURNAL
static void
on_journal_copied (BackupSaveInfo *info, GError *error)
{
if (info->callback) {
info->callback (error, info->user_data);
}
free_backup_save_info (info);
}
static void
process_context_destroy (ProcessContext *context, GError *error)
{
on_journal_copied (context->data, error);
if (context->lines) {
g_string_free (context->lines, TRUE);
}
if (context->stdin_channel) {
g_io_channel_shutdown (context->stdin_channel, FALSE, NULL);
g_io_channel_unref (context->stdin_channel);
context->stdin_channel = NULL;
}
if (context->stdout_watch_id != 0) {
g_source_remove (context->stdout_watch_id);
context->stdout_watch_id = 0;
}
if (context->stdout_channel) {
g_io_channel_shutdown (context->stdout_channel, FALSE, NULL);
g_io_channel_unref (context->stdout_channel);
context->stdout_channel = NULL;
}
if (context->stderr_watch_id != 0) {
g_source_remove (context->stderr_watch_id);
context->stderr_watch_id = 0;
}
if (context->stderr_channel) {
g_io_channel_shutdown (context->stderr_channel, FALSE, NULL);
g_io_channel_unref (context->stderr_channel);
context->stderr_channel = NULL;
}
if (context->pid != 0) {
g_spawn_close_pid (context->pid);
context->pid = 0;
}
g_free (context);
}
static gboolean
read_line_of_tar_output (GIOChannel *channel,
GIOCondition condition,
gpointer user_data)
{
if (condition & G_IO_ERR || condition & G_IO_HUP) {
ProcessContext *context = user_data;
context->stdout_watch_id = 0;
return FALSE;
}
/* TODO: progress support */
return TRUE;
}
static gboolean
read_error_of_tar_output (GIOChannel *channel,
GIOCondition condition,
gpointer user_data)
{
ProcessContext *context;
GIOStatus status;
gchar *line;
context = user_data;
status = G_IO_STATUS_NORMAL;
if (condition & G_IO_IN || condition & G_IO_PRI) {
do {
GError *error = NULL;
status = g_io_channel_read_line (channel, &line, NULL, NULL, &error);
if (status == G_IO_STATUS_NORMAL) {
if (context->lines == NULL)
context->lines = g_string_new (NULL);
g_string_append (context->lines, line);
g_free (line);
} else if (error) {
g_warning ("%s", error->message);
g_error_free (error);
}
} while (status == G_IO_STATUS_NORMAL);
if (status == G_IO_STATUS_EOF ||
status == G_IO_STATUS_ERROR) {
context->stderr_watch_id = 0;
return FALSE;
}
}
if (condition & G_IO_ERR || condition & G_IO_HUP) {
context->stderr_watch_id = 0;
return FALSE;
}
return TRUE;
}
static void
process_context_child_watch_cb (GPid pid,
gint status,
gpointer user_data)
{
ProcessContext *context;
GError *error = NULL;
g_debug ("Process '%d' exited with code %d", pid, status);
context = (ProcessContext *) user_data;
if (context->lines) {
g_set_error (&error, TRACKER_DATA_BACKUP_ERROR,
TRACKER_DATA_BACKUP_ERROR_UNKNOWN,
"%s", context->lines->str);
}
process_context_destroy (context, error);
}
#endif /* DISABLE_JOURNAL */
#ifdef DISABLE_JOURNAL
static void
on_backup_finished (GError *error,
gpointer user_data)
......@@ -240,8 +79,6 @@ on_backup_finished (GError *error,
free_backup_save_info (info);
}
#endif /* DISABLE_JOURNAL */
/* delete all regular files from the directory */