From 58f21a7a5d1a39c48cc2d25dc013ec0e3e1d118d Mon Sep 17 00:00:00 2001 From: Gregg Kellogg Date: Sat, 9 May 2015 14:15:38 -0700 Subject: [PATCH] notoneof and reverse operators. --- lib/sparql/algebra/operator/notin.rb | 2 +- lib/sparql/algebra/operator/notoneof.rb | 20 +- lib/sparql/algebra/operator/reverse.rb | 20 +- spec/algebra/query_spec.rb | 429 ++++++++++++++++++------ 4 files changed, 365 insertions(+), 106 deletions(-) diff --git a/lib/sparql/algebra/operator/notin.rb b/lib/sparql/algebra/operator/notin.rb index 22295f51..7a84cdce 100644 --- a/lib/sparql/algebra/operator/notin.rb +++ b/lib/sparql/algebra/operator/notin.rb @@ -6,7 +6,7 @@ class Operator # Used for filters with more than one expression. # # @example - # (ask (filter (notin 2) (bgp))) + # (ask (filter (notin ?o 1 2) (bgp))) # # @see http://www.w3.org/TR/sparql11-query/#func-notin class NotIn < Operator diff --git a/lib/sparql/algebra/operator/notoneof.rb b/lib/sparql/algebra/operator/notoneof.rb index 9adf18c0..cc319c4b 100644 --- a/lib/sparql/algebra/operator/notoneof.rb +++ b/lib/sparql/algebra/operator/notoneof.rb @@ -13,8 +13,12 @@ class NotOneOf < Operator NAME = :notoneof ## - # XXX - # + # Equivalant to: + # + # (path (:x (noteoneof :p :q) :y)) + # => (filter (notin ??p :p :q) (bgp (:x ??p :y))) + # + # @note all operands are terms, and not operators, so this can be done by filtering results usin # # @param [RDF::Queryable] queryable # the graph or repository to query @@ -30,6 +34,18 @@ class NotOneOf < Operator def execute(queryable, options = {}, &block) debug(options) {"NotOneOf #{operands.to_sse}"} subject, object = options[:subject], options[:object] + + v = RDF::Query::Variable.new + v.distinguished = false + bgp = RDF::Query.new do |q| + q.pattern [subject, v, object] + end + query = Filter.new(NotIn.new(v, *operands), bgp) + queryable.query(query, options.merge(depth: options[:depth].to_i + 1)) do |solution| + solution.bindings.delete(v.to_sym) + debug(options) {"(solution)-> #{solution.to_hash.to_sse}"} + block.call(solution) + end end end # NotOneOf end # Operator diff --git a/lib/sparql/algebra/operator/reverse.rb b/lib/sparql/algebra/operator/reverse.rb index 859bbfa9..0e7fc37a 100644 --- a/lib/sparql/algebra/operator/reverse.rb +++ b/lib/sparql/algebra/operator/reverse.rb @@ -13,7 +13,10 @@ class Reverse < Operator::Unary NAME = :reverse ## - # XXX + # Equivliant to: + # + # (path (:a (reverse :p) :b)) + # => (bgp (:b :p :a)) # # # @param [RDF::Queryable] queryable @@ -30,6 +33,21 @@ class Reverse < Operator::Unary def execute(queryable, options = {}, &block) debug(options) {"Reverse #{operands.to_sse}"} subject, object = options[:subject], options[:object] + + # Solutions where predicate exists + query = if operand.is_a?(RDF::Term) + RDF::Query.new do |q| + q.pattern [object, operand, subject] + end + else + operand(0) + end + queryable.query(query, options.merge( + subject: object, + object: subject, + depth: options[:depth].to_i + 1 + ), &block) + end end # Reverse end # Operator diff --git a/spec/algebra/query_spec.rb b/spec/algebra/query_spec.rb index 4f29225b..6aeca5a9 100644 --- a/spec/algebra/query_spec.rb +++ b/spec/algebra/query_spec.rb @@ -25,24 +25,18 @@ context "BGPs" do context "querying for a specific statement" do + let(:graph) {RDF::Graph.new.insert([EX.x1, EX.p1, EX.x2])} it "returns an empty solution sequence if the statement does not exist" do - @graph = RDF::Graph.new do |graph| - graph << [EX.x1, EX.p1, EX.x2] - end - query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ex:x1 ex:p2 ex:x2))))) - expect(query.execute(@graph)).to be_empty + expect(query.execute(graph)).to be_empty end end context "querying for a literal" do + let(:graph) {RDF::Graph.new.insert([EX.x1, EX.p1, RDF::Literal::Decimal.new(123.0)])} it "should return a sequence with an existing literal" do - graph = RDF::Graph.new do |graph| - graph << [EX.x1, EX.p1, RDF::Literal::Decimal.new(123.0)] - end - query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ?s ex:p1 123.0))))) @@ -51,56 +45,56 @@ end context "triple pattern combinations" do - before :each do + let(:graph) { # Normally we would not want all of this crap in the graph for each # test, but this gives us the nice benefit that each test implicitly # tests returning only the correct results and not random other ones. - @graph = RDF::Graph.new do |graph| + RDF::Graph.new do |g| # simple patterns - graph << [EX.x1, EX.p, 1] - graph << [EX.x2, EX.p, 2] - graph << [EX.x3, EX.p, 3] + g << [EX.x1, EX.p, 1] + g << [EX.x2, EX.p, 2] + g << [EX.x3, EX.p, 3] # pattern with same variable twice - graph << [EX.x4, EX.psame, EX.x4] + g << [EX.x4, EX.psame, EX.x4] # pattern with variable across 2 patterns - graph << [EX.x5, EX.p3, EX.x3] - graph << [EX.x5, EX.p2, EX.x3] + g << [EX.x5, EX.p3, EX.x3] + g << [EX.x5, EX.p2, EX.x3] # pattern following a chain - graph << [EX.x6, EX.pchain, EX.target] - graph << [EX.target, EX.pchain2, EX.target2] - graph << [EX.target2, EX.pchain3, EX.target3] + g << [EX.x6, EX.pchain, EX.target] + g << [EX.target, EX.pchain2, EX.target2] + g << [EX.target2, EX.pchain3, EX.target3] end - end + } it "?s p o" do query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ?s ex:p 1))))) - expect(query.execute(@graph)).to have_result_set([{ s: EX.x1 }]) + expect(query.execute(graph)).to have_result_set([{ s: EX.x1 }]) end it "s ?p o" do query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ex:x2 ?p 2))))) - expect(query.execute(@graph)).to have_result_set [ { p: EX.p } ] + expect(query.execute(graph)).to have_result_set [ { p: EX.p } ] end it "s p ?o" do query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ex:x3 ex:p ?o))))) - expect(query.execute(@graph)).to have_result_set [ { o: RDF::Literal.new(3) } ] + expect(query.execute(graph)).to have_result_set [ { o: RDF::Literal.new(3) } ] end it "?s p ?o" do query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ?s ex:p ?o))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, { s: EX.x2, o: RDF::Literal.new(2) }, { s: EX.x3, o: RDF::Literal.new(3) }] end @@ -109,14 +103,14 @@ query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ?s ?p 3))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x3, p: EX.p } ] + expect(query.execute(graph)).to have_result_set [ { s: EX.x3, p: EX.p } ] end it "s ?p ?o" do query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ex:x1 ?p ?o))))) - expect(query.execute(@graph)).to have_result_set [ { p: EX.p, o: RDF::Literal(1) } ] + expect(query.execute(graph)).to have_result_set [ { p: EX.p, o: RDF::Literal(1) } ] end it "?s p o / ?s p1 o1" do @@ -124,7 +118,7 @@ (prefix ((ex: )) (bgp (triple ?s ex:p3 ex:x3) (triple ?s ex:p2 ex:x3))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x5 } ] + expect(query.execute(graph)).to have_result_set [ { s: EX.x5 } ] end it "?s1 p ?o1 / ?o1 p2 ?o2 / ?o2 p3 ?o3" do @@ -133,14 +127,14 @@ (bgp (triple ?s ex:pchain ?o) (triple ?o ex:pchain2 ?o2) (triple ?o2 ex:pchain3 ?o3))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x6, o: EX.target, o2: EX.target2, o3: EX.target3 } ] + expect(query.execute(graph)).to have_result_set [ { s: EX.x6, o: EX.target, o2: EX.target2, o3: EX.target3 } ] end it "?same p ?same" do query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) (bgp (triple ?same ex:psame ?same))))) - expect(query.execute(@graph)).to have_result_set [ { same: EX.x4 } ] + expect(query.execute(graph)).to have_result_set [ { same: EX.x4 } ] end it "(distinct ?s)" do @@ -149,7 +143,7 @@ (distinct (project (?s) (bgp (triple ?s ?p ?o))))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1 }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1 }, { s: EX.x2 }, { s: EX.x3 }, { s: EX.x4 }, @@ -164,7 +158,7 @@ (prefix ((ex: )) (filter (isLiteral ?o) (bgp (triple ?s ex:p ?o)))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, { s: EX.x2, o: RDF::Literal.new(2) }, { s: EX.x3, o: RDF::Literal.new(3) }] end @@ -174,7 +168,7 @@ (prefix ((ex: )) (filter (< ?o 3) (bgp (triple ?s ex:p ?o)))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, { s: EX.x2, o: RDF::Literal.new(2) }] end @@ -183,7 +177,7 @@ (prefix ((ex: )) (filter (exprlist (> ?o 1) (< ?o 3)) (bgp (triple ?s ex:p ?o)))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x2, o: RDF::Literal.new(2) }] + expect(query.execute(graph)).to have_result_set [ { s: EX.x2, o: RDF::Literal.new(2) }] end it "(order ?o)" do @@ -191,7 +185,7 @@ (prefix ((ex: )) (order (?o) (bgp (triple ?s ex:p ?o)))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, { s: EX.x2, o: RDF::Literal.new(2) }, { s: EX.x3, o: RDF::Literal.new(3) }] end @@ -201,7 +195,7 @@ (prefix ((ex: )) (order ((asc ?o)) (bgp (triple ?s ex:p ?o)))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1) }, { s: EX.x2, o: RDF::Literal.new(2) }, { s: EX.x3, o: RDF::Literal.new(3) }] end @@ -211,7 +205,7 @@ (prefix ((ex: )) (order ((desc ?o)) (bgp (triple ?s ex:p ?o)))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x3, o: RDF::Literal.new(3) }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x3, o: RDF::Literal.new(3) }, { s: EX.x2, o: RDF::Literal.new(2) }, { s: EX.x1, o: RDF::Literal.new(1) }] end @@ -221,7 +215,7 @@ (prefix ((ex: )) (order (?o) (table (vars ?o) (row (?o _:1)) (row (?o undef)) (row (?o "example.org")) (row (?o ))))))) - expect(query.execute(@graph)).to have_result_set [ { }, + expect(query.execute(graph)).to have_result_set [ { }, { o: RDF::Node.new(1) }, { o: RDF::URI.new('http://www.example.org/') }, { o: RDF::Literal.new('example.org') }] @@ -234,7 +228,7 @@ (join (table (vars ?o) (row (?o _:1)) (row (?o undef)) (row (?o "example.org")) (row (?o ))) (table (vars ?o2) (row (?o2 _:2)) (row (?o2 undef)) (row (?o2 "example.org")) (row (?o2 )))))))) - expect(query.execute(@graph)).to have_result_set [ { }, + expect(query.execute(graph)).to have_result_set [ { }, { o2: RDF::Node.new(2) }, { o2: RDF::URI.new('http://www.example.org/') }, { o2: RDF::Literal.new('example.org') }, @@ -258,7 +252,7 @@ (order ((asc ?o) (desc ?o2)) (bgp (triple ?s ex:p ?o) (triple ?s2 ex:p ?o2)))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1), s2: EX.x3, o2: RDF::Literal.new(3) }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1, o: RDF::Literal.new(1), s2: EX.x3, o2: RDF::Literal.new(3) }, { s: EX.x1, o: RDF::Literal.new(1), s2: EX.x2, o2: RDF::Literal.new(2) }, { s: EX.x1, o: RDF::Literal.new(1), s2: EX.x1, o2: RDF::Literal.new(1) }, { s: EX.x2, o: RDF::Literal.new(2), s2: EX.x3, o2: RDF::Literal.new(3) }, @@ -274,7 +268,7 @@ (prefix ((ex: )) (project (?o) (bgp (triple ex:x1 ?p ?o)))))) - expect(query.execute(@graph)).to have_result_set [ { o: RDF::Literal(1) } ] + expect(query.execute(graph)).to have_result_set [ { o: RDF::Literal(1) } ] end it "(reduced ?s)" do @@ -283,7 +277,7 @@ (reduced (project (?s) (bgp (triple ?s ?p ?o))))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1 }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x1 }, { s: EX.x2 }, { s: EX.x3 }, { s: EX.x4 }, @@ -299,7 +293,7 @@ (slice _ 1 (project (?s) (bgp (triple ?s ?p ?o))))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x1 }] + expect(query.execute(graph)).to have_result_set [ { s: EX.x1 }] end it "(slice 1 2)" do @@ -308,7 +302,7 @@ (slice 1 2 (project (?s) (bgp (triple ?s ?p ?o))))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x2 }, { s: EX.x3 }] + expect(query.execute(graph)).to have_result_set [ { s: EX.x2 }, { s: EX.x3 }] end it "(slice 5 _)" do @@ -317,7 +311,7 @@ (slice 5 _ (project (?s) (bgp (triple ?s ?p ?o))))))) - expect(query.execute(@graph)).to have_result_set [ { s: EX.x5 }, + expect(query.execute(graph)).to have_result_set [ { s: EX.x5 }, { s: EX.x6 }, { s: EX.target }, { s: EX.target2 } ] @@ -325,36 +319,36 @@ # From sp2b benchmark, query 7 bgp 2 it "?class3 p o / ?doc3 p2 ?class3 / ?doc3 p3 ?bag3 / ?bag3 ?member3 ?doc" do - @graph << [EX.class1, EX.subclass, EX.document] - @graph << [EX.class2, EX.subclass, EX.document] - @graph << [EX.class3, EX.subclass, EX.other] + graph << [EX.class1, EX.subclass, EX.document] + graph << [EX.class2, EX.subclass, EX.document] + graph << [EX.class3, EX.subclass, EX.other] - @graph << [EX.doc1, EX.type, EX.class1] - @graph << [EX.doc2, EX.type, EX.class1] - @graph << [EX.doc3, EX.type, EX.class2] - @graph << [EX.doc4, EX.type, EX.class2] - @graph << [EX.doc5, EX.type, EX.class3] + graph << [EX.doc1, EX.type, EX.class1] + graph << [EX.doc2, EX.type, EX.class1] + graph << [EX.doc3, EX.type, EX.class2] + graph << [EX.doc4, EX.type, EX.class2] + graph << [EX.doc5, EX.type, EX.class3] - @graph << [EX.doc1, EX.refs, EX.bag1] - @graph << [EX.doc2, EX.refs, EX.bag2] - @graph << [EX.doc3, EX.refs, EX.bag3] - @graph << [EX.doc5, EX.refs, EX.bag5] + graph << [EX.doc1, EX.refs, EX.bag1] + graph << [EX.doc2, EX.refs, EX.bag2] + graph << [EX.doc3, EX.refs, EX.bag3] + graph << [EX.doc5, EX.refs, EX.bag5] - @graph << [EX.bag1, RDF::Node.new('ref1'), EX.doc11] - @graph << [EX.bag1, RDF::Node.new('ref2'), EX.doc12] - @graph << [EX.bag1, RDF::Node.new('ref3'), EX.doc13] + graph << [EX.bag1, RDF::Node.new('ref1'), EX.doc11] + graph << [EX.bag1, RDF::Node.new('ref2'), EX.doc12] + graph << [EX.bag1, RDF::Node.new('ref3'), EX.doc13] - @graph << [EX.bag2, RDF::Node.new('ref1'), EX.doc21] - @graph << [EX.bag2, RDF::Node.new('ref2'), EX.doc22] - @graph << [EX.bag2, RDF::Node.new('ref3'), EX.doc23] + graph << [EX.bag2, RDF::Node.new('ref1'), EX.doc21] + graph << [EX.bag2, RDF::Node.new('ref2'), EX.doc22] + graph << [EX.bag2, RDF::Node.new('ref3'), EX.doc23] - @graph << [EX.bag3, RDF::Node.new('ref1'), EX.doc31] - @graph << [EX.bag3, RDF::Node.new('ref2'), EX.doc32] - @graph << [EX.bag3, RDF::Node.new('ref3'), EX.doc33] + graph << [EX.bag3, RDF::Node.new('ref1'), EX.doc31] + graph << [EX.bag3, RDF::Node.new('ref2'), EX.doc32] + graph << [EX.bag3, RDF::Node.new('ref3'), EX.doc33] - @graph << [EX.bag5, RDF::Node.new('ref1'), EX.doc51] - @graph << [EX.bag5, RDF::Node.new('ref2'), EX.doc52] - @graph << [EX.bag5, RDF::Node.new('ref3'), EX.doc53] + graph << [EX.bag5, RDF::Node.new('ref1'), EX.doc51] + graph << [EX.bag5, RDF::Node.new('ref2'), EX.doc52] + graph << [EX.bag5, RDF::Node.new('ref3'), EX.doc53] query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) @@ -363,7 +357,7 @@ (triple ?doc3 ex:refs ?bag3) (triple ?bag3 ?member3 ?doc))))) - expect(query.execute(@graph).map(&:to_hash)).to include( + expect(query.execute(graph).map(&:to_hash)).to include( { doc3: EX.doc1, class3: EX.class1, bag3: EX.bag1, member3: RDF::Node.new('ref1'), doc: EX.doc11 }, { doc3: EX.doc1, class3: EX.class1, bag3: EX.bag1, member3: RDF::Node.new('ref2'), doc: EX.doc12 }, { doc3: EX.doc1, class3: EX.class1, bag3: EX.bag1, member3: RDF::Node.new('ref3'), doc: EX.doc13 }, @@ -378,44 +372,44 @@ # From sp2b benchmark, query 7 bgp 1 it "?class subclass document / ?doc type ?class / ?doc title ?title / ?bag2 ?member2 ?doc / ?doc2 refs ?bag2" do - @graph << [EX.class1, EX.subclass, EX.document] - @graph << [EX.class2, EX.subclass, EX.document] - @graph << [EX.class3, EX.subclass, EX.other] - - @graph << [EX.doc1, EX.type, EX.class1] - @graph << [EX.doc2, EX.type, EX.class1] - @graph << [EX.doc3, EX.type, EX.class2] - @graph << [EX.doc4, EX.type, EX.class2] - @graph << [EX.doc5, EX.type, EX.class3] + graph << [EX.class1, EX.subclass, EX.document] + graph << [EX.class2, EX.subclass, EX.document] + graph << [EX.class3, EX.subclass, EX.other] + + graph << [EX.doc1, EX.type, EX.class1] + graph << [EX.doc2, EX.type, EX.class1] + graph << [EX.doc3, EX.type, EX.class2] + graph << [EX.doc4, EX.type, EX.class2] + graph << [EX.doc5, EX.type, EX.class3] # no doc6 type - @graph << [EX.doc1, EX.title, EX.title1] - @graph << [EX.doc2, EX.title, EX.title2] - @graph << [EX.doc3, EX.title, EX.title3] - @graph << [EX.doc4, EX.title, EX.title4] - @graph << [EX.doc5, EX.title, EX.title5] - @graph << [EX.doc6, EX.title, EX.title6] + graph << [EX.doc1, EX.title, EX.title1] + graph << [EX.doc2, EX.title, EX.title2] + graph << [EX.doc3, EX.title, EX.title3] + graph << [EX.doc4, EX.title, EX.title4] + graph << [EX.doc5, EX.title, EX.title5] + graph << [EX.doc6, EX.title, EX.title6] - @graph << [EX.doc1, EX.refs, EX.bag1] - @graph << [EX.doc2, EX.refs, EX.bag2] - @graph << [EX.doc3, EX.refs, EX.bag3] - @graph << [EX.doc5, EX.refs, EX.bag5] + graph << [EX.doc1, EX.refs, EX.bag1] + graph << [EX.doc2, EX.refs, EX.bag2] + graph << [EX.doc3, EX.refs, EX.bag3] + graph << [EX.doc5, EX.refs, EX.bag5] - @graph << [EX.bag1, RDF::Node.new('ref1'), EX.doc11] - @graph << [EX.bag1, RDF::Node.new('ref2'), EX.doc12] - @graph << [EX.bag1, RDF::Node.new('ref3'), EX.doc13] + graph << [EX.bag1, RDF::Node.new('ref1'), EX.doc11] + graph << [EX.bag1, RDF::Node.new('ref2'), EX.doc12] + graph << [EX.bag1, RDF::Node.new('ref3'), EX.doc13] - @graph << [EX.bag2, RDF::Node.new('ref1'), EX.doc21] - @graph << [EX.bag2, RDF::Node.new('ref2'), EX.doc22] - @graph << [EX.bag2, RDF::Node.new('ref3'), EX.doc23] + graph << [EX.bag2, RDF::Node.new('ref1'), EX.doc21] + graph << [EX.bag2, RDF::Node.new('ref2'), EX.doc22] + graph << [EX.bag2, RDF::Node.new('ref3'), EX.doc23] - @graph << [EX.bag3, RDF::Node.new('ref1'), EX.doc31] - @graph << [EX.bag3, RDF::Node.new('ref2'), EX.doc32] - @graph << [EX.bag3, RDF::Node.new('ref3'), EX.doc33] + graph << [EX.bag3, RDF::Node.new('ref1'), EX.doc31] + graph << [EX.bag3, RDF::Node.new('ref2'), EX.doc32] + graph << [EX.bag3, RDF::Node.new('ref3'), EX.doc33] - @graph << [EX.bag5, RDF::Node.new('ref1'), EX.doc51] - @graph << [EX.bag5, RDF::Node.new('ref2'), EX.doc52] - @graph << [EX.bag5, RDF::Node.new('ref3'), EX.doc53] + graph << [EX.bag5, RDF::Node.new('ref1'), EX.doc51] + graph << [EX.bag5, RDF::Node.new('ref2'), EX.doc52] + graph << [EX.bag5, RDF::Node.new('ref3'), EX.doc53] query = SPARQL::Algebra::Expression.parse(%q( (prefix ((ex: )) @@ -425,7 +419,7 @@ (triple ?doc ex:refs ?bag) (triple ?bag ?member ?doc2))))) - expect(query.execute(@graph).map(&:to_hash)).to include( + expect(query.execute(graph).map(&:to_hash)).to include( { doc: EX.doc1, class: EX.class1, bag: EX.bag1, member: RDF::Node.new('ref1'), doc2: EX.doc11, title: EX.title1 }, { doc: EX.doc1, class: EX.class1, bag: EX.bag1, @@ -683,6 +677,237 @@ end end + context "property paths" do + let(:repo) { + RDF::Repository.new << RDF::TriG::Reader.new(%( + @prefix foaf: . + @prefix : . + @prefix ex: . + @prefix in: . + + # data-diamond.ttl + :a :p :b . + :b :p :z . + :a :p :c . + :c :p :z . + :b :q :B . + :B :r :Z . + + {:a :p1 :b .} + {:a :p1 :c .} + {:a :p1 :d .} + )) + } + + { + "path?" => { + ":a (path? :p) ?v" => { + query: %q{(prefix ((: )) (path :a (path? :p) ?v))}, + expected: [ + {v: RDF::URI("http://example.org/a")}, + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + ] + }, + "?v (path? :p) :z" => { + query: %q{(prefix ((: )) (path ?v (path? :p) :z))}, + expected: [ + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + {v: RDF::URI("http://example.org/z")}, + ] + }, + "?x (path? :p) ?y" => { + query: %q{(prefix ((: )) (path ?x (path? :p) ?y))}, + expected: [ + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/a")}, + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/b")}, + {x: RDF::URI("http://example.org/B"), y: RDF::URI("http://example.org/B")}, + {x: RDF::URI("http://example.org/c"), y: RDF::URI("http://example.org/c")}, + {x: RDF::URI("http://example.org/z"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/Z"), y: RDF::URI("http://example.org/Z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/b")}, + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/c")}, + {x: RDF::URI("http://example.org/c"), y: RDF::URI("http://example.org/z")}, + ] + }, + }, + "path+" => { + ":a (path+ :p) ?v" => { + query: %q{(prefix ((: )) (path :a (path+ :p) ?v))}, + expected: [ + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + {v: RDF::URI("http://example.org/z")}, + ] + }, + "?v (path+ :p) :z" => { + query: %q{(prefix ((: )) (path ?v (path+ :p) :z))}, + expected: [ + {v: RDF::URI("http://example.org/a")}, + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + ] + }, + "?x (path+ :p) ?y" => { + query: %q{(prefix ((: )) (path ?x (path+ :p) ?y))}, + expected: [ + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/b")}, + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/c")}, + {x: RDF::URI("http://example.org/c"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/z")}, + ] + }, + }, + "path*" => { + ":a (path* :p) ?v" => { + query: %q{(prefix ((: )) (path :a (path* :p) ?v))}, + expected: [ + {v: RDF::URI("http://example.org/a")}, + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + {v: RDF::URI("http://example.org/z")}, + ] + }, + "?v (path* :p) :z" => { + query: %q{(prefix ((: )) (path ?v (path* :p) :z))}, + expected: [ + {v: RDF::URI("http://example.org/a")}, + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + {v: RDF::URI("http://example.org/z")}, + ] + }, + "?x (path* :p) ?y" => { + query: %q{(prefix ((: )) (path ?x (path* :p) ?y))}, + expected: [ + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/a")}, + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/b")}, + {x: RDF::URI("http://example.org/B"), y: RDF::URI("http://example.org/B")}, + {x: RDF::URI("http://example.org/c"), y: RDF::URI("http://example.org/c")}, + {x: RDF::URI("http://example.org/z"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/Z"), y: RDF::URI("http://example.org/Z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/b")}, + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/c")}, + {x: RDF::URI("http://example.org/c"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/z")}, + ] + }, + }, + "alt" => { + ":b (alt :p :q) ?v" => { + query: %q{(prefix ((: )) (path :b (alt :p :q) ?v))}, + expected: [ + {v: RDF::URI("http://example.org/z")}, + {v: RDF::URI("http://example.org/B")}, + ] + }, + "?v (alt :p :q) :z" => { + query: %q{(prefix ((: )) (path ?v (alt :p :q) :z))}, + expected: [ + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + ] + }, + "?x (alt :p :q) ?y" => { + query: %q{(prefix ((: )) (path ?x (alt :p :q) ?y))}, + expected: [ + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/b")}, + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/c")}, + {x: RDF::URI("http://example.org/c"), y: RDF::URI("http://example.org/z")}, + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/B")}, + ] + }, + }, + "seq" => { + ":b (seq :p :q) ?v" => { + query: %q{(prefix ((: )) (path :a (seq :p :q) ?v))}, + expected: [ + {v: RDF::URI("http://example.org/B")}, + ] + }, + "?v (seq :p :q) :z" => { + query: %q{(prefix ((: )) (path ?v (seq :p :q) :B))}, + expected: [ + {v: RDF::URI("http://example.org/a")}, + ] + }, + "?x (seq :p :q) ?y" => { + query: %q{(prefix ((: )) (path ?x (seq :p :q) ?y))}, + expected: [ + {x: RDF::URI("http://example.org/a"), y: RDF::URI("http://example.org/B")}, + ] + }, + }, + "reverse" => { + ":z (reverse :p) ?v" => { + query: %q{(prefix ((: )) (path :z (reverse :p) ?v))}, + expected: [ + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + ] + }, + "?v (reverse :p) :z" => { + query: %q{(prefix ((: )) (path ?v (reverse :p) :a))}, + expected: [ + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + ] + }, + "?x (reverse :p) ?y" => { + query: %q{(prefix ((: )) (path ?x (reverse :p) ?y))}, + expected: [ + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/a")}, + {x: RDF::URI("http://example.org/c"), y: RDF::URI("http://example.org/a")}, + {x: RDF::URI("http://example.org/z"), y: RDF::URI("http://example.org/b")}, + {x: RDF::URI("http://example.org/z"), y: RDF::URI("http://example.org/c")}, + ] + }, + }, + "notoneof" => { + ":b (notoneof :p) ?v" => { + query: %q{(prefix ((: )) (path :b (notoneof :p) ?v))}, + expected: [ + {v: RDF::URI("http://example.org/B")}, + ] + }, + "?v (notoneof :q) :z" => { + query: %q{(prefix ((: )) (path ?v (notoneof :q) :z))}, + expected: [ + {v: RDF::URI("http://example.org/b")}, + {v: RDF::URI("http://example.org/c")}, + ] + }, + "?x (notoneof :p) ?y" => { + query: %q{(prefix ((: )) (path ?x (notoneof :p) ?y))}, + expected: [ + {x: RDF::URI("http://example.org/b"), y: RDF::URI("http://example.org/B")}, + {x: RDF::URI("http://example.org/B"), y: RDF::URI("http://example.org/Z")}, + ] + }, + } + }.each do |name, tests| + describe name, focus:true do + tests.each do |tname, opts| + it tname do + if opts[:error] + expect {sparql_query({sse: true, graphs: repo}.merge(opts))}.to raise_error(opts[:error]) + else + expected = opts[:expected] + actual = sparql_query({sse: true, graphs: repo}.merge(opts)) + expect(actual.map(&:to_hash)).to include(*expected) + expect(actual.length).to produce(expected.length, [{actual: actual.map(&:to_hash), expected: expected.map(&:to_hash)}.to_sse]) + end + end + end + end + end + end + context "query forms" do { # @see http://www.w3.org/TR/rdf-sparql-query/#QSynIRI