sparql 3.2.0 → 3.2.4

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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -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 +63 -10
  9. data/lib/sparql/algebra/extensions.rb +39 -35
  10. data/lib/sparql/algebra/operator/abs.rb +1 -1
  11. data/lib/sparql/algebra/operator/adjust.rb +69 -0
  12. data/lib/sparql/algebra/operator/alt.rb +1 -1
  13. data/lib/sparql/algebra/operator/avg.rb +3 -1
  14. data/lib/sparql/algebra/operator/bgp.rb +9 -1
  15. data/lib/sparql/algebra/operator/clear.rb +13 -3
  16. data/lib/sparql/algebra/operator/construct.rb +1 -1
  17. data/lib/sparql/algebra/operator/count.rb +36 -6
  18. data/lib/sparql/algebra/operator/create.rb +5 -4
  19. data/lib/sparql/algebra/operator/dataset.rb +29 -11
  20. data/lib/sparql/algebra/operator/day.rb +2 -2
  21. data/lib/sparql/algebra/operator/delete.rb +3 -1
  22. data/lib/sparql/algebra/operator/delete_data.rb +1 -1
  23. data/lib/sparql/algebra/operator/delete_where.rb +1 -1
  24. data/lib/sparql/algebra/operator/distinct.rb +2 -2
  25. data/lib/sparql/algebra/operator/divide.rb +1 -1
  26. data/lib/sparql/algebra/operator/drop.rb +15 -6
  27. data/lib/sparql/algebra/operator/encode_for_uri.rb +2 -4
  28. data/lib/sparql/algebra/operator/exprlist.rb +3 -1
  29. data/lib/sparql/algebra/operator/extend.rb +73 -5
  30. data/lib/sparql/algebra/operator/filter.rb +6 -1
  31. data/lib/sparql/algebra/operator/function_call.rb +64 -0
  32. data/lib/sparql/algebra/operator/graph.rb +57 -7
  33. data/lib/sparql/algebra/operator/group.rb +105 -6
  34. data/lib/sparql/algebra/operator/group_concat.rb +25 -1
  35. data/lib/sparql/algebra/operator/hours.rb +2 -2
  36. data/lib/sparql/algebra/operator/if.rb +10 -10
  37. data/lib/sparql/algebra/operator/insert.rb +3 -1
  38. data/lib/sparql/algebra/operator/insert_data.rb +1 -1
  39. data/lib/sparql/algebra/operator/is_blank.rb +1 -2
  40. data/lib/sparql/algebra/operator/is_iri.rb +1 -2
  41. data/lib/sparql/algebra/operator/is_literal.rb +1 -2
  42. data/lib/sparql/algebra/operator/is_numeric.rb +1 -2
  43. data/lib/sparql/algebra/operator/join.rb +39 -5
  44. data/lib/sparql/algebra/operator/lcase.rb +2 -3
  45. data/lib/sparql/algebra/operator/left_join.rb +27 -9
  46. data/lib/sparql/algebra/operator/max.rb +3 -1
  47. data/lib/sparql/algebra/operator/min.rb +4 -2
  48. data/lib/sparql/algebra/operator/minus.rb +46 -6
  49. data/lib/sparql/algebra/operator/minutes.rb +2 -2
  50. data/lib/sparql/algebra/operator/modify.rb +21 -0
  51. data/lib/sparql/algebra/operator/month.rb +2 -2
  52. data/lib/sparql/algebra/operator/multiply.rb +1 -1
  53. data/lib/sparql/algebra/operator/notoneof.rb +12 -3
  54. data/lib/sparql/algebra/operator/order.rb +44 -0
  55. data/lib/sparql/algebra/operator/path_opt.rb +9 -65
  56. data/lib/sparql/algebra/operator/path_plus.rb +18 -10
  57. data/lib/sparql/algebra/operator/path_range.rb +178 -0
  58. data/lib/sparql/algebra/operator/path_star.rb +7 -4
  59. data/lib/sparql/algebra/operator/path_zero.rb +110 -0
  60. data/lib/sparql/algebra/operator/plus.rb +8 -6
  61. data/lib/sparql/algebra/operator/project.rb +64 -5
  62. data/lib/sparql/algebra/operator/reduced.rb +3 -3
  63. data/lib/sparql/algebra/operator/regex.rb +1 -1
  64. data/lib/sparql/algebra/operator/reverse.rb +12 -1
  65. data/lib/sparql/algebra/operator/sample.rb +3 -1
  66. data/lib/sparql/algebra/operator/seconds.rb +2 -2
  67. data/lib/sparql/algebra/operator/seq.rb +4 -4
  68. data/lib/sparql/algebra/operator/sequence.rb +14 -1
  69. data/lib/sparql/algebra/operator/service.rb +86 -0
  70. data/lib/sparql/algebra/operator/strlang.rb +1 -2
  71. data/lib/sparql/algebra/operator/subtract.rb +10 -6
  72. data/lib/sparql/algebra/operator/sum.rb +9 -7
  73. data/lib/sparql/algebra/operator/table.rb +50 -7
  74. data/lib/sparql/algebra/operator/timezone.rb +2 -2
  75. data/lib/sparql/algebra/operator/triple.rb +51 -0
  76. data/lib/sparql/algebra/operator/tz.rb +2 -2
  77. data/lib/sparql/algebra/operator/ucase.rb +1 -1
  78. data/lib/sparql/algebra/operator/update.rb +22 -1
  79. data/lib/sparql/algebra/operator/using.rb +18 -1
  80. data/lib/sparql/algebra/operator/with.rb +1 -1
  81. data/lib/sparql/algebra/operator/year.rb +2 -2
  82. data/lib/sparql/algebra/operator.rb +69 -22
  83. data/lib/sparql/algebra/query.rb +5 -3
  84. data/lib/sparql/algebra.rb +42 -6
  85. data/lib/sparql/grammar/meta.rb +1367 -267
  86. data/lib/sparql/grammar/parser11.rb +842 -331
  87. data/lib/sparql/grammar/terminals11.rb +2 -2
  88. data/lib/sparql/grammar.rb +6 -4
  89. data/lib/sparql/results.rb +3 -2
  90. data/lib/sparql/server.rb +93 -0
  91. data/lib/sparql.rb +8 -5
  92. metadata +49 -13
