summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Garnacho <carlosg@gnome.org>2018-11-13 11:50:22 +0100
committerCarlos Garnacho <carlosg@gnome.org>2018-11-13 11:50:22 +0100
commiteb82efe8dacdca2b283264c52286244a01682e73 (patch)
treeaa751f4cf40153a8849101065637a68e5e88e72d
parentdb4a05d53f305855f1d5fa233764ef29538496da (diff)
parentafc32432722abbdcbf6748547f980579faed7a91 (diff)
downloadtracker-eb82efe8dacdca2b283264c52286244a01682e73.tar.gz
Merge branch 'wip/carlosg/sparql-parser-ng'
-rw-r--r--docs/reference/libtracker-sparql/libtracker-sparql-docs.sgml1
-rw-r--r--docs/reference/libtracker-sparql/libtracker-sparql-sections.txt25
-rw-r--r--docs/reference/libtracker-sparql/libtracker-sparql.types1
-rw-r--r--src/libtracker-common/tracker-utils.c89
-rw-r--r--src/libtracker-common/tracker-utils.h2
-rw-r--r--src/libtracker-data/meson.build8
-rw-r--r--src/libtracker-data/tracker-data-manager.c1
-rw-r--r--src/libtracker-data/tracker-data-query.c45
-rw-r--r--src/libtracker-data/tracker-data-query.h3
-rw-r--r--src/libtracker-data/tracker-data-update.c7
-rw-r--r--src/libtracker-data/tracker-data.h2
-rw-r--r--src/libtracker-data/tracker-db-interface-sqlite.c104
-rw-r--r--src/libtracker-data/tracker-db-interface.h3
-rw-r--r--src/libtracker-data/tracker-sparql-expression.vala1727
-rw-r--r--src/libtracker-data/tracker-sparql-grammar.h2360
-rw-r--r--src/libtracker-data/tracker-sparql-parser.c874
-rw-r--r--src/libtracker-data/tracker-sparql-parser.h52
-rw-r--r--src/libtracker-data/tracker-sparql-pattern.vala1659
-rw-r--r--src/libtracker-data/tracker-sparql-query.vala1102
-rw-r--r--src/libtracker-data/tracker-sparql-types.c908
-rw-r--r--src/libtracker-data/tracker-sparql-types.h362
-rw-r--r--src/libtracker-data/tracker-sparql.c6633
-rw-r--r--src/libtracker-data/tracker-sparql.h48
-rw-r--r--src/libtracker-data/tracker-string-builder.c314
-rw-r--r--src/libtracker-data/tracker-string-builder.h37
-rw-r--r--src/libtracker-data/tracker-uuid.c46
-rw-r--r--src/libtracker-data/tracker-uuid.h28
-rw-r--r--src/libtracker-direct/meson.build1
-rw-r--r--src/libtracker-direct/tracker-direct-statement.c265
-rw-r--r--src/libtracker-direct/tracker-direct-statement.h52
-rw-r--r--src/libtracker-direct/tracker-direct.c19
-rw-r--r--src/libtracker-direct/tracker-direct.h2
-rw-r--r--src/libtracker-sparql-backend/tracker-backend.vala10
-rw-r--r--src/libtracker-sparql-backend/tracker-sparql-2.map1
-rw-r--r--src/libtracker-sparql/meson.build1
-rw-r--r--src/libtracker-sparql/tracker-connection.vala12
-rw-r--r--src/libtracker-sparql/tracker-resource.c2
-rw-r--r--src/libtracker-sparql/tracker-statement.vala122
-rw-r--r--tests/libtracker-data/anon/query-3.out3
-rw-r--r--tests/libtracker-data/anon/query-3.rq1
-rw-r--r--tests/libtracker-data/anon/query-4.out3
-rw-r--r--tests/libtracker-data/anon/query-4.rq1
-rw-r--r--tests/libtracker-data/anon/query-5.out4
-rw-r--r--tests/libtracker-data/anon/query-5.rq1
-rw-r--r--tests/libtracker-data/bnode/data.ontology24
-rw-r--r--tests/libtracker-data/bnode/data.ttl30
-rw-r--r--tests/libtracker-data/bnode/query-1.out3
-rw-r--r--tests/libtracker-data/bnode/query-1.rq1
-rw-r--r--tests/libtracker-data/bnode/query-2.out3
-rw-r--r--tests/libtracker-data/bnode/query-2.rq1
-rw-r--r--tests/libtracker-data/bnode/query-3.out4
-rw-r--r--tests/libtracker-data/bnode/query-3.rq1
-rw-r--r--tests/libtracker-data/bnode/query-4.out3
-rw-r--r--tests/libtracker-data/bnode/query-4.rq1
-rw-r--r--tests/libtracker-data/bnode/query-5.out3
-rw-r--r--tests/libtracker-data/bnode/query-5.rq1
-rw-r--r--tests/libtracker-data/property-paths/data.ttl32
-rw-r--r--tests/libtracker-data/property-paths/inverse-path-1.out3
-rw-r--r--tests/libtracker-data/property-paths/inverse-path-1.rq1
-rw-r--r--tests/libtracker-data/property-paths/inverse-path-2.out6
-rw-r--r--tests/libtracker-data/property-paths/inverse-path-2.rq1
-rw-r--r--tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.out1
-rw-r--r--tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.rq1
-rw-r--r--tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.out1
-rw-r--r--tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.rq1
-rw-r--r--tests/libtracker-data/property-paths/query-1.rq0
-rw-r--r--tests/libtracker-data/property-paths/sequence-path-1.out2
-rw-r--r--tests/libtracker-data/property-paths/sequence-path-1.rq1
-rw-r--r--tests/libtracker-data/property-paths/sequence-path-2.out4
-rw-r--r--tests/libtracker-data/property-paths/sequence-path-2.rq1
-rw-r--r--tests/libtracker-data/property-paths/sequence-path-3.out1
-rw-r--r--tests/libtracker-data/property-paths/sequence-path-3.rq1
-rw-r--r--tests/libtracker-data/property-paths/test.ontology34
-rw-r--r--tests/libtracker-data/tracker-sparql-test.c16
-rw-r--r--tests/libtracker-fts/fts3aa-3.out1
-rw-r--r--tests/libtracker-fts/fts3aa-3.rq1
-rw-r--r--tests/libtracker-fts/tracker-fts-test.c2
77 files changed, 12618 insertions, 4509 deletions
diff --git a/docs/reference/libtracker-sparql/libtracker-sparql-docs.sgml b/docs/reference/libtracker-sparql/libtracker-sparql-docs.sgml
index fb0e51775..0f76996df 100644
--- a/docs/reference/libtracker-sparql/libtracker-sparql-docs.sgml
+++ b/docs/reference/libtracker-sparql/libtracker-sparql-docs.sgml
@@ -32,6 +32,7 @@
<xi:include href="xml/tracker-namespace-manager.xml"/>
<xi:include href="xml/tracker-sparql-builder.xml"/>
<xi:include href="xml/tracker-sparql-connection.xml"/>
+ <xi:include href="xml/tracker-sparql-statement.xml"/>
<xi:include href="xml/tracker-sparql-cursor.xml"/>
<xi:include href="xml/tracker-notifier.xml"/>
<xi:include href="xml/tracker-misc.xml"/>
diff --git a/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt b/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt
index faf73b3b2..5f7899a10 100644
--- a/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt
+++ b/docs/reference/libtracker-sparql/libtracker-sparql-sections.txt
@@ -143,6 +143,7 @@ tracker_sparql_connection_remote_new
tracker_sparql_connection_query
tracker_sparql_connection_query_async
tracker_sparql_connection_query_finish
+tracker_sparql_connection_query_statement
tracker_sparql_connection_update
tracker_sparql_connection_update_async
tracker_sparql_connection_update_finish
@@ -185,6 +186,30 @@ tracker_sparql_error_quark
tracker_sparql_connection_construct
</SECTION>
+<SECTION>
+<FILE>tracker-sparql-statement</FILE>
+<TITLE>TrackerSparqlStatement</TITLE>
+TrackerSparqlStatement
+tracker_sparql_statement_execute
+tracker_sparql_statement_execute_async
+tracker_sparql_statement_execute_finish
+tracker_sparql_statement_bind_int
+tracker_sparql_statement_bind_double
+tracker_sparql_statement_bind_string
+tracker_sparql_statement_bind_boolean
+<SUBSECTION Standard>
+TrackerSparqlStatementClass
+TRACKER_SPARQL_STATEMENT
+TRACKER_SPARQL_STATEMENT_CLASS
+TRACKER_SPARQL_STATEMENT_GET_CLASS
+TRACKER_SPARQL_IS_STATEMENT
+TRACKER_SPARQL_IS_STATEMENT_CLASS
+TRACKER_SPARQL_TYPE_STATEMENT
+tracker_sparql_statement_get_type
+<SUBSECTION Private>
+TrackerSparqlStatementPrivate
+tracker_sparql_statement_construct
+</SECTION>
<SECTION>
<FILE>tracker-sparql-cursor</FILE>
diff --git a/docs/reference/libtracker-sparql/libtracker-sparql.types b/docs/reference/libtracker-sparql/libtracker-sparql.types
index 162190a97..0b6587b7b 100644
--- a/docs/reference/libtracker-sparql/libtracker-sparql.types
+++ b/docs/reference/libtracker-sparql/libtracker-sparql.types
@@ -3,5 +3,6 @@ tracker_namespace_manager_get_type
tracker_sparql_builder_get_type
tracker_sparql_builder_state_get_type
tracker_sparql_connection_get_type
+tracker_sparql_statement_get_type
tracker_sparql_cursor_get_type
tracker_notifier_get_type
diff --git a/src/libtracker-common/tracker-utils.c b/src/libtracker-common/tracker-utils.c
index a9cb70643..c5a5721c7 100644
--- a/src/libtracker-common/tracker-utils.c
+++ b/src/libtracker-common/tracker-utils.c
@@ -236,3 +236,92 @@ tracker_utf8_truncate (const gchar *str,
return retv;
}
+
+static gboolean
+range_is_xdigit (const gchar *str,
+ gssize start,
+ gssize end)
+{
+ gssize i;
+
+ g_assert (end > start);
+
+ for (i = start; i < end; i++) {
+ if (!g_ascii_isxdigit (str[i]))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gunichar
+xdigit_to_unichar (const gchar *str,
+ gssize start,
+ gssize end)
+{
+ gunichar ch = 0;
+ gssize i;
+
+ g_assert (end > start);
+
+ for (i = start; i < end; i++) {
+ ch |= g_ascii_xdigit_value (str[i]);
+ if (i < end - 1)
+ ch <<= 4;
+ }
+
+ return ch;
+}
+
+/*
+ * tracker_unescape_unichars:
+ * @str: Input string
+ * @len: Length
+ *
+ * Unescapes \u and \U sequences into their respective unichars.
+ *
+ * Returns: a string with no \u nor \U sequences
+ */
+gchar *
+tracker_unescape_unichars (const gchar *str,
+ gssize len)
+{
+ GString *copy;
+ gunichar ch;
+ gssize i = 0;
+
+ if (len < 0)
+ len = strlen (str);
+
+ copy = g_string_new (NULL);
+
+ while (i < len) {
+ if (len - i >= 2 &&
+ str[i] == '\\' &&
+ g_ascii_tolower (str[i + 1]) != 'u') {
+ /* Not an unicode escape sequence */
+ g_string_append_c (copy, str[i]);
+ g_string_append_c (copy, str[i + 1]);
+ i += 2;
+ } if (len - i >= 6 &&
+ strncmp (&str[i], "\\u", 2) == 0 &&
+ range_is_xdigit (&str[i], 2, 6)) {
+ ch = xdigit_to_unichar (&str[i], 2, 6);
+ g_string_append_unichar (copy, ch);
+ i += 6;
+ continue;
+ } else if (len - i >= 10 &&
+ strncmp (&str[i], "\\U", 2) == 0 &&
+ range_is_xdigit (&str[i], 2, 10)) {
+ ch = xdigit_to_unichar (&str[i], 2, 10);
+ g_string_append_unichar (copy, ch);
+ i += 10;
+ continue;
+ } else {
+ g_string_append_c (copy, str[i]);
+ i++;
+ }
+ }
+
+ return g_string_free (copy, FALSE);
+}
diff --git a/src/libtracker-common/tracker-utils.h b/src/libtracker-common/tracker-utils.h
index 87ca7b395..2cb78e5ba 100644
--- a/src/libtracker-common/tracker-utils.h
+++ b/src/libtracker-common/tracker-utils.h
@@ -45,6 +45,8 @@ gchar * tracker_strhex (const guint8 *data,
gchar delimiter);
gchar * tracker_utf8_truncate (const gchar *str,
gsize max_size);
+gchar * tracker_unescape_unichars (const gchar *str,
+ gssize len);
G_END_DECLS
diff --git a/src/libtracker-data/meson.build b/src/libtracker-data/meson.build
index ae4487496..18f1a481e 100644
--- a/src/libtracker-data/meson.build
+++ b/src/libtracker-data/meson.build
@@ -3,9 +3,6 @@
# libtracker-sparql-query.
libtracker_data_vala = static_library('tracker-sparql-query',
'tracker-vala-namespace.vala',
- 'tracker-sparql-query.vala',
- 'tracker-sparql-expression.vala',
- 'tracker-sparql-pattern.vala',
'tracker-sparql-scanner.vala',
'tracker-turtle-reader.vala',
'../libtracker-common/libtracker-common.vapi',
@@ -58,6 +55,11 @@ libtracker_data = library('tracker-data',
'tracker-ontology.c',
'tracker-ontologies.c',
'tracker-property.c',
+ 'tracker-string-builder.c',
+ 'tracker-sparql-parser.c',
+ 'tracker-sparql-types.c',
+ 'tracker-sparql.c',
+ 'tracker-uuid.c',
tracker_common_enum_header,
tracker_data_enums[0],
tracker_data_enums[1],
diff --git a/src/libtracker-data/tracker-data-manager.c b/src/libtracker-data/tracker-data-manager.c
index 87c9b486f..c467096c5 100644
--- a/src/libtracker-data/tracker-data-manager.c
+++ b/src/libtracker-data/tracker-data-manager.c
@@ -48,6 +48,7 @@
#include "tracker-property.h"
#include "tracker-sparql-query.h"
#include "tracker-data-query.h"
+#include "tracker-sparql-parser.h"
#define RDF_PROPERTY TRACKER_PREFIX_RDF "Property"
#define RDF_TYPE TRACKER_PREFIX_RDF "type"
diff --git a/src/libtracker-data/tracker-data-query.c b/src/libtracker-data/tracker-data-query.c
index bce3e93a8..2694d6d0b 100644
--- a/src/libtracker-data/tracker-data-query.c
+++ b/src/libtracker-data/tracker-data-query.c
@@ -31,7 +31,7 @@
#include "tracker-db-interface-sqlite.h"
#include "tracker-db-manager.h"
#include "tracker-ontologies.h"
-#include "tracker-sparql-query.h"
+#include "tracker-sparql.h"
GPtrArray*
tracker_data_query_rdf_type (TrackerDataManager *manager,
@@ -130,23 +130,56 @@ tracker_data_query_resource_id (TrackerDataManager *manager,
return id;
}
+gchar *
+tracker_data_query_unused_uuid (TrackerDataManager *manager,
+ TrackerDBInterface *iface)
+{
+ TrackerDBCursor *cursor = NULL;
+ TrackerDBStatement *stmt;
+ GError *error = NULL;
+ gchar *uuid = NULL;
+
+ stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, &error,
+ "SELECT SparqlUUID()");
+
+ if (stmt) {
+ cursor = tracker_db_statement_start_cursor (stmt, &error);
+ g_object_unref (stmt);
+ }
+
+ if (cursor) {
+ if (tracker_db_cursor_iter_next (cursor, NULL, &error)) {
+ uuid = g_strdup (tracker_db_cursor_get_string (cursor, 0, NULL));
+ }
+
+ g_object_unref (cursor);
+ }
+
+ if (G_UNLIKELY (error)) {
+ g_critical ("Could not query resource ID: %s\n", error->message);
+ g_error_free (error);
+ }
+
+ return uuid;
+}
+
TrackerDBCursor *
tracker_data_query_sparql_cursor (TrackerDataManager *manager,
const gchar *query,
GError **error)
{
- TrackerSparqlQuery *sparql_query;
- TrackerDBCursor *cursor;
+ TrackerSparql *sparql_query;
+ TrackerSparqlCursor *cursor;
g_return_val_if_fail (query != NULL, NULL);
- sparql_query = tracker_sparql_query_new (manager, query);
+ sparql_query = tracker_sparql_new (manager, query);
- cursor = tracker_sparql_query_execute_cursor (sparql_query, error);
+ cursor = tracker_sparql_execute_cursor (sparql_query, NULL, error);
g_object_unref (sparql_query);
- return cursor;
+ return TRACKER_DB_CURSOR (cursor);
}
diff --git a/src/libtracker-data/tracker-data-query.h b/src/libtracker-data/tracker-data-query.h
index 1add89f91..3b2df21fd 100644
--- a/src/libtracker-data/tracker-data-query.h
+++ b/src/libtracker-data/tracker-data-query.h
@@ -37,6 +37,9 @@ G_BEGIN_DECLS
gint tracker_data_query_resource_id (TrackerDataManager *manager,
TrackerDBInterface *iface,
const gchar *uri);
+gchar *tracker_data_query_unused_uuid (TrackerDataManager *manager,
+ TrackerDBInterface *iface);
+
TrackerDBCursor *tracker_data_query_sparql_cursor (TrackerDataManager *manager,
const gchar *query,
GError **error);
diff --git a/src/libtracker-data/tracker-data-update.c b/src/libtracker-data/tracker-data-update.c
index 444e5efee..e687505bf 100644
--- a/src/libtracker-data/tracker-data-update.c
+++ b/src/libtracker-data/tracker-data-update.c
@@ -38,6 +38,7 @@
#include "tracker-ontologies.h"
#include "tracker-property.h"
#include "tracker-sparql-query.h"
+#include "tracker-sparql.h"
typedef struct _TrackerDataUpdateBuffer TrackerDataUpdateBuffer;
typedef struct _TrackerDataUpdateBufferResource TrackerDataUpdateBufferResource;
@@ -3687,7 +3688,7 @@ update_sparql (TrackerData *data,
GError **error)
{
GError *actual_error = NULL;
- TrackerSparqlQuery *sparql_query;
+ TrackerSparql *sparql_query;
GVariant *blank_nodes;
g_return_val_if_fail (update != NULL, NULL);
@@ -3698,8 +3699,8 @@ update_sparql (TrackerData *data,
return NULL;
}
- sparql_query = tracker_sparql_query_new_update (data->manager, update);
- blank_nodes = tracker_sparql_query_execute_update (sparql_query, blank, &actual_error);
+ sparql_query = tracker_sparql_new_update (data->manager, update);
+ blank_nodes = tracker_sparql_execute_update (sparql_query, blank, &actual_error);
g_object_unref (sparql_query);
if (actual_error) {
diff --git a/src/libtracker-data/tracker-data.h b/src/libtracker-data/tracker-data.h
index e25feb3f7..7188774e0 100644
--- a/src/libtracker-data/tracker-data.h
+++ b/src/libtracker-data/tracker-data.h
@@ -41,6 +41,8 @@
#include "tracker-ontologies.h"
#include "tracker-property.h"
#include "tracker-sparql-query.h"
+#include "tracker-sparql.h"
+#include "tracker-uuid.h"
#undef __LIBTRACKER_DATA_INSIDE__
diff --git a/src/libtracker-data/tracker-db-interface-sqlite.c b/src/libtracker-data/tracker-db-interface-sqlite.c
index 3f21ce00d..d0e8f7adf 100644
--- a/src/libtracker-data/tracker-db-interface-sqlite.c
+++ b/src/libtracker-data/tracker-db-interface-sqlite.c
@@ -55,6 +55,7 @@
#include "tracker-db-interface-sqlite.h"
#include "tracker-db-manager.h"
#include "tracker-data-enum-types.h"
+#include "tracker-uuid.h"
typedef struct {
TrackerDBStatement *head;
@@ -509,11 +510,11 @@ function_sparql_regex (sqlite3_context *context,
sqlite3_value *argv[])
{
gboolean ret;
- const gchar *text, *pattern, *flags;
+ const gchar *text, *pattern, *flags = "";
GRegexCompileFlags regex_flags;
GRegex *regex;
- if (argc != 3) {
+ if (argc != 2 && argc != 3) {
sqlite3_result_error (context, "Invalid argument count", -1);
return;
}
@@ -521,7 +522,9 @@ function_sparql_regex (sqlite3_context *context,
regex = sqlite3_get_auxdata (context, 1);
text = (gchar *)sqlite3_value_text (argv[0]);
- flags = (gchar *)sqlite3_value_text (argv[2]);
+
+ if (argc == 3)
+ flags = (gchar *)sqlite3_value_text (argv[2]);
if (regex == NULL) {
gchar *err_str;
@@ -1323,6 +1326,10 @@ function_sparql_checksum (sqlite3_context *context,
checksum = G_CHECKSUM_SHA1;
else if (g_ascii_strcasecmp (checksumstr, "sha256") == 0)
checksum = G_CHECKSUM_SHA256;
+#if GLIB_CHECK_VERSION (2, 51, 0)
+ else if (g_ascii_strcasecmp (checksumstr, "sha384") == 0)
+ checksum = G_CHECKSUM_SHA384;
+#endif
else if (g_ascii_strcasecmp (checksumstr, "sha512") == 0)
checksum = G_CHECKSUM_SHA512;
else {
@@ -1360,6 +1367,50 @@ stmt_step (sqlite3_stmt *stmt)
return result;
}
+static void
+function_sparql_uuid (sqlite3_context *context,
+ int argc,
+ sqlite3_value *argv[])
+{
+ gchar *uuid = NULL;
+ sqlite3_stmt *stmt;
+ sqlite3 *db;
+ gint result;
+
+ if (argc > 1) {
+ sqlite3_result_error (context, "Invalid argument count", -1);
+ return;
+ }
+
+ db = sqlite3_context_db_handle (context);
+
+ result = sqlite3_prepare_v2 (db, "SELECT ID FROM Resource WHERE Uri=?",
+ -1, &stmt, NULL);
+ if (result != SQLITE_OK) {
+ sqlite3_result_error (context, sqlite3_errstr (result), -1);
+ return;
+ }
+
+ do {
+ g_clear_pointer (&uuid, g_free);
+ uuid = tracker_generate_uuid ();
+
+ sqlite3_reset (stmt);
+ sqlite3_bind_text (stmt, 1, uuid, -1, SQLITE_TRANSIENT);
+ result = stmt_step (stmt);
+ } while (result == SQLITE_ROW);
+
+ sqlite3_finalize (stmt);
+
+ if (result != SQLITE_DONE) {
+ sqlite3_result_error (context, sqlite3_errstr (result), -1);
+ g_free (uuid);
+ return;
+ }
+
+ sqlite3_result_text (context, uuid, -1, g_free);
+}
+
static int
check_interrupt (void *user_data)
{
@@ -1396,7 +1447,7 @@ initialize_functions (TrackerDBInterface *db_interface)
{ "SparqlEncodeForUri", 1, SQLITE_ANY | SQLITE_DETERMINISTIC,
function_sparql_encode_for_uri },
/* Strings */
- { "SparqlRegex", 3, SQLITE_ANY | SQLITE_DETERMINISTIC,
+ { "SparqlRegex", -1, SQLITE_ANY | SQLITE_DETERMINISTIC,
function_sparql_regex },
{ "SparqlStringJoin", -1, SQLITE_ANY | SQLITE_DETERMINISTIC,
function_sparql_string_join },
@@ -1424,6 +1475,8 @@ initialize_functions (TrackerDBInterface *db_interface)
{ "SparqlFloor", 1, SQLITE_ANY | SQLITE_DETERMINISTIC,
function_sparql_floor },
{ "SparqlRand", 0, SQLITE_ANY, function_sparql_rand },
+ /* UUID */
+ { "SparqlUUID", 0, SQLITE_ANY, function_sparql_uuid },
};
for (i = 0; i < G_N_ELEMENTS (functions); i++) {
@@ -2668,6 +2721,49 @@ tracker_db_statement_bind_text (TrackerDBStatement *stmt,
}
void
+tracker_db_statement_bind_value (TrackerDBStatement *stmt,
+ int index,
+ const GValue *value)
+{
+ GType type;
+
+ g_return_if_fail (TRACKER_IS_DB_STATEMENT (stmt));
+
+ g_assert (!stmt->stmt_is_used);
+
+ tracker_db_interface_lock (stmt->db_interface);
+
+ type = G_VALUE_TYPE (value);
+
+ if (type == G_TYPE_INT) {
+ sqlite3_bind_int64 (stmt->stmt, index + 1, g_value_get_int (value));
+ } else if (type == G_TYPE_INT64) {
+ sqlite3_bind_int64 (stmt->stmt, index + 1, g_value_get_int64 (value));
+ } else if (type == G_TYPE_DOUBLE) {
+ sqlite3_bind_double (stmt->stmt, index + 1, g_value_get_double (value));
+ } else if (type == G_TYPE_FLOAT) {
+ sqlite3_bind_double (stmt->stmt, index + 1, g_value_get_float (value));
+ } else if (type == G_TYPE_STRING) {
+ sqlite3_bind_text (stmt->stmt, index + 1,
+ g_value_get_string (value), -1, SQLITE_TRANSIENT);
+ } else {
+ GValue dest = G_VALUE_INIT;
+
+ g_value_init (&dest, G_TYPE_STRING);
+
+ if (g_value_transform (value, &dest)) {
+ sqlite3_bind_text (stmt->stmt, index + 1,
+ g_value_get_string (&dest), -1, SQLITE_TRANSIENT);
+ g_value_unset (&dest);
+ } else {
+ g_assert_not_reached ();
+ }
+ }
+
+ tracker_db_interface_unlock (stmt->db_interface);
+}
+
+void
tracker_db_cursor_rewind (TrackerDBCursor *cursor)
{
TrackerDBInterface *iface;
diff --git a/src/libtracker-data/tracker-db-interface.h b/src/libtracker-data/tracker-db-interface.h
index 09e78b52e..287738afb 100644
--- a/src/libtracker-data/tracker-db-interface.h
+++ b/src/libtracker-data/tracker-db-interface.h
@@ -128,6 +128,9 @@ void tracker_db_statement_bind_null (TrackerDBS
void tracker_db_statement_bind_text (TrackerDBStatement *stmt,
int index,
const gchar *value);
+void tracker_db_statement_bind_value (TrackerDBStatement *stmt,
+ int index,
+ const GValue *value);
void tracker_db_statement_execute (TrackerDBStatement *stmt,
GError **error);
TrackerDBCursor * tracker_db_statement_start_cursor (TrackerDBStatement *stmt,
diff --git a/src/libtracker-data/tracker-sparql-expression.vala b/src/libtracker-data/tracker-sparql-expression.vala
deleted file mode 100644
index ed2a2c74a..000000000
--- a/src/libtracker-data/tracker-sparql-expression.vala
+++ /dev/null
@@ -1,1727 +0,0 @@
-/*
- * Copyright (C) 2008-2010, Nokia
- *
- * 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.
- */
-
-class Tracker.Sparql.Expression : Object {
- weak Query query;
-
- const int MAX_VARIABLES_FOR_IN = 20;
- const string XSD_NS = "http://www.w3.org/2001/XMLSchema#";
- const string FN_NS = "http://www.w3.org/2005/xpath-functions#";
- const string FTS_NS = "http://www.tracker-project.org/ontologies/fts#";
- const string TRACKER_NS = "http://www.tracker-project.org/ontologies/tracker#";
-
- enum TimeFormatType {
- SECONDS,
- MINUTES,
- HOURS
- }
-
- string? fts_sql;
-
- Data.Manager manager;
-
- public Expression (Query query) {
- this.query = query;
- this.manager = query.manager;
- }
-
- Context context {
- get { return query.context; }
- }
-
- Pattern pattern {
- get { return query.pattern; }
- }
-
- private inline bool next () throws Sparql.Error {
- return query.next ();
- }
-
- private inline SparqlTokenType current () {
- return query.current ();
- }
-
- private inline SparqlTokenType last () {
- return query.last ();
- }
-
- private inline bool accept (SparqlTokenType type) throws Sparql.Error {
- return query.accept (type);
- }
-
- private Sparql.Error get_error (string msg) {
- return query.get_error (msg);
- }
-
- private bool expect (SparqlTokenType type) throws Sparql.Error {
- return query.expect (type);
- }
-
- private string get_last_string (int strip = 0) {
- return query.get_last_string (strip);
- }
-
- private string escape_sql_string_literal (string literal) {
- return "'%s'".printf (string.joinv ("''", literal.split ("'")));
- }
-
- private bool maybe_numeric (PropertyType type) {
- return (type == PropertyType.INTEGER || type == PropertyType.DOUBLE || type == PropertyType.DATE || type == PropertyType.DATETIME || type == PropertyType.UNKNOWN);
- }
-
- private void append_collate (StringBuilder sql) {
- sql.append_printf (" COLLATE %s", COLLATION_NAME);
- }
-
- private void skip_bracketted_expression () throws Sparql.Error {
- expect (SparqlTokenType.OPEN_PARENS);
- while (true) {
- switch (current ()) {
- case SparqlTokenType.OPEN_PARENS:
- // skip nested bracketted expression
- skip_bracketted_expression ();
- continue;
- case SparqlTokenType.CLOSE_PARENS:
- case SparqlTokenType.EOF:
- break;
- default:
- next ();
- continue;
- }
- break;
- }
- expect (SparqlTokenType.CLOSE_PARENS);
- }
-
- internal void skip_select_variables () throws Sparql.Error {
- while (true) {
- switch (current ()) {
- case SparqlTokenType.OPEN_PARENS:
- skip_bracketted_expression ();
- continue;
- case SparqlTokenType.FROM:
- case SparqlTokenType.WHERE:
- case SparqlTokenType.OPEN_BRACE:
- case SparqlTokenType.GROUP:
- case SparqlTokenType.ORDER:
- case SparqlTokenType.LIMIT:
- case SparqlTokenType.OFFSET:
- case SparqlTokenType.EOF:
- break;
- default:
- next ();
- continue;
- }
- break;
- }
- }
-
- internal PropertyType translate_select_expression (StringBuilder sql, bool subquery, int variable_index) throws Sparql.Error {
- Variable variable = null;
- bool expect_close_parens = false;
- bool as_handled = false;
-
- long begin = sql.len;
- var type = PropertyType.UNKNOWN;
- if (current () == SparqlTokenType.VAR) {
- type = translate_expression (sql);
- // we need variable name in case of compositional subqueries
- variable = context.get_variable (get_last_string ().substring (1));
-
- if (variable.binding == null) {
- throw get_error ("use of undefined variable `%s'".printf (variable.name));
- }
- } else if (accept (SparqlTokenType.OPEN_PARENS)) {
- if (current () == SparqlTokenType.SELECT) {
- // no parenthesis around expression
- // deprecated but supported for backward compatibility
- sql.append ("(");
- var select_context = pattern.translate_select (sql, true, true);
- sql.append (")");
-
- expect (SparqlTokenType.CLOSE_PARENS);
- type = select_context.type;
- } else {
- type = translate_expression (sql);
- if (accept (SparqlTokenType.CLOSE_PARENS)) {
- // missing AS
- // deprecated but supported for backward compatibility
- } else {
- // syntax from SPARQL 1.1 Draft
- // (Expression AS Var)
- expect_close_parens = true;
- }
- }
- } else {
- // no parenthesis around expression
- // deprecated but supported for backward compatibility
- type = translate_expression (sql);
- }
-
- if (!subquery) {
- convert_expression_to_string (sql, type, begin);
- }
-
- if (accept (SparqlTokenType.AS)) {
- // (...) AS ?foo
- expect (SparqlTokenType.VAR);
- variable = context.get_variable (get_last_string ().substring (1));
- sql.append_printf (" AS %s", variable.sql_expression);
- as_handled = true;
-
- if (subquery) {
- var binding = new VariableBinding ();
- binding.data_type = type;
- binding.variable = variable;
- binding.sql_expression = variable.sql_expression;
- pattern.add_variable_binding (new StringBuilder (), binding, VariableState.BOUND);
- }
- }
-
- if (pattern.fts_subject != null) {
- if (variable == null) {
- // FTS matches still need aliases as the outer MATCH query
- // will fetch futher values from the joined select
- variable = context.get_variable ("var%d".printf (variable_index + 1));
- }
-
- if (fts_sql == null) {
- pattern.fts_variables += variable.sql_expression;
-
- if (as_handled == false) {
- sql.append_printf (" AS %s", variable.sql_expression);
- }
- } else {
- pattern.fts_variables += fts_sql;
- pattern.queries_fts_data = true;
- }
- }
-
- if (expect_close_parens) {
- expect (SparqlTokenType.CLOSE_PARENS);
- }
-
- if (variable != null) {
- int state = context.var_set.lookup (variable);
- if (state == 0) {
- state = VariableState.BOUND;
- }
- context.select_var_set.insert (variable, state);
-
- ((SelectContext) context).variable_names += variable.name;
- } else {
- ((SelectContext) context).variable_names += "var%d".printf (variable_index + 1);
- }
-
- fts_sql = null;
-
- return type;
- }
-
- private void translate_expression_as_order_condition (StringBuilder sql) throws Sparql.Error {
- long begin = sql.len;
- if (translate_expression (sql) == PropertyType.RESOURCE) {
- // ID => Uri
- sql.insert (begin, "(SELECT Uri FROM Resource WHERE ID = ");
- sql.append (")");
- }
- }
-
- internal void translate_order_condition (StringBuilder sql) throws Sparql.Error {
- if (accept (SparqlTokenType.ASC)) {
- translate_expression_as_order_condition (sql);
- sql.append (" ASC");
- } else if (accept (SparqlTokenType.DESC)) {
- translate_expression_as_order_condition (sql);
- sql.append (" DESC");
- } else {
- translate_expression_as_order_condition (sql);
- }
- }
-
- private void translate_bound_call (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.BOUND);
- expect (SparqlTokenType.OPEN_PARENS);
- sql.append ("(");
- translate_expression (sql);
- sql.append (" IS NOT NULL)");
- expect (SparqlTokenType.CLOSE_PARENS);
- }
-
- private PropertyType translate_if_call (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.IF);
- expect (SparqlTokenType.OPEN_PARENS);
-
- // condition
- sql.append ("(CASE ");
- translate_expression (sql);
-
- // if condition is true
- sql.append (" WHEN 1 THEN ");
- expect (SparqlTokenType.COMMA);
- var type = translate_expression (sql);
-
- // if condition is false
- sql.append (" WHEN 0 THEN ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
-
- sql.append (" ELSE NULL END)");
-
- expect (SparqlTokenType.CLOSE_PARENS);
-
- return type;
- }
-
- private void translate_regex (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.REGEX);
- expect (SparqlTokenType.OPEN_PARENS);
- sql.append ("SparqlRegex(");
- translate_expression_as_string (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- // SQLite's sqlite3_set_auxdata doesn't work correctly with bound
- // strings for the regex in function_sparql_regex.
- // translate_expression (sql);
- sql.append (escape_sql_string_literal (parse_string_literal ()));
- sql.append (", ");
- if (accept (SparqlTokenType.COMMA)) {
- // Same as above
- // translate_expression (sql);
- sql.append (escape_sql_string_literal (parse_string_literal ()));
- } else {
- sql.append ("''");
- }
- sql.append (")");
- expect (SparqlTokenType.CLOSE_PARENS);
- }
-
- private void translate_exists (StringBuilder sql) throws Sparql.Error {
- sql.append ("(");
- pattern.translate_exists (sql);
- sql.append (")");
- }
-
- internal static void append_expression_as_string (StringBuilder sql, string expression, PropertyType type) {
- long begin = sql.len;
- sql.append (expression);
- convert_expression_to_string (sql, type, begin);
- }
-
- private static void convert_expression_to_string (StringBuilder sql, PropertyType type, long begin) {
- switch (type) {
- case PropertyType.STRING:
- case PropertyType.INTEGER:
- // nothing to convert
- // do not use CAST to convert integers to strings as this breaks use
- // of index when sorting by variable introduced in select expression
- break;
- case PropertyType.RESOURCE:
- // ID => Uri
- sql.insert (begin, "(SELECT Uri FROM Resource WHERE ID = ");
- sql.append (")");
- break;
- case PropertyType.BOOLEAN:
- // 0/1 => false/true
- sql.insert (begin, "CASE ");
- sql.append (" WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE NULL END");
- break;
- case PropertyType.DATE:
- // ISO 8601 format
- sql.insert (begin, "strftime (\"%Y-%m-%d\", ");
- sql.append (", \"unixepoch\")");
- break;
- case PropertyType.DATETIME:
- // ISO 8601 format
- sql.insert (begin, "SparqlFormatTime (");
- sql.append (")");
- break;
- default:
- // let sqlite convert the expression to string
- sql.insert (begin, "CAST (");
- sql.append (" AS TEXT)");
- break;
- }
- }
-
- private void translate_expression_as_string (StringBuilder sql) throws Sparql.Error {
- switch (current ()) {
- case SparqlTokenType.IRI_REF:
- case SparqlTokenType.PN_PREFIX:
- case SparqlTokenType.COLON:
- // handle IRI literals separately as it wouldn't work for unknown IRIs otherwise
- var binding = new LiteralBinding ();
- bool is_var;
- binding.literal = pattern.parse_var_or_term (null, out is_var);
- if (accept (SparqlTokenType.OPEN_PARENS)) {
- // function call
- long begin = sql.len;
- var type = translate_function (sql, binding.literal);
- expect (SparqlTokenType.CLOSE_PARENS);
- convert_expression_to_string (sql, type, begin);
- } else {
- sql.append ("?");
- query.bindings.append (binding);
- }
- break;
- default:
- long begin = sql.len;
- var type = translate_expression (sql);
- convert_expression_to_string (sql, type, begin);
- break;
- }
- }
-
- private void translate_str (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.STR);
- expect (SparqlTokenType.OPEN_PARENS);
-
- translate_expression_as_string (sql);
-
- expect (SparqlTokenType.CLOSE_PARENS);
- }
-
- private void translate_isuri (StringBuilder sql) throws Sparql.Error {
- if (!accept (SparqlTokenType.ISURI)) {
- expect (SparqlTokenType.ISIRI);
- }
-
- expect (SparqlTokenType.OPEN_PARENS);
-
- sql.append ("?");
- var new_binding = new LiteralBinding ();
- new_binding.data_type = PropertyType.INTEGER;
-
- if (current() == SparqlTokenType.IRI_REF) {
- new_binding.literal = "1";
- next ();
- } else if (translate_expression (new StringBuilder ()) == PropertyType.RESOURCE) {
- new_binding.literal = "1";
- } else {
- new_binding.literal = "0";
- }
-
- query.bindings.append (new_binding);
-
- expect (SparqlTokenType.CLOSE_PARENS);
- }
-
- private void translate_datatype (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.DATATYPE);
- expect (SparqlTokenType.OPEN_PARENS);
-
- if (accept (SparqlTokenType.VAR)) {
- string variable_name = get_last_string().substring(1);
- var variable = context.get_variable (variable_name);
-
- if (variable.binding == null) {
- throw get_error ("`%s' is not a valid variable".printf (variable.name));
- }
-
- if (variable.binding.data_type == PropertyType.RESOURCE || variable.binding.type == null) {
- throw get_error ("Invalid FILTER");
- }
-
- sql.append ("(SELECT ID FROM Resource WHERE Uri = ?)");
-
- var new_binding = new LiteralBinding ();
- new_binding.literal = variable.binding.type.uri;
- query.bindings.append (new_binding);
-
- } else {
- throw get_error ("Invalid FILTER");
- }
-
- expect (SparqlTokenType.CLOSE_PARENS);
- }
-
- private void translate_date (StringBuilder sql, string format) throws Sparql.Error {
- sql.append_printf ("strftime (\"%s\", ", format);
-
- if (accept (SparqlTokenType.VAR)) {
- string variable_name = get_last_string ().substring (1);
- var variable = context.get_variable (variable_name);
- sql.append (variable.get_extra_sql_expression ("localDate"));
- sql.append (" * 24 * 3600");
- } else {
- translate_primary_expression (sql);
- }
-
- sql.append (", \"unixepoch\")");
- }
-
- private void translate_time (StringBuilder sql, TimeFormatType type) throws Sparql.Error {
- sql.append ("(");
- if (accept (SparqlTokenType.VAR)) {
- string variable_name = get_last_string ().substring (1);
- var variable = context.get_variable (variable_name);
- sql.append (variable.get_extra_sql_expression ("localTime"));
- } else {
- translate_primary_expression (sql);
- }
-
- switch (type) {
- case TimeFormatType.SECONDS:
- sql.append ("% 60");
- break;
- case TimeFormatType.MINUTES:
- sql.append (" / 60 % 60");
- break;
- case TimeFormatType.HOURS:
- sql.append (" / 3600 % 24");
- break;
- }
- sql.append (")");
- }
-
- private PropertyType translate_function (StringBuilder sql, string uri) throws Sparql.Error {
- if (uri == XSD_NS + "string") {
- // conversion to string
- translate_expression_as_string (sql);
-
- return PropertyType.STRING;
- } else if (uri == XSD_NS + "integer") {
- // conversion to integer
- sql.append ("CAST (");
- translate_expression_as_string (sql);
- sql.append (" AS INTEGER)");
-
- return PropertyType.INTEGER;
- } else if (uri == XSD_NS + "double") {
- // conversion to double
- sql.append ("CAST (");
- translate_expression_as_string (sql);
- sql.append (" AS REAL)");
-
- return PropertyType.DOUBLE;
- } else if (uri == TRACKER_NS + "case-fold") {
- // conversion to string
- sql.append ("SparqlCaseFold (");
- translate_expression_as_string (sql);
- sql.append (")");
- return PropertyType.STRING;
- } else if (uri == TRACKER_NS + "ascii-lower-case") {
- // conversion to string
- sql.append ("lower (");
- translate_expression_as_string (sql);
- sql.append (")");
- return PropertyType.STRING;
- } else if (uri == TRACKER_NS + "title-order") {
- translate_expression_as_string (sql);
- sql.append_printf (" COLLATE %s", TITLE_COLLATION_NAME);
- return PropertyType.STRING;
- } else if (uri == FN_NS + "lower-case") {
- // conversion to string
- sql.append ("SparqlLowerCase (");
- translate_expression_as_string (sql);
- sql.append (")");
- return PropertyType.STRING;
- } else if (uri == FN_NS + "upper-case") {
- sql.append ("SparqlUpperCase (");
- translate_expression_as_string (sql);
- sql.append (")");
- return PropertyType.STRING;
- } else if (uri == TRACKER_NS + "normalize") {
- // conversion to string
- sql.append ("SparqlNormalize (");
- translate_expression_as_string (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression_as_string (sql);
- sql.append (")");
- return PropertyType.STRING;
- } else if (uri == TRACKER_NS + "unaccent") {
- // conversion to string
- sql.append ("SparqlUnaccent (");
- translate_expression_as_string (sql);
- sql.append (")");
- return PropertyType.STRING;
- } else if (uri == FN_NS + "contains") {
- // fn:contains('A','B') => 'A' GLOB '*B*'
- sql.append ("(");
- translate_expression_as_string (sql);
- sql.append (" GLOB ");
- expect (SparqlTokenType.COMMA);
-
- sql.append ("?");
- var binding = new LiteralBinding ();
- binding.literal = "*%s*".printf (parse_string_literal ());
- query.bindings.append (binding);
-
- sql.append (")");
-
- return PropertyType.BOOLEAN;
- } else if (uri == FN_NS + "starts-with") {
- // fn:starts-with('A','B') => 'A' BETWEEN 'B' AND 'B\u0010fffd'
- // 0010fffd always sorts last
-
- translate_expression_as_string (sql);
- sql.append (" BETWEEN ");
-
- expect (SparqlTokenType.COMMA);
- string prefix = parse_string_literal ();
-
- sql.append ("?");
- var binding = new LiteralBinding ();
- binding.literal = prefix;
- query.bindings.append (binding);
-
- sql.append (" AND ");
-
- sql.append ("?");
- binding = new LiteralBinding ();
- binding.literal = prefix + COLLATION_LAST_CHAR.to_string ();
- query.bindings.append (binding);
-
- return PropertyType.BOOLEAN;
- } else if (uri == FN_NS + "ends-with") {
- // fn:ends-with('A','B') => 'A' GLOB '*B'
- sql.append ("(");
- translate_expression_as_string (sql);
- sql.append (" GLOB ");
- expect (SparqlTokenType.COMMA);
-
- sql.append ("?");
- var binding = new LiteralBinding ();
- binding.literal = "*%s".printf (parse_string_literal ());
- query.bindings.append (binding);
-
- sql.append (")");
-
- return PropertyType.BOOLEAN;
- } else if (uri == FN_NS + "substring") {
- sql.append ("substr(");
- translate_expression_as_string (sql);
-
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression_as_string (sql);
-
- if (accept (SparqlTokenType.COMMA)) {
- sql.append (", ");
- translate_expression_as_string (sql);
- }
-
- sql.append (")");
-
- return PropertyType.STRING;
- } else if (uri == FN_NS + "concat") {
- translate_expression_as_string (sql);
- sql.append ("||");
- expect (SparqlTokenType.COMMA);
- translate_expression_as_string (sql);
- while (accept (SparqlTokenType.COMMA)) {
- sql.append ("||");
- translate_expression_as_string (sql);
- }
-
- return PropertyType.STRING;
- } else if (uri == FN_NS + "string-join") {
- sql.append ("SparqlStringJoin(");
- expect (SparqlTokenType.OPEN_PARENS);
-
- translate_expression_as_string (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression_as_string (sql);
- while (accept (SparqlTokenType.COMMA)) {
- sql.append (", ");
- translate_expression_as_string (sql);
- }
-
- expect (SparqlTokenType.CLOSE_PARENS);
- sql.append (",");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (")");
-
- return PropertyType.STRING;
- } else if (uri == FN_NS + "year-from-dateTime") {
- translate_date (sql, "%Y");
- return PropertyType.INTEGER;
- } else if (uri == FN_NS + "month-from-dateTime") {
- translate_date (sql, "%m");
- return PropertyType.INTEGER;
- } else if (uri == FN_NS + "day-from-dateTime") {
- translate_date (sql, "%d");
- return PropertyType.INTEGER;
- } else if (uri == FN_NS + "hours-from-dateTime") {
- translate_time (sql, TimeFormatType.HOURS);
- return PropertyType.INTEGER;
- } else if (uri == FN_NS + "minutes-from-dateTime") {
- translate_time (sql, TimeFormatType.MINUTES);
- return PropertyType.INTEGER;
- } else if (uri == FN_NS + "seconds-from-dateTime") {
- translate_time (sql, TimeFormatType.SECONDS);
- return PropertyType.INTEGER;
- } else if (uri == FN_NS + "timezone-from-dateTime") {
- expect (SparqlTokenType.VAR);
- string variable_name = get_last_string ().substring (1);
- var variable = context.get_variable (variable_name);
-
- sql.append ("(");
- sql.append (variable.get_extra_sql_expression ("localDate"));
- sql.append (" * 24 * 3600 + ");
- sql.append (variable.get_extra_sql_expression ("localTime"));
- sql.append ("- ");
- sql.append ("CAST (");
- sql.append (variable.sql_expression);
- sql.append (" AS INTEGER)");
- sql.append (")");
-
- return PropertyType.INTEGER;
- } else if (uri == FN_NS + "replace") {
- sql.append ("SparqlReplace(");
- translate_expression_as_string (sql);
- sql.append (", ");
-
- expect (SparqlTokenType.COMMA);
- translate_expression_as_string (sql);
- sql.append (", ");
-
- expect (SparqlTokenType.COMMA);
- translate_expression_as_string (sql);
-
- if (accept (SparqlTokenType.COMMA)) {
- sql.append (", ");
- sql.append (escape_sql_string_literal (parse_string_literal ()));
- }
- sql.append (")");
- return PropertyType.STRING;
- } else if (uri == FTS_NS + "rank") {
- bool is_var;
- string v = pattern.parse_var_or_term (null, out is_var);
- sql.append_printf ("\"%s_u_rank\"", v);
-
- return PropertyType.DOUBLE;
- } else if (uri == FTS_NS + "offsets") {
- bool is_var;
- string v = pattern.parse_var_or_term (null, out is_var);
- var variable = context.get_variable (v);
-
- sql.append (variable.sql_expression);
- fts_sql = "tracker_offsets(\"fts5\")";
- return PropertyType.STRING;
- } else if (uri == FTS_NS + "snippet") {
- bool is_var;
-
- string v = pattern.parse_var_or_term (null, out is_var);
- var variable = context.get_variable (v);
- var fts = new StringBuilder ();
-
- fts.append_printf ("snippet(\"fts5\"");
-
- // lookup column
- fts.append (", -1");
-
- // "start match" text
- if (accept (SparqlTokenType.COMMA)) {
- fts.append (", ");
- translate_expression_as_string (fts);
-
- // "end match" text
- expect (SparqlTokenType.COMMA);
- fts.append (", ");
- translate_expression_as_string (fts);
- } else {
- fts.append(",'',''");
- }
-
- // "ellipsis" text
- if (accept (SparqlTokenType.COMMA)) {
- fts.append (", ");
- translate_expression_as_string (fts);
- } else {
- fts.append (", '...'");
- }
-
- // Approximate number of words in context
- if (accept (SparqlTokenType.COMMA)) {
- fts.append (", ");
- translate_expression_as_string (fts);
- } else {
- fts.append (", 5");
- }
-
- fts.append (")");
-
- fts_sql = fts.str;
- sql.append (variable.sql_expression);
- return PropertyType.STRING;
- } else if (uri == TRACKER_NS + "id") {
- var type = translate_expression (sql);
- if (type != PropertyType.RESOURCE) {
- throw get_error ("expected resource");
- }
-
- return PropertyType.INTEGER;
- } else if (uri == TRACKER_NS + "uri") {
- var type = translate_expression (sql);
- if (type != PropertyType.INTEGER) {
- throw get_error ("expected integer ID");
- }
-
- return PropertyType.RESOURCE;
- } else if (uri == TRACKER_NS + "cartesian-distance") {
- sql.append ("SparqlCartesianDistance(");
- translate_expression (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (")");
-
- return PropertyType.DOUBLE;
- } else if (uri == TRACKER_NS + "haversine-distance") {
- sql.append ("SparqlHaversineDistance(");
- translate_expression (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (")");
-
- return PropertyType.DOUBLE;
- } else if (uri == TRACKER_NS + "coalesce") {
- sql.append ("COALESCE(");
- translate_expression_as_string (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- translate_expression_as_string (sql);
- while (accept (SparqlTokenType.COMMA)) {
- sql.append (", ");
- translate_expression_as_string (sql);
- }
- sql.append (")");
-
- return PropertyType.STRING;
- } else if (uri == TRACKER_NS + "uri-is-parent") {
- sql.append ("SparqlUriIsParent(");
- translate_expression_as_string (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
-
- translate_expression_as_string (sql);
- sql.append (")");
-
- return PropertyType.BOOLEAN;
- } else if (uri == TRACKER_NS + "uri-is-descendant") {
- sql.append ("SparqlUriIsDescendant(");
- translate_expression_as_string (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
-
- translate_expression_as_string (sql);
- while (accept (SparqlTokenType.COMMA)) {
- sql.append (", ");
- translate_expression_as_string (sql);
- }
- sql.append (")");
-
- return PropertyType.BOOLEAN;
- } else if (uri == TRACKER_NS + "string-from-filename") {
- sql.append ("SparqlStringFromFilename(");
- translate_expression_as_string (sql);
- sql.append (")");
-
- return PropertyType.STRING;
- } else {
- // support properties as functions
- var ontologies = manager.get_ontologies ();
- var prop = ontologies.get_property_by_uri (uri);
- if (prop == null) {
- throw get_error ("Unknown function");
- }
-
- var expr = new StringBuilder ();
- translate_expression (expr);
-
- string value_separator = ",";
- string? graph_separator = null;
-
- if (accept (SparqlTokenType.COMMA)) {
- value_separator = parse_string_literal ();
-
- if (accept (SparqlTokenType.COMMA)) {
- graph_separator = parse_string_literal ();
- }
- }
-
- if (prop.multiple_values) {
- // multi-valued property
- sql.append ("(SELECT GROUP_CONCAT(");
- long begin = sql.len;
- sql.append_printf ("\"%s\"", prop.name);
- convert_expression_to_string (sql, prop.data_type, begin);
- if (graph_separator != null) {
- sql.append_printf (" || %s || COALESCE((SELECT Uri FROM Resource WHERE ID = \"%s:graph\"), '')", escape_sql_string_literal (graph_separator), prop.name);
- }
- sql.append_printf (",%s)", escape_sql_string_literal (value_separator));
- sql.append_printf (" FROM \"%s\" WHERE ID = %s)", prop.table_name, expr.str);
-
- return PropertyType.STRING;
- } else {
- // single-valued property
- if (graph_separator == null) {
- sql.append_printf ("(SELECT \"%s\" FROM \"%s\" WHERE ID = %s)", prop.name, prop.table_name, expr.str);
-
- if (prop.data_type == PropertyType.STRING) {
- append_collate (sql);
- }
-
- return prop.data_type;
- } else {
- sql.append ("(SELECT ");
- long begin = sql.len;
- sql.append_printf ("\"%s\"", prop.name);
- convert_expression_to_string (sql, prop.data_type, begin);
- sql.append_printf (" || %s || COALESCE((SELECT Uri FROM Resource WHERE ID = \"%s:graph\"), '')", escape_sql_string_literal (graph_separator), prop.name);
- sql.append_printf (" FROM \"%s\" WHERE ID = %s)", prop.table_name, expr.str);
-
- return PropertyType.STRING;
- }
- }
- }
- }
-
- private PropertyType parse_type_uri () throws Sparql.Error {
- string type_iri;
- PropertyType type;
-
- if (accept (SparqlTokenType.IRI_REF)) {
- type_iri = get_last_string (1);
- } else if (accept (SparqlTokenType.PN_PREFIX)) {
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- type_iri = query.resolve_prefixed_name (ns, get_last_string ().substring (1));
- } else {
- expect (SparqlTokenType.COLON);
- type_iri = query.resolve_prefixed_name ("", get_last_string ().substring (1));
- }
-
- if (type_iri == XSD_NS + "boolean") {
- type = PropertyType.BOOLEAN;
- } else if (type_iri == XSD_NS + "integer" ||
- type_iri == XSD_NS + "nonPositiveInteger" ||
- type_iri == XSD_NS + "negativeInteger" ||
- type_iri == XSD_NS + "long" ||
- type_iri == XSD_NS + "int" ||
- type_iri == XSD_NS + "short" ||
- type_iri == XSD_NS + "byte" ||
- type_iri == XSD_NS + "nonNegativeInteger" ||
- type_iri == XSD_NS + "unsignedLong" ||
- type_iri == XSD_NS + "unsignedInt" ||
- type_iri == XSD_NS + "unsignedShort" ||
- type_iri == XSD_NS + "unsignedByte" ||
- type_iri == XSD_NS + "positiveInteger") {
- type = PropertyType.INTEGER;
- } else if (type_iri == XSD_NS + "double") {
- type = PropertyType.DOUBLE;
- } else if (type_iri == XSD_NS + "date") {
- type = PropertyType.DATE;
- } else if (type_iri == XSD_NS + "dateTime") {
- type = PropertyType.DATETIME;
- } else {
- type = PropertyType.STRING;
- }
-
- return type;
- }
-
- internal string parse_string_literal (out PropertyType type = null) throws Sparql.Error {
- type = PropertyType.STRING;
-
- next ();
- switch (last ()) {
- case SparqlTokenType.STRING_LITERAL1:
- case SparqlTokenType.STRING_LITERAL2:
- var sb = new StringBuilder ();
-
- string s = get_last_string (1);
- string* p = s;
- string* end = p + s.length;
- while ((long) p < (long) end) {
- string* q = Posix.strchr (p, '\\');
- if (q == null) {
- sb.append_len (p, (long) (end - p));
- p = end;
- } else {
- sb.append_len (p, (long) (q - p));
- p = q + 1;
- switch (((char*) p)[0]) {
- case '\'':
- case '"':
- case '\\':
- sb.append_c (((char*) p)[0]);
- break;
- case 'b':
- sb.append_c ('\b');
- break;
- case 'f':
- sb.append_c ('\f');
- break;
- case 'n':
- sb.append_c ('\n');
- break;
- case 'r':
- sb.append_c ('\r');
- break;
- case 't':
- sb.append_c ('\t');
- break;
- case 'u':
- char* ptr = (char*) p + 1;
- unichar c = (((unichar) ptr[0].xdigit_value () * 16 + ptr[1].xdigit_value ()) * 16 + ptr[2].xdigit_value ()) * 16 + ptr[3].xdigit_value ();
- sb.append_unichar (c);
- p += 4;
- break;
- }
- p++;
- }
- }
-
- if (accept (SparqlTokenType.DOUBLE_CIRCUMFLEX)) {
- // typed literal
- type = parse_type_uri ();
- }
-
- return sb.str;
- case SparqlTokenType.STRING_LITERAL_LONG1:
- case SparqlTokenType.STRING_LITERAL_LONG2:
- string result = get_last_string (3);
-
- if (accept (SparqlTokenType.DOUBLE_CIRCUMFLEX)) {
- // typed literal
- type = parse_type_uri ();
- }
-
- return result;
- default:
- throw get_error ("expected string literal");
- }
- }
-
- private PropertyType translate_uri_expression (StringBuilder sql, string uri) throws Sparql.Error {
- if (accept (SparqlTokenType.OPEN_PARENS)) {
- // function
- var result = translate_function (sql, uri);
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- } else {
- // resource
- sql.append ("COALESCE((SELECT ID FROM Resource WHERE Uri = ?), 0)");
- var binding = new LiteralBinding ();
- binding.literal = uri;
- query.bindings.append (binding);
- return PropertyType.RESOURCE;
- }
- }
-
- private PropertyType translate_primary_expression (StringBuilder sql) throws Sparql.Error {
- PropertyType type;
-
- switch (current ()) {
- case SparqlTokenType.OPEN_PARENS:
- return translate_bracketted_expression (sql);
- case SparqlTokenType.IRI_REF:
- next ();
- return translate_uri_expression (sql, get_last_string (1));
- case SparqlTokenType.DECIMAL:
- case SparqlTokenType.DOUBLE:
- next ();
-
- if (query.no_cache) {
- sql.append (get_last_string ());
- } else {
- sql.append ("?");
-
- var binding = new LiteralBinding ();
- binding.literal = get_last_string ();
- query.bindings.append (binding);
- }
-
- return PropertyType.DOUBLE;
- case SparqlTokenType.TRUE:
- next ();
-
- if (query.no_cache) {
- sql.append ("1");
- } else {
- sql.append ("?");
-
- var binding = new LiteralBinding ();
- binding.literal = "1";
- binding.data_type = PropertyType.INTEGER;
- query.bindings.append (binding);
- }
-
- return PropertyType.BOOLEAN;
- case SparqlTokenType.FALSE:
- next ();
-
- if (query.no_cache) {
- sql.append ("0");
- } else {
- sql.append ("?");
-
- var binding = new LiteralBinding ();
- binding.literal = "0";
- binding.data_type = PropertyType.INTEGER;
- query.bindings.append (binding);
- }
-
- return PropertyType.BOOLEAN;
- case SparqlTokenType.STRING_LITERAL1:
- case SparqlTokenType.STRING_LITERAL2:
- case SparqlTokenType.STRING_LITERAL_LONG1:
- case SparqlTokenType.STRING_LITERAL_LONG2:
- var literal = parse_string_literal (out type);
-
- switch (type) {
- case PropertyType.INTEGER:
- case PropertyType.BOOLEAN:
- case PropertyType.DOUBLE:
- case PropertyType.DATE:
- case PropertyType.DATETIME:
- if (query.no_cache) {
- sql.append (escape_sql_string_literal (literal));
- } else {
- var binding = new LiteralBinding ();
- binding.literal = literal;
- binding.data_type = type;
- query.bindings.append (binding);
- sql.append ("?");
- }
- return type;
- default:
- if (query.no_cache) {
- sql.append (escape_sql_string_literal (literal));
- } else {
- var binding = new LiteralBinding ();
- binding.literal = literal;
- query.bindings.append (binding);
- sql.append ("?");
- }
- append_collate (sql);
- return PropertyType.STRING;
- }
- case SparqlTokenType.INTEGER:
- next ();
-
- if (query.no_cache) {
- sql.append (get_last_string ());
- } else {
- sql.append ("?");
-
- var binding = new LiteralBinding ();
- binding.literal = get_last_string ();
- binding.data_type = PropertyType.INTEGER;
- query.bindings.append (binding);
- }
-
- return PropertyType.INTEGER;
- case SparqlTokenType.VAR:
- next ();
- string variable_name = get_last_string ().substring (1);
- var variable = context.get_variable (variable_name);
-
- if (context.need_binding_expression && variable.binding != null) {
- sql.append (variable.binding.sql_expression);
- } else {
- sql.append (variable.sql_expression);
- }
-
- if (variable.binding == null) {
- return PropertyType.UNKNOWN;
- } else {
- if (variable.binding.data_type == PropertyType.STRING) {
- append_collate (sql);
- }
- return variable.binding.data_type;
- }
- case SparqlTokenType.STR:
- translate_str (sql);
- return PropertyType.STRING;
- case SparqlTokenType.LANG:
- next ();
- sql.append ("''");
- return PropertyType.STRING;
- case SparqlTokenType.LANGMATCHES:
- next ();
- sql.append ("0");
- return PropertyType.BOOLEAN;
- case SparqlTokenType.DATATYPE:
- translate_datatype (sql);
- return PropertyType.RESOURCE;
- case SparqlTokenType.BOUND:
- translate_bound_call (sql);
- return PropertyType.BOOLEAN;
- case SparqlTokenType.COALESCE:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, TRACKER_NS + "coalesce");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.CONCAT:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "concat");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.CONTAINS:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "contains");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.ENCODE_FOR_URI:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- sql.append ("SparqlEncodeForUri (");
- translate_expression_as_string (sql);
- sql.append (")");
- expect (SparqlTokenType.CLOSE_PARENS);
- return PropertyType.STRING;
- case SparqlTokenType.IF:
- return translate_if_call (sql);
- case SparqlTokenType.SAMETERM:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- sql.append ("(");
- translate_expression (sql);
- sql.append (" = ");
- expect (SparqlTokenType.COMMA);
- translate_expression (sql);
- sql.append (")");
- expect (SparqlTokenType.CLOSE_PARENS);
- return PropertyType.BOOLEAN;
- case SparqlTokenType.ISIRI:
- case SparqlTokenType.ISURI:
- translate_isuri (sql);
- return PropertyType.BOOLEAN;
- case SparqlTokenType.ISBLANK:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- next ();
- // TODO: support ISBLANK properly
- sql.append ("0");
- expect (SparqlTokenType.CLOSE_PARENS);
- return PropertyType.BOOLEAN;
- case SparqlTokenType.ISLITERAL:
- next ();
- return PropertyType.BOOLEAN;
- case SparqlTokenType.LCASE:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "lower-case");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.UCASE:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "upper-case");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.STRLEN:
- next ();
- sql.append ("LENGTH(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return PropertyType.INTEGER;
- case SparqlTokenType.STRSTARTS:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "starts-with");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.STRENDS:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "ends-with");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.SUBSTR:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "substring");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.STRBEFORE:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- sql.append ("SparqlStringBefore (");
- translate_expression_as_string (sql);
- expect (SparqlTokenType.COMMA);
- sql.append (",");
- translate_expression_as_string (sql);
- sql.append (")");
- expect (SparqlTokenType.CLOSE_PARENS);
- return PropertyType.STRING;
- case SparqlTokenType.STRAFTER:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- sql.append ("SparqlStringAfter (");
- translate_expression_as_string (sql);
- expect (SparqlTokenType.COMMA);
- sql.append (",");
- translate_expression_as_string (sql);
- sql.append (")");
- expect (SparqlTokenType.CLOSE_PARENS);
- return PropertyType.STRING;
- case SparqlTokenType.REGEX:
- translate_regex (sql);
- query.no_cache = true;
- return PropertyType.BOOLEAN;
- case SparqlTokenType.EXISTS:
- case SparqlTokenType.NOT:
- translate_exists (sql);
- return PropertyType.BOOLEAN;
- case SparqlTokenType.COUNT:
- next ();
- sql.append ("COUNT(");
- translate_aggregate_expression (sql);
- sql.append (")");
- return PropertyType.INTEGER;
- case SparqlTokenType.SUM:
- next ();
- sql.append ("SUM(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.AVG:
- next ();
- sql.append ("AVG(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.MIN:
- next ();
- sql.append ("MIN(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.MAX:
- next ();
- sql.append ("MAX(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.ABS:
- next ();
- sql.append ("ABS(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.ROUND:
- next ();
- sql.append ("ROUND(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.CEIL:
- next ();
- sql.append ("SparqlCeil(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.FLOOR:
- next ();
- sql.append ("SparqlFloor(");
- type = translate_aggregate_expression (sql);
- sql.append (")");
- return type;
- case SparqlTokenType.RAND:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- expect (SparqlTokenType.CLOSE_PARENS);
- sql.append ("SparqlRand()");
- return PropertyType.DOUBLE;
- case SparqlTokenType.NOW:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- expect (SparqlTokenType.CLOSE_PARENS);
- sql.append ("strftime('%s', 'now')");
- return PropertyType.DATETIME;
- case SparqlTokenType.SECONDS:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "seconds-from-dateTime");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.MINUTES:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "minutes-from-dateTime");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.HOURS:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "hours-from-dateTime");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.DAY:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "day-from-dateTime");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.MONTH:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "month-from-dateTime");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.YEAR:
- next ();
- expect (SparqlTokenType.OPEN_PARENS);
- var result = translate_function (sql, FN_NS + "year-from-dateTime");
- expect (SparqlTokenType.CLOSE_PARENS);
- return result;
- case SparqlTokenType.MD5:
- next ();
- sql.append ("SparqlChecksum(");
- type = translate_aggregate_expression (sql);
- sql.append (", \"md5\")");
- return type;
- case SparqlTokenType.SHA1:
- next ();
- sql.append ("SparqlChecksum(");
- type = translate_aggregate_expression (sql);
- sql.append (", \"sha1\")");
- return type;
- case SparqlTokenType.SHA256:
- next ();
- sql.append ("SparqlChecksum(");
- type = translate_aggregate_expression (sql);
- sql.append (", \"sha256\")");
- return type;
- case SparqlTokenType.SHA512:
- next ();
- sql.append ("SparqlChecksum(");
- type = translate_aggregate_expression (sql);
- sql.append (", \"sha512\")");
- return type;
- case SparqlTokenType.GROUP_CONCAT:
- next ();
- sql.append ("GROUP_CONCAT(");
- expect (SparqlTokenType.OPEN_PARENS);
- translate_expression_as_string (sql);
- sql.append (", ");
- expect (SparqlTokenType.COMMA);
- sql.append (escape_sql_string_literal (parse_string_literal ()));
- sql.append (")");
- expect (SparqlTokenType.CLOSE_PARENS);
- return PropertyType.STRING;
- case SparqlTokenType.PN_PREFIX:
- next ();
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- string uri = query.resolve_prefixed_name (ns, get_last_string ().substring (1));
- return translate_uri_expression (sql, uri);
- case SparqlTokenType.COLON:
- next ();
- string uri = query.resolve_prefixed_name ("", get_last_string ().substring (1));
- return translate_uri_expression (sql, uri);
- default:
- throw get_error ("expected primary expression");
- }
- }
-
- PropertyType translate_unary_expression (StringBuilder sql) throws Sparql.Error {
- if (accept (SparqlTokenType.OP_NEG)) {
- sql.append ("NOT (");
- var optype = translate_primary_expression (sql);
- sql.append (")");
- if (optype != PropertyType.BOOLEAN) {
- throw get_error ("expected boolean expression");
- }
- return PropertyType.BOOLEAN;
- } else if (accept (SparqlTokenType.PLUS)) {
- return translate_primary_expression (sql);
- } else if (accept (SparqlTokenType.MINUS)) {
- sql.append ("-(");
- var optype = translate_primary_expression (sql);
- sql.append (")");
- return optype;
- }
- return translate_primary_expression (sql);
- }
-
- private PropertyType translate_multiplicative_expression (StringBuilder sql) throws Sparql.Error {
- long begin = sql.len;
- var optype = translate_unary_expression (sql);
- while (true) {
- if (accept (SparqlTokenType.STAR)) {
- if (!maybe_numeric (optype)) {
- throw get_error ("expected numeric operand");
- }
- sql.insert (begin, "(");
- sql.append (" * ");
- if (!maybe_numeric (translate_unary_expression (sql))) {
- throw get_error ("expected numeric operand");
- }
- sql.append (")");
- } else if (accept (SparqlTokenType.DIV)) {
- if (!maybe_numeric (optype)) {
- throw get_error ("expected numeric operand");
- }
- sql.insert (begin, "(");
- sql.append (" / ");
- if (!maybe_numeric (translate_unary_expression (sql))) {
- throw get_error ("expected numeric operand");
- }
- sql.append (")");
- } else {
- break;
- }
- }
- return optype;
- }
-
- private PropertyType translate_additive_expression (StringBuilder sql) throws Sparql.Error {
- long begin = sql.len;
- var optype = translate_multiplicative_expression (sql);
- while (true) {
- if (accept (SparqlTokenType.PLUS)) {
- if (!maybe_numeric (optype)) {
- throw get_error ("expected numeric operand");
- }
- sql.insert (begin, "(");
- sql.append (" + ");
- if (!maybe_numeric (translate_multiplicative_expression (sql))) {
- throw get_error ("expected numeric operand");
- }
- sql.append (")");
- } else if (accept (SparqlTokenType.MINUS)) {
- if (!maybe_numeric (optype)) {
- throw get_error ("expected numeric operand");
- }
- sql.insert (begin, "(");
- sql.append (" - ");
- if (!maybe_numeric (translate_multiplicative_expression (sql))) {
- throw get_error ("expected numeric operand");
- }
- sql.append (")");
- } else {
- break;
- }
- }
- return optype;
- }
-
- private PropertyType translate_numeric_expression (StringBuilder sql) throws Sparql.Error {
- return translate_additive_expression (sql);
- }
-
- private PropertyType process_relational_expression (StringBuilder sql, long begin, uint n_bindings, PropertyType op1type, string operator) throws Sparql.Error {
- sql.insert (begin, "(");
- sql.append (operator);
- var op2type = translate_numeric_expression (sql);
- sql.append (")");
- if ((op1type == PropertyType.DATETIME && op2type == PropertyType.STRING)
- || (op1type == PropertyType.STRING && op2type == PropertyType.DATETIME)) {
- // TODO: improve performance (linked list)
- if (query.bindings.length () == n_bindings + 1) {
- // trigger string => datetime conversion
- query.bindings.last ().data.data_type = PropertyType.DATETIME;
- }
- } else if ((op1type == PropertyType.DATE && op2type == PropertyType.STRING)
- || (op1type == PropertyType.STRING && op2type == PropertyType.DATE)) {
- // TODO: improve performance (linked list)
- if (query.bindings.length () == n_bindings + 1) {
- // trigger string => date conversion
- query.bindings.last ().data.data_type = PropertyType.DATE;
- }
- }
- return PropertyType.BOOLEAN;
- }
-
- private PropertyType translate_in (StringBuilder sql, bool not) throws Sparql.Error {
-
- int in_variable_count = 0;
-
- if (not) {
- sql.append (" NOT");
- }
-
- expect (SparqlTokenType.OPEN_PARENS);
- sql.append (" IN (");
- if (!accept (SparqlTokenType.CLOSE_PARENS)) {
- in_variable_count++;
- translate_expression (sql);
- while (accept (SparqlTokenType.COMMA)) {
- sql.append (", ");
-
- in_variable_count++;
-
- if (in_variable_count > MAX_VARIABLES_FOR_IN && !query.no_cache) {
- query.no_cache = true;
- }
-
- translate_expression (sql);
- }
- expect (SparqlTokenType.CLOSE_PARENS);
- }
- sql.append (")");
-
- return PropertyType.BOOLEAN;
- }
-
- private PropertyType translate_relational_expression (StringBuilder sql) throws Sparql.Error {
- long begin = sql.len;
- // TODO: improve performance (linked list)
- uint n_bindings = query.bindings.length ();
- var optype = translate_numeric_expression (sql);
- if (accept (SparqlTokenType.OP_GE)) {
- return process_relational_expression (sql, begin, n_bindings, optype, " >= ");
- } else if (accept (SparqlTokenType.OP_EQ)) {
- return process_relational_expression (sql, begin, n_bindings, optype, " = ");
- } else if (accept (SparqlTokenType.OP_NE)) {
- return process_relational_expression (sql, begin, n_bindings, optype, " <> ");
- } else if (accept (SparqlTokenType.OP_LT)) {
- return process_relational_expression (sql, begin, n_bindings, optype, " < ");
- } else if (accept (SparqlTokenType.OP_LE)) {
- return process_relational_expression (sql, begin, n_bindings, optype, " <= ");
- } else if (accept (SparqlTokenType.OP_GT)) {
- return process_relational_expression (sql, begin, n_bindings, optype, " > ");
- } else if (accept (SparqlTokenType.OP_IN)) {
- return translate_in (sql, false);
- } else if (accept (SparqlTokenType.NOT)) {
- expect (SparqlTokenType.OP_IN);
- return translate_in (sql, true);
- }
- return optype;
- }
-
- private PropertyType translate_value_logical (StringBuilder sql) throws Sparql.Error {
- return translate_relational_expression (sql);
- }
-
- private PropertyType translate_conditional_and_expression (StringBuilder sql) throws Sparql.Error {
- long begin = sql.len;
- var optype = translate_value_logical (sql);
- while (accept (SparqlTokenType.OP_AND)) {
- if (optype != PropertyType.BOOLEAN) {
- throw get_error ("expected boolean expression");
- }
- sql.insert (begin, "(");
- sql.append (" AND ");
- optype = translate_value_logical (sql);
- sql.append (")");
- if (optype != PropertyType.BOOLEAN) {
- throw get_error ("expected boolean expression");
- }
- }
- return optype;
- }
-
- private PropertyType translate_conditional_or_expression (StringBuilder sql) throws Sparql.Error {
- long begin = sql.len;
- var optype = translate_conditional_and_expression (sql);
- while (accept (SparqlTokenType.OP_OR)) {
- if (optype != PropertyType.BOOLEAN) {
- throw get_error ("expected boolean expression");
- }
- sql.insert (begin, "(");
- sql.append (" OR ");
- optype = translate_conditional_and_expression (sql);
- sql.append (")");
- if (optype != PropertyType.BOOLEAN) {
- throw get_error ("expected boolean expression");
- }
- }
- return optype;
- }
-
- internal PropertyType translate_expression (StringBuilder sql) throws Sparql.Error {
- return translate_conditional_or_expression (sql);
- }
-
- private PropertyType translate_bracketted_expression (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.OPEN_PARENS);
-
- if (current () == SparqlTokenType.SELECT) {
- // scalar subquery
-
- sql.append ("(");
- var select_context = pattern.translate_select (sql, true, true);
- sql.append (")");
-
- expect (SparqlTokenType.CLOSE_PARENS);
- return select_context.type;
- }
-
- var optype = translate_expression (sql);
- expect (SparqlTokenType.CLOSE_PARENS);
- return optype;
- }
-
- private PropertyType translate_aggregate_expression (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.OPEN_PARENS);
- if (accept (SparqlTokenType.DISTINCT)) {
- sql.append ("DISTINCT ");
- }
-
- bool is_var = (current () == SparqlTokenType.VAR);
- var optype = translate_expression (sql);
-
- if (is_var) {
- var variable = context.get_variable (get_last_string ().substring (1));
- if (variable.binding == null) {
- throw get_error ("use of undefined variable `%s'".printf (variable.name));
- }
- }
-
- expect (SparqlTokenType.CLOSE_PARENS);
- return optype;
- }
-
- internal PropertyType translate_constraint (StringBuilder sql) throws Sparql.Error {
- switch (current ()) {
- case SparqlTokenType.STR:
- case SparqlTokenType.LANG:
- case SparqlTokenType.LANGMATCHES:
- case SparqlTokenType.DATATYPE:
- case SparqlTokenType.BOUND:
- case SparqlTokenType.IF:
- case SparqlTokenType.SAMETERM:
- case SparqlTokenType.ISIRI:
- case SparqlTokenType.ISURI:
- case SparqlTokenType.ISBLANK:
- case SparqlTokenType.ISLITERAL:
- case SparqlTokenType.REGEX:
- case SparqlTokenType.EXISTS:
- case SparqlTokenType.NOT:
- return translate_primary_expression (sql);
- default:
- return translate_bracketted_expression (sql);
- }
- }
-}
diff --git a/src/libtracker-data/tracker-sparql-grammar.h b/src/libtracker-data/tracker-sparql-grammar.h
new file mode 100644
index 000000000..75f5f801c
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql-grammar.h
@@ -0,0 +1,2360 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+#ifndef __TRACKER_GRAMMAR_H__
+#define __TRACKER_GRAMMAR_H__
+
+#include "string.h"
+
+typedef enum {
+ NAMED_RULE_QueryUnit,
+ NAMED_RULE_UpdateUnit,
+ NAMED_RULE_Query,
+ NAMED_RULE_Update,
+ NAMED_RULE_SelectClause,
+ NAMED_RULE_Prologue,
+ NAMED_RULE_BaseDecl,
+ NAMED_RULE_PrefixDecl,
+ NAMED_RULE_SelectQuery,
+ NAMED_RULE_SubSelect,
+ NAMED_RULE_ConstructQuery,
+ NAMED_RULE_DescribeQuery,
+ NAMED_RULE_AskQuery,
+ NAMED_RULE_DatasetClause,
+ NAMED_RULE_DefaultGraphClause,
+ NAMED_RULE_NamedGraphClause,
+ NAMED_RULE_SourceSelector,
+ NAMED_RULE_WhereClause,
+ NAMED_RULE_SolutionModifier,
+ NAMED_RULE_GroupClause,
+ NAMED_RULE_GroupCondition,
+ NAMED_RULE_HavingClause,
+ NAMED_RULE_HavingCondition,
+ NAMED_RULE_OrderClause,
+ NAMED_RULE_OrderCondition,
+ NAMED_RULE_LimitOffsetClauses,
+ NAMED_RULE_LimitClause,
+ NAMED_RULE_OffsetClause,
+ NAMED_RULE_ValuesClause,
+ NAMED_RULE_Update1,
+ NAMED_RULE_Load,
+ NAMED_RULE_Clear,
+ NAMED_RULE_Drop,
+ NAMED_RULE_Create,
+ NAMED_RULE_Add,
+ NAMED_RULE_Move,
+ NAMED_RULE_Copy,
+ NAMED_RULE_InsertData,
+ NAMED_RULE_DeleteData,
+ NAMED_RULE_DeleteWhere,
+ NAMED_RULE_Modify,
+ NAMED_RULE_DeleteClause,
+ NAMED_RULE_InsertClause,
+ NAMED_RULE_UsingClause,
+ NAMED_RULE_GraphOrDefault,
+ NAMED_RULE_GraphRefAll,
+ NAMED_RULE_GraphRef,
+ NAMED_RULE_QuadPattern,
+ NAMED_RULE_QuadData,
+ NAMED_RULE_Quads,
+ NAMED_RULE_QuadsNotTriples,
+ NAMED_RULE_TriplesTemplate,
+ NAMED_RULE_GroupGraphPatternSub,
+ NAMED_RULE_TriplesBlock,
+ NAMED_RULE_GraphPatternNotTriples,
+ NAMED_RULE_OptionalGraphPattern,
+ NAMED_RULE_GraphGraphPattern,
+ NAMED_RULE_ServiceGraphPattern,
+ NAMED_RULE_Bind,
+ NAMED_RULE_InlineData,
+ NAMED_RULE_DataBlock,
+ NAMED_RULE_InlineDataOneVar,
+ NAMED_RULE_InlineDataFull,
+ NAMED_RULE_DataBlockValue,
+ NAMED_RULE_MinusGraphPattern,
+ NAMED_RULE_GroupOrUnionGraphPattern,
+ NAMED_RULE_Filter,
+ NAMED_RULE_Constraint,
+ NAMED_RULE_FunctionCall,
+ NAMED_RULE_ArgList,
+ NAMED_RULE_ExpressionList,
+ NAMED_RULE_ConstructTemplate,
+ NAMED_RULE_ConstructTriples,
+ NAMED_RULE_TriplesSameSubject,
+ NAMED_RULE_GroupGraphPattern,
+ NAMED_RULE_PropertyList,
+ NAMED_RULE_PropertyListNotEmpty,
+ NAMED_RULE_Verb,
+ NAMED_RULE_ObjectList,
+ NAMED_RULE_Object,
+ NAMED_RULE_TriplesSameSubjectPath,
+ NAMED_RULE_PropertyListPath,
+ NAMED_RULE_PropertyListPathNotEmpty,
+ NAMED_RULE_VerbPath,
+ NAMED_RULE_VerbSimple,
+ NAMED_RULE_ObjectListPath,
+ NAMED_RULE_ObjectPath,
+ NAMED_RULE_Path,
+ NAMED_RULE_PathAlternative,
+ NAMED_RULE_PathSequence,
+ NAMED_RULE_PathEltOrInverse,
+ NAMED_RULE_PathElt,
+ NAMED_RULE_PathMod,
+ NAMED_RULE_PathPrimary,
+ NAMED_RULE_PathNegatedPropertySet,
+ NAMED_RULE_PathOneInPropertySet,
+ NAMED_RULE_Integer,
+ NAMED_RULE_TriplesNode,
+ NAMED_RULE_BlankNodePropertyList,
+ NAMED_RULE_TriplesNodePath,
+ NAMED_RULE_BlankNodePropertyListPath,
+ NAMED_RULE_Collection,
+ NAMED_RULE_CollectionPath,
+ NAMED_RULE_GraphNode,
+ NAMED_RULE_GraphNodePath,
+ NAMED_RULE_VarOrTerm,
+ NAMED_RULE_VarOrIri,
+ NAMED_RULE_Var,
+ NAMED_RULE_GraphTerm,
+ NAMED_RULE_Expression,
+ NAMED_RULE_ConditionalOrExpression,
+ NAMED_RULE_ConditionalAndExpression,
+ NAMED_RULE_ValueLogical,
+ NAMED_RULE_RelationalExpression,
+ NAMED_RULE_NumericExpression,
+ NAMED_RULE_AdditiveExpression,
+ NAMED_RULE_MultiplicativeExpression,
+ NAMED_RULE_UnaryExpression,
+ NAMED_RULE_PrimaryExpression,
+ NAMED_RULE_iriOrFunction,
+ NAMED_RULE_BrackettedExpression,
+ NAMED_RULE_BuiltInCall,
+ NAMED_RULE_RegexExpression,
+ NAMED_RULE_SubstringExpression,
+ NAMED_RULE_StrReplaceExpression,
+ NAMED_RULE_ExistsFunc,
+ NAMED_RULE_NotExistsFunc,
+ NAMED_RULE_Aggregate,
+ NAMED_RULE_RDFLiteral,
+ NAMED_RULE_NumericLiteral,
+ NAMED_RULE_NumericLiteralUnsigned,
+ NAMED_RULE_NumericLiteralPositive,
+ NAMED_RULE_NumericLiteralNegative,
+ NAMED_RULE_BooleanLiteral,
+ NAMED_RULE_String,
+ NAMED_RULE_iri,
+ NAMED_RULE_PrefixedName,
+ NAMED_RULE_BlankNode,
+ N_NAMED_RULES
+} TrackerGrammarNamedRule;
+
+typedef enum {
+ LITERAL_A,
+ LITERAL_ABS,
+ LITERAL_ADD,
+ LITERAL_ALL,
+ LITERAL_ARITH_MULT,
+ LITERAL_ARITH_DIV,
+ LITERAL_ARITH_PLUS,
+ LITERAL_ARITH_MINUS,
+ LITERAL_AS,
+ LITERAL_ASC,
+ LITERAL_ASK,
+ LITERAL_AVG,
+ LITERAL_BASE,
+ LITERAL_BIND,
+ LITERAL_BNODE,
+ LITERAL_BOUND,
+ LITERAL_BY,
+ LITERAL_CEIL,
+ LITERAL_CLEAR,
+ LITERAL_CLOSE_BRACE,
+ LITERAL_CLOSE_BRACKET,
+ LITERAL_CLOSE_PARENS,
+ LITERAL_COALESCE,
+ LITERAL_COLON,
+ LITERAL_CONCAT,
+ LITERAL_CONTAINS,
+ LITERAL_COMMA,
+ LITERAL_CONSTRUCT,
+ LITERAL_COPY,
+ LITERAL_COUNT,
+ LITERAL_CREATE,
+ LITERAL_DATA,
+ LITERAL_DATATYPE,
+ LITERAL_DAY,
+ LITERAL_DEFAULT,
+ LITERAL_DELETE,
+ LITERAL_DESC,
+ LITERAL_DESCRIBE,
+ LITERAL_DISTINCT,
+ LITERAL_DOT,
+ LITERAL_DOUBLE_CIRCUMFLEX,
+ LITERAL_DROP,
+ LITERAL_ENCODE_FOR_URI,
+ LITERAL_EXISTS,
+ LITERAL_FALSE,
+ LITERAL_FILTER,
+ LITERAL_FLOOR,
+ LITERAL_FROM,
+ LITERAL_GLOB,
+ LITERAL_GRAPH,
+ LITERAL_GROUP,
+ LITERAL_GROUP_CONCAT,
+ LITERAL_HAVING,
+ LITERAL_HOURS,
+ LITERAL_IF,
+ LITERAL_INSERT,
+ LITERAL_INTO,
+ LITERAL_IRI,
+ LITERAL_ISBLANK,
+ LITERAL_ISIRI,
+ LITERAL_ISLITERAL,
+ LITERAL_ISNUMERIC,
+ LITERAL_ISURI,
+ LITERAL_LANG,
+ LITERAL_LANGMATCHES,
+ LITERAL_LCASE,
+ LITERAL_LIMIT,
+ LITERAL_LOAD,
+ LITERAL_MAX,
+ LITERAL_MD5,
+ LITERAL_MIN,
+ LITERAL_MINUS,
+ LITERAL_MINUTES,
+ LITERAL_MONTH,
+ LITERAL_MOVE,
+ LITERAL_NAMED,
+ LITERAL_NOT,
+ LITERAL_NOW,
+ LITERAL_NULL, /* TRACKER EXTENSION */
+ LITERAL_OFFSET,
+ LITERAL_OP_AND,
+ LITERAL_OP_EQ,
+ LITERAL_OP_GE,
+ LITERAL_OP_GT,
+ LITERAL_OP_LE,
+ LITERAL_OP_LT,
+ LITERAL_OP_NE,
+ LITERAL_OP_NEG,
+ LITERAL_OP_OR,
+ LITERAL_OP_IN,
+ LITERAL_OPEN_BRACE,
+ LITERAL_OPEN_BRACKET,
+ LITERAL_OPEN_PARENS,
+ LITERAL_OPTIONAL,
+ LITERAL_OR,
+ LITERAL_ORDER,
+ LITERAL_PATH_SEQUENCE,
+ LITERAL_PATH_ALTERNATIVE,
+ LITERAL_PATH_INVERSE,
+ LITERAL_PATH_OPTIONAL,
+ LITERAL_PATH_STAR,
+ LITERAL_PATH_PLUS,
+ LITERAL_PREFIX,
+ LITERAL_RAND,
+ LITERAL_REDUCED,
+ LITERAL_REGEX,
+ LITERAL_REPLACE,
+ LITERAL_ROUND,
+ LITERAL_SAMETERM,
+ LITERAL_SAMPLE,
+ LITERAL_SECONDS,
+ LITERAL_SELECT,
+ LITERAL_SEMICOLON,
+ LITERAL_SEPARATOR,
+ LITERAL_SERVICE,
+ LITERAL_SHA1,
+ LITERAL_SHA256,
+ LITERAL_SHA384,
+ LITERAL_SHA512,
+ LITERAL_SILENT,
+ LITERAL_STR,
+ LITERAL_STRAFTER,
+ LITERAL_STRBEFORE,
+ LITERAL_STRDT,
+ LITERAL_STRENDS,
+ LITERAL_STRLANG,
+ LITERAL_STRLEN,
+ LITERAL_STRSTARTS,
+ LITERAL_STRUUID,
+ LITERAL_SUBSTR,
+ LITERAL_SUM,
+ LITERAL_TIMEZONE,
+ LITERAL_TO,
+ LITERAL_TRUE,
+ LITERAL_TZ,
+ LITERAL_UCASE,
+ LITERAL_UNDEF,
+ LITERAL_UNION,
+ LITERAL_URI,
+ LITERAL_USING,
+ LITERAL_UUID,
+ LITERAL_VALUES,
+ LITERAL_VAR,
+ LITERAL_WHERE,
+ LITERAL_WITH,
+ LITERAL_YEAR,
+ N_LITERALS
+} TrackerGrammarLiteral;
+
+static const gchar literals[][N_LITERALS] = {
+ "a", /* LITERAL_A */
+ "abs", /* LITERAL_ABS */
+ "add", /* LITERAL_ADD */
+ "all", /* LITERAL_ALL */
+ "*", /* LITERAL_ARITH_MULT */
+ "/", /* LITERAL_ARITH_DIV */
+ "+", /* LITERAL_ARITH_PLUS */
+ "-", /* LITERAL_ARITH_MINUS */
+ "as", /* LITERAL_AS */
+ "asc", /* LITERAL_ASC */
+ "ask", /* LITERAL_ASK */
+ "avg", /* LITERAL_AVG */
+ "base", /* LITERAL_BASE */
+ "bind", /* LITERAL_BIND */
+ "bnode", /* LITERAL_BNODE */
+ "bound", /* LITERAL_BOUND */
+ "by", /* LITERAL_BY */
+ "ceil", /* LITERAL_CEIL */
+ "clear", /* LITERAL_CLEAR */
+ "}", /* LITERAL_CLOSE_BRACE */
+ "]", /* LITERAL_CLOSE_BRACKET */
+ ")", /* LITERAL_CLOSE_PARENS */
+ "coalesce", /* LITERAL_COALESCE */
+ ":", /* LITERAL_COLON */
+ "concat", /* LITERAL_CONCAT */
+ "contains", /* LITERAL_CONTAINS */
+ ",", /* LITERAL_COMMA */
+ "construct", /* LITERAL_CONSTRUCT */
+ "copy", /* LITERAL_COPY */
+ "count", /* LITERAL_COUNT */
+ "create", /* LITERAL_CREATE */
+ "data", /* LITERAL_DATA */
+ "datatype", /* LITERAL_DATATYPE */
+ "day", /* LITERAL_DAY */
+ "default", /* LITERAL_DEFAULT */
+ "delete", /* LITERAL_DELETE */
+ "desc", /* LITERAL_DESC */
+ "describe", /* LITERAL_DESCRIBE */
+ "distinct", /* LITERAL_DISTINCT */
+ ".", /* LITERAL_DOT */
+ "^^", /* LITERAL_DOUBLE_CIRCUMFLEX */
+ "drop", /* LITERAL_DROP */
+ "encode_for_uri", /* LITERAL_ENCODE_FOR_URI */
+ "exists", /* LITERAL_EXISTS */
+ "false", /* LITERAL_FALSE */
+ "filter", /* LITERAL_FILTER */
+ "floor", /* LITERAL_FLOOR */
+ "from", /* LITERAL_FROM */
+ "*", /* LITERAL_GLOB */
+ "graph", /* LITERAL_GRAPH */
+ "group", /* LITERAL_GROUP */
+ "group_concat", /* LITERAL_GROUP_CONCAT */
+ "having", /* LITERAL_HAVING */
+ "hours", /* LITERAL_HOURS */
+ "if", /* LITERAL_IF */
+ "insert", /* LITERAL_INSERT */
+ "into", /* LITERAL_INTO */
+ "iri", /* LITERAL_IRI */
+ "isblank", /* LITERAL_ISBLANK */
+ "isiri", /* LITERAL_ISIRI */
+ "isliteral", /* LITERAL_ISLITERAL */
+ "isnumeric", /* LITERAL_ISNUMERIC */
+ "isuri", /* LITERAL_ISURI */
+ "lang", /* LITERAL_LANG */
+ "langmatches", /* LITERAL_LANGMATCHES */
+ "lcase", /* LITERAL_LCASE */
+ "limit", /* LITERAL_LIMIT */
+ "load", /* LITERAL_LOAD */
+ "max", /* LITERAL_MAX */
+ "md5", /* LITERAL_MD5 */
+ "min", /* LITERAL_MIN */
+ "minus", /* LITERAL_MINUS */
+ "minutes", /* LITERAL_MINUTES */
+ "month", /* LITERAL_MONTH */
+ "move", /* LITERAL_MOVE */
+ "named", /* LITERAL_NAMED */
+ "not", /* LITERAL_NOT */
+ "now", /* LITERAL_NOW */
+ "null", /* LITERAL_NULL (TRACKER EXTENSION) */
+ "offset", /* LITERAL_OFFSET */
+ "&&", /* LITERAL_OP_AND */
+ "=", /* LITERAL_OP_EQ */
+ ">=", /* LITERAL_OP_GE */
+ ">", /* LITERAL_OP_GT */
+ "<=", /* LITERAL_OP_LE */
+ "<", /* LITERAL_OP_LT */
+ "!=", /* LITERAL_OP_NE */
+ "!", /* LITERAL_OP_NEG */
+ "||", /* LITERAL_OP_OR */
+ "in", /* LITERAL_OP_IN */
+ "{", /* LITERAL_OPEN_BRACE */
+ "[", /* LITERAL_OPEN_BRACKET */
+ "(", /* LITERAL_OPEN_PARENS */
+ "optional", /* LITERAL_OPTIONAL */
+ "or", /* LITERAL_OR */
+ "order", /* LITERAL_ORDER */
+ "/", /* LITERAL_PATH_SEQUENCE */
+ "|", /* LITERAL_PATH_ALTERNATIVE */
+ "^", /* LITERAL_PATH_INVERSE */
+ "?", /* LITERAL_PATH_OPTIONAL */
+ "*", /* LITERAL_PATH_STAR */
+ "+", /* LITERAL_PATH_PLUS */
+ "prefix", /* LITERAL_PREFIX */
+ "rand", /* LITERAL_RAND */
+ "reduced", /* LITERAL_REDUCED */
+ "regex", /* LITERAL_REGEX */
+ "replace", /* LITERAL_REPLACE */
+ "round", /* LITERAL_ROUND */
+ "sameterm", /* LITERAL_SAMETERM */
+ "sample", /* LITERAL_SAMPLE */
+ "seconds", /* LITERAL_SECONDS */
+ "select", /* LITERAL_SELECT */
+ ";", /* LITERAL_SEMICOLON */
+ "separator", /* LITERAL_SEPARATOR */
+ "service", /* LITERAL_SERVICE */
+ "sha1", /* LITERAL_SHA1 */
+ "sha256", /* LITERAL_SHA256 */
+ "sha384", /* LITERAL_SHA384 */
+ "sha512", /* LITERAL_SHA512 */
+ "silent", /* LITERAL_SILENT */
+ "str", /* LITERAL_STR */
+ "strafter", /* LITERAL_STRAFTER */
+ "strbefore", /* LITERAL_STRBEFORE */
+ "strdt", /* LITERAL_STRDT */
+ "strends", /* LITERAL_STRENDS */
+ "strlang", /* LITERAL_STRLANG */
+ "strlen", /* LITERAL_STRLEN */
+ "strstarts", /* LITERAL_STRSTARTS */
+ "struuid", /* LITERAL_STRUUID */
+ "substr", /* LITERAL_SUBSTR */
+ "sum", /* LITERAL_SUM */
+ "timezone", /* LITERAL_TIMEZONE */
+ "to", /* LITERAL_TO */
+ "true", /* LITERAL_TRUE */
+ "tz", /* LITERAL_TZ */
+ "ucase", /* LITERAL_UCASE */
+ "undef", /* LITERAL_UNDEF */
+ "union", /* LITERAL_UNION */
+ "uri", /* LITERAL_URI */
+ "using", /* LITERAL_USING */
+ "uuid", /* LITERAL_UUID */
+ "values", /* LITERAL_VALUES */
+ "var", /* LITERAL_VAR */
+ "where", /* LITERAL_WHERE */
+ "with", /* LITERAL_WITH */
+ "year", /* LITERAL_YEAR */
+};
+
+typedef enum {
+ TERMINAL_TYPE_IRIREF,
+ TERMINAL_TYPE_PNAME_NS,
+ TERMINAL_TYPE_PNAME_LN,
+ TERMINAL_TYPE_BLANK_NODE_LABEL,
+ TERMINAL_TYPE_VAR1,
+ TERMINAL_TYPE_VAR2,
+ TERMINAL_TYPE_LANGTAG,
+ TERMINAL_TYPE_INTEGER,
+ TERMINAL_TYPE_DECIMAL,
+ TERMINAL_TYPE_DOUBLE,
+ TERMINAL_TYPE_INTEGER_POSITIVE,
+ TERMINAL_TYPE_DECIMAL_POSITIVE,
+ TERMINAL_TYPE_DOUBLE_POSITIVE,
+ TERMINAL_TYPE_INTEGER_NEGATIVE,
+ TERMINAL_TYPE_DECIMAL_NEGATIVE,
+ TERMINAL_TYPE_DOUBLE_NEGATIVE,
+ TERMINAL_TYPE_STRING_LITERAL1,
+ TERMINAL_TYPE_STRING_LITERAL2,
+ TERMINAL_TYPE_STRING_LITERAL_LONG1,
+ TERMINAL_TYPE_STRING_LITERAL_LONG2,
+ TERMINAL_TYPE_NIL,
+ TERMINAL_TYPE_ANON,
+ TERMINAL_TYPE_PARAMETERIZED_VAR,
+ N_TERMINAL_TYPES
+} TrackerGrammarTerminalType;
+
+typedef struct _TrackerGrammarRule TrackerGrammarRule;
+
+typedef enum {
+ RULE_TYPE_NIL,
+ RULE_TYPE_RULE,
+ RULE_TYPE_TERMINAL,
+ RULE_TYPE_LITERAL,
+ RULE_TYPE_SEQUENCE,
+ RULE_TYPE_OR,
+ RULE_TYPE_GT0,
+ RULE_TYPE_GTE0,
+ RULE_TYPE_OPTIONAL,
+} TrackerGrammarRuleType;
+
+struct _TrackerGrammarRule {
+ TrackerGrammarRuleType type;
+ const gchar *string;
+ union {
+ TrackerGrammarLiteral literal;
+ TrackerGrammarNamedRule rule;
+ TrackerGrammarTerminalType terminal;
+ const TrackerGrammarRule *children;
+ } data;
+};
+
+typedef gboolean (*TrackerTerminalFunc) (const gchar *str,
+ const gchar *end,
+ const gchar **str_out);
+
+/* R=Rule, L=Literal, S=Sequence, T=Terminal, OR=Or, GTE0=">=0"(*) GT0=">0"(+) OPT=Optional(?), NIL=Closing rule */
+#define R(target) { RULE_TYPE_RULE, #target, .data = { .rule = NAMED_RULE_##target } }
+#define L(lit) { RULE_TYPE_LITERAL, literals[LITERAL_##lit], .data = { .literal = LITERAL_##lit } }
+#define T(name) { RULE_TYPE_TERMINAL, #name, .data = { .terminal = TERMINAL_TYPE_##name } }
+
+#define OR(rules) { RULE_TYPE_OR, NULL, .data = { .children = (rules) } }
+#define S(rules) { RULE_TYPE_SEQUENCE, NULL, .data = { .children = (rules) } }
+#define GT0(rules) { RULE_TYPE_GT0, NULL, .data = { .children = (rules) } }
+#define GTE0(rules) { RULE_TYPE_GTE0, NULL, .data = { .children = (rules) } }
+#define OPT(rules) { RULE_TYPE_OPTIONAL, NULL, .data = { .children = (rules) } }
+#define NIL { RULE_TYPE_NIL }
+
+/* Rules to parse SPARQL, as per https://www.w3.org/TR/sparql11-query/#sparqlGrammar */
+
+/* BlankNode ::= BLANK_NODE_LABEL | ANON
+ */
+static const TrackerGrammarRule helper_BlankNode_or[] = { T(BLANK_NODE_LABEL), T(ANON), NIL };
+static const TrackerGrammarRule rule_BlankNode[] = { OR(helper_BlankNode_or), NIL };
+
+/* PrefixedName ::= PNAME_LN | PNAME_NS
+ */
+static const TrackerGrammarRule helper_PrefixedName_or[] = { T(PNAME_LN), T(PNAME_NS), NIL };
+static const TrackerGrammarRule rule_PrefixedName[] = { OR(helper_PrefixedName_or), NIL };
+
+/* iri ::= IRIREF | PrefixedName
+ */
+static const TrackerGrammarRule helper_iri_or[] = { T(IRIREF), R(PrefixedName), NIL };
+static const TrackerGrammarRule rule_iri[] = { OR(helper_iri_or), NIL };
+
+/* String ::= STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+static const TrackerGrammarRule helper_String_or[] = { T(STRING_LITERAL1), T(STRING_LITERAL2), T(STRING_LITERAL_LONG1), T(STRING_LITERAL_LONG2), T(PARAMETERIZED_VAR), NIL };
+static const TrackerGrammarRule rule_String[] = { OR(helper_String_or), NIL };
+
+/* BooleanLiteral ::= 'true' | 'false'
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+static const TrackerGrammarRule helper_BooleanLiteral_or[] = { L(TRUE), L(FALSE), T(PARAMETERIZED_VAR), NIL };
+static const TrackerGrammarRule rule_BooleanLiteral[] = { OR(helper_BooleanLiteral_or), NIL };
+
+/* NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+static const TrackerGrammarRule helper_NumericLiteralNegative_or[] = { T(DECIMAL_NEGATIVE), T(DOUBLE_NEGATIVE), T(INTEGER_NEGATIVE), T(PARAMETERIZED_VAR), NIL };
+static const TrackerGrammarRule rule_NumericLiteralNegative[] = { OR(helper_NumericLiteralNegative_or), NIL };
+
+/* NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+static const TrackerGrammarRule helper_NumericLiteralPositive_or[] = { T(DECIMAL_POSITIVE), T(DOUBLE_POSITIVE), T(INTEGER_POSITIVE), T(PARAMETERIZED_VAR), NIL };
+static const TrackerGrammarRule rule_NumericLiteralPositive[] = { OR(helper_NumericLiteralPositive_or), NIL };
+
+/* NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+static const TrackerGrammarRule helper_NumericLiteralUnsigned_or[] = { T(DECIMAL), T(DOUBLE), T(INTEGER), T(PARAMETERIZED_VAR), NIL };
+static const TrackerGrammarRule rule_NumericLiteralUnsigned[] = { OR(helper_NumericLiteralUnsigned_or), NIL };
+
+/* NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
+ */
+static const TrackerGrammarRule helper_NumericLiteral_or[] = { R(NumericLiteralUnsigned), R(NumericLiteralPositive), R(NumericLiteralNegative), NIL };
+static const TrackerGrammarRule rule_NumericLiteral[] = { OR(helper_NumericLiteral_or), NIL };
+
+/* RDFLiteral ::= String ( LANGTAG | ( '^^' iri ) )?
+ */
+static const TrackerGrammarRule helper_RDFLiteral_seq[] = { L(DOUBLE_CIRCUMFLEX), R(iri), NIL };
+static const TrackerGrammarRule helper_RDFLiteral_or[] = { T(LANGTAG), S(helper_RDFLiteral_seq), NIL };
+static const TrackerGrammarRule helper_RDFLiteral_opt[] = { OR(helper_RDFLiteral_or), NIL };
+static const TrackerGrammarRule rule_RDFLiteral[] = { R(String), OPT(helper_RDFLiteral_opt), NIL };
+
+/* Aggregate ::= 'COUNT' '(' 'DISTINCT'? ( '*' | Expression ) ')'
+ * | 'SUM' '(' 'DISTINCT'? Expression ')'
+ * | 'MIN' '(' 'DISTINCT'? Expression ')'
+ * | 'MAX' '(' 'DISTINCT'? Expression ')'
+ * | 'AVG' '(' 'DISTINCT'? Expression ')'
+ * | 'SAMPLE' '(' 'DISTINCT'? Expression ')'
+ * | 'GROUP_CONCAT' '(' 'DISTINCT'? Expression ( ';' 'SEPARATOR' '=' String )? ')'
+ *
+ * TRACKER EXTENSION:
+ *
+ * GROUP_CONCAT accepts a comma separator, so effectively:
+ * 'GROUP_CONCAT' '(' 'DISTINCT'? Expression ( ( ';' 'SEPARATOR' '=' | ',') String )? ')'
+ */
+static const TrackerGrammarRule helper_Aggregate_opt_1[] = { L(DISTINCT), NIL };
+static const TrackerGrammarRule helper_Aggregate_or_1[] = { L(GLOB), R(Expression), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_1[] = { L(COUNT), L(OPEN_PARENS), OPT(helper_Aggregate_opt_1), OR(helper_Aggregate_or_1), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_2[] = { L(SUM), L(OPEN_PARENS), OPT(helper_Aggregate_opt_1), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_3[] = { L(MIN), L(OPEN_PARENS), OPT(helper_Aggregate_opt_1), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_4[] = { L(MAX), L(OPEN_PARENS), OPT(helper_Aggregate_opt_1), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_5[] = { L(AVG), L(OPEN_PARENS), OPT(helper_Aggregate_opt_1), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_6[] = { L(SAMPLE), L(OPEN_PARENS), OPT(helper_Aggregate_opt_1), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_8[] = { L(SEMICOLON), L(SEPARATOR), L(OP_EQ), NIL };
+static const TrackerGrammarRule helper_Aggregate_or_3[] = { S(helper_Aggregate_seq_8), L(COMMA), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_in_opt[] = { OR(helper_Aggregate_or_3), R(String), NIL };
+static const TrackerGrammarRule helper_Aggregate_opt_2[] = { S(helper_Aggregate_seq_in_opt), NIL };
+static const TrackerGrammarRule helper_Aggregate_seq_7[] = { L(GROUP_CONCAT), L(OPEN_PARENS), OPT(helper_Aggregate_opt_1), R(Expression), OPT(helper_Aggregate_opt_2), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_Aggregate_or_2[] = { S(helper_Aggregate_seq_1), S(helper_Aggregate_seq_2), S(helper_Aggregate_seq_3), S(helper_Aggregate_seq_4), S(helper_Aggregate_seq_5), S(helper_Aggregate_seq_6), S(helper_Aggregate_seq_7), NIL };
+static const TrackerGrammarRule rule_Aggregate[] = { OR(helper_Aggregate_or_2), NIL };
+
+/* NotExistsFunc ::= 'NOT' 'EXISTS' GroupGraphPattern
+ */
+static const TrackerGrammarRule rule_NotExistsFunc[] = { L(NOT), L(EXISTS), R(GroupGraphPattern), NIL };
+
+/* ExistsFunc ::= 'EXISTS' GroupGraphPattern
+ */
+static const TrackerGrammarRule rule_ExistsFunc[] = { L(EXISTS), R(GroupGraphPattern), NIL };
+
+/* StrReplaceExpression ::= 'REPLACE' '(' Expression ',' Expression ',' Expression ( ',' Expression )? ')'
+ */
+static const TrackerGrammarRule helper_StrReplaceExpression_seq[] = { L(COMMA), R(Expression), NIL };
+static const TrackerGrammarRule helper_StrReplaceExpression_opt[] = { S(helper_StrReplaceExpression_seq), NIL };
+static const TrackerGrammarRule rule_StrReplaceExpression[] = { L(REPLACE), L(OPEN_PARENS), R(Expression), L(COMMA), R( Expression), L(COMMA), R( Expression), OPT(helper_StrReplaceExpression_opt), L(CLOSE_PARENS), NIL };
+
+/* SubstringExpression ::= 'SUBSTR' '(' Expression ',' Expression ( ',' Expression )? ')'
+ */
+static const TrackerGrammarRule helper_SubstringExpression_seq[] = { L(COMMA), R( Expression), NIL };
+static const TrackerGrammarRule helper_SubstringExpression_opt[] = { S(helper_SubstringExpression_seq), NIL };
+static const TrackerGrammarRule rule_SubstringExpression[] = { L(SUBSTR), L(OPEN_PARENS), R( Expression), L(COMMA), R(Expression), OPT(helper_SubstringExpression_opt), L(CLOSE_PARENS), NIL };
+
+/* RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
+ */
+static const TrackerGrammarRule helper_RegexExpression_seq[] = { L(COMMA), R(Expression), NIL };
+static const TrackerGrammarRule helper_RegexExpression_opt[] = { S(helper_RegexExpression_seq), NIL };
+static const TrackerGrammarRule rule_RegexExpression[] = { L(REGEX), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), OPT(helper_RegexExpression_opt), L(CLOSE_PARENS), NIL };
+
+/* BuiltInCall ::= Aggregate
+ * | 'STR' '(' Expression ')'
+ * | 'LANG' '(' Expression ')'
+ * | 'LANGMATCHES' '(' Expression ',' Expression ')'
+ * | 'DATATYPE' '(' Expression ')'
+ * | 'BOUND' '(' Var ')'
+ * | 'IRI' '(' Expression ')'
+ * | 'URI' '(' Expression ')'
+ * | 'BNODE' ( '(' Expression ')' | NIL )
+ * | 'RAND' NIL
+ * | 'ABS' '(' Expression ')'
+ * | 'CEIL' '(' Expression ')'
+ * | 'FLOOR' '(' Expression ')'
+ * | 'ROUND' '(' Expression ')'
+ * | 'CONCAT' ExpressionList
+ * | SubstringExpression
+ * | 'STRLEN' '(' Expression ')'
+ * | StrReplaceExpression
+ * | 'UCASE' '(' Expression ')'
+ * | 'LCASE' '(' Expression ')'
+ * | 'ENCODE_FOR_URI' '(' Expression ')'
+ * | 'CONTAINS' '(' Expression ',' Expression ')'
+ * | 'STRSTARTS' '(' Expression ',' Expression ')'
+ * | 'STRENDS' '(' Expression ',' Expression ')'
+ * | 'STRBEFORE' '(' Expression ',' Expression ')'
+ * | 'STRAFTER' '(' Expression ',' Expression ')'
+ * | 'YEAR' '(' Expression ')'
+ * | 'MONTH' '(' Expression ')'
+ * | 'DAY' '(' Expression ')'
+ * | 'HOURS' '(' Expression ')'
+ * | 'MINUTES' '(' Expression ')'
+ * | 'SECONDS' '(' Expression ')'
+ * | 'TIMEZONE' '(' Expression ')'
+ * | 'TZ' '(' Expression ')'
+ * | 'NOW' NIL
+ * | 'UUID' NIL
+ * | 'STRUUID' NIL
+ * | 'MD5' '(' Expression ')'
+ * | 'SHA1' '(' Expression ')'
+ * | 'SHA256' '(' Expression ')'
+ * | 'SHA384' '(' Expression ')'
+ * | 'SHA512' '(' Expression ')'
+ * | 'COALESCE' ExpressionList
+ * | 'IF' '(' Expression ',' Expression ',' Expression ')'
+ * | 'STRLANG' '(' Expression ',' Expression ')'
+ * | 'STRDT' '(' Expression ',' Expression ')'
+ * | 'sameTerm' '(' Expression ',' Expression ')'
+ * | 'isIRI' '(' Expression ')'
+ * | 'isURI' '(' Expression ')'
+ * | 'isBLANK' '(' Expression ')'
+ * | 'isLITERAL' '(' Expression ')'
+ * | 'isNUMERIC' '(' Expression ')'
+ * | RegexExpression
+ * | ExistsFunc
+ * | NotExistsFunc
+ *
+ * TRACKER EXTENSION:
+ * BOUND accepts the more generic Expression rule, resulting in:
+ * 'BOUND' '(' Expression ')'
+ */
+static const TrackerGrammarRule helper_BuiltInCall_seq_1[] = { L(OPEN_PARENS), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_or_1[] = { T(NIL), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_2[] = { L(STR), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_3[] = { L(LANG), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_4[] = { L(LANGMATCHES), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_5[] = { L(DATATYPE), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_6[] = { L(BOUND), L(OPEN_PARENS), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_7[] = { L(IRI), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_8[] = { L(URI), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_9[] = { L(BNODE), OR(helper_BuiltInCall_or_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_10[] = { L(RAND), T(NIL), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_11[] = { L(ABS), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_12[] = { L(CEIL), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_13[] = { L(FLOOR), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_14[] = { L(ROUND), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_15[] = { L(CONCAT), R(ExpressionList), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_16[] = { R(SubstringExpression), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_17[] = { L(STRLEN), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_18[] = { R(StrReplaceExpression), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_19[] = { L(UCASE), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_20[] = { L(LCASE), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_21[] = { L(ENCODE_FOR_URI), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_22[] = { L(CONTAINS), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_23[] = { L(STRSTARTS), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_24[] = { L(STRENDS), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_25[] = { L(STRBEFORE), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_26[] = { L(STRAFTER), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_27[] = { L(YEAR), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_28[] = { L(MONTH), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_29[] = { L(DAY), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_30[] = { L(HOURS), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_31[] = { L(MINUTES), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_32[] = { L(SECONDS), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_33[] = { L(TIMEZONE), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_34[] = { L(TZ), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_35[] = { L(NOW), T(NIL), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_36[] = { L(UUID), T(NIL), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_37[] = { L(STRUUID), T(NIL), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_38[] = { L(MD5), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_39[] = { L(SHA1), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_40[] = { L(SHA256), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_41[] = { L(SHA384), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_42[] = { L(SHA512), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_43[] = { L(COALESCE), R(ExpressionList), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_44[] = { L(IF), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_45[] = { L(STRLANG), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_46[] = { L(STRDT), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_47[] = { L(SAMETERM), L(OPEN_PARENS), R(Expression), L(COMMA), R(Expression), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_48[] = { L(ISIRI), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_49[] = { L(ISURI), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_50[] = { L(ISBLANK), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_51[] = { L(ISLITERAL), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_52[] = { L(ISNUMERIC), S(helper_BuiltInCall_seq_1), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_53[] = { R(RegexExpression), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_54[] = { R(ExistsFunc), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_55[] = { R(NotExistsFunc), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_seq_56[] = { R(Aggregate), NIL };
+static const TrackerGrammarRule helper_BuiltInCall_or_2[] = { S(helper_BuiltInCall_seq_2), S(helper_BuiltInCall_seq_3), S(helper_BuiltInCall_seq_4),
+ S(helper_BuiltInCall_seq_5), S(helper_BuiltInCall_seq_6), S(helper_BuiltInCall_seq_7),
+ S(helper_BuiltInCall_seq_8), S(helper_BuiltInCall_seq_9), S(helper_BuiltInCall_seq_10),
+ S(helper_BuiltInCall_seq_11), S(helper_BuiltInCall_seq_12), S(helper_BuiltInCall_seq_13),
+ S(helper_BuiltInCall_seq_14), S(helper_BuiltInCall_seq_15), S(helper_BuiltInCall_seq_16),
+ S(helper_BuiltInCall_seq_17), S(helper_BuiltInCall_seq_18), S(helper_BuiltInCall_seq_19),
+ S(helper_BuiltInCall_seq_20), S(helper_BuiltInCall_seq_21), S(helper_BuiltInCall_seq_22),
+ S(helper_BuiltInCall_seq_23), S(helper_BuiltInCall_seq_24), S(helper_BuiltInCall_seq_25),
+ S(helper_BuiltInCall_seq_26), S(helper_BuiltInCall_seq_27), S(helper_BuiltInCall_seq_28),
+ S(helper_BuiltInCall_seq_29), S(helper_BuiltInCall_seq_30), S(helper_BuiltInCall_seq_31),
+ S(helper_BuiltInCall_seq_32), S(helper_BuiltInCall_seq_33), S(helper_BuiltInCall_seq_34),
+ S(helper_BuiltInCall_seq_35), S(helper_BuiltInCall_seq_36), S(helper_BuiltInCall_seq_37),
+ S(helper_BuiltInCall_seq_38), S(helper_BuiltInCall_seq_39), S(helper_BuiltInCall_seq_40),
+ S(helper_BuiltInCall_seq_41), S(helper_BuiltInCall_seq_42), S(helper_BuiltInCall_seq_43),
+ S(helper_BuiltInCall_seq_44), S(helper_BuiltInCall_seq_45), S(helper_BuiltInCall_seq_46),
+ S(helper_BuiltInCall_seq_47), S(helper_BuiltInCall_seq_48), S(helper_BuiltInCall_seq_49),
+ S(helper_BuiltInCall_seq_50), S(helper_BuiltInCall_seq_51), S(helper_BuiltInCall_seq_52),
+ S(helper_BuiltInCall_seq_53), S(helper_BuiltInCall_seq_54), S(helper_BuiltInCall_seq_55),
+ S(helper_BuiltInCall_seq_56)};
+static const TrackerGrammarRule rule_BuiltInCall[] = { OR(helper_BuiltInCall_or_2), NIL };
+
+/* BrackettedExpression ::= '(' Expression ')'
+ *
+ * TRACKER EXTENSION:
+ * SubSelect is accepted too, thus the grammar results in:
+ * '(' ( Expression | SubSelect) ')'
+ */
+static const TrackerGrammarRule ext_BrackettedExpression_or[] = { R(Expression), R(SubSelect), NIL };
+static const TrackerGrammarRule rule_BrackettedExpression[] = { L(OPEN_PARENS), OR(ext_BrackettedExpression_or), L(CLOSE_PARENS), NIL };
+
+/* iriOrFunction ::= iri ArgList?
+ */
+static const TrackerGrammarRule helper_iriOrFunction_opt[] = { R(ArgList), NIL };
+static const TrackerGrammarRule rule_iriOrFunction[] = { R(iri), OPT(helper_iriOrFunction_opt), NIL };
+
+/* PrimaryExpression ::= BrackettedExpression | BuiltInCall | iriOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var
+ */
+static const TrackerGrammarRule helper_PrimaryExpression_or[] = { R(BrackettedExpression), R(BuiltInCall), R(iriOrFunction), R(RDFLiteral), R(NumericLiteral), R(BooleanLiteral), R(Var), NIL };
+static const TrackerGrammarRule rule_PrimaryExpression[] = { OR(helper_PrimaryExpression_or), NIL };
+
+/* UnaryExpression ::= '!' PrimaryExpression
+ * | '+' PrimaryExpression
+ * | '-' PrimaryExpression
+ * | PrimaryExpression
+ */
+static const TrackerGrammarRule helper_UnaryExpression_seq_1[] = { L(OP_NEG), R(PrimaryExpression), NIL };
+static const TrackerGrammarRule helper_UnaryExpression_seq_2[] = { L(ARITH_PLUS), R(PrimaryExpression), NIL };
+static const TrackerGrammarRule helper_UnaryExpression_seq_3[] = { L(ARITH_MINUS), R(PrimaryExpression), NIL };
+static const TrackerGrammarRule helper_UnaryExpression_or[] = { S(helper_UnaryExpression_seq_1), S(helper_UnaryExpression_seq_2), S(helper_UnaryExpression_seq_3), R(PrimaryExpression), NIL };
+static const TrackerGrammarRule rule_UnaryExpression[] = { OR(helper_UnaryExpression_or), NIL };
+
+/* MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
+ */
+static const TrackerGrammarRule helper_MultiplicativeExpression_seq_1[] = { L(ARITH_MULT), R(UnaryExpression), NIL };
+static const TrackerGrammarRule helper_MultiplicativeExpression_seq_2[] = { L(ARITH_DIV), R(UnaryExpression), NIL };
+static const TrackerGrammarRule helper_MultiplicativeExpression_or[] = { S(helper_MultiplicativeExpression_seq_1), S(helper_MultiplicativeExpression_seq_2), NIL };
+static const TrackerGrammarRule helper_MultiplicativeExpression_gte0[] = { OR(helper_MultiplicativeExpression_or), NIL };
+static const TrackerGrammarRule rule_MultiplicativeExpression[] = { R(UnaryExpression), GTE0(helper_MultiplicativeExpression_gte0), NIL };
+
+/* AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | ( NumericLiteralPositive | NumericLiteralNegative ) ( ( '*' UnaryExpression ) | ( '/' UnaryExpression ) )* )*
+ */
+static const TrackerGrammarRule helper_AdditiveExpression_seq_1[] = { L(ARITH_PLUS), R(MultiplicativeExpression), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_seq_2[] = { L(ARITH_MINUS), R(MultiplicativeExpression), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_or_1[] = { R(NumericLiteralPositive), R(NumericLiteralNegative), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_seq_3[] = { L(ARITH_MULT), R(UnaryExpression), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_seq_4[] = { L(ARITH_DIV), R(UnaryExpression), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_or_2[] = { S(helper_AdditiveExpression_seq_3), S(helper_AdditiveExpression_seq_4), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_gte0_1[] = { OR(helper_AdditiveExpression_or_2), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_seq_5[] = { OR(helper_AdditiveExpression_or_1), GTE0(helper_AdditiveExpression_gte0_1), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_or_3[] = { S(helper_AdditiveExpression_seq_1), S(helper_AdditiveExpression_seq_2), S(helper_AdditiveExpression_seq_5), NIL };
+static const TrackerGrammarRule helper_AdditiveExpression_gte0_2[] = { OR(helper_AdditiveExpression_or_3), NIL };
+static const TrackerGrammarRule rule_AdditiveExpression[] = { R(MultiplicativeExpression), GTE0(helper_AdditiveExpression_gte0_2), NIL };
+
+/* NumericExpression ::= AdditiveExpression
+ */
+static const TrackerGrammarRule rule_NumericExpression[] = { R(AdditiveExpression), NIL };
+
+/* RelationalExpression ::= NumericExpression ( '=' NumericExpression | '!=' NumericExpression | '<' NumericExpression | '>' NumericExpression | '<=' NumericExpression | '>=' NumericExpression | 'IN' ExpressionList | 'NOT' 'IN' ExpressionList )?
+ */
+static const TrackerGrammarRule helper_RelationalExpression_seq_1[] = { L(OP_EQ), R(NumericExpression), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_seq_2[] = { L(OP_NE), R(NumericExpression), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_seq_3[] = { L(OP_LT), R(NumericExpression), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_seq_4[] = { L(OP_GT), R(NumericExpression), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_seq_5[] = { L(OP_LE), R(NumericExpression), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_seq_6[] = { L(OP_GE), R(NumericExpression), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_seq_7[] = { L(OP_IN), R(ExpressionList), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_seq_8[] = { L(NOT), L(OP_IN), R(ExpressionList), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_or[] = { S(helper_RelationalExpression_seq_1), S(helper_RelationalExpression_seq_2), S(helper_RelationalExpression_seq_5), S(helper_RelationalExpression_seq_6), S(helper_RelationalExpression_seq_3), S(helper_RelationalExpression_seq_4), S(helper_RelationalExpression_seq_7), S(helper_RelationalExpression_seq_8), NIL };
+static const TrackerGrammarRule helper_RelationalExpression_opt[] = { OR(helper_RelationalExpression_or), NIL };
+static const TrackerGrammarRule rule_RelationalExpression[] = { R(NumericExpression), OPT(helper_RelationalExpression_opt), NIL };
+
+/* ValueLogical ::= RelationalExpression
+ */
+static const TrackerGrammarRule rule_ValueLogical[] = { R(RelationalExpression), NIL };
+
+/* ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
+ */
+static const TrackerGrammarRule helper_ConditionalAndExpression_seq[] = { L(OP_AND), R(ValueLogical), NIL };
+static const TrackerGrammarRule helper_ConditionalAndExpression_gte0[] = { S(helper_ConditionalAndExpression_seq), NIL };
+static const TrackerGrammarRule rule_ConditionalAndExpression[] = { R(ValueLogical), GTE0(helper_ConditionalAndExpression_gte0), NIL };
+
+/* ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
+ */
+static const TrackerGrammarRule helper_ConditionalOrExpression_seq[] = { L(OP_OR), R(ConditionalAndExpression), NIL };
+static const TrackerGrammarRule helper_ConditionalOrExpression_gte0[] = { S(helper_ConditionalOrExpression_seq), NIL };
+static const TrackerGrammarRule rule_ConditionalOrExpression[] = { R(ConditionalAndExpression), GTE0(helper_ConditionalOrExpression_gte0), NIL };
+
+/* Expression ::= ConditionalOrExpression
+ */
+static const TrackerGrammarRule rule_Expression[] = { R(ConditionalOrExpression), NIL };
+
+/* GraphTerm ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL
+ */
+static const TrackerGrammarRule helper_GraphTerm_or[] = { R(iri), R(RDFLiteral), R(NumericLiteral), R(BooleanLiteral), R(BlankNode), T(NIL), NIL };
+static const TrackerGrammarRule rule_GraphTerm[] = { OR(helper_GraphTerm_or), NIL };
+
+/* Var ::= VAR1 | VAR2
+ */
+static const TrackerGrammarRule helper_Var_or[] = { T(VAR1), T(VAR2), NIL };
+static const TrackerGrammarRule rule_Var[] = { OR(helper_Var_or), NIL };
+
+/* VarOrIri ::= Var | iri
+ */
+static const TrackerGrammarRule helper_VarOrIri_or[] = { R(Var), R(iri), NIL };
+static const TrackerGrammarRule rule_VarOrIri[] = { OR(helper_VarOrIri_or), NIL };
+
+/* VarOrTerm ::= Var | GraphTerm
+ */
+static const TrackerGrammarRule helper_VarOrTerm_or[] = { R(Var), R(GraphTerm), NIL };
+static const TrackerGrammarRule rule_VarOrTerm[] = { OR(helper_VarOrTerm_or), NIL };
+
+/* GraphNodePath ::= VarOrTerm | TriplesNodePath
+ */
+static const TrackerGrammarRule helper_GraphNodePath_or[] = { R(VarOrTerm), R(TriplesNodePath), NIL };
+static const TrackerGrammarRule rule_GraphNodePath[] = { OR(helper_GraphNodePath_or), NIL };
+
+/* GraphNode ::= VarOrTerm | TriplesNode
+ *
+ * TRACKER EXTENSION:
+ * Literal 'NULL' is also accepted, rule is effectively:
+ * VarOrTerm | TriplesNode | 'NULL'
+ */
+static const TrackerGrammarRule helper_GraphNode_or[] = { R(VarOrTerm), R(TriplesNode), L(NULL), NIL };
+static const TrackerGrammarRule rule_GraphNode[] = { OR(helper_GraphNode_or), NIL };
+
+/* CollectionPath ::= '(' GraphNodePath+ ')'
+ */
+static const TrackerGrammarRule helper_CollectionPath_gt0[] = { R(GraphNodePath), NIL };
+static const TrackerGrammarRule rule_CollectionPath[] = { L(OPEN_PARENS), GT0(helper_CollectionPath_gt0), L(CLOSE_PARENS), NIL };
+
+/* Collection ::= '(' GraphNode+ ')'
+ */
+static const TrackerGrammarRule helper_Collection_gt0[] = { R(GraphNode), NIL };
+static const TrackerGrammarRule rule_Collection[] = { L(OPEN_PARENS), GT0(helper_Collection_gt0), L(CLOSE_PARENS), NIL };
+
+/* BlankNodePropertyListPath ::= '[' PropertyListPathNotEmpty ']'
+ */
+static const TrackerGrammarRule rule_BlankNodePropertyListPath[] = { L(OPEN_BRACKET), R(PropertyListPathNotEmpty), L(CLOSE_BRACKET), NIL };
+
+/* TriplesNodePath ::= CollectionPath | BlankNodePropertyListPath
+ */
+static const TrackerGrammarRule helper_TriplesNodePath_or[] = { R(CollectionPath), R(BlankNodePropertyListPath), NIL };
+static const TrackerGrammarRule rule_TriplesNodePath[] = { OR(helper_TriplesNodePath_or ), NIL };
+
+/* BlankNodePropertyList ::= '[' PropertyListNotEmpty ']'
+ */
+static const TrackerGrammarRule rule_BlankNodePropertyList[] = { L(OPEN_BRACKET), R(PropertyListNotEmpty), L(CLOSE_BRACKET), NIL };
+
+/* TriplesNode ::= Collection | BlankNodePropertyList
+ */
+static const TrackerGrammarRule helper_TriplesNode_or[] = { R(Collection), R(BlankNodePropertyList), NIL };
+static const TrackerGrammarRule rule_TriplesNode[] = { OR(helper_TriplesNode_or), NIL };
+
+/* Integer ::= INTEGER
+ */
+static const TrackerGrammarRule rule_Integer[] = { T(INTEGER), NIL };
+
+/* PathOneInPropertySet ::= iri | 'a' | '^' ( iri | 'a' )
+ */
+static const TrackerGrammarRule helper_PathOneInPropertySet_or_1[] = { R(iri), L(A), NIL };
+static const TrackerGrammarRule helper_PathOneInPropertySet_seq[] = { L(PATH_INVERSE), OR(helper_PathOneInPropertySet_or_1), NIL };
+static const TrackerGrammarRule helper_PathOneInPropertySet_or_2[] = { R(iri), L(A), S(helper_PathOneInPropertySet_seq), NIL };
+static const TrackerGrammarRule rule_PathOneInPropertySet[] = { OR(helper_PathOneInPropertySet_or_2), NIL };
+
+/* PathNegatedPropertySet ::= PathOneInPropertySet | '(' ( PathOneInPropertySet ( '|' PathOneInPropertySet )* )? ')'
+ */
+static const TrackerGrammarRule helper_PathNegatedPropertySet_seq_1[] = { L(PATH_ALTERNATIVE), R(PathOneInPropertySet), NIL };
+static const TrackerGrammarRule helper_PathNegatedPropertySet_gte0[] = { S(helper_PathNegatedPropertySet_seq_1), NIL };
+static const TrackerGrammarRule helper_PathNegatedPropertySet_seq_2[] = { R(PathOneInPropertySet), GTE0(helper_PathNegatedPropertySet_gte0), NIL };
+static const TrackerGrammarRule helper_PathNegatedPropertySet_opt[] = { S(helper_PathNegatedPropertySet_seq_2), NIL };
+static const TrackerGrammarRule helper_PathNegatedPropertySet_seq_3[] = { L(OPEN_PARENS), OPT(helper_PathNegatedPropertySet_opt), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_PathNegatedPropertySet_or[] = { R(PathOneInPropertySet), S(helper_PathNegatedPropertySet_seq_3), NIL };
+static const TrackerGrammarRule rule_PathNegatedPropertySet[] = { OR(helper_PathNegatedPropertySet_or), NIL };
+
+/* PathPrimary ::= iri | 'a' | '!' PathNegatedPropertySet | '(' Path ')'
+ */
+static const TrackerGrammarRule helper_PathPrimary_seq_1[] = { L(OP_NEG), R(PathNegatedPropertySet), NIL };
+static const TrackerGrammarRule helper_PathPrimary_seq_2[] = { L(OPEN_PARENS), R(Path), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_PathPrimary_or[] = { R(iri), L(A), S(helper_PathPrimary_seq_1), S(helper_PathPrimary_seq_2), NIL };
+static const TrackerGrammarRule rule_PathPrimary[] = { OR(helper_PathPrimary_or), NIL };
+
+/* PathMod ::= '?' | '*' | '+'
+ */
+static const TrackerGrammarRule helper_PathMod_or[] = { L(PATH_OPTIONAL), L(PATH_STAR), L(PATH_PLUS), NIL };
+static const TrackerGrammarRule rule_PathMod[] = { OR(helper_PathMod_or), NIL };
+
+/* PathElt ::= PathPrimary PathMod?
+ */
+static const TrackerGrammarRule helper_PathElt_opt[] = { R(PathMod), NIL };
+static const TrackerGrammarRule rule_PathElt[] = { R(PathPrimary), OPT(helper_PathElt_opt), NIL };
+
+/* PathEltOrInverse ::= PathElt | '^' PathElt
+ */
+static const TrackerGrammarRule helper_PathEltOrInverse_seq[] = { L(PATH_INVERSE), R(PathElt), NIL };
+static const TrackerGrammarRule helper_PathEltOrInverse_or[] = { R(PathElt), S(helper_PathEltOrInverse_seq), NIL };
+static const TrackerGrammarRule rule_PathEltOrInverse[] = { OR(helper_PathEltOrInverse_or), NIL };
+
+/* PathSequence ::= PathEltOrInverse ( '/' PathEltOrInverse )*
+ */
+static const TrackerGrammarRule helper_PathSequence_seq[] = { L(PATH_SEQUENCE), R(PathEltOrInverse), NIL };
+static const TrackerGrammarRule helper_PathSequence_gte0[] = { S(helper_PathSequence_seq), NIL };
+static const TrackerGrammarRule rule_PathSequence[] = { R(PathEltOrInverse), GTE0(helper_PathSequence_gte0), NIL };
+
+/* PathAlternative ::= PathSequence ( '|' PathSequence )*
+ */
+static const TrackerGrammarRule helper_PathAlternative_seq[] = { L(PATH_ALTERNATIVE), R(PathSequence), NIL };
+static const TrackerGrammarRule helper_PathAlternative_gte0[] = { S(helper_PathAlternative_seq), NIL };
+static const TrackerGrammarRule rule_PathAlternative[] = { R(PathSequence), GTE0(helper_PathAlternative_gte0), NIL };
+
+/* Path ::= PathAlternative
+ */
+static const TrackerGrammarRule rule_Path[] = { R(PathAlternative), NIL };
+
+/* ObjectPath ::= GraphNodePath
+ */
+static const TrackerGrammarRule rule_ObjectPath[] = { R(GraphNodePath), NIL };
+
+/* ObjectListPath ::= ObjectPath ( ',' ObjectPath )*
+ */
+static const TrackerGrammarRule helper_ObjectListPath_seq[] = { L(COMMA), R(ObjectPath), NIL };
+static const TrackerGrammarRule helper_ObjectListPath_gte0[] = { S(helper_ObjectListPath_seq), NIL };
+static const TrackerGrammarRule rule_ObjectListPath[] = { R(ObjectPath), GTE0(helper_ObjectListPath_gte0), NIL };
+
+/* VerbSimple ::= Var
+ */
+static const TrackerGrammarRule rule_VerbSimple[] = { R(Var), NIL };
+
+/* VerbPath ::= Path
+ */
+static const TrackerGrammarRule rule_VerbPath[] = { R(Path), NIL };
+
+/* PropertyListPathNotEmpty ::= ( VerbPath | VerbSimple ) ObjectListPath ( ';' ( ( VerbPath | VerbSimple ) ObjectList )? )*
+ */
+static const TrackerGrammarRule helper_PropertyListPathNotEmpty_or_1[] = { R(VerbPath), R(VerbSimple), NIL };
+static const TrackerGrammarRule helper_PropertyListPathNotEmpty_seq_1[] = { OR(helper_PropertyListPathNotEmpty_or_1), R(ObjectList), NIL };
+static const TrackerGrammarRule helper_PropertyListPathNotEmpty_opt[] = { S(helper_PropertyListPathNotEmpty_seq_1), NIL };
+static const TrackerGrammarRule helper_PropertyListPathNotEmpty_seq_2[] = { L(SEMICOLON), OPT(helper_PropertyListPathNotEmpty_opt), NIL };
+static const TrackerGrammarRule helper_PropertyListPathNotEmpty_gte0[] = { S(helper_PropertyListPathNotEmpty_seq_2), NIL };
+static const TrackerGrammarRule rule_PropertyListPathNotEmpty[] = { OR(helper_PropertyListPathNotEmpty_or_1), R(ObjectListPath), GTE0(helper_PropertyListPathNotEmpty_gte0), NIL };
+
+/* PropertyListPath ::= PropertyListPathNotEmpty?
+ */
+static const TrackerGrammarRule helper_PropertyListPath_opt[] = { R(PropertyListPathNotEmpty), NIL };
+static const TrackerGrammarRule rule_PropertyListPath[] = { OPT(helper_PropertyListPath_opt), NIL };
+
+/* TriplesSameSubjectPath ::= VarOrTerm PropertyListPathNotEmpty | TriplesNodePath PropertyListPath
+ */
+static const TrackerGrammarRule helper_TriplesSameSubjectPath_seq_1[] = { R(VarOrTerm), R(PropertyListPathNotEmpty), NIL };
+static const TrackerGrammarRule helper_TriplesSameSubjectPath_seq_2[] = { R(TriplesNodePath), R(PropertyListPath), NIL };
+static const TrackerGrammarRule helper_TriplesSameSubjectPath_or[] = { S(helper_TriplesSameSubjectPath_seq_1), S(helper_TriplesSameSubjectPath_seq_2), NIL };
+static const TrackerGrammarRule rule_TriplesSameSubjectPath[] = { OR(helper_TriplesSameSubjectPath_or), NIL };
+
+/* Object ::= GraphNode
+ */
+static const TrackerGrammarRule rule_Object[] = { R(GraphNode), NIL };
+
+/* ObjectList ::= Object ( ',' Object )*
+ */
+static const TrackerGrammarRule helper_ObjectList_seq[] = { L(COMMA), R(Object), NIL };
+static const TrackerGrammarRule helper_ObjectList_gte0[] = { S(helper_ObjectList_seq), NIL };
+static const TrackerGrammarRule rule_ObjectList[] = { R(Object), GTE0(helper_ObjectList_gte0), NIL };
+
+/* Verb ::= VarOrIri | 'a'
+ */
+static const TrackerGrammarRule helper_Verb_or[] = { R(VarOrIri), L(A), NIL };
+static const TrackerGrammarRule rule_Verb[] = { OR(helper_Verb_or), NIL };
+
+/* PropertyListNotEmpty ::= Verb ObjectList ( ';' ( Verb ObjectList )? )*
+ */
+static const TrackerGrammarRule helper_PropertyListNotEmpty_seq_1[] = { R(Verb), R(ObjectList), NIL };
+static const TrackerGrammarRule helper_PropertyListNotEmpty_opt[] = { S(helper_PropertyListNotEmpty_seq_1), NIL };
+static const TrackerGrammarRule helper_PropertyListNotEmpty_seq_2[] = { L(SEMICOLON), OPT(helper_PropertyListNotEmpty_opt), NIL };
+static const TrackerGrammarRule helper_PropertyListNotEmpty_gte0[] = { S(helper_PropertyListNotEmpty_seq_2), NIL };
+static const TrackerGrammarRule rule_PropertyListNotEmpty[] = { R(Verb), R(ObjectList), GTE0(helper_PropertyListNotEmpty_gte0), NIL };
+
+/* PropertyList ::= PropertyListNotEmpty?
+ */
+static const TrackerGrammarRule helper_PropertyList_opt[] = { R(PropertyListNotEmpty), NIL };
+static const TrackerGrammarRule rule_PropertyList[] = { OPT(helper_PropertyList_opt), NIL };
+
+/* TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
+ */
+static const TrackerGrammarRule helper_TriplesSameSubject_seq_1[] = { R(VarOrTerm), R(PropertyListNotEmpty), NIL };
+static const TrackerGrammarRule helper_TriplesSameSubject_seq_2[] = { R(TriplesNode), R(PropertyList), NIL };
+static const TrackerGrammarRule helper_TriplesSameSubject_or[] = { S(helper_TriplesSameSubject_seq_1), S(helper_TriplesSameSubject_seq_2), NIL };
+static const TrackerGrammarRule rule_TriplesSameSubject[] = { OR(helper_TriplesSameSubject_or), NIL };
+
+/* ConstructTriples ::= TriplesSameSubject ( '.' ConstructTriples? )?
+ */
+static const TrackerGrammarRule helper_ConstructTriples_opt_1[] = { R(ConstructTriples), NIL };
+static const TrackerGrammarRule helper_ConstructTriples_seq[] = { L(DOT), OPT(helper_ConstructTriples_opt_1), NIL };
+static const TrackerGrammarRule helper_ConstructTriples_opt_2[] = { S(helper_ConstructTriples_seq), NIL };
+static const TrackerGrammarRule rule_ConstructTriples[] = { R(TriplesSameSubject), OPT(helper_ConstructTriples_opt_2), NIL };
+
+/* ConstructTemplate ::= '{' ConstructTriples? '}'
+ */
+static const TrackerGrammarRule helper_ConstructTemplate_opt[] = { R(ConstructTriples), NIL };
+static const TrackerGrammarRule rule_ConstructTemplate[] = { L(OPEN_BRACE), OPT(helper_ConstructTemplate_opt), L(CLOSE_BRACE), NIL };
+
+/* ExpressionList ::= NIL | '(' Expression ( ',' Expression )* ')'
+ */
+static const TrackerGrammarRule helper_ExpressionList_seq_1[] = { L(COMMA), R(Expression), NIL };
+static const TrackerGrammarRule helper_ExpressionList_gte0[] = { S(helper_ExpressionList_seq_1), NIL };
+static const TrackerGrammarRule helper_ExpressionList_seq_2[] = { L(OPEN_PARENS), R(Expression), GTE0(helper_ExpressionList_gte0), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_ExpressionList_or[] = { T(NIL), S(helper_ExpressionList_seq_2), NIL };
+static const TrackerGrammarRule rule_ExpressionList[] = { OR(helper_ExpressionList_or), NIL };
+
+/* ArgList ::= NIL | '(' 'DISTINCT'? Expression ( ',' Expression )* ')'
+ *
+ * TRACKER EXTENSION:
+ * First argument may be an ArgList in itself for fn:string-join, resulting in:
+ * ( ArgList | 'DISTINCT'? Expression )
+ */
+static const TrackerGrammarRule helper_ArgList_seq_1[] = { L(COMMA), R(Expression), NIL };
+static const TrackerGrammarRule helper_ArgList_gte0[] = { S(helper_ArgList_seq_1), NIL };
+static const TrackerGrammarRule helper_ArgList_opt[] = { L(DISTINCT), NIL };
+static const TrackerGrammarRule helper_ArgList_seq_3[] = { OPT(helper_ArgList_opt), R(Expression), NIL };
+static const TrackerGrammarRule helper_ArgList_or_2[] = { S(helper_ArgList_seq_3), R(ArgList), NIL };
+static const TrackerGrammarRule helper_ArgList_seq_2[] = { L(OPEN_PARENS), OR(helper_ArgList_or_2), GTE0(helper_ArgList_gte0), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_ArgList_or[] = { T(NIL), S(helper_ArgList_seq_2), NIL };
+static const TrackerGrammarRule rule_ArgList[] = { OR(helper_ArgList_or), NIL };
+
+/* FunctionCall ::= iri ArgList
+ */
+static const TrackerGrammarRule rule_FunctionCall[] = { R(iri), R(ArgList), NIL };
+
+/* Constraint ::= BrackettedExpression | BuiltInCall | FunctionCall
+ */
+static const TrackerGrammarRule helper_Constraint_or[] = { R(BrackettedExpression), R(BuiltInCall), R(FunctionCall), NIL };
+static const TrackerGrammarRule rule_Constraint[] = { OR(helper_Constraint_or), NIL };
+
+/* Filter ::= 'FILTER' Constraint
+ */
+static const TrackerGrammarRule rule_Filter[] = { L(FILTER), R(Constraint), NIL };
+
+/* GroupOrUnionGraphPattern ::= GroupGraphPattern ( 'UNION' GroupGraphPattern )*
+ */
+static const TrackerGrammarRule helper_GroupOrUnionGraphPattern_seq[] = { L(UNION), R(GroupGraphPattern), NIL };
+static const TrackerGrammarRule helper_GroupOrUnionGraphPattern_gte0[] = { S(helper_GroupOrUnionGraphPattern_seq), NIL };
+static const TrackerGrammarRule rule_GroupOrUnionGraphPattern[] = { R(GroupGraphPattern), GTE0(helper_GroupOrUnionGraphPattern_gte0), NIL };
+
+/* MinusGraphPattern ::= 'MINUS' GroupGraphPattern
+ */
+static const TrackerGrammarRule rule_MinusGraphPattern[] = { L(MINUS), R(GroupGraphPattern), NIL };
+
+/* DataBlockValue ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | 'UNDEF'
+ */
+static const TrackerGrammarRule helper_DataBlockValue_or[] = { R(iri), R(RDFLiteral), R(NumericLiteral), R(BooleanLiteral), L(UNDEF), NIL };
+static const TrackerGrammarRule rule_DataBlockValue[] = { OR(helper_DataBlockValue_or), NIL };
+
+/* InlineDataFull ::= ( NIL | '(' Var* ')' ) '{' ( '(' DataBlockValue* ')' | NIL )* '}'
+ */
+static const TrackerGrammarRule helper_InlineDataFull_gte0_1[] = { R(Var), NIL };
+static const TrackerGrammarRule helper_InlineDataFull_seq_1[] = { L(OPEN_PARENS), GTE0(helper_InlineDataFull_gte0_1), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_InlineDataFull_or_1[] = { T(NIL), S(helper_InlineDataFull_seq_1), NIL };
+static const TrackerGrammarRule helper_InlineDataFull_gte0_2[] = { R(DataBlockValue), NIL };
+static const TrackerGrammarRule helper_InlineDataFull_seq_2[] = { L(OPEN_PARENS), GTE0(helper_InlineDataFull_gte0_2), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_InlineDataFull_or_2[] = { S(helper_InlineDataFull_seq_2), T(NIL), NIL };
+static const TrackerGrammarRule helper_InlineDataFull_gte0_3[] = { OR(helper_InlineDataFull_or_2), NIL };
+static const TrackerGrammarRule rule_InlineDataFull[] = { OR(helper_InlineDataFull_or_1), L(OPEN_BRACE), GTE0(helper_InlineDataFull_gte0_3), L(CLOSE_BRACE), NIL };
+
+/* InlineDataOneVar ::= Var '{' DataBlockValue* '}'
+ */
+static const TrackerGrammarRule helper_InlineDataOneVar_gte0[] = { R(DataBlockValue), NIL };
+static const TrackerGrammarRule rule_InlineDataOneVar[] = { R(Var), L(OPEN_BRACE), GTE0(helper_InlineDataOneVar_gte0), L(CLOSE_BRACE), NIL };
+
+/* DataBlock ::= InlineDataOneVar | InlineDataFull
+ */
+static const TrackerGrammarRule helper_DataBlock_or[] = { R(InlineDataOneVar), R(InlineDataFull), NIL };
+static const TrackerGrammarRule rule_DataBlock[] = { OR(helper_DataBlock_or), NIL };
+
+/* InlineData ::= 'VALUES' DataBlock
+ */
+static const TrackerGrammarRule rule_InlineData[] = { L(VALUES), R(DataBlock), NIL };
+
+/* Bind ::= 'BIND' '(' Expression 'AS' Var ')'
+ */
+static const TrackerGrammarRule rule_Bind[] = { L(BIND), L(OPEN_PARENS), R(Expression), L(AS), R(Var), L(CLOSE_PARENS), NIL };
+
+/* ServiceGraphPattern ::= 'SERVICE' 'SILENT'? VarOrIri GroupGraphPattern
+ */
+static const TrackerGrammarRule helper_ServiceGraphPattern_opt[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_ServiceGraphPattern[] = { L(SERVICE), OPT(helper_ServiceGraphPattern_opt), R(VarOrIri), R(GroupGraphPattern), NIL };
+
+/* GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern
+ */
+static const TrackerGrammarRule rule_GraphGraphPattern[] = { L(GRAPH), R(VarOrIri), R(GroupGraphPattern), NIL };
+
+/* OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern
+ */
+static const TrackerGrammarRule rule_OptionalGraphPattern[] = { L(OPTIONAL), R(GroupGraphPattern), NIL };
+
+/* GraphPatternNotTriples ::= GroupOrUnionGraphPattern | OptionalGraphPattern | MinusGraphPattern | GraphGraphPattern | ServiceGraphPattern | Filter | Bind | InlineData
+ */
+static const TrackerGrammarRule helper_GraphPatternNotTriples_or[] = { R(GroupOrUnionGraphPattern), R(OptionalGraphPattern), R(MinusGraphPattern), R(GraphGraphPattern), R(ServiceGraphPattern), R(Filter), R(Bind), R(InlineData), NIL };
+static const TrackerGrammarRule rule_GraphPatternNotTriples[] = { OR(helper_GraphPatternNotTriples_or), NIL };
+
+/* TriplesBlock ::= TriplesSameSubjectPath ( '.' TriplesBlock? )?
+ */
+static const TrackerGrammarRule helper_TriplesBlock_opt_1[] = { R(TriplesBlock), NIL };
+static const TrackerGrammarRule helper_TriplesBlock_seq[] = { L(DOT), OPT(helper_TriplesBlock_opt_1), NIL };
+static const TrackerGrammarRule helper_TriplesBlock_opt_2[] = { S(helper_TriplesBlock_seq), NIL };
+static const TrackerGrammarRule rule_TriplesBlock[] = { R(TriplesSameSubjectPath), OPT(helper_TriplesBlock_opt_2), NIL };
+
+/* GroupGraphPatternSub ::= TriplesBlock? ( GraphPatternNotTriples '.'? TriplesBlock? )*
+ */
+static const TrackerGrammarRule helper_GroupGraphPatternSub_opt_1[] = { R(TriplesBlock), NIL };
+static const TrackerGrammarRule helper_GroupGraphPatternSub_opt_2[] = { L(DOT), NIL };
+static const TrackerGrammarRule helper_GroupGraphPatternSub_seq[] = { R(GraphPatternNotTriples), OPT(helper_GroupGraphPatternSub_opt_2), OPT(helper_GroupGraphPatternSub_opt_1), NIL };
+static const TrackerGrammarRule helper_GroupGraphPatternSub_gte0[] = { S(helper_GroupGraphPatternSub_seq), NIL };
+static const TrackerGrammarRule rule_GroupGraphPatternSub[] = { OPT(helper_GroupGraphPatternSub_opt_1), GTE0(helper_GroupGraphPatternSub_gte0), NIL };
+
+/* GroupGraphPattern ::= '{' ( SubSelect | GroupGraphPatternSub ) '}'
+ */
+static const TrackerGrammarRule helper_GroupGraphPattern_or[] = { R(SubSelect), R(GroupGraphPatternSub), NIL };
+static const TrackerGrammarRule rule_GroupGraphPattern[] = { L(OPEN_BRACE), OR(helper_GroupGraphPattern_or), L(CLOSE_BRACE), NIL };
+
+/* TriplesTemplate ::= TriplesSameSubject ( '.' TriplesTemplate? )?
+ */
+static const TrackerGrammarRule helper_TriplesTemplate_opt_2[] = { R(TriplesTemplate), NIL };
+static const TrackerGrammarRule helper_TriplesTemplate_seq[] = { L(DOT), OPT(helper_TriplesTemplate_opt_2), NIL };
+static const TrackerGrammarRule helper_TriplesTemplate_opt_1[] = { S(helper_TriplesTemplate_seq), NIL };
+static const TrackerGrammarRule rule_TriplesTemplate[] = { R(TriplesSameSubject), OPT(helper_TriplesTemplate_opt_1), NIL };
+
+/* QuadsNotTriples ::= 'GRAPH' VarOrIri '{' TriplesTemplate? '}'
+ */
+static const TrackerGrammarRule helper_QuadsNotTriples_opt[] = { R(TriplesTemplate), NIL };
+static const TrackerGrammarRule rule_QuadsNotTriples[] = { L(GRAPH), R(VarOrIri), L(OPEN_BRACE), OPT(helper_QuadsNotTriples_opt), L(CLOSE_BRACE), NIL };
+
+/* Quads ::= TriplesTemplate? ( QuadsNotTriples '.'? TriplesTemplate? )*
+ */
+static const TrackerGrammarRule helper_Quads_opt_3[] = { R(TriplesTemplate), NIL };
+static const TrackerGrammarRule helper_Quads_opt_1[] = { L(DOT), NIL };
+static const TrackerGrammarRule helper_Quads_opt_2[] = { R(TriplesTemplate), NIL };
+static const TrackerGrammarRule helper_Quads_seq[] = { R(QuadsNotTriples), OPT(helper_Quads_opt_1), OPT(helper_Quads_opt_2), NIL };
+static const TrackerGrammarRule helper_Quads_gte0[] = { S(helper_Quads_seq), NIL };
+static const TrackerGrammarRule rule_Quads[] = { OPT(helper_Quads_opt_3), GTE0(helper_Quads_gte0), NIL };
+
+/* QuadData ::= '{' Quads '}'
+ */
+static const TrackerGrammarRule rule_QuadData[] = { L(OPEN_BRACE), R(Quads), L(CLOSE_BRACE), NIL };
+
+/* QuadPattern ::= '{' Quads '}'
+ */
+static const TrackerGrammarRule rule_QuadPattern[] = { L(OPEN_BRACE), R(Quads), L(CLOSE_BRACE), NIL };
+
+/* GraphRef ::= 'GRAPH' iri
+ */
+static const TrackerGrammarRule rule_GraphRef[] = { L(GRAPH), R(iri), NIL };
+
+/* GraphRefAll ::= GraphRef | 'DEFAULT' | 'NAMED' | 'ALL'
+ */
+static const TrackerGrammarRule helper_GraphRefAll_or[] = { R(GraphRef), L(DEFAULT), L(NAMED), L(ALL), NIL };
+static const TrackerGrammarRule rule_GraphRefAll[] = { OR(helper_GraphRefAll_or), NIL };
+
+/* GraphOrDefault ::= 'DEFAULT' | 'GRAPH'? iri
+ */
+static const TrackerGrammarRule helper_GraphOrDefault_seq[] = { L(NAMED), R(iri), NIL };
+static const TrackerGrammarRule helper_GraphOrDefault_or[] = { L(DEFAULT), S(helper_GraphOrDefault_seq), NIL };
+static const TrackerGrammarRule rule_GraphOrDefault[] = { OR(helper_GraphOrDefault_or), NIL };
+
+/* UsingClause ::= 'USING' ( iri | 'NAMED' iri )
+ */
+static const TrackerGrammarRule helper_UsingClause_seq[] = { L(NAMED), R(iri), NIL };
+static const TrackerGrammarRule helper_UsingClause_or[] = { R(iri), S(helper_UsingClause_seq), NIL };
+static const TrackerGrammarRule rule_UsingClause[] = { L(USING), OR(helper_UsingClause_or), NIL };
+
+/* InsertClause ::= 'INSERT' QuadPattern
+ *
+ * TRACKER EXTENSION:
+ * Clause may start with:
+ * 'INSERT' ('OR' 'REPLACE')? ('SILENT')? ('INTO' iri)?
+ */
+static const TrackerGrammarRule helper_InsertClause_seq_1[] = { L(OR), L(REPLACE), NIL };
+static const TrackerGrammarRule helper_InsertClause_opt_1[] = { S(helper_InsertClause_seq_1), NIL };
+static const TrackerGrammarRule helper_InsertClause_opt_2[] = { L(SILENT), NIL };
+static const TrackerGrammarRule helper_InsertClause_seq_2[] = { L(INTO), R(iri), NIL };
+static const TrackerGrammarRule helper_InsertClause_opt_3[] = { S(helper_InsertClause_seq_2), NIL };
+static const TrackerGrammarRule rule_InsertClause[] = { L(INSERT), OPT(helper_InsertClause_opt_1), OPT(helper_InsertClause_opt_2), OPT(helper_InsertClause_opt_3), R(QuadPattern), NIL };
+
+/* DeleteClause ::= 'DELETE' QuadPattern
+ *
+ * TRACKER EXTENSION:
+ * Clause may start too with:
+ * 'DELETE' 'SILENT'
+ */
+static const TrackerGrammarRule helper_DeleteClause_opt_1[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_DeleteClause[] = { L(DELETE), OPT(helper_DeleteClause_opt_1), R(QuadPattern), NIL };
+
+/* Modify ::= ( 'WITH' iri )? ( DeleteClause InsertClause? | InsertClause ) UsingClause* 'WHERE' GroupGraphPattern
+ *
+ * TRACKER EXTENSION:
+ * Last part of the clause is:
+ * ('WHERE' GroupGraphPattern)?
+ */
+static const TrackerGrammarRule helper_Modify_seq_1[] = { L(WITH), R(iri), NIL };
+static const TrackerGrammarRule helper_Modify_opt_1[] = { S(helper_Modify_seq_1), NIL };
+static const TrackerGrammarRule helper_Modify_opt_2[] = { R(InsertClause), NIL };
+static const TrackerGrammarRule helper_Modify_seq_2[] = { R(DeleteClause), OPT(helper_Modify_opt_2), NIL };
+static const TrackerGrammarRule helper_Modify_or[] = { S(helper_Modify_seq_2), R(InsertClause), NIL };
+static const TrackerGrammarRule helper_Modify_gte0[] = { R(UsingClause), NIL };
+static const TrackerGrammarRule helper_Modify_seq_3[] = { L(WHERE), R(GroupGraphPattern), NIL };
+static const TrackerGrammarRule helper_Modify_opt_3[] = { S(helper_Modify_seq_3), NIL };
+static const TrackerGrammarRule rule_Modify[] = { OPT(helper_Modify_opt_1), OR(helper_Modify_or), GTE0(helper_Modify_gte0), OPT(helper_Modify_opt_3), NIL };
+
+/* DeleteWhere ::= 'DELETE WHERE' QuadPattern
+ */
+static const TrackerGrammarRule rule_DeleteWhere[] = { L(DELETE), L(WHERE), R(QuadPattern), NIL };
+
+/* DeleteData ::= 'DELETE DATA' QuadData
+ */
+static const TrackerGrammarRule rule_DeleteData[] = { L(DELETE), L(DATA), R(QuadData), NIL };
+
+/* InsertData ::= 'INSERT DATA' QuadData
+ */
+static const TrackerGrammarRule rule_InsertData[] = { L(INSERT), L(DATA), R(QuadData), NIL };
+
+/* Copy ::= 'COPY' 'SILENT'? GraphOrDefault 'TO' GraphOrDefault
+ */
+static const TrackerGrammarRule helper_Copy_opt[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_Copy[] = { L(COPY), OPT(helper_Copy_opt), R(GraphOrDefault), L(TO), R(GraphOrDefault), NIL };
+
+/* Move ::= 'MOVE' 'SILENT'? GraphOrDefault 'TO' GraphOrDefault
+ */
+static const TrackerGrammarRule helper_Move_opt[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_Move[] = { L(MOVE), OPT(helper_Move_opt), R(GraphOrDefault), L(TO), R(GraphOrDefault), NIL };
+
+/* Add ::= 'ADD' 'SILENT'? GraphOrDefault 'TO' GraphOrDefault
+ */
+static const TrackerGrammarRule helper_Add_opt[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_Add[] = { L(ADD), OPT(helper_Add_opt), R(GraphOrDefault), L(TO), R(GraphOrDefault), NIL };
+
+/* Create ::= 'CREATE' 'SILENT'? GraphRef
+ */
+static const TrackerGrammarRule helper_Create_opt[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_Create[] = { L(CREATE), OPT(helper_Create_opt), R(GraphRef), NIL };
+
+/* Drop ::= 'DROP' 'SILENT'? GraphRefAll
+ */
+static const TrackerGrammarRule helper_Drop_opt[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_Drop[] = { L(DROP), OPT(helper_Drop_opt), R(GraphRefAll), NIL };
+
+/* Clear ::= 'CLEAR' 'SILENT'? GraphRefAll
+ */
+static const TrackerGrammarRule helper_Clear_opt[] = { L(SILENT), NIL };
+static const TrackerGrammarRule rule_Clear[] = { L(CLEAR), OPT(helper_Clear_opt), R(GraphRefAll), NIL };
+
+/* Load ::= 'LOAD' 'SILENT'? iri ( 'INTO' GraphRef )?
+ */
+static const TrackerGrammarRule helper_Load_opt_1[] = { L(SILENT), NIL };
+static const TrackerGrammarRule helper_Load_seq[] = { L(INTO), R(GraphRef), NIL };
+static const TrackerGrammarRule helper_Load_opt_2[] = { S(helper_Load_seq), NIL };
+static const TrackerGrammarRule rule_Load[] = { L(LOAD), OPT(helper_Load_opt_1), R(iri), OPT(helper_Load_opt_2), NIL };
+
+/* Update1 ::= Load | Clear | Drop | Add | Move | Copy | Create | InsertData | DeleteData | DeleteWhere | Modify
+ */
+static const TrackerGrammarRule helper_Update1_or[] = { R(Load), R(Clear), R(Drop), R(Add), R(Move), R(Copy), R(Create), R(InsertData), R(DeleteData), R(DeleteWhere), R(Modify), NIL };
+static const TrackerGrammarRule rule_Update1[] = { OR(helper_Update1_or), NIL };
+
+/* ValuesClause ::= ( 'VALUES' DataBlock )?
+ */
+static const TrackerGrammarRule helper_ValuesClause_seq[] = { L(VALUES), R(DataBlock), NIL };
+static const TrackerGrammarRule helper_ValuesClause_opt[] = { S(helper_ValuesClause_seq), NIL };
+static const TrackerGrammarRule rule_ValuesClause[] = { OPT(helper_ValuesClause_opt), NIL };
+
+/* OffsetClause ::= 'OFFSET' INTEGER
+ */
+static const TrackerGrammarRule rule_OffsetClause[] = { L(OFFSET), T(INTEGER), NIL };
+
+/* LimitClause ::= 'LIMIT' INTEGER
+ */
+static const TrackerGrammarRule rule_LimitClause[] = { L(LIMIT), T(INTEGER), NIL };
+
+/* LimitOffsetClauses ::= LimitClause OffsetClause? | OffsetClause LimitClause?
+ */
+static const TrackerGrammarRule helper_LimitOffsetClauses_opt_1[] = { R(OffsetClause), NIL };
+static const TrackerGrammarRule helper_LimitOffsetClauses_seq_1[] = { R(LimitClause), OPT(helper_LimitOffsetClauses_opt_1), NIL };
+static const TrackerGrammarRule helper_LimitOffsetClauses_opt_2[] = { R(LimitClause), NIL };
+static const TrackerGrammarRule helper_LimitOffsetClauses_seq_2[] = { R(OffsetClause), OPT(helper_LimitOffsetClauses_opt_2), NIL };
+static const TrackerGrammarRule helper_LimitOffsetClauses_or[] = { S(helper_LimitOffsetClauses_seq_1), S(helper_LimitOffsetClauses_seq_2), NIL };
+static const TrackerGrammarRule rule_LimitOffsetClauses[] = { OR(helper_LimitOffsetClauses_or), NIL };
+
+/* OrderCondition ::= ( ( 'ASC' | 'DESC' ) BrackettedExpression )
+ * | ( Constraint | Var )
+ *
+ * TRACKER EXTENSION:
+ * The first rule is turned into the more generic:
+ * ( ( 'ASC' | 'DESC' ) Expression )
+ */
+static const TrackerGrammarRule helper_OrderCondition_or_1[] = { L(ASC), L(DESC), NIL };
+static const TrackerGrammarRule helper_OrderCondition_seq[] = { OR(helper_OrderCondition_or_1), R(Expression), NIL };
+static const TrackerGrammarRule helper_OrderCondition_or_2[] = { S(helper_OrderCondition_seq), R(Constraint), R(Var), NIL };
+static const TrackerGrammarRule rule_OrderCondition[] = { OR(helper_OrderCondition_or_2), NIL };
+
+/* OrderClause ::= 'ORDER' 'BY' OrderCondition+
+ */
+static const TrackerGrammarRule helper_OrderClause[] = { R(OrderCondition), NIL };
+static const TrackerGrammarRule rule_OrderClause[] = { L(ORDER), L(BY), GT0 (helper_OrderClause), NIL };
+
+/* HavingCondition ::= Constraint
+ */
+static const TrackerGrammarRule rule_HavingCondition[] = { R(Constraint), NIL };
+
+/* HavingClause ::= 'HAVING' HavingCondition+
+ */
+static const TrackerGrammarRule helper_HavingClause_gt0[] = { R(HavingCondition), NIL };
+static const TrackerGrammarRule rule_HavingClause[] = { L(HAVING), GT0(helper_HavingClause_gt0), NIL };
+
+/* GroupCondition ::= BuiltInCall | FunctionCall | '(' Expression ( 'AS' Var )? ')' | Var
+ */
+static const TrackerGrammarRule helper_GroupCondition_opt[] = { L(AS), R(Var), NIL };
+static const TrackerGrammarRule helper_GroupCondition_seq[] = { L(OPEN_PARENS), R(Expression), OPT(helper_GroupCondition_opt), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_GroupCondition_or[] = { R(BuiltInCall), R(FunctionCall), S(helper_GroupCondition_seq), R(Var), NIL };
+static const TrackerGrammarRule rule_GroupCondition[] = { OR(helper_GroupCondition_or), NIL };
+
+/* GroupClause ::= 'GROUP' 'BY' GroupCondition+
+ */
+static const TrackerGrammarRule helper_GroupClause_gt0[] = { R(GroupCondition), NIL };
+static const TrackerGrammarRule rule_GroupClause[] = { L(GROUP), L(BY), GT0(helper_GroupClause_gt0), NIL };
+
+/* SolutionModifier ::= GroupClause? HavingClause? OrderClause? LimitOffsetClauses?
+ */
+static const TrackerGrammarRule helper_SolutionModifier_opt_1[] = { R(GroupClause), NIL };
+static const TrackerGrammarRule helper_SolutionModifier_opt_2[] = { R(HavingClause), NIL };
+static const TrackerGrammarRule helper_SolutionModifier_opt_3[] = { R(OrderClause), NIL };
+static const TrackerGrammarRule helper_SolutionModifier_opt_4[] = { R(LimitOffsetClauses), NIL };
+static const TrackerGrammarRule rule_SolutionModifier[] = { OPT(helper_SolutionModifier_opt_1), OPT (helper_SolutionModifier_opt_2), OPT(helper_SolutionModifier_opt_3), OPT(helper_SolutionModifier_opt_4), NIL };
+
+
+/* WhereClause ::= 'WHERE'? GroupGraphPattern
+ */
+static const TrackerGrammarRule helper_WhereClause_opt[] = { L(WHERE), NIL };
+static const TrackerGrammarRule rule_WhereClause[] = { OPT(helper_WhereClause_opt), R(GroupGraphPattern), NIL };
+
+/* SourceSelector ::= iri
+ */
+static const TrackerGrammarRule rule_SourceSelector[] = { R(iri), NIL };
+
+/* NamedGraphClause ::= 'NAMED' SourceSelector
+ */
+static const TrackerGrammarRule rule_NamedGraphClause[] = { L(NAMED), R(SourceSelector), NIL };
+
+/* DefaultGraphClause ::= SourceSelector
+ */
+static const TrackerGrammarRule rule_DefaultGraphClause[] = { R(SourceSelector), NIL };
+
+/* DatasetClause ::= 'FROM' ( DefaultGraphClause | NamedGraphClause )
+ */
+static const TrackerGrammarRule helper_DatasetClause_or[] = { R(DefaultGraphClause), R(NamedGraphClause), NIL };
+static const TrackerGrammarRule rule_DatasetClause[] = { L(FROM), OR(helper_DatasetClause_or), NIL };
+
+/* AskQuery ::= 'ASK' DatasetClause* WhereClause SolutionModifier
+ */
+static const TrackerGrammarRule helper_AskQuery_gte0[] = { R(DatasetClause), NIL };
+static const TrackerGrammarRule rule_AskQuery[] = { L(ASK), GTE0(helper_AskQuery_gte0), R(WhereClause), R(SolutionModifier), NIL };
+
+/* DescribeQuery ::= 'DESCRIBE' ( VarOrIri+ | '*' ) DatasetClause* WhereClause? SolutionModifier
+ */
+static const TrackerGrammarRule helper_DescribeQuery_opt[] = { R(WhereClause), NIL };
+static const TrackerGrammarRule helper_DescribeQuery_gte0[] = { R(DatasetClause), NIL };
+static const TrackerGrammarRule helper_DescribeQuery_gt0[] = { R(VarOrIri), NIL };
+static const TrackerGrammarRule helper_DescribeQuery_or[] = { GT0 (helper_DescribeQuery_gt0), L(GLOB), NIL };
+static const TrackerGrammarRule rule_DescribeQuery[] = { L(DESCRIBE), OR(helper_DescribeQuery_or), GTE0(helper_DescribeQuery_gte0), OPT(helper_DescribeQuery_opt), R(SolutionModifier), NIL };
+
+/* ConstructQuery ::= 'CONSTRUCT' ( ConstructTemplate DatasetClause* WhereClause SolutionModifier |
+ * DatasetClause* 'WHERE' '{' TriplesTemplate? '}' SolutionModifier )
+ */
+static const TrackerGrammarRule helper_ConstructQuery_gte0[] = { R(DatasetClause), NIL };
+static const TrackerGrammarRule helper_ConstructQuery_seq_1[] = { R(ConstructTemplate), GTE0 (helper_ConstructQuery_gte0), R(WhereClause), R(SolutionModifier), NIL };
+static const TrackerGrammarRule helper_ConstructQuery_opt[] = { R(TriplesTemplate), NIL };
+static const TrackerGrammarRule helper_ConstructQuery_seq_2[] = { GTE0 (helper_ConstructQuery_gte0), L(WHERE), L(OPEN_BRACE), OPT (helper_ConstructQuery_opt), L(CLOSE_BRACE), R(SolutionModifier), NIL };
+static const TrackerGrammarRule helper_ConstructQuery_or[] = { S (helper_ConstructQuery_seq_1), S (helper_ConstructQuery_seq_2), NIL };
+static const TrackerGrammarRule rule_ConstructQuery[] = { L(CONSTRUCT), OR(helper_ConstructQuery_or), NIL };
+
+/* SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ | '*' )
+ *
+ * TRACKER EXTENSION:
+ * Variable set also accepts the following syntax:
+ * Expression ('AS' Var)?
+ * Var ('AS' Var)?
+ */
+static const TrackerGrammarRule ext_SelectClause_seq_1[] = { L(AS), R(Var), NIL };
+static const TrackerGrammarRule ext_SelectClause_opt[] = { S(ext_SelectClause_seq_1), NIL };
+static const TrackerGrammarRule ext_SelectClause_seq_2[] = { R(Var), OPT(ext_SelectClause_opt), NIL };
+static const TrackerGrammarRule ext_SelectClause_seq_3[] = { R(Expression), OPT(ext_SelectClause_opt), NIL };
+static const TrackerGrammarRule helper_SelectClause_seq_1[] = { L(OPEN_PARENS), R(Expression), L(AS), R(Var), L(CLOSE_PARENS), NIL };
+static const TrackerGrammarRule helper_SelectClause_or_1[] = { L(DISTINCT), L(REDUCED), NIL };
+static const TrackerGrammarRule helper_SelectClause_or_2[] = { S(ext_SelectClause_seq_2), S(ext_SelectClause_seq_3), R(Var), S(helper_SelectClause_seq_1), NIL };
+static const TrackerGrammarRule helper_SelectClause_gt0[] = { OR(helper_SelectClause_or_2), NIL };
+static const TrackerGrammarRule helper_SelectClause_opt[] = { OR(helper_SelectClause_or_1), NIL };
+static const TrackerGrammarRule helper_SelectClause_or_3[] = { L(GLOB), GT0(helper_SelectClause_gt0), NIL };
+static const TrackerGrammarRule rule_SelectClause[] = { L(SELECT), OPT(helper_SelectClause_opt), OR(helper_SelectClause_or_3), NIL };
+
+/* SubSelect ::= SelectClause WhereClause SolutionModifier ValuesClause
+ */
+static const TrackerGrammarRule rule_SubSelect[] = { R(SelectClause), R(WhereClause), R(SolutionModifier), R(ValuesClause), NIL };
+
+/* SelectQuery ::= SelectClause DatasetClause* WhereClause SolutionModifier
+ */
+static const TrackerGrammarRule helper_SelectQuery_gte0[] = { R(DatasetClause), NIL };
+static const TrackerGrammarRule rule_SelectQuery[] = { R(SelectClause), GTE0(helper_SelectQuery_gte0), R(WhereClause), R(SolutionModifier), NIL };
+
+/* PrefixDecl ::= 'PREFIX' PNAME_NS IRIREF
+ */
+static const TrackerGrammarRule rule_PrefixDecl[] = { L(PREFIX), T(PNAME_NS), T(IRIREF), NIL };
+
+
+/* BaseDecl ::= 'BASE' IRIREF
+ */
+static const TrackerGrammarRule rule_BaseDecl[] = { L(BASE), T(IRIREF), NIL };
+
+/* Prologue ::= ( BaseDecl | PrefixDecl )*
+ */
+static const TrackerGrammarRule helper_Prologue_or[] = { R(BaseDecl), R(PrefixDecl), NIL };
+static const TrackerGrammarRule helper_Prologue_gte0[] = { OR(helper_Prologue_or), NIL };
+static const TrackerGrammarRule rule_Prologue[] = { GTE0 (helper_Prologue_gte0), NIL };
+
+/* Update ::= Prologue ( Update1 ( ';' Update )? )?
+ *
+ * TRACKER EXTENSION:
+ * ';' separator is made optional.
+ */
+static const TrackerGrammarRule helper_Update_opt_3[] = { L(SEMICOLON), NIL };
+static const TrackerGrammarRule helper_Update_seq_1[] = { OPT(helper_Update_opt_3), R(Update), NIL };
+static const TrackerGrammarRule helper_Update_opt_1[] = { S (helper_Update_seq_1), NIL };
+static const TrackerGrammarRule helper_Update_seq_2[] = { R(Update1), OPT (helper_Update_opt_1), NIL };
+static const TrackerGrammarRule helper_Update_opt_2[] = { S(helper_Update_seq_2), NIL };
+static const TrackerGrammarRule rule_Update[] = { R(Prologue), OPT(helper_Update_opt_2), NIL };
+
+/* UpdateUnit ::= Update
+ */
+static const TrackerGrammarRule rule_UpdateUnit[] = { R(Update), NIL };
+
+/* Query ::= Prologue
+ * ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery )
+ * ValuesClause
+ */
+static const TrackerGrammarRule helper_Query_or[] = { R(SelectQuery), R(ConstructQuery), R(DescribeQuery), R(AskQuery), NIL };
+static const TrackerGrammarRule rule_Query[] = { R(Prologue), OR(helper_Query_or), R(ValuesClause), NIL };
+
+/* QueryUnit ::= Query
+ */
+static const TrackerGrammarRule rule_QueryUnit[] = { R(Query), NIL };
+
+/* Inline funcs for terminal parsers */
+#define READ_CHAR(set, _C_) \
+ G_STMT_START { \
+ gchar ch = *str; \
+ if ((set)) { \
+ str++; \
+ } else { \
+ _C_; \
+ } \
+ } G_STMT_END
+
+#define READ_UNICHAR(set, _C_) \
+ G_STMT_START { \
+ gunichar ch = g_utf8_get_char (str); \
+ if ((set)) { \
+ str = g_utf8_next_char (str); \
+ } else { \
+ _C_; \
+ } \
+ } G_STMT_END
+
+#define READ_STRING(set, _C_) \
+ G_STMT_START { \
+ const gchar *tmp = str; \
+ while (str < end) { \
+ gchar ch = *str; \
+ if ((set)) { \
+ str++; \
+ } else { \
+ break; \
+ } \
+ } \
+ if (tmp == str) { \
+ _C_; \
+ } \
+ } G_STMT_END
+
+#define READ_UNICODE_STRING(set, _C_) \
+ G_STMT_START { \
+ const gchar *tmp = str; \
+ while (str < end) { \
+ gunichar ch = g_utf8_get_char (str); \
+ if ((set)) { \
+ str = g_utf8_next_char (str); \
+ } else { \
+ break; \
+ } \
+ } \
+ if (tmp == str) { \
+ _C_; \
+ } \
+ } G_STMT_END
+
+#define OPTIONAL_CHAR(set) READ_CHAR(set, )
+#define OPTIONAL_UNICHAR(set) READ_UNICHAR(set, )
+#define OPTIONAL_STRING(set) READ_STRING(set, )
+#define OPTIONAL_UNICODE_STRING(set) READ_UNICODE_STRING(set, )
+
+#define ACCEPT_CHAR(set) READ_CHAR(set, return FALSE)
+#define ACCEPT_UNICHAR(set) READ_UNICHAR(set, return FALSE)
+#define ACCEPT_STRING(set) READ_STRING(set, return FALSE)
+#define ACCEPT_UNICODE_STRING(set) READ_UNICODE_STRING(set, return FALSE)
+
+
+#define RANGE_NUMBER ((ch >= '0' && ch <= '9'))
+#define RANGE_UPPERCASE_ASCII ((ch >= 'A' && ch <= 'Z'))
+#define RANGE_LOWERCASE_ASCII ((ch >= 'a' && ch <= 'z'))
+#define RANGE_ASCII (RANGE_UPPERCASE_ASCII || RANGE_LOWERCASE_ASCII)
+
+/* PN_CHARS_BASE ::= [A-Z] | [a-z] | [#x00C0-#x00D6] | [#x00D8-#x00F6] | [#x00F8-#x02FF] | [#x0370-#x037D] | [#x037F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF]
+ */
+#define PN_CHARS_BASE \
+ (RANGE_ASCII || \
+ (ch >= 0x00C0 && ch <= 0x00D6) || \
+ (ch >= 0x00D8 && ch <= 0x00F6) || \
+ (ch >= 0x00F8 && ch <= 0x02FF) || \
+ (ch >= 0x0370 && ch <= 0x037D) || \
+ (ch >= 0x037F && ch <= 0x1FFF) || \
+ (ch >= 0x200C && ch <= 0x200D) || \
+ (ch >= 0x2070 && ch <= 0x218F) || \
+ (ch >= 0x2C00 && ch <= 0x2FEF) || \
+ (ch >= 0x3001 && ch <= 0xD7FF) || \
+ (ch >= 0xF900 && ch <= 0xFDCF) || \
+ (ch >= 0xFDF0 && ch <= 0xFFFD) || \
+ (ch >= 0x10000 && ch <= 0xEFFFF))
+
+/* PN_CHARS_U ::= PN_CHARS_BASE | '_'
+ */
+#define PN_CHARS_U \
+ (PN_CHARS_BASE || ch == '_')
+
+/* PN_CHARS ::= PN_CHARS_U | '-' | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040]
+ */
+#define PN_CHARS \
+ (PN_CHARS_U || ch == '-' || RANGE_NUMBER || \
+ ch == 0x00B7 || (ch >= 0x0300 && ch <= 0x036F) || \
+ (ch >= 0x203F && ch <= 0x2040))
+
+/* WS ::= #x20 | #x9 | #xD | #xA
+ */
+#define WS \
+ (ch == 0x20 || ch == 0x9 || ch == 0xD || ch == 0xA)
+
+/* HEX ::= [0-9] | [A-F] | [a-f]
+ */
+#define HEX \
+ (RANGE_NUMBER || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
+
+/* IRIREF ::= '<' ([^<>"{}|^`\]-[#x00-#x20])* '>'
+ */
+static inline gboolean
+terminal_IRIREF (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '<'));
+ OPTIONAL_UNICODE_STRING ((ch != '<' && ch != '>' &&
+ ch != '"' && ch != '{' &&
+ ch != '}' && ch != '|' &&
+ ch != '^' && ch != '`' &&
+ ch != '\\' && !(ch >= 0x00 && ch <= 0x20)));
+ ACCEPT_CHAR ((ch == '>'));
+ *str_out = str;
+ return TRUE;
+}
+
+/* PN_PREFIX ::= PN_CHARS_BASE ((PN_CHARS|'.')* PN_CHARS)?
+ */
+static inline gboolean
+terminal_PN_PREFIX (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_UNICHAR (PN_CHARS_BASE);
+
+ while (str < end) {
+ READ_UNICODE_STRING ((PN_CHARS || ch == '.'), goto out);
+
+ /* The last PN_CHARS shall be read above, check the last
+ * char being read is within that range (i.e. not a dot).
+ */
+ if (*(str - 1) == '.')
+ str--;
+
+ break;
+ }
+out:
+ *str_out = str;
+ return TRUE;
+}
+
+/* PNAME_NS ::= PN_PREFIX? ':'
+ */
+static inline gboolean
+terminal_PNAME_NS (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ terminal_PN_PREFIX (str, end, &str);
+ ACCEPT_UNICHAR ((ch == ':'));
+ *str_out = str;
+ return TRUE;
+}
+
+/* PERCENT ::= '%' HEX HEX
+ */
+static inline gboolean
+terminal_PERCENT (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '%'));
+ ACCEPT_CHAR (HEX);
+ ACCEPT_CHAR (HEX);
+ *str_out = str;
+ return TRUE;
+}
+
+/* PN_LOCAL_ESC ::= '\' ( '_' | '~' | '.' | '-' | '!' | '$' | '&' | "'" | '(' | ')' | '*' | '+' | ',' | ';' | '=' | '/' | '?' | '#' | '@' | '%' )
+ */
+static inline gboolean
+terminal_PN_LOCAL_ESC (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '\\'));
+ ACCEPT_CHAR ((ch == '_' || ch == '~' || ch == '.' || ch == '-' ||
+ ch == '!' || ch == '$' || ch == '&' || ch == '\'' ||
+ ch == '(' || ch == ')' || ch == '*' || ch == '+' ||
+ ch == ',' || ch == ';' || ch == '=' || ch == '/' ||
+ ch == '?' || ch == '#' || ch == '@' || ch == '%'));
+ *str_out = str;
+ return TRUE;
+}
+
+/* PLX ::= PERCENT | PN_LOCAL_ESC
+ */
+static inline gboolean
+terminal_PLX (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ if (terminal_PERCENT (str, end, str_out))
+ return TRUE;
+ if (terminal_PN_LOCAL_ESC (str, end, str_out))
+ return TRUE;
+ return FALSE;
+}
+
+/* PN_LOCAL ::= (PN_CHARS_U | ':' | [0-9] | PLX ) ((PN_CHARS | '.' | ':' | PLX)* (PN_CHARS | ':' | PLX) )?
+ */
+static inline gboolean
+terminal_PN_LOCAL (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ if (!terminal_PLX (str, end, &str))
+ ACCEPT_UNICHAR (PN_CHARS_U || RANGE_NUMBER || ch == ':');
+
+ while (str < end) {
+ if (!terminal_PLX (str, end, &str))
+ READ_UNICHAR ((PN_CHARS || ch == '.' || ch == ':'), goto out);
+ }
+
+out:
+ /* check the last char being read is not a period. */
+ if (*(str - 1) == '.')
+ str--;
+
+ *str_out = str;
+ return TRUE;
+}
+
+/* PNAME_LN ::= PNAME_NS PN_LOCAL
+ */
+static inline gboolean
+terminal_PNAME_LN (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ if (!terminal_PNAME_NS (str, end, &str))
+ return FALSE;
+ if (!terminal_PN_LOCAL (str, end, str_out))
+ return FALSE;
+ return TRUE;
+}
+
+/* BLANK_NODE_LABEL ::= '_:' ( PN_CHARS_U | [0-9] ) ((PN_CHARS|'.')* PN_CHARS)?
+ */
+static inline gboolean
+terminal_BLANK_NODE_LABEL (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '_'));
+ ACCEPT_CHAR ((ch == ':'));
+ ACCEPT_UNICHAR (PN_CHARS_U || RANGE_NUMBER);
+
+ OPTIONAL_UNICODE_STRING (PN_CHARS || ch == '.');
+ OPTIONAL_UNICHAR (PN_CHARS);
+ *str_out = str;
+ return TRUE;
+}
+
+/* VARNAME ::= ( PN_CHARS_U | [0-9] ) ( PN_CHARS_U | [0-9] | #x00B7 | [#x0300-#x036F] | [#x203F-#x2040] )*
+ */
+static inline gboolean
+terminal_VARNAME (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR(PN_CHARS_U || RANGE_NUMBER);
+ OPTIONAL_UNICODE_STRING (PN_CHARS_U || RANGE_NUMBER ||
+ ch == 0x00B7 || (ch >= 0x0300 && ch <= 0x036F) ||
+ (ch >= 0x203F && ch <= 0x2040));
+ *str_out = str;
+ return TRUE;
+}
+
+/* VAR1 ::= '?' VARNAME
+ */
+static inline gboolean
+terminal_VAR1 (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR((ch == '?'));
+ return terminal_VARNAME (str, end, str_out);
+}
+
+/* VAR1 ::= '$' VARNAME
+ */
+static inline gboolean
+terminal_VAR2 (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR((ch == '$'));
+ return terminal_VARNAME (str, end, str_out);
+}
+
+/* PARAMETERIZED_VAR ::= '~' VARNAME
+ */
+static inline gboolean
+terminal_PARAMETERIZED_VAR (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR((ch == '~'));
+ return terminal_VARNAME (str, end, str_out);
+}
+
+/* LANGTAG ::= '@' [a-zA-Z]+ ('-' [a-zA-Z0-9]+)*
+ */
+static inline gboolean
+terminal_LANGTAG (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '@'));
+ ACCEPT_STRING (RANGE_ASCII);
+
+ while (str < end) {
+ READ_CHAR ((ch == '-'), goto out);
+ ACCEPT_STRING (RANGE_ASCII || RANGE_NUMBER);
+ }
+out:
+ *str_out = str;
+ return TRUE;
+}
+
+/* INTEGER ::= [0-9]+
+ */
+static inline gboolean
+terminal_INTEGER (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_STRING (RANGE_NUMBER);
+ *str_out = str;
+ return TRUE;
+}
+
+/* DECIMAL ::= [0-9]* '.' [0-9]+
+ */
+static inline gboolean
+terminal_DECIMAL (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ OPTIONAL_STRING (RANGE_NUMBER);
+ ACCEPT_CHAR ((ch == '.'));
+ ACCEPT_STRING (RANGE_NUMBER);
+ *str_out = str;
+ return TRUE;
+}
+
+/* EXPONENT ::= [eE] [+-]? [0-9]+
+ */
+static inline gboolean
+terminal_EXPONENT (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == 'e' || ch == 'E'));
+ OPTIONAL_CHAR ((ch == '+' || ch == '-'));
+ ACCEPT_STRING (RANGE_NUMBER);
+ *str_out = str;
+ return TRUE;
+}
+
+/* DOUBLE ::= [0-9]+ '.' [0-9]* EXPONENT | '.' ([0-9])+ EXPONENT | ([0-9])+ EXPONENT
+ */
+static inline gboolean
+terminal_DOUBLE (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ const gchar *start = str;
+
+ OPTIONAL_STRING (RANGE_NUMBER);
+ OPTIONAL_CHAR ((ch == '.'));
+ OPTIONAL_STRING (RANGE_NUMBER);
+
+ if (str == start)
+ return FALSE;
+ if (str == start + 1 && str[0] != '.')
+ return FALSE;
+
+ return terminal_EXPONENT (str, end, str_out);
+}
+
+/* INTEGER_POSITIVE ::= '+' INTEGER
+ */
+static inline gboolean
+terminal_INTEGER_POSITIVE (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '+'));
+ return terminal_INTEGER (str, end, str_out);
+}
+
+/* DECIMAL_POSITIVE ::= '+' DECIMAL
+ */
+static inline gboolean
+terminal_DECIMAL_POSITIVE (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '+'));
+ return terminal_DECIMAL (str, end, str_out);
+}
+
+/* DOUBLE_POSITIVE ::= '+' DOUBLE
+ */
+static inline gboolean
+terminal_DOUBLE_POSITIVE (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '+'));
+ return terminal_DOUBLE_POSITIVE (str, end, str_out);
+}
+
+/* INTEGER_NEGATIVE ::= '-' INTEGER
+ */
+static inline gboolean
+terminal_INTEGER_NEGATIVE (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '-'));
+ return terminal_INTEGER (str, end, str_out);
+}
+
+/* DECIMAL_NEGATIVE ::= '-' DECIMAL
+ */
+static inline gboolean
+terminal_DECIMAL_NEGATIVE (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '-'));
+ return terminal_DECIMAL (str, end, str_out);
+}
+
+/* DOUBLE_NEGATIVE ::= '-' DOUBLE
+ */
+static inline gboolean
+terminal_DOUBLE_NEGATIVE (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '-'));
+ return terminal_DOUBLE (str, end, str_out);
+}
+
+/* ECHAR ::= '\' [tbnrf\"']
+ */
+static inline gboolean
+terminal_ECHAR (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '\\'));
+ ACCEPT_CHAR ((ch == 't' || ch == 'b' || ch == 'n' ||
+ ch == 'r' || ch == 'f' || ch == '\\' ||
+ ch == '"' || ch == '\''));
+ *str_out = str;
+ return TRUE;
+}
+
+/* STRING_LITERAL1 ::= "'" ( ([^#x27#x5C#xA#xD]) | ECHAR )* "'"
+ */
+static inline gboolean
+terminal_STRING_LITERAL1 (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '\''));
+
+ while (str < end) {
+ if (!terminal_ECHAR (str, end, &str)) {
+ READ_UNICHAR ((ch != 0x27 && ch != 0x5C &&
+ ch != 0xA && ch != 0xD), goto out);
+ }
+ }
+
+out:
+ ACCEPT_CHAR ((ch == '\''));
+ *str_out = str;
+ return TRUE;
+}
+
+/* STRING_LITERAL2 ::= '"' ( ([^#x22#x5C#xA#xD]) | ECHAR )* '"'
+ */
+static inline gboolean
+terminal_STRING_LITERAL2 (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '"'));
+
+ while (str < end) {
+ if (!terminal_ECHAR (str, end, &str))
+ READ_UNICHAR ((ch != 0x22 && ch != 0x5C &&
+ ch != 0xA && ch != 0xD), goto out);
+ }
+
+out:
+ ACCEPT_CHAR ((ch == '"'));
+ *str_out = str;
+ return TRUE;
+}
+
+/* STRING_LITERAL_LONG1 ::= "'''" ( ( "'" | "''" )? ( [^'\] | ECHAR ) )* "'''"
+ */
+static inline gboolean
+terminal_STRING_LITERAL_LONG1 (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '\''));
+ ACCEPT_CHAR ((ch == '\''));
+ ACCEPT_CHAR ((ch == '\''));
+
+ while (str < end) {
+ if (strncmp (str, "\\'", 2) == 0) {
+ str += 2;
+ } else if (strncmp (str, "'''", 3) == 0) {
+ str += 3;
+ *str_out = str;
+ return TRUE;
+ } else {
+ str++;
+ }
+ }
+
+ return FALSE;
+}
+
+/* STRING_LITERAL_LONG2 ::= '"""' ( ( '"' | '""' )? ( [^"\] | ECHAR ) )* '"""'
+ */
+static inline gboolean
+terminal_STRING_LITERAL_LONG2 (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '"'));
+ ACCEPT_CHAR ((ch == '"'));
+ ACCEPT_CHAR ((ch == '"'));
+
+ while (str < end) {
+ if (strncmp (str, "\\\"", 2) == 0) {
+ str += 2;
+ } else if (strncmp (str, "\"\"\"", 3) == 0) {
+ str += 3;
+ *str_out = str;
+ return TRUE;
+ } else {
+ str++;
+ }
+ }
+
+ return FALSE;
+}
+
+/* NIL ::= '(' WS* ')'
+ */
+static inline gboolean
+terminal_NIL (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '('));
+ OPTIONAL_STRING (WS);
+ ACCEPT_CHAR ((ch == ')'));
+ *str_out = str;
+ return TRUE;
+}
+
+/* ANON ::= '[' WS* ']'
+ */
+static inline gboolean
+terminal_ANON (const gchar *str,
+ const gchar *end,
+ const gchar **str_out)
+{
+ ACCEPT_CHAR ((ch == '['));
+ OPTIONAL_STRING (WS);
+ ACCEPT_CHAR ((ch == ']'));
+ *str_out = str;
+ return TRUE;
+}
+
+#define NAMED_RULE(rule) (named_rules[NAMED_RULE_##rule])
+
+/* Order must match the enum's */
+static const TrackerGrammarRule *named_rules[N_NAMED_RULES] = {
+ rule_QueryUnit,
+ rule_UpdateUnit,
+ rule_Query,
+ rule_Update,
+ rule_SelectClause,
+ rule_Prologue,
+ rule_BaseDecl,
+ rule_PrefixDecl,
+ rule_SelectQuery,
+ rule_SubSelect,
+ rule_ConstructQuery,
+ rule_DescribeQuery,
+ rule_AskQuery,
+ rule_DatasetClause,
+ rule_DefaultGraphClause,
+ rule_NamedGraphClause,
+ rule_SourceSelector,
+ rule_WhereClause,
+ rule_SolutionModifier,
+ rule_GroupClause,
+ rule_GroupCondition,
+ rule_HavingClause,
+ rule_HavingCondition,
+ rule_OrderClause,
+ rule_OrderCondition,
+ rule_LimitOffsetClauses,
+ rule_LimitClause,
+ rule_OffsetClause,
+ rule_ValuesClause,
+ rule_Update1,
+ rule_Load,
+ rule_Clear,
+ rule_Drop,
+ rule_Create,
+ rule_Add,
+ rule_Move,
+ rule_Copy,
+ rule_InsertData,
+ rule_DeleteData,
+ rule_DeleteWhere,
+ rule_Modify,
+ rule_DeleteClause,
+ rule_InsertClause,
+ rule_UsingClause,
+ rule_GraphOrDefault,
+ rule_GraphRefAll,
+ rule_GraphRef,
+ rule_QuadPattern,
+ rule_QuadData,
+ rule_Quads,
+ rule_QuadsNotTriples,
+ rule_TriplesTemplate,
+ rule_GroupGraphPatternSub,
+ rule_TriplesBlock,
+ rule_GraphPatternNotTriples,
+ rule_OptionalGraphPattern,
+ rule_GraphGraphPattern,
+ rule_ServiceGraphPattern,
+ rule_Bind,
+ rule_InlineData,
+ rule_DataBlock,
+ rule_InlineDataOneVar,
+ rule_InlineDataFull,
+ rule_DataBlockValue,
+ rule_MinusGraphPattern,
+ rule_GroupOrUnionGraphPattern,
+ rule_Filter,
+ rule_Constraint,
+ rule_FunctionCall,
+ rule_ArgList,
+ rule_ExpressionList,
+ rule_ConstructTemplate,
+ rule_ConstructTriples,
+ rule_TriplesSameSubject,
+ rule_GroupGraphPattern,
+ rule_PropertyList,
+ rule_PropertyListNotEmpty,
+ rule_Verb,
+ rule_ObjectList,
+ rule_Object,
+ rule_TriplesSameSubjectPath,
+ rule_PropertyListPath,
+ rule_PropertyListPathNotEmpty,
+ rule_VerbPath,
+ rule_VerbSimple,
+ rule_ObjectListPath,
+ rule_ObjectPath,
+ rule_Path,
+ rule_PathAlternative,
+ rule_PathSequence,
+ rule_PathEltOrInverse,
+ rule_PathElt,
+ rule_PathMod,
+ rule_PathPrimary,
+ rule_PathNegatedPropertySet,
+ rule_PathOneInPropertySet,
+ rule_Integer,
+ rule_TriplesNode,
+ rule_BlankNodePropertyList,
+ rule_TriplesNodePath,
+ rule_BlankNodePropertyListPath,
+ rule_Collection,
+ rule_CollectionPath,
+ rule_GraphNode,
+ rule_GraphNodePath,
+ rule_VarOrTerm,
+ rule_VarOrIri,
+ rule_Var,
+ rule_GraphTerm,
+ rule_Expression,
+ rule_ConditionalOrExpression,
+ rule_ConditionalAndExpression,
+ rule_ValueLogical,
+ rule_RelationalExpression,
+ rule_NumericExpression,
+ rule_AdditiveExpression,
+ rule_MultiplicativeExpression,
+ rule_UnaryExpression,
+ rule_PrimaryExpression,
+ rule_iriOrFunction,
+ rule_BrackettedExpression,
+ rule_BuiltInCall,
+ rule_RegexExpression,
+ rule_SubstringExpression,
+ rule_StrReplaceExpression,
+ rule_ExistsFunc,
+ rule_NotExistsFunc,
+ rule_Aggregate,
+ rule_RDFLiteral,
+ rule_NumericLiteral,
+ rule_NumericLiteralUnsigned,
+ rule_NumericLiteralPositive,
+ rule_NumericLiteralNegative,
+ rule_BooleanLiteral,
+ rule_String,
+ rule_iri,
+ rule_PrefixedName,
+ rule_BlankNode
+};
+
+static const TrackerTerminalFunc terminal_funcs[N_TERMINAL_TYPES] = {
+ terminal_IRIREF,
+ terminal_PNAME_NS,
+ terminal_PNAME_LN,
+ terminal_BLANK_NODE_LABEL,
+ terminal_VAR1,
+ terminal_VAR2,
+ terminal_LANGTAG,
+ terminal_INTEGER,
+ terminal_DECIMAL,
+ terminal_DOUBLE,
+ terminal_INTEGER_POSITIVE,
+ terminal_DECIMAL_POSITIVE,
+ terminal_DOUBLE_POSITIVE,
+ terminal_INTEGER_NEGATIVE,
+ terminal_DECIMAL_NEGATIVE,
+ terminal_DOUBLE_NEGATIVE,
+ terminal_STRING_LITERAL1,
+ terminal_STRING_LITERAL2,
+ terminal_STRING_LITERAL_LONG1,
+ terminal_STRING_LITERAL_LONG2,
+ terminal_NIL,
+ terminal_ANON,
+ terminal_PARAMETERIZED_VAR,
+};
+
+static inline const TrackerGrammarRule *
+tracker_grammar_rule_get_children (const TrackerGrammarRule *rule)
+{
+ if (rule->type == RULE_TYPE_RULE) {
+ g_assert (rule->data.rule < N_NAMED_RULES);
+ return named_rules[rule->data.rule];
+ } else if (rule->type != RULE_TYPE_LITERAL &&
+ rule->type != RULE_TYPE_TERMINAL) {
+ return rule->data.children;
+ }
+
+ return NULL;
+}
+
+static inline TrackerTerminalFunc
+tracker_grammar_rule_get_terminal_func (const TrackerGrammarRule *rule)
+{
+ if (rule->type == RULE_TYPE_TERMINAL) {
+ g_assert (rule->data.terminal < N_TERMINAL_TYPES);
+ return terminal_funcs[rule->data.terminal];
+ }
+
+ return NULL;
+}
+
+static inline gboolean
+tracker_grammar_rule_is_a (const TrackerGrammarRule *rule,
+ TrackerGrammarRuleType rule_type,
+ guint value)
+{
+ if (rule->type != rule_type)
+ return FALSE;
+
+ switch (rule->type) {
+ case RULE_TYPE_NIL:
+ case RULE_TYPE_SEQUENCE:
+ case RULE_TYPE_OR:
+ case RULE_TYPE_GT0:
+ case RULE_TYPE_GTE0:
+ case RULE_TYPE_OPTIONAL:
+ return TRUE;
+ case RULE_TYPE_RULE:
+ g_assert (value < N_NAMED_RULES);
+ return (rule->data.rule == (TrackerGrammarNamedRule) value);
+ case RULE_TYPE_TERMINAL:
+ g_assert (value < N_TERMINAL_TYPES);
+ return (rule->data.terminal == (TrackerGrammarTerminalType) value);
+ case RULE_TYPE_LITERAL:
+ g_assert (value < N_LITERALS);
+ return (rule->data.literal == (TrackerGrammarLiteral) value);
+ }
+
+ return FALSE;
+}
+
+#endif /* __TRACKER_GRAMMAR_H__ */
diff --git a/src/libtracker-data/tracker-sparql-parser.c b/src/libtracker-data/tracker-sparql-parser.c
new file mode 100644
index 000000000..d6cc6e532
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql-parser.c
@@ -0,0 +1,874 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+#include "config.h"
+
+#include "tracker-sparql-query.h"
+#include "tracker-sparql-parser.h"
+#include "tracker-sparql-grammar.h"
+
+#include "string.h"
+
+typedef struct _TrackerRuleState TrackerRuleState;
+typedef struct _TrackerNodeTree TrackerNodeTree;
+typedef struct _TrackerParserNode TrackerParserNode;
+typedef struct _TrackerParserState TrackerParserState;
+typedef struct _TrackerGrammarParser TrackerGrammarParser;
+
+#define NODES_PER_CHUNK 128
+#define RULE_STATE_DEFAULT_SIZE 128
+
+struct _TrackerRuleState {
+ const TrackerGrammarRule *rule;
+ TrackerParserNode *node;
+ gssize start_pos;
+ gint cur_child;
+ guint visited : 1;
+ guint finished : 1;
+};
+
+struct _TrackerNodeTree {
+ GPtrArray *chunks;
+ gint current;
+ TrackerParserNode *root;
+};
+
+struct _TrackerParserNode {
+ GNode node;
+ const TrackerGrammarRule *rule;
+ gssize start;
+ gssize end;
+ guint n_children;
+ gint cur_child;
+};
+
+struct _TrackerParserState {
+ TrackerParserNode *root;
+ TrackerNodeTree *node_tree;
+ gssize current;
+ struct {
+ TrackerRuleState *rules;
+ guint array_size;
+ guint len;
+ } rule_states;
+
+
+ const TrackerGrammarRule *error_rule;
+ gssize error_len;
+};
+
+struct _TrackerGrammarParser {
+ const gchar *query;
+ gsize query_len;
+};
+
+static void tracker_grammar_rule_print_helper (GString *str,
+ const TrackerGrammarRule *rule,
+ gint depth);
+
+static void
+tracker_grammar_rule_print_children (GString *str,
+ const TrackerGrammarRule *rules,
+ const gchar *start,
+ const gchar *sep,
+ const gchar *end,
+ gint depth)
+{
+ gint i;
+
+ g_string_append (str, start);
+
+ for (i = 0; rules[i].type != RULE_TYPE_NIL; i++) {
+ if (i != 0)
+ g_string_append (str, sep);
+ tracker_grammar_rule_print_helper (str, &rules[i], depth);
+ }
+
+ g_string_append (str, end);
+}
+
+static void
+tracker_grammar_rule_print_helper (GString *str,
+ const TrackerGrammarRule *rule,
+ gint depth)
+{
+ if (depth == 0) {
+ g_string_append (str, "…");
+ return;
+ }
+
+ depth--;
+
+ switch (rule->type) {
+ case RULE_TYPE_LITERAL:
+ g_string_append_printf (str, "'%s'", rule->string);
+ break;
+ case RULE_TYPE_RULE:
+ case RULE_TYPE_TERMINAL:
+ g_string_append_printf (str, "%s", rule->string);
+ break;
+ case RULE_TYPE_SEQUENCE:
+ tracker_grammar_rule_print_children (str, rule->data.children,
+ "(", " ", ")", depth);
+ break;
+ case RULE_TYPE_OR:
+ tracker_grammar_rule_print_children (str, rule->data.children,
+ "(", " | ", ")", depth);
+ break;
+ case RULE_TYPE_GTE0:
+ tracker_grammar_rule_print_children (str, rule->data.children,
+ "(", " ", ")*", depth);
+ break;
+ case RULE_TYPE_GT0:
+ tracker_grammar_rule_print_children (str, rule->data.children,
+ "(", " ", ")+", depth);
+ break;
+ case RULE_TYPE_OPTIONAL:
+ tracker_grammar_rule_print_children (str, rule->data.children,
+ "(", " ", ")?", depth);
+ break;
+ case RULE_TYPE_NIL:
+ break;
+ }
+}
+
+static gchar *
+tracker_grammar_rule_print (const TrackerGrammarRule *rule)
+{
+ GString *str;
+
+ str = g_string_new (NULL);
+ tracker_grammar_rule_print_helper (str, rule, 5);
+ return g_string_free (str, FALSE);
+}
+
+static TrackerNodeTree *
+tracker_node_tree_new (void)
+{
+ TrackerNodeTree *tree;
+
+ tree = g_slice_new0 (TrackerNodeTree);
+ tree->chunks = g_ptr_array_new_with_free_func (g_free);
+
+ return tree;
+}
+
+void
+tracker_node_tree_free (TrackerNodeTree *tree)
+{
+ g_ptr_array_unref (tree->chunks);
+ g_slice_free (TrackerNodeTree, tree);
+}
+
+TrackerParserNode *
+tracker_node_tree_get_root (TrackerNodeTree *tree)
+{
+ return tree->root;
+}
+
+static inline TrackerParserNode *
+tracker_node_tree_allocate (TrackerNodeTree *tree)
+{
+ TrackerParserNode *node_array;
+ guint chunk, chunk_idx;
+
+ chunk = tree->current / NODES_PER_CHUNK;
+ chunk_idx = tree->current % NODES_PER_CHUNK;
+ tree->current++;
+
+ if (chunk >= tree->chunks->len) {
+ node_array = g_new0 (TrackerParserNode, NODES_PER_CHUNK);
+ g_ptr_array_add (tree->chunks, node_array);
+ } else {
+ node_array = g_ptr_array_index (tree->chunks, chunk);
+ }
+
+ return &node_array[chunk_idx];
+}
+
+static void
+tracker_node_tree_reset (TrackerNodeTree *tree,
+ TrackerParserNode *node)
+{
+ gint i;
+
+ if (!node)
+ return;
+
+ g_node_unlink ((GNode *) node);
+
+ for (i = tree->chunks->len - 1; i >= 0; i--) {
+ TrackerParserNode *range = g_ptr_array_index (tree->chunks, i);
+
+ if (node >= range && node < &range[NODES_PER_CHUNK]) {
+ guint pos = node - range;
+ tree->current = (i * NODES_PER_CHUNK) + pos;
+ return;
+ }
+ }
+
+ g_assert_not_reached ();
+}
+
+static inline void
+tracker_parser_node_reset (TrackerParserNode *node,
+ const TrackerGrammarRule *rule,
+ const TrackerParserState *state)
+{
+ node->rule = rule;
+ node->start = node->end = state->current;
+
+ switch (rule->type) {
+ case RULE_TYPE_RULE:
+ case RULE_TYPE_SEQUENCE:
+ case RULE_TYPE_GT0:
+ case RULE_TYPE_GTE0:
+ case RULE_TYPE_OPTIONAL:
+ case RULE_TYPE_OR:
+ node->cur_child = -1;
+ break;
+ case RULE_TYPE_LITERAL:
+ case RULE_TYPE_TERMINAL:
+ break;
+ case RULE_TYPE_NIL:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static inline TrackerParserNode *
+tracker_parser_node_new (const TrackerGrammarRule *rule,
+ const TrackerParserState *state)
+{
+ TrackerParserNode *node;
+
+ node = tracker_node_tree_allocate (state->node_tree);
+ node->node = (GNode) { node, 0, };
+ tracker_parser_node_reset (node, rule, state);
+
+ return node;
+}
+
+static void
+tracker_grammar_parser_init (TrackerGrammarParser *parser,
+ const gchar *query,
+ gsize len)
+{
+ parser->query = query;
+ parser->query_len = len;
+}
+
+static void
+tracker_parser_state_push (TrackerParserState *state,
+ const TrackerGrammarRule *rule)
+{
+ TrackerRuleState *rule_state;
+
+ state->rule_states.len++;
+
+ if (state->rule_states.len > state->rule_states.array_size) {
+ state->rule_states.array_size <<= 1;
+ state->rule_states.rules = g_realloc_n (state->rule_states.rules,
+ state->rule_states.array_size,
+ sizeof (TrackerRuleState));
+ }
+
+ rule_state = &state->rule_states.rules[state->rule_states.len - 1];
+
+ rule_state->rule = rule;
+ rule_state->node = NULL;
+ rule_state->start_pos = state->current;
+ rule_state->cur_child = 0;
+ rule_state->visited = rule_state->finished = FALSE;
+}
+
+static TrackerRuleState *
+tracker_parser_state_peek (TrackerParserState *state)
+{
+ return &state->rule_states.rules[state->rule_states.len - 1];
+}
+
+static TrackerParserNode *
+tracker_parser_state_pop (TrackerParserState *state)
+{
+ TrackerRuleState *rule_state;
+ TrackerParserNode *node = NULL;
+
+ rule_state = tracker_parser_state_peek (state);
+ if (rule_state->node) {
+ node = rule_state->node;
+ node->end = state->current;
+ }
+
+ state->rule_states.len--;
+
+ return node;
+}
+
+static const TrackerGrammarRule *
+tracker_parser_state_peek_current_rule (TrackerParserState *state)
+{
+ TrackerRuleState *rule_state;
+
+ rule_state = tracker_parser_state_peek (state);
+
+ return rule_state->rule;
+}
+
+static const TrackerGrammarRule *
+tracker_parser_state_lookup_child (TrackerParserState *state)
+{
+ TrackerRuleState *rule_state;
+ const TrackerGrammarRule *children;
+
+ rule_state = tracker_parser_state_peek (state);
+
+ if (rule_state->finished)
+ return NULL;
+
+ if (rule_state->rule->type == RULE_TYPE_LITERAL ||
+ rule_state->rule->type == RULE_TYPE_TERMINAL)
+ return NULL;
+
+ children = tracker_grammar_rule_get_children (rule_state->rule);
+ if (!children)
+ return NULL;
+
+ return &children[rule_state->cur_child];
+}
+
+static inline gboolean
+tracker_parser_state_next_child (TrackerParserState *state,
+ gboolean success)
+{
+ const TrackerGrammarRule *children;
+ TrackerRuleState *rule_state;
+
+ rule_state = tracker_parser_state_peek (state);
+
+ if (rule_state->finished)
+ return FALSE;
+
+ if (success) {
+ if (rule_state->rule->type == RULE_TYPE_OR) {
+ /* Successful OR rules are satisfied already */
+ rule_state->finished = TRUE;
+ return FALSE;
+ } else if (rule_state->rule->type == RULE_TYPE_GT0 ||
+ rule_state->rule->type == RULE_TYPE_GTE0) {
+ /* Successful + and * rules are evaluated again */
+ return TRUE;
+ }
+ } else {
+ if (rule_state->rule->type == RULE_TYPE_GT0 ||
+ rule_state->rule->type == RULE_TYPE_GTE0) {
+ rule_state->finished = TRUE;
+ return FALSE;
+ }
+ }
+
+ children = tracker_grammar_rule_get_children (rule_state->rule);
+ if (!children)
+ return FALSE;
+
+ rule_state->cur_child++;
+ rule_state->finished = children[rule_state->cur_child].type == RULE_TYPE_NIL;
+
+ return !rule_state->finished;
+}
+
+static TrackerParserNode *
+tracker_parser_state_transact_match (TrackerParserState *state)
+{
+ TrackerParserNode *parser_node = NULL;
+ guint i;
+
+ for (i = 0; i < state->rule_states.len; i++) {
+ TrackerRuleState *rule_state = &state->rule_states.rules[i];
+
+ rule_state->visited = TRUE;
+
+ if (rule_state->rule->type != RULE_TYPE_LITERAL &&
+ rule_state->rule->type != RULE_TYPE_TERMINAL &&
+ rule_state->rule->type != RULE_TYPE_RULE)
+ continue;
+
+ if (rule_state->node == NULL) {
+ rule_state->node = tracker_parser_node_new (rule_state->rule, state);
+ if (parser_node) {
+ g_node_append ((GNode *) parser_node,
+ (GNode *) rule_state->node);
+ }
+ }
+
+ parser_node = rule_state->node;
+ }
+
+ return parser_node;
+}
+
+static void
+tracker_parser_state_take_error (TrackerParserState *state,
+ const TrackerGrammarRule *rule)
+{
+ if (state->current < state->error_len) {
+ return;
+ }
+
+ state->error_len = state->current;
+ state->error_rule = rule;
+}
+
+static void
+tracker_parser_state_forward (TrackerParserState *state,
+ TrackerGrammarParser *parser,
+ gssize len)
+{
+ g_assert (len >= 0 && state->current + len <= parser->query_len);
+ state->current += len;
+}
+
+static void
+tracker_parser_state_rewind (TrackerParserState *state)
+{
+ TrackerRuleState *rule_state;
+
+ rule_state = tracker_parser_state_peek (state);
+ g_assert (rule_state->start_pos >= 0 && rule_state->start_pos <= state->current);
+ state->current = rule_state->start_pos;
+}
+
+static void
+tracker_parser_state_skip_whitespace (TrackerParserState *state,
+ TrackerGrammarParser *parser)
+{
+ while (state->current < parser->query_len) {
+ /* Skip comments too */
+ if (parser->query[state->current] == '#') {
+ while (state->current < parser->query_len &&
+ parser->query[state->current] != '\n') {
+ tracker_parser_state_forward (state, parser, 1);
+ }
+ }
+
+ if (parser->query[state->current] != ' ' &&
+ parser->query[state->current] != '\n' &&
+ parser->query[state->current] != '\t')
+ break;
+
+ tracker_parser_state_forward (state, parser, 1);
+ }
+}
+
+static gboolean
+tracker_grammar_parser_apply_rule_literal (TrackerGrammarParser *parser,
+ TrackerParserState *state,
+ const TrackerGrammarRule *rule)
+{
+ TrackerParserNode *node;
+ gboolean next_isalnum;
+ gsize len;
+
+ if (rule->string[0] != parser->query[state->current] &&
+ rule->string[0] != g_ascii_tolower (parser->query[state->current]))
+ goto error;
+
+ len = strlen (rule->string);
+ g_assert (len > 0);
+
+ if (state->current + len > parser->query_len)
+ goto error;
+
+ if (len > 1 &&
+ rule->string[len - 1] != parser->query[state->current + len - 1] &&
+ rule->string[len - 1] != g_ascii_tolower (parser->query[state->current + len - 1]))
+ goto error;
+
+ next_isalnum = g_ascii_isalnum (parser->query[state->current + len]);
+
+ /* Special case for '?', which may be a property path operator, and
+ * the beginning of VAR1. If the next char is alphanumeric, it's probably
+ * the latter.
+ */
+ if (rule->data.literal == LITERAL_PATH_OPTIONAL && next_isalnum)
+ goto error;
+
+ /* Generic check for other literals, if the literal is alphanumeric, and
+ * the remaining text starts with alphanumeric, probably that was not it.
+ */
+ if (rule->string[0] >= 'a' && rule->string[0] <= 'z' && next_isalnum)
+ goto error;
+
+ if (len > 1 &&
+ g_ascii_strncasecmp (rule->string, &parser->query[state->current], len) != 0)
+ goto error;
+
+ node = tracker_parser_state_transact_match (state);
+ tracker_parser_state_forward (state, parser, len);
+ node->end = state->current;
+ return TRUE;
+
+error:
+ tracker_parser_state_take_error (state, rule);
+ return FALSE;
+}
+
+static gboolean
+tracker_grammar_parser_apply_rule_terminal (TrackerGrammarParser *parser,
+ TrackerParserState *state,
+ const TrackerGrammarRule *rule)
+{
+ TrackerParserNode *node;
+ TrackerTerminalFunc func;
+ const gchar *str, *end;
+
+ str = &parser->query[state->current];
+
+ if (state->current == parser->query_len || str[0] == '\0') {
+ tracker_parser_state_take_error (state, rule);
+ return FALSE;
+ }
+
+ func = tracker_grammar_rule_get_terminal_func (rule);
+
+ if (!func (str, &parser->query[parser->query_len], &end)) {
+ tracker_parser_state_take_error (state, rule);
+ return FALSE;
+ }
+
+ node = tracker_parser_state_transact_match (state);
+ tracker_parser_state_forward (state, parser, end - str);
+ node->end = state->current;
+ return TRUE;
+}
+
+static gboolean
+tracker_grammar_parser_apply_rule (TrackerGrammarParser *parser,
+ TrackerParserState *state,
+ const TrackerGrammarRule *rule)
+{
+ switch (rule->type) {
+ case RULE_TYPE_LITERAL:
+ return tracker_grammar_parser_apply_rule_literal (parser,
+ state, rule);
+ case RULE_TYPE_TERMINAL:
+ return tracker_grammar_parser_apply_rule_terminal (parser,
+ state, rule);
+ case RULE_TYPE_RULE:
+ case RULE_TYPE_SEQUENCE:
+ case RULE_TYPE_GT0:
+ case RULE_TYPE_GTE0:
+ case RULE_TYPE_OPTIONAL:
+ case RULE_TYPE_OR:
+ return TRUE;
+ case RULE_TYPE_NIL:
+ g_assert_not_reached ();
+ return FALSE;
+ }
+
+ g_assert_not_reached ();
+}
+
+static gboolean
+tracker_parser_state_iterate (TrackerParserState *state,
+ TrackerGrammarParser *parser,
+ gboolean try_children)
+{
+ const TrackerGrammarRule *child;
+
+ if (try_children) {
+ /* Try iterating into children first */
+ tracker_parser_state_peek_current_rule (state);
+ child = tracker_parser_state_lookup_child (state);
+
+ if (child) {
+ tracker_parser_state_push (state, child);
+ return TRUE;
+ }
+ }
+
+ tracker_parser_state_pop (state);
+
+ /* Find the first parent that has a next child to handle */
+ while (state->rule_states.len > 0) {
+ tracker_parser_state_peek_current_rule (state);
+
+ if (tracker_parser_state_next_child (state, TRUE)) {
+ child = tracker_parser_state_lookup_child (state);
+ tracker_parser_state_push (state, child);
+ return TRUE;
+ }
+
+ tracker_parser_state_pop (state);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+tracker_parser_state_rollback (TrackerParserState *state,
+ TrackerGrammarParser *parser)
+{
+ const TrackerGrammarRule *rule, *child;
+ TrackerParserNode *node, *discard;
+
+ /* Reset state to retry again the failed portions */
+ tracker_parser_state_rewind (state);
+ discard = tracker_parser_state_pop (state);
+
+ while (state->rule_states.len > 0) {
+ rule = tracker_parser_state_peek_current_rule (state);
+
+ switch (rule->type) {
+ case RULE_TYPE_OR:
+ if (tracker_parser_state_next_child (state, FALSE)) {
+ tracker_node_tree_reset (state->node_tree, discard);
+ child = tracker_parser_state_lookup_child (state);
+ tracker_parser_state_push (state, child);
+ return TRUE;
+ }
+ break;
+ case RULE_TYPE_GT0:
+ /* If we errored out the first time we
+ * parse ()+, raise an error.
+ */
+ if (!tracker_parser_state_peek (state)->visited)
+ break;
+
+ /* Fall through */
+ case RULE_TYPE_GTE0:
+ case RULE_TYPE_OPTIONAL:
+ tracker_parser_state_iterate (state, parser, FALSE);
+ tracker_node_tree_reset (state->node_tree, discard);
+ return TRUE;
+ case RULE_TYPE_RULE:
+ tracker_parser_state_take_error (state, rule);
+ break;
+ default:
+ break;
+ }
+
+ /* Reset state to retry again the failed portions */
+ tracker_parser_state_rewind (state);
+ node = tracker_parser_state_pop (state);
+ if (node)
+ discard = node;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+tracker_grammar_parser_read (TrackerGrammarParser *parser,
+ TrackerParserState *state)
+{
+
+ while (state->rule_states.len > 0) {
+ const TrackerGrammarRule *rule;
+
+ tracker_parser_state_skip_whitespace (state, parser);
+ rule = tracker_parser_state_peek_current_rule (state);
+
+ if (tracker_grammar_parser_apply_rule (parser, state, rule)) {
+ if (!tracker_parser_state_iterate (state, parser, TRUE))
+ break;
+ } else {
+ if (!tracker_parser_state_rollback (state, parser))
+ break;
+
+ /* We rolled back successfully, keep going. */
+ tracker_parser_state_take_error (state, NULL);
+ }
+ }
+
+ return state->error_rule == NULL;
+}
+
+static void
+tracker_parser_state_propagate_error (TrackerParserState *state,
+ GError **error)
+{
+ const TrackerGrammarRule *rule = state->error_rule;
+ gchar *expected;
+
+ if (rule->type == RULE_TYPE_LITERAL)
+ expected = g_strdup_printf ("literal '%s'", rule->string);
+ else if (rule->type == RULE_TYPE_TERMINAL)
+ expected = g_strdup_printf ("terminal '%s'", rule->string);
+ else
+ expected = tracker_grammar_rule_print (rule);
+
+ g_set_error (error,
+ TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_PARSE,
+ "Parser error at byte %ld: Expected %s",
+ state->error_len, expected);
+
+ g_free (expected);
+}
+
+TrackerNodeTree *
+tracker_grammar_parser_apply (TrackerGrammarParser *parser,
+ const TrackerGrammarRule *rule,
+ gsize *len_out,
+ GError **error)
+{
+ TrackerParserState state = { 0, };
+
+ state.node_tree = tracker_node_tree_new ();
+ state.rule_states.array_size = RULE_STATE_DEFAULT_SIZE;
+ state.rule_states.rules = g_new0 (TrackerRuleState,
+ state.rule_states.array_size);
+
+ tracker_parser_state_push (&state, rule);
+ state.node_tree->root = tracker_parser_state_transact_match (&state);
+
+ if (!tracker_grammar_parser_read (parser, &state)) {
+ tracker_parser_state_propagate_error (&state, error);
+ g_free (state.rule_states.rules);
+ return NULL;
+ }
+
+ if (len_out)
+ *len_out = state.current;
+
+ g_free (state.rule_states.rules);
+
+ return state.node_tree;
+}
+
+TrackerNodeTree *
+tracker_sparql_parse_query (const gchar *query,
+ gssize len,
+ gsize *len_out,
+ GError **error)
+{
+ TrackerGrammarParser parser;
+ TrackerNodeTree *tree;
+
+ g_return_val_if_fail (query != NULL, NULL);
+
+ if (len < 0)
+ len = strlen (query);
+
+ tracker_grammar_parser_init (&parser, query, len);
+ tree = tracker_grammar_parser_apply (&parser, NAMED_RULE (QueryUnit), len_out, error);
+
+ return tree;
+}
+
+TrackerNodeTree *
+tracker_sparql_parse_update (const gchar *query,
+ gssize len,
+ gsize *len_out,
+ GError **error)
+{
+ TrackerGrammarParser parser;
+ TrackerNodeTree *tree;
+
+ g_return_val_if_fail (query != NULL, NULL);
+
+ if (len < 0)
+ len = strlen (query);
+
+ tracker_grammar_parser_init (&parser, query, len);
+ tree = tracker_grammar_parser_apply (&parser, NAMED_RULE (UpdateUnit), len_out, error);
+
+ return tree;
+}
+
+const TrackerGrammarRule *
+tracker_parser_node_get_rule (TrackerParserNode *node)
+{
+ return node->rule;
+}
+
+gboolean
+tracker_parser_node_get_extents (TrackerParserNode *node,
+ gssize *start,
+ gssize *end)
+{
+ if (start)
+ *start = node->start;
+ if (end)
+ *end = node->end;
+
+ return node->end != node->start;
+}
+
+TrackerParserNode *
+tracker_sparql_parser_tree_find_first (TrackerParserNode *node,
+ gboolean leaves_only)
+{
+ g_return_val_if_fail (node != NULL, NULL);
+
+ while (node) {
+ if ((!leaves_only && node->rule->type == RULE_TYPE_RULE) ||
+ node->rule->type == RULE_TYPE_LITERAL ||
+ node->rule->type == RULE_TYPE_TERMINAL) {
+ return node;
+ } else if (!node->node.children) {
+ return tracker_sparql_parser_tree_find_next (node, leaves_only);
+ }
+
+ node = (TrackerParserNode *) node->node.children;
+ }
+
+ return NULL;
+}
+
+TrackerParserNode *
+tracker_sparql_parser_tree_find_next (TrackerParserNode *node,
+ gboolean leaves_only)
+{
+ g_return_val_if_fail (node != NULL, NULL);
+
+ while (TRUE) {
+ if (node->node.children)
+ node = (TrackerParserNode *) node->node.children;
+ else if (node->node.next)
+ node = (TrackerParserNode *) node->node.next;
+ else if (node->node.parent) {
+ node = (TrackerParserNode *) node->node.parent;
+
+ /* Traverse up all parents till we find one
+ * with a next node.
+ */
+ while (node) {
+ if (node->node.next) {
+ node = (TrackerParserNode *) node->node.next;
+ break;
+ }
+
+ node = (TrackerParserNode *) node->node.parent;
+ }
+ }
+
+ if (!node)
+ break;
+
+ if ((!leaves_only && node->rule->type == RULE_TYPE_RULE) ||
+ node->rule->type == RULE_TYPE_LITERAL ||
+ node->rule->type == RULE_TYPE_TERMINAL) {
+ return node;
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/libtracker-data/tracker-sparql-parser.h b/src/libtracker-data/tracker-sparql-parser.h
new file mode 100644
index 000000000..a0c755948
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql-parser.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+#ifndef __TRACKER_SPARQL_PARSER_H__
+#define __TRACKER_SPARQL_PARSER_H__
+
+#include <glib.h>
+
+typedef struct _TrackerParserNode TrackerParserNode;
+typedef struct _TrackerGrammarRule TrackerGrammarRule;
+typedef struct _TrackerNodeTree TrackerNodeTree;
+
+TrackerNodeTree * tracker_sparql_parse_query (const gchar *query,
+ gssize len,
+ gsize *len_out,
+ GError **error);
+TrackerNodeTree * tracker_sparql_parse_update (const gchar *query,
+ gssize len,
+ gsize *len_out,
+ GError **error);
+
+void tracker_node_tree_free (TrackerNodeTree *tree);
+TrackerParserNode * tracker_node_tree_get_root (TrackerNodeTree *tree);
+
+TrackerParserNode * tracker_sparql_parser_tree_find_first (TrackerParserNode *node,
+ gboolean leaves_only);
+TrackerParserNode * tracker_sparql_parser_tree_find_next (TrackerParserNode *node,
+ gboolean leaves_only);
+
+const TrackerGrammarRule * tracker_parser_node_get_rule (TrackerParserNode *node);
+
+gboolean tracker_parser_node_get_extents (TrackerParserNode *node,
+ gssize *start,
+ gssize *end);
+
+#endif /* __TRACKER_SPARQL_PARSER_H__ */
diff --git a/src/libtracker-data/tracker-sparql-pattern.vala b/src/libtracker-data/tracker-sparql-pattern.vala
deleted file mode 100644
index 59810da63..000000000
--- a/src/libtracker-data/tracker-sparql-pattern.vala
+++ /dev/null
@@ -1,1659 +0,0 @@
-/*
- * Copyright (C) 2008-2010, Nokia
- *
- * 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.
- */
-
-namespace Tracker.Sparql {
- // Represents a variable used as a predicate
- class PredicateVariable : Object {
- public string? subject;
- public string? object;
- public bool return_graph;
-
- public Class? domain;
-
- Data.Manager manager;
-
- public PredicateVariable (Data.Manager manager) {
- this.manager = manager;
- }
-
- public string get_sql_query (Query query) throws Sparql.Error {
- try {
- var sql = new StringBuilder ();
- var ontologies = manager.get_ontologies ();
- var iface = manager.get_db_interface ();
-
- if (subject != null) {
- // single subject
- var subject_id = Tracker.Data.query_resource_id (manager, iface, subject);
-
- DBCursor cursor = null;
- if (subject_id > 0) {
- var stmt = iface.create_statement (DBStatementCacheType.SELECT,
- "SELECT (SELECT Uri FROM Resource WHERE ID = \"rdf:type\") " +
- "FROM \"rdfs:Resource_rdf:type\" WHERE ID = ?");
- stmt.bind_int (0, subject_id);
- cursor = stmt.start_cursor ();
- }
-
- bool first = true;
- if (cursor != null) {
- while (cursor.next ()) {
- var domain = ontologies.get_class_by_uri (cursor.get_string (0));
-
- foreach (Property prop in ontologies.get_properties ()) {
- if (prop.domain == domain) {
- if (first) {
- first = false;
- } else {
- sql.append (" UNION ALL ");
- }
- sql.append_printf ("SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') AS \"predicate\", ", prop.uri);
-
- Expression.append_expression_as_string (sql, "\"%s\"".printf (prop.name), prop.data_type);
-
- sql.append (" AS \"object\"");
- if (return_graph) {
- sql.append_printf (", \"%s:graph\" AS \"graph\"", prop.name);
- }
- sql.append_printf (" FROM \"%s\"", prop.table_name);
-
- sql.append (" WHERE ID = ?");
-
- var binding = new LiteralBinding ();
- binding.literal = subject_id.to_string ();
- binding.data_type = PropertyType.INTEGER;
- query.bindings.append (binding);
- }
- }
- }
- }
-
- if (first) {
- /* no match */
- sql.append ("SELECT NULL AS ID, NULL AS \"predicate\", NULL AS \"object\", NULL AS \"graph\"");
- }
- } else if (object != null) {
- // single object
- var object_id = Data.query_resource_id (manager, iface, object);
-
- var stmt = iface.create_statement (DBStatementCacheType.SELECT,
- "SELECT (SELECT Uri FROM Resource WHERE ID = \"rdf:type\") " +
- "FROM \"rdfs:Resource_rdf:type\" WHERE ID = ?");
- stmt.bind_int (0, object_id);
- var cursor = stmt.start_cursor ();
-
- bool first = true;
- if (cursor != null) {
- while (cursor.next ()) {
- var range = ontologies.get_class_by_uri (cursor.get_string (0));
-
- foreach (Property prop in ontologies.get_properties ()) {
- if (prop.range == range) {
- if (first) {
- first = false;
- } else {
- sql.append (" UNION ALL ");
- }
- sql.append_printf ("SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') AS \"predicate\", ", prop.uri);
-
- Expression.append_expression_as_string (sql, "\"%s\"".printf (prop.name), prop.data_type);
-
- sql.append (" AS \"object\"");
- if (return_graph) {
- sql.append_printf (", \"%s:graph\" AS \"graph\"", prop.name);
- }
- sql.append_printf (" FROM \"%s\"", prop.table_name);
- }
- }
- }
- }
-
- if (first) {
- /* no match */
- sql.append ("SELECT NULL AS ID, NULL AS \"predicate\", NULL AS \"object\", NULL AS \"graph\"");
- }
- } else if (domain != null) {
- // any subject, predicates limited to a specific domain
- bool first = true;
- foreach (Property prop in ontologies.get_properties ()) {
- if (prop.domain == domain) {
- if (first) {
- first = false;
- } else {
- sql.append (" UNION ALL ");
- }
- sql.append_printf ("SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') AS \"predicate\", ", prop.uri);
-
- Expression.append_expression_as_string (sql, "\"%s\"".printf (prop.name), prop.data_type);
-
- sql.append (" AS \"object\"");
- if (return_graph) {
- sql.append_printf (", \"%s:graph\" AS \"graph\"", prop.name);
- }
- sql.append_printf (" FROM \"%s\"", prop.table_name);
- }
- }
- } else {
- // UNION over all properties would exceed SQLite limits
- throw query.get_internal_error ("Unrestricted predicate variables not supported");
- }
- return sql.str;
- } catch (GLib.Error e) {
- throw new Sparql.Error.INTERNAL (e.message);
- }
- }
- }
-}
-
-class Tracker.Sparql.Pattern : Object {
- weak Query query;
- weak Expression expression;
-
- int counter;
-
- int next_table_index;
-
- internal string current_graph;
- bool current_graph_is_var;
- string current_subject;
- bool current_subject_is_var;
- string current_predicate;
- bool current_predicate_is_var;
- public Variable? fts_subject;
- public string[] fts_variables;
- internal StringBuilder? match_str;
- public bool queries_fts_data = false;
-
- Data.Manager manager;
-
- public Pattern (Query query) {
- this.query = query;
- this.manager = query.manager;
- this.expression = query.expression;
- }
-
- Context context {
- get { return query.context; }
- set { query.context = value; }
- }
-
- private inline bool next () throws Sparql.Error {
- return query.next ();
- }
-
- private inline SparqlTokenType current () {
- return query.current ();
- }
-
- private inline bool accept (SparqlTokenType type) throws Sparql.Error {
- return query.accept (type);
- }
-
- private inline void optional (SparqlTokenType type) throws Sparql.Error {
- query.optional (type);
- }
-
- private Sparql.Error get_error (string msg) {
- return query.get_error (msg);
- }
-
- private bool expect (SparqlTokenType type) throws Sparql.Error {
- return query.expect (type);
- }
-
- private SourceLocation get_location () {
- return query.get_location ();
- }
-
- private void set_location (SourceLocation location) {
- query.set_location (location);
- }
-
- private string get_last_string (int strip = 0) {
- return query.get_last_string (strip);
- }
-
- class TripleContext : Context {
- // SQL tables
- public List<DataTable> tables;
- public HashTable<string,DataTable> table_map;
- // SPARQL literals
- public List<LiteralBinding> bindings;
- // SPARQL variables
- public List<Variable> variables;
- public HashTable<Variable,VariableBindingList> var_bindings;
-
- public TripleContext (Query query, Context parent_context) {
- base (query, parent_context);
-
- tables = new List<DataTable> ();
- table_map = new HashTable<string,DataTable>.full (str_hash, str_equal, g_free, g_object_unref);
-
- variables = new List<Variable> ();
- var_bindings = new HashTable<Variable,VariableBindingList>.full (Variable.hash, Variable.equal, g_object_unref, g_object_unref);
-
- bindings = new List<LiteralBinding> ();
- }
- }
-
- TripleContext? triple_context;
-
- internal SelectContext translate_select (StringBuilder sql, bool subquery = false, bool scalar_subquery = false) throws Sparql.Error {
- SelectContext result;
-
- if (scalar_subquery) {
- result = new SelectContext.subquery (query, context);
- } else {
- result = new SelectContext (query, context);
- }
- context = result;
- var type = PropertyType.UNKNOWN;
-
- var pattern_sql = new StringBuilder ();
- var old_bindings = (owned) query.bindings;
-
- sql.append ("SELECT ");
-
- expect (SparqlTokenType.SELECT);
-
- if (accept (SparqlTokenType.DISTINCT)) {
- sql.append ("DISTINCT ");
- } else if (accept (SparqlTokenType.REDUCED)) {
- }
-
- // skip select variables (processed later)
- var select_variables_location = get_location ();
- expression.skip_select_variables ();
-
- if (accept (SparqlTokenType.FROM)) {
- accept (SparqlTokenType.NAMED);
- expect (SparqlTokenType.IRI_REF);
- }
-
- optional (SparqlTokenType.WHERE);
-
- var pattern = translate_group_graph_pattern (pattern_sql);
- foreach (var key in pattern.var_set.get_keys ()) {
- context.var_set.insert (key, VariableState.BOUND);
- }
-
- // process select variables
- var after_where = get_location ();
- set_location (select_variables_location);
-
- // report use of undefined variables
- foreach (var variable in context.var_set.get_keys ()) {
- if (variable.binding == null) {
- throw get_error ("use of undefined variable `%s'".printf (variable.name));
- }
- }
-
- var where_bindings = (owned) query.bindings;
- query.bindings = (owned) old_bindings;
-
- bool first = true;
- if (accept (SparqlTokenType.STAR)) {
- foreach (var variable in context.var_set.get_keys ()) {
- if (!first) {
- sql.append (", ");
- } else {
- first = false;
- }
- if (subquery) {
- // don't convert to string in subqueries
- sql.append (variable.sql_expression);
- } else {
- Expression.append_expression_as_string (sql, variable.sql_expression, variable.binding.data_type);
- sql.append_printf (" AS \"%s\"", variable.name);
- }
- result.types += variable.binding.data_type;
- result.variable_names += variable.name;
- }
- } else {
- for (int i = 0; ; i++) {
- first = false;
-
- if (i > 0) {
- sql.append (", ");
- }
-
- type = expression.translate_select_expression (sql, subquery, i);
- result.types += type;
-
- switch (current ()) {
- case SparqlTokenType.FROM:
- case SparqlTokenType.WHERE:
- case SparqlTokenType.OPEN_BRACE:
- case SparqlTokenType.GROUP:
- case SparqlTokenType.ORDER:
- case SparqlTokenType.LIMIT:
- case SparqlTokenType.OFFSET:
- case SparqlTokenType.EOF:
- break;
- default:
- continue;
- }
- break;
- }
- }
-
- if (queries_fts_data && fts_subject != null) {
- // Ensure there's a rowid to match on in FTS queries
- if (!first) {
- sql.append (", ");
- } else {
- first = false;
- }
-
- sql.append ("%s AS rowid ".printf (fts_subject.sql_expression));
- }
-
- // literals in select expressions need to be bound before literals in the where clause
- foreach (var binding in where_bindings) {
- query.bindings.append (binding);
- }
-
- if (first) {
- sql.append ("NULL");
- }
-
- // select from results of WHERE clause
- sql.append (" FROM (");
- sql.append (pattern_sql.str);
- sql.append (")");
-
- set_location (after_where);
-
- if (accept (SparqlTokenType.GROUP)) {
- expect (SparqlTokenType.BY);
- sql.append (" GROUP BY ");
- bool first_group = true;
- do {
- if (first_group) {
- first_group = false;
- } else {
- sql.append (", ");
- }
- expression.translate_expression (sql);
- } while (current () != SparqlTokenType.HAVING && current () != SparqlTokenType.ORDER && current () != SparqlTokenType.LIMIT && current () != SparqlTokenType.OFFSET && current () != SparqlTokenType.CLOSE_BRACE && current () != SparqlTokenType.CLOSE_PARENS && current () != SparqlTokenType.EOF);
-
- if (accept (SparqlTokenType.HAVING)) {
- sql.append (" HAVING ");
- expression.translate_constraint (sql);
- }
- }
-
- if (accept (SparqlTokenType.ORDER)) {
- expect (SparqlTokenType.BY);
- sql.append (" ORDER BY ");
- bool first_order = true;
- do {
- if (first_order) {
- first_order = false;
- } else {
- sql.append (", ");
- }
- expression.translate_order_condition (sql);
- } while (current () != SparqlTokenType.LIMIT && current () != SparqlTokenType.OFFSET && current () != SparqlTokenType.CLOSE_BRACE && current () != SparqlTokenType.CLOSE_PARENS && current () != SparqlTokenType.EOF);
- }
-
- int limit = -1;
- int offset = -1;
-
- if (accept (SparqlTokenType.LIMIT)) {
- expect (SparqlTokenType.INTEGER);
- limit = int.parse (get_last_string ());
- if (accept (SparqlTokenType.OFFSET)) {
- expect (SparqlTokenType.INTEGER);
- offset = int.parse (get_last_string ());
- }
- } else if (accept (SparqlTokenType.OFFSET)) {
- expect (SparqlTokenType.INTEGER);
- offset = int.parse (get_last_string ());
- if (accept (SparqlTokenType.LIMIT)) {
- expect (SparqlTokenType.INTEGER);
- limit = int.parse (get_last_string ());
- }
- }
-
- // LIMIT and OFFSET
- if (limit >= 0) {
- sql.append (" LIMIT ?");
-
- var binding = new LiteralBinding ();
- binding.literal = limit.to_string ();
- binding.data_type = PropertyType.INTEGER;
- query.bindings.append (binding);
-
- if (offset >= 0) {
- sql.append (" OFFSET ?");
-
- binding = new LiteralBinding ();
- binding.literal = offset.to_string ();
- binding.data_type = PropertyType.INTEGER;
- query.bindings.append (binding);
- }
- } else if (offset >= 0) {
- sql.append (" LIMIT -1 OFFSET ?");
-
- var binding = new LiteralBinding ();
- binding.literal = offset.to_string ();
- binding.data_type = PropertyType.INTEGER;
- query.bindings.append (binding);
- }
-
- if (queries_fts_data && match_str != null && fts_subject != null) {
- var str = new StringBuilder ("SELECT ");
- first = true;
-
- foreach (var fts_var in fts_variables) {
- if (!first) {
- str.append (", ");
- } else {
- first = false;
- }
-
- str.append (fts_var);
- }
-
- str.append (" FROM fts5 JOIN (");
- sql.prepend (str.str);
- sql.append_printf (") AS ranks ON fts5.rowid=rowid WHERE fts5 %s".printf (match_str.str));
- }
-
- context = context.parent_context;
-
- result.type = type;
- match_str = null;
- fts_subject = null;
-
- return result;
- }
-
- internal void translate_exists (StringBuilder sql) throws Sparql.Error {
- bool not = accept (SparqlTokenType.NOT);
- expect (SparqlTokenType.EXISTS);
-
- SelectContext result;
- result = new SelectContext.subquery (query, context);
- context = result;
-
- if (not) {
- // NOT EXISTS
- sql.append ("NOT EXISTS (");
- } else {
- // EXISTS
- sql.append ("EXISTS (");
- }
-
- var pattern = translate_group_graph_pattern (sql);
- foreach (var key in pattern.var_set.get_keys ()) {
- context.var_set.insert (key, VariableState.BOUND);
- }
-
- // report use of undefined variables
- foreach (var variable in context.var_set.get_keys ()) {
- if (variable.binding == null) {
- throw get_error ("use of undefined variable `%s'".printf (variable.name));
- }
- }
-
- sql.append (")");
-
- context = context.parent_context;
- }
-
- internal string parse_var_or_term (StringBuilder? sql, out bool is_var) throws Sparql.Error {
- string result = "";
- is_var = false;
- if (current () == SparqlTokenType.VAR) {
- is_var = true;
- next ();
- result = get_last_string ().substring (1);
- } else if (current () == SparqlTokenType.IRI_REF) {
- next ();
- result = get_last_string (1);
- } else if (current () == SparqlTokenType.PN_PREFIX) {
- // prefixed name with namespace foo:bar
- next ();
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- result = query.resolve_prefixed_name (ns, get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.COLON) {
- // prefixed name without namespace :bar
- next ();
- result = query.resolve_prefixed_name ("", get_last_string ().substring (1));
- } else if (accept (SparqlTokenType.BLANK_NODE)) {
- // _:foo
- expect (SparqlTokenType.COLON);
- result = query.generate_bnodeid (get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.STRING_LITERAL1) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.STRING_LITERAL2) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.STRING_LITERAL_LONG1) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.STRING_LITERAL_LONG2) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.INTEGER) {
- next ();
- result = get_last_string ();
- } else if (current () == SparqlTokenType.DECIMAL) {
- next ();
- result = get_last_string ();
- } else if (current () == SparqlTokenType.DOUBLE) {
- next ();
- result = get_last_string ();
- } else if (current () == SparqlTokenType.TRUE) {
- next ();
- result = "true";
- } else if (current () == SparqlTokenType.FALSE) {
- next ();
- result = "false";
- } else if (current () == SparqlTokenType.OPEN_BRACKET) {
- next ();
-
- result = query.generate_bnodeid (null);
-
- string old_subject = current_subject;
- bool old_subject_is_var = current_subject_is_var;
-
- current_subject = result;
- current_subject_is_var = true;
- parse_property_list_not_empty (sql);
- expect (SparqlTokenType.CLOSE_BRACKET);
-
- current_subject = old_subject;
- current_subject_is_var = old_subject_is_var;
-
- is_var = true;
- } else {
- throw get_error ("expected variable or term");
- }
- return result;
- }
-
- private void parse_object_list (StringBuilder sql, bool in_simple_optional = false) throws Sparql.Error {
- while (true) {
- parse_object (sql, in_simple_optional);
- if (accept (SparqlTokenType.COMMA)) {
- continue;
- }
- break;
- }
- }
-
- private void parse_property_list_not_empty (StringBuilder sql, bool in_simple_optional = false) throws Sparql.Error {
- while (true) {
- var old_predicate = current_predicate;
- var old_predicate_is_var = current_predicate_is_var;
-
- current_predicate = null;
- current_predicate_is_var = false;
- if (current () == SparqlTokenType.VAR) {
- current_predicate_is_var = true;
- next ();
- current_predicate = get_last_string ().substring (1);
- } else if (current () == SparqlTokenType.IRI_REF) {
- next ();
- current_predicate = get_last_string (1);
- } else if (current () == SparqlTokenType.PN_PREFIX) {
- next ();
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- current_predicate = query.resolve_prefixed_name (ns, get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.COLON) {
- next ();
- current_predicate = query.resolve_prefixed_name ("", get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.A) {
- next ();
- current_predicate = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
- } else {
- throw get_error ("expected non-empty property list");
- }
- parse_object_list (sql, in_simple_optional);
-
- current_predicate = old_predicate;
- current_predicate_is_var = old_predicate_is_var;
-
- if (accept (SparqlTokenType.SEMICOLON)) {
- if (current () == SparqlTokenType.DOT) {
- // semicolon before dot is allowed in both, SPARQL and Turtle
- break;
- }
- continue;
- }
- break;
- }
- }
-
- private void translate_filter (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.FILTER);
- expression.translate_constraint (sql);
- }
-
- private void skip_filter () throws Sparql.Error {
- expect (SparqlTokenType.FILTER);
-
- switch (current ()) {
- case SparqlTokenType.STR:
- case SparqlTokenType.LANG:
- case SparqlTokenType.LANGMATCHES:
- case SparqlTokenType.DATATYPE:
- case SparqlTokenType.BOUND:
- case SparqlTokenType.SAMETERM:
- case SparqlTokenType.ISIRI:
- case SparqlTokenType.ISURI:
- case SparqlTokenType.ISBLANK:
- case SparqlTokenType.ISLITERAL:
- case SparqlTokenType.REGEX:
- next ();
- break;
- default:
- break;
- }
-
- expect (SparqlTokenType.OPEN_PARENS);
- int n_parens = 1;
- while (n_parens > 0) {
- if (accept (SparqlTokenType.OPEN_PARENS)) {
- n_parens++;
- } else if (accept (SparqlTokenType.CLOSE_PARENS)) {
- n_parens--;
- } else if (current () == SparqlTokenType.EOF) {
- throw get_error ("unexpected end of query, expected )");
- } else {
- // ignore everything else
- next ();
- }
- }
- }
-
- private void start_triples_block (StringBuilder sql) throws Sparql.Error {
- context = triple_context = new TripleContext (query, context);
-
- sql.append ("SELECT ");
- }
-
- private void end_triples_block (StringBuilder sql, ref bool first_where, bool in_group_graph_pattern) throws Sparql.Error {
- // remove last comma and space
- sql.truncate (sql.len - 2);
-
- sql.append (" FROM ");
- bool first = true;
- foreach (DataTable table in triple_context.tables) {
- if (!first) {
- sql.append (", ");
- } else {
- first = false;
- }
- if (table.sql_db_tablename != null) {
- sql.append_printf ("\"%s\"", table.sql_db_tablename);
- } else {
- sql.append_printf ("(%s)", table.predicate_variable.get_sql_query (query));
- }
- sql.append_printf (" AS \"%s\"", table.sql_query_tablename);
- }
-
- foreach (var variable in triple_context.variables) {
- bool maybe_null = true;
- bool in_simple_optional = false;
- PropertyType last_type = PropertyType.UNKNOWN;
- string last_name = null;
- foreach (VariableBinding binding in triple_context.var_bindings.lookup (variable).list) {
- string name;
- if (binding.table != null) {
- name = binding.sql_expression;
- } else {
- // simple optional with inverse functional property
- // always first in loop as variable is required to be unbound
- name = variable.sql_expression;
- }
- var type = binding.data_type;
- if (last_name != null) {
- if (!first_where) {
- sql.append (" AND ");
- } else {
- sql.append (" WHERE ");
- first_where = false;
- }
-
- if (last_type == PropertyType.STRING && type == PropertyType.RESOURCE) {
- sql.append_printf ("(SELECT ID FROM Resource WHERE Uri = %s)", last_name);
- } else {
- sql.append (last_name);
- }
-
- sql.append (" = ");
-
- if (last_type == PropertyType.RESOURCE && type == PropertyType.STRING) {
- sql.append_printf ("(SELECT ID FROM Resource WHERE Uri = %s)", name);
- } else {
- sql.append (name);
- }
- }
- last_name = name;
- last_type = type;
- if (!binding.maybe_null) {
- maybe_null = false;
- }
- in_simple_optional = binding.in_simple_optional;
- }
-
- if (maybe_null && !in_simple_optional) {
- // ensure that variable is bound in case it could return NULL in SQL
- // assuming SPARQL variable is not optional
- if (!first_where) {
- sql.append (" AND ");
- } else {
- sql.append (" WHERE ");
- first_where = false;
- }
- sql.append_printf ("%s IS NOT NULL", variable.sql_expression);
- }
- }
- foreach (LiteralBinding binding in triple_context.bindings) {
- if (!first_where) {
- sql.append (" AND ");
- } else {
- sql.append (" WHERE ");
- first_where = false;
- }
- sql.append (binding.sql_expression);
- if (binding.is_fts_match) {
- // parameters do not work with fts MATCH
- string escaped_literal = string.joinv ("''", binding.literal.split ("'"));
- sql.append_printf (" MATCH '%s'", escaped_literal);
-
- if (match_str == null) {
- match_str = new StringBuilder ();
- match_str.append_printf (" MATCH '%s'", escaped_literal);
- }
- } else {
- sql.append (" = ");
- if (binding.data_type == PropertyType.RESOURCE) {
- sql.append ("(SELECT ID FROM Resource WHERE Uri = ?)");
- } else {
- sql.append ("?");
- }
- query.bindings.append (binding);
- }
- }
-
- if (in_group_graph_pattern) {
- sql.append (")");
- }
-
- foreach (var v in context.var_set.get_keys ()) {
- context.parent_context.var_set.insert (v, VariableState.BOUND);
- }
-
- triple_context = null;
- context = context.parent_context;
- }
-
- private void parse_triples (StringBuilder sql, long group_graph_pattern_start, ref bool in_triples_block, ref bool first_where, ref bool in_group_graph_pattern, bool found_simple_optional) throws Sparql.Error {
- while (true) {
- if (current () != SparqlTokenType.VAR &&
- current () != SparqlTokenType.IRI_REF &&
- current () != SparqlTokenType.PN_PREFIX &&
- current () != SparqlTokenType.COLON &&
- current () != SparqlTokenType.OPEN_BRACKET) {
- break;
- }
- if (in_triples_block && !in_group_graph_pattern && found_simple_optional) {
- // if there is a regular triple pattern after a simple optional
- // we need to use a separate triple block to avoid possible conflicts
- // due to not using a JOIN for the simple optional
- end_triples_block (sql, ref first_where, in_group_graph_pattern);
- in_triples_block = false;
- in_group_graph_pattern = true;
- }
- if (!in_triples_block) {
- if (in_group_graph_pattern) {
- sql.insert (group_graph_pattern_start, "SELECT * FROM (");
- sql.append (") NATURAL INNER JOIN (");
- }
- in_triples_block = true;
- first_where = true;
- start_triples_block (sql);
- }
-
- current_subject = parse_var_or_term (sql, out current_subject_is_var);
- parse_property_list_not_empty (sql);
-
- if (!accept (SparqlTokenType.DOT)) {
- break;
- }
- }
- }
-
- private bool is_subclass (Class class1, Class class2) {
- if (class1 == class2) {
- return true;
- }
- foreach (var superclass in class1.get_super_classes ()) {
- if (is_subclass (superclass, class2)) {
- return true;
- }
- }
- return false;
- }
-
- private bool is_simple_optional () {
- var optional_start = get_location ();
- try {
- // check that we have { ?v foo:bar ?o }
- // where ?v is an already BOUND variable
- // foo:bar is a single-valued property
- // that is known to be in domain of ?v
- // ?o has not been used before
- // or
- // where ?v has not been used before
- // foo:bar is an inverse functional property
- // ?o is an already ?BOUND variable
-
- expect (SparqlTokenType.OPEN_BRACE);
-
- // check subject
- if (!accept (SparqlTokenType.VAR)) {
- return false;
- }
- var left_variable = context.get_variable (get_last_string ().substring (1));
- var left_variable_state = context.var_set.lookup (left_variable);
-
- // check predicate
- string predicate;
- if (accept (SparqlTokenType.IRI_REF)) {
- predicate = get_last_string (1);
- } else if (accept (SparqlTokenType.PN_PREFIX)) {
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- predicate = query.resolve_prefixed_name (ns, get_last_string ().substring (1));
- } else if (accept (SparqlTokenType.COLON)) {
- predicate = query.resolve_prefixed_name ("", get_last_string ().substring (1));
- } else {
- return false;
- }
- var ontologies = manager.get_ontologies ();
- var prop = ontologies.get_property_by_uri (predicate);
- if (prop == null) {
- return false;
- }
-
- // check object
- if (!accept (SparqlTokenType.VAR)) {
- return false;
- }
- var right_variable = context.get_variable (get_last_string ().substring (1));
- var right_variable_state = context.var_set.lookup (right_variable);
-
- optional (SparqlTokenType.DOT);
-
- // check it is only one triple pattern
- if (!accept (SparqlTokenType.CLOSE_BRACE)) {
- return false;
- }
-
- if (left_variable_state == VariableState.BOUND && !prop.multiple_values && right_variable_state == 0) {
- bool in_domain = false;
- foreach (VariableBinding binding in triple_context.var_bindings.lookup (left_variable).list) {
- if (binding.type != null && is_subclass (binding.type, prop.domain)) {
- in_domain = true;
- break;
- }
- }
-
- if (in_domain) {
- // first valid case described in above comment
- return true;
- }
- } else if (left_variable_state == 0 && prop.is_inverse_functional_property && right_variable_state == VariableState.BOUND) {
- // second valid case described in above comment
- return true;
- }
-
- // no match
- return false;
- } catch (Sparql.Error e) {
- return false;
- } finally {
- // in any case, go back to the start of the optional
- set_location (optional_start);
- }
- }
-
- internal Context translate_group_graph_pattern (StringBuilder sql) throws Sparql.Error {
- expect (SparqlTokenType.OPEN_BRACE);
-
- if (current () == SparqlTokenType.SELECT) {
- var result = translate_select (sql, true);
- context = result;
-
- // only export selected variables
- context.var_set = context.select_var_set;
- context.select_var_set = new HashTable<Variable,int>.full (Variable.hash, Variable.equal, g_object_unref, null);
-
- expect (SparqlTokenType.CLOSE_BRACE);
-
- context = context.parent_context;
- return result;
- }
-
- var result = new Context (query, context);
- context = result;
-
- SourceLocation[] filters = { };
-
- bool in_triples_block = false;
- bool in_group_graph_pattern = false;
- bool first_where = true;
- bool found_simple_optional = false;
- long group_graph_pattern_start = sql.len;
-
- // optional TriplesBlock
- parse_triples (sql, group_graph_pattern_start, ref in_triples_block, ref first_where, ref in_group_graph_pattern, found_simple_optional);
-
- while (true) {
- // check whether we have GraphPatternNotTriples | Filter
- if (accept (SparqlTokenType.OPTIONAL)) {
- if (!in_group_graph_pattern && is_simple_optional ()) {
- // perform join-less optional (like non-optional except for the IS NOT NULL check)
- found_simple_optional = true;
- expect (SparqlTokenType.OPEN_BRACE);
-
- current_subject = parse_var_or_term (sql, out current_subject_is_var);
- parse_property_list_not_empty (sql, true);
-
- accept (SparqlTokenType.DOT);
- expect (SparqlTokenType.CLOSE_BRACE);
- } else {
- if (!in_triples_block && !in_group_graph_pattern) {
- // expand { OPTIONAL { ... } } into { { } OPTIONAL { ... } }
- // empty graph pattern => return one result without bound variables
- sql.append ("SELECT 1");
- } else if (in_triples_block) {
- end_triples_block (sql, ref first_where, in_group_graph_pattern);
- in_triples_block = false;
- }
- if (!in_group_graph_pattern) {
- in_group_graph_pattern = true;
- }
-
- var select = new StringBuilder ("SELECT ");
-
- int left_index = ++next_table_index;
- int right_index = ++next_table_index;
-
- sql.append_printf (") AS t%d_g LEFT JOIN (", left_index);
-
- context = translate_group_graph_pattern (sql);
-
- sql.append_printf (") AS t%d_g", right_index);
-
- bool first = true;
- bool first_common = true;
- foreach (var v in context.var_set.get_keys ()) {
- if (first) {
- first = false;
- } else {
- select.append (", ");
- }
-
- var old_state = context.parent_context.var_set.lookup (v);
- if (old_state == 0) {
- // first used in optional part
- context.parent_context.var_set.insert (v, VariableState.OPTIONAL);
- select.append_printf ("t%d_g.%s", right_index, v.sql_expression);
-
- if (v.binding.data_type == PropertyType.DATETIME) {
- select.append_printf (", t%d_g.%s", right_index, v.get_extra_sql_expression ("localDate"));
- select.append_printf (", t%d_g.%s", right_index, v.get_extra_sql_expression ("localTime"));
- }
- } else {
- if (first_common) {
- sql.append (" ON ");
- first_common = false;
- } else {
- sql.append (" AND ");
- }
-
- if (old_state == VariableState.BOUND) {
- // variable definitely bound in non-optional part
- sql.append_printf ("t%d_g.%s = t%d_g.%s", left_index, v.sql_expression, right_index, v.sql_expression);
- select.append_printf ("t%d_g.%s", left_index, v.sql_expression);
-
- if (v.binding.data_type == PropertyType.DATETIME) {
- select.append_printf (", t%d_g.%s", left_index, v.get_extra_sql_expression ("localDate"));
- select.append_printf (", t%d_g.%s", left_index, v.get_extra_sql_expression ("localTime"));
- }
- } else if (old_state == VariableState.OPTIONAL) {
- // variable maybe bound in non-optional part
- sql.append_printf ("(t%d_g.%s IS NULL OR t%d_g.%s = t%d_g.%s)", left_index, v.sql_expression, left_index, v.sql_expression, right_index, v.sql_expression);
- select.append_printf ("COALESCE (t%d_g.%s, t%d_g.%s) AS %s", left_index, v.sql_expression, right_index, v.sql_expression, v.sql_expression);
-
- if (v.binding.data_type == PropertyType.DATETIME) {
- select.append_printf (", COALESCE (t%d_g.%s, t%d_g.%s) AS %s", left_index, v.get_extra_sql_expression ("localDate"), right_index, v.get_extra_sql_expression ("localDate"), v.get_extra_sql_expression ("localDate"));
- select.append_printf (", COALESCE (t%d_g.%s, t%d_g.%s) AS %s", left_index, v.get_extra_sql_expression ("localTime"), right_index, v.get_extra_sql_expression ("localTime"), v.get_extra_sql_expression ("localTime"));
- }
- }
- }
- }
- foreach (var v in context.parent_context.var_set.get_keys ()) {
- if (context.var_set.lookup (v) == 0) {
- // only used in non-optional part
- if (first) {
- first = false;
- } else {
- select.append (", ");
- }
-
- select.append_printf ("t%d_g.%s", left_index, v.sql_expression);
-
- if (v.binding.data_type == PropertyType.DATETIME) {
- select.append_printf (", t%d_g.%s", left_index, v.get_extra_sql_expression ("localDate"));
- select.append_printf (", t%d_g.%s", left_index, v.get_extra_sql_expression ("localTime"));
- }
- }
- }
- if (first) {
- // no variables used at all
- select.append ("1");
- }
-
- context = context.parent_context;
-
- select.append (" FROM (");
- sql.insert (group_graph_pattern_start, select.str);
-
- // surround with SELECT * FROM (...) to avoid ambiguous column names
- // in SQL generated for FILTER (triggered by using table aliases for join sources)
- sql.insert (group_graph_pattern_start, "SELECT * FROM (");
- sql.append (")");
- }
- } else if (accept (SparqlTokenType.GRAPH)) {
- var old_graph = current_graph;
- var old_graph_is_var = current_graph_is_var;
- current_graph = parse_var_or_term (sql, out current_graph_is_var);
-
- if (!in_triples_block && !in_group_graph_pattern) {
- in_group_graph_pattern = true;
-
- sql.insert (group_graph_pattern_start, "SELECT * FROM (");
- translate_group_or_union_graph_pattern (sql);
- sql.append (")");
- } else {
- if (in_triples_block) {
- end_triples_block (sql, ref first_where, in_group_graph_pattern);
- in_triples_block = false;
- }
- if (!in_group_graph_pattern) {
- in_group_graph_pattern = true;
- }
-
- sql.insert (group_graph_pattern_start, "SELECT * FROM (");
- sql.append (") NATURAL INNER JOIN (");
- translate_group_or_union_graph_pattern (sql);
- sql.append (")");
- }
-
- current_graph = old_graph;
- current_graph_is_var = old_graph_is_var;
- } else if (accept (SparqlTokenType.BIND)) {
- var binding = new VariableBinding ();
- var bind_sql = new StringBuilder ();
-
- expect (SparqlTokenType.OPEN_PARENS);
-
- // We only need the binding sql expression when
- // we are not in a group graph pattern, both
- // cases are handled differently below.
- context.need_binding_expression = !in_group_graph_pattern;
- expression.translate_expression (bind_sql);
- context.need_binding_expression = false;
- binding.sql_expression = bind_sql.str;
-
- expect (SparqlTokenType.AS);
- expect (SparqlTokenType.VAR);
-
- var as_var = context.get_variable (get_last_string ().substring (1));
-
- if (as_var.binding != null) {
- throw query.get_internal_error ("Expected undefined variable in BIND alias");
- }
-
- binding.variable = as_var;
-
- if (in_group_graph_pattern) {
- // Surround the entire group graph pattern with
- // SELECT $binding , * FROM (...)
- var binding_sql = new StringBuilder ("SELECT ");
- add_variable_binding (binding_sql, binding, VariableState.BOUND);
- binding_sql.append (" * FROM (");
- sql.insert (group_graph_pattern_start, binding_sql.str);
- sql.append (")");
- } else {
- // This is the "simple" case, where we are
- // still constructing the SELECT ... part,
- // just add the binding sql in this case.
- add_variable_binding (sql, binding, VariableState.BOUND);
- }
-
- expect (SparqlTokenType.CLOSE_PARENS);
- } else if (current () == SparqlTokenType.OPEN_BRACE) {
- if (!in_triples_block && !in_group_graph_pattern) {
- in_group_graph_pattern = true;
-
- sql.insert (group_graph_pattern_start, "SELECT * FROM (");
- translate_group_or_union_graph_pattern (sql);
- sql.append (")");
- } else {
- if (in_triples_block) {
- end_triples_block (sql, ref first_where, in_group_graph_pattern);
- in_triples_block = false;
- }
- if (!in_group_graph_pattern) {
- in_group_graph_pattern = true;
- }
-
- sql.insert (group_graph_pattern_start, "SELECT * FROM (");
- sql.append (") NATURAL INNER JOIN (");
- translate_group_or_union_graph_pattern (sql);
- sql.append (")");
- }
- } else if (current () == SparqlTokenType.FILTER) {
- filters += get_location ();
- skip_filter ();
- } else {
- break;
- }
-
- optional (SparqlTokenType.DOT);
-
- // optional TriplesBlock
- parse_triples (sql, group_graph_pattern_start, ref in_triples_block, ref first_where, ref in_group_graph_pattern, found_simple_optional);
- }
-
- expect (SparqlTokenType.CLOSE_BRACE);
-
- if (!in_triples_block && !in_group_graph_pattern) {
- // empty graph pattern => return one result without bound variables
- sql.append ("SELECT 1");
- } else if (in_triples_block) {
- end_triples_block (sql, ref first_where, in_group_graph_pattern);
- in_triples_block = false;
- }
-
- if (in_group_graph_pattern) {
- first_where = true;
- }
-
- // handle filters last, they apply to the pattern as a whole
- if (filters.length > 0) {
- var end = get_location ();
-
- foreach (var filter_location in filters) {
- if (!first_where) {
- sql.append (" AND ");
- } else {
- sql.append (" WHERE ");
- first_where = false;
- }
-
- set_location (filter_location);
- translate_filter (sql);
- }
-
- set_location (end);
- }
-
- context = context.parent_context;
- return result;
- }
-
- private void translate_group_or_union_graph_pattern (StringBuilder sql) throws Sparql.Error {
- Variable[] all_vars = { };
- HashTable<Variable,int> all_var_set = new HashTable<Variable,int>.full (Variable.hash, Variable.equal, g_object_unref, null);
-
- Context[] contexts = { };
- long[] offsets = { };
-
- do {
- offsets += sql.len;
- contexts += translate_group_graph_pattern (sql);
- } while (accept (SparqlTokenType.UNION));
-
- if (contexts.length > 1) {
- // union graph pattern
-
- // create union of all variables
- foreach (var sub_context in contexts) {
- foreach (var v in sub_context.var_set.get_keys ()) {
- if (all_var_set.lookup (v) == 0) {
- all_vars += v;
- all_var_set.insert (v, VariableState.BOUND);
- context.var_set.insert (v, VariableState.BOUND);
- }
- }
- }
-
- long extra_offset = 0;
- for (int i = 0; i < contexts.length; i++) {
- var projection = new StringBuilder ();
- if (i > 0) {
- projection.append (") UNION ALL ");
- }
- projection.append ("SELECT ");
- foreach (var v in all_vars) {
- if (contexts[i].var_set.lookup (v) == 0) {
- // variable not used in this subgraph
- // use NULL
- projection.append ("NULL AS ");
- }
- projection.append_printf ("%s, ", v.sql_expression);
- }
- // delete last comma and space
- projection.truncate (projection.len - 2);
- projection.append (" FROM (");
-
- sql.insert (offsets[i] + extra_offset, projection.str);
- extra_offset += projection.len;
- }
- sql.append (")");
- } else {
- foreach (var key in contexts[0].var_set.get_keys ()) {
- context.var_set.insert (key, VariableState.BOUND);
- }
- }
- }
-
- private VariableBindingList? get_variable_binding_list (Variable variable) {
- VariableBindingList binding_list = null;
- if (triple_context != null) {
- binding_list = triple_context.var_bindings.lookup (variable);
- }
- if (binding_list == null && variable.binding != null) {
- // might be in scalar subquery: check variables of outer queries
- var current_context = context;
- while (current_context != null) {
- // only allow access to variables of immediate parent context of the subquery
- // allowing access to other variables leads to invalid SQL or wrong results
- if (current_context.scalar_subquery && current_context.parent_context.var_set.lookup (variable) != 0) {
- // capture outer variable
- var binding = new VariableBinding ();
- binding.data_type = variable.binding.data_type;
- binding.variable = context.get_variable (variable.name);
- binding.type = variable.binding.type;
- binding.sql_expression = variable.sql_expression;
- binding_list = new VariableBindingList ();
- if (triple_context != null) {
- triple_context.variables.append (variable);
- triple_context.var_bindings.insert (variable, binding_list);
- }
-
- context.var_set.insert (variable, VariableState.BOUND);
- binding_list.list.append (binding);
- break;
- }
- current_context = current_context.parent_context;
- }
- }
- return binding_list;
- }
-
- internal void add_variable_binding (StringBuilder sql, VariableBinding binding, VariableState variable_state) {
- var binding_list = get_variable_binding_list (binding.variable);
- if (binding_list == null) {
- binding_list = new VariableBindingList ();
- if (triple_context != null) {
- triple_context.variables.append (binding.variable);
- triple_context.var_bindings.insert (binding.variable, binding_list);
- }
-
- sql.append_printf ("%s AS %s, ",
- binding.sql_expression,
- binding.variable.sql_expression);
-
- if (binding.data_type == PropertyType.DATETIME) {
- sql.append_printf ("%s AS %s, ",
- binding.get_extra_sql_expression ("localDate"),
- binding.variable.get_extra_sql_expression ("localDate"));
- sql.append_printf ("%s AS %s, ",
- binding.get_extra_sql_expression ("localTime"),
- binding.variable.get_extra_sql_expression ("localTime"));
- }
-
- context.var_set.insert (binding.variable, variable_state);
- }
- binding_list.list.append (binding);
- if (binding.variable.binding == null) {
- binding.variable.binding = binding;
- }
- }
-
- private void parse_object (StringBuilder sql, bool in_simple_optional = false) throws Sparql.Error {
- long begin_sql_len = sql.len;
-
- bool object_is_var;
- string object = parse_var_or_term (sql, out object_is_var);
-
- string db_table = null;
- bool rdftype = false;
- bool share_table = true;
- bool is_fts_match = false;
-
- bool newtable;
- DataTable table;
- Property prop = null;
-
- Class subject_type = null;
-
- var ontologies = manager.get_ontologies ();
-
- if (!current_predicate_is_var) {
- prop = ontologies.get_property_by_uri (current_predicate);
-
- if (current_predicate == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
- && !object_is_var && current_graph == null) {
- // rdf:type query
- // avoid special casing if GRAPH is used as graph matching is not supported when using class tables
- rdftype = true;
- var cl = ontologies.get_class_by_uri (object);
- if (cl == null) {
- throw new Sparql.Error.UNKNOWN_CLASS ("Unknown class `%s'".printf (object));
- }
- db_table = cl.name;
- subject_type = cl;
- } else if (prop == null) {
- if (current_predicate == "http://www.tracker-project.org/ontologies/fts#match") {
- // fts:match
- db_table = "fts5";
- share_table = false;
- is_fts_match = true;
- fts_subject = context.get_variable (current_subject);
- } else {
- throw new Sparql.Error.UNKNOWN_PROPERTY ("Unknown property `%s'".printf (current_predicate));
- }
- } else {
- if (current_predicate == "http://www.w3.org/2000/01/rdf-schema#domain"
- && current_subject_is_var
- && !object_is_var) {
- // rdfs:domain
- var domain = ontologies.get_class_by_uri (object);
- if (domain == null) {
- throw new Sparql.Error.UNKNOWN_CLASS ("Unknown class `%s'".printf (object));
- }
- var pv = context.predicate_variable_map.lookup (context.get_variable (current_subject));
- if (pv == null) {
- pv = new PredicateVariable (manager);
- context.predicate_variable_map.insert (context.get_variable (current_subject), pv);
- }
- pv.domain = domain;
- }
-
- if (current_subject_is_var) {
- // Domain specific index might be a possibility, let's check
- Variable v = context.get_variable (current_subject);
- VariableBindingList list = triple_context.var_bindings.lookup (v);
-
- if (list != null && list.list != null) {
- bool stop = false;
- foreach (Class cl in prop.get_domain_indexes ()) {
- foreach (VariableBinding b in list.list) {
- if (b.type == cl) {
- db_table = cl.name;
- stop = true;
- break;
- }
- }
- if (stop) {
- break;
- }
- }
- }
- }
-
- if (db_table == null)
- db_table = prop.table_name;
-
- if (prop.multiple_values) {
- // we can never share the table with multiple triples
- // for multi value properties as a property may consist of multiple rows
- share_table = false;
- }
- subject_type = prop.domain;
-
- if (in_simple_optional && context.var_set.lookup (context.get_variable (current_subject)) == 0) {
- // use subselect instead of join in simple optional where the subject is the unbound variable
- // this can only happen with inverse functional properties
- var binding = new VariableBinding ();
- binding.data_type = PropertyType.RESOURCE;
- binding.variable = context.get_variable (current_subject);
-
- assert (triple_context.var_bindings.lookup (binding.variable) == null);
- var binding_list = new VariableBindingList ();
- triple_context.variables.append (binding.variable);
- triple_context.var_bindings.insert (binding.variable, binding_list);
-
- // need to use table and column name for object, can't refer to variable in nested select
- var object_binding = triple_context.var_bindings.lookup (context.get_variable (object)).list.data;
-
- sql.append_printf ("(SELECT ID FROM \"%s\" WHERE \"%s\" = %s) AS %s, ",
- db_table,
- prop.name,
- object_binding.sql_expression,
- binding.variable.sql_expression);
-
- context.var_set.insert (binding.variable, VariableState.OPTIONAL);
- binding_list.list.append (binding);
-
- assert (binding.variable.binding == null);
- binding.variable.binding = binding;
-
- return;
- }
- }
- table = get_table (current_subject, db_table, share_table, out newtable);
- } else {
- // variable in predicate
- newtable = true;
- table = new DataTable ();
- table.predicate_variable = context.predicate_variable_map.lookup (context.get_variable (current_predicate));
- if (table.predicate_variable == null) {
- table.predicate_variable = new PredicateVariable (manager);
- context.predicate_variable_map.insert (context.get_variable (current_predicate), table.predicate_variable);
- }
- if (!current_subject_is_var) {
- // single subject
- table.predicate_variable.subject = current_subject;
- }
- if (!object_is_var) {
- // single object
- table.predicate_variable.object = object;
- }
- if (current_graph != null) {
- table.predicate_variable.return_graph = true;
- }
- table.sql_query_tablename = current_predicate + (++counter).to_string ();
- triple_context.tables.append (table);
-
- // add to variable list
- var binding = new VariableBinding ();
- binding.data_type = PropertyType.RESOURCE;
- binding.variable = context.get_variable (current_predicate);
- binding.table = table;
- binding.sql_db_column_name = "predicate";
-
- add_variable_binding (sql, binding, VariableState.BOUND);
- }
-
- if (newtable) {
- if (current_subject_is_var) {
- var binding = new VariableBinding ();
- binding.data_type = PropertyType.RESOURCE;
- binding.variable = context.get_variable (current_subject);
- binding.table = table;
- binding.type = subject_type;
- if (is_fts_match) {
- binding.sql_db_column_name = "rowid";
- } else {
- binding.sql_db_column_name = "ID";
- }
-
- add_variable_binding (sql, binding, VariableState.BOUND);
- } else {
- var binding = new LiteralBinding ();
- binding.data_type = PropertyType.RESOURCE;
- binding.literal = current_subject;
- // binding.data_type = triple.subject.type;
- binding.table = table;
- binding.sql_db_column_name = "ID";
- triple_context.bindings.append (binding);
- }
- }
-
- if (!rdftype) {
- if (object_is_var) {
- var binding = new VariableBinding ();
- binding.variable = context.get_variable (object);
- binding.table = table;
- if (prop != null) {
-
- binding.type = prop.range;
-
- binding.data_type = prop.data_type;
- binding.sql_db_column_name = prop.name;
- if (!prop.multiple_values) {
- // for single value properties, row may have NULL
- // in any column except the ID column
- binding.maybe_null = true;
- binding.in_simple_optional = in_simple_optional;
- }
- } else {
- // variable as predicate
- binding.data_type = PropertyType.STRING;
- binding.sql_db_column_name = "object";
- binding.maybe_null = true;
- }
-
- VariableState state;
- if (in_simple_optional) {
- state = VariableState.OPTIONAL;
- } else {
- state = VariableState.BOUND;
- }
-
- add_variable_binding (sql, binding, state);
- } else if (is_fts_match) {
- var binding = new LiteralBinding ();
- binding.is_fts_match = true;
- binding.literal = object;
- // binding.data_type = triple.object.type;
- binding.table = table;
- binding.sql_db_column_name = "fts5";
- triple_context.bindings.append (binding);
-
- sql.append_printf ("\"%s\".\"rowid\" AS \"ID\", ",
- binding.table.sql_query_tablename);
- sql.append_printf ("\"%s\".\"rank\" AS \"%s_u_rank\", ",
- binding.table.sql_query_tablename,
- context.get_variable (current_subject).name);
- } else {
- var binding = new LiteralBinding ();
- binding.literal = object;
- // binding.data_type = triple.object.type;
- binding.table = table;
- if (prop != null) {
- binding.data_type = prop.data_type;
- binding.sql_db_column_name = prop.name;
- } else {
- // variable as predicate
- binding.sql_db_column_name = "object";
- }
- triple_context.bindings.append (binding);
- }
-
- if (current_graph != null) {
- if (current_graph_is_var) {
- var binding = new VariableBinding ();
- binding.variable = context.get_variable (current_graph);
- binding.table = table;
- binding.data_type = PropertyType.RESOURCE;
-
- if (prop != null) {
- binding.sql_db_column_name = prop.name + ":graph";
- } else {
- // variable as predicate
- binding.sql_db_column_name = "graph";
- }
-
- binding.maybe_null = true;
- binding.in_simple_optional = in_simple_optional;
-
- VariableState state;
- if (in_simple_optional) {
- state = VariableState.OPTIONAL;
- } else {
- state = VariableState.BOUND;
- }
-
- add_variable_binding (sql, binding, state);
- } else {
- var binding = new LiteralBinding ();
- binding.literal = current_graph;
- binding.table = table;
- binding.data_type = PropertyType.RESOURCE;
-
- if (prop != null) {
- binding.sql_db_column_name = prop.name + ":graph";
- } else {
- // variable as predicate
- binding.sql_db_column_name = "graph";
- }
-
- triple_context.bindings.append (binding);
- }
- }
- }
-
- if (sql.len == begin_sql_len) {
- // no SELECT expression was added, add dummy expression
- // this is required in cases where no values need to be retrieved
- sql.append ("1, ");
- }
- }
-
- private DataTable get_table (string subject, string db_table, bool share_table, out bool newtable) {
- string tablestring = "%s.%s".printf (subject, db_table);
- DataTable table = null;
- newtable = false;
- if (share_table) {
- table = triple_context.table_map.lookup (tablestring);
- }
- if (table == null) {
- newtable = true;
- table = new DataTable ();
- table.sql_db_tablename = db_table;
- table.sql_query_tablename = db_table + (++counter).to_string ();
- triple_context.tables.append (table);
- triple_context.table_map.insert (tablestring, table);
- }
- return table;
- }
-}
diff --git a/src/libtracker-data/tracker-sparql-query.vala b/src/libtracker-data/tracker-sparql-query.vala
deleted file mode 100644
index 54ce57003..000000000
--- a/src/libtracker-data/tracker-sparql-query.vala
+++ /dev/null
@@ -1,1102 +0,0 @@
-/*
- * Copyright (C) 2008-2010, Nokia
- *
- * 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.
- */
-
-namespace Tracker.Sparql {
- enum VariableState {
- NONE,
- BOUND,
- OPTIONAL
- }
-
- enum UpdateType {
- DELETE,
- INSERT,
- UPDATE
- }
-
- // Represents a SQL table
- class DataTable : Object {
- public string sql_db_tablename; // as in db schema
- public string sql_query_tablename; // temp. name, generated
- public PredicateVariable predicate_variable;
- }
-
- abstract class DataBinding : Object {
- public PropertyType data_type;
- public DataTable table;
- public string sql_db_column_name;
- public string sql_expression {
- get {
- if (this._sql_expression == null && table != null) {
- this._sql_expression = "\"%s\".\"%s\"".printf (table.sql_query_tablename, sql_db_column_name);
- }
- return this._sql_expression;
- }
- set {
- this._sql_expression = value;
- }
- }
- string? _sql_expression;
- public string get_extra_sql_expression (string suffix) {
- return "\"%s\".\"%s:%s\"".printf (table.sql_query_tablename, sql_db_column_name, suffix);
- }
- }
-
- // Represents a mapping of a SPARQL literal to a SQL table and column
- class LiteralBinding : DataBinding {
- public bool is_fts_match;
- public string literal;
- }
-
- // Represents a mapping of a SPARQL variable to a SQL table and column
- class VariableBinding : DataBinding {
- public weak Variable variable;
- // Specified whether SQL column may contain NULL entries
- public bool maybe_null;
- public bool in_simple_optional;
- public Class? type;
- }
-
- class VariableBindingList : Object {
- public List<VariableBinding> list;
- }
-
- class Variable : Object {
- public string name { get; private set; }
- public int index { get; private set; }
- public string sql_expression { get; private set; }
- public VariableBinding binding;
- string sql_identifier;
-
- public Variable (string name, int index) {
- this.name = name;
- this.index = index;
- this.sql_identifier = "%d_u".printf (index);
- this.sql_expression = "\"%s\"".printf (sql_identifier);
- }
-
- public string get_extra_sql_expression (string suffix) {
- return "\"%s:%s\"".printf (sql_identifier, suffix);
- }
-
- public static bool equal (Variable a, Variable b) {
- return a.index == b.index;
- }
-
- public static uint hash (Variable variable) {
- return (uint) variable.index;
- }
- }
-
- class Context {
- public weak Query query;
-
- public Context? parent_context;
- // All SPARQL variables within a subgraph pattern (used by UNION)
- // value is VariableState
- public HashTable<Variable,int> var_set;
-
- public HashTable<string,Variable> var_map;
- // All selected SPARQL variables (used by compositional subqueries)
- public HashTable<Variable,int> select_var_set;
-
- // Variables used as predicates
- public HashTable<Variable,PredicateVariable> predicate_variable_map;
-
- public bool scalar_subquery;
- public bool need_binding_expression;
-
- public Context (Query query, Context? parent_context = null) {
- this.query = query;
- this.parent_context = parent_context;
- this.var_set = new HashTable<Variable,int>.full (Variable.hash, Variable.equal, g_object_unref, null);
-
- if (parent_context == null) {
- select_var_set = new HashTable<Variable,int>.full (Variable.hash, Variable.equal, g_object_unref, null);
- var_map = new HashTable<string,Variable>.full (str_hash, str_equal, g_free, g_object_unref);
- predicate_variable_map = new HashTable<Variable,PredicateVariable>.full (Variable.hash, Variable.equal, g_object_unref, g_object_unref);
- } else {
- select_var_set = parent_context.select_var_set;
- var_map = parent_context.var_map;
- predicate_variable_map = parent_context.predicate_variable_map;
- }
- }
-
- public Context.subquery (Query query, Context parent_context) {
- this.query = query;
- this.parent_context = parent_context;
- this.var_set = new HashTable<Variable,int>.full (Variable.hash, Variable.equal, g_object_unref, null);
-
- select_var_set = new HashTable<Variable,int>.full (Variable.hash, Variable.equal, g_object_unref, null);
- var_map = parent_context.var_map;
- predicate_variable_map = new HashTable<Variable,PredicateVariable>.full (Variable.hash, Variable.equal, g_object_unref, g_object_unref);
- scalar_subquery = true;
- }
-
- internal unowned Variable get_variable (string name) {
- unowned Variable result = this.var_map.lookup (name);
- if (result == null) {
- var variable = new Variable (name, ++query.last_var_index);
- this.var_map.insert (name, variable);
-
- result = variable;
- }
- return result;
- }
- }
-
- class SelectContext : Context {
- public PropertyType type;
- public PropertyType[] types = {};
- public string[] variable_names = {};
-
- public SelectContext (Query query, Context? parent_context = null) {
- base (query, parent_context);
- }
-
- public SelectContext.subquery (Query query, Context parent_context) {
- base.subquery (query, parent_context);
- }
- }
-
- class Solution {
- public HashTable<string,int?> hash;
- public GenericArray<string> values;
- public int solution_index;
-
- public Solution () {
- this.hash = new HashTable<string,int?> (str_hash, str_equal);
- this.values = new GenericArray<string> ();
- }
-
- public string? lookup (string variable_name) {
- int? variable_index = hash.get (variable_name);
- if (variable_index == null) {
- return null;
- }
- return values[solution_index * hash.size () + variable_index];
- }
- }
-}
-
-public class Tracker.Sparql.Query : Object {
- SparqlScanner scanner;
-
- // token buffer
- TokenInfo[] tokens;
- // index of current token in buffer
- int index;
- // number of tokens in buffer
- int size;
-
- const int BUFFER_SIZE = 32;
-
- struct TokenInfo {
- public SparqlTokenType type;
- public SourceLocation begin;
- public SourceLocation end;
- }
-
- const string FN_NS = "http://www.w3.org/2005/xpath-functions#";
-
- string query_string;
- bool update_extensions;
-
- internal Expression expression;
- internal Pattern pattern;
-
- string current_graph;
- string current_subject;
- bool current_subject_is_var;
- string current_predicate;
- bool current_predicate_is_var;
-
- // SILENT => ignore (non-syntax) errors
- bool silent;
-
- HashTable<string,string> prefix_map;
-
- // All SPARQL literals
- internal List<LiteralBinding> bindings;
-
- internal Context context;
-
- int bnodeid = 0;
- // base UUID used for blank nodes
- uchar[] base_uuid;
- HashTable<string,string> blank_nodes;
-
- public Data.Manager manager;
-
- // Keep track of used SQL identifiers for SPARQL variables
- public int last_var_index;
-
- public bool no_cache { get; set; }
-
- public Query (Data.Manager manager, string query) {
- no_cache = false; /* Start with false, expression sets it */
- tokens = new TokenInfo[BUFFER_SIZE];
- prefix_map = new HashTable<string,string>.full (str_hash, str_equal, g_free, g_free);
-
- base_uuid = new uchar[16];
- uuid_generate (base_uuid);
-
- this.query_string = query;
- this.manager = manager;
-
- expression = new Expression (this);
- pattern = new Pattern (this);
- }
-
- public Query.update (Data.Manager manager, string query) {
- this (manager, query);
- this.update_extensions = true;
- }
-
- string get_uuid_for_name (uchar[] base_uuid, string name) {
- var checksum = new Checksum (ChecksumType.SHA1);
- // base UUID, unique per file
- checksum.update (base_uuid, 16);
-
- // node ID
- checksum.update ((uchar[]) name, -1);
-
- string sha1 = checksum.get_string ();
-
- // generate name based uuid
- return "urn:uuid:%.8s-%.4s-%.4s-%.4s-%.12s".printf (
- sha1, sha1.substring (8), sha1.substring (12), sha1.substring (16), sha1.substring (20));
- }
-
- internal string generate_bnodeid (string? user_bnodeid) {
- // user_bnodeid is NULL for anonymous nodes
- if (user_bnodeid == null) {
- return ":%d".printf (++bnodeid);
- } else {
- string uri = null;
-
- if (blank_nodes != null) {
- uri = blank_nodes.lookup (user_bnodeid);
- if (uri != null) {
- return uri;
- }
- }
-
- uri = get_uuid_for_name (base_uuid, user_bnodeid);
-
- if (blank_nodes != null) {
- var iface = manager.get_db_interface ();
- while (Data.query_resource_id (manager, iface, uri) > 0) {
- // uri collision, generate new UUID
- uchar[] new_base_uuid = new uchar[16];
- uuid_generate (new_base_uuid);
- uri = get_uuid_for_name (new_base_uuid, user_bnodeid);
- }
-
- blank_nodes.insert (user_bnodeid, uri);
- }
-
- return uri;
- }
- }
-
- internal bool next () throws Sparql.Error {
- index = (index + 1) % BUFFER_SIZE;
- size--;
- if (size <= 0) {
- SourceLocation begin, end;
- SparqlTokenType type = scanner.read_token (out begin, out end);
- tokens[index].type = type;
- tokens[index].begin = begin;
- tokens[index].end = end;
- size = 1;
- }
- return (tokens[index].type != SparqlTokenType.EOF);
- }
-
- internal SparqlTokenType current () {
- return tokens[index].type;
- }
-
- internal SparqlTokenType last () {
- int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE;
- return tokens[last_index].type;
- }
-
- internal bool accept (SparqlTokenType type) throws Sparql.Error {
- if (current () == type) {
- next ();
- return true;
- }
- return false;
- }
-
- internal void optional (SparqlTokenType type) throws Sparql.Error {
- if (current () == type)
- next ();
- }
-
- internal Sparql.Error get_error (string msg) {
- return new Sparql.Error.PARSE ("%d.%d: syntax error, %s".printf (tokens[index].begin.line, tokens[index].begin.column, msg));
- }
-
- internal Sparql.Error get_internal_error (string msg) {
- return new Sparql.Error.INTERNAL ("%d.%d: %s".printf (tokens[index].begin.line, tokens[index].begin.column, msg));
- }
-
- internal bool expect (SparqlTokenType type) throws Sparql.Error {
- if (accept (type)) {
- return true;
- }
-
- throw get_error ("expected %s".printf (type.to_string ()));
- }
-
- internal SourceLocation get_location () {
- return tokens[index].begin;
- }
-
- internal void set_location (SourceLocation location) {
- scanner.seek (location);
- size = 0;
- index = 0;
- try {
- next ();
- } catch (Sparql.Error e) {
- // this should never happen as this is the second time we scan this token
- critical ("internal error: next in set_location failed");
- }
- }
-
- internal string get_last_string (int strip = 0) {
- int last_index = (index + BUFFER_SIZE - 1) % BUFFER_SIZE;
- return ((string) (tokens[last_index].begin.pos + strip)).substring (0, (int) (tokens[last_index].end.pos - tokens[last_index].begin.pos - 2 * strip));
- }
-
- private void parse_prologue () throws Sparql.Error {
- if (accept (SparqlTokenType.BASE)) {
- expect (SparqlTokenType.IRI_REF);
- }
- while (accept (SparqlTokenType.PREFIX)) {
- string ns = "";
- if (accept (SparqlTokenType.PN_PREFIX)) {
- ns = get_last_string ();
- }
- expect (SparqlTokenType.COLON);
- expect (SparqlTokenType.IRI_REF);
- string uri = get_last_string (1);
- prefix_map.insert (ns, uri);
- }
- }
-
- private void prepare_execute () throws DBInterfaceError, Sparql.Error, DateError {
- assert (!update_extensions);
-
- scanner = new SparqlScanner ((char*) query_string, (long) query_string.length);
- next ();
-
- // declare fn prefix for XPath functions
- prefix_map.insert ("fn", FN_NS);
- var ontologies = manager.get_ontologies ();
-
- foreach (Namespace ns in ontologies.get_namespaces ()) {
- if (ns.prefix == null) {
- critical ("Namespace does not specify a prefix: %s", ns.uri);
- continue;
- }
- prefix_map.insert (ns.prefix, ns.uri);
- }
-
- parse_prologue ();
- }
-
-
- public DBCursor? execute_cursor () throws DBInterfaceError, Sparql.Error, DateError {
-
- prepare_execute ();
-
- switch (current ()) {
- case SparqlTokenType.SELECT:
- return execute_select_cursor ();
- case SparqlTokenType.CONSTRUCT:
- throw get_internal_error ("CONSTRUCT is not supported");
- case SparqlTokenType.DESCRIBE:
- throw get_internal_error ("DESCRIBE is not supported");
- case SparqlTokenType.ASK:
- return execute_ask_cursor ();
- case SparqlTokenType.INSERT:
- case SparqlTokenType.DELETE:
- case SparqlTokenType.DROP:
- throw get_error ("INSERT and DELETE are not supported in query mode");
- default:
- throw get_error ("expected SELECT or ASK");
- }
- }
-
- public Variant? execute_update (bool blank) throws GLib.Error {
- Variant result = null;
- assert (update_extensions);
-
- scanner = new SparqlScanner ((char*) query_string, (long) query_string.length);
- next ();
-
- // declare fn prefix for XPath functions
- prefix_map.insert ("fn", FN_NS);
- var ontologies = manager.get_ontologies ();
-
- foreach (Namespace ns in ontologies.get_namespaces ()) {
- if (ns.prefix == null) {
- critical ("Namespace does not specify a prefix: %s", ns.uri);
- continue;
- }
- prefix_map.insert (ns.prefix, ns.uri);
- }
-
- parse_prologue ();
-
- // SPARQL update supports multiple operations in a single query
- VariantBuilder? ublank_nodes = null;
-
- if (blank) {
- ublank_nodes = new VariantBuilder ((VariantType) "aaa{ss}");
- }
-
- while (current () != SparqlTokenType.EOF) {
- switch (current ()) {
- case SparqlTokenType.WITH:
- case SparqlTokenType.INSERT:
- case SparqlTokenType.DELETE:
- if (blank) {
- ublank_nodes.open ((VariantType) "aa{ss}");
- execute_insert_delete (ublank_nodes);
- ublank_nodes.close ();
- } else {
- execute_insert_delete (null);
- }
- break;
- case SparqlTokenType.DROP:
- throw get_internal_error ("DROP GRAPH is not supported");
- case SparqlTokenType.SELECT:
- case SparqlTokenType.CONSTRUCT:
- case SparqlTokenType.DESCRIBE:
- case SparqlTokenType.ASK:
- throw get_error ("SELECT, CONSTRUCT, DESCRIBE, and ASK are not supported in update mode");
- default:
- throw get_error ("expected INSERT or DELETE");
- }
-
- // semicolon is used to separate multiple operations in the current SPARQL Update draft
- // keep it optional for now to reatin backward compatibility
- optional (SparqlTokenType.SEMICOLON);
- }
-
- if (blank) {
- result = ublank_nodes.end ();
- }
-
- return result;
- }
-
- private DBStatement prepare_for_exec (DBInterface iface, string sql) throws DBInterfaceError, Sparql.Error, DateError {
- var stmt = iface.create_statement (no_cache ? DBStatementCacheType.NONE : DBStatementCacheType.SELECT, "%s", sql);
-
- // set literals specified in query
- int i = 0;
- foreach (LiteralBinding binding in bindings) {
- if (binding.data_type == PropertyType.BOOLEAN) {
- if (binding.literal == "true" || binding.literal == "1") {
- stmt.bind_int (i, 1);
- } else if (binding.literal == "false" || binding.literal == "0") {
- stmt.bind_int (i, 0);
- } else {
- throw new Sparql.Error.TYPE ("`%s' is not a valid boolean".printf (binding.literal));
- }
- } else if (binding.data_type == PropertyType.DATE) {
- stmt.bind_int (i, (int) string_to_date (binding.literal + "T00:00:00Z", null));
- } else if (binding.data_type == PropertyType.DATETIME) {
- stmt.bind_double (i, string_to_date (binding.literal, null));
- } else if (binding.data_type == PropertyType.INTEGER) {
- stmt.bind_int (i, int.parse (binding.literal));
- } else {
- stmt.bind_text (i, binding.literal);
- }
- i++;
- }
-
- return stmt;
- }
-
- private DBCursor? exec_sql_cursor (DBInterface iface, string sql, PropertyType[]? types, string[]? variable_names) throws DBInterfaceError, Sparql.Error, DateError {
- var stmt = prepare_for_exec (iface, sql);
-
- return stmt.start_sparql_cursor (types, variable_names);
- }
-
- private string get_select_query (out SelectContext context) throws DBInterfaceError, Sparql.Error, DateError {
- // SELECT query
-
- // build SQL
- var sql = new StringBuilder ();
- context = pattern.translate_select (sql);
-
- expect (SparqlTokenType.EOF);
-
- return sql.str;
- }
-
- private DBCursor? execute_select_cursor () throws DBInterfaceError, Sparql.Error, DateError {
- SelectContext context;
- string sql = get_select_query (out context);
- var iface = manager.get_db_interface ();
-
- return exec_sql_cursor (iface, sql, context.types, context.variable_names);
- }
-
- private string get_ask_query () throws DBInterfaceError, Sparql.Error, DateError {
- // ASK query
-
- var pattern_sql = new StringBuilder ();
-
- // build SQL
- var sql = new StringBuilder ();
- sql.append ("SELECT CASE EXISTS ( ");
-
- expect (SparqlTokenType.ASK);
-
- optional (SparqlTokenType.WHERE);
-
- context = pattern.translate_group_graph_pattern (pattern_sql);
-
- // select from results of WHERE clause
- sql.append (pattern_sql.str);
- sql.append (" ) WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE NULL END");
-
- if (accept (SparqlTokenType.GROUP) || accept (SparqlTokenType.ORDER) ||
- accept (SparqlTokenType.OFFSET) || accept (SparqlTokenType.LIMIT)) {
- throw get_error ("invalid use of %s in ASK".printf (last().to_string()));
- }
-
- expect (SparqlTokenType.EOF);
-
- context = context.parent_context;
-
- return sql.str;
- }
-
- private DBCursor? execute_ask_cursor () throws DBInterfaceError, Sparql.Error, DateError {
- var iface = manager.get_db_interface ();
- return exec_sql_cursor (iface, get_ask_query (), new PropertyType[] { PropertyType.BOOLEAN }, new string[] { "result" });
- }
-
- private void parse_from_or_into_param () throws Sparql.Error {
- if (accept (SparqlTokenType.IRI_REF)) {
- current_graph = get_last_string (1);
- } else if (accept (SparqlTokenType.PN_PREFIX)) {
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- current_graph = resolve_prefixed_name (ns, get_last_string ().substring (1));
- } else {
- expect (SparqlTokenType.COLON);
- current_graph = resolve_prefixed_name ("", get_last_string ().substring (1));
- }
- }
-
- private void execute_insert_delete (VariantBuilder? update_blank_nodes) throws GLib.Error {
- bool blank = true;
-
- // DELETE and/or INSERT
-
- if (accept (SparqlTokenType.WITH)) {
- parse_from_or_into_param ();
- } else {
- current_graph = null;
- }
-
- SourceLocation? delete_location = null;
- SourceLocation? insert_location = null;
- bool insert_is_update = false;
- bool delete_where = false;
- bool data = false;
-
- var data_update = manager.get_data ();
-
- // Sparql 1.1 defines deletes/inserts as a single
- // operation with the syntax:
- // [DELETE {...}] [INSERT {...}] WHERE {...}
-
- if (accept (SparqlTokenType.DELETE)) {
- blank = false;
-
- // SILENT => ignore (non-syntax) errors
- silent = accept (SparqlTokenType.SILENT);
-
- if (current_graph == null && accept (SparqlTokenType.FROM)) {
- parse_from_or_into_param ();
- }
-
- if (current_graph == null && accept (SparqlTokenType.DATA)) {
- // INSERT/DELETE DATA are simpler variants
- // that don't support variables
- data = true;
- } else if (accept (SparqlTokenType.WHERE)) {
- // DELETE WHERE is a short form where the pattern
- // is also used as the template for deletion
- delete_where = true;
- }
-
- delete_location = get_location ();
-
- if (!data && !delete_where) {
- skip_braces ();
- }
- }
-
- if (!data && !delete_where && accept (SparqlTokenType.INSERT)) {
- if (accept (SparqlTokenType.OR)) {
- expect (SparqlTokenType.REPLACE);
- insert_is_update = true;
- }
-
- if (!insert_is_update) {
- // SILENT => ignore (non-syntax) errors
- silent = accept (SparqlTokenType.SILENT);
- }
-
- if (current_graph == null && accept (SparqlTokenType.INTO)) {
- parse_from_or_into_param ();
- }
-
- if (current_graph == null && accept (SparqlTokenType.DATA)) {
- // INSERT/DELETE DATA are simpler variants
- // that don't support variables
- data = true;
- }
-
- if (current () != SparqlTokenType.OPEN_BRACE) {
- throw get_error ("Expected '{' beginning a quad data/pattern block");
- }
-
- insert_location = get_location ();
-
- if (!data) {
- skip_braces ();
- }
- }
-
- var pattern_sql = new StringBuilder ();
-
- var sql = new StringBuilder ();
-
- if (!data) {
- if (delete_where || accept (SparqlTokenType.WHERE)) {
- pattern.current_graph = current_graph;
- context = pattern.translate_group_graph_pattern (pattern_sql);
- pattern.current_graph = null;
- } else {
- context = new Context (this);
-
- pattern_sql.append ("SELECT 1");
- }
- } else {
- // WHERE pattern not supported for INSERT/DELETE DATA,
- // nor unbound values in the quad data.
- if (quad_data_unbound_var_count () > 0) {
- throw get_error ("INSERT/DELETE DATA do not allow unbound values");
- }
-
- context = new Context (this);
-
- pattern_sql.append ("SELECT 1");
- }
-
- var after_where = get_location ();
-
- var solution = new Solution ();
-
- // build SQL
- sql.append ("SELECT ");
- int var_idx = 0;
- foreach (var variable in context.var_set.get_keys ()) {
- if (var_idx > 0) {
- sql.append (", ");
- }
-
- if (variable.binding == null) {
- throw get_error ("use of undefined variable `%s'".printf (variable.name));
- }
- Expression.append_expression_as_string (sql, variable.sql_expression, variable.binding.data_type);
-
- solution.hash.insert (variable.name, var_idx++);
- }
-
- if (var_idx == 0) {
- sql.append ("1");
- }
-
- // select from results of WHERE clause
- sql.append (" FROM (");
- sql.append (pattern_sql.str);
- sql.append (")");
-
- var iface = manager.get_writable_db_interface ();
- var cursor = exec_sql_cursor (iface, sql.str, null, null);
-
- int n_solutions = 0;
- while (cursor.next ()) {
- // get values of all variables to be bound
- for (var_idx = 0; var_idx < solution.hash.size (); var_idx++) {
- solution.values.add (cursor.get_string (var_idx));
- }
- n_solutions++;
- }
-
- cursor = null;
-
- // Iterate over all solutions twice
- // First handle deletes
- if (delete_location != null) {
- for (int i = 0; i < n_solutions; i++) {
- solution.solution_index = i;
- set_location (delete_location);
- parse_construct_triples_block (solution, UpdateType.DELETE);
- data_update.update_buffer_might_flush ();
- }
-
- // Force flush on delete/insert operations,
- // so the elements are already removed at
- // the time of insertion.
- if (insert_location != null)
- data_update.update_buffer_flush ();
- }
-
- // Then handle inserts/updates
- if (insert_location != null) {
- for (int i = 0; i < n_solutions; i++) {
- uuid_generate (base_uuid);
- blank_nodes = new HashTable<string,string>.full (str_hash, str_equal, g_free, g_free);
- solution.solution_index = i;
-
- set_location (insert_location);
- parse_construct_triples_block (solution,
- insert_is_update ?
- UpdateType.UPDATE :
- UpdateType.INSERT);
-
- if (blank && update_blank_nodes != null) {
- update_blank_nodes.add_value (blank_nodes);
- }
-
- data_update.update_buffer_might_flush ();
- }
- }
-
- solution = null;
-
- if (!data) {
- // reset location to the end of the update
- set_location (after_where);
- }
-
- // ensure possible WHERE clause in next part gets the correct results
- data_update.update_buffer_flush ();
- bindings = null;
-
- context = context.parent_context;
- }
-
- internal string resolve_prefixed_name (string prefix, string local_name) throws Sparql.Error {
- string ns = prefix_map.lookup (prefix);
- if (ns == null) {
- throw get_error ("use of undefined prefix `%s'".printf (prefix));
- }
- return ns + local_name;
- }
-
- private int quad_data_unbound_var_count () throws Sparql.Error {
- SourceLocation current_pos = get_location ();
- int n_braces = 1;
- int n_unbound = 0;
-
- expect (SparqlTokenType.OPEN_BRACE);
- while (n_braces > 0) {
- if (accept (SparqlTokenType.OPEN_BRACE)) {
- n_braces++;
- } else if (accept (SparqlTokenType.CLOSE_BRACE)) {
- n_braces--;
- } else if (current () == SparqlTokenType.EOF) {
- throw get_error ("unexpected end of query, expected }");
- } else {
- if (current () == SparqlTokenType.VAR)
- n_unbound++;
- // ignore everything else
- next ();
- }
- }
-
- set_location (current_pos);
- return n_unbound;
- }
-
- private void skip_braces () throws Sparql.Error {
- expect (SparqlTokenType.OPEN_BRACE);
- int n_braces = 1;
- while (n_braces > 0) {
- if (accept (SparqlTokenType.OPEN_BRACE)) {
- n_braces++;
- } else if (accept (SparqlTokenType.CLOSE_BRACE)) {
- n_braces--;
- } else if (current () == SparqlTokenType.EOF) {
- throw get_error ("unexpected end of query, expected }");
- } else {
- // ignore everything else
- next ();
- }
- }
- }
-
- private void parse_construct_triples_block (Solution var_value_map, UpdateType type) throws Sparql.Error, DateError {
- expect (SparqlTokenType.OPEN_BRACE);
-
- while (current () != SparqlTokenType.CLOSE_BRACE) {
- bool is_null = false;
-
- if (accept (SparqlTokenType.GRAPH)) {
- var old_graph = current_graph;
- current_graph = parse_construct_var_or_term (var_value_map, type, out is_null);
-
- if (is_null) {
- throw get_error ("'null' not supported for graph");
- }
-
- expect (SparqlTokenType.OPEN_BRACE);
-
- while (current () != SparqlTokenType.CLOSE_BRACE) {
- current_subject = parse_construct_var_or_term (var_value_map, type, out is_null);
-
- if (is_null) {
- throw get_error ("'null' not supported for subject");
- }
-
- parse_construct_property_list_not_empty (var_value_map, type);
- if (!accept (SparqlTokenType.DOT)) {
- // no triples following
- break;
- }
- }
-
- expect (SparqlTokenType.CLOSE_BRACE);
-
- current_graph = old_graph;
-
- optional (SparqlTokenType.DOT);
- } else {
- current_subject = parse_construct_var_or_term (var_value_map, type, out is_null);
-
- if (is_null) {
- throw get_error ("'null' not supported for subject");
- }
-
- parse_construct_property_list_not_empty (var_value_map, type);
- if (!accept (SparqlTokenType.DOT) && current () != SparqlTokenType.GRAPH) {
- // neither GRAPH nor triples following
- break;
- }
- }
- }
-
- expect (SparqlTokenType.CLOSE_BRACE);
- }
-
- bool anon_blank_node_open = false;
-
- private string? parse_construct_var_or_term (Solution var_value_map, UpdateType type, out bool is_null) throws Sparql.Error, DateError {
- string result = "";
- is_null = false;
- if (current () == SparqlTokenType.VAR) {
- next ();
- result = var_value_map.lookup (get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.IRI_REF) {
- next ();
- result = get_last_string (1);
- } else if (current () == SparqlTokenType.PN_PREFIX) {
- // prefixed name with namespace foo:bar
- next ();
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- result = resolve_prefixed_name (ns, get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.COLON) {
- // prefixed name without namespace :bar
- next ();
- result = resolve_prefixed_name ("", get_last_string ().substring (1));
- } else if (accept (SparqlTokenType.BLANK_NODE)) {
- // _:foo
- expect (SparqlTokenType.COLON);
- result = generate_bnodeid (get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.MINUS) {
- next ();
- if (current () == SparqlTokenType.INTEGER ||
- current () == SparqlTokenType.DECIMAL ||
- current () == SparqlTokenType.DOUBLE) {
- next ();
- result = "-" + get_last_string ();
- } else {
- throw get_error ("expected variable or term");
- }
- } else if (current () == SparqlTokenType.INTEGER) {
- next ();
- result = get_last_string ();
- } else if (current () == SparqlTokenType.NULL) {
- next ();
- result = "null";
- is_null = true;
- } else if (current () == SparqlTokenType.DECIMAL) {
- next ();
- result = get_last_string ();
- } else if (current () == SparqlTokenType.DOUBLE) {
- next ();
- result = get_last_string ();
- } else if (current () == SparqlTokenType.TRUE) {
- next ();
- result = "true";
- } else if (current () == SparqlTokenType.FALSE) {
- next ();
- result = "false";
- } else if (current () == SparqlTokenType.STRING_LITERAL1) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.STRING_LITERAL2) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.STRING_LITERAL_LONG1) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.STRING_LITERAL_LONG2) {
- result = expression.parse_string_literal ();
- } else if (current () == SparqlTokenType.OPEN_BRACKET) {
-
- if (anon_blank_node_open) {
- throw get_error ("no support for nested anonymous blank nodes");
- }
-
- anon_blank_node_open = true;
- next ();
-
- result = generate_bnodeid (null);
-
- string old_subject = current_subject;
- bool old_subject_is_var = current_subject_is_var;
-
- current_subject = result;
- parse_construct_property_list_not_empty (var_value_map, type);
- expect (SparqlTokenType.CLOSE_BRACKET);
- anon_blank_node_open = false;
-
- current_subject = old_subject;
- current_subject_is_var = old_subject_is_var;
- } else {
- throw get_error ("expected variable or term");
- }
- return result;
- }
-
- private void parse_construct_property_list_not_empty (Solution var_value_map, UpdateType type) throws Sparql.Error, DateError {
- while (true) {
- var old_predicate = current_predicate;
-
- current_predicate = null;
- if (current () == SparqlTokenType.VAR) {
- current_predicate_is_var = true;
- next ();
- current_predicate = var_value_map.lookup (get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.IRI_REF) {
- next ();
- current_predicate = get_last_string (1);
- } else if (current () == SparqlTokenType.PN_PREFIX) {
- next ();
- string ns = get_last_string ();
- expect (SparqlTokenType.COLON);
- current_predicate = resolve_prefixed_name (ns, get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.COLON) {
- next ();
- current_predicate = resolve_prefixed_name ("", get_last_string ().substring (1));
- } else if (current () == SparqlTokenType.A) {
- next ();
- current_predicate = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
- } else {
- throw get_error ("expected non-empty property list");
- }
- parse_construct_object_list (var_value_map, type);
-
- current_predicate = old_predicate;
-
- if (accept (SparqlTokenType.SEMICOLON)) {
- continue;
- }
- break;
- }
- }
-
- private void parse_construct_object_list (Solution var_value_map, UpdateType type) throws Sparql.Error, DateError {
- while (true) {
- parse_construct_object (var_value_map, type);
- if (accept (SparqlTokenType.COMMA)) {
- continue;
- }
- break;
- }
- }
-
- private void parse_construct_object (Solution var_value_map, UpdateType type) throws Sparql.Error, DateError {
- bool is_null = false;
- string object = parse_construct_var_or_term (var_value_map, type, out is_null);
- var data = manager.get_data ();
- if (current_subject == null || current_predicate == null || object == null) {
- // the SPARQL specification says that triples containing unbound variables
- // should be excluded from the output RDF graph of CONSTRUCT
- return;
- }
- try {
- if (type == UpdateType.UPDATE) {
- // update triple in database
- data.update_statement (current_graph, current_subject, current_predicate, is_null ? null : object);
- } else if (type == UpdateType.DELETE) {
- // delete triple from database
- if (is_null) {
- throw get_error ("'null' not supported in this mode");
- }
- data.delete_statement (current_graph, current_subject, current_predicate, object);
- } else if (type == UpdateType.INSERT) {
- // insert triple into database
- if (is_null) {
- throw get_error ("'null' not supported in this mode");
- }
- data.insert_statement (current_graph, current_subject, current_predicate, object);
- }
- } catch (Sparql.Error e) {
- if (!silent) {
- throw e;
- }
- } catch (DateError e) {
- if (!silent) {
- throw new Sparql.Error.TYPE (e.message);
- }
- }
- }
-
- [CCode (cname = "uuid_generate")]
- public extern static void uuid_generate ([CCode (array_length = false)] uchar[] uuid);
-}
-
diff --git a/src/libtracker-data/tracker-sparql-types.c b/src/libtracker-data/tracker-sparql-types.c
new file mode 100644
index 000000000..1c6125f0f
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql-types.c
@@ -0,0 +1,908 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+
+#include "config.h"
+
+#include "tracker-sparql-types.h"
+
+enum {
+ TOKEN_TYPE_NONE,
+ TOKEN_TYPE_LITERAL,
+ TOKEN_TYPE_VARIABLE,
+ TOKEN_TYPE_PARAMETER,
+};
+
+/* Helper structs */
+static TrackerDataTable *
+tracker_data_table_new (const gchar *tablename,
+ const gchar *subject,
+ gint idx)
+{
+ TrackerDataTable *table;
+
+ table = g_new0 (TrackerDataTable, 1);
+ table->subject = g_strdup (subject);
+ table->sql_db_tablename = g_strdup (tablename);
+ table->sql_query_tablename = g_strdup_printf ("%s%d", tablename, idx);
+
+ return table;
+}
+
+static void
+tracker_data_table_free (TrackerDataTable *table)
+{
+ g_free (table->subject);
+ g_free (table->sql_db_tablename);
+ g_free (table->sql_query_tablename);
+ g_free (table);
+}
+
+void
+tracker_data_table_set_predicate_variable (TrackerDataTable *table,
+ TrackerPredicateVariable *variable)
+{
+ table->predicate_variable = variable;
+}
+
+TrackerPredicateVariable *
+tracker_predicate_variable_new (void)
+{
+ return g_new0 (TrackerPredicateVariable, 1);
+}
+
+static void
+tracker_predicate_variable_free (TrackerPredicateVariable *pred_var)
+{
+ g_clear_object (&pred_var->domain);
+ g_free (pred_var->subject);
+ g_free (pred_var->object);
+ g_free (pred_var);
+}
+
+void
+tracker_predicate_variable_set_domain (TrackerPredicateVariable *pred_var,
+ TrackerClass *domain)
+{
+ g_set_object (&pred_var->domain, domain);
+}
+
+void
+tracker_predicate_variable_set_triple_details (TrackerPredicateVariable *pred_var,
+ const gchar *subject,
+ const gchar *object,
+ gboolean return_graph)
+{
+ g_free (pred_var->subject);
+ pred_var->subject = g_strdup (subject);
+ g_free (pred_var->object);
+ pred_var->object = g_strdup (object);
+ pred_var->return_graph = !!return_graph;
+}
+
+static TrackerVariable *
+tracker_variable_new (const gchar *sql_prefix,
+ const gchar *name)
+{
+ TrackerVariable *variable;
+
+ variable = g_new0 (TrackerVariable, 1);
+ variable->name = g_strdup (name);
+ variable->sql_expression = g_strdup_printf ("\"%s_%s\"", sql_prefix, name);
+
+ return variable;
+}
+
+static void
+tracker_variable_free (TrackerVariable *variable)
+{
+ g_clear_object (&variable->binding);
+ g_free (variable->sql_expression);
+ g_free (variable->name);
+ g_free (variable);
+}
+
+void
+tracker_variable_set_sql_expression (TrackerVariable *variable,
+ const gchar *sql_expression)
+{
+ g_free (variable->sql_expression);
+ variable->sql_expression = g_strdup (sql_expression);
+}
+
+const gchar *
+tracker_variable_get_sql_expression (TrackerVariable *variable)
+{
+ return variable->sql_expression;
+}
+
+gchar *
+tracker_variable_get_extra_sql_expression (TrackerVariable *variable,
+ const gchar *suffix)
+{
+ return g_strdup_printf ("%s:%s", variable->sql_expression, suffix);
+}
+
+gboolean
+tracker_variable_has_bindings (TrackerVariable *variable)
+{
+ return variable->binding != NULL;
+}
+
+void
+tracker_variable_set_sample_binding (TrackerVariable *variable,
+ TrackerVariableBinding *binding)
+{
+ g_set_object (&variable->binding, binding);
+}
+
+TrackerVariableBinding *
+tracker_variable_get_sample_binding (TrackerVariable *variable)
+{
+ return variable->binding;
+}
+
+guint
+tracker_variable_hash (gconstpointer data)
+{
+ const TrackerVariable *variable = data;
+ return g_str_hash (variable->name);
+}
+
+gboolean
+tracker_variable_equal (gconstpointer data1,
+ gconstpointer data2)
+{
+ const TrackerVariable *var1 = data1, *var2 = data2;
+ return g_str_equal (var1->name, var2->name);
+}
+
+void
+tracker_token_literal_init (TrackerToken *token,
+ const gchar *literal)
+{
+ token->type = TOKEN_TYPE_LITERAL;
+ token->content.literal = g_strdup (literal);
+}
+
+void
+tracker_token_variable_init (TrackerToken *token,
+ TrackerVariable *variable)
+{
+ token->type = TOKEN_TYPE_VARIABLE;
+ token->content.var = variable;
+}
+
+void
+tracker_token_parameter_init (TrackerToken *token,
+ const gchar *parameter)
+{
+ token->type = TOKEN_TYPE_PARAMETER;
+ token->content.parameter = g_strdup (parameter);
+}
+
+void
+tracker_token_unset (TrackerToken *token)
+{
+ if (token->type == TOKEN_TYPE_LITERAL)
+ g_clear_pointer (&token->content.literal, g_free);
+ else if (token->type == TOKEN_TYPE_PARAMETER)
+ g_clear_pointer (&token->content.parameter, g_free);
+ token->type = TOKEN_TYPE_NONE;
+}
+
+gboolean
+tracker_token_is_empty (TrackerToken *token)
+{
+ return token->type == TOKEN_TYPE_NONE;
+}
+
+const gchar *
+tracker_token_get_literal (TrackerToken *token)
+{
+ if (token->type == TOKEN_TYPE_LITERAL)
+ return token->content.literal;
+ return NULL;
+}
+
+TrackerVariable *
+tracker_token_get_variable (TrackerToken *token)
+{
+ if (token->type == TOKEN_TYPE_VARIABLE)
+ return token->content.var;
+ return NULL;
+}
+
+const gchar *
+tracker_token_get_parameter (TrackerToken *token)
+{
+ if (token->type == TOKEN_TYPE_PARAMETER)
+ return token->content.parameter;
+ return NULL;
+}
+
+const gchar *
+tracker_token_get_idstring (TrackerToken *token)
+{
+ if (token->type == TOKEN_TYPE_LITERAL)
+ return token->content.literal;
+ else if (token->type == TOKEN_TYPE_VARIABLE)
+ return token->content.var->sql_expression;
+ else
+ return NULL;
+}
+
+/* Solution */
+TrackerSolution *
+tracker_solution_new (guint n_cols)
+{
+ TrackerSolution *solution;
+
+ solution = g_new0 (TrackerSolution, 1);
+ solution->n_cols = n_cols;
+ solution->columns = g_ptr_array_new_with_free_func (g_free);
+ solution->values = g_ptr_array_new_with_free_func (g_free);
+ solution->solution_index = -1;
+
+ return solution;
+}
+
+void
+tracker_solution_add_column_name (TrackerSolution *solution,
+ const gchar *name)
+{
+ g_ptr_array_add (solution->columns, g_strdup (name));
+}
+
+void
+tracker_solution_add_value (TrackerSolution *solution,
+ const gchar *str)
+{
+ g_ptr_array_add (solution->values, g_strdup (str));
+}
+
+gboolean
+tracker_solution_next (TrackerSolution *solution)
+{
+ solution->solution_index++;
+ return solution->solution_index * solution->n_cols < solution->values->len;
+}
+
+void
+tracker_solution_rewind (TrackerSolution *solution)
+{
+ solution->solution_index = -1;
+}
+
+void
+tracker_solution_free (TrackerSolution *solution)
+{
+ g_ptr_array_unref (solution->columns);
+ g_ptr_array_unref (solution->values);
+ g_free (solution);
+}
+
+GHashTable *
+tracker_solution_get_bindings (TrackerSolution *solution)
+{
+ GHashTable *ht;
+ gint i;
+
+ ht = g_hash_table_new (g_str_hash, g_str_equal);
+
+ for (i = 0; i < solution->columns->len; i++) {
+ gint values_pos = solution->solution_index * solution->n_cols + i;
+ gchar *name, *value;
+
+ if (values_pos >= solution->values->len)
+ break;
+
+ name = g_ptr_array_index (solution->columns, i);
+ value = g_ptr_array_index (solution->values, values_pos);
+ g_hash_table_insert (ht, name, value);
+ }
+
+ return ht;
+}
+
+/* Data binding */
+G_DEFINE_ABSTRACT_TYPE (TrackerBinding, tracker_binding, G_TYPE_OBJECT)
+
+static void
+tracker_binding_finalize (GObject *object)
+{
+ TrackerBinding *binding = TRACKER_BINDING (object);
+
+ g_free (binding->sql_db_column_name);
+ g_free (binding->sql_expression);
+
+ G_OBJECT_CLASS (tracker_binding_parent_class)->finalize (object);
+}
+
+static void
+tracker_binding_class_init (TrackerBindingClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tracker_binding_finalize;
+}
+
+static void
+tracker_binding_init (TrackerBinding *binding)
+{
+}
+
+TrackerDataTable *
+tracker_binding_get_table (TrackerBinding *binding)
+{
+ return binding->table;
+}
+
+void
+tracker_binding_set_db_column_name (TrackerBinding *binding,
+ const gchar *column_name)
+{
+ g_free (binding->sql_db_column_name);
+ binding->sql_db_column_name = g_strdup (column_name);
+}
+
+void
+tracker_binding_set_sql_expression (TrackerBinding *binding,
+ const gchar *sql_expression)
+{
+ g_free (binding->sql_expression);
+ binding->sql_expression = g_strdup (sql_expression);
+}
+
+const gchar *
+tracker_binding_get_sql_expression (TrackerBinding *binding)
+{
+ if (!binding->sql_expression && binding->table) {
+ binding->sql_expression = g_strdup_printf ("\"%s\".\"%s\"",
+ binding->table->sql_query_tablename,
+ binding->sql_db_column_name);
+ }
+
+ return binding->sql_expression;
+}
+
+gchar *
+tracker_binding_get_extra_sql_expression (TrackerBinding *binding,
+ const gchar *suffix)
+{
+ return g_strdup_printf ("\"%s\".\"%s:%s\"",
+ binding->table->sql_query_tablename,
+ binding->sql_db_column_name,
+ suffix);
+}
+
+void
+tracker_binding_set_data_type (TrackerBinding *binding,
+ TrackerPropertyType property_type)
+{
+ binding->data_type = property_type;
+}
+
+/* Literal binding */
+G_DEFINE_TYPE (TrackerLiteralBinding, tracker_literal_binding, TRACKER_TYPE_BINDING)
+
+static void
+tracker_literal_binding_finalize (GObject *object)
+{
+ TrackerLiteralBinding *binding = TRACKER_LITERAL_BINDING (object);
+
+ g_free (binding->literal);
+
+ G_OBJECT_CLASS (tracker_literal_binding_parent_class)->finalize (object);
+}
+
+static void
+tracker_literal_binding_class_init (TrackerLiteralBindingClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tracker_literal_binding_finalize;
+}
+
+static void
+tracker_literal_binding_init (TrackerLiteralBinding *binding)
+{
+}
+
+TrackerBinding *
+tracker_literal_binding_new (const gchar *literal,
+ TrackerDataTable *table)
+{
+ TrackerBinding *binding;
+
+ binding = g_object_new (TRACKER_TYPE_LITERAL_BINDING, NULL);
+ binding->table = table;
+ TRACKER_LITERAL_BINDING (binding)->literal = g_strdup (literal);
+
+ return binding;
+}
+
+/* Parameter binding */
+G_DEFINE_TYPE (TrackerParameterBinding, tracker_parameter_binding, TRACKER_TYPE_LITERAL_BINDING)
+
+static void
+tracker_parameter_binding_finalize (GObject *object)
+{
+ TrackerParameterBinding *binding = TRACKER_PARAMETER_BINDING (object);
+
+ g_free (binding->name);
+
+ G_OBJECT_CLASS (tracker_parameter_binding_parent_class)->finalize (object);
+}
+
+static void
+tracker_parameter_binding_class_init (TrackerParameterBindingClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tracker_parameter_binding_finalize;
+}
+
+static void
+tracker_parameter_binding_init (TrackerParameterBinding *binding)
+{
+}
+
+TrackerBinding *
+tracker_parameter_binding_new (const gchar *name,
+ TrackerDataTable *table)
+{
+ TrackerBinding *binding;
+
+ binding = g_object_new (TRACKER_TYPE_PARAMETER_BINDING, NULL);
+ binding->table = table;
+ TRACKER_PARAMETER_BINDING (binding)->name = g_strdup (name);
+
+ return binding;
+}
+
+/* Variable binding */
+G_DEFINE_TYPE (TrackerVariableBinding, tracker_variable_binding, TRACKER_TYPE_BINDING)
+
+static void
+tracker_variable_binding_class_init (TrackerVariableBindingClass *klass)
+{
+}
+
+static void
+tracker_variable_binding_init (TrackerVariableBinding *binding)
+{
+}
+
+TrackerBinding *
+tracker_variable_binding_new (TrackerVariable *variable,
+ TrackerClass *type,
+ TrackerDataTable *table)
+{
+ TrackerBinding *binding;
+
+ binding = g_object_new (TRACKER_TYPE_VARIABLE_BINDING, NULL);
+ binding->table = table;
+ TRACKER_VARIABLE_BINDING (binding)->type = type;
+ TRACKER_VARIABLE_BINDING (binding)->variable = variable;
+
+ return binding;
+}
+
+void
+tracker_variable_binding_set_nullable (TrackerVariableBinding *binding,
+ gboolean nullable)
+{
+ binding->nullable = !!nullable;
+}
+
+gboolean
+tracker_variable_binding_get_nullable (TrackerVariableBinding *binding)
+{
+ return binding->nullable;
+}
+
+TrackerVariable *
+tracker_variable_binding_get_variable (TrackerVariableBinding *binding)
+{
+ return binding->variable;
+}
+
+TrackerClass *
+tracker_variable_binding_get_class (TrackerVariableBinding *binding)
+{
+ return binding->type;
+}
+
+/* Context */
+G_DEFINE_TYPE (TrackerContext, tracker_context, G_TYPE_INITIALLY_UNOWNED)
+
+static void
+tracker_context_finalize (GObject *object)
+{
+ TrackerContext *context = (TrackerContext *) object;
+
+ while (context->children) {
+ g_object_unref (context->children->data);
+ context->children = g_list_delete_link (context->children,
+ context->children);
+ }
+
+ if (context->variable_set)
+ g_hash_table_unref (context->variable_set);
+
+ G_OBJECT_CLASS (tracker_context_parent_class)->finalize (object);
+}
+
+static void
+tracker_context_class_init (TrackerContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tracker_context_finalize;
+}
+
+static void
+tracker_context_init (TrackerContext *context)
+{
+ context->variable_set = g_hash_table_new (tracker_variable_hash,
+ tracker_variable_equal);
+}
+
+TrackerContext *
+tracker_context_new (void)
+{
+ return g_object_new (TRACKER_TYPE_CONTEXT, NULL);
+}
+
+void
+tracker_context_set_parent (TrackerContext *context,
+ TrackerContext *parent)
+{
+ g_assert (context->parent == NULL);
+
+ context->parent = parent;
+ parent->children = g_list_append (parent->children,
+ g_object_ref_sink (context));
+}
+
+TrackerContext *
+tracker_context_get_parent (TrackerContext *context)
+{
+ return context->parent;
+}
+
+void
+tracker_context_add_variable_ref (TrackerContext *context,
+ TrackerVariable *variable)
+{
+ g_hash_table_add (context->variable_set, variable);
+}
+
+gboolean
+tracker_context_lookup_variable_ref (TrackerContext *context,
+ TrackerVariable *variable)
+{
+ return g_hash_table_lookup (context->variable_set, variable) != NULL;
+}
+
+void
+tracker_context_propagate_variables (TrackerContext *context)
+{
+ GHashTableIter iter;
+ gpointer key;
+
+ g_assert (context->parent != NULL);
+ g_hash_table_iter_init (&iter, context->variable_set);
+
+ while (g_hash_table_iter_next (&iter, &key, NULL))
+ g_hash_table_add (context->parent->variable_set, key);
+}
+
+/* Select context */
+G_DEFINE_TYPE (TrackerSelectContext, tracker_select_context, TRACKER_TYPE_CONTEXT)
+
+static void
+tracker_select_context_finalize (GObject *object)
+{
+ TrackerSelectContext *context = TRACKER_SELECT_CONTEXT (object);
+
+ g_clear_pointer (&context->variables, g_hash_table_unref);
+ g_clear_pointer (&context->predicate_variables, g_hash_table_unref);
+ g_clear_pointer (&context->generated_variables, g_ptr_array_unref);
+ g_clear_pointer (&context->literal_bindings, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (tracker_select_context_parent_class)->finalize (object);
+}
+
+static void
+tracker_select_context_class_init (TrackerSelectContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tracker_select_context_finalize;
+}
+
+static void
+tracker_select_context_init (TrackerSelectContext *context)
+{
+}
+
+TrackerContext *
+tracker_select_context_new (void)
+{
+ return g_object_new (TRACKER_TYPE_SELECT_CONTEXT, NULL);
+}
+
+TrackerVariable *
+tracker_select_context_lookup_variable (TrackerSelectContext *context,
+ const gchar *name)
+{
+ if (!context->variables)
+ return NULL;
+ return g_hash_table_lookup (context->variables, name);
+}
+
+TrackerVariable *
+tracker_select_context_ensure_variable (TrackerSelectContext *context,
+ const gchar *name)
+{
+ TrackerVariable *variable;
+
+ /* All variables are reserved to the root context */
+ g_assert (TRACKER_CONTEXT (context)->parent == NULL);
+
+ if (!context->variables) {
+ context->variables =
+ g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+ (GDestroyNotify) tracker_variable_free);
+ }
+
+ variable = g_hash_table_lookup (context->variables, name);
+
+ if (!variable) {
+ variable = tracker_variable_new ("v", name);
+ g_hash_table_insert (context->variables, variable->name, variable);
+ }
+
+ return variable;
+}
+
+TrackerVariable *
+tracker_select_context_add_generated_variable (TrackerSelectContext *context)
+{
+ TrackerVariable *variable;
+ gchar *name;
+
+ /* All variables are reserved to the root context */
+ g_assert (TRACKER_CONTEXT (context)->parent == NULL);
+
+ if (!context->generated_variables) {
+ context->generated_variables =
+ g_ptr_array_new_with_free_func ((GDestroyNotify) tracker_variable_free);
+ }
+
+ name = g_strdup_printf ("%d", context->generated_variables->len + 1);
+ variable = tracker_variable_new ("g", name);
+ g_free (name);
+
+ g_ptr_array_add (context->generated_variables, variable);
+
+ return variable;
+}
+
+TrackerPredicateVariable *
+tracker_select_context_lookup_predicate_variable (TrackerSelectContext *context,
+ TrackerVariable *variable)
+{
+ if (!context->predicate_variables)
+ return NULL;
+ return g_hash_table_lookup (context->predicate_variables, variable);
+}
+
+void
+tracker_select_context_add_predicate_variable (TrackerSelectContext *context,
+ TrackerVariable *variable,
+ TrackerPredicateVariable *pred_var)
+{
+ if (!context->predicate_variables) {
+ context->predicate_variables =
+ g_hash_table_new_full (tracker_variable_hash,
+ tracker_variable_equal, NULL,
+ (GDestroyNotify) tracker_predicate_variable_free);
+ }
+
+ g_hash_table_insert (context->predicate_variables, variable, pred_var);
+}
+
+void
+tracker_select_context_add_literal_binding (TrackerSelectContext *context,
+ TrackerLiteralBinding *binding)
+{
+ /* Literal bindings are reserved to the root context */
+ g_assert (TRACKER_CONTEXT (context)->parent == NULL);
+
+ if (!context->literal_bindings)
+ context->literal_bindings = g_ptr_array_new_with_free_func (g_object_unref);
+
+ g_ptr_array_add (context->literal_bindings, g_object_ref (binding));
+}
+
+guint
+tracker_select_context_get_literal_binding_index (TrackerSelectContext *context,
+ TrackerLiteralBinding *binding)
+{
+ guint i;
+
+ for (i = 0; i < context->literal_bindings->len; i++) {
+ if (binding == g_ptr_array_index (context->literal_bindings, i))
+ return i;
+ }
+
+ g_assert_not_reached ();
+ return -1;
+}
+
+/* Triple context */
+G_DEFINE_TYPE (TrackerTripleContext, tracker_triple_context, TRACKER_TYPE_CONTEXT)
+
+static void
+tracker_triple_context_finalize (GObject *object)
+{
+ TrackerTripleContext *context = TRACKER_TRIPLE_CONTEXT (object);
+
+ g_ptr_array_unref (context->sql_tables);
+ g_ptr_array_unref (context->literal_bindings);
+ g_hash_table_unref (context->variable_bindings);
+
+ G_OBJECT_CLASS (tracker_triple_context_parent_class)->finalize (object);
+}
+
+static void
+tracker_triple_context_class_init (TrackerTripleContextClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tracker_triple_context_finalize;
+}
+
+static void
+tracker_triple_context_init (TrackerTripleContext *context)
+{
+ context->sql_tables = g_ptr_array_new_with_free_func ((GDestroyNotify) tracker_data_table_free);
+ context->literal_bindings = g_ptr_array_new_with_free_func (g_object_unref);
+ context->variable_bindings =
+ g_hash_table_new_full (tracker_variable_hash,
+ tracker_variable_equal, NULL,
+ (GDestroyNotify) g_ptr_array_unref);
+}
+
+TrackerContext *
+tracker_triple_context_new (void)
+{
+ return g_object_new (TRACKER_TYPE_TRIPLE_CONTEXT, NULL);
+}
+
+TrackerDataTable *
+tracker_triple_context_lookup_table (TrackerTripleContext *context,
+ const gchar *subject,
+ const gchar *tablename)
+{
+ TrackerDataTable *table = NULL;
+ guint i;
+
+ for (i = 0; i < context->sql_tables->len; i++) {
+ TrackerDataTable *table;
+
+ table = g_ptr_array_index (context->sql_tables, i);
+
+ if (g_strcmp0 (table->subject, subject) == 0 &&
+ g_strcmp0 (table->sql_db_tablename, tablename) == 0)
+ return table;
+ }
+
+ return table;
+}
+
+TrackerDataTable *
+tracker_triple_context_add_table (TrackerTripleContext *context,
+ const gchar *subject,
+ const gchar *tablename)
+{
+ TrackerDataTable *table;
+
+ table = tracker_data_table_new (tablename, subject, ++context->table_counter);
+ g_ptr_array_add (context->sql_tables, table);
+
+ return table;
+}
+
+void
+tracker_triple_context_add_literal_binding (TrackerTripleContext *context,
+ TrackerLiteralBinding *binding)
+{
+ g_ptr_array_add (context->literal_bindings, g_object_ref (binding));
+}
+
+GPtrArray *
+tracker_triple_context_lookup_variable_binding_list (TrackerTripleContext *context,
+ TrackerVariable *variable)
+{
+ return g_hash_table_lookup (context->variable_bindings, variable);
+}
+
+GPtrArray *
+tracker_triple_context_get_variable_binding_list (TrackerTripleContext *context,
+ TrackerVariable *variable)
+{
+ GPtrArray *binding_list = NULL;
+
+ binding_list = g_hash_table_lookup (context->variable_bindings, variable);
+
+ if (!binding_list) {
+ TrackerContext *current_context = (TrackerContext *) context;
+ TrackerContext *parent_context;
+
+ binding_list = g_ptr_array_new_with_free_func (g_object_unref);
+ g_hash_table_insert (context->variable_bindings, variable, binding_list);
+
+ if (tracker_variable_has_bindings (variable)) {
+ /* might be in scalar subquery: check variables of outer queries */
+ while (current_context) {
+ parent_context = tracker_context_get_parent (current_context);
+
+ /* only allow access to variables of immediate parent context of the subquery
+ * allowing access to other variables leads to invalid SQL or wrong results
+ */
+ if (TRACKER_IS_SELECT_CONTEXT (current_context) &&
+ tracker_context_get_parent (current_context) &&
+ g_hash_table_lookup (parent_context->variable_set, variable)) {
+ TrackerVariableBinding *sample;
+ TrackerBinding *binding;
+
+ sample = tracker_variable_get_sample_binding (variable);
+ binding = tracker_variable_binding_new (variable, sample->type,
+ tracker_binding_get_table (TRACKER_BINDING (sample)));
+ tracker_binding_set_sql_expression (binding,
+ tracker_variable_get_sql_expression (variable));
+ tracker_binding_set_data_type (binding,
+ TRACKER_BINDING (sample)->data_type);
+ g_ptr_array_add (binding_list, binding);
+ break;
+ }
+
+ current_context = parent_context;
+ }
+ }
+ }
+
+ return binding_list;
+}
+
+void
+tracker_triple_context_add_variable_binding (TrackerTripleContext *context,
+ TrackerVariable *variable,
+ TrackerVariableBinding *binding)
+{
+ GPtrArray *binding_list;
+
+ binding_list = tracker_triple_context_get_variable_binding_list (context,
+ variable);
+ g_ptr_array_add (binding_list, g_object_ref (binding));
+}
diff --git a/src/libtracker-data/tracker-sparql-types.h b/src/libtracker-data/tracker-sparql-types.h
new file mode 100644
index 000000000..1df06764c
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql-types.h
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+
+#ifndef __TRACKER_SPARQL_TYPES_H__
+#define __TRACKER_SPARQL_TYPES_H__
+
+#include "tracker-ontologies.h"
+
+#define TRACKER_TYPE_BINDING (tracker_binding_get_type ())
+#define TRACKER_BINDING(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_BINDING, TrackerBinding))
+#define TRACKER_IS_BINDING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_BINDING))
+
+#define TRACKER_TYPE_LITERAL_BINDING (tracker_literal_binding_get_type ())
+#define TRACKER_LITERAL_BINDING(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_LITERAL_BINDING, TrackerLiteralBinding))
+#define TRACKER_IS_LITERAL_BINDING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_LITERAL_BINDING))
+
+#define TRACKER_TYPE_PARAMETER_BINDING (tracker_parameter_binding_get_type ())
+#define TRACKER_PARAMETER_BINDING(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_PARAMETER_BINDING, TrackerParameterBinding))
+#define TRACKER_IS_PARAMETER_BINDING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_PARAMETER_BINDING))
+
+#define TRACKER_TYPE_VARIABLE_BINDING (tracker_variable_binding_get_type ())
+#define TRACKER_VARIABLE_BINDING(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_VARIABLE_BINDING, TrackerVariableBinding))
+#define TRACKER_IS_VARIABLE_BINDING(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_VARIABLE_BINDING))
+
+#define TRACKER_TYPE_CONTEXT (tracker_context_get_type ())
+#define TRACKER_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_CONTEXT, TrackerContext))
+#define TRACKER_IS_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_CONTEXT))
+
+#define TRACKER_TYPE_SELECT_CONTEXT (tracker_select_context_get_type ())
+#define TRACKER_SELECT_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_SELECT_CONTEXT, TrackerSelectContext))
+#define TRACKER_IS_SELECT_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_SELECT_CONTEXT))
+
+#define TRACKER_TYPE_TRIPLE_CONTEXT (tracker_triple_context_get_type ())
+#define TRACKER_TRIPLE_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_TRIPLE_CONTEXT, TrackerTripleContext))
+#define TRACKER_IS_TRIPLE_CONTEXT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_TRIPLE_CONTEXT))
+
+typedef struct _TrackerBinding TrackerBinding;
+typedef struct _TrackerBindingClass TrackerBindingClass;
+typedef struct _TrackerLiteralBinding TrackerLiteralBinding;
+typedef struct _TrackerLiteralBindingClass TrackerLiteralBindingClass;
+typedef struct _TrackerParameterBinding TrackerParameterBinding;
+typedef struct _TrackerParameterBindingClass TrackerParameterBindingClass;
+typedef struct _TrackerVariableBinding TrackerVariableBinding;
+typedef struct _TrackerVariableBindingClass TrackerVariableBindingClass;
+typedef struct _TrackerContext TrackerContext;
+typedef struct _TrackerContextClass TrackerContextClass;
+typedef struct _TrackerSelectContext TrackerSelectContext;
+typedef struct _TrackerSelectContextClass TrackerSelectContextClass;
+typedef struct _TrackerTripleContext TrackerTripleContext;
+typedef struct _TrackerTripleContextClass TrackerTripleContextClass;
+
+typedef struct _TrackerDataTable TrackerDataTable;
+typedef struct _TrackerVariable TrackerVariable;
+typedef struct _TrackerToken TrackerToken;
+typedef struct _TrackerSolution TrackerSolution;
+typedef struct _TrackerPredicateVariable TrackerPredicateVariable;
+
+struct _TrackerDataTable {
+ gchar *subject; /* Subject this table is pulled from */
+ gchar *sql_db_tablename; /* as in db schema */
+ gchar *sql_query_tablename; /* temp. name, generated */
+ TrackerPredicateVariable *predicate_variable;
+};
+
+struct _TrackerBinding {
+ GObject parent_instance;
+ TrackerPropertyType data_type;
+ TrackerDataTable *table;
+ gchar *sql_db_column_name;
+ gchar *sql_expression;
+};
+
+struct _TrackerBindingClass {
+ GObjectClass parent_class;
+};
+
+/* Represents a mapping of a SPARQL literal to a SQL table and column */
+struct _TrackerLiteralBinding {
+ TrackerBinding parent_instance;
+ gchar *literal;
+};
+
+struct _TrackerLiteralBindingClass {
+ TrackerBindingClass parent_class;
+};
+
+/* Represents a mapping of a SPARQL parameter variable to a user-provided value */
+struct _TrackerParameterBinding {
+ TrackerLiteralBinding parent_instance;
+ gchar *name;
+};
+
+struct _TrackerParameterBindingClass {
+ TrackerLiteralBindingClass parent_class;
+};
+
+/* Represents a mapping of a SPARQL variable to a SQL table and column */
+struct _TrackerVariableBinding {
+ TrackerBinding parent_instance;
+ TrackerVariable *variable;
+ TrackerClass *type;
+ guint nullable : 1;
+};
+
+struct _TrackerVariableBindingClass {
+ TrackerBindingClass parent_class;
+};
+
+struct _TrackerVariable {
+ gchar *name;
+ gchar *sql_expression;
+ TrackerVariableBinding *binding;
+};
+
+struct _TrackerToken {
+ guint type;
+ union {
+ gchar *literal;
+ gchar *parameter;
+ TrackerVariable *var;
+ } content;
+};
+
+struct _TrackerPredicateVariable {
+ gchar *subject;
+ gchar *object;
+ TrackerClass *domain;
+
+ guint return_graph : 1;
+};
+
+struct _TrackerSolution {
+ GPtrArray *columns;
+ GPtrArray *values;
+ int solution_index;
+ int n_cols;
+};
+
+struct _TrackerContext {
+ GInitiallyUnowned parent_instance;
+ TrackerContext *parent;
+ GList *children;
+
+ /* Variables used in this context, these will be owned by the
+ * root select context.
+ */
+ GHashTable *variable_set;
+};
+
+struct _TrackerContextClass {
+ GInitiallyUnownedClass parent_class;
+};
+
+struct _TrackerSelectContext {
+ TrackerContext parent_instance;
+
+ /* Variables used as predicates */
+ GHashTable *predicate_variables; /* TrackerVariable -> TrackerPredicateVariable */
+
+ /* All variables declared from this context. All these TrackerVariables
+ * are shared with children contexts. Only the root context has contents
+ * here.
+ */
+ GHashTable *variables; /* string -> TrackerVariable */
+
+ GPtrArray *generated_variables;
+
+ /* SPARQL literals. Content is TrackerLiteralBinding */
+ GPtrArray *literal_bindings;
+
+ /* Counter for sqlite3_stmt query bindings */
+ gint binding_counter;
+
+ /* Type to propagate upwards */
+ TrackerPropertyType type;
+};
+
+struct _TrackerSelectContextClass {
+ TrackerContextClass parent_class;
+};
+
+struct _TrackerTripleContext {
+ TrackerContext parent_instance;
+
+ /* Data tables pulled by the bindings below */
+ GPtrArray *sql_tables;
+
+ /* SPARQL literals. Content is TrackerLiteralBinding */
+ GPtrArray *literal_bindings;
+
+ /* SPARQL variables. */
+ GHashTable *variable_bindings; /* TrackerVariable -> GPtrArray(TrackerVariableBinding) */
+
+ /* Counter for disambiguating table names in queries */
+ gint table_counter;
+};
+
+struct _TrackerTripleContextClass {
+ TrackerContextClass parent_class;
+};
+
+/* Data table */
+void tracker_data_table_set_predicate_variable (TrackerDataTable *table,
+ TrackerPredicateVariable *variable);
+
+/* Binding */
+GType tracker_binding_get_type (void) G_GNUC_CONST;
+TrackerDataTable * tracker_binding_get_table (TrackerBinding *binding);
+
+void tracker_binding_set_db_column_name (TrackerBinding *binding,
+ const gchar *column_name);
+
+void tracker_binding_set_sql_expression (TrackerBinding *binding,
+ const gchar *sql_expression);
+
+void tracker_binding_set_data_type (TrackerBinding *binding,
+ TrackerPropertyType type);
+
+const gchar * tracker_binding_get_sql_expression (TrackerBinding *binding);
+gchar * tracker_binding_get_extra_sql_expression (TrackerBinding *binding,
+ const gchar *suffix);
+
+/* Literal binding */
+GType tracker_literal_binding_get_type (void) G_GNUC_CONST;
+TrackerBinding * tracker_literal_binding_new (const gchar *literal,
+ TrackerDataTable *table);
+
+/* Parameter binding */
+GType tracker_parameter_binding_get_type (void) G_GNUC_CONST;
+TrackerBinding * tracker_parameter_binding_new (const gchar *name,
+ TrackerDataTable *table);
+
+/* Variable binding */
+GType tracker_variable_binding_get_type (void) G_GNUC_CONST;
+TrackerBinding * tracker_variable_binding_new (TrackerVariable *variable,
+ TrackerClass *class,
+ TrackerDataTable *table);
+void tracker_variable_binding_set_nullable (TrackerVariableBinding *binding,
+ gboolean nullable);
+gboolean tracker_variable_binding_get_nullable (TrackerVariableBinding *binding);
+TrackerVariable * tracker_variable_binding_get_variable (TrackerVariableBinding *binding);
+
+/* Variable */
+void tracker_variable_set_sql_expression (TrackerVariable *variable,
+ const gchar *sql_expression);
+const gchar * tracker_variable_get_sql_expression (TrackerVariable *variable);
+gchar * tracker_variable_get_extra_sql_expression (TrackerVariable *variable,
+ const gchar *suffix);
+
+gboolean tracker_variable_has_bindings (TrackerVariable *variable);
+void tracker_variable_set_sample_binding (TrackerVariable *variable,
+ TrackerVariableBinding *binding);
+TrackerVariableBinding * tracker_variable_get_sample_binding (TrackerVariable *variable);
+
+/* Token */
+void tracker_token_literal_init (TrackerToken *token,
+ const gchar *literal);
+void tracker_token_variable_init (TrackerToken *token,
+ TrackerVariable *variable);
+void tracker_token_parameter_init (TrackerToken *token,
+ const gchar *pameter);
+void tracker_token_unset (TrackerToken *token);
+
+gboolean tracker_token_is_empty (TrackerToken *token);
+const gchar * tracker_token_get_literal (TrackerToken *token);
+TrackerVariable * tracker_token_get_variable (TrackerToken *token);
+const gchar * tracker_token_get_idstring (TrackerToken *token);
+const gchar * tracker_token_get_parameter (TrackerToken *token);
+
+/* Predicate variable */
+TrackerPredicateVariable *tracker_predicate_variable_new (void);
+
+void tracker_predicate_variable_set_domain (TrackerPredicateVariable *pred_var,
+ TrackerClass *domain);
+void tracker_predicate_variable_set_triple_details (TrackerPredicateVariable *pred_var,
+ const gchar *subject,
+ const gchar *object,
+ gboolean return_graph);
+
+/* Solution */
+TrackerSolution * tracker_solution_new (guint n_cols);
+void tracker_solution_free (TrackerSolution *solution);
+gboolean tracker_solution_next (TrackerSolution *solution);
+void tracker_solution_rewind (TrackerSolution *solution);
+
+void tracker_solution_add_column_name (TrackerSolution *solution,
+ const gchar *str);
+void tracker_solution_add_value (TrackerSolution *solution,
+ const gchar *str);
+GHashTable * tracker_solution_get_bindings (TrackerSolution *solution);
+
+
+/* Context */
+GType tracker_context_get_type (void) G_GNUC_CONST;
+TrackerContext * tracker_context_new (void);
+void tracker_context_set_parent (TrackerContext *context,
+ TrackerContext *parent);
+TrackerContext * tracker_context_get_parent (TrackerContext *context);
+
+void tracker_context_propagate_variables (TrackerContext *context);
+void tracker_context_add_variable_ref (TrackerContext *context,
+ TrackerVariable *variable);
+gboolean tracker_context_lookup_variable_ref (TrackerContext *context,
+ TrackerVariable *variable);
+
+/* Select context */
+GType tracker_select_context_get_type (void) G_GNUC_CONST;
+TrackerContext * tracker_select_context_new (void);
+TrackerVariable * tracker_select_context_lookup_variable (TrackerSelectContext *context,
+ const gchar *name);
+TrackerVariable * tracker_select_context_ensure_variable (TrackerSelectContext *context,
+ const gchar *name);
+TrackerVariable * tracker_select_context_add_generated_variable (TrackerSelectContext *context);
+
+TrackerPredicateVariable * tracker_select_context_lookup_predicate_variable (TrackerSelectContext *context,
+ TrackerVariable *variable);
+void tracker_select_context_add_predicate_variable (TrackerSelectContext *context,
+ TrackerVariable *variable,
+ TrackerPredicateVariable *pred_var);
+void tracker_select_context_add_literal_binding (TrackerSelectContext *context,
+ TrackerLiteralBinding *binding);
+guint tracker_select_context_get_literal_binding_index (TrackerSelectContext *context,
+ TrackerLiteralBinding *binding);
+
+/* Triple context */
+GType tracker_triple_context_get_type (void) G_GNUC_CONST;
+TrackerContext * tracker_triple_context_new (void);
+
+TrackerDataTable * tracker_triple_context_lookup_table (TrackerTripleContext *context,
+ const gchar *subject,
+ const gchar *table);
+TrackerDataTable * tracker_triple_context_add_table (TrackerTripleContext *context,
+ const gchar *subject,
+ const gchar *table);
+void tracker_triple_context_add_literal_binding (TrackerTripleContext *context,
+ TrackerLiteralBinding *binding);
+void tracker_triple_context_add_variable_binding (TrackerTripleContext *context,
+ TrackerVariable *variable,
+ TrackerVariableBinding *binding);
+GPtrArray * tracker_triple_context_lookup_variable_binding_list (TrackerTripleContext *context,
+ TrackerVariable *variable);
+GPtrArray * tracker_triple_context_get_variable_binding_list (TrackerTripleContext *context,
+ TrackerVariable *variable);
+
+#endif /* __TRACKER_SPARQL_TYPES_H__ */
diff --git a/src/libtracker-data/tracker-sparql.c b/src/libtracker-data/tracker-sparql.c
new file mode 100644
index 000000000..faac603ad
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql.c
@@ -0,0 +1,6633 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+
+#include "config.h"
+
+#include <glib-object.h>
+#include "tracker-data-query.h"
+#include "tracker-string-builder.h"
+#include "tracker-sparql.h"
+#include "tracker-sparql-types.h"
+#include "tracker-sparql-parser.h"
+#include "tracker-sparql-grammar.h"
+#include "tracker-collation.h"
+#include "tracker-db-interface-sqlite.h"
+
+#define TRACKER_NS "http://www.tracker-project.org/ontologies/tracker#"
+#define RDF_NS "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define RDFS_NS "http://www.w3.org/2000/01/rdf-schema#"
+#define FTS_NS "http://www.tracker-project.org/ontologies/fts#"
+#define XSD_NS "http://www.w3.org/2001/XMLSchema#"
+#define FN_NS "http://www.w3.org/2005/xpath-functions#"
+
+/* FIXME: This should be dependent on SQLITE_LIMIT_VARIABLE_NUMBER */
+#define MAX_VARIABLES 999
+
+enum {
+ TIME_FORMAT_SECONDS,
+ TIME_FORMAT_MINUTES,
+ TIME_FORMAT_HOURS
+};
+
+static inline gboolean _call_rule_func (TrackerSparql *sparql,
+ TrackerGrammarNamedRule rule,
+ GError **error);
+static gboolean handle_function_call (TrackerSparql *sparql,
+ GError **error);
+static gboolean helper_translate_date (TrackerSparql *sparql,
+ const gchar *format,
+ GError **error);
+static gboolean helper_translate_time (TrackerSparql *sparql,
+ guint format,
+ GError **error);
+static TrackerDBStatement * prepare_query (TrackerDBInterface *iface,
+ TrackerStringBuilder *str,
+ GPtrArray *literals,
+ GHashTable *parameters,
+ gboolean cached,
+ GError **error);
+static inline TrackerVariable * _ensure_variable (TrackerSparql *sparql,
+ const gchar *name);
+
+#define _raise(v,s,sub) \
+ G_STMT_START { \
+ g_set_error (error, TRACKER_SPARQL_ERROR, \
+ TRACKER_SPARQL_ERROR_##v, \
+ s " '%s'", sub); \
+ return FALSE; \
+ } G_STMT_END
+
+#define _unimplemented(s) _raise(UNSUPPORTED, "Unsupported syntax", s)
+
+/* Added for control flow simplicity. All processing will be stopped
+ * whenever any rule sets an error and returns FALSE.
+ */
+#define _call_rule(c,r,e) \
+ G_STMT_START { \
+ if (!_call_rule_func(c, r, e)) \
+ return FALSE; \
+ } G_STMT_END
+
+typedef gboolean (* RuleTranslationFunc) (TrackerSparql *sparql,
+ GError **error);
+
+enum
+{
+ TRACKER_SPARQL_TYPE_SELECT,
+ TRACKER_SPARQL_TYPE_DELETE,
+ TRACKER_SPARQL_TYPE_INSERT,
+ TRACKER_SPARQL_TYPE_UPDATE,
+};
+
+struct _TrackerSparql
+{
+ GObject parent_instance;
+ TrackerDataManager *data_manager;
+ gchar *sparql;
+
+ TrackerNodeTree *tree;
+ GError *parser_error;
+
+ TrackerContext *context;
+ TrackerStringBuilder *sql;
+
+ GHashTable *prefix_map;
+ GList *filter_clauses;
+
+ GPtrArray *var_names;
+ GArray *var_types;
+
+ GVariantBuilder *blank_nodes;
+ GHashTable *solution_var_map;
+
+ gboolean silent;
+ gboolean cacheable;
+
+ GHashTable *parameters;
+
+ struct {
+ TrackerContext *context;
+ TrackerContext *select_context;
+ TrackerStringBuilder *sql;
+ TrackerParserNode *node;
+ TrackerParserNode *prev_node;
+ TrackerParserNode *object_list;
+
+ TrackerToken graph;
+ TrackerToken subject;
+ TrackerToken predicate;
+ TrackerToken object;
+
+ TrackerToken *token;
+
+ GHashTable *blank_node_map;
+
+ const gchar *expression_list_separator;
+ TrackerPropertyType expression_type;
+ guint type;
+ } current_state;
+};
+
+G_DEFINE_TYPE (TrackerSparql, tracker_sparql, G_TYPE_OBJECT)
+
+static void
+tracker_sparql_finalize (GObject *object)
+{
+ TrackerSparql *sparql = TRACKER_SPARQL (object);
+
+ g_object_unref (sparql->data_manager);
+ g_hash_table_destroy (sparql->prefix_map);
+ g_hash_table_destroy (sparql->parameters);
+
+ if (sparql->sql)
+ tracker_string_builder_free (sparql->sql);
+ if (sparql->tree)
+ tracker_node_tree_free (sparql->tree);
+
+ g_clear_object (&sparql->context);
+
+ /* Unset all possible current state (eg. after error) */
+ tracker_token_unset (&sparql->current_state.graph);
+ tracker_token_unset (&sparql->current_state.subject);
+ tracker_token_unset (&sparql->current_state.predicate);
+ tracker_token_unset (&sparql->current_state.object);
+
+ g_ptr_array_unref (sparql->var_names);
+ g_array_unref (sparql->var_types);
+
+ if (sparql->blank_nodes)
+ g_variant_builder_unref (sparql->blank_nodes);
+
+ g_free (sparql->sparql);
+
+ G_OBJECT_CLASS (tracker_sparql_parent_class)->finalize (object);
+}
+
+static inline void
+tracker_sparql_push_context (TrackerSparql *sparql,
+ TrackerContext *context)
+{
+ if (sparql->current_state.context)
+ tracker_context_set_parent (context, sparql->current_state.context);
+ sparql->current_state.context = context;
+}
+
+static inline void
+tracker_sparql_pop_context (TrackerSparql *sparql,
+ gboolean propagate_variables)
+{
+ TrackerContext *parent;
+
+ g_assert (sparql->current_state.context);
+
+ parent = tracker_context_get_parent (sparql->current_state.context);
+
+ if (parent && propagate_variables)
+ tracker_context_propagate_variables (sparql->current_state.context);
+
+ sparql->current_state.context = parent;
+}
+
+static inline TrackerStringBuilder *
+tracker_sparql_swap_builder (TrackerSparql *sparql,
+ TrackerStringBuilder *string)
+{
+ TrackerStringBuilder *old;
+
+ old = sparql->current_state.sql;
+ sparql->current_state.sql = string;
+
+ return old;
+}
+
+static inline const gchar *
+tracker_sparql_swap_current_expression_list_separator (TrackerSparql *sparql,
+ const gchar *sep)
+{
+ const gchar *old;
+
+ old = sparql->current_state.expression_list_separator;
+ sparql->current_state.expression_list_separator = sep;
+
+ return old;
+}
+
+static inline gchar *
+tracker_sparql_expand_prefix (TrackerSparql *sparql,
+ const gchar *term)
+{
+ const gchar *sep;
+ gchar *ns, *expanded_ns;
+
+ sep = strchr (term, ':');
+
+ if (sep) {
+ ns = g_strndup (term, sep - term);
+ sep++;
+ } else {
+ ns = g_strdup (term);
+ }
+
+ expanded_ns = g_hash_table_lookup (sparql->prefix_map, ns);
+
+ if (!expanded_ns && g_strcmp0 (ns, "fn") == 0)
+ expanded_ns = FN_NS;
+
+ if (!expanded_ns) {
+ TrackerOntologies *ontologies;
+ TrackerNamespace **namespaces;
+ guint n_namespaces, i;
+
+ ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+ namespaces = tracker_ontologies_get_namespaces (ontologies, &n_namespaces);
+
+ for (i = 0; i < n_namespaces; i++) {
+ if (!g_str_equal (ns, tracker_namespace_get_prefix (namespaces[i])))
+ continue;
+
+ expanded_ns = g_strdup (tracker_namespace_get_uri (namespaces[i]));
+ g_hash_table_insert (sparql->prefix_map, g_strdup (ns), expanded_ns);
+ }
+
+ if (!expanded_ns)
+ return NULL;
+ }
+
+ g_free (ns);
+
+ if (sep) {
+ return g_strdup_printf ("%s%s", expanded_ns, sep);
+ } else {
+ return g_strdup (expanded_ns);
+ }
+}
+
+static inline void
+tracker_sparql_iter_next (TrackerSparql *sparql)
+{
+ sparql->current_state.prev_node = sparql->current_state.node;
+ sparql->current_state.node =
+ tracker_sparql_parser_tree_find_next (sparql->current_state.node, FALSE);
+}
+
+static inline gboolean
+_check_in_rule (TrackerSparql *sparql,
+ TrackerGrammarNamedRule named_rule)
+{
+ TrackerParserNode *node = sparql->current_state.node;
+ const TrackerGrammarRule *rule;
+
+ g_assert (named_rule < N_NAMED_RULES);
+
+ if (!node)
+ return FALSE;
+
+ rule = tracker_parser_node_get_rule (node);
+
+ return tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, named_rule);
+}
+
+static inline TrackerGrammarNamedRule
+_current_rule (TrackerSparql *sparql)
+{
+ TrackerParserNode *parser_node = sparql->current_state.node;
+ const TrackerGrammarRule *rule;
+
+ if (!parser_node)
+ return -1;
+ rule = tracker_parser_node_get_rule (parser_node);
+ if (rule->type != RULE_TYPE_RULE)
+ return -1;
+
+ return rule->data.rule;
+}
+
+static inline gboolean
+_accept (TrackerSparql *sparql,
+ TrackerGrammarRuleType type,
+ guint value)
+{
+ TrackerParserNode *parser_node = sparql->current_state.node;
+ const TrackerGrammarRule *rule;
+
+ if (!parser_node)
+ return FALSE;
+
+ rule = tracker_parser_node_get_rule (parser_node);
+
+ if (tracker_grammar_rule_is_a (rule, type, value)) {
+ tracker_sparql_iter_next (sparql);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static inline void
+_expect (TrackerSparql *sparql,
+ TrackerGrammarRuleType type,
+ guint value)
+{
+ if (!_accept (sparql, type, value)) {
+ TrackerParserNode *parser_node = sparql->current_state.node;
+ const TrackerGrammarRule *rule = NULL;
+
+ if (parser_node)
+ rule = tracker_parser_node_get_rule (parser_node);
+
+ if (type == RULE_TYPE_LITERAL) {
+ if (rule) {
+ g_error ("Parser expects literal '%s'. Got rule %d, value %d(%s)", literals[value],
+ rule->type, rule->data.literal, rule->string ? rule->string : "Unknown");
+ } else {
+ g_error ("Parser expects literal '%s'. Got EOF", literals[value]);
+ }
+ } else {
+ if (rule) {
+ g_error ("Parser expects rule %d (%d). Got rule %d, value %d(%s)", type, value,
+ rule->type, rule->data.literal, rule->string ? rule->string : "Unknown");
+ } else {
+ g_error ("Parser expects rule %d (%d). Got EOF", type, value);
+ }
+ }
+ }
+}
+
+static inline void
+_step (TrackerSparql *sparql)
+{
+ tracker_sparql_iter_next (sparql);
+}
+
+static inline void
+_prepend_string (TrackerSparql *sparql,
+ const gchar *str)
+{
+ tracker_string_builder_prepend (sparql->current_state.sql, str, -1);
+}
+
+static inline TrackerStringBuilder *
+_prepend_placeholder (TrackerSparql *sparql)
+{
+ return tracker_string_builder_prepend_placeholder (sparql->current_state.sql);
+}
+
+static inline void
+_append_string (TrackerSparql *sparql,
+ const gchar *str)
+{
+ tracker_string_builder_append (sparql->current_state.sql, str, -1);
+}
+
+static inline void
+_append_string_printf (TrackerSparql *sparql,
+ const gchar *format,
+ ...)
+{
+ va_list varargs;
+
+ va_start (varargs, format);
+ tracker_string_builder_append_valist (sparql->current_state.sql, format, varargs);
+ va_end (varargs);
+}
+
+static inline TrackerStringBuilder *
+_append_placeholder (TrackerSparql *sparql)
+{
+ return tracker_string_builder_append_placeholder (sparql->current_state.sql);
+}
+
+static inline gchar *
+_escape_sql_string (const gchar *str)
+{
+ int i, j, len;
+ gchar *copy;
+
+ len = strlen (str);
+ copy = g_new (char, (len * 2) + 1);
+ i = j = 0;
+
+ while (i < len) {
+ if (str[i] == '\'') {
+ copy[j] = '\'';
+ j++;
+ }
+
+ copy[j] = str[i];
+ i++;
+ j++;
+ }
+
+ copy[j] = '\0';
+
+ return copy;
+}
+
+static inline void
+_append_literal_sql (TrackerSparql *sparql,
+ TrackerLiteralBinding *binding)
+{
+ guint idx;
+
+ idx = tracker_select_context_get_literal_binding_index (TRACKER_SELECT_CONTEXT (sparql->context),
+ binding);
+
+ if (idx >= MAX_VARIABLES) {
+ sparql->cacheable = FALSE;
+ }
+
+ if (TRACKER_BINDING (binding)->data_type == TRACKER_PROPERTY_TYPE_RESOURCE) {
+ _append_string_printf (sparql,
+ "COALESCE ((SELECT ID FROM Resource WHERE Uri = ");
+ }
+
+ if (!sparql->cacheable) {
+ gchar *escaped, *full_str;
+
+ switch (TRACKER_BINDING (binding)->data_type) {
+ case TRACKER_PROPERTY_TYPE_DATE:
+ full_str = g_strdup_printf ("%sT00:00:00Z", binding->literal);
+ escaped = _escape_sql_string (full_str);
+ _append_string (sparql, escaped);
+ g_free (escaped);
+ g_free (full_str);
+ break;
+ case TRACKER_PROPERTY_TYPE_DATETIME:
+ case TRACKER_PROPERTY_TYPE_STRING:
+ case TRACKER_PROPERTY_TYPE_RESOURCE:
+ escaped = _escape_sql_string (binding->literal);
+ _append_string (sparql, escaped);
+ g_free (escaped);
+ break;
+ case TRACKER_PROPERTY_TYPE_BOOLEAN:
+ if (g_str_equal (binding->literal, "1") ||
+ g_ascii_strcasecmp (binding->literal, "true") == 0) {
+ _append_string (sparql, "1");
+ } else {
+ _append_string (sparql, "0");
+ }
+ break;
+ case TRACKER_PROPERTY_TYPE_UNKNOWN:
+ case TRACKER_PROPERTY_TYPE_INTEGER:
+ case TRACKER_PROPERTY_TYPE_DOUBLE:
+ _append_string (sparql, binding->literal);
+ break;
+ }
+ } else {
+ _append_string_printf (sparql, "?%d ", idx + 1);
+ }
+
+ if (TRACKER_BINDING (binding)->data_type == TRACKER_PROPERTY_TYPE_RESOURCE)
+ _append_string_printf (sparql, "), 0) ");
+ if (TRACKER_BINDING (binding)->data_type == TRACKER_PROPERTY_TYPE_STRING)
+ _append_string (sparql, "COLLATE " TRACKER_COLLATION_NAME " ");
+}
+
+static void
+_append_variable_sql (TrackerSparql *sparql,
+ TrackerVariable *variable)
+{
+ TrackerBinding *binding;
+
+ binding = TRACKER_BINDING (tracker_variable_get_sample_binding (variable));
+
+ if (binding &&
+ binding->data_type == TRACKER_PROPERTY_TYPE_DATETIME) {
+ TrackerVariable *local_time;
+ gchar *name;
+
+ name = g_strdup_printf ("%s:local", variable->name);
+ local_time = _ensure_variable (sparql, name);
+ g_free (name);
+
+ _append_string_printf (sparql, "%s ",
+ tracker_variable_get_sql_expression (local_time));
+ } else {
+ _append_string_printf (sparql, "%s ",
+ tracker_variable_get_sql_expression (variable));
+ }
+}
+
+static inline gchar *
+_extract_node_string (TrackerParserNode *node,
+ TrackerSparql *sparql)
+{
+ const TrackerGrammarRule *rule;
+ gchar *str = NULL;
+ gssize start, end;
+
+ if (!tracker_parser_node_get_extents (node, &start, &end))
+ return NULL;
+
+ rule = tracker_parser_node_get_rule (node);
+
+ if (rule->type == RULE_TYPE_LITERAL) {
+ switch (rule->data.literal) {
+ case LITERAL_A:
+ str = g_strdup (RDF_NS "type");
+ break;
+ default:
+ str = g_strndup (&sparql->sparql[start], end - start);
+ break;
+ }
+ } else if (rule->type == RULE_TYPE_TERMINAL) {
+ const gchar *terminal_start, *terminal_end;
+ gssize add_start = 0, subtract_end = 0;
+ gboolean compress = FALSE;
+
+ terminal_start = &sparql->sparql[start];
+ terminal_end = &sparql->sparql[end];
+ rule = tracker_parser_node_get_rule (node);
+
+ switch (rule->data.terminal) {
+ case TERMINAL_TYPE_VAR1:
+ case TERMINAL_TYPE_VAR2:
+ case TERMINAL_TYPE_PARAMETERIZED_VAR:
+ add_start = 1;
+ break;
+ case TERMINAL_TYPE_STRING_LITERAL1:
+ case TERMINAL_TYPE_STRING_LITERAL2:
+ add_start = subtract_end = 1;
+ compress = TRUE;
+ break;
+ case TERMINAL_TYPE_STRING_LITERAL_LONG1:
+ case TERMINAL_TYPE_STRING_LITERAL_LONG2:
+ add_start = subtract_end = 3;
+ compress = TRUE;
+ break;
+ case TERMINAL_TYPE_IRIREF:
+ add_start = subtract_end = 1;
+ break;
+ case TERMINAL_TYPE_BLANK_NODE_LABEL:
+ add_start = 2;
+ break;
+ case TERMINAL_TYPE_PNAME_NS:
+ subtract_end = 1;
+ /* Fall through */
+ case TERMINAL_TYPE_PNAME_LN: {
+ gchar *unexpanded;
+
+ unexpanded = g_strndup (terminal_start + add_start,
+ terminal_end - terminal_start - subtract_end);
+ str = tracker_sparql_expand_prefix (sparql, unexpanded);
+ g_free (unexpanded);
+ break;
+ }
+ default:
+ break;
+ }
+
+ terminal_start += add_start;
+ terminal_end -= subtract_end;
+ g_assert (terminal_end >= terminal_start);
+
+ if (!str)
+ str = g_strndup (terminal_start, terminal_end - terminal_start);
+
+ if (compress) {
+ gchar *tmp = str;
+
+ str = g_strcompress (tmp);
+ g_free (tmp);
+ }
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return str;
+}
+
+static inline gchar *
+_dup_last_string (TrackerSparql *sparql)
+{
+ return _extract_node_string (sparql->current_state.prev_node, sparql);
+}
+
+static inline TrackerBinding *
+_convert_terminal (TrackerSparql *sparql)
+{
+ const TrackerGrammarRule *rule;
+ TrackerBinding *binding;
+ gchar *str;
+
+ str = _dup_last_string (sparql);
+ g_assert (str != NULL);
+
+ rule = tracker_parser_node_get_rule (sparql->current_state.prev_node);
+
+ if (tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+ binding = tracker_parameter_binding_new (str, NULL);
+ } else {
+ binding = tracker_literal_binding_new (str, NULL);
+ tracker_binding_set_data_type (binding, sparql->current_state.expression_type);
+ }
+
+ g_free (str);
+
+ return binding;
+}
+
+static void
+_add_binding (TrackerSparql *sparql,
+ TrackerBinding *binding)
+{
+ TrackerTripleContext *context;
+
+ context = TRACKER_TRIPLE_CONTEXT (sparql->current_state.context);
+
+ if (TRACKER_IS_LITERAL_BINDING (binding)) {
+ tracker_triple_context_add_literal_binding (context,
+ TRACKER_LITERAL_BINDING (binding));
+
+ /* Also add on the root SelectContext right away */
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (binding));
+ } else if (TRACKER_IS_VARIABLE_BINDING (binding)) {
+ TrackerVariableBinding *variable_binding = TRACKER_VARIABLE_BINDING (binding);
+ TrackerVariable *variable;
+
+ variable = tracker_variable_binding_get_variable (variable_binding);
+ tracker_triple_context_add_variable_binding (context,
+ variable,
+ variable_binding);
+
+ if (!tracker_variable_has_bindings (variable))
+ tracker_variable_set_sample_binding (variable, variable_binding);
+ } else {
+ g_assert_not_reached ();
+ }
+}
+
+static inline TrackerVariable *
+_ensure_variable (TrackerSparql *sparql,
+ const gchar *name)
+{
+ TrackerVariable *var;
+
+ var = tracker_select_context_ensure_variable (TRACKER_SELECT_CONTEXT (sparql->context),
+ name);
+ tracker_context_add_variable_ref (sparql->current_state.context, var);
+
+ return var;
+}
+
+static inline TrackerVariable *
+_extract_node_variable (TrackerParserNode *node,
+ TrackerSparql *sparql)
+{
+ const TrackerGrammarRule *rule = tracker_parser_node_get_rule (node);
+ TrackerVariable *variable = NULL;
+ gchar *str;
+
+ if (!tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1) &&
+ !tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2))
+ return NULL;
+
+ str = _extract_node_string (node, sparql);
+ variable = _ensure_variable (sparql, str);
+ g_free (str);
+
+ return variable;
+}
+
+static inline TrackerVariable *
+_last_node_variable (TrackerSparql *sparql)
+{
+ return _extract_node_variable (sparql->current_state.prev_node, sparql);
+}
+
+static void
+_init_token (TrackerToken *token,
+ TrackerParserNode *node,
+ TrackerSparql *sparql)
+{
+ const TrackerGrammarRule *rule = tracker_parser_node_get_rule (node);
+ TrackerVariable *var;
+ gchar *str;
+
+ str = _extract_node_string (node, sparql);
+
+ if (tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1) ||
+ tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2)) {
+ if (sparql->current_state.type == TRACKER_SPARQL_TYPE_SELECT) {
+ var = _ensure_variable (sparql, str);
+ tracker_token_variable_init (token, var);
+ } else {
+ const gchar *value;
+
+ value = g_hash_table_lookup (sparql->solution_var_map, str);
+ tracker_token_literal_init (token, value);
+ }
+ } else if (tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+ tracker_token_parameter_init (token, str);
+ } else {
+ tracker_token_literal_init (token, str);
+ }
+
+ g_free (str);
+}
+
+static inline gboolean
+_accept_token (TrackerParserNode **node,
+ TrackerGrammarRuleType type,
+ guint value,
+ TrackerParserNode **prev)
+{
+ const TrackerGrammarRule *rule;
+
+ g_assert (node != NULL && *node != NULL);
+ rule = tracker_parser_node_get_rule (*node);
+
+ if (!tracker_grammar_rule_is_a (rule, type, value))
+ return FALSE;
+
+ if (prev)
+ *prev = *node;
+
+ *node = tracker_sparql_parser_tree_find_next (*node, TRUE);
+ return TRUE;
+}
+
+static gboolean
+extract_fts_snippet_parameters (TrackerSparql *sparql,
+ TrackerParserNode *node,
+ gchar **match_start,
+ gchar **match_end,
+ gchar **ellipsis,
+ gchar **num_tokens,
+ GError **error)
+{
+ TrackerParserNode *val = NULL;
+
+ if (_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+ if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1, &val) ||
+ _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2, &val)) {
+ *match_start = _extract_node_string (val, sparql);
+ } else {
+ _raise (PARSE, "«Match start» argument expects string", "fts:snippet");
+ }
+
+ if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+ _raise (PARSE, "Both «Match start» and «Match end» arguments expected", "fts:snippet");
+ }
+
+ if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1, &val) ||
+ _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2, &val)) {
+ *match_end = _extract_node_string (val, sparql);
+ } else {
+ _raise (PARSE, "«Match end» argument expects string", "fts:snippet");
+ }
+ }
+
+ if (_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+ if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1, &val) ||
+ _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2, &val)) {
+ *ellipsis = _extract_node_string (val, sparql);
+ } else {
+ _raise (PARSE, "«Ellipsis» argument expects string", "fts:snippet");
+ }
+ }
+
+ if (_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_COMMA, NULL)) {
+ if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER, &val) ||
+ _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER_POSITIVE, &val)) {
+ *num_tokens = _extract_node_string (val, sparql);
+ } else {
+ _raise (PARSE, "«Num. tokens» argument expects integer", "fts:snippet");
+ }
+ }
+
+ if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS, NULL)) {
+ _raise (PARSE, "Unexpected number of parameters", "fts:snippet");
+ }
+
+ return TRUE;
+}
+
+static gboolean
+introspect_fts_snippet (TrackerSparql *sparql,
+ TrackerVariable *subject,
+ TrackerDataTable *table,
+ TrackerTripleContext *triple_context,
+ GError **error)
+{
+ TrackerParserNode *node = tracker_node_tree_get_root (sparql->tree);
+
+ for (node = tracker_sparql_parser_tree_find_first (node, TRUE);
+ node;
+ node = tracker_sparql_parser_tree_find_next (node, TRUE)) {
+ gchar *match_start = NULL, *match_end = NULL, *ellipsis = NULL, *num_tokens = NULL;
+ gchar *str, *var_name, *sql_expression;
+ const TrackerGrammarRule *rule;
+ TrackerBinding *binding;
+ TrackerVariable *var;
+
+ rule = tracker_parser_node_get_rule (node);
+ if (!tracker_grammar_rule_is_a (rule, RULE_TYPE_TERMINAL,
+ TERMINAL_TYPE_PNAME_LN))
+ continue;
+
+ str = _extract_node_string (node, sparql);
+
+ if (g_str_equal (str, FTS_NS "snippet")) {
+ g_free (str);
+ node = tracker_sparql_parser_tree_find_next (node, TRUE);
+ } else {
+ g_free (str);
+ continue;
+ }
+
+ if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS, NULL)) {
+ _raise (PARSE, "Expected open parens", "fts:snippet");
+ }
+
+ var = _extract_node_variable (node, sparql);
+ if (var != subject)
+ continue;
+
+ node = tracker_sparql_parser_tree_find_next (node, TRUE);
+
+ if (!extract_fts_snippet_parameters (sparql, node,
+ &match_start,
+ &match_end,
+ &ellipsis,
+ &num_tokens,
+ error)) {
+ g_free (match_start);
+ g_free (match_end);
+ g_free (ellipsis);
+ g_free (num_tokens);
+ return FALSE;
+ }
+
+ var_name = g_strdup_printf ("%s:ftsSnippet", subject->name);
+ var = _ensure_variable (sparql, var_name);
+ g_free (var_name);
+
+ sql_expression = g_strdup_printf ("snippet(\"%s\".\"fts5\", -1, '%s', '%s', '%s', %s)",
+ table->sql_query_tablename,
+ match_start ? match_start : "",
+ match_end ? match_end : "",
+ ellipsis ? ellipsis : "…",
+ num_tokens ? num_tokens : "5");
+
+ binding = tracker_variable_binding_new (var, NULL, NULL);
+ tracker_binding_set_sql_expression (binding, sql_expression);
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+
+ g_free (sql_expression);
+ g_free (match_start);
+ g_free (match_end);
+ g_free (ellipsis);
+ g_free (num_tokens);
+ break;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_add_quad (TrackerSparql *sparql,
+ TrackerToken *graph,
+ TrackerToken *subject,
+ TrackerToken *predicate,
+ TrackerToken *object,
+ GError **error)
+{
+ TrackerSelectContext *select_context;
+ TrackerTripleContext *triple_context;
+ TrackerOntologies *ontologies;
+ TrackerDataTable *table = NULL;
+ TrackerVariable *variable;
+ TrackerBinding *binding;
+ TrackerProperty *property = NULL;
+ TrackerClass *subject_type = NULL;
+ gboolean new_table = FALSE, is_fts = FALSE, is_rdf_type = FALSE;
+
+ select_context = TRACKER_SELECT_CONTEXT (sparql->current_state.select_context);
+ triple_context = TRACKER_TRIPLE_CONTEXT (sparql->current_state.context);
+ ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+
+ if (tracker_token_get_literal (predicate)) {
+ gboolean share_table = TRUE;
+ const gchar *db_table;
+
+ property = tracker_ontologies_get_property_by_uri (ontologies,
+ tracker_token_get_literal (predicate));
+
+ if (tracker_token_is_empty (graph) &&
+ !tracker_token_get_variable (object) &&
+ g_strcmp0 (tracker_token_get_literal (predicate), RDF_NS "type") == 0) {
+ /* rdf:type query */
+ subject_type = tracker_ontologies_get_class_by_uri (ontologies,
+ tracker_token_get_literal (object));
+ if (!subject_type) {
+ g_set_error (error, TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_UNKNOWN_CLASS,
+ "Unknown class '%s'",
+ tracker_token_get_literal (object));
+ return FALSE;
+ }
+
+ is_rdf_type = TRUE;
+ db_table = tracker_class_get_name (subject_type);
+ } else if (g_strcmp0 (tracker_token_get_literal (predicate), FTS_NS "match") == 0) {
+ db_table = "fts5";
+ share_table = FALSE;
+ is_fts = TRUE;
+ } else if (property != NULL) {
+ db_table = tracker_property_get_table_name (property);
+
+ if (tracker_token_get_variable (subject)) {
+ GPtrArray *binding_list;
+
+ variable = tracker_token_get_variable (subject);
+
+ if (!tracker_token_get_variable (object) &&
+ g_strcmp0 (tracker_token_get_literal (predicate), RDFS_NS "domain") == 0) {
+ TrackerPredicateVariable *pred_var;
+ TrackerClass *domain;
+
+ /* rdfs:domain */
+ domain = tracker_ontologies_get_class_by_uri (ontologies,
+ tracker_token_get_literal (object));
+ if (!domain) {
+ g_set_error (error, TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_UNKNOWN_CLASS,
+ "Unknown class '%s'",
+ tracker_token_get_literal (object));
+ return FALSE;
+ }
+
+ pred_var = tracker_select_context_lookup_predicate_variable (select_context,
+ variable);
+ if (!pred_var) {
+ pred_var = tracker_predicate_variable_new ();
+ tracker_select_context_add_predicate_variable (select_context,
+ variable,
+ pred_var);
+ }
+
+ tracker_predicate_variable_set_domain (pred_var, domain);
+ }
+
+ binding_list = tracker_triple_context_lookup_variable_binding_list (triple_context,
+ variable);
+ /* Domain specific index might be a possibility, let's check */
+ if (binding_list) {
+ TrackerClass *domain_index = NULL;
+ TrackerClass **classes;
+ gint i = 0, j;
+
+ classes = tracker_property_get_domain_indexes (property);
+
+ while (!domain_index && classes[i]) {
+ for (j = 0; j < binding_list->len; j++) {
+ TrackerVariableBinding *list_binding;
+
+ list_binding = g_ptr_array_index (binding_list, j);
+ if (list_binding->type != classes[i])
+ continue;
+
+ domain_index = classes[i];
+ break;
+ }
+
+ i++;
+ }
+
+ if (domain_index)
+ db_table = tracker_class_get_name (domain_index);
+ }
+ }
+
+ /* We can never share the table with multiple triples for
+ * multi value properties as a property may consist of multiple rows.
+ */
+ share_table = !tracker_property_get_multiple_values (property);
+
+ subject_type = tracker_property_get_domain (property);
+ } else if (property == NULL) {
+ g_set_error (error, TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_UNKNOWN_PROPERTY,
+ "Unknown property '%s'",
+ tracker_token_get_literal (predicate));
+ return FALSE;
+ }
+
+ if (share_table) {
+ table = tracker_triple_context_lookup_table (triple_context,
+ tracker_token_get_idstring (subject),
+ db_table);
+ }
+
+ if (!table) {
+ table = tracker_triple_context_add_table (triple_context,
+ tracker_token_get_idstring (subject),
+ db_table);
+ new_table = TRUE;
+ }
+ } else if (tracker_token_get_variable (predicate)) {
+ TrackerPredicateVariable *pred_var;
+
+ /* Variable in predicate */
+ variable = tracker_token_get_variable (predicate);
+ table = tracker_triple_context_add_table (triple_context,
+ variable->name, variable->name);
+ new_table = TRUE;
+
+ pred_var = tracker_select_context_lookup_predicate_variable (select_context,
+ variable);
+ if (!pred_var) {
+ pred_var = tracker_predicate_variable_new ();
+ tracker_predicate_variable_set_triple_details (pred_var,
+ tracker_token_get_literal (subject),
+ tracker_token_get_literal (object),
+ !tracker_token_is_empty (graph));
+
+ tracker_select_context_add_predicate_variable (select_context,
+ variable,
+ pred_var);
+ }
+
+ tracker_data_table_set_predicate_variable (table, pred_var);
+
+ /* Add to binding list */
+ binding = tracker_variable_binding_new (variable, NULL, table);
+ tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
+ tracker_binding_set_db_column_name (binding, "predicate");
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+ } else {
+ /* The parser disallows parameter predicates */
+ g_assert_not_reached ();
+ }
+
+ if (new_table) {
+ if (tracker_token_get_variable (subject)) {
+ variable = tracker_token_get_variable (subject);
+ binding = tracker_variable_binding_new (variable, subject_type, table);
+ } else if (tracker_token_get_literal (subject)) {
+ binding = tracker_literal_binding_new (tracker_token_get_literal (subject),
+ table);
+ } else if (tracker_token_get_parameter (subject)) {
+ binding = tracker_parameter_binding_new (tracker_token_get_parameter (subject),
+ table);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
+ tracker_binding_set_db_column_name (binding, is_fts ? "ROWID" : "ID");
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+ }
+
+ if (is_rdf_type) {
+ /* The type binding is already implicit in the data table */
+ return TRUE;
+ }
+
+ if (tracker_token_get_variable (object)) {
+ variable = tracker_token_get_variable (object);
+ binding = tracker_variable_binding_new (variable,
+ property ? tracker_property_get_range (property) : NULL,
+ table);
+
+ if (tracker_token_get_variable (predicate)) {
+ tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_STRING);
+ tracker_binding_set_db_column_name (binding, "object");
+ tracker_variable_binding_set_nullable (TRACKER_VARIABLE_BINDING (binding), TRUE);
+ } else {
+ g_assert (property != NULL);
+ tracker_binding_set_data_type (binding, tracker_property_get_data_type (property));
+ tracker_binding_set_db_column_name (binding, tracker_property_get_name (property));
+
+ if (!tracker_property_get_multiple_values (property)) {
+ /* For single value properties, row may have NULL
+ * in any column except the ID column
+ */
+ tracker_variable_binding_set_nullable (TRACKER_VARIABLE_BINDING (binding), TRUE);
+ }
+
+ if (tracker_property_get_data_type (property) == TRACKER_PROPERTY_TYPE_DATETIME) {
+ gchar *date_var, *sql_expression, *local_date, *local_time;
+ TrackerBinding *local_time_binding;
+
+ /* Merge localDate/localTime into $var:local */
+ date_var = g_strdup_printf ("%s:local", variable->name);
+ variable = _ensure_variable (sparql, date_var);
+
+ local_date = tracker_binding_get_extra_sql_expression (binding, "localDate");
+ local_time = tracker_binding_get_extra_sql_expression (binding, "localTime");
+ sql_expression = g_strdup_printf ("((%s * 24 * 3600) + %s)",
+ local_date, local_time);
+
+ local_time_binding = tracker_variable_binding_new (variable, NULL, NULL);
+ tracker_binding_set_sql_expression (local_time_binding,
+ sql_expression);
+ _add_binding (sparql, local_time_binding);
+ g_object_unref (local_time_binding);
+
+ g_free (sql_expression);
+ g_free (local_date);
+ g_free (local_time);
+ g_free (date_var);
+ }
+ }
+
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+ } else if (is_fts) {
+ if (tracker_token_get_literal (object)) {
+ binding = tracker_literal_binding_new (tracker_token_get_literal (object), table);
+ } else if (tracker_token_get_parameter (object)) {
+ binding = tracker_parameter_binding_new (tracker_token_get_parameter (object), table);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ tracker_binding_set_db_column_name (binding, "fts5");
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+
+ if (tracker_token_get_variable (subject)) {
+ gchar *var_name, *sql_expression;
+ TrackerVariable *fts_var;
+
+ variable = tracker_token_get_variable (subject);
+
+ /* FTS rank */
+ var_name = g_strdup_printf ("%s:ftsRank", variable->name);
+ fts_var = _ensure_variable (sparql, var_name);
+ g_free (var_name);
+
+ binding = tracker_variable_binding_new (fts_var, NULL, table);
+ tracker_binding_set_db_column_name (binding, "rank");
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+
+ /* FTS offsets */
+ var_name = g_strdup_printf ("%s:ftsOffsets", variable->name);
+ fts_var = _ensure_variable (sparql, var_name);
+ g_free (var_name);
+
+ sql_expression = g_strdup_printf ("tracker_offsets(\"%s\".\"fts5\")",
+ table->sql_query_tablename);
+ binding = tracker_variable_binding_new (fts_var, NULL, NULL);
+ tracker_binding_set_sql_expression (binding, sql_expression);
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+ g_free (sql_expression);
+
+ /* FTS snippet */
+ if (!introspect_fts_snippet (sparql, variable,
+ table, triple_context, error)) {
+ return FALSE;
+ }
+ }
+ } else {
+ if (tracker_token_get_literal (object)) {
+ binding = tracker_literal_binding_new (tracker_token_get_literal (object), table);
+ } else if (tracker_token_get_parameter (object)) {
+ binding = tracker_parameter_binding_new (tracker_token_get_parameter (object), table);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ if (tracker_token_get_variable (predicate)) {
+ tracker_binding_set_db_column_name (binding, "object");
+ } else {
+ g_assert (property != NULL);
+ tracker_binding_set_data_type (binding, tracker_property_get_data_type (property));
+ tracker_binding_set_db_column_name (binding, tracker_property_get_name (property));
+ }
+
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+ }
+
+ if (!tracker_token_is_empty (graph)) {
+ if (tracker_token_get_variable (graph)) {
+ variable = tracker_token_get_variable (graph);
+ binding = tracker_variable_binding_new (variable, NULL, table);
+ tracker_variable_binding_set_nullable (TRACKER_VARIABLE_BINDING (binding), TRUE);
+ } else if (tracker_token_get_literal (graph)) {
+ binding = tracker_literal_binding_new (tracker_token_get_literal (graph), table);
+ } else if (tracker_token_get_parameter (graph)) {
+ binding = tracker_parameter_binding_new (tracker_token_get_parameter (graph), table);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
+
+ if (tracker_token_get_variable (predicate)) {
+ tracker_binding_set_db_column_name (binding, "graph");
+ } else {
+ gchar *column_name;
+
+ g_assert (property != NULL);
+ column_name = g_strdup_printf ("%s:graph", tracker_property_get_name (property));
+ tracker_binding_set_db_column_name (binding, column_name);
+ g_free (column_name);
+ }
+
+ _add_binding (sparql, binding);
+ g_object_unref (binding);
+ }
+
+ return TRUE;
+}
+
+static TrackerParserNode *
+_skip_rule (TrackerSparql *sparql,
+ guint named_rule)
+{
+ TrackerParserNode *current, *iter, *next = NULL;
+
+ g_assert (_check_in_rule (sparql, named_rule));
+ current = iter = sparql->current_state.node;
+
+ while (iter) {
+ next = (TrackerParserNode *) g_node_next_sibling ((GNode *) iter);
+ if (next) {
+ next = tracker_sparql_parser_tree_find_first (next, FALSE);
+ break;
+ }
+
+ iter = (TrackerParserNode *) ((GNode *) iter)->parent;
+ }
+
+ sparql->current_state.node = next;
+
+ return current;
+}
+
+static void
+convert_expression_to_string (TrackerSparql *sparql,
+ TrackerPropertyType type)
+{
+ switch (type) {
+ case TRACKER_PROPERTY_TYPE_STRING:
+ case TRACKER_PROPERTY_TYPE_INTEGER:
+ /* Nothing to convert. Do not use CAST to convert integers to
+ * strings as this breaks use of index when sorting by variable
+ * introduced in select expression
+ */
+ break;
+ case TRACKER_PROPERTY_TYPE_RESOURCE:
+ /* ID => Uri */
+ _prepend_string (sparql, "(SELECT Uri FROM Resource WHERE ID = ");
+ _append_string (sparql, ") ");
+ break;
+ case TRACKER_PROPERTY_TYPE_BOOLEAN:
+ _prepend_string (sparql, "CASE ");
+ _append_string (sparql, " WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE NULL END ");
+ break;
+ case TRACKER_PROPERTY_TYPE_DATE:
+ /* ISO 8601 format */
+ _prepend_string (sparql, "strftime (\"%Y-%m-%d\", ");
+ _append_string (sparql, ", \"unixepoch\") ");
+ break;
+ case TRACKER_PROPERTY_TYPE_DATETIME:
+ /* ISO 8601 format */
+ _prepend_string (sparql, "SparqlFormatTime (");
+ _append_string (sparql, ") ");
+ default:
+ /* Let sqlite convert the expression to string */
+ _prepend_string (sparql, "CAST (");
+ _append_string (sparql, " AS TEXT) ");
+ break;
+ }
+}
+
+static TrackerClass **
+lookup_resource_types (TrackerSparql *sparql,
+ const gchar *resource,
+ gint *n_types,
+ GError **error)
+{
+ TrackerOntologies *ontologies;
+ TrackerDBInterface *iface;
+ TrackerDBStatement *stmt;
+ TrackerDBCursor *cursor = NULL;
+ GError *inner_error = NULL;
+ GPtrArray *types;
+ gint resource_id;
+
+ if (n_types)
+ *n_types = 0;
+
+ ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+ iface = tracker_data_manager_get_writable_db_interface (sparql->data_manager);
+ resource_id = tracker_data_query_resource_id (sparql->data_manager,
+ iface, resource);
+
+ /* This is not an error condition, query might refer to an unknown resource */
+ if (resource_id <= 0)
+ return NULL;
+
+ stmt = tracker_db_interface_create_statement (iface, TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT, &inner_error,
+ "SELECT (SELECT Uri FROM Resource WHERE ID = \"rdf:type\") "
+ "FROM \"rdfs:Resource_rdf:type\" WHERE ID = ?");
+ if (!stmt) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ tracker_db_statement_bind_int (stmt, 0, resource_id);
+ cursor = tracker_db_statement_start_cursor (stmt, error);
+ g_object_unref (stmt);
+
+ if (!cursor) {
+ g_propagate_error (error, inner_error);
+ return NULL;
+ }
+
+ types = g_ptr_array_new ();
+
+ while (tracker_db_cursor_iter_next (cursor, NULL, &inner_error)) {
+ TrackerClass *type;
+
+ type = tracker_ontologies_get_class_by_uri (ontologies,
+ tracker_db_cursor_get_string (cursor, 0, NULL));
+ g_ptr_array_add (types, type);
+ }
+
+ g_object_unref (cursor);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ g_ptr_array_unref (types);
+ return NULL;
+ }
+
+ if (n_types)
+ *n_types = types->len;
+
+ return (TrackerClass **) g_ptr_array_free (types, FALSE);
+}
+
+static gboolean
+append_predicate_variable_query (TrackerSparql *sparql,
+ TrackerPredicateVariable *pred_var,
+ GError **error)
+{
+ TrackerOntologies *ontologies;
+ TrackerProperty **properties;
+ TrackerStringBuilder *str, *old;
+ TrackerClass **types;
+ TrackerBinding *binding = NULL;
+ gint n_properties, n_types, i, j;
+ GError *inner_error = NULL;
+ gboolean first = TRUE;
+
+ ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+ properties = tracker_ontologies_get_properties (ontologies, &n_properties);
+
+ if (pred_var->subject) {
+ /* <s> ?p ?o */
+ types = lookup_resource_types (sparql, pred_var->subject, &n_types, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ for (i = 0; i < n_types; i++) {
+ for (j = 0; j < n_properties; j++) {
+ if (types[i] != tracker_property_get_domain (properties[j]))
+ continue;
+
+ if (!first)
+ _append_string (sparql, "UNION ALL ");
+
+ first = FALSE;
+ _append_string_printf (sparql,
+ "SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') AS \"predicate\", ",
+ tracker_property_get_uri (properties[j]));
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+ _append_string_printf (sparql, "\"%s\" ", tracker_property_get_name (properties[j]));
+ convert_expression_to_string (sparql, tracker_property_get_data_type (properties[j]));
+ tracker_sparql_swap_builder (sparql, old);
+
+ _append_string (sparql, " AS \"object\" ");
+
+ if (pred_var->return_graph) {
+ _append_string_printf (sparql, ", \"%s:graph\" AS \"graph\" ",
+ tracker_property_get_name (properties[j]));
+ }
+
+ _append_string_printf (sparql, "FROM \"%s\" WHERE ID = ",
+ tracker_property_get_table_name (properties[j]));
+
+ if (!binding) {
+ binding = tracker_literal_binding_new (pred_var->subject, NULL);
+ tracker_binding_set_data_type (binding, TRACKER_PROPERTY_TYPE_RESOURCE);
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (binding));
+ }
+
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+ }
+ }
+
+ if (first) {
+ /* No match */
+ _append_string (sparql,
+ "SELECT NULL AS ID, NULL AS \"predicate\", NULL AS \"object\", NULL AS \"graph\"");
+ }
+
+ g_free (types);
+ } else if (pred_var->object) {
+ /* ?s ?p <o> */
+ types = lookup_resource_types (sparql, pred_var->object, &n_types, &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ for (i = 0; i < n_types; i++) {
+ for (j = 0; j < n_properties; j++) {
+ if (types[i] != tracker_property_get_range (properties[j]))
+ continue;
+
+ if (!first)
+ _append_string (sparql, "UNION ALL ");
+
+ first = FALSE;
+ _append_string_printf (sparql,
+ "SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') AS \"predicate\", ",
+ tracker_property_get_uri (properties[j]));
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+ _append_string_printf (sparql, "\"%s\" ", tracker_property_get_name (properties[j]));
+ convert_expression_to_string (sparql, tracker_property_get_data_type (properties[j]));
+ tracker_sparql_swap_builder (sparql, old);
+
+ _append_string (sparql, " AS \"object\" ");
+
+ if (pred_var->return_graph) {
+ _append_string_printf (sparql,
+ ", \"%s:graph\" AS \"graph\" ",
+ tracker_property_get_name (properties[j]));
+ }
+
+ _append_string_printf (sparql, " FROM \"%s\" ",
+ tracker_property_get_table_name (properties[j]));
+ }
+ }
+
+ if (first) {
+ /* No match */
+ _append_string (sparql,
+ "SELECT NULL AS ID, NULL AS \"predicate\", NULL AS \"object\", NULL AS \"graph\" ");
+ }
+
+ g_free (types);
+ } else if (pred_var->domain) {
+ /* Any subject, predicates limited to a specific domain */
+
+ for (j = 0; j < n_properties; j++) {
+ if (pred_var->domain != tracker_property_get_domain (properties[j]))
+ continue;
+
+ if (!first)
+ _append_string (sparql, "UNION ALL ");
+
+ first = FALSE;
+ _append_string_printf (sparql,
+ "SELECT ID, (SELECT ID FROM Resource WHERE Uri = '%s') AS \"predicate\", ",
+ tracker_property_get_uri (properties[j]));
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+ _append_string_printf (sparql, "\"%s\" ", tracker_property_get_name (properties[j]));
+ convert_expression_to_string (sparql, tracker_property_get_data_type (properties[j]));
+ tracker_sparql_swap_builder (sparql, old);
+
+ _append_string (sparql, " AS \"object\" ");
+
+ if (pred_var->return_graph) {
+ _append_string_printf (sparql,
+ ", \"%s:graph\" AS \"graph\" ",
+ tracker_property_get_name (properties[j]));
+ }
+
+ _append_string_printf (sparql, "FROM \"%s\" ",
+ tracker_property_get_table_name (properties[j]));
+ }
+ } else {
+ /* ?s ?p ?o */
+ _unimplemented ("Unrestricted predicate variables are not supported");
+ }
+
+ return TRUE;
+}
+
+static TrackerContext *
+_begin_triples_block (TrackerSparql *sparql)
+{
+ TrackerContext *context;
+
+ context = tracker_triple_context_new ();
+ tracker_sparql_push_context (sparql, context);
+
+ return context;
+}
+
+static gboolean
+_end_triples_block (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerTripleContext *triple_context;
+ TrackerStringBuilder *where_placeholder;
+ TrackerVariable *var;
+ TrackerContext *context;
+ GHashTableIter iter;
+ gboolean first = TRUE;
+ gint i;
+
+ context = sparql->current_state.context;
+ g_assert (TRACKER_IS_TRIPLE_CONTEXT (context));
+ triple_context = (TrackerTripleContext *) context;
+
+ /* Triple is empty */
+ if (triple_context->sql_tables->len == 0) {
+ tracker_sparql_pop_context (sparql, TRUE);
+ return TRUE;
+ }
+
+ _append_string (sparql, "SELECT ");
+ g_hash_table_iter_init (&iter, triple_context->variable_bindings);
+
+ /* Add select variables */
+ while (g_hash_table_iter_next (&iter, (gpointer *) &var, NULL)) {
+ TrackerBinding *binding;
+ GPtrArray *binding_list;
+
+ binding_list = tracker_triple_context_get_variable_binding_list (triple_context,
+ var);
+ if (!binding_list)
+ continue;
+
+ if (!first)
+ _append_string (sparql, ", ");
+
+ first = FALSE;
+ binding = g_ptr_array_index (binding_list, 0);
+ _append_string_printf (sparql, "%s AS %s ",
+ tracker_binding_get_sql_expression (binding),
+ tracker_variable_get_sql_expression (var));
+ }
+
+ if (first)
+ _append_string (sparql, "1 ");
+
+ _append_string (sparql, "FROM ");
+ first = TRUE;
+
+ /* Add tables */
+ for (i = 0; i < triple_context->sql_tables->len; i++) {
+ TrackerDataTable *table = g_ptr_array_index (triple_context->sql_tables, i);
+
+ if (!first)
+ _append_string (sparql, ", ");
+
+ if (table->predicate_variable) {
+ _append_string (sparql, "(");
+
+ if (!append_predicate_variable_query (sparql,
+ table->predicate_variable,
+ error))
+ return FALSE;
+
+ _append_string (sparql, ") ");
+ } else {
+ _append_string_printf (sparql, "\"%s\" ", table->sql_db_tablename);
+ }
+
+ _append_string_printf (sparql, "AS \"%s\" ", table->sql_query_tablename);
+ first = FALSE;
+ }
+
+ g_hash_table_iter_init (&iter, triple_context->variable_bindings);
+
+ where_placeholder = _append_placeholder (sparql);
+ first = TRUE;
+
+ /* Add variable bindings */
+ while (g_hash_table_iter_next (&iter, (gpointer *) &var, NULL)) {
+ GPtrArray *binding_list;
+ gboolean nullable = TRUE;
+ guint i;
+
+ binding_list = tracker_triple_context_lookup_variable_binding_list (triple_context,
+ var);
+ if (!binding_list)
+ continue;
+
+ for (i = 0; i < binding_list->len; i++) {
+ const gchar *expression1, *expression2;
+ TrackerBinding *binding1, *binding2;
+
+ binding1 = g_ptr_array_index (binding_list, i);
+ if (!tracker_variable_binding_get_nullable (TRACKER_VARIABLE_BINDING (binding1)))
+ nullable = FALSE;
+
+ if (i + 1 >= binding_list->len)
+ break;
+
+ if (!first)
+ _append_string (sparql, "AND ");
+
+ /* Concatenate each binding with the next */
+ binding2 = g_ptr_array_index (binding_list, i + 1);
+ expression1 = tracker_binding_get_sql_expression (binding1);
+ expression2 = tracker_binding_get_sql_expression (binding2);
+
+ if (binding1->data_type == TRACKER_PROPERTY_TYPE_STRING &&
+ binding2->data_type == TRACKER_PROPERTY_TYPE_RESOURCE) {
+ _append_string_printf (sparql,
+ "(SELECT ID FROM Resource WHERE Uri = %s) ",
+ expression1);
+ } else {
+ _append_string_printf (sparql, "%s ", expression1);
+ }
+
+ _append_string (sparql, "= ");
+
+ if (binding1->data_type == TRACKER_PROPERTY_TYPE_RESOURCE &&
+ binding2->data_type == TRACKER_PROPERTY_TYPE_STRING) {
+ _append_string_printf (sparql,
+ "(SELECT ID FROM Resource WHERE Uri = %s) ",
+ expression2);
+ } else {
+ _append_string_printf (sparql, "%s ", expression2);
+ }
+
+ if (!tracker_variable_binding_get_nullable (TRACKER_VARIABLE_BINDING (binding1)) ||
+ !tracker_variable_binding_get_nullable (TRACKER_VARIABLE_BINDING (binding2)))
+ nullable = FALSE;
+
+ first = FALSE;
+ }
+
+ if (nullable) {
+ if (!first)
+ _append_string (sparql, "AND ");
+ _append_string_printf (sparql, "%s IS NOT NULL ",
+ tracker_variable_get_sql_expression (var));
+ first = FALSE;
+ }
+ }
+
+ /* Add literal bindings */
+ for (i = 0; i < triple_context->literal_bindings->len; i++) {
+ TrackerBinding *binding;
+
+ if (!first)
+ _append_string (sparql, "AND ");
+
+ first = FALSE;
+ binding = g_ptr_array_index (triple_context->literal_bindings, i);
+ _append_string_printf (sparql, "%s = ", tracker_binding_get_sql_expression (binding));
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+ }
+
+ /* If we had any where clauses, prepend the 'WHERE' literal */
+ if (!first)
+ tracker_string_builder_append (where_placeholder, "WHERE ", -1);
+
+ tracker_sparql_pop_context (sparql, TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_Query (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* Query ::= Prologue
+ * ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery )
+ * ValuesClause
+ */
+ _call_rule (sparql, NAMED_RULE_Prologue, error);
+
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_SelectQuery:
+ case NAMED_RULE_AskQuery:
+ case NAMED_RULE_ConstructQuery:
+ case NAMED_RULE_DescribeQuery:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ _call_rule (sparql, NAMED_RULE_ValuesClause, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_Update (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Update ::= Prologue ( Update1 ( ';' Update )? )?
+ *
+ * TRACKER EXTENSION:
+ * ';' separator is made optional.
+ */
+ _call_rule (sparql, NAMED_RULE_Prologue, error);
+
+ if (_check_in_rule (sparql, NAMED_RULE_Update1)) {
+ if (sparql->blank_nodes)
+ g_variant_builder_open (sparql->blank_nodes, G_VARIANT_TYPE ("aa{ss}"));
+
+ _call_rule (sparql, NAMED_RULE_Update1, error);
+
+ if (sparql->blank_nodes)
+ g_variant_builder_close (sparql->blank_nodes);
+
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON);
+
+ if (_check_in_rule (sparql, NAMED_RULE_Update))
+ _call_rule (sparql, NAMED_RULE_Update, error);
+ }
+
+ return TRUE;
+}
+
+static void
+tracker_sparql_add_select_var (TrackerSparql *sparql,
+ const gchar *name,
+ TrackerPropertyType type)
+{
+ g_ptr_array_add (sparql->var_names, g_strdup (name));
+ g_array_append_val (sparql->var_types, type);
+}
+
+static gboolean
+handle_as (TrackerSparql *sparql,
+ TrackerPropertyType type,
+ GError **error)
+{
+ TrackerBinding *binding;
+ TrackerVariable *var;
+
+ _call_rule (sparql, NAMED_RULE_Var, error);
+ var = _last_node_variable (sparql);
+
+ binding = tracker_variable_binding_new (var, NULL, NULL);
+ tracker_binding_set_data_type (binding, type);
+ tracker_variable_set_sample_binding (var, TRACKER_VARIABLE_BINDING (binding));
+ _append_string_printf (sparql, "AS %s ",
+ tracker_variable_get_sql_expression (var));
+
+ if (sparql->current_state.select_context == sparql->context)
+ tracker_sparql_add_select_var (sparql, var->name, type);
+
+ return TRUE;
+}
+
+static gboolean
+translate_SelectClause (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerSelectContext *select_context;
+ TrackerStringBuilder *str, *old;
+ gboolean first = TRUE;
+
+ /* SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ | '*' )
+ *
+ * TRACKER EXTENSION:
+ * Variable set also accepts the following syntax:
+ * Expression ('AS' Var)?
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_SELECT);
+ _append_string (sparql, "SELECT ");
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT)) {
+ _append_string (sparql, "DISTINCT ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_REDUCED)) {
+ /* REDUCED is allowed to return the same amount of elements, so... *shrug* */
+ }
+
+ select_context = TRACKER_SELECT_CONTEXT (sparql->current_state.select_context);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GLOB)) {
+ TrackerVariable *var;
+ GHashTableIter iter;
+
+ g_hash_table_iter_init (&iter, select_context->variables);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &var)) {
+ if (!first)
+ _append_string (sparql, ", ");
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+
+ _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);
+
+ tracker_sparql_swap_builder (sparql, old);
+ first = FALSE;
+ }
+ } else {
+ do {
+ TrackerVariable *var;
+ TrackerBinding *binding;
+
+ if (_check_in_rule (sparql, NAMED_RULE_Var)) {
+ if (!first)
+ _append_string (sparql, ", ");
+
+ _call_rule (sparql, NAMED_RULE_Var, error);
+ var = _last_node_variable (sparql);
+
+ if (!tracker_variable_has_bindings (var)) {
+ _raise (PARSE, "Undefined variable", var->name);
+ }
+
+ binding = TRACKER_BINDING (tracker_variable_get_sample_binding (var));
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+
+ _append_string_printf (sparql, "%s ",
+ tracker_variable_get_sql_expression (var));
+
+ select_context->type = binding->data_type;
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_AS)) {
+ if (!handle_as (sparql, binding->data_type, error))
+ return FALSE;
+ } else if (sparql->current_state.select_context == sparql->context) {
+ convert_expression_to_string (sparql, binding->data_type);
+ tracker_sparql_add_select_var (sparql, var->name, binding->data_type);
+ }
+
+ tracker_sparql_swap_builder (sparql, old);
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS) ||
+ _check_in_rule (sparql, NAMED_RULE_Expression)) {
+ if (!first)
+ _append_string (sparql, ", ");
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ if (sparql->current_state.select_context == sparql->context) {
+ convert_expression_to_string (sparql, sparql->current_state.expression_type);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ }
+
+ select_context->type = sparql->current_state.expression_type;
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_AS)) {
+ if (!handle_as (sparql, sparql->current_state.expression_type, error))
+ return FALSE;
+ } else {
+ tracker_sparql_add_select_var (sparql, "", sparql->current_state.expression_type);
+ }
+
+ tracker_sparql_swap_builder (sparql, old);
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ } else {
+ break;
+ }
+
+ first = FALSE;
+ } while (TRUE);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_Prologue (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* Prologue ::= ( BaseDecl | PrefixDecl )*
+ */
+ rule = _current_rule (sparql);
+
+ while (rule == NAMED_RULE_BaseDecl || rule == NAMED_RULE_PrefixDecl) {
+ _call_rule (sparql, rule, error);
+ rule = _current_rule (sparql);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_BaseDecl (TrackerSparql *sparql,
+ GError **error)
+{
+ /* BaseDecl ::= 'BASE' IRIREF
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BASE);
+
+ /* FIXME: BASE is unimplemented, and we never raised an error */
+
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_IRIREF);
+
+ return TRUE;
+}
+
+static gboolean
+translate_PrefixDecl (TrackerSparql *sparql,
+ GError **error)
+{
+ gchar *ns, *uri;
+
+ /* PrefixDecl ::= 'PREFIX' PNAME_NS IRIREF
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_PREFIX);
+
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PNAME_NS);
+ ns = _dup_last_string (sparql);
+
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_IRIREF);
+ uri = _dup_last_string (sparql);
+
+ g_hash_table_insert (sparql->prefix_map, ns, uri);
+
+ return TRUE;
+}
+
+static gboolean
+_check_undefined_variables (TrackerSparql *sparql,
+ TrackerSelectContext *context,
+ GError **error)
+{
+ TrackerVariable *variable;
+ GHashTableIter iter;
+
+ if (!context->variables)
+ return TRUE;
+
+ g_hash_table_iter_init (&iter, context->variables);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer*) &variable)) {
+ if (!tracker_variable_has_bindings (variable)) {
+ _raise (PARSE, "Use of undefined variable", variable->name);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+_postprocess_rule (TrackerSparql *sparql,
+ TrackerParserNode *node,
+ TrackerStringBuilder *str,
+ GError **error)
+{
+ TrackerStringBuilder *old_str;
+ TrackerParserNode *old_node;
+ const TrackerGrammarRule *rule;
+
+ old_node = sparql->current_state.node;
+ sparql->current_state.node = node;
+ if (str)
+ old_str = tracker_sparql_swap_builder (sparql, str);
+
+ rule = tracker_parser_node_get_rule (node);
+ g_assert (rule->type == RULE_TYPE_RULE);
+ _call_rule (sparql, rule->data.rule, error);
+ sparql->current_state.node = old_node;
+
+ if (str)
+ tracker_sparql_swap_builder (sparql, old_str);
+
+ return TRUE;
+}
+
+static gboolean
+translate_SelectQuery (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerParserNode *select_clause;
+ TrackerStringBuilder *str;
+
+ /* SelectQuery ::= SelectClause DatasetClause* WhereClause SolutionModifier
+ */
+ sparql->context = g_object_ref_sink (tracker_select_context_new ());
+ sparql->current_state.select_context = sparql->context;
+ tracker_sparql_push_context (sparql, sparql->context);
+
+ /* Skip select clause here */
+ str = _append_placeholder (sparql);
+ select_clause = _skip_rule (sparql, NAMED_RULE_SelectClause);
+
+ while (_check_in_rule (sparql, NAMED_RULE_DatasetClause)) {
+ _call_rule (sparql, NAMED_RULE_DatasetClause, error);
+ }
+
+ _call_rule (sparql, NAMED_RULE_WhereClause, error);
+
+ if (!_check_undefined_variables (sparql, TRACKER_SELECT_CONTEXT (sparql->context), error))
+ return FALSE;
+
+ /* Now that we have all variable/binding information available,
+ * process the select clause.
+ */
+ if (!_postprocess_rule (sparql, select_clause, str, error))
+ return FALSE;
+
+ _call_rule (sparql, NAMED_RULE_SolutionModifier, error);
+
+ tracker_sparql_pop_context (sparql, FALSE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_SubSelect (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerContext *context, *prev;
+ TrackerStringBuilder *str;
+ TrackerParserNode *select_clause;
+
+ /* SubSelect ::= SelectClause WhereClause SolutionModifier ValuesClause
+ */
+ context = tracker_select_context_new ();
+ prev = sparql->current_state.select_context;
+ sparql->current_state.select_context = context;
+ tracker_sparql_push_context (sparql, context);
+
+ /* Skip select clause here */
+ str = _append_placeholder (sparql);
+ select_clause = _skip_rule (sparql, NAMED_RULE_SelectClause);
+
+ _call_rule (sparql, NAMED_RULE_WhereClause, error);
+
+ /* Now that we have all variable/binding information available,
+ * process the select clause.
+ */
+ if (!_postprocess_rule (sparql, select_clause, str, error))
+ return FALSE;
+
+ _call_rule (sparql, NAMED_RULE_SolutionModifier, error);
+ _call_rule (sparql, NAMED_RULE_ValuesClause, error);
+
+ sparql->current_state.expression_type = TRACKER_SELECT_CONTEXT (context)->type;
+ tracker_sparql_pop_context (sparql, FALSE);
+ sparql->current_state.select_context = prev;
+
+ return TRUE;
+}
+
+static gboolean
+translate_ConstructQuery (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("CONSTRUCT");
+}
+
+static gboolean
+translate_DescribeQuery (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("DESCRIBE");
+}
+
+static gboolean
+translate_AskQuery (TrackerSparql *sparql,
+ GError **error)
+{
+ /* AskQuery ::= 'ASK' DatasetClause* WhereClause SolutionModifier
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_ASK);
+
+ sparql->context = g_object_ref_sink (tracker_select_context_new ());
+ sparql->current_state.select_context = sparql->context;
+ tracker_sparql_push_context (sparql, sparql->context);
+
+ _append_string (sparql, "SELECT CASE EXISTS (SELECT 1 ");
+
+ while (_check_in_rule (sparql, NAMED_RULE_DatasetClause)) {
+ _call_rule (sparql, NAMED_RULE_DatasetClause, error);
+ }
+
+ _call_rule (sparql, NAMED_RULE_WhereClause, error);
+ _call_rule (sparql, NAMED_RULE_SolutionModifier, error);
+
+ tracker_sparql_pop_context (sparql, FALSE);
+
+ _append_string (sparql, ") WHEN 1 THEN 'true' WHEN 0 THEN 'false' ELSE NULL END");
+
+ return TRUE;
+}
+
+static gboolean
+translate_DatasetClause (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* DatasetClause ::= 'FROM' ( DefaultGraphClause | NamedGraphClause )
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_FROM);
+
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_DefaultGraphClause:
+ case NAMED_RULE_NamedGraphClause:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_DefaultGraphClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* DefaultGraphClause ::= SourceSelector
+ */
+ _call_rule (sparql, NAMED_RULE_SourceSelector, error);
+
+ /* FIXME: FROM <graph> is unimplemented, and we never raised an error */
+
+ return TRUE;
+}
+
+static gboolean
+translate_NamedGraphClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* NamedGraphClause ::= 'NAMED' SourceSelector
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_NAMED);
+ _call_rule (sparql, NAMED_RULE_SourceSelector, error);
+
+ /* FIXME: FROM NAMED <graph> is unimplemented, and we never raised an error */
+
+ return TRUE;
+}
+
+static gboolean
+translate_SourceSelector (TrackerSparql *sparql,
+ GError **error)
+{
+ /* SourceSelector ::= iri
+ */
+ _call_rule (sparql, NAMED_RULE_iri, error);
+ return TRUE;
+}
+
+static gboolean
+translate_WhereClause (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerStringBuilder *child, *old;
+
+ /* WhereClause ::= 'WHERE'? GroupGraphPattern
+ */
+ child = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, child);
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_WHERE);
+ _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+ if (!tracker_string_builder_is_empty (child)) {
+ _prepend_string (sparql, "FROM (");
+ _append_string (sparql, ") ");
+ }
+
+ tracker_sparql_swap_builder (sparql, old);
+
+ return TRUE;
+}
+
+static gboolean
+translate_SolutionModifier (TrackerSparql *sparql,
+ GError **error)
+{
+ /* SolutionModifier ::= GroupClause? HavingClause? OrderClause? LimitOffsetClauses?
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_GroupClause)) {
+ _call_rule (sparql, NAMED_RULE_GroupClause, error);
+ }
+
+ if (_check_in_rule (sparql, NAMED_RULE_HavingClause)) {
+ _call_rule (sparql, NAMED_RULE_HavingClause, error);
+ }
+
+ if (_check_in_rule (sparql, NAMED_RULE_OrderClause)) {
+ _call_rule (sparql, NAMED_RULE_OrderClause, error);
+ }
+
+ if (_check_in_rule (sparql, NAMED_RULE_LimitOffsetClauses)) {
+ _call_rule (sparql, NAMED_RULE_LimitOffsetClauses, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_GroupClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* GroupClause ::= 'GROUP' 'BY' GroupCondition+
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GROUP);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BY);
+ _append_string (sparql, "GROUP BY ");
+
+ while (_check_in_rule (sparql, NAMED_RULE_GroupCondition)) {
+ _call_rule (sparql, NAMED_RULE_GroupCondition, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_GroupCondition (TrackerSparql *sparql,
+ GError **error)
+{
+ /* GroupCondition ::= BuiltInCall | FunctionCall | '(' Expression ( 'AS' Var )? ')' | Var
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_AS)) {
+ _unimplemented ("AS in GROUP BY");
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ } else {
+ TrackerGrammarNamedRule rule;
+ TrackerVariable *variable;
+
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_Var:
+ _call_rule (sparql, rule, error);
+ variable = _last_node_variable (sparql);
+ _append_variable_sql (sparql, variable);
+ break;
+ case NAMED_RULE_BuiltInCall:
+ case NAMED_RULE_FunctionCall:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_HavingClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* HavingClause ::= 'HAVING' HavingCondition+
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_HAVING);
+ _append_string (sparql, "HAVING ");
+
+ while (_check_in_rule (sparql, NAMED_RULE_HavingCondition)) {
+ _call_rule (sparql, NAMED_RULE_HavingCondition, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_HavingCondition (TrackerSparql *sparql,
+ GError **error)
+{
+ /* HavingCondition ::= Constraint
+ */
+ _call_rule (sparql, NAMED_RULE_Constraint, error);
+ return TRUE;
+}
+
+static gboolean
+translate_OrderClause (TrackerSparql *sparql,
+ GError **error)
+{
+ gboolean first = TRUE;
+
+ /* OrderClause ::= 'ORDER' 'BY' OrderCondition+
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_ORDER);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BY);
+ _append_string (sparql, "ORDER BY ");
+
+ while (_check_in_rule (sparql, NAMED_RULE_OrderCondition)) {
+ if (!first)
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_OrderCondition, error);
+ first = FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_OrderCondition (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerStringBuilder *str, *old;
+ const gchar *order_str = NULL;
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+
+ /* OrderCondition ::= ( ( 'ASC' | 'DESC' ) BrackettedExpression )
+ * | ( Constraint | Var )
+ *
+ * TRACKER EXTENSION:
+ * plain Expression is also accepted, the last group is:
+ * ( Constraint | Var | Expression )
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ASC)) {
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ order_str = "ASC ";
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DESC)) {
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ order_str = "DESC ";
+ } else if (_check_in_rule (sparql, NAMED_RULE_Constraint)) {
+ _call_rule (sparql, NAMED_RULE_Constraint, error);
+ } else if (_check_in_rule (sparql, NAMED_RULE_Var)) {
+ TrackerVariableBinding *binding;
+ TrackerVariable *variable;
+
+ _call_rule (sparql, NAMED_RULE_Var, error);
+
+ variable = _last_node_variable (sparql);
+ _append_variable_sql (sparql, variable);
+
+ binding = tracker_variable_get_sample_binding (variable);
+ sparql->current_state.expression_type = TRACKER_BINDING (binding)->data_type;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ if (sparql->current_state.expression_type == TRACKER_PROPERTY_TYPE_STRING)
+ _append_string (sparql, "COLLATE " TRACKER_COLLATION_NAME " ");
+ else if (sparql->current_state.expression_type == TRACKER_PROPERTY_TYPE_RESOURCE)
+ convert_expression_to_string (sparql, sparql->current_state.expression_type);
+
+ tracker_sparql_swap_builder (sparql, old);
+
+ if (order_str)
+ _append_string (sparql, order_str);
+
+ return TRUE;
+}
+
+static gboolean
+translate_LimitOffsetClauses (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerBinding *limit = NULL, *offset = NULL;
+
+ /* LimitOffsetClauses ::= LimitClause OffsetClause? | OffsetClause LimitClause?
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_LimitClause)) {
+ _call_rule (sparql, NAMED_RULE_LimitClause, error);
+ limit = _convert_terminal (sparql);
+
+ if (_check_in_rule (sparql, NAMED_RULE_OffsetClause)) {
+ _call_rule (sparql, NAMED_RULE_OffsetClause, error);
+ offset = _convert_terminal (sparql);
+ }
+ } else if (_check_in_rule (sparql, NAMED_RULE_OffsetClause)) {
+ _call_rule (sparql, NAMED_RULE_OffsetClause, error);
+ offset = _convert_terminal (sparql);
+
+ if (_check_in_rule (sparql, NAMED_RULE_LimitClause)) {
+ _call_rule (sparql, NAMED_RULE_LimitClause, error);
+ limit = _convert_terminal (sparql);
+ }
+ } else {
+ g_assert_not_reached ();
+ }
+
+ if (limit) {
+ _append_string (sparql, "LIMIT ");
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (limit));
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (limit));
+ g_object_unref (limit);
+ }
+
+ if (offset) {
+ _append_string (sparql, "OFFSET ");
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (offset));
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (offset));
+ g_object_unref (offset);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_LimitClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* LimitClause ::= 'LIMIT' INTEGER
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_LIMIT);
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+
+ return TRUE;
+}
+
+static gboolean
+translate_OffsetClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* OffsetClause ::= 'OFFSET' INTEGER
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OFFSET);
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+
+ return TRUE;
+}
+
+static gboolean
+translate_ValuesClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ValuesClause ::= ( 'VALUES' DataBlock )?
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_VALUES)) {
+ _unimplemented ("VALUES");
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_Update1 (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+ GError *inner_error = NULL;
+
+ /* Update1 ::= Load | Clear | Drop | Add | Move | Copy | Create | InsertData | DeleteData | DeleteWhere | Modify
+ */
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_Load:
+ case NAMED_RULE_Clear:
+ case NAMED_RULE_Drop:
+ case NAMED_RULE_Add:
+ case NAMED_RULE_Move:
+ case NAMED_RULE_Copy:
+ case NAMED_RULE_Create:
+ case NAMED_RULE_InsertData:
+ case NAMED_RULE_DeleteData:
+ case NAMED_RULE_DeleteWhere:
+ case NAMED_RULE_Modify:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ tracker_data_update_buffer_flush (tracker_data_manager_get_data (sparql->data_manager),
+ &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+static gboolean
+translate_Load (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("LOAD");
+}
+
+static gboolean
+translate_Clear (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("CLEAR");
+}
+
+static gboolean
+translate_Drop (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("DROP");
+}
+
+static gboolean
+translate_Create (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("CREATE");
+}
+
+static gboolean
+translate_Add (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("ADD");
+}
+
+static gboolean
+translate_Move (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("MOVE");
+}
+
+static gboolean
+translate_Copy (TrackerSparql *sparql,
+ GError **error)
+{
+ _unimplemented ("COPY");
+}
+
+static gboolean
+translate_InsertData (TrackerSparql *sparql,
+ GError **error)
+{
+ /* InsertData ::= 'INSERT DATA' QuadData
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_INSERT);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DATA);
+
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_INSERT;
+ _call_rule (sparql, NAMED_RULE_QuadData, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_DeleteData (TrackerSparql *sparql,
+ GError **error)
+{
+ /* DeleteData ::= 'DELETE DATA' QuadData
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DELETE);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DATA);
+
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_DELETE;
+ _call_rule (sparql, NAMED_RULE_QuadData, error);
+
+ return TRUE;
+}
+
+static gboolean
+prepare_solution_select (TrackerSparql *sparql,
+ TrackerParserNode *pattern,
+ GError **error)
+{
+ TrackerSelectContext *select_context;
+ TrackerVariable *var;
+ GHashTableIter iter;
+ TrackerStringBuilder *outer_select;
+
+ _begin_triples_block (sparql);
+
+ if (!_postprocess_rule (sparql, pattern, NULL, error))
+ return FALSE;
+
+ if (!_end_triples_block (sparql, error))
+ return FALSE;
+
+ /* Surround by select to casts all variables to text */
+ _append_string (sparql, ")");
+
+ select_context = TRACKER_SELECT_CONTEXT (sparql->context);
+
+ outer_select = _prepend_placeholder (sparql);
+ tracker_sparql_swap_builder (sparql, outer_select);
+ _append_string (sparql, "SELECT ");
+
+ if (select_context->variables) {
+ gboolean first = TRUE;
+
+ g_hash_table_iter_init (&iter, select_context->variables);
+
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &var)) {
+ TrackerStringBuilder *str, *old;
+ TrackerPropertyType prop_type;
+
+ if (!first)
+ _append_string (sparql, ", ");
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+
+ _append_string_printf (sparql, "%s ",
+ tracker_variable_get_sql_expression (var));
+ prop_type = TRACKER_BINDING (tracker_variable_get_sample_binding (var))->data_type;
+ convert_expression_to_string (sparql, prop_type);
+ tracker_sparql_swap_builder (sparql, old);
+
+ _append_string_printf (sparql, "AS \"%s\" ", var->name);
+ first = FALSE;
+ }
+ } else {
+ _append_string (sparql, "1 ");
+ }
+
+ _append_string (sparql, "FROM (");
+ return TRUE;
+}
+
+static TrackerSolution *
+get_solution_for_pattern (TrackerSparql *sparql,
+ TrackerParserNode *pattern,
+ GError **error)
+{
+ TrackerDBStatement *stmt;
+ TrackerDBInterface *iface;
+ TrackerDBCursor *cursor;
+ TrackerSolution *solution;
+ gint i, n_cols;
+ gboolean retval;
+
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_SELECT;
+ sparql->context = g_object_ref_sink (tracker_select_context_new ());
+ sparql->current_state.select_context = sparql->context;
+ tracker_sparql_push_context (sparql, sparql->context);
+
+ g_clear_pointer (&sparql->sql, tracker_string_builder_free);
+ sparql->sql = tracker_string_builder_new ();
+ tracker_sparql_swap_builder (sparql, sparql->sql);
+
+ retval = prepare_solution_select (sparql, pattern, error);
+ tracker_sparql_pop_context (sparql, FALSE);
+
+ if (!retval) {
+ g_clear_object (&sparql->context);
+ return NULL;
+ }
+
+ iface = tracker_data_manager_get_writable_db_interface (sparql->data_manager);
+ stmt = prepare_query (iface, sparql->sql,
+ TRACKER_SELECT_CONTEXT (sparql->context)->literal_bindings,
+ NULL, FALSE,
+ error);
+ g_clear_object (&sparql->context);
+
+ if (!stmt)
+ return NULL;
+
+ cursor = tracker_db_statement_start_sparql_cursor (stmt,
+ NULL, 0,
+ NULL, 0,
+ error);
+ g_object_unref (stmt);
+
+ if (!cursor)
+ return NULL;
+
+ n_cols = tracker_db_cursor_get_n_columns (cursor);
+ solution = tracker_solution_new (n_cols);
+
+ for (i = 0; i < n_cols; i++) {
+ const gchar *name = tracker_db_cursor_get_variable_name (cursor, i);
+ tracker_solution_add_column_name (solution, name);
+ }
+
+ while (tracker_db_cursor_iter_next (cursor, NULL, NULL)) {
+ for (i = 0; i < n_cols; i++) {
+ const gchar *str = tracker_db_cursor_get_string (cursor, i, NULL);
+ tracker_solution_add_value (solution, str);
+ }
+ }
+
+ g_object_unref (cursor);
+
+ return solution;
+}
+
+static gboolean
+iterate_solution (TrackerSparql *sparql,
+ TrackerSolution *solution,
+ TrackerParserNode *node,
+ GError **error)
+{
+ gboolean retval = TRUE;
+
+ tracker_solution_rewind (solution);
+
+ while (retval && tracker_solution_next (solution)) {
+ GError *flush_error = NULL;
+
+ sparql->solution_var_map = tracker_solution_get_bindings (solution);
+ retval = _postprocess_rule (sparql, node, NULL, error);
+ g_clear_pointer (&sparql->solution_var_map, g_hash_table_unref);
+
+ tracker_data_update_buffer_might_flush (tracker_data_manager_get_data (sparql->data_manager),
+ &flush_error);
+ if (flush_error) {
+ g_propagate_error (error, flush_error);
+ retval = FALSE;
+ }
+ }
+
+ return retval;
+}
+
+static gboolean
+translate_DeleteWhere (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerParserNode *quad_pattern;
+ TrackerSolution *solution;
+ gboolean retval;
+
+ /* DeleteWhere ::= 'DELETE WHERE' QuadPattern
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DELETE);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_WHERE);
+
+ quad_pattern = _skip_rule (sparql, NAMED_RULE_QuadPattern);
+
+ /* 'DELETE WHERE' uses the same pattern for both query and update */
+ solution = get_solution_for_pattern (sparql, quad_pattern, error);
+ if (!solution)
+ return FALSE;
+
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_DELETE;
+ retval = iterate_solution (sparql, solution, quad_pattern, error);
+ tracker_solution_free (solution);
+
+ return retval;
+}
+
+static gboolean
+translate_Modify (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerParserNode *delete = NULL, *insert = NULL, *where = NULL;
+ TrackerSolution *solution;
+ gboolean retval = TRUE;
+
+ /* Modify ::= ( 'WITH' iri )? ( DeleteClause InsertClause? | InsertClause ) UsingClause* 'WHERE' GroupGraphPattern
+ *
+ * TRACKER EXTENSION:
+ * Last part of the clause is:
+ * ('WHERE' GroupGraphPattern)?
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_WITH)) {
+ _call_rule (sparql, NAMED_RULE_iri, error);
+ _init_token (&sparql->current_state.graph,
+ sparql->current_state.prev_node, sparql);
+ }
+
+ if (_check_in_rule (sparql, NAMED_RULE_DeleteClause)) {
+ delete = _skip_rule (sparql, NAMED_RULE_DeleteClause);
+ }
+
+ if (_check_in_rule (sparql, NAMED_RULE_InsertClause)) {
+ insert = _skip_rule (sparql, NAMED_RULE_InsertClause);
+ }
+
+ while (_check_in_rule (sparql, NAMED_RULE_UsingClause)) {
+ _call_rule (sparql, NAMED_RULE_UsingClause, error);
+ }
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_WHERE)) {
+ where = _skip_rule (sparql, NAMED_RULE_GroupGraphPattern);
+ solution = get_solution_for_pattern (sparql, where, error);
+ if (!solution)
+ return FALSE;
+ } else {
+ solution = tracker_solution_new (1);
+ tracker_solution_add_value (solution, "");
+ }
+
+ if (delete) {
+ retval = iterate_solution (sparql, solution, delete, error);
+ }
+
+ /* Flush in between */
+ if (retval && delete && insert) {
+ GError *flush_error = NULL;
+
+ tracker_data_update_buffer_flush (tracker_data_manager_get_data (sparql->data_manager),
+ &flush_error);
+ if (flush_error) {
+ g_propagate_error (error, flush_error);
+ retval = FALSE;
+ }
+ }
+
+ if (insert && retval) {
+ retval = iterate_solution (sparql, solution, insert, error);
+ }
+
+ tracker_solution_free (solution);
+
+ return retval;
+}
+
+static gboolean
+translate_DeleteClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* DeleteClause ::= 'DELETE' QuadPattern
+ *
+ * TRACKER EXTENSION:
+ * Clause may start too with:
+ * 'DELETE' 'SILENT'
+ */
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_DELETE;
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_DELETE);
+ sparql->silent = _accept (sparql, RULE_TYPE_LITERAL, LITERAL_SILENT);
+
+ _call_rule (sparql, NAMED_RULE_QuadPattern, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_InsertClause (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_graph;
+
+ /* InsertClause ::= 'INSERT' QuadPattern
+ *
+ * TRACKER EXTENSION:
+ * Clause may start with:
+ * 'INSERT' ('OR' 'REPLACE')? ('SILENT')? ('INTO' iri)?
+ */
+ if (sparql->blank_nodes) {
+ g_variant_builder_open (sparql->blank_nodes, G_VARIANT_TYPE ("a{ss}"));
+ sparql->current_state.blank_node_map =
+ g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+ }
+
+ old_graph = sparql->current_state.graph;
+
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_INSERT;
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_INSERT);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OR)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_REPLACE);
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_UPDATE;
+ } else {
+ sparql->current_state.type = TRACKER_SPARQL_TYPE_INSERT;
+ }
+
+ sparql->silent = _accept (sparql, RULE_TYPE_LITERAL, LITERAL_SILENT);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_INTO)) {
+ _call_rule (sparql, NAMED_RULE_iri, error);
+ _init_token (&sparql->current_state.graph,
+ sparql->current_state.prev_node, sparql);
+ }
+
+ _call_rule (sparql, NAMED_RULE_QuadPattern, error);
+
+ tracker_token_unset (&sparql->current_state.graph);
+ sparql->current_state.graph = old_graph;
+
+ if (sparql->blank_nodes) {
+ g_variant_builder_close (sparql->blank_nodes);
+ }
+
+ g_clear_pointer (&sparql->current_state.blank_node_map,
+ g_hash_table_unref);
+
+ return TRUE;
+}
+
+static gboolean
+translate_UsingClause (TrackerSparql *sparql,
+ GError **error)
+{
+ /* UsingClause ::= 'USING' ( iri | 'NAMED' iri )
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_USING);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_NAMED)) {
+ }
+
+ _call_rule (sparql, NAMED_RULE_iri, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_GraphOrDefault (TrackerSparql *sparql,
+ GError **error)
+{
+ /* GraphOrDefault ::= 'DEFAULT' | 'GRAPH'? iri
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DEFAULT)) {
+
+ } else {
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+ _call_rule (sparql, NAMED_RULE_iri, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_GraphRefAll (TrackerSparql *sparql,
+ GError **error)
+{
+ /* GraphRefAll ::= GraphRef | 'DEFAULT' | 'NAMED' | 'ALL'
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DEFAULT) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_NAMED) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_ALL)) {
+ } else {
+ _call_rule (sparql, NAMED_RULE_GraphRef, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_GraphRef (TrackerSparql *sparql,
+ GError **error)
+{
+ /* GraphRef ::= 'GRAPH' iri
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+ _call_rule (sparql, NAMED_RULE_iri, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_QuadPattern (TrackerSparql *sparql,
+ GError **error)
+{
+ /* QuadPattern ::= '{' Quads '}'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+ _call_rule (sparql, NAMED_RULE_Quads, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_QuadData (TrackerSparql *sparql,
+ GError **error)
+{
+ /* QuadData ::= '{' Quads '}'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+ _call_rule (sparql, NAMED_RULE_Quads, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_Quads (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Quads ::= TriplesTemplate? ( QuadsNotTriples '.'? TriplesTemplate? )*
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+ _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+ }
+
+ while (_check_in_rule (sparql, NAMED_RULE_QuadsNotTriples)) {
+ _call_rule (sparql, NAMED_RULE_QuadsNotTriples, error);
+
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT);
+
+ if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+ _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_QuadsNotTriples (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_graph;
+
+ /* QuadsNotTriples ::= 'GRAPH' VarOrIri '{' TriplesTemplate? '}'
+ */
+ old_graph = sparql->current_state.graph;
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+
+ _call_rule (sparql, NAMED_RULE_VarOrIri, error);
+ _init_token (&sparql->current_state.graph,
+ sparql->current_state.prev_node, sparql);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+ if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+ _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+ tracker_token_unset (&sparql->current_state.graph);
+ sparql->current_state.graph = old_graph;
+
+ return TRUE;
+}
+
+static gboolean
+translate_TriplesTemplate (TrackerSparql *sparql,
+ GError **error)
+{
+ /* TriplesTemplate ::= TriplesSameSubject ( '.' TriplesTemplate? )?
+ */
+ _call_rule (sparql, NAMED_RULE_TriplesSameSubject, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT)) {
+ if (_check_in_rule (sparql, NAMED_RULE_TriplesTemplate)) {
+ _call_rule (sparql, NAMED_RULE_TriplesTemplate, error);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_GroupGraphPatternSub (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerStringBuilder *child, *old;
+ TrackerParserNode *root;
+
+ /* GroupGraphPatternSub ::= TriplesBlock? ( GraphPatternNotTriples '.'? TriplesBlock? )*
+ */
+ root = (TrackerParserNode *) ((GNode *) sparql->current_state.node)->parent;
+ child = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, child);
+
+ if (_check_in_rule (sparql, NAMED_RULE_TriplesBlock)) {
+ _begin_triples_block (sparql);
+ _call_rule (sparql, NAMED_RULE_TriplesBlock, error);
+ if (!_end_triples_block (sparql, error))
+ return FALSE;
+ }
+
+ while (_check_in_rule (sparql, NAMED_RULE_GraphPatternNotTriples)) {
+ /* XXX: In the older code there was a minor optimization for
+ * simple OPTIONAL {} clauses. Two cases where handled where the
+ * optional is added inside the triples block:
+ *
+ * 1) OPTIONAL { ?u <p> ?o }, where ?u would be already bound
+ * in the non optional part, <p> is a single-valued property,
+ * and ?o is unbound. The binding representing pred/obj would
+ * be folded into the previous triples block, simply without:
+ *
+ * [AND] "var" IS NOT NULL
+ *
+ * 2) OPTIONAL { ?u <p> ?o }, where ?o is bound in the non optional
+ * part, <p> is an InverseFunctionalProperty and ?u is unbound.
+ * The previous triples block select clause would contain:
+ *
+ * SELECT ...,
+ * (SELECT ID FROM "$prop_table" WHERE "$prop" = "$table_in_from_clause"."$prop") AS ...,
+ * ...
+ *
+ * i.e. the resource ID is obtained in a subquery.
+ *
+ * The first one could be useful way more frequently than the
+ * second, and both involved substantial complications to SQL
+ * query preparation, so they have been left out at the moment.
+ */
+ _call_rule (sparql, NAMED_RULE_GraphPatternNotTriples, error);
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT);
+
+ if (_check_in_rule (sparql, NAMED_RULE_TriplesBlock)) {
+ gboolean do_join;
+
+ do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+ if (do_join) {
+ _prepend_string (sparql, "SELECT * FROM (");
+ _append_string (sparql, ") NATURAL INNER JOIN (");
+ }
+
+ _begin_triples_block (sparql);
+ _call_rule (sparql, NAMED_RULE_TriplesBlock, error);
+ if (!_end_triples_block (sparql, error))
+ return FALSE;
+
+ if (do_join)
+ _append_string (sparql, ") ");
+ }
+ }
+
+ /* Handle filters last, they apply to the pattern as a whole */
+ if (sparql->filter_clauses) {
+ GList *filters = sparql->filter_clauses;
+ gboolean first = TRUE;
+
+ while (filters) {
+ TrackerParserNode *filter_node = filters->data;
+ GList *elem = filters;
+
+ filters = filters->next;
+
+ if (!g_node_is_ancestor ((GNode *) root, (GNode *) filter_node))
+ continue;
+
+ if (first) {
+ if (tracker_string_builder_is_empty (sparql->current_state.sql)) {
+ _prepend_string (sparql, "SELECT 1 ");
+ _append_string (sparql, "WHERE ");
+ } else {
+ _prepend_string (sparql, "SELECT * FROM (");
+ _append_string (sparql, ") WHERE ");
+ }
+ first = FALSE;
+ } else {
+ _append_string (sparql, "AND ");
+ }
+
+ if (!_postprocess_rule (sparql, filter_node,
+ NULL, error))
+ return FALSE;
+
+ sparql->filter_clauses =
+ g_list_delete_link (sparql->filter_clauses, elem);
+ }
+ }
+
+ tracker_sparql_swap_builder (sparql, old);
+
+ return TRUE;
+}
+
+static gboolean
+translate_TriplesBlock (TrackerSparql *sparql,
+ GError **error)
+{
+ /* TriplesBlock ::= TriplesSameSubjectPath ( '.' TriplesBlock? )?
+ */
+ _call_rule (sparql, NAMED_RULE_TriplesSameSubjectPath, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT)) {
+ if (_check_in_rule (sparql, NAMED_RULE_TriplesBlock)) {
+ _call_rule (sparql, NAMED_RULE_TriplesBlock, error);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_GraphPatternNotTriples (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* GraphPatternNotTriples ::= GroupOrUnionGraphPattern | OptionalGraphPattern | MinusGraphPattern | GraphGraphPattern | ServiceGraphPattern | Filter | Bind | InlineData
+ */
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_GroupOrUnionGraphPattern:
+ case NAMED_RULE_OptionalGraphPattern:
+ case NAMED_RULE_MinusGraphPattern:
+ case NAMED_RULE_GraphGraphPattern:
+ case NAMED_RULE_ServiceGraphPattern:
+ case NAMED_RULE_Filter:
+ case NAMED_RULE_Bind:
+ case NAMED_RULE_InlineData:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_OptionalGraphPattern (TrackerSparql *sparql,
+ GError **error)
+{
+ gboolean do_join;
+
+ /* OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern
+ */
+ do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPTIONAL);
+
+ if (do_join) {
+ _prepend_string (sparql, "SELECT * FROM (");
+ _append_string (sparql, ") NATURAL LEFT JOIN (");
+ }
+
+ _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+ if (do_join)
+ _append_string (sparql, ") ");
+
+ return TRUE;
+}
+
+static gboolean
+translate_GraphGraphPattern (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_graph;
+ gboolean do_join;
+
+ /* GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern
+ */
+
+ do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+ if (do_join) {
+ _prepend_string (sparql, "SELECT * FROM (");
+ _append_string (sparql, ") NATURAL INNER JOIN (");
+ }
+
+ old_graph = sparql->current_state.graph;
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_GRAPH);
+ _call_rule (sparql, NAMED_RULE_VarOrIri, error);
+ _init_token (&sparql->current_state.graph,
+ sparql->current_state.prev_node, sparql);
+ _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+ tracker_token_unset (&sparql->current_state.graph);
+ sparql->current_state.graph = old_graph;
+
+ if (do_join)
+ _append_string (sparql, ") ");
+
+ return TRUE;
+}
+
+static gboolean
+translate_ServiceGraphPattern (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ServiceGraphPattern ::= 'SERVICE' 'SILENT'? VarOrIri GroupGraphPattern
+ */
+ _unimplemented ("SERVICE");
+}
+
+static gboolean
+translate_Bind (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerStringBuilder *str, *old;
+ TrackerVariable *variable;
+ TrackerBinding *binding;
+ TrackerPropertyType type;
+
+ /* Bind ::= 'BIND' '(' Expression 'AS' Var ')'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_BIND);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+
+ str = _prepend_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+
+ _append_string (sparql, "SELECT *, ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ type = sparql->current_state.expression_type;
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_AS);
+ _call_rule (sparql, NAMED_RULE_Var, error);
+
+ variable = _last_node_variable (sparql);
+
+ if (tracker_variable_has_bindings (variable))
+ _raise (PARSE, "Expected undefined variable", "BIND");
+
+ _append_string_printf (sparql, "AS %s FROM (",
+ tracker_variable_get_sql_expression (variable));
+
+ binding = tracker_variable_binding_new (variable, NULL, NULL);
+ tracker_binding_set_data_type (binding, type);
+ tracker_variable_set_sample_binding (variable, TRACKER_VARIABLE_BINDING (binding));
+
+ tracker_sparql_swap_builder (sparql, old);
+ _append_string (sparql, ") ");
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+
+ return TRUE;
+}
+
+static gboolean
+translate_InlineData (TrackerSparql *sparql,
+ GError **error)
+{
+ /* InlineData ::= 'VALUES' DataBlock
+ */
+ _unimplemented ("VALUES");
+}
+
+static gboolean
+translate_DataBlock (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* DataBlock ::= InlineDataOneVar | InlineDataFull
+ */
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_InlineDataOneVar:
+ case NAMED_RULE_InlineDataFull:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_InlineDataOneVar (TrackerSparql *sparql,
+ GError **error)
+{
+ /* InlineDataOneVar ::= Var '{' DataBlockValue* '}'
+ */
+ _call_rule (sparql, NAMED_RULE_Var, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+ while (_check_in_rule (sparql, NAMED_RULE_DataBlockValue)) {
+ _call_rule (sparql, NAMED_RULE_DataBlockValue, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_InlineDataFull (TrackerSparql *sparql,
+ GError **error)
+{
+ /* InlineDataFull ::= ( NIL | '(' Var* ')' ) '{' ( '(' DataBlockValue* ')' | NIL )* '}'
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+ while (_check_in_rule (sparql, NAMED_RULE_Var)) {
+ _call_rule (sparql, NAMED_RULE_Var, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+ do {
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+ while (_check_in_rule (sparql, NAMED_RULE_DataBlockValue)) {
+ _call_rule (sparql, NAMED_RULE_DataBlockValue, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ } else {
+ break;
+ }
+ } while (TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_DataBlockValue (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* DataBlockValue ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | 'UNDEF'
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UNDEF)) {
+ return TRUE;
+ }
+
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_iri:
+ case NAMED_RULE_RDFLiteral:
+ case NAMED_RULE_NumericLiteral:
+ case NAMED_RULE_BooleanLiteral:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_MinusGraphPattern (TrackerSparql *sparql,
+ GError **error)
+{
+ /* MinusGraphPattern ::= 'MINUS' GroupGraphPattern
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_MINUS);
+ _prepend_string (sparql, "SELECT * FROM (");
+ _append_string (sparql, ") EXCEPT ");
+ _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+ return TRUE;
+}
+
+static void
+append_union_select_vars (TrackerSparql *sparql,
+ TrackerContext *context,
+ GList *vars)
+{
+ GList *l;
+
+ _append_string (sparql, "SELECT ");
+
+ if (vars == NULL)
+ _append_string (sparql, "* ");
+
+ for (l = vars; l; l = l->next) {
+ TrackerVariable *variable = l->data;
+
+ if (l != vars)
+ _append_string (sparql, ", ");
+
+ if (!tracker_context_lookup_variable_ref (context, variable))
+ _append_string (sparql, "NULL AS ");
+
+ _append_string_printf (sparql, "%s ",
+ tracker_variable_get_sql_expression (variable));
+ }
+
+ _append_string (sparql, "FROM (");
+}
+
+static gboolean
+translate_GroupOrUnionGraphPattern (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerContext *context;
+ GPtrArray *placeholders;
+ GList *vars, *c;
+ gint idx = 0;
+ gboolean do_join;
+
+ /* GroupOrUnionGraphPattern ::= GroupGraphPattern ( 'UNION' GroupGraphPattern )*
+ */
+ do_join = !tracker_string_builder_is_empty (sparql->current_state.sql);
+
+ if (do_join) {
+ _prepend_string (sparql, "SELECT * FROM (");
+ _append_string (sparql, ") NATURAL INNER JOIN (");
+ }
+
+ placeholders = g_ptr_array_new ();
+ context = tracker_context_new ();
+ tracker_sparql_push_context (sparql, context);
+
+ do {
+ g_ptr_array_add (placeholders, _append_placeholder (sparql));
+
+ if (!_call_rule_func (sparql, NAMED_RULE_GroupGraphPattern, error)) {
+ g_ptr_array_unref (placeholders);
+ return FALSE;
+ }
+ } while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UNION));
+
+ vars = g_hash_table_get_keys (context->variable_set);
+
+ if (placeholders->len > 1) {
+ /* We are performing an union of multiple GroupGraphPattern,
+ * we must fix up the junction between all nested selects so we
+ * do UNION ALL on a common set of variables in a fixed
+ * order.
+ *
+ * If a variable is unused in the current subcontext
+ * it is defined as NULL.
+ */
+ for (c = context->children; c; c = c->next) {
+ TrackerStringBuilder *str, *old;
+
+ g_assert (idx < placeholders->len);
+ str = g_ptr_array_index (placeholders, idx);
+ old = tracker_sparql_swap_builder (sparql, str);
+
+ if (c != context->children)
+ _append_string (sparql, ") UNION ALL ");
+
+ append_union_select_vars (sparql, c->data, vars);
+ tracker_sparql_swap_builder (sparql, old);
+ idx++;
+ }
+
+ _append_string (sparql, ") ");
+ }
+
+ tracker_sparql_pop_context (sparql, TRUE);
+ g_ptr_array_unref (placeholders);
+ g_list_free (vars);
+
+ if (do_join)
+ _append_string (sparql, ") ");
+
+ return TRUE;
+}
+
+static gboolean
+translate_Filter (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerParserNode *node;
+
+ /* Filter ::= 'FILTER' Constraint
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_FILTER);
+ node = _skip_rule (sparql, NAMED_RULE_Constraint);
+ /* Add constraints to list for later processing */
+ sparql->filter_clauses = g_list_prepend (sparql->filter_clauses, node);
+
+ return TRUE;
+}
+
+static gboolean
+translate_Constraint (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* Constraint ::= BrackettedExpression | BuiltInCall | FunctionCall
+ */
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_BrackettedExpression:
+ case NAMED_RULE_BuiltInCall:
+ case NAMED_RULE_FunctionCall:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_FunctionCall (TrackerSparql *sparql,
+ GError **error)
+{
+ /* FunctionCall ::= iri ArgList
+ */
+ _call_rule (sparql, NAMED_RULE_iri, error);
+ return handle_function_call (sparql, error);
+}
+
+static gboolean
+translate_ArgList (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ArgList ::= NIL | '(' 'DISTINCT'? Expression ( ',' Expression )* ')'
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+ if (_check_in_rule (sparql, NAMED_RULE_ArgList))
+ _raise (PARSE, "Recursive ArgList is not allowed", "ArgList");
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT)) {
+ _unimplemented ("DISTINCT in ArgList");
+ }
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ const gchar *separator = ", ";
+
+ if (sparql->current_state.expression_list_separator)
+ separator = sparql->current_state.expression_list_separator;
+
+ _append_string (sparql, separator);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_ExpressionList (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ExpressionList ::= NIL | '(' Expression ( ',' Expression )* ')'
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+ _append_string (sparql, "() ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+ _append_string (sparql, "(");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ _append_string (sparql,
+ sparql->current_state.expression_list_separator);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_ConstructTemplate (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ConstructTemplate ::= '{' ConstructTriples? '}'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+
+ if (_check_in_rule (sparql, NAMED_RULE_ConstructTriples)) {
+ _call_rule (sparql, NAMED_RULE_ConstructTriples, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_ConstructTriples (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ConstructTriples ::= TriplesSameSubject ( '.' ConstructTriples? )?
+ */
+ _call_rule (sparql, NAMED_RULE_TriplesSameSubject, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOT)) {
+ if (_check_in_rule (sparql, NAMED_RULE_ConstructTriples)) {
+ _call_rule (sparql, NAMED_RULE_ConstructTriples, error);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_TriplesSameSubject (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_subject = sparql->current_state.subject;
+ TrackerGrammarNamedRule rule;
+
+ /* TriplesSameSubject ::= VarOrTerm PropertyListNotEmpty | TriplesNode PropertyList
+ */
+ rule = _current_rule (sparql);
+ sparql->current_state.token = &sparql->current_state.subject;
+
+ if (rule == NAMED_RULE_VarOrTerm) {
+ _call_rule (sparql, rule, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+ sparql->current_state.token = &sparql->current_state.object;
+ _call_rule (sparql, NAMED_RULE_PropertyListNotEmpty, error);
+ } else if (rule == NAMED_RULE_TriplesNode) {
+ _call_rule (sparql, rule, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+ sparql->current_state.token = &sparql->current_state.object;
+ _call_rule (sparql, NAMED_RULE_PropertyList, error);
+ }
+
+ tracker_token_unset (&sparql->current_state.subject);
+ sparql->current_state.subject = old_subject;
+ sparql->current_state.token = NULL;
+
+ return TRUE;
+}
+
+static gboolean
+translate_GroupGraphPattern (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+ TrackerContext *context;
+
+ /* GroupGraphPattern ::= '{' ( SubSelect | GroupGraphPatternSub ) '}'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACE);
+ context = tracker_context_new ();
+ tracker_sparql_push_context (sparql, context);
+
+ rule = _current_rule (sparql);
+
+ if (rule == NAMED_RULE_SubSelect) {
+ _append_string (sparql, "(");
+ _call_rule (sparql, rule, error);
+ _append_string (sparql, ") ");
+ } else if (rule == NAMED_RULE_GroupGraphPatternSub) {
+ _call_rule (sparql, rule, error);
+ }
+
+ tracker_sparql_pop_context (sparql, TRUE);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_PropertyList (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PropertyList ::= PropertyListNotEmpty?
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_PropertyListNotEmpty)) {
+ _call_rule (sparql, NAMED_RULE_PropertyListNotEmpty, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_PropertyListNotEmpty (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_pred, *prev_token;
+
+ old_pred = sparql->current_state.predicate;
+ prev_token = sparql->current_state.token;
+ sparql->current_state.token = &sparql->current_state.object;
+
+ /* PropertyListNotEmpty ::= Verb ObjectList ( ';' ( Verb ObjectList )? )*
+ */
+ _call_rule (sparql, NAMED_RULE_Verb, error);
+ _init_token (&sparql->current_state.predicate,
+ sparql->current_state.prev_node, sparql);
+
+ _call_rule (sparql, NAMED_RULE_ObjectList, error);
+ tracker_token_unset (&sparql->current_state.predicate);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON)) {
+ if (!_check_in_rule (sparql, NAMED_RULE_Verb))
+ break;
+
+ _call_rule (sparql, NAMED_RULE_Verb, error);
+ _init_token (&sparql->current_state.predicate,
+ sparql->current_state.prev_node, sparql);
+
+ _call_rule (sparql, NAMED_RULE_ObjectList, error);
+
+ tracker_token_unset (&sparql->current_state.predicate);
+ }
+
+ sparql->current_state.predicate = old_pred;
+ sparql->current_state.token = prev_token;
+
+ return TRUE;
+}
+
+static gboolean
+translate_Verb (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Verb ::= VarOrIri | 'a'
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_A)) {
+ } else {
+ _call_rule (sparql, NAMED_RULE_VarOrIri, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_ObjectList (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ObjectList ::= Object ( ',' Object )*
+ */
+ _call_rule (sparql, NAMED_RULE_Object, error);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ _call_rule (sparql, NAMED_RULE_Object, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_Object (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Object ::= GraphNode
+ */
+ _call_rule (sparql, NAMED_RULE_GraphNode, error);
+ return TRUE;
+}
+
+static gboolean
+translate_TriplesSameSubjectPath (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_subject = sparql->current_state.subject;
+ TrackerGrammarNamedRule rule;
+
+ /* TriplesSameSubjectPath ::= VarOrTerm PropertyListPathNotEmpty | TriplesNodePath PropertyListPath
+ */
+ rule = _current_rule (sparql);
+ sparql->current_state.token = &sparql->current_state.subject;
+
+ if (rule == NAMED_RULE_VarOrTerm) {
+ _call_rule (sparql, rule, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+ sparql->current_state.token = &sparql->current_state.object;
+ _call_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty, error);
+ } else if (rule == NAMED_RULE_TriplesNodePath) {
+ _call_rule (sparql, rule, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.subject));
+ sparql->current_state.token = &sparql->current_state.object;
+ _call_rule (sparql, NAMED_RULE_PropertyListPath, error);
+ }
+
+ tracker_token_unset (&sparql->current_state.subject);
+ sparql->current_state.subject = old_subject;
+ sparql->current_state.token = NULL;
+
+ return TRUE;
+}
+
+static gboolean
+translate_PropertyListPath (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PropertyListPath ::= PropertyListPathNotEmpty?
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty)) {
+ _call_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_PropertyListPathNotEmpty (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+ TrackerToken old_predicate, *prev_token;
+ TrackerParserNode *verb;
+
+ /* PropertyListPathNotEmpty ::= ( VerbPath | VerbSimple ) ObjectListPath ( ';' ( ( VerbPath | VerbSimple ) ObjectList )? )*
+ */
+ rule = _current_rule (sparql);
+ old_predicate = sparql->current_state.predicate;
+ prev_token = sparql->current_state.token;
+ sparql->current_state.token = &sparql->current_state.object;
+
+ if (rule == NAMED_RULE_VerbPath || rule == NAMED_RULE_VerbSimple) {
+ verb = _skip_rule (sparql, rule);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ sparql->current_state.object_list = _skip_rule (sparql, NAMED_RULE_ObjectListPath);
+ if (!_postprocess_rule (sparql, verb, NULL, error))
+ return FALSE;
+
+ tracker_token_unset (&sparql->current_state.predicate);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON)) {
+ rule = _current_rule (sparql);
+
+ if (rule == NAMED_RULE_VerbPath || rule == NAMED_RULE_VerbSimple) {
+ verb = _skip_rule (sparql, rule);
+ } else {
+ break;
+ }
+
+ sparql->current_state.object_list = _skip_rule (sparql, NAMED_RULE_ObjectList);
+ if (!_postprocess_rule (sparql, verb, NULL, error))
+ return FALSE;
+
+ tracker_token_unset (&sparql->current_state.predicate);
+ }
+
+ sparql->current_state.predicate = old_predicate;
+ sparql->current_state.token = prev_token;
+
+ return TRUE;
+}
+
+static gboolean
+translate_VerbPath (TrackerSparql *sparql,
+ GError **error)
+{
+ /* VerbPath ::= Path
+ */
+ _call_rule (sparql, NAMED_RULE_Path, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_VerbSimple (TrackerSparql *sparql,
+ GError **error)
+{
+ /* VerbSimple ::= Var
+ */
+ _call_rule (sparql, NAMED_RULE_Var, error);
+ _init_token (&sparql->current_state.predicate,
+ sparql->current_state.prev_node, sparql);
+
+ if (!_postprocess_rule (sparql, sparql->current_state.object_list,
+ NULL, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+translate_ObjectListPath (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ObjectListPath ::= ObjectPath ( ',' ObjectPath )*
+ */
+ _call_rule (sparql, NAMED_RULE_ObjectPath, error);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ _call_rule (sparql, NAMED_RULE_ObjectPath, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_ObjectPath (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ObjectPath ::= GraphNodePath
+ */
+ _call_rule (sparql, NAMED_RULE_GraphNodePath, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_Path (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Path ::= PathAlternative
+ */
+ _call_rule (sparql, NAMED_RULE_PathAlternative, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_PathAlternative (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PathAlternative ::= PathSequence ( '|' PathSequence )*
+ */
+ _call_rule (sparql, NAMED_RULE_PathSequence, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_PATH_ALTERNATIVE)) {
+ _unimplemented ("Alternative property path");
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_PathSequence (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_object, old_subject;
+ TrackerVariable *var;
+ TrackerParserNode *rule;
+
+ /* PathSequence ::= PathEltOrInverse ( '/' PathEltOrInverse )*
+ */
+ old_object = sparql->current_state.object;
+ old_subject = sparql->current_state.subject;
+
+ rule = _skip_rule (sparql, NAMED_RULE_PathEltOrInverse);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_PATH_SEQUENCE)) {
+ var = tracker_select_context_add_generated_variable (TRACKER_SELECT_CONTEXT (sparql->context));
+ tracker_token_variable_init (&sparql->current_state.object, var);
+
+ if (!_postprocess_rule (sparql, rule, NULL, error))
+ return FALSE;
+
+ rule = _skip_rule (sparql, NAMED_RULE_PathEltOrInverse);
+ sparql->current_state.subject = sparql->current_state.object;
+ tracker_token_unset (&sparql->current_state.object);
+ }
+
+ if (!_postprocess_rule (sparql, rule, NULL, error))
+ return FALSE;
+
+ sparql->current_state.subject = old_subject;
+ sparql->current_state.object = old_object;
+
+ return TRUE;
+}
+
+static gboolean
+translate_PathEltOrInverse (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_object, old_subject, *old_token;
+
+ /* PathEltOrInverse ::= PathElt | '^' PathElt
+ */
+ old_object = sparql->current_state.object;
+ old_subject = sparql->current_state.subject;
+ old_token = sparql->current_state.token;
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_PATH_INVERSE)) {
+ sparql->current_state.object = old_subject;
+ sparql->current_state.subject = old_object;
+ sparql->current_state.token = &sparql->current_state.subject;
+ }
+
+ _call_rule (sparql, NAMED_RULE_PathElt, error);
+
+ sparql->current_state.subject = old_subject;
+ sparql->current_state.object = old_object;
+ sparql->current_state.token = old_token;
+
+ return TRUE;
+}
+
+static gboolean
+translate_PathElt (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PathElt ::= PathPrimary PathMod?
+ */
+ _call_rule (sparql, NAMED_RULE_PathPrimary, error);
+ _init_token (&sparql->current_state.predicate,
+ sparql->current_state.prev_node, sparql);
+
+ if (_check_in_rule (sparql, NAMED_RULE_PathMod)) {
+ _call_rule (sparql, NAMED_RULE_PathMod, error);
+ }
+
+ if (!tracker_token_is_empty (sparql->current_state.token)) {
+ return _add_quad (sparql,
+ &sparql->current_state.graph,
+ &sparql->current_state.subject,
+ &sparql->current_state.predicate,
+ &sparql->current_state.object,
+ error);
+ } else {
+ return _postprocess_rule (sparql, sparql->current_state.object_list,
+ NULL, error);
+ }
+}
+
+static gboolean
+translate_PathMod (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PathMod ::= '?' | '*' | '+'
+ */
+ _unimplemented ("Path modifiers");
+}
+
+static gboolean
+translate_PathPrimary (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PathPrimary ::= iri | 'a' | '!' PathNegatedPropertySet | '(' Path ')'
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_NEG)) {
+ _call_rule (sparql, NAMED_RULE_PathNegatedPropertySet, error);
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS)) {
+ _call_rule (sparql, NAMED_RULE_Path, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_A)) {
+
+ } else if (_check_in_rule (sparql, NAMED_RULE_iri)) {
+ _call_rule (sparql, NAMED_RULE_iri, error);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_PathNegatedPropertySet (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PathNegatedPropertySet ::= PathOneInPropertySet | '(' ( PathOneInPropertySet ( '|' PathOneInPropertySet )* )? ')'
+ */
+ _unimplemented ("Negated property set in property paths");
+ return FALSE;
+}
+
+static gboolean
+translate_PathOneInPropertySet (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PathOneInPropertySet ::= iri | 'a' | '^' ( iri | 'a' )
+ */
+ return FALSE;
+}
+
+static gboolean
+translate_Integer (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Integer ::= INTEGER
+ */
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+
+ return TRUE;
+}
+
+static gboolean
+translate_TriplesNode (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* TriplesNode ::= Collection | BlankNodePropertyList
+ */
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_Collection:
+ case NAMED_RULE_BlankNodePropertyList:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_BlankNodePropertyList (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_subject = sparql->current_state.subject;
+ TrackerVariable *var;
+
+ /* BlankNodePropertyList ::= '[' PropertyListNotEmpty ']'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACKET);
+
+ if (sparql->current_state.type == TRACKER_SPARQL_TYPE_SELECT) {
+ var = tracker_select_context_add_generated_variable (TRACKER_SELECT_CONTEXT (sparql->context));
+ tracker_token_variable_init (&sparql->current_state.subject, var);
+ } else {
+ TrackerDBInterface *iface;
+ gchar *bnode_id;
+
+ iface = tracker_data_manager_get_writable_db_interface (sparql->data_manager);
+ bnode_id = tracker_data_query_unused_uuid (sparql->data_manager, iface);
+ tracker_token_literal_init (&sparql->current_state.subject, bnode_id);
+ g_free (bnode_id);
+ }
+
+ _call_rule (sparql, NAMED_RULE_PropertyListNotEmpty, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACKET);
+
+ /* Return the blank node subject through the token, if token is already
+ * the subject, doesn't need changing.
+ */
+ g_assert (sparql->current_state.token != NULL);
+
+ if (sparql->current_state.token != &sparql->current_state.subject) {
+ *sparql->current_state.token = sparql->current_state.subject;
+ sparql->current_state.subject = old_subject;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_TriplesNodePath (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* TriplesNodePath ::= CollectionPath | BlankNodePropertyListPath
+ */
+ rule = _current_rule (sparql);
+
+ if (rule == NAMED_RULE_CollectionPath) {
+ _call_rule (sparql, rule, error);
+ } else if (rule == NAMED_RULE_BlankNodePropertyListPath) {
+ _call_rule (sparql, rule, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_BlankNodePropertyListPath (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerToken old_subject = sparql->current_state.subject;
+ TrackerToken *token_location = sparql->current_state.token;
+ TrackerVariable *var;
+
+ /* BlankNodePropertyListPath ::= '[' PropertyListPathNotEmpty ']'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_BRACKET);
+
+ var = tracker_select_context_add_generated_variable (TRACKER_SELECT_CONTEXT (sparql->context));
+ tracker_token_variable_init (&sparql->current_state.subject, var);
+ _call_rule (sparql, NAMED_RULE_PropertyListPathNotEmpty, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_BRACKET);
+
+ tracker_token_unset (&sparql->current_state.subject);
+ sparql->current_state.subject = old_subject;
+
+ /* Return the blank node subject through the token */
+ g_assert (sparql->current_state.token != NULL);
+ tracker_token_unset (token_location);
+ tracker_token_variable_init (token_location, var);
+
+ return TRUE;
+}
+
+static gboolean
+translate_Collection (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Collection ::= '(' GraphNode+ ')'
+ */
+ _unimplemented ("Collections are not supported");
+}
+
+static gboolean
+translate_CollectionPath (TrackerSparql *sparql,
+ GError **error)
+{
+ /* CollectionPath ::= '(' GraphNodePath+ ')'
+ */
+ _unimplemented ("Collections are not supported");
+}
+
+static gboolean
+translate_GraphNode (TrackerSparql *sparql,
+ GError **error)
+{
+ GError *inner_error = NULL;
+
+ /* GraphNode ::= VarOrTerm | TriplesNode
+ *
+ * TRACKER EXTENSION:
+ * Literal 'NULL' is also accepted, rule is effectively:
+ * VarOrTerm | TriplesNode | 'NULL'
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_VarOrTerm)) {
+ _call_rule (sparql, NAMED_RULE_VarOrTerm, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+ } else if (_check_in_rule (sparql, NAMED_RULE_TriplesNode)) {
+ _call_rule (sparql, NAMED_RULE_TriplesNode, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_NULL)) {
+ if (sparql->current_state.type != TRACKER_SPARQL_TYPE_UPDATE)
+ _raise (PARSE, "«NULL» literal is not allowed in this mode", "NULL");
+ /* Object token is left unset on purpose */
+ } else {
+ g_assert_not_reached ();
+ }
+
+ switch (sparql->current_state.type) {
+ case TRACKER_SPARQL_TYPE_SELECT:
+ _add_quad (sparql,
+ &sparql->current_state.graph,
+ &sparql->current_state.subject,
+ &sparql->current_state.predicate,
+ &sparql->current_state.object,
+ &inner_error);
+ break;
+ case TRACKER_SPARQL_TYPE_INSERT:
+ tracker_data_insert_statement (tracker_data_manager_get_data (sparql->data_manager),
+ tracker_token_get_idstring (&sparql->current_state.graph),
+ tracker_token_get_idstring (&sparql->current_state.subject),
+ tracker_token_get_idstring (&sparql->current_state.predicate),
+ tracker_token_get_idstring (&sparql->current_state.object),
+ &inner_error);
+ break;
+ case TRACKER_SPARQL_TYPE_DELETE:
+ tracker_data_delete_statement (tracker_data_manager_get_data (sparql->data_manager),
+ tracker_token_get_idstring (&sparql->current_state.graph),
+ tracker_token_get_idstring (&sparql->current_state.subject),
+ tracker_token_get_idstring (&sparql->current_state.predicate),
+ tracker_token_get_idstring (&sparql->current_state.object),
+ &inner_error);
+ break;
+ case TRACKER_SPARQL_TYPE_UPDATE:
+ tracker_data_update_statement (tracker_data_manager_get_data (sparql->data_manager),
+ tracker_token_get_idstring (&sparql->current_state.graph),
+ tracker_token_get_idstring (&sparql->current_state.subject),
+ tracker_token_get_idstring (&sparql->current_state.predicate),
+ tracker_token_get_idstring (&sparql->current_state.object),
+ &inner_error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ tracker_token_unset (&sparql->current_state.object);
+
+ if (inner_error && !sparql->silent) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ } else {
+ g_clear_error (&inner_error);
+ return TRUE;
+ }
+}
+
+static gboolean
+translate_GraphNodePath (TrackerSparql *sparql,
+ GError **error)
+{
+ /* GraphNodePath ::= VarOrTerm | TriplesNodePath
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_VarOrTerm)) {
+ _call_rule (sparql, NAMED_RULE_VarOrTerm, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+ } else if (_check_in_rule (sparql, NAMED_RULE_TriplesNodePath)) {
+ _call_rule (sparql, NAMED_RULE_TriplesNodePath, error);
+ g_assert (!tracker_token_is_empty (&sparql->current_state.object));
+ } else {
+ g_assert_not_reached ();
+ }
+
+ if (!_add_quad (sparql,
+ &sparql->current_state.graph,
+ &sparql->current_state.subject,
+ &sparql->current_state.predicate,
+ &sparql->current_state.object,
+ error))
+ return FALSE;
+
+ tracker_token_unset (&sparql->current_state.object);
+
+ return TRUE;
+}
+
+static gboolean
+translate_VarOrTerm (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* VarOrTerm ::= Var | GraphTerm
+ */
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_Var:
+ if (sparql->current_state.type != TRACKER_SPARQL_TYPE_SELECT &&
+ !sparql->solution_var_map) {
+ TrackerParserNode *node = sparql->current_state.node;
+ const gchar *str = "Unknown";
+
+ /* Find the insert/delete clause, a child of Update1 */
+ while (node) {
+ TrackerParserNode *parent;
+ const TrackerGrammarRule *rule;
+
+ parent = (TrackerParserNode *) ((GNode *)node)->parent;
+ rule = tracker_parser_node_get_rule (parent);
+
+ if (tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, NAMED_RULE_Update1)) {
+ rule = tracker_parser_node_get_rule (node);
+ str = rule->string;
+ break;
+ }
+
+ node = parent;
+ }
+
+ _raise (PARSE, "Variables are not allowed in update clause", str);
+ }
+
+ _call_rule (sparql, rule, error);
+ g_assert (sparql->current_state.token != NULL);
+ _init_token (sparql->current_state.token,
+ sparql->current_state.prev_node, sparql);
+ break;
+ case NAMED_RULE_GraphTerm:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_VarOrIri (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* VarOrIri ::= Var | iri
+ */
+ rule = _current_rule (sparql);
+
+ if (rule == NAMED_RULE_Var) {
+ _call_rule (sparql, rule, error);
+ } else if (rule == NAMED_RULE_iri) {
+ _call_rule (sparql, rule, error);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_Var (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Var ::= VAR1 | VAR2
+ */
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2)) {
+ if (sparql->current_state.type == TRACKER_SPARQL_TYPE_SELECT) {
+ TrackerVariableBinding *binding;
+ TrackerVariable *var;
+
+ /* Ensure the variable is referenced in the context */
+ var = _extract_node_variable (sparql->current_state.prev_node,
+ sparql);
+
+ binding = tracker_variable_get_sample_binding (var);
+
+ if (binding)
+ sparql->current_state.expression_type = TRACKER_BINDING (binding)->data_type;
+ }
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_GraphTerm (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* GraphTerm ::= iri | RDFLiteral | NumericLiteral | BooleanLiteral | BlankNode | NIL
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL)) {
+ return TRUE;
+ }
+
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_iri:
+ case NAMED_RULE_RDFLiteral:
+ case NAMED_RULE_NumericLiteral:
+ case NAMED_RULE_BooleanLiteral:
+ _call_rule (sparql, rule, error);
+ g_assert (sparql->current_state.token != NULL);
+ _init_token (sparql->current_state.token,
+ sparql->current_state.prev_node, sparql);
+ break;
+ case NAMED_RULE_BlankNode:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_Expression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Expression ::= ConditionalOrExpression
+ */
+ _call_rule (sparql, NAMED_RULE_ConditionalOrExpression, error);
+
+ return TRUE;
+
+}
+
+static gboolean
+translate_ConditionalOrExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )*
+ */
+ _call_rule (sparql, NAMED_RULE_ConditionalAndExpression, error);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_OR)) {
+ if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+ _raise (PARSE, "Expected boolean expression", "||");
+
+ _append_string (sparql, " OR ");
+ _call_rule (sparql, NAMED_RULE_ConditionalAndExpression, error);
+
+ if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+ _raise (PARSE, "Expected boolean expression", "||");
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_ConditionalAndExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ConditionalAndExpression ::= ValueLogical ( '&&' ValueLogical )*
+ */
+ _call_rule (sparql, NAMED_RULE_ValueLogical, error);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_AND)) {
+ if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+ _raise (PARSE, "Expected boolean expression", "&&");
+
+ _append_string (sparql, " AND ");
+ _call_rule (sparql, NAMED_RULE_ValueLogical, error);
+
+ if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN)
+ _raise (PARSE, "Expected boolean expression", "&&");
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_ValueLogical (TrackerSparql *sparql,
+ GError **error)
+{
+ /* ValueLogical ::= RelationalExpression
+ */
+ _call_rule (sparql, NAMED_RULE_RelationalExpression, error);
+
+ return TRUE;
+}
+
+static gboolean
+translate_RelationalExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ const gchar *old_sep;
+
+ /* RelationalExpression ::= NumericExpression ( '=' NumericExpression | '!=' NumericExpression | '<' NumericExpression | '>' NumericExpression | '<=' NumericExpression | '>=' NumericExpression | 'IN' ExpressionList | 'NOT' 'IN' ExpressionList )?
+ */
+ _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_IN)) {
+ _append_string (sparql, "IN ");
+ old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+ tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_NOT)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OP_IN);
+ _append_string (sparql, "NOT IN ");
+ old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+ tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_EQ)) {
+ _append_string (sparql, " = ");
+ _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_NE)) {
+ _append_string (sparql, " != ");
+ _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_LT)) {
+ _append_string (sparql, " < ");
+ _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_GT)) {
+ _append_string (sparql, " > ");
+ _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_LE)) {
+ _append_string (sparql, " <= ");
+ _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_GE)) {
+ _append_string (sparql, " >= ");
+ _call_rule (sparql, NAMED_RULE_NumericExpression, error);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_NumericExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* NumericExpression ::= AdditiveExpression
+ */
+ _call_rule (sparql, NAMED_RULE_AdditiveExpression, error);
+
+ return TRUE;
+}
+
+static gboolean
+maybe_numeric (TrackerPropertyType prop_type)
+{
+ return (prop_type == TRACKER_PROPERTY_TYPE_INTEGER ||
+ prop_type == TRACKER_PROPERTY_TYPE_DOUBLE ||
+ prop_type == TRACKER_PROPERTY_TYPE_DATE ||
+ prop_type == TRACKER_PROPERTY_TYPE_DATETIME ||
+ prop_type == TRACKER_PROPERTY_TYPE_UNKNOWN);
+}
+
+static gboolean
+translate_AdditiveExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression | '-' MultiplicativeExpression | ( NumericLiteralPositive | NumericLiteralNegative ) ( ( '*' UnaryExpression ) | ( '/' UnaryExpression ) )* )*
+ */
+ _call_rule (sparql, NAMED_RULE_MultiplicativeExpression, error);
+
+ do {
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_PLUS)) {
+ if (!maybe_numeric (sparql->current_state.expression_type))
+ _raise (PARSE, "Expected numeric operand", "+");
+
+ _append_string (sparql, " + ");
+ _call_rule (sparql, NAMED_RULE_MultiplicativeExpression, error);
+
+ if (!maybe_numeric (sparql->current_state.expression_type))
+ _raise (PARSE, "Expected numeric operand", "+");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MINUS)) {
+ if (!maybe_numeric (sparql->current_state.expression_type))
+ _raise (PARSE, "Expected numeric operand", "-");
+ _append_string (sparql, " - ");
+ _call_rule (sparql, NAMED_RULE_MultiplicativeExpression, error);
+
+ if (!maybe_numeric (sparql->current_state.expression_type))
+ _raise (PARSE, "Expected numeric operand", "+");
+ } else if (_check_in_rule (sparql, NAMED_RULE_NumericLiteralPositive) ||
+ _check_in_rule (sparql, NAMED_RULE_NumericLiteralNegative)) {
+ if (!maybe_numeric (sparql->current_state.expression_type))
+ _raise (PARSE, "Expected numeric operand", "multiplication/division");
+
+ _call_rule (sparql, _current_rule (sparql), error);
+
+ do {
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MULT)) {
+ _append_string (sparql, " * ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_DIV)) {
+ _append_string (sparql, " / ");
+ } else {
+ break;
+ }
+
+ _call_rule (sparql, NAMED_RULE_UnaryExpression, error);
+ if (!maybe_numeric (sparql->current_state.expression_type))
+ _raise (PARSE, "Expected numeric operand", "multiplication/division");
+ } while (TRUE);
+ } else {
+ break;
+ }
+ } while (TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_MultiplicativeExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* MultiplicativeExpression ::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )*
+ */
+ _call_rule (sparql, NAMED_RULE_UnaryExpression, error);
+
+ do {
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MULT)) {
+ _append_string (sparql, " * ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_DIV)) {
+ _append_string (sparql, " / ");
+ } else {
+ break;
+ }
+
+ _call_rule (sparql, NAMED_RULE_UnaryExpression, error);
+ } while (TRUE);
+
+ return TRUE;
+}
+
+static gboolean
+translate_UnaryExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* UnaryExpression ::= '!' PrimaryExpression
+ * | '+' PrimaryExpression
+ * | '-' PrimaryExpression
+ * | PrimaryExpression
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_OP_NEG)) {
+ _append_string (sparql, "NOT (");
+ _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+ _append_string (sparql, ") ");
+
+ if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_BOOLEAN) {
+ _raise (PARSE, "Expected boolean expression", "UnaryExpression");
+ }
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_PLUS)) {
+ _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ARITH_MINUS)) {
+ _append_string (sparql, "-(");
+ _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+ _append_string (sparql, ") ");
+ } else {
+ _call_rule (sparql, NAMED_RULE_PrimaryExpression, error);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_PrimaryExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerSelectContext *select_context;
+ TrackerGrammarNamedRule rule;
+ TrackerBinding *binding;
+ TrackerVariable *variable;
+
+ /* PrimaryExpression ::= BrackettedExpression | BuiltInCall | iriOrFunction | RDFLiteral | NumericLiteral | BooleanLiteral | Var
+ */
+ rule = _current_rule (sparql);
+ select_context = TRACKER_SELECT_CONTEXT (sparql->context);
+
+ switch (rule) {
+ case NAMED_RULE_NumericLiteral:
+ case NAMED_RULE_BooleanLiteral:
+ _call_rule (sparql, rule, error);
+ binding = _convert_terminal (sparql);
+ tracker_select_context_add_literal_binding (select_context,
+ TRACKER_LITERAL_BINDING (binding));
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+ g_object_unref (binding);
+ break;
+ case NAMED_RULE_Var:
+ _call_rule (sparql, rule, error);
+ variable = _last_node_variable (sparql);
+ _append_variable_sql (sparql, variable);
+
+ /* If the variable is bound, propagate the binding data type */
+ if (tracker_variable_has_bindings (variable)) {
+ binding = TRACKER_BINDING (tracker_variable_get_sample_binding (variable));
+ sparql->current_state.expression_type = binding->data_type;
+ }
+ break;
+ case NAMED_RULE_RDFLiteral:
+ _call_rule (sparql, rule, error);
+ binding = g_ptr_array_index (select_context->literal_bindings,
+ select_context->literal_bindings->len - 1);
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+ break;
+ case NAMED_RULE_BrackettedExpression:
+ case NAMED_RULE_BuiltInCall:
+ case NAMED_RULE_iriOrFunction:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+handle_property_function (TrackerSparql *sparql,
+ TrackerProperty *property,
+ GError **error)
+{
+ if (tracker_property_get_multiple_values (property)) {
+ TrackerStringBuilder *str, *old;
+
+ _append_string (sparql, "(SELECT GROUP_CONCAT (");
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+ _append_string_printf (sparql, "\"%s\"", tracker_property_get_name (property));
+ convert_expression_to_string (sparql, tracker_property_get_data_type (property));
+ tracker_sparql_swap_builder (sparql, old);
+
+ _append_string_printf (sparql, ", ',') FROM \"%s\" WHERE ID = ",
+ tracker_property_get_table_name (property));
+
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else {
+ _append_string_printf (sparql,
+ "(SELECT \"%s\" FROM \"%s\" WHERE ID = ",
+ tracker_property_get_name (property),
+ tracker_property_get_table_name (property));
+
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ sparql->current_state.expression_type = tracker_property_get_data_type (property);
+ }
+
+ _append_string (sparql, ") ");
+
+ return TRUE;
+}
+
+static gboolean
+handle_type_cast (TrackerSparql *sparql,
+ const gchar *function,
+ GError **error)
+{
+ if (g_str_equal (function, XSD_NS "string")) {
+ _append_string (sparql, "CAST (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, "AS TEXT) ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (g_str_equal (function, XSD_NS "integer")) {
+ _append_string (sparql, "CAST (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, "AS INTEGER) ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, XSD_NS "double")) {
+ _append_string (sparql, "CAST (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, "AS REAL) ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else {
+ _raise (PARSE, "Unhandled cast conversion", function);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+handle_xpath_function (TrackerSparql *sparql,
+ const gchar *function,
+ GError **error)
+{
+ if (g_str_equal (function, FN_NS "lower-case")) {
+ _append_string (sparql, "SparqlLowerCase (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ } else if (g_str_equal (function, FN_NS "upper-case")) {
+ _append_string (sparql, "SparqlUpperCase (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ } else if (g_str_equal (function, FN_NS "contains")) {
+ /* contains('A','B') => 'A' GLOB '*B*' */
+ _step (sparql);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "(");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, " GLOB '*' || ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _append_string (sparql, " || '*') ");
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (g_str_equal (function, FN_NS "starts-with")) {
+ gchar buf[6] = { 0 };
+ TrackerParserNode *node;
+
+ /* strstarts('A','B') => 'A' BETWEEN 'B' AND 'B\u0010fffd'
+ * 0010fffd always sorts last.
+ */
+
+ _step (sparql);
+ _append_string (sparql, "( ");
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, "BETWEEN ");
+
+ node = sparql->current_state.node;
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _append_string (sparql, "AND ");
+
+ /* Evaluate the same expression node again */
+ sparql->current_state.node = node;
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ g_unichar_to_utf8 (TRACKER_COLLATION_LAST_CHAR, buf);
+ _append_string_printf (sparql, "|| '%s') ", buf);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (g_str_equal (function, FN_NS "ends-with")) {
+ /* strends('A','B') => 'A' GLOB '*B' */
+ _step (sparql);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "(");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, " GLOB '*' || ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (g_str_equal (function, FN_NS "substring")) {
+ _append_string (sparql, "SUBSTR (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (g_str_equal (function, FN_NS "concat")) {
+ const gchar *old_sep;
+
+ old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, " || ");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (g_str_equal (function, FN_NS "string-join")) {
+ _append_string (sparql, "SparqlStringJoin (");
+ _step (sparql);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+
+ if (!_check_in_rule (sparql, NAMED_RULE_ArgList))
+ _raise (PARSE, "List of strings to join must be surrounded by parentheses", "fn:string-join");
+
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+
+ while (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (g_str_equal (function, FN_NS "replace")) {
+ _append_string (sparql, "SparqlReplace (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (g_str_equal (function, FN_NS "year-from-dateTime")) {
+ _step (sparql);
+ if (!helper_translate_date (sparql, "%Y", error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, FN_NS "month-from-dateTime")) {
+ _step (sparql);
+ if (!helper_translate_date (sparql, "%m", error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, FN_NS "day-from-dateTime")) {
+ _step (sparql);
+ if (!helper_translate_date (sparql, "%d", error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, FN_NS "hours-from-dateTime")) {
+ _step (sparql);
+ if (!helper_translate_time (sparql, TIME_FORMAT_HOURS, error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, FN_NS "minutes-from-dateTime")) {
+ _step (sparql);
+ if (!helper_translate_time (sparql, TIME_FORMAT_MINUTES, error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, FN_NS "seconds-from-dateTime")) {
+ _step (sparql);
+ if (!helper_translate_time (sparql, TIME_FORMAT_SECONDS, error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, FN_NS "timezone-from-dateTime")) {
+ TrackerVariable *variable;
+
+ _step (sparql);
+ _append_string (sparql, "( ");
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ variable = _last_node_variable (sparql);
+
+ if (!variable) {
+ _raise (PARSE, "Expected variable", "fn:timezone-from-dateTime");
+ } else {
+ _append_string_printf (sparql, " - %s ",
+ tracker_variable_get_sql_expression (variable));
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else {
+ _raise (PARSE, "Unknown XPath function", function);
+ }
+
+ return TRUE;
+}
+
+static TrackerVariable *
+find_fts_variable (TrackerSparql *sparql,
+ TrackerParserNode *node,
+ const gchar *suffix)
+{
+ TrackerParserNode *var = NULL;
+
+ node = tracker_sparql_parser_tree_find_next (node, TRUE);
+
+ if (!_accept_token (&node, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS, NULL))
+ return NULL;
+
+ if (_accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR1, &var) ||
+ _accept_token (&node, RULE_TYPE_TERMINAL, TERMINAL_TYPE_VAR2, &var)) {
+ TrackerVariable *variable;
+ gchar *node_var, *full;
+
+ node_var = _extract_node_string (var, sparql);
+ full = g_strdup_printf ("%s:%s", node_var, suffix);
+ variable = _ensure_variable (sparql, full);
+ g_free (full);
+ g_free (node_var);
+
+ return variable;
+ }
+
+ return NULL;
+}
+
+static gboolean
+handle_custom_function (TrackerSparql *sparql,
+ const gchar *function,
+ GError **error)
+{
+ TrackerVariable *variable;
+ TrackerParserNode *node;
+
+ if (g_str_equal (function, TRACKER_NS "case-fold")) {
+ _append_string (sparql, "SparqlCaseFold (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ } else if (g_str_equal (function, TRACKER_NS "title-order")) {
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, "COLLATE " TRACKER_TITLE_COLLATION_NAME " ");
+ } else if (g_str_equal (function, TRACKER_NS "ascii-lower-case")) {
+ _append_string (sparql, "lower (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ } else if (g_str_equal (function, TRACKER_NS "normalize")) {
+ _append_string (sparql, "SparqlNormalize (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ } else if (g_str_equal (function, TRACKER_NS "unaccent")) {
+ _append_string (sparql, "SparqlUnaccent (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ } else if (g_str_equal (function, TRACKER_NS "id")) {
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+
+ if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_RESOURCE)
+ _raise (PARSE, "Expected resource", "tracker:id");
+
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, TRACKER_NS "uri")) {
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+
+ if (sparql->current_state.expression_type != TRACKER_PROPERTY_TYPE_INTEGER)
+ _raise (PARSE, "Expected integer ID", "tracker:uri");
+
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_RESOURCE;
+ } else if (g_str_equal (function, TRACKER_NS "cartesian-distance")) {
+ _append_string (sparql, "SparqlCartesianDistance (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (g_str_equal (function, TRACKER_NS "haversine-distance")) {
+ _append_string (sparql, "SparqlHaversineDistance (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (g_str_equal (function, TRACKER_NS "uri-is-parent")) {
+ _append_string (sparql, "SparqlUriIsParent (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (g_str_equal (function, TRACKER_NS "uri-is-descendant")) {
+ _append_string (sparql, "SparqlUriIsDescendant (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (g_str_equal (function, TRACKER_NS "string-from-filename")) {
+ _append_string (sparql, "SparqlStringFromFilename (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (g_str_equal (function, TRACKER_NS "coalesce")) {
+ _append_string (sparql, "COALESCE (");
+ _call_rule (sparql, NAMED_RULE_ArgList, error);
+ _append_string (sparql, ") ");
+ } else if (g_str_equal (function, FTS_NS "rank")) {
+ node = _skip_rule (sparql, NAMED_RULE_ArgList);
+ variable = find_fts_variable (sparql, node, "ftsRank");
+ if (!variable)
+ _raise (PARSE, "Function expects single variable argument", "fts:rank");
+
+ _append_variable_sql (sparql, variable);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (function, FTS_NS "offsets")) {
+ node = _skip_rule (sparql, NAMED_RULE_ArgList);
+ variable = find_fts_variable (sparql, node, "ftsOffsets");
+ if (!variable || !tracker_variable_has_bindings (variable))
+ _raise (PARSE, "Function expects single variable argument", "fts:offsets");
+
+ _append_variable_sql (sparql, variable);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (g_str_equal (function, FTS_NS "snippet")) {
+ node = _skip_rule (sparql, NAMED_RULE_ArgList);
+ variable = find_fts_variable (sparql, node, "ftsSnippet");
+ if (!variable || !tracker_variable_has_bindings (variable))
+ _raise (PARSE, "Function expects variable argument", "fts:snippet");
+
+ _append_variable_sql (sparql, variable);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else {
+ _raise (PARSE, "Unknown function", function);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+handle_function_call (TrackerSparql *sparql,
+ GError **error)
+{
+ gchar *function = _dup_last_string (sparql);
+ gboolean handled;
+
+ if (g_str_has_prefix (function, XSD_NS)) {
+ handled = handle_type_cast (sparql, function, error);
+ } else if (g_str_has_prefix (function, FN_NS)) {
+ handled = handle_xpath_function (sparql, function, error);
+ } else {
+ TrackerOntologies *ontologies;
+ TrackerProperty *property;
+
+ ontologies = tracker_data_manager_get_ontologies (sparql->data_manager);
+ property = tracker_ontologies_get_property_by_uri (ontologies, function);
+
+ if (property) {
+ handled = handle_property_function (sparql, property, error);
+ } else {
+ handled = handle_custom_function (sparql, function, error);
+ }
+ }
+
+ g_free (function);
+
+ return handled;
+}
+
+static gboolean
+translate_iriOrFunction (TrackerSparql *sparql,
+ GError **error)
+{
+ gboolean handled = TRUE;
+
+ /* iriOrFunction ::= iri ArgList?
+ */
+ _call_rule (sparql, NAMED_RULE_iri, error);
+
+ if (_check_in_rule (sparql, NAMED_RULE_ArgList)) {
+ handled = handle_function_call (sparql, error);
+ } else {
+ TrackerBinding *binding;
+
+ binding = _convert_terminal (sparql);
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (binding));
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+ g_object_unref (binding);
+ }
+
+ return handled;
+}
+
+static gboolean
+translate_BrackettedExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* BrackettedExpression ::= '(' Expression ')'
+ *
+ * TRACKER EXTENSION:
+ * SubSelect is accepted too, thus the grammar results in:
+ * '(' ( Expression | SubSelect) ')'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "(");
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_Expression:
+ case NAMED_RULE_SubSelect:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+
+ return TRUE;
+}
+
+static gboolean
+helper_translate_date (TrackerSparql *sparql,
+ const gchar *format,
+ GError **error)
+{
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string_printf (sparql, "strftime (\"%s\", ", format);
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ", \"unixepoch\") ");
+
+ return TRUE;
+}
+
+static gboolean
+helper_translate_time (TrackerSparql *sparql,
+ guint format,
+ GError **error)
+{
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+
+ switch (format) {
+ case TIME_FORMAT_SECONDS:
+ _append_string (sparql, " % 60 ");
+ break;
+ case TIME_FORMAT_MINUTES:
+ _append_string (sparql, " / 60 % 60 ");
+ break;
+ case TIME_FORMAT_HOURS:
+ _append_string (sparql, " / 3600 % 24 ");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_BuiltInCall (TrackerSparql *sparql,
+ GError **error)
+{
+ const gchar *old_sep;
+
+ if (_check_in_rule (sparql, NAMED_RULE_Aggregate)) {
+ _call_rule (sparql, NAMED_RULE_Aggregate, error);
+ } else if (_check_in_rule (sparql, NAMED_RULE_RegexExpression)) {
+ _call_rule (sparql, NAMED_RULE_RegexExpression, error);
+ } else if (_check_in_rule (sparql, NAMED_RULE_ExistsFunc)) {
+ _call_rule (sparql, NAMED_RULE_ExistsFunc, error);
+ } else if (_check_in_rule (sparql, NAMED_RULE_NotExistsFunc)) {
+ _call_rule (sparql, NAMED_RULE_NotExistsFunc, error);
+ } else if (_check_in_rule (sparql, NAMED_RULE_SubstringExpression)) {
+ _call_rule (sparql, NAMED_RULE_SubstringExpression, error);
+ } else if (_check_in_rule (sparql, NAMED_RULE_StrReplaceExpression)) {
+ _call_rule (sparql, NAMED_RULE_StrReplaceExpression, error);
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STR)) {
+ TrackerStringBuilder *str, *old;
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+
+ convert_expression_to_string (sparql, sparql->current_state.expression_type);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ tracker_sparql_swap_builder (sparql, old);
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DATATYPE)) {
+ _unimplemented ("DATATYPE");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_IRI)) {
+ _unimplemented ("IRI");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_URI)) {
+ _unimplemented ("URI");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ABS)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "ABS (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_CEIL)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlCeil (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_FLOOR)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlFloor (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ROUND)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "ROUND (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRLEN)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "LENGTH (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UCASE)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlUpperCase (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_LCASE)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlLowerCase (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ENCODE_FOR_URI)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlEncodeForUri (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_YEAR)) {
+ if (!helper_translate_date (sparql, "%Y", error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_MONTH)) {
+ if (!helper_translate_date (sparql, "%m", error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DAY)) {
+ if (!helper_translate_date (sparql, "%d", error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_HOURS)) {
+ if (!helper_translate_time (sparql, TIME_FORMAT_HOURS, error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_MINUTES)) {
+ if (!helper_translate_time (sparql, TIME_FORMAT_MINUTES, error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SECONDS)) {
+ if (!helper_translate_time (sparql, TIME_FORMAT_SECONDS, error))
+ return FALSE;
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_TIMEZONE)) {
+ _unimplemented ("TIMEZONE");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_TZ)) {
+ _unimplemented ("TZ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_MD5)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlChecksum (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ", \"md5\") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA1)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlChecksum (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ", \"sha1\") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA256)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlChecksum (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ", \"sha256\") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA384)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlChecksum (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ", \"sha384\") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SHA512)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlChecksum (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ", \"sha512\") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISIRI) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISURI)) {
+ TrackerBinding *binding;
+ const gchar *str;
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ str = (sparql->current_state.expression_type == TRACKER_PROPERTY_TYPE_RESOURCE) ? "1" : "0";
+
+ binding = tracker_literal_binding_new (str, NULL);
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (binding));
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISBLANK)) {
+ _unimplemented ("ISBLANK");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISLITERAL)) {
+ _unimplemented ("ISLITERAL");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_ISNUMERIC)) {
+ _unimplemented ("ISNUMERIC");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_LANGMATCHES)) {
+ _unimplemented ("LANGMATCHES");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_CONTAINS)) {
+ /* contains('A','B') => 'A' GLOB '*B*' */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "(");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, " GLOB '*' || ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _append_string (sparql, " || '*') ");
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRSTARTS)) {
+ gchar buf[6] = { 0 };
+ TrackerParserNode *node;
+
+ /* strstarts('A','B') => 'A' BETWEEN 'B' AND 'B\u0010fffd'
+ * 0010fffd always sorts last.
+ */
+ _append_string (sparql, "( ");
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, "BETWEEN ");
+
+ node = sparql->current_state.node;
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _append_string (sparql, "AND ");
+
+ /* Evaluate the same expression node again */
+ sparql->current_state.node = node;
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ g_unichar_to_utf8 (TRACKER_COLLATION_LAST_CHAR, buf);
+ _append_string_printf (sparql, "|| '%s') ", buf);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRENDS)) {
+ /* strends('A','B') => 'A' GLOB '*B' */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "(");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, " GLOB '*' || ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRBEFORE)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlStringBefore (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRAFTER)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlStringAfter (");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRLANG)) {
+ _unimplemented ("STRLANG");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRDT)) {
+ _unimplemented ("STRDT");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SAMETERM)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, " ( ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, " = ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, " ) ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_IF)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "CASE ");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, "WHEN 1 THEN ");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, "WHEN 0 THEN ");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, "ELSE NULL END ");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_BOUND)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "(");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, "IS NOT NULL) ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_BNODE)) {
+ _unimplemented ("BNODE");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_RAND)) {
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+ _append_string (sparql, "SparqlRand() ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_NOW)) {
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+ _append_string (sparql, "strftime('%s', 'now') ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DATETIME;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_UUID)) {
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+ _unimplemented ("UUID");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_STRUUID)) {
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_NIL);
+ _unimplemented ("STRUUID");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_CONCAT)) {
+ old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, " || ");
+ _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+ tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COALESCE)) {
+ _append_string (sparql, "COALESCE ");
+ old_sep = tracker_sparql_swap_current_expression_list_separator (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_ExpressionList, error);
+ tracker_sparql_swap_current_expression_list_separator (sparql, old_sep);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_RegexExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerStringBuilder *str, *old;
+
+ /* RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_REGEX);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlRegex (");
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ convert_expression_to_string (sparql, sparql->current_state.expression_type);
+ tracker_sparql_swap_builder (sparql, old);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, ", ");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+
+ return TRUE;
+}
+
+static gboolean
+translate_SubstringExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* SubstringExpression ::= 'SUBSTR' '(' Expression ',' Expression ( ',' Expression )? ')'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_SUBSTR);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SUBSTR (");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, ", ");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+
+ return TRUE;
+}
+
+static gboolean
+translate_StrReplaceExpression (TrackerSparql *sparql,
+ GError **error)
+{
+ /* StrReplaceExpression ::= 'REPLACE' '(' Expression ',' Expression ',' Expression ( ',' Expression )? ')'
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_REPLACE);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "SparqlReplace (");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, ", ");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA);
+ _append_string (sparql, ", ");
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+
+ return TRUE;
+}
+
+static gboolean
+translate_ExistsFunc (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerContext *context;
+
+ /* ExistsFunc ::= 'EXISTS' GroupGraphPattern
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_EXISTS);
+ _append_string (sparql, "EXISTS (");
+
+ context = tracker_select_context_new ();
+ tracker_sparql_push_context (sparql, context);
+
+ _call_rule (sparql, NAMED_RULE_GroupGraphPattern, error);
+
+ tracker_sparql_pop_context (sparql, FALSE);
+
+ if (!_check_undefined_variables (sparql, TRACKER_SELECT_CONTEXT (context), error))
+ return FALSE;
+
+ _append_string (sparql, ") ");
+
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+
+ return TRUE;
+}
+
+static gboolean
+translate_NotExistsFunc (TrackerSparql *sparql,
+ GError **error)
+{
+ /* NotExistsFunc ::= 'NOT' 'EXISTS' GroupGraphPattern
+ */
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_NOT);
+ _append_string (sparql, "NOT ");
+
+ return translate_ExistsFunc (sparql, error);
+}
+
+static gboolean
+translate_Aggregate (TrackerSparql *sparql,
+ GError **error)
+{
+ /* Aggregate ::= 'COUNT' '(' 'DISTINCT'? ( '*' | Expression ) ')'
+ * | 'SUM' '(' 'DISTINCT'? Expression ')'
+ * | 'MIN' '(' 'DISTINCT'? Expression ')'
+ * | 'MAX' '(' 'DISTINCT'? Expression ')'
+ * | 'AVG' '(' 'DISTINCT'? Expression ')'
+ * | 'SAMPLE' '(' 'DISTINCT'? Expression ')'
+ * | 'GROUP_CONCAT' '(' 'DISTINCT'? Expression ( ';' 'SEPARATOR' '=' String )? ')'
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COUNT) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_SUM) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_MIN) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_MAX) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_AVG)) {
+ gchar *last_string = _dup_last_string (sparql);
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ /* Luckily the SQL literals are the same than Sparql's */
+ _append_string (sparql, last_string);
+ _append_string (sparql, "(");
+ g_free (last_string);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT))
+ _append_string (sparql, "DISTINCT ");
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GLOB)) {
+ _append_string (sparql, "* ");
+ } else if (_check_in_rule (sparql, NAMED_RULE_Expression)) {
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_GROUP_CONCAT)) {
+ TrackerStringBuilder *str, *old;
+ gboolean separator = FALSE;
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OPEN_PARENS);
+ _append_string (sparql, "GROUP_CONCAT(");
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DISTINCT))
+ _append_string (sparql, "DISTINCT ");
+
+ str = _append_placeholder (sparql);
+ old = tracker_sparql_swap_builder (sparql, str);
+
+ _call_rule (sparql, NAMED_RULE_Expression, error);
+
+ if (sparql->current_state.expression_type == TRACKER_PROPERTY_TYPE_RESOURCE)
+ convert_expression_to_string (sparql, sparql->current_state.expression_type);
+
+ tracker_sparql_swap_builder (sparql, old);
+
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SEMICOLON)) {
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_SEPARATOR);
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_OP_EQ);
+ separator = TRUE;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_COMMA)) {
+ separator = TRUE;
+ }
+
+ if (separator) {
+ TrackerBinding *binding;
+
+ _append_string (sparql, ", ");
+ _call_rule (sparql, NAMED_RULE_String, error);
+
+ binding = _convert_terminal (sparql);
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (binding));
+ _append_literal_sql (sparql, TRACKER_LITERAL_BINDING (binding));
+ g_object_unref (binding);
+ }
+
+ _expect (sparql, RULE_TYPE_LITERAL, LITERAL_CLOSE_PARENS);
+ _append_string (sparql, ") ");
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_SAMPLE)) {
+ _unimplemented ("SAMPLE");
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_RDFLiteral (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerBinding *binding;
+
+ /* RDFLiteral ::= String ( LANGTAG | ( '^^' iri ) )?
+ */
+ _call_rule (sparql, NAMED_RULE_String, error);
+ binding = _convert_terminal (sparql);
+
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_LANGTAG)) {
+ g_object_unref (binding);
+ _unimplemented ("LANGTAG");
+ } else if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_DOUBLE_CIRCUMFLEX)) {
+ gchar *cast;
+
+ _call_rule (sparql, NAMED_RULE_iri, error);
+ cast = _dup_last_string (sparql);
+
+ if (g_str_equal (cast, XSD_NS "boolean")) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ } else if (g_str_equal (cast, XSD_NS "integer") ||
+ g_str_equal (cast, XSD_NS "nonPositiveInteger") ||
+ g_str_equal (cast, XSD_NS "negativeInteger") ||
+ g_str_equal (cast, XSD_NS "long") ||
+ g_str_equal (cast, XSD_NS "int") ||
+ g_str_equal (cast, XSD_NS "short") ||
+ g_str_equal (cast, XSD_NS "byte") ||
+ g_str_equal (cast, XSD_NS "nonNegativeInteger") ||
+ g_str_equal (cast, XSD_NS "unsignedLong") ||
+ g_str_equal (cast, XSD_NS "unsignedInt") ||
+ g_str_equal (cast, XSD_NS "unsignedShort") ||
+ g_str_equal (cast, XSD_NS "unsignedByte") ||
+ g_str_equal (cast, XSD_NS "positiveInteger")) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (g_str_equal (cast, XSD_NS "double")) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (g_str_equal (cast, XSD_NS "date")) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DATE;
+ } else if (g_str_equal (cast, XSD_NS "dateTime")) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DATETIME;
+ } else {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ }
+
+ g_free (cast);
+ }
+
+ tracker_binding_set_data_type (binding, sparql->current_state.expression_type);
+
+ if (sparql->current_state.type == TRACKER_SPARQL_TYPE_SELECT) {
+ tracker_select_context_add_literal_binding (TRACKER_SELECT_CONTEXT (sparql->context),
+ TRACKER_LITERAL_BINDING (binding));
+ }
+
+ g_object_unref (binding);
+
+ return TRUE;
+}
+
+static gboolean
+translate_NumericLiteral (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerGrammarNamedRule rule;
+
+ /* NumericLiteral ::= NumericLiteralUnsigned | NumericLiteralPositive | NumericLiteralNegative
+ */
+ rule = _current_rule (sparql);
+
+ switch (rule) {
+ case NAMED_RULE_NumericLiteralUnsigned:
+ case NAMED_RULE_NumericLiteralPositive:
+ case NAMED_RULE_NumericLiteralNegative:
+ _call_rule (sparql, rule, error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_NumericLiteralUnsigned (TrackerSparql *sparql,
+ GError **error)
+{
+ /* NumericLiteralUnsigned ::= INTEGER | DECIMAL | DOUBLE
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DOUBLE) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DECIMAL)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_NumericLiteralPositive (TrackerSparql *sparql,
+ GError **error)
+{
+ /* NumericLiteralPositive ::= INTEGER_POSITIVE | DECIMAL_POSITIVE | DOUBLE_POSITIVE
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER_POSITIVE)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DECIMAL_POSITIVE) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DOUBLE_POSITIVE)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_NumericLiteralNegative (TrackerSparql *sparql,
+ GError **error)
+{
+ /* NumericLiteralNegative ::= INTEGER_NEGATIVE | DECIMAL_NEGATIVE | DOUBLE_NEGATIVE
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_INTEGER_NEGATIVE)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_INTEGER;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DECIMAL_NEGATIVE) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_DOUBLE_NEGATIVE)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_DOUBLE;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_BooleanLiteral (TrackerSparql *sparql,
+ GError **error)
+{
+ /* BooleanLiteral ::= 'true' | 'false'
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+ if (_accept (sparql, RULE_TYPE_LITERAL, LITERAL_TRUE) ||
+ _accept (sparql, RULE_TYPE_LITERAL, LITERAL_FALSE)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_BOOLEAN;
+ return TRUE;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_String (TrackerSparql *sparql,
+ GError **error)
+{
+ /* String ::= STRING_LITERAL1 | STRING_LITERAL2 | STRING_LITERAL_LONG1 | STRING_LITERAL_LONG2
+ *
+ * TRACKER EXTENSION:
+ * The terminal PARAMETERIZED_VAR is additionally accepted
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL1) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL2) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL_LONG1) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_STRING_LITERAL_LONG2)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_STRING;
+ return TRUE;
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PARAMETERIZED_VAR)) {
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_UNKNOWN;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_iri (TrackerSparql *sparql,
+ GError **error)
+{
+ /* iri ::= IRIREF | PrefixedName
+ */
+ if (_check_in_rule (sparql, NAMED_RULE_PrefixedName)) {
+ _call_rule (sparql, NAMED_RULE_PrefixedName, error);
+ } else {
+ _expect (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_IRIREF);
+ }
+
+ sparql->current_state.expression_type = TRACKER_PROPERTY_TYPE_RESOURCE;
+
+ return TRUE;
+}
+
+static gboolean
+translate_PrefixedName (TrackerSparql *sparql,
+ GError **error)
+{
+ /* PrefixedName ::= PNAME_LN | PNAME_NS
+ */
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PNAME_LN) ||
+ _accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_PNAME_NS)) {
+ return TRUE;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ return TRUE;
+}
+
+static gboolean
+translate_BlankNode (TrackerSparql *sparql,
+ GError **error)
+{
+ TrackerDBInterface *iface;
+ gchar *bnode_id;
+ TrackerVariable *var;
+
+ /* BlankNode ::= BLANK_NODE_LABEL | ANON
+ */
+ g_assert (sparql->current_state.token != NULL);
+
+ iface = tracker_data_manager_get_writable_db_interface (sparql->data_manager);
+
+ if (sparql->current_state.type != TRACKER_SPARQL_TYPE_SELECT) {
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_ANON)) {
+ bnode_id = tracker_data_query_unused_uuid (sparql->data_manager, iface);
+ tracker_token_literal_init (sparql->current_state.token, bnode_id);
+ g_free (bnode_id);
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_BLANK_NODE_LABEL)) {
+ gchar *str;
+
+ str = _dup_last_string (sparql);
+
+ if (sparql->current_state.blank_node_map) {
+ bnode_id = g_hash_table_lookup (sparql->current_state.blank_node_map, str);
+
+ if (!bnode_id) {
+ bnode_id = tracker_data_query_unused_uuid (sparql->data_manager, iface);
+ g_hash_table_insert (sparql->current_state.blank_node_map,
+ g_strdup (str), bnode_id);
+ g_variant_builder_add (sparql->blank_nodes, "{ss}", str, bnode_id);
+ }
+
+ tracker_token_literal_init (sparql->current_state.token, bnode_id);
+ } else {
+ tracker_token_literal_init (sparql->current_state.token, str);
+ }
+
+ g_free (str);
+ } else {
+ g_assert_not_reached ();
+ }
+ } else {
+ if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_ANON)) {
+ var = tracker_select_context_add_generated_variable (TRACKER_SELECT_CONTEXT (sparql->context));
+ } else if (_accept (sparql, RULE_TYPE_TERMINAL, TERMINAL_TYPE_BLANK_NODE_LABEL)) {
+ gchar *str, *var_name;
+
+ str = _dup_last_string (sparql);
+ var_name = g_strdup_printf ("BlankNode:%s", str);
+ var = _ensure_variable (sparql, var_name);
+ g_free (var_name);
+ g_free (str);
+ } else {
+ g_assert_not_reached ();
+ }
+
+ tracker_token_variable_init (sparql->current_state.token, var);
+ }
+
+ return TRUE;
+}
+
+const RuleTranslationFunc rule_translation_funcs[N_NAMED_RULES] = {
+ NULL, /* Grammar parser entry points */
+ NULL,
+ translate_Query,
+ translate_Update,
+ translate_SelectClause,
+ translate_Prologue,
+ translate_BaseDecl,
+ translate_PrefixDecl,
+ translate_SelectQuery,
+ translate_SubSelect,
+ translate_ConstructQuery,
+ translate_DescribeQuery,
+ translate_AskQuery,
+ translate_DatasetClause,
+ translate_DefaultGraphClause,
+ translate_NamedGraphClause,
+ translate_SourceSelector,
+ translate_WhereClause,
+ translate_SolutionModifier,
+ translate_GroupClause,
+ translate_GroupCondition,
+ translate_HavingClause,
+ translate_HavingCondition,
+ translate_OrderClause,
+ translate_OrderCondition,
+ translate_LimitOffsetClauses,
+ translate_LimitClause,
+ translate_OffsetClause,
+ translate_ValuesClause,
+ translate_Update1,
+ translate_Load,
+ translate_Clear,
+ translate_Drop,
+ translate_Create,
+ translate_Add,
+ translate_Move,
+ translate_Copy,
+ translate_InsertData,
+ translate_DeleteData,
+ translate_DeleteWhere,
+ translate_Modify,
+ translate_DeleteClause,
+ translate_InsertClause,
+ translate_UsingClause,
+ translate_GraphOrDefault,
+ translate_GraphRefAll,
+ translate_GraphRef,
+ translate_QuadPattern,
+ translate_QuadData,
+ translate_Quads,
+ translate_QuadsNotTriples,
+ translate_TriplesTemplate,
+ translate_GroupGraphPatternSub,
+ translate_TriplesBlock,
+ translate_GraphPatternNotTriples,
+ translate_OptionalGraphPattern,
+ translate_GraphGraphPattern,
+ translate_ServiceGraphPattern,
+ translate_Bind,
+ translate_InlineData,
+ translate_DataBlock,
+ translate_InlineDataOneVar,
+ translate_InlineDataFull,
+ translate_DataBlockValue,
+ translate_MinusGraphPattern,
+ translate_GroupOrUnionGraphPattern,
+ translate_Filter,
+ translate_Constraint,
+ translate_FunctionCall,
+ translate_ArgList,
+ translate_ExpressionList,
+ translate_ConstructTemplate,
+ translate_ConstructTriples,
+ translate_TriplesSameSubject,
+ translate_GroupGraphPattern,
+ translate_PropertyList,
+ translate_PropertyListNotEmpty,
+ translate_Verb,
+ translate_ObjectList,
+ translate_Object,
+ translate_TriplesSameSubjectPath,
+ translate_PropertyListPath,
+ translate_PropertyListPathNotEmpty,
+ translate_VerbPath,
+ translate_VerbSimple,
+ translate_ObjectListPath,
+ translate_ObjectPath,
+ translate_Path,
+ translate_PathAlternative,
+ translate_PathSequence,
+ translate_PathEltOrInverse,
+ translate_PathElt,
+ translate_PathMod,
+ translate_PathPrimary,
+ translate_PathNegatedPropertySet,
+ translate_PathOneInPropertySet,
+ translate_Integer,
+ translate_TriplesNode,
+ translate_BlankNodePropertyList,
+ translate_TriplesNodePath,
+ translate_BlankNodePropertyListPath,
+ translate_Collection,
+ translate_CollectionPath,
+ translate_GraphNode,
+ translate_GraphNodePath,
+ translate_VarOrTerm,
+ translate_VarOrIri,
+ translate_Var,
+ translate_GraphTerm,
+ translate_Expression,
+ translate_ConditionalOrExpression,
+ translate_ConditionalAndExpression,
+ translate_ValueLogical,
+ translate_RelationalExpression,
+ translate_NumericExpression,
+ translate_AdditiveExpression,
+ translate_MultiplicativeExpression,
+ translate_UnaryExpression,
+ translate_PrimaryExpression,
+ translate_iriOrFunction,
+ translate_BrackettedExpression,
+ translate_BuiltInCall,
+ translate_RegexExpression,
+ translate_SubstringExpression,
+ translate_StrReplaceExpression,
+ translate_ExistsFunc,
+ translate_NotExistsFunc,
+ translate_Aggregate,
+ translate_RDFLiteral,
+ translate_NumericLiteral,
+ translate_NumericLiteralUnsigned,
+ translate_NumericLiteralPositive,
+ translate_NumericLiteralNegative,
+ translate_BooleanLiteral,
+ translate_String,
+ translate_iri,
+ translate_PrefixedName,
+ translate_BlankNode,
+};
+
+static inline gboolean
+_call_rule_func (TrackerSparql *sparql,
+ TrackerGrammarNamedRule named_rule,
+ GError **error)
+{
+ TrackerParserNode *parser_node = sparql->current_state.node;
+ const TrackerGrammarRule *rule;
+ GError *inner_error = NULL;
+ gboolean retval;
+
+ g_assert (named_rule < N_NAMED_RULES);
+ g_assert (rule_translation_funcs[named_rule]);
+
+ /* Empty rules pass */
+ if (!parser_node ||
+ !tracker_parser_node_get_extents (parser_node, NULL, NULL))
+ return TRUE;
+
+ rule = tracker_parser_node_get_rule (parser_node);
+
+ if (!tracker_grammar_rule_is_a (rule, RULE_TYPE_RULE, named_rule))
+ return TRUE;
+
+ tracker_sparql_iter_next (sparql);
+
+ retval = rule_translation_funcs[named_rule] (sparql, &inner_error);
+
+ if (!retval) {
+ if (!inner_error) {
+ g_error ("Translation rule '%s' returns FALSE, but no error",
+ rule->string);
+ }
+
+ g_assert (inner_error != NULL);
+ g_propagate_error (error, inner_error);
+ }
+
+ return retval;
+}
+
+static void
+tracker_sparql_class_init (TrackerSparqlClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = tracker_sparql_finalize;
+}
+
+static void
+tracker_sparql_init (TrackerSparql *sparql)
+{
+ sparql->prefix_map = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, g_free);
+ sparql->parameters = g_hash_table_new (g_str_hash, g_str_equal);
+ sparql->var_names = g_ptr_array_new_with_free_func (g_free);
+ sparql->var_types = g_array_new (FALSE, FALSE, sizeof (TrackerPropertyType));
+ sparql->cacheable = TRUE;
+}
+
+TrackerSparql*
+tracker_sparql_new (TrackerDataManager *manager,
+ const gchar *query)
+{
+ TrackerNodeTree *tree;
+ TrackerSparql *sparql;
+
+ g_return_val_if_fail (TRACKER_IS_DATA_MANAGER (manager), NULL);
+ g_return_val_if_fail (query != NULL, NULL);
+
+ sparql = g_object_new (TRACKER_TYPE_SPARQL, NULL);
+ sparql->data_manager = g_object_ref (manager);
+ sparql->sparql = tracker_unescape_unichars (query, -1);
+
+ tree = tracker_sparql_parse_query (sparql->sparql, -1, NULL,
+ &sparql->parser_error);
+ if (tree) {
+ sparql->tree = tree;
+ sparql->sql = tracker_string_builder_new ();
+
+ sparql->current_state.node = tracker_node_tree_get_root (sparql->tree);
+ sparql->current_state.sql = sparql->sql;
+ }
+
+ return sparql;
+}
+
+static TrackerDBStatement *
+prepare_query (TrackerDBInterface *iface,
+ TrackerStringBuilder *str,
+ GPtrArray *literals,
+ GHashTable *parameters,
+ gboolean cached,
+ GError **error)
+{
+ TrackerDBStatement *stmt;
+ gchar *query;
+ guint i;
+
+ query = tracker_string_builder_to_string (str);
+ stmt = tracker_db_interface_create_statement (iface,
+ cached ?
+ TRACKER_DB_STATEMENT_CACHE_TYPE_SELECT :
+ TRACKER_DB_STATEMENT_CACHE_TYPE_NONE,
+ error, "%s", query);
+ g_free (query);
+
+ if (!stmt || !literals)
+ return stmt;
+
+ for (i = 0; i < literals->len; i++) {
+ TrackerLiteralBinding *binding;
+ TrackerPropertyType prop_type;
+
+ binding = g_ptr_array_index (literals, i);
+ prop_type = TRACKER_BINDING (binding)->data_type;
+
+ if (TRACKER_IS_PARAMETER_BINDING (binding)) {
+ const gchar *name;
+ GValue *value = NULL;
+
+ name = TRACKER_PARAMETER_BINDING (binding)->name;
+
+ if (parameters)
+ value = g_hash_table_lookup (parameters, name);
+
+ if (value) {
+ tracker_db_statement_bind_value (stmt, i, value);
+ } else {
+ g_set_error (error, TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_TYPE,
+ "Parameter '%s' has no given value", name);
+ }
+ } else if (prop_type == TRACKER_PROPERTY_TYPE_BOOLEAN) {
+ if (g_str_equal (binding->literal, "1") ||
+ g_ascii_strcasecmp (binding->literal, "true") == 0) {
+ tracker_db_statement_bind_int (stmt, i, 1);
+ } else if (g_str_equal (binding->literal, "0") ||
+ g_ascii_strcasecmp (binding->literal, "false") == 0) {
+ tracker_db_statement_bind_int (stmt, i, 0);
+ } else {
+ g_set_error (error, TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_TYPE,
+ "'%s' is not a valid boolean",
+ binding->literal);
+ g_object_unref (stmt);
+ return NULL;
+ }
+ } else if (prop_type == TRACKER_PROPERTY_TYPE_DATE) {
+ gchar *full_str;
+ gdouble datetime;
+
+ full_str = g_strdup_printf ("%sT00:00:00Z", binding->literal);
+ datetime = tracker_string_to_date (full_str, NULL, error);
+ g_free (full_str);
+
+ if (datetime < 0) {
+ g_object_unref (stmt);
+ return NULL;
+ }
+
+ tracker_db_statement_bind_int (stmt, i, (int) datetime);
+ } else if (prop_type == TRACKER_PROPERTY_TYPE_DATETIME) {
+ gdouble datetime;
+
+ datetime = tracker_string_to_date (binding->literal, NULL, error);
+ if (datetime < 0) {
+ g_object_unref (stmt);
+ return NULL;
+ }
+
+ tracker_db_statement_bind_double (stmt, i, datetime);
+ } else if (prop_type == TRACKER_PROPERTY_TYPE_INTEGER) {
+ tracker_db_statement_bind_int (stmt, i, atoi (binding->literal));
+ } else {
+ tracker_db_statement_bind_text (stmt, i, binding->literal);
+ }
+ }
+
+ return stmt;
+}
+
+TrackerSparqlCursor *
+tracker_sparql_execute_cursor (TrackerSparql *sparql,
+ GHashTable *parameters,
+ GError **error)
+{
+ TrackerDBStatement *stmt;
+ TrackerDBInterface *iface;
+ TrackerDBCursor *cursor;
+ TrackerPropertyType *types;
+ const gchar * const *names;
+ guint n_types, n_names;
+
+ if (sparql->parser_error) {
+ g_propagate_error (error, sparql->parser_error);
+ return NULL;
+ }
+
+ if (!_call_rule_func (sparql, NAMED_RULE_Query, error))
+ return NULL;
+
+ iface = tracker_data_manager_get_db_interface (sparql->data_manager);
+ stmt = prepare_query (iface, sparql->sql,
+ TRACKER_SELECT_CONTEXT (sparql->context)->literal_bindings,
+ parameters,
+ sparql->cacheable,
+ error);
+ if (!stmt)
+ return NULL;
+
+ 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,
+ error);
+ g_object_unref (stmt);
+
+ return TRACKER_SPARQL_CURSOR (cursor);
+}
+
+TrackerSparql *
+tracker_sparql_new_update (TrackerDataManager *manager,
+ const gchar *query)
+{
+ TrackerNodeTree *tree;
+ TrackerSparql *sparql;
+ gsize len;
+
+ g_return_val_if_fail (TRACKER_IS_DATA_MANAGER (manager), NULL);
+ g_return_val_if_fail (query != NULL, NULL);
+
+ sparql = g_object_new (TRACKER_TYPE_SPARQL, NULL);
+ sparql->data_manager = g_object_ref (manager);
+ sparql->sparql = tracker_unescape_unichars (query, -1);
+
+ tree = tracker_sparql_parse_update (sparql->sparql, -1, &len,
+ &sparql->parser_error);
+
+ if (tree && !sparql->parser_error && query[len] != '\0') {
+ tracker_node_tree_free (tree);
+ tree = NULL;
+ g_set_error (&sparql->parser_error,
+ TRACKER_SPARQL_ERROR,
+ TRACKER_SPARQL_ERROR_PARSE,
+ "Parser error at byte %ld: Expected NIL character",
+ len);
+ }
+
+ if (tree) {
+ sparql->tree = tree;
+ sparql->sql = tracker_string_builder_new ();
+
+ sparql->current_state.node = tracker_node_tree_get_root (sparql->tree);
+ sparql->current_state.sql = sparql->sql;
+ }
+
+ return sparql;
+}
+
+GVariant *
+tracker_sparql_execute_update (TrackerSparql *sparql,
+ gboolean blank,
+ GError **error)
+{
+ if (sparql->parser_error) {
+ g_propagate_error (error, sparql->parser_error);
+ return NULL;
+ }
+
+ if (blank)
+ sparql->blank_nodes = g_variant_builder_new (G_VARIANT_TYPE ("aaa{ss}"));
+
+ if (!_call_rule_func (sparql, NAMED_RULE_Update, error))
+ return NULL;
+
+ if (sparql->blank_nodes) {
+ GVariant *blank_nodes;
+
+ blank_nodes = g_variant_builder_end (sparql->blank_nodes);
+ return g_variant_ref_sink (blank_nodes);
+ }
+
+ return NULL;
+}
diff --git a/src/libtracker-data/tracker-sparql.h b/src/libtracker-data/tracker-sparql.h
new file mode 100644
index 000000000..e86665c51
--- /dev/null
+++ b/src/libtracker-data/tracker-sparql.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+
+#ifndef __TRACKER_SPARQL_H__
+#define __TRACKER_SPARQL_H__
+
+#if !defined (__LIBTRACKER_DATA_INSIDE__) && !defined (TRACKER_COMPILATION)
+#error "only <libtracker-data/tracker-data.h> must be included directly."
+#endif
+
+#include <glib.h>
+#include "tracker-data-manager.h"
+
+#define TRACKER_TYPE_SPARQL (tracker_sparql_get_type ())
+G_DECLARE_FINAL_TYPE (TrackerSparql, tracker_sparql,
+ TRACKER, SPARQL, GObject)
+
+TrackerSparql * tracker_sparql_new (TrackerDataManager *manager,
+ const gchar *sparql);
+
+TrackerSparqlCursor * tracker_sparql_execute_cursor (TrackerSparql *sparql,
+ GHashTable *parameters,
+ GError **error);
+
+TrackerSparql * tracker_sparql_new_update (TrackerDataManager *manager,
+ const gchar *query);
+GVariant * tracker_sparql_execute_update (TrackerSparql *sparql,
+ gboolean blank,
+ GError **error);
+
+#endif /* __TRACKER_SPARQL_H__ */
diff --git a/src/libtracker-data/tracker-string-builder.c b/src/libtracker-data/tracker-string-builder.c
new file mode 100644
index 000000000..b43d1a29c
--- /dev/null
+++ b/src/libtracker-data/tracker-string-builder.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2017-2018, Carlos Garnacho
+ *
+ * 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 <string.h>
+
+#include "tracker-string-builder.h"
+
+typedef struct _TrackerStringChunk TrackerStringChunk;
+typedef struct _TrackerStringElement TrackerStringElement;
+
+struct _TrackerStringChunk
+{
+ gchar *string;
+ gsize allocated_size;
+ gsize len;
+};
+
+enum {
+ ELEM_TYPE_STRING,
+ ELEM_TYPE_BUILDER
+};
+
+struct _TrackerStringElement
+{
+ guint type;
+ union {
+ TrackerStringChunk *chunk;
+ TrackerStringBuilder *builder;
+ } data;
+};
+
+struct _TrackerStringBuilder
+{
+ GArray *elems;
+};
+
+static void
+free_string_chunk (TrackerStringChunk *chunk)
+{
+ g_free (chunk->string);
+ g_free (chunk);
+}
+
+static void
+free_string_element (gpointer data)
+{
+ TrackerStringElement *elem = data;
+
+ if (elem->type == ELEM_TYPE_STRING)
+ free_string_chunk (elem->data.chunk);
+ else if (elem->type == ELEM_TYPE_BUILDER)
+ tracker_string_builder_free (elem->data.builder);
+}
+
+TrackerStringBuilder *
+tracker_string_builder_new (void)
+{
+ TrackerStringBuilder *builder;
+
+ builder = g_slice_new0 (TrackerStringBuilder);
+ builder->elems = g_array_new (FALSE, TRUE, sizeof (TrackerStringElement));
+ g_array_set_clear_func (builder->elems, free_string_element);
+
+ return builder;
+}
+
+void
+tracker_string_builder_free (TrackerStringBuilder *builder)
+{
+ g_array_free (builder->elems, TRUE);
+ g_slice_free (TrackerStringBuilder, builder);
+}
+
+TrackerStringBuilder *
+tracker_string_builder_append_placeholder (TrackerStringBuilder *builder)
+{
+ TrackerStringBuilder *child;
+ TrackerStringElement elem;
+
+ child = tracker_string_builder_new ();
+
+ elem.type = ELEM_TYPE_BUILDER;
+ elem.data.builder = child;
+ g_array_append_val (builder->elems, elem);
+
+ return child;
+}
+
+TrackerStringBuilder *
+tracker_string_builder_prepend_placeholder (TrackerStringBuilder *builder)
+{
+ TrackerStringBuilder *child;
+ TrackerStringElement elem;
+
+ child = tracker_string_builder_new ();
+
+ elem.type = ELEM_TYPE_BUILDER;
+ elem.data.builder = child;
+ g_array_prepend_val (builder->elems, elem);
+
+ return child;
+}
+
+static TrackerStringChunk *
+ensure_last_chunk (TrackerStringBuilder *builder)
+{
+ TrackerStringElement elem;
+ TrackerStringChunk *chunk;
+
+ if (builder->elems->len > 0) {
+ TrackerStringElement *last;
+
+ last = &g_array_index (builder->elems, TrackerStringElement,
+ builder->elems->len - 1);
+ if (last->type == ELEM_TYPE_STRING)
+ return last->data.chunk;
+ }
+
+ chunk = g_new0 (TrackerStringChunk, 1);
+
+ elem.type = ELEM_TYPE_STRING;
+ elem.data.chunk = chunk;
+ g_array_append_val (builder->elems, elem);
+
+ return chunk;
+}
+
+static TrackerStringChunk *
+ensure_first_chunk (TrackerStringBuilder *builder)
+{
+ TrackerStringElement elem;
+ TrackerStringChunk *chunk;
+
+ /* Always create a new element instead of trying to prepend on
+ * the first string chunk. Between memory relocations and memory
+ * fragmentation, we choose the latter. This object is short lived
+ * anyway.
+ */
+ chunk = g_new0 (TrackerStringChunk, 1);
+
+ elem.type = ELEM_TYPE_STRING;
+ elem.data.chunk = chunk;
+ g_array_prepend_val (builder->elems, elem);
+
+ return chunk;
+}
+
+static inline gsize
+fitting_power_of_two (gsize string_len)
+{
+ gsize s = 1;
+
+ while (s <= string_len)
+ s <<= 1;
+
+ return s;
+}
+
+static void
+string_chunk_append (TrackerStringChunk *chunk,
+ const gchar *str,
+ gssize len)
+{
+ if (len < 0)
+ len = strlen (str);
+
+ if (chunk->len + len > chunk->allocated_size) {
+ /* Expand size */
+ gssize new_size = fitting_power_of_two (chunk->len + len);
+
+ g_assert (new_size > chunk->allocated_size);
+ chunk->string = g_realloc (chunk->string, new_size);
+ chunk->allocated_size = new_size;
+ }
+
+ /* String (now) fits in allocated size */
+ strncpy (&chunk->string[chunk->len], str, len);
+ chunk->len += len;
+ g_assert (chunk->len <= chunk->allocated_size);
+}
+
+void
+tracker_string_builder_append (TrackerStringBuilder *builder,
+ const gchar *string,
+ gssize len)
+{
+ TrackerStringChunk *chunk;
+
+ chunk = ensure_last_chunk (builder);
+ string_chunk_append (chunk, string, len);
+}
+
+void
+tracker_string_builder_prepend (TrackerStringBuilder *builder,
+ const gchar *string,
+ gssize len)
+{
+ TrackerStringChunk *chunk;
+
+ chunk = ensure_first_chunk (builder);
+ string_chunk_append (chunk, string, len);
+}
+
+void
+tracker_string_builder_append_valist (TrackerStringBuilder *builder,
+ const gchar *format,
+ va_list args)
+{
+ TrackerStringChunk *chunk;
+ gchar *str;
+
+ str = g_strdup_vprintf (format, args);
+
+ chunk = ensure_last_chunk (builder);
+ string_chunk_append (chunk, str, -1);
+ g_free (str);
+}
+
+void
+tracker_string_builder_prepend_valist (TrackerStringBuilder *builder,
+ const gchar *format,
+ va_list args)
+{
+ TrackerStringChunk *chunk;
+ gchar *str;
+
+ str = g_strdup_vprintf (format, args);
+
+ chunk = ensure_first_chunk (builder);
+ string_chunk_append (chunk, str, -1);
+ g_free (str);
+}
+
+void
+tracker_string_builder_append_printf (TrackerStringBuilder *builder,
+ const gchar *format,
+ ...)
+{
+ va_list varargs;
+
+ va_start (varargs, format);
+ tracker_string_builder_append_valist (builder, format, varargs);
+ va_end (varargs);
+}
+
+void
+tracker_string_builder_prepend_printf (TrackerStringBuilder *builder,
+ const gchar *format,
+ ...)
+{
+ va_list varargs;
+
+ va_start (varargs, format);
+ tracker_string_builder_prepend_valist (builder, format, varargs);
+ va_end (varargs);
+}
+
+static void
+tracker_string_builder_to_gstring (TrackerStringBuilder *builder,
+ GString *str)
+{
+ guint i;
+
+ for (i = 0; i < builder->elems->len; i++) {
+ TrackerStringElement *elem;
+
+ elem = &g_array_index (builder->elems, TrackerStringElement, i);
+
+ if (elem->type == ELEM_TYPE_STRING) {
+ g_string_append_len (str,
+ elem->data.chunk->string,
+ elem->data.chunk->len);
+ } else if (elem->type == ELEM_TYPE_BUILDER) {
+ tracker_string_builder_to_gstring (elem->data.builder,
+ str);
+ }
+ }
+}
+
+gchar *
+tracker_string_builder_to_string (TrackerStringBuilder *builder)
+{
+ GString *str = g_string_new (NULL);
+
+ tracker_string_builder_to_gstring (builder, str);
+
+ return g_string_free (str, FALSE);
+}
+
+gboolean
+tracker_string_builder_is_empty (TrackerStringBuilder *builder)
+{
+ return builder->elems->len == 0;
+}
diff --git a/src/libtracker-data/tracker-string-builder.h b/src/libtracker-data/tracker-string-builder.h
new file mode 100644
index 000000000..dd04ee190
--- /dev/null
+++ b/src/libtracker-data/tracker-string-builder.h
@@ -0,0 +1,37 @@
+#ifndef __TRACKER_STRING_BUILDER_H__
+#define __TRACKER_STRING_BUILDER_H__
+
+#include <glib.h>
+
+typedef struct _TrackerStringBuilder TrackerStringBuilder;
+
+TrackerStringBuilder * tracker_string_builder_new (void);
+void tracker_string_builder_free (TrackerStringBuilder *builder);
+
+TrackerStringBuilder * tracker_string_builder_append_placeholder (TrackerStringBuilder *builder);
+TrackerStringBuilder * tracker_string_builder_prepend_placeholder (TrackerStringBuilder *builder);
+
+void tracker_string_builder_append (TrackerStringBuilder *builder,
+ const gchar *string,
+ gssize len);
+void tracker_string_builder_prepend (TrackerStringBuilder *builder,
+ const gchar *string,
+ gssize len);
+void tracker_string_builder_prepend_valist (TrackerStringBuilder *builder,
+ const gchar *format,
+ va_list args);
+void tracker_string_builder_append_valist (TrackerStringBuilder *builder,
+ const gchar *format,
+ va_list args);
+void tracker_string_builder_append_printf (TrackerStringBuilder *builder,
+ const gchar *format,
+ ...);
+void tracker_string_builder_prepend_printf (TrackerStringBuilder *builder,
+ const gchar *format,
+ ...);
+
+gchar * tracker_string_builder_to_string (TrackerStringBuilder *builder);
+
+gboolean tracker_string_builder_is_empty (TrackerStringBuilder *builder);
+
+#endif /* __TRACKER_STRING_BUILDER_H__ */
diff --git a/src/libtracker-data/tracker-uuid.c b/src/libtracker-data/tracker-uuid.c
new file mode 100644
index 000000000..fec35efa4
--- /dev/null
+++ b/src/libtracker-data/tracker-uuid.c
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+
+#include "config.h"
+#include "tracker-uuid.h"
+
+#if GLIB_CHECK_VERSION (2, 52, 0)
+#include <uuid/uuid.h>
+#endif
+
+gchar *
+tracker_generate_uuid (void)
+{
+ gchar *result;
+#if GLIB_CHECK_VERSION (2, 52, 0)
+ gchar *uuid = g_uuid_string_random ();
+ result = g_strdup_printf ("urn:uuid:%s", uuid);
+ g_free (uuid);
+#else
+ uuid_t base = { 0, };
+ gchar uuid[37];
+
+ uuid_generate (base);
+ uuid_unparse_lower (base, uuid);
+ result = g_strdup_printf ("urn:uuid:%s", uuid);
+#endif
+
+ return result;
+}
diff --git a/src/libtracker-data/tracker-uuid.h b/src/libtracker-data/tracker-uuid.h
new file mode 100644
index 000000000..744171e32
--- /dev/null
+++ b/src/libtracker-data/tracker-uuid.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2008-2010, Nokia
+ * Copyright (C) 2018, 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.
+ */
+
+#ifndef __TRACKER_UUID_H__
+#define __TRACKER_UUID_H__
+
+#include <glib.h>
+
+gchar * tracker_generate_uuid (void);
+
+#endif /* __TRACKER_UUID_H__ */
diff --git a/src/libtracker-direct/meson.build b/src/libtracker-direct/meson.build
index 0c515eaa7..aff19a885 100644
--- a/src/libtracker-direct/meson.build
+++ b/src/libtracker-direct/meson.build
@@ -1,5 +1,6 @@
libtracker_direct = static_library('tracker-direct',
'tracker-direct.c',
+ 'tracker-direct-statement.c',
c_args: tracker_c_args,
dependencies: [ glib, gio, tracker_data_dep ],
include_directories: [commoninc, configinc, srcinc],
diff --git a/src/libtracker-direct/tracker-direct-statement.c b/src/libtracker-direct/tracker-direct-statement.c
new file mode 100644
index 000000000..68ede258f
--- /dev/null
+++ b/src/libtracker-direct/tracker-direct-statement.c
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2018, 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.
+ */
+
+#include "config.h"
+#include "tracker-direct-statement.h"
+#include "tracker-data.h"
+
+typedef struct _TrackerDirectStatementPrivate TrackerDirectStatementPrivate;
+
+struct _TrackerDirectStatementPrivate
+{
+ TrackerSparql *sparql;
+ GHashTable *values;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE (TrackerDirectStatement,
+ tracker_direct_statement,
+ TRACKER_SPARQL_TYPE_STATEMENT)
+
+static void
+tracker_direct_statement_finalize (GObject *object)
+{
+ TrackerDirectStatementPrivate *priv;
+
+ priv = tracker_direct_statement_get_instance_private (TRACKER_DIRECT_STATEMENT (object));
+ g_hash_table_destroy (priv->values);
+ g_clear_object (&priv->sparql);
+
+ G_OBJECT_CLASS (tracker_direct_statement_parent_class)->finalize (object);
+}
+
+static void
+tracker_direct_statement_constructed (GObject *object)
+{
+ TrackerDirectStatementPrivate *priv;
+ TrackerSparqlConnection *conn;
+ gchar *sparql;
+
+ priv = tracker_direct_statement_get_instance_private (TRACKER_DIRECT_STATEMENT (object));
+
+ g_object_get (object,
+ "sparql", &sparql,
+ "connection", &conn,
+ NULL);
+
+ priv->sparql = tracker_sparql_new (tracker_direct_connection_get_data_manager (TRACKER_DIRECT_CONNECTION (conn)),
+ sparql);
+ g_object_unref (conn);
+ g_free (sparql);
+
+ G_OBJECT_CLASS (tracker_direct_statement_parent_class)->constructed (object);
+}
+
+static GValue *
+insert_value (TrackerDirectStatement *stmt,
+ const gchar *name,
+ GType type)
+{
+ TrackerDirectStatementPrivate *priv;
+ GValue *value;
+
+ priv = tracker_direct_statement_get_instance_private (stmt);
+ value = g_new0 (GValue, 1);
+ g_value_init (value, type);
+
+ g_hash_table_insert (priv->values, g_strdup (name), value);
+
+ return value;
+}
+
+static void
+tracker_direct_statement_bind_int (TrackerSparqlStatement *stmt,
+ const gchar *name,
+ gint64 value)
+{
+ GValue *gvalue;
+
+ gvalue = insert_value (TRACKER_DIRECT_STATEMENT (stmt), name, G_TYPE_INT64);
+ g_value_set_int64 (gvalue, value);
+}
+
+static void
+tracker_direct_statement_bind_double (TrackerSparqlStatement *stmt,
+ const gchar *name,
+ double value)
+{
+ GValue *gvalue;
+
+ gvalue = insert_value (TRACKER_DIRECT_STATEMENT (stmt), name, G_TYPE_DOUBLE);
+ g_value_set_double (gvalue, value);
+}
+
+static void
+tracker_direct_statement_bind_boolean (TrackerSparqlStatement *stmt,
+ const gchar *name,
+ gboolean value)
+{
+ GValue *gvalue;
+
+ gvalue = insert_value (TRACKER_DIRECT_STATEMENT (stmt), name, G_TYPE_BOOLEAN);
+ g_value_set_boolean (gvalue, value);
+}
+
+static void
+tracker_direct_statement_bind_string (TrackerSparqlStatement *stmt,
+ const gchar *name,
+ const gchar *value)
+{
+ GValue *gvalue;
+
+ gvalue = insert_value (TRACKER_DIRECT_STATEMENT (stmt), name, G_TYPE_STRING);
+ g_value_set_string (gvalue, value);
+}
+
+static TrackerSparqlCursor *
+tracker_direct_statement_execute (TrackerSparqlStatement *stmt,
+ GCancellable *cancellable,
+ GError **error)
+{
+ TrackerDirectStatementPrivate *priv;
+
+ priv = tracker_direct_statement_get_instance_private (TRACKER_DIRECT_STATEMENT (stmt));
+
+ return tracker_sparql_execute_cursor (priv->sparql, priv->values, error);
+}
+
+static void
+execute_in_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ TrackerDirectStatementPrivate *priv;
+ TrackerSparqlCursor *cursor;
+ GHashTable *values = task_data;
+ GError *error = NULL;
+
+ priv = tracker_direct_statement_get_instance_private (object);
+ cursor = tracker_sparql_execute_cursor (priv->sparql, values, &error);
+
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_pointer (task, cursor, g_object_unref);
+
+ g_object_unref (task);
+}
+
+static void
+free_gvalue (gpointer data)
+{
+ g_value_unset (data);
+ g_free (data);
+}
+
+static GHashTable *
+create_values_ht (void)
+{
+ return g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, free_gvalue);
+}
+
+static GHashTable *
+copy_values_deep (GHashTable *values)
+{
+ GHashTable *copy;
+ GHashTableIter iter;
+ gpointer key, val;
+
+ copy = create_values_ht ();
+ g_hash_table_iter_init (&iter, values);
+
+ while (g_hash_table_iter_next (&iter, &key, &val)) {
+ GValue *copy_value;
+
+ copy_value = g_new0 (GValue, 1);
+ g_value_init (copy_value, G_VALUE_TYPE (val));
+ g_value_copy (copy_value, val);
+
+ g_hash_table_insert (copy, g_strdup (key), copy_value);
+ }
+
+ return copy;
+}
+
+static void
+tracker_direct_statement_execute_async (TrackerSparqlStatement *stmt,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ TrackerDirectStatementPrivate *priv;
+ GHashTable *values;
+ GTask *task;
+
+ priv = tracker_direct_statement_get_instance_private (TRACKER_DIRECT_STATEMENT (stmt));
+
+ values = copy_values_deep (priv->values);
+
+ task = g_task_new (stmt, cancellable, callback, user_data);
+ g_task_set_task_data (task, values, (GDestroyNotify) g_hash_table_unref);
+ g_task_run_in_thread (task, execute_in_thread);
+}
+
+static TrackerSparqlCursor *
+tracker_direct_statement_execute_finish (TrackerSparqlStatement *stmt,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+tracker_direct_statement_class_init (TrackerDirectStatementClass *klass)
+{
+ TrackerSparqlStatementClass *stmt_class = (TrackerSparqlStatementClass *) klass;
+ GObjectClass *object_class = (GObjectClass *) klass;
+
+ object_class->finalize = tracker_direct_statement_finalize;
+ object_class->constructed = tracker_direct_statement_constructed;
+
+ stmt_class->bind_int = tracker_direct_statement_bind_int;
+ stmt_class->bind_boolean = tracker_direct_statement_bind_boolean;
+ stmt_class->bind_double = tracker_direct_statement_bind_double;
+ stmt_class->bind_string = tracker_direct_statement_bind_string;
+ 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;
+}
+
+static void
+tracker_direct_statement_init (TrackerDirectStatement *stmt)
+{
+ TrackerDirectStatementPrivate *priv;
+
+ priv = tracker_direct_statement_get_instance_private (stmt);
+ priv->values = create_values_ht ();
+}
+
+TrackerDirectStatement *
+tracker_direct_statement_new (TrackerSparqlConnection *conn,
+ const gchar *sparql,
+ GError **error)
+{
+ return g_object_new (TRACKER_TYPE_DIRECT_STATEMENT,
+ "sparql", sparql,
+ "connection", conn,
+ NULL);
+}
diff --git a/src/libtracker-direct/tracker-direct-statement.h b/src/libtracker-direct/tracker-direct-statement.h
new file mode 100644
index 000000000..d68bb6bc8
--- /dev/null
+++ b/src/libtracker-direct/tracker-direct-statement.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2018, 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.
+ */
+
+#ifndef __TRACKER_DIRECT_STATEMENT_H__
+#define __TRACKER_DIRECT_STATEMENT_H__
+
+#include "tracker-direct.h"
+#include <libtracker-sparql/tracker-sparql.h>
+
+#define TRACKER_TYPE_DIRECT_STATEMENT (tracker_direct_statement_get_type ())
+#define TRACKER_DIRECT_STATEMENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TRACKER_TYPE_DIRECT_STATEMENT, TrackerDirectStatement))
+#define TRACKER_DIRECT_STATEMENT_CLASS(c) (G_TYPE_CHECK_CLASS_CAST ((c), TRACKER_TYPE_DIRECT_STATEMENT, TrackerDirectStatementClass))
+#define TRACKER_IS_DIRECT_STATEMENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TRACKER_TYPE_DIRECT_STATEMENT))
+#define TRACKER_IS_DIRECT_STATEMENT_CLASS(c) (G_TYPE_CHECK_CLASS_TYPE ((c), TRACKER_TYPE_DIRECT_STATEMENT))
+#define TRACKER_DIRECT_STATEMENT_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), TRACKER_TYPE_DIRECT_STATEMENT, TrackerDirectStatementClass))
+
+typedef struct _TrackerDirectStatement TrackerDirectStatement;
+typedef struct _TrackerDirectStatementClass TrackerDirectStatementClass;
+
+struct _TrackerDirectStatementClass
+{
+ TrackerSparqlStatementClass parent_class;
+};
+
+struct _TrackerDirectStatement
+{
+ TrackerSparqlStatement parent_instance;
+};
+
+GType tracker_direct_statement_get_type (void) G_GNUC_CONST;
+
+TrackerDirectStatement * tracker_direct_statement_new (TrackerSparqlConnection *conn,
+ const gchar *sparql,
+ GError **error);
+
+#endif /* __TRACKER_DIRECT_STATEMENT_H__ */
diff --git a/src/libtracker-direct/tracker-direct.c b/src/libtracker-direct/tracker-direct.c
index 3c54c7929..7d62c6f30 100644
--- a/src/libtracker-direct/tracker-direct.c
+++ b/src/libtracker-direct/tracker-direct.c
@@ -21,7 +21,9 @@
#include "config.h"
#include "tracker-direct.h"
+#include "tracker-direct-statement.h"
#include <libtracker-data/tracker-data.h>
+#include <libtracker-data/tracker-sparql.h>
static TrackerDBManagerFlags default_flags = 0;
@@ -429,16 +431,17 @@ tracker_direct_connection_query (TrackerSparqlConnection *self,
{
TrackerDirectConnectionPrivate *priv;
TrackerDirectConnection *conn;
- TrackerSparqlQuery *query;
+ TrackerSparql *query;
TrackerSparqlCursor *cursor;
conn = TRACKER_DIRECT_CONNECTION (self);
priv = tracker_direct_connection_get_instance_private (conn);
g_mutex_lock (&priv->mutex);
- query = tracker_sparql_query_new (priv->data_manager, sparql);
- cursor = TRACKER_SPARQL_CURSOR (tracker_sparql_query_execute_cursor (query, error));
+ query = tracker_sparql_new (priv->data_manager, sparql);
+ cursor = tracker_sparql_execute_cursor (query, NULL, error);
g_object_unref (query);
+
if (cursor)
tracker_sparql_cursor_set_connection (cursor, self);
g_mutex_unlock (&priv->mutex);
@@ -478,6 +481,15 @@ tracker_direct_connection_query_finish (TrackerSparqlConnection *self,
return g_task_propagate_pointer (G_TASK (res), error);
}
+static TrackerSparqlStatement *
+tracker_direct_connection_query_statement (TrackerSparqlConnection *self,
+ const gchar *query,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return TRACKER_SPARQL_STATEMENT (tracker_direct_statement_new (self, query, error));
+}
+
static void
tracker_direct_connection_update (TrackerSparqlConnection *self,
const gchar *sparql,
@@ -743,6 +755,7 @@ tracker_direct_connection_class_init (TrackerDirectConnectionClass *klass)
sparql_connection_class->query = tracker_direct_connection_query;
sparql_connection_class->query_async = tracker_direct_connection_query_async;
sparql_connection_class->query_finish = tracker_direct_connection_query_finish;
+ sparql_connection_class->query_statement = tracker_direct_connection_query_statement;
sparql_connection_class->update = tracker_direct_connection_update;
sparql_connection_class->update_async = tracker_direct_connection_update_async;
sparql_connection_class->update_finish = tracker_direct_connection_update_finish;
diff --git a/src/libtracker-direct/tracker-direct.h b/src/libtracker-direct/tracker-direct.h
index 0b66fdb1f..950db3975 100644
--- a/src/libtracker-direct/tracker-direct.h
+++ b/src/libtracker-direct/tracker-direct.h
@@ -44,6 +44,8 @@ struct _TrackerDirectConnection
TrackerSparqlConnection parent_instance;
};
+GType tracker_direct_connection_get_type (void) G_GNUC_CONST;
+
TrackerDirectConnection *tracker_direct_connection_new (TrackerSparqlConnectionFlags flags,
GFile *store,
GFile *journal,
diff --git a/src/libtracker-sparql-backend/tracker-backend.vala b/src/libtracker-sparql-backend/tracker-backend.vala
index 7768d5f07..d99ceb291 100644
--- a/src/libtracker-sparql-backend/tracker-backend.vala
+++ b/src/libtracker-sparql-backend/tracker-backend.vala
@@ -81,6 +81,16 @@ class Tracker.Sparql.Backend : Connection {
}
}
+ public override Statement? query_statement (string sparql, Cancellable? cancellable = null) throws Sparql.Error {
+ debug ("%s(): '%s'", GLib.Log.METHOD, sparql);
+ if (direct != null) {
+ return direct.query_statement (sparql, cancellable);
+ } else {
+ warning ("Interface 'query_statement' not implemented on dbus interface");
+ return null;
+ }
+ }
+
public override void update (string sparql, int priority = GLib.Priority.DEFAULT, Cancellable? cancellable = null) throws Sparql.Error, IOError, DBusError, GLib.Error {
debug ("%s(priority:%d): '%s'", GLib.Log.METHOD, priority, sparql);
if (bus == null) {
diff --git a/src/libtracker-sparql-backend/tracker-sparql-2.map b/src/libtracker-sparql-backend/tracker-sparql-2.map
index 38cba0046..3dee56514 100644
--- a/src/libtracker-sparql-backend/tracker-sparql-2.map
+++ b/src/libtracker-sparql-backend/tracker-sparql-2.map
@@ -6,6 +6,7 @@ global:
tracker_sparql_escape_*;
tracker_sparql_error_*;
tracker_sparql_value_*;
+ tracker_sparql_statement_*;
tracker_sparql_get_*;
tracker_namespace_manager_*;
tracker_resource_*;
diff --git a/src/libtracker-sparql/meson.build b/src/libtracker-sparql/meson.build
index 94ada9aaf..65985baad 100644
--- a/src/libtracker-sparql/meson.build
+++ b/src/libtracker-sparql/meson.build
@@ -18,6 +18,7 @@ libtracker_sparql_intermediate_vala = static_library('tracker-sparql-intermediat
'tracker-builder.vala',
'tracker-connection.vala',
'tracker-cursor.vala',
+ 'tracker-statement.vala',
'tracker-utils.vala',
vala_header: 'tracker-generated-no-checks.h',
c_args: tracker_c_args,
diff --git a/src/libtracker-sparql/tracker-connection.vala b/src/libtracker-sparql/tracker-connection.vala
index a949ea26a..cdb6c36eb 100644
--- a/src/libtracker-sparql/tracker-connection.vala
+++ b/src/libtracker-sparql/tracker-connection.vala
@@ -635,4 +635,16 @@ public abstract class Tracker.Sparql.Connection : Object {
* Since: 2.0
*/
public extern static DBusConnection? get_dbus_connection ();
+
+ /**
+ * tracker_sparql_connection_query_statement:
+ *
+ * Prepares the given @sparql as a #TrackerSparqlStatement.
+ *
+ * Since: 2.2
+ */
+ public virtual Statement? query_statement (string sparql, Cancellable? cancellable = null) throws Sparql.Error {
+ warning ("Interface 'query_statement' not implemented");
+ return null;
+ }
}
diff --git a/src/libtracker-sparql/tracker-resource.c b/src/libtracker-sparql/tracker-resource.c
index e7d46df36..6000e3862 100644
--- a/src/libtracker-sparql/tracker-resource.c
+++ b/src/libtracker-sparql/tracker-resource.c
@@ -1518,7 +1518,7 @@ tracker_resource_print_sparql_update (TrackerResource *resource,
context.done_list = NULL;
/* Finally insert the data */
- g_string_append (context.string, "INSERT {\n");
+ g_string_append (context.string, "INSERT DATA {\n");
if (graph_id) {
g_string_append_printf (context.string, "GRAPH <%s> {\n", graph_id);
}
diff --git a/src/libtracker-sparql/tracker-statement.vala b/src/libtracker-sparql/tracker-statement.vala
new file mode 100644
index 000000000..fa965cacf
--- /dev/null
+++ b/src/libtracker-sparql/tracker-statement.vala
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018, 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.
+ */
+
+/**
+ * SECTION: tracker-sparql-statement
+ * @short_description: Prepared statements
+ * @title: TrackerSparqlStatement
+ * @stability: Stable
+ * @include: tracker-sparql.h
+ *
+ * The <structname>TrackerSparqlStatement</structname> object represents
+ * a SPARQL query. This query may contain parameterized variables
+ * (expressed as ~var in the syntax), which may be mapped to arbitrary
+ * values prior to execution. This statement may be reused for future
+ * queries with different values.
+ *
+ * The argument bindings may be changed through tracker_sparql_statement_bind_int(),
+ * tracker_sparql_statement_bind_boolean(), tracker_sparql_statement_bind_double()
+ * and tracker_sparql_statement_bind_string(). Those functions receive
+ * a @name argument corresponding for the variable name in the SPARQL query
+ * (eg. "var" for ~var) and a @value to map the variable to.
+ *
+ * Once all arguments have a value, the query may be executed through
+ * tracker_sparql_statement_execute() or tracker_sparql_statement_execute_async().
+ *
+ * This object was added in Tracker 2.2.
+ */
+public abstract class Tracker.Sparql.Statement : Object {
+ public string sparql { get; construct set; }
+ public Connection connection { get; construct set; }
+
+ /**
+ * tracker_sparql_statement_bind_int:
+ * @self: a #TrackerSparqlStatement
+ * @name: variable name
+ * @value: value
+ *
+ * Binds the integer @value to variable @name.
+ */
+ public abstract void bind_int (string name, int64 value);
+
+ /**
+ * tracker_sparql_statement_bind_boolean:
+ * @self: a #TrackerSparqlStatement
+ * @name: variable name
+ * @value: value
+ *
+ * Binds the boolean @value to variable @name.
+ */
+ public abstract void bind_boolean (string name, bool value);
+
+ /**
+ * tracker_sparql_statement_bind_string:
+ * @self: a #TrackerSparqlStatement
+ * @name: variable name
+ * @value: value
+ *
+ * Binds the string @value to variable @name.
+ */
+ public abstract void bind_string (string name, string value);
+
+ /**
+ * tracker_sparql_statement_bind_double:
+ * @self: a #TrackerSparqlStatement
+ * @name: variable name
+ * @value: value
+ *
+ * Binds the double @value to variable @name.
+ */
+ public abstract void bind_double (string name, double value);
+
+ /**
+ * tracker_sparql_statement_execute:
+ * @cancellable: a #GCancellable used to cancel the operation
+ * @error: #GError for error reporting.
+ *
+ * Executes the SPARQL query with the currently bound values.
+ *
+ * Returns: (transfer full): A #TrackerSparqlCursor
+ */
+ public abstract Cursor execute (Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, DBusError;
+
+ /**
+ * tracker_sparql_statement_execute_finish:
+ * @self: a #TrackerSparqlStatement
+ * @_res_: The #GAsyncResult from the callback used to return the #TrackerSparqlCursor
+ * @error: The error which occurred or %NULL
+ *
+ * Finishes the asynchronous operation started through
+ * tracker_sparql_statement_execute_async().
+ *
+ * Returns: (transfer full): A #TrackerSparqlCursor
+ */
+
+ /**
+ * tracker_sparql_statement_execute_async:
+ * @self: a #TrackerSparqlStatement
+ * @cancellable: 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_
+ *
+ * Asynchronously executes the SPARQL query with the currently bound values.
+ */
+ public async abstract Cursor execute_async (Cancellable? cancellable) throws Sparql.Error, GLib.Error, GLib.IOError, DBusError;
+}
diff --git a/tests/libtracker-data/anon/query-3.out b/tests/libtracker-data/anon/query-3.out
new file mode 100644
index 000000000..fe1e48f34
--- /dev/null
+++ b/tests/libtracker-data/anon/query-3.out
@@ -0,0 +1,3 @@
+"Alice"
+"Bob"
+"Fred"
diff --git a/tests/libtracker-data/anon/query-3.rq b/tests/libtracker-data/anon/query-3.rq
new file mode 100644
index 000000000..ba9252f0b
--- /dev/null
+++ b/tests/libtracker-data/anon/query-3.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { [] foaf:knows ?u } order by foaf:name(?u) \ No newline at end of file
diff --git a/tests/libtracker-data/anon/query-4.out b/tests/libtracker-data/anon/query-4.out
new file mode 100644
index 000000000..3970b1c89
--- /dev/null
+++ b/tests/libtracker-data/anon/query-4.out
@@ -0,0 +1,3 @@
+"Alice"
+"Bob"
+"Eve"
diff --git a/tests/libtracker-data/anon/query-4.rq b/tests/libtracker-data/anon/query-4.rq
new file mode 100644
index 000000000..772c1bf8c
--- /dev/null
+++ b/tests/libtracker-data/anon/query-4.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { ?u foaf:knows [] } order by foaf:name(?u) \ No newline at end of file
diff --git a/tests/libtracker-data/anon/query-5.out b/tests/libtracker-data/anon/query-5.out
new file mode 100644
index 000000000..a0dc77220
--- /dev/null
+++ b/tests/libtracker-data/anon/query-5.out
@@ -0,0 +1,4 @@
+"Alice"
+"Bob"
+"Eve"
+"Fred"
diff --git a/tests/libtracker-data/anon/query-5.rq b/tests/libtracker-data/anon/query-5.rq
new file mode 100644
index 000000000..2f3df0ad5
--- /dev/null
+++ b/tests/libtracker-data/anon/query-5.rq
@@ -0,0 +1 @@
+select ?u { [ a foaf:Person ; foaf:name ?u ] } order by ?u \ No newline at end of file
diff --git a/tests/libtracker-data/bnode/data.ontology b/tests/libtracker-data/bnode/data.ontology
new file mode 100644
index 000000000..ca4c99676
--- /dev/null
+++ b/tests/libtracker-data/bnode/data.ontology
@@ -0,0 +1,24 @@
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix tracker: <http://www.tracker-project.org/ontologies/tracker#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+foaf: a tracker:Namespace ;
+ tracker:prefix "foaf" .
+
+foaf:Person a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+foaf:knows a rdf:Property ;
+ rdfs:domain foaf:Person ;
+ rdfs:range foaf:Person .
+
+foaf:mbox a rdf:Property ;
+ rdfs:domain foaf:Person ;
+ rdfs:range rdfs:Resource .
+
+foaf:name a rdf:Property ;
+ rdfs:domain foaf:Person ;
+ rdfs:range xsd:string .
+
diff --git a/tests/libtracker-data/bnode/data.ttl b/tests/libtracker-data/bnode/data.ttl
new file mode 100644
index 000000000..5f0613f28
--- /dev/null
+++ b/tests/libtracker-data/bnode/data.ttl
@@ -0,0 +1,30 @@
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+_:alice
+ rdf:type foaf:Person ;
+ foaf:name "Alice" ;
+ foaf:mbox <mailto:alice@work> ;
+ foaf:knows _:bob ;
+ .
+
+_:bob
+ rdf:type foaf:Person ;
+ foaf:name "Bob" ;
+ foaf:knows _:alice ;
+ foaf:mbox <mailto:bob@work> ;
+ foaf:mbox <mailto:bob@home> ;
+ .
+
+
+_:eve
+ rdf:type foaf:Person ;
+ foaf:name "Eve" ;
+ foaf:knows _:fred ;
+ .
+
+_:fred
+ rdf:type foaf:Person ;
+ foaf:name "Fred" ;
+ foaf:mbox <mailto:fred@edu> .
diff --git a/tests/libtracker-data/bnode/query-1.out b/tests/libtracker-data/bnode/query-1.out
new file mode 100644
index 000000000..3970b1c89
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-1.out
@@ -0,0 +1,3 @@
+"Alice"
+"Bob"
+"Eve"
diff --git a/tests/libtracker-data/bnode/query-1.rq b/tests/libtracker-data/bnode/query-1.rq
new file mode 100644
index 000000000..7f3f3705c
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-1.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { ?u foaf:knows _:foo . _:foo a foaf:Person } order by foaf:name(?u) \ No newline at end of file
diff --git a/tests/libtracker-data/bnode/query-2.out b/tests/libtracker-data/bnode/query-2.out
new file mode 100644
index 000000000..3970b1c89
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-2.out
@@ -0,0 +1,3 @@
+"Alice"
+"Bob"
+"Eve"
diff --git a/tests/libtracker-data/bnode/query-2.rq b/tests/libtracker-data/bnode/query-2.rq
new file mode 100644
index 000000000..33b99de9b
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-2.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { _:foo a foaf:Person . ?u foaf:knows _:foo } order by foaf:name(?u) \ No newline at end of file
diff --git a/tests/libtracker-data/bnode/query-3.out b/tests/libtracker-data/bnode/query-3.out
new file mode 100644
index 000000000..a0dc77220
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-3.out
@@ -0,0 +1,4 @@
+"Alice"
+"Bob"
+"Eve"
+"Fred"
diff --git a/tests/libtracker-data/bnode/query-3.rq b/tests/libtracker-data/bnode/query-3.rq
new file mode 100644
index 000000000..277a204a2
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-3.rq
@@ -0,0 +1 @@
+select ?u { _:foo a foaf:Person ; foaf:name ?u } order by ?u
diff --git a/tests/libtracker-data/bnode/query-4.out b/tests/libtracker-data/bnode/query-4.out
new file mode 100644
index 000000000..3970b1c89
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-4.out
@@ -0,0 +1,3 @@
+"Alice"
+"Bob"
+"Eve"
diff --git a/tests/libtracker-data/bnode/query-4.rq b/tests/libtracker-data/bnode/query-4.rq
new file mode 100644
index 000000000..7f3f3705c
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-4.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { ?u foaf:knows _:foo . _:foo a foaf:Person } order by foaf:name(?u) \ No newline at end of file
diff --git a/tests/libtracker-data/bnode/query-5.out b/tests/libtracker-data/bnode/query-5.out
new file mode 100644
index 000000000..fe1e48f34
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-5.out
@@ -0,0 +1,3 @@
+"Alice"
+"Bob"
+"Fred"
diff --git a/tests/libtracker-data/bnode/query-5.rq b/tests/libtracker-data/bnode/query-5.rq
new file mode 100644
index 000000000..38fd47855
--- /dev/null
+++ b/tests/libtracker-data/bnode/query-5.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { _:foo a foaf:Person ; foaf:knows ?u } order by foaf:name(?u) \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/data.ttl b/tests/libtracker-data/property-paths/data.ttl
new file mode 100644
index 000000000..5088a80d3
--- /dev/null
+++ b/tests/libtracker-data/property-paths/data.ttl
@@ -0,0 +1,32 @@
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+_:alice
+ rdf:type foaf:Person ;
+ foaf:name "Alice" ;
+ foaf:mbox <mailto:alice@work> ;
+ foaf:knows _:bob ;
+ foaf:member [ a foaf:Group ; foaf:name "Foo" ] ;
+ .
+
+_:bob
+ rdf:type foaf:Person ;
+ foaf:name "Bob" ;
+ foaf:knows _:alice ;
+ foaf:mbox <mailto:bob@work> ;
+ foaf:mbox <mailto:bob@home> ;
+ foaf:member [ a foaf:Group ; foaf:name "Foo" ] ;
+ .
+
+
+_:eve
+ rdf:type foaf:Person ;
+ foaf:name "Eve" ;
+ foaf:knows _:fred ;
+ .
+
+_:fred
+ rdf:type foaf:Person ;
+ foaf:name "Fred" ;
+ foaf:mbox <mailto:fred@edu> .
diff --git a/tests/libtracker-data/property-paths/inverse-path-1.out b/tests/libtracker-data/property-paths/inverse-path-1.out
new file mode 100644
index 000000000..0a9f74d4f
--- /dev/null
+++ b/tests/libtracker-data/property-paths/inverse-path-1.out
@@ -0,0 +1,3 @@
+"Bob" "Alice"
+"Alice" "Bob"
+"Fred" "Eve"
diff --git a/tests/libtracker-data/property-paths/inverse-path-1.rq b/tests/libtracker-data/property-paths/inverse-path-1.rq
new file mode 100644
index 000000000..1aaf070ce
--- /dev/null
+++ b/tests/libtracker-data/property-paths/inverse-path-1.rq
@@ -0,0 +1 @@
+select foaf:name(?a) foaf:name(?b) { ?a ^foaf:knows ?b } \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/inverse-path-2.out b/tests/libtracker-data/property-paths/inverse-path-2.out
new file mode 100644
index 000000000..b74164513
--- /dev/null
+++ b/tests/libtracker-data/property-paths/inverse-path-2.out
@@ -0,0 +1,6 @@
+"1" "Alice" "Bob"
+"1" "Bob" "Alice"
+"1" "Fred" "Eve"
+"2" "Alice" "Bob"
+"2" "Bob" "Alice"
+"2" "Fred" "Eve"
diff --git a/tests/libtracker-data/property-paths/inverse-path-2.rq b/tests/libtracker-data/property-paths/inverse-path-2.rq
new file mode 100644
index 000000000..e886c8761
--- /dev/null
+++ b/tests/libtracker-data/property-paths/inverse-path-2.rq
@@ -0,0 +1 @@
+select ?s foaf:name(?a) foaf:name(?b) { { select ('1' as ?s) ?a ?b { ?a ^foaf:knows ?b } } union { select ('2' as ?s) ?a ?b { ?b foaf:knows ?a } } } order by ?s foaf:name(?a) foaf:name(?b) \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.out b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.out
new file mode 100644
index 000000000..52c90ceee
--- /dev/null
+++ b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.out
@@ -0,0 +1 @@
+"Fred"
diff --git a/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.rq b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.rq
new file mode 100644
index 000000000..a77629f6a
--- /dev/null
+++ b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-1.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { ?u ^foaf:knows/foaf:name 'Eve' } \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.out b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.out
new file mode 100644
index 000000000..0686dec37
--- /dev/null
+++ b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.out
@@ -0,0 +1 @@
+"Eve"
diff --git a/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.rq b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.rq
new file mode 100644
index 000000000..f862af007
--- /dev/null
+++ b/tests/libtracker-data/property-paths/mixed-inverse-and-sequence-2.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { ?u foaf:knows/^foaf:knows/foaf:name 'Eve' } \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/query-1.rq b/tests/libtracker-data/property-paths/query-1.rq
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/libtracker-data/property-paths/query-1.rq
diff --git a/tests/libtracker-data/property-paths/sequence-path-1.out b/tests/libtracker-data/property-paths/sequence-path-1.out
new file mode 100644
index 000000000..d12a7b1c3
--- /dev/null
+++ b/tests/libtracker-data/property-paths/sequence-path-1.out
@@ -0,0 +1,2 @@
+"Alice" "Alice"
+"Bob" "Bob"
diff --git a/tests/libtracker-data/property-paths/sequence-path-1.rq b/tests/libtracker-data/property-paths/sequence-path-1.rq
new file mode 100644
index 000000000..8900f9d99
--- /dev/null
+++ b/tests/libtracker-data/property-paths/sequence-path-1.rq
@@ -0,0 +1 @@
+select foaf:name(?a) foaf:name(?b) { ?a foaf:knows/foaf:knows ?b } \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/sequence-path-2.out b/tests/libtracker-data/property-paths/sequence-path-2.out
new file mode 100644
index 000000000..312a17008
--- /dev/null
+++ b/tests/libtracker-data/property-paths/sequence-path-2.out
@@ -0,0 +1,4 @@
+"1" "Alice" "Alice"
+"1" "Bob" "Bob"
+"2" "Alice" "Alice"
+"2" "Bob" "Bob"
diff --git a/tests/libtracker-data/property-paths/sequence-path-2.rq b/tests/libtracker-data/property-paths/sequence-path-2.rq
new file mode 100644
index 000000000..2fbc0642f
--- /dev/null
+++ b/tests/libtracker-data/property-paths/sequence-path-2.rq
@@ -0,0 +1 @@
+select ?s foaf:name(?a) foaf:name(?b) { { select ('1' as ?s) ?a ?b { ?a foaf:knows/foaf:knows ?b } } union { select ('2' as ?s) ?a ?b { ?a foaf:knows ?y . ?y foaf:knows ?b } } } order by ?s foaf:name(?a) foaf:name(?b) \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/sequence-path-3.out b/tests/libtracker-data/property-paths/sequence-path-3.out
new file mode 100644
index 000000000..0686dec37
--- /dev/null
+++ b/tests/libtracker-data/property-paths/sequence-path-3.out
@@ -0,0 +1 @@
+"Eve"
diff --git a/tests/libtracker-data/property-paths/sequence-path-3.rq b/tests/libtracker-data/property-paths/sequence-path-3.rq
new file mode 100644
index 000000000..3cdee92a9
--- /dev/null
+++ b/tests/libtracker-data/property-paths/sequence-path-3.rq
@@ -0,0 +1 @@
+select foaf:name(?u) { ?u foaf:knows/foaf:name 'Fred' } \ No newline at end of file
diff --git a/tests/libtracker-data/property-paths/test.ontology b/tests/libtracker-data/property-paths/test.ontology
new file mode 100644
index 000000000..de0a07b06
--- /dev/null
+++ b/tests/libtracker-data/property-paths/test.ontology
@@ -0,0 +1,34 @@
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix tracker: <http://www.tracker-project.org/ontologies/tracker#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+foaf: a tracker:Namespace ;
+ tracker:prefix "foaf" .
+
+foaf:Agent a rdfs:Class ;
+ rdfs:subClassOf rdfs:Resource .
+
+foaf:Person a rdfs:Class ;
+ rdfs:subClassOf foaf:Agent .
+
+foaf:Group a rdfs:Class ;
+ rdfs:subClassOf foaf:Agent .
+
+foaf:knows a rdf:Property ;
+ rdfs:domain foaf:Person ;
+ rdfs:range foaf:Person .
+
+foaf:mbox a rdf:Property ;
+ rdfs:domain foaf:Person ;
+ rdfs:range rdfs:Resource .
+
+foaf:name a rdf:Property ;
+ rdfs:domain foaf:Agent ;
+ rdfs:range xsd:string .
+
+foaf:member a rdf:Property ;
+ rdfs:domain foaf:Person ;
+ rdfs:range foaf:Group .
+
diff --git a/tests/libtracker-data/tracker-sparql-test.c b/tests/libtracker-data/tracker-sparql-test.c
index 960388f18..1bfb5a4a4 100644
--- a/tests/libtracker-data/tracker-sparql-test.c
+++ b/tests/libtracker-data/tracker-sparql-test.c
@@ -63,6 +63,9 @@ const TestInfo tests[] = {
{ "algebra/var-scope-join-1", "algebra/var-scope-join-1", FALSE },
{ "anon/query", "anon/data", FALSE },
{ "anon/query-2", "anon/data", FALSE },
+ { "anon/query-3", "anon/data", FALSE },
+ { "anon/query-4", "anon/data", FALSE },
+ { "anon/query-5", "anon/data", FALSE },
{ "ask/ask-1", "ask/data", FALSE },
{ "basic/base-prefix-3", "basic/data-1", FALSE },
{ "basic/compare-cast", "basic/data-1", FALSE },
@@ -70,6 +73,11 @@ const TestInfo tests[] = {
{ "basic/predicate-variable-2", "basic/data-1", FALSE },
{ "basic/predicate-variable-3", "basic/data-1", FALSE },
{ "basic/predicate-variable-4", "basic/data-1", FALSE },
+ { "bnode/query-1", "bnode/data", FALSE },
+ { "bnode/query-2", "bnode/data", FALSE },
+ { "bnode/query-3", "bnode/data", FALSE },
+ { "bnode/query-4", "bnode/data", FALSE },
+ { "bnode/query-5", "bnode/data", FALSE },
{ "bnode-coreference/query", "bnode-coreference/data", FALSE },
{ "bound/bound1", "bound/data", FALSE },
{ "datetime/delete-1", "datetime/data-3", FALSE },
@@ -138,6 +146,14 @@ const TestInfo tests[] = {
{ "bind/bind2", "bind/data", FALSE },
{ "bind/bind3", "bind/data", FALSE },
{ "bind/bind4", "bind/data", FALSE },
+ /* Property paths */
+ { "property-paths/inverse-path-1", "property-paths/data", FALSE },
+ { "property-paths/inverse-path-2", "property-paths/data", FALSE },
+ { "property-paths/sequence-path-1", "property-paths/data", FALSE },
+ { "property-paths/sequence-path-2", "property-paths/data", FALSE },
+ { "property-paths/sequence-path-3", "property-paths/data", FALSE },
+ { "property-paths/mixed-inverse-and-sequence-1", "property-paths/data", FALSE },
+ { "property-paths/mixed-inverse-and-sequence-2", "property-paths/data", FALSE },
/* Update tests */
{ "update/insert-data-query-1", "update/insert-data-1", FALSE, FALSE },
{ "update/insert-data-query-2", "update/insert-data-2", FALSE, TRUE },
diff --git a/tests/libtracker-fts/fts3aa-3.out b/tests/libtracker-fts/fts3aa-3.out
new file mode 100644
index 000000000..2722bd529
--- /dev/null
+++ b/tests/libtracker-fts/fts3aa-3.out
@@ -0,0 +1 @@
+"one two three four five"
diff --git a/tests/libtracker-fts/fts3aa-3.rq b/tests/libtracker-fts/fts3aa-3.rq
new file mode 100644
index 000000000..ef415e0a0
--- /dev/null
+++ b/tests/libtracker-fts/fts3aa-3.rq
@@ -0,0 +1 @@
+select ?s { test:31 fts:match 'three' ; test:p ?s } \ No newline at end of file
diff --git a/tests/libtracker-fts/tracker-fts-test.c b/tests/libtracker-fts/tracker-fts-test.c
index b20b64e37..b389c281b 100644
--- a/tests/libtracker-fts/tracker-fts-test.c
+++ b/tests/libtracker-fts/tracker-fts-test.c
@@ -36,7 +36,7 @@ struct _TestInfo {
};
const TestInfo tests[] = {
- { "fts3aa", 2 },
+ { "fts3aa", 3 },
{ "fts3ae", 1 },
{ "prefix/fts3prefix", 3 },
{ "limits/fts3limits", 4 },