sparql 3.2.1 → 3.2.3
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 +57 -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 +35 -7
- data/lib/sparql/algebra/extensions.rb +18 -18
- data/lib/sparql/algebra/operator/adjust.rb +69 -0
- data/lib/sparql/algebra/operator/bgp.rb +1 -1
- data/lib/sparql/algebra/operator/construct.rb +1 -1
- data/lib/sparql/algebra/operator/dataset.rb +10 -0
- data/lib/sparql/algebra/operator/day.rb +2 -2
- data/lib/sparql/algebra/operator/delete.rb +1 -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/extend.rb +32 -2
- data/lib/sparql/algebra/operator/group.rb +34 -6
- data/lib/sparql/algebra/operator/hours.rb +2 -2
- data/lib/sparql/algebra/operator/insert.rb +1 -1
- data/lib/sparql/algebra/operator/insert_data.rb +1 -1
- data/lib/sparql/algebra/operator/join.rb +3 -3
- data/lib/sparql/algebra/operator/left_join.rb +3 -3
- data/lib/sparql/algebra/operator/minus.rb +1 -1
- 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/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 +7 -5
- data/lib/sparql/algebra/operator/project.rb +42 -1
- data/lib/sparql/algebra/operator/seconds.rb +2 -2
- data/lib/sparql/algebra/operator/seq.rb +3 -3
- data/lib/sparql/algebra/operator/sequence.rb +10 -0
- data/lib/sparql/algebra/operator/subtract.rb +9 -5
- data/lib/sparql/algebra/operator/table.rb +11 -2
- 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/using.rb +2 -2
- data/lib/sparql/algebra/operator/year.rb +2 -2
- data/lib/sparql/algebra/operator.rb +27 -10
- data/lib/sparql/algebra/query.rb +5 -3
- data/lib/sparql/algebra.rb +22 -3
- data/lib/sparql/grammar/meta.rb +1367 -267
- data/lib/sparql/grammar/parser11.rb +826 -328
- 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 +39 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 853c52b99bab0b9455b25a81c8c946e5bcb1429dddadf010b66315f1dbafda51
|
4
|
+
data.tar.gz: 90d89877c031476efdaa1d0b119b2a2404925186b4e0b3de99818d75ead76bff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 98816c81664147aa0fa4ccd90ed14461eb51b652b011395f38f044934c2ff335197fbc3d2d7f447771a5b1e46fa24042aef45fd1f95356b91f52bafee48d1a72
|
7
|
+
data.tar.gz: 5545d7ddd245d73382229ed6f9996b24bd66a4285ee18d6295f697202ad5901eb4221cbf2533ef1539b98ccc0c095519e531b541db3e1c4757d813ab888cb9fd
|
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`).
|
@@ -286,6 +291,20 @@ a full set of RDF formats.
|
|
286
291
|
query = SPARQL::Algebra.parse(%{(bgp (triple ?s ?p ?o))})
|
287
292
|
sparql = query.to_sparql #=> "SELECT * WHERE { ?s ?p ?o }"
|
288
293
|
|
294
|
+
### Query with Binding
|
295
|
+
|
296
|
+
bindings = {page: RDF::URI("https://greggkellogg.net/")}
|
297
|
+
queryable = RDF::Repository.load("etc/doap.ttl")
|
298
|
+
query = SPARQL.parse(%(
|
299
|
+
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
|
300
|
+
SELECT ?person
|
301
|
+
WHERE {
|
302
|
+
?person foaf:homepage ?page .
|
303
|
+
}
|
304
|
+
))
|
305
|
+
solutions = query.execute(queryable, bindings: bindings)
|
306
|
+
solutions.to_sxp #=> (((person <https://greggkellogg.net/foaf#me>)))
|
307
|
+
|
289
308
|
### Command line processing
|
290
309
|
|
291
310
|
sparql execute --dataset etc/doap.ttl etc/from_default.rq
|
@@ -435,7 +454,7 @@ This repository uses [Git Flow](https://github.com/nvie/gitflow) to mange develo
|
|
435
454
|
## License
|
436
455
|
|
437
456
|
This is free and unencumbered public domain software. For more information,
|
438
|
-
see <https://unlicense.org/> or the accompanying {file:UNLICENSE}
|
457
|
+
see <https://unlicense.org/> or the accompanying {file:UNLICENSE}.
|
439
458
|
|
440
459
|
A copy of the [SPARQL EBNF][] and derived parser files are included in the repository, which are not covered under the UNLICENSE. These files are covered via the [W3C Document License](https://www.w3.org/Consortium/Legal/2002/copyright-documents-20021231).
|
441
460
|
|
@@ -454,14 +473,14 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are also included in
|
|
454
473
|
[SPARQL 1.0 tests]:https://www.w3.org/2001/sw/DataAccess/tests/
|
455
474
|
[SPARQL 1.1 tests]: https://www.w3.org/2009/sparql/docs/tests/
|
456
475
|
[SSE]: https://jena.apache.org/documentation/notes/sse.html
|
457
|
-
[SXP]: https://
|
476
|
+
[SXP]: https://dryruby.github.io/sxp
|
458
477
|
[grammar]: https://www.w3.org/TR/sparql11-query/#grammar
|
459
478
|
[RDF 1.1]: https://www.w3.org/TR/rdf11-concepts
|
460
|
-
[RDF.rb]: https://
|
479
|
+
[RDF.rb]: https://ruby-rdf.github.io/rdf
|
461
480
|
[RDF-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html
|
462
481
|
[SPARQL-star]: https://w3c.github.io/rdf-star/rdf-star-cg-spec.html#sparql-query-language
|
463
482
|
[Linked Data]: https://rubygems.org/gems/linkeddata
|
464
|
-
[SPARQL doc]: https://
|
483
|
+
[SPARQL doc]: https://ruby-rdf.github.io/sparql/frames
|
465
484
|
[SPARQL XML]: https://www.w3.org/TR/rdf-sparql-XMLres/
|
466
485
|
[SPARQL JSON]: https://www.w3.org/TR/rdf-sparql-json-res/
|
467
486
|
[SPARQL EBNF]: https://www.w3.org/TR/sparql11-query/#sparqlGrammar
|
@@ -481,4 +500,4 @@ A copy of the [SPARQL 1.0 tests][] and [SPARQL 1.1 tests][] are also included in
|
|
481
500
|
[SPARQL 1.1 Entailment Regimes]: https://www.w3.org/TR/sparql11-entailment/
|
482
501
|
[SPARQL 1.1 Protocol]: https://www.w3.org/TR/sparql11-protocol/
|
483
502
|
[SPARQL 1.1 Graph Store HTTP Protocol]: https://www.w3.org/TR/sparql11-http-rdf-update/
|
484
|
-
|
503
|
+
[Linked Data Platform]: https://www.w3.org/TR/ldp/
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.2.
|
1
|
+
3.2.3
|
data/bin/sparql
CHANGED
@@ -63,39 +63,10 @@ def run(input, **options)
|
|
63
63
|
end
|
64
64
|
|
65
65
|
def server(options)
|
66
|
-
|
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)')
|
@@ -84,7 +97,7 @@ module SPARQL; module Algebra
|
|
84
97
|
# any additional options (see {Operator#initialize})
|
85
98
|
# @return [Expression]
|
86
99
|
# @raise [TypeError] if any of the operands is invalid
|
87
|
-
def self.new(sse, **options)
|
100
|
+
def self.new(sse, parent_operator: nil, **options)
|
88
101
|
raise ArgumentError, "invalid SPARQL::Algebra::Expression form: #{sse.inspect}" unless sse.is_a?(Array)
|
89
102
|
|
90
103
|
operator = Operator.for(sse.first, sse.length - 1)
|
@@ -99,12 +112,12 @@ module SPARQL; module Algebra
|
|
99
112
|
return case sse.first
|
100
113
|
when Array
|
101
114
|
debug(options) {"Map array elements #{sse}"}
|
102
|
-
sse.map {|s| self.new(s, depth: options[:depth].to_i + 1, **options)}
|
115
|
+
sse.map {|s| self.new(s, parent_operator: parent_operator, depth: options[:depth].to_i + 1, **options)}
|
103
116
|
else
|
104
117
|
debug(options) {"No operator found for #{sse.first}"}
|
105
118
|
sse.map do |s|
|
106
119
|
s.is_a?(Array) ?
|
107
|
-
self.new(s, depth: options[:depth].to_i + 1) :
|
120
|
+
self.new(s, parent_operator: parent_operator, depth: options[:depth].to_i + 1) :
|
108
121
|
s
|
109
122
|
end
|
110
123
|
end
|
@@ -114,7 +127,7 @@ module SPARQL; module Algebra
|
|
114
127
|
debug(options) {"Operator=#{operator.inspect}, Operand=#{operand.inspect}"}
|
115
128
|
case operand
|
116
129
|
when Array
|
117
|
-
self.new(operand, depth: options[:depth].to_i + 1, **options)
|
130
|
+
self.new(operand, parent_operator: operator, depth: options[:depth].to_i + 1, **options)
|
118
131
|
when Operator, Variable, RDF::Term, RDF::Query, Symbol
|
119
132
|
operand
|
120
133
|
when TrueClass, FalseClass, Numeric, String, DateTime, Date, Time
|
@@ -127,7 +140,13 @@ module SPARQL; module Algebra
|
|
127
140
|
logger = options[:logger]
|
128
141
|
options.delete_if {|k, v| [:debug, :logger, :depth, :prefixes, :base_uri, :update, :validate].include?(k) }
|
129
142
|
begin
|
130
|
-
|
143
|
+
# Due to confusiong over (triple) and special-case for (qtriple)
|
144
|
+
if operator == RDF::Query::Pattern
|
145
|
+
options = options.merge(quoted: true) if sse.first == :qtriple
|
146
|
+
elsif operator == Operator::Triple && PATTERN_PARENTS.include?(parent_operator)
|
147
|
+
operator = RDF::Query::Pattern
|
148
|
+
end
|
149
|
+
operator.new(*operands, parent_operator: operator, **options)
|
131
150
|
rescue ArgumentError => e
|
132
151
|
if logger
|
133
152
|
logger.error("Operator=#{operator.inspect}: #{e}")
|
@@ -215,7 +234,7 @@ module SPARQL; module Algebra
|
|
215
234
|
#
|
216
235
|
# @param [RDF::URI] datatype
|
217
236
|
# Datatype to evaluate, one of:
|
218
|
-
# xsd:integer, xsd:decimal xsd:float, xsd:double, xsd:string, xsd:boolean,
|
237
|
+
# xsd:integer, xsd:decimal xsd:float, xsd:double, xsd:string, xsd:boolean, xsd:dateTime, xsd:duration, xsd:dayTimeDuration, xsd:yearMonthDuration
|
219
238
|
# @param [RDF::Term] value
|
220
239
|
# Value, which should be a typed literal, where the type must be that specified
|
221
240
|
# @raise [TypeError] if datatype is not a URI or value cannot be cast to datatype
|
@@ -223,7 +242,7 @@ module SPARQL; module Algebra
|
|
223
242
|
# @see https://www.w3.org/TR/sparql11-query/#FunctionMapping
|
224
243
|
def self.cast(datatype, value)
|
225
244
|
case datatype
|
226
|
-
when RDF::XSD.dateTime
|
245
|
+
when RDF::XSD.date, RDF::XSD.time, RDF::XSD.dateTime
|
227
246
|
case value
|
228
247
|
when RDF::Literal::DateTime, RDF::Literal::Date, RDF::Literal::Time
|
229
248
|
RDF::Literal.new(value, datatype: datatype)
|
@@ -232,6 +251,15 @@ module SPARQL; module Algebra
|
|
232
251
|
else
|
233
252
|
RDF::Literal.new(value.value, datatype: datatype, validate: true)
|
234
253
|
end
|
254
|
+
when RDF::XSD.duration, RDF::XSD.dayTimeDuration, RDF::XSD.yearMonthDuration
|
255
|
+
case value
|
256
|
+
when RDF::Literal::Duration, RDF::Literal::DayTimeDuration, RDF::Literal::YearMonthDuration
|
257
|
+
RDF::Literal.new(value, datatype: datatype, validate: true, canonicalize: true)
|
258
|
+
when RDF::Literal::Numeric, RDF::Literal::Boolean, RDF::URI, RDF::Node
|
259
|
+
raise TypeError, "Value #{value.inspect} cannot be cast as #{datatype}"
|
260
|
+
else
|
261
|
+
RDF::Literal.new(value.value, datatype: datatype, validate: true, canonicalize: true)
|
262
|
+
end
|
235
263
|
when RDF::XSD.float, RDF::XSD.double
|
236
264
|
case value
|
237
265
|
when RDF::Literal::Boolean
|
@@ -124,6 +124,14 @@ class Array
|
|
124
124
|
end
|
125
125
|
def constant?; !(variable?); end
|
126
126
|
|
127
|
+
##
|
128
|
+
# The variables used in this array.
|
129
|
+
#
|
130
|
+
# @return [Hash{Symbol => RDF::Query::Variable}]
|
131
|
+
def variables
|
132
|
+
self.inject({}) {|hash, o| o.respond_to?(:variables) ? hash.merge(o.variables) : hash}
|
133
|
+
end
|
134
|
+
|
127
135
|
##
|
128
136
|
# Does this contain any nodes?
|
129
137
|
#
|
@@ -347,7 +355,7 @@ class RDF::Statement
|
|
347
355
|
# Transform Statement Pattern into an SXP
|
348
356
|
# @return [Array]
|
349
357
|
def to_sxp_bin
|
350
|
-
[ (has_graph? ? :quad : :triple),
|
358
|
+
[ (has_graph? ? :quad : (quoted? ? :qtriple : :triple)),
|
351
359
|
(:inferred if inferred?),
|
352
360
|
subject,
|
353
361
|
predicate,
|
@@ -370,20 +378,10 @@ class RDF::Statement
|
|
370
378
|
#
|
371
379
|
# Returns a partial SPARQL grammar for this term.
|
372
380
|
#
|
373
|
-
# @param [Boolean] as_statement (false) serialize as < ... >, otherwise TRIPLE(...)
|
374
381
|
# @return [String]
|
375
|
-
def to_sparql(
|
376
|
-
|
377
|
-
|
378
|
-
if term.is_a?(::RDF::Statement)
|
379
|
-
"<<" + term.to_sparql(as_statement: true, **options) + ">>"
|
380
|
-
else
|
381
|
-
term.to_sparql(**options)
|
382
|
-
end
|
383
|
-
end.join(" ")
|
384
|
-
else
|
385
|
-
"TRIPLE(#{to_triple.to_sparql(as_statement: true, **options)})"
|
386
|
-
end
|
382
|
+
def to_sparql(**options)
|
383
|
+
str = to_triple.map {|term| term.to_sparql(**options)}.join(" ")
|
384
|
+
quoted? ? ('<<' + str + '>>') : str
|
387
385
|
end
|
388
386
|
|
389
387
|
##
|
@@ -441,7 +439,9 @@ class RDF::Query
|
|
441
439
|
# Filter Operations
|
442
440
|
# @return [String]
|
443
441
|
def to_sparql(top_level: true, filter_ops: [], **options)
|
444
|
-
str = @patterns.map
|
442
|
+
str = @patterns.map do |e|
|
443
|
+
e.to_sparql(top_level: false, **options) + " . \n"
|
444
|
+
end.join("")
|
445
445
|
str = "GRAPH #{graph_name.to_sparql(**options)} {\n#{str}\n}\n" if graph_name
|
446
446
|
if top_level
|
447
447
|
SPARQL::Algebra::Operator.to_sparql(str, filter_ops: filter_ops, **options)
|
@@ -454,9 +454,9 @@ class RDF::Query
|
|
454
454
|
# Extensons
|
455
455
|
extensions = options.fetch(:extensions, [])
|
456
456
|
extensions.each do |as, expression|
|
457
|
-
v = expression.to_sparql(
|
458
|
-
|
459
|
-
str << "\nBIND (" << v << " AS " <<
|
457
|
+
v = expression.to_sparql(**options)
|
458
|
+
pp = RDF::Query::Variable.new(as).to_sparql(**options)
|
459
|
+
str << "\nBIND (" << v << " AS " << pp << ") ."
|
460
460
|
end
|
461
461
|
str = "{#{str}}" unless filter_ops.empty? && extensions.empty?
|
462
462
|
str
|