@@ -10,14 +10,47 @@ module SPARQL; module Algebra
10
10
  # @example SPARQL Grammar
11
11
  # SELECT ?z
12
12
  # {
13
- # ?x <http://example/p> ?o
14
- # BIND(?o+1 AS ?z)
13
+ # ?x <http://example.org/p> ?o
14
+ # BIND(?o+10 AS ?z)
15
15
  # }
16
16
  #
17
17
  # @example SSE
18
18
  # (project (?z)
19
19
  # (extend ((?z (+ ?o 10)))
20
- # (bgp (triple ?s <http://example/p> ?o))))
20
+ # (bgp (triple ?x <http://example.org/p> ?o))))
21
+ #
22
+ # @example SPARQL Grammar (cast as boolean)
23
+ # PREFIX : <http://example.org/>
24
+ # PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
25
+ # PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
26
+ # SELECT ?a ?v (xsd:boolean(?v) AS ?boolean)
27
+ # WHERE { ?a :p ?v . }
28
+ #
29
+ # @example SSE (cast as boolean)
30
+ # (prefix ((: <http://example.org/>)
31
+ # (rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>)
32
+ # (xsd: <http://www.w3.org/2001/XMLSchema#>))
33
+ # (project (?a ?v ?boolean)
34
+ # (extend ((?boolean (xsd:boolean ?v)))
35
+ # (bgp (triple ?a :p ?v)))))
36
+ #
37
+ # @example SPARQL Grammar (inner bind)
38
+ # PREFIX : <http://example.org/>
39
+ #
40
+ # SELECT ?z ?s1
41
+ # {
42
+ # ?s ?p ?o .
43
+ # BIND(?o+1 AS ?z)
44
+ # ?s1 ?p1 ?z
45
+ # }
46
+ #
47
+ # @example SSE (inner bind)
48
+ # (prefix ((: <http://example.org/>))
49
+ # (project (?z ?s1)
50
+ # (join
51
+ # (extend ((?z (+ ?o 1)))
52
+ # (bgp (triple ?s ?p ?o)))
53
+ # (bgp (triple ?s1 ?p1 ?z)))))
21
54
  #
