sparql 1.0.8 → 1.1.0p0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +14 -6
  2. data/README.md +57 -25
  3. data/VERSION +1 -1
  4. data/lib/sinatra/sparql.rb +5 -3
  5. data/lib/sparql/algebra/aggregate.rb +67 -0
  6. data/lib/sparql/algebra/evaluatable.rb +49 -4
  7. data/lib/sparql/algebra/expression.rb +6 -4
  8. data/lib/sparql/algebra/extensions.rb +99 -9
  9. data/lib/sparql/algebra/operator/and.rb +7 -4
  10. data/lib/sparql/algebra/operator/asc.rb +6 -3
  11. data/lib/sparql/algebra/operator/avg.rb +36 -0
  12. data/lib/sparql/algebra/operator/bnode.rb +5 -4
  13. data/lib/sparql/algebra/operator/bound.rb +5 -2
  14. data/lib/sparql/algebra/operator/coalesce.rb +6 -3
  15. data/lib/sparql/algebra/operator/concat.rb +6 -3
  16. data/lib/sparql/algebra/operator/count.rb +30 -0
  17. data/lib/sparql/algebra/operator/exists.rb +39 -0
  18. data/lib/sparql/algebra/operator/exprlist.rb +6 -3
  19. data/lib/sparql/algebra/operator/extend.rb +3 -1
  20. data/lib/sparql/algebra/operator/filter.rb +2 -1
  21. data/lib/sparql/algebra/operator/group.rb +101 -0
  22. data/lib/sparql/algebra/operator/group_concat.rb +55 -0
  23. data/lib/sparql/algebra/operator/if.rb +5 -5
  24. data/lib/sparql/algebra/operator/in.rb +7 -4
  25. data/lib/sparql/algebra/operator/max.rb +38 -0
  26. data/lib/sparql/algebra/operator/min.rb +38 -0
  27. data/lib/sparql/algebra/operator/minus.rb +51 -16
  28. data/lib/sparql/algebra/operator/negate.rb +31 -0
  29. data/lib/sparql/algebra/operator/notexists.rb +37 -0
  30. data/lib/sparql/algebra/operator/notin.rb +7 -4
  31. data/lib/sparql/algebra/operator/or.rb +7 -4
  32. data/lib/sparql/algebra/operator/order.rb +2 -2
  33. data/lib/sparql/algebra/operator/sample.rb +32 -0
  34. data/lib/sparql/algebra/operator/strlang.rb +1 -1
  35. data/lib/sparql/algebra/operator/sum.rb +36 -0
  36. data/lib/sparql/algebra/operator/table.rb +44 -0
  37. data/lib/sparql/algebra/operator.rb +37 -2
  38. data/lib/sparql/algebra.rb +1 -0
  39. data/lib/sparql/grammar/meta.rb +1143 -696
  40. data/lib/sparql/grammar/parser11.rb +178 -32
  41. data/lib/sparql/grammar/terminals11.rb +2 -2
  42. data/lib/sparql/grammar.rb +0 -2
  43. data/lib/sparql/results.rb +55 -0
  44. metadata +78 -81
@@ -24,11 +24,14 @@ module SPARQL; module Algebra
24
24
  # concat("foo"@en, "bar") #=> "foobar"
25
25
  # concat("foo"@en, "bar"^^xsd:string) #=> "foobar"
26
26
  #
27
- # @param [RDF::Query::Solution, #[]] bindings
27
+ # @param [RDF::Query::Solution] bindings
28
+ # a query solution containing zero or more variable bindings
29
+ # @param [Hash{Symbol => Object}] options ({})
30
+ # options passed from query
28
31
  # @return [RDF::Term]
29
32
  # @raise [TypeError] if any operand is not a literal
30
- def evaluate(bindings = {})
31
- ops = operands.map {|op| op.evaluate(bindings)}
33
+ def evaluate(bindings, options = {})
34
+ ops = operands.map {|op| op.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))}
32
35
 
33
36
  raise TypeError, "expected all plain literal operands" unless ops.all? {|op| op.literal? && op.plain?}
34
37
 
