diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0aaedb94..9a760e16 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,7 +22,7 @@ jobs:
ruby:
- 2.6
- 2.7
- - 3.0
+ - "3.0"
- 3.1
- ruby-head
- jruby
diff --git a/README.md b/README.md
index 799045af..6df83b8f 100755
--- a/README.md
+++ b/README.md
@@ -281,6 +281,8 @@ a full set of RDF formats.
### Parsing a SSE to SPARQL query or update string to SPARQL
+ # Note: if the SSE uses extension functions, they either must be XSD casting functions, or custom functions which are registered extensions. (See [SPARQL Extension Functions](#sparql-extension-functions))
+
query = SPARQL::Algebra.parse(%{(bgp (triple ?s ?p ?o))})
sparql = query.to_sparql #=> "SELECT * WHERE { ?s ?p ?o }"
diff --git a/VERSION b/VERSION
index 944880fa..e4604e3a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-3.2.0
+3.2.1
diff --git a/examples/function_call.sse b/examples/function_call.sse
new file mode 100644
index 00000000..5b10ff8a
--- /dev/null
+++ b/examples/function_call.sse
@@ -0,0 +1,6 @@
+(prefix ((: )
+ (rdf: )
+ (xsd: ))
+ (project (?a ?v ?boolean)
+ (extend ((?boolean (xsd:boolean ?v)))
+ (bgp (triple ?a :p ?v)))))
\ No newline at end of file
diff --git a/lib/sparql/algebra.rb b/lib/sparql/algebra.rb
index 8deea09b..a2636915 100644
--- a/lib/sparql/algebra.rb
+++ b/lib/sparql/algebra.rb
@@ -19,6 +19,24 @@ module SPARQL
#
# {RDF::Query} and {RDF::Query::Pattern} are used as primitives for `bgp` and `triple` expressions.
#
+ # # Background
+ #
+ # The SPARQL Algebra, and the S-Expressions used to represent it, are based on those of [Jena](https://jena.apache.org/documentation/notes/sse.html). Generally, an S-Expression generated by this Gem can be used as an SSE input to Jena, or an SSE output from Jena can also be used as input to this Gem.
+ #
+ # S-Expressions generally follow a standardized nesting resulting from parsing the original SPARQL Grammar. The individual operators map to SPARQL Grammar productions, and in most cases, the SPARQL Grammar can be reproduced by turning the S-Expression back into SPARQL (see {SPARQL::Algebra::Operator#to_sparql}). The order of operations will typically be as follows:
+ #
+ # * {SPARQL::Algebra::Operator::Base}
+ # * {SPARQL::Algebra::Operator::Prefix}
+ # * {SPARQL::Algebra::Operator::Slice}
+ # * {SPARQL::Algebra::Operator::Distinct}
+ # * {SPARQL::Algebra::Operator::Reduced}
+ # * {SPARQL::Algebra::Operator::Project}
+ # * {SPARQL::Algebra::Operator::Order}
+ # * {SPARQL::Algebra::Operator::Filter}
+ # * {SPARQL::Algebra::Operator::Extend}
+ # * {SPARQL::Algebra::Operator::Group}
+ # * {SPARQL::Algebra::Query} (many classes implement Query)
+ #
# # Queries
#
# require 'sparql/algebra'
@@ -262,6 +280,7 @@ module SPARQL
# * {SPARQL::Algebra::Operator::Extend}
# * {SPARQL::Algebra::Operator::Filter}
# * {SPARQL::Algebra::Operator::Floor}
+ # * {SPARQL::Algebra::Operator::FunctionCall}
# * {SPARQL::Algebra::Operator::Graph}
# * {SPARQL::Algebra::Operator::GreaterThan}
# * {SPARQL::Algebra::Operator::GreaterThanOrEqual}
@@ -339,11 +358,9 @@ module SPARQL
# * {SPARQL::Algebra::Operator::With}
# * {SPARQL::Algebra::Operator::Year}
#
- # TODO
- # ====
- # * Operator#optimize needs to be completed and tested.
#
# @see http://www.w3.org/TR/sparql11-query/#sparqlAlgebra
+ # @see https://jena.apache.org/documentation/notes/sse.html
module Algebra
include RDF
diff --git a/lib/sparql/algebra/expression.rb b/lib/sparql/algebra/expression.rb
index 7e7a73b1..3aec2313 100644
--- a/lib/sparql/algebra/expression.rb
+++ b/lib/sparql/algebra/expression.rb
@@ -66,9 +66,11 @@ def self.open(filename, **options, &block)
#
# @param [Array] sse
# a SPARQL S-Expression (SSE) form
+ # @param [Hash{Symbol => Object}] options
+ # any additional options (see {Operator#initialize})
# @return [Expression]
- def self.for(*sse)
- self.new(sse)
+ def self.for(*sse, **options)
+ self.new(sse, **options)
end
class << self; alias_method :[], :for; end
@@ -86,6 +88,13 @@ def self.new(sse, **options)
raise ArgumentError, "invalid SPARQL::Algebra::Expression form: #{sse.inspect}" unless sse.is_a?(Array)
operator = Operator.for(sse.first, sse.length - 1)
+
+ # If we don't find an operator, and sse.first is an extension IRI, use a function call
+ if !operator && sse.first.is_a?(RDF::URI) && self.extension?(sse.first)
+ operator = Operator.for(:function_call, sse.length)
+ sse.unshift(:function_call)
+ end
+
unless operator
return case sse.first
when Array
@@ -115,11 +124,16 @@ def self.new(sse, **options)
end
debug(options) {"#{operator.inspect}(#{operands.map(&:inspect).join(',')})"}
+ logger = options[:logger]
options.delete_if {|k, v| [:debug, :logger, :depth, :prefixes, :base_uri, :update, :validate].include?(k) }
begin
operator.new(*operands, **options)
rescue ArgumentError => e
- error(options) {"Operator=#{operator.inspect}: #{e}"}
+ if logger
+ logger.error("Operator=#{operator.inspect}: #{e}")
+ else
+ raise "Operator=#{operator.inspect}: #{e}"
+ end
end
end
@@ -163,6 +177,17 @@ def self.extensions
@extensions ||= {}
end
+ ##
+ # Is an extension function available?
+ #
+ # It's either a registered extension, or an XSD casting function
+ #
+ # @param [RDF::URI] function
+ # @return [Boolean]
+ def self.extension?(function)
+ function.to_s.start_with?(RDF::XSD.to_s) || self.extensions[function]
+ end
+
##
# Invoke an extension function.
#
diff --git a/lib/sparql/algebra/extensions.rb b/lib/sparql/algebra/extensions.rb
index 6b8cce51..0c94d008 100644
--- a/lib/sparql/algebra/extensions.rb
+++ b/lib/sparql/algebra/extensions.rb
@@ -70,28 +70,12 @@ def to_sxp_bin
# Returns a partial SPARQL grammar for this array.
#
# @param [String] delimiter (" ")
+ # If the first element is an IRI, treat it as an extension function
# @return [String]
- def to_sparql(delimiter: " ", **options)
+ def to_sparql(delimiter: " ", **options)
map {|e| e.to_sparql(**options)}.join(delimiter)
end
- ##
- # Evaluates the array using the given variable `bindings`.
- #
- # In this case, the Array has two elements, the first of which is
- # an XSD datatype, and the second is the expression to be evaluated.
- # The result is cast as a literal of the appropriate type
- #
- # @param [RDF::Query::Solution] bindings
- # a query solution containing zero or more variable bindings
- # @param [Hash{Symbol => Object}] options ({})
- # options passed from query
- # @return [RDF::Term]
- # @see SPARQL::Algebra::Expression.evaluate
- def evaluate(bindings, **options)
- SPARQL::Algebra::Expression.extension(*self.map {|o| o.evaluate(bindings, **options)})
- end
-
##
# If `#execute` is invoked, it implies that a non-implemented Algebra operator
# is being invoked
@@ -302,7 +286,6 @@ def to_sparql(**options)
end
end # RDF::Term
-
# Override RDF::Queryable to execute against SPARQL::Algebra::Query elements as well as RDF::Query and RDF::Pattern
module RDF::Queryable
alias_method :query_without_sparql, :query
@@ -390,15 +373,17 @@ def to_sxp(prefixes: nil, base_uri: nil)
# @param [Boolean] as_statement (false) serialize as < ... >, otherwise TRIPLE(...)
# @return [String]
def to_sparql(as_statement: false, **options)
- return "TRIPLE(#{to_triple.to_sparql(as_statement: true, **options)})" unless as_statement
-
- to_triple.map do |term|
- if term.is_a?(::RDF::Statement)
- "<<" + term.to_sparql(**options) + ">>"
- else
- term.to_sparql(**options)
- end
- end.join(" ") + " ."
+ if as_statement
+ to_triple.map do |term|
+ if term.is_a?(::RDF::Statement)
+ "<<" + term.to_sparql(as_statement: true, **options) + ">>"
+ else
+ term.to_sparql(**options)
+ end
+ end.join(" ")
+ else
+ "TRIPLE(#{to_triple.to_sparql(as_statement: true, **options)})"
+ end
end
##
@@ -448,15 +433,34 @@ def to_sxp_bin
##
#
- # Returns a partial SPARQL grammar for this term.
+ # Returns a partial SPARQL grammar for this query.
#
# @param [Boolean] top_level (true)
# Treat this as a top-level, generating SELECT ... WHERE {}
+ # @param [Array] filter_ops ([])
+ # Filter Operations
# @return [String]
- def to_sparql(top_level: true, **options)
- str = @patterns.map { |e| e.to_sparql(as_statement: true, top_level: false, **options) }.join("\n")
+ def to_sparql(top_level: true, filter_ops: [], **options)
+ str = @patterns.map { |e| e.to_sparql(as_statement: true, top_level: false, **options) }.join(". \n")
str = "GRAPH #{graph_name.to_sparql(**options)} {\n#{str}\n}\n" if graph_name
- top_level ? SPARQL::Algebra::Operator.to_sparql(str, **options) : str
+ if top_level
+ SPARQL::Algebra::Operator.to_sparql(str, filter_ops: filter_ops, **options)
+ else
+ # Filters
+ filter_ops.each do |op|
+ str << "\nFILTER (#{op.to_sparql(**options)}) ."
+ end
+
+ # Extensons
+ extensions = options.fetch(:extensions, [])
+ extensions.each do |as, expression|
+ v = expression.to_sparql(as_statement: true, **options)
+ v = "<< #{v} >>" if expression.is_a?(RDF::Statement)
+ str << "\nBIND (" << v << " AS " << as.to_sparql(**options) << ") ."
+ end
+ str = "{#{str}}" unless filter_ops.empty? && extensions.empty?
+ str
+ end
end
##
diff --git a/lib/sparql/algebra/operator.rb b/lib/sparql/algebra/operator.rb
index 15e9f374..7fd4983a 100644
--- a/lib/sparql/algebra/operator.rb
+++ b/lib/sparql/algebra/operator.rb
@@ -106,6 +106,7 @@ class Operator
autoload :Coalesce, 'sparql/algebra/operator/coalesce'
autoload :Desc, 'sparql/algebra/operator/desc'
autoload :Exprlist, 'sparql/algebra/operator/exprlist'
+ autoload :FunctionCall, 'sparql/algebra/operator/function_call'
autoload :GroupConcat, 'sparql/algebra/operator/group_concat'
autoload :In, 'sparql/algebra/operator/in'
autoload :NotIn, 'sparql/algebra/operator/notin'
@@ -254,6 +255,7 @@ def self.for(name, arity = nil)
when :asc then Asc
when :desc then Desc
when :exprlist then Exprlist
+ when :function_call then FunctionCall
# Datasets
when :dataset then Dataset
@@ -336,31 +338,41 @@ def self.arity
# Generate a top-level Grammar, using collected options
#
# @param [String] content
+ # @param [Operator] datasets ([])
+ # @param [Operator] distinct (false)
# @param [Hash{Symbol => Operator}] extensions
# Variable bindings
- # @param [Operator] distinct (false)
# @param [Array] filter_ops ([])
# Filter Operations
# @param [Integer] limit (nil)
# @param [Array] group_ops ([])
+ # @param [Array] having_ops ([])
# @param [Integer] offset (nil)
# @param [Array] order_ops ([])
# Order Operations
# @param [Array] project (%i(*))
# Terms to project
# @param [Operator] reduced (false)
+ # @param [Operator] values_clause (nil)
+ # Top-level Values clause
+ # @param [Operator] where_clause (true)
+ # Emit 'WHERE' before GroupGraphPattern
# @param [Hash{Symbol => Object}] options
# @return [String]
def self.to_sparql(content,
+ datasets: [],
distinct: false,
extensions: {},
filter_ops: [],
group_ops: [],
+ having_ops: [],
limit: nil,
offset: nil,
order_ops: [],
project: %i(*),
reduced: false,
+ values_clause: nil,
+ where_clause: true,
**options)
str = ""
@@ -372,28 +384,41 @@ def self.to_sparql(content,
str << project.map do |p|
if expr = extensions.delete(p)
+ v = expr.to_sparql(as_statement: true, **options)
+ v = "<< #{v} >>" if expr.is_a?(RDF::Statement)
+ pp = p.to_sparql(**options)
# Replace projected variables with their extension, if any
- "(" + [expr, :AS, p].to_sparql(**options) + ")"
+ '(' + v + ' AS ' + pp + ')'
else
p.to_sparql(**options)
end
end.join(" ") + "\n"
end
- # Extensions
+ # DatasetClause
+ datasets.each do |ds|
+ str << "FROM #{ds.to_sparql(**options)}\n"
+ end
+
+ # Bind
extensions.each do |as, expression|
- content << "\nBIND (#{expression.to_sparql(**options)} AS #{as.to_sparql(**options)}) ."
+ v = expression.to_sparql(as_statement: true, **options)
+ v = "<< #{v} >>" if expression.is_a?(RDF::Statement)
+ content << "\nBIND (" << v << " AS " << as.to_sparql(**options) << ") ."
end
- # Filters
+ # Filter
filter_ops.each do |f|
- content << "\nFILTER #{f.to_sparql(**options)} ."
+ content << "\nFILTER (#{f.to_sparql(**options)}) ."
end
- # Where clause
- str << "WHERE {\n#{content}\n}\n"
+ # WhereClause / GroupGraphPattern
+ str << (where_clause ? "WHERE {\n#{content}\n}\n" : "{\n#{content}\n}\n")
- # Group
+ ##
+ # SolutionModifier
+ #
+ # GroupClause
unless group_ops.empty?
ops = group_ops.map do |o|
# Replace projected variables with their extension, if any
@@ -404,14 +429,22 @@ def self.to_sparql(content,
str << "GROUP BY #{ops.join(' ')}\n"
end
- # Order
+ # HavingClause
+ unless having_ops.empty?
+ str << "HAVING #{having_ops.to_sparql(**options)}"
+ end
+
+ # OrderClause
unless order_ops.empty?
str << "ORDER BY #{order_ops.to_sparql(**options)}\n"
end
- # Offset and Limmit
+ # LimitOffsetClauses
str << "OFFSET #{offset}\n" unless offset.nil?
str << "LIMIT #{limit}\n" unless limit.nil?
+
+ # Values Clause
+ str << values_clause.to_sparql(top_level: false, **options) if values_clause
str
end
@@ -640,12 +673,8 @@ def optimize!(**options)
# @return [SPARQL::Algebra::Expression] `self`
def rewrite(&block)
@operands = @operands.map do |op|
- # Rewrite the operand
- unless new_op = block.call(op)
- # Not re-written, rewrite
- new_op = op.respond_to?(:rewrite) ? op.rewrite(&block) : op
- end
- new_op
+ new_op = block.call(op)
+ new_op.respond_to?(:rewrite) ? new_op.rewrite(&block) : new_op
end
self
end
@@ -671,7 +700,6 @@ def to_sxp(prefixes: nil, base_uri: nil)
end
##
- #
# Returns a partial SPARQL grammar for the operator.
#
# @return [String]
diff --git a/lib/sparql/algebra/operator/abs.rb b/lib/sparql/algebra/operator/abs.rb
index 60e2096e..c0401c56 100644
--- a/lib/sparql/algebra/operator/abs.rb
+++ b/lib/sparql/algebra/operator/abs.rb
@@ -5,7 +5,7 @@ class Operator
#
# [121] BuiltInCall ::= ... | 'ABS' '(' Expression ')'
#
- # @example SPARQL Query
+ # @example SPARQL Grammar
# PREFIX :
# SELECT * WHERE {
# ?s :num ?num
diff --git a/lib/sparql/algebra/operator/alt.rb b/lib/sparql/algebra/operator/alt.rb
index 8f839809..8ea82176 100644
--- a/lib/sparql/algebra/operator/alt.rb
+++ b/lib/sparql/algebra/operator/alt.rb
@@ -79,7 +79,7 @@ def execute(queryable, **options, &block)
#
# @return [String]
def to_sparql(**options)
- "#{operands.first.to_sparql(**options)}|#{operands.last.to_sparql(**options)}"
+ "(#{operands.first.to_sparql(**options)}|#{operands.last.to_sparql(**options)})"
end
end # Alt
end # Operator
diff --git a/lib/sparql/algebra/operator/avg.rb b/lib/sparql/algebra/operator/avg.rb
index 774aa6d9..f57bc59e 100644
--- a/lib/sparql/algebra/operator/avg.rb
+++ b/lib/sparql/algebra/operator/avg.rb
@@ -54,7 +54,9 @@ def apply(enum, **options)
#
# @return [String]
def to_sparql(**options)
- "AVG(#{operands.to_sparql(**options)})"
+ distinct = operands.first == :distinct
+ args = distinct ? operands[1..-1] : operands
+ "AVG(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
end
end # Avg
end # Operator
diff --git a/lib/sparql/algebra/operator/bgp.rb b/lib/sparql/algebra/operator/bgp.rb
index 1e3fe183..6d1704f8 100644
--- a/lib/sparql/algebra/operator/bgp.rb
+++ b/lib/sparql/algebra/operator/bgp.rb
@@ -7,12 +7,20 @@ class Operator
#
# @example SPARQL Grammar
# PREFIX :
- # SELECT * { :s :p :o }
+ # SELECT * { ?s ?p ?o }
#
# @example SSE
# (prefix ((: ))
# (bgp (triple ?s ?p ?o)))
#
+ # @example SPARQL Grammar (sparql-star)
+ # PREFIX :
+ # SELECT * {<< :a :b :c >> :p1 :o1.}
+ #
+ # @example SSE (sparql-star)
+ # (prefix ((: ))
+ # (bgp (triple (triple :a :b :c) :p1 :o1)))
+ #
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
class BGP < Operator
NAME = [:bgp]
diff --git a/lib/sparql/algebra/operator/clear.rb b/lib/sparql/algebra/operator/clear.rb
index ffda3ced..684989d3 100644
--- a/lib/sparql/algebra/operator/clear.rb
+++ b/lib/sparql/algebra/operator/clear.rb
@@ -8,12 +8,18 @@ class Operator
#
# [32] Clear ::= 'CLEAR' 'SILENT'? GraphRefAll
#
- # @example SPARQL Grammar
+ # @example SPARQL Grammar (SILENT DEFAULT)
# CLEAR SILENT DEFAULT
#
- # @example SSE
+ # @example SSE (SILENT DEFAULT)
# (update (clear silent default))
#
+ # @example SPARQL Grammar (IRI)
+ # CLEAR GRAPH
+ #
+ # @example SSE (IRI)
+ # (update (clear ))
+ #
# @see https://www.w3.org/TR/sparql11-update/#clear
class Clear < Operator
include SPARQL::Algebra::Update
@@ -70,7 +76,11 @@ def execute(queryable, **options)
#
# @return [String]
def to_sparql(**options)
- "CLEAR " + operands.to_sparql(**options)
+ silent = operands.first == :silent
+ str = "CLEAR "
+ str << "SILENT " if operands.first == :silent
+ str << "GRAPH " if operands.last.is_a?(RDF::URI)
+ str << operands.last.to_sparql(**options)
end
end # Clear
end # Operator
diff --git a/lib/sparql/algebra/operator/construct.rb b/lib/sparql/algebra/operator/construct.rb
index 0b9845b2..c1278e75 100644
--- a/lib/sparql/algebra/operator/construct.rb
+++ b/lib/sparql/algebra/operator/construct.rb
@@ -99,7 +99,7 @@ def query_yields_statements?
# @return [String]
def to_sparql(**options)
str = "CONSTRUCT {\n" +
- operands[0].map { |e| e.to_sparql(as_statement: true, top_level: false, **options) }.join("\n") +
+ operands[0].map { |e| e.to_sparql(as_statement: true, top_level: false, **options) }.join(". \n") +
"\n}\n"
str << operands[1].to_sparql(top_level: true, project: nil, **options)
diff --git a/lib/sparql/algebra/operator/count.rb b/lib/sparql/algebra/operator/count.rb
index f7efcbeb..ef976399 100644
--- a/lib/sparql/algebra/operator/count.rb
+++ b/lib/sparql/algebra/operator/count.rb
@@ -11,11 +11,39 @@ class Operator
# WHERE { ?S ?P ?O }
#
# @example SSE
- # (prefix ((: ))
- # (project (?C)
- # (extend ((?C ??.0))
- # (group () ((??.0 (count ?O)))
- # (bgp (triple ?S ?P ?O))))))
+ # (prefix ((: ))
+ # (project (?C)
+ # (extend ((?C ??.0))
+ # (group () ((??.0 (count ?O)))
+ # (bgp (triple ?S ?P ?O))))))
+ #
+ # @example SPARQL Grammar (count(*))
+ # PREFIX :
+ #
+ # SELECT (COUNT(*) AS ?C)
+ # WHERE { ?S ?P ?O }
+ #
+ # @example SSE (count(*))
+ # (prefix
+ # ((: ))
+ # (project (?C)
+ # (extend ((?C ??.0))
+ # (group () ((??.0 (count)))
+ # (bgp (triple ?S ?P ?O))))))
+ #
+ # @example SPARQL Grammar (count(distinct *))
+ # PREFIX :
+ #
+ # SELECT (COUNT(DISTINCT *) AS ?C)
+ # WHERE { ?S ?P ?O }
+ #
+ # @example SSE (count(distinct *))
+ # (prefix
+ # ((: ))
+ # (project (?C)
+ # (extend ((?C ??.0))
+ # (group () ((??.0 (count distinct)))
+ # (bgp (triple ?S ?P ?O))))))
#
# @see https://www.w3.org/TR/sparql11-query/#defn_aggCount
class Count < Operator
@@ -39,7 +67,9 @@ def apply(enum, **options)
#
# @return [String]
def to_sparql(**options)
- "COUNT(#{operands.to_sparql(**options)})"
+ distinct = operands.first == :distinct
+ args = distinct ? operands[1..-1] : operands
+ "COUNT(#{'DISTINCT ' if distinct}#{args.empty? ? '*' : args.to_sparql(**options)})"
end
end # Count
end # Operator
diff --git a/lib/sparql/algebra/operator/create.rb b/lib/sparql/algebra/operator/create.rb
index 7c69c9c7..1ef956f2 100644
--- a/lib/sparql/algebra/operator/create.rb
+++ b/lib/sparql/algebra/operator/create.rb
@@ -55,10 +55,11 @@ def execute(queryable, **options)
#
# @return [String]
def to_sparql(**options)
- *args, last = operands.dup
- args += [:GRAPH, last]
-
- "CREATE " + args.to_sparql(**options)
+ silent = operands.first == :silent
+ str = "CREATE "
+ str << "SILENT " if operands.first == :silent
+ str << "GRAPH " if operands.last.is_a?(RDF::URI)
+ str << operands.last.to_sparql(**options)
end
end # Create
end # Operator
diff --git a/lib/sparql/algebra/operator/dataset.rb b/lib/sparql/algebra/operator/dataset.rb
index 61d00bfd..cbcd31ef 100644
--- a/lib/sparql/algebra/operator/dataset.rb
+++ b/lib/sparql/algebra/operator/dataset.rb
@@ -73,7 +73,7 @@ class Operator
# @example Dataset with two default data sources
#
# (prefix ((: ))
- # (dataset ( ) || (= ?g ))
# (graph ?g (bgp (triple ?s ?p ?o))))))
#
- # @example Dataset with multiple named graphs
+ #
+ # @example SPARQL Grammar
+ # BASE
+ # PREFIX :
+ #
+ # SELECT *
+ # FROM
+ # { ?s ?p ?o }
+ #
+ # @example SSE
+ # (base
+ # (prefix ((: ))
+ # (dataset ()
+ # (bgp (triple ?s ?p ?o)))))
+ #
# @see https://www.w3.org/TR/sparql11-query/#specifyingDataset
class Dataset < Binary
include Query
@@ -163,17 +177,11 @@ def execute(queryable, **options, &base)
#
# Returns a partial SPARQL grammar for this operator.
#
+ # Extracts datasets
+ #
# @return [String]
def to_sparql(**options)
- operands[0].each_with_object('') do |graph, str|
- str << if graph.is_a?(Array)
- "FROM #{graph[0].upcase} #{graph[1].to_sparql(**options)}\n"
- else
- "FROM #{graph.to_sparql(**options)}\n"
- end
- end.tap do |str|
- str << operands[1].to_sparql(**options)
- end
+ operands.last.to_sparql(datasets: operands.first, **options)
end
end # Dataset
end # Operator
diff --git a/lib/sparql/algebra/operator/delete.rb b/lib/sparql/algebra/operator/delete.rb
index 228dbf37..bc65d76b 100644
--- a/lib/sparql/algebra/operator/delete.rb
+++ b/lib/sparql/algebra/operator/delete.rb
@@ -75,7 +75,9 @@ def execute(queryable, solutions: nil, **options)
#
# @return [String]
def to_sparql(**options)
- "DELETE {\n" + operands.first.to_sparql(as_statement: true, **options) + "\n}"
+ "DELETE {\n" +
+ operands.first.to_sparql(as_statement: true, delimiter: " .\n", **options) +
+ "\n}"
end
end # Delete
end # Operator
diff --git a/lib/sparql/algebra/operator/delete_data.rb b/lib/sparql/algebra/operator/delete_data.rb
index 1511b80e..5794457d 100644
--- a/lib/sparql/algebra/operator/delete_data.rb
+++ b/lib/sparql/algebra/operator/delete_data.rb
@@ -54,7 +54,7 @@ def execute(queryable, **options)
# @return [String]
def to_sparql(**options)
"DELETE DATA {\n" +
- operands.first.to_sparql(as_statement: true, top_level: false, delimiter: "\n", **options) +
+ operands.first.to_sparql(as_statement: true, top_level: false, delimiter: ". \n", **options) +
"\n}"
end
end # DeleteData
diff --git a/lib/sparql/algebra/operator/delete_where.rb b/lib/sparql/algebra/operator/delete_where.rb
index 83245769..26eec944 100644
--- a/lib/sparql/algebra/operator/delete_where.rb
+++ b/lib/sparql/algebra/operator/delete_where.rb
@@ -71,7 +71,7 @@ def execute(queryable, **options)
# @return [String]
def to_sparql(**options)
"DELETE WHERE {\n" +
- operands.first.to_sparql(as_statement: true, top_level: false, delimiter: "\n", **options) +
+ operands.first.to_sparql(as_statement: true, top_level: false, delimiter: ". \n", **options) +
"\n}"
end
end # DeleteWhere
diff --git a/lib/sparql/algebra/operator/distinct.rb b/lib/sparql/algebra/operator/distinct.rb
index da99fbd2..3742ac9b 100644
--- a/lib/sparql/algebra/operator/distinct.rb
+++ b/lib/sparql/algebra/operator/distinct.rb
@@ -12,8 +12,8 @@ class Operator
# WHERE { ?x ?p ?v }
#
# @example SSE
- # (prefix ((xsd: )
- # (: ))
+ # (prefix ((: )
+ # (xsd: ))
# (distinct
# (project (?v)
# (bgp (triple ?x ?p ?v)))))
diff --git a/lib/sparql/algebra/operator/divide.rb b/lib/sparql/algebra/operator/divide.rb
index 370da362..a09d1cbf 100644
--- a/lib/sparql/algebra/operator/divide.rb
+++ b/lib/sparql/algebra/operator/divide.rb
@@ -60,7 +60,7 @@ def apply(left, right, **options)
#
# @return [String]
def to_sparql(**options)
- "#{operands.first.to_sparql(**options)} / #{operands.last.to_sparql(**options)}"
+ "(#{operands.first.to_sparql(**options)} / #{operands.last.to_sparql(**options)})"
end
end # Divide
end # Operator
diff --git a/lib/sparql/algebra/operator/drop.rb b/lib/sparql/algebra/operator/drop.rb
index e610322a..a3ebc4a9 100644
--- a/lib/sparql/algebra/operator/drop.rb
+++ b/lib/sparql/algebra/operator/drop.rb
@@ -10,12 +10,18 @@ class Operator
#
# [33] Drop ::= 'DROP' 'SILENT'? GraphRefAll
#
- # @example SPARQL Grammar
- # DROP DEFAULT
+ # @example SPARQL Grammar (SILENT DEFAULT)
+ # DROP SILENT DEFAULT
#
- # @example SSE
+ # @example SSE (SILENT DEFAULT)
# (update
- # (drop default))
+ # (drop silent default))
+ #
+ # @example SPARQL Grammar (IRI)
+ # DROP GRAPH
+ #
+ # @example SSE (IRI)
+ # (update (drop ))
#
# @see https://www.w3.org/TR/sparql11-update/#drop
class Drop < Operator
@@ -40,7 +46,6 @@ class Drop < Operator
def execute(queryable, **options)
debug(options) {"Drop"}
silent = operands.first == :silent
- silent = operands.first == :silent
operands.shift if silent
raise ArgumentError, "drop expected operand to be 'default', 'named', 'all', or an IRI" unless operands.length == 1
@@ -74,7 +79,11 @@ def execute(queryable, **options)
#
# @return [String]
def to_sparql(**options)
- "DROP " + operands.to_sparql(**options)
+ silent = operands.first == :silent
+ str = "DROP "
+ str << "SILENT " if operands.first == :silent
+ str << "GRAPH " if operands.last.is_a?(RDF::URI)
+ str << operands.last.to_sparql(**options)
end
end # Drop
end # Operator
diff --git a/lib/sparql/algebra/operator/encode_for_uri.rb b/lib/sparql/algebra/operator/encode_for_uri.rb
index 23e382e3..b6fb4f84 100644
--- a/lib/sparql/algebra/operator/encode_for_uri.rb
+++ b/lib/sparql/algebra/operator/encode_for_uri.rb
@@ -9,15 +9,13 @@ class Operator
#
# @example SPARQL Grammar
# PREFIX :
- # PREFIX xsd:
# SELECT ?s ?str (ENCODE_FOR_URI(?str) AS ?encoded) WHERE {
# ?s :str ?str
# }
#
# @example SSE
- # (prefix
- # ((: ))
- # (project (?str ?encoded)
+ # (prefix ((: ))
+ # (project (?s ?str ?encoded)
# (extend ((?encoded (encode_for_uri ?str)))
# (bgp (triple ?s :str ?str)))))
#
diff --git a/lib/sparql/algebra/operator/exprlist.rb b/lib/sparql/algebra/operator/exprlist.rb
index 3964c24a..0554ef95 100644
--- a/lib/sparql/algebra/operator/exprlist.rb
+++ b/lib/sparql/algebra/operator/exprlist.rb
@@ -8,6 +8,8 @@ class Operator
# [72] ExpressionList ::= NIL | '(' Expression ( ',' Expression )* ')'
#
# @example SPARQL Grammar
+ # PREFIX :
+ #
# SELECT ?v ?w
# {
# FILTER (?v = 2)
@@ -17,7 +19,7 @@ class Operator
# }
#
# @example SSE
- # (prefix ((: ))
+ # (prefix ((: ))
# (project (?v ?w)
# (filter (exprlist (= ?v 2) (= ?w 3))
# (bgp
diff --git a/lib/sparql/algebra/operator/extend.rb b/lib/sparql/algebra/operator/extend.rb
index 9dd564f7..8d81976e 100644
--- a/lib/sparql/algebra/operator/extend.rb
+++ b/lib/sparql/algebra/operator/extend.rb
@@ -10,14 +10,47 @@ class Operator
# @example SPARQL Grammar
# SELECT ?z
# {
- # ?x ?o
- # BIND(?o+1 AS ?z)
+ # ?x ?o
+ # BIND(?o+10 AS ?z)
# }
#
# @example SSE
# (project (?z)
# (extend ((?z (+ ?o 10)))
- # (bgp (triple ?s ?o))))
+ # (bgp (triple ?x ?o))))
+ #
+ # @example SPARQL Grammar (cast as boolean)
+ # PREFIX :
+ # PREFIX rdf:
+ # PREFIX xsd:
+ # SELECT ?a ?v (xsd:boolean(?v) AS ?boolean)
+ # WHERE { ?a :p ?v . }
+ #
+ # @example SSE (cast as boolean)
+ # (prefix ((: )
+ # (rdf: )
+ # (xsd: ))
+ # (project (?a ?v ?boolean)
+ # (extend ((?boolean (xsd:boolean ?v)))
+ # (bgp (triple ?a :p ?v)))))
+ #
+ # @example SPARQL Grammar (inner bind)
+ # PREFIX :
+ #
+ # SELECT ?z ?s1
+ # {
+ # ?s ?p ?o .
+ # BIND(?o+1 AS ?z)
+ # ?s1 ?p1 ?z
+ # }
+ #
+ # @example SSE (inner bind)
+ # (prefix ((: ))
+ # (project (?z ?s1)
+ # (join
+ # (extend ((?z (+ ?o 1)))
+ # (bgp (triple ?s ?p ?o)))
+ # (bgp (triple ?s1 ?p1 ?z)))))
#
# @see https://www.w3.org/TR/sparql11-query/#evaluation
class Extend < Operator::Binary
diff --git a/lib/sparql/algebra/operator/filter.rb b/lib/sparql/algebra/operator/filter.rb
index c7493135..26e1179e 100644
--- a/lib/sparql/algebra/operator/filter.rb
+++ b/lib/sparql/algebra/operator/filter.rb
@@ -89,7 +89,7 @@ def validate!
# @return [String]
def to_sparql(**options)
filter_ops = operands.first.is_a?(Operator::Exprlist) ? operands.first.operands : [operands.first]
- operands.last.to_sparql(filter_ops: filter_ops, **options)
+ str = operands.last.to_sparql(filter_ops: filter_ops, **options)
end
end # Filter
end # Operator
diff --git a/lib/sparql/algebra/operator/function_call.rb b/lib/sparql/algebra/operator/function_call.rb
new file mode 100644
index 00000000..e1a7b013
--- /dev/null
+++ b/lib/sparql/algebra/operator/function_call.rb
@@ -0,0 +1,64 @@
+
+module SPARQL; module Algebra
+ class Operator
+ ##
+ # The SPARQL `function_call` operator.
+ #
+ # [70] FunctionCall ::= iri ArgList
+ #
+ # @example SPARQL Grammar
+ # PREFIX xsd:
+ # SELECT *
+ # WHERE { ?s ?p ?o . FILTER xsd:integer(?o) }
+ #
+ # @example SSE
+ # (prefix
+ # ((xsd: ))
+ # (filter (xsd:integer ?o)
+ # (bgp (triple ?s ?p ?o))))
+ #
+ # @see https://www.w3.org/TR/sparql11-query/#funcex-regex
+ # @see https://www.w3.org/TR/xpath-functions/#func-matches
+ class FunctionCall < Operator
+ include Evaluatable
+
+ NAME = :function_call
+
+ ##
+ # Invokes the function with the passed arguments.
+ #
+ # @param [RDF::IRI] iri
+ # Identifies the function
+ # @param [Array] args
+ # @return [RDF::Term]
+ def apply(iri, *args, **options)
+ args = RDF.nil == args.last ? args[0..-2] : args
+ SPARQL::Algebra::Expression.extension(iri, *args, **options)
+ end
+
+ ##
+ # Returns the SPARQL S-Expression (SSE) representation of this expression.
+ #
+ # Remove the optional argument.
+ #
+ # @return [Array] `self`
+ # @see https://openjena.org/wiki/SSE
+ def to_sxp_bin
+ @operands.map(&:to_sxp_bin)
+ end
+
+ ##
+ #
+ # Returns a partial SPARQL grammar for this operator.
+ #
+ # @return [String]
+ def to_sparql(**options)
+ iri, args = operands
+ iri.to_sparql(**options) +
+ '(' +
+ args.to_sparql(delimiter: ', ', **options) +
+ ')'
+ end
+ end # FunctionCall
+ end # Operator
+end; end # SPARQL::Algebra
diff --git a/lib/sparql/algebra/operator/graph.rb b/lib/sparql/algebra/operator/graph.rb
index 3bb31ddd..1f700cfb 100644
--- a/lib/sparql/algebra/operator/graph.rb
+++ b/lib/sparql/algebra/operator/graph.rb
@@ -7,7 +7,7 @@ class Operator
#
# [58] GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern
#
- # @example SPARQL Grammar
+ # @example SPARQL Grammar (query)
# PREFIX :
# SELECT * {
# GRAPH ?g { ?s ?p ?o }
@@ -18,15 +18,50 @@ class Operator
# (graph ?g
# (bgp (triple ?s ?p ?o))))
#
- # @example of a query
+ # @example SPARQL Grammar (named set of statements)
+ # PREFIX :
+ # SELECT * {
+ # GRAPH :g { :s :p :o }
+ # }
+ #
+ # @example SSE (named set of statements)
# (prefix ((: ))
+ # (graph :g
+ # (bgp (triple :s :p :o))))
+ #
+ # @example SPARQL Grammar (syntax-graph-05.rq)
+ # PREFIX :
+ # SELECT *
+ # WHERE
+ # {
+ # :x :p :z
+ # GRAPH ?g { :x :b ?a . GRAPH ?g2 { :x :p ?x } }
+ # }
+ #
+ # @example SSE (syntax-graph-05.rq)
+ # (prefix ((: ))
+ # (join
+ # (bgp (triple :x :p :z))
# (graph ?g
- # (bgp (triple ?s ?p ?o))))
+ # (join
+ # (bgp (triple :x :b ?a))
+ # (graph ?g2
+ # (bgp (triple :x :p ?x)))))))
#
- # @example named set of statements
- # (prefix ((: ))
- # (graph :g
- # ((triple :s :p :o))))
+ # @example SPARQL Grammar (pp06.rq)
+ # prefix ex:
+ # prefix in:
+ #
+ # select ?x where {
+ # graph ?g {in:a ex:p1/ex:p2 ?x}
+ # }
+ #
+ # @example SSE (syntax-graph-05.rq)
+ # (prefix ((ex: )
+ # (in: ))
+ # (project (?x)
+ # (graph ?g
+ # (path in:a (seq ex:p1 ex:p2) ?x))))
#
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
class Graph < Operator::Binary
@@ -89,6 +124,21 @@ def execute(queryable, **options, &block)
def rewrite(&block)
self
end
+
+ ##
+ #
+ # Returns a partial SPARQL grammar for this operator.
+ #
+ # @param [Boolean] top_level (true)
+ # Treat this as a top-level, generating SELECT ... WHERE {}
+ # @return [String]
+ def to_sparql(top_level: true, **options)
+ query = operands.last.to_sparql(top_level: false, **options)
+ # Paths don't automatically get braces.
+ query = "{\n#{query}\n}" unless query.start_with?('{')
+ str = "GRAPH #{operands.first.to_sparql(**options)} " + query
+ top_level ? Operator.to_sparql(str, **options) : str
+ end
end # Graph
end # Operator
end; end # SPARQL::Algebra
diff --git a/lib/sparql/algebra/operator/group.rb b/lib/sparql/algebra/operator/group.rb
index de834c59..15222055 100644
--- a/lib/sparql/algebra/operator/group.rb
+++ b/lib/sparql/algebra/operator/group.rb
@@ -25,6 +25,37 @@ class Operator
# (group (?P) ((??.0 (count ?O)))
# (bgp (triple ?S ?P ?O))))))
#
+ # @example SPARQL Grammar (HAVING aggregate)
+ # PREFIX :
+ # SELECT ?s (AVG(?o) AS ?avg)
+ # WHERE { ?s ?p ?o }
+ # GROUP BY ?s
+ # HAVING (AVG(?o) <= 2.0)
+ #
+ # @example SSE (HAVING aggregate)
+ # (prefix ((: ))
+ # (project (?s ?avg)
+ # (filter (<= ??.0 2.0)
+ # (extend ((?avg ??.0))
+ # (group (?s) ((??.0 (avg ?o)))
+ # (bgp (triple ?s ?p ?o)))))) )
+ #
+ # @example SPARQL Grammar (non-triveal filters)
+ # PREFIX :
+ # SELECT ?g (AVG(?p) AS ?avg) ((MIN(?p) + MAX(?p)) / 2 AS ?c)
+ # WHERE { ?g :p ?p . }
+ # GROUP BY ?g
+ #
+ # @example SSE (non-triveal filters)
+ # (prefix ((: ))
+ # (project (?g ?avg ?c)
+ # (extend ((?avg ??.0) (?c (/ (+ ??.1 ??.2) 2)))
+ # (group (?g)
+ # ((??.0 (avg ?p))
+ # (??.1 (min ?p))
+ # (??.2 (max ?p)))
+ # (bgp (triple ?g :p ?p)))) ))
+ #
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
class Group < Operator
include Query
@@ -137,16 +168,56 @@ def validate!
#
# @param [Hash{Symbol => Operator}] extensions
# Variable bindings
+ # @param [Array] filter_ops ([])
+ # Filter Operations
# @return [String]
- def to_sparql(extensions: {}, **options)
+ def to_sparql(extensions: {}, filter_ops: [], **options)
+ having_ops = []
if operands.length > 2
+ temp_bindings = operands[1].inject({}) {|memo, (var, op)| memo.merge(var => op)}
# Replace extensions from temporary bindings
- operands[1].each do |var, op|
- ext_var = extensions.invert.fetch(var)
- extensions[ext_var] = op
+ temp_bindings.each do |var, op|
+ # Update extensions using a temporarily bound variable with its binding
+ extensions = extensions.inject({}) do |memo, (ext_var, ext_op)|
+ if ext_op.is_a?(Operator)
+ # Try to recursivley replace variable within operator
+ new_op = ext_op.deep_dup.rewrite do |operand|
+ if operand.is_a?(Variable) && operand.to_sym == var.to_sym
+ op.dup
+ else
+ operand
+ end
+ end
+ memo.merge(ext_var => new_op)
+ elsif ext_op.is_a?(Variable) && ext_op.to_sym == var.to_sym
+ memo.merge(ext_var => op)
+ else
+ # Doesn't match this variable, so don't change
+ memo.merge(ext_var => ext_op)
+ end
+ end
+
+ # Filter ops using temporary bindinds are used for HAVING clauses
+ filter_ops.each do |fop|
+ having_ops << fop if fop.descendants.include?(var) && !having_ops.include?(fop)
+ end
+ end
+
+ # If used in a HAVING clause, it's not also a filter
+ filter_ops -= having_ops
+
+ # Replace each operand in having using var with it's corresponding operation
+ having_ops = having_ops.map do |op|
+ op.dup.rewrite do |operand|
+ # Rewrite based on temporary bindings
+ temp_bindings.fetch(operand, operand)
+ end
end
end
- operands.last.to_sparql(extensions: extensions, group_ops: operands.first, **options)
+ operands.last.to_sparql(extensions: extensions,
+ group_ops: operands.first,
+ having_ops: having_ops,
+ **options)
end
end # Group
end # Operator
diff --git a/lib/sparql/algebra/operator/group_concat.rb b/lib/sparql/algebra/operator/group_concat.rb
index 4fc35088..27e1f1ca 100644
--- a/lib/sparql/algebra/operator/group_concat.rb
+++ b/lib/sparql/algebra/operator/group_concat.rb
@@ -16,6 +16,24 @@ class Operator
# (group () ((??.0 (group_concat ?x)))
# (bgp))))
#
+ # @example SPARQL Grammar (DISTINCT)
+ # SELECT (GROUP_CONCAT(DISTINCT ?x) AS ?y) {}
+ #
+ # @example SSE (DISTINCT)
+ # (project (?y)
+ # (extend ((?y ??.0))
+ # (group () ((??.0 (group_concat distinct ?x)))
+ # (bgp))))
+ #
+ # @example SPARQL Grammar (SEPARATOR)
+ # SELECT (GROUP_CONCAT(?x; SEPARATOR=';') AS ?y) {}
+ #
+ # @example SSE (SEPARATOR)
+ # (project (?y)
+ # (extend ((?y ??.0))
+ # (group () ((??.0 (group_concat (separator ";") ?x)))
+ # (bgp))))
+ #
# @see https://www.w3.org/TR/sparql11-query/#defn_aggGroupConcat
class GroupConcat < Operator
include Aggregate
@@ -63,7 +81,13 @@ def apply(enum, separator, **options)
#
# @return [String]
def to_sparql(**options)
- "GROUP_CONCAT(#{operands.to_sparql(delimiter: ', ', **options)})"
+ distinct = operands.first == :distinct
+ args = distinct ? operands[1..-1] : operands
+ separator = args.first.last if args.first.is_a?(Array) && args.first.first == :separator
+ args = args[1..-1] if separator
+ str = "GROUP_CONCAT(#{'DISTINCT ' if distinct}#{args.to_sparql(delimiter: ', ', **options)}"
+ str << "; SEPARATOR=#{separator.to_sparql}" if separator
+ str << ")"
end
end # GroupConcat
end # Operator
diff --git a/lib/sparql/algebra/operator/if.rb b/lib/sparql/algebra/operator/if.rb
index eae31f27..9cb9568b 100644
--- a/lib/sparql/algebra/operator/if.rb
+++ b/lib/sparql/algebra/operator/if.rb
@@ -48,15 +48,15 @@ def evaluate(bindings, **options)
rescue
raise TypeError
end
- end # If
- ##
- #
- # Returns a partial SPARQL grammar for this operator.
- #
- # @return [String]
- def to_sparql(**options)
- "IF(" + operands.to_sparql(delimiter: ', ', **options) + ")"
- end
- end # If
+ ##
+ #
+ # Returns a partial SPARQL grammar for this operator.
+ #
+ # @return [String]
+ def to_sparql(**options)
+ "IF(" + operands.to_sparql(delimiter: ', ', **options) + ")"
+ end
+ end # If
+ end # Operator
end; end # SPARQL::Algebra
diff --git a/lib/sparql/algebra/operator/insert.rb b/lib/sparql/algebra/operator/insert.rb
index 7a096006..f9eb4ecb 100644
--- a/lib/sparql/algebra/operator/insert.rb
+++ b/lib/sparql/algebra/operator/insert.rb
@@ -69,7 +69,9 @@ def execute(queryable, solutions: nil, **options)
#
# @return [String]
def to_sparql(**options)
- "INSERT {\n" + operands.first.to_sparql(as_statement: true, **options) + "\n}"
+ "INSERT {\n" +
+ operands.first.to_sparql(as_statement: true, delimiter: " .\n", **options) +
+ "\n}"
end
end # Insert
end # Operator
diff --git a/lib/sparql/algebra/operator/insert_data.rb b/lib/sparql/algebra/operator/insert_data.rb
index f4e66f15..86137965 100644
--- a/lib/sparql/algebra/operator/insert_data.rb
+++ b/lib/sparql/algebra/operator/insert_data.rb
@@ -54,7 +54,7 @@ def execute(queryable, **options)
# @return [String]
def to_sparql(**options)
"INSERT DATA {\n" +
- operands.first.to_sparql(as_statement: true, top_level: false, delimiter: "\n", **options) +
+ operands.first.to_sparql(as_statement: true, top_level: false, delimiter: ". \n", **options) +
"\n}"
end
end # InsertData
diff --git a/lib/sparql/algebra/operator/is_blank.rb b/lib/sparql/algebra/operator/is_blank.rb
index a833a927..b0045d6c 100644
--- a/lib/sparql/algebra/operator/is_blank.rb
+++ b/lib/sparql/algebra/operator/is_blank.rb
@@ -13,8 +13,7 @@ class Operator
# }
#
# @example SSE
- # (prefix ((xsd: )
- # (: ))
+ # (prefix ((: ))
# (project (?x ?v)
# (filter (isBlank ?v)
# (bgp (triple ?x :p ?v)))))
diff --git a/lib/sparql/algebra/operator/is_iri.rb b/lib/sparql/algebra/operator/is_iri.rb
index 8d49b16d..e4c3ed60 100644
--- a/lib/sparql/algebra/operator/is_iri.rb
+++ b/lib/sparql/algebra/operator/is_iri.rb
@@ -13,8 +13,7 @@ class Operator
# }
#
# @example SSE
- # (prefix ((xsd: )
- # (: ))
+ # (prefix ((: ))
# (project (?x ?v)
# (filter (isIRI ?v)
# (bgp (triple ?x :p ?v)))))
diff --git a/lib/sparql/algebra/operator/is_literal.rb b/lib/sparql/algebra/operator/is_literal.rb
index fdd23447..0f16332a 100644
--- a/lib/sparql/algebra/operator/is_literal.rb
+++ b/lib/sparql/algebra/operator/is_literal.rb
@@ -13,8 +13,7 @@ class Operator
# }
#
# @example SSE
- # (prefix ((xsd: )
- # (: ))
+ # (prefix ((: ))
# (project (?x ?v)
# (filter (isLiteral ?v)
# (bgp (triple ?x :p ?v)))))
diff --git a/lib/sparql/algebra/operator/is_numeric.rb b/lib/sparql/algebra/operator/is_numeric.rb
index b43076b0..ec3b6382 100644
--- a/lib/sparql/algebra/operator/is_numeric.rb
+++ b/lib/sparql/algebra/operator/is_numeric.rb
@@ -15,8 +15,7 @@ class Operator
# }
#
# @example SSE
- # (prefix ((xsd: )
- # (: ))
+ # (prefix ((: ))
# (project (?x ?v)
# (filter (isNumeric ?v)
# (bgp (triple ?x :p ?v)))))
diff --git a/lib/sparql/algebra/operator/join.rb b/lib/sparql/algebra/operator/join.rb
index 2f2edd2c..dab09d5e 100644
--- a/lib/sparql/algebra/operator/join.rb
+++ b/lib/sparql/algebra/operator/join.rb
@@ -19,6 +19,22 @@ class Operator
# (graph ?g
# (bgp (triple ?s ?q ?v)))))
#
+ # @example SPARQL Grammar (inline filter)
+ # PREFIX :
+ # ASK {
+ # :who :homepage ?homepage
+ # FILTER REGEX(?homepage, "^http://example.org/")
+ # :who :schoolHomepage ?schoolPage
+ # }
+ #
+ # @example SSE (inline filter)
+ # (prefix ((: ))
+ # (ask
+ # (filter (regex ?homepage "^http://example.org/")
+ # (join
+ # (bgp (triple :who :homepage ?homepage))
+ # (bgp (triple :who :schoolHomepage ?schoolPage))))))
+ #
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
class Join < Operator::Binary
include Query
@@ -98,10 +114,28 @@ def optimize!(**options)
#
# @param [Boolean] top_level (true)
# Treat this as a top-level, generating SELECT ... WHERE {}
+ # @param [Hash{Symbol => Operator}] extensions
+ # Variable bindings
+ # @param [Array] filter_ops ([])
+ # Filter Operations
# @return [String]
- def to_sparql(top_level: true, **options)
- str = operands.to_sparql(top_level: false, delimiter: "\n", **options)
- top_level ? Operator.to_sparql(str, **options) : str
+ def to_sparql(top_level: true, filter_ops: [], extensions: {}, **options)
+ # If this is top-level, and the last operand is a Table (values), put the values at the outer-level
+ str = "{\n" + operands.first.to_sparql(top_level: false, extensions: {}, **options)
+
+ # Any accrued filters go here.
+ filter_ops.each do |op|
+ str << "\nFILTER (#{op.to_sparql(**options)}) ."
+ end
+
+ if top_level && operands.last.is_a?(Table)
+ str << "\n}"
+ options = options.merge(values_clause: operands.last)
+ else
+ str << "\n{\n" + operands.last.to_sparql(top_level: false, extensions: {}, **options) + "\n}\n}"
+ end
+
+ top_level ? Operator.to_sparql(str, extensions: extensions, **options) : str
end
end # Join
end # Operator
diff --git a/lib/sparql/algebra/operator/lcase.rb b/lib/sparql/algebra/operator/lcase.rb
index 125cfea2..b25f0e67 100644
--- a/lib/sparql/algebra/operator/lcase.rb
+++ b/lib/sparql/algebra/operator/lcase.rb
@@ -12,9 +12,8 @@ class Operator
# }
#
# @example SSE
- # (prefix
- # ((: ))
- # (project (?str ?lstr)
+ # (prefix ((: ))
+ # (project (?s ?lstr)
# (extend ((?lstr (lcase ?str)))
# (bgp (triple ?s :str ?str)))))
#
diff --git a/lib/sparql/algebra/operator/left_join.rb b/lib/sparql/algebra/operator/left_join.rb
index c3eae45a..7dc20592 100644
--- a/lib/sparql/algebra/operator/left_join.rb
+++ b/lib/sparql/algebra/operator/left_join.rb
@@ -131,14 +131,27 @@ def optimize!(**options)
#
# @param [Boolean] top_level (true)
# Treat this as a top-level, generating SELECT ... WHERE {}
+ # @param [Hash{Symbol => Operator}] extensions
+ # Variable bindings
+ # @param [Array] filter_ops ([])
+ # Filter Operations
# @return [String]
- def to_sparql(top_level: true, **options)
- str = operands[0].to_sparql(top_level: false, **options) +
- "\nOPTIONAL { \n" +
- operands[1].to_sparql(top_level: false, **options) + "\n"
- str << 'FILTER (' + operands[2].to_sparql(**options) + ") \n" if operands[2]
- str << '}'
- top_level ? Operator.to_sparql(str, **options) : str
+ def to_sparql(top_level: true, filter_ops: [], extensions: {}, **options)
+ str = "{\n" + operands[0].to_sparql(top_level: false, extensions: {}, **options)
+ str <<
+ "\nOPTIONAL {\n" +
+ operands[1].to_sparql(top_level: false, extensions: {}, **options)
+ case operands[2]
+ when SPARQL::Algebra::Operator::Exprlist
+ operands[2].operands.each do |op|
+ str << "\nFILTER (" + op.to_sparql(**options) + ")"
+ end
+ when nil
+ else
+ str << "\nFILTER (" + operands[2].to_sparql(**options) + ")"
+ end
+ str << "\n}}"
+ top_level ? Operator.to_sparql(str, filter_ops: filter_ops, extensions: extensions, **options) : str
end
end # LeftJoin
end # Operator
diff --git a/lib/sparql/algebra/operator/max.rb b/lib/sparql/algebra/operator/max.rb
index 62ca8add..52fb3902 100644
--- a/lib/sparql/algebra/operator/max.rb
+++ b/lib/sparql/algebra/operator/max.rb
@@ -56,7 +56,9 @@ def apply(enum, **options)
#
# @return [String]
def to_sparql(**options)
- "MAX(" + operands.to_sparql(**options) + ")"
+ distinct = operands.first == :distinct
+ args = distinct ? operands[1..-1] : operands
+ "MAX(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
end
end # Max
end # Operator
diff --git a/lib/sparql/algebra/operator/min.rb b/lib/sparql/algebra/operator/min.rb
index 20cc6169..48ab4c13 100644
--- a/lib/sparql/algebra/operator/min.rb
+++ b/lib/sparql/algebra/operator/min.rb
@@ -15,7 +15,7 @@ class Operator
# (project (?min)
# (extend ((?min ??.0))
# (group () ((??.0 (min ?o)))
- # (bgp (triple ?s ?p ?o))))))
+ # (bgp (triple ?s :dec ?o))))))
#
# @see https://www.w3.org/TR/sparql11-query/#defn_aggMin
class Min < Operator
@@ -56,7 +56,9 @@ def apply(enum, **options)
#
# @return [String]
def to_sparql(**options)
- "MIN(" + operands.to_sparql(**options) + ")"
+ distinct = operands.first == :distinct
+ args = distinct ? operands[1..-1] : operands
+ "MIN(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
end
end # Min
end # Operator
diff --git a/lib/sparql/algebra/operator/minus.rb b/lib/sparql/algebra/operator/minus.rb
index a858416a..ce48c453 100644
--- a/lib/sparql/algebra/operator/minus.rb
+++ b/lib/sparql/algebra/operator/minus.rb
@@ -14,6 +14,32 @@ class Operator
# (triple ?s ?p ?o))
# (bgp (triple ?s ?q ?v)))
#
+ # @example SPARQL Grammar (inline filter)
+ # PREFIX :
+ # SELECT (?s1 AS ?subset) (?s2 AS ?superset)
+ # WHERE {
+ # ?s2 a :Set .
+ # ?s1 a :Set .
+ # FILTER(?s1 != ?s2)
+ # MINUS {
+ # ?s1 a :Set .
+ # ?s2 a :Set .
+ # FILTER(?s1 != ?s2)
+ # }
+ # }
+ #
+ # @example SSE (inline filter)
+ # (prefix ((: ))
+ # (project (?subset ?superset)
+ # (extend ((?subset ?s1) (?superset ?s2))
+ # (filter (!= ?s1 ?s2)
+ # (minus
+ # (bgp (triple ?s2 a :Set) (triple ?s1 a :Set))
+ # (filter (!= ?s1 ?s2)
+ # (bgp
+ # (triple ?s1 a :Set)
+ # (triple ?s2 a :Set))))))))
+ #
# @see https://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
class Minus < Operator::Binary
@@ -74,15 +100,29 @@ def optimize!(**options)
#
# Returns a partial SPARQL grammar for this operator.
#
+ # @param [Hash{Symbol => Operator}] extensions
+ # Variable bindings
+ # @param [Array] filter_ops ([])
+ # Filter Operations
# @param [Boolean] top_level (true)
# Treat this as a top-level, generating SELECT ... WHERE {}
# @return [String]
- def to_sparql(top_level: true, **options)
- str = operands.first.to_sparql(top_level: false, **options) + "\n"
- str << "MINUS {\n"
- str << operands.last.to_sparql(top_level: false, **options)
- str << "\n}"
- top_level ? Operator.to_sparql(str, **options) : str
+ def to_sparql(top_level: true, filter_ops: [], extensions: {}, **options)
+ lhs, *rhs = operands
+ str = "{\n" + lhs.to_sparql(top_level: false, extensions: {}, **options)
+
+ # Any accrued filters go here.
+ filter_ops.each do |op|
+ str << "\nFILTER (#{op.to_sparql(**options)}) ."
+ end
+
+ rhs.each do |minus|
+ str << "\nMINUS {\n"
+ str << minus.to_sparql(top_level: false, extensions: {}, **options)
+ str << "\n}"
+ end
+ str << "}"
+ top_level ? Operator.to_sparql(str, extensions: extensions, **options) : str
end
end # Minus
end # Operator
diff --git a/lib/sparql/algebra/operator/multiply.rb b/lib/sparql/algebra/operator/multiply.rb
index 77daba5b..03e15ca9 100644
--- a/lib/sparql/algebra/operator/multiply.rb
+++ b/lib/sparql/algebra/operator/multiply.rb
@@ -50,7 +50,7 @@ def apply(left, right, **options)
#
# @return [String]
def to_sparql(**options)
- "#{operands.first.to_sparql(**options)} * #{operands.last.to_sparql(**options)}"
+ "(#{operands.first.to_sparql(**options)} * #{operands.last.to_sparql(**options)})"
end
end # Multiply
end # Operator
diff --git a/lib/sparql/algebra/operator/notoneof.rb b/lib/sparql/algebra/operator/notoneof.rb
index bdddc1b5..c397c11c 100644
--- a/lib/sparql/algebra/operator/notoneof.rb
+++ b/lib/sparql/algebra/operator/notoneof.rb
@@ -8,13 +8,13 @@ class Operator
# @example SPARQL Grammar
# PREFIX ex:
# PREFIX in:
- # ASK { in:b ^ex:p in:a }
+ # ASK { in:a !(ex:p1|ex:p2) ?x }
#
# @example SSE
# (prefix ((ex: )
- # (in: ))
+ # (in: ))
# (ask
- # (path in:b (reverse ex:p) in:a)))
+ # (path in:a (notoneof ex:p1 ex:p2) ?x)))
#
# @see https://www.w3.org/TR/sparql11-query/#eval_negatedPropertySet
class NotOneOf < Operator
@@ -56,6 +56,15 @@ def execute(queryable, **options, &block)
block.call(solution)
end
end
+
+ ##
+ #
+ # Returns a partial SPARQL grammar for this operator.
+ #
+ # @return [String]
+ def to_sparql(**options)
+ "!(" + operands.to_sparql(delimiter: ' | ', **options) + ')'
+ end
end # NotOneOf
end # Operator
end; end # SPARQL::Algebra
diff --git a/lib/sparql/algebra/operator/order.rb b/lib/sparql/algebra/operator/order.rb
index 4df4861d..a0a8bf85 100644
--- a/lib/sparql/algebra/operator/order.rb
+++ b/lib/sparql/algebra/operator/order.rb
@@ -17,6 +17,50 @@ class Operator
# (order ((asc ?name))
# (bgp (triple ?x foaf:name ?name)))))
#
+ # @example SPARQL Grammar (with builtin)
+ # PREFIX :
+ # SELECT ?s WHERE {
+ # ?s :p ?o .
+ # }
+ # ORDER BY str(?o)
+ #
+ # @example SSE (with builtin)
+ # (prefix ((: ))
+ # (project (?s)
+ # (order ((str ?o))
+ # (bgp (triple ?s :p ?o)))))
+ #
+ # @example SPARQL Grammar (with bracketed expression)
+ # PREFIX :
+ # SELECT ?s WHERE {
+ # ?s :p ?o1 ; :q ?o2 .
+ # } ORDER BY (?o1 + ?o2)
+ #
+ # @example SSE (with bracketed expression)
+ # (prefix
+ # ((: ))
+ # (project (?s)
+ # (order ((+ ?o1 ?o2))
+ # (bgp
+ # (triple ?s :p ?o1)
+ # (triple ?s :q ?o2)))))
+ #
+ # @example SPARQL Grammar (with function call)
+ # PREFIX :
+ # PREFIX xsd:
+ # SELECT *
+ # { ?s ?p ?o }
+ # ORDER BY
+ # DESC(?o+57) xsd:string(?o) ASC(?s)
+ #
+ # @example SSE (with function call)
+ # (prefix ((: )
+ # (xsd: ))
+ # (order ((desc (+ ?o 57))
+ # (xsd:string ?o)
+ # (asc ?s))
+ # (bgp (triple ?s ?p ?o))))
+ #
# @see https://www.w3.org/TR/sparql11-query/#modOrderBy
class Order < Operator::Binary
include Query
diff --git a/lib/sparql/algebra/operator/plus.rb b/lib/sparql/algebra/operator/plus.rb
index 31c31ecc..5f4e975b 100644
--- a/lib/sparql/algebra/operator/plus.rb
+++ b/lib/sparql/algebra/operator/plus.rb
@@ -69,7 +69,7 @@ def apply(left, right = nil, **options)
#
# @return [String]
def to_sparql(**options)
- "#{operands.first.to_sparql(**options)} + #{operands.last.to_sparql(**options)}"
+ "(#{operands.first.to_sparql(**options)} + #{operands.last.to_sparql(**options)})"
end
end # Plus
end # Operator
diff --git a/lib/sparql/algebra/operator/project.rb b/lib/sparql/algebra/operator/project.rb
index 60913997..902cb0e7 100644
--- a/lib/sparql/algebra/operator/project.rb
+++ b/lib/sparql/algebra/operator/project.rb
@@ -20,20 +20,37 @@ class Operator
# (filter (= ?v 2)
# (bgp (triple ?s :p ?v)))))
#
- # ## Sub select
- #
- # @example SPARQL Grammar
+ # @example SPARQL Grammar (Sub select)
# SELECT (1 AS ?X ) {
# SELECT (2 AS ?Y ) {}
# }
#
- # @example SSE
+ # @example SSE (Sub select)
# (project (?X)
# (extend ((?X 1))
# (project (?Y)
# (extend ((?Y 2))
# (bgp)))))
#
+ # @example SPARQL Grammar (filter projection)
+ # PREFIX :
+ # ASK {
+ # {SELECT (GROUP_CONCAT(?o) AS ?g) WHERE {
+ # :a :p1 ?o
+ # }}
+ # FILTER(?g = "1 22" || ?g = "22 1")
+ # }
+ #
+ # @example SSE (filter projection)
+ # (prefix ((: ))
+ # (ask
+ # (filter
+ # (|| (= ?g "1 22") (= ?g "22 1"))
+ # (project (?g)
+ # (extend ((?g ??.0))
+ # (group () ((??.0 (group_concat ?o)))
+ # (bgp (triple :a :p1 ?o)))))) ))
+ #
# @see https://www.w3.org/TR/sparql11-query/#modProjection
class Project < Operator::Binary
include Query
@@ -77,6 +94,7 @@ def to_sparql(**options)
# Any of these options indicates we're in a sub-select
opts = options.dup.delete_if {|k,v| %I{extensions filter_ops project}.include?(k)}
content = operands.last.to_sparql(project: vars, **opts)
+ content = "{#{content}}" unless content.start_with?('{') && content.end_with?('}')
Operator.to_sparql(content, **options)
else
operands.last.to_sparql(project: vars, **options)
diff --git a/lib/sparql/algebra/operator/reduced.rb b/lib/sparql/algebra/operator/reduced.rb
index ae9daa6c..27590403 100644
--- a/lib/sparql/algebra/operator/reduced.rb
+++ b/lib/sparql/algebra/operator/reduced.rb
@@ -8,12 +8,12 @@ class Operator
# @example SPARQL Grammar
# PREFIX :
# PREFIX xsd:
- # SELECT DISTINCT ?v
+ # SELECT REDUCED ?v
# WHERE { ?x ?p ?v }
#
# @example SSE
- # (prefix ((xsd: )
- # (: ))
+ # (prefix ((: )
+ # (xsd: ))
# (reduced
# (project (?v)
# (bgp (triple ?x ?p ?v)))))
diff --git a/lib/sparql/algebra/operator/regex.rb b/lib/sparql/algebra/operator/regex.rb
index 4be5b901..ddd3fe30 100644
--- a/lib/sparql/algebra/operator/regex.rb
+++ b/lib/sparql/algebra/operator/regex.rb
@@ -6,8 +6,8 @@ class Operator
# [122] RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')'
#
# @example SPARQL Grammar
- # PREFIX rdf:
# PREFIX ex:
+ # PREFIX rdf:
# SELECT ?val
# WHERE {
# ex:foo rdf:value ?val .
diff --git a/lib/sparql/algebra/operator/reverse.rb b/lib/sparql/algebra/operator/reverse.rb
index 430ae987..c40881bc 100644
--- a/lib/sparql/algebra/operator/reverse.rb
+++ b/lib/sparql/algebra/operator/reverse.rb
@@ -15,6 +15,17 @@ class Operator
# (in: ))
# (ask (path in:b (reverse ex:p) in:a)))
#
+ # @example SPARQL Grammar
+ # prefix ex:
+ # prefix in:
+ #
+ # select * where { in:c ^(ex:p1/ex:p2) ?x }
+ #
+ # @example SSE
+ # (prefix ((ex: )
+ # (in: ))
+ # (path in:c (reverse (seq ex:p1 ex:p2)) ?x))
+ #
# @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_inverse
class Reverse < Operator::Unary
include Query
@@ -65,7 +76,7 @@ def execute(queryable, **options, &block)
#
# @return [String]
def to_sparql(**options)
- "^" + operands.first.to_sparql(**options)
+ "^(" + operands.first.to_sparql(**options) + ')'
end
end # Reverse
end # Operator
diff --git a/lib/sparql/algebra/operator/sample.rb b/lib/sparql/algebra/operator/sample.rb
index e4e8900f..fcffe166 100644
--- a/lib/sparql/algebra/operator/sample.rb
+++ b/lib/sparql/algebra/operator/sample.rb
@@ -54,7 +54,9 @@ def apply(enum, **options)
#
# @return [String]
def to_sparql(**options)
- "SAMPLE(#{operands.to_sparql(**options)})"
+ distinct = operands.first == :distinct
+ args = distinct ? operands[1..-1] : operands
+ "SAMPLE(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
end
end # Sample
end # Operator
diff --git a/lib/sparql/algebra/operator/seq.rb b/lib/sparql/algebra/operator/seq.rb
index e206e0b4..2c899fc1 100644
--- a/lib/sparql/algebra/operator/seq.rb
+++ b/lib/sparql/algebra/operator/seq.rb
@@ -83,7 +83,7 @@ def execute(queryable, **options, &block)
#
# @return [String]
def to_sparql(**options)
- operands.to_sparql(delimiter: '/', **options)
+ '(' + operands.to_sparql(delimiter: '/', **options) + ')'
end
end # Seq
end # Operator
diff --git a/lib/sparql/algebra/operator/sequence.rb b/lib/sparql/algebra/operator/sequence.rb
index 7e9c7c91..46efdd0b 100644
--- a/lib/sparql/algebra/operator/sequence.rb
+++ b/lib/sparql/algebra/operator/sequence.rb
@@ -4,8 +4,11 @@ class Operator
##
# The SPARQL UPDATE `sequence` operator.
#
- # Sequences through each operand
+ # Sequences through each operand.
#
+ # [103] CollectionPath ::= '(' GraphNodePath+ ')'
+ #
+ # @see https://www.w3.org/TR/sparql11-query/#collections
class Sequence < Operator
include SPARQL::Algebra::Update
diff --git a/lib/sparql/algebra/operator/strlang.rb b/lib/sparql/algebra/operator/strlang.rb
index 6162434e..1f5745c2 100644
--- a/lib/sparql/algebra/operator/strlang.rb
+++ b/lib/sparql/algebra/operator/strlang.rb
@@ -13,8 +13,7 @@ class Operator
# }
#
# @example SSE
- # (prefix
- # ((: ) (xsd: ))
+ # (prefix ((: ))
# (project (?s ?s2)
# (extend ((?s2 (strlang ?str "en-US")))
# (filter (langMatches (lang ?str) "en")
diff --git a/lib/sparql/algebra/operator/subtract.rb b/lib/sparql/algebra/operator/subtract.rb
index e4309e7c..96622c97 100644
--- a/lib/sparql/algebra/operator/subtract.rb
+++ b/lib/sparql/algebra/operator/subtract.rb
@@ -51,7 +51,7 @@ def apply(left, right, **options)
#
# @return [String]
def to_sparql(**options)
- "#{operands.first.to_sparql(**options)} - #{operands.last.to_sparql(**options)}"
+ "(#{operands.first.to_sparql(**options)} - #{operands.last.to_sparql(**options)})"
end
end # Subtract
end # Operator
diff --git a/lib/sparql/algebra/operator/sum.rb b/lib/sparql/algebra/operator/sum.rb
index 1222c4f5..ed957ae1 100644
--- a/lib/sparql/algebra/operator/sum.rb
+++ b/lib/sparql/algebra/operator/sum.rb
@@ -7,15 +7,15 @@ class Operator
#
# @example SPARQL Grammar
# PREFIX :
- # SELECT (SUM(?O) AS ?sum)
+ # SELECT (SUM(?o) AS ?sum)
# WHERE { ?s :dec ?o }
#
# @example SSE
- # (prefix ((: ))
- # (project (?sum)
- # (extend ((?sum ??.0))
- # (group () ((??.0 (sum ?o)))
- # (bgp (triple ?s :dec ?o))))))
+ # (prefix ((: ))
+ # (project (?sum)
+ # (extend ((?sum ??.0))
+ # (group () ((??.0 (sum ?o)))
+ # (bgp (triple ?s :dec ?o))))))
#
# @see https://www.w3.org/TR/sparql11-query/#defn_aggSum
class Sum < Operator
@@ -47,7 +47,9 @@ def apply(enum, **options)
#
# @return [String]
def to_sparql(**options)
- "SUM(" + operands.to_sparql(**options) + ")"
+ distinct = operands.first == :distinct
+ args = distinct ? operands[1..-1] : operands
+ "SUM(#{'DISTINCT ' if distinct}#{args.to_sparql(**options)})"
end
end # Sum
end # Operator
diff --git a/lib/sparql/algebra/operator/table.rb b/lib/sparql/algebra/operator/table.rb
index 2924684f..013fea32 100644
--- a/lib/sparql/algebra/operator/table.rb
+++ b/lib/sparql/algebra/operator/table.rb
@@ -8,7 +8,7 @@ class Operator
#
# [28] ValuesClause ::= ( 'VALUES' DataBlock )?
#
- # @example SPARQL Grammar
+ # @example SPARQL Grammar (ValuesClause)
# PREFIX dc:
# PREFIX :
# PREFIX ns:
@@ -18,7 +18,7 @@ class Operator
# }
# VALUES ?book { :book1 }
#
- # @example SSE
+ # @example SSE (ValuesClause)
# (prefix ((dc: )
# (: )
# (ns: ))
@@ -27,8 +27,38 @@ class Operator
# (bgp (triple ?book dc:title ?title) (triple ?book ns:price ?price))
# (table (vars ?book) (row (?book :book1)))) ))
#
+ # @example SPARQL Grammar (empty query no values)
+ # SELECT * { } VALUES () { }
+ #
+ # @example SSE (empty query no values)
+ # (join (bgp) (table empty))
+ #
+ # [61] InlineData ::= 'VALUES' DataBlock
+ #
+ # @example SPARQL Grammar (InlineData)
+ # PREFIX dc:
+ # PREFIX :
+ # PREFIX ns:
+ #
+ # SELECT ?book ?title ?price
+ # {
+ # VALUES ?book { :book1 }
+ # ?book dc:title ?title ;
+ # ns:price ?price .
+ # }
+ #
+ # @example SSE (InlineData)
+ # (prefix ((dc: )
+ # (: )
+ # (ns: ))
+ # (project (?book ?title ?price)
+ # (join
+ # (table (vars ?book) (row (?book :book1)))
+ # (bgp (triple ?book dc:title ?title) (triple ?book ns:price ?price))) ))
+ #
# @example empty table
# (table unit)
+ #
# @see https://www.w3.org/TR/2013/REC-sparql11-query-20130321/#inline-data
class Table < Operator
include Query
@@ -67,22 +97,26 @@ def execute(queryable, **options, &block)
#
# Returns a partial SPARQL grammar for this operator.
#
+ # @param [Boolean] top_level (true)
+ # Treat this as a top-level, generating SELECT ... WHERE {}
# @return [String]
- def to_sparql(**options)
- str = "VALUES (#{operands.first[1..-1].map { |e| e.to_sparql(**options) }.join(' ')}) {\n"
+ def to_sparql(top_level: true, **options)
+ str = "VALUES (#{Array(operands.first)[1..-1].map { |e| e.to_sparql(**options) }.join(' ')}) {\n"
operands[1..-1].each do |row|
line = '('
row[1..-1].each do |col|
- line << "#{col[1].to_sparql(**options)} "
+ v = col[1].to_sparql(as_statement: true, **options)
+ v = "<< #{v} >>" if col[1].is_a?(RDF::Statement)
+ line << v + ' '
end
- line = line.chop
+ line = line.chomp(' ')
line << ")\n"
str << line
end
str << "}\n"
- str
+ top_level ? Operator.to_sparql(str, **options) : str
end
end # Table
end # Operator
diff --git a/lib/sparql/algebra/operator/ucase.rb b/lib/sparql/algebra/operator/ucase.rb
index a48d7269..6a974782 100644
--- a/lib/sparql/algebra/operator/ucase.rb
+++ b/lib/sparql/algebra/operator/ucase.rb
@@ -14,7 +14,7 @@ class Operator
# @example SSE
# (prefix
# ((: ))
- # (project (?str ?ustr)
+ # (project (?s ?ustr)
# (extend ((?ustr (ucase ?str)))
# (bgp (triple ?s :str ?str)))))
#
diff --git a/lib/sparql/algebra/operator/update.rb b/lib/sparql/algebra/operator/update.rb
index a12e24e8..c0f28ef5 100644
--- a/lib/sparql/algebra/operator/update.rb
+++ b/lib/sparql/algebra/operator/update.rb
@@ -21,6 +21,27 @@ class Operator
# (delete ((triple ?a foaf:knows ?b)))
# (insert ((triple ?b foaf:knows ?a)))) ))
#
+ # @example SPARQL Grammar (update multiple)
+ # PREFIX :
+ # PREFIX foaf:
+ #
+ # DELETE { ?a foaf:knows ?b . }
+ # WHERE { ?a foaf:knows ?b . }
+ # ;
+ # INSERT { ?b foaf:knows ?a . }
+ # WHERE { ?a foaf:knows ?b .}
+ #
+ # @example SSE (update multiple)
+ # (prefix ((: )
+ # (foaf: ))
+ # (update
+ # (modify
+ # (bgp (triple ?a foaf:knows ?b))
+ # (delete ((triple ?a foaf:knows ?b))))
+ # (modify
+ # (bgp (triple ?a foaf:knows ?b))
+ # (insert ((triple ?b foaf:knows ?a))))))
+ #
# @see https://www.w3.org/TR/sparql11-update/#graphUpdate
class Update < Operator
include SPARQL::Algebra::Update
@@ -58,7 +79,7 @@ def execute(queryable, **options)
#
# @return [String]
def to_sparql(**options)
- str = operands.map { |e| e.to_sparql(**options) }.join("\n")
+ str = operands.map { |e| e.to_sparql(**options) }.join(";\n")
end
end # Update
end # Operator
diff --git a/lib/sparql/algebra/operator/using.rb b/lib/sparql/algebra/operator/using.rb
index 84ce5462..f84e3dcd 100644
--- a/lib/sparql/algebra/operator/using.rb
+++ b/lib/sparql/algebra/operator/using.rb
@@ -28,6 +28,21 @@ class Operator
# (bgp (triple :a foaf:knows ?s) (triple ?s ?p ?o)))
# (delete ((triple ?s ?p ?o)))) ))
#
+ # @example SPARQL Grammar (multiple clauses)
+ # PREFIX :
+ #
+ # INSERT { ?s ?p "q" }
+ # USING :g1
+ # USING :g2
+ # WHERE { ?s ?p ?o }
+ #
+ # @example SSE (multiple clauses)
+ # (prefix ((: ))
+ # (update
+ # (modify (using (:g1 :g2)
+ # (bgp (triple ?s ?p ?o)))
+ # (insert ((triple ?s ?p "q"))))))
+ #
# @see https://www.w3.org/TR/sparql11-update/#add
class Using < Operator
include SPARQL::Algebra::Query
@@ -61,7 +76,9 @@ def execute(queryable, **options, &block)
#
# @return [String]
def to_sparql(**options)
- str = "USING #{operands.first.to_sparql(**options)}\n"
+ str = "\n" + operands.first.map do |op|
+ "USING #{op.to_sparql(**options)}\n"
+ end.join("")
content = operands.last.to_sparql(top_level: false, **options)
str << Operator.to_sparql(content, project: nil, **options)
end
diff --git a/lib/sparql/algebra/operator/with.rb b/lib/sparql/algebra/operator/with.rb
index 9e51a1d2..04382351 100644
--- a/lib/sparql/algebra/operator/with.rb
+++ b/lib/sparql/algebra/operator/with.rb
@@ -85,7 +85,7 @@ def execute(queryable, **options)
#
# @return [String]
def to_sparql(**options)
- with, where, ops = operands
+ with, where, *ops = operands
str = "WITH #{with.to_sparql(**options)}\n"
# The content of the WHERE clause, may be USING
diff --git a/lib/sparql/grammar/parser11.rb b/lib/sparql/grammar/parser11.rb
index 93999a78..96a9d843 100644
--- a/lib/sparql/grammar/parser11.rb
+++ b/lib/sparql/grammar/parser11.rb
@@ -197,7 +197,7 @@ class Parser
# [2] Query ::= Prologue
# ( SelectQuery | ConstructQuery | DescribeQuery | AskQuery )
production(:Query) do |input, data, callback|
- query = data[:query].first
+ query = data[:query].first if data[:query]
# Add prefix
if data[:PrefixDecl]
@@ -806,7 +806,7 @@ class Parser
# [70] FunctionCall ::= iri ArgList
production(:FunctionCall) do |input, data, callback|
- add_prod_data(:Function, Array(data[:iri]) + data[:ArgList])
+ add_prod_data(:Function, SPARQL::Algebra::Operator::FunctionCall.new(data[:iri], *data[:ArgList]))
end
# [71] ArgList ::= NIL
@@ -1437,7 +1437,7 @@ class Parser
production(:iriOrFunction) do |input, data, callback|
if data.has_key?(:ArgList)
# Function is (func arg1 arg2 ...)
- add_prod_data(:Function, Array(data[:iri]) + data[:ArgList])
+ add_prod_data(:Function, SPARQL::Algebra::Operator::FunctionCall.new(data[:iri], *data[:ArgList]))
else
input[:iri] = data[:iri]
end
diff --git a/sparql.gemspec b/sparql.gemspec
index 38771f86..580f230e 100755
--- a/sparql.gemspec
+++ b/sparql.gemspec
@@ -22,12 +22,12 @@ Gem::Specification.new do |gem|
gem.required_ruby_version = '>= 2.6'
gem.requirements = []
- gem.add_runtime_dependency 'rdf', '~> 3.2'
+ gem.add_runtime_dependency 'rdf', '~> 3.2', '>= 3.2.3'
gem.add_runtime_dependency 'rdf-aggregate-repo', '~> 3.2'
gem.add_runtime_dependency 'ebnf', '~> 2.2'
gem.add_runtime_dependency 'builder', '~> 3.2'
gem.add_runtime_dependency 'logger', '~> 1.4'
- gem.add_runtime_dependency 'sxp', '~> 1.2'
+ gem.add_runtime_dependency 'sxp', '~> 1.2', '>= 1.2.1'
gem.add_runtime_dependency 'sparql-client', '~> 3.2'
gem.add_runtime_dependency 'rdf-xsd', '~> 3.2'
diff --git a/spec/algebra/expression_spec.rb b/spec/algebra/expression_spec.rb
index 955c78c0..1f283a5f 100644
--- a/spec/algebra/expression_spec.rb
+++ b/spec/algebra/expression_spec.rb
@@ -10,124 +10,125 @@
{
# String
"(equal (xsd:string 'foo'^^xsd:string) xsd:string)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.string, RDF::Literal.new("foo", datatype: RDF::XSD.string)]), RDF::XSD.string),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.string, RDF::Literal.new("foo", datatype: RDF::XSD.string))), RDF::XSD.string),
"(equal (xsd:string '1.0e10'^^xsd:double) xsd:string)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.string, RDF::Literal.new(1.0e10)]), RDF::XSD.string),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.string, RDF::Literal.new(1.0e10))), RDF::XSD.string),
"(equal (xsd:string 'foo'^^xsd:integer) xsd:string)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.string, RDF::Literal.new(1)]), RDF::XSD.string),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.string, RDF::Literal.new(1))), RDF::XSD.string),
"(equal (xsd:string '2011-02-20T00:00:00'^^xsd:dateTime) xsd:string)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.string, RDF::Literal.new(DateTime.now)]), RDF::XSD.string),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.string, RDF::Literal.new(DateTime.now))), RDF::XSD.string),
"(equal (xsd:string 'foo'^^xsd:boolean) xsd:string)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.string, RDF::Literal.new(true)]), RDF::XSD.string),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.string, RDF::Literal.new(true))), RDF::XSD.string),
"(equal (xsd:string ) xsd:string)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.string, RDF::URI("foo")]), RDF::XSD.string),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.string, RDF::URI("foo"))), RDF::XSD.string),
"(equal (xsd:string 'foo') xsd:string)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.string, RDF::Literal.new("foo")]), RDF::XSD.string),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.string, RDF::Literal.new("foo"))), RDF::XSD.string),
# Double
"(equal (xsd:double '1.0e10'^^xsd:string) xsd:double)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new("1.0e10", datatype: RDF::XSD.string)]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new("1.0e10", datatype: RDF::XSD.string))), RDF::XSD.double),
"(equal (xsd:double 'foo'^^xsd:string) xsd:double) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new("foo", datatype: RDF::XSD.string)]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new("foo", datatype: RDF::XSD.string))), RDF::XSD.double),
"(equal (xsd:double '1.0e10'^^xsd:double) xsd:double)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new(1.0e10)]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new(1.0e10))), RDF::XSD.double),
"(equal (xsd:double '1'^^xsd:integer) xsd:double)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new(1)]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new(1))), RDF::XSD.double),
"(equal (xsd:double '2011-02-20T00:00:00'^^xsd:dateTime) xsd:double) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new(DateTime.now)]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new(DateTime.now))), RDF::XSD.double),
"(equal (xsd:double 'foo'^^xsd:boolean) xsd:double)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new(true)]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new(true))), RDF::XSD.double),
"(equal (xsd:double ) xsd:double) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::URI("foo")]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::URI("foo"))), RDF::XSD.double),
"(equal (xsd:double '1') xsd:double)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new("1")]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new("1"))), RDF::XSD.double),
"(equal (xsd:double 'foo') xsd:double) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.double, RDF::Literal.new("foo")]), RDF::XSD.double),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.double, RDF::Literal.new("foo"))), RDF::XSD.double),
# Decimal
"(equal (xsd:decimal '1.0'^^xsd:string) xsd:decimal)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal.new("1.0", datatype: RDF::XSD.string)]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal.new("1.0", datatype: RDF::XSD.string))), RDF::XSD.decimal),
"(equal (xsd:decimal 'foo'^^xsd:string) xsd:decimal) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal.new("foo", datatype: RDF::XSD.string)]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal.new("foo", datatype: RDF::XSD.string))), RDF::XSD.decimal),
"(equal (xsd:decimal '1.0e10'^^xsd:double) xsd:decimal)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal::Double.new("1.0e10")]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal::Double.new("1.0e10"))), RDF::XSD.decimal),
"(equal (xsd:decimal '1'^^xsd:integer) xsd:decimal)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal.new(1)]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal.new(1))), RDF::XSD.decimal),
"(equal (xsd:decimal '2011-02-20T00:00:00'^^xsd:dateTime) xsd:decimal) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal.new(DateTime.now)]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal.new(DateTime.now))), RDF::XSD.decimal),
"(equal (xsd:decimal 'foo'^^xsd:boolean) xsd:decimal)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal.new(true)]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal.new(true))), RDF::XSD.decimal),
"(equal (xsd:decimal ) xsd:decimal) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::URI("foo")]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::URI("foo"))), RDF::XSD.decimal),
"(equal (xsd:decimal '1.0') xsd:decimal)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal.new("1.0")]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal.new("1.0"))), RDF::XSD.decimal),
"(equal (xsd:decimal 'foo') xsd:decimal) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.decimal, RDF::Literal.new("foo")]), RDF::XSD.decimal),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.decimal, RDF::Literal.new("foo"))), RDF::XSD.decimal),
# Integer
"(equal (xsd:integer '1'^^xsd:string) xsd:integer)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal.new("1", datatype: RDF::XSD.string)]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal.new("1", datatype: RDF::XSD.string))), RDF::XSD.integer),
"(equal (xsd:integer 'foo'^^xsd:string) xsd:integer) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal.new("foo", datatype: RDF::XSD.string)]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal.new("foo", datatype: RDF::XSD.string))), RDF::XSD.integer),
"(equal (xsd:integer '1.0e10'^^xsd:double) xsd:integer)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal::Double.new("1.0e10")]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal::Double.new("1.0e10"))), RDF::XSD.integer),
"(equal (xsd:integer '1'^^xsd:integer) xsd:integer)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal.new(1)]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal.new(1))), RDF::XSD.integer),
"(equal (xsd:integer '2011-02-20T00:00:00'^^xsd:dateTime) xsd:integer) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal.new(DateTime.now)]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal.new(DateTime.now))), RDF::XSD.integer),
"(equal (xsd:integer 'foo'^^xsd:boolean) xsd:integer)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal.new(true)]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal.new(true))), RDF::XSD.integer),
"(equal (xsd:integer ) xsd:integer) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::URI("foo")]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::URI("foo"))), RDF::XSD.integer),
"(equal (xsd:integer '1') xsd:integer)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal.new("1")]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal.new("1"))), RDF::XSD.integer),
"(equal (xsd:integer 'foo') xsd:integer) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.integer, RDF::Literal.new("foo")]), RDF::XSD.integer),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.integer, RDF::Literal.new("foo"))), RDF::XSD.integer),
# DateTime
"(equal (xsd:dateTime '1'^^xsd:string) xsd:dateTime) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal.new("1", datatype: RDF::XSD.string)]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal.new("1", datatype: RDF::XSD.string))), RDF::XSD.dateTime),
"(equal (xsd:dateTime 'foo'^^xsd:string) xsd:dateTime) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal.new("foo", datatype: RDF::XSD.string)]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal.new("foo", datatype: RDF::XSD.string))), RDF::XSD.dateTime),
"(equal (xsd:dateTime '1.0e10'^^xsd:double) xsd:dateTime) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal::Double.new("1.0e10")]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal::Double.new("1.0e10"))), RDF::XSD.dateTime),
"(equal (xsd:dateTime '2011-02-20T00:00:00'^^xsd:dateTime) xsd:dateTime)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal.new(DateTime.now)]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal.new(DateTime.now))), RDF::XSD.dateTime),
"(equal (xsd:dateTime 'foo'^^xsd:boolean) xsd:dateTime) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal.new(true)]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal.new(true))), RDF::XSD.dateTime),
"(equal (xsd:dateTime ) xsd:dateTime) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::URI("foo")]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::URI("foo"))), RDF::XSD.dateTime),
"(equal (xsd:dateTime '1') xsd:dateTime) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal.new("1")]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal.new("1"))), RDF::XSD.dateTime),
"(equal (xsd:dateTime 'foo') xsd:dateTime) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal.new("foo")]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal.new("foo"))), RDF::XSD.dateTime),
"(equal (xsd:dateTime '2011-02-20T00:00:00'^^xsd:string) xsd:dateTime)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.dateTime, RDF::Literal.new("2011-02-20T00:00:00", datatype: RDF::XSD.string)]), RDF::XSD.dateTime),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.dateTime, RDF::Literal.new("2011-02-20T00:00:00", datatype: RDF::XSD.string))), RDF::XSD.dateTime),
# Boolean
"(equal (xsd:boolean '1'^^xsd:string) xsd:boolean)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal.new("1", datatype: RDF::XSD.string)]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal.new("1", datatype: RDF::XSD.string))), RDF::XSD.boolean),
"(equal (xsd:boolean 'foo'^^xsd:string) xsd:boolean) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal.new("foo", datatype: RDF::XSD.string)]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal.new("foo", datatype: RDF::XSD.string))), RDF::XSD.boolean),
"(equal (xsd:boolean '1.0e10'^^xsd:double) xsd:boolean)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal::Double.new("1.0e10")]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal::Double.new("1.0e10"))), RDF::XSD.boolean),
"(equal (xsd:boolean '1'^^xsd:boolean) xsd:boolean)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal.new(1)]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal.new(1))), RDF::XSD.boolean),
"(equal (xsd:boolean '2011-02-20T00:00:00'^^xsd:dateTime) xsd:boolean) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal.new(DateTime.now)]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal.new(DateTime.now))), RDF::XSD.boolean),
"(equal (xsd:boolean 'foo'^^xsd:boolean) xsd:boolean)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal.new(true)]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal.new(true))), RDF::XSD.boolean),
"(equal (xsd:boolean ) xsd:boolean) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::URI("foo")]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::URI("foo"))), RDF::XSD.boolean),
"(equal (xsd:boolean '1') xsd:boolean)" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal.new("1")]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal.new("1"))), RDF::XSD.boolean),
"(equal (xsd:boolean 'foo') xsd:boolean) raises TypeError" =>
- Operator::Equal.new(Operator::Datatype.new([RDF::XSD.boolean, RDF::Literal.new("foo")]), RDF::XSD.boolean),
+ Operator::Equal.new(Operator::Datatype.new(Operator::FunctionCall.new(RDF::XSD.boolean, RDF::Literal.new("foo"))), RDF::XSD.boolean),
}.each do |spec, op|
it spec do
if spec =~ /raises/
expect { op.evaluate(RDF::Query::Solution.new) }.to raise_error(TypeError)
else
+ op.evaluate(RDF::Query::Solution.new)
expect(op.evaluate(RDF::Query::Solution.new)).to eq RDF::Literal::TRUE
end
end
@@ -158,7 +159,8 @@
it "raises error unless function is registered" do
expect {
- [RDF::URI("func"), RDF::Literal("foo")].evaluate(RDF::Query::Solution.new)
+ Operator::FunctionCall.new(RDF::URI("func"), RDF::Literal("foo")).
+ evaluate(RDF::Query::Solution.new)
}.to raise_error(TypeError)
end
@@ -168,7 +170,8 @@
did_yield = true
expect(literal).to eq RDF::Literal("foo")
end
- [RDF::URI("func"), RDF::Literal("foo")].evaluate(RDF::Query::Solution.new)
+ Operator::FunctionCall.new(RDF::URI("func"), RDF::Literal("foo")).
+ evaluate(RDF::Query::Solution.new)
expect(did_yield).to be_truthy
end
end
@@ -191,11 +194,11 @@
}.each do |given, expected|
if expected == TypeError
it "raises TypeError given #{given.inspect}" do
- expect {[RDF::XSD.dateTime, given].evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
+ expect {Operator::FunctionCall.new(RDF::XSD.dateTime, given).evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
end
else
it "generates #{expected.inspect} given #{given.inspect}" do
- expect([RDF::XSD.dateTime, given].evaluate(RDF::Query::Solution.new)).to eq expected
+ expect(Operator::FunctionCall.new(RDF::XSD.dateTime, given).evaluate(RDF::Query::Solution.new)).to eq expected
end
end
end
@@ -220,11 +223,11 @@
}.each do |given, expected|
if expected == TypeError
it "raises TypeError given #{given.inspect}" do
- expect {[RDF::XSD.float, given].evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
+ expect {Operator::FunctionCall.new(RDF::XSD.float, given).evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
end
else
it "generates #{expected.inspect} given #{given.inspect}" do
- expect([RDF::XSD.float, given].evaluate(RDF::Query::Solution.new)).to eq expected
+ expect(Operator::FunctionCall.new(RDF::XSD.float, given).evaluate(RDF::Query::Solution.new)).to eq expected
end
end
end
@@ -249,11 +252,11 @@
}.each do |given, expected|
if expected == TypeError
it "raises TypeError given #{given.inspect}" do
- expect {[RDF::XSD.double, given].evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
+ expect {Operator::FunctionCall.new(RDF::XSD.double, given).evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
end
else
it "generates #{expected.inspect} given #{given.inspect}" do
- expect([RDF::XSD.double, given].evaluate(RDF::Query::Solution.new)).to eq expected
+ expect(Operator::FunctionCall.new(RDF::XSD.double, given).evaluate(RDF::Query::Solution.new)).to eq expected
end
end
end
@@ -278,11 +281,11 @@
}.each do |given, expected|
if expected == TypeError
it "raises TypeError given #{given.inspect}" do
- expect {[RDF::XSD.decimal, given].evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
+ expect {Operator::FunctionCall.new(RDF::XSD.decimal, given).evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
end
else
it "generates #{expected.inspect} given #{given.inspect}" do
- expect([RDF::XSD.decimal, given].evaluate(RDF::Query::Solution.new)).to eq expected
+ expect(Operator::FunctionCall.new(RDF::XSD.decimal, given).evaluate(RDF::Query::Solution.new)).to eq expected
end
end
end
@@ -307,11 +310,11 @@
}.each do |given, expected|
if expected == TypeError
it "raises TypeError given #{given.inspect}" do
- expect {[RDF::XSD.integer, given].evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
+ expect {Operator::FunctionCall.new(RDF::XSD.integer, given).evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
end
else
it "generates #{expected.inspect} given #{given.inspect}" do
- expect([RDF::XSD.integer, given].evaluate(RDF::Query::Solution.new)).to eq expected
+ expect(Operator::FunctionCall.new(RDF::XSD.integer, given).evaluate(RDF::Query::Solution.new)).to eq expected
end
end
end
@@ -336,11 +339,11 @@
}.each do |given, expected|
if expected == TypeError
it "raises TypeError given #{given.inspect}" do
- expect {[RDF::XSD.boolean, given].evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
+ expect {Operator::FunctionCall.new(RDF::XSD.boolean, given).evaluate(RDF::Query::Solution.new)}.to raise_error(TypeError)
end
else
it "generates #{expected.inspect} given #{given.inspect}" do
- expect([RDF::XSD.boolean, given].evaluate(RDF::Query::Solution.new)).to eq expected
+ expect(Operator::FunctionCall.new(RDF::XSD.boolean, given).evaluate(RDF::Query::Solution.new)).to eq expected
end
end
end
diff --git a/spec/algebra/to_sparql_spec.rb b/spec/algebra/to_sparql_spec.rb
index 9fb7e5c3..76b09e3b 100644
--- a/spec/algebra/to_sparql_spec.rb
+++ b/spec/algebra/to_sparql_spec.rb
@@ -5,14 +5,19 @@
include SPARQL::Algebra
-describe SPARQL::Algebra::Operator do
- it "reproduces simple query" do
- sxp = %{(prefix ((: ))
- (bgp (triple :s :p :o)))}
+shared_examples "SXP to SPARQL" do |name, sxp, **options|
+ it(name) do
sse = SPARQL::Algebra.parse(sxp)
sparql_result = sse.to_sparql
- expect(sparql_result).to generate(sxp, resolve_iris: false, validate: true)
+ production = sparql_result.match?(/ASK|SELECT|CONSTRUCT|DESCRIBE/) ? :QueryUnit : :UpdateUnit
+ expect(sparql_result).to generate(sxp, resolve_iris: false, production: production, validate: true, **options)
end
+end
+
+describe SPARQL::Algebra::Operator do
+ it_behaves_like "SXP to SPARQL", "simple query",
+ %{(prefix ((: ))
+ (bgp (triple :s :p :o)))}
context "Examples" do
def self.read_examples
@@ -20,12 +25,13 @@ def self.read_examples
Dir.glob(File.expand_path("../../../lib/sparql/algebra/operator/*.rb", __FILE__)).each do |rb|
op = File.basename(rb, ".rb")
scanner = StringScanner.new(File.read(rb))
- while scanner.skip_until(/# @example SSE/)
- ex = scanner.scan_until(/^\s+#\s*$/)
-
- # Trim off comment prefix
- ex = ex.gsub(/^\s*#/, '')
- (examples[op] ||= []) << ex
+ while scanner.skip_until(/# @example SPARQL Grammar(.*)$/)
+ ctx = scanner.matched.sub(/.*Grammar\s*/, '')
+ current = {}
+ current[:sparql] = scanner.scan_until(/# @example SSE.*$/).gsub(/^\s*#/, '').sub(/@example SSE.*$/, '')
+ current[:sxp] = scanner.scan_until(/^\s+#\s*$/).gsub(/^\s*#/, '')
+ current[:ctx] = ctx unless ctx.empty?
+ (examples[op] ||= []) << current
end
end
examples
@@ -33,18 +39,96 @@ def self.read_examples
read_examples.each do |op, examples|
describe "Operator #{op}:" do
- examples.each do |sxp|
- it(sxp) do
- pending "not implemented yet" if %w(
-
- ).include?(op)
- sse = SPARQL::Algebra.parse(sxp)
- sparql_result = sse.to_sparql
- production = sparql_result.match?(/ASK|SELECT|CONSTRUCT|DESCRIBE/) ? :QueryUnit : :UpdateUnit
- expect(sparql_result).to generate(sxp, resolve_iris: false, production: production, validate: true)
- end
+ examples.each do |example|
+ sxp, sparql, ctx = example[:sxp], example[:sparql], example[:ctx]
+ it_behaves_like "SXP to SPARQL", (ctx || ('sxp: ' + sxp)), sxp, logger: "Source:\n#{sparql}"
end
end
end
end
+
+ context "Issues" do
+ it_behaves_like "SXP to SPARQL", "#39",
+ SPARQL.parse(%(
+ PREFIX obo:
+
+ SELECT DISTINCT ?enst
+ FROM
+ WHERE {
+
+ ?enst obo:SO_transcribed_from ?ensg .
+ }
+ LIMIT 10
+ )).to_sxp
+
+ # PREFIX obo:
+ # PREFIX taxon:
+ # PREFIX rdfs:
+ # PREFIX faldo:
+ # PREFIX dc:
+ #
+ # SELECT DISTINCT ?parent ?child ?child_label
+ # FROM
+ # WHERE {
+ # ?enst obo:SO_transcribed_from ?ensg .
+ # ?ensg a ?parent ;
+ # obo:RO_0002162 taxon:9606 ;
+ # faldo:location ?ensg_location ;
+ # dc:identifier ?child ;
+ # rdfs:label ?child_label .
+ # FILTER(CONTAINS(STR(?parent), "terms/ensembl/"))
+ # BIND(STRBEFORE(STRAFTER(STR(?ensg_location), "GRCh38/"), ":") AS ?chromosome)
+ # }
+ # VALUES ?chromosome {
+ # "1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
+ # "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22"
+ # "X" "Y" "MT"
+ # }
+ it_behaves_like "SXP to SPARQL", "#40", %{
+ (prefix ((obo: )
+ (taxon: )
+ (rdfs: )
+ (faldo: )
+ (dc: ))
+ (dataset ()
+ (distinct
+ (project (?parent ?child ?child_label)
+ (join
+ (filter (contains (str ?parent) "terms/ensembl/")
+ (extend ((?chromosome (strbefore (strafter (str ?ensg_location) "GRCh38/") ":")))
+ (bgp
+ (triple ?enst obo:SO_transcribed_from ?ensg)
+ (triple ?ensg a ?parent)
+ (triple ?ensg obo:RO_0002162 taxon:9606)
+ (triple ?ensg faldo:location ?ensg_location)
+ (triple ?ensg dc:identifier ?child)
+ (triple ?ensg rdfs:label ?child_label))))
+ (table (vars ?chromosome)
+ (row (?chromosome "1"))
+ (row (?chromosome "2"))
+ (row (?chromosome "3"))
+ (row (?chromosome "4"))
+ (row (?chromosome "5"))
+ (row (?chromosome "6"))
+ (row (?chromosome "7"))
+ (row (?chromosome "8"))
+ (row (?chromosome "9"))
+ (row (?chromosome "10"))
+ (row (?chromosome "11"))
+ (row (?chromosome "12"))
+ (row (?chromosome "13"))
+ (row (?chromosome "14"))
+ (row (?chromosome "15"))
+ (row (?chromosome "16"))
+ (row (?chromosome "17"))
+ (row (?chromosome "18"))
+ (row (?chromosome "19"))
+ (row (?chromosome "20"))
+ (row (?chromosome "21"))
+ (row (?chromosome "22"))
+ (row (?chromosome "X"))
+ (row (?chromosome "Y"))
+ (row (?chromosome "MT"))))))))
+ }
+ end
end
diff --git a/spec/grammar/examples_spec.rb b/spec/grammar/examples_spec.rb
index 35fa3ff0..904966d4 100644
--- a/spec/grammar/examples_spec.rb
+++ b/spec/grammar/examples_spec.rb
@@ -39,5 +39,37 @@ def parse(query, **options)
parser = SPARQL::Grammar::Parser.new(query)
parser.parse(options[:update] ? :UpdateUnit: :QueryUnit)
end
+
+ context "Operator Examples" do
+ def self.read_operator_examples
+ examples = {}
+ Dir.glob(File.expand_path("../../../lib/sparql/algebra/operator/*.rb", __FILE__)).each do |rb|
+ op = File.basename(rb, ".rb")
+ scanner = StringScanner.new(File.read(rb))
+ while scanner.skip_until(/# @example SPARQL Grammar(.*)$/)
+ current = {}
+ current[:sparql] = scanner.scan_until(/# @example SSE.*$/).gsub(/^\s*#/, '').sub(/@example SSE.*$/, '')
+ current[:sxp] = scanner.scan_until(/^\s+#\s*$/).gsub(/^\s*#/, '')
+ current[:prod] = current[:sxp].include?('(update') ? :UpdateUnit : :QueryUnit
+ (examples[op] ||= []) << current
+ end
+ end
+ examples
+ end
+
+ read_operator_examples.each do |op, examples|
+ describe "Operator #{op}:" do
+ examples.each do |example|
+ sxp, sparql, production = example[:sxp], example[:sparql], example[:prod]
+ it(sparql) do
+ pending "not implemented yet" if %w(
+
+ ).include?(op)
+ expect(sparql).to generate(sxp, resolve_iris: false, production: production, validate: true)
+ end
+ end
+ end
+ end
+ end
end
end
\ No newline at end of file
diff --git a/spec/grammar/parser_spec.rb b/spec/grammar/parser_spec.rb
index cbbc47cc..b939e34f 100644
--- a/spec/grammar/parser_spec.rb
+++ b/spec/grammar/parser_spec.rb
@@ -13,10 +13,10 @@ def self.variable(id, distinguished = true)
context "FunctionCall nonterminal" do
{
"('bar')" => [
- %q(("bar")), [RDF::URI("foo"), RDF::Literal("bar")]
+ %q(("bar")), SPARQL::Algebra::Expression[:function_call, RDF::URI("foo"), RDF::Literal("bar")]
],
"()" => [
- %q(()), [RDF::URI("foo"), RDF["nil"]]
+ %q(()), SPARQL::Algebra::Expression[:function_call, RDF::URI("foo"), RDF["nil"]]
]
}.each do |title, (input, output)|
it title do |example|
@@ -1750,7 +1750,7 @@ def self.variable(id, distinguished = true)
%(FILTER REGEX ("foo", "bar")), [:filter, SPARQL::Algebra::Expression[:regex, RDF::Literal("foo"), RDF::Literal("bar")]]
],
"" => [
- %(FILTER ("arg")), [:filter, [RDF::URI("fun"), RDF::Literal("arg")]]
+ %(FILTER ("arg")), [:filter, SPARQL::Algebra::Expression[:function_call, RDF::URI("fun"), RDF::Literal("arg")]]
],
"bound" => [
%(FILTER BOUND (?e)), [:filter, SPARQL::Algebra::Expression[:bound, RDF::Query::Variable.new("e")]]
diff --git a/spec/suite_spec.rb b/spec/suite_spec.rb
index 1ecf03b7..731c8392 100644
--- a/spec/suite_spec.rb
+++ b/spec/suite_spec.rb
@@ -11,21 +11,21 @@
case t.type
when 'mf:QueryEvaluationTest'
it "evaluates #{t.entry} - #{t.name}: #{t.comment}" do
- case t.name
- when 'Basic - Term 6', 'Basic - Term 7'
+ case t.entry
+ when 'term-6.rq', 'term-7.rq'
skip "Decimal format changed in SPARQL 1.1"
- when 'datatype-2 : Literals with a datatype'
- skip "datatype now returns rdf:langString for language-tagged literals"
- when /REDUCED/
+ when 'reduced-1.rq', 'reduced-2.rq'
skip "REDUCED equivalent to DISTINCT"
- when 'Strings: Distinct', 'All: Distinct'
+ when 'distinct-1.rq'
skip "More compact representation"
+ when 'q-datatype-2.rq'
+ skip "datatype now returns rdf:langString for language-tagged literals"
when /sq03/
pending "Graph variable binding differences"
- when /pp11|pp31/
+ when 'pp11.rq', 'path-p2.rq'
pending "Expects multiple equivalent property path solutions"
- when 'date-1', /dawg-optional-filter-005-not-simplified/
- pending "Different results on unapproved tests" unless t.approved?
+ when 'date-1.rq', 'expr-5.rq'
+ pending "Different results on unapproved tests" unless t.name.include?('dawg-optional-filter-005-simplified')
end
t.logger = RDF::Spec.logger
@@ -65,15 +65,11 @@
end
when 'mf:PositiveSyntaxTest', 'mf:PositiveSyntaxTest11'
it "positive syntax for #{t.entry} - #{t.name} - #{t.comment}" do
- skip "Spurrious error on Ruby < 2.0" if t.name == 'syntax-bind-02.rq'
- case t.name
- when 'Basic - Term 7', 'syntax-lit-08.rq'
+ case t.entry
+ when 'term-7.rq', 'syntax-lit-08.rq'
skip "Decimal format changed in SPARQL 1.1"
when 'syntax-esc-04.rq', 'syntax-esc-05.rq'
skip "PNAME_LN changed in SPARQL 1.1"
- when 'dawg-optional-filter-005-simplified', 'dawg-optional-filter-005-not-simplified',
- 'dataset-10'
- pending 'New problem with different manifest processing?'
end
expect do
SPARQL.parse(t.action.query_string, base_uri: t.base_uri, validate: true, logger: t.logger)
@@ -81,11 +77,11 @@
end
when 'mf:NegativeSyntaxTest', 'mf:NegativeSyntaxTest11'
it "detects syntax error for #{t.entry} - #{t.name} - #{t.comment}" do
- skip("Better Error Detection") if %w(
+ pending("Better Error Detection") if %w(
agg08.rq agg09.rq agg10.rq agg11.rq agg12.rq
syn-bad-pname-06.rq group06.rq group07.rq
).include?(t.entry)
- skip("Better Error Detection") if %w(
+ pending("Better Error Detection") if %w(
syn-bad-01.rq syn-bad-02.rq
).include?(t.entry) && man_name == 'syntax-query'
expect do
@@ -131,12 +127,90 @@
end
end
+shared_examples "to_sparql" do |id, label, comment, tests|
+ man_name = id.to_s.split("/")[-2]
+ describe [man_name, label, comment].compact.join(" - ") do
+ tests.each do |t|
+ next unless t.action
+ case t.type
+ when 'mf:QueryEvaluationTest', 'mf:PositiveSyntaxTest', 'mf:PositiveSyntaxTest11'
+ it "Round Trips #{t.entry} - #{t.name}: #{t.comment}" do
+ case t.entry
+ when 'syntax-expr-05.rq', 'syntax-order-05.rq', 'syntax-function-04.rq'
+ pending("Unregistered function calls")
+ when 'term-7.rq', 'syntax-lit-08.rq'
+ skip "Decimal format changed in SPARQL 1.1"
+ when 'syntax-esc-04.rq', 'syntax-esc-05.rq'
+ skip "PNAME_LN changed in SPARQL 1.1"
+ when 'syn-pp-in-collection.rq'
+ pending "CollectionPath"
+ when 'bind05.rq', 'bind08.rq', 'syntax-bind-02.rq', 'strbefore02.rq',
+ 'agg-groupconcat-1.rq', 'agg-groupconcat-2.rq',
+ 'sq08.rq', 'sq12.rq', 'sq13.rq',
+ 'syntax-SELECTscope1.rq', 'syntax-SELECTscope3.rq'
+ skip "Equivalent form"
+ when 'sq09.rq', 'sq14.rq'
+ pending("SubSelect")
+ when 'sparql-star-order-by.rq'
+ pending("OFFSET/LIMIT in sub-select")
+ end
+ t.logger = RDF::Spec.logger
+ t.logger.debug "Source:\n#{t.action.query_string}"
+ sse = SPARQL.parse(t.action.query_string, base_uri: t.base_uri)
+ sparql = sse.to_sparql(base_uri: t.base_uri)
+ expect(sparql).to generate(sse,
+ base_uri: t.base_uri,
+ resolve_iris: false,
+ production: :QueryUnit,
+ logger: t.logger)
+ end
+ when 'ut:UpdateEvaluationTest', 'mf:UpdateEvaluationTest', 'mf:PositiveUpdateSyntaxTest11'
+ it "Round Trips #{t.entry} - #{t.name}: #{t.comment}" do
+ case t.entry
+ when 'syntax-update-38.ru'
+ pending "empty query"
+ when 'large-request-01.ru'
+ skip "large request"
+ when 'syntax-update-26.ru', 'syntax-update-27.ru', 'syntax-update-28.ru',
+ 'syntax-update-36.ru'
+ pending("Whitespace in string tokens")
+ when 'insert-05a.ru', 'insert-data-same-bnode.ru',
+ 'insert-where-same-bnode.ru', 'insert-where-same-bnode2.ru'
+ skip "Equivalent form"
+ when 'delete-insert-04.ru'
+ pending("SubSelect")
+ end
+ t.logger = RDF::Spec.logger
+ t.logger.debug "Source:\n#{t.action.query_string}\n"
+ sse = SPARQL.parse(t.action.query_string,
+ base_uri: t.base_uri,
+ update: true)
+ sparql = sse.to_sparql(base_uri: t.base_uri)
+ expect(sparql).to generate(sse,
+ base_uri: t.base_uri,
+ resolve_iris: false,
+ production: :UpdateUnit,
+ logger: t.logger)
+ end
+ when 'mf:NegativeSyntaxTest', 'mf:NegativeSyntaxTest11', 'mf:NegativeUpdateSyntaxTest11'
+ # Do nothing.
+ else
+ it "??? #{t.entry} - #{t.name}" do
+ puts t.inspect
+ fail "Unknown test type #{t.type}"
+ end
+ end
+ end
+ end
+end
+
describe SPARQL do
BASE = "http://w3c.github.io/rdf-tests/sparql11/"
describe "w3c dawg SPARQL 1.0 syntax tests" do
SPARQL::Spec.sparql1_0_syntax_tests.each do |path|
SPARQL::Spec::Manifest.open(path) do |man|
it_behaves_like "SUITE", man.attributes['id'], man.label, man.comment, man.entries
+ it_behaves_like "to_sparql", man.attributes['id'], man.label, man.comment, man.entries
end
end
end
@@ -145,6 +219,7 @@
SPARQL::Spec.sparql1_0_tests.each do |path|
SPARQL::Spec::Manifest.open(path) do |man|
it_behaves_like "SUITE", man.attributes['id'], man.label, man.comment, man.entries
+ it_behaves_like "to_sparql", man.attributes['id'], man.label, man.comment, man.entries
end
end
end
@@ -153,6 +228,7 @@
SPARQL::Spec.sparql1_1_tests.each do |path|
SPARQL::Spec::Manifest.open(path) do |man|
it_behaves_like "SUITE", man.attributes['id'], man.label, man.comment, man.entries
+ it_behaves_like "to_sparql", man.attributes['id'], man.label, man.comment, man.entries
end
end
end
@@ -161,6 +237,7 @@
SPARQL::Spec.sparql_star_tests.each do |path|
SPARQL::Spec::Manifest.open(path) do |man|
it_behaves_like "SUITE", man.attributes['id'], man.label, man.comment, man.entries
+ it_behaves_like "to_sparql", man.attributes['id'], man.label, man.comment, man.entries
end
end
end
diff --git a/spec/support/matchers/generate.rb b/spec/support/matchers/generate.rb
index ee27271b..ddb5fb1c 100644
--- a/spec/support/matchers/generate.rb
+++ b/spec/support/matchers/generate.rb
@@ -57,22 +57,22 @@ def normalize(obj)
end
failure_message do |input|
- "Input : #{@input}\n" +
+ "Input:\n#{@input}\n" +
case expected
when String
- "Expected : #{expected}\n"
+ "Expected:\n#{expected}\n"
else
- "Expected : #{expected.ai}\n" +
- "Expected(sse): #{expected.to_sxp}\n"
+ "Expected:\n#{expected.ai}\n" +
+ "Expected(sse):\n#{expected.to_sxp}\n"
end +
case input
when String
- "Actual : #{actual}\n"
+ "Actual:\n#{actual}\n"
else
- "Actual : #{actual.ai}\n" +
- "Actual(sse) : #{actual.to_sxp}\n"
+ "Actual:\n#{actual.ai}\n" +
+ "Actual(sse):\n#{actual.to_sxp}\n"
end +
(@exception ? "Exception: #{@exception}" : "") +
- "Processing results:\n#{@debug.is_a?(Array) ? @debug.join("\n") : ''}"
+ "Processing results:\n#{options[:logger].to_s}"
end
end