sparql 3.2.1 → 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e9bcab10eb6706316b68b44a0fd917d7b735b18a8353b51e49ef124fc966176d
4
- data.tar.gz: 6da59b4e90fd1b7a7d80c42d941f7c27f6bf95892d17302caf16a13809b03afa
3
+ metadata.gz: 853c52b99bab0b9455b25a81c8c946e5bcb1429dddadf010b66315f1dbafda51
4
+ data.tar.gz: 90d89877c031476efdaa1d0b119b2a2404925186b4e0b3de99818d75ead76bff
5
5
  SHA512:
6
- metadata.gz: d2f99aee7d5b9482bbd653333eeefd215da872291993a2ba138ed9bcfc3f92812672cd1508dfbd11a729d527c9724f0a80ec816c5c84fd2df9933b4b91b5a296
7
- data.tar.gz: 41a11caf8e85ca4bd01213bb0c0c236314560338dbd2825bd2437afbf4fd9855b994150737dbcad00838dc5ac50e368d72e5bfe2e516566a6319f824bdcacc56
6
+ metadata.gz: 98816c81664147aa0fa4ccd90ed14461eb51b652b011395f38f044934c2ff335197fbc3d2d7f447771a5b1e46fa24042aef45fd1f95356b91f52bafee48d1a72
7
+ data.tar.gz: 5545d7ddd245d73382229ed6f9996b24bd66a4285ee18d6295f697202ad5901eb4221cbf2533ef1539b98ccc0c095519e531b541db3e1c4757d813ab888cb9fd
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # SPARQL for RDF.rb
1
+ # SPARQL Query and Update library for Ruby
2
2
 
3
- This is a [Ruby][] implementation of [SPARQL][] for [RDF.rb][].
3
+ An implementation of [SPARQL][] for [RDF.rb][].
4
4
 