22
55
  # @see https://www.w3.org/TR/sparql11-query/#evaluation
23
56
  class Extend < Operator::Binary
@@ -51,6 +84,11 @@ module SPARQL; module Algebra
51
84
  debug(options) {"Extend"}
52
85
  @solutions = operand(1).execute(queryable, depth: options[:depth].to_i + 1, **options)
53
86
  @solutions.each do |solution|
87
+ # Re-bind to bindings, if defined, as they might not be found in solution
88
+ options[:bindings].each_binding do |name, value|
89
+ solution[name] = value if operands.first.variables.include?(name)
90
+ end if options[:bindings] && operands.first.respond_to?(:variables)
91
+
54
92
  debug(options) {"===> soln #{solution.to_h.inspect}"}
55
93
  operand(0).each do |(var, expr)|
56
94
  begin
@@ -71,17 +109,46 @@ module SPARQL; module Algebra
71
109
  end
72
110
 
73
111
  # 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
112
+ #
113
+ # Also, variables used in a binding expression must be projected by the query.
74
114
  def validate!
75
115
  bind_vars = operand(0).map(&:first).map(&:name)
76
- query_vars = operand(1).vars.map(&:name)
116
+ query_vars = operand(1).variables.keys
77
117
 
78
118
  unless (bind_vars.compact & query_vars.compact).empty?
79
119
  raise ArgumentError,
80
120
  "bound variable used in query: #{(bind_vars.compact & query_vars.compact).to_sse}"
81
121
  end
122
+
123
+ # Special case for group variables
124
+ if operands.last.is_a?(Group)
125
+ bind_expr_vars = operand(0).map(&:last).variables.keys
126
+ group_vars = operands.last.variables.keys
127
+ group_internal_vars = operands.last.internal_variables.keys
128
+
129
+ bind_expr_vars.each do |v|
130
+ raise ArgumentError,
131
+ "extension expression uses variable not in scope: #{v}" if
132
+ group_internal_vars.include?(v) &&
133
+ !group_vars.include?(v)
134
+ end
135
+ end
136
+
82
137
  super
83
138
  end
84
139
 
140
+ ##
141
+ # The variables used in the extension.
142
+ # Includes extended variables.
143
+ #
144
+ # @return [Hash{Symbol => RDF::Query::Variable}]
145
+ def variables
146
+ operands.first.
147
+ map(&:first).
148
+ map(&:variables).
149
+ inject(operands.last.variables) {|memo, h| memo.merge(h)}
150
+ end
151
+
85
152
  ##
86
153
  #
87
154
  # Returns a partial SPARQL grammar for this operator.
@@ -91,7 +158,8 @@ module SPARQL; module Algebra
91
158
  # @return [String]
92
159
  def to_sparql(**options)
93
160
  extensions = operands.first.inject({}) do |memo, (as, expression)|
94
- memo.merge(as => expression)
161
+ # Use string/name of variable "as" to aid in later matching
162
+ memo.merge(as.to_s => expression)
95
163
  end
96
164
 
97
165
  # Merge any inherited extensions from options
@@ -49,6 +49,11 @@ module SPARQL; module Algebra
49
49
  opts = options.merge(queryable: queryable, depth: options[:depth].to_i + 1)
50
50
  @solutions = RDF::Query::Solutions()
51
51
  queryable.query(operands.last, depth: options[:depth].to_i + 1, **options) do |solution|
52
+ # Re-bind to bindings, if defined, as they might not be found in solution
53
+ options[:bindings].each_binding do |name, value|
54
+ solution[name] ||= value if operands.first.variables.include?(name)
55
+ end if options[:bindings] && operands.first.respond_to?(:variables)
56
+
52
57
  begin
53
58
  pass = boolean(operands.first.evaluate(solution, **opts)).true?
54
59
  debug(options) {"(filter) #{pass.inspect} #{solution.to_h.inspect}"}
