sparql 3.2.0 → 3.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
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
|