diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2018-11-13 11:50:22 +0100 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2018-11-13 11:50:22 +0100 |
commit | eb82efe8dacdca2b283264c52286244a01682e73 (patch) | |
tree | aa751f4cf40153a8849101065637a68e5e88e72d | |
parent | db4a05d53f305855f1d5fa233764ef29538496da (diff) | |
parent | afc32432722abbdcbf6748547f980579faed7a91 (diff) | |
download | tracker-eb82efe8dacdca2b283264c52286244a01682e73.tar.gz |
Merge branch 'wip/carlosg/sparql-parser-ng'
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 }, |