@@ -89,7 +94,7 @@ module SPARQL; module Algebra
89
94
  # @return [String]
90
95
  def to_sparql(**options)
91
96
  filter_ops = operands.first.is_a?(Operator::Exprlist) ? operands.first.operands : [operands.first]
92
- operands.last.to_sparql(filter_ops: filter_ops, **options)
97
+ str = operands.last.to_sparql(filter_ops: filter_ops, **options)
93
98
  end
94
99
  end # Filter
95
100
  end # Operator
@@ -0,0 +1,64 @@
1
+
2
+ module SPARQL; module Algebra
3
+ class Operator
4
+ ##
5
+ # The SPARQL `function_call` operator.
6
+ #
7
+ # [70] FunctionCall ::= iri ArgList
8
+ #
9
+ # @example SPARQL Grammar
10
+ # PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
11
+ # SELECT *
12
+ # WHERE { ?s ?p ?o . FILTER xsd:integer(?o) }
13
+ #
14
+ # @example SSE
15
+ # (prefix
16
+ # ((xsd: <http://www.w3.org/2001/XMLSchema#>))
17
+ # (filter (xsd:integer ?o)
18
+ # (bgp (triple ?s ?p ?o))))
19
+ #
20
+ # @see https://www.w3.org/TR/sparql11-query/#funcex-regex
21
+ # @see https://www.w3.org/TR/xpath-functions/#func-matches
22
+ class FunctionCall < Operator
23
+ include Evaluatable
24
+
25
+ NAME = :function_call
26
+
27
+ ##
28
+ # Invokes the function with the passed arguments.
29
+ #
30
+ # @param [RDF::IRI] iri
31
+ # Identifies the function
32
+ # @param [Array<RDF::Term>] args
33
+ # @return [RDF::Term]
34
+ def apply(iri, *args, **options)
35
+ args = RDF.nil == args.last ? args[0..-2] : args
36
+ SPARQL::Algebra::Expression.extension(iri, *args, **options)
37
+ end
38
+
39
+ ##
40
+ # Returns the SPARQL S-Expression (SSE) representation of this expression.
41
+ #
42
+ # Remove the optional argument.
43
+ #
44
+ # @return [Array] `self`
45
+ # @see https://openjena.org/wiki/SSE
46
+ def to_sxp_bin
47
+ @operands.map(&:to_sxp_bin)
48
+ end
49
+
50
+ ##
51
+ #
52
+ # Returns a partial SPARQL grammar for this operator.
53
+ #
54
+ # @return [String]
55
+ def to_sparql(**options)
56
+ iri, args = operands
57
+ iri.to_sparql(**options) +
58
+ '(' +
59
+ args.to_sparql(delimiter: ', ', **options) +
60
+ ')'
61
+ end
62
+ end # FunctionCall
63
+ end # Operator
64
+ end; end # SPARQL::Algebra
@@ -7,7 +7,7 @@ module SPARQL; module Algebra
7
7
  #
8
8
  # [58] GraphGraphPattern ::= 'GRAPH' VarOrIri GroupGraphPattern
9
9
  #
10
- # @example SPARQL Grammar
10
+ # @example SPARQL Grammar (query)
11
11
  # PREFIX : <http://example/>
12
12
  # SELECT * {
13
13
  # GRAPH ?g { ?s ?p ?o }
@@ -18,15 +18,50 @@ module SPARQL; module Algebra
18
18
  # (graph ?g
19
19
  # (bgp (triple ?s ?p ?o))))
20
20
  #
21
- # @example of a query
21
+ # @example SPARQL Grammar (named set of statements)
22
+ # PREFIX : <http://example/>
23
+ # SELECT * {
24
+ # GRAPH :g { :s :p :o }
25
+ # }
26
+ #
27
+ # @example SSE (named set of statements)
22
28
  # (prefix ((: <http://example/>))
