sparql 1.0.8 → 1.1.0p0

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 (44) hide show
  1. checksums.yaml +14 -6
  2. data/README.md +57 -25
  3. data/VERSION +1 -1
  4. data/lib/sinatra/sparql.rb +5 -3
  5. data/lib/sparql/algebra/aggregate.rb +67 -0
  6. data/lib/sparql/algebra/evaluatable.rb +49 -4
  7. data/lib/sparql/algebra/expression.rb +6 -4
  8. data/lib/sparql/algebra/extensions.rb +99 -9
  9. data/lib/sparql/algebra/operator/and.rb +7 -4
  10. data/lib/sparql/algebra/operator/asc.rb +6 -3
  11. data/lib/sparql/algebra/operator/avg.rb +36 -0
  12. data/lib/sparql/algebra/operator/bnode.rb +5 -4
  13. data/lib/sparql/algebra/operator/bound.rb +5 -2
  14. data/lib/sparql/algebra/operator/coalesce.rb +6 -3
  15. data/lib/sparql/algebra/operator/concat.rb +6 -3
  16. data/lib/sparql/algebra/operator/count.rb +30 -0
  17. data/lib/sparql/algebra/operator/exists.rb +39 -0
  18. data/lib/sparql/algebra/operator/exprlist.rb +6 -3
  19. data/lib/sparql/algebra/operator/extend.rb +3 -1
  20. data/lib/sparql/algebra/operator/filter.rb +2 -1
  21. data/lib/sparql/algebra/operator/group.rb +101 -0
  22. data/lib/sparql/algebra/operator/group_concat.rb +55 -0
  23. data/lib/sparql/algebra/operator/if.rb +5 -5
  24. data/lib/sparql/algebra/operator/in.rb +7 -4
  25. data/lib/sparql/algebra/operator/max.rb +38 -0
  26. data/lib/sparql/algebra/operator/min.rb +38 -0
  27. data/lib/sparql/algebra/operator/minus.rb +51 -16
  28. data/lib/sparql/algebra/operator/negate.rb +31 -0
  29. data/lib/sparql/algebra/operator/notexists.rb +37 -0
  30. data/lib/sparql/algebra/operator/notin.rb +7 -4
  31. data/lib/sparql/algebra/operator/or.rb +7 -4
  32. data/lib/sparql/algebra/operator/order.rb +2 -2
  33. data/lib/sparql/algebra/operator/sample.rb +32 -0
  34. data/lib/sparql/algebra/operator/strlang.rb +1 -1
  35. data/lib/sparql/algebra/operator/sum.rb +36 -0
  36. data/lib/sparql/algebra/operator/table.rb +44 -0
  37. data/lib/sparql/algebra/operator.rb +37 -2
  38. data/lib/sparql/algebra.rb +1 -0
  39. data/lib/sparql/grammar/meta.rb +1143 -696
  40. data/lib/sparql/grammar/parser11.rb +178 -32
  41. data/lib/sparql/grammar/terminals11.rb +2 -2
  42. data/lib/sparql/grammar.rb +0 -2
  43. data/lib/sparql/results.rb +55 -0
  44. metadata +78 -81
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: eabf23290e66c519d04a3903fb9c10cab87236c5
4
- data.tar.gz: ccf93905bff11c897f0fb00b01113095c527aa9e
5
- SHA512:
6
- metadata.gz: de063e0c1d091dcb541f43d00942db7636c1e931f1504337cb9b0c8648085e30856bfbb332b08cfc392aece68c62595c1f6bd2924be9f715bc1fb2574b3b75a6
7
- data.tar.gz: d03282b7ed8ae2edadbd9050d8101a9195df45844baa4cf126fefefef38c0b5089a07a67f75902e6a54d3a8404a86358ab13445a161f0b9fdc7fd0ad10aa9ac1
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NWU0YWQ2MjFiNjU1MDZlZmNmMGI4ZGU5ZGRhMDA1ZTI2NDk0NTdmYQ==
5
+ data.tar.gz: !binary |-
6
+ ZmNlOTk2OGUzMjQwZDY5OGIxZjM1Yjc3NmFlYjdkMjIyNmZlOWRhNw==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ Mzc1ZmQ3NmNjYWJiOGU2ZDUzYjE4Mjk5NzM0NzIzZGU5MDQ4MjM3MzkxZjZh
10
+ YzIyYzJkNzJmNmMwMTJhNWE5ZjZjMzEyODlhNjkxYjk3MDE3YTg2YTQyZmEz
11
+ OGExNWMyZjhkYjBjZjQwZDFiOTQzNjFmODA0Y2ZlN2M1ZmYwMTI=
12
+ data.tar.gz: !binary |-
13
+ NzE5OTg5NDQ4MjEzOTEyY2E3NmMxYTM2ZDRlZmUwODMzZTAzNGE5ODUzMzVi
14
+ NGRiMzc0N2EzMTRiZTc0ZmJmZGQ1ZTgwNmY5YzQ5NDFmNGRmYWE1NjYzNTYz
15
+ ZjZhODkzN2E1NjQ3ZmIwNjUzZTRjZWJmMWIyZWQ1N2MwMDJkNzI=
data/README.md CHANGED
@@ -9,9 +9,11 @@ This is a [Ruby][] implementation of [SPARQL][] for [RDF.rb][].
9
9
  ## Features
