sparql 3.2.1 → 3.2.3
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 +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,69 @@
|
|
1
|
+
module SPARQL; module Algebra
|
2
|
+
class Operator
|
3
|
+
##
|
4
|
+
# The SPARQL `adjust` operator.
|
5
|
+
#
|
6
|
+
# [121] BuiltInCall ::= ... | 'ADJUST' '(' Expression ',' Expression ')'
|
7
|
+
#
|
8
|
+
# @example SPARQL Grammar
|
9
|
+
# PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
10
|
+
# SELECT ?id (ADJUST(?d, ?tz) AS ?adjusted) WHERE {
|
11
|
+
# VALUES (?id ?tz ?d) {
|
12
|
+
# (1 "-PT10H"^^xsd:dayTimeDuration "2002-03-07"^^xsd:date)
|
13
|
+
# }
|
14
|
+
# }
|
15
|
+
#
|
16
|
+
# @example SSE
|
17
|
+
# (prefix ((xsd: <http://www.w3.org/2001/XMLSchema#>))
|
18
|
+
# (project (?id ?adjusted)
|
19
|
+
# (extend ((?adjusted (adjust ?d ?tz)))
|
20
|
+
# (table (vars ?id ?tz ?d)
|
21
|
+
# (row
|
22
|
+
# (?id 1)
|
23
|
+
# (?tz "-PT10H"^^xsd:dayTimeDuration)
|
24
|
+
# (?d "2002-03-07"^^xsd:date))))))
|
25
|
+
#
|
26
|
+
# @see https://www.w3.org/TR/sparql11-query/#func-abs
|
27
|
+
# @see https://www.w3.org/TR/xpath-functions/#func-abs
|
28
|
+
class Adjust < Operator::Binary
|
29
|
+
include Evaluatable
|
30
|
+
|
31
|
+
NAME = [:adjust]
|
32
|
+
|
33
|
+
##
|
34
|
+
# Returns the first operand adjusted by the dayTimeDuration of the second operand
|
35
|
+
#
|
36
|
+
# @param [RDF::Literal::Temporal] operand
|
37
|
+
# the operand
|
38
|
+
# @param [RDF::Literal, String] duration
|
39
|
+
# the dayTimeDuration or an empty string.
|
40
|
+
# @return [RDF::Literal] literal of same type
|
41
|
+
# @raise [TypeError] if the operand is not a numeric value
|
42
|
+
def apply(operand, duration, **options)
|
43
|
+
case operand
|
44
|
+
when RDF::Literal::Temporal
|
45
|
+
case duration
|
46
|
+
when RDF::Literal::DayTimeDuration
|
47
|
+
operand.adjust_to_timezone(duration)
|
48
|
+
when RDF::Literal
|
49
|
+
raise TypeError, "expected second operand to be an empty literal, but got #{duration.inspect}" unless duration.to_s.empty?
|
50
|
+
operand.adjust_to_timezone(nil)
|
51
|
+
else
|
52
|
+
raise TypeError, "expected second operand to be an RDF::Literal::DayTimeDuration, but got #{duration.inspect}"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
raise TypeError, "expected first operand to be an RDF::Literal::Temporal, but got #{operand.inspect}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
#
|
61
|
+
# Returns a partial SPARQL grammar for this operator.
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
def to_sparql(**options)
|
65
|
+
"ADJUST(#{operands.to_sparql(delimiter: ', ', **options)})"
|
66
|
+
end
|
67
|
+
end # Abs
|
68
|
+
end # Operator
|
69
|
+
end; end # SPARQL::Algebra
|
@@ -19,7 +19,7 @@ module SPARQL; module Algebra
|
|
19
19
|
#
|
20
20
|
# @example SSE (sparql-star)
|
21
21
|
# (prefix ((: <http://example.com/ns#>))
|
22
|
-
# (bgp (triple (
|
22
|
+
# (bgp (triple (qtriple :a :b :c) :p1 :o1)))
|
23
23
|
#
|
24
24
|
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
25
25
|
class BGP < Operator
|
@@ -99,7 +99,7 @@ module SPARQL; module Algebra
|
|
99
99
|
# @return [String]
|
100
100
|
def to_sparql(**options)
|
101
101
|
str = "CONSTRUCT {\n" +
|
102
|
-
operands[0].map { |e| e.to_sparql(
|
102
|
+
operands[0].map { |e| e.to_sparql(top_level: false, **options) }.join(". \n") +
|
103
103
|
"\n}\n"
|
104
104
|
|
105
105
|
str << operands[1].to_sparql(top_level: true, project: nil, **options)
|
@@ -129,6 +129,11 @@ module SPARQL; module Algebra
|
|
129
129
|
#
|
130
130
|
# Datasets are specified in operand(1), which is an array of default or named graph URIs.
|
131
131
|
#
|
132
|
+
# If `options` contains any of the Protocol attributes, the dataset is constructed on creation, and these operations should be ignored:
|
133
|
+
#
|
134
|
+
# * `default-graph-uri`
|
135
|
+
# * `named-graph-uri`
|
136
|
+
#
|
132
137
|
# @param [RDF::Queryable] queryable
|
133
138
|
# the graph or repository to query
|
134
139
|
# @param [Hash{Symbol => Object}] options
|
@@ -142,6 +147,11 @@ module SPARQL; module Algebra
|
|
142
147
|
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
143
148
|
def execute(queryable, **options, &base)
|
144
149
|
debug(options) {"Dataset"}
|
150
|
+
if %i(default-graph-uri named-graph-uri).any? {|k| options.key?(k)}
|
151
|
+
debug("=> Skip constructing merge repo due to options", options)
|
152
|
+
return queryable.query(operands.last, depth: options[:depth].to_i + 1, **options, &base)
|
153
|
+
end
|
154
|
+
|
145
155
|
default_datasets = []
|
146
156
|
named_datasets = []
|
147
157
|
operand(0).each do |uri|
|
@@ -29,10 +29,10 @@ module SPARQL; module Algebra
|
|
29
29
|
#
|
30
30
|
# @param [RDF::Literal] operand
|
31
31
|
# the operand
|
32
|
-
# @return [RDF::Literal]
|
32
|
+
# @return [RDF::Literal::Temporal]
|
33
33
|
# @raise [TypeError] if the operand is not a simple literal
|
34
34
|
def apply(operand, **options)
|
35
|
-
raise TypeError, "expected an RDF::Literal::
|
35
|
+
raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
|
36
36
|
RDF::Literal(operand.object.day)
|
37
37
|
end
|
38
38
|
|
@@ -76,7 +76,7 @@ module SPARQL; module Algebra
|
|
76
76
|
# @return [String]
|
77
77
|
def to_sparql(**options)
|
78
78
|
"DELETE {\n" +
|
79
|
-
operands.first.to_sparql(
|
79
|
+
operands.first.to_sparql(delimiter: " .\n", **options) +
|
80
80
|
"\n}"
|
81
81
|
end
|
82
82
|
end # Delete
|
@@ -54,7 +54,7 @@ module SPARQL; module Algebra
|
|
54
54
|
# @return [String]
|
55
55
|
def to_sparql(**options)
|
56
56
|
"DELETE DATA {\n" +
|
57
|
-
operands.first.to_sparql(
|
57
|
+
operands.first.to_sparql(top_level: false, delimiter: ". \n", **options) +
|
58
58
|
"\n}"
|
59
59
|
end
|
60
60
|
end # DeleteData
|
@@ -71,7 +71,7 @@ module SPARQL; module Algebra
|
|
71
71
|
# @return [String]
|
72
72
|
def to_sparql(**options)
|
73
73
|
"DELETE WHERE {\n" +
|
74
|
-
operands.first.to_sparql(
|
74
|
+
operands.first.to_sparql(top_level: false, delimiter: ". \n", **options) +
|
75
75
|
"\n}"
|
76
76
|
end
|
77
77
|
end # DeleteWhere
|
@@ -104,17 +104,46 @@ module SPARQL; module Algebra
|
|
104
104
|
end
|
105
105
|
|
106
106
|
# The variable introduced by the BIND clause must not have been used in the group graph pattern up to the point of use in BIND
|
107
|
+
#
|
108
|
+
# Also, variables used in a binding expression must be projected by the query.
|
107
109
|
def validate!
|
108
110
|
bind_vars = operand(0).map(&:first).map(&:name)
|
109
|
-
query_vars = operand(1).
|
111
|
+
query_vars = operand(1).variables.keys
|
110
112
|
|
111
113
|
unless (bind_vars.compact & query_vars.compact).empty?
|
112
114
|
raise ArgumentError,
|
113
115
|
"bound variable used in query: #{(bind_vars.compact & query_vars.compact).to_sse}"
|
114
116
|
end
|
117
|
+
|
118
|
+
# Special case for group variables
|
119
|
+
if operands.last.is_a?(Group)
|
120
|
+
bind_expr_vars = operand(0).map(&:last).variables.keys
|
121
|
+
group_vars = operands.last.variables.keys
|
122
|
+
group_internal_vars = operands.last.internal_variables.keys
|
123
|
+
|
124
|
+
bind_expr_vars.each do |v|
|
125
|
+
raise ArgumentError,
|
126
|
+
"extension expression uses variable not in scope: #{v}" if
|
127
|
+
group_internal_vars.include?(v) &&
|
128
|
+
!group_vars.include?(v)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
115
132
|
super
|
116
133
|
end
|
117
134
|
|
135
|
+
##
|
136
|
+
# The variables used in the extension.
|
137
|
+
# Includes extended variables.
|
138
|
+
#
|
139
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
140
|
+
def variables
|
141
|
+
operands.first.
|
142
|
+
map(&:first).
|
143
|
+
map(&:variables).
|
144
|
+
inject(operands.last.variables) {|memo, h| memo.merge(h)}
|
145
|
+
end
|
146
|
+
|
118
147
|
##
|
119
148
|
#
|
120
149
|
# Returns a partial SPARQL grammar for this operator.
|
@@ -124,7 +153,8 @@ module SPARQL; module Algebra
|
|
124
153
|
# @return [String]
|
125
154
|
def to_sparql(**options)
|
126
155
|
extensions = operands.first.inject({}) do |memo, (as, expression)|
|
127
|
-
|
156
|
+
# Use string/name of variable "as" to aid in later matching
|
157
|
+
memo.merge(as.to_s => expression)
|
128
158
|
end
|
129
159
|
|
130
160
|
# Merge any inherited extensions from options
|
@@ -40,13 +40,13 @@ module SPARQL; module Algebra
|
|
40
40
|
# (group (?s) ((??.0 (avg ?o)))
|
41
41
|
# (bgp (triple ?s ?p ?o)))))) )
|
42
42
|
#
|
43
|
-
# @example SPARQL Grammar (non-
|
43
|
+
# @example SPARQL Grammar (non-trivial filters)
|
44
44
|
# PREFIX : <http://example.com/data/#>
|
45
45
|
# SELECT ?g (AVG(?p) AS ?avg) ((MIN(?p) + MAX(?p)) / 2 AS ?c)
|
46
46
|
# WHERE { ?g :p ?p . }
|
47
47
|
# GROUP BY ?g
|
48
48
|
#
|
49
|
-
# @example SSE (non-
|
49
|
+
# @example SSE (non-trivial filters)
|
50
50
|
# (prefix ((: <http://example.com/data/#>))
|
51
51
|
# (project (?g ?avg ?c)
|
52
52
|
# (extend ((?avg ??.0) (?c (/ (+ ??.1 ??.2) 2)))
|
@@ -162,11 +162,39 @@ module SPARQL; module Algebra
|
|
162
162
|
super
|
163
163
|
end
|
164
164
|
|
165
|
+
##
|
166
|
+
# The variables used in the extension.
|
167
|
+
# Includes grouped variables and temporary, but not those in the query, itself
|
168
|
+
#
|
169
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
170
|
+
def variables
|
171
|
+
group_vars = operands.first
|
172
|
+
|
173
|
+
aggregate_vars = (operands.length == 3 ? operand(1) : [])
|
174
|
+
|
175
|
+
# Extract first element of each and merge it's variables
|
176
|
+
(group_vars + aggregate_vars).
|
177
|
+
map do |o|
|
178
|
+
v = Array(o).first
|
179
|
+
v if v.is_a?(RDF::Query::Variable)
|
180
|
+
end.compact.
|
181
|
+
map(&:variables).
|
182
|
+
inject({}) {|memo, h| memo.merge(h)}
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# The variables used within the query
|
187
|
+
#
|
188
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
189
|
+
def internal_variables
|
190
|
+
operands.last.variables
|
191
|
+
end
|
192
|
+
|
165
193
|
##
|
166
194
|
#
|
167
195
|
# Returns a partial SPARQL grammar for this operator.
|
168
196
|
#
|
169
|
-
# @param [Hash{
|
197
|
+
# @param [Hash{String => Operator}] extensions
|
170
198
|
# Variable bindings
|
171
199
|
# @param [Array<Operator>] filter_ops ([])
|
172
200
|
# Filter Operations
|
@@ -188,12 +216,12 @@ module SPARQL; module Algebra
|
|
188
216
|
operand
|
189
217
|
end
|
190
218
|
end
|
191
|
-
memo.merge(ext_var => new_op)
|
219
|
+
memo.merge(ext_var.to_s => new_op)
|
192
220
|
elsif ext_op.is_a?(Variable) && ext_op.to_sym == var.to_sym
|
193
|
-
memo.merge(ext_var => op)
|
221
|
+
memo.merge(ext_var.to_s => op)
|
194
222
|
else
|
195
223
|
# Doesn't match this variable, so don't change
|
196
|
-
memo.merge(ext_var => ext_op)
|
224
|
+
memo.merge(ext_var.to_s => ext_op)
|
197
225
|
end
|
198
226
|
end
|
199
227
|
|
@@ -29,10 +29,10 @@ module SPARQL; module Algebra
|
|
29
29
|
#
|
30
30
|
# @param [RDF::Literal] operand
|
31
31
|
# the operand
|
32
|
-
# @return [RDF::Literal]
|
32
|
+
# @return [RDF::Literal::Temporal]
|
33
33
|
# @raise [TypeError] if the operand is not a simple literal
|
34
34
|
def apply(operand, **options)
|
35
|
-
raise TypeError, "expected an RDF::Literal::
|
35
|
+
raise TypeError, "expected an RDF::Literal::Temporal, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::Temporal)
|
36
36
|
RDF::Literal(operand.object.hour)
|
37
37
|
end
|
38
38
|
|
@@ -70,7 +70,7 @@ module SPARQL; module Algebra
|
|
70
70
|
# @return [String]
|
71
71
|
def to_sparql(**options)
|
72
72
|
"INSERT {\n" +
|
73
|
-
operands.first.to_sparql(
|
73
|
+
operands.first.to_sparql(delimiter: " .\n", **options) +
|
74
74
|
"\n}"
|
75
75
|
end
|
76
76
|
end # Insert
|
@@ -54,7 +54,7 @@ module SPARQL; module Algebra
|
|
54
54
|
# @return [String]
|
55
55
|
def to_sparql(**options)
|
56
56
|
"INSERT DATA {\n" +
|
57
|
-
operands.first.to_sparql(
|
57
|
+
operands.first.to_sparql(top_level: false, delimiter: ". \n", **options) +
|
58
58
|
"\n}"
|
59
59
|
end
|
60
60
|
end # InsertData
|
@@ -57,8 +57,8 @@ module SPARQL; module Algebra
|
|
57
57
|
# @return [RDF::Query::Solutions]
|
58
58
|
# the resulting solution sequence
|
59
59
|
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
60
|
-
# @see https://
|
61
|
-
# @see https://
|
60
|
+
# @see https://ruby-rdf.github.io/rdf/RDF/Query/Solution#merge-instance_method
|
61
|
+
# @see https://ruby-rdf.github.io/rdf/RDF/Query/Solution#compatible%3F-instance_method
|
62
62
|
def execute(queryable, **options, &block)
|
63
63
|
# Join(Ω1, Ω2) = { merge(μ1, μ2) | μ1 in Ω1 and μ2 in Ω2, and μ1 and μ2 are compatible }
|
64
64
|
# eval(D(G), Join(P1, P2)) = Join(eval(D(G), P1), eval(D(G), P2))
|
@@ -114,7 +114,7 @@ module SPARQL; module Algebra
|
|
114
114
|
#
|
115
115
|
# @param [Boolean] top_level (true)
|
116
116
|
# Treat this as a top-level, generating SELECT ... WHERE {}
|
117
|
-
# @param [Hash{
|
117
|
+
# @param [Hash{String => Operator}] extensions
|
118
118
|
# Variable bindings
|
119
119
|
# @param [Array<Operator>] filter_ops ([])
|
120
120
|
# Filter Operations
|
@@ -44,8 +44,8 @@ module SPARQL; module Algebra
|
|
44
44
|
# @return [RDF::Query::Solutions]
|
45
45
|
# the resulting solution sequence
|
46
46
|
# @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
|
47
|
-
# @see https://
|
48
|
-
# @see https://
|
47
|
+
# @see https://ruby-rdf.github.io/rdf/RDF/Query/Solution#merge-instance_method
|
48
|
+
# @see https://ruby-rdf.github.io/rdf/RDF/Query/Solution#compatible%3F-instance_method
|
49
49
|
def execute(queryable, **options, &block)
|
50
50
|
filter = operand(2)
|
51
51
|
|
@@ -131,7 +131,7 @@ module SPARQL; module Algebra
|
|
131
131
|
#
|
132
132
|
# @param [Boolean] top_level (true)
|
133
133
|
# Treat this as a top-level, generating SELECT ... WHERE {}
|
134
|
-
# @param [Hash{
|
134
|
+
# @param [Hash{String => Operator}] extensions
|
135
135
|
# Variable bindings
|
136
136
|
# @param [Array<Operator>] filter_ops ([])
|
137
137
|
# Filter Operations
|
@@ -100,7 +100,7 @@ module SPARQL; module Algebra
|
|
100
100
|
#
|
101
101
|
# Returns a partial SPARQL grammar for this operator.
|
102
102
|
#
|
103
|
-
# @param [Hash{
|
103
|
+
# @param [Hash{String => Operator}] extensions
|
104
104
|
# Variable bindings
|
105
105
|
# @param [Array<Operator>] filter_ops ([])
|
106
106
|
# Filter Operations
|
@@ -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.minute)
|
39
39
|
end
|
40
40
|
|
@@ -6,6 +6,11 @@ module SPARQL; module Algebra
|
|
6
6
|
#
|
7
7
|
# Wraps delete/insert
|
8
8
|
#
|
9
|
+
# If `options` contains any of the Protocol attributes, it is treated as if there is a USING or USING NAMED clause inserted.
|
10
|
+
#
|
11
|
+
# * `using-graph-uri`
|
12
|
+
# * `using-named-graph-uri`
|
13
|
+
#
|
9
14
|
# [41] Modify ::= ( 'WITH' iri )? ( DeleteClause InsertClause? | InsertClause ) UsingClause* 'WHERE' GroupGraphPattern
|
10
15
|
#
|
11
16
|
# @example SPARQL Grammar
|
@@ -35,6 +40,10 @@ module SPARQL; module Algebra
|
|
35
40
|
#
|
36
41
|
# Execute the first operand to get solutions, and apply those solutions to the subsequent operators.
|
37
42
|
#
|
43
|
+
# If `options` contains any of the Protocol attributes, any `using` clause is removed and a new `using` clause is added with entries taken from the `using-graph-uri` and `using-named-graph-uri`.
|
44
|
+
#
|
45
|
+
# It is an error to supply the using-graph-uri or using-named-graph-uri parameters when using this protocol to convey a SPARQL 1.1 Update request that contains an operation that uses the USING, USING NAMED, or WITH clause.
|
46
|
+
#
|
38
47
|
# @param [RDF::Queryable] queryable
|
39
48
|
# the graph or repository to write
|
40
49
|
# @param [Hash{Symbol => Object}] options
|
@@ -50,6 +59,18 @@ module SPARQL; module Algebra
|
|
50
59
|
debug(options) {"Modify"}
|
51
60
|
query = operands.shift
|
52
61
|
|
62
|
+
if %i(using-graph-uri using-named-graph-uri).any? {|k| options.key?(k)}
|
63
|
+
raise ArgumentError,
|
64
|
+
"query contains USING/WITH clause, which is incompatible with using-graph-uri or using-named-graph-uri query parameters" if
|
65
|
+
query.is_a?(Operator::Using) || query.is_a?(Operator::With)
|
66
|
+
|
67
|
+
debug("=> Insert USING clause", options)
|
68
|
+
defaults = Array(options.delete(:'using-graph-uri')).map {|uri| RDF::URI(uri)}
|
69
|
+
named = Array(options.delete(:'using-named-graph-uri')).map {|uri| [:named, RDF::URI(uri)]}
|
70
|
+
|
71
|
+
query = Operator::Using.new((defaults + named), query, **options)
|
72
|
+
end
|
73
|
+
|
53
74
|
queryable.query(query, depth: options[:depth].to_i + 1, **options) do |solution|
|
54
75
|
debug(options) {"(solution)=>#{solution.inspect}"}
|
55
76
|
|
@@ -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.month)
|
39
39
|
end
|
40
40
|
|
@@ -3,8 +3,8 @@ module SPARQL; module Algebra
|
|
3
3
|
##
|
4
4
|
# The SPARQL Property Path `path?` (ZeroOrOnePath) 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,11 +23,10 @@ module SPARQL; module Algebra
|
|
23
23
|
NAME = :path?
|
24
24
|
|
25
25
|
##
|
26
|
-
#
|
26
|
+
# Optional path:
|
27
27
|
#
|
28
28
|
# (path x (path? :p) y)
|
29
|
-
# => (union (bgp ((x :p y))) (filter (x =
|
30
|
-
#
|
29
|
+
# => (union (bgp ((x :p y))) (filter (x = y) (solution x y)))
|
31
30
|
#
|
32
31
|
# @param [RDF::Queryable] queryable
|
33
32
|
# the graph or repository to query
|
@@ -44,76 +43,21 @@ module SPARQL; module Algebra
|
|
44
43
|
subject, object = options[:subject], options[:object]
|
45
44
|
debug(options) {"Path? #{[subject, operands, object].to_sse}"}
|
46
45
|
|
47
|
-
|
48
|
-
|
49
|
-
case
|
50
|
-
when subject.variable? && object.variable?
|
51
|
-
# Nodes is the set of all subjects and objects in queryable
|
52
|
-
# FIXME: should this be Queryable#enum_nodes?
|
53
|
-
# All subjects which are `object`
|
54
|
-
query = RDF::Query.new {|q| q.pattern({subject: subject})}
|
55
|
-
queryable.query(query, **options) do |solution|
|
56
|
-
solution.merge!(object.to_sym => solution[subject])
|
57
|
-
debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
|
58
|
-
solutions << solution
|
59
|
-
end if query.valid?
|
60
|
-
|
61
|
-
# All objects which are `object`
|
62
|
-
query = RDF::Query.new {|q| q.pattern({object: object})}
|
63
|
-
queryable.query(query, **options) do |solution|
|
64
|
-
solution.merge!(subject.to_sym => solution[object])
|
65
|
-
debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
|
66
|
-
solutions << solution
|
67
|
-
end if query.valid?
|
68
|
-
when subject.variable?
|
69
|
-
# All subjects which are `object`
|
70
|
-
query = RDF::Query.new {|q| q.pattern({subject: object})}
|
71
|
-
queryable.query(query, **options) do |solution|
|
72
|
-
solution.merge!(subject.to_sym => object)
|
73
|
-
debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
|
74
|
-
solutions << solution
|
75
|
-
end if query.valid?
|
76
|
-
|
77
|
-
# All objects which are `object`
|
78
|
-
query = RDF::Query.new {|q| q.pattern({object: object})}
|
79
|
-
queryable.query(query, **options) do |solution|
|
80
|
-
solution.merge!(subject.to_sym => object)
|
81
|
-
debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
|
82
|
-
solutions << solution
|
83
|
-
end if query.valid?
|
84
|
-
when object.variable?
|
85
|
-
# All subjects which are `subject`
|
86
|
-
query = RDF::Query.new {|q| q.pattern({subject: subject})}
|
87
|
-
queryable.query(query, **options) do |solution|
|
88
|
-
solution.merge!(object.to_sym => subject)
|
89
|
-
debug(options) {"(solution-s0)-> #{solution.to_h.to_sse}"}
|
90
|
-
solutions << solution
|
91
|
-
end if query.valid?
|
92
|
-
|
93
|
-
# All objects which are `subject
|
94
|
-
query = RDF::Query.new {|q| q.pattern({object: subject})}
|
95
|
-
queryable.query(query, **options) do |solution|
|
96
|
-
solution.merge!(object.to_sym => subject)
|
97
|
-
debug(options) {"(solution-o0)-> #{solution.to_h.to_sse}"}
|
98
|
-
solutions << solution
|
99
|
-
end if query.valid?
|
100
|
-
else
|
101
|
-
# Otherwise, if subject == object, an empty solution
|
102
|
-
solutions << RDF::Query::Solution.new if subject == object
|
103
|
-
end
|
46
|
+
query = PathZero.new(operand)
|
47
|
+
solutions = query.execute(queryable, **options.merge(depth: options[:depth].to_i + 1))
|
104
48
|
|
105
49
|
# Solutions where predicate exists
|
106
50
|
query = if operand.is_a?(RDF::Term)
|
107
51
|
RDF::Query.new do |q|
|
108
52
|
q.pattern [subject, operand, object]
|
109
53
|
end
|
110
|
-
else
|
54
|
+
else # path
|
111
55
|
operand
|
112
56
|
end
|
113
57
|
|
114
58
|
# Recurse into query
|
115
|
-
solutions +=
|
116
|
-
|
59
|
+
solutions += query.execute(queryable, **options.merge(depth: options[:depth].to_i + 1))
|
60
|
+
debug(options) {"(path?)=> #{solutions.to_sxp}"}
|
117
61
|
solutions.each(&block) if block_given?
|
118
62
|
solutions
|
119
63
|
end
|
@@ -3,8 +3,8 @@ module SPARQL; module Algebra
|
|
3
3
|
##
|
4
4
|
# The SPARQL Property Path `path+` (OneOrMorePath) 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/>
|
@@ -25,6 +25,16 @@ module SPARQL; module Algebra
|
|
25
25
|
##
|
26
26
|
# Match on simple relation of subject to object, and then recurse on solutions
|
27
27
|
#
|
28
|
+
# Path including at least one:
|
29
|
+
#
|
30
|
+
# (path :a (path+ :p) :b)
|
31
|
+
#
|
32
|
+
# into
|
33
|
+
#
|
34
|
+
# (union
|
35
|
+
# (bgp (triple :a :p :b))
|
36
|
+
# (path :a (path* :p) :b))
|
37
|
+
#
|
28
38
|
# @param [RDF::Queryable] queryable
|
29
39
|
# the graph or repository to query
|
30
40
|
# @param [Hash{Symbol => Object}] options
|
@@ -62,10 +72,8 @@ module SPARQL; module Algebra
|
|
62
72
|
|
63
73
|
# Keep track of solutions
|
64
74
|
# Recurse into query
|
65
|
-
immediate_solutions =
|
66
|
-
|
67
|
-
immediate_solutions << solution
|
68
|
-
end
|
75
|
+
immediate_solutions =
|
76
|
+
query.execute(queryable, depth: options[:depth].to_i + 1, **options)
|
69
77
|
|
70
78
|
# For all solutions, if they are not in the accumulator, add them and recurse, otherwise skip
|
71
79
|
recursive_solutions = RDF::Query::Solutions.new
|
@@ -76,23 +84,23 @@ module SPARQL; module Algebra
|
|
76
84
|
case
|
77
85
|
when subject.variable? && object.variable?
|
78
86
|
# Query starting with bound object as subject, but replace result with subject
|
79
|
-
rs =
|
87
|
+
rs = self.execute(queryable, **options.merge(
|
80
88
|
subject: solution[object],
|
81
89
|
accumulator: (cumulative_solutions + immediate_solutions),
|
82
90
|
depth: options[:depth].to_i + 1)).map {|s| s.merge(subject.to_sym => solution[subject])}
|
83
91
|
# Query starting with bound subject as object, but replace result with subject
|
84
|
-
ro =
|
92
|
+
ro = self.execute(queryable, **options.merge(
|
85
93
|
object: solution[subject],
|
86
94
|
accumulator: (cumulative_solutions + immediate_solutions),
|
87
95
|
depth: options[:depth].to_i + 1)).map {|s| s.merge(object.to_sym => solution[object])}
|
88
96
|
recursive_solutions += (rs + ro).uniq
|
89
97
|
when subject.variable?
|
90
|
-
recursive_solutions +=
|
98
|
+
recursive_solutions += self.execute(queryable, **options.merge(
|
91
99
|
object: solution[subject],
|
92
100
|
accumulator: (cumulative_solutions + immediate_solutions),
|
93
101
|
depth: options[:depth].to_i + 1)).uniq
|
94
102
|
when object.variable?
|
95
|
-
recursive_solutions +=
|
103
|
+
recursive_solutions += self.execute(queryable, **options.merge(
|
96
104
|
subject: solution[object],
|
97
105
|
accumulator: (cumulative_solutions + immediate_solutions),
|
98
106
|
depth: options[:depth].to_i + 1)).uniq
|