29
+ # (graph :g
30
+ # (bgp (triple :s :p :o))))
31
+ #
32
+ # @example SPARQL Grammar (syntax-graph-05.rq)
33
+ # PREFIX : <http://example.org/>
34
+ # SELECT *
35
+ # WHERE
36
+ # {
37
+ # :x :p :z
38
+ # GRAPH ?g { :x :b ?a . GRAPH ?g2 { :x :p ?x } }
39
+ # }
40
+ #
41
+ # @example SSE (syntax-graph-05.rq)
42
+ # (prefix ((: <http://example.org/>))
43
+ # (join
44
+ # (bgp (triple :x :p :z))
23
45
  # (graph ?g
24
- # (bgp (triple ?s ?p ?o))))
46
+ # (join
47
+ # (bgp (triple :x :b ?a))
48
+ # (graph ?g2
49
+ # (bgp (triple :x :p ?x)))))))
25
50
  #
26
- # @example named set of statements
27
- # (prefix ((: <http://example/>))
28
- # (graph :g
29
- # ((triple :s :p :o))))
51
+ # @example SPARQL Grammar (pp06.rq)
52
+ # prefix ex: <http://www.example.org/schema#>
53
+ # prefix in: <http://www.example.org/instance#>
54
+ #
55
+ # select ?x where {
56
+ # graph ?g {in:a ex:p1/ex:p2 ?x}
57
+ # }
58
+ #
59
+ # @example SSE (syntax-graph-05.rq)
60
+ # (prefix ((ex: <http://www.example.org/schema#>)
61
+ # (in: <http://www.example.org/instance#>))
62
+ # (project (?x)
63
+ # (graph ?g
64
+ # (path in:a (seq ex:p1 ex:p2) ?x))))
30
65
  #
31
66
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
32
67
  class Graph < Operator::Binary
@@ -89,6 +124,21 @@ module SPARQL; module Algebra
89
124
  def rewrite(&block)
90
125
  self
91
126
  end
127
+
128
+ ##
129
+ #
130
+ # Returns a partial SPARQL grammar for this operator.
131
+ #
132
+ # @param [Boolean] top_level (true)
133
+ # Treat this as a top-level, generating SELECT ... WHERE {}
134
+ # @return [String]
135
+ def to_sparql(top_level: true, **options)
136
+ query = operands.last.to_sparql(top_level: false, **options)
137
+ # Paths don't automatically get braces.
138
+ query = "{\n#{query}\n}" unless query.start_with?('{')
139
+ str = "GRAPH #{operands.first.to_sparql(**options)} " + query
140
+ top_level ? Operator.to_sparql(str, **options) : str
141
+ end
92
142
  end # Graph
93
143
  end # Operator
94
144
  end; end # SPARQL::Algebra
@@ -25,6 +25,37 @@ module SPARQL; module Algebra
25
25
  # (group (?P) ((??.0 (count ?O)))
26
26
  # (bgp (triple ?S ?P ?O))))))
27
27
  #
28
+ # @example SPARQL Grammar (HAVING aggregate)
29
+ # PREFIX : <http://www.example.org/>
30
+ # SELECT ?s (AVG(?o) AS ?avg)
31
+ # WHERE { ?s ?p ?o }
32
+ # GROUP BY ?s
33
+ # HAVING (AVG(?o) <= 2.0)
34
+ #
35
+ # @example SSE (HAVING aggregate)
36
+ # (prefix ((: <http://www.example.org/>))
37
+ # (project (?s ?avg)
38
+ # (filter (<= ??.0 2.0)
39
+ # (extend ((?avg ??.0))
40
+ # (group (?s) ((??.0 (avg ?o)))
41
+ # (bgp (triple ?s ?p ?o)))))) )
42
+ #
43
+ # @example SPARQL Grammar (non-trivial filters)
44
+ # PREFIX : <http://example.com/data/#>
45
+ # SELECT ?g (AVG(?p) AS ?avg) ((MIN(?p) + MAX(?p)) / 2 AS ?c)
46
+ # WHERE { ?g :p ?p . }
47
+ # GROUP BY ?g
48
+ #
49
+ # @example SSE (non-trivial filters)
50
+ # (prefix ((: <http://example.com/data/#>))
51
+ # (project (?g ?avg ?c)
52
+ # (extend ((?avg ??.0) (?c (/ (+ ??.1 ??.2) 2)))
53
+ # (group (?g)
54
+ # ((??.0 (avg ?p))
55
+ # (??.1 (min ?p))
56
+ # (??.2 (max ?p)))
57
+ # (bgp (triple ?g :p ?p)))) ))
58
+ #
28
59
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
29
60
  class Group < Operator
