diff --git a/deps.edn b/deps.edn index d085200f..0812f2b8 100644 --- a/deps.edn +++ b/deps.edn @@ -120,7 +120,9 @@ meta-merge/meta-merge {:mvn/version "1.0.0"} com.exoscale/lingo {:mvn/version "1.0.0-alpha14"} io.github.swirrl/dogstatsd {:mvn/version "0.1.39"} - org.clojure/algo.monads {:mvn/version "0.1.6"}} + org.clojure/algo.monads {:mvn/version "0.1.6"} + io.lambdaforge/datalog-parser {:mvn/version "0.1.9"} + clj-stacktrace/clj-stacktrace {:mvn/version "0.2.8"}} :classpath-overrides {org.clojure/clojure nil org.clojure/spec.alpha nil}} :clj-nvd diff --git a/doc/libraries.csv b/doc/libraries.csv index e84bf70f..eb9a9417 100644 --- a/doc/libraries.csv +++ b/doc/libraries.csv @@ -10,6 +10,7 @@ camel-snake-kebab/camel-snake-kebab,https://github.com/clj-commons/camel-snake-k circleci/bond,https://github.com/circleci/bond clj-commons/clj-yaml,https://github.com/clj-commons/clj-yaml clj-commons/multigrep,https://github.com/clj-commons/multigrep +clj-stacktrace/clj-stacktrace,https://github.com/mmcgrana/clj-stacktrace clojure-csv/clojure-csv,https://github.com/davidsantiago/clojure-csv clojure-term-colors/clojure-term-colors,https://github.com/trhura/clojure-term-colors com.exoscale/lingo,https://github.com/exoscale/lingo @@ -43,6 +44,7 @@ io.github.cognitect-labs/test-runner,https://github.com/cognitect-labs/test-runn io.github.swirrl/dogstatsd,https://github.com/swirrl/dogstatsd io.github.technomancy/limit-break,https://github.com/technomancy/limit-break io.helins/binf,https://github.com/helins/binf.cljc +io.lambdaforge/datalog-parser,https://github.com/lambdaforge/datalog-parser io.replikativ/hasch,https://github.com/replikativ/hasch java-http-clj/java-http-clj,http://www.github.com/schmee/java-http-clj lambdaisland/regal,https://github.com/lambdaisland/regal diff --git a/script/add-libtest.clj b/script/add-libtest.clj index 98215340..9eeea5f2 100755 --- a/script/add-libtest.clj +++ b/script/add-libtest.clj @@ -78,7 +78,8 @@ (let [lib-dir (if branch (gl/procure git-url lib-name branch) (or (gl/procure git-url lib-name "master") - (gl/procure git-url lib-name "main"))) + (gl/procure git-url lib-name "main") + (throw (ex-info "Unable to clone git-url" {})))) _ (println "Git clone is at" lib-dir) lib-root-dir (if directory (fs/file lib-dir directory) lib-dir) test-dirs (if test-directories diff --git a/test-resources/lib_tests/bb-tested-libs.edn b/test-resources/lib_tests/bb-tested-libs.edn index bc8984d2..a6310591 100644 --- a/test-resources/lib_tests/bb-tested-libs.edn +++ b/test-resources/lib_tests/bb-tested-libs.edn @@ -103,4 +103,6 @@ meta-merge/meta-merge {:git-url "https://github.com/weavejester/meta-merge", :test-namespaces (meta-merge.core-test), :git-sha "c968c38baccd4219fe0ba592d89af37ea8e426bf"} com.exoscale/lingo {:git-url "https://github.com/exoscale/lingo", :test-namespaces (exoscale.lingo.test.core-test), :git-sha "30b5084fab28d24c99ec683e21535366910d9f2f" :skip-windows true} io.github.swirrl/dogstatsd {:git-url "https://github.com/swirrl/dogstatsd", :test-namespaces (swirrl.dogstatsd-test), :git-sha "e110caae452cd1185e65e389a359b69502076d61"} - org.clojure/algo.monads {:git-url "https://github.com/clojure/algo.monads", :test-namespaces (clojure.algo.test-monads), :git-sha "3a985b0b099110b1654d568fecf597bc9c8d1ff5"}} + org.clojure/algo.monads {:git-url "https://github.com/clojure/algo.monads", :test-namespaces (clojure.algo.test-monads), :git-sha "3a985b0b099110b1654d568fecf597bc9c8d1ff5"} + io.lambdaforge/datalog-parser {:git-url "https://github.com/lambdaforge/datalog-parser", :test-namespaces (datalog.parser.pull-test datalog.parser.test.util datalog.parser.impl-test datalog.parser-test datalog.unparser-test), :git-sha "02d193f397afc3f93da704e7c6c850b194f0e797"} + clj-stacktrace/clj-stacktrace {:git-url "https://github.com/mmcgrana/clj-stacktrace", :test-namespaces (clj-stacktrace.repl-test clj-stacktrace.core-test), :git-sha "94dc2dd748710e79800e94b713e167e5dc525717"}} diff --git a/test-resources/lib_tests/clj_stacktrace/core_test.clj b/test-resources/lib_tests/clj_stacktrace/core_test.clj new file mode 100644 index 00000000..b93a31a5 --- /dev/null +++ b/test-resources/lib_tests/clj_stacktrace/core_test.clj @@ -0,0 +1,83 @@ +(ns clj-stacktrace.core-test + (:use clojure.test) + (:use clj-stacktrace.core) + (:use clj-stacktrace.utils)) + +(def cases + [["foo.bar$biz__123" "invoke" "bar.clj" 456 + {:clojure true :ns "foo.bar" :fn "biz" + :file "bar.clj" :line 456 :anon-fn false}] + + ["foo.bar$biz_bat__123" "invoke" "bar.clj" 456 + {:clojure true :ns "foo.bar" :fn "biz-bat" + :file "bar.clj" :line 456 :anon-fn false}] + + ["foo.bar$biz_bat_QMARK___448" "invoke" "bar.clj" 456 + {:clojure true :ns "foo.bar" :fn "biz-bat?" + :file "bar.clj" :line 456 :anon-fn false}] + + ["foo.bar$biz_bat_QMARK___448$fn__456" "invoke" "bar.clj" 456 + {:clojure true :ns "foo.bar" :fn "biz-bat?" + :file "bar.clj" :line 456 :anon-fn true}] + + ["foo.bar$repl$fn__5629.invoke" "invoke" "bar.clj" 456 + {:clojure true :ns "foo.bar" :fn "repl" + :file "bar.clj" :line 456 :anon-fn true}] + + ["foo.bar$repl$read_eval_print__5624" "invoke" "bar.clj" 456 + {:clojure true :ns "foo.bar" :fn "repl" + :file "bar.clj" :line 456 :anon-fn true}] + + ["foo.bar$biz__123$fn__456" "invoke" "bar.clj" 789 + {:clojure true :ns "foo.bar" :fn "biz" + :file "bar.clj" :line 789 :anon-fn true}] + + ["foo.bar_bat$biz__123" "invoke" "bar.clj" 456 + {:clojure true :ns "foo.bar-bat" :fn "biz" + :file "bar.clj" :line 456 :anon-fn false}] + + ["user$eval__345" "invoke" nil -1 + {:clojure true :ns "user" :fn "eval" + :file nil :line nil :anon-fn false}] + + ["lamina.core.observable.ConstantObservable" "message" "observable.clj" 198 + {:clojure true :ns "lamina.core.observable" + :fn "lamina.core.observable.ConstantObservable" + :file "observable.clj" :line 198 :anon-fn false}] + + ["clojure.lang.Var" "invoke" "Var.java" 123 + {:java true :class "clojure.lang.Var" :method "invoke" + :file "Var.java" :line 123}] + + ["clojure.proxy.space.SomeClass" "someMethod" "SomeClass.java" 123 + {:java true :class "clojure.proxy.space.SomeClass" :method "someMethod" + :file "SomeClass.java" :line 123}] + + ["some.space.SomeClass" "someMethod" "SomeClass.java" 123 + {:java true :class "some.space.SomeClass" :method "someMethod" + :file "SomeClass.java" :line 123}] + + ["some.space.SomeClass$SomeInner" "someMethod" "SomeClass.java" 123 + {:java true :class "some.space.SomeClass$SomeInner" :method "someMethod" + :file "SomeClass.java" :line 123}] + + ["some.space.SomeClass" "someMethod" nil -1 + {:java true :class "some.space.SomeClass" :method "someMethod" + :file nil :line nil}]]) + +(deftest test-parse-trace-elem + (doseq [[class method file line parsed] cases + :let [elem (StackTraceElement. class method file line)]] + (is (= parsed (parse-trace-elem elem))))) + +(deftest test-trim-redundant + (let [trim-fn (resolve 'clj-stacktrace.core/trim-redundant)] + (is (= '(d c) (trim-fn '(d c b a) '(f e b a)))) + (is (= '(c) (trim-fn '(c b a) '(f e b a)))) + (is (= '(d c) (trim-fn '(d c b a) '(e b a)))))) + +(deftest test-parse-exception + (try + (eval '(/)) + (catch Exception e + (is (parse-exception e))))) diff --git a/test-resources/lib_tests/clj_stacktrace/repl_test.clj b/test-resources/lib_tests/clj_stacktrace/repl_test.clj new file mode 100644 index 00000000..83d6a46f --- /dev/null +++ b/test-resources/lib_tests/clj_stacktrace/repl_test.clj @@ -0,0 +1,31 @@ +(ns clj-stacktrace.repl-test + (:use clojure.test) + (:use clj-stacktrace.utils) + (:use clj-stacktrace.repl)) + +(defmacro with-cascading-exception + "Execute body in the context of a variable bound to an exception instance + that includes a caused-by cascade." + [binding-sym & body] + `(try (first (lazy-seq (cons (/) nil))) + (catch Exception e# + (let [~binding-sym e#] + ~@body)))) + +(deftest test-pst + (with-cascading-exception e + (is (with-out-str (pst e))) + (binding [*e e] + (is (with-out-str (pst)))))) + +(deftest test-pst-str + (with-cascading-exception e + (is (pst-str e)) + (binding [*e e] + (is (pst-str))))) + +(deftest test-pst+ + (with-cascading-exception e + (is (with-out-str (pst+ e))) + (binding [*e e] + (is (with-out-str (pst+)))))) diff --git a/test-resources/lib_tests/datalog/parser/impl_test.cljc b/test-resources/lib_tests/datalog/parser/impl_test.cljc new file mode 100644 index 00000000..10e1bef9 --- /dev/null +++ b/test-resources/lib_tests/datalog/parser/impl_test.cljc @@ -0,0 +1,492 @@ +(ns datalog.parser.impl-test + (:require #?(:cljs [cljs.test :refer-macros [is are deftest testing]] + :clj [clojure.test :refer [is are deftest testing]]) + [datalog.parser.impl :as dp] + [datalog.parser.type :as t] + [datalog.parser.test.util]) + (:import [clojure.lang ExceptionInfo])) + +(deftest bindings + (are [form res] (= (dp/parse-binding form) res) + '?x + (t/->BindScalar (t/->Variable '?x)) + + '_ + (t/->BindIgnore) + + '[?x ...] + (t/->BindColl (t/->BindScalar (t/->Variable '?x))) + + '[?x] + (t/->BindTuple [(t/->BindScalar (t/->Variable '?x))]) + + '[?x ?y] + (t/->BindTuple [(t/->BindScalar (t/->Variable '?x)) (t/->BindScalar (t/->Variable '?y))]) + + '[_ ?y] + (t/->BindTuple [(t/->BindIgnore) (t/->BindScalar (t/->Variable '?y))]) + + '[[_ [?x ...]] ...] + (t/->BindColl + (t/->BindTuple [(t/->BindIgnore) + (t/->BindColl + (t/->BindScalar (t/->Variable '?x)))])) + + '[[?a ?b ?c]] + (t/->BindColl + (t/->BindTuple [(t/->BindScalar (t/->Variable '?a)) + (t/->BindScalar (t/->Variable '?b)) + (t/->BindScalar (t/->Variable '?c))]))) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse binding" + (dp/parse-binding :key)))) + +(deftest in + (are [form res] (= (dp/parse-in form) res) + '[?x] + [(t/->BindScalar (t/->Variable '?x))] + + '[$ $1 % _ ?x] + [(t/->BindScalar (t/->SrcVar '$)) + (t/->BindScalar (t/->SrcVar '$1)) + (t/->BindScalar (t/->RulesVar)) + (t/->BindIgnore) + (t/->BindScalar (t/->Variable '?x))] + + '[$ [[_ [?x ...]] ...]] + [(t/->BindScalar (t/->SrcVar '$)) + (t/->BindColl + (t/->BindTuple [(t/->BindIgnore) + (t/->BindColl + (t/->BindScalar (t/->Variable '?x)))]))]) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse binding" + (dp/parse-in ['?x :key])))) + +(deftest with + (is (= (dp/parse-with '[?x ?y]) + [(t/->Variable '?x) (t/->Variable '?y)])) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse :with clause" + (dp/parse-with '[?x _])))) + +(deftest test-parse-find + (is (= (dp/parse-find '[?a ?b]) + (t/->FindRel [(t/->Variable '?a) (t/->Variable '?b)]))) + (is (= (dp/parse-find '[[?a ...]]) + (t/->FindColl (t/->Variable '?a)))) + (is (= (dp/parse-find '[?a .]) + (t/->FindScalar (t/->Variable '?a)))) + (is (= (dp/parse-find '[[?a ?b]]) + (t/->FindTuple [(t/->Variable '?a) (t/->Variable '?b)])))) + +(deftest test-parse-aggregate + (is (= (dp/parse-find '[?a (count ?b)]) + (t/->FindRel [(t/->Variable '?a) (t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?b)])]))) + (is (= (dp/parse-find '[[(count ?a) ...]]) + (t/->FindColl (t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?a)])))) + (is (= (dp/parse-find '[(count ?a) .]) + (t/->FindScalar (t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?a)])))) + (is (= (dp/parse-find '[[(count ?a) ?b]]) + (t/->FindTuple [(t/->Aggregate (t/->PlainSymbol 'count) [(t/->Variable '?a)]) (t/->Variable '?b)])))) + +(deftest test-parse-custom-aggregates + (is (= (dp/parse-find '[(aggregate ?f ?a)]) + (t/->FindRel [(t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)])]))) + (is (= (dp/parse-find '[?a (aggregate ?f ?b)]) + (t/->FindRel [(t/->Variable '?a) (t/->Aggregate (t/->Variable '?f) [(t/->Variable '?b)])]))) + (is (= (dp/parse-find '[[(aggregate ?f ?a) ...]]) + (t/->FindColl (t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)])))) + (is (= (dp/parse-find '[(aggregate ?f ?a) .]) + (t/->FindScalar (t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)])))) + (is (= (dp/parse-find '[[(aggregate ?f ?a) ?b]]) + (t/->FindTuple [(t/->Aggregate (t/->Variable '?f) [(t/->Variable '?a)]) (t/->Variable '?b)])))) + +(deftest test-parse-find-elements + (is (= (dp/parse-find '[(count ?b 1 $x) .]) + (t/->FindScalar (t/->Aggregate (t/->PlainSymbol 'count) + [(t/->Variable '?b) + (t/->Constant 1) + (t/->SrcVar '$x)]))))) + +(deftest clauses + (are [form res] (= (set (dp/parse-rules form)) res) + '[[(rule ?x) + [?x :name _]]] + #{(t/->Rule + (t/->PlainSymbol 'rule) + [(t/->RuleBranch + (t/->RuleVars nil [(t/->Variable '?x)]) + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?x) (t/->Constant :name) (t/->Placeholder)])])])}) + (is (thrown-with-msg? ExceptionInfo #"Reference to the unknown variable" + (dp/parse-rules '[[(rule ?x) [?x :name ?y]]])))) + +(deftest rule-vars + (are [form res] (= (set (dp/parse-rules form)) res) + '[[(rule [?x] ?y) + [_]]] + #{(t/->Rule + (t/->PlainSymbol 'rule) + [(t/->RuleBranch + (t/->RuleVars [(t/->Variable '?x)] [(t/->Variable '?y)]) + [(t/->Pattern (t/->DefaultSrc) [(t/->Placeholder)])])])} + + '[[(rule [?x ?y] ?a ?b) + [_]]] + #{(t/->Rule + (t/->PlainSymbol 'rule) + + [(t/->RuleBranch + (t/->RuleVars [(t/->Variable '?x) (t/->Variable '?y)] + [(t/->Variable '?a) (t/->Variable '?b)]) + [(t/->Pattern (t/->DefaultSrc) [(t/->Placeholder)])])])} + + '[[(rule [?x]) + [_]]] + #{(t/->Rule + (t/->PlainSymbol 'rule) + [(t/->RuleBranch + (t/->RuleVars [(t/->Variable '?x)] nil) + [(t/->Pattern (t/->DefaultSrc) [(t/->Placeholder)])])])}) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars" + (dp/parse-rules '[[(rule) [_]]]))) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars" + (dp/parse-rules '[[(rule []) [_]]]))) + + (is (thrown-with-msg? ExceptionInfo #"Rule variables should be distinct" + (dp/parse-rules '[[(rule ?x ?y ?x) [_]]]))) + + (is (thrown-with-msg? ExceptionInfo #"Rule variables should be distinct" + (dp/parse-rules '[[(rule [?x ?y] ?z ?x) [_]]])))) + +(deftest branches + (are [form res] (= (set (dp/parse-rules form)) res) + '[[(rule ?x) + [:a] + [:b]] + [(rule ?x) + [:c]]] + #{(t/->Rule + (t/->PlainSymbol 'rule) + [(t/->RuleBranch + (t/->RuleVars nil [(t/->Variable '?x)]) + [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :a)]) + (t/->Pattern (t/->DefaultSrc) [(t/->Constant :b)])]) + (t/->RuleBranch + (t/->RuleVars nil [(t/->Variable '?x)]) + [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :c)])])])} + + '[[(rule ?x) + [:a] + [:b]] + [(other ?x) + [:c]]] + #{(t/->Rule + (t/->PlainSymbol 'rule) + [(t/->RuleBranch + (t/->RuleVars nil [(t/->Variable '?x)]) + [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :a)]) + (t/->Pattern (t/->DefaultSrc) [(t/->Constant :b)])])]) + (t/->Rule + (t/->PlainSymbol 'other) + [(t/->RuleBranch + (t/->RuleVars nil [(t/->Variable '?x)]) + [(t/->Pattern (t/->DefaultSrc) [(t/->Constant :c)])])])}) + + (is (thrown-with-msg? ExceptionInfo #"Rule branch should have clauses" + (dp/parse-rules '[[(rule ?x)]]))) + + (is (thrown-with-msg? ExceptionInfo #"Arity mismatch" + (dp/parse-rules '[[(rule ?x) [_]] + [(rule ?x ?y) [_]]]))) + + (is (thrown-with-msg? ExceptionInfo #"Arity mismatch" + (dp/parse-rules '[[(rule ?x) [_]] + [(rule [?x]) [_]]])))) + +(deftest pattern + (are [clause pattern] (= (dp/parse-clause clause) pattern) + '[?e ?a ?v] + (t/->Pattern (t/->DefaultSrc) [(t/->Variable '?e) (t/->Variable '?a) (t/->Variable '?v)]) + + '[_ ?a _ _] + (t/->Pattern (t/->DefaultSrc) [(t/->Placeholder) (t/->Variable '?a) (t/->Placeholder) (t/->Placeholder)]) + + '[$x _ ?a _ _] + (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Variable '?a) (t/->Placeholder) (t/->Placeholder)]) + + '[$x _ :name ?v] + (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Constant :name) (t/->Variable '?v)]) + + '[$x _ sym ?v] + (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Constant 'sym) (t/->Variable '?v)]) + + '[$x _ $src-sym ?v] + (t/->Pattern (t/->SrcVar '$x) [(t/->Placeholder) (t/->Constant '$src-sym) (t/->Variable '?v)])) + + (is (thrown-with-msg? ExceptionInfo #"Pattern could not be empty" + (dp/parse-clause '[])))) + +(deftest test-pred + (are [clause res] (= (dp/parse-clause clause) res) + '[(pred ?a 1)] + (t/->Predicate (t/->PlainSymbol 'pred) [(t/->Variable '?a) (t/->Constant 1)]) + + '[(pred)] + (t/->Predicate (t/->PlainSymbol 'pred) []) + + '[(?custom-pred ?a)] + (t/->Predicate (t/->Variable '?custom-pred) [(t/->Variable '?a)]))) + +(deftest test-fn + (are [clause res] (= (dp/parse-clause clause) res) + '[(fn ?a 1) ?x] + (t/->Function (t/->PlainSymbol 'fn) [(t/->Variable '?a) (t/->Constant 1)] (t/->BindScalar (t/->Variable '?x))) + + '[(fn) ?x] + (t/->Function (t/->PlainSymbol 'fn) [] (t/->BindScalar (t/->Variable '?x))) + + '[(?custom-fn) ?x] + (t/->Function (t/->Variable '?custom-fn) [] (t/->BindScalar (t/->Variable '?x))) + + '[(?custom-fn ?arg) ?x] + (t/->Function (t/->Variable '?custom-fn) [(t/->Variable '?arg)] (t/->BindScalar (t/->Variable '?x))))) + +(deftest rule-expr + (are [clause res] (= (dp/parse-clause clause) res) + '(friends ?x ?y) + (t/->RuleExpr (t/->DefaultSrc) (t/->PlainSymbol 'friends) [(t/->Variable '?x) (t/->Variable '?y)]) + + '(friends "Ivan" _) + (t/->RuleExpr (t/->DefaultSrc) (t/->PlainSymbol 'friends) [(t/->Constant "Ivan") (t/->Placeholder)]) + + '($1 friends ?x ?y) + (t/->RuleExpr (t/->SrcVar '$1) (t/->PlainSymbol 'friends) [(t/->Variable '?x) (t/->Variable '?y)]) + + '(friends something) + (t/->RuleExpr (t/->DefaultSrc) (t/->PlainSymbol 'friends) [(t/->Constant 'something)])) + + (is (thrown-with-msg? ExceptionInfo #"rule-expr requires at least one argument" + (dp/parse-clause '(friends))))) + +(deftest not-clause + (are [clause res] (= (dp/parse-clause clause) res) + '(not [?e :follows ?x]) + (t/->Not + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Variable '?x)] + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])]) + + '(not + [?e :follows ?x] + [?x _ ?y]) + (t/->Not + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Variable '?x) (t/->Variable '?y)] + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)]) + (t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?x) (t/->Placeholder) (t/->Variable '?y)])]) + + '($1 not [?x]) + (t/->Not + (t/->SrcVar '$1) + [(t/->Variable '?x)] + [(t/->Pattern (t/->DefaultSrc) [(t/->Variable '?x)])]) + + '(not-join [?e ?y] + [?e :follows ?x] + [?x _ ?y]) + (t/->Not + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Variable '?y)] + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)]) + (t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?x) (t/->Placeholder) (t/->Variable '?y)])]) + + '($1 not-join [?e] [?e :follows ?x]) + (t/->Not + (t/->SrcVar '$1) + [(t/->Variable '?e)] + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])])) + + (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?x\]" + (dp/parse-clause '(not-join [?x] [?y])))) + + (is (thrown-with-msg? ExceptionInfo #"Join variables should not be empty" + (dp/parse-clause '(not-join [] [?y])))) + + (is (thrown-with-msg? ExceptionInfo #"Join variables should not be empty" + (dp/parse-clause '(not [_])))) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'not-join' clause" + (dp/parse-clause '(not-join [?x])))) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'not' clause" + (dp/parse-clause '(not)))) + + (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?y\]" + (dp/parse-clause '(not-join [?y] + (not-join [?x] + [?x :follows ?y])))))) + +(deftest or-clause + (are [clause res] (= (dp/parse-clause clause) res) + '(or [?e :follows ?x]) + (t/->Or + (t/->DefaultSrc) + (t/->RuleVars nil [(t/->Variable '?e) (t/->Variable '?x)]) + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])]) + + '(or + [?e :follows ?x] + [?e :friend ?x]) + (t/->Or + (t/->DefaultSrc) + (t/->RuleVars nil [(t/->Variable '?e) (t/->Variable '?x)]) + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)]) + (t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?x)])]) + + '(or + [?e :follows ?x] + (and + [?e :friend ?x] + [?x :friend ?e])) + (t/->Or + (t/->DefaultSrc) + (t/->RuleVars nil [(t/->Variable '?e) (t/->Variable '?x)]) + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)]) + (t/->And + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?x)]) + (t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?x) (t/->Constant :friend) (t/->Variable '?e)])])]) + + '($1 or [?x]) + (t/->Or + (t/->SrcVar '$1) + (t/->RuleVars nil [(t/->Variable '?x)]) + [(t/->Pattern (t/->DefaultSrc) [(t/->Variable '?x)])]) + + '(or-join [?e] + [?e :follows ?x] + [?e :friend ?y]) + (t/->Or + (t/->DefaultSrc) + (t/->RuleVars nil [(t/->Variable '?e)]) + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)]) + (t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?y)])]) + + '(or-join [[?e]] + (and [?e :follows ?x] + [?e :friend ?y])) + (t/->Or + (t/->DefaultSrc) + (t/->RuleVars [(t/->Variable '?e)] nil) + [(t/->And + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)]) + (t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :friend) (t/->Variable '?y)])])]) + + '($1 or-join [[?e] ?x] + [?e :follows ?x]) + (t/->Or + (t/->SrcVar '$1) + (t/->RuleVars [(t/->Variable '?e)] [(t/->Variable '?x)]) + [(t/->Pattern + (t/->DefaultSrc) + [(t/->Variable '?e) (t/->Constant :follows) (t/->Variable '?x)])])) + + ;; These tests reflect the or-join semantics of Datomic Datalog, https://docs.datomic.com/on-prem/query.html + ;; TODO use record constructors instead of wordy literals as for rest in this buffer + (is (= (dp/parse-clause '(or-join [?x] [?y])) + '#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{}, + :rule-vars #datalog.parser.type.RuleVars{:required nil, + :free [#datalog.parser.type.Variable{:symbol ?x}]}, + :clauses [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, + :pattern [#datalog.parser.type.Variable{:symbol ?y}]}]})) + (is (= (dp/parse-clause '(or-join [?x ?y] [?x ?y] [?y])) + '#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{}, + :rule-vars #datalog.parser.type.RuleVars{:required nil, + :free [#datalog.parser.type.Variable{:symbol ?x} + #datalog.parser.type.Variable{:symbol ?y}]}, + :clauses [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, + :pattern [#datalog.parser.type.Variable{:symbol ?x} + #datalog.parser.type.Variable{:symbol ?y}]} + #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, + :pattern [#datalog.parser.type.Variable{:symbol ?y}]}]})) + + (is (= (dp/parse-clause '(or-join [?y] + (or-join [?x] + [?x :follows ?y]))) + '#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{}, + :rule-vars #datalog.parser.type.RuleVars{:required nil, + :free [#datalog.parser.type.Variable{:symbol ?y}]}, + :clauses [#datalog.parser.type.Or{:source #datalog.parser.type.DefaultSrc{}, + :rule-vars #datalog.parser.type.RuleVars{:required nil, + :free [#datalog.parser.type.Variable{:symbol ?x}]}, + :clauses [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, + :pattern [#datalog.parser.type.Variable{:symbol ?x} + #datalog.parser.type.Constant{:value :follows} #datalog.parser.type.Variable{:symbol ?y}]}]}]})) + + + (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?y\]" + (dp/parse-clause '(or [?x] [?x ?y])))) + + (is (thrown-with-msg? ExceptionInfo #"Join variable not declared inside clauses: \[\?y\]" + (dp/parse-clause '(or [?x] [?y])))) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars" + (dp/parse-clause '(or-join [] [?y])))) + + (is (thrown-with-msg? ExceptionInfo #"Join variables should not be empty" + (dp/parse-clause '(or [_])))) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'or-join' clause" + (dp/parse-clause '(or-join [?x])))) + + (is (thrown-with-msg? ExceptionInfo #"Cannot parse 'or' clause" + (dp/parse-clause '(or))))) + + +(deftest test-parse-return-maps + (testing "failed parsing" + (is (thrown-with-msg? ExceptionInfo #"Only one of these three options is allowed: :keys :strs :syms" + (dp/parse-return-maps {:keys '("keys" "strs" "syms") :syms '("keys" "strs" "syms")})))) + (testing "parsing correct options" + (is (= #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key "keys"} #datalog.parser.type.MappingKey{:mapping-key "strs"} #datalog.parser.type.MappingKey{:mapping-key "syms"})} + (dp/parse-return-maps {:keys '("keys" "strs" "syms")}))) + (is (= #datalog.parser.type.ReturnMaps{:mapping-type :strs, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key "keys"} #datalog.parser.type.MappingKey{:mapping-key "strs"} #datalog.parser.type.MappingKey{:mapping-key "syms"})} + (dp/parse-return-maps {:strs '("keys" "strs" "syms")}))) + (is (= #datalog.parser.type.ReturnMaps{:mapping-type :syms, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key "keys"} #datalog.parser.type.MappingKey{:mapping-key "strs"} #datalog.parser.type.MappingKey{:mapping-key "syms"})} + (dp/parse-return-maps {:syms '("keys" "strs" "syms")}))))) diff --git a/test-resources/lib_tests/datalog/parser/pull_test.cljc b/test-resources/lib_tests/datalog/parser/pull_test.cljc new file mode 100644 index 00000000..1a13ad84 --- /dev/null +++ b/test-resources/lib_tests/datalog/parser/pull_test.cljc @@ -0,0 +1,42 @@ +(ns datalog.parser.pull-test + (:require [datalog.parser.pull :as dpp] + #?(:cljs [cljs.test :as t :refer-macros [is are deftest testing]] + :clj [clojure.test :as t :refer [is are deftest testing]]))) + +#?(:cljs + (def Throwable js/Error)) + +(deftest test-parse-pattern + (are [pattern expected] (= expected (dpp/parse-pull pattern)) + '[:db/id :foo/bar] + (dpp/->PullSpec false {:db/id {:attr :db/id} + :foo/bar {:attr :foo/bar}}) + + '[(limit :foo 1)] + (dpp/->PullSpec false {:foo {:attr :foo :limit 1}}) + + '[* (default :foo "bar")] + (dpp/->PullSpec true {:foo {:attr :foo :default "bar"}}) + + '[{:foo ...}] + (dpp/->PullSpec false {:foo {:attr :foo :recursion nil}}) + + '[{(limit :foo 2) [:bar :me]}] + (dpp/->PullSpec + false + {:foo {:attr :foo + :limit 2 + :subpattern (dpp/->PullSpec + false + {:bar {:attr :bar} + :me {:attr :me}})}}))) + +(deftest test-parse-bad-limit + (is + (thrown? Throwable (dpp/parse-pull '[(limit :foo :bar)])))) + +(deftest test-parse-bad-default + (is + (thrown? Throwable (dpp/parse-pull '[(default 1 :bar)])))) + +#_(t/test-ns 'datahike.test.pull-parser) diff --git a/test-resources/lib_tests/datalog/parser/test/util.cljc b/test-resources/lib_tests/datalog/parser/test/util.cljc new file mode 100644 index 00000000..747ec8b8 --- /dev/null +++ b/test-resources/lib_tests/datalog/parser/test/util.cljc @@ -0,0 +1,16 @@ +(ns datalog.parser.test.util + (:require [#?(:clj clojure.test :cljs cljs.test) :as test])) + +#?(:clj + (defmethod test/assert-expr 'thrown-msg? [msg form] + (let [[_ match & body] form] + `(try ~@body + (test/do-report {:type :fail, :message ~msg, :expected '~form, :actual nil}) + (catch Throwable e# + (let [m# (.getMessage e#)] + (test/do-report + {:type (if (= ~match m#) :pass :fail) + :message ~msg + :expected '~form + :actual e#})) + e#))))) diff --git a/test-resources/lib_tests/datalog/parser_test.cljc b/test-resources/lib_tests/datalog/parser_test.cljc new file mode 100644 index 00000000..95764275 --- /dev/null +++ b/test-resources/lib_tests/datalog/parser_test.cljc @@ -0,0 +1,96 @@ +(ns datalog.parser-test + (:require #?(:cljs [cljs.test :refer-macros [are deftest]] + :clj [clojure.test :refer [are deftest]]) + [datalog.parser :as parser] + [datalog.parser.test.util])) + +(deftest validation + (are [q result] (= result (parser/parse q)) + '[:find ?e + :in $ ?fname ?lname + :keys foo + :where [?e :user/firstName ?fname] + [?e :user/lastName ?lname]] + '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}} + + '[:find ?e + :in $ ?fname ?lname + :strs foo + :where [?e :user/firstName ?fname] + [?e :user/lastName ?lname]] + '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :strs, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}} + + '[:find ?e + :in $ ?fname ?lname + :syms foo + :where [?e :user/firstName ?fname] + [?e :user/lastName ?lname]] + '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :syms, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}} + + '{:find [?e] + :in [$ ?fname ?lname] + :keys [foo] + :where [[?e :user/firstName ?fname] + [?e :user/lastName ?lname]]} + '#datalog.parser.type.Query{:qfind #datalog.parser.type.FindRel{:elements [#datalog.parser.type.Variable{:symbol ?e}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}} + + '{:find [[?e ?fname]] + :keys [foo] + :in [$ ?fname ?lname] + :where [[?e :user/firstName ?fname] + [?e :user/lastName ?lname]]} +#datalog.parser.type.Query{:qfind #datalog.parser.type.FindTuple{:elements [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Variable{:symbol ?fname}]}, :qwith nil, :qin [#datalog.parser.type.BindScalar{:variable #datalog.parser.type.SrcVar{:symbol $}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?fname}} #datalog.parser.type.BindScalar{:variable #datalog.parser.type.Variable{:symbol ?lname}}], :qwhere [#datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/firstName} #datalog.parser.type.Variable{:symbol ?fname}]} #datalog.parser.type.Pattern{:source #datalog.parser.type.DefaultSrc{}, :pattern [#datalog.parser.type.Variable{:symbol ?e} #datalog.parser.type.Constant{:value :user/lastName} #datalog.parser.type.Variable{:symbol ?lname}]}], :qlimit nil, :qoffset nil, :qreturnmaps #datalog.parser.type.ReturnMaps{:mapping-type :keys, :mapping-keys (#datalog.parser.type.MappingKey{:mapping-key foo})}} + )) + +(deftest validation-fails + (are [q msg] (thrown-msg? msg (parser/parse q)) + '[:find ?e :where [?x]] + "Query for unknown vars: [?e]" + + '[:find ?e :with ?f :where [?e]] + "Query for unknown vars: [?f]" + + '[:find ?e ?x ?t :in ?x :where [?e]] + "Query for unknown vars: [?t]" + + '[:find ?x ?e :with ?y ?e :where [?x ?e ?y]] + ":find and :with should not use same variables: [?e]" + + '[:find ?e :in $ $ ?x :where [?e]] + "Vars used in :in should be distinct" + + '[:find ?e :in ?x $ ?x :where [?e]] + "Vars used in :in should be distinct" + + '[:find ?e :in $ % ?x % :where [?e]] + "Vars used in :in should be distinct" + + '[:find ?n :with ?e ?f ?e :where [?e ?f ?n]] + "Vars used in :with should be distinct" + + '[:find ?x :where [$1 ?x]] + "Where uses unknown source vars: [$1]" + + '[:find ?x :in $1 :where [$2 ?x]] + "Where uses unknown source vars: [$2]" + + '[:find ?e :where (rule ?e)] + "Missing rules var '%' in :in" + + '[:find ?e :where [?e] :limit [42]] + "Cannot parse :limit, expected java.lang.Long" + + '[:find ?e :where [?e] :offset [666]] + "Cannot parse :offset, expected java.lang.Long" + + '[:find ?e :keys foo bar :where [?e] :offset 666] + "Count of :keys/:strs/:syms must match count of :find" + + '[:find ?e ?f :keys foo :where [?e ?f] :offset 666] + "Count of :keys/:strs/:syms must match count of :find" + + '[:find [?e ?f] :keys foo bar :where [?e ?f] :offset 666] + "Count of :keys/:strs/:syms must match count of :find" + + '[:find ?e :strs '(foo bar) :keys '("foo" "bar") :where [?e] :offset 666] + "Only one of these three options is allowed: :keys :strs :syms")) diff --git a/test-resources/lib_tests/datalog/unparser_test.clj b/test-resources/lib_tests/datalog/unparser_test.clj new file mode 100644 index 00000000..8dc959ef --- /dev/null +++ b/test-resources/lib_tests/datalog/unparser_test.clj @@ -0,0 +1,25 @@ +(ns datalog.unparser-test + (:require [datalog.unparser :refer [unparse]] + [datalog.parser :refer [parse]] + [clojure.test :refer [deftest testing is] :as test]) + (:use [datalog.unparser])) + +(let [q '[:find (sum ?balance-before) ?balance-before + :in $before $after $txn $txs + :where + [(= ?balance-before 42)]]] + (deftest unparse-roundtrip-test + (testing "Datahike query unparsing." + (is (= q (unparse (parse q))))))) + + + +(comment ;; TODO + (let [q '[:find ?foo ?baz + :in $before $after + :where + [(= ?balance-before 42)] + (not [?foo :bar ?baz])]] + (deftest unparse-roundtrip-test + (testing "Datahike query unparsing." + (is (= q (unparse (parse q))))))))