10
10
 
11
11
  * 100% free and unencumbered [public domain](http://unlicense.org/) software.
12
- * [SPARQL 1.0][] query parsing and execution
13
- * Limited [SPARQL 1.1][] query parsing and execution
14
- * SPARQL results as [XML][SPARQL XML], [JSON][SPARQL JSON] or HTML.
12
+ * [SPARQL 1.1 Query][] parsing and execution (excluding [Property Paths][])
13
+ * SPARQL results as [XML][SPARQL XML], [JSON][SPARQL JSON],
14
+ [CSV][SPARQL 1.1 Query Results CSV and TSV Formats],
15
+ [TSV][SPARQL 1.1 Query Results CSV and TSV Formats]
16
+ or HTML.
15
17
  * SPARQL CONSTRUCT or DESCRIBE serialized based on Format, Extension of Mime Type
16
18
  using available RDF Writers (see [Linked Data][])
17
19
  * SPARQL Client for accessing remote SPARQL endpoints.
@@ -24,32 +26,48 @@ This is a [Ruby][] implementation of [SPARQL][] for [RDF.rb][].
24
26
 
25
27
  ## Description
26
28
 
27
- The {SPARQL} gem implements the [SPARQL 1.0][] using the [SPARQL 1.1][] grammar,
28
- and provides [Rack][] and [Sinatra][]
29
+ The {SPARQL} gem implements [SPARQL 1.1 Query][], and provides [Rack][] and [Sinatra][]
29
30
  middleware to provide results using [HTTP Content Negotiation][conneg].
30
31
 
31
- * {SPARQL::Grammar} implements a [SPARQL 1.1][] parser generating [SPARQL S-Expressions (SSE)][SSE].
32
- * [SPARQL 1.1][] capabilities beyond [SPARQL 1.0][] are not yet supported.
33
- See the section on [SPARQL 1.1][] extensions and limitations for further detail.
32
+ * {SPARQL::Grammar} implements a [SPARQL 1.1 Query][] parser generating [SPARQL S-Expressions (SSE)][SSE].
33
+ * Support for [Property Paths][] is excluded.
34
+ See the section on [SPARQL 1.1 Query][] extensions and limitations for further detail.
34
35
  * {SPARQL::Algebra} executes SSE against Any `RDF::Graph` or `RDF::Repository`, including
35
36
  compliant [RDF.rb][] repository adaptors such as [RDF::DO][] and [RDF::Mongo][].
36
37
  * {Rack::SPARQL} and {Sinatra::SPARQL} provide middleware components to format results
37
38
  using an appropriate format based on [HTTP content negotiation][conneg].
38
39
 
39
- ### [SPARQL 1.1][] Extensions and Limitations
40
- The {SPARQL} gem uses the [SPARQL 1.1][] {file:etc/sparql11.bnf EBNF grammar}, which provides
40
+ ### [SPARQL 1.1 Query][] Extensions and Limitations
41
+ The {SPARQL} gem uses the [SPARQL 1.1 Query][] {file:etc/sparql11.bnf EBNF grammar}, which provides
41
42
  much more capability than [SPARQL 1.0][], but has a few limitations:
42
43
 
43
44
  * The format for decimal datatypes has changed in [RDF 1.1][]; they may no
44
45
  longer have a trailing ".", although they do not need a leading digit.
45
- * BNodes may now include extended characters, including ".". As a consequence, BNodes
46
- used in the object position, where the statement or pattern is terminated by a "."
47
- must contain whitespace separating the BNode label, and the ".".
46
+ * BNodes may now include extended characters, including ".".
48
47
 
49
- The SPARQL gem now implements the following [SPARQL 1.1][] operations:
48
+ The SPARQL gem now implements the following [SPARQL 1.1 Query][] operations:
50
49
 
51
- * Support for all [functions](http://www.w3.org/TR/sparql11-query/#SparqlOps).
52
- * Support for [BIND](http://www.w3.org/TR/sparql11-query/#bind)
50
+ * [Functions](http://www.w3.org/TR/sparql11-query/#SparqlOps)
51
+ * [BIND](http://www.w3.org/TR/sparql11-query/#bind)
52
+ * [GROUP BY](http://www.w3.org/TR/sparql11-query/#groupby)
53
+ * [Aggregates](http://www.w3.org/TR/sparql11-query/#aggregates)
54
+ * [Subqueries](http://www.w3.org/TR/sparql11-query/#subqueries)
55
+ * [Inline Data](http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#inline-data)
56
+ * [Inline Data](http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#inline-data)
57
+ * [Exists](http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#func-filter-exists)
58
+ * [Negation](http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#negation)
59
+
60
+ The only major area of [SPARQL 1.1 Query][] missing is
61
+ [Property Paths][], which
62
+ will be in later release along with:
63
+
64
+ * [Update][SPARQL 1.1 Update],
65
+ * [Federated Query][SPARQL 1.1 Federated Query],
66
+ * [Entailment Regimes][SPARQL 1.1 Entailment Regimes],
67
+ * [Protocol][SPARQL 1.1 Protocol], and
68
+ * [Graph Store HTTP Protocol][SPARQL 1.1 Graph Store HTTP Protocol]
69
+
70
+ either in this, or related gems.
53
71
 
54
72
  ### SPARQL Extension Functions
55
73
  Extension functions may be defined, which will be invoked during query evaluation. For example:
@@ -71,18 +89,21 @@ Then, use the function in a query:
71
89
  BIND(rsp:crypt(?email) AS ?crypted)
72
90
  }
73
91
 
92
+ See {SPARQL::Algebra::Expression.register_extension} for details.
93
+
74
94
  ### Middleware
75
95
 
76
- `Rack::SPARQL` is a superset of [Rack::LinkedData][] to allow content negotiated results
96
+ {Rack::SPARQL} is a superset of [Rack::LinkedData][] to allow content negotiated results
77
97
  to be returned any `RDF::Enumerable` or `RDF::Query::Solutions` compatible results.
78
98
  You would typically return an instance of `RDF::Graph`, `RDF::Repository` or `RDF::Query::Solutions`
79
99
  from your Rack application, and let the `Rack::SPARQL::ContentNegotiation` middleware
80
100
  take care of serializing your response into whatever format the HTTP
81
101
  client requested and understands.
82
102
 
83
- `Sinatra::SPARQL` is a thin Sinatra-specific wrapper around the
103
+ {Sinatra::SPARQL} is a thin Sinatra-specific wrapper around the
84
104
  {Rack::SPARQL} middleware, which implements SPARQL
85
- content negotiation for Rack applications.
105
+ content negotiation for Rack applications. {Sinatra::SPARQL} also supports
106
+ [SPARQL 1.1 Service Description][].
86
107
 
87
108
  The middleware queries [RDF.rb][] for the MIME content types of known RDF
88
109
  serialization formats, so it will work with whatever serialization plugins
@@ -102,8 +123,8 @@ Queries using datasets are re-written to use the identified graphs for `FROM` an
102
123
  ### Result formats
103
124
 
104
125
  `SPARQL.serialize_results` may be used on it's own, or in conjunction with {Rack::SPARQL} or {Sinatra::SPARQL}
105
- to provide content-negotiated query results. For basic `SELECT` and `ASK` this includes HTML, XML and JSON formats.
106
- `DESCRIBE` and `CONSTRUCT` create an `RDF::Graph`, which can be serialized through [HTTP Content Netogiation][conneg]
126
+ to provide content-negotiated query results. For basic `SELECT` and `ASK` this includes HTML, XML, CSV, TSV and JSON formats.
127
+ `DESCRIBE` and `CONSTRUCT` create an `RDF::Graph`, which can be serialized through [HTTP Content Negotiation][conneg]
107
128
  using available RDF writers. For best results, require [Linked Data][] to enable
108
129
  a full set of RDF formats.
109
130
 
@@ -212,8 +233,8 @@ Full documentation available on [Rubydoc.info][SPARQL doc]
212
233
  ## Dependencies
213
234
 
214
235
  * [Ruby](http://ruby-lang.org/) (>= 1.9.2)
215
- * [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.1)
216
- * [SPARQL::Client](https://rubygems.org/gems/sparql-client) (>= 1.0)
236
+ * [RDF.rb](http://rubygems.org/gems/rdf) (>= 1.0.7)
237
+ * [SPARQL::Client](https://rubygems.org/gems/sparql-client) (>= 1.0.3)
217
238
  * [SXP](https://rubygems.org/gems/sxp) (>= 0.1.0)
218
239
  * [Builder](https://rubygems.org/gems/builder) (>= 3.0.0)
219
240
  * [JSON](https://rubygems.org/gems/json) (>= 1.5.1)
@@ -281,7 +302,6 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are included in the
281
302
  [SPARQL 1.0]: http://www.w3.org/TR/rdf-sparql-query/
282
303
  [SPARQL 1.0 tests]:http://www.w3.org/2001/sw/DataAccess/tests/
283
304
  [SPARQL 1.1 tests]: http://www.w3.org/2009/sparql/docs/tests/
284
- [SPARQL 1.1]: http://www.w3.org/TR/sparql11-query/
285
305
  [SSE]: http://openjena.org/wiki/SSE
286
306
  [SXP]: http://sxp.rubyforge.org/
287
307
  [grammar]: http://www.w3.org/TR/rdf-sparql-query/#grammar
@@ -292,9 +312,21 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are included in the
292
312
  [SPARQL doc]: http://rubydoc.info/github/ruby-rdf/sparql/frames
293
313
  [SPARQL XML]: http://www.w3.org/TR/rdf-sparql-XMLres/
294
314
  [SPARQL JSON]: http://www.w3.org/TR/rdf-sparql-json-res/
295
- [SPARQL Protocol]: http://www.w3.org/TR/rdf-sparql-protocol/
315
+ [Property Paths]: http://www.w3.org/TR/2013/REC-sparql11-query-20130321/#propertypaths
316
+
296
317
  [SSD]: http://www.w3.org/TR/sparql11-service-description/
297
318
  [Rack]: http://rack.rubyforge.org/
298
319
  [Sinatra]: http://www.sinatrarb.com/
299
320
  [conneg]: http://en.wikipedia.org/wiki/Content_negotiation
300
321
 
322
+ [SPARQL 1.1 Query]: http://www.w3.org/TR/sparql11-query/
323
+ [SPARQL 1.1 Update]: http://www.w3.org/TR/sparql11-update/
324
+ [SPARQL 1.1 Service Description]: http://www.w3.org/TR/sparql11-service-description/
325
+ [SPARQL 1.1 Federated Query]: http://www.w3.org/TR/sparql11-federated-query/
326
+ [SPARQL 1.1 Query Results JSON Format]: http://www.w3.org/TR/sparql11-results-json/
327
+ [SPARQL 1.1 Query Results CSV and TSV Formats]: http://www.w3.org/TR/sparql11-results-csv-tsv/
328
+ [SPARQL Query Results XML Format]: http://www.w3.org/TR/rdf-sparql-XMLres/
329
+ [SPARQL 1.1 Entailment Regimes]: http://www.w3.org/TR/sparql11-entailment/
330
+ [SPARQL 1.1 Protocol]: http://www.w3.org/TR/sparql11-protocol/
331
+ [SPARQL 1.1 Graph Store HTTP Protocol]: http://www.w3.org/TR/sparql11-http-rdf-update/
332
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.8
1
+ 1.1.0p0
@@ -42,7 +42,7 @@ module Sinatra
42
42
  node = RDF::Node.new
43
43
  g << [node, RDF.type, sd.join("#Service")]
44
44
  g << [node, sd.join("#endpoint"), options[:endpoint] || url("/sparql")]
45
- g << [node, sd.join("#supportedLanguage"), sd.join("#SPARQL10Query")]
45
+ g << [node, sd.join("#supportedLanguage"), sd.join("#SPARQL11Query")]
46
46
 
47
47
  # Result formats, both RDF and SPARQL Results.
48
48
  # FIXME: We should get this from the avaliable serializers
@@ -50,8 +50,10 @@ module Sinatra
50
50
  g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/Turtle")]
51
51
  g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/RDFa")]
52
52
  g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/N-Triples")]
53
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_RESULTS_XML")]
54
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_RESULTS_JSON")]
53
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_XML")]
54
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_JSON")]
55
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_CSV")]
56
+ g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_TSV")]
55
57
 
56
58
  # Features
57
59
  g << [node, sd.join("#feature"), sd.join("#DereferencesURIs")]
@@ -0,0 +1,67 @@
1
+ module SPARQL; module Algebra
2
+ ##
3
+ # A SPARQL algebra aggregate.
4
+ #
5
+ # Aggregates are for SPARQL set functions. Aggregates take one
6
+ # or more operands which are `Enumerable` lists of `RDF::Term`
7
+ # and return a single `RDF::Term` or `TypeError`.
8
+ #
9
+ # @see http://www.w3.org/TR/sparql11-query/#setFunctions
10
+ # @see http://www.w3.org/TR/sparql11-query/#aggregates
11
+ #
12
+ # @abstract
13
+ module Aggregate
14
+ ##
15
+ # Aggregates this operator accross its operands using
16
+ # a solutions enumerable.
17
+ #
18
+ # @param [Enumerable<RDF::Query::Solution>] solutions ([])
19
+ # an enumerable set of query solutions
20
+ # @param [Hash{Symbol => Object}] options ({})
21
+ # options passed from query
22
+ # @return [RDF::Term]
23
+ # @raise [TypeError]
24
+ # @abstract
25
+ def aggregate(solutions = [], options = {})
26
+ args_enum = solutions.map do |solution|
27
+ operands.map do |operand|
28
+ begin
29
+ operand.evaluate(solution, options.merge(:depth => options[:depth].to_i + 1))
30
+ rescue TypeError
31
+ # Ignore errors
32
+ nil
33
+ end
34
+ end.compact
35
+ end
36
+ apply(args_enum)
37
+ end
38
+
39
+ ##
40
+ # @param [Enumerable<Array<RDF::Term>>] enum
41
+ # Enumerable yielding evaluated operands
42
+ # @return [RDF::Term]
43
+ # @abstract
44
+ def apply(enum)
45
+ raise NotImplementedError, "#{self.class}#apply(#{operands.map(&:class).join(', ')})"
46
+ end
47
+
48
+ ##
49
+ # This is a no-op for Aggregates.
50
+ #
51
+ # @return [SPARQL::Algebra::Evaluatable] self
52
+ def replace_vars!(&block)
53
+ self
54
+ end
55
+
56
+ ##
57
+ # Replace ourselves with a variable returned from the block
58
+ #
59
+ # @yield agg
60
+ # @yieldparam [SPARQL::Algebra::Aggregate] agg
61
+ # @yieldreturn [RDF::Query::Variable]
62
+ # @return [RDF::Query::Variable] the returned variable
63
+ def replace_aggregate!(&block)
64
+ yield self
65
+ end
66
+ end # Aggregate
67
+ end; end # SPARQL::Algebra
@@ -7,12 +7,14 @@ module SPARQL; module Algebra
7
7
  ##
8
8
  # Evaluates this operator using the given variable `bindings`.
9
9
  #
10
- # @param [RDF::Query::Solution, #[]] bindings
10
+ # @param [RDF::Query::Solution] bindings
11
11
  # a query solution containing zero or more variable bindings
12
+ # @param [Hash{Symbol => Object}] options ({})
13
+ # options passed from query
12
14
  # @return [RDF::Term]
13
15
  # @abstract
14
- def evaluate(bindings = {})
15
- args = operands.map { |operand| operand.evaluate(bindings) }
16
+ def evaluate(bindings, options = {})
17
+ args = operands.map { |operand| operand.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1)) }
16
18
  options[:memoize] ? memoize(*args) : apply(*args)
17
19
  end
18
20
 
@@ -33,5 +35,48 @@ module SPARQL; module Algebra
33
35
  def apply(*operands)
34
36
  raise NotImplementedError, "#{self.class}#apply(#{operands.map(&:class).join(', ')})"
35
37
  end
36
- end # Query
38
+
39
+ ##
40
+ # Replace operators which are variables with the result of the block
41
+ # descending into operators which are also evaluatable
42
+ #
43
+ # @yield var
44
+ # @yieldparam [RDF::Query::Variable] var
45
+ # @yieldreturn [RDF::Query::Variable, SPARQL::Algebra::Evaluatable]
46
+ # @return [SPARQL::Algebra::Evaluatable] self
47
+ def replace_vars!(&block)
48
+ @operands.map! do |op|
49
+ case
50
+ when op.is_a?(RDF::Query::Variable)
51
+ yield op
52
+ when op.respond_to?(:replace_vars!)
53
+ op.replace_vars!(&block)
54
+ else
55
+ op
56
+ end
57
+ end
58
+ self
59
+ end
60
+
61
+ ##
62
+ # Recursively re-map operators to replace aggregates with temporary variables returned from the block
63
+ #
64
+ # @yield agg
65
+ # @yieldparam [SPARQL::Algebra::Aggregate] agg
66
+ # @yieldreturn [RDF::Query::Variable]
67
+ # @return [SPARQL::Algebra::Evaluatable, RDF::Query::Variable] self
68
+ def replace_aggregate!(&block)
69
+ @operands.map! do |op|
70
+ case
71
+ when op.aggregate?
72
+ yield op
73
+ when op.respond_to?(:replace_aggregate!)
74
+ op.replace_aggregate!(&block)
75
+ else
76
+ op
77
+ end
78
+ end
79
+ self
80
+ end
81
+ end # Evaluatable
37
82
  end; end # SPARQL::Algebra
@@ -149,7 +149,6 @@ module SPARQL; module Algebra
149
149
  # @yield *args
150
150
  # @yieldparam [Array<RDF::Term>] *args
151
151
  # @yieldreturn [RDF::Term]
152
- # @param [Proc] function
153
152
  # @return [void]
154
153
  # @raise [TypeError] if `uri` is not an RDF::URI or no block is given
155
154
  def self.register_extension(uri, &block)
@@ -174,7 +173,7 @@ module SPARQL; module Algebra
174
173
  # that.
175
174
  #
176
175
  # @param [RDF::URI] function
177
- # @param [Array<RDF::Term>] *args
176
+ # @param [Array<RDF::Term>] args splat of args to function
178
177
  # @return [RDF::Term]
179
178
  # @see http://www.w3.org/TR/sparql11-query/#extensionFunctions
180
179
  # @see http://www.w3.org/TR/sparql11-query/#FunctionMapping
@@ -289,9 +288,12 @@ module SPARQL; module Algebra
289
288
  # Subclasses can override this method in order to implement something
290
289
  # more useful.
291
290
  #
292
- # @param [RDF::Query::Solution, #[]] bindings
291
+ # @param [RDF::Query::Solution] bindings
292
+ # a query solution containing zero or more variable bindings
293
+ # @param [Hash{Symbol => Object}] options ({})
294
+ # options passed from query
293
295
  # @return [Expression] `self`
294
- def evaluate(bindings = {})
296
+ def evaluate(bindings, options = {})
295
297
  self
296
298
  end
297
299
 
@@ -20,7 +20,7 @@ class Object
20
20
  end
21
21
 
22
22
  ##
23
- # Extensions for Ruby's `Object` class.
23
+ # Extensions for Ruby's `Array` class.
24
24
  class Array
25
25
  ##
26
26
  # Returns the SXP representation of this object, defaults to `self`.
@@ -29,7 +29,7 @@ class Array
29
29
  def to_sxp_bin
30
30
  map {|x| x.to_sxp_bin}
31
31
  end
32
-
32
+
33
33
  ##
34
34
  # Evaluates the array using the given variable `bindings`.
35
35
  #
@@ -37,12 +37,14 @@ class Array
37
37
  # an XSD datatype, and the second is the expression to be evaluated.
38
38
  # The result is cast as a literal of the appropriate type
39
39
  #
40
- # @param [RDF::Query::Solution, #[]] bindings
40
+ # @param [RDF::Query::Solution] bindings
41
41
  # a query solution containing zero or more variable bindings
42
+ # @param [Hash{Symbol => Object}] options ({})
43
+ # options passed from query
42
44
  # @return [RDF::Term]
43
- def evaluate(bindings)
44
- dt, val = self.map {|o| o.evaluate(bindings)}
45
- SPARQL::Algebra::Expression.extension(*self.map {|o| o.evaluate(bindings)})
45
+ # @see {SPARQL::Algebra::Expression.evaluate}
46
+ def evaluate(bindings, options = {})
47
+ SPARQL::Algebra::Expression.extension(*self.map {|o| o.evaluate(bindings, options)})
46
48
  end
47
49
 
48
50
  ##
@@ -58,6 +60,79 @@ class Array
58
60
  def execute(queryable, options = {})
59
61
  raise NotImplementedError, "SPARQL::Algebra '#{first}' operator not implemented"
60
62
  end
63
+
64
+ ##
65
+ # Returns `true` if any of the operands are variables, `false`
66
+ # otherwise.
67
+ #
68
+ # @return [Boolean] `true` or `false`
69
+ # @see #constant?
70
+ def variable?
71
+ any? do |operand|
72
+ operand.is_a?(Variable) ||
73
+ (operand.respond_to?(:variable?) && operand.variable?)
74
+ end
75
+ end
76
+ def constant?; !(variable?); end
77
+ def evaluatable?; true; end
78
+ def executable?; false; end
79
+ def aggregate?; false; end
80
+
81
+ ##
82
+ # Replace operators which are variables with the result of the block
83
+ # descending into operators which are also evaluatable
84
+ #
85
+ # @yield var
86
+ # @yieldparam [RDF::Query::Variable] var
87
+ # @yieldreturn [RDF::Query::Variable, SPARQL::Algebra::Evaluatable]
88
+ # @return [SPARQL::Algebra::Evaluatable] self
89
+ def replace_vars!(&block)
90
+ map! do |op|
91
+ case
92
+ when op.respond_to?(:variable?) && op.variable?
93
+ yield op
94
+ when op.respond_to?(:replace_vars!)
95
+ op.replace_vars!(&block)
96
+ else
97
+ op
98
+ end
99
+ end
100
+ self
101
+ end
102
+
103
+ ##
104
+ # Recursively re-map operators to replace aggregates with temporary variables returned from the block
105
+ #
106
+ # @yield agg
107
+ # @yieldparam [SPARQL::Algebra::Aggregate] agg
108
+ # @yieldreturn [RDF::Query::Variable]
109
+ # @return [SPARQL::Algebra::Evaluatable, RDF::Query::Variable] self
110
+ def replace_aggregate!(&block)
111
+ map! do |op|
112
+ case
113
+ when op.respond_to?(:aggregate?) && op.aggregate?
114
+ yield op
115
+ when op.respond_to?(:replace_aggregate!)
116
+ op.replace_aggregate!(&block)
117
+ else
118
+ op
119
+ end
120
+ end
121
+ self
122
+ end
123
+ end
124
+
125
+ ##
126
+ # Extensions for Ruby's `Hash` class.
127
+ class Hash
128
+ ##
129
+ # Returns the SXP representation of this object, defaults to `self`.
130
+ #
131
+ # @return [String]
132
+ def to_sxp_bin
133
+ to_a.to_sxp_bin
134
+ end
135
+ def to_sxp; to_sxp_bin; end
61
136
  end
62
137
 
63
138
  ##
@@ -65,10 +140,17 @@ end
65
140
  module RDF::Term
66
141
  include SPARQL::Algebra::Expression
67
142
 
68
- def evaluate(bindings)
143
+ # @param [RDF::Query::Solution] bindings
144
+ # a query solution containing zero or more variable bindings
145
+ # @param [Hash{Symbol => Object}] options ({})
146
+ # options passed from query
147
+ # @return [RDF::Term]
148
+ def evaluate(bindings, options = {})
69
149
  self
70
150
  end
71
151
 
152
+ def aggregate?; false; end
153
+
72
154
  # Term compatibility according to SPARQL
73
155
  #
74
156
  # Compatibility of two arguments is defined as:
@@ -185,13 +267,21 @@ class RDF::Query::Variable
185
267
  ##
186
268
  # Returns the value of this variable in the given `bindings`.
187
269
  #
188
- # @param [RDF::Query::Solution, #[]] bindings
270
+ # @param [RDF::Query::Solution] bindings
271
+ # a query solution containing zero or more variable bindings
272
+ # @param [Hash{Symbol => Object}] options ({})
273
+ # options passed from query
189
274
  # @return [RDF::Term] the value of this variable
190
275
  # @raise [TypeError] if the variable is not bound
191
- def evaluate(bindings = {})
276
+ def evaluate(bindings, options = {})
192
277
  raise TypeError if bindings.respond_to?(:bound?) && !bindings.bound?(self)
193
278
  bindings[name.to_sym]
194
279
  end
280
+
281
+ def to_s
282
+ prefix = distinguished? || name.to_s[0,1] == '.' ? '?' : "??"
283
+ unbound? ? "#{prefix}#{name}" : "#{prefix}#{name}=#{value}"
284
+ end
195
285
  end # RDF::Query::Variable
196
286
 
197
287
  ##
@@ -34,18 +34,21 @@ module SPARQL; module Algebra
34
34
  # Note that this operator operates on the effective boolean value
35
35
  # (EBV) of its operands.
36
36
  #
37
- # @param [RDF::Query::Solution, #[]] bindings
37
+ # @param [RDF::Query::Solution] bindings
38
+ # a query solution containing zero or more variable bindings
39
+ # @param [Hash{Symbol => Object}] options ({})
40
+ # options passed from query
38
41
  # @return [RDF::Literal::Boolean] `true` or `false`
39
42
  # @raise [TypeError] if the operands could not be coerced to boolean literals
40
- def evaluate(bindings = {})
43
+ def evaluate(bindings, options = {})
41
44
  begin
42
- left = boolean(operand(0).evaluate(bindings)).true?
45
+ left = boolean(operand(0).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))).true?
43
46
  rescue TypeError
44
47
  left = nil
45
48
  end
46
49
 
47
50
  begin
48
- right = boolean(operand(1).evaluate(bindings)).true?
51
+ right = boolean(operand(1).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))).true?
49
52
  rescue TypeError
50
53
  right = nil
51
54
  end
@@ -19,10 +19,13 @@ module SPARQL; module Algebra
19
19
  # Returns the evaluation of it's operand. Default comparison is in
20
20
  # ascending order. Ordering is applied in {Order}.
21
21
  #
22
- # @param [RDF::Query::Solution, #[]] bindings
22
+ # @param [RDF::Query::Solution] bindings
23
+ # a query solution containing zero or more variable bindings
24
+ # @param [Hash{Symbol => Object}] options ({})
25
+ # options passed from query
23
26
  # @return [RDF::Term]
24
- def evaluate(bindings = {})
25
- operand(0).evaluate(bindings)
27
+ def evaluate(bindings, options = {})
28
+ operand(0).evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))
26
29
  end
27
30
  end # Asc
28
31
  end # Operator
@@ -0,0 +1,36 @@
1
+ module SPARQL; module Algebra
2
+ class Operator
3
+ ##
4
+ # The SPARQL `avg` set function.
5
+ #
6
+ # @example
7
+ # (prefix ((: <http://www.example.org/>))
8
+ # (project (?avg)
9
+ # (extend ((?avg ?.0))
10
+ # (group () ((?.0 (avg ?o)))
11
+ # (bgp (triple ?s :dec ?o))))))
12
+ #
13
+ # @see http://www.w3.org/TR/sparql11-query/#defn_aggAvg
14
+ class Avg < Operator::Unary
15
+ include Aggregate
16
+
17
+ NAME = :avg
18
+
19
+ ##
20
+ # The Avg set function calculates the average value for an expression over a group. It is defined in terms of Sum and Count.
21
+ #
22
+ # @param [Enumerable<Array<RDF::Term>>] enum
23
+ # enum of evaluated operand
24
+ # @return [RDF::Literal::Numeric] The numeric average of the terms
25
+ def apply(enum)
26
+ if enum.empty?
27
+ RDF::Literal(0)
28
+ elsif enum.flatten.all? {|n| n.is_a?(RDF::Literal::Numeric)}
29
+ enum.flatten.reduce(:+) / RDF::Literal::Decimal.new(enum.length)
30
+ else
31
+ raise TypeError, "Averaging non-numeric types: #{enum.flatten}"
32
+ end
33
+ end
34
+ end # Avg
35
+ end # Operator
36
+ end; end # SPARQL::Algebra
@@ -35,12 +35,13 @@ module SPARQL; module Algebra
35
35
  ##
36
36
  # Evaluates this operator using the given variable `bindings`.
37
37
  #
38
- # @param [RDF::Query::Solution, #[]] bindings
38
+ # @param [RDF::Query::Solution] bindings
39
39
  # a query solution containing zero or more variable bindings
40
+ # @param [Hash{Symbol => Object}] options ({})
41
+ # options passed from query
40
42
  # @return [RDF::Term]
41
- # @abstract
42
- def evaluate(bindings = {})
43
- args = operands.map { |operand| operand.evaluate(bindings) }
43
+ def evaluate(bindings, options = {})
44
+ args = operands.map { |operand| operand.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1)) }
44
45
  apply(args.first, bindings)
45
46
  end
46
47
 
@@ -33,10 +33,13 @@ module SPARQL; module Algebra
33
33
  # Returns `true` if the operand is a variable that is bound to a
34
34
  # value in the given `bindings`, `false` otherwise.
35
35
  #
36
- # @param [RDF::Query::Solution, #[]] bindings
36
+ # @param [RDF::Query::Solution] bindings
37
+ # a query solution containing zero or more variable bindings
38
+ # @param [Hash{Symbol => Object}] options ({})
39
+ # options passed from query
37
40
  # @return [RDF::Literal::Boolean] `true` or `false`
38
41
  # @raise [TypeError] if the operand is not a variable
39
- def evaluate(bindings = {})
42
+ def evaluate(bindings, options = {})
40
43
  case var = operand
41
44
  when Variable
42
45
  bindings.respond_to?(:bound?) && bindings.bound?(var) ?
@@ -38,13 +38,16 @@ module SPARQL; module Algebra
38
38
  # COALESCE(?y, 3) #=> 3
39
39
  # COALESCE(?y) #=> raises an error because y is not bound.
40
40
  #
41
- # @param [RDF::Query::Solution, #[]] bindings
41
+ # @param [RDF::Query::Solution] bindings
42
+ # a query solution containing zero or more variable bindings
43
+ # @param [Hash{Symbol => Object}] options ({})
44
+ # options passed from query
42
45
  # @return [RDF::Term]
43
46
  # @raise [TypeError] if none of the operands succeeds
44
- def evaluate(bindings = {})
47
+ def evaluate(bindings, options = {})
45
48
  operands.each do |op|
46
49
  begin
47
- return op.evaluate(bindings)
50
+ return op.evaluate(bindings, options.merge(:depth => options[:depth].to_i + 1))
48
51
  rescue
49
52
  end
50
53
  end