30
61
  include Query
@@ -131,22 +162,90 @@ module SPARQL; module Algebra
131
162
  super
132
163
  end
133
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
+
134
193
  ##
135
194
  #
136
195
  # Returns a partial SPARQL grammar for this operator.
137
196
  #
138
- # @param [Hash{Symbol => Operator}] extensions
197
+ # @param [Hash{String => Operator}] extensions
139
198
  # Variable bindings
199
+ # @param [Array<Operator>] filter_ops ([])
200
+ # Filter Operations
140
201
  # @return [String]
141
- def to_sparql(extensions: {}, **options)
202
+ def to_sparql(extensions: {}, filter_ops: [], **options)
203
+ having_ops = []
142
204
  if operands.length > 2
205
+ temp_bindings = operands[1].inject({}) {|memo, (var, op)| memo.merge(var => op)}
143
206
  # Replace extensions from temporary bindings
144
- operands[1].each do |var, op|
145
- ext_var = extensions.invert.fetch(var)
146
- extensions[ext_var] = op
207
+ temp_bindings.each do |var, op|
208
+ # Update extensions using a temporarily bound variable with its binding
209
+ extensions = extensions.inject({}) do |memo, (ext_var, ext_op)|
210
+ if ext_op.is_a?(Operator)
211
+ # Try to recursivley replace variable within operator
212
+ new_op = ext_op.deep_dup.rewrite do |operand|
213
+ if operand.is_a?(Variable) && operand.to_sym == var.to_sym
214
+ op.dup
215
+ else
216
+ operand
217
+ end
218
+ end
219
+ memo.merge(ext_var.to_s => new_op)
220
+ elsif ext_op.is_a?(Variable) && ext_op.to_sym == var.to_sym
221
+ memo.merge(ext_var.to_s => op)
222
+ else
223
+ # Doesn't match this variable, so don't change
224
+ memo.merge(ext_var.to_s => ext_op)
225
+ end
226
+ end
227
+
228
+ # Filter ops using temporary bindinds are used for HAVING clauses
229
+ filter_ops.each do |fop|
230
+ having_ops << fop if fop.descendants.include?(var) && !having_ops.include?(fop)
231
+ end
232
+ end
233
+
234
+ # If used in a HAVING clause, it's not also a filter
235
+ filter_ops -= having_ops
236
+
237
+ # Replace each operand in having using var with it's corresponding operation
238
+ having_ops = having_ops.map do |op|
239
+ op.dup.rewrite do |operand|
240
+ # Rewrite based on temporary bindings
241
+ temp_bindings.fetch(operand, operand)
242
+ end
147
243
  end
148
244
  end
149
- operands.last.to_sparql(extensions: extensions, group_ops: operands.first, **options)
245
+ operands.last.to_sparql(extensions: extensions,
246
+ group_ops: operands.first,
247
+ having_ops: having_ops,
248
+ **options)
150
249
  end
151
250
  end # Group
152
251
  end # Operator
@@ -16,6 +16,24 @@ module SPARQL; module Algebra
16
16
  # (group () ((??.0 (group_concat ?x)))
17
17
  # (bgp))))
18
18
  #
19
+ # @example SPARQL Grammar (DISTINCT)
20
+ # SELECT (GROUP_CONCAT(DISTINCT ?x) AS ?y) {}
21
+ #
22
+ # @example SSE (DISTINCT)
23
+ # (project (?y)
24
+ # (extend ((?y ??.0))
25
+ # (group () ((??.0 (group_concat distinct ?x)))
26
+ # (bgp))))
27
+ #
28
+ # @example SPARQL Grammar (SEPARATOR)
29
+ # SELECT (GROUP_CONCAT(?x; SEPARATOR=';') AS ?y) {}
30
+ #
31
+ # @example SSE (SEPARATOR)
32
+ # (project (?y)
33
+ # (extend ((?y ??.0))
34
+ # (group () ((??.0 (group_concat (separator ";") ?x)))
35
+ # (bgp))))
36
+ #
19
37
  # @see https://www.w3.org/TR/sparql11-query/#defn_aggGroupConcat
