sparql 3.2.1 → 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +57 -38
- data/VERSION +1 -1
- data/bin/sparql +2 -31
- data/lib/rack/sparql/conneg.rb +22 -1
- data/lib/sinatra/sparql/extensions.rb +1 -1
- data/lib/sinatra/sparql.rb +57 -12
- data/lib/sparql/algebra/expression.rb +35 -7
- data/lib/sparql/algebra/extensions.rb +18 -18
- data/lib/sparql/algebra/operator/adjust.rb +69 -0
- data/lib/sparql/algebra/operator/bgp.rb +1 -1
- data/lib/sparql/algebra/operator/construct.rb +1 -1
- data/lib/sparql/algebra/operator/dataset.rb +10 -0
- data/lib/sparql/algebra/operator/day.rb +2 -2
- data/lib/sparql/algebra/operator/delete.rb +1 -1
- data/lib/sparql/algebra/operator/delete_data.rb +1 -1
- data/lib/sparql/algebra/operator/delete_where.rb +1 -1
- data/lib/sparql/algebra/operator/extend.rb +32 -2
- data/lib/sparql/algebra/operator/group.rb +34 -6
- data/lib/sparql/algebra/operator/hours.rb +2 -2
- data/lib/sparql/algebra/operator/insert.rb +1 -1
- data/lib/sparql/algebra/operator/insert_data.rb +1 -1
- data/lib/sparql/algebra/operator/join.rb +3 -3
- data/lib/sparql/algebra/operator/left_join.rb +3 -3
- data/lib/sparql/algebra/operator/minus.rb +1 -1
- data/lib/sparql/algebra/operator/minutes.rb +2 -2
- data/lib/sparql/algebra/operator/modify.rb +21 -0
- data/lib/sparql/algebra/operator/month.rb +2 -2
- data/lib/sparql/algebra/operator/path_opt.rb +9 -65
- data/lib/sparql/algebra/operator/path_plus.rb +18 -10
- data/lib/sparql/algebra/operator/path_range.rb +178 -0
- data/lib/sparql/algebra/operator/path_star.rb +7 -4
- data/lib/sparql/algebra/operator/path_zero.rb +110 -0
- data/lib/sparql/algebra/operator/plus.rb +7 -5
- data/lib/sparql/algebra/operator/project.rb +42 -1
- data/lib/sparql/algebra/operator/seconds.rb +2 -2
- data/lib/sparql/algebra/operator/seq.rb +3 -3
- data/lib/sparql/algebra/operator/sequence.rb +10 -0
- data/lib/sparql/algebra/operator/subtract.rb +9 -5
- data/lib/sparql/algebra/operator/table.rb +11 -2
- data/lib/sparql/algebra/operator/timezone.rb +2 -2
- data/lib/sparql/algebra/operator/triple.rb +51 -0
- data/lib/sparql/algebra/operator/tz.rb +2 -2
- data/lib/sparql/algebra/operator/using.rb +2 -2
- data/lib/sparql/algebra/operator/year.rb +2 -2
- data/lib/sparql/algebra/operator.rb +27 -10
- data/lib/sparql/algebra/query.rb +5 -3
- data/lib/sparql/algebra.rb +22 -3
- data/lib/sparql/grammar/meta.rb +1367 -267
- data/lib/sparql/grammar/parser11.rb +826 -328
- data/lib/sparql/grammar/terminals11.rb +2 -2
- data/lib/sparql/grammar.rb +6 -4
- data/lib/sparql/results.rb +3 -2
- data/lib/sparql/server.rb +93 -0
- data/lib/sparql.rb +8 -5
- metadata +39 -17
@@ -0,0 +1,178 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL Property Path `pathRange` (NonCountingPath) operator.
|
5
|
+
#
|
6
|
+
# Property path ranges allow specific path lenghts to be queried. The minimum range describes the minimum path length that will yield solutions, and the maximum range the maximum path that will be returned. A minumum of zero is similar to the `path?` operator. The maximum range of `*` is similar to the `path*` or `path+` operators.
|
7
|
+
#
|
8
|
+
# For example, the two queries are functionally equivalent:
|
9
|
+
#
|
10
|
+
# SELECT * WHERE {:a :p{1,2} :b}
|
11
|
+
#
|
12
|
+
# SELECT * WHERE {:a (:p/:p?) :b}
|
13
|
+
#
|
14
|
+
# [91] PathElt ::= PathPrimary PathMod?
|
15
|
+
# [93] PathMod ::= '*' | '?' | '+' | '{' INTEGER? (',' INTEGER?)? '}'
|
16
|
+
#
|
17
|
+
# @example SPARQL Grammar range with fixed length
|
18
|
+
# PREFIX : <http://example/>
|
19
|
+
# SELECT * WHERE {
|
20
|
+
# :a :p{2} ?z
|
21
|
+
# }
|
22
|
+
#
|
23
|
+
# @example SSE range with fixed length only
|
24
|
+
# (prefix ((: <http://example/>))
|
25
|
+
# (path :a (pathRange 2 2 :p) ?z))
|
26
|
+
#
|
27
|
+
# @example SPARQL Grammar range with min only
|
28
|
+
# PREFIX : <http://example/>
|
29
|
+
# SELECT * WHERE {
|
30
|
+
# :a :p{1,} ?z
|
31
|
+
# }
|
32
|
+
#
|
33
|
+
# @example SSE range with min only
|
34
|
+
# (prefix ((: <http://example/>))
|
35
|
+
# (path :a (pathRange 1 * :p) ?z))
|
36
|
+
#
|
37
|
+
# @example SPARQL Grammar range with max only
|
38
|
+
# PREFIX : <http://example/>
|
39
|
+
# SELECT * WHERE {
|
40
|
+
# :a :p{,2} ?z
|
41
|
+
# }
|
42
|
+
#
|
43
|
+
# @example SSE range with max only
|
44
|
+
# (prefix ((: <http://example/>))
|
45
|
+
# (path :a (pathRange 0 2 :p) ?z))
|
46
|
+
#
|
47
|
+
# @example SPARQL Grammar range with min and max
|
48
|
+
# PREFIX : <http://example/>
|
49
|
+
# SELECT * WHERE {
|
50
|
+
# :a :p{1,2} ?z
|
51
|
+
# }
|
52
|
+
#
|
53
|
+
# @example SSE range with min and max
|
54
|
+
# (prefix ((: <http://example/>))
|
55
|
+
# (path :a (pathRange 1 2 :p) ?z))
|
56
|
+
#
|
57
|
+
class PathRange < Operator::Ternary
|
58
|
+
include Query
|
59
|
+
|
60
|
+
NAME = :"pathRange"
|
61
|
+
|
62
|
+
##
|
63
|
+
# Initializes a new operator instance.
|
64
|
+
#
|
65
|
+
# @param [RDF::Literal::Integer] max
|
66
|
+
# the range minimum
|
67
|
+
# @param [RDF::Literal::Integer, Symbol] min
|
68
|
+
# the range maximum (may be `*`)
|
69
|
+
# @param [SPARQL::Operator] path
|
70
|
+
# the query
|
71
|
+
# @param [Hash{Symbol => Object}] options
|
72
|
+
# any additional options (see {Operator#initialize})
|
73
|
+
# @raise [TypeError] if any operand is invalid
|
74
|
+
# @raise [ArgumentError] range element is invalid
|
75
|
+
def initialize(min, max, path, **options)
|
76
|
+
raise ArgumentError, "expect min <= max {#{min},#{max}}" if
|
77
|
+
max.is_a?(RDF::Literal::Integer) && max < min
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Path with lower and upper bounds on lenghts:
|
83
|
+
#
|
84
|
+
# (path :a (pathRange 1 2 :p) :b)
|
85
|
+
# => (path)
|
86
|
+
#
|
87
|
+
# @param [RDF::Queryable] queryable
|
88
|
+
# the graph or repository to query
|
89
|
+
# @param [RDF::Query::Solutions] accumulator (RDF::Query::Solutions.new)
|
90
|
+
# For previous solutions to avoid duplicates.
|
91
|
+
# @param [Hash{Symbol => Object}] options
|
92
|
+
# any additional keyword options
|
93
|
+
# @option options [RDF::Term, RDF::Variable] :subject
|
94
|
+
# @option options [RDF::Term, RDF::Variable] :object
|
95
|
+
# @yield [solution]
|
96
|
+
# each matching solution
|
97
|
+
# @yieldparam [RDF::Query::Solution] solution
|
98
|
+
# @yieldreturn [void] ignored
|
99
|
+
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
100
|
+
def execute(queryable,
|
101
|
+
accumulator: RDF::Query::Solutions.new,
|
102
|
+
index: RDF::Literal(0),
|
103
|
+
**options,
|
104
|
+
&block)
|
105
|
+
|
106
|
+
min, max, op = *operands
|
107
|
+
subject, object = options[:subject], options[:object]
|
108
|
+
debug(options) {"Path{#{min},#{max}} #{[subject, op, object].to_sse}"}
|
109
|
+
|
110
|
+
path_mm = if min.zero? && max.is_a?(RDF::Literal::Integer) && max.zero?
|
111
|
+
PathZero.new(op, **options)
|
112
|
+
else
|
113
|
+
# Build up a sequence
|
114
|
+
path_opt = nil
|
115
|
+
seq_len = min.to_i
|
116
|
+
if max.is_a?(RDF::Literal::Integer)
|
117
|
+
# min to max is a sequence of optional sequences
|
118
|
+
opt_len = (max - min).to_i
|
119
|
+
while opt_len > 0 do
|
120
|
+
path_opt = PathOpt.new(path_opt ? Seq.new(op, path_opt, **options) : op, **options)
|
121
|
+
opt_len -= 1
|
122
|
+
end
|
123
|
+
elsif seq_len > 0
|
124
|
+
path_opt = PathPlus.new(op, **options)
|
125
|
+
seq_len -= 1
|
126
|
+
else
|
127
|
+
path_opt = PathStar.new(op, **options)
|
128
|
+
end
|
129
|
+
|
130
|
+
# sequence ending in op, op+, op*, or path_opt
|
131
|
+
path_seq = nil
|
132
|
+
while seq_len > 0 do
|
133
|
+
path_seq = if path_opt
|
134
|
+
opt, path_opt = path_opt, nil
|
135
|
+
Seq.new(op, opt, **options)
|
136
|
+
elsif path_seq
|
137
|
+
Seq.new(op, path_seq, **options)
|
138
|
+
else
|
139
|
+
op
|
140
|
+
end
|
141
|
+
seq_len -= 1
|
142
|
+
end
|
143
|
+
path_seq || path_opt || op
|
144
|
+
end
|
145
|
+
debug(options) {"=> #{path_mm.to_sse}"}
|
146
|
+
|
147
|
+
# After this, path_mm may just be the original op, which can be a term, not an operator.
|
148
|
+
if path_mm.is_a?(RDF::Term)
|
149
|
+
path_mm = RDF::Query.new do |q|
|
150
|
+
q.pattern [subject, path_mm, object]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
solutions = path_mm.execute(queryable, **options.merge(depth: options[:depth].to_i + 1)).uniq
|
155
|
+
debug(options) {"(path{#{min},#{max}})=> #{solutions.to_sxp}"}
|
156
|
+
solutions.each(&block) if block_given?
|
157
|
+
solutions
|
158
|
+
end
|
159
|
+
|
160
|
+
##
|
161
|
+
#
|
162
|
+
# Returns a partial SPARQL grammar for this operator.
|
163
|
+
#
|
164
|
+
# @return [String]
|
165
|
+
def to_sparql(**options)
|
166
|
+
min, max, path = operands
|
167
|
+
"(#{path.to_sparql(**options)})" +
|
168
|
+
if max == :*
|
169
|
+
"{#{min},}"
|
170
|
+
elsif min == max
|
171
|
+
"{#{min}}"
|
172
|
+
else
|
173
|
+
"{#{min},#{max}}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end # PathStar
|
177
|
+
end # Operator
|
178
|
+
end; end # SPARQL::Algebra
|
@@ -3,8 +3,8 @@ module SPARQL; module Algebra
|
|
3
3
|
##
|
4
4
|
# The SPARQL Property Path `path*` (ZeroOrMorePath) operator.
|
5
5
|
#
|
6
|
-
# [91] PathElt
|
7
|
-
# [93] PathMod
|
6
|
+
# [91] PathElt ::= PathPrimary PathMod?
|
7
|
+
# [93] PathMod ::= '*' | '?' | '+' | '{' INTEGER? (',' INTEGER?)? '}'
|
8
8
|
|
9
9
|
# @example SPARQL Grammar
|
10
10
|
# PREFIX : <http://example/>
|
@@ -23,10 +23,13 @@ module SPARQL; module Algebra
|
|
23
23
|
NAME = :"path*"
|
24
24
|
|
25
25
|
##
|
26
|
-
# Path including zero length:
|
26
|
+
# Path including at zero length:
|
27
27
|
#
|
28
28
|
# (path :a (path* :p) :b)
|
29
|
-
#
|
29
|
+
#
|
30
|
+
# into
|
31
|
+
#
|
32
|
+
# (path :a (path? (path+ :p)) :b)
|
30
33
|
#
|
31
34
|
# @param [RDF::Queryable] queryable
|
32
35
|
# the graph or repository to query
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL Property Path `path0` (ZeroLengthPath) operator.
|
5
|
+
#
|
6
|
+
# A zero length path matches all subjects and objects in the graph, and also any RDF terms explicitly given as endpoints of the path pattern.
|
7
|
+
#
|
8
|
+
# @see https://www.w3.org/TR/sparql11-query/#defn_evalPP_ZeroOrOnePath
|
9
|
+
class PathZero < Operator::Unary
|
10
|
+
include Query
|
11
|
+
|
12
|
+
NAME = :path0
|
13
|
+
|
14
|
+
##
|
15
|
+
# Zero length path:
|
16
|
+
#
|
17
|
+
# (path x (path0 :p) y)
|
18
|
+
# => (filter (x = y) (solution x y))
|
19
|
+
#
|
20
|
+
# @param [RDF::Queryable] queryable
|
21
|
+
# the graph or repository to query
|
22
|
+
# @param [Hash{Symbol => Object}] options
|
23
|
+
# any additional keyword options
|
24
|
+
# @option options [RDF::Term, RDF::Variable] :subject
|
25
|
+
# @option options [RDF::Term, RDF::Variable] :object
|
26
|
+
# @yield [solution]
|
27
|
+
# each matching solution
|
28
|
+
# @yieldparam [RDF::Query::Solution] solution
|
29
|
+
# @yieldreturn [void] ignored
|
30
|
+
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
31
|
+
def execute(queryable, **options, &block)
|
32
|
+
subject, object = options[:subject], options[:object]
|
33
|
+
debug(options) {"PathZero #{[subject, operands, object].to_sse}"}
|
34
|
+
|
35
|
+
solutions = RDF::Query::Solutions.new
|
36
|
+
# The zero-length case implies subject == object.
|
37
|
+
case
|
38
|
+
when subject.variable? && object.variable?
|
39
|
+
# Nodes is the set of all subjects and objects in queryable
|
40
|
+
query = RDF::Query.new {|q| q.pattern({subject: subject})}
|
41
|
+
query.execute(queryable, **options) do |solution|
|
42
|
+
solution.merge!(object.to_sym => solution[subject])
|
43
|
+
unless solutions.include?(solution)
|
44
|
+
#debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
|
45
|
+
solutions << solution
|
46
|
+
end
|
47
|
+
end if query.valid?
|
48
|
+
|
49
|
+
# All objects which are `object`
|
50
|
+
query = RDF::Query.new {|q| q.pattern({object: object})}
|
51
|
+
query.execute(queryable, **options) do |solution|
|
52
|
+
solution.merge!(subject.to_sym => solution[object])
|
53
|
+
unless solutions.include?(solution)
|
54
|
+
#debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
|
55
|
+
solutions << solution
|
56
|
+
end
|
57
|
+
end if query.valid?
|
58
|
+
when subject.variable?
|
59
|
+
# All subjects which are `object`
|
60
|
+
query = RDF::Query.new {|q| q.pattern({subject: object})}
|
61
|
+
query.execute(queryable, **options) do |solution|
|
62
|
+
solution.merge!(subject.to_sym => object)
|
63
|
+
unless solutions.include?(solution)
|
64
|
+
#debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
|
65
|
+
solutions << solution
|
66
|
+
end
|
67
|
+
end if query.valid?
|
68
|
+
|
69
|
+
# All objects which are `object`
|
70
|
+
query = RDF::Query.new {|q| q.pattern({object: object})}
|
71
|
+
query.execute(queryable, **options) do |solution|
|
72
|
+
solution.merge!(subject.to_sym => object)
|
73
|
+
unless solutions.include?(solution)
|
74
|
+
#debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
|
75
|
+
solutions << solution
|
76
|
+
end
|
77
|
+
end if query.valid?
|
78
|
+
when object.variable?
|
79
|
+
# All subjects which are `subject`
|
80
|
+
query = RDF::Query.new {|q| q.pattern({subject: subject})}
|
81
|
+
query.execute(queryable, **options) do |solution|
|
82
|
+
solution.merge!(object.to_sym => subject)
|
83
|
+
unless solutions.include?(solution)
|
84
|
+
#debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
|
85
|
+
solutions << solution
|
86
|
+
end
|
87
|
+
end if query.valid?
|
88
|
+
|
89
|
+
# All objects which are `subject`
|
90
|
+
query = RDF::Query.new {|q| q.pattern({object: subject})}
|
91
|
+
query.execute(queryable, **options) do |solution|
|
92
|
+
solution.merge!(object.to_sym => subject)
|
93
|
+
unless solutions.include?(solution)
|
94
|
+
#debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
|
95
|
+
solutions << solution
|
96
|
+
end
|
97
|
+
end if query.valid?
|
98
|
+
else
|
99
|
+
# Otherwise, if subject == object, an empty solution
|
100
|
+
solutions << RDF::Query::Solution.new if subject == object
|
101
|
+
end
|
102
|
+
|
103
|
+
solutions.uniq!
|
104
|
+
debug(options) {"(path0)=> #{solutions.to_sxp}"}
|
105
|
+
solutions.each(&block) if block_given?
|
106
|
+
solutions
|
107
|
+
end
|
108
|
+
end # PathZero
|
109
|
+
end # Operator
|
110
|
+
end; end # SPARQL::Algebra
|
@@ -47,19 +47,21 @@ module SPARQL; module Algebra
|
|
47
47
|
##
|
48
48
|
# Returns the arithmetic sum of the operands, unless there is no `right`.
|
49
49
|
#
|
50
|
-
# @param [RDF::Literal::Numeric] left
|
50
|
+
# @param [RDF::Literal::Numeric, RDF::Literal::Temporal] left
|
51
51
|
# a numeric literal
|
52
|
-
# @param [RDF::Literal::Numeric] right
|
52
|
+
# @param [RDF::Literal::Numeric, RDF::Literal::Duration] right
|
53
53
|
# a numeric literal
|
54
|
-
# @return [RDF::Literal::Numeric]
|
55
|
-
# @raise [TypeError] if either operand is
|
54
|
+
# @return [RDF::Literal::Numeric, RDF::Literal::Temporal]
|
55
|
+
# @raise [TypeError] if either operand is neither a numeric nor a temporal literal
|
56
56
|
def apply(left, right = nil, **options)
|
57
57
|
case
|
58
58
|
when left.is_a?(RDF::Literal::Numeric) && right.is_a?(RDF::Literal::Numeric)
|
59
59
|
left + right
|
60
60
|
when left.is_a?(RDF::Literal::Numeric) && right.nil?
|
61
61
|
left
|
62
|
-
|
62
|
+
when left.is_a?(RDF::Literal::Temporal) && right.is_a?(RDF::Literal::Duration)
|
63
|
+
left + right
|
64
|
+
else raise TypeError, "expected two RDF::Literal::Numeric operands or a Temporal and a Duration, but got #{left.inspect} and #{right.inspect}"
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
@@ -57,10 +57,38 @@ module SPARQL; module Algebra
|
|
57
57
|
|
58
58
|
NAME = [:project]
|
59
59
|
|
60
|
+
##
|
61
|
+
# Can only project in-scope variables.
|
62
|
+
#
|
63
|
+
# @return (see Algebra::Operator#initialize)
|
64
|
+
def validate!
|
65
|
+
if (group = descendants.detect {|o| o.is_a?(Group)})
|
66
|
+
raise ArgumentError, "project * on group is illegal" if operands.first.empty?
|
67
|
+
query_vars = operands.last.variables
|
68
|
+
variables.keys.each do |v|
|
69
|
+
raise ArgumentError,
|
70
|
+
"projecting #{v.to_sse} not projected from group" unless
|
71
|
+
query_vars.key?(v.to_sym)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
##
|
79
|
+
# The projected variables.
|
80
|
+
#
|
81
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
82
|
+
def variables
|
83
|
+
operands(1).inject({}) {|hash, o| hash.merge(o.variables)}
|
84
|
+
end
|
85
|
+
|
60
86
|
##
|
61
87
|
# Executes this query on the given `queryable` graph or repository.
|
62
88
|
# Reduces the result set to the variables listed in the first operand
|
63
89
|
#
|
90
|
+
# If the first operand is empty, this indicates a `SPARQL *`, and all in-scope variables are projected.
|
91
|
+
#
|
64
92
|
# @param [RDF::Queryable] queryable
|
65
93
|
# the graph or repository to query
|
66
94
|
# @param [Hash{Symbol => Object}] options
|
@@ -74,10 +102,23 @@ module SPARQL; module Algebra
|
|
74
102
|
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
75
103
|
def execute(queryable, **options, &block)
|
76
104
|
@solutions = queryable.query(operands.last, depth: options[:depth].to_i + 1, **options)
|
77
|
-
@solutions =
|
105
|
+
@solutions.variable_names = self.variables.keys
|
106
|
+
@solutions = @solutions.project(*(operands.first)) unless operands.first.empty?
|
78
107
|
@solutions.each(&block) if block_given?
|
79
108
|
@solutions
|
80
109
|
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# In-scope variables for a select are limited to those projected.
|
113
|
+
#
|
114
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
115
|
+
def variables
|
116
|
+
in_scope = operands.first.empty? ?
|
117
|
+
operands.last.variables.values :
|
118
|
+
operands.first
|
119
|
+
|
120
|
+
in_scope.inject({}) {|memo, v| memo.merge(v.variables)}
|
121
|
+
end
|
81
122
|
|
82
123
|
##
|
83
124
|
#
|
@@ -31,10 +31,10 @@ module SPARQL; module Algebra
|
|
31
31
|
#
|
32
32
|
# @param [RDF::Literal] operand
|
33
33
|
# the operand
|
34
|
-
# @return [RDF::Literal]
|
34
|
+
# @return [RDF::Literal::Temporal]
|
35
35
|
# @raise [TypeError] if the operand is not a simple literal
|
36
36
|
def apply(operand, **options)
|
37
|
-
raise TypeError, "expected an RDF::Literal::
|
37
|
+
raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
|
38
38
|
RDF::Literal(operand.object.second)
|
39
39
|
end
|
40
40
|
|
@@ -59,10 +59,10 @@ module SPARQL; module Algebra
|
|
59
59
|
end
|
60
60
|
|
61
61
|
left = queryable.query(q1, **options.merge(object: v, depth: options[:depth].to_i + 1))
|
62
|
-
debug(options) {"(seq)=>(left) #{left.map(&:to_h).to_sse}"}
|
62
|
+
#debug(options) {"(seq)=>(left) #{left.map(&:to_h).to_sse}"}
|
63
63
|
|
64
64
|
right = queryable.query(q2, **options.merge(subject: v, depth: options[:depth].to_i + 1))
|
65
|
-
debug(options) {"(seq)=>(right) #{right.map(&:to_h).to_sse}"}
|
65
|
+
#debug(options) {"(seq)=>(right) #{right.map(&:to_h).to_sse}"}
|
66
66
|
|
67
67
|
@solutions = RDF::Query::Solutions(left.map do |s1|
|
68
68
|
right.map do |s2|
|
@@ -72,7 +72,7 @@ module SPARQL; module Algebra
|
|
72
72
|
solution.bindings.delete(v.to_sym)
|
73
73
|
solution
|
74
74
|
end
|
75
|
-
debug(options) {"(seq)=> #{@solutions.
|
75
|
+
debug(options) {"(seq)=> #{@solutions.to_sxp}"}
|
76
76
|
@solutions.each(&block) if block_given?
|
77
77
|
@solutions
|
78
78
|
end
|
@@ -51,6 +51,16 @@ module SPARQL; module Algebra
|
|
51
51
|
@solutions.each(&block) if block_given?
|
52
52
|
@solutions
|
53
53
|
end
|
54
|
+
|
55
|
+
##
|
56
|
+
#
|
57
|
+
# Returns a partial SPARQL grammar for this operator.
|
58
|
+
#
|
59
|
+
# @return [String]
|
60
|
+
def to_sparql(**options)
|
61
|
+
str = "{\n" + operands.to_sparql(top_level: false, **options) + "\n}"
|
62
|
+
Operator.to_sparql(str, **options)
|
63
|
+
end
|
54
64
|
end # Sequence
|
55
65
|
end # Operator
|
56
66
|
end; end # SPARQL::Algebra
|
@@ -31,17 +31,21 @@ module SPARQL; module Algebra
|
|
31
31
|
##
|
32
32
|
# Returns the arithmetic difference of the operands.
|
33
33
|
#
|
34
|
-
# @param [RDF::Literal::Numeric] left
|
34
|
+
# @param [RDF::Literal::Numeric, RDF::Literal::Temporal] left
|
35
35
|
# a numeric literal
|
36
|
-
# @param [RDF::Literal::Numeric] right
|
36
|
+
# @param [RDF::Literal::Numeric, RDF::Literal::Temporal, RDF::Literal::Duration] right
|
37
37
|
# a numeric literal
|
38
|
-
# @return [RDF::Literal::Numeric]
|
39
|
-
# @raise [TypeError] if either operand is
|
38
|
+
# @return [RDF::Literal::Numeric, RDF::Literal::Temporal, RDF::Literal::Duration]
|
39
|
+
# @raise [TypeError] if either operand is neither a numeric nor a temporal literal
|
40
40
|
def apply(left, right, **options)
|
41
41
|
case
|
42
42
|
when left.is_a?(RDF::Literal::Numeric) && right.is_a?(RDF::Literal::Numeric)
|
43
43
|
left - right
|
44
|
-
|
44
|
+
when left.is_a?(RDF::Literal::Temporal) && right.is_a?(RDF::Literal::Temporal)
|
45
|
+
left - right
|
46
|
+
when left.is_a?(RDF::Literal::Temporal) && right.is_a?(RDF::Literal::Duration)
|
47
|
+
left - right
|
48
|
+
else raise TypeError, "expected two RDF::Literal::Numeric, Temporal operands, or a Temporal and a Duration, but got #{left.inspect} and #{right.inspect}"
|
45
49
|
end
|
46
50
|
end
|
47
51
|
|
@@ -89,9 +89,19 @@ module SPARQL; module Algebra
|
|
89
89
|
end
|
90
90
|
@solutions << RDF::Query::Solution.new(bindings)
|
91
91
|
end
|
92
|
+
@solutions.variable_names = self.variables.keys
|
92
93
|
@solutions.each(&block) if block_given?
|
93
94
|
@solutions
|
94
95
|
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# In-scope variables for a table are the variables operand
|
99
|
+
#
|
100
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
101
|
+
def variables
|
102
|
+
in_scope = operands.first.is_a?(Array) ? operands.first[1..-1] : []
|
103
|
+
in_scope.inject({}) {|memo, v| memo.merge(v.variables)}
|
104
|
+
end
|
95
105
|
|
96
106
|
##
|
97
107
|
#
|
@@ -105,8 +115,7 @@ module SPARQL; module Algebra
|
|
105
115
|
operands[1..-1].each do |row|
|
106
116
|
line = '('
|
107
117
|
row[1..-1].each do |col|
|
108
|
-
v = col[1].to_sparql(
|
109
|
-
v = "<< #{v} >>" if col[1].is_a?(RDF::Statement)
|
118
|
+
v = col[1].to_sparql(**options)
|
110
119
|
line << v + ' '
|
111
120
|
end
|
112
121
|
line = line.chomp(' ')
|
@@ -33,10 +33,10 @@ module SPARQL; module Algebra
|
|
33
33
|
#
|
34
34
|
# @param [RDF::Literal] operand
|
35
35
|
# the operand
|
36
|
-
# @return [RDF::Literal]
|
36
|
+
# @return [RDF::Literal::Temporal]
|
37
37
|
# @raise [TypeError] if the operand is not a simple literal
|
38
38
|
def apply(operand, **options)
|
39
|
-
raise TypeError, "expected an RDF::Literal::
|
39
|
+
raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
|
40
40
|
raise TypeError, "literal has no timezone" unless res = operand.timezone
|
41
41
|
res
|
42
42
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL `triple` operator.
|
5
|
+
#
|
6
|
+
# If the 3-tuple (term1, term2, term3) is an RDF-star triple, the function returns this triple. If the 3-tuple is not an RDF-star triple, then the function raises an error.
|
7
|
+
#
|
8
|
+
# [121] BuiltInCall ::= ... | 'TRIPLE' '(' Expression ',' Expression ',' Expression ')'
|
9
|
+
#
|
10
|
+
# @example SPARQL Grammar
|
11
|
+
# PREFIX : <http://example.com/ns#>
|
12
|
+
# SELECT * {
|
13
|
+
# ?s ?p ?o .
|
14
|
+
# BIND(TRIPLE(?s, ?p, ?o) AS ?t1)
|
15
|
+
# }
|
16
|
+
#
|
17
|
+
# @example SSE
|
18
|
+
# (prefix ((: <http://example.com/ns#>))
|
19
|
+
# (extend ((?t1 (triple ?s ?p ?o)))
|
20
|
+
# (bgp (triple ?s ?p ?o))))
|
21
|
+
#
|
22
|
+
# @note This operator overlaps with RDF::Query::Pattern as used as an operand to a BGP.
|
23
|
+
# @see https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#triple
|
24
|
+
class Triple < Operator::Ternary
|
25
|
+
include Evaluatable
|
26
|
+
|
27
|
+
NAME = :triple
|
28
|
+
|
29
|
+
##
|
30
|
+
# @param [RDF::Term] subject
|
31
|
+
# @param [RDF::Term] predicate
|
32
|
+
# @param [RDF::Term] object
|
33
|
+
# @return [RDF::URI]
|
34
|
+
# @raise [TypeError] if the operand is not a simple literal
|
35
|
+
def apply(subject, predicate, object, **options)
|
36
|
+
triple = RDF::Statement(subject, predicate, object)
|
37
|
+
raise TypeError, "valid components, but got #{triple.inspect}" unless triple.valid?
|
38
|
+
triple
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
#
|
43
|
+
# Returns a partial SPARQL grammar for this operator.
|
44
|
+
#
|
45
|
+
# @return [String]
|
46
|
+
def to_sparql(**options)
|
47
|
+
"TRIPLE(#{operands.to_sparql(delimiter: ', ', **options)})"
|
48
|
+
end
|
49
|
+
end # Triple
|
50
|
+
end # Operator
|
51
|
+
end; end # SPARQL::Algebra
|
@@ -29,12 +29,12 @@ module SPARQL; module Algebra
|
|
29
29
|
##
|
30
30
|
# Returns the timezone part of arg as a simple literal. Returns the empty string if there is no timezone.
|
31
31
|
#
|
32
|
-
# @param [RDF::Literal] operand
|
32
|
+
# @param [RDF::Literal::Temporal] operand
|
33
33
|
# the operand
|
34
34
|
# @return [RDF::Literal]
|
35
35
|
# @raise [TypeError] if the operand is not a simple literal
|
36
36
|
def apply(operand, **options)
|
37
|
-
raise TypeError, "expected an RDF::Literal::
|
37
|
+
raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
|
38
38
|
operand.tz
|
39
39
|
end
|
40
40
|
##
|
@@ -39,8 +39,8 @@ module SPARQL; module Algebra
|
|
39
39
|
# @example SSE (multiple clauses)
|
40
40
|
# (prefix ((: <http://example.org/>))
|
41
41
|
# (update
|
42
|
-
# (modify
|
43
|
-
# (bgp (triple ?s ?p ?o)))
|
42
|
+
# (modify
|
43
|
+
# (using (:g1 :g2) (bgp (triple ?s ?p ?o)))
|
44
44
|
# (insert ((triple ?s ?p "q"))))))
|
45
45
|
#
|
46
46
|
# @see https://www.w3.org/TR/sparql11-update/#add
|
@@ -31,10 +31,10 @@ module SPARQL; module Algebra
|
|
31
31
|
#
|
32
32
|
# @param [RDF::Literal] operand
|
33
33
|
# the operand
|
34
|
-
# @return [RDF::Literal]
|
34
|
+
# @return [RDF::Literal::Temporal]
|
35
35
|
# @raise [TypeError] if the operand is not a simple literal
|
36
36
|
def apply(operand, **options)
|
37
|
-
raise TypeError, "expected an RDF::Literal::
|
37
|
+
raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
|
38
38
|
RDF::Literal(operand.object.year)
|
39
39
|
end
|
40
40
|
|