sparql 1.0.8 → 1.1.0p0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|