20
38
  class GroupConcat < Operator
21
39
  include Aggregate
@@ -63,7 +81,13 @@ module SPARQL; module Algebra
63
81
  #
64
82
  # @return [String]
65
83
  def to_sparql(**options)
66
- "GROUP_CONCAT(#{operands.to_sparql(delimiter: ', ', **options)})"
84
+ distinct = operands.first == :distinct
85
+ args = distinct ? operands[1..-1] : operands
86
+ separator = args.first.last if args.first.is_a?(Array) && args.first.first == :separator
87
+ args = args[1..-1] if separator
88
+ str = "GROUP_CONCAT(#{'DISTINCT ' if distinct}#{args.to_sparql(delimiter: ', ', **options)}"
89
+ str << "; SEPARATOR=#{separator.to_sparql}" if separator
90
+ str << ")"
67
91
  end
68
92
  end # GroupConcat
69
93
  end # Operator
@@ -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
 
@@ -48,15 +48,15 @@ module SPARQL; module Algebra
48
48
  rescue
49
49
  raise TypeError
50
50
  end
51
- end # If
52
51
 
53
- ##
54
- #
55
- # Returns a partial SPARQL grammar for this operator.
56
- #
57
- # @return [String]
58
- def to_sparql(**options)
59
- "IF(" + operands.to_sparql(delimiter: ', ', **options) + ")"
60
- end
61
- end # If
52
+ ##
53
+ #
54
+ # Returns a partial SPARQL grammar for this operator.
55
+ #
56
+ # @return [String]
57
+ def to_sparql(**options)
58
+ "IF(" + operands.to_sparql(delimiter: ', ', **options) + ")"
59
+ end
60
+ end # If
61
+ end # Operator
62
62
  end; end # SPARQL::Algebra
@@ -69,7 +69,9 @@ module SPARQL; module Algebra
69
69
  #
70
70
  # @return [String]
71
71
  def to_sparql(**options)
72
- "INSERT {\n" + operands.first.to_sparql(as_statement: true, **options) + "\n}"
72
+ "INSERT {\n" +
73
+ operands.first.to_sparql(delimiter: " .\n", **options) +
74
+ "\n}"
73
75
  end
74
76
  end # Insert
75
77
  end # Operator
@@ -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
@@ -13,8 +13,7 @@ module SPARQL; module Algebra
13
13
  # }
14
14
  #
15
15
  # @example SSE
16
- # (prefix ((xsd: <http://www.w3.org/2001/XMLSchema#>)
17
- # (: <http://example.org/things#>))
16
+ # (prefix ((: <http://example.org/things#>))
18
17
  # (project (?x ?v)
19
18
  # (filter (isBlank ?v)
20
19
  # (bgp (triple ?x :p ?v)))))
@@ -13,8 +13,7 @@ module SPARQL; module Algebra
13
13
  # }
14
14
  #
15
15
  # @example SSE
16
- # (prefix ((xsd: <http://www.w3.org/2001/XMLSchema#>)
17
- # (: <http://example.org/things#>))
16
+ # (prefix ((: <http://example.org/things#>))
18
17
  # (project (?x ?v)
19
18
  # (filter (isIRI ?v)
20
19
  # (bgp (triple ?x :p ?v)))))
@@ -13,8 +13,7 @@ module SPARQL; module Algebra
13
13
  # }
14
14
  #
15
15
  # @example SSE
16
- # (prefix ((xsd: <http://www.w3.org/2001/XMLSchema#>)
17
- # (: <http://example.org/things#>))
16
+ # (prefix ((: <http://example.org/things#>))
18
17
  # (project (?x ?v)
19
18
  # (filter (isLiteral ?v)
20
19
  # (bgp (triple ?x :p ?v)))))
@@ -15,8 +15,7 @@ module SPARQL; module Algebra
15
15
  # }
16
16
  #
17
17
  # @example SSE