@@ -0,0 +1,30 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `count` set function.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://www.example.org>))
8
+ # (project (?C)
9
+ # (extend ((?C ?.0))
10
+ # (group () ((?.0 (count ?O)))
11
+ # (bgp (triple ?S ?P ?O))))))
12
+ #
13
+ # @see http://www.w3.org/TR/sparql11-query/#defn_aggCount
14
+ class Count < Operator
15
+ include Aggregate
16
+
17
+ NAME = :count
18
+
19
+ ##
20
+ # Count is a SPARQL set function which counts the number of times a given expression has a bound, and non-error value within the aggregate group.
21
+ #
22
+ # @param [Enumerable<Array<RDF::Term>>] enum
23
+ # enum of evaluated operand
24
+ # @return [RDF::Literal::Integer] The number of non-error terms in the multiset
25
+ def apply(enum)
26
+ RDF::Literal(enum.length)
27
+ end
28
+ end # Count
29
+ end # Operator
30
+ end; end # SPARQL::Algebra
@@ -0,0 +1,39 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL logical `exists` operator.
5
+ #
6
+ # @example
7
+ # (prefix ((ex: <http://www.example.org/>))
8
+ # (filter (exists
9
+ # (filter (notexists (bgp (triple ?s ?p ex:o2)))
10
+ # (bgp (triple ?s ?p ex:o1))))
11
+ # (bgp (triple ?s ?p ex:o))))
12
+ #
13
+ # @see http://www.w3.org/TR/sparql11-query/#func-abs
14
+ # @see http://www.w3.org/TR/xpath-functions/#func-abs
15
+ class Exists < Operator::Unary
16
+ include Evaluatable
17
+
18
+ NAME = [:exists]
19
+
20
+ ##
21
+ # Exvaluating this operator executes the query in the first operator passing in each existing bindings.
22
+ #
23
+ # @param [RDF::Query::Solution] bindings
24
+ # a query solution containing zero or more variable bindings
25
+ # @param [Hash{Symbol => Object}] options ({})
26
+ # options passed from query
27
+ # @option options[RDF::Queryable] queryable
28
+ # queryable to execute, using bindings as an initial solution.
29
+ # @return [RDF::Literal::Boolean] `true` or `false`
30
+ def evaluate(bindings, options = {})
31
+ solutions = RDF::Query::Solutions.new << bindings
32
+ queryable = options[:queryable]
33
+ !operand(0).execute(queryable, options.merge(
34
+ :solutions => solutions,
35
+ :depth => options[:depth].to_i + 1)).empty?
36
+ end
37
+ end # Exists
38
+ end # Operator
39
+ end; end # SPARQL::Algebra
@@ -30,11 +30,14 @@ module SPARQL; module Algebra
30
30
  #
31
31
  # (exprlist (= 1 1) (!= 1 0))
32
32
  #
33
- # @param [RDF::Query::Solution, #[]] bindings
33
+ # @param [RDF::Query::Solution] bindings
34
+ # a query solution containing zero or more variable bindings
35
+ # @param [Hash{Symbol => Object}] options ({})
36
+ # options passed from query
34
37
  # @return [RDF::Literal::Boolean] `true` or `false`
35
38
  # @raise [TypeError] if the operands could not be coerced to a boolean literal
36
- def evaluate(bindings = {})
37
- res = operands.all? {|op| boolean(op.evaluate(bindings)).true? }
39
+ def evaluate(bindings, options = {})
40
+ res = operands.all? {|op| boolean(op.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))).true? }
38
41
  RDF::Literal(res) # FIXME: error handling
39
42
  end
40
43
 
@@ -37,7 +37,9 @@ module SPARQL; module Algebra
37
37
  debug(options) {"===> soln #{solution.to_hash.inspect}"}
38
38
  operands.first.each do |(var, expr)|
39
39
  begin
40
- val = expr.evaluate(solution)
40
+ val = expr.evaluate(solution, options.merge(
41
+ :queryable => queryable,
42
+ :depth => options[:depth].to_i + 1))
41
43
  debug(options) {"===> + #{var} => #{val.inspect}"}
42
44
  solution.bindings[var.to_sym] = val
43
45
  rescue TypeError => e
@@ -36,6 +36,7 @@ module SPARQL; module Algebra
36
36
  debug(options) {"Filter #{operands.first.to_sxp}"}
37
37
  @solutions = operands.last.execute(queryable, options.merge(:depth => options[:depth].to_i + 1))
38
38
  debug(options) {"=>(before) #{@solutions.map(&:to_hash).inspect}"}
39
+ opts = options.merge(:queryable => queryable, :depth => options[:depth].to_i + 1)
39
40
  @solutions = @solutions.filter do |solution|
