diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b65360c..0aaedb94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,14 +15,15 @@ jobs: runs-on: ubuntu-latest env: CI: true + ALLOW_FAILURES: ${{ endsWith(matrix.ruby, 'head') }} strategy: fail-fast: false matrix: ruby: - - 2.5 - 2.6 - 2.7 - 3.0 + - 3.1 - ruby-head - jruby steps: @@ -35,7 +36,7 @@ jobs: - name: Install dependencies run: bundle install --jobs 4 --retry 3 - name: Run tests - run: bundle exec rspec spec + run: ruby --version; bundle exec rspec spec || $ALLOW_FAILURES - name: Coveralls GitHub Action uses: coverallsapp/github-action@v1.1.2 if: "matrix.ruby == '3.0'" diff --git a/README.md b/README.md index e2ba5ddd..799045af 100755 --- a/README.md +++ b/README.md @@ -23,8 +23,7 @@ This is a [Ruby][] implementation of [SPARQL][] for [RDF.rb][]. * Compatible with any [Rack][] or [Sinatra][] application and any Rack-based framework. * Helper method for describing [SPARQL Service Description][SSD] * Implementation Report: {file:etc/earl.html EARL} -* Compatible with Ruby >= 2.2.2. -* Compatible with older Ruby versions with the help of the [Backports][] gem. +* Compatible with Ruby >= 2.6. * Supports Unicode query strings both on all versions of Ruby. * Provisional support for [SPARQL-star][]. @@ -248,27 +247,27 @@ a full set of RDF formats. ### Querying a repository with a SPARQL query queryable = RDF::Repository.load("etc/doap.ttl") - sse = SPARQL.parse("SELECT * WHERE { ?s ?p ?o }") - queryable.query(sse) do |result| + query = SPARQL.parse("SELECT * WHERE { ?s ?p ?o }") + queryable.query(query) do |result| result.inspect end ### Executing a SPARQL query against a repository queryable = RDF::Repository.load("etc/doap.ttl") - sse = SPARQL.parse("SELECT * WHERE { ?s ?p ?o }") - sse.execute(queryable) do |result| + query = SPARQL.parse("SELECT * WHERE { ?s ?p ?o }") + query.execute(queryable) do |result| result.inspect end ### Updating a repository queryable = RDF::Repository.load("etc/doap.ttl") - sse = SPARQL.parse(%( + update = SPARQL.parse(%( PREFIX doap: INSERT DATA { doap:implements } ), update: true) - sse.execute(queryable) + update.execute(queryable) ### Rendering solutions as JSON, XML, CSV, TSV or HTML queryable = RDF::Repository.load("etc/doap.ttl") @@ -277,8 +276,13 @@ a full set of RDF formats. ### Parsing a SPARQL query string to SSE - sse = SPARQL.parse("SELECT * WHERE { ?s ?p ?o }") - sse.to_sxp #=> (bgp (triple ?s ?p ?o)) + query = SPARQL.parse("SELECT * WHERE { ?s ?p ?o }") + query.to_sxp #=> (bgp (triple ?s ?p ?o)) + +### Parsing a SSE to SPARQL query or update string to SPARQL + + query = SPARQL::Algebra.parse(%{(bgp (triple ?s ?p ?o))}) + sparql = query.to_sparql #=> "SELECT * WHERE { ?s ?p ?o }" ### Command line processing @@ -289,6 +293,10 @@ a full set of RDF formats. sparql parse etc/input.rq sparql parse -e "SELECT * WHERE { ?s ?p ?o }" + # Generate SPARQL Query from SSE + sparql parse --sse etc/input.sse --format sparql + sparql parse --sse --format sparql -e "(dataset () (bgp (triple ?s ?p ?o))))" + # Run query using SSE input sparql execute --dataset etc/doap.ttl --sse etc/input.sse sparql execute --sse -e "(dataset () (bgp (triple ?s ?p ?o))))" @@ -368,19 +376,19 @@ Full documentation available on [Rubydoc.info][SPARQL doc] ## Dependencies -* [Ruby](https://ruby-lang.org/) (>= 2.2.2) -* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.0) -* [SPARQL::Client](https://rubygems.org/gems/sparql-client) (~> 3.0) -* [SXP](https://rubygems.org/gems/sxp) (~> 1.0) -* [Builder](https://rubygems.org/gems/builder) (>= 3.0.0) -* [JSON](https://rubygems.org/gems/json) (>= 1.8.2) -* Soft dependency on [Linked Data][] (>= 3.0) -* Soft dependency on [Nokogiri](https://rubygems.org/gems/nokogiri) (>= 1.7) +* [Ruby](https://ruby-lang.org/) (>= 2.6) +* [RDF.rb](https://rubygems.org/gems/rdf) (~> 3.2) +* [SPARQL::Client](https://rubygems.org/gems/sparql-client) (~> 3.1) +* [SXP](https://rubygems.org/gems/sxp) (~> 1.2) +* [Builder](https://rubygems.org/gems/builder) (~> 3.2) +* [JSON](https://rubygems.org/gems/json) (~> 2.6) +* Soft dependency on [Linked Data][] (>= 3.1) +* Soft dependency on [Nokogiri](https://rubygems.org/gems/nokogiri) (~> 1.12) Falls back to REXML for XML parsing Builder for XML serializing. Nokogiri is much more efficient -* Soft dependency on [Equivalent XML](https://rubygems.org/gems/equivalent-xml) (>= 0.3.0) +* Soft dependency on [Equivalent XML](https://rubygems.org/gems/equivalent-xml) (>= 0.6) Equivalent XML performs more efficient comparisons of XML Literals when Nokogiri is included -* Soft dependency on [Rack][] (>= 2.0) -* Soft dependency on [Sinatra][] (>= 2.0) +* Soft dependency on [Rack][] (~> 2.2) +* Soft dependency on [Sinatra][] (~> 2.1) ## Installation @@ -450,7 +458,6 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are also included in [RDF.rb]: https://rubydoc.info/github/ruby-rdf/rdf [RDF-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html [SPARQL-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#sparql-query-language -[Backports]: https://rubygems.org/gems/backports [Linked Data]: https://rubygems.org/gems/linkeddata [SPARQL doc]: https://rubydoc.info/github/ruby-rdf/sparql/frames [SPARQL XML]: https://www.w3.org/TR/rdf-sparql-XMLres/ diff --git a/VERSION b/VERSION index c848fb9c..944880fa 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.8 +3.2.0 diff --git a/bin/sparql b/bin/sparql index be34d8d1..008288c3 100755 --- a/bin/sparql +++ b/bin/sparql @@ -45,9 +45,18 @@ def run(input, **options) SPARQL::Grammar.parse(input, **options) end - puts ("\nSSE:\n" + query.to_sse) if options[:debug] || options[:to_sse] + puts ("\nSSE:\n" + query.to_sse) if options[:debug] - unless options[:to_sse] + if options[:parse_only] + case options[:format] + when :sparql + puts ("\nSPARQL:\n" + query.to_sparql) + when nil, :sse + puts ("\nSSE:\n" + query.to_sse) + else + $stderr.puts "Unknown output format for parsing: #{options[:format]}. Use 'sse' or 'sparql'" + end + else res = query.execute(options[:dataset], logger: options[:logger]) display_results(res, **options) end @@ -98,7 +107,7 @@ def usage puts " --dataset: File containing RDF graph or dataset" puts " --debug: Display detailed debug output" puts " --execute,-e: Use option argument as the SPARQL input if no query-file given" - puts " --format: Output format for results" + puts " --format: Output format for results (json, xml, csv, tsv, html, sparql, sse, or another RDF format)" puts " --port,-p Port on which to run server; defaults to 9292" puts " --sse: Query input is in SSE format" puts " --update: Process query as a SPARQL Update" @@ -151,7 +160,7 @@ end case cmd when 'execute', 'parse' - options[:to_sse] = true if cmd == 'parse' + options[:parse_only] = true if cmd == 'parse' input ||= ARGV.empty? ? $stdin.read : RDF::Util::File.open_file(ARGV.first).read run(input, **options) when 'query' diff --git a/lib/sparql/algebra/extensions.rb b/lib/sparql/algebra/extensions.rb index c59f03d9..6b8cce51 100644 --- a/lib/sparql/algebra/extensions.rb +++ b/lib/sparql/algebra/extensions.rb @@ -43,6 +43,15 @@ def optimize(**options) def deep_dup dup end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + to_sxp(**options) + end end ## @@ -56,6 +65,16 @@ def to_sxp_bin map {|x| x.to_sxp_bin} end + ## + # + # Returns a partial SPARQL grammar for this array. + # + # @param [String] delimiter (" ") + # @return [String] + def to_sparql(delimiter: " ", **options) + map {|e| e.to_sparql(**options)}.join(delimiter) + end + ## # Evaluates the array using the given variable `bindings`. # @@ -216,15 +235,6 @@ def deep_dup ## # Extensions for Ruby's `Hash` class. class Hash - ## - # Returns the SXP representation of this object, defaults to `self`. - # - # @return [String] - def to_sxp_bin - to_a.to_sxp_bin - end - def to_sxp; to_sxp_bin; end - ## # A duplicate of this hash. # @@ -278,24 +288,20 @@ def vars # @see SPARQL::Algebra::Expression#optimize def optimize(**options) optimized = self.deep_dup - optimized.lexical = nil if optimized.respond_to?(:lexical=) - optimized + #optimized.lexical = nil if optimized.respond_to?(:lexical=) + #optimized end -end # RDF::Term -class RDF::Literal::Double ## - # Returns the SXP representation of this object. + # + # Returns a partial SPARQL grammar for this term. # # @return [String] - def to_sxp - case - when nan? then 'nan.0' - when infinite? then (infinite? > 0 ? '+inf.0' : '-inf.0') - else canonicalize.to_s.downcase - end + def to_sparql(**options) + to_sxp(**options) end -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 @@ -343,6 +349,15 @@ def query(pattern, **options, &block) query_without_sparql(pattern, **options, &block) end end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + raise NotImplementedError, "SPARQL::Algebra '#{first}' operator not implemented" + end end class RDF::Statement @@ -361,9 +376,29 @@ def to_sxp_bin ## # Returns an S-Expression (SXP) representation # + # @param [Hash{Symbol => RDF::URI}] prefixes (nil) + # @param [RDF::URI] base_uri (nil) # @return [String] - def to_sxp - to_sxp_bin.to_sxp + def to_sxp(prefixes: nil, base_uri: nil) + to_sxp_bin.to_sxp(prefixes: prefixes, base_uri: base_uri) + end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @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(" ") + " ." end ## @@ -411,6 +446,19 @@ def to_sxp_bin end end + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @param [Boolean] top_level (true) + # Treat this as a top-level, generating SELECT ... WHERE {} + # @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") + str = "GRAPH #{graph_name.to_sparql(**options)} {\n#{str}\n}\n" if graph_name + top_level ? SPARQL::Algebra::Operator.to_sparql(str, **options) : str + end + ## # Binds the pattern to a solution, making it no longer variable if all variables are resolved to bound variables # @@ -462,11 +510,11 @@ def vars def optimize!(**options) @patterns = @patterns.map do |pattern| components = pattern.to_quad.map do |term| - if term.respond_to?(:lexical=) - term.dup.instance_eval {@lexical = nil; self} - else + #if term.respond_to?(:lexical=) + # term.dup.instance_eval {@lexical = nil; self} + #else term - end + #end end RDF::Query::Pattern.from(components, **pattern.options) end @@ -530,11 +578,15 @@ def optimize(**options) self end - # Display variable as SXP - # @return [Array] - def to_sxp - prefix = distinguished? ? (existential? ? '$' : '?') : (existential? ? '$$' : '??') - unbound? ? "#{prefix}#{name}".to_sym.to_sxp : ["#{prefix}#{name}".to_sym, value].to_sxp + ## + # + # Returns a partial SPARQL grammar for this term. + # + # The Non-distinguished form (`??xxx`) is not part of the grammar, so replace with a blank-node + # + # @return [String] + def to_sparql(**options) + self.distinguished? ? super : "_:_nd#{self.name}" end end # RDF::Query::Variable @@ -576,5 +628,13 @@ class RDF::Query::Solution def to_sxp_bin to_a.to_sxp_bin end - def to_sxp; to_sxp_bin; end + + # Transform Solution into an SXP + # + # @param [Hash{Symbol => RDF::URI}] prefixes (nil) + # @param [RDF::URI] base_uri (nil) + # @return [String] + def to_sxp(prefixes: nil, base_uri: nil) + to_sxp_bin.to_sxp(prefixes: prefixes, base_uri: base_uri) + end end # RDF::Query::Solution diff --git a/lib/sparql/algebra/operator.rb b/lib/sparql/algebra/operator.rb index a2e4ccba..15e9f374 100644 --- a/lib/sparql/algebra/operator.rb +++ b/lib/sparql/algebra/operator.rb @@ -49,6 +49,7 @@ class Operator autoload :Sum, 'sparql/algebra/operator/sum' autoload :SHA1, 'sparql/algebra/operator/sha1' autoload :SHA256, 'sparql/algebra/operator/sha256' + autoload :SHA384, 'sparql/algebra/operator/sha384' autoload :SHA512, 'sparql/algebra/operator/sha512' autoload :Str, 'sparql/algebra/operator/str' autoload :Timezone, 'sparql/algebra/operator/timezone' @@ -171,7 +172,6 @@ def self.for(name, arity = nil) when :> then GreaterThan when :>= then GreaterThanOrEqual when :abs then Abs - when :add then Add when :alt then Alt when :and, :'&&' then And when :avg then Avg @@ -204,7 +204,6 @@ def self.for(name, arity = nil) when :md5 then MD5 when :max then Max when :min then Min - when :minus then Minus when :minutes then Minutes when :month then Month when :multiply then Multiply @@ -231,6 +230,7 @@ def self.for(name, arity = nil) when :sequence then Sequence when :sha1 then SHA1 when :sha256 then SHA256 + when :sha384 then SHA384 when :sha512 then SHA512 when :str then Str when :strafter then StrAfter @@ -278,7 +278,6 @@ def self.for(name, arity = nil) when :reduced then Reduced when :slice then Slice when :table then Table - when :triple then RDF::Query::Pattern when :union then Union # Update forms @@ -301,7 +300,7 @@ def self.for(name, arity = nil) # RDF-star when :istriple then IsTriple - when :triple then Triple + when :triple then RDF::Query::Pattern when :subject then Subject when :predicate then Predicate when :object then Object @@ -333,6 +332,89 @@ def self.arity self.const_get(:ARITY) end + ## + # Generate a top-level Grammar, using collected options + # + # @param [String] content + # @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 [Integer] offset (nil) + # @param [Array] order_ops ([]) + # Order Operations + # @param [Array] project (%i(*)) + # Terms to project + # @param [Operator] reduced (false) + # @param [Hash{Symbol => Object}] options + # @return [String] + def self.to_sparql(content, + distinct: false, + extensions: {}, + filter_ops: [], + group_ops: [], + limit: nil, + offset: nil, + order_ops: [], + project: %i(*), + reduced: false, + **options) + str = "" + + # Projections + if project + str << "SELECT " + str << "DISTINCT " if distinct + str << "REDUCED " if reduced + + str << project.map do |p| + if expr = extensions.delete(p) + # Replace projected variables with their extension, if any + "(" + [expr, :AS, p].to_sparql(**options) + ")" + else + p.to_sparql(**options) + end + end.join(" ") + "\n" + end + + # Extensions + extensions.each do |as, expression| + content << "\nBIND (#{expression.to_sparql(**options)} AS #{as.to_sparql(**options)}) ." + end + + # Filters + filter_ops.each do |f| + content << "\nFILTER #{f.to_sparql(**options)} ." + end + + # Where clause + str << "WHERE {\n#{content}\n}\n" + + # Group + unless group_ops.empty? + ops = group_ops.map do |o| + # Replace projected variables with their extension, if any + o.is_a?(Array) ? + "(" + [o.last, :AS, o.first].to_sparql(**options) + ")" : + o.to_sparql(**options) + end + str << "GROUP BY #{ops.join(' ')}\n" + end + + # Order + unless order_ops.empty? + str << "ORDER BY #{order_ops.to_sparql(**options)}\n" + end + + # Offset and Limmit + str << "OFFSET #{offset}\n" unless offset.nil? + str << "LIMIT #{limit}\n" unless limit.nil? + str + end + ARITY = -1 # variable arity ## @@ -581,9 +663,20 @@ def to_sxp_bin ## # Returns an S-Expression (SXP) representation of this operator # + # @param [Hash{Symbol => RDF::URI}] prefixes (nil) + # @param [RDF::URI] base_uri (nil) + # @return [String] + def to_sxp(prefixes: nil, base_uri: nil) + to_sxp_bin.to_sxp(prefixes: prefixes, base_uri: base_uri) + end + + ## + # + # Returns a partial SPARQL grammar for the operator. + # # @return [String] - def to_sxp - to_sxp_bin.to_sxp + def to_sparql(**options) + raise NotImplementedError, "#{self.class}#to_sparql(#{operands.map(&:class).join(', ')})" end ## diff --git a/lib/sparql/algebra/operator/abs.rb b/lib/sparql/algebra/operator/abs.rb index 5692a815..60e2096e 100644 --- a/lib/sparql/algebra/operator/abs.rb +++ b/lib/sparql/algebra/operator/abs.rb @@ -3,8 +3,19 @@ class Operator ## # The SPARQL logical `abs` operator. # - # @example - # (abs ?x) + # [121] BuiltInCall ::= ... | 'ABS' '(' Expression ')' + # + # @example SPARQL Query + # PREFIX : + # SELECT * WHERE { + # ?s :num ?num + # FILTER(ABS(?num) >= 2) + # } + # + # @example SSE + # (prefix ((: )) + # (filter (>= (abs ?num) 2) + # (bgp (triple ?s :num ?num)))) # # @see https://www.w3.org/TR/sparql11-query/#func-abs # @see https://www.w3.org/TR/xpath-functions/#func-abs @@ -26,6 +37,15 @@ def apply(operand, **options) else raise TypeError, "expected an RDF::Literal::Numeric, but got #{operand.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "ABS(#{operands.first.to_sparql(**options)})" + end end # Abs end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/add.rb b/lib/sparql/algebra/operator/add.rb index 23d5a6dd..0db08b54 100644 --- a/lib/sparql/algebra/operator/add.rb +++ b/lib/sparql/algebra/operator/add.rb @@ -6,8 +6,15 @@ class Operator # # The ADD operation is a shortcut for inserting all data from an input graph into a destination graph. Data from the input graph is not affected, and initial data from the destination graph, if any, is kept intact. # - # @example - # (add default ) + # [35] Add ::= "ADD" "SILENT"? GraphOrDefault "TO" GraphOrDefault + # + # @example SPARQL Update + # PREFIX : + # ADD DEFAULT TO :g1 + # + # @example SSE + # (prefix ((: )) + # (update (add default :g1))) # # @see https://www.w3.org/TR/sparql11-update/#add class Add < Operator @@ -51,6 +58,18 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + *args, last = operands.dup + args += [:TO, last] + + "ADD " + args.to_sparql(**options) + end end # Add end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/alt.rb b/lib/sparql/algebra/operator/alt.rb index 04221d9f..8f839809 100644 --- a/lib/sparql/algebra/operator/alt.rb +++ b/lib/sparql/algebra/operator/alt.rb @@ -7,8 +7,23 @@ class Operator # # eval(Path(X, alt(P,Q), Y)) = Union(eval(Path(X, P, Y)), eval(Path(X, Q, Y))) # - # @example - # (alt a b) + # [89] PathAlternative ::= PathSequence ( '|' PathSequence )* + # + # @example SPARQL Query + # PREFIX : + # SELECT ?t + # WHERE { + # :a :p1|:p2/:p3|:p4 ?t + # } + # + # @example SSE + # (prefix ((: )) + # (project (?t) + # (path :a + # (alt + # (alt :p1 (seq :p2 :p3)) + # :p4) + # ?t))) # # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_alternative class Alt < Operator::Binary @@ -57,6 +72,15 @@ def execute(queryable, **options, &block) query = Union.new(qa, qb) queryable.query(query, depth: options[:depth].to_i + 1, **options, &block) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "#{operands.first.to_sparql(**options)}|#{operands.last.to_sparql(**options)}" + end end # Alt end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/and.rb b/lib/sparql/algebra/operator/and.rb index 3a847f6a..adbe0889 100644 --- a/lib/sparql/algebra/operator/and.rb +++ b/lib/sparql/algebra/operator/and.rb @@ -3,9 +3,22 @@ class Operator ## # The SPARQL logical `and` operator. # - # @example - # (&& ?x ?y) - # (and ?x ?y) + # [112] ConditionalAndExpression::= ValueLogical ( '&&' ValueLogical )* + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?a + # WHERE { ?a :p ?v . + # FILTER ("true"^^xsd:boolean && ?v) . + # } + # + # @example SSE + # (prefix + # ((xsd: ) (: )) + # (project (?a) + # (filter (&& true ?v) + # (bgp (triple ?a :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#func-logical-and # @see https://www.w3.org/TR/sparql11-query/#evaluation @@ -60,6 +73,15 @@ def evaluate(bindings, **options) else RDF::Literal(left && right) end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(#{operands.first.to_sparql(**options)} && #{operands.last.to_sparql(**options)})" + end end # And end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/asc.rb b/lib/sparql/algebra/operator/asc.rb index c8f90613..460a54aa 100644 --- a/lib/sparql/algebra/operator/asc.rb +++ b/lib/sparql/algebra/operator/asc.rb @@ -3,7 +3,15 @@ class Operator ## # The SPARQL ascending sort operator. # - # @example + # [24] OrderCondition ::= ( ( 'ASC' | 'DESC' ) BrackettedExpression ) | ( Constraint | Var ) + # + # @example SPARQL Query + # PREFIX foaf: + # SELECT ?name + # WHERE { ?x foaf:name ?name } + # ORDER BY ASC(?name) + # + # @example SSE # (prefix ((foaf: )) # (project (?name) # (order ((asc ?name)) @@ -27,6 +35,17 @@ class Asc < Operator::Unary def evaluate(bindings, **options) operand(0).evaluate(bindings, depth: options[:depth].to_i + 1, **options) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # Provides order to descendant query. + # + # @return [String] + def to_sparql(**options) + "ASC(#{operands.last.to_sparql(**options)})" + end end # Asc end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/ask.rb b/lib/sparql/algebra/operator/ask.rb index 2e669631..52fcb89f 100644 --- a/lib/sparql/algebra/operator/ask.rb +++ b/lib/sparql/algebra/operator/ask.rb @@ -5,7 +5,13 @@ class Operator # # Applications can use the ASK form to test whether or not a query pattern has a solution. No information is returned about the possible query solutions, just whether or not a solution exists. # - # @example + # [12] AskQuery ::= 'ASK' DatasetClause* WhereClause ValuesClause + # + # @example SPARQL Query + # PREFIX : + # ASK { :x :p ?x } + # + # @example SSE # (prefix ((: )) # (ask # (bgp (triple :x :p ?x)))) @@ -41,6 +47,16 @@ def execute(queryable, **options) def query_yields_boolean? true end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + "ASK\n" + + operands.first.to_sparql(top_level: true, project: nil, **options) + end end # Ask end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/avg.rb b/lib/sparql/algebra/operator/avg.rb index 7f6c81ee..774aa6d9 100644 --- a/lib/sparql/algebra/operator/avg.rb +++ b/lib/sparql/algebra/operator/avg.rb @@ -3,7 +3,14 @@ class Operator ## # The SPARQL `avg` set function. # - # @example + # [127] Aggregate::= ... | 'AVG' '(' 'DISTINCT'? Expression ')' + # + # @example SPARQL Query + # PREFIX : + # SELECT (AVG(?o) AS ?avg) + # WHERE { ?s :dec ?o } + # + # @example SSE # (prefix ((: )) # (project (?avg) # (extend ((?avg ??.0)) @@ -40,6 +47,15 @@ def apply(enum, **options) raise TypeError, "Averaging non-numeric types: #{enum.flatten}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "AVG(#{operands.to_sparql(**options)})" + end end # Avg end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/base.rb b/lib/sparql/algebra/operator/base.rb index bacb14df..af6e1c23 100644 --- a/lib/sparql/algebra/operator/base.rb +++ b/lib/sparql/algebra/operator/base.rb @@ -3,7 +3,13 @@ class Operator ## # The SPARQL GraphPattern `base` operator. # - # @example + # [5] BaseDecl ::= 'BASE' IRIREF + # + # @example SPARQL Grammar + # BASE + # SELECT * { 123.0 } + # + # @example SSE # (base # (bgp (triple 123.0))) # @@ -56,6 +62,17 @@ def query_yields_boolean? def query_yields_statements? operands.last.query_yields_statements? end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + str = "BASE #{operands.first.to_sparql}\n" + + str << operands.last.to_sparql(base_uri: operands.first, **options) + end end # Base end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/bgp.rb b/lib/sparql/algebra/operator/bgp.rb index e82c92df..1e3fe183 100644 --- a/lib/sparql/algebra/operator/bgp.rb +++ b/lib/sparql/algebra/operator/bgp.rb @@ -5,7 +5,11 @@ class Operator # # Query with `graph_name` set to false. # - # @example + # @example SPARQL Grammar + # PREFIX : + # SELECT * { :s :p :o } + # + # @example SSE # (prefix ((: )) # (bgp (triple ?s ?p ?o))) # diff --git a/lib/sparql/algebra/operator/bnode.rb b/lib/sparql/algebra/operator/bnode.rb index e37c90ab..cb837e18 100644 --- a/lib/sparql/algebra/operator/bnode.rb +++ b/lib/sparql/algebra/operator/bnode.rb @@ -5,16 +5,30 @@ class Operator # # The BNODE function constructs a blank node that is distinct from all blank nodes in the dataset being queried and distinct from all blank nodes created by calls to this constructor for other query solutions. If the no argument form is used, every call results in a distinct blank node. If the form with a simple literal is used, every call results in distinct blank nodes for different simple literals, and the same blank node for calls with the same simple literal within expressions for one solution mapping. # - # @example - # (prefix ((: ) - # (xsd: )) - # (project (?s1 ?s2 ?b1 ?b2) - # (extend ((?b1 (bnode ?s1)) (?b2 (bnode ?s2))) - # (filter (exprlist (|| (= ?a :s1) (= ?a :s3)) (|| (= ?b :s1) (= ?b :s3))) - # (bgp - # (triple ?a :str ?s1) - # (triple ?b :str ?s2) - # ))))) + # [121] BuiltInCall ::= ... | 'BNODE' ( '(' Expression ')' | NIL ) + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s1 ?s2 (BNODE(?s1) AS ?b1) (BNODE(?s2) AS ?b2) + # WHERE { + # ?a :str ?s1 . + # ?b :str ?s2 . + # FILTER (?a = :s1 || ?a = :s3) + # FILTER (?b = :s1 || ?b = :s3) + # } + # + # @example SSE + # (prefix + # ((: ) (xsd: )) + # (project (?s1 ?s2 ?b1 ?b2) + # (extend + # ((?b1 (bnode ?s1)) (?b2 (bnode ?s2))) + # (filter + # (exprlist + # (|| (= ?a :s1) (= ?a :s3)) + # (|| (= ?b :s1) (= ?b :s3))) + # (bgp (triple ?a :str ?s1) (triple ?b :str ?s2))) )) ) # # @see https://www.w3.org/TR/sparql11-query/#func-bnode class BNode < Operator::Unary @@ -86,6 +100,15 @@ def apply(literal, bindings, **options) def to_sxp_bin [NAME] + operands.reject {|o| o == false} end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "BNODE(#{operands.last.to_sparql(**options)})" + end end # BNode end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/bound.rb b/lib/sparql/algebra/operator/bound.rb index 25133b40..4cb22136 100644 --- a/lib/sparql/algebra/operator/bound.rb +++ b/lib/sparql/algebra/operator/bound.rb @@ -3,7 +3,19 @@ class Operator ## # The SPARQL `bound` operator. # - # @example + # [121] BuiltInCall ::= ... | 'BOUND' '(' Var ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?a ?c + # WHERE + # { ?a :b ?c . + # OPTIONAL + # { ?c :d ?e } . + # FILTER (! bound(?e)) + # } + # + # @example SSE # (prefix ((: )) # (project (?a ?c) # (filter (! (bound ?e)) @@ -46,6 +58,15 @@ def evaluate(bindings, **options) else raise TypeError, "expected an RDF::Query::Variable, but got #{var.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "bound(" + operands.first.to_sparql(**options) + ")" + end end # Bound end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/ceil.rb b/lib/sparql/algebra/operator/ceil.rb index dba923bc..73c29a0a 100644 --- a/lib/sparql/algebra/operator/ceil.rb +++ b/lib/sparql/algebra/operator/ceil.rb @@ -3,8 +3,22 @@ class Operator ## # The SPARQL logical `ceil` operator. # - # @example - # (ceil ?x) + # [121] BuiltInCall ::= ... 'CEIL' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s ?num (CEIL(?num) AS ?ceil) WHERE { + # ?s :num ?num + # } + # + # @example SSE + # (prefix + # ((: ) + # (xsd: )) + # (project (?s ?num ?ceil) + # (extend ((?ceil (ceil ?num))) + # (bgp (triple ?s :num ?num))))) # # @see https://www.w3.org/TR/sparql11-query/#func-ceil # @see https://www.w3.org/TR/xpath-functions/#func-ceil @@ -26,6 +40,15 @@ def apply(operand, **options) else raise TypeError, "expected an RDF::Literal::Numeric, but got #{operand.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "CEIL(#{operands.to_sparql(**options)})" + end end # Ceil end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/clear.rb b/lib/sparql/algebra/operator/clear.rb index ca4d441d..ffda3ced 100644 --- a/lib/sparql/algebra/operator/clear.rb +++ b/lib/sparql/algebra/operator/clear.rb @@ -6,8 +6,13 @@ class Operator # # The CLEAR operation removes all the triples in the specified graph(s) in the Graph Store. # - # @example - # (clear silent default) + # [32] Clear ::= 'CLEAR' 'SILENT'? GraphRefAll + # + # @example SPARQL Grammar + # CLEAR SILENT DEFAULT + # + # @example SSE + # (update (clear silent default)) # # @see https://www.w3.org/TR/sparql11-update/#clear class Clear < Operator @@ -58,6 +63,15 @@ def execute(queryable, **options) queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "CLEAR " + operands.to_sparql(**options) + end end # Clear end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/coalesce.rb b/lib/sparql/algebra/operator/coalesce.rb index e5e3476a..5f3a8476 100644 --- a/lib/sparql/algebra/operator/coalesce.rb +++ b/lib/sparql/algebra/operator/coalesce.rb @@ -3,17 +3,30 @@ class Operator ## # The SPARQL `coalesce` function. # - # @example - # (prefix ((: ) - # (xsd: )) - # (project (?cx ?div ?def ?err) - # (extend ((?cx (coalesce ?x -1)) - # (?div (coalesce (/ ?o ?x) -2)) - # (?def (coalesce ?z -3)) - # (?err (coalesce ?z))) - # (leftjoin - # (bgp (triple ?s :p ?o)) - # (bgp (triple ?s :q ?x)))))) + # [121] BuiltInCall ::= ... | 'COALESCE' ExpressionList + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # + # SELECT ?X (SAMPLE(?v) AS ?S) + # { + # ?s :p ?v . + # OPTIONAL { ?s :q ?w } + # } + # GROUP BY (COALESCE(?w, "1605-11-05"^^xsd:date) AS ?X) + # + # @example SSE + # (prefix + # ((: ) (xsd: )) + # (project (?X ?S) + # (extend ((?S ??.0)) + # (group + # ((?X (coalesce ?w "1605-11-05"^^xsd:date))) + # ((??.0 (sample ?v))) + # (leftjoin + # (bgp (triple ?s :p ?v)) + # (bgp (triple ?s :q ?w))))))) # # @see https://www.w3.org/TR/sparql11-query/#func-coalesce class Coalesce < Operator @@ -50,6 +63,15 @@ def evaluate(bindings, **options) end raise TypeError, "None of the operands evaluated" end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "COALESCE(#{operands.to_sparql(delimiter: ', ', **options)})" + end end # Coalesce end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/compare.rb b/lib/sparql/algebra/operator/compare.rb index 4927a8ee..d946d84f 100644 --- a/lib/sparql/algebra/operator/compare.rb +++ b/lib/sparql/algebra/operator/compare.rb @@ -35,6 +35,15 @@ def apply(left, right, **options) RDF::Literal(spaceship(left, right, **options)) end + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(#{operands.first.to_sparql(**options)} #{self.class.const_get(:NAME)} #{operands.last.to_sparql(**options)})" + end + private # Define <=> as private for recursive statements def spaceship(left, right, **options) diff --git a/lib/sparql/algebra/operator/concat.rb b/lib/sparql/algebra/operator/concat.rb index 9172f05a..32b1a0a9 100644 --- a/lib/sparql/algebra/operator/concat.rb +++ b/lib/sparql/algebra/operator/concat.rb @@ -5,8 +5,23 @@ class Operator # # The CONCAT function corresponds to the XPath fn:concat function. The function accepts string literals as arguments. # - # @example - # (concat ?a ?b ...) + # [121] BuiltInCall ::= ... 'CONCAT' ExpressionList + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (CONCAT(?str1,?str2) AS ?str) WHERE { + # :s6 :str ?str1 . + # :s7 :str ?str2 . + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?str) + # (extend ((?str (concat ?str1 ?str2))) + # (bgp + # (triple :s6 :str ?str1) + # (triple :s7 :str ?str2))))) # # @see https://www.w3.org/TR/sparql11-query/#func-concat # @see https://www.w3.org/TR/xpath-functions/#func-concat @@ -48,6 +63,15 @@ def evaluate(bindings, **options) end end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "CONCAT(#{operands.to_sparql(delimiter: ', ', **options)})" + end end # Concat end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/construct.rb b/lib/sparql/algebra/operator/construct.rb index 7a6c9d62..0b9845b2 100644 --- a/lib/sparql/algebra/operator/construct.rb +++ b/lib/sparql/algebra/operator/construct.rb @@ -5,12 +5,22 @@ class Operator # # The CONSTRUCT query form returns a single RDF graph specified by a graph template. The result is an RDF graph formed by taking each query solution in the solution sequence, substituting for the variables in the graph template, and combining the triples into a single RDF graph by set union. # - # @example - # (prefix ((rdf: ) - # (foaf: )) - # (construct ((triple ?s ?p ?o)) - # (project (?s ?p ?o) - # (bgp (triple ?s ?p ?o))))) + # [10] ConstructQuery ::= 'CONSTRUCT' ( ConstructTemplate DatasetClause* WhereClause SolutionModifier | DatasetClause* 'WHERE' '{' TriplesTemplate? '}' SolutionModifier ) ValuesClause + # + # @example SPARQL Grammar + # PREFIX : + # CONSTRUCT { ?x :p2 ?v } + # WHERE { + # ?x :p ?o . + # OPTIONAL {?o :q ?v } + # } + # + # @example SSE + # (prefix ((: )) + # (construct ((triple ?x :p2 ?v)) + # (leftjoin + # (bgp (triple ?x :p ?o)) + # (bgp (triple ?o :q ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#construct class Construct < Operator::Binary @@ -81,6 +91,19 @@ def execute(queryable, **options, &block) def query_yields_statements? true end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @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") + + "\n}\n" + + str << operands[1].to_sparql(top_level: true, project: nil, **options) + end end # Construct end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/contains.rb b/lib/sparql/algebra/operator/contains.rb index 4ca705f1..4fbf1e05 100644 --- a/lib/sparql/algebra/operator/contains.rb +++ b/lib/sparql/algebra/operator/contains.rb @@ -3,8 +3,21 @@ class Operator ## # A SPARQL `contains` operator. # - # @example - # (contains ?x ?y) + # [121] BuiltInCall ::= ... | 'CONTAINS' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s ?str WHERE { + # ?s :str ?str + # FILTER CONTAINS(?str, "a") + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?str) + # (filter (contains ?str "a") + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-contains # @see https://www.w3.org/TR/xpath-functions/#func-contains @@ -39,6 +52,15 @@ def apply(left, right, **options) else RDF::Literal::FALSE end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "contains(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # Contains end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/copy.rb b/lib/sparql/algebra/operator/copy.rb index 8cd48436..46ca3cab 100644 --- a/lib/sparql/algebra/operator/copy.rb +++ b/lib/sparql/algebra/operator/copy.rb @@ -6,8 +6,13 @@ class Operator # # The COPY operation is a shortcut for inserting all data from an input graph into a destination graph. Data from the input graph is not affected, but data from the destination graph, if any, is removed before insertion. # - # @example - # (copy silent to default) + # [37] Copy ::= 'COPY' 'SILENT'? GraphOrDefault 'TO' GraphOrDefault + # + # @example SPARQL Grammar + # COPY SILENT GRAPH TO DEFAULT + # + # @example SSE + # (update (copy silent default)) # # @see https://www.w3.org/TR/sparql11-update/#copy class Copy < Operator @@ -59,6 +64,18 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + *args, last = operands.dup + args += [:TO, last] + + "COPY " + args.to_sparql(**options) + end end # Copy end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/count.rb b/lib/sparql/algebra/operator/count.rb index a3578dc8..f7efcbeb 100644 --- a/lib/sparql/algebra/operator/count.rb +++ b/lib/sparql/algebra/operator/count.rb @@ -3,7 +3,14 @@ class Operator ## # The SPARQL `count` set function. # - # @example + # [127] Aggregate::= 'COUNT' '(' 'DISTINCT'? ( '*' | Expression ) ')' ... + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (COUNT(?O) AS ?C) + # WHERE { ?S ?P ?O } + # + # @example SSE # (prefix ((: )) # (project (?C) # (extend ((?C ??.0)) @@ -25,6 +32,15 @@ class Count < Operator def apply(enum, **options) RDF::Literal(enum.length) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "COUNT(#{operands.to_sparql(**options)})" + end end # Count end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/create.rb b/lib/sparql/algebra/operator/create.rb index 8a9b01a0..7c69c9c7 100644 --- a/lib/sparql/algebra/operator/create.rb +++ b/lib/sparql/algebra/operator/create.rb @@ -8,8 +8,13 @@ class Operator # # This is a no-op for RDF.rb implementations, unless the graph exists # - # @example - # (create silent ) + # [34] Create ::= 'CREATE' 'SILENT'? GraphRef + # + # @example SPARQL Grammar + # CREATE SILENT GRAPH + # + # @example SSE + # (update (create silent )) # # @see https://www.w3.org/TR/sparql11-update/#create class Create < Operator @@ -43,6 +48,18 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + *args, last = operands.dup + args += [:GRAPH, last] + + "CREATE " + args.to_sparql(**options) + end end # Create end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/dataset.rb b/lib/sparql/algebra/operator/dataset.rb index eac97b29..61d00bfd 100644 --- a/lib/sparql/algebra/operator/dataset.rb +++ b/lib/sparql/algebra/operator/dataset.rb @@ -158,6 +158,23 @@ def execute(queryable, **options, &base) aggregate.default(*default_datasets.select {|name| queryable.has_graph?(name)}) aggregate.query(operands.last, depth: options[:depth].to_i + 1, **options, &base) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @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 + end end # Dataset end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/datatype.rb b/lib/sparql/algebra/operator/datatype.rb index 4eba701f..99e42e65 100644 --- a/lib/sparql/algebra/operator/datatype.rb +++ b/lib/sparql/algebra/operator/datatype.rb @@ -3,13 +3,23 @@ class Operator ## # The SPARQL `datatype` operator. # - # @example + # [121] BuiltInCall ::= ... | 'DATATYPE' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x ?v + # WHERE + # { ?x :p ?v . + # FILTER ( datatype(?v) = xsd:double ) . + # } + # + # @example SSE # (prefix ((xsd: ) - # (rdf: ) - # (: )) - # (project (?s) - # (filter (= (datatype (xsd:double ?v)) xsd:double) - # (bgp (triple ?s :p ?v))))) + # (: )) + # (project (?x ?v) + # (filter (= (datatype ?v) xsd:double) + # (bgp (triple ?x :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#func-datatype class Datatype < Operator::Unary @@ -33,6 +43,15 @@ def apply(literal, **options) else raise TypeError, "expected an RDF::Literal, but got #{literal.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "DATATYPE(#{operands.last.to_sparql(**options)})" + end end # Datatype end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/day.rb b/lib/sparql/algebra/operator/day.rb index 6f8cf221..a9048e3d 100644 --- a/lib/sparql/algebra/operator/day.rb +++ b/lib/sparql/algebra/operator/day.rb @@ -3,11 +3,20 @@ class Operator ## # The SPARQL logical `day` operator. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (day ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'DAY' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (DAY(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (day ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-day class Day < Operator::Unary @@ -26,6 +35,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime) RDF::Literal(operand.object.day) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "DAY(#{operands.last.to_sparql(**options)})" + end end # Day end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/delete.rb b/lib/sparql/algebra/operator/delete.rb index 05622645..228dbf37 100644 --- a/lib/sparql/algebra/operator/delete.rb +++ b/lib/sparql/algebra/operator/delete.rb @@ -6,8 +6,24 @@ class Operator # # The DELETE operation is a form of the DELETE/INSERT operation having no INSERT section # - # @example - # (delete ((triple ?s ?p ?o)))) + # [42] DeleteClause ::= 'DELETE' QuadPattern + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX foaf: + # + # DELETE { ?s ?p ?o } + # WHERE { :a foaf:knows ?s . ?s ?p ?o } + # + # @example SSE + # (prefix ((: ) + # (foaf: )) + # (update + # (modify + # (bgp + # (triple :a foaf:knows ?s) + # (triple ?s ?p ?o)) + # (delete ((triple ?s ?p ?o)))))) # # @see https://www.w3.org/TR/sparql11-update/#delete class Delete < Operator::Unary @@ -52,6 +68,15 @@ def execute(queryable, solutions: nil, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + "DELETE {\n" + operands.first.to_sparql(as_statement: true, **options) + "\n}" + end end # Delete end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/delete_data.rb b/lib/sparql/algebra/operator/delete_data.rb index 42ab1a55..1511b80e 100644 --- a/lib/sparql/algebra/operator/delete_data.rb +++ b/lib/sparql/algebra/operator/delete_data.rb @@ -6,8 +6,18 @@ class Operator # # The DELETE DATA operation removes some triples, given inline in the request, if the respective graphs in the Graph Store contain those # - # @example - # (deleteData ((triple :a foaf:knows :c))) + # [39] DeleteData ::= 'DELETE DATA' QuadData + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX foaf: + # DELETE DATA { + # :a foaf:knows :b . + # } + # + # @example SSE + # (prefix ((: ) (foaf: )) + # (update (deleteData ((triple :a foaf:knows :b))))) # # @see https://www.w3.org/TR/sparql11-update/#deleteData class DeleteData < Operator::Unary @@ -36,6 +46,17 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + "DELETE DATA {\n" + + operands.first.to_sparql(as_statement: true, top_level: false, delimiter: "\n", **options) + + "\n}" + end end # DeleteData end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/delete_where.rb b/lib/sparql/algebra/operator/delete_where.rb index 232f402a..83245769 100644 --- a/lib/sparql/algebra/operator/delete_where.rb +++ b/lib/sparql/algebra/operator/delete_where.rb @@ -6,8 +6,19 @@ class Operator # # The DELETE WHERE operation is a shortcut form for the DELETE/INSERT operation where bindings matched by the WHERE clause are used to define the triples in a graph that will be deleted. # - # @example - # (deleteWhere ((triple :a foaf:knows ?b)) + # [40] DeleteWhere ::= 'DELETE WHERE' QuadPattern + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX foaf: + # DELETE WHERE { :a foaf:knows ?b } + # + # @example SSE + # (prefix + # ((: ) + # (foaf: )) + # (update + # (deleteWhere ((triple :a foaf:knows ?b))))) # # @see https://www.w3.org/TR/sparql11-update/#deleteWhere class DeleteWhere < Operator::Unary @@ -52,6 +63,17 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + "DELETE WHERE {\n" + + operands.first.to_sparql(as_statement: true, top_level: false, delimiter: "\n", **options) + + "\n}" + end end # DeleteWhere end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/desc.rb b/lib/sparql/algebra/operator/desc.rb index d66d9296..7fe23cd1 100644 --- a/lib/sparql/algebra/operator/desc.rb +++ b/lib/sparql/algebra/operator/desc.rb @@ -3,7 +3,15 @@ class Operator ## # The SPARQL descending sort operator. # - # @example + # [24] OrderCondition ::= ( ( 'ASC' | 'DESC' ) BrackettedExpression ) | ( Constraint | Var ) + # + # @example SPARQL Grammar + # PREFIX foaf: + # SELECT ?name + # WHERE { ?x foaf:name ?name } + # ORDER BY DESC(?name) + # + # @example SSE # (prefix ((foaf: )) # (project (?name) # (order ((desc ?name)) @@ -12,6 +20,17 @@ class Operator # @see https://www.w3.org/TR/sparql11-query/#modOrderBy class Desc < Operator::Asc NAME = :desc + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # Provides order to descendant query. + # + # @return [String] + def to_sparql(**options) + "DESC(#{operands.last.to_sparql(**options)})" + end end # Desc end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/describe.rb b/lib/sparql/algebra/operator/describe.rb index e1d758e4..fa599272 100644 --- a/lib/sparql/algebra/operator/describe.rb +++ b/lib/sparql/algebra/operator/describe.rb @@ -5,10 +5,16 @@ class Operator # # Generages a graph across specified terms using {RDF::Queryable}`#concise_bounded_description`. # - # @example - # (prefix ((exOrg: )) - # (describe (?x) - # (bgp (triple ?x exOrg:employeeId "1234")))) + # [11] DescribeQuery ::= 'DESCRIBE' ( VarOrIri+ | '*' ) DatasetClause* WhereClause? SolutionModifier ValuesClause + # + # @example SPARQL Grammar + # BASE + # DESCRIBE ?u WHERE { ?u . } + # + # @example SSE + # (base + # (describe ( ?u) + # (bgp (triple ?u)))) # # @see https://www.w3.org/TR/sparql11-query/#describe class Describe < Operator::Binary @@ -66,6 +72,23 @@ def execute(queryable, **options, &block) def query_yields_statements? true end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + str = "DESCRIBE " + str << if operands[0].empty? + "*" + else + operands[0].map { |e| e.to_sparql(**options) }.join(" ") + end + + str << "\n" + str << operands[1].to_sparql(top_level: true, project: nil, **options) + end end # Construct end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/distinct.rb b/lib/sparql/algebra/operator/distinct.rb index e790cfd1..da99fbd2 100644 --- a/lib/sparql/algebra/operator/distinct.rb +++ b/lib/sparql/algebra/operator/distinct.rb @@ -3,7 +3,15 @@ class Operator ## # The SPARQL GraphPattern `distinct` operator. # - # @example + # [9] SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ | '*' ) + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT DISTINCT ?v + # WHERE { ?x ?p ?v } + # + # @example SSE # (prefix ((xsd: ) # (: )) # (distinct @@ -36,6 +44,15 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + operands.first.to_sparql(distinct: true, **options) + end end # Distinct end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/divide.rb b/lib/sparql/algebra/operator/divide.rb index 0543977a..370da362 100644 --- a/lib/sparql/algebra/operator/divide.rb +++ b/lib/sparql/algebra/operator/divide.rb @@ -3,8 +3,23 @@ class Operator ## # The SPARQL numeric `divide` operator. # - # @example - # (/ 4 2) + # [117] MultiplicativeExpression::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )* + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s WHERE { + # ?s :p ?o . + # ?s2 :p ?o2 . + # FILTER(?o / ?o2 = 4) . + # } + # + # @example SSE + # (prefix ((: )) + # (project (?s) + # (filter (= (/ ?o ?o2) 4) + # (bgp + # (triple ?s :p ?o) + # (triple ?s2 :p ?o2))))) # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-divide class Divide < Operator::Binary @@ -38,6 +53,15 @@ def apply(left, right, **options) else raise TypeError, "expected two RDF::Literal::Numeric operands, but got #{left.inspect} and #{right.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "#{operands.first.to_sparql(**options)} / #{operands.last.to_sparql(**options)}" + end end # Divide end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/drop.rb b/lib/sparql/algebra/operator/drop.rb index e1d92d8f..e610322a 100644 --- a/lib/sparql/algebra/operator/drop.rb +++ b/lib/sparql/algebra/operator/drop.rb @@ -8,8 +8,14 @@ class Operator # # Equivalent to `clear` in this implementation # - # @example - # (drop default) + # [33] Drop ::= 'DROP' 'SILENT'? GraphRefAll + # + # @example SPARQL Grammar + # DROP DEFAULT + # + # @example SSE + # (update + # (drop default)) # # @see https://www.w3.org/TR/sparql11-update/#drop class Drop < Operator @@ -61,6 +67,15 @@ def execute(queryable, **options) queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "DROP " + operands.to_sparql(**options) + end end # Drop end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/encode_for_uri.rb b/lib/sparql/algebra/operator/encode_for_uri.rb index 547836ec..23e382e3 100644 --- a/lib/sparql/algebra/operator/encode_for_uri.rb +++ b/lib/sparql/algebra/operator/encode_for_uri.rb @@ -5,8 +5,21 @@ class Operator ## # The SPARQL logical `abs` operator. # - # @example - # (encode_for_uri ?x) + # [121] BuiltInCall ::= ... | 'ENCODE_FOR_URI' '(' Expression ')' + # + # @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) + # (extend ((?encoded (encode_for_uri ?str))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-encode # @see https://www.w3.org/TR/xpath-functions/#func-abs @@ -33,6 +46,15 @@ def apply(operand, **options) else raise TypeError, "expected an RDF::Literal, but got #{operand.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "ENCODE_FOR_URI(#{operands.last.to_sparql(**options)})" + end end # EncodeForURI end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/equal.rb b/lib/sparql/algebra/operator/equal.rb index 302abbfc..22fbff3d 100644 --- a/lib/sparql/algebra/operator/equal.rb +++ b/lib/sparql/algebra/operator/equal.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL relational `=` (equal) comparison operator. # - # @example - # (= ?x ?y) + # [114] RelationalExpression ::= NumericExpression ('=' NumericExpression)? + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x + # WHERE { ?x :p ?v . FILTER ( ?v = 1 ) } + # + # @example SSE + # (prefix + # ((xsd: ) (: )) + # (project (?x) (filter (= ?v 1) (bgp (triple ?x :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#OperatorMapping # @see https://www.w3.org/TR/sparql11-query/#func-RDFterm-equal diff --git a/lib/sparql/algebra/operator/exists.rb b/lib/sparql/algebra/operator/exists.rb index e2f53e76..ba50a694 100644 --- a/lib/sparql/algebra/operator/exists.rb +++ b/lib/sparql/algebra/operator/exists.rb @@ -5,10 +5,21 @@ class Operator # # There is a filter operator EXISTS that takes a graph pattern. EXISTS returns `true`/`false` depending on whether the pattern matches the dataset given the bindings in the current group graph pattern, the dataset and the active graph at this point in the query evaluation. No additional binding of variables occurs. The `NOT EXISTS` form translates into `fn:not(EXISTS{...})`. # - # @example - # (prefix ((ex: )) - # (filter (exists (bgp (triple ?s ?p ex:o))) - # (bgp (triple ?s ?p ?o)))) + # [125] ExistsFunc ::= 'EXISTS' GroupGraphPattern + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * + # WHERE { + # ?set a :Set . + # FILTER EXISTS { ?set :member 9 } + # } + # + # @example SSE + # (prefix ((: )) + # (filter + # (exists (bgp (triple ?set :member 9))) + # (bgp (triple ?set a :Set)))) # # @see https://www.w3.org/TR/sparql11-query/#func-filter-exists class Exists < Operator::Unary @@ -32,6 +43,19 @@ def evaluate(bindings, **options) depth: options[:depth].to_i + 1, **options).empty? 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) + "EXISTS {\n" + + operands.last.to_sparql(top_level: false, **options) + + "\n}" + end end # Exists end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/exprlist.rb b/lib/sparql/algebra/operator/exprlist.rb index 4cb75310..3964c24a 100644 --- a/lib/sparql/algebra/operator/exprlist.rb +++ b/lib/sparql/algebra/operator/exprlist.rb @@ -5,7 +5,18 @@ class Operator # # Used for filters with more than one expression. # - # @example + # [72] ExpressionList ::= NIL | '(' Expression ( ',' Expression )* ')' + # + # @example SPARQL Grammar + # SELECT ?v ?w + # { + # FILTER (?v = 2) + # FILTER (?w = 3) + # ?s :p ?v . + # ?s :q ?w . + # } + # + # @example SSE # (prefix ((: )) # (project (?v ?w) # (filter (exprlist (= ?v 2) (= ?w 3)) diff --git a/lib/sparql/algebra/operator/extend.rb b/lib/sparql/algebra/operator/extend.rb index 8b16d540..9dd564f7 100644 --- a/lib/sparql/algebra/operator/extend.rb +++ b/lib/sparql/algebra/operator/extend.rb @@ -5,11 +5,19 @@ class Operator # # Extends a solution # - # @example - # (select (?z) - # (project (?z) - # (extend ((?z (+ ?o 10))) - # (bgp (triple ?s ?o))))) + # [60] Bind ::= 'BIND' '(' Expression 'AS' Var ')' + # + # @example SPARQL Grammar + # SELECT ?z + # { + # ?x ?o + # BIND(?o+1 AS ?z) + # } + # + # @example SSE + # (project (?z) + # (extend ((?z (+ ?o 10))) + # (bgp (triple ?s ?o)))) # # @see https://www.w3.org/TR/sparql11-query/#evaluation class Extend < Operator::Binary @@ -73,6 +81,23 @@ def validate! end super end - end # Filter + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # Extracts bindings. + # + # @return [String] + def to_sparql(**options) + extensions = operands.first.inject({}) do |memo, (as, expression)| + memo.merge(as => expression) + end + + # Merge any inherited extensions from options + extensions = options.delete(:extensions).merge(extensions) if options.key?(:extensions) + operands.last.to_sparql(extensions: extensions, **options) + end + end # Extend end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/filter.rb b/lib/sparql/algebra/operator/filter.rb index 8732927b..c7493135 100644 --- a/lib/sparql/algebra/operator/filter.rb +++ b/lib/sparql/algebra/operator/filter.rb @@ -3,11 +3,19 @@ class Operator ## # The SPARQL GraphPattern `filter` operator. # - # @example - # (select (?v) - # (project (?v) - # (filter (= ?v 2) - # (bgp (triple ?s ?v))))) + # [68] Filter ::= 'FILTER' Constraint + # + # @example SPARQL Grammar + # SELECT ?v + # { + # ?s ?v + # FILTER(?v = 2) + # } + # + # @example SSE + # (project (?v) + # (filter (= ?v 2) + # (bgp (triple ?s ?v)))) # # @see https://www.w3.org/TR/sparql11-query/#evaluation class Filter < Operator::Binary @@ -69,6 +77,20 @@ def validate! end self end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # Provides filters to descendant query. + # + # If filter operation is an Exprlist, then separate into multiple filter ops. + # + # @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) + end end # Filter end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/floor.rb b/lib/sparql/algebra/operator/floor.rb index 21e13c46..775b874b 100644 --- a/lib/sparql/algebra/operator/floor.rb +++ b/lib/sparql/algebra/operator/floor.rb @@ -3,8 +3,22 @@ class Operator ## # The SPARQL logical `floor` operator. # - # @example - # (floor ?x) + # [121] BuiltInCall ::= ... 'FLOOR' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s ?num (FLOOR(?num) AS ?floor) WHERE { + # ?s :num ?num + # } + # + # @example SSE + # (prefix + # ((: ) + # (xsd: )) + # (project (?s ?num ?floor) + # (extend ((?floor (floor ?num))) + # (bgp (triple ?s :num ?num))))) # # @see https://www.w3.org/TR/sparql11-query/#func-floor # @see https://www.w3.org/TR/xpath-functions/#func-floor @@ -30,6 +44,15 @@ def apply(operand, **options) else raise TypeError, "expected an RDF::Literal::Numeric, but got #{operand.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "FLOOR(#{operands.to_sparql(**options)})" + end end # Floor end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/graph.rb b/lib/sparql/algebra/operator/graph.rb index f0cc6625..3bb31ddd 100644 --- a/lib/sparql/algebra/operator/graph.rb +++ b/lib/sparql/algebra/operator/graph.rb @@ -5,6 +5,19 @@ class Operator # # This is a wrapper to add a `graph_name` to the query, or an array of statements. # + # [58] GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # GRAPH ?g { ?s ?p ?o } + # } + # + # @example SSE + # (prefix ((: )) + # (graph ?g + # (bgp (triple ?s ?p ?o)))) + # # @example of a query # (prefix ((: )) # (graph ?g diff --git a/lib/sparql/algebra/operator/greater_than.rb b/lib/sparql/algebra/operator/greater_than.rb index 6ad9f0f8..86a4a9c6 100644 --- a/lib/sparql/algebra/operator/greater_than.rb +++ b/lib/sparql/algebra/operator/greater_than.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL relational `>` (greater than) comparison operator. # - # @example - # (> ?x ?y) + # [114] RelationalExpression ::= NumericExpression ('>' NumericExpression)? + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x + # WHERE { ?x :p ?v . FILTER ( ?v > 1 ) } + # + # @example SSE + # (prefix + # ((xsd: ) (: )) + # (project (?x) (filter (> ?v 1) (bgp (triple ?x :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#OperatorMapping # @see https://www.w3.org/TR/xpath-functions/#func-compare @@ -25,7 +35,6 @@ class GreaterThan < Compare # @return [RDF::Literal::Boolean] `true` or `false` # @raise [TypeError] if either operand is not a literal def apply(left, right, **options) - #require 'byebug'; byebug if super == RDF::Literal(1) RDF::Literal(super == RDF::Literal(1)) end end # GreaterThan diff --git a/lib/sparql/algebra/operator/greater_than_or_equal.rb b/lib/sparql/algebra/operator/greater_than_or_equal.rb index bc8f8dd6..00cccb0f 100644 --- a/lib/sparql/algebra/operator/greater_than_or_equal.rb +++ b/lib/sparql/algebra/operator/greater_than_or_equal.rb @@ -4,8 +4,18 @@ class Operator # The SPARQL relational `>=` (greater than or equal) comparison # operator. # - # @example - # (>= ?x ?y) + # [114] RelationalExpression ::= NumericExpression ('>=' NumericExpression)? + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x + # WHERE { ?x :p ?v . FILTER ( ?v >= 1 ) } + # + # @example SSE + # (prefix + # ((xsd: ) (: )) + # (project (?x) (filter (>= ?v 1) (bgp (triple ?x :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#OperatorMapping # @see https://www.w3.org/TR/xpath-functions/#func-compare diff --git a/lib/sparql/algebra/operator/group.rb b/lib/sparql/algebra/operator/group.rb index 5b62a06c..de834c59 100644 --- a/lib/sparql/algebra/operator/group.rb +++ b/lib/sparql/algebra/operator/group.rb @@ -8,14 +8,22 @@ class Operator # query to be grouped. If three operands are provided, # the second is an array of temporary bindings. # - # @example - # (prefix ((: )) - # (project (?w ?S) - # (extend ((?S ??.0)) - # (group (?w) ((??.0 (sample ?v))) - # (leftjoin - # (bgp (triple ?s :p ?v)) - # (bgp (triple ?s :q ?w))))))) + # [19] GroupClause ::= 'GROUP' 'BY' GroupCondition+ + # + # @example SPARQL Grammar + # PREFIX : + # + # SELECT ?P (COUNT(?O) AS ?C) + # WHERE { ?S ?P ?O } + # GROUP BY ?P + # + # @example SSE + # (prefix + # ((: )) + # (project (?P ?C) + # (extend ((?C ??.0)) + # (group (?P) ((??.0 (count ?O))) + # (bgp (triple ?S ?P ?O)))))) # # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra class Group < Operator @@ -122,6 +130,24 @@ def validate! end super end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @param [Hash{Symbol => Operator}] extensions + # Variable bindings + # @return [String] + def to_sparql(extensions: {}, **options) + if operands.length > 2 + # Replace extensions from temporary bindings + operands[1].each do |var, op| + ext_var = extensions.invert.fetch(var) + extensions[ext_var] = op + end + end + operands.last.to_sparql(extensions: extensions, group_ops: operands.first, **options) + end end # Group end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/group_concat.rb b/lib/sparql/algebra/operator/group_concat.rb index 59afa500..4fc35088 100644 --- a/lib/sparql/algebra/operator/group_concat.rb +++ b/lib/sparql/algebra/operator/group_concat.rb @@ -5,13 +5,16 @@ class Operator # # GroupConcat is a set function which performs a string concatenation across the values of an expression with a group. The order of the strings is not specified. The separator character used in the concatenation may be given with the scalar argument SEPARATOR. # - # @example - # (prefix ((: )) - # (filter (|| (= ?g "1 22") (= ?g "22 1")) - # (project (?g) - # (extend ((?g ??.0)) - # (group () ((??.0 (group_concat ?o))) - # (bgp (triple ??.0 :p1 ?o))))))) + # [127] Aggregate::= ... | 'GROUP_CONCAT' '(' 'DISTINCT'? Expression ( ';' 'SEPARATOR' '=' String )? ')' + # + # @example SPARQL Grammar + # SELECT (GROUP_CONCAT(?x) AS ?y) {} + # + # @example SSE + # (project (?y) + # (extend ((?y ??.0)) + # (group () ((??.0 (group_concat ?x))) + # (bgp)))) # # @see https://www.w3.org/TR/sparql11-query/#defn_aggGroupConcat class GroupConcat < Operator @@ -53,6 +56,15 @@ def aggregate(solutions = [], **options) def apply(enum, separator, **options) RDF::Literal(enum.flatten.map(&:to_s).join(separator.to_s)) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "GROUP_CONCAT(#{operands.to_sparql(delimiter: ', ', **options)})" + end end # GroupConcat end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/hours.rb b/lib/sparql/algebra/operator/hours.rb index 3144c9df..f4949582 100644 --- a/lib/sparql/algebra/operator/hours.rb +++ b/lib/sparql/algebra/operator/hours.rb @@ -3,11 +3,20 @@ class Operator ## # The SPARQL logical `hours` operator. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (hours ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'HOURS' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (HOURS(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (hours ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-hours class Hours < Operator::Unary @@ -26,6 +35,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime) RDF::Literal(operand.object.hour) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "HOURS(#{operands.last.to_sparql(**options)})" + end end # Hours end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/if.rb b/lib/sparql/algebra/operator/if.rb index 02342eb3..eae31f27 100644 --- a/lib/sparql/algebra/operator/if.rb +++ b/lib/sparql/algebra/operator/if.rb @@ -3,7 +3,15 @@ class Operator ## # The SPARQL `if` function. # - # @example + # [121] BuiltInCall ::= ... | 'IF' '(' Expression ',' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # BASE + # PREFIX xsd: + # SELECT ?o (IF(lang(?o) = "ja", true, false) AS ?integer) + # WHERE { ?s ?p ?o } + # + # @example SSE # (base # (prefix ((xsd: )) # (project (?o ?integer) @@ -37,9 +45,18 @@ def evaluate(bindings, **options) operand(0).evaluate(bindings, depth: options[:depth].to_i + 1, **options) == RDF::Literal::TRUE ? operand(1).evaluate(bindings, depth: options[:depth].to_i + 1, **options) : operand(2).evaluate(bindings, depth: options[:depth].to_i + 1, **options) - rescue - raise TypeError + rescue + raise TypeError end end # If - end # Operator + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "IF(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end + end # If end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/in.rb b/lib/sparql/algebra/operator/in.rb index 346165d6..20f01e98 100644 --- a/lib/sparql/algebra/operator/in.rb +++ b/lib/sparql/algebra/operator/in.rb @@ -3,7 +3,12 @@ class Operator ## # The SPARQL GraphPattern `in` operator. # - # @example + # [114] RelationalExpression ::= NumericExpression ('IN' ExpressionList)? + # + # @example SPARQL Grammar + # ASK { FILTER(2 IN (1, 2, 3)) } + # + # @example SSE # (ask (filter (in 2 1 2 3) (bgp))) # # @see https://www.w3.org/TR/sparql11-query/#func-in @@ -54,6 +59,18 @@ def evaluate(bindings, **options) else RDF::Literal::FALSE end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(" + operands.first.to_sparql(**options) + + " IN (" + + operands[1..-1].map {|e| e.to_sparql(**options)}.join(", ") + + "))" + end end # In end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/insert.rb b/lib/sparql/algebra/operator/insert.rb index 8f17729f..7a096006 100644 --- a/lib/sparql/algebra/operator/insert.rb +++ b/lib/sparql/algebra/operator/insert.rb @@ -6,8 +6,19 @@ class Operator # # The INSERT operation is a form of the DELETE/INSERT operation having no DELETE section # - # @example - # (insert ((triple ?s ?p "q"))) + # [43] InsertClause ::= 'INSERT' QuadPattern + # + # @example SPARQL Grammar + # PREFIX : + # INSERT { ?s ?p "q" } + # WHERE { ?s ?p ?o } + # + # @example SSE + # (prefix + # ((: )) + # (update + # (modify (bgp (triple ?s ?p ?o)) + # (insert ((triple ?s ?p "q")))))) # # @see https://www.w3.org/TR/sparql11-update/#insert class Insert < Operator::Unary @@ -51,6 +62,15 @@ def execute(queryable, solutions: nil, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + "INSERT {\n" + operands.first.to_sparql(as_statement: true, **options) + "\n}" + end end # Insert end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/insert_data.rb b/lib/sparql/algebra/operator/insert_data.rb index 9a0c6829..f4e66f15 100644 --- a/lib/sparql/algebra/operator/insert_data.rb +++ b/lib/sparql/algebra/operator/insert_data.rb @@ -6,8 +6,18 @@ class Operator # # The INSERT DATA operation adds some triples, given inline in the request, into the Graph Store # - # @example - # (insertData ((graph ((triple :s :p :o))))) + # [38] InsertData ::= 'INSERT DATA' QuadData + # + # @example SPARQL Grammar + # PREFIX : + # INSERT DATA { GRAPH { :s :p :o } } + # + # @example SSE + # (prefix + # ((: )) + # (update + # (insertData ( + # (graph ((triple :s :p :o))))))) # # @see https://www.w3.org/TR/sparql11-update/#insertData class InsertData < Operator::Unary @@ -36,6 +46,17 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + "INSERT DATA {\n" + + operands.first.to_sparql(as_statement: true, top_level: false, delimiter: "\n", **options) + + "\n}" + end end # InsertData end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/iri.rb b/lib/sparql/algebra/operator/iri.rb index 2ce25589..8a09ac11 100644 --- a/lib/sparql/algebra/operator/iri.rb +++ b/lib/sparql/algebra/operator/iri.rb @@ -3,10 +3,18 @@ class Operator ## # The SPARQL `iri` operator. # - # @example - # (base (project (?uri ?iri) - # (extend ((?uri (uri "uri")) (?iri (iri "iri"))) - # (bgp)))) + # [121] BuiltInCall ::= ... | 'IRI' '(' Expression ')' | 'URI' '(' Expression ')' + # + # @example SPARQL Grammar + # BASE + # SELECT (URI("uri") AS ?uri) (IRI("iri") AS ?iri) + # WHERE {} + # + # @example SSE + # (base + # (project (?uri ?iri) + # (extend ((?uri (iri "uri")) (?iri (iri "iri"))) + # (bgp)))) # # @see https://www.w3.org/TR/sparql11-query/#func-iri class IRI < Operator::Unary @@ -35,6 +43,15 @@ def apply(literal, **options) base.join(literal.to_s) end + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "IRI(" + operands.last.to_sparql(**options) + ")" + end + Operator::URI = IRI end # IRI end # Operator diff --git a/lib/sparql/algebra/operator/is_blank.rb b/lib/sparql/algebra/operator/is_blank.rb index ac547f04..a833a927 100644 --- a/lib/sparql/algebra/operator/is_blank.rb +++ b/lib/sparql/algebra/operator/is_blank.rb @@ -3,7 +3,16 @@ class Operator ## # The SPARQL `isBlank` operator. # - # @example + # [121] BuiltInCall ::= ... | 'isBlank' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?x ?v WHERE { + # ?x :p ?v . + # FILTER isBlank(?v) . + # } + # + # @example SSE # (prefix ((xsd: ) # (: )) # (project (?x ?v) @@ -30,6 +39,15 @@ def apply(term, **options) else raise TypeError, "expected an RDF::Term, but got #{term.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "isBlank(" + operands.first.to_sparql(**options) + ")" + end end # IsBlank end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/is_iri.rb b/lib/sparql/algebra/operator/is_iri.rb index 72233b82..8d49b16d 100644 --- a/lib/sparql/algebra/operator/is_iri.rb +++ b/lib/sparql/algebra/operator/is_iri.rb @@ -3,7 +3,16 @@ class Operator ## # The SPARQL `isIRI`/`isURI` operator. # - # @example + # [121] BuiltInCall ::= ... | 'isIRI' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?x ?v WHERE { + # ?x :p ?v . + # FILTER isIRI(?v) . + # } + # + # @example SSE # (prefix ((xsd: ) # (: )) # (project (?x ?v) @@ -32,6 +41,15 @@ def apply(term, **options) end Operator::IsURI = IsIRI + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "isIRI(" + operands.first.to_sparql(**options) + ")" + end end # IsIRI end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/is_literal.rb b/lib/sparql/algebra/operator/is_literal.rb index 6f33f9a1..fdd23447 100644 --- a/lib/sparql/algebra/operator/is_literal.rb +++ b/lib/sparql/algebra/operator/is_literal.rb @@ -3,7 +3,16 @@ class Operator ## # The SPARQL `isLiteral` operator. # - # @example + # [121] BuiltInCall ::= ... | 'isLiteral' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?x ?v WHERE { + # ?x :p ?v . + # FILTER isLiteral(?v) . + # } + # + # @example SSE # (prefix ((xsd: ) # (: )) # (project (?x ?v) @@ -31,6 +40,15 @@ def apply(term, **options) else raise TypeError, "expected an RDF::Term, but got #{term.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "isLiteral(" + operands.first.to_sparql(**options) + ")" + end end # IsLiteral end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/is_numeric.rb b/lib/sparql/algebra/operator/is_numeric.rb index f8eca138..b43076b0 100644 --- a/lib/sparql/algebra/operator/is_numeric.rb +++ b/lib/sparql/algebra/operator/is_numeric.rb @@ -3,9 +3,18 @@ class Operator ## # The SPARQL `isNumeric` operator. # - # Note numeric denotes typed literals with datatypes xsd:integer, xsd:decimal, xsd:float, and xsd:double, not derived types. + # Note numeric denotes typed literals with datatypes `xsd:integer`, `xsd:decimal`, `xsd:float`, and `xsd:double`, not derived types. # - # @example + # [121] BuiltInCall ::= ... | 'isNumeric' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?x ?v WHERE { + # ?x :p ?v . + # FILTER isNumeric(?v) . + # } + # + # @example SSE # (prefix ((xsd: ) # (: )) # (project (?x ?v) @@ -16,7 +25,7 @@ class Operator class IsNumeric < Operator::Unary include Evaluatable - NAME = :isnumeric + NAME = :isNumeric ## # Returns `true` if the operand is an `RDF::Literal::Numeric`, `false` @@ -36,6 +45,15 @@ def apply(term, **options) else raise TypeError, "expected an RDF::Term, but got #{term.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "isNumeric(" + operands.first.to_sparql(**options) + ")" + end end # IsNumeric end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/is_triple.rb b/lib/sparql/algebra/operator/is_triple.rb index 5b1cf02e..8965d8df 100644 --- a/lib/sparql/algebra/operator/is_triple.rb +++ b/lib/sparql/algebra/operator/is_triple.rb @@ -5,6 +5,29 @@ class Operator # # Returns true if term is an RDF-star triple. Returns false otherwise. # + # [121] BuiltInCall ::= ... | 'isTreiple' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # ?t :source :g + # FILTER(isTriple(?t)) + # FILTER(SUBJECT(?t) = :s) + # FILTER(PREDICATE(?t) = :p) + # FILTER(OBJECT(?t) = :o) + # } + # + # @example SSE + # (prefix + # ((: )) + # (filter + # (exprlist + # (isTRIPLE ?t) + # (= (subject ?t) :s) + # (= (predicate ?t) :p) + # (= (object ?t) :o)) + # (bgp (triple ?t :source :g))) ) + # # @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#istriple class IsTriple < Operator::Unary include Evaluatable @@ -25,6 +48,15 @@ def apply(term, **options) else raise TypeError, "expected an RDF::Term, but got #{term.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "isTRIPLE(" + operands.first.to_sparql(**options) + ")" + end end # IsTriple end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/join.rb b/lib/sparql/algebra/operator/join.rb index fcddbbf6..2f2edd2c 100644 --- a/lib/sparql/algebra/operator/join.rb +++ b/lib/sparql/algebra/operator/join.rb @@ -3,7 +3,16 @@ class Operator ## # The SPARQL GraphPattern `join` operator. # - # @example + # [54] GroupGraphPatternSub ::= TriplesBlock? (GraphPatternNotTriples "."? TriplesBlock? )* + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # ?s ?p ?o + # GRAPH ?g { ?s ?q ?v } + # } + # + # @example SSE # (prefix ((: )) # (join # (bgp (triple ?s ?p ?o)) @@ -82,6 +91,18 @@ def optimize!(**options) @operands = ops 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) + str = operands.to_sparql(top_level: false, delimiter: "\n", **options) + top_level ? Operator.to_sparql(str, **options) : str + end end # Join end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/lang.rb b/lib/sparql/algebra/operator/lang.rb index 9b5e7dab..e525a4a2 100644 --- a/lib/sparql/algebra/operator/lang.rb +++ b/lib/sparql/algebra/operator/lang.rb @@ -3,6 +3,22 @@ class Operator ## # The SPARQL `lang` operator. # + # [121] BuiltInCall ::= ... | 'LANG' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # + # SELECT ?x + # { ?x :p ?v . + # FILTER ( lang(?v) != '@NotALangTag@' ) + # } + # + # @example SSE + # (prefix ((: )) + # (project (?x) + # (filter (!= (lang ?v) "@NotALangTag@") + # (bgp (triple ?x :p ?v))))) + # # @see https://www.w3.org/TR/sparql11-query/#func-lang class Lang < Operator::Unary include Evaluatable @@ -24,6 +40,15 @@ def apply(literal, **options) else raise TypeError, "expected an RDF::Literal, but got #{literal.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "lang(" + operands.first.to_sparql(**options) + ")" + end end # Lang end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/lang_matches.rb b/lib/sparql/algebra/operator/lang_matches.rb index 1c068e3d..96536e8d 100644 --- a/lib/sparql/algebra/operator/lang_matches.rb +++ b/lib/sparql/algebra/operator/lang_matches.rb @@ -3,7 +3,15 @@ class Operator ## # The SPARQL `langMatches` operator. # - # @example + # [121] BuiltInCall ::= ... | 'LANGMATCHES' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # + # SELECT * + # { :x ?p ?v . FILTER langMatches(lang(?v), "en-GB") . } + # + # @example SSE # (prefix ((: )) # (filter (langMatches (lang ?v) "en-GB") # (bgp (triple :x ?p ?v)))) @@ -48,6 +56,19 @@ def apply(language_tag, language_range, **options) RDF::Literal(language_tag.start_with?(language_range + '-')) end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "langMatches(" + + operands.first.to_sparql(**options) + + ", " + + operands.last.to_sparql(**options) + + ")" + end end # LangMatches end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/lcase.rb b/lib/sparql/algebra/operator/lcase.rb index ee76bb4d..125cfea2 100644 --- a/lib/sparql/algebra/operator/lcase.rb +++ b/lib/sparql/algebra/operator/lcase.rb @@ -3,8 +3,20 @@ class Operator ## # The SPARQL logical `lcase` operator. # - # @example - # (lcase ?x) + # [121] BuiltInCall ::= ... | 'LCASE' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (LCASE(?str) AS ?lstr) WHERE { + # ?s :str ?str + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?str ?lstr) + # (extend ((?lstr (lcase ?str))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-lcase # @see https://www.w3.org/TR/xpath-functions/#func-lcase @@ -26,6 +38,15 @@ def apply(operand, **options) else raise TypeError, "expected an RDF::Literal::Numeric, but got #{operand.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "LCASE(" + operands.last.to_sparql(**options) + ")" + end end # LCase end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/left_join.rb b/lib/sparql/algebra/operator/left_join.rb index 98e615b7..c3eae45a 100644 --- a/lib/sparql/algebra/operator/left_join.rb +++ b/lib/sparql/algebra/operator/left_join.rb @@ -3,7 +3,19 @@ class Operator ## # The SPARQL GraphPattern `leftjoin` operator. # - # @example + # [57] OptionalGraphPattern ::= 'OPTIONAL' GroupGraphPattern + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # ?x :p ?v . + # OPTIONAL { + # ?y :q ?w . + # FILTER(?v=2) + # } + # } + # + # @example SSE # (prefix ((: )) # (leftjoin # (bgp (triple ?x :p ?v)) @@ -112,6 +124,22 @@ def optimize!(**options) expr ? LeftJoin.new(ops[0], ops[1], expr) : LeftJoin.new(ops[0], ops[1]) end 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) + 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 + end end # LeftJoin end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/less_than.rb b/lib/sparql/algebra/operator/less_than.rb index 98a584e5..f0a09f4f 100644 --- a/lib/sparql/algebra/operator/less_than.rb +++ b/lib/sparql/algebra/operator/less_than.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL relational `<` (less than) comparison operator. # - # @example - # (< ?x ?y) + # [114] RelationalExpression ::= NumericExpression ('<' NumericExpression)? + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x + # WHERE { ?x :p ?v . FILTER ( ?v < 1 ) } + # + # @example SSE + # (prefix + # ((xsd: ) (: )) + # (project (?x) (filter (< ?v 1) (bgp (triple ?x :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#OperatorMapping # @see https://www.w3.org/TR/xpath-functions/#func-compare @@ -25,7 +35,6 @@ class LessThan < Compare # @return [RDF::Literal::Boolean] `true` or `false` # @raise [TypeError] if either operand is not a literal def apply(left, right, **options) - #require 'byebug'; byebug if super == RDF::Literal(-1) RDF::Literal(super == RDF::Literal(-1)) end end # LessThan diff --git a/lib/sparql/algebra/operator/less_than_or_equal.rb b/lib/sparql/algebra/operator/less_than_or_equal.rb index 8b1cc5c9..2eed484a 100644 --- a/lib/sparql/algebra/operator/less_than_or_equal.rb +++ b/lib/sparql/algebra/operator/less_than_or_equal.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL relational `<=` (less than or equal) comparison operator. # - # @example - # (<= ?x ?y) + # [114] RelationalExpression ::= NumericExpression ('<=' NumericExpression)? + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x + # WHERE { ?x :p ?v . FILTER ( ?v <= 1 ) } + # + # @example SSE + # (prefix + # ((xsd: ) (: )) + # (project (?x) (filter (<= ?v 1) (bgp (triple ?x :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#OperatorMapping # @see https://www.w3.org/TR/xpath-functions/#func-compare diff --git a/lib/sparql/algebra/operator/load.rb b/lib/sparql/algebra/operator/load.rb index e952bda5..f5931b8d 100644 --- a/lib/sparql/algebra/operator/load.rb +++ b/lib/sparql/algebra/operator/load.rb @@ -6,8 +6,14 @@ class Operator # # The LOAD operation reads an RDF document from a IRI and inserts its triples into the specified graph in the Graph Store. The specified destination graph should be created if required; again, implementations providing an update service over a fixed set of graphs must return with failure for a request that would create a disallowed graph. If the destination graph already exists, then no data in that graph will be removed. # - # @example - # (load ) + # + # [31] Load ::= 'LOAD' 'SILENT'? iri ( 'INTO' GraphRef )? + # + # @example SPARQL Grammar + # LOAD INTO GRAPH ; + # + # @example SSE + # (update (load )) # # @see https://www.w3.org/TR/sparql11-update/#load class Load < Operator @@ -43,6 +49,23 @@ def execute(queryable, **options) ensure queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + silent = operands.first == :silent + ops = silent ? operands[1..-1] : operands + location, name = ops + + str = "LOAD " + str << "SILENT " if silent + str << location.to_sparql(**options) + str << " INTO GRAPH " + name.to_sparql(**options) if name + str + end end # Load end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/max.rb b/lib/sparql/algebra/operator/max.rb index 002f5857..62ca8add 100644 --- a/lib/sparql/algebra/operator/max.rb +++ b/lib/sparql/algebra/operator/max.rb @@ -3,7 +3,14 @@ class Operator ## # The SPARQL `max` set function. # - # @example + # [127] Aggregate::= ... | 'MAX' '(' 'DISTINCT'? Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (MAX(?o) AS ?max) + # WHERE { ?s ?p ?o } + # + # @example SSE # (prefix ((: )) # (project (?max) # (extend ((?max ??.0)) @@ -42,6 +49,15 @@ def apply(enum, **options) raise TypeError, "Maximum of non-literals: #{enum.flatten}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "MAX(" + operands.to_sparql(**options) + ")" + end end # Max end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/md5.rb b/lib/sparql/algebra/operator/md5.rb index bdcaf962..3bc7cd1e 100644 --- a/lib/sparql/algebra/operator/md5.rb +++ b/lib/sparql/algebra/operator/md5.rb @@ -7,11 +7,19 @@ class Operator # # Returns the MD5 checksum, as a hex digit string, calculated on the UTF-8 representation of the simple literal or lexical form of the `xsd:string`. Hex digits `SHOULD` be in lower case. # - # @example - # (prefix ((: )) - # (project (?hash) - # (extend ((?hash (md5 ?l))) - # (bgp (triple :s1 :str ?l))))) + # [121] BuiltInCall ::= ... | 'MD5' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (MD5(?l) AS ?hash) WHERE { + # :s1 :str ?l + # } + # + # @example SSE + # (prefix ((: )) + # (project (?hash) + # (extend ((?hash (md5 ?l))) + # (bgp (triple :s1 :str ?l))))) # # @see https://www.w3.org/TR/sparql11-query/#func-md5 class MD5 < Operator::Unary @@ -31,6 +39,15 @@ def apply(operand, **options) raise TypeError, "expected simple literal or xsd:string, but got #{operand.inspect}" unless (operand.datatype || RDF::XSD.string) == RDF::XSD.string RDF::Literal(Digest::MD5.new.hexdigest(operand.to_s)) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "MD5(" + operands.to_sparql(**options) + ")" + end end # MD5 end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/min.rb b/lib/sparql/algebra/operator/min.rb index b2520587..20cc6169 100644 --- a/lib/sparql/algebra/operator/min.rb +++ b/lib/sparql/algebra/operator/min.rb @@ -3,9 +3,16 @@ class Operator ## # The SPARQL `min` set function. # - # @example + # [127] Aggregate::= ... | 'MIN' '(' 'DISTINCT'? Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (MIN(?o) AS ?min) + # WHERE { ?s :dec ?o } + # + # @example SSE # (prefix ((: )) - # (project (?max) + # (project (?min) # (extend ((?min ??.0)) # (group () ((??.0 (min ?o))) # (bgp (triple ?s ?p ?o)))))) @@ -42,6 +49,15 @@ def apply(enum, **options) raise TypeError, "Minumuim of non-literals: #{enum.flatten}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "MIN(" + operands.to_sparql(**options) + ")" + end end # Min end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/minus.rb b/lib/sparql/algebra/operator/minus.rb index 288933d4..a858416a 100644 --- a/lib/sparql/algebra/operator/minus.rb +++ b/lib/sparql/algebra/operator/minus.rb @@ -3,13 +3,16 @@ class Operator ## # The SPARQL GraphPattern `minus` operator. # - # @example - # (prefix ((ex: )) - # (project (?animal) - # (minus - # (bgp (triple ?animal ex:Animal)) - # (filter (|| (= ?type ex:Reptile) (= ?type ex:Insect)) - # (bgp (triple ?animal ?type)))))) + # [66] MinusGraphPattern ::= 'MINUS' GroupGraphPattern + # + # @example SPARQL Grammar + # SELECT * { ?s ?p ?o MINUS { ?s ?q ?v } } + # + # @example SSE + # (minus + # (bgp + # (triple ?s ?p ?o)) + # (bgp (triple ?s ?q ?v))) # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra @@ -66,6 +69,21 @@ def optimize!(**options) @operands = ops 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) + 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 + end end # Minus end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/minutes.rb b/lib/sparql/algebra/operator/minutes.rb index 70576892..f71f4aeb 100644 --- a/lib/sparql/algebra/operator/minutes.rb +++ b/lib/sparql/algebra/operator/minutes.rb @@ -5,11 +5,20 @@ class Operator # # Returns the minutes part of the lexical form of `arg`. The value is as given in the lexical form of the XSD dateTime. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (minutes ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'MINUTES' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (MINUTES(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (minutes ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-minutes class Minutes < Operator::Unary @@ -28,6 +37,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime) RDF::Literal(operand.object.minute) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "MINUTES(#{operands.last.to_sparql(**options)})" + end end # Minutes end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/modify.rb b/lib/sparql/algebra/operator/modify.rb index 202e2965..da2c6f64 100644 --- a/lib/sparql/algebra/operator/modify.rb +++ b/lib/sparql/algebra/operator/modify.rb @@ -6,11 +6,23 @@ class Operator # # Wraps delete/insert # - # @example - # (modify - # (bgp (triple ?a foaf:knows ?b)) - # (delete ((triple ?a foaf:knows ?b))) - # (insert ((triple ?b foaf:knows ?a))) + # [41] Modify ::= ( 'WITH' iri )? ( DeleteClause InsertClause? | InsertClause ) UsingClause* 'WHERE' GroupGraphPattern + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX foaf: + # DELETE { ?a foaf:knows ?b } + # INSERT { ?b foaf:knows ?a } + # WHERE { ?a foaf:knows ?b } + # + # @example SSE + # (prefix ((: ) + # (foaf: )) + # (update + # (modify + # (bgp (triple ?a foaf:knows ?b)) + # (delete ((triple ?a foaf:knows ?b))) + # (insert ((triple ?b foaf:knows ?a)))) )) # # @see XXX class Modify < Operator @@ -48,6 +60,30 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + if operands.first.is_a?(With) + operands.first.to_sparql(**options) + else + # The content of the WHERE clause, may be USING + content = operands.first.to_sparql(top_level: false, **options) + + # DELETE | INSERT | DELETE INSERT + str = operands[1..-1].to_sparql(top_level: false, delimiter: "\n", **options) + "\n" + + # Append the WHERE or USING clause + str << if operands.first.is_a?(Using) + content + else + Operator.to_sparql(content, project: nil, **options) + end + end + end end # Modify end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/month.rb b/lib/sparql/algebra/operator/month.rb index a82f3beb..aeef7605 100644 --- a/lib/sparql/algebra/operator/month.rb +++ b/lib/sparql/algebra/operator/month.rb @@ -5,11 +5,20 @@ class Operator # # Returns the month part of `arg` as an integer. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (month ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'MONTH' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (MONTH(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (month ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-month class Month < Operator::Unary @@ -28,6 +37,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime) RDF::Literal(operand.object.month) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "MONTH(#{operands.last.to_sparql(**options)})" + end end # Month end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/move.rb b/lib/sparql/algebra/operator/move.rb index 6dfb1d0c..bcd2c3e1 100644 --- a/lib/sparql/algebra/operator/move.rb +++ b/lib/sparql/algebra/operator/move.rb @@ -6,8 +6,14 @@ class Operator # # The MOVE operation is a shortcut for moving all data from an input graph into a destination graph. The input graph is removed after insertion and data from the destination graph, if any, is removed before insertion. # - # @example - # (move silent to default) + # [36] Move ::= 'MOVE' 'SILENT'? GraphOrDefault 'TO' GraphOrDefault + # + # @example SPARQL Grammar + # MOVE SILENT GRAPH TO DEFAULT + # + # @example SSE + # (update + # (move silent default)) # # @see https://www.w3.org/TR/sparql11-update/#move class Move < Operator @@ -62,6 +68,18 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + *args, last = operands.dup + args += [:TO, last] + + "MOVE " + args.to_sparql(**options) + end end # Move end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/multiply.rb b/lib/sparql/algebra/operator/multiply.rb index 2eba25ef..77daba5b 100644 --- a/lib/sparql/algebra/operator/multiply.rb +++ b/lib/sparql/algebra/operator/multiply.rb @@ -3,9 +3,23 @@ class Operator ## # The SPARQL numeric `multiply` operator. # - # @example - # (* ?x ?y) - # (multiply ?x ?y) + # [117] MultiplicativeExpression::= UnaryExpression ( '*' UnaryExpression | '/' UnaryExpression )* + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s WHERE { + # ?s :p ?o . + # ?s2 :p ?o2 . + # FILTER(?o * ?o2 = 4) . + # } + # + # @example SSE + # (prefix ((: )) + # (project (?s) + # (filter (= (* ?o ?o2) 4) + # (bgp + # (triple ?s :p ?o) + # (triple ?s2 :p ?o2))))) # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-multiply class Multiply < Operator::Binary @@ -29,6 +43,15 @@ def apply(left, right, **options) else raise TypeError, "expected two RDF::Literal::Numeric operands, but got #{left.inspect} and #{right.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "#{operands.first.to_sparql(**options)} * #{operands.last.to_sparql(**options)}" + end end # Multiply end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/negate.rb b/lib/sparql/algebra/operator/negate.rb index c583c837..37d55228 100644 --- a/lib/sparql/algebra/operator/negate.rb +++ b/lib/sparql/algebra/operator/negate.rb @@ -3,9 +3,20 @@ class Operator ## # The SPARQL numeric unary `-` (negation) operator. # - # @example - # (- ?x) - # (negate ?x) + # [118] UnaryExpression ::= ... | '-' PrimaryExpression + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s WHERE { + # ?s :p ?o . + # FILTER(-?o = -2) . + # } + # + # @example SSE + # (prefix ((: )) + # (project (?s) + # (filter (= (- ?o) -2) + # (bgp (triple ?s :p ?o))))) # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus class Negate < Operator::Unary @@ -26,6 +37,15 @@ def apply(term, **options) else raise TypeError, "expected an RDF::Literal::Numeric, but got #{term.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(-#{operands.to_sparql(**options)})" + end end # Negate end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/not.rb b/lib/sparql/algebra/operator/not.rb index 80c2da7f..70921d00 100644 --- a/lib/sparql/algebra/operator/not.rb +++ b/lib/sparql/algebra/operator/not.rb @@ -3,9 +3,21 @@ class Operator ## # The SPARQL logical `not` operator. # - # @example - # (! ?x) - # (not ?x) + # [118] UnaryExpression ::= ... | '!' PrimaryExpression + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?a + # WHERE { + # ?a :p ?v . + # FILTER ( ! ?v ) . + # } + # + # @example SSE + # (prefix ((: )) + # (project (?a) + # (filter (! ?v) + # (bgp (triple ?a :p ?v))))) # # @see https://www.w3.org/TR/xpath-functions/#func-not class Not < Operator::Unary @@ -30,6 +42,15 @@ def apply(operand, **options) else super end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(!" + operands.first.to_sparql(**options) + ")" + end end # Not end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/not_equal.rb b/lib/sparql/algebra/operator/not_equal.rb index 7b8f6242..d27f984c 100644 --- a/lib/sparql/algebra/operator/not_equal.rb +++ b/lib/sparql/algebra/operator/not_equal.rb @@ -3,6 +3,19 @@ class Operator ## # The SPARQL relational `!=` (not equal) comparison operator. # + # [114] RelationalExpression ::= NumericExpression ('!=' NumericExpression)? + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x + # WHERE { ?x :p ?v . FILTER ( ?v != 1 ) } + # + # @example SSE + # (prefix + # ((xsd: ) (: )) + # (project (?x) (filter (!= ?v 1) (bgp (triple ?x :p ?v))))) + # # @see https://www.w3.org/TR/sparql11-query/#OperatorMapping # @see https://www.w3.org/TR/sparql11-query/#func-RDFterm-equal class NotEqual < Equal diff --git a/lib/sparql/algebra/operator/notexists.rb b/lib/sparql/algebra/operator/notexists.rb index d6a3fa73..af4c4fe0 100644 --- a/lib/sparql/algebra/operator/notexists.rb +++ b/lib/sparql/algebra/operator/notexists.rb @@ -5,12 +5,23 @@ class Operator # # There is a filter operator EXISTS that takes a graph pattern. EXISTS returns `true`/`false` depending on whether the pattern matches the dataset given the bindings in the current group graph pattern, the dataset and the active graph at this point in the query evaluation. No additional binding of variables occurs. The `NOT EXISTS` form translates into `fn:not(EXISTS{...})`. # - # @example - # (prefix ((ex: )) - # (filter (exists - # (filter (notexists (bgp (triple ?s ?p ex:o2))) - # (bgp (triple ?s ?p ex:o1)))) - # (bgp (triple ?s ?p ex:o)))) + # [126] NotExistsFunc ::= 'NOT' 'EXISTS' GroupGraphPattern + # + # @example SPARQL Grammar + # PREFIX ex: + # SELECT ?animal { + # ?animal a ex:Animal + # FILTER NOT EXISTS { ?animal a ex:Insect } + # } + # + # @example SSE + # (prefix + # ((ex: )) + # (project (?animal) + # (filter + # (notexists + # (bgp (triple ?animal a ex:Insect))) + # (bgp (triple ?animal a ex:Animal)))) ) # # @see https://www.w3.org/TR/sparql11-query/#func-abs # @see https://www.w3.org/TR/xpath-functions/#func-abs @@ -34,6 +45,19 @@ def evaluate(bindings, **options) queryable = options[:queryable] operand(0).execute(queryable, solutions: solutions, **options).empty? 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) + "NOT EXISTS {\n" + + operands.last.to_sparql(top_level: false, **options) + + "\n}" + end end # NotExists end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/notin.rb b/lib/sparql/algebra/operator/notin.rb index f841e02d..5338a68f 100644 --- a/lib/sparql/algebra/operator/notin.rb +++ b/lib/sparql/algebra/operator/notin.rb @@ -5,8 +5,13 @@ class Operator # # Used for filters with more than one expression. # - # @example - # (ask (filter (notin ?o 1 2) (bgp))) + # [114] RelationalExpression ::= NumericExpression ('NOT' 'IN' ExpressionList)? + # + # @example SPARQL Grammar + # ASK { FILTER(2 NOT IN ()) } + # + # @example SSE + # (ask (filter (notin 2) (bgp))) # # @see https://www.w3.org/TR/sparql11-query/#func-notin class NotIn < Operator @@ -58,6 +63,18 @@ def evaluate(bindings, **options) else RDF::Literal::TRUE end end - end # Exprlist + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(" + operands.first.to_sparql(**options) + + " NOT IN (" + + operands[1..-1].map {|e| e.to_sparql(**options)}.join(", ") + + "))" + end + end # NotIn end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/notoneof.rb b/lib/sparql/algebra/operator/notoneof.rb index 6029fcbf..bdddc1b5 100644 --- a/lib/sparql/algebra/operator/notoneof.rb +++ b/lib/sparql/algebra/operator/notoneof.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL Property Path `notoneof` (NegatedPropertySet) operator. # - # @example - # (notoneof ex:p1 ex:p2) + # [96] PathOneInPropertySet ::= iri | 'a' | '^' ( iri | 'a' ) + # + # @example SPARQL Grammar + # PREFIX ex: + # PREFIX in: + # ASK { in:b ^ex:p in:a } + # + # @example SSE + # (prefix ((ex: ) + # (in: )) + # (ask + # (path in:b (reverse ex:p) in:a))) # # @see https://www.w3.org/TR/sparql11-query/#eval_negatedPropertySet class NotOneOf < Operator diff --git a/lib/sparql/algebra/operator/now.rb b/lib/sparql/algebra/operator/now.rb index 51594ee5..57ea698b 100644 --- a/lib/sparql/algebra/operator/now.rb +++ b/lib/sparql/algebra/operator/now.rb @@ -7,11 +7,21 @@ class Operator # # Returns an XSD dateTime value for the current query execution. All calls to this function in any one query execution must return the same value. The exact moment returned is not specified. # - # @example - # (prefix ((xsd: )) - # (ask (filter (= (datatype ?n) xsd:dateTime) - # (extend ((?n (now))) - # (bgp))))) + # [121] BuiltInCall ::= ... | 'NOW' NIL + # + # @example SPARQL Grammar + # PREFIX xsd: + # ASK { + # BIND(NOW() AS ?n) + # FILTER(DATATYPE(?n) = xsd:dateTime) + # } + # + # @example SSE + # (prefix ((xsd: )) + # (ask + # (filter (= (datatype ?n) xsd:dateTime) + # (extend ((?n (now))) + # (bgp))))) # # @see https://www.w3.org/TR/sparql11-query/#func-now class Now < Operator::Nullary @@ -26,6 +36,15 @@ class Now < Operator::Nullary def apply(**options) RDF::Literal(DateTime.now) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "NOW()" + end end # Now end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/object.rb b/lib/sparql/algebra/operator/object.rb index 1d08e8fb..644476da 100644 --- a/lib/sparql/algebra/operator/object.rb +++ b/lib/sparql/algebra/operator/object.rb @@ -5,6 +5,29 @@ class Operator # # If triple is an RDF-star triple, the function returns the object of this triple. Passing anything other than an RDF-star triple is an error. # + # [121] BuiltInCall ::= ... | 'OBJECT' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # ?t :source :g + # FILTER(isTriple(?t)) + # FILTER(SUBJECT(?t) = :s) + # FILTER(PREDICATE(?t) = :p) + # FILTER(OBJECT(?t) = :o) + # } + # + # @example SSE + # (prefix + # ((: )) + # (filter + # (exprlist + # (isTRIPLE ?t) + # (= (subject ?t) :s) + # (= (predicate ?t) :p) + # (= (object ?t) :o)) + # (bgp (triple ?t :source :g))) ) + # # @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#object class Object < Operator::Unary include Evaluatable @@ -22,6 +45,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Statement, but got #{operand.inspect}" unless operand.is_a?(RDF::Statement) operand.object end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "OBJECT(" + operands.last.to_sparql(**options) + ")" + end end # Object end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/or.rb b/lib/sparql/algebra/operator/or.rb index 8986ede7..9e1ed9e7 100644 --- a/lib/sparql/algebra/operator/or.rb +++ b/lib/sparql/algebra/operator/or.rb @@ -3,9 +3,23 @@ class Operator ## # The SPARQL logical `or` operator. # - # @example - # (|| ?x ?y) - # (or ?x ?y) + # [111] ConditionalOrExpression ::= ConditionalAndExpression ( '||' ConditionalAndExpression )* + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?a + # WHERE { + # ?a :p ?v . + # FILTER ("false"^^xsd:boolean || ?v) . + # } + # + # @example SSE + # (prefix ((xsd: ) + # (: )) + # (project (?a) + # (filter (|| false ?v) + # (bgp (triple ?a :p ?v))))) # # @see https://www.w3.org/TR/sparql11-query/#func-logical-or # @see https://www.w3.org/TR/sparql11-query/#evaluation @@ -63,6 +77,15 @@ def evaluate(bindings, **options) else RDF::Literal(left || right) end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(#{operands.first.to_sparql(**options)} || #{operands.last.to_sparql(**options)})" + end end # Or end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/order.rb b/lib/sparql/algebra/operator/order.rb index 0426ff64..4df4861d 100644 --- a/lib/sparql/algebra/operator/order.rb +++ b/lib/sparql/algebra/operator/order.rb @@ -3,7 +3,15 @@ class Operator ## # The SPARQL GraphPattern `order` operator. # - # @example + # [23] OrderClause ::= 'ORDER' 'BY' OrderCondition+ + # + # @example SPARQL Grammar + # PREFIX foaf: + # SELECT ?name + # WHERE { ?x foaf:name ?name } + # ORDER BY ASC(?name) + # + # @example SSE # (prefix ((foaf: )) # (project (?name) # (order ((asc ?name)) @@ -56,6 +64,17 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # Provides order to descendant query. + # + # @return [String] + def to_sparql(**options) + operands.last.to_sparql(order_ops: operands.first, **options) + end end # Order end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/path.rb b/lib/sparql/algebra/operator/path.rb index ddb67627..4a762dbc 100644 --- a/lib/sparql/algebra/operator/path.rb +++ b/lib/sparql/algebra/operator/path.rb @@ -3,8 +3,23 @@ class Operator ## # The SPARQL Property Path `path` operator. # - # @example - # (path :a (path+ :p) ?z) + # [88] Path ::= PathAlternative + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?t + # WHERE { + # :a :p1|:p2/:p3|:p4 ?t + # } + # + # @example SSE + # (prefix ((: )) + # (project (?t) + # (path :a + # (alt + # (alt :p1 (seq :p2 :p3)) + # :p4) + # ?t))) # # @see https://www.w3.org/TR/sparql11-query/#sparqlTranslatePathExpressions class Path < Operator::Ternary @@ -45,6 +60,18 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions 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) + str = operands.to_sparql(top_level: false, **options) + " ." + top_level ? Operator.to_sparql(str, **options) : str + end end # Path end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/path_opt.rb b/lib/sparql/algebra/operator/path_opt.rb index b8113357..8615be71 100644 --- a/lib/sparql/algebra/operator/path_opt.rb +++ b/lib/sparql/algebra/operator/path_opt.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL Property Path `path?` (ZeroOrOnePath) operator. # - # @example - # (path? :p) + # [91] PathElt ::= PathPrimary PathMod? + # [93] PathMod ::= '*' | '?' | '+' + + # @example SPARQL Grammar + # PREFIX : + # SELECT * WHERE { + # :a (:p/:p)? ?t + # } + # + # @example SSE + # (prefix ((: )) + # (path :a (path? (seq :p :p)) ?t)) # # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrOnePath class PathOpt < Operator::Unary @@ -107,6 +117,15 @@ def execute(queryable, **options, &block) solutions.each(&block) if block_given? solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(#{operands.first.to_sparql(**options)})?" + end end # PathOpt end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/path_plus.rb b/lib/sparql/algebra/operator/path_plus.rb index a84ac124..6e84cafc 100644 --- a/lib/sparql/algebra/operator/path_plus.rb +++ b/lib/sparql/algebra/operator/path_plus.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL Property Path `path+` (OneOrMorePath) operator. # - # @example - # (path+ :p) + # [91] PathElt ::= PathPrimary PathMod? + # [93] PathMod ::= '*' | '?' | '+' + + # @example SPARQL Grammar + # PREFIX : + # SELECT * WHERE { + # :a :p+ ?z + # } + # + # @example SSE + # (prefix ((: )) + # (path :a (path+ :p) ?z)) # # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_OneOrMorePath class PathPlus < Operator::Unary @@ -94,6 +104,15 @@ def execute(queryable, **options, &block) solutions.each(&block) if block_given? # Only at top-level solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(#{operands.first.to_sparql(**options)})+" + end end # PathPlus end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/path_star.rb b/lib/sparql/algebra/operator/path_star.rb index f71e949e..f5093771 100644 --- a/lib/sparql/algebra/operator/path_star.rb +++ b/lib/sparql/algebra/operator/path_star.rb @@ -3,8 +3,18 @@ class Operator ## # The SPARQL Property Path `path*` (ZeroOrMorePath) operator. # - # @example - # (path* :p) + # [91] PathElt ::= PathPrimary PathMod? + # [93] PathMod ::= '*' | '?' | '+' + + # @example SPARQL Grammar + # PREFIX : + # SELECT * WHERE { + # :a :p* ?z + # } + # + # @example SSE + # (prefix ((: )) + # (path :a (path* :p) ?z)) # # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrMorePath class PathStar < Operator::Unary @@ -37,6 +47,14 @@ def execute(queryable, **options, &block) query = PathOpt.new(PathPlus.new(*operands)) query.execute(queryable, depth: options[:depth].to_i + 1, **options, &block) end + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "(#{operands.first.to_sparql(**options)})*" + end end # PathStar end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/plus.rb b/lib/sparql/algebra/operator/plus.rb index bea09a24..31c31ecc 100644 --- a/lib/sparql/algebra/operator/plus.rb +++ b/lib/sparql/algebra/operator/plus.rb @@ -3,9 +3,39 @@ class Operator ## # The SPARQL numeric binary/unary `+` operator. # - # @example - # (+ ?x ?y) - # (plus ?x ?y) + # [118] UnaryExpression ::= ... | '+' PrimaryExpression + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s WHERE { + # ?s :p ?o . + # FILTER(-?o = +3) . + # } + # + # @example SSE + # (prefix ((: )) + # (project (?s) + # (filter (= (- ?o) +3) + # (bgp (triple ?s :p ?o))))) + # + # [116] AdditiveExpression ::= MultiplicativeExpression ( '+' MultiplicativeExpression )? + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s WHERE { + # ?s :p ?o . + # ?s2 :p ?o2 . + # FILTER(?o + ?o2 = 3) . + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s) + # (filter (= (+ ?o ?o2) 3) + # (bgp + # (triple ?s :p ?o) + # (triple ?s2 :p ?o2))))) # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-unary-plus # @see https://www.w3.org/TR/xpath-functions/#func-numeric-add @@ -32,6 +62,15 @@ def apply(left, right = nil, **options) else raise TypeError, "expected two RDF::Literal::Numeric operands, but got #{left.inspect} and #{right.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "#{operands.first.to_sparql(**options)} + #{operands.last.to_sparql(**options)}" + end end # Plus end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/predicate.rb b/lib/sparql/algebra/operator/predicate.rb index d2f746ab..310485a5 100644 --- a/lib/sparql/algebra/operator/predicate.rb +++ b/lib/sparql/algebra/operator/predicate.rb @@ -5,6 +5,29 @@ class Operator # # If triple is an RDF-star triple, the function returns the predicate of this triple. Passing anything other than an RDF-star triple is an error. # + # [121] BuiltInCall ::= ... | 'PREDICATE' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # ?t :source :g + # FILTER(isTriple(?t)) + # FILTER(SUBJECT(?t) = :s) + # FILTER(PREDICATE(?t) = :p) + # FILTER(OBJECT(?t) = :o) + # } + # + # @example SSE + # (prefix + # ((: )) + # (filter + # (exprlist + # (isTRIPLE ?t) + # (= (subject ?t) :s) + # (= (predicate ?t) :p) + # (= (object ?t) :o)) + # (bgp (triple ?t :source :g))) ) + # # @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#predicate class Predicate < Operator::Unary include Evaluatable @@ -22,6 +45,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Statement, but got #{operand.inspect}" unless operand.is_a?(RDF::Statement) operand.predicate end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "PREDICATE(" + operands.last.to_sparql(**options) + ")" + end end # Predicate end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/prefix.rb b/lib/sparql/algebra/operator/prefix.rb index 5573a454..f3d68760 100644 --- a/lib/sparql/algebra/operator/prefix.rb +++ b/lib/sparql/algebra/operator/prefix.rb @@ -3,10 +3,15 @@ class Operator ## # The SPARQL GraphPattern `prefix` operator. # - # @example + # [6] PrefixDecl ::= 'PREFIX' PNAME_NS IRIREF + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { :s :p :o } + # + # @example SSE # (prefix ((: )) - # (graph ?g - # (bgp (triple ?s ?p ?o)))) + # (bgp (triple :s :p :o))) # # @see https://www.w3.org/TR/sparql11-query/#QSynIRI class Prefix < Binary @@ -65,6 +70,22 @@ def query_yields_boolean? def query_yields_statements? operands.last.query_yields_statements? end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + prefixes = {} + str = operands.first.map do |(pfx, sfx)| + pfx = pfx.to_s.chomp(':').to_sym + prefixes[pfx] = sfx + "PREFIX #{pfx}: #{sfx.to_sparql}\n" + end.join("") + + str << operands.last.to_sparql(prefixes: prefixes, **options) + end end # Prefix end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/project.rb b/lib/sparql/algebra/operator/project.rb index 5d1f5235..60913997 100644 --- a/lib/sparql/algebra/operator/project.rb +++ b/lib/sparql/algebra/operator/project.rb @@ -3,11 +3,36 @@ class Operator ## # The SPARQL GraphPattern `project` operator. # - # @example - # (select (?v) - # (project (?v) - # (filter (= ?v 2) - # (bgp (triple ?s ?v))))) + # [9] SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ | '*' ) + # + # ## Basic Projection + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?v { + # ?s :p ?v . + # FILTER (?v = 2) + # } + # + # @example SSE + # (prefix ((: )) + # (project (?v) + # (filter (= ?v 2) + # (bgp (triple ?s :p ?v))))) + # + # ## Sub select + # + # @example SPARQL Grammar + # SELECT (1 AS ?X ) { + # SELECT (2 AS ?Y ) {} + # } + # + # @example SSE + # (project (?X) + # (extend ((?X 1)) + # (project (?Y) + # (extend ((?Y 2)) + # (bgp))))) # # @see https://www.w3.org/TR/sparql11-query/#modProjection class Project < Operator::Binary @@ -36,6 +61,27 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # Extracts projections + # + # If there are already extensions or filters, then this is a sub-select. + # + # @return [String] + def to_sparql(**options) + vars = operands[0].empty? ? [:*] : operands[0] + if options[:extensions] || options[:filter_ops] || options[:project] + # 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) + Operator.to_sparql(content, **options) + else + operands.last.to_sparql(project: vars, **options) + end + end end # Project end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/rand.rb b/lib/sparql/algebra/operator/rand.rb index 2e503db4..e81b53e6 100644 --- a/lib/sparql/algebra/operator/rand.rb +++ b/lib/sparql/algebra/operator/rand.rb @@ -5,8 +5,25 @@ class Operator # # Returns a pseudo-random number between 0 (inclusive) and 1.0e0 (exclusive). Different numbers can be produced every time this function is invoked. Numbers should be produced with approximately equal probability. # - # @example - # (rand) + # [121] BuiltInCall ::= ... | 'RAND' NIL + # + # @example SPARQL Grammar + # PREFIX xsd: + # ASK { + # BIND(RAND() AS ?r) + # FILTER(DATATYPE(?r) = xsd:double && ?r >= 0.0 && ?r < 1.0) + # } + # + # @example SSE + # (prefix + # ((xsd: )) + # (ask + # (filter + # (&& + # (&& (= (datatype ?r) xsd:double) (>= ?r 0.0)) + # (< ?r 1.0)) + # (extend ((?r (rand))) + # (bgp))))) # # @see https://www.w3.org/TR/sparql11-query/#idp2130040 class Rand < Operator::Nullary @@ -21,6 +38,17 @@ class Rand < Operator::Nullary def apply(**options) RDF::Literal::Double.new(Random.rand) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # Extracts projections + # + # @return [String] + def to_sparql(**options) + "RAND()" + end end # Rand end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/reduced.rb b/lib/sparql/algebra/operator/reduced.rb index 46069441..ae9daa6c 100644 --- a/lib/sparql/algebra/operator/reduced.rb +++ b/lib/sparql/algebra/operator/reduced.rb @@ -3,7 +3,15 @@ class Operator ## # The SPARQL GraphPattern `reduced` operator. # - # @example + # [9] SelectClause ::= 'SELECT' ( 'DISTINCT' | 'REDUCED' )? ( ( Var | ( '(' Expression 'AS' Var ')' ) )+ | '*' ) + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT DISTINCT ?v + # WHERE { ?x ?p ?v } + # + # @example SSE # (prefix ((xsd: ) # (: )) # (reduced @@ -37,6 +45,15 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + operands.first.to_sparql(reduced: true, **options) + end end # Reduced end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/regex.rb b/lib/sparql/algebra/operator/regex.rb index ecc4a836..4be5b901 100644 --- a/lib/sparql/algebra/operator/regex.rb +++ b/lib/sparql/algebra/operator/regex.rb @@ -3,33 +3,31 @@ class Operator ## # The SPARQL `regex` operator. # - # @example + # [122] RegexExpression ::= 'REGEX' '(' Expression ',' Expression ( ',' Expression )? ')' + # + # @example SPARQL Grammar + # PREFIX rdf: + # PREFIX ex: + # SELECT ?val + # WHERE { + # ex:foo rdf:value ?val . + # FILTER regex(?val, "GHI") + # } + # + # @example SSE # (prefix ((ex: ) # (rdf: )) - # (project (?val) - # (filter (regex ?val "GHI") - # (bgp (triple ex:foo rdf:value ?val))))) + # (project (?val) + # (filter (regex ?val "GHI") + # (bgp (triple ex:foo rdf:value ?val))))) # # @see https://www.w3.org/TR/sparql11-query/#funcex-regex # @see https://www.w3.org/TR/xpath-functions/#func-matches - class Regex < Operator::Ternary + class Regex < Operator include Evaluatable NAME = :regex - ## - # Initializes a new operator instance. - # - # @param [RDF::Term] text - # @param [RDF::Term] pattern - # @param [RDF::Term] flags - # @param [Hash{Symbol => Object}] options - # any additional options (see {Operator#initialize}) - # @raise [TypeError] if any operand is invalid - def initialize(text, pattern, flags = RDF::Literal(''), **options) - super - end - ## # Matches `text` against a regular expression `pattern`. # @@ -65,6 +63,16 @@ def apply(text, pattern, flags = RDF::Literal(''), **options) options |= Regexp::EXTENDED if flags.include?(?x) RDF::Literal(Regexp.new(pattern, options) === text) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + ops = operands.last.to_s.empty? ? operands[0..-2] : operands + "regex(" + ops.to_sparql(delimiter: ', ', **options) + ")" + end end # Regex end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/replace.rb b/lib/sparql/algebra/operator/replace.rb index 281c965d..8ada1c17 100644 --- a/lib/sparql/algebra/operator/replace.rb +++ b/lib/sparql/algebra/operator/replace.rb @@ -3,12 +3,22 @@ class Operator ## # The SPARQL `replace` operator. # - # @example - # (prefix ((: ) - # (xsd: )) - # (project (?s ?new) - # (extend ((?new (replace ?str "[^a-z0-9]" "-"))) - # (bgp (triple ?s :str ?str))))) + # [124] StrReplaceExpression ::= 'REPLACE' '(' Expression ',' Expression ',' Expression ( ',' Expression )? ')' + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s (REPLACE(?str,"[^a-z0-9]", "-") AS ?new) + # WHERE { + # ?s :str ?str + # } + # + # @example SSE + # (prefix ((: ) + # (xsd: )) + # (project (?s ?new) + # (extend ((?new (replace ?str "[^a-z0-9]" "-"))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#funcex-replace # @see https://www.w3.org/TR/xpath-functions/#func-replace @@ -76,6 +86,16 @@ def apply(text, pattern, replacement, flags = RDF::Literal(''), **options) def to_sxp_bin [NAME] + operands.reject {|o| o.to_s == ""} end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + ops = operands.last.to_s.empty? ? operands[0..-2] : operands + "REPLACE(" + ops.to_sparql(delimiter: ', ', **options) + ")" + end end # Replace end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/reverse.rb b/lib/sparql/algebra/operator/reverse.rb index 6d89c148..430ae987 100644 --- a/lib/sparql/algebra/operator/reverse.rb +++ b/lib/sparql/algebra/operator/reverse.rb @@ -3,8 +3,17 @@ class Operator ## # The SPARQL Property Path `reverse` (NegatedPropertySet) operator. # - # @example - # (reverse :p) + # [92] PathEltOrInverse ::= PathElt | '^' PathElt + # + # @example SPARQL Grammar + # PREFIX ex: + # PREFIX in: + # ASK { in:b ^ex:p in:a } + # + # @example SSE + # (prefix ((ex: ) + # (in: )) + # (ask (path in:b (reverse ex:p) in:a))) # # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_inverse class Reverse < Operator::Unary @@ -49,6 +58,15 @@ def execute(queryable, **options, &block) ), &block) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "^" + operands.first.to_sparql(**options) + end end # Reverse end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/round.rb b/lib/sparql/algebra/operator/round.rb index feafd738..145b0682 100644 --- a/lib/sparql/algebra/operator/round.rb +++ b/lib/sparql/algebra/operator/round.rb @@ -5,8 +5,22 @@ class Operator # # Returns the number with no fractional part that is closest to the argument. If there are two such numbers, then the one that is closest to positive infinity is returned. An error is raised if `arg` is not a numeric value. # - # @example - # (round ?x) + # [121] BuiltInCall ::= ... 'ROUND' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s ?num (ROUND(?num) AS ?round) WHERE { + # ?s :num ?num + # } + # + # @example SSE + # (prefix + # ((: ) + # (xsd: )) + # (project (?s ?num ?round) + # (extend ((?round (round ?num))) + # (bgp (triple ?s :num ?num))))) # # @see https://www.w3.org/TR/sparql11-query/#func-round # @see https://www.w3.org/TR/xpath-functions/#func-round @@ -29,5 +43,14 @@ def apply(operand, **options) end end end # Round + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "ROUND(#{operands.to_sparql(**options)})" + end end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/same_term.rb b/lib/sparql/algebra/operator/same_term.rb index 4cf5646a..78e7d789 100644 --- a/lib/sparql/algebra/operator/same_term.rb +++ b/lib/sparql/algebra/operator/same_term.rb @@ -3,12 +3,21 @@ class Operator ## # The SPARQL `sameTerm` operator. # - # @example - # (prefix ((xsd: ) - # (: )) - # (project (?x ?v) - # (filter (sameTerm ?v) - # (bgp (triple ?x :p ?v))))) + # [121] BuiltInCall ::= ... | 'sameTerm' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # ?x1 :p ?v1 . + # ?x2 :p ?v2 . + # FILTER sameTerm(?v1, ?v2) + # } + # + # @example SSE + # (prefix + # ((: )) + # (filter (sameTerm ?v1 ?v2) + # (bgp (triple ?x1 :p ?v1) (triple ?x2 :p ?v2)))) # # @see https://www.w3.org/TR/sparql11-query/#func-sameTerm class SameTerm < Operator::Binary @@ -44,6 +53,15 @@ def optimize(**options) super # @see Operator#optimize! end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "sameTerm(#{operands.to_sparql(delimiter: ', ', **options)})" + end end # SameTerm end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/sample.rb b/lib/sparql/algebra/operator/sample.rb index a1d6f77c..e4e8900f 100644 --- a/lib/sparql/algebra/operator/sample.rb +++ b/lib/sparql/algebra/operator/sample.rb @@ -3,13 +3,26 @@ class Operator ## # The SPARQL `sample` set function. # - # @example - # (prefix ((: )) - # (filter (|| (|| (= ?sample 1.0) (= ?sample 2.2)) (= ?sample 3.5)) - # (project (?sample) - # (extend ((?sample ??.0)) - # (group () ((??.0 (sample ?o))) - # (bgp (triple ?s :dec ?o))))))) + # [127] Aggregate::= ... | 'SAMPLE' '(' 'DISTINCT'? Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # + # SELECT ?w (SAMPLE(?v) AS ?S) + # { + # ?s :p ?v . + # OPTIONAL { ?s :q ?w } + # } + # GROUP BY ?w + # + # @example SSE + # (prefix ((: )) + # (project (?w ?S) + # (extend ((?S ??.0)) + # (group (?w) ((??.0 (sample ?v))) + # (leftjoin + # (bgp (triple ?s :p ?v)) + # (bgp (triple ?s :q ?w))))) )) # # @see https://www.w3.org/TR/sparql11-query/#defn_aggSample class Sample < Operator @@ -34,6 +47,15 @@ def initialize(*operands, **options) def apply(enum, **options) enum.detect(lambda {raise TypeError, "Sampling an empty multiset"}) {|e| e.first}.first end - end # LCase + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SAMPLE(#{operands.to_sparql(**options)})" + end + end # Sample end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/seconds.rb b/lib/sparql/algebra/operator/seconds.rb index bb6b7fd7..b066abc0 100644 --- a/lib/sparql/algebra/operator/seconds.rb +++ b/lib/sparql/algebra/operator/seconds.rb @@ -5,11 +5,20 @@ class Operator # # Returns the seconds part of the lexical form of `arg`. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (seconds ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'SECONDS' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (SECONDS(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (seconds ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-seconds class Seconds < Operator::Unary @@ -28,6 +37,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime) RDF::Literal(operand.object.second) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SECONDS(#{operands.last.to_sparql(**options)})" + end end # Seconds end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/seq.rb b/lib/sparql/algebra/operator/seq.rb index 0263e28d..e206e0b4 100644 --- a/lib/sparql/algebra/operator/seq.rb +++ b/lib/sparql/algebra/operator/seq.rb @@ -3,8 +3,17 @@ class Operator ## # The SPARQL Property Path `sequence` (SequencePath) operator. # - # @example - # (seq :a :b) + # # [90] PathSequence ::= PathEltOrInverse ( '/' PathEltOrInverse )* + # + # @example SPARQL Grammar + # PREFIX ex: + # PREFIX in: + # SELECT * WHERE { in:a ex:p1/ex:p2 ?x } + # + # @example SSE + # (prefix ((ex: ) + # (in: )) + # (path in:a (seq ex:p1 ex:p2) ?x)) # # @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_sequence class Seq < Operator::Binary @@ -67,6 +76,15 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + operands.to_sparql(delimiter: '/', **options) + end end # Seq end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/sequence.rb b/lib/sparql/algebra/operator/sequence.rb index 6dc4e995..7e9c7c91 100644 --- a/lib/sparql/algebra/operator/sequence.rb +++ b/lib/sparql/algebra/operator/sequence.rb @@ -6,16 +6,6 @@ class Operator # # Sequences through each operand # - # @example - # (sequence - # (bgp - # (triple ?s ?p ??0) - # (triple ??0 rdf:first ??1) - # (triple ??0 rdf:rest ??2) - # (triple ??2 rdf:first ??3) - # (triple ??2 rdf:rest rdf:nil)) - # (path ??1 (seq (path* :p) :q) 123) - # (path ??3 (reverse :r) "hello")) class Sequence < Operator include SPARQL::Algebra::Update diff --git a/lib/sparql/algebra/operator/sha1.rb b/lib/sparql/algebra/operator/sha1.rb index f29be4d7..ae9bcb3d 100644 --- a/lib/sparql/algebra/operator/sha1.rb +++ b/lib/sparql/algebra/operator/sha1.rb @@ -7,7 +7,15 @@ class Operator # # Returns the SHA1 checksum, as a hex digit string, calculated on the UTF-8 representation of the simple literal or lexical form of the `xsd:string`. Hex digits `SHOULD` be in lower case. # - # @example + # [121] BuiltInCall ::= ... | 'SHA1' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (SHA1(?l) AS ?hash) WHERE { + # :s1 :str ?l + # } + # + # @example SSE # (prefix ((: )) # (project (?hash) # (extend ((?hash (sha1 ?l))) @@ -31,6 +39,15 @@ def apply(operand, **options) raise TypeError, "expected simple literal or xsd:string, but got #{operand.inspect}" unless (operand.datatype || RDF::XSD.string) == RDF::XSD.string RDF::Literal(Digest::SHA1.new.hexdigest(operand.to_s)) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SHA1(" + operands.to_sparql(**options) + ")" + end end # SHA1 end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/sha256.rb b/lib/sparql/algebra/operator/sha256.rb index 273f9d0e..b8c08aa2 100644 --- a/lib/sparql/algebra/operator/sha256.rb +++ b/lib/sparql/algebra/operator/sha256.rb @@ -7,7 +7,15 @@ class Operator # # Returns the SHA256 checksum, as a hex digit string, calculated on the UTF-8 representation of the simple literal or lexical form of the `xsd:string`. Hex digits `SHOULD` be in lower case. # - # @example + # [121] BuiltInCall ::= ... | 'SHA256' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (SHA256(?l) AS ?hash) WHERE { + # :s1 :str ?l + # } + # + # @example SSE # (prefix ((: )) # (project (?hash) # (extend ((?hash (sha256 ?l))) @@ -31,6 +39,15 @@ def apply(operand, **options) raise TypeError, "expected simple literal or xsd:string, but got #{operand.inspect}" unless (operand.datatype || RDF::XSD.string) == RDF::XSD.string RDF::Literal(Digest::SHA256.new.hexdigest(operand.to_s)) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SHA256(" + operands.to_sparql(**options) + ")" + end end # SHA256 end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/sha384.rb b/lib/sparql/algebra/operator/sha384.rb index fe612802..7cae245d 100644 --- a/lib/sparql/algebra/operator/sha384.rb +++ b/lib/sparql/algebra/operator/sha384.rb @@ -7,7 +7,15 @@ class Operator # # Returns the SHA384 checksum, as a hex digit string, calculated on the UTF-8 representation of the simple literal or lexical form of the `xsd:string`. Hex digits `SHOULD` be in lower case. # - # @example + # [121] BuiltInCall ::= ... | 'SHA384' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (SHA384(?l) AS ?hash) WHERE { + # :s1 :str ?l + # } + # + # @example SSE # (prefix ((: )) # (project (?hash) # (extend ((?hash (sha384 ?l))) @@ -31,6 +39,15 @@ def apply(operand, **options) raise TypeError, "expected simple literal or xsd:string, but got #{operand.inspect}" unless (operand.datatype || RDF::XSD.string) == RDF::XSD.string RDF::Literal(Digest::SHA384.new.hexdigest(operand.to_s)) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SHA384(" + operands.to_sparql(**options) + ")" + end end # SHA384 end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/sha512.rb b/lib/sparql/algebra/operator/sha512.rb index 9f54d706..49e77345 100644 --- a/lib/sparql/algebra/operator/sha512.rb +++ b/lib/sparql/algebra/operator/sha512.rb @@ -7,7 +7,15 @@ class Operator # # Returns the SHA512 checksum, as a hex digit string, calculated on the UTF-8 representation of the simple literal or lexical form of the `xsd:string`. Hex digits `SHOULD` be in lower case. # - # @example + # [121] BuiltInCall ::= ... | 'SHA512' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (SHA512(?l) AS ?hash) WHERE { + # :s1 :str ?l + # } + # + # @example SSE # (prefix ((: )) # (project (?hash) # (extend ((?hash (sha512 ?l))) @@ -31,6 +39,15 @@ def apply(operand, **options) raise TypeError, "expected simple literal or xsd:string, but got #{operand.inspect}" unless (operand.datatype || RDF::XSD.string) == RDF::XSD.string RDF::Literal(Digest::SHA512.new.hexdigest(operand.to_s)) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SHA512(" + operands.to_sparql(**options) + ")" + end end # SHA512 end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/slice.rb b/lib/sparql/algebra/operator/slice.rb index 4af79123..d97056b0 100644 --- a/lib/sparql/algebra/operator/slice.rb +++ b/lib/sparql/algebra/operator/slice.rb @@ -3,12 +3,23 @@ class Operator ## # The SPARQL GraphPattern `slice` operator. # - # @example + # [25] LimitOffsetClauses ::= LimitClause OffsetClause? | OffsetClause LimitClause? + # + # @example SPARQL Grammar + # PREFIX : + # + # SELECT ?v + # WHERE { :a ?p ?v } + # ORDER BY ?v + # OFFSET 100 + # LIMIT 1 + # + # @example SSE # (prefix ((: )) - # (slice 1 1 - # (project (?v) - # (order (?v) - # (bgp (triple ??0 :num ?v)))))) + # (slice 100 1 + # (project (?v) + # (order (?v) + # (bgp (triple :a ?p ?v)))))) # # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra class Slice < Operator::Ternary @@ -52,6 +63,17 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions end + + ## + # + # Extracts OFFSET and LIMIT. + # + # @return [String] + def to_sparql(**options) + offset = operands[0].to_i unless operands[0] == :_ + limit = operands[1].to_i unless operands[1] == :_ + operands.last.to_sparql(offset: offset, limit: limit, **options) + end end # Slice end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/str.rb b/lib/sparql/algebra/operator/str.rb index 58623745..8f90dbe3 100644 --- a/lib/sparql/algebra/operator/str.rb +++ b/lib/sparql/algebra/operator/str.rb @@ -3,7 +3,18 @@ class Operator ## # The SPARQL `str` operator. # - # @example + # [121] BuiltInCall ::= ... | 'STR' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX xsd: + # PREFIX : + # SELECT ?x ?v + # WHERE + # { ?x :p ?v . + # FILTER ( str(?v) = "1" ) . + # } + # + # @example SSE # (prefix ((xsd: ) # (: )) # (project (?x ?v) @@ -30,6 +41,15 @@ def apply(term, **options) else raise TypeError, "expected an RDF::Literal or RDF::URI, but got #{term.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "str(" + operands.first.to_sparql(**options) + ")" + end end # Str end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/strafter.rb b/lib/sparql/algebra/operator/strafter.rb index 02d42fd9..04338f74 100644 --- a/lib/sparql/algebra/operator/strafter.rb +++ b/lib/sparql/algebra/operator/strafter.rb @@ -3,8 +3,22 @@ class Operator ## # A SPARQL `strafter` operator. # - # @example - # (strafter ?x ?y) + # [121] BuiltInCall ::= ... | 'STRAFTER' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s (STRAFTER(?str,"e") AS ?suffix) WHERE { + # ?s :str ?str + # } + # + # @example SSE + # (prefix + # ((: ) + # (xsd: )) + # (project (?s ?suffix) + # (extend ((?suffix (strafter ?str "e"))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-strafter # @see https://www.w3.org/TR/xpath-functions/#func-substring-after @@ -52,6 +66,15 @@ def apply(left, right, **options) RDF::Literal(parts.last, datatype: left.datatype, language: left.language) end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRAFTER(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrAfter end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/strbefore.rb b/lib/sparql/algebra/operator/strbefore.rb index 33343d2c..641f354a 100644 --- a/lib/sparql/algebra/operator/strbefore.rb +++ b/lib/sparql/algebra/operator/strbefore.rb @@ -3,8 +3,22 @@ class Operator ## # A SPARQL `strbefore` operator. # - # @example - # (strbefore ?x ?y) + # [121] BuiltInCall ::= ... | 'STRBEFORE' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s (STRBEFORE(?str,"s") AS ?prefix) WHERE { + # ?s :str ?str + # } + # + # @example SSE + # (prefix + # ((: ) + # (xsd: )) + # (project (?s ?prefix) + # (extend ((?prefix (strbefore ?str "s"))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-strbefore # @see https://www.w3.org/TR/xpath-functions/#func-substring-before @@ -54,6 +68,15 @@ def apply(left, right, **options) RDF::Literal(parts.first, language: left.language, datatype: left.datatype) end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRBEFORE(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrBefore end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/strdt.rb b/lib/sparql/algebra/operator/strdt.rb index b54d334d..3582bca1 100644 --- a/lib/sparql/algebra/operator/strdt.rb +++ b/lib/sparql/algebra/operator/strdt.rb @@ -3,7 +3,19 @@ class Operator ## # The SPARQL `strdt` operator. # - # @example + # [121] BuiltInCall ::= ... | 'STRDT' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT ?s (STRDT(?str,xsd:string) AS ?str1) WHERE { + # ?s :str ?str + # FILTER(LANGMATCHES(LANG(?str), "en")) + # } + # + # @example SSE + # (prefix + # ((: ) (xsd: )) # (project (?s ?str1) # (extend ((?str1 (strdt ?str xsd:string))) # (filter (langMatches (lang ?str) "en") @@ -28,6 +40,15 @@ def apply(value, datatypeIRI, **options) raise TypeError, "Literal #{value.inspect} is not simple" unless value.simple? RDF::Literal.new(value.to_s, datatype: datatypeIRI) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRDT(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrDT end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/strends.rb b/lib/sparql/algebra/operator/strends.rb index 3b55f068..53dc2677 100644 --- a/lib/sparql/algebra/operator/strends.rb +++ b/lib/sparql/algebra/operator/strends.rb @@ -1,10 +1,23 @@ module SPARQL; module Algebra class Operator ## - # A SPARQL `contains` operator. + # A SPARQL `strends` operator. # - # @example - # (strends ?x ?y) + # [121] BuiltInCall ::= ... | 'STRENDS' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s ?str WHERE { + # ?s :str ?str + # FILTER STRENDS(?str, "a") + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?str) + # (filter (strends ?str "a") + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-strends # @see https://wwww.w3.org/TR/xpath-functions/#func-ends-with @@ -41,6 +54,15 @@ def apply(left, right, **options) else RDF::Literal::FALSE end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRENDS(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrEnds end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/strlang.rb b/lib/sparql/algebra/operator/strlang.rb index 8a2ea9b4..6162434e 100644 --- a/lib/sparql/algebra/operator/strlang.rb +++ b/lib/sparql/algebra/operator/strlang.rb @@ -3,12 +3,22 @@ class Operator ## # The SPARQL `strlang` operator. # - # @example - # (prefix ((: )) - # (project (?s ?s2) - # (extend ((?s2 (strlang ?str "en-US"))) - # (filter (langMatches (lang ?str) "en") - # (bgp (triple ?s :str ?str)))))) + # [121] BuiltInCall ::= ... | 'STRLANG' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (STRLANG(?str,"en-US") AS ?s2) WHERE { + # ?s :str ?str + # FILTER(LANGMATCHES(LANG(?str), "en")) + # } + # + # @example SSE + # (prefix + # ((: ) (xsd: )) + # (project (?s ?s2) + # (extend ((?s2 (strlang ?str "en-US"))) + # (filter (langMatches (lang ?str) "en") + # (bgp (triple ?s :str ?str)))))) # # @see https://www.w3.org/TR/sparql11-query/#func-strlang class StrLang < Operator::Binary @@ -29,6 +39,15 @@ def apply(value, langTag, **options) raise TypeError, "Literal #{value.inspect} is not simple" unless value.simple? RDF::Literal.new(value.to_s, language: langTag.to_s) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRLANG(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrLang end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/strlen.rb b/lib/sparql/algebra/operator/strlen.rb index b07952e7..f3497872 100644 --- a/lib/sparql/algebra/operator/strlen.rb +++ b/lib/sparql/algebra/operator/strlen.rb @@ -3,8 +3,20 @@ class Operator ## # The SPARQL `strlen` operator. # - # @example - # (strlen ?x) + # [121] BuiltInCall ::= ... 'STRLEN' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?str (STRLEN(?str) AS ?len) WHERE { + # ?s :str ?str + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?str ?len) + # (extend ((?len (strlen ?str))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-strlen # @see https://www.w3.org/TR/xpath-functions/#func-string-length @@ -29,6 +41,15 @@ def apply(operand, **options) raise TypeError, "expected a plain RDF::Literal, but got #{operand.inspect}" unless operand.literal? && operand.plain? RDF::Literal(operand.to_s.length) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRLEN(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrLen end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/strstarts.rb b/lib/sparql/algebra/operator/strstarts.rb index 89fde231..9cc7fe40 100644 --- a/lib/sparql/algebra/operator/strstarts.rb +++ b/lib/sparql/algebra/operator/strstarts.rb @@ -3,8 +3,22 @@ class Operator ## # A SPARQL `strstarts` operator. # - # @example - # (strstarts ?x ?y) + # [121] BuiltInCall ::= ... | 'STRSTARTS' '(' Expression ',' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + + # SELECT ?s ?str WHERE { + # ?s :str ?str + # FILTER STRSTARTS(?str, "a") + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?str) + # (filter (strstarts ?str "a") + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-strstarts # @see https://www.w3.org/TR/xpath-functions/#func-starts-with @@ -41,6 +55,15 @@ def apply(left, right, **options) else RDF::Literal::FALSE end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRSTARTS(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrStarts end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/struuid.rb b/lib/sparql/algebra/operator/struuid.rb index 173c1866..91e4db39 100644 --- a/lib/sparql/algebra/operator/struuid.rb +++ b/lib/sparql/algebra/operator/struuid.rb @@ -3,16 +3,27 @@ module SPARQL; module Algebra class Operator ## - # The SPARQL `uuid` function. + # [121] BuiltInCall ::= ... | 'STRUUID' NIL # - # @example - # (prefix ((: ) - # (xsd: )) - # (project (?length) - # (extend ((?length (strlen ?uuid))) - # (filter (&& (isLiteral ?uuid) (regex ?uuid "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$" "i")) - # (extend ((?uuid (struuid))) - # (bgp)))))) + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # SELECT (STRLEN(?uuid) AS ?length) + # WHERE { + # BIND(STRUUID() AS ?uuid) + # FILTER(ISLITERAL(?uuid) && REGEX(?uuid, "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$", "i")) + # } + # + # @example SSE + # (prefix ((: ) (xsd: )) + # (project (?length) + # (extend ((?length (strlen ?uuid))) + # (filter + # (&& + # (isLiteral ?uuid) + # (regex ?uuid "^[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$" "i")) + # (extend ((?uuid (struuid))) + # (bgp)))))) # # @see https://www.w3.org/TR/sparql11-query/#func-struuid class StrUUID < Operator::Nullary @@ -27,6 +38,15 @@ class StrUUID < Operator::Nullary def apply(**options) RDF::Literal(SecureRandom.uuid) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "STRUUID(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # StrUUID end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/subject.rb b/lib/sparql/algebra/operator/subject.rb index 91b89abe..e8bf1f25 100644 --- a/lib/sparql/algebra/operator/subject.rb +++ b/lib/sparql/algebra/operator/subject.rb @@ -7,6 +7,29 @@ class Operator # # If triple is an RDF-star triple, the function returns the subject of this triple. Passing anything other than an RDF-star triple is an error. # + # [121] BuiltInCall ::= ... | 'SUBJECT' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT * { + # ?t :source :g + # FILTER(isTriple(?t)) + # FILTER(SUBJECT(?t) = :s) + # FILTER(PREDICATE(?t) = :p) + # FILTER(OBJECT(?t) = :o) + # } + # + # @example SSE + # (prefix + # ((: )) + # (filter + # (exprlist + # (isTRIPLE ?t) + # (= (subject ?t) :s) + # (= (predicate ?t) :p) + # (= (object ?t) :o)) + # (bgp (triple ?t :source :g))) ) + # # @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#subject class Subject < Operator::Unary include Evaluatable @@ -24,6 +47,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Statement, but got #{operand.inspect}" unless operand.is_a?(RDF::Statement) operand.subject end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SUBJECT(" + operands.last.to_sparql(**options) + ")" + end end # Subject end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/substr.rb b/lib/sparql/algebra/operator/substr.rb index c77ca1c8..294311a8 100644 --- a/lib/sparql/algebra/operator/substr.rb +++ b/lib/sparql/algebra/operator/substr.rb @@ -3,8 +3,20 @@ class Operator ## # A SPARQL `substr` operator. # - # @example - # (substr ?x ?y) + # [123] SubstringExpression ::= 'SUBSTR' '(' Expression ',' Expression ( ',' Expression )? ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s ?str (SUBSTR(?str,1,1) AS ?substr) + # WHERE { + # ?s :str ?str + # } + # + # @example SSE + # (prefix ((: )) + # (project (?s ?str ?substr) + # (extend ((?substr (substr ?str 1 1))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-substr # @see https://www.w3.org/TR/xpath-functions/#func-substring @@ -75,6 +87,15 @@ def apply(source, startingLoc, length, **options) def to_sxp_bin [NAME] + operands.reject {|o| o.to_s == ""} end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SUBSTR(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # SubStr end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/subtract.rb b/lib/sparql/algebra/operator/subtract.rb index a1e6995e..e4309e7c 100644 --- a/lib/sparql/algebra/operator/subtract.rb +++ b/lib/sparql/algebra/operator/subtract.rb @@ -2,8 +2,25 @@ module SPARQL; module Algebra class Operator ## # The SPARQL numeric `subtract` operator. - # (- ?x ?y) - # (subtract ?x ?y) + # + # [116] AdditiveExpression ::= MultiplicativeExpression ( '-' MultiplicativeExpression )? + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s WHERE { + # ?s :p ?o . + # ?s2 :p ?o2 . + # FILTER(?o - ?o2 = 3) . + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s) + # (filter (= (- ?o ?o2) 3) + # (bgp + # (triple ?s :p ?o) + # (triple ?s2 :p ?o2))))) # # @see https://www.w3.org/TR/xpath-functions/#func-numeric-subtract class Subtract < Operator::Binary @@ -27,6 +44,15 @@ def apply(left, right, **options) else raise TypeError, "expected two RDF::Literal::Numeric operands, but got #{left.inspect} and #{right.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "#{operands.first.to_sparql(**options)} - #{operands.last.to_sparql(**options)}" + end end # Subtract end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/sum.rb b/lib/sparql/algebra/operator/sum.rb index fb59e24e..1222c4f5 100644 --- a/lib/sparql/algebra/operator/sum.rb +++ b/lib/sparql/algebra/operator/sum.rb @@ -3,7 +3,14 @@ class Operator ## # The SPARQL `sum` set function. # - # @example + # [127] Aggregate::= ... | 'SUM' '(' 'DISTINCT'? Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT (SUM(?O) AS ?sum) + # WHERE { ?s :dec ?o } + # + # @example SSE # (prefix ((: )) # (project (?sum) # (extend ((?sum ??.0)) @@ -33,6 +40,15 @@ def apply(enum, **options) raise TypeError, "Averaging non-numeric types: #{enum.flatten}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "SUM(" + operands.to_sparql(**options) + ")" + end end # Sum end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/table.rb b/lib/sparql/algebra/operator/table.rb index d30fc78c..2924684f 100644 --- a/lib/sparql/algebra/operator/table.rb +++ b/lib/sparql/algebra/operator/table.rb @@ -6,10 +6,26 @@ class Operator # This is used to provide inline values. Each row becomes # a solution. # - # @example - # (table (vars ?book ?title) - # (row (?title "SPARQL Tutorial")) - # (row (?book :book2))) + # [28] ValuesClause ::= ( 'VALUES' DataBlock )? + # + # @example SPARQL Grammar + # PREFIX dc: + # PREFIX : + # PREFIX ns: + # SELECT ?book ?title ?price { + # ?book dc:title ?title ; + # ns:price ?price . + # } + # VALUES ?book { :book1 } + # + # @example SSE + # (prefix ((dc: ) + # (: ) + # (ns: )) + # (project (?book ?title ?price) + # (join + # (bgp (triple ?book dc:title ?title) (triple ?book ns:price ?price)) + # (table (vars ?book) (row (?book :book1)))) )) # # @example empty table # (table unit) @@ -46,6 +62,28 @@ def execute(queryable, **options, &block) @solutions.each(&block) if block_given? @solutions end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + str = "VALUES (#{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)} " + end + line = line.chop + line << ")\n" + + str << line + end + + str << "}\n" + str + end end # Table end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/timezone.rb b/lib/sparql/algebra/operator/timezone.rb index 0fc91bf6..be3c2a19 100644 --- a/lib/sparql/algebra/operator/timezone.rb +++ b/lib/sparql/algebra/operator/timezone.rb @@ -5,11 +5,20 @@ class Operator # # Returns the timezone part of `arg` as an xsd:dayTimeDuration. Raises an error if there is no timezone. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (timezone ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'TIMEZONE' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (TIMEZONE(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (timezone ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-timezone class Timezone < Operator::Unary @@ -31,6 +40,15 @@ def apply(operand, **options) raise TypeError, "literal has no timezone" unless res = operand.timezone res end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "TIMEZONE(" + operands.to_sparql(**options) + ")" + end end # Timezone end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/triple.rb b/lib/sparql/algebra/operator/triple.rb deleted file mode 100644 index 99d2217d..00000000 --- a/lib/sparql/algebra/operator/triple.rb +++ /dev/null @@ -1,27 +0,0 @@ -module SPARQL; module Algebra - class Operator - ## - # The SPARQL `triple` operator. - # - # If the 3-tuple (term1, term2, term3) is an RDF-star triple, the function returns this triple. If the 3-tuple is not an RDF-star triple, then the function raises an error. - # - # @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#triple - class Triple < Operator::Ternary - include Evaluatable - - NAME = :triple - - ## - # @param [RDF::Term] subject - # @param [RDF::Term] predicate - # @param [RDF::Term] object - # @return [RDF::URI] - # @raise [TypeError] if the operand is not a simple literal - def apply(subject, predicate, object, **options) - triple = RDF::Statement(subject, predicate, object) - raise TypeError, "valid components, but got #{triple.inspect}" unless triple.valid? - triple - end - end # Triple - end # Operator -end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/tz.rb b/lib/sparql/algebra/operator/tz.rb index 463c792d..3b4fd640 100644 --- a/lib/sparql/algebra/operator/tz.rb +++ b/lib/sparql/algebra/operator/tz.rb @@ -5,11 +5,20 @@ class Operator # # Returns the timezone part of `arg` as a simple literal. Returns the empty string if there is no timezone. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (tz ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'TZ' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (TZ(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (tz ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-tz class TZ < Operator::Unary @@ -28,6 +37,14 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime) operand.tz end + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "TZ(" + operands.to_sparql(**options) + ")" + end end # TZ end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/ucase.rb b/lib/sparql/algebra/operator/ucase.rb index f26b0cd9..a48d7269 100644 --- a/lib/sparql/algebra/operator/ucase.rb +++ b/lib/sparql/algebra/operator/ucase.rb @@ -3,8 +3,20 @@ class Operator ## # The SPARQL logical `ucase` operator. # - # @example - # (ucase ?x) + # [121] BuiltInCall ::= ... | 'UCASE' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (UCASE(?str) AS ?ustr) WHERE { + # ?s :str ?str + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?str ?ustr) + # (extend ((?ustr (ucase ?str))) + # (bgp (triple ?s :str ?str))))) # # @see https://www.w3.org/TR/sparql11-query/#func-ucase # @see https://www.w3.org/TR/xpath-functions/#func-ucase @@ -26,6 +38,15 @@ def apply(operand, **options) else raise TypeError, "expected an RDF::Literal::Numeric, but got #{operand.inspect}" end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "UCASE(" + operands.last.to_sparql(**options) + ")" + end end # UCase end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/union.rb b/lib/sparql/algebra/operator/union.rb index 4d03e36b..4475c4f1 100644 --- a/lib/sparql/algebra/operator/union.rb +++ b/lib/sparql/algebra/operator/union.rb @@ -3,12 +3,19 @@ class Operator ## # The SPARQL GraphPattern `union` operator. # - # @example - # (prefix ((: )) - # (union - # (bgp (triple ?s ?p ?o)) - # (graph ?g - # (bgp (triple ?s ?p ?o))))) + # [67] GroupOrUnionGraphPattern::= GroupGraphPattern ( 'UNION' GroupGraphPattern )* + # + # @example SPARQL Grammar + # SELECT * { + # { ?s ?p ?o } + # UNION + # { GRAPH ?g { ?s ?p ?o } }} + # + # @example SSE + # (union + # (bgp (triple ?s ?p ?o)) + # (graph ?g + # (bgp (triple ?s ?p ?o)))) # # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra class Union < Operator::Binary @@ -66,6 +73,22 @@ def optimize!(**options) @operands = ops 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) + str = "{\n" + str << operands[0].to_sparql(top_level: false, **options) + str << "\n} UNION {\n" + str << operands[1].to_sparql(top_level: false, **options) + str << "\n}" + top_level ? Operator.to_sparql(str, **options) : str + end end # Union end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/update.rb b/lib/sparql/algebra/operator/update.rb index bf831cb5..a12e24e8 100644 --- a/lib/sparql/algebra/operator/update.rb +++ b/lib/sparql/algebra/operator/update.rb @@ -3,11 +3,23 @@ class Operator ## # The SPARQL GraphPattern `prefix` operator. # - # @example - # (update + # [29] Update ::= Prologue ( Update1 ( ';' Update )? )? + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX foaf: + # DELETE { ?a foaf:knows ?b } + # INSERT { ?b foaf:knows ?a } + # WHERE { ?a foaf:knows ?b } + # + # @example SSE + # (prefix ((: ) + # (foaf: )) + # (update # (modify - # (bgp (triple ?s ?p ?o)) - # (insert ((triple ?s ?p "q"))))) + # (bgp (triple ?a foaf:knows ?b)) + # (delete ((triple ?a foaf:knows ?b))) + # (insert ((triple ?b foaf:knows ?a)))) )) # # @see https://www.w3.org/TR/sparql11-update/#graphUpdate class Update < Operator @@ -39,6 +51,15 @@ def execute(queryable, **options) end queryable end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + str = operands.map { |e| e.to_sparql(**options) }.join("\n") + end end # Update end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/using.rb b/lib/sparql/algebra/operator/using.rb index c8a728db..84ce5462 100644 --- a/lib/sparql/algebra/operator/using.rb +++ b/lib/sparql/algebra/operator/using.rb @@ -6,8 +6,27 @@ class Operator # # The USING and USING NAMED clauses affect the RDF Dataset used while evaluating the WHERE clause. This describes a dataset in the same way as FROM and FROM NAMED clauses describe RDF Datasets in the SPARQL 1.1 Query Language # - # @example - # (using (:g1) (bgp (triple ?s ?p ?o))) + # [44] UsingClause ::= 'USING' ( iri | 'NAMED' iri ) + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX foaf: + # + # DELETE { ?s ?p ?o } + # USING + # WHERE { + # :a foaf:knows ?s . + # ?s ?p ?o + # } + # + # @example SSE + # (prefix + # ((: ) (foaf: )) + # (update + # (modify + # (using (:g2) + # (bgp (triple :a foaf:knows ?s) (triple ?s ?p ?o))) + # (delete ((triple ?s ?p ?o)))) )) # # @see https://www.w3.org/TR/sparql11-update/#add class Using < Operator @@ -35,6 +54,17 @@ def execute(queryable, **options, &block) debug(options) {"Using"} Dataset.new(*operands).execute(queryable, depth: options[:depth].to_i + 1, **options, &block) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + str = "USING #{operands.first.to_sparql(**options)}\n" + content = operands.last.to_sparql(top_level: false, **options) + str << Operator.to_sparql(content, project: nil, **options) + end end # Using end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/uuid.rb b/lib/sparql/algebra/operator/uuid.rb index ee202eb0..1a4617dd 100644 --- a/lib/sparql/algebra/operator/uuid.rb +++ b/lib/sparql/algebra/operator/uuid.rb @@ -5,14 +5,24 @@ class Operator ## # The SPARQL `uuid` function. # - # @example - # (prefix ((: ) - # (xsd: )) - # (project (?length) - # (extend ((?length (strlen (str ?uuid)))) - # (filter (&& (isIRI ?uuid) (regex (str ?uuid) "^urn:uuid:[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}$" "i")) - # (extend ((?uuid (uuid))) - # (bgp)))))) + # [121] BuiltInCall ::= ... | 'UUID' NIL + # + # @example SPARQL Grammar + # PREFIX : + # PREFIX xsd: + # ASK { + # BIND(UUID() AS ?u1) + # BIND(UUID() AS ?u2) + # FILTER(?u1 != ?u2) + # } + # + # @example SSE + # (prefix + # ((: ) (xsd: )) + # (ask + # (filter (!= ?u1 ?u2) + # (extend ((?u1 (uuid)) (?u2 (uuid))) + # (bgp))))) # # @see https://www.w3.org/TR/sparql11-query/#func-uuid class UUID < Operator::Nullary @@ -27,6 +37,15 @@ class UUID < Operator::Nullary def apply(**options) RDF::URI("urn:uuid:#{SecureRandom.uuid}") end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "UUID(" + operands.to_sparql(delimiter: ', ', **options) + ")" + end end # UUID end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/with.rb b/lib/sparql/algebra/operator/with.rb index 9eb67ac9..9e51a1d2 100644 --- a/lib/sparql/algebra/operator/with.rb +++ b/lib/sparql/algebra/operator/with.rb @@ -6,10 +6,21 @@ class Operator # # The WITH clause provides a convenience for when an operation primarily refers to a single graph. # - # @example - # (with :g1 - # (bgp (triple ?s ?p ?o)) - # (insert ((triple ?s ?p "z")))) + # [41] Modify ::= ( 'WITH' iri )? ( DeleteClause InsertClause? | InsertClause ) UsingClause* 'WHERE' GroupGraphPattern + # + # @example SPARQL Grammar + # PREFIX : + # WITH :g + # DELETE { ?p ?o . } + # WHERE { ?s ?p ?o } + # + # @example SSE + # (prefix ((: )) + # (update + # (modify + # (with :g + # (bgp (triple ?s ?p ?o)) + # (delete ((triple ?p ?o))))))) # # @see https://www.w3.org/TR/sparql11-update/#deleteInsert class With < Operator @@ -67,6 +78,29 @@ def execute(queryable, **options) end end end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + with, where, ops = operands + str = "WITH #{with.to_sparql(**options)}\n" + + # The content of the WHERE clause, may be USING + content = where.to_sparql(top_level: false, **options) + + # DELETE | INSERT | DELETE INSERT + str << ops.to_sparql(top_level: false, delimiter: "\n", **options) + "\n" + + # Append the WHERE or USING clause + str << if where.is_a?(Using) + content + else + Operator.to_sparql(content, project: nil, **options) + end + end end # With end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/operator/year.rb b/lib/sparql/algebra/operator/year.rb index 4b0cfce3..65d9bf1d 100644 --- a/lib/sparql/algebra/operator/year.rb +++ b/lib/sparql/algebra/operator/year.rb @@ -5,11 +5,20 @@ class Operator # # Returns the year part of `arg` as an integer. # - # @example - # (prefix ((: )) - # (project (?s ?x) - # (extend ((?x (year ?date))) - # (bgp (triple ?s :date ?date))))) + # [121] BuiltInCall ::= ... | 'YEAR' '(' Expression ')' + # + # @example SPARQL Grammar + # PREFIX : + # SELECT ?s (YEAR(?date) AS ?x) WHERE { + # ?s :date ?date + # } + # + # @example SSE + # (prefix + # ((: )) + # (project (?s ?x) + # (extend ((?x (year ?date))) + # (bgp (triple ?s :date ?date))))) # # @see https://www.w3.org/TR/sparql11-query/#func-year class Year < Operator::Unary @@ -28,6 +37,15 @@ def apply(operand, **options) raise TypeError, "expected an RDF::Literal::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime) RDF::Literal(operand.object.year) end + + ## + # + # Returns a partial SPARQL grammar for this operator. + # + # @return [String] + def to_sparql(**options) + "YEAR(#{operands.last.to_sparql(**options)})" + end end # Year end # Operator end; end # SPARQL::Algebra diff --git a/lib/sparql/algebra/sxp_extensions.rb b/lib/sparql/algebra/sxp_extensions.rb index 37841f74..113837e9 100644 --- a/lib/sparql/algebra/sxp_extensions.rb +++ b/lib/sparql/algebra/sxp_extensions.rb @@ -5,7 +5,7 @@ class NilClass # Returns the SXP representation of this object. # # @return [String] - def to_sxp + def to_sxp(**options) RDF.nil.to_s end end @@ -17,7 +17,7 @@ class FalseClass # Returns the SXP representation of this object. # # @return [String] - def to_sxp + def to_sxp(**options) 'false' end end @@ -29,7 +29,7 @@ class TrueClass # Returns the SXP representation of this object. # # @return [String] - def to_sxp + def to_sxp(**options) 'true' end end diff --git a/lib/sparql/grammar/parser11.rb b/lib/sparql/grammar/parser11.rb index 5baf0656..93999a78 100644 --- a/lib/sparql/grammar/parser11.rb +++ b/lib/sparql/grammar/parser11.rb @@ -1812,8 +1812,8 @@ def iri(value) value = RDF::URI(value) if base_uri && value.relative? u = base_uri.join(value) - u.lexical = "<#{value}>" unless resolve_iris? - u + #u.lexical = "<#{value}>" unless resolve_iris? + #u else value end @@ -1825,8 +1825,8 @@ def ns(prefix, suffix) debug {"ns(#{prefix.inspect}): base: '#{base}', suffix: '#{suffix}'"} iri = iri(base + suffix.to_s) # Cause URI to be serialized as a lexical - iri.lexical = "#{prefix}:#{suffix}" unless resolve_iris? - iri + #iri.lexical = "#{prefix}:#{suffix}" unless resolve_iris? + #iri end # Create a literal diff --git a/script/sparql2sse b/script/sparql2sse index 70e3f63f..4fa0f1a0 100755 --- a/script/sparql2sse +++ b/script/sparql2sse @@ -23,6 +23,8 @@ def run(input, parser_options) print "." elsif $dump puts res.inspect + elsif $to_sparql + puts res.to_sparql else puts res.to_sse end @@ -68,6 +70,7 @@ opts = GetoptLong.new( ["--quiet", GetoptLong::NO_ARGUMENT], ["--resolve-uris", GetoptLong::NO_ARGUMENT], ["--standard-prefixes", GetoptLong::NO_ARGUMENT], + ["--to-sparql", GetoptLong::NO_ARGUMENT], ["--uri", GetoptLong::REQUIRED_ARGUMENT], ["--validate", GetoptLong::NO_ARGUMENT] ) @@ -88,6 +91,7 @@ opts.each do |opt, arg| prefix = v.__name__.to_s.split('::').last.downcase.to_sym parser_options[:prefixes][prefix] = v.to_uri end + when '--to-sparql' then $to_sparql = true when '--uri' then parser_options[:base_uri] = arg when '--validate' then parser_options[:validate] = true when "--help" @@ -101,6 +105,7 @@ opts.each do |opt, arg| puts " --quiet: Output '.' when parsing files, except on error" puts " --resolve-uris: Resolve URIs and QNames in output rather than generating (base ...) or (prefix ...)" puts " --standard-prefixes: Apply standard prefixes to parser (for non-standard productions)" + puts " --to-sparql: Convert back to SPARQL Grammar" puts " --uri: Use argument as default BASE (for non-standard productions)" puts " --validate: Run in validation mode" puts " --help,-?: This message" diff --git a/sparql.gemspec b/sparql.gemspec index e712a2af..38771f86 100755 --- a/sparql.gemspec +++ b/sparql.gemspec @@ -20,21 +20,21 @@ Gem::Specification.new do |gem| gem.require_paths = %w(lib) gem.description = %(SPARQL Implements SPARQL 1.1 Query, Update and result formats for the Ruby RDF.rb library suite.) - gem.required_ruby_version = '>= 2.4' + gem.required_ruby_version = '>= 2.6' gem.requirements = [] - gem.add_runtime_dependency 'rdf', '~> 3.1', '>= 3.1.14' - gem.add_runtime_dependency 'rdf-aggregate-repo', '~> 3.1' - gem.add_runtime_dependency 'ebnf', '~> 2.1' + gem.add_runtime_dependency 'rdf', '~> 3.2' + 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.1' - gem.add_runtime_dependency 'sparql-client', '~> 3.1', '>= 3.1.2' - gem.add_runtime_dependency 'rdf-xsd', '~> 3.1' + gem.add_runtime_dependency 'sxp', '~> 1.2' + gem.add_runtime_dependency 'sparql-client', '~> 3.2' + gem.add_runtime_dependency 'rdf-xsd', '~> 3.2' - gem.add_development_dependency 'sinatra', '~> 2.0' - gem.add_development_dependency 'rack', '~> 2.0' + gem.add_development_dependency 'sinatra', '~> 2.1' + gem.add_development_dependency 'rack', '~> 2.2' gem.add_development_dependency 'rack-test', '~> 1.1' - gem.add_development_dependency 'rdf-spec', '~> 3.1' + gem.add_development_dependency 'rdf-spec', '~> 3.2' gem.add_development_dependency 'linkeddata', '~> 3.1' gem.add_development_dependency 'rspec', '~> 3.10' gem.add_development_dependency 'rspec-its', '~> 1.2' diff --git a/spec/algebra/extensions_spec.rb b/spec/algebra/extensions_spec.rb index 6eac0aab..cc222f8c 100644 --- a/spec/algebra/extensions_spec.rb +++ b/spec/algebra/extensions_spec.rb @@ -8,19 +8,6 @@ [nil, RDF.nil], [false, 'false'], [true, 'true'], - ['', '""'], - ['string', '"string"'], - [:symbol, 'symbol'], - [1, '1'], - [1.0, '1.0'], - [BigDecimal("10"), '10.0'], - [1.0e1, '10.0'], - [Float::INFINITY, "+inf.0"], - [-Float::INFINITY, "-inf.0"], - [Float::NAN, "nan.0"], - [['a', 2], '("a" 2)'], - [Time.parse("2011-03-13T11:22:33Z"), '#@2011-03-13T11:22:33Z'], - [/foo/, '#/foo/'], ].each do |(value, result)| it "returns #{result.inspect} for #{value.inspect}" do expect(value.to_sxp).to eq result diff --git a/spec/algebra/to_sparql_spec.rb b/spec/algebra/to_sparql_spec.rb new file mode 100644 index 00000000..9fb7e5c3 --- /dev/null +++ b/spec/algebra/to_sparql_spec.rb @@ -0,0 +1,50 @@ +$:.unshift File.expand_path("../..", __FILE__) +require 'spec_helper' +require 'algebra/algebra_helper' +require 'strscan' + +include SPARQL::Algebra + +describe SPARQL::Algebra::Operator do + it "reproduces simple query" do + sxp = %{(prefix ((: )) + (bgp (triple :s :p :o)))} + sse = SPARQL::Algebra.parse(sxp) + sparql_result = sse.to_sparql + expect(sparql_result).to generate(sxp, resolve_iris: false, validate: true) + end + + context "Examples" do + def self.read_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 SSE/) + ex = scanner.scan_until(/^\s+#\s*$/) + + # Trim off comment prefix + ex = ex.gsub(/^\s*#/, '') + (examples[op] ||= []) << ex + end + end + examples + end + + 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 + end + end + end + end +end diff --git a/spec/grammar/parser_spec.rb b/spec/grammar/parser_spec.rb index 099ea6a8..cbbc47cc 100644 --- a/spec/grammar/parser_spec.rb +++ b/spec/grammar/parser_spec.rb @@ -247,7 +247,8 @@ def self.variable(id, distinguished = true) context "RegexExpression" do { %q(REGEX ("foo")) => EBNF::LL1::Parser::Error, - %q(REGEX ("foo", "bar")) => %q((regex "foo" "bar" "")), + %q(REGEX ("foo", "bar")) => %q((regex "foo" "bar")), + %q(REGEX ("foo", "bar", "i")) => %q((regex "foo" "bar" "i")), }.each do |input, output| it input do |example| expect(input).to generate(output, example.metadata.merge(resolve_iris: false, last: true).merge(options)) @@ -1179,12 +1180,12 @@ def self.variable(id, distinguished = true) "('bar')", %q((GroupCondition ( "bar"))) ], "Expression" => [ - %q((COALESCE(?w, "1605-11-05"^^xsd:date))), - %q((GroupCondition (coalesce ?w "1605-11-05"^^xsd:date))) + %q((COALESCE(?w, "1605-11-05"^^))), + %q((GroupCondition (coalesce ?w "1605-11-05"^^))) ], "Expression+VAR" => [ - %q((COALESCE(?w, "1605-11-05"^^xsd:date) AS ?X)), - %q((GroupCondition (?X (coalesce ?w "1605-11-05"^^xsd:date)))) + %q((COALESCE(?w, "1605-11-05"^^) AS ?X)), + %q((GroupCondition (?X (coalesce ?w "1605-11-05"^^)))) ], "Var" => [ "?s", %q((GroupCondition ?s)) @@ -1429,16 +1430,16 @@ def self.variable(id, distinguished = true) %q((insertData ())) ], "insert triple" => [ - %q(INSERT DATA {:a foaf:knows :b .}), - %q((insertData ((triple :a foaf:knows :b)))) + %q(INSERT DATA { .}), + %q((insertData ((triple )))) ], "insert graph" => [ - %q(INSERT DATA {GRAPH {:a foaf:knows :b .}}), - %q((insertData ((graph ((triple :a foaf:knows :b)))))) + %q(INSERT DATA {GRAPH { .}}), + %q((insertData ((graph ((triple )))))) ], #"insert triple newline" => [ - # %(INSERT\nDATA {:a foaf:knows :b .}), - # %q((insertData ((triple :a foaf:knows :b)))) + # %(INSERT\nDATA { .}), + # %q((insertData ((triple )))) #], }.each do |title, (input, output)| it title do |example| @@ -1454,22 +1455,22 @@ def self.variable(id, distinguished = true) %q((deleteData ())) ], "delete triple" => [ - %q(DELETE DATA {:a foaf:knows :b .}), - %q((deleteData ((triple :a foaf:knows :b)))) + %q(DELETE DATA { .}), + %q((deleteData ((triple )))) ], "delete graph" => [ - %q(DELETE DATA {GRAPH {:a foaf:knows :b .}}), - %q((deleteData ((graph ((triple :a foaf:knows :b)))))) + %q(DELETE DATA {GRAPH { .}}), + %q((deleteData ((graph ((triple )))))) ], "delete triple and graph" => [ %q(DELETE DATA { - :A foaf:knows :B . - GRAPH {:a foaf:knows :b .} - :c foaf:knows :d . + . + GRAPH { .} + . }), - %q((deleteData ((triple :A foaf:knows :B) - (graph ((triple :a foaf:knows :b))) - (triple :c foaf:knows :d)))) + %q((deleteData ((triple ) + (graph ((triple ))) + (triple )))) ], }.each do |title, (input, output)| it title do |example| @@ -1651,12 +1652,12 @@ def self.variable(id, distinguished = true) "OPTIONAL {}", %q((leftjoin (bgp (triple )))).to_sym, ], "optional filter (1)" => [ - "OPTIONAL {?book :price ?price . FILTER (?price < 15)}", - %q((leftjoin (bgp (triple ?book :price ?price)) (< ?price 15))).to_sym, + "OPTIONAL {?book ?price . FILTER (?price < 15)}", + %q((leftjoin (bgp (triple ?book ?price)) (< ?price 15))).to_sym, ], "optional filter (2)" => [ - %q(OPTIONAL {?y :q ?w . FILTER(?v=2) FILTER(?w=3)}), - %q((leftjoin (bgp (triple ?y :q ?w)) (exprlist (= ?v 2) (= ?w 3)))).to_sym, + %q(OPTIONAL {?y ?w . FILTER(?v=2) FILTER(?w=3)}), + %q((leftjoin (bgp (triple ?y ?w)) (exprlist (= ?v 2) (= ?w 3)))).to_sym, ], }.each do |title, (input, output)| it title do |example| @@ -1869,19 +1870,19 @@ def self.variable(id, distinguished = true) # Property paths describe "Property Paths [88] Path production rule", production: :Path do { - %(:p?) => %((path? :p)), - %(:p*) => %((path* :p)), - %(:p+) => %((path+ :p)), - %((:p+)) => %((path+ :p)), - %(^:p) => %((reverse :p)), - %(!^:p) => %((notoneof (reverse :p))), - %(:p1/:p2) => %((seq :p1 :p2)), - %(:p1|:p2) => %((alt :p1 :p2)), - %(:p1/:p2/:p3) => %((seq (seq :p1 :p2) :p3)), - %(:p1|:p2|:p3) => %((alt (alt :p1 :p2) :p3)), - %((!:p)+/foaf:name) => %((seq (path+ (notoneof :p)) foaf:name)), - %(:p1|(:p2+/:p3+)) => %((alt :p1 (seq (path+ :p2) (path+ :p3)))), - %((((:p)*)*)*) => %((path* (path* (path* :p)))) + %(