18
- # (prefix ((xsd: <http://www.w3.org/2001/XMLSchema#>)
19
- # (: <http://example.org/things#>))
18
+ # (prefix ((: <http://example.org/things#>))
20
19
  # (project (?x ?v)
21
20
  # (filter (isNumeric ?v)
22
21
  # (bgp (triple ?x :p ?v)))))
@@ -19,6 +19,22 @@ module SPARQL; module Algebra
19
19
  # (graph ?g
20
20
  # (bgp (triple ?s ?q ?v)))))
21
21
  #
22
+ # @example SPARQL Grammar (inline filter)
23
+ # PREFIX : <http://xmlns.com/foaf/0.1/>
24
+ # ASK {
25
+ # :who :homepage ?homepage
26
+ # FILTER REGEX(?homepage, "^http://example.org/")
27
+ # :who :schoolHomepage ?schoolPage
28
+ # }
29
+ #
30
+ # @example SSE (inline filter)
31
+ # (prefix ((: <http://xmlns.com/foaf/0.1/>))
32
+ # (ask
33
+ # (filter (regex ?homepage "^http://example.org/")
34
+ # (join
35
+ # (bgp (triple :who :homepage ?homepage))
36
+ # (bgp (triple :who :schoolHomepage ?schoolPage))))))
37
+ #
22
38
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
23
39
  class Join < Operator::Binary
24
40
  include Query
@@ -41,8 +57,8 @@ module SPARQL; module Algebra
41
57
  # @return [RDF::Query::Solutions]
42
58
  # the resulting solution sequence
43
59
  # @see https://www.w3.org/TR/sparql11-query/#sparqlAlgebra
44
- # @see https://www.rubydoc.info/github/ruby-rdf/rdf/RDF/Query/Solution#merge-instance_method
45
- # @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
46
62
  def execute(queryable, **options, &block)
47
63
  # Join(Ω1, Ω2) = { merge(μ1, μ2) | μ1 in Ω1 and μ2 in Ω2, and μ1 and μ2 are compatible }
48
64
  # eval(D(G), Join(P1, P2)) = Join(eval(D(G), P1), eval(D(G), P2))
@@ -98,10 +114,28 @@ module SPARQL; module Algebra
98
114
  #
99
115
  # @param [Boolean] top_level (true)
100
116
  # Treat this as a top-level, generating SELECT ... WHERE {}
117
+ # @param [Hash{String => Operator}] extensions
118
+ # Variable bindings
119
+ # @param [Array<Operator>] filter_ops ([])
120
+ # Filter Operations
101
121
  # @return [String]
102
- def to_sparql(top_level: true, **options)
103
- str = operands.to_sparql(top_level: false, delimiter: "\n", **options)
104
- top_level ? Operator.to_sparql(str, **options) : str
122
+ def to_sparql(top_level: true, filter_ops: [], extensions: {}, **options)
123
+ # If this is top-level, and the last operand is a Table (values), put the values at the outer-level
124
+ str = "{\n" + operands.first.to_sparql(top_level: false, extensions: {}, **options)
125
+
126
+ # Any accrued filters go here.
127
+ filter_ops.each do |op|
128
+ str << "\nFILTER (#{op.to_sparql(**options)}) ."
129
+ end
130
+
131
+ if top_level && operands.last.is_a?(Table)
132
+ str << "\n}"
133
+ options = options.merge(values_clause: operands.last)
134
+ else
135
+ str << "\n{\n" + operands.last.to_sparql(top_level: false, extensions: {}, **options) + "\n}\n}"
136
+ end
137
+
138
+ top_level ? Operator.to_sparql(str, extensions: extensions, **options) : str
105
139
  end
106
140
  end # Join
107
141
  end # Operator
@@ -12,9 +12,8 @@ module SPARQL; module Algebra
12
12
  # }
13
13
  #
14
14
  # @example SSE
15
- # (prefix
16
- # ((: <http://example.org/>))
17
- # (project (?str ?lstr)
15
+ # (prefix ((: <http://example.org/>))
16
+ # (project (?s ?lstr)
18
17
  # (extend ((?lstr (lcase ?str)))
19
18
  # (bgp (triple ?s :str ?str)))))
20
19
  #