diff --git a/CHANGELOG.md b/CHANGELOG.md index fce873d..72487a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v0.3.0 + +- Add support for RDF List syntactic sugar. +- Add support for blank node vector syntactic sugar. +- Modify the AST tree for triples to support the new features and to remove redundant nodes in the tree. +- Rework blank node validation to make the implementation simpler (this results in minor changes to the error output). + ## v0.2.1 - Update GitHub Actions CI and CD to remove deprecation warnings. diff --git a/README.md b/README.md index 8b593d4..59f8ec6 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ If you are using Apache Jena, check out the [flint-jena](https://github.com/yeta Add the following to your `deps.edn` map. ```clojure -com.yetanalytics/flint {:mvn/version "0.2.1" +com.yetanalytics/flint {:mvn/version "0.3.0" :exclusions [org.clojure/clojure org.clojure/clojurescript]} ``` diff --git a/dev-resources/test-fixtures/inputs/query/select/select-12.edn b/dev-resources/test-fixtures/inputs/query/select/select-12.edn new file mode 100644 index 0000000..051e0c4 --- /dev/null +++ b/dev-resources/test-fixtures/inputs/query/select/select-12.edn @@ -0,0 +1,3 @@ +;; SELECT with lists +{:select [?x] + :where [[?s ?p (1 ?x 3 4)]]} diff --git a/dev-resources/test-fixtures/inputs/query/select/select-13.edn b/dev-resources/test-fixtures/inputs/query/select/select-13.edn new file mode 100644 index 0000000..ecf6e55 --- /dev/null +++ b/dev-resources/test-fixtures/inputs/query/select/select-13.edn @@ -0,0 +1,7 @@ +;; SELECT with blank node vectors +{:prefixes {:$ "" + :foaf ""} + :select [?x ?name] + :where [[[:p1 "v"] :q1 "w"] + [:x :q2 [:p2 "v"]] + [[:foaf/name ?name :foaf/mbox ""]]]} diff --git a/dev-resources/test-fixtures/inputs/query/select/select-14.edn b/dev-resources/test-fixtures/inputs/query/select/select-14.edn new file mode 100644 index 0000000..95dfc62 --- /dev/null +++ b/dev-resources/test-fixtures/inputs/query/select/select-14.edn @@ -0,0 +1,5 @@ +;; SELECT with both lists and blank node vectors +{:prefixes {:$ ""} + :select [?x] + :where [{(1 [:p :q] (2 ?x)) {} + () {:r #{[]}}}]} diff --git a/dev-resources/test-fixtures/outputs/query/select/select-12.rq b/dev-resources/test-fixtures/outputs/query/select/select-12.rq new file mode 100644 index 0000000..594c674 --- /dev/null +++ b/dev-resources/test-fixtures/outputs/query/select/select-12.rq @@ -0,0 +1,4 @@ +SELECT ?x +WHERE { + ?s ?p ( 1 ?x 3 4 ) . +} diff --git a/dev-resources/test-fixtures/outputs/query/select/select-13.rq b/dev-resources/test-fixtures/outputs/query/select/select-13.rq new file mode 100644 index 0000000..0d01066 --- /dev/null +++ b/dev-resources/test-fixtures/outputs/query/select/select-13.rq @@ -0,0 +1,9 @@ +PREFIX : +PREFIX foaf: +SELECT ?x ?name +WHERE { + [ :p1 "v" ] :q1 "w" . + :x :q2 [ :p2 "v" ] . + [ foaf:name ?name ; + foaf:mbox ] . +} diff --git a/dev-resources/test-fixtures/outputs/query/select/select-14.rq b/dev-resources/test-fixtures/outputs/query/select/select-14.rq new file mode 100644 index 0000000..ea4e806 --- /dev/null +++ b/dev-resources/test-fixtures/outputs/query/select/select-14.rq @@ -0,0 +1,6 @@ +PREFIX : +SELECT ?x +WHERE { + ( 1 [ :p :q ] ( 2 ?x ) ) . + () :r [] . +} diff --git a/doc/triple.md b/doc/triple.md index a2ed955..8d607d6 100644 --- a/doc/triple.md +++ b/doc/triple.md @@ -108,9 +108,65 @@ Objects can be one of the following: **NOTE:** Variables are not allowed in triples in `DELETE DATA` OR `INSERT DATA` clauses. -**NOTE:** Technically literals are allowed in subject position according to the SPARQL spec, but no RDF implementation accepts that, so Flint does not allow for subject literals either. +**NOTE:** Technically literals are allowed in subject position according to the SPARQL spec, but no RDF implementation accepts that, so Flint does not allow for subject literals either (unless they are in an RDF list as described below). -**NOTE:** SPARQL has [syntactic sugar](https://www.w3.org/TR/sparql11-query/#collections) for easy writing of RDF lists, but for simplicity that is not implemented in Flint. +## RDF Lists + +Flint supports SPARQL's [syntactic sugar](https://www.w3.org/TR/sparql11-query/#collections) for easy writing of RDF lists. For example: +```clojure +{:select [?x] + :where [[(1 ?x 3 4) ?p ?o]]} +``` + +becomes +```sparql +SELECT ?x +WHERE { + (1 ?x 3 4) ?p ?o +} +``` +which should then expanded out into an RDF list by your SPARQL query engine. + +RDF lists be placed in both subject and object position. If they are placed in subject position, then predicates and objects are optional (which is not usually the case). The predicate-object map can be left empty in normal form representation: +```clojure +{:select [?x] + :where [{(1 ?x 3 4) {}}]} +``` + +and can be left out entirely in triple representation: +```clojure +{:select [?x] + :where [[(1 ?x 3 4)]]} +``` + +RDF lists can also be nested, both with themselves and with blank node vectors (see below): +```clojure +{:select [?x] + :where [[(1 [:p :q] (2))]]} +``` + +## Blank Node Vectors + +Flint also has support for SPARQL's [blank node syntactic sugar](https://www.w3.org/TR/sparql11-query/#QSynBlankNodes), which will be expanded out by the SPARQL engine. For example: + +```clojure +{:prefixes {:foaf "" + :select [?name] + :where [{[:foaf/name ?name + :foaf/mbox ""] {}}]}} +``` + +becomes: +```sparql +PREFIX foaf: +SELECT ?name +WHERE { + [ foaf:name ?name + foaf:mbox ] . +} +``` + +Note that like with RDF lists, the predicate and object may be omitted here. ## Property Paths diff --git a/src/dev/com/yetanalytics/flint/sparql.clj b/src/dev/com/yetanalytics/flint/sparql.clj index 9636073..e25a6fb 100644 --- a/src/dev/com/yetanalytics/flint/sparql.clj +++ b/src/dev/com/yetanalytics/flint/sparql.clj @@ -38,6 +38,36 @@ ;; Query/Update writing sandbox +(comment + (QueryFactory/create + "CONSTRUCT { _:b0 ?pred _:b0 } + WHERE { _:b0 ?pred _:b0 . + OPTIONAL { + ?y ?pred2 _:b1 + } + _:b0 ?question _:b0 . }") + + (QueryFactory/create + "SELECT ?x + WHERE { + { ?x _:1 . } + { ?y _:1 . } + }") + + (QueryFactory/create + "PREFIX foo: + SELECT ?x WHERE { + ?x foo:bar _:1 . + FILTER NOT EXISTS { ?z foo:baz ?w . } + ?y foo:qux _:1 . + }") + + (QueryFactory/create + "SELECT ?x WHERE { + [ [ ?x]] \"w\" + }") + ) + (comment (QueryFactory/create "SELECT ((AVG(MAX(?x)) + 1) AS ?avg) diff --git a/src/main/com/yetanalytics/flint/format/triple.cljc b/src/main/com/yetanalytics/flint/format/triple.cljc index 1c38597..3b3f99a 100644 --- a/src/main/com/yetanalytics/flint/format/triple.cljc +++ b/src/main/com/yetanalytics/flint/format/triple.cljc @@ -7,32 +7,63 @@ (defmethod f/format-ast-node :triple/path [_ [_ path]] path) -(defmethod f/format-ast-node :triple/vec [_ [_ [s p o]]] - (str s " " p " " o " .")) - -(defmethod f/format-ast-node :triple/nform [_ [_ nform]] - nform) - -(defmethod f/format-ast-node :triple/spo [{:keys [pretty?]} [_ spo]] - (if pretty? - (str (->> spo - (map (fn [[s po]] - (let [indent (->> (repeat (inc (count s)) " ") - (cstr/join "") - (str "\n"))] - (str s " " (cstr/replace po #"\n" indent))))) - (cstr/join " .\n")) - " .") - (str (->> spo - (map (fn [[s po]] (str s " " po))) - (cstr/join " . ")) +(defmethod f/format-ast-node :triple/list [_ [_ list]] + (if (empty? list) + "()" ; Special case for empty lists + (str "( " (cstr/join " " list) " )"))) + +(defmethod f/format-ast-node :triple/bnodes [{:keys [pretty?]} [_ po-pairs]] + (if (empty? po-pairs) + "[]" ; Treat as a scalar blank node + (let [join-sep (if pretty? " ;\n " " ; ") + po-strs (mapv (fn [[p-str o-str]] (str p-str " " o-str)) po-pairs)] + (str "[ " (cstr/join join-sep po-strs) " ]")))) + +(defmethod f/format-ast-node :triple.vec/spo [_ [_ [s-str p-str o-str]]] + (str s-str " " p-str " " o-str " .")) + +(defmethod f/format-ast-node :triple.vec/s [_ [_ [s-str]]] + (str s-str " .")) + +(defn- format-spo-pretty [s-str po-str] + (let [indent (->> (repeat (inc (count s-str)) " ") + (cstr/join "") + (str "\n"))] + (str s-str " " (cstr/replace po-str #"\n" indent)))) + +(defn- format-spo [s-str po-str] + (str s-str " " po-str)) + +(defmethod f/format-ast-node :triple.nform/spo [{:keys [pretty?]} [_ spo-pairs]] + (let [format-spo (if pretty? format-spo-pretty format-spo) + join-sep (if pretty? " .\n" " . ")] + (str (->> spo-pairs + (map (fn [[s-str po-str]] + (if (empty? po-str) s-str (format-spo s-str po-str)))) + (cstr/join join-sep)) " ."))) -(defmethod f/format-ast-node :triple/po [{:keys [pretty?]} [_ po]] +(defmethod f/format-ast-node :triple.nform/po [{:keys [pretty?]} [_ po-strs]] (let [join-sep (if pretty? " ;\n" " ; ")] - (->> po + (->> po-strs (map (fn [[p o]] (str p " " o))) (cstr/join join-sep)))) -(defmethod f/format-ast-node :triple/o [_ [_ o]] - (->> o (cstr/join " , "))) +(defmethod f/format-ast-node :triple.nform/po-empty [_ _] + "") + +(defmethod f/format-ast-node :triple.nform/o [_ [_ o-strs]] + (->> o-strs (cstr/join " , "))) + +(defn format-quads [quads pretty?] + (-> quads + (f/join-clauses pretty?) + (f/wrap-in-braces pretty?))) + +(defmethod f/format-ast-node :triple.quad/gspo + [_ [_ [graph-str spo-str]]] + (str "GRAPH " graph-str " " spo-str)) + +(defmethod f/format-ast-node :triple.quad/spo + [{:keys [pretty?]} [_ spo-strs]] + (format-quads spo-strs pretty?)) diff --git a/src/main/com/yetanalytics/flint/format/update.cljc b/src/main/com/yetanalytics/flint/format/update.cljc index b82f8f1..7cd1b65 100644 --- a/src/main/com/yetanalytics/flint/format/update.cljc +++ b/src/main/com/yetanalytics/flint/format/update.cljc @@ -3,26 +3,9 @@ [com.yetanalytics.flint.format :as f] [com.yetanalytics.flint.format.axiom] [com.yetanalytics.flint.format.prologue] - [com.yetanalytics.flint.format.triple] + [com.yetanalytics.flint.format.triple :as tf] [com.yetanalytics.flint.format.where])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Quads -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn- format-quads [quads pretty?] - (-> quads - (f/join-clauses pretty?) - (f/wrap-in-braces pretty?))) - -(defmethod f/format-ast-node :triple/quads - [_ [_ [var-or-iri triples-str]]] - (str "GRAPH " var-or-iri " " triples-str)) - -(defmethod f/format-ast-node :triple/quad-triples - [{:keys [pretty?]} [_ triples]] - (format-quads triples pretty?)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Graph Management ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -128,19 +111,19 @@ (str "WITH " with)) (defmethod f/format-ast-node :insert-data [{:keys [pretty?]} [_ insert-data]] - (str "INSERT DATA " (format-quads insert-data pretty?))) + (str "INSERT DATA " (tf/format-quads insert-data pretty?))) (defmethod f/format-ast-node :delete-data [{:keys [pretty?]} [_ delete-data]] - (str "DELETE DATA " (format-quads delete-data pretty?))) + (str "DELETE DATA " (tf/format-quads delete-data pretty?))) (defmethod f/format-ast-node :delete-where [{:keys [pretty?]} [_ delete-where]] - (str "DELETE WHERE " (format-quads delete-where pretty?))) + (str "DELETE WHERE " (tf/format-quads delete-where pretty?))) (defmethod f/format-ast-node :delete [{:keys [pretty?]} [_ delete]] - (str "DELETE " (format-quads delete pretty?))) + (str "DELETE " (tf/format-quads delete pretty?))) (defmethod f/format-ast-node :insert [{:keys [pretty?]} [_ insert]] - (str "INSERT " (format-quads insert pretty?))) + (str "INSERT " (tf/format-quads insert pretty?))) (defmethod f/format-ast-node :update/insert-data [{:keys [pretty?]} [_ id-update]] (f/join-clauses id-update pretty?)) diff --git a/src/main/com/yetanalytics/flint/format/where.cljc b/src/main/com/yetanalytics/flint/format/where.cljc index 6113fec..c339a1d 100644 --- a/src/main/com/yetanalytics/flint/format/where.cljc +++ b/src/main/com/yetanalytics/flint/format/where.cljc @@ -58,5 +58,8 @@ (defmethod f/format-ast-node :where/special [_ [_ where-form]] where-form) +(defmethod f/format-ast-node :where/triple [_ [_ triples]] + triples) + (defmethod f/format-ast-node :where [_ [_ where]] (str "WHERE " where)) diff --git a/src/main/com/yetanalytics/flint/spec/query.cljc b/src/main/com/yetanalytics/flint/spec/query.cljc index 0a8f252..1e96eac 100644 --- a/src/main/com/yetanalytics/flint/spec/query.cljc +++ b/src/main/com/yetanalytics/flint/spec/query.cljc @@ -75,13 +75,7 @@ ::vs/values] :key-comp-fn key-comp)) -(def triples-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-nopath-spec - :triple/nform ts/normal-form-nopath-spec) - :min-count 0 - :kind vector?)) - -(s/def ::construct triples-spec) +(s/def ::construct ts/triple-coll-nopath-spec) (def construct-query-spec (sparql-keys :req-un [::construct ::ws/where] diff --git a/src/main/com/yetanalytics/flint/spec/triple.cljc b/src/main/com/yetanalytics/flint/spec/triple.cljc index 358a084..faef4f9 100644 --- a/src/main/com/yetanalytics/flint/spec/triple.cljc +++ b/src/main/com/yetanalytics/flint/spec/triple.cljc @@ -14,175 +14,371 @@ ;; Just paths banned in ;; - CONSTRUCT +;; Variables (and paths) banned in +;; - DELETE DATA +;; - INSERT DATA + ;; Blank nodes (and paths) banned in ;; - DELETE WHERE ;; - DELETE DATA ;; - DELETE -;; Variables (and paths) banned in -;; - DELETE DATA -;; - INSERT DATA - ;; Subjects -(def subj-spec +(s/def ::subject (s/or :ax/var ax/variable-spec :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec - :ax/bnode ax/bnode-spec)) + :ax/bnode ax/bnode-spec + :triple/list ::list + :triple/bnodes ::bnodes)) -(def subj-novar-spec +(s/def ::subject-coll + (s/or :triple/list ::list + :triple/bnodes ::bnodes)) + +(s/def ::subject-nopath + (s/or :ax/var ax/variable-spec + :ax/iri ax/iri-spec + :ax/prefix-iri ax/prefix-iri-spec + :ax/bnode ax/bnode-spec + :triple/list ::list-nopath + :triple/bnodes ::bnodes-nopath)) + +(s/def ::subject-coll-nopath + (s/or :triple/list ::list-nopath + :triple/bnodes ::bnodes-nopath)) + +(s/def ::subject-novar (s/or :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec - :ax/bnode ax/bnode-spec)) + :ax/bnode ax/bnode-spec + :triple/list ::list-novar + :triple/bnodes ::bnodes-novar)) + +(s/def ::subject-coll-novar + (s/or :triple/list ::list-novar + :triple/bnodes ::bnodes-novar)) -(def subj-noblank-spec +(s/def ::subject-noblank (s/or :ax/var ax/variable-spec :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec)) -(def subj-novar-noblank-spec +(s/def ::subject-novar-noblank (s/or :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec)) ;; Predicates -(def pred-spec +(s/def ::predicate (s/or :ax/var ax/variable-spec :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec :ax/rdf-type ax/rdf-type-spec :triple/path ::ps/path)) -(def pred-nopath-spec +(s/def ::predicate-nopath (s/or :ax/var ax/variable-spec :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec :ax/rdf-type ax/rdf-type-spec)) -(def pred-novar-spec +(s/def ::predicate-novar (s/or :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec :ax/rdf-type ax/rdf-type-spec)) -;; Objects +(s/def ::predicate-noblank + ::predicate-nopath) -(def obj-spec +(s/def ::predicate-novar-noblank + ::predicate-novar) + +;; Objects (includes Lists) + +(s/def ::object (s/or :ax/var ax/variable-spec :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec :ax/bnode ax/bnode-spec - :ax/literal ax/literal-spec)) + :ax/literal ax/literal-spec + :triple/list ::list + :triple/bnodes ::bnodes)) -(def obj-novar-spec +(s/def ::object-nopath + (s/or :ax/var ax/variable-spec + :ax/iri ax/iri-spec + :ax/prefix-iri ax/prefix-iri-spec + :ax/bnode ax/bnode-spec + :ax/literal ax/literal-spec + :triple/list ::list-nopath + :triple/bnodes ::bnodes-nopath)) + +(s/def ::object-novar (s/or :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec :ax/bnode ax/bnode-spec - :ax/literal ax/literal-spec)) + :ax/literal ax/literal-spec + :triple/list ::list-novar + :triple/bnodes ::bnodes-novar)) -(def obj-noblank-spec +(s/def ::object-noblank (s/or :ax/var ax/variable-spec :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec :ax/literal ax/literal-spec)) -(def obj-novar-noblank-spec +(s/def ::object-novar-noblank (s/or :ax/iri ax/iri-spec :ax/prefix-iri ax/prefix-iri-spec :ax/literal ax/literal-spec)) +;; Lists + +;; Since lists are constructed out of blank nodes, we do not allow list +;; syntactic sugar (i.e. `:triple/list`) where blank nodes are banned. + +(s/def ::list + (s/coll-of ::object :kind list? :into [])) + +(s/def ::list-nopath + (s/coll-of ::object-nopath :kind list? :into [])) + +(s/def ::list-novar + (s/coll-of ::object-novar :kind list? :into [])) + +;; Blank Node Vectors + +;; For obvious reasons, we don't allow bnode vectors to exist where blank +;; nodes are banned. + +(defn- conform-pred-obj-pairs [po-pairs] + (mapv (fn [{:keys [pred obj]}] [pred obj]) po-pairs)) + +(s/def ::bnodes + (s/and vector? + (s/* (s/cat :pred ::predicate :obj ::object)) + (s/conformer conform-pred-obj-pairs))) + +(s/def ::bnodes-nopath + (s/and vector? + (s/* (s/cat :pred ::predicate-nopath :obj ::object)) + (s/conformer conform-pred-obj-pairs))) + +(s/def ::bnodes-novar + (s/and vector? + (s/* (s/cat :pred ::predicate-novar :obj ::object-novar)) + (s/conformer conform-pred-obj-pairs))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Combo Specs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; NOTE: Subjects can be non-IRIs in SPARQL, but not in RDF -;; NOTE: RDF collections not supported (yet?) - ;; single-branch `s/or`s are used to conform values ;; Object #?(:clj (defmacro make-obj-spec - [obj-spec] - `(s/or :triple/o (s/coll-of ~obj-spec - :min-count 1 - :kind set? - :into [])))) + ([obj-spec] + `(s/coll-of ~obj-spec :min-count 1 :kind set? :into [])))) (def obj-set-spec - (make-obj-spec obj-spec)) + (make-obj-spec ::object)) + +(def obj-set-nopath-spec + (make-obj-spec ::object-nopath)) (def obj-set-novar-spec - (make-obj-spec obj-novar-spec)) + (make-obj-spec ::object-novar)) (def obj-set-noblank-spec - (make-obj-spec obj-noblank-spec)) + (make-obj-spec ::object-noblank)) (def obj-set-novar-noblank-spec - (make-obj-spec obj-novar-noblank-spec)) + (make-obj-spec ::object-novar-noblank)) ;; Predicate Object #?(:clj - (defmacro make-pred-objs-spec - [pred-spec objs-spec] - `(s/or :triple/po (s/map-of ~pred-spec ~objs-spec - :min-count 1 - :into [])))) + (defmacro make-pred-objs-spec [pred-spec objs-spec] + `(s/map-of ~pred-spec (s/or :triple.nform/o ~objs-spec) + :min-count 1 :conform-keys true :into []))) (def pred-objs-spec - (make-pred-objs-spec pred-spec obj-set-spec)) + (make-pred-objs-spec ::predicate obj-set-spec)) (def pred-objs-nopath-spec - (make-pred-objs-spec pred-nopath-spec obj-set-spec)) + (make-pred-objs-spec ::predicate-nopath obj-set-nopath-spec)) (def pred-objs-novar-spec - (make-pred-objs-spec pred-novar-spec obj-set-novar-spec)) + (make-pred-objs-spec ::predicate-novar obj-set-novar-spec)) (def pred-objs-noblank-spec - (make-pred-objs-spec pred-nopath-spec obj-set-noblank-spec)) + (make-pred-objs-spec ::predicate-noblank obj-set-noblank-spec)) (def pred-objs-novar-noblank-spec - (make-pred-objs-spec pred-novar-spec obj-set-novar-noblank-spec)) + (make-pred-objs-spec ::predicate-novar-noblank obj-set-novar-noblank-spec)) ;; Subject Predicate Object +(def empty-map-spec + (s/map-of any? any? :count 0 :conform-keys true :into [])) + +;; cljs-specific ignore is needed since this fn is called in a macro. +#_{:clj-kondo/ignore #?(:clj [] :cljs [:unused-private-var])} +(defn- valid-conformed-spo? [spo-pairs] + (every? + (fn [[s po]] + (or (#{:triple/list :triple/bnodes} (first s)) + (#{:triple.nform/po} (first po)))) + spo-pairs)) + #?(:clj - (defmacro make-nform-spec - [subj-spec pred-objs-spec] - `(s/or :triple/spo (s/map-of ~subj-spec ~pred-objs-spec - :conform-keys true - :into [])))) + (defmacro make-nform-spec [subj-spec pred-objs-spec] + `(s/and (s/map-of ~subj-spec + (s/or :triple.nform/po ~pred-objs-spec + :triple.nform/po-empty empty-map-spec) + :conform-keys true :into []) + valid-conformed-spo?))) (def normal-form-spec - (make-nform-spec subj-spec pred-objs-spec)) + (make-nform-spec ::subject pred-objs-spec)) (def normal-form-nopath-spec - (make-nform-spec subj-spec pred-objs-nopath-spec)) + (make-nform-spec ::subject-nopath pred-objs-nopath-spec)) (def normal-form-novar-spec - (make-nform-spec subj-novar-spec pred-objs-novar-spec)) + (make-nform-spec ::subject-novar pred-objs-novar-spec)) (def normal-form-noblank-spec - (make-nform-spec subj-noblank-spec pred-objs-noblank-spec)) + (make-nform-spec ::subject-noblank pred-objs-noblank-spec)) (def normal-form-novar-noblank-spec - (make-nform-spec subj-novar-noblank-spec pred-objs-novar-noblank-spec)) + (make-nform-spec ::subject-novar-noblank pred-objs-novar-noblank-spec)) ;; Triple Vectors (def triple-vec-spec - (s/tuple subj-spec pred-spec obj-spec)) + (s/tuple ::subject ::predicate ::object)) (def triple-vec-nopath-spec - (s/tuple subj-spec pred-nopath-spec obj-spec)) + (s/tuple ::subject-nopath ::predicate-nopath ::object-nopath)) (def triple-vec-novar-spec - (s/tuple subj-novar-spec pred-novar-spec obj-novar-spec)) + (s/tuple ::subject-novar ::predicate-novar ::object-novar)) (def triple-vec-noblank-spec - (s/tuple subj-noblank-spec pred-nopath-spec obj-noblank-spec)) + (s/tuple ::subject-noblank ::predicate-noblank ::object-noblank)) (def triple-vec-novar-noblank-spec - (s/tuple subj-novar-noblank-spec pred-novar-spec obj-novar-noblank-spec)) + (s/tuple ::subject-novar-noblank ::predicate-novar-noblank ::object-novar-noblank)) + +;; Triple Vectors (Coll, no predicates + objects) + +(def triple-vec-no-po-spec + (s/tuple ::subject-coll)) + +(def triple-vec-no-po-nopath-spec + (s/tuple ::subject-coll-nopath)) + +(def triple-vec-no-po-novar-spec + (s/tuple ::subject-coll-novar)) + +;; Triples + +(def triple-spec + (s/or :triple.vec/spo triple-vec-spec + :triple.vec/s triple-vec-no-po-spec + :triple.nform/spo normal-form-spec)) + +(def triple-nopath-spec + (s/or :triple.vec/spo triple-vec-nopath-spec + :triple.vec/s triple-vec-no-po-nopath-spec + :triple.nform/spo normal-form-nopath-spec)) + +(def triple-novar-spec + (s/or :triple.vec/spo triple-vec-novar-spec + :triple.vec/s triple-vec-no-po-novar-spec + :triple.nform/spo normal-form-novar-spec)) + +(def triple-noblank-spec + (s/or :triple.vec/spo triple-vec-noblank-spec + :triple.nform/spo normal-form-noblank-spec)) + +(def triple-novar-noblank-spec + (s/or :triple.vec/spo triple-vec-novar-noblank-spec + :triple.nform/spo normal-form-novar-noblank-spec)) + +;; Collection of Triples + +(def triple-coll-spec + (s/coll-of triple-spec :kind vector?)) + +(def triple-coll-nopath-spec + (s/coll-of triple-nopath-spec :kind vector?)) + +(def triple-coll-novar-spec + (s/coll-of triple-novar-spec :kind vector?)) + +(def triple-coll-noblank-spec + (s/coll-of triple-noblank-spec :kind vector?)) + +(def triple-coll-novar-noblank-spec + (s/coll-of triple-novar-noblank-spec :kind vector?)) + +;; Quads (for UPDATE) + +(def quad-nopath-spec + (s/and (s/tuple #{:graph} + ax/iri-or-var-spec + (s/or :triple.quad/spo triple-coll-nopath-spec)) + (s/conformer (fn [[_ iri triples]] [iri triples])))) + +(def quad-novar-spec + (s/and (s/tuple #{:graph} + ax/iri-or-var-spec + (s/or :triple.quad/spo triple-coll-novar-spec)) + (s/conformer (fn [[_ iri triples]] [iri triples])))) + +(def quad-noblank-spec + (s/and (s/tuple #{:graph} + ax/iri-or-var-spec + (s/or :triple.quad/spo triple-coll-noblank-spec)) + (s/conformer (fn [[_ iri triples]] [iri triples])))) + +(def quad-novar-noblank-spec + (s/and (s/tuple #{:graph} + ax/iri-or-var-spec + (s/or :triple.quad/spo triple-coll-novar-noblank-spec)) + (s/conformer (fn [[_ iri triples]] [iri triples])))) + +;; Collection of Quads (for UPDATE) + +(def quad-coll-nopath-spec + (s/coll-of (s/or :triple.vec/spo triple-vec-nopath-spec + :triple.vec/s triple-vec-no-po-nopath-spec + :triple.nform/spo normal-form-nopath-spec + :triple.quad/gspo quad-nopath-spec) + :kind vector?)) + +(def quad-coll-novar-spec + (s/coll-of (s/or :triple.vec/spo triple-vec-novar-spec + :triple.vec/s triple-vec-no-po-novar-spec + :triple.nform/spo normal-form-novar-spec + :triple.quad/gspo quad-novar-spec) + :kind vector?)) + +(def quad-coll-noblank-spec + (s/coll-of (s/or :triple.vec/spo triple-vec-noblank-spec + :triple.nform/spo normal-form-noblank-spec + :triple.quad/gspo quad-noblank-spec) + :kind vector?)) + +(def quad-coll-novar-noblank-spec + (s/coll-of (s/or :triple.vec/spo triple-vec-novar-noblank-spec + :triple.nform/spo normal-form-novar-noblank-spec + :triple.quad/gspo quad-novar-noblank-spec) + :kind vector?)) diff --git a/src/main/com/yetanalytics/flint/spec/update.cljc b/src/main/com/yetanalytics/flint/spec/update.cljc index e34db19..6503d59 100644 --- a/src/main/com/yetanalytics/flint/spec/update.cljc +++ b/src/main/com/yetanalytics/flint/spec/update.cljc @@ -45,78 +45,6 @@ n2 (get key-order-map k2 100)] (- n1 n2))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Quad specs -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(def triples-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-nopath-spec - :triple/nform ts/normal-form-nopath-spec) - :kind vector?)) - -(def triples-novar-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-novar-spec - :triple/nform ts/normal-form-novar-spec) - :kind vector?)) - -(def triples-noblank-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-noblank-spec - :triple/nform ts/normal-form-noblank-spec) - :kind vector?)) - -(def triples-novar-noblank-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-novar-noblank-spec - :triple/nform ts/normal-form-novar-noblank-spec) - :kind vector?)) - -(def quad-spec - (s/and (s/tuple #{:graph} - ax/iri-or-var-spec - (s/or :triple/quad-triples triples-spec)) - (s/conformer (fn [[_ iri t]] [iri t])))) - -(def quad-novar-spec - (s/and (s/tuple #{:graph} - ax/iri-or-var-spec - (s/or :triple/quad-triples triples-novar-spec)) - (s/conformer (fn [[_ iri t]] [iri t])))) - -(def quad-noblank-spec - (s/and (s/tuple #{:graph} - ax/iri-or-var-spec - (s/or :triple/quad-triples triples-noblank-spec)) - (s/conformer (fn [[_ iri t]] [iri t])))) - -(def quad-novar-noblank-spec - (s/and (s/tuple #{:graph} - ax/iri-or-var-spec - (s/or :triple/quad-triples triples-novar-noblank-spec)) - (s/conformer (fn [[_ iri t]] [iri t])))) - -(def triple-or-quads-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-nopath-spec - :triple/nform ts/normal-form-nopath-spec - :triple/quads quad-spec) - :kind vector?)) - -(def triple-or-quads-novar-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-novar-spec - :triple/nform ts/normal-form-novar-spec - :triple/quads quad-novar-spec) - :kind vector?)) - -(def triple-or-quads-noblank-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-noblank-spec - :triple/nform ts/normal-form-noblank-spec - :triple/quads quad-noblank-spec) - :kind vector?)) - -(def triple-or-quads-novar-noblank-spec - (s/coll-of (s/or :triple/vec ts/triple-vec-novar-noblank-spec - :triple/nform ts/normal-form-novar-noblank-spec - :triple/quads quad-novar-noblank-spec) - :kind vector?)) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Graph Management specs ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -221,7 +149,7 @@ ;; INSERT DATA -(s/def ::insert-data triple-or-quads-novar-spec) +(s/def ::insert-data ts/quad-coll-novar-spec) (def insert-data-update-spec (sparql-keys :req-un [::insert-data] @@ -230,7 +158,7 @@ ;; DELETE DATA -(s/def ::delete-data triple-or-quads-novar-noblank-spec) +(s/def ::delete-data ts/quad-coll-novar-noblank-spec) (def delete-data-update-spec (sparql-keys :req-un [::delete-data] @@ -239,7 +167,7 @@ ;; DELETE WHERE -(s/def ::delete-where triple-or-quads-noblank-spec) +(s/def ::delete-where ts/quad-coll-noblank-spec) (def delete-where-update-spec (sparql-keys :req-un [::delete-where] @@ -254,8 +182,8 @@ (s/or :update/iri ax/iri-or-prefixed-spec :update/named-iri (s/tuple #{:named} ax/iri-or-prefixed-spec))) -(s/def ::insert triple-or-quads-spec) -(s/def ::delete triple-or-quads-noblank-spec) +(s/def ::insert ts/quad-coll-nopath-spec) +(s/def ::delete ts/quad-coll-noblank-spec) (def modify-update-spec (sparql-keys :req-un [(or ::delete ::insert) diff --git a/src/main/com/yetanalytics/flint/spec/where.cljc b/src/main/com/yetanalytics/flint/spec/where.cljc index cbf3103..10d0a3a 100644 --- a/src/main/com/yetanalytics/flint/spec/where.cljc +++ b/src/main/com/yetanalytics/flint/spec/where.cljc @@ -110,19 +110,24 @@ :v ::vs/values) (s/conformer (fn [{:keys [v]}] [:where/values v])))) +(defmethod where-special-form-mm :default [_] + (constantly false)) + (def where-special-form-spec "Specs for special WHERE forms/graph patterns, which should be of the form `[:keyword ...]`." - (s/and vector? (s/multi-spec where-special-form-mm first))) + (s/and vector? + #(keyword? (first %)) + (s/multi-spec where-special-form-mm first))) + +(def where-spec + (s/or :where/special where-special-form-spec + :where/triple ts/triple-spec)) (s/def ::where (s/or :where-sub/select ::select :where-sub/where - (s/coll-of (s/or :where/special where-special-form-spec - :triple/vec ts/triple-vec-spec - :triple/nform ts/normal-form-spec) - :min-count 1 - :kind vector?) + (s/coll-of where-spec :min-count 1 :kind vector?) :where-sub/empty (s/and vector? empty?))) diff --git a/src/main/com/yetanalytics/flint/validate.cljc b/src/main/com/yetanalytics/flint/validate.cljc index fc56c89..423d909 100644 --- a/src/main/com/yetanalytics/flint/validate.cljc +++ b/src/main/com/yetanalytics/flint/validate.cljc @@ -3,6 +3,38 @@ [com.yetanalytics.flint.spec.expr :as es] [com.yetanalytics.flint.util :as u])) +(defn- bgp-divider? + "Is `ast-node` a divider between BGPs? BGPs are divided by `:where/special` + clauses, with the exception of filters (unless they themselves have a + subquery): + + [:where/triple + [:triple.vec/spo ...]] => 0 + [:where/triple + [:triple.nform/spo ...]] => 0 + [:where/special + [:where/filter ...]] => 0 ; FILTERs don't divide BGPs + [:where/special + [:where/optional ...]] => X ; BGP divider + [:where/triple + [:triple.nform/spo ...]] => 1 + " + [loc] + (let [?left-node (some-> loc zip/left zip/node) + ?curr-node (some-> loc zip/node)] + (or + ;; BGPs are separated by special clauses, with the exception of FILTER... + (and (some-> ?left-node (get-in [0]) (= :where/special)) + (some-> ?left-node (get-in [1 0]) (not= :where/filter))) + ;; BGPs are also separated via nesting (which also separates BGPs at the + ;; top level) + (some-> ?curr-node (get-in [0]) (= :where-sub/where)) + ;; ...including the case of FILTER (NOT) EXISTS, which has its own BGP + ;; as a subquery (c.f. MINUS) + (and (some-> ?curr-node (get-in [0]) (= :expr/branch)) + (or (some-> ?curr-node (get-in [1 0]) (= [:expr/op 'exists])) + (some-> ?curr-node (get-in [1 0]) (= [:expr/op 'not-exists]))))))) + (def axiom-keys #{:ax/iri :ax/prefix-iri :ax/var :ax/bnode :ax/wildcard :ax/rdf-type :ax/str-lit :ax/lmap-list :ax/num-lit :ax/bool-lit :ax/dt-lit @@ -17,12 +49,15 @@ (defn- ast-children [[k children]] (cond - (#{:triple/spo :triple/po} k) + ;; These values are colls of non-AST colls + (#{:triple/bnodes :triple.nform/spo :triple.nform/po} k) (apply concat children) + ;; These values are either AST nodes or scalars (or (and (vector? children) (keyword? (first children))) (not (coll? children))) [children] + ;; These values are colls of AST nodes :else children)) @@ -65,22 +100,37 @@ (defn collect-nodes [ast] - (loop [loc (ast-zipper ast) - node-m {}] + (loop [loc (ast-zipper ast) + node-m {} + bgp-idx 0] (if-not (zip/end? loc) - (let [ast-node (zip/node loc)] + (let [ast-node (zip/node loc) + bgp-idx* (cond-> bgp-idx (bgp-divider? loc) inc)] (if-some [k (u/get-keyword ast-node)] (cond - ;; Prefixes, blank nodes, and BIND clauses - (node-keys k) - (recur (zip/next loc) - (update-in node-m [k (second ast-node)] conj loc)) + ;; Prefixes, BIND (expr AS var), and SELECT ... (expr AS var) ... + (#{:ax/prefix-iri :where/bind :select/expr-as-var} k) + (let [v (second ast-node)] + (recur (zip/next loc) + (update-in node-m [k v] conj loc) + bgp-idx*)) + ;; Blank nodes + (#{:ax/bnode} k) + (let [bnode (second ast-node) + top-level (-> loc zip/path second first) + bgp-path (cond-> [top-level] + (= :where top-level) + (conj bgp-idx*))] + (recur (zip/next loc) + (update-in node-m [k bnode bgp-path] conj loc) + bgp-idx*)) ;; SELECT with GROUP BY (#{:group-by} k) (let [select-loc (zip/up loc) select (zip/node select-loc)] (recur (zip/next loc) - (update node-m :agg/select assoc select select-loc))) + (update node-m :agg/select assoc select select-loc) + bgp-idx*)) ;; SELECT with an aggregate expression (#{:expr/branch} k) (let [op (-> ast-node ; [:expr/branch ...] @@ -97,9 +147,9 @@ assoc select select-loc)] - (recur (zip/next loc) node-m*)) - (recur (zip/next loc) node-m))) + (recur (zip/next loc) node-m* bgp-idx*)) + (recur (zip/next loc) node-m bgp-idx*))) :else - (recur (zip/next loc) node-m)) - (recur (zip/next loc) node-m))) + (recur (zip/next loc) node-m bgp-idx*)) + (recur (zip/next loc) node-m bgp-idx*))) node-m))) diff --git a/src/main/com/yetanalytics/flint/validate/aggregate.cljc b/src/main/com/yetanalytics/flint/validate/aggregate.cljc index c8eca8f..5be545f 100644 --- a/src/main/com/yetanalytics/flint/validate/aggregate.cljc +++ b/src/main/com/yetanalytics/flint/validate/aggregate.cljc @@ -1,7 +1,7 @@ (ns com.yetanalytics.flint.validate.aggregate - (:require [clojure.zip :as zip] - [com.yetanalytics.flint.validate.variable :as vv] - [com.yetanalytics.flint.util :as u])) + (:require [com.yetanalytics.flint.validate.variable :as vv] + [com.yetanalytics.flint.util :as u] + [com.yetanalytics.flint.validate.util :as vu])) ;; In a query level which uses aggregates, only expressions consisting of ;; aggregates and constants may be projected, with one exception. @@ -9,7 +9,7 @@ ;; just a variable, those variables may be projected from the level. ;; GOOD: -;; SELECT (2 AS ?two) WHER { ?x ?y ?z } +;; SELECT (2 AS ?two) WHERE { ?x ?y ?z } ;; SELECT (SUM(?x) AS ?sum) WHERE { ?x ?y ?z } ;; SELECT ?x WHERE { ?x ?y ?z } GROUP BY ?x @@ -57,12 +57,12 @@ (case sel-k :ax/wildcard {:kind ::wildcard-group-by - :path (->> loc zip/path (mapv first))} + :path (vu/zip-path loc)} :select/var-or-exprs (when-some [bad-vars (validate-agg-select-clause group-by-vs sel-v)] {:kind ::invalid-aggregate-var :variables bad-vars - :path (->> loc zip/path (mapv first))}) + :path (vu/zip-path loc)}) ;; else - perhaps it is a CONSTRUCT, DESCRIBE, or ASK query instead nil))) diff --git a/src/main/com/yetanalytics/flint/validate/bnode.cljc b/src/main/com/yetanalytics/flint/validate/bnode.cljc index 757ba42..b99edbd 100644 --- a/src/main/com/yetanalytics/flint/validate/bnode.cljc +++ b/src/main/com/yetanalytics/flint/validate/bnode.cljc @@ -1,127 +1,18 @@ (ns com.yetanalytics.flint.validate.bnode (:require [clojure.set :as cset] - [clojure.zip :as zip])) + [com.yetanalytics.flint.validate.util :as vu])) -(defn- get-parent-loc - "Given a bnode's loc, return its parent (either a `:triple/vec` - or `:triple/nform` node)." - [loc] - (let [penultimate (-> loc zip/path last first)] - (case penultimate - :triple/vec - (-> loc ; [:ax/bnode ...] - zip/up ; [:triple/vec ...] - ) - :triple/o - (-> loc ; [:ax/bnode ...] - zip/up ; [:triple/o ...] - zip/up ; [:triple/po ...] - zip/up ; [:triple/spo ...] - zip/up ; [:triple/nform ...] - ) - :triple/spo - (-> loc ; [:ax/bnode ...] - zip/up ; [:triple/spo ...] - zip/up ; [:triple/nform ...] - )))) - -(defn- bgp-divider? - [ast-node] - (and (-> ast-node (get-in [0]) (= :where/special)) - (-> ast-node (get-in [1 0]) (not= :where/filter)))) - -(defn- get-bgp-index - "The BGP path is the regular zip loc path appended with an index that - corresponds to that of the BGP in the WHERE vector. For example: - - [:triple/vec ...] => 0 - [:triple/nform ...] => 0 - [:where/special - [:where/filter ...]] => 0 ; FILTERs don't divide BGPs - [:where/special - [:where/optional ...]] => X ; BGP divider - [:triple/nform ...] => 1 - - Note that this only works with locs that are immediate children - of `:where-sub/where` nodes. - " - [loc] - (let [lefts (zip/lefts loc)] - (count (filter bgp-divider? lefts)))) - -(defn- get-where-index - [pnode nnode] - (let [indexed (map-indexed (fn [i x] [x i]) (second pnode))] - (some (fn [[x i]] (when (= x nnode) i)) indexed))) - -(defn- annotated-path - "Create a coll of AST keywords where all `:where-sub/where`s are - followed by indices, either the index in the WHERE vector or the - index of the BGP (for the very last one)." - [loc] - (let [parent-loc (get-parent-loc loc)] - (loop [zip-path (zip/path parent-loc) - res-path []] - (let [?pnode (first zip-path) - ?nnode (second zip-path)] - (cond - (and ?pnode - ?nnode - (= :where-sub/where (first ?pnode))) - (recur (rest zip-path) - (conj res-path (first ?pnode) (get-where-index ?pnode ?nnode))) - (and ?pnode - (not ?nnode) - (= :where-sub/where (first ?pnode))) - (recur (rest zip-path) - (conj res-path (first ?pnode) (get-bgp-index parent-loc))) - ?pnode - (recur (rest zip-path) - (conj res-path (first ?pnode))) - :else - res-path))))) - -(defn- valid-bnode-locs? - "Given `locs`, return `false` if `bnode` is duplicated across multiple - BGPs, `true` otherwise." - [[bnode locs]] - (if (<= (count locs) 1) - true ; Can't have dupe bnodes if there's only one instance :p - (let [loc-paths (map (fn [loc] (mapv first (zip/path loc))) locs) - [wh non-wh] (split-with #(some #{:where-sub/where} %) loc-paths) - ?wheres (not-empty wh) - ?non-wheres (not-empty non-wh)] - (cond - ;; Blank nodes only exist in a non-WHERE clause (e.g. CONSTRUCT, - ;; INSERT DATA, or INSERT). Since only one such clause may exist - ;; in a Query or Update, and since each counts as a single BGP, - ;; we are done. - (and (not ?wheres) - ?non-wheres) - true - ;; Blank nodes exist in both a WHERE and non-WHERE clause. Since - ;; those automatically count as two different BGPs, we are done. - (and ?wheres - ?non-wheres) - false - ;; Blank nodes only exist in WHERE clauses. They may all be in one - ;; or more BGP, so we need to investigate further. - (and ?wheres - (not ?non-wheres)) - (let [bgp-paths (map annotated-path locs)] - (apply = bgp-paths)) - :else - (throw (ex-info "Blank nodes located in invalid locations!" - {:kind ::invalid-bnode-loc - :bnode bnode - :zip-locs locs})))))) +(defn- invalid-bnode? + "Is the blank node that is associated with `bgp-loc-m` invalid? It is + if the map has more than one entry, indicating that the blank node is + located across multiple BGPs." + [bgp-loc-m] + (< 1 (count bgp-loc-m))) (defn- bnode-err-map [bnode loc] {:bnode bnode - ;; Rather wasteful to call `annotated-path` twice, but this only - ;; occurs during exn throwing so performance isn't a priority. - :path (annotated-path loc)}) + :path (vu/zip-path loc)}) (defn- bnode-locs->err-map [bnode-locs] @@ -140,19 +31,22 @@ ([node-m] (validate-bnodes #{} node-m)) ([prev-bnodes node-m] - (let [bnode-locs (->> (:ax/bnode node-m) - (filter (fn [[bnode _]] (not= '_ bnode)))) - new-bnodes (set (keys bnode-locs)) + (let [bnode-bgp-m (-> (:ax/bnode node-m) (dissoc '_)) + new-bnodes (set (keys bnode-bgp-m)) bnode-union (cset/union prev-bnodes new-bnodes)] - (if-some [bad-bnode-locs (->> bnode-locs - (filter (comp prev-bnodes first)) + (if-some [bad-bnode-locs (->> bnode-bgp-m + (keep (fn [[bnode bgp-loc-m]] + (when (contains? prev-bnodes bnode) + [bnode (apply concat (vals bgp-loc-m))]))) not-empty)] [bnode-union {:kind ::dupe-bnodes-update :errors (bnode-locs->err-map bad-bnode-locs) :prev-bnodes prev-bnodes}] - (if-some [bad-bnode-locs (->> bnode-locs - (filter (comp not valid-bnode-locs?)) + (if-some [bad-bnode-locs (->> bnode-bgp-m + (keep (fn [[bnode bgp-loc-m]] + (when (invalid-bnode? bgp-loc-m) + [bnode (apply concat (vals bgp-loc-m))]))) not-empty)] [bnode-union {:kind ::dupe-bnodes-bgp diff --git a/src/main/com/yetanalytics/flint/validate/prefix.cljc b/src/main/com/yetanalytics/flint/validate/prefix.cljc index b4f9353..9207d18 100644 --- a/src/main/com/yetanalytics/flint/validate/prefix.cljc +++ b/src/main/com/yetanalytics/flint/validate/prefix.cljc @@ -1,5 +1,5 @@ (ns com.yetanalytics.flint.validate.prefix - (:require [clojure.zip :as zip])) + (:require [com.yetanalytics.flint.validate.util :as vu])) (defn- invalid-prefix? "Does the prefix of `prefix-iri` exist in the `prefixes` map/set? @@ -14,7 +14,7 @@ {:prefixes prefixes :iri prefix-iri :prefix (or (some->> prefix-iri namespace keyword) :$) - :path (conj (->> loc zip/path (mapv first)) :ax/prefix-iri)}) + :path (conj (vu/zip-path loc) :ax/prefix-iri)}) (defn validate-prefixes "Given `node-m` a map from nodes to zipper locs, check that each prefix diff --git a/src/main/com/yetanalytics/flint/validate/scope.cljc b/src/main/com/yetanalytics/flint/validate/scope.cljc index 42ab598..f786ad6 100644 --- a/src/main/com/yetanalytics/flint/validate/scope.cljc +++ b/src/main/com/yetanalytics/flint/validate/scope.cljc @@ -1,6 +1,7 @@ (ns com.yetanalytics.flint.validate.scope (:require [clojure.zip :as zip] [com.yetanalytics.flint.validate.variable :as vv] + [com.yetanalytics.flint.validate.util :as vu] [com.yetanalytics.flint.util :as u])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -12,14 +13,14 @@ {:kind ::var-in-scope :variable var :scope-vars scope-vars - :path (conj (->> zip-loc zip/path (mapv first)) k)}) + :path (conj (vu/zip-path zip-loc) k)}) (defn- not-in-scope-err-map [vars scope-vars zip-loc k] {:kind ::var-not-in-scope :variables vars :scope-vars scope-vars - :path (conj (->> zip-loc zip/path (mapv first)) k)}) + :path (conj (vu/zip-path zip-loc) k)}) (defn- validate-bind "Validate `BIND (expr AS var)` in WHERE clauses." diff --git a/src/main/com/yetanalytics/flint/validate/util.cljc b/src/main/com/yetanalytics/flint/validate/util.cljc new file mode 100644 index 0000000..1b639eb --- /dev/null +++ b/src/main/com/yetanalytics/flint/validate/util.cljc @@ -0,0 +1,7 @@ +(ns com.yetanalytics.flint.validate.util + (:require [clojure.zip :as zip])) + +(defn zip-path + "Return the path vector (excluding array indices) that leads up to `loc`." + [loc] + (->> loc zip/path (mapv first))) diff --git a/src/main/com/yetanalytics/flint/validate/variable.cljc b/src/main/com/yetanalytics/flint/validate/variable.cljc index 94103a1..f906592 100644 --- a/src/main/com/yetanalytics/flint/validate/variable.cljc +++ b/src/main/com/yetanalytics/flint/validate/variable.cljc @@ -95,30 +95,40 @@ ;; Basic Graph Pattern -(defmethod get-scope-vars :triple/vec [[_ spo]] +(defmethod get-scope-vars :triple/list [[_ list]] + (mapcat get-scope-vars list)) + +(defmethod get-scope-vars :triple/bnodes [[_ po-pairs]] + (mapcat (fn [[p o]] (concat (get-scope-vars p) + (get-scope-vars o))) + po-pairs)) + +(mapcat get-scope-vars '[[[:ax/var ?z1] [:ax/var ?z2]]]) + +(defmethod get-scope-vars :triple.vec/spo [[_ spo]] (mapcat get-scope-vars spo)) -(defmethod get-scope-vars :triple/o [[_ o]] - (mapcat get-scope-vars o)) +(defmethod get-scope-vars :triple.vec/s [[_ s]] + (get-scope-vars s)) + +(defmethod get-scope-vars :triple.nform/o [[_ o-coll]] + (mapcat get-scope-vars o-coll)) -(defmethod get-scope-vars :triple/po [[_ po]] - (reduce-kv (fn [acc p o] (apply concat - acc - (get-scope-vars p) - (map get-scope-vars o))) +(defmethod get-scope-vars :triple.nform/po [[_ po-pairs]] + (reduce-kv (fn [acc p o-coll] (apply concat + acc + (get-scope-vars p) + (map get-scope-vars o-coll))) [] - po)) + po-pairs)) -(defmethod get-scope-vars :triple/spo [[_ spo]] +(defmethod get-scope-vars :triple.nform/spo [[_ spo-pairs]] (reduce-kv (fn [acc s po] (apply concat acc (get-scope-vars s) (map get-scope-vars po))) [] - spo)) - -(defmethod get-scope-vars :triple/nform [[_ nform]] - (get-scope-vars nform)) + spo-pairs)) ;; Path @@ -133,6 +143,9 @@ ;; Group +(defmethod get-scope-vars :where/triple [[_ vs]] + (get-scope-vars vs)) + (defmethod get-scope-vars :where/special [[_ vs]] (get-scope-vars vs)) @@ -181,4 +194,3 @@ (defmethod get-scope-vars :where/values [[_ [_ values]]] (mapcat get-scope-vars (first values))) - diff --git a/src/test/com/yetanalytics/flint/format/expr_test.cljc b/src/test/com/yetanalytics/flint/format/expr_test.cljc index 1486c83..504b118 100644 --- a/src/test/com/yetanalytics/flint/format/expr_test.cljc +++ b/src/test/com/yetanalytics/flint/format/expr_test.cljc @@ -179,16 +179,18 @@ (is (= "EXISTS {\n ?x ?y ?z .\n}" (->> '[:expr/branch [[:expr/op exists] [:expr/args [[:where-sub/where - [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]]] + [:where/triple + [[:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]]] format-ast))) (is (= "NOT EXISTS {\n ?x ?y ?z .\n}" (->> '[:expr/branch [[:expr/op not-exists] [:expr/args [[:where-sub/where - [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]]] + [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]]] format-ast))))) (deftest format-expr-as-var-test diff --git a/src/test/com/yetanalytics/flint/format/query_test.cljc b/src/test/com/yetanalytics/flint/format/query_test.cljc index 607fa28..b08d170 100644 --- a/src/test/com/yetanalytics/flint/format/query_test.cljc +++ b/src/test/com/yetanalytics/flint/format/query_test.cljc @@ -23,9 +23,10 @@ [[:prefixes [[:prologue/prefix [[:ax/prefix :foo] [:ax/iri ""]]]]] [:select [:select/var-or-exprs [[:ax/var ?x]]]] [:from [:ax/iri ""]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]] [:order-by [[:mod/asc-desc [[:mod/op asc] [:mod/asc-desc-expr [:expr/terminal [:ax/var ?y]]]]]]] @@ -42,13 +43,14 @@ " ?x ?y ?z ." "}"]) (->> '[:query/construct - [[:construct [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]] + [[:construct [[:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]] [:from [:ax/iri ""]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]] format-ast))) (is (= (cstr/join "\n" ["CONSTRUCT" "FROM " @@ -58,9 +60,10 @@ (->> '[:query/construct [[:construct []] [:from [:ax/iri ""]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]] format-ast))) (is (= (cstr/join "\n" ["CONSTRUCT" "WHERE {" @@ -68,9 +71,10 @@ "}"]) (->> '[:query/construct [[:construct []] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]] format-ast)))) (testing "Formatting DESCRIBE queries" (is (= (cstr/join "\n" ["DESCRIBE ?x ?y" @@ -83,9 +87,10 @@ [[:describe [:describe/vars-or-iris [[:ax/var ?x] [:ax/var ?y]]]] [:from-named [[:ax/iri ""] [:ax/iri ""]]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]] format-ast)))) (testing "Formatting ASK queries" (is (= (cstr/join "\n" ["ASK" @@ -98,9 +103,10 @@ [[:ask []] [:from-named [[:ax/iri ""] [:ax/iri ""]]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]] format-ast))) (is (= (cstr/join "\n" ["ASK" "WHERE {" @@ -108,7 +114,8 @@ "}"]) (->> '[:query/ask [[:ask []] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]] format-ast))))) diff --git a/src/test/com/yetanalytics/flint/format/triple_test.cljc b/src/test/com/yetanalytics/flint/format/triple_test.cljc index bc1c8e9..951f756 100644 --- a/src/test/com/yetanalytics/flint/format/triple_test.cljc +++ b/src/test/com/yetanalytics/flint/format/triple_test.cljc @@ -13,21 +13,110 @@ "?s2 ?p1 ?o1 , ?o2 ;" " ?p2 ?o1 , ?o2 ."]) (f/format-ast - '[:triple/nform - [:triple/spo [[[:ax/iri ""] - [:triple/po - [[[:ax/var ?p1] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]] - [[:ax/var ?p2] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]]]]] - [[:ax/var ?s2] - [:triple/po - [[[:ax/var ?p1] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]] - [[:ax/var ?p2] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]]]]]]]] + '[:triple.nform/spo + [[[:ax/iri ""] + [:triple.nform/po + [[[:ax/var ?p1] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]] + [[:ax/var ?p2] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]]]]] + [[:ax/var ?s2] + [:triple.nform/po + [[[:ax/var ?p1] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]] + [[:ax/var ?p2] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]]]]]]] {:pretty? true}))) (is (= "?s ?p ?o ." (f/format-ast - '[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]] - {:pretty? true}))))) + '[:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]] + {:pretty? true}))) + (is (= "( 1 2 ) :p \"w\" ." + (f/format-ast + '[:triple.vec/spo + [[:triple/list [[:ax/literal 1] [:ax/literal 2]]] + [:ax/prefix-iri :p] + [:ax/literal "w"]]] + {:pretty? true}))) + (is (= "( ?x ?y ) ." + (f/format-ast + '[:triple.vec/s + [[:triple/list [[:ax/var ?x] [:ax/var ?y]]]]] + {:pretty? true}))) + (is (= "( ?x ?y ) ." + (f/format-ast + '[:triple.nform/spo + [[[:triple/list [[:ax/var ?x] [:ax/var ?y]]] + [:triple.nform/po-empty []]]]] + {:pretty? true}))) + (is (= "\"v\" :p ( 1 2 ( 3 ) ) ." + (f/format-ast + '[:triple.vec/spo + [[:ax/literal "v"] + [:ax/prefix-iri :p] + [:triple/list [[:ax/literal 1] + [:ax/literal 2] + [:triple/list [[:ax/literal 3]]]]]]] + {:pretty? true}))) + (is (= "\"v\" :p () ." + (f/format-ast + '[:triple.vec/spo + [[:ax/literal "v"] + [:ax/prefix-iri :p] + [:triple/list []]]] + {:pretty? true}))) + (is (= "[ foaf:name ?name ;\n foaf:mbox ] :q \"w\" ." + (f/format-ast + '[:triple.vec/spo + [[:triple/bnodes [[[:ax/prefix-iri :foaf/name] + [:ax/var ?name]] + [[:ax/prefix-iri :foaf/mbox] + [:ax/iri ""]]]] + [:ax/prefix-iri :q] + [:ax/literal "w"]]] + {:pretty? true}))) + (is (= "[ foaf:name ?name ; foaf:mbox ] :q \"w\" ." + (f/format-ast + '[:triple.vec/spo + [[:triple/bnodes [[[:ax/prefix-iri :foaf/name] + [:ax/var ?name]] + [[:ax/prefix-iri :foaf/mbox] + [:ax/iri ""]]]] + [:ax/prefix-iri :q] + [:ax/literal "w"]]] + {:pretty? false}))) + (is (= "[ foaf:name ?name ; foaf:mbox ] ." + (f/format-ast + '[:triple.vec/s + [[:triple/bnodes [[[:ax/prefix-iri :foaf/name] + [:ax/var ?name]] + [[:ax/prefix-iri :foaf/mbox] + [:ax/iri ""]]]]]] + {:pretty? false}))) + (is (= "[ foo:bar [ foaf:mbox ] ] ." + (f/format-ast + '[:triple.vec/s + [[:triple/bnodes [[[:ax/prefix-iri :foo/bar] + [:triple/bnodes + [[[:ax/prefix-iri :foaf/mbox] + [:ax/iri ""]]]]]]]]] + {:pretty? true}))) + (is (= "[ foo:bar [ foaf:mbox ] ] ." + (f/format-ast + '[:triple.vec/s + [[:triple/bnodes [[[:ax/prefix-iri :foo/bar] + [:triple/bnodes + [[[:ax/prefix-iri :foaf/mbox] + [:ax/iri ""]]]]]]]]] + {:pretty? false}))) + (is (= "[] ?p ?o ." + (f/format-ast + '[:triple.vec/spo + [[:triple/bnodes []] + [:ax/var ?p] + [:ax/var ?o]]] + {:pretty? false}))))) diff --git a/src/test/com/yetanalytics/flint/format/update_test.cljc b/src/test/com/yetanalytics/flint/format/update_test.cljc index 31e6237..b27a5c7 100644 --- a/src/test/com/yetanalytics/flint/format/update_test.cljc +++ b/src/test/com/yetanalytics/flint/format/update_test.cljc @@ -13,9 +13,9 @@ " foo:x dc:title \"Title\" ." "}"]) (->> '[:update/insert-data - [[:insert-data [[:triple/vec [[:ax/prefix-iri :foo/x] - [:ax/prefix-iri :dc/title] - [:ax/literal "Title"]]]]]]] + [[:insert-data [[:triple.vec/spo [[:ax/prefix-iri :foo/x] + [:ax/prefix-iri :dc/title] + [:ax/literal "Title"]]]]]]] format-ast)))) (testing "Formatting DELETE DATA clauses" (is (= (cstr/join "\n" ["DELETE DATA {" @@ -25,12 +25,12 @@ "}"]) (->> '[:update/delete-data [[:delete-data - [[:triple/quads + [[:triple.quad/gspo [[:ax/iri ""] - [:triple/quad-triples - [[:triple/vec [[:ax/prefix-iri :foo/x] - [:ax/prefix-iri :dc/title] - [:ax/literal "Title"]]]]]]]]]]] + [:triple.quad/spo + [[:triple.vec/spo [[:ax/prefix-iri :foo/x] + [:ax/prefix-iri :dc/title] + [:ax/literal "Title"]]]]]]]]]]] format-ast)))) (testing "Formatting DELETE WHERE clauses" (is (= (cstr/join "\n" ["DELETE WHERE {" @@ -39,8 +39,8 @@ "}"]) (->> '[:update/delete-where [[:delete-where - [[:triple/vec [[:ax/var ?x] [:ax/var ?y] [:ax/var ?z]]] - [:triple/vec [[:ax/var ?i] [:ax/var ?j] [:ax/var ?k]]]]]]] + [[:triple.vec/spo [[:ax/var ?x] [:ax/var ?y] [:ax/var ?z]]] + [:triple.vec/spo [[:ax/var ?i] [:ax/var ?j] [:ax/var ?k]]]]]]] format-ast))) (is (= (cstr/join "\n" ["DELETE WHERE {" " ?x ?y ?z ." @@ -52,19 +52,21 @@ "}"]) (->> '[:update/delete-where [[:delete-where - [[:triple/vec + [[:triple.vec/spo [[:ax/var ?x] [:ax/var ?y] [:ax/var ?z]]] - [:triple/nform - [:triple/spo [[[:ax/var ?i] - [:triple/po [[[:ax/var ?j] - [:triple/o [[:ax/var ?k]]]]]]] - [[:ax/var ?s] - [:triple/po [[[:ax/var ?p] - [:triple/o [[:ax/var ?o]]]]]]]]]] - [:triple/quads + [:triple.nform/spo + [[[:ax/var ?i] + [:triple.nform/po + [[[:ax/var ?j] + [:triple.nform/o [[:ax/var ?k]]]]]]] + [[:ax/var ?s] + [:triple.nform/po + [[[:ax/var ?p] + [:triple.nform/o [[:ax/var ?o]]]]]]]]] + [:triple.quad/gspo [[:ax/iri ""] - [:triple/quad-triples - [[:triple/vec [[:ax/var ?q] [:ax/var ?r] [:ax/var ?s]]]]]]]]]]] + [:triple.quad/spo + [[:triple.vec/spo [[:ax/var ?q] [:ax/var ?r] [:ax/var ?s]]]]]]]]]]] format-ast)))) (testing "Formatting DELETE...INSERT clauses" (is (= (cstr/join "\n" ["INSERT {" @@ -76,12 +78,13 @@ "}"]) (->> '[:update/modify [[:insert - [[:triple/vec [[:ax/var ?a] [:ax/var ?b] [:ax/var ?c]]]]] + [[:triple.vec/spo [[:ax/var ?a] [:ax/var ?b] [:ax/var ?c]]]]] [:using [:update/named-iri [:named [:ax/iri ""]]]] [:where [:where-sub/where - [[:triple/vec [[:ax/var ?a] [:ax/var ?b] [:ax/var ?c]]]]]]]] + [[:where/triple + [:triple.vec/spo [[:ax/var ?a] [:ax/var ?b] [:ax/var ?c]]]]]]]]] format-ast))) (is (= (cstr/join "\n" ["WITH " "DELETE {" @@ -97,18 +100,20 @@ "}"]) (->> '[:update/modify [[:with [:ax/iri ""]] - [:delete [[:triple/vec [[:ax/var ?x] [:ax/var ?y] [:ax/var ?z]]]]] - [:insert [[:triple/vec [[:ax/var ?a] [:ax/var ?b] [:ax/var ?c]]]]] + [:delete [[:triple.vec/spo [[:ax/var ?x] [:ax/var ?y] [:ax/var ?z]]]]] + [:insert [[:triple.vec/spo [[:ax/var ?a] [:ax/var ?b] [:ax/var ?c]]]]] [:using [:update/iri [:ax/iri ""]]] [:where [:where-sub/where - [[:triple/nform - [:triple/spo + [[:where/triple + [:triple.nform/spo [[[:ax/var ?x] - [:triple/po [[[:ax/var ?y] - [:triple/o [[:ax/var ?z]]]]]]] + [:triple.nform/po + [[[:ax/var ?y] + [:triple.nform/o [[:ax/var ?z]]]]]]] [[:ax/var ?a] - [:triple/po [[[:ax/var ?b] - [:triple/o [[:ax/var ?c]]]]]]]]]]]]]]] + [:triple.nform/po + [[[:ax/var ?b] + [:triple.nform/o [[:ax/var ?c]]]]]]]]]]]]]]] format-ast)))) (testing "Formatting graph management updates" (testing "- LOAD" diff --git a/src/test/com/yetanalytics/flint/format/where_test.cljc b/src/test/com/yetanalytics/flint/format/where_test.cljc index 9ddd7ce..797b059 100644 --- a/src/test/com/yetanalytics/flint/format/where_test.cljc +++ b/src/test/com/yetanalytics/flint/format/where_test.cljc @@ -11,8 +11,8 @@ " ?s ?p ?o ." "}"]) (-> '[:where-sub/where - [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]] - [:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]] + [:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]] (f/format-ast {:pretty? true})))) (is (= (cstr/join "\n" ["{" " ?s1 ?p1 ?o1 ." @@ -51,47 +51,50 @@ " }" "}"]) (-> '[:where-sub/where - [[:triple/vec - [[:ax/var ?s1] [:ax/var ?p1] [:ax/var ?o1]]] - [:triple/nform - [:triple/spo [[[:ax/var ?s2] - [:triple/po [[[:ax/var ?p2] - [:triple/o [[:ax/var ?o2a] - [:ax/var ?o2b]]]]]]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s1] [:ax/var ?p1] [:ax/var ?o1]]]] + [:where/triple + [:triple.nform/spo + [[[:ax/var ?s2] + [:triple.nform/po + [[[:ax/var ?p2] + [:triple.nform/o [[:ax/var ?o2a] + [:ax/var ?o2b]]]]]]]]]] [:where/special [:where/recurse [:where-sub/where - [[:triple/vec [[:ax/var ?s3] [:ax/var ?p3] [:ax/var ?o3]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s3] [:ax/var ?p3] [:ax/var ?o3]]]]]]]] [:where/special [:where/union [[:where-sub/where - [[:triple/vec - [[:ax/var ?s4] [:ax/var ?p4] [:ax/var ?o4]]]]] + [[:where/triple [:triple.vec/spo + [[:ax/var ?s4] [:ax/var ?p4] [:ax/var ?o4]]]]]] [:where-sub/where - [[:triple/vec [[:ax/var ?s5] [:ax/var ?p5] [:ax/var ?o5]]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s5] [:ax/var ?p5] [:ax/var ?o5]]]]]]]]] [:where/special [:where/optional [:where-sub/where - [[:triple/vec [[:ax/var ?s6] [:ax/var ?p6] [:ax/var ?o6]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s6] [:ax/var ?p6] [:ax/var ?o6]]]]]]]] [:where/special [:where/minus [:where-sub/where - [[:triple/vec [[:ax/var ?s7] [:ax/var ?p7] [:ax/var ?o7]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s7] [:ax/var ?p7] [:ax/var ?o7]]]]]]]] [:where/special [:where/graph [[:ax/prefix-iri :ns/my-graph] [:where-sub/where - [[:triple/vec [[:ax/var ?s8] [:ax/var ?p8] [:ax/var ?o8]]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s8] [:ax/var ?p8] [:ax/var ?o8]]]]]]]]] [:where/special [:where/service [[:ax/prefix-iri :ns/my-uri] [:where-sub/where - [[:triple/vec [[:ax/var ?s9] [:ax/var ?p9] [:ax/var ?o9]]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s9] [:ax/var ?p9] [:ax/var ?o9]]]]]]]]] [:where/special [:where/service-silent [[:ax/prefix-iri :ns/my-uri] [:where-sub/where - [[:triple/vec [[:ax/var ?s10] [:ax/var ?p10] [:ax/var ?o10]]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s10] [:ax/var ?p10] [:ax/var ?o10]]]]]]]]] [:where/special [:where/bind [:expr/as-var diff --git a/src/test/com/yetanalytics/flint/spec/expr_test.cljc b/src/test/com/yetanalytics/flint/spec/expr_test.cljc index 7814ddc..649ad60 100644 --- a/src/test/com/yetanalytics/flint/spec/expr_test.cljc +++ b/src/test/com/yetanalytics/flint/spec/expr_test.cljc @@ -41,9 +41,10 @@ ;; (We can't `require` it due to circular dependencies.) (is (= [:expr/branch [[:expr/op 'exists] [:expr/args [[:where-sub/where - [[:triple/vec '[[:ax/var ?s] - [:ax/var ?p] - [:ax/var ?o]]]]]]]]] + [[:where/triple + [:triple.vec/spo '[[:ax/var ?s] + [:ax/var ?p] + [:ax/var ?o]]]]]]]]]] (s/conform ::es/expr '(exists [[?s ?p ?o]])))) (is (= [:expr/branch [[:expr/op 'contains] [:expr/args [[:expr/terminal [:ax/literal "foo"]] diff --git a/src/test/com/yetanalytics/flint/spec/query_test.cljc b/src/test/com/yetanalytics/flint/spec/query_test.cljc index 19ba375..b60b3ba 100644 --- a/src/test/com/yetanalytics/flint/spec/query_test.cljc +++ b/src/test/com/yetanalytics/flint/spec/query_test.cljc @@ -18,9 +18,10 @@ [[:prefixes [[:prologue/prefix [[:ax/prefix :foo] [:ax/iri ""]]]]] [:select [:select/var-or-exprs [[:ax/var ?x]]]] [:from [:ax/iri ""]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]] [:order-by [[:mod/asc-desc [[:mod/op asc] [:mod/asc-desc-expr [:expr/terminal [:ax/var ?y]]]]]]] diff --git a/src/test/com/yetanalytics/flint/spec/triple_test.cljc b/src/test/com/yetanalytics/flint/spec/triple_test.cljc index eb4f5af..b2677b4 100644 --- a/src/test/com/yetanalytics/flint/spec/triple_test.cljc +++ b/src/test/com/yetanalytics/flint/spec/triple_test.cljc @@ -5,52 +5,299 @@ (deftest conform-triple-test (testing "Conforming triples" - (is (= '[:triple/spo [[[:ax/var ?s] - [:triple/po [[[:ax/var ?p] - [:triple/o [[:ax/var ?o]]]]]]]]] - (s/conform ts/normal-form-spec '{?s {?p #{?o}}}))) - (is (= '[:triple/spo + (is (= '[:triple.nform/spo + [[[:ax/var ?s] + [:triple.nform/po + [[[:ax/var ?p] + [:triple.nform/o [[:ax/var ?o]]]]]]]]] + (s/conform ts/triple-spec '{?s {?p #{?o}}}))) + (is (= '[:triple.nform/spo [[[:ax/var ?s1] - [:triple/po [[[:ax/var ?p1] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]] - [[:ax/var ?p2] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]]]]] + [:triple.nform/po + [[[:ax/var ?p1] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]] + [[:ax/var ?p2] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]]]]] [[:ax/var ?s2] - [:triple/po [[[:ax/var ?p1] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]] - [[:ax/var ?p2] - [:triple/o [[:ax/var ?o1] [:ax/var ?o2]]]]]]]]] - (s/conform ts/normal-form-spec '{?s1 {?p1 #{?o1 ?o2} - ?p2 #{?o1 ?o2}} - ?s2 {?p1 #{?o1 ?o2} - ?p2 #{?o1 ?o2}}}))) - (is (= '[[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]] - (s/conform ts/triple-vec-spec '[?s ?p ?o]))) + [:triple.nform/po + [[[:ax/var ?p1] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]] + [[:ax/var ?p2] + [:triple.nform/o [[:ax/var ?o1] + [:ax/var ?o2]]]]]]]]] + (s/conform ts/triple-spec '{?s1 {?p1 #{?o1 ?o2} + ?p2 #{?o1 ?o2}} + ?s2 {?p1 #{?o1 ?o2} + ?p2 #{?o1 ?o2}}}))) + (is (= '[:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]] + (s/conform ts/triple-spec '[?s ?p ?o]))) (testing "with and without paths" - (is (= '[:triple/spo + (is (= '[:triple.nform/spo [[[:ax/var ?s] - [:triple/po + [:triple.nform/po [[[:triple/path [:path/branch [[:path/op cat] [:path/paths [[:path/terminal [:ax/prefix-iri :x/one]] [:path/terminal [:ax/prefix-iri :x/two]]]]]]] - [:triple/o [[:ax/var ?o]]]]]]]]] - (s/conform ts/normal-form-spec + [:triple.nform/o [[:ax/var ?o]]]]]]]]] + (s/conform ts/triple-spec '{?s {(cat :x/one :x/two) #{?o}}}))) - (is (= '[[:ax/var ?s] - [:triple/path - [:path/branch [[:path/op cat] - [:path/paths - [[:path/terminal [:ax/prefix-iri :x/one]] - [:path/terminal [:ax/prefix-iri :x/two]]]]]]] - [:ax/var ?o]] - (s/conform ts/triple-vec-spec + (is (= '[:triple.vec/spo + [[:ax/var ?s] + [:triple/path + [:path/branch [[:path/op cat] + [:path/paths + [[:path/terminal [:ax/prefix-iri :x/one]] + [:path/terminal [:ax/prefix-iri :x/two]]]]]]] + [:ax/var ?o]]] + (s/conform ts/triple-spec '[?s (cat :x/one :x/two) ?o]))) - (is (not (s/valid? ts/normal-form-nopath-spec + (is (not (s/valid? ts/triple-nopath-spec '{?s {(cat :x/one :x/two) #{?o}}}))) (is (->> '{?s {(cat :x/one :x/two) #{?o}}} - (s/explain-data ts/normal-form-nopath-spec) + (s/explain-data ts/triple-nopath-spec) ::s/problems + (filter #(-> % :path butlast (= [:triple.nform/spo 1 :triple.nform/po 0]))) (map :val) - (every? (partial = '(cat :x/one :x/two)))))))) + (every? (partial = '(cat :x/one :x/two)))))) + (testing "with list and blank nodes" + (is (= '[:triple.vec/spo + [[:triple/list [[:ax/literal 1] + [:ax/var ?x] + [:ax/literal 3] + [:ax/literal 4]]] + [:ax/prefix-iri :p] + [:ax/literal "w"]]] + (s/conform ts/triple-spec + '[(1 ?x 3 4) :p "w"]))) + (is (= '[:triple.vec/spo + [[:ax/var ?s] + [:ax/var ?p] + [:triple/list [[:ax/literal 100] [:ax/literal 200] [:ax/literal 300]]]]] + (s/conform ts/triple-spec + '[?s ?p (100 200 300)]))) + (is (= '[:triple.nform/spo + [[[:triple/list [[:ax/literal 1] + [:ax/var ?x] + [:ax/literal 3] + [:ax/literal 4]]] + [:triple.nform/po + [[[:ax/prefix-iri :p] + [:triple.nform/o [[:ax/literal "w"]]]]]]]]] + (s/conform ts/triple-spec + '{(1 ?x 3 4) {:p #{"w"}}}))) + (is (= '[:triple.nform/spo + [[[:ax/var ?x] + [:triple.nform/po + [[[:ax/prefix-iri :p] + [:triple.nform/o + [[:triple/list [[:ax/literal 100] + [:ax/literal 200] + [:ax/literal 300]]]]]]]]]]] + (s/conform ts/triple-spec + '{?x {:p #{(100 200 300)}}}))) + (is (= '[:triple.vec/s + [[:triple/list [[:ax/literal 1] + [:ax/var ?x] + [:triple/list [[:ax/literal 2]]]]]]] + (s/conform ts/triple-spec + '[(1 ?x (2))]))) + (is (= '[:triple.nform/spo + [[[:triple/list [[:ax/literal 1] + [:ax/var ?x] + [:triple/list [[:ax/literal 2]]]]] + [:triple.nform/po-empty []]]]] + (s/conform ts/triple-spec + '{(1 ?x (2)) {}}))) + (is (= '[:triple.vec/s + [[:triple/list [[:ax/literal 1] + [:triple/bnodes [[[:ax/prefix-iri :p] + [:ax/prefix-iri :q]]]] + [:triple/list [[:ax/literal 2]]]]]]] + (s/conform ts/triple-spec + '[(1 [:p :q] (2))]))) + (is (= '[:triple.vec/spo + [[:triple/bnodes [[[:ax/prefix-iri :p] + [:ax/literal "v"]]]] + [:ax/prefix-iri :q] + [:ax/literal "w"]]] + (s/conform ts/triple-spec + '[[:p "v"] :q "w"]))) + (is (= '[:triple.vec/spo + [[:ax/prefix-iri :x] + [:ax/prefix-iri :q] + [:triple/bnodes [[[:ax/prefix-iri :p] + [:ax/literal "v"]]]]]] + (s/conform ts/triple-spec + '[:x :q [:p "v"]]))) + (is (= '[:triple.nform/spo + [[[:triple/bnodes + [[[:ax/prefix-iri :p] + [:ax/literal "v"]]]] + [:triple.nform/po + [[[:ax/prefix-iri :q] + [:triple.nform/o [[:ax/literal "w"]]]]]]]]] + (s/conform ts/triple-spec + '{[:p "v"] {:q #{"w"}}}))) + (is (= '[:triple.nform/spo + [[[:ax/prefix-iri :x] + [:triple.nform/po + [[[:ax/prefix-iri :q] + [:triple.nform/o + [[:triple/bnodes [[[:ax/prefix-iri :p] + [:ax/literal "v"]]]]]]]]]]]] + (s/conform ts/triple-spec + '{:x {:q #{[:p "v"]}}}))) + (is (= '[:triple.vec/spo + [[:triple/bnodes []] + [:ax/prefix-iri :q] + [:ax/literal "w"]]] + (s/conform ts/triple-spec + '[[] :q "w"]))) + (is (= '[:triple.vec/spo + [[:triple/bnodes + [[[:ax/prefix-iri :p0] + [:triple/bnodes + [[[:ax/prefix-iri :p1] + [:triple/bnodes [[[:ax/prefix-iri :p2] + [:ax/literal "v"]]]]]]]]]] + [:ax/prefix-iri :q] + [:ax/literal "w"]]] + (s/conform ts/triple-spec + '[[:p0 [:p1 [:p2 "v"]]] :q "w"]))) + (is (= '[:triple.vec/spo + [[:triple/bnodes + [[[:ax/prefix-iri :p1] + [:ax/var ?x1]] + [[:ax/prefix-iri :p2] + [:ax/var ?x2]]]] + [:ax/prefix-iri :q] + [:ax/literal "w"]]] + (s/conform ts/triple-spec + '[[:p1 ?x1 :p2 ?x2] :q "w"]))) + (testing "and path + var in subject" + (is (= '[:triple.vec/spo + [[:triple/bnodes + [[[:triple/path + [:path/branch + [[:path/op cat] + [:path/paths + [[:path/terminal [:ax/prefix-iri :p1]] + [:path/terminal [:ax/prefix-iri :p2]]]]]]] + [:ax/var ?x]]]] + [:ax/prefix-iri :q] + [:ax/literal "w"]]] + (s/conform ts/triple-spec + '[[(cat :p1 :p2) ?x] :q "w"]))) + (is (= '[:triple.nform/spo + [[[:triple/bnodes + [[[:triple/path + [:path/branch + [[:path/op cat] + [:path/paths + [[:path/terminal [:ax/prefix-iri :p1]] + [:path/terminal [:ax/prefix-iri :p2]]]]]]] + [:ax/var ?x]]]] + [:triple.nform/po + [[[:ax/prefix-iri :q] + [:triple.nform/o + [[:ax/literal "w"]]]]]]]]] + (s/conform ts/triple-spec + '{[(cat :p1 :p2) ?x] {:q #{"w"}}}))) + (is (not (s/valid? ts/triple-nopath-spec + '[[(cat :p1 :p2) ?x] :q "w"]))) + (is (not (s/valid? ts/triple-novar-spec + '[[(cat :p1 :p2) ?x] :q "w"]))) + (is (not (s/valid? ts/triple-noblank-spec + '[[(cat :p1 :p2) ?x] :q "w"]))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '[[(cat :p1 :p2) ?x] :q "w"]))) + (is (not (s/valid? ts/triple-nopath-spec + '[[(cat :p1 :p2) ?x]]))) + (is (not (s/valid? ts/triple-novar-spec + '[[(cat :p1 :p2) ?x]]))) + (is (not (s/valid? ts/triple-noblank-spec + '[[(cat :p1 :p2) ?x]]))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '[[(cat :p1 :p2) ?x]]))) + (is (not (s/valid? ts/triple-nopath-spec + '{[(cat :p1 :p2) ?x] {:q #{"w"}}}))) + (is (not (s/valid? ts/triple-novar-spec + '{[(cat :p1 :p2) ?x] {:q #{"w"}}}))) + (is (not (s/valid? ts/triple-noblank-spec + '{[(cat :p1 :p2) ?x] {:q #{"w"}}}))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '{[(cat :p1 :p2) ?x] {:q #{"w"}}}))) + (is (not (s/valid? ts/triple-novar-spec + '[(?x ?y) :q "w"]))) + (is (not (s/valid? ts/triple-noblank-spec + '[(?x ?y) :q "w"]))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '[(?x ?y) :q "w"]))) + (is (not (s/valid? ts/triple-novar-spec + '{(?x ?y) {:q #{"w"}}}))) + (is (not (s/valid? ts/triple-noblank-spec + '{(?x ?y) {:q #{"w"}}}))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '{(?x ?y) {:q #{"w"}}})))) + (testing "and path + var in object" + (is (= '[:triple.vec/spo + [[:ax/prefix-iri :x] + [:ax/prefix-iri :q] + [:triple/bnodes + [[[:triple/path + [:path/branch + [[:path/op cat] + [:path/paths + [[:path/terminal [:ax/prefix-iri :r1]] + [:path/terminal [:ax/prefix-iri :r2]]]]]]] + [:ax/literal "v"]]]]]] + (s/conform ts/triple-spec + '[:x :q [(cat :r1 :r2) "v"]]))) + (is (= '[:triple.nform/spo + [[[:ax/prefix-iri :x] + [:triple.nform/po + [[[:ax/prefix-iri :q] + [:triple.nform/o + [[:triple/bnodes + [[[:triple/path + [:path/branch + [[:path/op cat] + [:path/paths + [[:path/terminal [:ax/prefix-iri :r1]] + [:path/terminal [:ax/prefix-iri :r2]]]]]]] + [:ax/literal "v"]]]]]]]]]]]] + (s/conform ts/triple-spec + '{:x {:q #{[(cat :r1 :r2) "v"]}}}))) + (is (not (s/valid? ts/triple-nopath-spec + '[:x :q [(cat :r1 :r2) "v"]]))) + (is (not (s/valid? ts/triple-novar-spec + '[:x :q [:r ?v]]))) + (is (not (s/valid? ts/triple-noblank-spec + '[:x :q [:r ?v]]))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '[:x :q [:r ?v]]))) + (is (not (s/valid? ts/triple-nopath-spec + '{:x {:q #{[(cat :r1 :r2) "v"]}}}))) + (is (not (s/valid? ts/triple-novar-spec + '{:x {:q #{[:r ?v]}}}))) + (is (not (s/valid? ts/triple-noblank-spec + '{:x {:q #{[:r ?v]}}}))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '{:x {:q #{[:r ?v]}}}))) + (is (not (s/valid? ts/triple-novar-spec + '[:x :q (?w ?v)]))) + (is (not (s/valid? ts/triple-noblank-spec + '[:x :q (?w ?v)]))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '[:x :q (?w ?v)]))) + (is (not (s/valid? ts/triple-novar-spec + '{:x {:q #{(?w ?v)}}}))) + (is (not (s/valid? ts/triple-noblank-spec + '{:x {:q #{(?w ?v)}}}))) + (is (not (s/valid? ts/triple-novar-noblank-spec + '{:x {:q #{(?w ?v)}}}))))))) diff --git a/src/test/com/yetanalytics/flint/spec/update_test.cljc b/src/test/com/yetanalytics/flint/spec/update_test.cljc index bb3775d..f60957b 100644 --- a/src/test/com/yetanalytics/flint/spec/update_test.cljc +++ b/src/test/com/yetanalytics/flint/spec/update_test.cljc @@ -5,24 +5,24 @@ (deftest conform-update-test (testing "Conforming updates" - (is (= '[[:insert-data [[:triple/vec [[:ax/prefix-iri :foo/x] - [:ax/prefix-iri :dc/title] - [:ax/literal "Title"]]] - [:triple/vec [[:ax/prefix-iri :foo/y] - [:ax/rdf-type :a] - [:ax/literal "MyType"]]]]]] + (is (= '[[:insert-data [[:triple.vec/spo [[:ax/prefix-iri :foo/x] + [:ax/prefix-iri :dc/title] + [:ax/literal "Title"]]] + [:triple.vec/spo [[:ax/prefix-iri :foo/y] + [:ax/rdf-type :a] + [:ax/literal "MyType"]]]]]] (s/conform us/insert-data-update-spec '{:insert-data [[:foo/x :dc/title "Title"] [:foo/y :a "MyType"]]}))) - (is (= '[[:delete-data [[:triple/quads + (is (= '[[:delete-data [[:triple.quad/gspo [[:ax/iri ""] - [:triple/quad-triples - [[:triple/vec [[:ax/prefix-iri :foo/x] - [:ax/prefix-iri :dc/title] - [:ax/literal "Title"]]] - [:triple/vec [[:ax/prefix-iri :foo/y] - [:ax/rdf-type :a] - [:ax/literal "MyType"]]]]]]]]]] + [:triple.quad/spo + [[:triple.vec/spo [[:ax/prefix-iri :foo/x] + [:ax/prefix-iri :dc/title] + [:ax/literal "Title"]]] + [:triple.vec/spo [[:ax/prefix-iri :foo/y] + [:ax/rdf-type :a] + [:ax/literal "MyType"]]]]]]]]]] (s/conform us/delete-data-update-spec '{:delete-data [[:graph "" diff --git a/src/test/com/yetanalytics/flint/spec/where_test.cljc b/src/test/com/yetanalytics/flint/spec/where_test.cljc index 3e61951..33ed794 100644 --- a/src/test/com/yetanalytics/flint/spec/where_test.cljc +++ b/src/test/com/yetanalytics/flint/spec/where_test.cljc @@ -7,16 +7,18 @@ (testing "Conforming WHERE clauses" (is (= '[:where-sub/select [[:select [:select/var-or-exprs [[:ax/var ?s]]]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?s] - [:ax/var ?p] - [:ax/var ?o]]]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?s] + [:ax/var ?p] + [:ax/var ?o]]]]]]]]] (s/conform ::ws/where '{:where [[?s ?p ?o]] :select [?s]}))) (is (= '[:where-sub/select [[:select [:select/var-or-exprs [[:ax/var ?s]]]] - [:where [:where-sub/where [[:triple/vec [[:ax/var ?s] - [:ax/var ?p] - [:ax/var ?o]]]]]] + [:where [:where-sub/where [[:where/triple + [:triple.vec/spo [[:ax/var ?s] + [:ax/var ?p] + [:ax/var ?o]]]]]]] [:group-by [[:mod/expr-as-var [:expr/as-var [[:expr/branch @@ -31,37 +33,43 @@ [[:where/special [:where/union [[:where-sub/where - [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]] - [:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]] - [:where-sub/where [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]] + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]] + [:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]] + [:where-sub/where + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]]] (s/conform ::ws/where '[[:union [[?s ?p ?o] [?s ?p ?o]] [[?s ?p ?o]]]]))) (is (= '[:where-sub/where [[:where/special [:where/optional - [:where-sub/where [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]] + [:where-sub/where + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]] (s/conform ::ws/where [[:optional '[[?s ?p ?o]]]]))) (is (= '[:where-sub/where [[:where/special [:where/minus - [:where-sub/where [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]] + [:where-sub/where + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]] (s/conform ::ws/where [[:minus '[[?s ?p ?o]]]]))) (is (= '[:where-sub/where [[:where/special [:where/graph [[:ax/prefix-iri :foo/my-graph] - [:where-sub/where [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]] + [:where-sub/where + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]]] (s/conform ::ws/where [[:graph :foo/my-graph '[[?s ?p ?o]]]]))) (is (= '[:where-sub/where [[:where/special [:where/service [[:ax/prefix-iri :foo/my-uri] - [:where-sub/where [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]] + [:where-sub/where + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]]] (s/conform ::ws/where [[:service :foo/my-uri '[[?s ?p ?o]]]]))) (is (= '[:where-sub/where [[:where/special [:where/service-silent [[:ax/prefix-iri :foo/my-uri] - [:where-sub/where [[:triple/vec [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]] + [:where-sub/where + [[:where/triple [:triple.vec/spo [[:ax/var ?s] [:ax/var ?p] [:ax/var ?o]]]]]]]]]]] (s/conform ::ws/where [[:service-silent :foo/my-uri '[[?s ?p ?o]]]]))) (is (= '[:where-sub/where [[:where/special @@ -92,9 +100,9 @@ ;; This is not a special form since it does not conform to the UNION ;; spec (or any other special form spec really) (is (= '[:where-sub/where - [[:triple/vec [[:ax/prefix-iri :union] - [:ax/prefix-iri :foo/bar] - [:ax/prefix-iri :baz/qux]]]]] + [[:where/triple [:triple.vec/spo [[:ax/prefix-iri :union] + [:ax/prefix-iri :foo/bar] + [:ax/prefix-iri :baz/qux]]]]]] (s/conform ::ws/where '[[:union :foo/bar :baz/qux]]))))) (deftest invalid-where-test diff --git a/src/test/com/yetanalytics/flint/validate/bnode_test.cljc b/src/test/com/yetanalytics/flint/validate/bnode_test.cljc index 43fe0d7..a8438bb 100644 --- a/src/test/com/yetanalytics/flint/validate/bnode_test.cljc +++ b/src/test/com/yetanalytics/flint/validate/bnode_test.cljc @@ -49,13 +49,19 @@ [:where [[?y :baz/qux _]]]]} (s/conform qs/query-spec) v/collect-nodes + vb/validate-bnodes))) + (is (= [#{} nil] + (->> '{:select [?p] + :where [[?x ?y] ?p (?z ?w)]} + (s/conform qs/query-spec) + v/collect-nodes vb/validate-bnodes)))) (testing "invalid blank nodes" (is (= [#{'_1} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]}]}] + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.vec/spo]}]}] (->> '{:select [?x] :where [[:where [[?x :foo/bar _1]]] [:where [[?y :baz/qux _1]]]]} @@ -64,13 +70,13 @@ vb/validate-bnodes))) (is (= [#{'_1} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]} {:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]}]}] + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]}]}] (->> '{:select [?x] :where [[:where [{?x {:foo/bar #{_1} :baz/qux #{_1}}}]] @@ -81,17 +87,17 @@ vb/validate-bnodes))) (is (= [#{'_1 '_2} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_2 - :path [:query/select :where :where-sub/where 1 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo]} {:bnode '_2 - :path [:query/select :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]} {:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]}]}] + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.nform/spo :triple.nform/po :triple.nform/o]}]}] (->> '{:select [?x] :where [[:where [{_2 {:foo/bar #{_1} :baz/qux #{_1}}}]] @@ -102,9 +108,9 @@ vb/validate-bnodes))) (is (= [#{'_1} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/optional :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0]}]}] + :path [:query/select :where :where-sub/where :where/special :where/optional :where-sub/where :where/triple :triple.vec/spo]}]}] (->> '{:select [?x] :where [[?x :foo/bar _1] [:optional [[?y :baz/qux _1]]]]} @@ -113,11 +119,11 @@ vb/validate-bnodes))) (is (= [#{'_1} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_1 - :path [:query/select :where :where-sub/where 2 :where/special :where/optional :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0]}]}] + :path [:query/select :where :where-sub/where :where/special :where/optional :where-sub/where :where/triple :triple.vec/spo]}]}] (->> '{:select [?x] :where [[?x :foo/bar _1] [?y :baz/qux _1] @@ -127,11 +133,11 @@ vb/validate-bnodes))) (is (= [#{'_1} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_1 - :path [:query/select :where :where-sub/where 1]} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/optional :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/special :where/optional :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0]}]}] + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]}]}] (->> '{:select [?x] :where [[?x :foo/bar _1] [:optional [[?z :far/lands _1]]] @@ -139,13 +145,25 @@ (s/conform qs/query-spec) v/collect-nodes vb/validate-bnodes))) + (is (= [#{'_1 '_2} {:kind ::vb/dupe-bnodes-bgp + :errors [{:bnode '_2 + :path [:query/select :where :where-sub/where :where/special :where/optional :where-sub/where :where/triple :triple.vec/spo]} + {:bnode '_2 + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]}]}] + (->> '{:select [?x] + :where [[?x :foo/bar _1] + [:optional [[?z :far/lands _2]]] + [?y :baz/qux _2]]} + (s/conform qs/query-spec) + v/collect-nodes + vb/validate-bnodes))) (is (= [#{'_1} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_1 - :path [:query/select :where :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 1 :where/special :where/filter :expr/branch :expr/args :where-sub/where 0]} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo]} {:bnode '_1 - :path [:query/select :where :where-sub/where 0]}]}] + :path [:query/select :where :where-sub/where :where/special :where/filter :expr/branch :expr/args :where-sub/where :where/triple :triple.vec/spo]}]}] (->> '{:select [?x] :where [[?x :foo/bar _1] [:filter (not-exists [[?z :far/lands _1]])] @@ -155,7 +173,7 @@ vb/validate-bnodes))) (is (= [#{'_1} {:kind ::vb/dupe-bnodes-update :errors [{:bnode '_1 - :path [:query/select :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]}] + :path [:query/select :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.vec/spo]}] :prev-bnodes #{'_1}}] (->> '{:select [?x] :where [[:where [[?x :foo/bar _1]]]]} @@ -164,9 +182,9 @@ (vb/validate-bnodes #{'_1})))) (is (= [[#{'_1} {:kind ::vb/dupe-bnodes-bgp :errors [{:bnode '_1 - :path [:update/modify :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]} + :path [:update/modify :insert :triple.vec/spo]} {:bnode '_1 - :path [:update/modify :insert]}]}]] + :path [:update/modify :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.vec/spo]}]}]] (->> '[{:insert [[?x :foo/bar _1]] :where [[:where [[?x :foo/bar _1]]]]}] (map (partial s/conform us/update-spec)) @@ -186,9 +204,9 @@ ::vb/dupe-bnodes-update :errors [{:bnode '_1 - :path [:update/modify :insert]} + :path [:update/modify :insert :triple.vec/spo]} {:bnode '_2 - :path [:update/modify :where :where-sub/where 0 :where/special :where/recurse :where-sub/where 0]}] + :path [:update/modify :where :where-sub/where :where/special :where/recurse :where-sub/where :where/triple :triple.vec/spo]}] :prev-bnodes #{'_1 '_2}}] (->> '[{:insert [[?x :foo/bar _1]] diff --git a/src/test/com/yetanalytics/flint/validate/prefix_test.cljc b/src/test/com/yetanalytics/flint/validate/prefix_test.cljc index a41a4c1..9faa487 100644 --- a/src/test/com/yetanalytics/flint/validate/prefix_test.cljc +++ b/src/test/com/yetanalytics/flint/validate/prefix_test.cljc @@ -23,26 +23,39 @@ v/collect-nodes (vp/validate-prefixes (assoc (:prefixes query) :$ ""))))) - (is (= [{:iri :bar - :prefix :$ + (is (= [{:iri :bar + :prefix :$ :prefixes {:foo ""} - :path [:query/select :where :where-sub/where :triple/vec :ax/prefix-iri]}] + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo :ax/prefix-iri]}] (->> (assoc query :where '[[:bar :a ?y]]) (s/conform qs/query-spec) v/collect-nodes (vp/validate-prefixes (:prefixes query))))) + (is (= [{:iri :bar + :prefix :$ + :prefixes {:foo ""} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo :triple/bnodes :ax/prefix-iri]} + {:iri :bee + :prefix :$ + :prefixes {:foo ""} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo :triple/bnodes :ax/prefix-iri]}] + (->> (assoc query :where '[[[:bar ?x] :a ?y] + [?z :a [:bee ?w]]]) + (s/conform qs/query-spec) + v/collect-nodes + (vp/validate-prefixes (:prefixes query))))) (is (= [{:iri :fee/bar :prefix :fee :prefixes {:foo ""} - :path [:query/select :where :where-sub/where :triple/vec :ax/prefix-iri]} + :path [:query/select :where :where-sub/where :where/triple :triple.vec/spo :ax/prefix-iri]} {:iri :fii/bar :prefix :fii :prefixes {:foo ""} - :path [:query/select :where :where-sub/where :where/special :where/union :where-sub/where :triple/vec :ax/prefix-iri]} + :path [:query/select :where :where-sub/where :where/special :where/union :where-sub/where :where/triple :triple.vec/spo :ax/prefix-iri]} {:iri :fum/bar :prefix :fum :prefixes {:foo ""} - :path [:query/select :where :where-sub/where :where/special :where/union :where-sub/where :triple/vec :ax/prefix-iri]}] + :path [:query/select :where :where-sub/where :where/special :where/union :where-sub/where :where/triple :triple.vec/spo :ax/prefix-iri]}] (->> query (s/conform qs/query-spec) v/collect-nodes @@ -50,16 +63,15 @@ (is (= [{:iri :baz/Qux :prefix :baz :prefixes {:rdf ""} - :path [:update/insert-data :insert-data :triple/quads :triple/quad-triples :triple/nform :triple/spo :triple/po :triple/o :ax/prefix-iri]} + :path [:update/insert-data :insert-data :triple.quad/gspo :triple.quad/spo :triple.nform/spo :triple.nform/po :triple.nform/o :ax/prefix-iri]} {:iri :baz/Quu :prefix :baz :prefixes {:rdf ""} - :path [:update/insert-data :insert-data :triple/quads :triple/quad-triples :triple/vec :ax/prefix-iri]}] - (->> - {:prefixes {:rdf ""} - :insert-data [[:graph "" - [{"" {:rdf/type #{:baz/Qux}}} - ["" :rdf/type :baz/Quu]]]]} - (s/conform us/update-spec) - v/collect-nodes - (vp/validate-prefixes {:rdf ""})))))) + :path [:update/insert-data :insert-data :triple.quad/gspo :triple.quad/spo :triple.vec/spo :ax/prefix-iri]}] + (->> {:prefixes {:rdf ""} + :insert-data [[:graph "" + [{"" {:rdf/type #{:baz/Qux}}} + ["" :rdf/type :baz/Quu]]]]} + (s/conform us/update-spec) + v/collect-nodes + (vp/validate-prefixes {:rdf ""})))))) diff --git a/src/test/com/yetanalytics/flint/validate/scope_test.cljc b/src/test/com/yetanalytics/flint/validate/scope_test.cljc index 56ab2f4..e2b673f 100644 --- a/src/test/com/yetanalytics/flint/validate/scope_test.cljc +++ b/src/test/com/yetanalytics/flint/validate/scope_test.cljc @@ -36,47 +36,63 @@ (testing "in basic graph patterns" (is (= '[?x ?y ?z] (vv/get-scope-vars - '[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]))) + '[:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]))) (is (= '[?s ?p ?o] (vv/get-scope-vars - '[:triple/nform - [:triple/spo - [[[:ax/var ?s] - [:triple/po [[[:ax/var ?p] - [:triple/o [[:ax/var ?o]]]]]]]]]]))) + '[:triple.nform/spo + [[[:ax/var ?s] + [:triple.nform/po + [[[:ax/var ?p] + [:triple.nform/o [[:ax/var ?o]]]]]]]]]))) (is (= '[?x ?y ?z] (vv/get-scope-vars '[:where-sub/where - [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]))) + [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]))) (is (= '[?x ?y ?z] (vv/get-scope-vars '[:where-sub/where - [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]))) + [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]))) (is (= '[?x ?ya ?yb ?z] (vv/get-scope-vars - '[:triple/vec + '[:triple.vec/spo [[:ax/var ?x] [:triple/path [:path/branch [[:path/op cat] [:path/paths [[:path/terminal [:ax/var ?ya]] [:path/terminal [:ax/var ?yb]]]]]]] - [:ax/var ?z]]])))) + [:ax/var ?z]]]))) + (is (= '[?x1 ?x2 ?y ?z1 ?z2] + (vv/get-scope-vars + '[:triple.vec/spo [[:triple/list [[:ax/var ?x1] [:ax/var ?x2]]] + [:ax/var ?y] + [:triple/bnodes [[[:ax/var ?z1] [:ax/var ?z2]]]]]]))) + (is (= '[?x1 ?x2 ?y ?z1 ?z2] + (vv/get-scope-vars + '[:triple.nform/spo + [[[:triple/list [[:ax/var ?x1] [:ax/var ?x2]]] + [:triple.nform/po + [[[:ax/var ?y] + [:triple.nform/o + [[:triple/bnodes [[[:ax/var ?z1] [:ax/var ?z2]]]]]]]]]]]])))) (testing "in sub-SELECT queries" (is (= '[?x ?y ?z] (vv/get-scope-vars '[:where-sub/select [[:select [:ax/wildcard '*]] [:where [:where-sub/where - [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]]]]))) + [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]]]]))) ;; This is an illegal sub-SELECT since it has both a wildcard and ;; GROUP BY, but we want to cover all of our bases. (is (= '[?x ?y ?z ?w] @@ -84,9 +100,10 @@ '[:where-sub/select [[:select [:ax/wildcard '*]] [:where [:where-sub/where - [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]] + [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]] [:group-by [[:ax/var ?w]]]]]))) (is (= '[?a ?b ?c] (vv/get-scope-vars @@ -98,9 +115,10 @@ [:expr/as-var [[:expr/terminal [:ax/num-lit 2]] [:ax/var ?c]]]]]]] [:where [:where-sub/where - [[:triple/vec [[:ax/var ?x] - [:ax/var ?y] - [:ax/var ?z]]]]]] + [[:where/triple + [:triple.vec/spo [[:ax/var ?x] + [:ax/var ?y] + [:ax/var ?z]]]]]]] [:group-by [[:ax/var ?a] [:ax/var ?b] [:ax/var ?c]]]]])))) @@ -117,33 +135,41 @@ '[:where-sub/where [[:where/recurse [:where-sub/where - [[:triple/vec [[:ax/var ?s1] [:ax/var ?p1] [:ax/var ?o1]]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s1] [:ax/var ?p1] [:ax/var ?o1]]]]]]] [:where/union [[:where-sub/where - [[:triple/vec - [[:ax/var ?s2] [:ax/var ?p2] [:ax/var ?o2]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s2] [:ax/var ?p2] [:ax/var ?o2]]]]]] [:where-sub/where - [[:triple/vec - [[:ax/var ?s3] [:ax/var ?p3] [:ax/var ?o3]]]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s3] [:ax/var ?p3] [:ax/var ?o3]]]]]]]] [:where/optional [:where-sub/where - [[:triple/vec - [[:ax/var ?s4] [:ax/var ?p4] [:ax/var ?o4]]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s4] [:ax/var ?p4] [:ax/var ?o4]]]]]]] [:where/graph [[:ax/var ?graphTerm] [:where-sub/where - [[:triple/vec - [[:ax/var ?s5] [:ax/var ?p5] [:ax/var ?o5]]]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s5] [:ax/var ?p5] [:ax/var ?o5]]]]]]]] [:where/service [[:ax/var ?serviceTerm] [:where-sub/where - [[:triple/vec - [[:ax/var ?s6] [:ax/var ?p6] [:ax/var ?o6]]]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s6] [:ax/var ?p6] [:ax/var ?o6]]]]]]]] [:where/service-silent [[:ax/var ?serviceSilentTerm] [:where-sub/where - [[:triple/vec - [[:ax/var ?s7] [:ax/var ?p7] [:ax/var ?o7]]]]]]] + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s7] [:ax/var ?p7] [:ax/var ?o7]]]]]]]] [:where/bind [:expr/as-var [[:expr/branch @@ -163,8 +189,9 @@ [:expr/terminal [:ax/var ?bar]])]]]] [:where/minus [:where-sub/where - [[:triple/vec - [[:ax/var ?s5] [:ax/var ?p5] [:ax/var ?o5]]]]]]]]))))) + [[:where/triple + [:triple.vec/spo + [[:ax/var ?s5] [:ax/var ?p5] [:ax/var ?o5]]]]]]]]]))))) (deftest scope-validation-test