Skip to content

Commit

Permalink
Merge pull request #125 from Swirrl/sl/nil-undef
Browse files Browse the repository at this point in the history
Disallow nil bindings in VALUES clauses
  • Loading branch information
RickMoynihan authored Jan 11, 2018
2 parents c72a957 + a0b6c24 commit ddf5776
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 24 deletions.
63 changes: 48 additions & 15 deletions src/grafter/rdf/sparql.clj
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,12 @@
Pattern/DOTALL)))

(defn- serialise-val [v]
(NTriplesUtil/toNTriplesString (rio/->sesame-rdf-type v)))
(if (= ::undef v)
"UNDEF"
(NTriplesUtil/toNTriplesString (rio/->sesame-rdf-type v))))

(defn- ->sparql-str [k v]
(cond
(nil? v)
"UNDEF"

(and (sequential? k) (sequential? v)
(= (count k) (count v)))
(str "(" (str/join " " (map serialise-val v)) ")")
Expand All @@ -55,30 +54,45 @@
(serialise-val v)

:else
(assert false (str "Key and value types don't match. Key: " k " Val " v))))
(assert false
(str "VALUES clause keys & vals don't match up. Key: " k " Val " v))))

(defn- rewrite-values-clauses* [q [k vals :as clause]]
(let [regex (key->replacer k)
values-block (str/join " " (map (partial ->sparql-str k) vals))]
(str/replace q regex (str "$1 " values-block " $2"))))

(defn- rewrite-values-clauses [q bindings]
(let [values-bindings (->> bindings
(filter (comp sequential? second))
(into {}))]
(reduce rewrite-values-clauses* q values-bindings)))
(->> bindings
(map (fn [[k v]]
(cond
(= ::undef v)
[k [v]]

(nil? v)
(throw
(ex-info (str "nil value SPARQL binding found for key " k
". Consider explicitly binding value as ::sparql/undef")
{:bindings bindings
:sparql-query q
:error :nil-sparql-binding}))
:else
[k v])))
(filter (comp sequential? second))
(into {})
(reduce rewrite-values-clauses* q)))

(defn- rewrite-clauses
"Rewrites each instance of CLAUSE (literal | ?varname) with CLAUSE
value with the given mappings."
[sparl-query clause-name mappings]
[sparql-query clause-name mappings]
(reduce (fn [memo [key val]]
(if-let [pattern (get-clause-pattern clause-name key)]
(str/replace memo
(re-pattern pattern)
(str clause-name " " val))
memo))
sparl-query
sparql-query
mappings))