5
5
  [![Gem Version](https://badge.fury.io/rb/sparql.png)](https://badge.fury.io/rb/sparql)
6
6
  [![Build Status](https://github.com/ruby-rdf/sparql/workflows/CI/badge.svg?branch=develop)](https://github.com/ruby-rdf/sparql/actions?query=workflow%3ACI)
@@ -17,11 +17,13 @@ This is a [Ruby][] implementation of [SPARQL][] for [RDF.rb][].
17
17
  or HTML.
18
18
  * SPARQL CONSTRUCT or DESCRIBE serialized based on Format, Extension of Mime Type
19
19
  using available RDF Writers (see [Linked Data][])
20
- * SPARQL Client for accessing remote SPARQL endpoints.
21
- * SPARQL Update
20
+ * SPARQL Client for accessing remote SPARQL endpoints (via [sparql-client](https://github.com/ruby-rdf/sparql-client)).
21
+ * [SPARQL 1.1 Protocol][] (via {SPARQL::Server}).
22
+ * [SPARQL 1.1 Update][]
22
23
  * [Rack][] and [Sinatra][] middleware to perform [HTTP content negotiation][conneg] for result formats
23
24
  * Compatible with any [Rack][] or [Sinatra][] application and any Rack-based framework.
24
25
  * Helper method for describing [SPARQL Service Description][SSD]
26
+ * Helper method for setting up datasets as part of the [SPARQL 1.1 Protocol][].
25
27
  * Implementation Report: {file:etc/earl.html EARL}
26
28
  * Compatible with Ruby >= 2.6.
27
29
  * Supports Unicode query strings both on all versions of Ruby.
@@ -29,11 +31,12 @@ This is a [Ruby][] implementation of [SPARQL][] for [RDF.rb][].
29
31
 
30
32
  ## Description
31
33
 
32
- The {SPARQL} gem implements [SPARQL 1.1 Query][], and [SPARQL 1.1 Update][], and provides [Rack][] and [Sinatra][] middleware to provide results using [HTTP Content Negotiation][conneg].
34
+ The {SPARQL} gem implements [SPARQL 1.1 Query][], and [SPARQL 1.1 Update][], and provides [Rack][] and [Sinatra][] middleware to provide results using [HTTP Content Negotiation][conneg] and to support [SPARQL 1.1 Protocol][].
33
35
 
34
36
  * {SPARQL::Grammar} implements a [SPARQL 1.1 Query][] and [SPARQL 1.1 Update][] parser generating [SPARQL S-Expressions (SSE)][SSE].
35
37
  * {SPARQL::Algebra} executes SSE against Any `RDF::Graph` or `RDF::Repository`, including compliant [RDF.rb][] repository adaptors such as [RDF::DO][] and [RDF::Mongo][].
36
38
  * {Rack::SPARQL} and {Sinatra::SPARQL} provide middleware components to format results using an appropriate format based on [HTTP content negotiation][conneg].
39
+ * {SPARQL::Server} implements the [SPARQL 1.1 Protocol][] using {Sinatra::SPARQL}.
37
40
 
38
41
  ### [SPARQL 1.1 Query][] Extensions and Limitations
39
42
  The {SPARQL} gem uses the [SPARQL 1.1 Query][] {file:etc/sparql11.html EBNF grammar}, which provides much more capability than [SPARQL 1.0][], but has a few limitations:
@@ -62,17 +65,22 @@ The gem also includes the following [SPARQL 1.1 Update][] operations:
62
65
  Not supported:
63
66
 
64
67
  * [Federated Query][SPARQL 1.1 Federated Query],
65
- * [Entailment Regimes][SPARQL 1.1 Entailment Regimes],
66
- * [Protocol][SPARQL 1.1 Protocol], and
67
- * [Graph Store HTTP Protocol][SPARQL 1.1 Graph Store HTTP Protocol]
68
-
69
- either in this, or related gems.
68
+ * [Entailment Regimes][SPARQL 1.1 Entailment Regimes], and
69
+ * [Graph Store HTTP Protocol][SPARQL 1.1 Graph Store HTTP Protocol] but the closely related [Linked Data Platform][] implemented in [rdf-ldp](https://github.com/ruby-rdf/rdf-ldp) supports these use cases.
70
70
 
71
71
  ### Updates for RDF 1.1
72
72
  Starting with version 1.1.2, the SPARQL gem uses the 1.1 version of the [RDF.rb][], which adheres to [RDF 1.1 Concepts](https://www.w3.org/TR/rdf11-concepts/) rather than [RDF 1.0](https://www.w3.org/TR/rdf-concepts/). The main difference is that there is now no difference between a _Simple Literal_ (a literal with no datatype or language) and a Literal with datatype _xsd:string_; this causes some minor differences in the way in which queries are understood, and when expecting different results.
73
73
 
74
74
  Additionally, queries now take a block, or return an `Enumerator`; this is in keeping with much of the behavior of [RDF.rb][] methods, including `Queryable#query`, and with version 1.1 or [RDF.rb][], Query#execute. As a consequence, all queries which used to be of the form `query.execute(repository)` may equally be called as `repository.query(query)`. Previously, results were returned as a concrete class implementing `RDF::Queryable` or `RDF::Query::Solutions`, these are now `Enumerators`.
75
75
 
76
+ ### SPARQL 1.2
77
+ The gem supports some of the extensions proposed by the [SPARQL 1.2 Community Group](https://github.com/w3c/sparql-12). In particular, the following extensions are now implemented:
78
+
79
+ * [SEP-0002: better support for Durations, Dates, and Times](https://github.com/w3c/sparql-12/blob/main/SEP/SEP-0002/sep-0002.md)
80
+ * This includes full support for `xsd:date`, `xsd:time`, `xsd:duration`, `xsd:dayTimeDuration`, and `xsd:yearMonthDuration` along with associated XPath/XQuery functions including a new `ADJUST` builtin. (**Note: This feature is subject to change or elimination as the standards process progresses.**)
81
+ * [SEP-0003: Property paths with a min/max hop](https://github.com/w3c/sparql-12/blob/main/SEP/SEP-0003/sep-0003.md)
82
+ * This includes support for non-counting path forms such as `rdf:rest{1,3}` to match the union of paths `rdf:rest`, `rdf:rest/rdf:rest`, and `rdf:rest/rdf:rest/rdf:rest`. (**Note: This feature is subject to change or elimination as the standards process progresses.**)
83
+
76
84
  ### SPARQL Extension Functions
77
85
  Extension functions may be defined, which will be invoked during query evaluation. For example:
78
86
 
@@ -95,6 +103,10 @@ Then, use the function in a query:
95
103
 
96
104
  See {SPARQL::Algebra::Expression.register_extension} for details.
97
105
 
106
+ ### Variable Pre-binding
107
+
108
+ A call to execute a parsed query can include pre-bound variables, which cause queries to be executed with matching variables bound as defined. Variable pre-binding can be done using a Hash structure, or a Query Solution. See [Query with Binding example](#query-with-binding) and {SPARQL::Algebra::Query#execute}.
109
+
98
110
  ### SPARQLStar (SPARQL-star)
99
111
 
100
112
  The gem supports [SPARQL-star][] where patterns may include sub-patterns recursively, for a kind of Reification.
@@ -162,44 +174,31 @@ Note that results can be serialized only when the format supports [RDF-star][].
162
174
 
163
175
  #### SPARQL results
164
176
 
165
- The SPARQL results formats are extended to serialize embedded triples as described for [RDF4J](https://rdf4j.org/documentation/programming/rdfstar/):
177
+ The SPARQL results formats are extended to serialize quoted triples as described for [RDF4J](https://rdf4j.org/documentation/programming/rdfstar/):
166
178
 
167
179
  {
168
180
  "head" : {
169
- "vars" : [
170
- "a",
171
- "b",
172
- "c"
173
- ]
181
+ "vars" : ["a", "b", "c"]
174
182
  },
175
183
  "results" : {
176
184
  "bindings": [
177
185
  { "a" : {
178
186
  "type" : "triple",
179
187
  "value" : {
180
- "s" : {
181
- "type" : "uri",
182
- "value" : "http://example.org/bob"
183
- },
184
- "p" : {
185
- "type" : "uri",
186
- "value" : "http://xmlns.com/foaf/0.1/name"
187
- },
188
+ "s" : {"value" : "http://example.org/bob", "type": "uri"},
189
+ "p" : {"value" : "http://xmlns.com/foaf/0.1/name", "type": "uri"},
188
190
  "o" : {
189
- "datatype" : "http://www.w3.org/2001/XMLSchema#integer",
191
+ "value" : "23",
190
192
  "type" : "literal",
191
- "value" : "23"
193
+ "datatype" : "http://www.w3.org/2001/XMLSchema#integer"
192
194
  }
193
195
  }
194
196
  },
195
- "b": {
196
- "type": "uri",
197
- "value": "http://example.org/certainty"
198
- },
197
+ "b": {"value": "http://example.org/certainty", "type": "uri"},
199
198
  "c" : {
200
- "datatype" : "http://www.w3.org/2001/XMLSchema#decimal",
199
+ "value" : "0.9",
201
200
  "type" : "literal",
202
- "value" : "0.9"
201
+ "datatype" : "http://www.w3.org/2001/XMLSchema#decimal"
203
202
  }
204
203
  }
205
204
  ]
@@ -214,17 +213,23 @@ You would typically return an instance of `RDF::Graph`, `RDF::Repository` or an
214
213
  from your Rack application, and let the `Rack::SPARQL::ContentNegotiation` middleware
215
214
  take care of serializing your response into whatever format the HTTP
216
215
  client requested and understands.
216
+ Content negotiation also transforms `application/x-www-form-urlencoded` to either `application/sparql-query`
217
+ or `application/sparql-update` as appropriate for [SPARQL 1.1 Protocol][].
217
218
 
218
219
  {Sinatra::SPARQL} is a thin Sinatra-specific wrapper around the
219
220
  {Rack::SPARQL} middleware, which implements SPARQL
220
221
  content negotiation for Rack applications. {Sinatra::SPARQL} also supports
221
- [SPARQL 1.1 Service Description][].
222
+ [SPARQL 1.1 Service Description][] (via {Sinatra::SPARQL::Helpers.service_description} and protocol-based dataset mangement via {Sinatra::SPARQL::Helpers.dataset} for `default-graph-uri` and `named-graph-uri` The `using-graph-uri` and `using-named-graph-uri` query parameters are managed through {SPARQL::Algebra::Operator::Modify#execute}.
222
223
 
223
224
  The middleware queries [RDF.rb][] for the MIME content types of known RDF
224
225
  serialization formats, so it will work with whatever serialization extensions
225
226
  that are currently available for RDF.rb. (At present, this includes support
226
227
  for N-Triples, N-Quads, Turtle, RDF/XML, RDF/JSON, JSON-LD, RDFa, TriG and TriX.)
227
228
 
229
+ ### Server
230
+
231
+ A simple [Sinatra][]-based server is implemented in {SPARQL::Server.application} using {Rack::SPARQL} and {Sinatra::SPARQL} completes the implementation of [SPARQL 1.1 Protocol][] and can be used to compose a server including other capabilities.
232
+
228
233
  ### Remote datasets
229
234
 
230
235
  A SPARQL query containing `FROM` or `FROM NAMED` (also `UPDATE` or `UPDATE NAMED`) will load the referenced IRI unless the repository already contains a graph with that same IRI. This is performed using [RDF.rb][] `RDF::Util::File.open_file` passing HTTP Accept headers for various available RDF formats. For best results, require [Linked Data][] to enable a full set of RDF formats in the `GET` request. Also, consider overriding `RDF::Util::File.open_file` with an implementation with support for HTTP Get headers (such as `Net::HTTP`).
@@ -286,6 +291,20 @@ a full set of RDF formats.
286
291
  query = SPARQL::Algebra.parse(%{(bgp (triple ?s ?p ?o))})
287
292
  sparql = query.to_sparql #=> "SELECT * WHERE { ?s ?p ?o }"
288
293
 
294
+ ### Query with Binding
295
+
296
+ bindings = {page: RDF::URI("https://greggkellogg.net/")}
297
+ queryable = RDF::Repository.load("etc/doap.ttl")
298
+ query = SPARQL.parse(%(
299
+ PREFIX foaf: <http://xmlns.com/foaf/0.1/>
300
+ SELECT ?person
301
+ WHERE {
302
+ ?person foaf:homepage ?page .
303
+ }
304
+ ))
305
+ solutions = query.execute(queryable, bindings: bindings)
306
+ solutions.to_sxp #=> (((person <https://greggkellogg.net/foaf#me>)))
307
+
289
308
  ### Command line processing
290
309
 
291
310
  sparql execute --dataset etc/doap.ttl etc/from_default.rq
@@ -435,7 +454,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo
435
454
  ## License
436
455
 
437
456
  This is free and unencumbered public domain software. For more information,
438
- see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
457
+ see <https://unlicense.org/> or the accompanying {file:UNLICENSE}.
439
458
 
440
459
  A copy of the [SPARQL EBNF][] and derived parser files are included in the repository, which are not covered under the UNLICENSE. These files are covered via the [W3C Document License](https://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231).
441
460
 
@@ -454,14 +473,14 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are also included in
454
473
  [SPARQL 1.0 tests]:https://www.w3.org/2001/sw/DataAccess/tests/
455
474
  [SPARQL 1.1 tests]: https://www.w3.org/2009/sparql/docs/tests/
456
475
  [SSE]: https://jena.apache.org/documentation/notes/sse.html
457
- [SXP]: https://www.rubydoc.info/github/dryruby/sxp
476
+ [SXP]: https://dryruby.github.io/sxp
458
477
  [grammar]: https://www.w3.org/TR/sparql11-query/#grammar
459
478
  [RDF 1.1]: https://www.w3.org/TR/rdf11-concepts
460
- [RDF.rb]: https://rubydoc.info/github/ruby-rdf/rdf
479
+ [RDF.rb]: https://ruby-rdf.github.io/rdf
461
480
  [RDF-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html
462
481
  [SPARQL-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#sparql-query-language
463
482
  [Linked Data]: https://rubygems.org/gems/linkeddata
464
- [SPARQL doc]: https://rubydoc.info/github/ruby-rdf/sparql/frames
483
+ [SPARQL doc]: https://ruby-rdf.github.io/sparql/frames
465
484
  [SPARQL XML]: https://www.w3.org/TR/rdf-sparql-XMLres/
466
485
  [SPARQL JSON]: https://www.w3.org/TR/rdf-sparql-json-res/
467
486
  [SPARQL EBNF]: https://www.w3.org/TR/sparql11-query/#sparqlGrammar
@@ -481,4 +500,4 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are also included in
481
500
  [SPARQL 1.1 Entailment Regimes]: https://www.w3.org/TR/sparql11-entailment/
482
501
  [SPARQL 1.1 Protocol]: https://www.w3.org/TR/sparql11-protocol/
483
502
  [SPARQL 1.1 Graph Store HTTP Protocol]: https://www.w3.org/TR/sparql11-http-rdf-update/
484
-
503
+ [Linked Data Platform]: https://www.w3.org/TR/ldp/
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.1
1
+ 3.2.3
data/bin/sparql CHANGED
@@ -63,39 +63,10 @@ def run(input, **options)
63
63
  end
64
64
 
65
65
  def server(options)
66
- require 'sinatra/sparql'
67
- repository = options.fetch(:dataset, RDF::Repository.new)
68
-
69
- app = Sinatra.new do
70
- register Sinatra::SPARQL
71
- set :repository, repository
72
-
73
- before do
74
- options[:logger].info "#{request.request_method} [#{request.path_info}], " +
75
- params.merge(Accept: request.accept.map(&:to_s)).map {|k,v| "#{k}=#{v}" unless k.to_s == "content"}.join(" ")
76
- end
77
-
78
- get '/' do
79
- if params["query"]
80
- query = params["query"].to_s.match(/^http:/) ? RDF::Util::File.open_file(params["query"]) : ::CGI.unescape(params["query"].to_s)
81
- SPARQL.execute(query, settings.repository)
82
- else
83
- settings.sparql_options.replace(standard_prefixes: true)
84
- settings.sparql_options.merge!(:prefixes => {
85
- ssd: "http://www.w3.org/ns/sparql-service-description#",
86
- void: "http://rdfs.org/ns/void#"
87
- })
88
- service_description(repo: settings.repository, endpoint: url)
89
- end
90
- end
91
-
92
- post '/' do
93
- SPARQL.execute(params['query'], settings.repository)
94
- end
95
- end
66
+ app = SPARQL::Server.application(**options)
96
67
  Rack::Server.start(app: app, Port: options.fetch(:port, 9292))
97
68
  rescue LoadError
98
- $stderr.puts "Running SPARQL server requires Rack to be in environment: #{$!.message}"
69
+ $stderr.puts "Running SPARQL server requires Rack and Sinatra to be in environment: #{$!.message}"
99
70
  end
100
71
 
101
72
  def usage
@@ -39,15 +39,36 @@ module Rack; module SPARQL
39
39
  # If result is `RDF::Literal::Boolean`, `RDF::Query::Results`, or `RDF::Enumerable`
40
40
  # The result is serialized using {SPARQL::Results}
41
41
  #
42
- # Inserts ordered content types into the environment as `ORDERED_CONTENT_TYPES` if an Accept header is present
42
+ # Inserts ordered content types into the environment as `ORDERED_CONTENT_TYPES` if an Accept header is present.
43
+ #
44
+ # Normalizes `application/x-www-form-urlencoded` to either `application/sparql-query` or `application/sparql-update` forms.
43
45
  #
44
46
  # @param [Hash{String => String}] env
45
47
  # @return [Array(Integer, Hash, #each)]
46
48
  # @see https://www.rubydoc.info/github/rack/rack/Rack/Runtime#call-instance_method
47
49
  def call(env)
48
50
  env['ORDERED_CONTENT_TYPES'] = parse_accept_header(env['HTTP_ACCEPT']) if env.has_key?('HTTP_ACCEPT')
51
+ # Normalize application/x-www-form-urlencoded to application/sparql-query or application/sparql-update
52
+ if env['REQUEST_METHOD'] == 'POST' && env.fetch('CONTENT_TYPE', 'application/x-www-form-urlencoded').to_s.start_with?('application/x-www-form-urlencoded')
53
+ content = env['rack.input'].read
54
+ params = Rack::Utils.parse_query(content)
55
+ if query = params.delete('query')
56
+ return [406, {"Content-Type" => "text/plain"}, ["Multiple query parameters"]] unless query.is_a?(String)
57
+ env['rack.input'] = StringIO.new(query)
58
+ env['CONTENT_TYPE'] = 'application/sparql-query'
59
+ env['QUERY_STRING'] = Rack::Utils.build_query(params)
60
+ elsif update = params.delete('update')
61
+ return [406, {"Content-Type" => "text/plain"}, ["Multiple update parameters"]] unless update.is_a?(String)
62
+ env['rack.input'] = StringIO.new(update)
63
+ env['CONTENT_TYPE'] = 'application/sparql-update'
64
+ env['QUERY_STRING'] = Rack::Utils.build_query(params)
65
+ else
66
+ env['rack.input'].rewind # never mind
67
+ end
68
+ end
49
69
  response = app.call(env)
50
70
  body = response[2].respond_to?(:body) ? response[2].body : response[2]
71
+ body = body.first if body.is_a?(Array) && body.length == 1 && body.first.is_a?(RDF::Literal::Boolean)
51
72
  case body
52
73
  when RDF::Enumerable, RDF::Query::Solutions, RDF::Literal::Boolean
53
74
  response[2] = body # Put it back in the response, it might have been a proxy
@@ -11,7 +11,7 @@ class Sinatra::Response
11
11
 
12
12
  # Rack::Response#finish sometimes returns self as response body. We don't want that.
13
13
  status, headers, result = super
14
- result = body if result == self
14
+ result = body if self == result
15
15
  [status, headers, result]
16
16
  end
17
17
  end
@@ -1,6 +1,7 @@
1
1
  require 'sinatra/base'
2
2
  require 'sinatra/sparql/extensions'
3
3
  require 'rack/sparql'
4
+ require 'rdf/aggregate_repo'
4
5
 
5
6
  module Sinatra
6
7
  ##
@@ -13,6 +14,7 @@ module Sinatra
13
14
  ##
14
15
  # Helper methods.
15
16
  module Helpers
17
+
16
18
  ##
17
19
  # This is useful when a GET request is performed against a SPARQL endpoint and no query is performed. Provide a set of datasets, including a default dataset along with optional triple count, dump location, and description of the dataset.
18
20
  #
@@ -36,23 +38,36 @@ module Sinatra
36
38
 
37
39
  node = RDF::Node.new
38
40
  g << [node, RDF.type, sd.join("#Service")]
39
- g << [node, sd.join("#endpoint"), options[:endpoint] || url("/sparql")]
41
+ g << [node, sd.join("#endpoint"), RDF::URI(url(options.fetch(:endpoint, "/sparql")))]
42
+ g << [node, sd.join("#supportedLanguage"), sd.join("#SPARQL10Query")]
40
43
  g << [node, sd.join("#supportedLanguage"), sd.join("#SPARQL11Query")]
44
+ g << [node, sd.join("#supportedLanguage"), sd.join("#SPARQL11Update")]
45
+ g << [node, sd.join("#supportedLanguage"), RDF::URI('http://www.w3.org/ns/rdf-star#SPARQLStarQuery')]
46
+ g << [node, sd.join("#supportedLanguage"), RDF::URI('http://www.w3.org/ns/rdf-star#SPARQLStarUpdate')]
41
47
 
48
+ # Input formats
49
+ RDF::Reader.map(&:format).select(&:to_uri).each do |format|
50
+ g << [node, sd.join("#inputFormat"), format.to_uri]
51
+ end
52
+
42
53
  # Result formats, both RDF and SPARQL Results.
43
- # FIXME: We should get this from the avaliable serializers
44
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/RDF_XML")]
45
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/Turtle")]
46
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/RDFa")]
47
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/N-Triples")]
48
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_XML")]
49
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_JSON")]
50
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_CSV")]
51
- g << [node, sd.join("#resultFormat"), RDF::URI("http://www.w3.org/ns/formats/SPARQL_Results_TSV")]
52
-
54
+ %w(
55
+ http://www.w3.org/ns/formats/SPARQL_Results_XML
56
+ http://www.w3.org/ns/formats/SPARQL_Results_JSON
57
+ http://www.w3.org/ns/formats/SPARQL_Results_CSV
58
+ http://www.w3.org/ns/formats/SPARQL_Results_TSV
59
+ ).each do |uri|
60
+ g << [node, sd.join("#resultFormat"), uri]
61
+ end
62
+
63
+ RDF::Writer.map(&:format).select(&:to_uri).each do |format|
64
+ g << [node, sd.join("#resultFormat"), format.to_uri]
65
+ end
66
+
53
67
  # Features
54
68
  g << [node, sd.join("#feature"), sd.join("#DereferencesURIs")]
55
-
69
+ #g << [node, sd.join("#feature"), sd.join("#BasicFederatedQuery")]
70
+
56
71
  # Datasets
57
72
  ds = RDF::Node.new
58
73
  g << [node, sd.join("#defaultDataset"), ds]
@@ -86,6 +101,36 @@ module Sinatra
86
101
  end
87
102
  g
88
103
  end
104
+
105
+ ##
106
+ # This either creates a merge repo, or uses the standard repository for performing the query, based on the parameters passed (`default-graph-uri` and `named-graph-uri`).
107
+ # Loads from the datasource, unless a graph named by
108
+ # the datasource URI already exists in the repository.
109
+ #
110
+ # @return [RDF::Dataset]
111
+ # @see Algebra::Operator::Dataset
112
+ def dataset(**options)
113
+ logger = options.fetch(:logger, ::Logger.new(false))
114
+ repo = settings.repository
115
+ if %i(default-graph-uri named-graph-uri).any? {|k| options.key?(k)}
116
+ default_datasets = Array(options[:"default-graph-uri"]).map {|u| RDF::URI(u)}
117
+ named_datasets = Array(options[:"named-graph-uri"]).map {|u| RDF::URI(u)}
118
+
119
+ (default_datasets + named_datasets).each do |uri|
120
+ load_opts = {logger: logger, graph_name: uri, base_uri: uri}
121
+ unless repo.has_graph?(uri)
122
+ logger.debug(options) {"=> load #{uri}"}
123
+ repo.load(uri.to_s, **load_opts)
124
+ end
125
+ end
126
+
127
+ # Create an aggregate based on queryable having just the bits we want
128
+ aggregate = RDF::AggregateRepo.new(repo)
129
+ named_datasets.each {|name| aggregate.named(name) if repo.has_graph?(name)}
130
+ aggregate.default(*default_datasets.select {|name| repo.has_graph?(name)})
131
+ aggregate
132
+ end || settings.repository
133
+ end
89
134
  end
90
135
 
91
136
  ##
@@ -6,6 +6,19 @@ module SPARQL; module Algebra
6
6
  module Expression
7
7
  include RDF::Util::Logger
8
8
 
9
+ # Operators for which `:triple` denotes a pattern, not a builtin
10
+ PATTERN_PARENTS = [
11
+ Operator::BGP,
12
+ Operator::Construct,
13
+ Operator::Delete,
14
+ Operator::DeleteData,
15
+ Operator::DeleteWhere,
16
+ Operator::Graph,
17
+ Operator::Insert,
18
+ Operator::InsertData,
19
+ Operator::Path,
20
+ ].freeze
21
+
9
22
  ##
10
23
  # @example
11
24
  # Expression.parse('(isLiteral 3.1415)')
@@ -84,7 +97,7 @@ module SPARQL; module Algebra
84
97
  # any additional options (see {Operator#initialize})
85
98
  # @return [Expression]
86
99
  # @raise [TypeError] if any of the operands is invalid
87
- def self.new(sse, **options)
100
+ def self.new(sse, parent_operator: nil, **options)
88
101
  raise ArgumentError, "invalid SPARQL::Algebra::Expression form: #{sse.inspect}" unless sse.is_a?(Array)
89
102
 
90
103
  operator = Operator.for(sse.first, sse.length - 1)
@@ -99,12 +112,12 @@ module SPARQL; module Algebra
99
112
  return case sse.first
100
113
  when Array
101
114
  debug(options) {"Map array elements #{sse}"}
102
- sse.map {|s| self.new(s, depth: options[:depth].to_i + 1, **options)}
115
+ sse.map {|s| self.new(s, parent_operator: parent_operator, depth: options[:depth].to_i + 1, **options)}
103
116
  else
104
117
  debug(options) {"No operator found for #{sse.first}"}
105
118
  sse.map do |s|
106
119
  s.is_a?(Array) ?
107
- self.new(s, depth: options[:depth].to_i + 1) :
120
+ self.new(s, parent_operator: parent_operator, depth: options[:depth].to_i + 1) :
108
121
  s
109
122
  end
110
123
  end
@@ -114,7 +127,7 @@ module SPARQL; module Algebra
114
127
  debug(options) {"Operator=#{operator.inspect}, Operand=#{operand.inspect}"}
115
128
  case operand
116
129
  when Array
117
- self.new(operand, depth: options[:depth].to_i + 1, **options)
130
+ self.new(operand, parent_operator: operator, depth: options[:depth].to_i + 1, **options)
118
131
  when Operator, Variable, RDF::Term, RDF::Query, Symbol
119
132
  operand
120
133
  when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time
@@ -127,7 +140,13 @@ module SPARQL; module Algebra
127
140
  logger = options[:logger]
128
141
  options.delete_if {|k, v| [:debug, :logger, :depth, :prefixes, :base_uri, :update, :validate].include?(k) }
129
142
  begin
130
- operator.new(*operands, **options)
143
+ # Due to confusiong over (triple) and special-case for (qtriple)
144
+ if operator == RDF::Query::Pattern
145
+ options = options.merge(quoted: true) if sse.first == :qtriple
146
+ elsif operator == Operator::Triple && PATTERN_PARENTS.include?(parent_operator)
147
+ operator = RDF::Query::Pattern
148
+ end
149
+ operator.new(*operands, parent_operator: operator, **options)
131
150
  rescue ArgumentError => e
132
151
  if logger
133
152
  logger.error("Operator=#{operator.inspect}: #{e}")
@@ -215,7 +234,7 @@ module SPARQL; module Algebra
215
234
  #
216
235
  # @param [RDF::URI] datatype
217
236
  # Datatype to evaluate, one of:
218
- # xsd:integer, xsd:decimal xsd:float, xsd:double, xsd:string, xsd:boolean, or xsd:dateTime
237
+ # xsd:integer, xsd:decimal xsd:float, xsd:double, xsd:string, xsd:boolean, xsd:dateTime, xsd:duration, xsd:dayTimeDuration, xsd:yearMonthDuration
219
238
  # @param [RDF::Term] value
220
239
  # Value, which should be a typed literal, where the type must be that specified
221
240
  # @raise [TypeError] if datatype is not a URI or value cannot be cast to datatype
@@ -223,7 +242,7 @@ module SPARQL; module Algebra
223
242
  # @see https://www.w3.org/TR/sparql11-query/#FunctionMapping
224
243
  def self.cast(datatype, value)
225
244
  case datatype
226
- when RDF::XSD.dateTime
245
+ when RDF::XSD.date, RDF::XSD.time, RDF::XSD.dateTime
227
246
  case value
228
247
  when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time
229
248
  RDF::Literal.new(value, datatype: datatype)
@@ -232,6 +251,15 @@ module SPARQL; module Algebra
232
251
  else
233
252
  RDF::Literal.new(value.value, datatype: datatype, validate: true)
234
253
  end
254
+ when RDF::XSD.duration, RDF::XSD.dayTimeDuration, RDF::XSD.yearMonthDuration
255
+ case value
256
+ when RDF::Literal::Duration, RDF::Literal::DayTimeDuration, RDF::Literal::YearMonthDuration
257
+ RDF::Literal.new(value, datatype: datatype, validate: true, canonicalize: true)
258
+ when RDF::Literal::Numeric, RDF::Literal::Boolean, RDF::URI, RDF::Node
259
+ raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
260
+ else
261
+ RDF::Literal.new(value.value, datatype: datatype, validate: true, canonicalize: true)
262
+ end
235
263
  when RDF::XSD.float, RDF::XSD.double
236
264
  case value
237
265
  when RDF::Literal::Boolean
@@ -124,6 +124,14 @@ class Array
124
124
  end
125
125
  def constant?; !(variable?); end
126
126
 
127
+ ##
128
+ # The variables used in this array.
129
+ #
130
+ # @return [Hash{Symbol => RDF::Query::Variable}]
131
+ def variables
132
+ self.inject({}) {|hash, o| o.respond_to?(:variables) ? hash.merge(o.variables) : hash}
133
+ end
134
+
127
135
  ##
128
136
  # Does this contain any nodes?
129
137
  #
@@ -347,7 +355,7 @@ class RDF::Statement
347
355
  # Transform Statement Pattern into an SXP
348
356
  # @return [Array]
349
357
  def to_sxp_bin
350
- [ (has_graph? ? :quad : :triple),
358
+ [ (has_graph? ? :quad : (quoted? ? :qtriple : :triple)),
351
359
  (:inferred if inferred?),
352
360
  subject,
353
361
  predicate,
@@ -370,20 +378,10 @@ class RDF::Statement
370
378
  #
371
379
  # Returns a partial SPARQL grammar for this term.
372
380
  #
373
- # @param [Boolean] as_statement (false) serialize as < ... >, otherwise TRIPLE(...)
374
381
  # @return [String]
375
- def to_sparql(as_statement: false, **options)
376
- if as_statement
377
- to_triple.map do |term|
378
- if term.is_a?(::RDF::Statement)
379
- "<<" + term.to_sparql(as_statement: true, **options) + ">>"
380
- else
381
- term.to_sparql(**options)
382
- end
383
- end.join(" ")
384
- else
385
- "TRIPLE(#{to_triple.to_sparql(as_statement: true, **options)})"
386
- end
382
+ def to_sparql(**options)
383
+ str = to_triple.map {|term| term.to_sparql(**options)}.join(" ")
384
+ quoted? ? ('<<' + str + '>>') : str
387
385
  end
388
386
 
389
387
  ##
@@ -441,7 +439,9 @@ class RDF::Query
441
439
  # Filter Operations
442
440
  # @return [String]
443
441
  def to_sparql(top_level: true, filter_ops: [], **options)
444
- str = @patterns.map { |e| e.to_sparql(as_statement: true, top_level: false, **options) }.join(". \n")
442
+ str = @patterns.map do |e|
443
+ e.to_sparql(top_level: false, **options) + " . \n"
444
+ end.join("")
445
445
  str = "GRAPH #{graph_name.to_sparql(**options)} {\n#{str}\n}\n" if graph_name
446
446
  if top_level
447
447
  SPARQL::Algebra::Operator.to_sparql(str, filter_ops: filter_ops, **options)
@@ -454,9 +454,9 @@ class RDF::Query
454
454
  # Extensons
455
455
  extensions = options.fetch(:extensions, [])
456
456
  extensions.each do |as, expression|
457
- v = expression.to_sparql(as_statement: true, **options)
458
- v = "<< #{v} >>" if expression.is_a?(RDF::Statement)
459
- str << "\nBIND (" << v << " AS " << as.to_sparql(**options) << ") ."
457
+ v = expression.to_sparql(**options)
458
+ pp = RDF::Query::Variable.new(as).to_sparql(**options)
459
+ str << "\nBIND (" << v << " AS " << pp << ") ."
460
460
  end
461
461
  str = "{#{str}}" unless filter_ops.empty? && extensions.empty?
462
462
  str