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.
- checksums.yaml +14 -6
- data/README.md +57 -25
- data/VERSION +1 -1
- data/lib/sinatra/sparql.rb +5 -3
- data/lib/sparql/algebra/aggregate.rb +67 -0
- data/lib/sparql/algebra/evaluatable.rb +49 -4
- data/lib/sparql/algebra/expression.rb +6 -4
- data/lib/sparql/algebra/extensions.rb +99 -9
- data/lib/sparql/algebra/operator/and.rb +7 -4
- data/lib/sparql/algebra/operator/asc.rb +6 -3
- data/lib/sparql/algebra/operator/avg.rb +36 -0
- data/lib/sparql/algebra/operator/bnode.rb +5 -4
- data/lib/sparql/algebra/operator/bound.rb +5 -2
- data/lib/sparql/algebra/operator/coalesce.rb +6 -3
- data/lib/sparql/algebra/operator/concat.rb +6 -3
- data/lib/sparql/algebra/operator/count.rb +30 -0
- data/lib/sparql/algebra/operator/exists.rb +39 -0
- data/lib/sparql/algebra/operator/exprlist.rb +6 -3
- data/lib/sparql/algebra/operator/extend.rb +3 -1
- data/lib/sparql/algebra/operator/filter.rb +2 -1
- data/lib/sparql/algebra/operator/group.rb +101 -0
- data/lib/sparql/algebra/operator/group_concat.rb +55 -0
- data/lib/sparql/algebra/operator/if.rb +5 -5
- data/lib/sparql/algebra/operator/in.rb +7 -4
- data/lib/sparql/algebra/operator/max.rb +38 -0
- data/lib/sparql/algebra/operator/min.rb +38 -0
- data/lib/sparql/algebra/operator/minus.rb +51 -16
- data/lib/sparql/algebra/operator/negate.rb +31 -0
- data/lib/sparql/algebra/operator/notexists.rb +37 -0
- data/lib/sparql/algebra/operator/notin.rb +7 -4
- data/lib/sparql/algebra/operator/or.rb +7 -4
- data/lib/sparql/algebra/operator/order.rb +2 -2
- data/lib/sparql/algebra/operator/sample.rb +32 -0
- data/lib/sparql/algebra/operator/strlang.rb +1 -1
- data/lib/sparql/algebra/operator/sum.rb +36 -0
- data/lib/sparql/algebra/operator/table.rb +44 -0
- data/lib/sparql/algebra/operator.rb +37 -2
- data/lib/sparql/algebra.rb +1 -0
- data/lib/sparql/grammar/meta.rb +1143 -696
- data/lib/sparql/grammar/parser11.rb +178 -32
- data/lib/sparql/grammar/terminals11.rb +2 -2
- data/lib/sparql/grammar.rb +0 -2
- data/lib/sparql/results.rb +55 -0
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4
|
+
# The SPARQL GraphPattern `minus` operator.
|
|
5
5
|
#
|
|
6
6
|
# @example
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
|
|
12
|
-
|
|
15
|
+
# @see http://www.w3.org/TR/rdf-sparql-query/#sparqlAlgebra
|
|
16
|
+
class Minus < Operator::Binary
|
|
17
|
+
include Query
|
|
13
18
|
|
|
14
|
-
NAME =
|
|
19
|
+
NAME = :minus
|
|
15
20
|
|
|
16
21
|
##
|
|
17
|
-
#
|
|
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::
|
|
20
|
-
#
|
|
21
|
-
# @
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
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
|
|
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
|