?) => %((path?

)), + %(

*) => %((path*

)), + %(

+) => %((path+

)), + %((

+)) => %((path+

)), + %(^

) => %((reverse

)), + %(!^

) => %((notoneof (reverse

))), + %(/) => %((seq )), + %(|) => %((alt )), + %(//) => %((seq (seq ) )), + %(||) => %((alt (alt ) )), + %((!

)+/) => %((seq (path+ (notoneof

)) )), + %(|(+/+)) => %((alt (seq (path+ ) (path+ )))), + %((((

)*)*)*) => %((path* (path* (path*

)))) }.each do |input, output| it input do |example| expect(input).to generate(output, example.metadata.merge(resolve_iris: false, last: true)) diff --git a/spec/support/matchers/generate.rb b/spec/support/matchers/generate.rb index 63f85600..ee27271b 100644 --- a/spec/support/matchers/generate.rb +++ b/spec/support/matchers/generate.rb @@ -21,37 +21,43 @@ def normalize(obj) end match do |input| - case - when expected == EBNF::LL1::Parser::Error - expect {parser(**options).call(input)}.to raise_error(expected) - when options[:last] - # Only look at end of production - @actual = parser(**options).call(input).last - if expected.is_a?(String) - expect(normalize(@actual.to_sxp)).to eq normalize(expected) + @input = input + begin + case + when expected == EBNF::LL1::Parser::Error + expect {parser(**options).call(input)}.to raise_error(expected) + when options[:last] + # Only look at end of production + @actual = parser(**options).call(input).last + if expected.is_a?(String) + expect(normalize(@actual.to_sxp)).to eq normalize(expected) + else + expect(@actual).to eq expected + end + when options[:shift] + @actual = parser(**options).call(input)[1..-1] + expect(@actual).to eq expected + when expected.nil? + @actual = parser(**options).call(input) + expect(@actual).to be_nil + when expected.is_a?(String) + @actual = parser(**options).call(input).to_sxp + expect(normalize(@actual)).to eq normalize(expected) + when expected.is_a?(Symbol) + @actual = parser(**options).call(input) + expect(@actual.to_sxp).to eq expected.to_s else + @actual = parser(**options).call(input) expect(@actual).to eq expected end - when options[:shift] - @actual = parser(**options).call(input)[1..-1] - expect(@actual).to eq expected - when expected.nil? - @actual = parser(**options).call(input) - expect(@actual).to be_nil - when expected.is_a?(String) - @actual = parser(**options).call(input).to_sxp - expect(normalize(@actual)).to eq normalize(expected) - when expected.is_a?(Symbol) - @actual = parser(**options).call(input) - expect(@actual.to_sxp).to eq expected.to_s - else - @actual = parser(**options).call(input) - expect(@actual).to eq expected + rescue + @exception = $! + false end end failure_message do |input| - "Input : #{input}\n" + "Input : #{@input}\n" + case expected when String "Expected : #{expected}\n" @@ -66,6 +72,7 @@ def normalize(obj) "Actual : #{actual.ai}\n" + "Actual(sse) : #{actual.to_sxp}\n" end + + (@exception ? "Exception: #{@exception}" : "") + "Processing results:\n#{@debug.is_a?(Array) ? @debug.join("\n") : ''}" end end