sparql 1.0.8 → 1.1.0p0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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