sparql 3.2.1 → 3.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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`).
|
@@ -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
|