diff --git a/doc/axiom.md b/doc/axiom.md index c2cf20c..b115efd 100644 --- a/doc/axiom.md +++ b/doc/axiom.md @@ -57,9 +57,9 @@ the `:where` triples are all equivalent. ## Variables -Examples: `?var` +Examples: `?var`, `$var` -Variables are written as symbols prefixed with a question mark `?`. Translating to SPARQL does not change the variable other than stringifying it. +Variables are written as symbols prefixed with a question mark `?` or dollar sign `$`. The sigil is not part of the variable name, so `?var` and `$var` identify the same variable. Translating to SPARQL preserves the sigil and otherwise only stringifies the symbol. ## Blank Nodes diff --git a/src/main/com/yetanalytics/flint/axiom/impl/format.cljc b/src/main/com/yetanalytics/flint/axiom/impl/format.cljc index 4c9b847..64930b2 100644 --- a/src/main/com/yetanalytics/flint/axiom/impl/format.cljc +++ b/src/main/com/yetanalytics/flint/axiom/impl/format.cljc @@ -23,7 +23,7 @@ (str kns ":" kname))) (defn format-var-symbol - "Return the var `v-sym` as a string of the form `?var`." + "Return the var `v-sym` as a string of the form `?var` or `$var`." [v-sym] (str v-sym)) diff --git a/src/main/com/yetanalytics/flint/axiom/impl/validation.cljc b/src/main/com/yetanalytics/flint/axiom/impl/validation.cljc index bdfb195..5bccee0 100644 --- a/src/main/com/yetanalytics/flint/axiom/impl/validation.cljc +++ b/src/main/com/yetanalytics/flint/axiom/impl/validation.cljc @@ -11,7 +11,7 @@ [c] #?(:clj (int c) :cljs (.charCodeAt c))) -#?(:clj (def ^:private qmark-range [(char->int \?)])) +(def ^:private var-sigil-range (mapv char->int [\? \$])) #?(:clj (def ^:private uscore-range [(char->int \_)])) #?(:clj (def ^:private hyphen-range [(char->int \-)])) #?(:clj (def ^:private bslash-range [(char->int \\)])) @@ -188,9 +188,11 @@ (re-pattern (format "<%s*>" iri-banned)))) (def var-regex - (let [var-start (ranges->regex-charset var-start-range) + (let [var-sigil (ranges->regex-charset var-sigil-range) + var-start (ranges->regex-charset var-start-range) var-body (ranges->regex-charset var-body-range)] - (re-pattern (format "\\?%s%s*" + (re-pattern (format "%s%s%s*" + var-sigil var-start var-body)))) @@ -259,8 +261,8 @@ `(.set ~r#))) cp-ranges#))))) -#?(:clj (def ^{:private true :tag BitSet} qmark-bitset - (unicode-bitset qmark-range))) +#?(:clj (def ^{:private true :tag BitSet} var-sigil-bitset + (unicode-bitset var-sigil-range))) #?(:clj (def ^{:private true :tag BitSet} uscore-bitset (unicode-bitset uscore-range))) #?(:clj (def ^{:private true :tag BitSet} hyphen-bitset @@ -376,9 +378,9 @@ (loop [idx 0] (cond (>= idx ccnt) - (<= 2 ccnt) ; Need to have initial qmark + at least one start char + (<= 2 ccnt) ; Need to have a sigil + at least one start char (= idx 0) - (recur-if (in-bitset? qmark-bitset vs idx) + (recur-if (in-bitset? var-sigil-bitset vs idx) (inc idx)) (= idx 1) (recur-if (in-bitset? var-start-bitset vs idx) @@ -578,7 +580,7 @@ :cljs (boolean (re-matches bnode-regex bnode-str)))) (defn valid-var-symbol? - "Is `var-sym` a symbol that starts with `?`?" + "Is `var-sym` a symbol that starts with `?` or `$`?" [var-sym] (valid-var-str? (str var-sym))) diff --git a/src/main/com/yetanalytics/flint/axiom/protocol.cljc b/src/main/com/yetanalytics/flint/axiom/protocol.cljc index 0fe11f6..01e38e4 100644 --- a/src/main/com/yetanalytics/flint/axiom/protocol.cljc +++ b/src/main/com/yetanalytics/flint/axiom/protocol.cljc @@ -29,12 +29,17 @@ "Convert the prefixed IRI `this` into its string representation.")) (defprotocol Variable - "A SPARQL variable (e.g. `?var`)." + "A SPARQL variable (e.g. `?var` or `$var`)." (-valid-variable? [this] "Return `true` if `this` is a valid variable of its type.") (-format-variable [this] "Convert the variable `this` into its string representation.")) +(defn variable-name + "Return the name of `variable` without its leading `?` or `$` sigil." + [variable] + (subs (-format-variable variable) 1)) + (defprotocol BlankNode "A SPARQL blank node (e.g. `_:b0`)." (-valid-bnode? [this] diff --git a/src/main/com/yetanalytics/flint/error.cljc b/src/main/com/yetanalytics/flint/error.cljc index 036f8b7..6a018c1 100644 --- a/src/main/com/yetanalytics/flint/error.cljc +++ b/src/main/com/yetanalytics/flint/error.cljc @@ -6,6 +6,7 @@ [com.yetanalytics.flint.validate.aggregate :as va] [com.yetanalytics.flint.validate.bnode :as vb] [com.yetanalytics.flint.validate.scope :as vs] + [com.yetanalytics.flint.validate.variable :as vv] #?@(:clj [[clojure.core :refer [format]]] :cljs [[goog.string :as gstring] [goog.string.format]]))) @@ -157,8 +158,8 @@ (let [[nots ins] (split-with #(= ::vs/var-not-in-scope (:kind %)) scope-errs) var-coll (if (not-empty nots) - (->> nots (mapcat :variables) distinct sort) - (->> ins (map :variable) distinct sort)) + (->> nots (mapcat :variables) vv/distinct-vars sort) + (->> ins (map :variable) vv/distinct-vars sort)) var-count (->> var-coll count) var-strs (->> var-coll (map str)) var-str (join-str-coll var-strs)] @@ -195,7 +196,7 @@ (plural-s wild-count) index-str (plural-has wild-count))) - (let [var-coll (->> errs (mapcat :variables) distinct sort) + (let [var-coll (->> errs (mapcat :variables) vv/distinct-vars sort) var-count (->> var-coll count) var-strs (->> var-coll (map str)) var-str (join-str-coll var-strs)] diff --git a/src/main/com/yetanalytics/flint/spec/select.cljc b/src/main/com/yetanalytics/flint/spec/select.cljc index 1671da3..0576e8e 100644 --- a/src/main/com/yetanalytics/flint/spec/select.cljc +++ b/src/main/com/yetanalytics/flint/spec/select.cljc @@ -1,21 +1,24 @@ (ns com.yetanalytics.flint.spec.select (:require [clojure.spec.alpha :as s] - [com.yetanalytics.flint.spec.axiom :as ax] - [com.yetanalytics.flint.spec.expr :as es])) + [com.yetanalytics.flint.axiom.protocol :as p] + [com.yetanalytics.flint.spec.axiom :as ax] + [com.yetanalytics.flint.spec.expr :as es])) (defn- no-duplicate-vars? [var-or-exprs] (boolean (reduce (fn [seen [k x]] (case k :ax/var - (if (contains? seen x) - (reduced false) - (conj seen x)) + (let [vname (p/variable-name x)] + (if (contains? seen vname) + (reduced false) + (conj seen vname))) :select/expr-as-var - (let [v (-> x second second second)] - (if (contains? seen v) + (let [v (-> x second second second) + vname (p/variable-name v)] + (if (contains? seen vname) (reduced false) - (conj seen v))))) + (conj seen vname))))) #{} var-or-exprs))) diff --git a/src/main/com/yetanalytics/flint/validate/aggregate.cljc b/src/main/com/yetanalytics/flint/validate/aggregate.cljc index 5be545f..551fbea 100644 --- a/src/main/com/yetanalytics/flint/validate/aggregate.cljc +++ b/src/main/com/yetanalytics/flint/validate/aggregate.cljc @@ -1,7 +1,8 @@ (ns com.yetanalytics.flint.validate.aggregate - (:require [com.yetanalytics.flint.validate.variable :as vv] - [com.yetanalytics.flint.util :as u] - [com.yetanalytics.flint.validate.util :as vu])) + (:require [com.yetanalytics.flint.axiom.protocol :as p] + [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. @@ -25,7 +26,7 @@ (reduce (fn [[valid-vars bad-vars] [k x]] (case k :ax/var - (if-not (valid-vars x) + (if-not (valid-vars (p/variable-name x)) [valid-vars (conj bad-vars x)] [valid-vars bad-vars]) :select/expr-as-var @@ -38,10 +39,10 @@ [valid-vars (concat bad-vars bad-expr-vars)] ;; Somehow already-projected vars are now valid, ;; at least according to Apache Jena's query parser - [(conj valid-vars v) bad-vars])))) + [(conj valid-vars (p/variable-name v)) bad-vars])))) [group-by-vars []] sel-clause)] - (not-empty bad-vars))) + (some-> bad-vars vv/distinct-vars not-empty))) (defn- validate-agg-select [[[_select-k select] loc]] @@ -51,6 +52,7 @@ (->> ?group-by (map vv/group-by-projected-vars) (filter some?) + (map p/variable-name) set) #{}) [sel-k sel-v] select-cls] diff --git a/src/main/com/yetanalytics/flint/validate/scope.cljc b/src/main/com/yetanalytics/flint/validate/scope.cljc index f786ad6..4699ac8 100644 --- a/src/main/com/yetanalytics/flint/validate/scope.cljc +++ b/src/main/com/yetanalytics/flint/validate/scope.cljc @@ -1,5 +1,6 @@ (ns com.yetanalytics.flint.validate.scope (:require [clojure.zip :as zip] + [com.yetanalytics.flint.axiom.protocol :as p] [com.yetanalytics.flint.validate.variable :as vv] [com.yetanalytics.flint.validate.util :as vu] [com.yetanalytics.flint.util :as u])) @@ -29,8 +30,9 @@ prev-elems (-> loc zip/up ; :where/special zip/lefts) - scope (set (mapcat vv/get-scope-vars prev-elems))] - (when (contains? scope bind-var) + scope (set (mapcat vv/get-scope-vars prev-elems)) + scope-names (set (map p/variable-name scope))] + (when (contains? scope-names (p/variable-name bind-var)) (in-scope-err-map bind-var scope loc :where/bind)))) (defn- validate-select @@ -49,10 +51,15 @@ where-vars (-> where second vv/get-scope-vars) group-vars (some-> ?group-by vv/group-by-projected-vars) prev-vars (mapcat vv/get-scope-vars prev-elems) - scope (set (concat where-vars group-vars prev-vars))] - (if-some [bad-expr-vars (not-empty (filter #(not (scope %)) expr-vars))] + scope (set (concat where-vars group-vars prev-vars)) + scope-names (set (map p/variable-name scope))] + (if-some [bad-expr-vars (->> expr-vars + (remove #(contains? scope-names + (p/variable-name %))) + vv/distinct-vars + not-empty)] (not-in-scope-err-map bad-expr-vars scope loc :select/expr-as-var) - (when (contains? scope bind-var) + (when (contains? scope-names (p/variable-name bind-var)) (in-scope-err-map bind-var scope loc :select/expr-as-var))))) (defn- validate-node-locs diff --git a/src/main/com/yetanalytics/flint/validate/variable.cljc b/src/main/com/yetanalytics/flint/validate/variable.cljc index f906592..57302ee 100644 --- a/src/main/com/yetanalytics/flint/validate/variable.cljc +++ b/src/main/com/yetanalytics/flint/validate/variable.cljc @@ -1,6 +1,20 @@ (ns com.yetanalytics.flint.validate.variable - (:require [com.yetanalytics.flint.util :as u] - [com.yetanalytics.flint.spec.expr :as es])) + (:require [com.yetanalytics.flint.axiom.protocol :as p] + [com.yetanalytics.flint.util :as u] + [com.yetanalytics.flint.spec.expr :as es])) + +(defn distinct-vars + "Return the distinct variables in `vars`, comparing their SPARQL names while + retaining the first encountered representation of each variable." + [vars] + (second + (reduce (fn [[seen ret] v] + (let [vname (p/variable-name v)] + (if (contains? seen vname) + [seen ret] + [(conj seen vname) (conj ret v)]))) + [#{} []] + vars))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Expression variables @@ -45,7 +59,7 @@ (invalid-agg-expr-vars valid-vars x)) (defmethod invalid-agg-expr-vars :ax/var [valid-vars [_ v]] - (if-not (valid-vars v) [v] [])) + (if-not (valid-vars (p/variable-name v)) [v] [])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; GROUP BY projection variables diff --git a/src/test/com/yetanalytics/flint/axiom_test.cljc b/src/test/com/yetanalytics/flint/axiom_test.cljc index 5d2a4f9..0c9f8a0 100644 --- a/src/test/com/yetanalytics/flint/axiom_test.cljc +++ b/src/test/com/yetanalytics/flint/axiom_test.cljc @@ -34,7 +34,16 @@ (is (= "foo:bar" (p/-format-prefix-iri :foo/bar)))) (testing "Variables" (is (p/-valid-variable? '?foo)) - (is (= "?foo" (p/-format-variable '?foo)))) + (is (p/-valid-variable? '$foo)) + (is (= "?foo" (p/-format-variable '?foo))) + (is (= "$foo" (p/-format-variable '$foo))) + (is (= "foo" + (p/variable-name '?foo) + (p/variable-name '$foo))) + (let [custom-var (reify p/Variable + (-valid-variable? [_] true) + (-format-variable [_] "$custom"))] + (is (= "custom" (p/variable-name custom-var))))) (testing "Blank Nodes" (is (p/-valid-bnode? '_bar)) (is (= "_:bar" (p/-format-bnode '_bar)))) @@ -303,7 +312,7 @@ {:prefixes {:xsd (java.net.URI. "http://www.w3.org/2001/XMLSchema#") :foo (java.net.URI. "http://foo.org/")} :select ['?x] - :where [['?x :foo/time (java.time.Instant/EPOCH)]]}) + :where [['?x :foo/time java.time.Instant/EPOCH]]}) (flint/format-query {:prefixes {:xsd (java.net.URI. "http://www.w3.org/2001/XMLSchema#") :foo (java.net.URI. "http://foo.org/")} diff --git a/src/test/com/yetanalytics/flint/error_test.cljc b/src/test/com/yetanalytics/flint/error_test.cljc index c6e55c5..6b174f9 100644 --- a/src/test/com/yetanalytics/flint/error_test.cljc +++ b/src/test/com/yetanalytics/flint/error_test.cljc @@ -181,6 +181,10 @@ v/collect-nodes vs/validate-scoped-vars err/scope-error-msg))) + (is (= "1 variable in 2 `expr AS var` clauses was already defined in scope: ?x!'" + (err/scope-error-msg + [{:kind ::vs/var-in-scope :variable '?x} + {:kind ::vs/var-in-scope :variable '$x}]))) (is (= "1 variable at index 0 in 1 `expr AS var` clause was already defined in scope: ?x!'" (->> '[{:delete [[?x ?y ?z]] :where [[?x ?y ?z] @@ -227,6 +231,9 @@ v/collect-nodes va/validate-agg-selects err/aggregate-error-msg))) + (is (= "1 variable was illegally used in SELECTs with aggregates: ?y!" + (err/aggregate-error-msg + [{:kind ::va/invalid-aggregate-var :variables ['?y '$y]}]))) (is (= "2 variables at index 0 were illegally used in SELECTs with aggregates: ?y and ?z!" (->> '[{:delete [[?x ?y ?z]] :where {:select [?x ?y ?z] diff --git a/src/test/com/yetanalytics/flint/format/axiom_test.cljc b/src/test/com/yetanalytics/flint/format/axiom_test.cljc index 64b49fb..449b457 100644 --- a/src/test/com/yetanalytics/flint/format/axiom_test.cljc +++ b/src/test/com/yetanalytics/flint/format/axiom_test.cljc @@ -21,6 +21,8 @@ (f/format-ast-node {} [:ax/prefix-iri :bar]))) (is (= "?xyz" (f/format-ast-node {} [:ax/var '?xyz]))) + (is (= "$xyz" + (f/format-ast-node {} [:ax/var '$xyz]))) (is (= "_:b0" (f/format-ast-node {} [:ax/bnode '_b0]))) (is (= "[]" diff --git a/src/test/com/yetanalytics/flint/spec/axiom_test.cljc b/src/test/com/yetanalytics/flint/spec/axiom_test.cljc index 0534fdf..bceeac7 100644 --- a/src/test/com/yetanalytics/flint/spec/axiom_test.cljc +++ b/src/test/com/yetanalytics/flint/spec/axiom_test.cljc @@ -36,10 +36,11 @@ (deftest string-validation-gentest (testing "variables" - (let [var-prop (make-prop (fn [s] (symbol (str "?" s))) - v/var-regex - v/valid-var-symbol?)] - (is (:pass? (tc/quick-check 100 var-prop))))) + (doseq [sigil ["?" "$"]] + (let [var-prop (make-prop (fn [s] (symbol (str sigil s))) + v/var-regex + v/valid-var-symbol?)] + (is (:pass? (tc/quick-check 100 var-prop)))))) (testing "blank nodes" (let [bnode-prop (make-prop (fn [s] (symbol (str "_" s))) v/bnode-regex @@ -112,20 +113,22 @@ (deftest string-validation-unit-test (testing "variable strings" - (multilingual-test - (fn [x] (v/valid-var-symbol? (symbol (str "?" x))))) - (are [x] (not (v/valid-var-symbol? (symbol (str "?" x)))) - "???" - "foo bar" - "foo.bar" - ".foobar" - "foo'bar" - "foo#bar" - (str "foo" (char 0x037E) "bar") - (str \u0308) - "·t" - biang-biang-noodles) + (doseq [sigil ["?" "$"]] + (multilingual-test + (fn [x] (v/valid-var-symbol? (symbol (str sigil x))))) + (are [x] (not (v/valid-var-symbol? (symbol (str sigil x)))) + "???" + "foo bar" + "foo.bar" + ".foobar" + "foo'bar" + "foo#bar" + (str "foo" (char 0x037E) "bar") + (str \u0308) + "·t" + biang-biang-noodles)) (is (v/valid-var-symbol? '?1234567890)) + (is (v/valid-var-symbol? '$1234567890)) (is (not (v/valid-var-symbol? 'foo))) (is (not (v/valid-var-symbol? '?foo)))) (testing "blank node strings" @@ -239,9 +242,12 @@ (is (not (s/valid? ax/prefix-iri-spec :*))))) (testing "variables" (is (s/valid? ax/variable-spec '?foo)) + (is (s/valid? ax/variable-spec '$foo)) (is (not (s/valid? ax/variable-spec "?foo"))) + (is (not (s/valid? ax/variable-spec "$foo"))) (is (not (s/valid? ax/variable-spec 'foo))) - (is (not (s/valid? ax/variable-spec `?foo)))) + (is (not (s/valid? ax/variable-spec `?foo))) + (is (not (s/valid? ax/variable-spec `$foo)))) (testing "blank nodes" (is (s/valid? ax/bnode-spec '_)) (is (s/valid? ax/bnode-spec '_foo)) diff --git a/src/test/com/yetanalytics/flint/spec/select_test.cljc b/src/test/com/yetanalytics/flint/spec/select_test.cljc index ac97fff..4b7c5a2 100644 --- a/src/test/com/yetanalytics/flint/spec/select_test.cljc +++ b/src/test/com/yetanalytics/flint/spec/select_test.cljc @@ -18,6 +18,10 @@ (deftest invalid-select-test (testing "Invalid SELECT clauses" (is (not (s/valid? ss/select-spec '[?x ?x]))) + (is (not (s/valid? ss/select-spec '[?x $x]))) (is (not (s/valid? ss/select-spec '[?x ?y [2 ?y]]))) + (is (not (s/valid? ss/select-spec '[?x ?y [2 $y]]))) (is (not (s/valid? ss/select-spec '[?x [2 ?y] ?y]))) - (is (not (s/valid? ss/select-spec '[[2 ?y] [3 ?y]]))))) + (is (not (s/valid? ss/select-spec '[?x [2 ?y] $y]))) + (is (not (s/valid? ss/select-spec '[[2 ?y] [3 ?y]]))) + (is (not (s/valid? ss/select-spec '[[2 ?y] [3 $y]]))))) diff --git a/src/test/com/yetanalytics/flint/validate/aggregate_test.cljc b/src/test/com/yetanalytics/flint/validate/aggregate_test.cljc index 8477d4a..630978c 100644 --- a/src/test/com/yetanalytics/flint/validate/aggregate_test.cljc +++ b/src/test/com/yetanalytics/flint/validate/aggregate_test.cljc @@ -8,6 +8,9 @@ (deftest helper-test (testing "helper multimethods" + (testing "to compare variables by SPARQL name" + (is (= '[?x $y] + (vv/distinct-vars '[?x $x $y ?y])))) (testing "to find GROUP BY projected vars" (is (= [] (vv/group-by-projected-vars @@ -30,8 +33,12 @@ '[:expr/terminal [:ax/var ?x]]))) (is (= [] (vv/invalid-agg-expr-vars - #{'?x} + #{"x"} '[:expr/terminal [:ax/var ?x]]))) + (is (= [] + (vv/invalid-agg-expr-vars + #{"x"} + '[:expr/terminal [:ax/var $x]]))) (is (= ['?x] (vv/invalid-agg-expr-vars #{} @@ -44,7 +51,7 @@ [:expr/args [[:expr/terminal [:ax/var ?x]]]]]]))) (is (= [] (vv/invalid-agg-expr-vars - #{'?x} + #{"x"} '[:expr/branch [[:expr/op str] [:expr/args [[:expr/terminal [:ax/var ?x]]]]]])))))) @@ -81,6 +88,20 @@ (s/conform qs/query-spec) v/collect-nodes va/validate-agg-selects))) + (is (nil? + (->> '{:select [$x] + :where [[?x ?y ?z]] + :group-by [?x]} + (s/conform qs/query-spec) + v/collect-nodes + va/validate-agg-selects))) + (is (nil? + (->> '{:select [?x] + :where [[?x ?y ?z]] + :group-by [$x]} + (s/conform qs/query-spec) + v/collect-nodes + va/validate-agg-selects))) (is (nil? (->> '{:select [[(sum ?x) ?x2] [(str ?x2) ?x3]] :where [[?x ?y ?z]] @@ -149,6 +170,16 @@ v/collect-nodes va/validate-agg-selects (map #(dissoc % :path))))) + (is (= [{:kind ::va/invalid-aggregate-var + :variables ['?z]}] + (->> '{:select [[(str ?z) ?str] + [(str $z) $str2]] + :where [[?x ?y ?z]] + :group-by [?x]} + (s/conform qs/query-spec) + v/collect-nodes + va/validate-agg-selects + (map #(dissoc % :path))))) (is (= [{:kind ::va/invalid-aggregate-var :variables ['?z]}] (->> '{:select [[("" ?x) ?sum] [(str ?z) ?str]] diff --git a/src/test/com/yetanalytics/flint/validate/scope_test.cljc b/src/test/com/yetanalytics/flint/validate/scope_test.cljc index e2b673f..d1b156b 100644 --- a/src/test/com/yetanalytics/flint/validate/scope_test.cljc +++ b/src/test/com/yetanalytics/flint/validate/scope_test.cljc @@ -223,6 +223,20 @@ (s/conform qs/query-spec) v/collect-nodes vs/validate-scoped-vars))) + (is (nil? (->> '{:select [[(+ $x 1) ?sum]] + :where [[?x ?y ?z]]} + (s/conform qs/query-spec) + v/collect-nodes + vs/validate-scoped-vars))) + (is (= [{:kind ::vs/var-not-in-scope + :variables ['?u] + :scope-vars #{'?x '?y '?z} + :path [:query/select :select :select/var-or-exprs :select/expr-as-var]}] + (->> '{:select [[(+ ?u $u) ?sum]] + :where [[?x ?y ?z]]} + (s/conform qs/query-spec) + v/collect-nodes + vs/validate-scoped-vars))) (is (= [{:kind ::vs/var-not-in-scope :variables ['?u '?v] :scope-vars #{'?x '?y '?z} @@ -279,6 +293,25 @@ (s/conform qs/query-spec) v/collect-nodes vs/validate-scoped-vars))) + (is (= [{:kind ::vs/var-in-scope + :variable '$x + :scope-vars #{'?x '?y '?z} + :path [:query/select :select :select/var-or-exprs :select/expr-as-var]}] + (->> '{:select [[2 $x]] + :where [[?x ?y ?z]]} + (s/conform qs/query-spec) + v/collect-nodes + vs/validate-scoped-vars))) + (is (= [{:kind ::vs/var-in-scope + :variable '$y + :scope-vars #{'?x '?y '?z} + :path [:query/select :where :where-sub/where :where/special :where/bind]}] + (->> '{:select [?x] + :where [[?x ?y ?z] + [:bind [3 $y]]]} + (s/conform qs/query-spec) + v/collect-nodes + vs/validate-scoped-vars))) (is (= #{{:kind ::vs/var-in-scope :variable '?x :scope-vars #{'?x '?y '?z '?w} diff --git a/src/test/com/yetanalytics/flint_test.cljc b/src/test/com/yetanalytics/flint_test.cljc index 44b9ccd..3f64f8b 100644 --- a/src/test/com/yetanalytics/flint_test.cljc +++ b/src/test/com/yetanalytics/flint_test.cljc @@ -74,6 +74,18 @@ (make-format-pretty-tests (fn [ups] (format-updates ups :pretty? true)) "dev-resources/test-fixtures/inputs/update-seq/")) +(deftest dollar-variable-integration-test + (testing "Dollar-prefixed variables through the public API" + (is (= "SELECT ($x AS ?same) WHERE { ?x a $o . } GROUP BY ?x VALUES $o { 1 }" + (format-query '{:select [[$x ?same]] + :where [[?x :a $o]] + :group-by [?x] + :values {$o [1]}}))) + (is (= "DELETE { $x a ?o . } INSERT { ?x a $new . } WHERE { ?x a $o . }" + (format-update '{:delete [[$x :a ?o]] + :insert [[?x :a $new]] + :where [[?x :a $o]]}))))) + (deftest exception-tests (testing "API functions throwing exceptions" (is (= ::flint/invalid-query