40
41
  # Evaluate the solution, which will return true or false
41
42
  #debug(options) {"===>(evaluate) #{operands.first.inspect} against #{solution.to_hash.inspect}"}
@@ -44,7 +45,7 @@ module SPARQL; module Algebra
44
45
  # FILTERs eliminate any solutions that, when substituted into the expression, either
45
46
  # result in an effective boolean value of false or produce an error.
46
47
  begin
47
- res = boolean(operands.first.evaluate(solution)).true?
48
+ res = boolean(operands.first.evaluate(solution, opts)).true?
48
49
  debug(options) {"===>#{res} #{solution.to_hash.inspect}"}
49
50
  res
50
51
  rescue
@@ -0,0 +1,101 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `group` operator.
5
+ #
6
+ # `group` takes either two or three operands. The first operand
7
+ # is an array of grouped variables. The last operand is the
8
+ # query to be grouped. If three operands are provided,
9
+ # the second is an array of temporary bindings.
10
+ #
11
+ # @example
12
+ # (prefix ((: <http://example/>))
13
+ # (project (?w ?S)
14
+ # (extend ((?S ?.0))
15
+ # (group (?w) ((?.0 (sample ?v)))
16
+ # (leftjoin
17
+ # (bgp (triple ?s :p ?v))
18
+ # (bgp (triple ?s :q ?w)))))))
19
+ #
20
+ # @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
21
+ class Group < Operator
22
+ include Query
23
+
24
+ NAME = [:group]
25
+
26
+ ##
27
+ # Executes `query` with `queryable` and groups results based
28
+ # on the first operand.
29
+ #
30
+ # @param [RDF::Queryable] queryable
31
+ # the graph or repository to query
32
+ # @param [Hash{Symbol => Object}] options
33
+ # any additional keyword options
34
+ # @return [RDF::Query::Solutions]
35
+ # the resulting solution sequence
36
+ # @see http://www.w3.org/TR/sparql11-query/#sparqlGroupAggregate
37
+ def execute(queryable, options = {})
38
+ debug(options) {"Group"}
39
+ exprlist = operands.first
40
+ query = operands.last
41
+ aggregates = operands.length == 3 ? operand(1) : []
42
+ solutions = query.execute(queryable, options.merge(:depth => options[:depth].to_i + 1)) || {}
43
+
44
+ groups = solutions.group_by do |solution|
45
+ # Evaluate each exprlist operand to get groups where each key is a new solution
46
+ # ListEval((expr1, ..., exprn), μ) returns a list (e1, ..., en), where ei = expri(μ) or error.
47
+ soln = RDF::Query::Solution.new
48
+ exprlist.each do |operand|
49
+ begin
50
+ if operand.is_a?(Array)
51
+ # Form is [variable, expression]
52
+ soln[operand.first] = operand.last.evaluate(solution,
53
+ options.merge(
54
+ :queryable => queryable,
55
+ :depth => options[:depth].to_i + 1))
56
+ else
57
+ # Form is variable
58
+ soln[operand] = operand.evaluate(solution, options.merge(
59
+ :queryable => queryable,
60
+ :depth => options[:depth].to_i + 1))
61
+ end
62
+ rescue TypeError
63
+ # Ignore expression
64
+ end
65
+ end
66
+ soln
67
+ end
68
+
69
+ debug(options) {"=>(groups) #{groups.inspect}"}
70
+
71
+ # Aggregate solutions in each group using aggregates to get solutions
72
+ @solutions = groups.map do |group_soln, solns|
73
+ aggregates.each do |(var, aggregate)|
74
+ begin
75
+ group_soln[var] = aggregate.aggregate(solns, options)
76
+ rescue TypeError
77
+ # Ignored in output
78
+ end
79
+ end
80
+ group_soln
81
+ end
82
+
83
+ # Make sure that's at least an empty solution
84
+ @solutions << RDF::Query::Solution.new if @solutions.empty?
85
+
86
+ debug(options) {"=>(solutions) #{@solutions.inspect}"}
87
+ @solutions = RDF::Query::Solutions.new(@solutions)
88
+ end
89
+
90
+ ##
91
+ # Returns an optimized version of this query.
92
+ #
93
+ # TODO
94
+ #
95
+ # @return [Group] `self`
96
+ def optimize
97
+ self
98
+ end
99
+ end # Group
100
+ end # Operator
101
+ end; end # SPARQL::Algebra
@@ -0,0 +1,55 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `groupconcat` set function.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://www.example.org/>))
8
+ # (filter (|| (= ?g "1 22") (= ?g "22 1"))
9
+ # (project (?g)
10
+ # (extend ((?g ?.0))
11
+ # (group () ((?.0 (group_concat ?o)))
12
+ # (bgp (triple ??0 :p1 ?o)))))))
13
+ #
14
+ # @see http://www.w3.org/TR/sparql11-query/#defn_aggGroupConcat
15
+ class GroupConcat < Operator
16
+ include Aggregate
17
+
18
+ NAME = :group_concat
19
+ ##
20
+ # One or two operands, the second operand, if it exists, is a separator, defaulting to ' '.
21
+ #
22
+ # @param [Enumerable<RDF::Query::Solution>] solutions ([])
23
+ # an enumerable set of query solutions
24
+ # @param [Hash{Symbol => Object}] options ({})
25
+ # options passed from query
26
+ # @return [RDF::Term]
27
+ # @raise [TypeError]
28
+ # @abstract
29
+ def aggregate(solutions = [], options = {})
30
+ sep = operands.length == 2 ? operand(0).last : RDF::Literal(' ')
31
+ args_enum = solutions.map do |solution|
32
+ begin
33
+ operands.last.evaluate(solution, options.merge(:depth => options[:depth].to_i + 1))
34
+ rescue TypeError
35
+ # Ignore errors
36
+ nil
37
+ end
38
+ end
39
+ apply(args_enum, sep)
40
+ end
41
+
42
+
43
+ ##
44
+ # 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.
45
+ #
46
+ # @param [Enumerable<Array<RDF::Term>>] enum
47
+ # enum of evaluated operand
48
+ # @return [RDF::Term] An arbitrary term
49
+ # @raise [TypeError] If enum is empty
50
+ def apply(enum, separator)
51
+ RDF::Literal(enum.flatten.map(&:to_s).join(separator.to_s))
52
+ end
53
+ end # GroupConcat
54
+ end # Operator
55
+ end; end # SPARQL::Algebra
@@ -29,14 +29,14 @@ module SPARQL; module Algebra
29
29
  #
