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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +57 -38
  3. data/VERSION +1 -1
  4. data/bin/sparql +2 -31
  5. data/lib/rack/sparql/conneg.rb +22 -1
  6. data/lib/sinatra/sparql/extensions.rb +1 -1
  7. data/lib/sinatra/sparql.rb +57 -12
  8. data/lib/sparql/algebra/expression.rb +35 -7
  9. data/lib/sparql/algebra/extensions.rb +18 -18
  10. data/lib/sparql/algebra/operator/adjust.rb +69 -0
  11. data/lib/sparql/algebra/operator/bgp.rb +1 -1
  12. data/lib/sparql/algebra/operator/construct.rb +1 -1
  13. data/lib/sparql/algebra/operator/dataset.rb +10 -0
  14. data/lib/sparql/algebra/operator/day.rb +2 -2
  15. data/lib/sparql/algebra/operator/delete.rb +1 -1
  16. data/lib/sparql/algebra/operator/delete_data.rb +1 -1
  17. data/lib/sparql/algebra/operator/delete_where.rb +1 -1
  18. data/lib/sparql/algebra/operator/extend.rb +32 -2
  19. data/lib/sparql/algebra/operator/group.rb +34 -6
  20. data/lib/sparql/algebra/operator/hours.rb +2 -2
  21. data/lib/sparql/algebra/operator/insert.rb +1 -1
  22. data/lib/sparql/algebra/operator/insert_data.rb +1 -1
  23. data/lib/sparql/algebra/operator/join.rb +3 -3
  24. data/lib/sparql/algebra/operator/left_join.rb +3 -3
  25. data/lib/sparql/algebra/operator/minus.rb +1 -1
  26. data/lib/sparql/algebra/operator/minutes.rb +2 -2
  27. data/lib/sparql/algebra/operator/modify.rb +21 -0
  28. data/lib/sparql/algebra/operator/month.rb +2 -2
  29. data/lib/sparql/algebra/operator/path_opt.rb +9 -65
  30. data/lib/sparql/algebra/operator/path_plus.rb +18 -10
  31. data/lib/sparql/algebra/operator/path_range.rb +178 -0
  32. data/lib/sparql/algebra/operator/path_star.rb +7 -4
  33. data/lib/sparql/algebra/operator/path_zero.rb +110 -0
  34. data/lib/sparql/algebra/operator/plus.rb +7 -5
  35. data/lib/sparql/algebra/operator/project.rb +42 -1
  36. data/lib/sparql/algebra/operator/seconds.rb +2 -2
  37. data/lib/sparql/algebra/operator/seq.rb +3 -3
  38. data/lib/sparql/algebra/operator/sequence.rb +10 -0
  39. data/lib/sparql/algebra/operator/subtract.rb +9 -5
  40. data/lib/sparql/algebra/operator/table.rb +11 -2
  41. data/lib/sparql/algebra/operator/timezone.rb +2 -2
  42. data/lib/sparql/algebra/operator/triple.rb +51 -0
  43. data/lib/sparql/algebra/operator/tz.rb +2 -2
  44. data/lib/sparql/algebra/operator/using.rb +2 -2
  45. data/lib/sparql/algebra/operator/year.rb +2 -2
  46. data/lib/sparql/algebra/operator.rb +27 -10
  47. data/lib/sparql/algebra/query.rb +5 -3
  48. data/lib/sparql/algebra.rb +22 -3
  49. data/lib/sparql/grammar/meta.rb +1367 -267
  50. data/lib/sparql/grammar/parser11.rb +826 -328
  51. data/lib/sparql/grammar/terminals11.rb +2 -2
  52. data/lib/sparql/grammar.rb +6 -4
  53. data/lib/sparql/results.rb +3 -2
  54. data/lib/sparql/server.rb +93 -0
  55. data/lib/sparql.rb +8 -5
  56. 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 (triple :a :b :c) :p1 :o1)))
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(as_statement: true, top_level: false, **options) }.join(". \n") +
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::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
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(as_statement: true, delimiter: " .\n", **options) +
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(as_statement: true, top_level: false, delimiter: ". \n", **options) +
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(as_statement: true, top_level: false, delimiter: ". \n", **options) +
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).vars.map(&:name)
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
- memo.merge(as => expression)
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-triveal filters)
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-triveal filters)
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{Symbol => Operator}] extensions
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::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
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(as_statement: true, delimiter: " .\n", **options) +
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(as_statement: true, top_level: false, delimiter: ". \n", **options) +
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://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Solution#merge-instance_method
61
- # @see https://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Solution#compatible%3F-instance_method
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{Symbol => Operator}] extensions
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://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Solution#merge-instance_method
48
- # @see https://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Solution#compatible%3F-instance_method
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{Symbol => Operator}] extensions
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{Symbol => Operator}] extensions
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::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
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::DateTime, but got #{operand.inspect}" unless operand.is_a?(RDF::Literal::DateTime)
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 ::= PathPrimary PathMod?
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
- # Equivalent to:
26
+ # Optional path:
27
27
  #
28
28
  # (path x (path? :p) y)
29
- # => (union (bgp ((x :p y))) (filter (x = x) (solution x y)))
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
- solutions = RDF::Query::Solutions.new
48
- # Solutions where subject == object with no predicate
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
- queryable.query(query, depth: options[:depth].to_i + 1, **options)
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 ::= PathPrimary PathMod?
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
- queryable.query(query, depth: options[:depth].to_i + 1, **options) do |solution|
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 = queryable.query(self, **options.merge(
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 = queryable.query(self, **options.merge(
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 += queryable.query(self, **options.merge(
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 += queryable.query(self, **options.merge(
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