sparql 3.2.0 → 3.2.4

Sign up to get free protection for your applications and to get access to all the features.
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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecbe9289bf43f7fa28940a82545ec08b19c54d8bdf64693934405e83ddb4cc53
4
- data.tar.gz: 24ab0942be18a9d9d281452ba9a7195dcad2a31755bda6d84afe4f49fe7c34c1
3
+ metadata.gz: 932897287930ea7265a07628e73c13a06b02b9e0d1572b578356166d3c8fe70a
4
+ data.tar.gz: 9d17310bc07080475780c8acccbe57acb4bbeb40e3f66ab7b74a09b15ccf0863
5
5
  SHA512:
6
- metadata.gz: 89a77c749eb99b0fe83f27d5f413556ac2f6cf22af7e06b8e0588155bbf5a50eb053b1f87ffe11a13b5fd873d5fcaaee1166cdf336aacd3029380898a82da382
7
- data.tar.gz: 13be95c405b9fdf7e678bc9bbaf6dcc45075a692a08e1762deacc0779948996fdc992376f3a564644f50270282c76a759023d7393e28db07ec7828a73324a948
6
+ metadata.gz: e701784500aece828b2cfd41eb0af78a809bda575eed047158e962ff38678d524b179c0c3faef21fc5b5724894ada3066637b608778a1809fdc7b9bda2ffe2a2
7
+ data.tar.gz: 77c2520a5f4923a1fe27d589ea6a09b6f5a36fa9c17b564184f206971204fec6988b0941c350e10d812ccfd9b0934e6ae629b82207abfaabf8d7bcc3a380ab33
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`).
@@ -281,9 +286,25 @@ a full set of RDF formats.
281
286
 
282
287
  ### Parsing a SSE to SPARQL query or update string to SPARQL
283
288
 
289
+ # Note: if the SSE uses extension functions, they either must be XSD casting functions, or custom functions which are registered extensions. (See [SPARQL Extension Functions](#sparql-extension-functions))
290
+
284
291
  query = SPARQL::Algebra.parse(%{(bgp (triple ?s ?p ?o))})
285
292
  sparql = query.to_sparql #=> "SELECT * WHERE { ?s ?p ?o }"
286
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
+
287
308
  ### Command line processing
288
309
 
289
310
  sparql execute --dataset etc/doap.ttl etc/from_default.rq
@@ -433,7 +454,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo
433
454
  ## License
434
455
 
435
456
  This is free and unencumbered public domain software. For more information,
436
- see <https://unlicense.org/> or the accompanying {file:UNLICENSE} file.
457
+ see <https://unlicense.org/> or the accompanying {file:UNLICENSE}.
437
458
 
438
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).
439
460
 
@@ -452,14 +473,14 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are also included in
452
473
  [SPARQL 1.0 tests]:https://www.w3.org/2001/sw/DataAccess/tests/
453
474
  [SPARQL 1.1 tests]: https://www.w3.org/2009/sparql/docs/tests/
454
475
  [SSE]: https://jena.apache.org/documentation/notes/sse.html
455
- [SXP]: https://www.rubydoc.info/github/dryruby/sxp
476
+ [SXP]: https://dryruby.github.io/sxp
456
477
  [grammar]: https://www.w3.org/TR/sparql11-query/#grammar
457
478
  [RDF 1.1]: https://www.w3.org/TR/rdf11-concepts
458
- [RDF.rb]: https://rubydoc.info/github/ruby-rdf/rdf
479
+ [RDF.rb]: https://ruby-rdf.github.io/rdf
459
480
  [RDF-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html
460
481
  [SPARQL-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#sparql-query-language
461
482
  [Linked Data]: https://rubygems.org/gems/linkeddata
462
- [SPARQL doc]: https://rubydoc.info/github/ruby-rdf/sparql/frames
483
+ [SPARQL doc]: https://ruby-rdf.github.io/sparql/frames
463
484
  [SPARQL XML]: https://www.w3.org/TR/rdf-sparql-XMLres/
464
485
  [SPARQL JSON]: https://www.w3.org/TR/rdf-sparql-json-res/
465
486
  [SPARQL EBNF]: https://www.w3.org/TR/sparql11-query/#sparqlGrammar
@@ -479,4 +500,4 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are also included in
479
500
  [SPARQL 1.1 Entailment Regimes]: https://www.w3.org/TR/sparql11-entailment/
480
501
  [SPARQL 1.1 Protocol]: https://www.w3.org/TR/sparql11-protocol/
481
502
  [SPARQL 1.1 Graph Store HTTP Protocol]: https://www.w3.org/TR/sparql11-http-rdf-update/
482
-
503
+ [Linked Data Platform]: https://www.w3.org/TR/ldp/
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.2.0
1
+ 3.2.4
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)')
@@ -66,9 +79,11 @@ module SPARQL; module Algebra
66
79
  #
67
80
  # @param [Array] sse
68
81
  # a SPARQL S-Expression (SSE) form
82
+ # @param [Hash{Symbol => Object}] options
83
+ # any additional options (see {Operator#initialize})
69
84
  # @return [Expression]
70
- def self.for(*sse)
71
- self.new(sse)
85
+ def self.for(*sse, **options)
86
+ self.new(sse, **options)
72
87
  end
73
88
  class << self; alias_method :[], :for; end
74
89
 
@@ -82,20 +97,27 @@ module SPARQL; module Algebra
82
97
  # any additional options (see {Operator#initialize})
83
98
  # @return [Expression]
84
99
  # @raise [TypeError] if any of the operands is invalid
85
- def self.new(sse, **options)
100
+ def self.new(sse, parent_operator: nil, **options)
86
101
  raise ArgumentError, "invalid SPARQL::Algebra::Expression form: #{sse.inspect}" unless sse.is_a?(Array)
87
102
 
88
103
  operator = Operator.for(sse.first, sse.length - 1)
104
+
105
+ # If we don't find an operator, and sse.first is an extension IRI, use a function call
106
+ if !operator && sse.first.is_a?(RDF::URI) && self.extension?(sse.first)
107
+ operator = Operator.for(:function_call, sse.length)
108
+ sse.unshift(:function_call)
109
+ end
110
+
89
111
  unless operator
90
112
  return case sse.first
91
113
  when Array
92
114
  debug(options) {"Map array elements #{sse}"}
93
- 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)}
94
116
  else
95
117
  debug(options) {"No operator found for #{sse.first}"}
96
118
  sse.map do |s|
97
119
  s.is_a?(Array) ?
98
- self.new(s, depth: options[:depth].to_i + 1) :
120
+ self.new(s, parent_operator: parent_operator, depth: options[:depth].to_i + 1) :
99
121
  s
100
122
  end
101
123
  end
@@ -105,7 +127,7 @@ module SPARQL; module Algebra
105
127
  debug(options) {"Operator=#{operator.inspect}, Operand=#{operand.inspect}"}
106
128
  case operand
107
129
  when Array
108
- self.new(operand, depth: options[:depth].to_i + 1, **options)
130
+ self.new(operand, parent_operator: operator, depth: options[:depth].to_i + 1, **options)
109
131
  when Operator, Variable, RDF::Term, RDF::Query, Symbol
110
132
  operand
111
133
  when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time
@@ -115,11 +137,22 @@ module SPARQL; module Algebra
115
137
  end
116
138
 
117
139
  debug(options) {"#{operator.inspect}(#{operands.map(&:inspect).join(',')})"}
140
+ logger = options[:logger]
118
141
  options.delete_if {|k, v| [:debug, :logger, :depth, :prefixes, :base_uri, :update, :validate].include?(k) }
119
142
  begin
120
- operator.new(*operands, **options)
143
+ # Due to confusion 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)
121
150
  rescue ArgumentError => e
122
- error(options) {"Operator=#{operator.inspect}: #{e}"}
151
+ if logger
152
+ logger.error("Operator=#{operator.inspect}: #{e}")
153
+ else
154
+ raise "Operator=#{operator.inspect}: #{e}"
155
+ end
123
156
  end
124
157
  end
125
158
 
@@ -163,6 +196,17 @@ module SPARQL; module Algebra
163
196
  @extensions ||= {}
164
197
  end
165
198
 
199
+ ##
200
+ # Is an extension function available?
201
+ #
202
+ # It's either a registered extension, or an XSD casting function
203
+ #
204
+ # @param [RDF::URI] function
205
+ # @return [Boolean]
206
+ def self.extension?(function)
207
+ function.to_s.start_with?(RDF::XSD.to_s) || self.extensions[function]
208
+ end
209
+
166
210
  ##
167
211
  # Invoke an extension function.
168
212
  #
@@ -190,7 +234,7 @@ module SPARQL; module Algebra
190
234
  #
191
235
  # @param [RDF::URI] datatype
192
236
  # Datatype to evaluate, one of:
193
- # 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
194
238
  # @param [RDF::Term] value
195
239
  # Value, which should be a typed literal, where the type must be that specified
196
240
  # @raise [TypeError] if datatype is not a URI or value cannot be cast to datatype
@@ -198,7 +242,7 @@ module SPARQL; module Algebra
198
242
  # @see https://www.w3.org/TR/sparql11-query/#FunctionMapping
199
243
  def self.cast(datatype, value)
200
244
  case datatype
201
- when RDF::XSD.dateTime
245
+ when RDF::XSD.date, RDF::XSD.time, RDF::XSD.dateTime
202
246
  case value
203
247
  when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time
204
248
  RDF::Literal.new(value, datatype: datatype)
@@ -207,6 +251,15 @@ module SPARQL; module Algebra
207
251
  else
208
252
  RDF::Literal.new(value.value, datatype: datatype, validate: true)
209
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
210
263
  when RDF::XSD.float, RDF::XSD.double
211
264
  case value
212
265
  when RDF::Literal::Boolean