30
30
  # Evaluates the first operand and returns the evaluation of either the second or third operands
31
31
  #
32
- # @param [RDF::Query::Solution, #[]] bindings
32
+ # @param [RDF::Query::Solution] bindings
33
33
  # a query solution containing zero or more variable bindings
34
34
  # @return [RDF::Term]
35
35
  # @raise [TypeError]
36
- def evaluate(bindings = {})
37
- operand(0).evaluate(bindings) == RDF::Literal::TRUE ?
38
- operand(1).evaluate(bindings) :
39
- operand(2).evaluate(bindings)
36
+ def evaluate(bindings, options = {})
37
+ operand(0).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1)) == RDF::Literal::TRUE ?
38
+ operand(1).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1).merge(:depth => options[:depth].to_i + 1)) :
39
+ operand(2).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))
40
40
  rescue
41
41
  raise TypeError
42
42
  end
@@ -34,15 +34,18 @@ module SPARQL; module Algebra
34
34
  # 2 IN (2, 1/0) #=> true
35
35
  # 2 IN (3, 1/0) #=> raises an error
36
36
  #
37
- # @param [RDF::Query::Solution, #[]] bindings
37
+ # @param [RDF::Query::Solution] bindings
38
+ # a query solution containing zero or more variable bindings
39
+ # @param [Hash{Symbol => Object}] options ({})
40
+ # options passed from query
38
41
  # @return [RDF::Literal::Boolean] `true` or `false`
39
42
  # @raise [TypeError] if term is not found and any operand raises an error
40
- def evaluate(bindings = {})
41
- lhs = operands.shift.evaluate(bindings)
43
+ def evaluate(bindings, options = {})
44
+ lhs = operands.shift.evaluate(bindings, options)
42
45
  error_found = false
43
46
  found = operands.any? do |op|
44
47
  begin
45
- lhs == op.evaluate(bindings)
48
+ lhs == op.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))
46
49
  rescue TypeError
47
50
  error_found = true
48
51
  end
