A Clojure SPARQL query constructor
2014-11-11: This library is no longer activly maintained
Add the following to your project.clj:
[matsu "0.1.2"] ; stable
[matsu "0.1.3-SNAPSHOT"] ; dev
Matsu is a DSL for constructing SPARQL queries:
(query
(select :person)
(where :person a [:foaf :Person] \;
[:foaf :mbox] (URI. "mailto:me@me.com") \.))
Which would yield the following string:
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?person
WHERE
{
?person a foaf:Person ;
foaf:mbox <mailto:me@me.com> .
}
query
by default outputs a string without newlines or indentation, but there is a pretty-printer provided in the util
namespace which aims to produce a formatted query like the one seen above. While far from perfect, I find it very helpfull when debugging long and complex queries.
The prefixes are automatically infered provided that they exists in the global prefixes
map. An exception will be thrown if the prefix cannot be resolved. You add prefixes using register-namespaces
:
(register-namespaces {:foaf "<http://xmlns.com/foaf/0.1/>"
:rdfs "<http://www.w3.org/2000/01/rdf-schema#>"})
You can also supply query-local prefixes which will override the global prefixes
:
(query-with-prefixes {:foaf "<mylocalfoaf>"}
(select :person)
(where :person [:foaf :name] "Petter"))
PREFIX foaf: <mylocalfoaf>
SELECT ?person WHERE { ?person foaf:name "Petter" }
Matsu makes it possible to create complex, nested queries:
(query
(select :mbox :nick :ppd)
(from-named (URI. "http://example.org/foaf/aliceFoaf")
(URI. "http://example.org/foaf/bobFoaf"))
(where
(graph [:data :aliceFoaf]
:alice [:foaf :mbox] (URI. "mailto:alice@work.example") \;
[:foaf :knows] :whom \.
:whom [:foaf :mbox] :mbox \;
[:rdfs :seeAlso] :ppd \.
:ppd a [:foaf :PersonalProfileDocument] \.) \.
(graph :ppd
:w [:foaf :mbox] :mbox \;
[:foaf :nick] :nick)))
Yielding the following SPARQL string:
PREFIX data: <http://example.org/foaf/>
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?mbox ?nick ?ppd
FROM NAMED <http://example.org/foaf/aliceFoaf>
FROM NAMED <http://example.org/foaf/bobFoaf>
WHERE
{
GRAPH data:aliceFoaf
{
?alice foaf:mbox <mailto:alice@work.example> ;
foaf:knows ?whom .
?whom foaf:mbox ?mbox ;
rdfs:seeAlso ?ppd .
?ppd a foaf:PersonalProfileDocument .
} .
GRAPH ?ppd
{
?w foaf:mbox ?mbox ;
foaf:nick ?nick
}
}
defquery
is a convenience macro that binds a query to a function, which allow for easy reuse of queries with variable arguments:
(defquery who [name email]
(select :who)
(where :who [:foaf :name] name \;
[:foaf :mbox] email))
Typing (who "petter" "petter@dott.com")
then yields:
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
SELECT ?who
WHERE
{
?who foaf:name "petter" ;
foaf:mbox "petter@dott.com"
}
INSERT
and DELETE
supported:
(query
(with (URI. "http://example/addresses"))
(delete :person [:foaf :givenName] "Bill")
(insert :person [:foaf :givenName] "William")
(where :person [:foaf :givenName] "Bill"))
PREFIX foaf: <http://xmlns.com/foaf/0.1/>
WITH <http://example/addresses>
DELETE { ?person foaf:givenName 'Bill' }
INSERT { ?person foaf:givenName 'William' }
WHERE
{ ?person foaf:givenName 'Bill'
}
While the aim of matsu is to cover the full SPARQL 1.1 specification, there will no doubt be cases where it falls short. In such cases you can always insert a raw string into your query with raw
:
(query
(select :title :price)
(where (group :x [:ns :price] :p \.
:x [:ns :discount] :discount
(bind [(raw "?p*(1-?discount)") :price]))
(group :x [:dc :title] :title \.)
(filter :price < 20)))
Yielding the following SPARQL string:
PREFIX dc: <http://purl.org/dc/elements/1.1/>
PREFIX ns: <http://example.org/ns#>
SELECT ?title ?price
WHERE {
{ ?x ns:price ?p .
?x ns:discount ?discount
BIND(?p*(1-?discount) AS ?price)
}
{ ?x dc:title ?title . }
FILTER(?price < 20)
}
See the doc directory for more examples:
- /doc/example.clj - an example REPL session using matsu to query the DBpedia SPARQL endpoint.
- /doc/sparql_spec.md - The example queries from the WC3 SPARQL 1.1 specification
- /doc/update_spec.md - The example queries from the WC3 SPARQL 1.1 Update specification
- Single colon keyword prefix is not possible, use the equivalent
BASE
-form instead - Dollar-prefixed variables not supported
There might be other limitations, especially when dealing with SPARQL expressions. But most, if not all of the limitations can be circumvented by interpolating raw strings into the query with the raw
function.
- Find a smarter (non-regex) way to infer namespaces from query
- Better BIND syntax
- Subqueries
- Property path syntax
- Specifying variables with
VALUES
in data block - Federated queries (SERVICE)
- Syntacic sugar (macros)
By all means! I'm open for discussing any ideas you might have.
Copyright © 2012 Petter Goksøyr Åsen
Distributed under the Eclipse Public License, the same as Clojure.