(defn- rewrite-limit-and-offset-clauses
Expand Down Expand Up @@ -112,20 +126,39 @@
The bindings map is optional, and if it's not provided then the
query in the file is run as is.
Additionally if your sparql query specifies a LIMIT or OFFSET the
bindings map supports the special keys ::limits and ::offstets.
Additionally, if your sparql query specifies a LIMIT or OFFSET the
bindings map supports the special keys ::limits and ::offsets.
Which should be maps binding identifiable limits/offsets from your
query to new values.
The final argument should be the repository to query.
VALUES clause bindings are supported like normal ?var bindings when
there is just one VALUES binding. When there are more than one, you
should provide a vector containing the component var names as the
key in the map, with a sequence of sequences as the values
themselves. e.g. to override a clause like this:
VALUES ?a ?b { (1 2) (3 4) }
You would provide a map that looked like this:
{[:a :b] [[1 1] [2 2] [3 3]]}
nil's inside the VALUES row's themselves will raise an error.
The clojure keyword :grafter.rdf.sparql/undef can be used to
represent a SPARQL UNDEF, in the bound VALUES data.
The final argument `repo` should be the repository to query.
If only one argument referencing a resource path to a SPARQL query
then a partially applied function is returned. e.g.
then a partially applied function is returned. e.g.
(def spog (query \"grafter/rdf/sparql/select-spog.sparql\"))
(spog r) ;; ... triples ...
(spog r {:s [(URI. \"http://s1\") (URI. \"http://s2\")]}) ;; triples for VALUES clause subjects s.
(spog r {:s (java.net.URI. \"http://example.org/data/a-triple\")}) ;; triples for given subject s.
"
([sparql-file]
Expand Down
74 changes: 65 additions & 9 deletions test/grafter/rdf/sparql_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
[clojure.string :as str]
[grafter.rdf.io :as rio]
[grafter.rdf.protocols :as pr])
(:import java.net.URI))
(:import java.net.URI
clojure.lang.ExceptionInfo))

(deftest pre-process-limit-clauses-test
(let [sparql-file (slurp (resource "grafter/rdf/sparql/select-spog-unprocessed.sparql"))
Expand Down Expand Up @@ -54,22 +55,77 @@
:p [(URI. "http://p")]
:o [10 "string" (rdf/language "bonjour" :fr)]}))))

(let [q3 (sparql-query "grafter/rdf/sparql/select-values-3.sparql")]
(let [q3 (sparql-query "grafter/rdf/sparql/select-values-3.sparql")
q3-ex-info (is (thrown? ExceptionInfo
(#'sparql/rewrite-values-clauses q3 {:o nil})))]
(is (= (:error (ex-data q3-ex-info))
:nil-sparql-binding))

(is (same-query?
(str "SELECT * WHERE {"
"{ VALUES ?o { \"val\" } }"
"?s ?p ?o . "
"}")
(#'sparql/rewrite-values-clauses q3 {:o ["val"]})))

(is (same-query?
(str "SELECT * WHERE {"
"{ VALUES ?o { \"val\" } }"
"?s ?p ?o . "
"}")
(#'sparql/rewrite-values-clauses q3 { :o ["val"]}))))
"{ VALUES ?o { \"val\" } }"
"?s ?p ?o . "
"}")
(#'sparql/rewrite-values-clauses q3 {:s (URI. "http://foo/") :o ["val"]})))

(is (same-query?
(str "SELECT * WHERE {"
"{ VALUES ?o {} }"
"?s ?p ?o . "
"}")
(#'sparql/rewrite-values-clauses q3 {:o []})))

(is (same-query?
(str "SELECT * WHERE {"
"{ VALUES ?o { UNDEF } }"
"?s ?p ?o . "
"}")
(#'sparql/rewrite-values-clauses q3 {:o ::sparql/undef}))))

(let [q4 (sparql-query "grafter/rdf/sparql/select-values-4.sparql")
q4-ex-info (is (thrown? ExceptionInfo
(#'sparql/rewrite-values-clauses q4 {[:s :p] nil})))]
(is (= (:error (ex-data q4-ex-info))
:nil-sparql-binding))

(let [q4 (sparql-query "grafter/rdf/sparql/select-values-4.sparql")]
(is (same-query?
(str "SELECT * WHERE {"
"VALUES (?s ?p) { (<http://s1> <http://p1>) (<http://s2> <http://p2>) }"
"?s ?p ?o ."
"}")
(#'sparql/rewrite-values-clauses q4 { [:s :p] [[(URI. "http://s1") (URI. "http://p1")]
[(URI. "http://s2") (URI. "http://p2")]]}))))
[(URI. "http://s2") (URI. "http://p2")]]})))
(is (same-query?
(str "SELECT * WHERE {"
"VALUES (?s ?p) { (UNDEF UNDEF) }"
"?s ?p ?o ."
"}")
(#'sparql/rewrite-values-clauses q4 {[:s :p] [[::sparql/undef ::sparql/undef]]})))

(is (same-query?
(str "SELECT * WHERE {"
"VALUES (?s ?p) { (UNDEF <http://p1>) (<http://s1> UNDEF) }"
"?s ?p ?o ."
"}")
(#'sparql/rewrite-values-clauses q4 {[:s :p] [[::sparql/undef (URI. "http://p1")]
[(URI. "http://s1") ::sparql/undef]]})))

(is (same-query?
(str "SELECT * WHERE {"
"VALUES (?s ?p) {}"
"?s ?p ?o ."
"}")
(#'sparql/rewrite-values-clauses q4 {[:s :p] []})))

(is (thrown-with-msg? AssertionError #"VALUES clause keys & vals don't match up"
(#'sparql/rewrite-values-clauses q4 {[:s :p] [::sparql/undef]}))))

(let [q5 (sparql-query "grafter/rdf/sparql/select-values-5.sparql")]
(is (same-query?
Expand Down Expand Up @@ -112,4 +168,4 @@
(let [q1 (sparql-query "grafter/rdf/sparql/select-values-1.sparql")]
(is (same-query?
(str "SELECT * WHERE { VALUES ?s { <http://s1> <http://s2> } ?s ?p ?o . }")
(#'sparql/pre-process-query q1 {:s [(URI. "http://s1") (URI. "http://s2")]}))))))
(#'sparql/pre-process-query q1 {:s [(URI. "http://s1") (URI. "http://s2")]}))))))

0 comments on commit ddf5776

Please sign in to comment.