@@ -0,0 +1,38 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `max` set function.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://www.example.org/>))
8
+ # (project (?max)
9
+ # (extend ((?max ?.0))
10
+ # (group () ((?.0 (max ?o)))
11
+ # (bgp (triple ?s ?p ?o))))))
12
+ #
13
+ # @see http://www.w3.org/TR/sparql11-query/#defn_aggMax
14
+ class Max < Operator::Unary
15
+ include Aggregate
16
+
17
+ NAME = :max
18
+
19
+ ##
20
+ # Max is a SPARQL set function that return the maximum value from a group respectively.
21
+ #
22
+ # It makes use of the SPARQL ORDER BY ordering definition, to allow ordering over arbitrarily typed expressions.
23
+ #
24
+ # @param [Enumerable<Array<RDF::Term>>] enum
25
+ # enum of evaluated operand
26
+ # @return [RDF::Literal] The maximum value of the terms
27
+ def apply(enum)
28
+ if enum.empty?
29
+ raise TypeError, "Maximum of an empty multiset"
30
+ elsif enum.flatten.all? {|n| n.literal?}
31
+ RDF::Literal(enum.flatten.max)
32
+ else
33
+ raise TypeError, "Maximum of non-literals: #{enum.flatten}"
34
+ end
35
+ end
36
+ end # Max
37
+ end # Operator
38
+ end; end # SPARQL::Algebra
@@ -0,0 +1,38 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `min` set function.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://www.example.org/>))
8
+ # (project (?max)
9
+ # (extend ((?min ?.0))
10
+ # (group () ((?.0 (min ?o)))
11
+ # (bgp (triple ?s ?p ?o))))))
12
+ #
13
+ # @see http://www.w3.org/TR/sparql11-query/#defn_aggMin
14
+ class Min < Operator::Unary
15
+ include Aggregate
16
+
17
+ NAME = :min
18
+
19
+ ##
20
+ # Min is a SPARQL set function that return the minimum value from a group respectively.
21
+ #
22
+ # It makes use of the SPARQL ORDER BY ordering definition, to allow ordering over arbitrarily typed expressions.
23
+ #
24
+ # @param [Enumerable<Array<RDF::Term>>] enum
25
+ # enum of evaluated operand
26
+ # @return [RDF::Literal] The maximum value of the terms
27
+ def apply(enum)
28
+ if enum.empty?
29
+ raise TypeError, "Minumuim of an empty multiset"
30
+ elsif enum.flatten.all? {|n| n.literal?}
31
+ RDF::Literal(enum.flatten.min)
32
+ else
33
+ raise TypeError, "Minumuim of non-literals: #{enum.flatten}"
34
+ end
35
+ end
36
+ end # Min
37
+ end # Operator
38
+ end; end # SPARQL::Algebra
@@ -1,30 +1,65 @@
1
1
  module SPARQL; module Algebra
2
2
  class Operator
3
3
  ##
4
- # The SPARQL numeric unary `-` (negation) operator.
4
+ # The SPARQL GraphPattern `minus` operator.
5
5
  #
6
6
  # @example
7
- # (- ?x)
8
- # (minus ?x)
7
+ # (prefix ((ex: <http://www.w3.org/2009/sparql/docs/tests/data-sparql11/negation#>))
8
+ # (project (?animal)
9
+ # (minus
10
+ # (bgp (triple ?animal <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> ex:Animal))
11
+ # (filter (|| (= ?type ex:Reptile) (= ?type ex:Insect))
12
+ # (bgp (triple ?animal <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> ?type))))))
9
13
  #
10
14
  # @see http://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus
11
- class Minus < Operator::Unary
12
- include Evaluatable
15
+ # @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
16
+ class Minus < Operator::Binary
17
+ include Query
13
18
 
14
- NAME = [:-, :minus]
19
+ NAME = :minus
15
20
 
16
21
  ##
17
- # Returns the operand with its sign reversed.
22
+ # Executes each operand with `queryable` and performs the `join` operation
23
+ # by creating a new solution set containing the `merge` of all solutions
24
+ # from each set that are `compatible` with each other.
18
25
  #
