diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2018-09-03 18:11:04 +0200 |
---|---|---|
committer | Carlos Garnacho <carlosg@gnome.org> | 2018-11-13 11:17:31 +0100 |
commit | 40054c464174ae3de5d372092d780e3e31460884 (patch) | |
tree | 16f55ce4063d8759c4b4e23de877c88d6c83ccd5 | |
parent | a46f5e29503dbd6feceedefd545433bebd32751d (diff) | |
download | tracker-40054c464174ae3de5d372092d780e3e31460884.tar.gz |
libtracker-data: Drop old SPARQL parser
-rw-r--r-- | src/libtracker-data/meson.build | 3 | ||||
-rw-r--r-- | src/libtracker-data/tracker-sparql-expression.vala | 1727 | ||||
-rw-r--r-- | src/libtracker-data/tracker-sparql-pattern.vala | 1659 | ||||
-rw-r--r-- | src/libtracker-data/tracker-sparql-query.vala | 1102 |
4 files changed, 0 insertions, 4491 deletions
diff --git a/src/libtracker-data/meson.build b/src/libtracker-data/meson.build index 9eaef5bf1..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', 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-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); -} - |