From 3e49b034d86524687253e18762b5f34b2ca6d929 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 13 May 2022 13:24:40 -0700 Subject: [PATCH 1/5] Assign pre-bound variables in Filter and LeftJoin, as necessary. --- lib/sparql/algebra/operator/filter.rb | 5 +++++ lib/sparql/algebra/operator/left_join.rb | 5 +++++ spec/algebra/query_spec.rb | 27 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/lib/sparql/algebra/operator/filter.rb b/lib/sparql/algebra/operator/filter.rb index 26e1179e..9e73c895 100644 --- a/lib/sparql/algebra/operator/filter.rb +++ b/lib/sparql/algebra/operator/filter.rb @@ -49,6 +49,11 @@ def execute(queryable, **options, &block) opts = options.merge(queryable: queryable, depth: options[:depth].to_i + 1) @solutions = RDF::Query::Solutions() queryable.query(operands.last, depth: options[:depth].to_i + 1, **options) do |solution| + # Re-bind to bindings, if defined, as they might not be found in solution + options[:bindings].each_binding do |name, value| + solution[name] ||= value if operands.first.variables.include?(name) + end if options[:bindings] && operands.first.respond_to?(:variables) + begin pass = boolean(operands.first.evaluate(solution, **opts)).true? debug(options) {"(filter) #{pass.inspect} #{solution.to_h.inspect}"} diff --git a/lib/sparql/algebra/operator/left_join.rb b/lib/sparql/algebra/operator/left_join.rb index d069c679..378c50e6 100644 --- a/lib/sparql/algebra/operator/left_join.rb +++ b/lib/sparql/algebra/operator/left_join.rb @@ -66,6 +66,11 @@ def execute(queryable, **options, &block) load_left = true right.each do |s2| s = s2.merge(s1) + # Re-bind to bindings, if defined, as they might not be found in solution + options[:bindings].each_binding do |name, value| + s[name] = value if filter.variables.include?(name) + end if options[:bindings] && filter.respond_to?(:variables) + expr = filter ? boolean(filter.evaluate(s)).true? : true rescue false debug(options) {"===>(evaluate) #{s.inspect}"} if filter diff --git a/spec/algebra/query_spec.rb b/spec/algebra/query_spec.rb index c68fd810..fb024fd2 100644 --- a/spec/algebra/query_spec.rb +++ b/spec/algebra/query_spec.rb @@ -503,6 +503,33 @@ end end + context "with variable pre-binding" do + # Only test exceptions, as pre-binding is handled in RDF::Query.execute. + let(:graph) {RDF::Graph.new} + + { + "filter unbound": { + query: %{(project (?this) (filter (= ?this ) (bgp)))}, + bindings: {this: RDF::URI("http://example.org/this")}, + result: [{this: RDF::URI("http://example.org/this")}] + }, + "left_join unbound": { + query: %{ + (project (?this) + (leftjoin (bgp) (bgp) (= ?this ))) + }, + bindings: {this: RDF::URI("http://example.org/this")}, + result: [{this: RDF::URI("http://example.org/this")}] + }, + }.each do |name, params| + it name do + query = SPARQL::Algebra::Expression.parse(params[:query]) + bindings = RDF::Query::Solution.new(params[:bindings]) + expect(query.execute(graph, bindings: bindings)).to have_result_set params[:result] + end + end + end + context "aggregates" do let(:graph) { RDF::Graph.new do |g| From ea61cb80b654846efa63cd9458f3fc2259d023ce Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 13 May 2022 14:16:59 -0700 Subject: [PATCH 2/5] Parse SERVICE operator. --- lib/sparql/algebra/operator.rb | 2 + lib/sparql/algebra/operator/service.rb | 86 ++++++++++++++++++++++++++ lib/sparql/grammar/parser11.rb | 13 ++++ spec/suite_spec.rb | 2 + 4 files changed, 103 insertions(+) create mode 100644 lib/sparql/algebra/operator/service.rb diff --git a/lib/sparql/algebra/operator.rb b/lib/sparql/algebra/operator.rb index f3c0fee2..faadbc07 100644 --- a/lib/sparql/algebra/operator.rb +++ b/lib/sparql/algebra/operator.rb @@ -134,6 +134,7 @@ class Operator autoload :Prefix, 'sparql/algebra/operator/prefix' autoload :Project, 'sparql/algebra/operator/project' autoload :Reduced, 'sparql/algebra/operator/reduced' + autoload :Service, 'sparql/algebra/operator/service' autoload :Slice, 'sparql/algebra/operator/slice' autoload :Table, 'sparql/algebra/operator/table' autoload :Union, 'sparql/algebra/operator/union' @@ -236,6 +237,7 @@ def self.for(name, arity = nil) when :seconds then Seconds when :seq then Seq when :sequence then Sequence + when :service then Service when :sha1 then SHA1 when :sha256 then SHA256 when :sha384 then SHA384 diff --git a/lib/sparql/algebra/operator/service.rb b/lib/sparql/algebra/operator/service.rb new file mode 100644 index 00000000..bb5b2de2 --- /dev/null +++ b/lib/sparql/algebra/operator/service.rb @@ -0,0 +1,86 @@ +module SPARQL; module Algebra + class Operator + ## + # The SPARQL Service operator. + # + # [59] ServiceGraphPattern ::= 'SERVICE' 'SILENT'? VarOrIri GroupGraphPattern + # + # @example SPARQL Grammar + # PREFIX : + # + # SELECT ?s ?o1 ?o2 { + # ?s ?p1 ?o1 . + # SERVICE { + # ?s ?p2 ?o2 + # } + # } + # + # @example SSE + # (prefix ((: )) + # (project (?s ?o1 ?o2) + # (join + # (bgp (triple ?s ?p1 ?o1)) + # (service :sparql + # (bgp (triple ?s ?p2 ?o2)))))) + # + # @see https://www.w3.org/TR/sparql11-query/#QSynIRI + class Service < Operator + include Query + + NAME = [:service] + + ## + # Executes this query on the given `queryable` graph or repository. + # Really a pass-through, as this is a syntactic object used for providing + # graph_name for URIs. + # + # @param [RDF::Queryable] queryable + # the graph or repository to query + # @param [Hash{Symbol => Object}] options + # any additional keyword options + # @yield [solution] + # each matching solution, statement or boolean + # @yieldparam [RDF::Statement, RDF::Query::Solution, Boolean] solution + # @yieldreturn [void] ignored + # @return [RDF::Query::Solutions] + # the resulting solution sequence + # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra + def execute(queryable, **options, &block) + debug(options) {"Service"} + silent = operands.first == :silent + location, query = operands + query_sparql = query.to_sparql + debug(options) {"query: #{query_sparql}"} + raise NotImplementedError, "SERVICE operator not implemented" + end + + ## + # Returns an optimized version of this query. + # + # Replace with the query with URIs having their lexical shortcut removed + # + # @return [Prefix] a copy of `self` + # @see SPARQL::Algebra::Expression#optimize + def optimize(**options) + operands.last.optimize(**options) + end + + ## + # + # Returns a partial SPARQL grammar for this term. + # + # @return [String] + def to_sparql(**options) + silent = operands.first == :silent + ops = silent ? operands[1..-1] : operands + location, query = ops + + + str = "SERVICE " + str << "SILENT " if silent + str << location.to_sparql(**options) + " {" + query.to_sparql(**options) + "}" + str + end + end # Service + end # Operator +end; end # SPARQL::Algebra diff --git a/lib/sparql/grammar/parser11.rb b/lib/sparql/grammar/parser11.rb index 06564002..2623095d 100644 --- a/lib/sparql/grammar/parser11.rb +++ b/lib/sparql/grammar/parser11.rb @@ -916,6 +916,19 @@ class Parser end end + # [59] ServiceGraphPattern ::= 'SERVICE' 'SILENT'? VarOrIri GroupGraphPattern + # + # Input from `data` is TODO. + # Output to prod_data is TODO. + production(:ServiceGraphPattern) do |input, data, callback| + args = [] + args << :silent if data[:silent] + args << (data[:VarOrIri]).last + args << data.fetch(:query, [SPARQL::Algebra::Operator::BGP.new]).first + service = SPARQL::Algebra::Expression.for(:service, *args) + add_prod_data(:query, service) + end + # [60] Bind ::= 'BIND' '(' Expression 'AS' Var ')' # # Input from `data` is TODO. diff --git a/spec/suite_spec.rb b/spec/suite_spec.rb index fa4cca19..4e2a72aa 100644 --- a/spec/suite_spec.rb +++ b/spec/suite_spec.rb @@ -175,6 +175,8 @@ skip "Equivalent form" when 'sq09.rq', 'sq14.rq' pending("SubSelect") + when 'service03.rq', 'service06.rq', 'syntax-service-01.rq' + pending("Service") when 'sparql-star-order-by.rq' pending("OFFSET/LIMIT in sub-select") when 'compare_time-01.rq', From 2358ded08655beec3d012813c71be71dab553f5d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Fri, 13 May 2022 14:36:11 -0700 Subject: [PATCH 3/5] Assign pre-bound variables in Expand, as necessary. --- lib/sparql/algebra/operator/extend.rb | 5 +++++ spec/algebra/query_spec.rb | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/lib/sparql/algebra/operator/extend.rb b/lib/sparql/algebra/operator/extend.rb index 2b7f989a..8f3ccf7f 100644 --- a/lib/sparql/algebra/operator/extend.rb +++ b/lib/sparql/algebra/operator/extend.rb @@ -84,6 +84,11 @@ def execute(queryable, **options, &block) debug(options) {"Extend"} @solutions = operand(1).execute(queryable, depth: options[:depth].to_i + 1, **options) @solutions.each do |solution| + # Re-bind to bindings, if defined, as they might not be found in solution + options[:bindings].each_binding do |name, value| + solution[name] = value if operands.first.variables.include?(name) + end if options[:bindings] && operands.first.respond_to?(:variables) + debug(options) {"===> soln #{solution.to_h.inspect}"} operand(0).each do |(var, expr)| begin diff --git a/spec/algebra/query_spec.rb b/spec/algebra/query_spec.rb index fb024fd2..b10f9da2 100644 --- a/spec/algebra/query_spec.rb +++ b/spec/algebra/query_spec.rb @@ -508,6 +508,11 @@ let(:graph) {RDF::Graph.new} { + "extend unbound": { + query: %{(project (?z) (extend ((?z (+ ?o 1))) (bgp)))}, + bindings: {o: RDF::Literal(1)}, + result: [{z: RDF::Literal(2)}] + }, "filter unbound": { query: %{(project (?this) (filter (= ?this ) (bgp)))}, bindings: {this: RDF::URI("http://example.org/this")}, From e180cc3bb118c05325d4242616ddfe9405524dd3 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 7 Jun 2022 15:25:35 -0700 Subject: [PATCH 4/5] Update RDF dependency to 3.2.8. --- lib/sparql/algebra/expression.rb | 2 +- sparql.gemspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/sparql/algebra/expression.rb b/lib/sparql/algebra/expression.rb index 05a2c94b..86aded60 100644 --- a/lib/sparql/algebra/expression.rb +++ b/lib/sparql/algebra/expression.rb @@ -140,7 +140,7 @@ def self.new(sse, parent_operator: nil, **options) logger = options[:logger] options.delete_if {|k, v| [:debug, :logger, :depth, :prefixes, :base_uri, :update, :validate].include?(k) } begin - # Due to confusiong over (triple) and special-case for (qtriple) + # Due to confusion over (triple) and special-case for (qtriple) if operator == RDF::Query::Pattern options = options.merge(quoted: true) if sse.first == :qtriple elsif operator == Operator::Triple && PATTERN_PARENTS.include?(parent_operator) diff --git a/sparql.gemspec b/sparql.gemspec index 0e1f660d..fbbc7201 100755 --- a/sparql.gemspec +++ b/sparql.gemspec @@ -29,7 +29,7 @@ Gem::Specification.new do |gem| gem.required_ruby_version = '>= 2.6' gem.requirements = [] - gem.add_runtime_dependency 'rdf', '~> 3.2', '>= 3.2.7' + gem.add_runtime_dependency 'rdf', '~> 3.2', '>= 3.2.8' gem.add_runtime_dependency 'rdf-aggregate-repo', '~> 3.2' gem.add_runtime_dependency 'ebnf', '~> 2.2', '>= 2.3.1' gem.add_runtime_dependency 'builder', '~> 3.2' From 20a67505f7029cab57e7e7c01820384e2aa5c620 Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Tue, 7 Jun 2022 15:30:34 -0700 Subject: [PATCH 5/5] Version 3.2.4. --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index b347b11e..351227fc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.2.3 +3.2.4