19
- # @param [RDF::Literal::Numeric] term
20
- # a numeric literal
21
- # @return [RDF::Literal::Numeric]
22
- # @raise [TypeError] if the operand is not a numeric literal
23
- def apply(term)
24
- case term
25
- when RDF::Literal::Numeric then -term
26
- else raise TypeError, "expected an RDF::Literal::Numeric, but got #{term.inspect}"
27
- end
26
+ # @param [RDF::Queryable] queryable
27
+ # the graph or repository to query
28
+ # @param [Hash{Symbol => Object}] options
29
+ # any additional keyword options
30
+ # @return [RDF::Query::Solutions]
31
+ # the resulting solution sequence
32
+ # @see http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#defn_algMinus
33
+ # @see http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#negation
34
+ def execute(queryable, options = {})
35
+ # Let Ω1 and Ω2 be multisets of solution mappings. We define:
36
+ #
37
+ # Minus(Ω1, Ω2) = { μ | μ in Ω1 . ∀ μ' in Ω2, either μ and μ' are not compatible or dom(μ) and dom(μ') are disjoint }
38
+ #
39
+ # card[Minus(Ω1, Ω2)](μ) = card[Ω1](μ)
40
+ debug(options) {"Minus"}
41
+ solutions1 = operand(0).execute(queryable, options.merge(:depth => options[:depth].to_i + 1)) || {}
42
+ debug(options) {"=>(left) #{solutions1.inspect}"}
43
+ solutions2 = operand(1).execute(queryable, options.merge(:depth => options[:depth].to_i + 1)) || {}
44
+ debug(options) {"=>(right) #{solutions2.inspect}"}
45
+ @solutions = solutions1.minus(solutions2)
46
+ debug(options) {"=> #{@solutions.inspect}"}
47
+ @solutions
48
+ end
49
+
50
+ ##
51
+ # Returns an optimized version of this query.
52
+ #
53
+ # Groups of one graph pattern (not a filter) become join(Z, A) and can be replaced by A.
54
+ # The empty graph pattern Z is the identity for join:
55
+ # Replace join(Z, A) by A
56
+ # Replace join(A, Z) by A
57
+ #
58
+ # @return [Join, RDF::Query] `self`
59
+ def optimize
60
+ ops = operands.map {|o| o.optimize }.select {|o| o.respond_to?(:empty?) && !o.empty?}
61
+ @operands = ops
62
+ self
28
63
  end
29
64
  end # Minus
30
65
  end # Operator
@@ -0,0 +1,31 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL numeric unary `-` (negation) operator.
5
+ #
6
+ # @example
7
+ # (- ?x)
8
+ # (negate ?x)
9
+ #
10
+ # @see http://www.w3.org/TR/xpath-functions/#func-numeric-unary-minus
11
+ class Negate < Operator::Unary
12
+ include Evaluatable
13
+
14
+ NAME = [:-, :negate]
15
+
16
+ ##
17
+ # Returns the operand with its sign reversed.
18
+ #
19
+ # @param [RDF::Literal::Numeric] term
20
+ # a numeric literal
21
+ # @return [RDF::Literal::Numeric]
22
+ # @raise [TypeError] if the operand is not a numeric literal
23
+ def apply(term)
24
+ case term
25
+ when RDF::Literal::Numeric then -term
26
+ else raise TypeError, "expected an RDF::Literal::Numeric, but got #{term.inspect}"
27
+ end
28
+ end
29
+ end # Negate
30
+ end # Operator
31
+ end; end # SPARQL::Algebra
@@ -0,0 +1,37 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL logical `exists` operator.
5
+ #
6
+ # @example
7
+ # (prefix ((ex: <http://www.example.org/>))
8
+ # (filter (exists
9
+ # (filter (notexists (bgp (triple ?s ?p ex:o2)))
10
+ # (bgp (triple ?s ?p ex:o1))))
11
+ # (bgp (triple ?s ?p ex:o))))
12
+ #
13
+ # @see http://www.w3.org/TR/sparql11-query/#func-abs
14
+ # @see http://www.w3.org/TR/xpath-functions/#func-abs
15
+ class NotExists < Operator::Unary
16
+ include Evaluatable
17
+
18
+ NAME = [:notexists]
19
+
20
+ ##
21
+ # Exvaluating this operator executes the query in the first operator passing in each existing bindings.
22
+ #
23
+ # @param [RDF::Query::Solution] bindings
24
+ # a query solution containing zero or more variable bindings
25
+ # @param [Hash{Symbol => Object}] options ({})
26
+ # options passed from query
27
+ # @option options[RDF::Queryable] queryable
28
+ # queryable to execute, using bindings as an initial solution.
29
+ # @return [RDF::Literal::Boolean] `true` or `false`
30
+ def evaluate(bindings, options = {})
31
+ solutions = RDF::Query::Solutions.new << bindings
32
+ queryable = options[:queryable]
33
+ operand(0).execute(queryable, options.merge(:solutions => solutions)).empty?
34
+ end
35
+ end # NotExists
36
+ end # Operator
37
+ end; end # SPARQL::Algebra
@@ -36,15 +36,18 @@ module SPARQL; module Algebra
36
36
  # 2 NOT IN (2, 1/0) false
