sparql 3.2.0 → 3.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +59 -38
- data/VERSION +1 -1
- data/bin/sparql +2 -31
- data/lib/rack/sparql/conneg.rb +22 -1
- data/lib/sinatra/sparql/extensions.rb +1 -1
- data/lib/sinatra/sparql.rb +57 -12
- data/lib/sparql/algebra/expression.rb +63 -10
- data/lib/sparql/algebra/extensions.rb +39 -35
- data/lib/sparql/algebra/operator/abs.rb +1 -1
- data/lib/sparql/algebra/operator/adjust.rb +69 -0
- data/lib/sparql/algebra/operator/alt.rb +1 -1
- data/lib/sparql/algebra/operator/avg.rb +3 -1
- data/lib/sparql/algebra/operator/bgp.rb +9 -1
- data/lib/sparql/algebra/operator/clear.rb +13 -3
- data/lib/sparql/algebra/operator/construct.rb +1 -1
- data/lib/sparql/algebra/operator/count.rb +36 -6
- data/lib/sparql/algebra/operator/create.rb +5 -4
- data/lib/sparql/algebra/operator/dataset.rb +29 -11
- data/lib/sparql/algebra/operator/day.rb +2 -2
- data/lib/sparql/algebra/operator/delete.rb +3 -1
- data/lib/sparql/algebra/operator/delete_data.rb +1 -1
- data/lib/sparql/algebra/operator/delete_where.rb +1 -1
- data/lib/sparql/algebra/operator/distinct.rb +2 -2
- data/lib/sparql/algebra/operator/divide.rb +1 -1
- data/lib/sparql/algebra/operator/drop.rb +15 -6
- data/lib/sparql/algebra/operator/encode_for_uri.rb +2 -4
- data/lib/sparql/algebra/operator/exprlist.rb +3 -1
- data/lib/sparql/algebra/operator/extend.rb +73 -5
- data/lib/sparql/algebra/operator/filter.rb +6 -1
- data/lib/sparql/algebra/operator/function_call.rb +64 -0
- data/lib/sparql/algebra/operator/graph.rb +57 -7
- data/lib/sparql/algebra/operator/group.rb +105 -6
- data/lib/sparql/algebra/operator/group_concat.rb +25 -1
- data/lib/sparql/algebra/operator/hours.rb +2 -2
- data/lib/sparql/algebra/operator/if.rb +10 -10
- data/lib/sparql/algebra/operator/insert.rb +3 -1
- data/lib/sparql/algebra/operator/insert_data.rb +1 -1
- data/lib/sparql/algebra/operator/is_blank.rb +1 -2
- data/lib/sparql/algebra/operator/is_iri.rb +1 -2
- data/lib/sparql/algebra/operator/is_literal.rb +1 -2
- data/lib/sparql/algebra/operator/is_numeric.rb +1 -2
- data/lib/sparql/algebra/operator/join.rb +39 -5
- data/lib/sparql/algebra/operator/lcase.rb +2 -3
- data/lib/sparql/algebra/operator/left_join.rb +27 -9
- data/lib/sparql/algebra/operator/max.rb +3 -1
- data/lib/sparql/algebra/operator/min.rb +4 -2
- data/lib/sparql/algebra/operator/minus.rb +46 -6
- data/lib/sparql/algebra/operator/minutes.rb +2 -2
- data/lib/sparql/algebra/operator/modify.rb +21 -0
- data/lib/sparql/algebra/operator/month.rb +2 -2
- data/lib/sparql/algebra/operator/multiply.rb +1 -1
- data/lib/sparql/algebra/operator/notoneof.rb +12 -3
- data/lib/sparql/algebra/operator/order.rb +44 -0
- data/lib/sparql/algebra/operator/path_opt.rb +9 -65
- data/lib/sparql/algebra/operator/path_plus.rb +18 -10
- data/lib/sparql/algebra/operator/path_range.rb +178 -0
- data/lib/sparql/algebra/operator/path_star.rb +7 -4
- data/lib/sparql/algebra/operator/path_zero.rb +110 -0
- data/lib/sparql/algebra/operator/plus.rb +8 -6
- data/lib/sparql/algebra/operator/project.rb +64 -5
- data/lib/sparql/algebra/operator/reduced.rb +3 -3
- data/lib/sparql/algebra/operator/regex.rb +1 -1
- data/lib/sparql/algebra/operator/reverse.rb +12 -1
- data/lib/sparql/algebra/operator/sample.rb +3 -1
- data/lib/sparql/algebra/operator/seconds.rb +2 -2
- data/lib/sparql/algebra/operator/seq.rb +4 -4
- data/lib/sparql/algebra/operator/sequence.rb +14 -1
- data/lib/sparql/algebra/operator/service.rb +86 -0
- data/lib/sparql/algebra/operator/strlang.rb +1 -2
- data/lib/sparql/algebra/operator/subtract.rb +10 -6
- data/lib/sparql/algebra/operator/sum.rb +9 -7
- data/lib/sparql/algebra/operator/table.rb +50 -7
- data/lib/sparql/algebra/operator/timezone.rb +2 -2
- data/lib/sparql/algebra/operator/triple.rb +51 -0
- data/lib/sparql/algebra/operator/tz.rb +2 -2
- data/lib/sparql/algebra/operator/ucase.rb +1 -1
- data/lib/sparql/algebra/operator/update.rb +22 -1
- data/lib/sparql/algebra/operator/using.rb +18 -1
- data/lib/sparql/algebra/operator/with.rb +1 -1
- data/lib/sparql/algebra/operator/year.rb +2 -2
- data/lib/sparql/algebra/operator.rb +69 -22
- data/lib/sparql/algebra/query.rb +5 -3
- data/lib/sparql/algebra.rb +42 -6
- data/lib/sparql/grammar/meta.rb +1367 -267
- data/lib/sparql/grammar/parser11.rb +842 -331
- data/lib/sparql/grammar/terminals11.rb +2 -2
- data/lib/sparql/grammar.rb +6 -4
- data/lib/sparql/results.rb +3 -2
- data/lib/sparql/server.rb +93 -0
- data/lib/sparql.rb +8 -5
- metadata +49 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 932897287930ea7265a07628e73c13a06b02b9e0d1572b578356166d3c8fe70a
|
4
|
+
data.tar.gz: 9d17310bc07080475780c8acccbe57acb4bbeb40e3f66ab7b74a09b15ccf0863
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e701784500aece828b2cfd41eb0af78a809bda575eed047158e962ff38678d524b179c0c3faef21fc5b5724894ada3066637b608778a1809fdc7b9bda2ffe2a2
|
7
|
+
data.tar.gz: 77c2520a5f4923a1fe27d589ea6a09b6f5a36fa9c17b564184f206971204fec6988b0941c350e10d812ccfd9b0934e6ae629b82207abfaabf8d7bcc3a380ab33
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# SPARQL for
|
1
|
+
# SPARQL Query and Update library for Ruby
|
2
2
|
|
3
|
-
|
3
|
+
An implementation of [SPARQL][] for [RDF.rb][].
|
4
4
|
|
5
5
|
[](https://badge.fury.io/rb/sparql)
|
6
6
|
[](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
|
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]
|
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
|
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
|
-
|
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
|
-
"
|
191
|
+
"value" : "23",
|
190
192
|
"type" : "literal",
|
191
|
-
"
|
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
|
-
"
|
199
|
+
"value" : "0.9",
|
201
200
|
"type" : "literal",
|
202
|
-
"
|
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}
|
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://
|
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://
|
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://
|
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.
|
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
|
-
|
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
|
data/lib/rack/sparql/conneg.rb
CHANGED
@@ -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
|
14
|
+
result = body if self == result
|
15
15
|
[status, headers, result]
|
16
16
|
end
|
17
17
|
end
|
data/lib/sinatra/sparql.rb
CHANGED
@@ -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
|
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
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|