37
37
  # 2 NOT IN (3, 1/0) raises an error
38
38
  #
39
- # @param [RDF::Query::Solution, #[]] bindings
39
+ # @param [RDF::Query::Solution] bindings
40
+ # a query solution containing zero or more variable bindings
41
+ # @param [Hash{Symbol => Object}] options ({})
42
+ # options passed from query
40
43
  # @return [RDF::Literal::Boolean] `true` or `false`
41
44
  # @raise [TypeError] if term is not found and any operand raises an error
42
- def evaluate(bindings = {})
43
- lhs = operands.shift.evaluate(bindings)
45
+ def evaluate(bindings, options = {})
46
+ lhs = operands.shift.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))
44
47
  error_found = false
45
48
  found = operands.any? do |op|
46
49
  begin
47
- lhs == op.evaluate(bindings)
50
+ lhs == op.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))
48
51
  rescue TypeError
49
52
  error_found = true
50
53
  end
@@ -34,18 +34,21 @@ module SPARQL; module Algebra
34
34
  # Note that this operator operates on the effective boolean value
35
35
  # (EBV) of its operands.
36
36
  #
37
- # @param [RDF::Query::Solution, #[]] bindings
37
+ # @param [RDF::Query::Solution] bindings
38
+ # a query solution containing zero or more variable bindings
39
+ # @param [Hash{Symbol => Object}] options ({})
40
+ # options passed from query
38
41
  # @return [RDF::Literal::Boolean] `true` or `false`
39
42
  # @raise [TypeError] if the operands could not be coerced to a boolean literal
40
- def evaluate(bindings = {})
43
+ def evaluate(bindings, options = {})
41
44
  begin
42
- left = boolean(operand(0).evaluate(bindings)).true?
45
+ left = boolean(operand(0).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))).true?
43
46
  rescue TypeError
44
47
  left = nil
45
48
  end
46
49
 
47
50
  begin
48
- right = boolean(operand(1).evaluate(bindings)).true?
51
+ right = boolean(operand(1).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))).true?
49
52
  rescue TypeError
50
53
  right = nil
51
54
  end
@@ -33,8 +33,8 @@ module SPARQL; module Algebra
33
33
  operand(0).inject(false) do |memo, op|
34
34
  debug(options) {"=> #{op.inspect}"}
35
35
  memo ||= begin
36
- a_eval = op.evaluate(a) rescue nil
37
- b_eval = op.evaluate(b) rescue nil
36
+ a_eval = op.evaluate(a, options.merge(:queryable => queryable, :depth => options[:depth].to_i + 1)) rescue nil
37
+ b_eval = op.evaluate(b, options.merge(:queryable => queryable, :depth => options[:depth].to_i + 1)) rescue nil
38
38
  comp = if a_eval.nil?
39
39
  RDF::Literal(-1)
40
40
  elsif b_eval.nil?
@@ -0,0 +1,32 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `sample` set function.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://www.example.org/>))
8
+ # (filter (|| (|| (= ?sample 1.0) (= ?sample 2.2)) (= ?sample 3.5))
9
+ # (project (?sample)
10
+ # (extend ((?sample ?.0))
11
+ # (group () ((?.0 (sample ?o)))
12
+ # (bgp (triple ?s :dec ?o)))))))
13
+ #
14
+ # @see http://www.w3.org/TR/sparql11-query/#defn_aggSample
15
+ class Sample < Operator::Unary
16
+ include Aggregate
17
+
18
+ NAME = :sample
19
+
20
+ ##
21
+ # Sample is a set function which returns an arbitrary value from the multiset passed to it.
22
+ #
23
+ # @param [Enumerable<Array<RDF::Term>>] enum
24
+ # enum of evaluated operand
25
+ # @return [RDF::Term] An arbitrary term
26
+ # @raise [TypeError] If enum is empty
27
+ def apply(enum)
28
+ enum.detect(lambda {raise TypeError, "Sampling an empty multiset"}) {|e| e.first}.first
29
+ end
30
+ end # LCase
31
+ end # Operator
32
+ end; end # SPARQL::Algebra