From 923536d9a24266cbc077ef7f783cf51565e680b2 Mon Sep 17 00:00:00 2001 From: Ian Fernandez Date: Mon, 23 Feb 2026 22:48:46 -0300 Subject: [PATCH] Add CLJD support for datascript --- bench/datascript/bench/bench.cljc | 15 +- bench/datascript/bench/datascript.cljc | 32 +- script/bench_all.sh | 3 +- script/bench_cljd.sh | 25 + script/test_all.sh | 3 +- script/test_cljd.sh | 24 + src/datascript/built_ins.cljc | 11 +- src/datascript/conn.cljc | 49 +- src/datascript/core.cljc | 172 ++-- src/datascript/db.cljc | 856 +++++++++++++------- src/datascript/impl/entity.cljc | 82 +- src/datascript/lru.cljc | 11 + src/datascript/parser.cljc | 96 +-- src/datascript/pull_api.cljc | 307 +++---- src/datascript/pull_parser.cljc | 7 +- src/datascript/query.cljc | 198 +++-- src/datascript/query_v3.cljc | 65 +- src/datascript/serialize.cljc | 116 ++- src/datascript/storage.cljd | 13 + src/datascript/util.cljc | 90 +- test/datascript/test/components.cljc | 42 +- test/datascript/test/conn.cljc | 16 +- test/datascript/test/core.cljc | 63 +- test/datascript/test/db.cljc | 4 +- test/datascript/test/entity.cljc | 9 +- test/datascript/test/explode.cljc | 52 +- test/datascript/test/index.cljc | 10 +- test/datascript/test/issues.cljc | 8 +- test/datascript/test/listen.cljc | 5 +- test/datascript/test/lookup_refs.cljc | 92 ++- test/datascript/test/lru.cljc | 3 +- test/datascript/test/parser.cljc | 53 +- test/datascript/test/parser_find.cljc | 9 +- test/datascript/test/parser_query.cljc | 24 +- test/datascript/test/parser_return_map.cljc | 11 +- test/datascript/test/parser_rules.cljc | 67 +- test/datascript/test/parser_where.cljc | 120 +-- test/datascript/test/pull_api.cljc | 66 +- test/datascript/test/pull_parser.cljc | 18 +- test/datascript/test/query.cljc | 38 +- test/datascript/test/query_aggregates.cljc | 30 +- test/datascript/test/query_find_specs.cljc | 3 +- test/datascript/test/query_fns.cljc | 31 +- test/datascript/test/query_not.cljc | 43 +- test/datascript/test/query_or.cljc | 42 +- test/datascript/test/query_pull.cljc | 13 +- test/datascript/test/query_return_map.cljc | 5 +- test/datascript/test/query_rules.cljc | 16 +- test/datascript/test/query_v3.cljc | 9 +- test/datascript/test/serialize.cljc | 71 +- test/datascript/test/transact.cljc | 8 +- test/datascript/test/tuples.cljc | 46 +- test/datascript/test/upsert.cljc | 67 +- test/datascript/test/validation.cljc | 46 +- 54 files changed, 2105 insertions(+), 1210 deletions(-) create mode 100755 script/bench_cljd.sh create mode 100755 script/test_cljd.sh create mode 100644 src/datascript/storage.cljd diff --git a/bench/datascript/bench/bench.cljc b/bench/datascript/bench/bench.cljc index 290cf236..8e03069f 100644 --- a/bench/datascript/bench/bench.cljc +++ b/bench/datascript/bench/bench.cljc @@ -1,6 +1,6 @@ (ns datascript.bench.bench (:require - #?(:clj [clj-async-profiler.core :as clj-async-profiler])) + #?(:cljd nil :clj [clj-async-profiler.core :as clj-async-profiler])) #?(:cljs (:require-macros datascript.bench.bench))) ; Measure time @@ -12,7 +12,8 @@ (def ^:dynamic *profile* false) #?(:cljs (defn ^number now [] (js/performance.now)) - :clj (defn now ^double [] (/ (System/nanoTime) 1000000.0))) + :cljd (defn now [] (/ (.-microsecondsSinceEpoch (DateTime/now)) 1000.0)) + :clj (defn now ^double [] (/ (System/nanoTime) 1000000.0))) #?(:clj (defmacro dotime @@ -27,15 +28,13 @@ (recur (+ *batch* iterations#)) (double (/ (- now# start-t#) iterations#)))))))) -(defn- if-cljs [env then else] - (if (:ns env) then else)) - (defn median [xs] (nth (sort xs) (quot (count xs) 2))) (defn to-fixed [n places] #?(:cljs (.toFixed n places) - :clj (String/format java.util.Locale/ROOT (str "%." places "f") (to-array [(double n)])))) + :cljd (.toStringAsFixed (double n) places) + :clj (String/format java.util.Locale/ROOT (str "%." places "f") (to-array [(double n)])))) (defn round [n] (cond @@ -60,7 +59,7 @@ (let [[title body] (if (string? title) [title body] ["unknown-bench" (cons title body)])] - (if-cljs &env + (if #?(:cljd/clj-host true :default (:ns &env)) `(let [_# (dotime *warmup-ms* ~@body) times# (mapv (fn [_#] @@ -115,7 +114,7 @@ └ 15" [id depth width] (if (pos? depth) - (let [children (map #(+ (* id width) %) (range width))] + (let [children (mapv #(+ (* id width) %) (range width))] (cons (assoc (random-man) :db/id (str id) diff --git a/bench/datascript/bench/datascript.cljc b/bench/datascript/bench/datascript.cljc index e1e54307..fcfdc827 100644 --- a/bench/datascript/bench/datascript.cljc +++ b/bench/datascript/bench/datascript.cljc @@ -2,7 +2,8 @@ (:require [datascript.core :as d] [datascript.bench.bench :as bench] - #?(:clj [jsonista.core :as jsonista]))) + #?@(:cljd [cljd.reader :as edn-reader] + :clj [jsonista.core :as jsonista]))) #?(:cljs (enable-console-print!)) @@ -54,7 +55,7 @@ (defn bench-init [] (let [datoms (into [] (for [p @bench/*people20k - :let [id (#?(:clj Integer/parseInt :cljs js/parseInt) (:db/id p))] + :let [id (#?(:clj Integer/parseInt :cljs js/parseInt :cljd int/parse) (:db/id p))] [k v] p :when (not= k :db/id)] (d/datom id k v)))] @@ -219,13 +220,22 @@ (com.fasterxml.jackson.databind.ObjectMapper.))) (defn bench-freeze [] - (bench/bench - (-> @*serialize-db (d/serializable) #?(:clj (jsonista/write-value-as-string mapper) :cljs js/JSON.stringify)))) + #?(:cljd + (bench/bench + (-> @*serialize-db d/serializable pr-str)) + :default + (bench/bench + (-> @*serialize-db (d/serializable) #?(:clj (jsonista/write-value-as-string mapper) :cljs js/JSON.stringify))))) (defn bench-thaw [] - (let [json (-> @*serialize-db (d/serializable) #?(:clj (jsonista/write-value-as-string mapper) :cljs js/JSON.stringify))] - (bench/bench - (-> json #?(:clj (jsonista/read-value mapper) :cljs js/JSON.parse) d/from-serializable)))) + #?(:cljd + (let [edn (-> @*serialize-db d/serializable pr-str)] + (bench/bench + (-> edn edn-reader/read-string d/from-serializable))) + :default + (let [json (-> @*serialize-db (d/serializable) #?(:clj (jsonista/write-value-as-string mapper) :cljs js/JSON.stringify))] + (bench/bench + (-> json #?(:clj (jsonista/read-value mapper) :cljs js/JSON.parse) d/from-serializable))))) (def benches {"add-1" bench-add-1 @@ -261,10 +271,10 @@ "clj -A:bench -M -m datascript.bench.datascript [--profile] (add-1 | add-5 | ...)*" [& args] (let [args (or args ()) - profile? (.contains ^java.util.List args "--profile") + profile? (boolean (some #{"--profile"} args)) args (remove #{"--profile"} args) names (or (not-empty args) (sort (keys benches))) - _ (apply println #?(:clj "CLJ:" :cljs "CLJS:") names) + _ (apply println #?(:clj "CLJ:" :cljs "CLJS:" :cljd "CLJD:") names) longest (last (sort-by count names))] (binding [bench/*profile* profile?] (doseq [name names @@ -279,6 +289,10 @@ " " (or file "")))))) #?(:clj (shutdown-agents)))) +#?(:cljd + (defn ^:export main [args] + (apply -main args))) + (comment (require 'datascript.bench.datascript :reload-all) diff --git a/script/bench_all.sh b/script/bench_all.sh index a29736c4..85f9c7ac 100755 --- a/script/bench_all.sh +++ b/script/bench_all.sh @@ -3,4 +3,5 @@ cd "`dirname $0`/.." ./script/bench_clj.sh $@ ./script/bench_cljs.sh $@ -./script/bench_datomic.sh $@ \ No newline at end of file +./script/bench_datomic.sh $@ +./script/bench_cljd.sh $@ diff --git a/script/bench_cljd.sh b/script/bench_cljd.sh new file mode 100755 index 00000000..670f7603 --- /dev/null +++ b/script/bench_cljd.sh @@ -0,0 +1,25 @@ + +#!/bin/bash +set -o errexit -o nounset -o pipefail +cd "`dirname $0`/.." +PATH="$PWD/.fvm/flutter_sdk/bin:$PATH" + +rm -rf tmp/cljdbench +mkdir -p tmp/cljdbench/src/cljd +cat > tmp/cljdbench/deps.edn < tmp/cljdtests/deps.edn <Conn (atom opts))) @@ -36,7 +60,8 @@ (defn conn? [conn] (and - #?(:clj (instance? clojure.lang.IDeref conn) + #?(:cljd (instance? Conn conn) + :clj (instance? clojure.lang.IDeref conn) :cljs (satisfies? cljs.core/IDeref conn)) (if-some [db @conn] (db/db? db) @@ -112,7 +137,7 @@ (transact! conn tx-data nil)) ([conn tx-data tx-meta] {:pre [(conn? conn)]} - (locking conn + (#?(:cljd do :default locking) #?(:cljd nil :default conn) (let [report (-transact! conn tx-data tx-meta)] (doseq [[_ callback] (:listeners @(:atom conn))] (callback report)) diff --git a/src/datascript/core.cljc b/src/datascript/core.cljc index 3bb171d0..5ca008ff 100644 --- a/src/datascript/core.cljc +++ b/src/datascript/core.cljc @@ -1,38 +1,36 @@ (ns datascript.core (:refer-clojure :exclude [filter]) (:require - [#?(:cljs cljs.reader :clj clojure.edn) :as edn] + [#?(:cljs cljs.reader :cljd cljd.reader :clj clojure.edn) :as edn] [datascript.conn :as conn] - [datascript.db :as db #?@(:cljs [:refer [Datom DB FilteredDB]])] - #?(:clj [datascript.pprint]) + [datascript.db :as db #?@(:cljd [:refer [Datom DB FilteredDB ->FilteredDB]] + :cljs [:refer [Datom DB FilteredDB]])] + #?(:cljd nil :clj [datascript.pprint]) [datascript.pull-api :as dp] [datascript.serialize :as ds] [datascript.storage :as storage] [datascript.query :as dq] - [datascript.impl.entity :as de] + [datascript.impl.entity :as de #?@(:cljd [:refer [Entity]])] [datascript.util :as util] - [me.tonsky.persistent-sorted-set :as set]) - #?(:clj + #?(:cljd nil :default [me.tonsky.persistent-sorted-set :as set])) + #?(:cljd nil + :clj (:import [datascript.db Datom DB FilteredDB] [datascript.impl.entity Entity] [java.util UUID]))) -(def ^:const ^:no-doc tx0 - db/tx0) - +(def #?(:cljd tx0 :default ^:const ^:no-doc tx0) db/tx0) ; Entities -(def ^{:tag Entity +(def ^{:tag #?(:cljd Entity? :default Entity) :arglists '([db eid]) :doc "Retrieves an entity by its id from database. Entities are lazy map-like structures to navigate DataScript database content. For `eid` pass entity id or lookup attr: - (entity db 1) (entity db [:unique-attr :value]) - If entity does not exist, `nil` is returned: (entity db 100500) ; => nil @@ -42,7 +40,6 @@ (entity db 1) ; => {:db/id 1} Entity attributes can be lazily accessed through key lookups: - (:attr (entity db 1)) ; => :value (get (entity db 1) :attr) ; => :value @@ -59,13 +56,11 @@ (:_ref (entity db 2)) ; => [{:db/id 1}] (:ns/_ref (entity db 2)) ; => [{:db/id 1}] - Reverse reference lookup returns sequence of entities unless attribute is marked as `:db/isComponent`: (:_component-ref (entity db 2)) ; => {:db/id 1} Entity gotchas: - - Entities print as map, but are not exactly maps (they have compatible get interface though). - Entities are effectively immutable “views” into a particular version of a database. - Entities retain reference to the whole database. @@ -74,7 +69,8 @@ - Comparing entities just compares their ids. Be careful when comparing entities taken from different dbs or from different versions of the same db. - Accessed entity attributes are cached on entity itself (except backward references). - When printing, only cached attributes (the ones you have accessed before) are printed. See [[touch]]."} - entity de/entity) + entity + #(de/entity %1 %2)) ; CLJD bug (def ^{:arglists '([db eid]) :doc "Given lookup ref `[unique-attr value]`, returns numberic entity id. @@ -88,7 +84,7 @@ {:pre [(de/entity? entity)]} (.-db entity)) -(def ^{:tag Entity +(def ^{:tag #?(:cljd Entity? :default Entity) :arglists '([e]) :doc "Forces all entity attributes to be eagerly fetched and cached. Only usable for debug output. @@ -98,7 +94,7 @@ (entity db 1) ; => {:db/id 1} (touch (entity db 1)) ; => {:db/id 1, :dislikes [:pie], :likes [:pizza]} ```"} - touch de/touch) + touch #(de/touch %)) ; cljd bug ; Pull @@ -136,7 +132,6 @@ :doc "Executes a datalog query. See [docs.datomic.com/on-prem/query.html](https://docs.datomic.com/on-prem/query.html). Usage: - ``` (q '[:find ?value :where [_ :likes ?value]] @@ -152,7 +147,6 @@ "Creates an empty database with an optional schema. Usage: - ``` (empty-db) ; => #datascript/DB {:schema {}, :datoms []} @@ -160,9 +154,7 @@ ; => #datascript/DB {:schema {:likes {:db/cardinality :db.cardinality/many}} ; :datoms []} ``` - Options are: - :branching-factor , default 512. B-tree max node length :ref-type :strong | :soft | :weak, default :soft. How will nodes that are already stored on disk be referenced. Soft or weak means they might be unloaded @@ -246,7 +238,6 @@ (defn filter "Returns a view over database that has same interface but only includes datoms for which the `(pred db datom)` is true. Can be applied multiple times. - Filtered DB gotchas: - All operations on filtered database are proxied to original DB, then filter pred is applied. @@ -259,19 +250,21 @@ (let [^FilteredDB fdb db orig-pred (.-pred fdb) orig-db (.-unfiltered-db fdb)] - (FilteredDB. orig-db #(and (orig-pred %) (pred orig-db %)) (atom 0))) - (FilteredDB. db #(pred db %) (atom 0)))) + (#?(:cljd ->FilteredDB :default FilteredDB.) orig-db #(and (orig-pred %) (pred orig-db %)) (atom 0))) + (#?(:cljd ->FilteredDB :default FilteredDB.) db #(pred db %) (atom 0)))) ; Changing DB (def ^{:arglists '([db tx-data] [db tx-data tx-meta])} with "Same as [[transact!]], but applies to an immutable database value. Returns transaction report (see [[transact!]])." - conn/with) + #?(:cljd (fn ([db tx-data] (conn/with db tx-data)) + ([db tx-data tx-meta] (conn/with db tx-data tx-meta))) + :default conn/with)) (def ^{:arglists '([db tx-data]) :tag DB} db-with "Applies transaction to an immutable db value, returning new immutable db value. Same as `(:db-after (with db tx-data))`." - conn/db-with) + #?(:cljd #(conn/db-with %1 %2) :default conn/db-with)) (defn ^DB with-schema "Warning! No validation or conversion. Only change schema in a compatible way" @@ -294,17 +287,14 @@ ; #datascript/Datom [1 :likes \"fries\"] ; #datascript/Datom [1 :likes \"pizza\"] ; #datascript/Datom [1 :name \"Ivan\"]) - ; find all datoms for entity id == 1 and attribute == :likes (any values) ; sorted by value (datoms db :eavt 1 :likes) ; => (#datascript/Datom [1 :likes \"fries\"] ; #datascript/Datom [1 :likes \"pizza\"]) - ; find all datoms for entity id == 1, attribute == :likes and value == \"pizza\" (datoms db :eavt 1 :likes \"pizza\") ; => (#datascript/Datom [1 :likes \"pizza\"]) - ; find all datoms for attribute == :likes (any entity ids and values) ; sorted by entity id, then value (datoms db :aevt :likes) @@ -313,13 +303,11 @@ ; #datascript/Datom [2 :likes \"candy\"] ; #datascript/Datom [2 :likes \"pie\"] ; #datascript/Datom [2 :likes \"pizza\"]) - ; find all datoms that have attribute == `:likes` and value == `\"pizza\"` (any entity id) ; `:likes` must be a unique attr, reference or marked as `:db/index true` (datoms db :avet :likes \"pizza\") ; => (#datascript/Datom [1 :likes \"pizza\"] ; #datascript/Datom [2 :likes \"pizza\"]) - ; find all datoms sorted by entity id, then attribute, then value (datoms db :eavt) ; => (...) @@ -327,24 +315,18 @@ ; get all values of :db.cardinality/many attribute (->> (datoms db :eavt eid attr) (map :v)) - ; lookup entity ids by attribute value (->> (datoms db :avet attr value) (map :e)) - ; find all entities with a specific attribute (->> (datoms db :aevt attr) (map :e)) - ; find “singleton” entity by its attr (->> (datoms db :aevt attr) first :e) - ; find N entities with lowest attr value (e.g. 10 earliest posts) (->> (datoms db :avet attr) (take N)) - ; find N entities with highest attr value (e.g. 10 latest posts) (->> (datoms db :avet attr) (reverse) (take N)) Gotchas: - - Index lookup is usually more efficient than doing a query with a single clause. - Resulting iterator is calculated in constant time and small constant memory overhead. - Iterator supports efficient `first`, `next`, `reverse`, `seq` and is itself a sequence. @@ -357,7 +339,7 @@ ([db index c0 c1 c2] {:pre [(db/db? db)]} (db/-datoms db index c0 c1 c2 nil)) ([db index c0 c1 c2 c3] {:pre [(db/db? db)]} (db/-datoms db index c0 c1 c2 c3))) -(defn ^Datom find-datom +(defn ^#?(:cljd Datom? :default Datom) find-datom "Same as [[datoms]], but only returns single datom. Faster than `(first (datoms ...))`" ([db index] {:pre [(db/db? db)]} (db/find-datom db index nil nil nil nil)) ([db index c0] {:pre [(db/db? db)]} (db/find-datom db index c0 nil nil nil)) @@ -386,12 +368,10 @@ ; #datascript/Datom [2 :likes \"candy\"] ; #datascript/Datom [2 :likes \"pie\"] ; #datascript/Datom [2 :likes \"pizza\"]) - - (seek-datoms db :eavt 2) + (seek-datoms db :eavt 2) ; => (#datascript/Datom [2 :likes \"candy\"] ; #datascript/Datom [2 :likes \"pie\"] ; #datascript/Datom [2 :likes \"pizza\"]) - ; no datom [2 :likes \"fish\"], so starts with one immediately following such in index (seek-datoms db :eavt 2 :likes \"fish\") ; => (#datascript/Datom [2 :likes \"pie\"] @@ -412,11 +392,8 @@ (defn index-range "Returns part of `:avet` index between `[_ attr start]` and `[_ attr end]` in AVET sort order. - Same properties as [[datoms]]. - `attr` must be a reference, unique attribute or marked as `:db/index true`. - Usage: (index-range db :likes \"a\" \"zzzzzzzzz\") @@ -425,13 +402,10 @@ ; #datascript/Datom [2 :likes \"pie\"] ; #datascript/Datom [1 :likes \"pizza\"] ; #datascript/Datom [2 :likes \"pizza\"]) - (index-range db :likes \"egg\" \"pineapple\") ; => (#datascript/Datom [1 :likes \"fries\"] ; #datascript/Datom [2 :likes \"pie\"]) - Useful patterns: - ; find all entities with age in a specific range (inclusive) (->> (index-range db :age 18 60) (map :e))" [db attr start end] @@ -442,15 +416,18 @@ (def ^{:arglists '([conn])} conn? "Returns `true` if this is a connection to a DataScript db, `false` otherwise." - conn/conn?) + #?(:cljd #(conn/conn? %) :default conn/conn?)) (def ^{:arglists '([db])} conn-from-db "Creates a mutable reference to a given immutable database. See [[create-conn]]." - conn/conn-from-db) + #?(:cljd #(conn/conn-from-db %) :default conn/conn-from-db)) (def ^{:arglists '([datoms] [datoms schema] [datoms schema opts])} conn-from-datoms "Creates an empty DB and a mutable reference to it. See [[create-conn]]." - conn/conn-from-datoms) + #?(:cljd (fn ([datoms] (conn/conn-from-datoms datoms)) + ([datoms schema] (conn/conn-from-datoms datoms schema)) + ([datoms schema opts] (conn/conn-from-datoms datoms schema opts))) + :default conn/conn-from-datoms)) (def ^{:arglists '([] [schema] [schema opts])} create-conn "Creates a mutable reference (a “connection”) to an empty immutable database. @@ -458,21 +435,22 @@ Connections are lightweight in-memory structures (~atoms) with direct support of transaction listeners ([[listen!]], [[unlisten!]]) and other handy DataScript APIs ([[transact!]], [[reset-conn!]], [[db]]). To access underlying immutable DB value, deref: `@conn`. - For list of options, see [[empty-db]]. - If you specify `:storage` option, conn will be stored automatically after each transaction" - conn/create-conn) + #?(:cljd (fn ([] (conn/create-conn)) + ([schema] (conn/create-conn schema)) + ([schema opts] (conn/create-conn schema opts))) + :default conn/create-conn)) -#?(:clj +#?(:cljd nil + :clj (def ^{:arglists '([storage] [storage opts])} restore-conn "Lazy-load database from storage and make conn out of it. - Returns nil if there’s no database yet in storage" + Returns nil if there's no database yet in storage" conn/restore-conn)) (def ^{:arglists '([conn tx-data] [conn tx-data tx-meta])} transact! "Applies transaction the underlying database value and atomically updates connection reference to point to the result of that transaction, new db value. - Returns transaction report, a map: {:db-before ... ; db value before transaction @@ -482,56 +460,43 @@ :tx-meta tx-meta} ; the exact value you passed as `tx-meta` Note! `conn` will be updated in-place and is not returned from [[transact!]]. - Usage: ; add a single datom to an existing entity (1) (transact! conn [[:db/add 1 :name \"Ivan\"]]) - ; retract a single datom (transact! conn [[:db/retract 1 :name \"Ivan\"]]) - ; retract single entity attribute (transact! conn [[:db.fn/retractAttribute 1 :name]]) - ; ... or equivalently (since Datomic changed its API to support this): (transact! conn [[:db/retract 1 :name]]) - ; retract all entity attributes (effectively deletes entity) (transact! conn [[:db.fn/retractEntity 1]]) - ; create a new entity (`-1`, as any other negative value, is a tempid ; that will be replaced with DataScript to a next unused eid) (transact! conn [[:db/add -1 :name \"Ivan\"]]) - ; check assigned id (here `*1` is a result returned from previous `transact!` call) (def report *1) (:tempids report) ; => {-1 296} - ; check actual datoms inserted (:tx-data report) ; => [#datascript/Datom [296 :name \"Ivan\"]] - ; tempid can also be a string (transact! conn [[:db/add \"ivan\" :name \"Ivan\"]]) (:tempids *1) ; => {\"ivan\" 297} - ; reference another entity (must exist) (transact! conn [[:db/add -1 :friend 296]]) - ; create an entity and set multiple attributes (in a single transaction ; equal tempids will be replaced with the same yet unused entid) (transact! conn [[:db/add -1 :name \"Ivan\"] [:db/add -1 :likes \"fries\"] [:db/add -1 :likes \"pizza\"] [:db/add -1 :friend 296]]) - ; create an entity and set multiple attributes (alternative map form) (transact! conn [{:db/id -1 :name \"Ivan\" :likes [\"fries\" \"pizza\"] :friend 296}]) - - ; update an entity (alternative map form). Can’t retract attributes in + ; update an entity (alternative map form). Can't retract attributes in ; map form. For cardinality many attrs, value (fish in this example) ; will be added to the list of existing values (transact! conn [{:db/id 296 @@ -543,7 +508,6 @@ :name \"Oleg\" :friend {:db/id -2 :name \"Sergey\"}}]) - ; reverse attribute name can be used if you want created entity to become ; a value in another entity reference (transact! conn [{:db/id -1 @@ -555,27 +519,31 @@ ; equivalent to (transact! conn [[:db/add -1 :name \"Oleg\"] [:db/add 296 :friend -1]])" - conn/transact!) + #?(:cljd (fn ([conn tx-data] (conn/transact! conn tx-data)) + ([conn tx-data tx-meta] (conn/transact! conn tx-data tx-meta))) + :default conn/transact!)) (def ^{:arglists '([conn db] [conn db tx-meta])} reset-conn! "Forces underlying `conn` value to become `db`. Will generate a tx-report that will remove everything from old value and insert everything from the new one." - conn/reset-conn!) + #?(:cljd (fn ([conn db] (conn/reset-conn! conn db)) + ([conn db tx-meta] (conn/reset-conn! conn db tx-meta))) + :default conn/reset-conn!)) (def ^{:arglists '([conn schema])} reset-schema! - conn/reset-schema!) + #?(:cljd #(conn/reset-schema! %1 %2) :default conn/reset-schema!)) (def ^{:arglists '([conn callback] [conn key callback])} listen! "Listen for changes on the given connection. Whenever a transaction is applied to the database via [[transact!]], the callback is called with the transaction report. `key` is any opaque unique value. - Idempotent. Calling [[listen!]] with the same key twice will override old callback with the new value. - Returns the key under which this listener is registered. See also [[unlisten!]]." - conn/listen!) + #?(:cljd (fn ([conn callback] (conn/listen! conn callback)) + ([conn key callback] (conn/listen! conn key callback))) + :default conn/listen!)) (def ^{:arglists '([conn key])} unlisten! "Removes registered listener from connection. See also [[listen!]]." - conn/unlisten!) + #?(:cljd #(conn/unlisten! %1 %2) :default conn/unlisten!)) ; Data Readers @@ -598,7 +566,6 @@ (defn tempid "Allocates and returns an unique temporary id (a negative integer). Ignores `part`. Returns `x` if it is specified. - Exists for Datomic API compatibility. Prefer using negative integers directly if possible." ([part] (if (= part :db.part/tx) @@ -611,14 +578,12 @@ (defn resolve-tempid "Does a lookup in tempids map, returning an entity id that tempid was resolved to. - Exists for Datomic API compatibility. Prefer using map lookup directly if possible." [_db tempids tempid] (get tempids tempid)) (defn ^DB db "Returns the underlying immutable database value from a connection. - Exists for Datomic API compatibility. Prefer using `@conn` directly if possible." [conn] {:pre [(conn? conn)]} @@ -626,13 +591,18 @@ (defn transact "Same as [[transact!]], but returns an immediately realized future. - Exists for Datomic API compatibility. Prefer using [[transact!]] if possible." ([conn tx-data] (transact conn tx-data nil)) ([conn tx-data tx-meta] {:pre [(conn? conn)]} (let [res (transact! conn tx-data tx-meta)] - #?(:cljs + #?(:cljd + (reify + cljd.core/IDeref + (-deref [_] res) + cljd.core/IPending + (-realized? [_] true)) + :cljs (reify IDeref (-deref [_] res) @@ -666,19 +636,20 @@ (defn transact-async "In CLJ, calls [[transact!]] on a future thread pool, returning immediately. - In CLJS, just calls [[transact!]] and returns a realized future." ([conn tx-data] (transact-async conn tx-data nil)) ([conn tx-data tx-meta] {:pre [(conn? conn)]} - (future-call #(transact! conn tx-data tx-meta)))) + #?(:cljd + (future (transact! conn tx-data tx-meta)) + :default + (future-call #(transact! conn tx-data tx-meta))))) ;; squuid (def ^{:arglists '([] [msec])} squuid "Generates a UUID that grow with time. Such UUIDs will always go to the end of the index and that will minimize insertions in the middle. - Consist of 64 bits of current UNIX timestamp (in seconds) and 64 random bits (2^64 different unique values per second)." util/squuid) @@ -688,54 +659,57 @@ ;; Storage -#?(:clj +#?(:cljd nil + :clj (def ^{:arglists '([db])} storage "Returns IStorage used by DB instance" storage/storage)) -#?(:clj +#?(:cljd nil + :clj (def ^{:arglists '([db] [db storage])} store "Stores databases to provided storage. If database was created with :storage option or restored from storage, use single-argument version. - Subsequent stores are incremental, i.e. only newly added nodes will be actually stored. - Storing already stored dbs into another storage is not supported (may change)." storage/store)) -#?(:clj +#?(:cljd nil + :clj (def ^{:arglists '([storage] [storage opts])} restore "Lazy-loads database from storage. Ultra-fast, fetches the rest as it’s needed" storage/restore)) -#?(:clj +#?(:cljd nil + :clj (defn addresses "Returns all addresses in use by current db (as java.util.HashSet). Anything that is not in the return set is safe to be deleted" [& dbs] (storage/addresses dbs))) -#?(:clj +#?(:cljd nil + :clj (def ^{:arglists '([storage])} collect-garbage "Deletes all keys from storage that are not referenced by any of the currently alive db refs. Has a side-effect of fully loading databases fully into memory, so, can be slow" storage/collect-garbage)) -#?(:clj +#?(:cljd nil + :clj (def ^{:arglists '([dir] [dir opts])} file-storage "Default implementation that stores data in files in a dir. - Options are: - :freeze-fn :: (data) -> String. A serialization function :thaw-fn :: (String) -> data. A deserialization function :write-fn :: (OutputStream data) -> void. Implement your own writer to FileOutputStream :read-fn :: (InputStream) -> Object. Implement your own reader from FileInputStream :addr->filename-fn :: (UUID) -> String. Construct file name from address :filename->addr-fn :: (String) -> UUID. Reconstruct address from file name - All options are optional." storage/file-storage)) (defn settings [db] - (set/settings (:eavt db))) + #?(:cljd nil + :default + (set/settings (:eavt db)))) diff --git a/src/datascript/db.cljc b/src/datascript/db.cljc index b20d75a2..e5c4c816 100644 --- a/src/datascript/db.cljc +++ b/src/datascript/db.cljc @@ -2,17 +2,20 @@ (:require #?(:cljs [goog.array :as garray]) [clojure.walk] + [clojure.string] + #?(:cljd [cljd.core :refer [HashRankedWideTreapSet]]) [clojure.data] - #?(:clj [datascript.inline :refer [update]]) + #?(:cljd nil :clj [datascript.inline :refer [update]]) [datascript.lru :as lru] [datascript.util :as util] - [me.tonsky.persistent-sorted-set :as set] - [me.tonsky.persistent-sorted-set.arrays :as arrays]) - #?(:clj (:import clojure.lang.IFn$OOL)) - #?(:cljs (:require-macros [datascript.db :refer [case-tree combine-cmp defn+ defcomp defrecord-updatable int-compare validate-attr validate-val]])) - (:refer-clojure :exclude [seqable? #?(:clj update)])) + #?@(:cljd () + :default [[me.tonsky.persistent-sorted-set :as set] + [me.tonsky.persistent-sorted-set.arrays :as arrays]])) + #?(:cljd nil :clj (:import clojure.lang.IFn$OOL)) + #?(:cljs (:require-macros [datascript.db :refer [case-tree combine-cmp declare+ defn+ defcomp defrecord-updatable int-compare validate-attr validate-val]])) + (:refer-clojure :exclude [seqable? #?(:cljd nil :clj update)])) -#?(:clj (set! *warn-on-reflection* true)) +#?(:cljd nil :clj (set! *warn-on-reflection* true)) ;; ---------------------------------------------------------------------------- @@ -22,28 +25,19 @@ (def IllegalArgumentException js/Error) (def UnsupportedOperationException js/Error))) -(def ^:const e0 - 0) - -(def ^:const tx0 - 0x20000000) - -(def ^:const emax - 0x7FFFFFFF) - -(def ^:const txmax - 0x7FFFFFFF) - -(def ^:const implicit-schema - {:db/ident {:db/unique :db.unique/identity}}) +(def #?(:cljd e0 :default ^:const e0) 0) +(def #?(:cljd tx0 :default ^:const tx0) 0x20000000) +(def #?(:cljd emax :default ^:const emax) 0x7FFFFFFF) +(def #?(:cljd txmax :default ^:const txmax) 0x7FFFFFFF) +(def #?(:cljd implicit-schema :default ^:const implicit-schema) {:db/ident {:db/unique :db.unique/identity}}) ;; ---------------------------------------------------------------------------- -(defn #?@(:clj [^Boolean seqable?] - :cljs [^boolean seqable?]) +(defn ^#?(:cljd bool :clj Boolean :cljs boolean) seqable? [x] (and (not (string? x)) - #?(:cljs (or (cljs.core/seqable? x) + #?(:cljd (cljd.core/seqable? x) + :cljs (or (cljs.core/seqable? x) (arrays/array? x)) :clj (or (seq? x) (instance? clojure.lang.Seqable x) @@ -59,7 +53,7 @@ ;; https://github.com/Prismatic/schema/commit/e31c419c56555c83ef9ee834801e13ef3c112597 ;; -(defn- cljs-env? +(defn- ^:macro-support cljs-env? "Take the &env from a macro, and tell whether we are expanding into cljs." [env] (boolean (:ns env))) @@ -69,9 +63,19 @@ "Return then if we are generating cljs code and else for Clojure code. https://groups.google.com/d/msg/clojurescript/iBY5HaQda4A/w1lAQi9_AwsJ" [then else] - (if (cljs-env? &env) then else))) + #?(:cljd/clj-host true + :default + (if (cljs-env? &env) then else)))) -#?(:clj +#?(:cljd + (defn ^:macro-support patch-tag [meta cljs-env?] + (if cljs-env? + meta + (condp = (:tag meta) + 'boolean (assoc meta :tag 'bool) + 'number (assoc meta :tag 'int) + meta))) + :clj (defn patch-tag [meta cljs-env?] (if cljs-env? meta @@ -80,6 +84,16 @@ 'number (assoc meta :tag clojure.core$long) meta)))) +#?(:clj + (defmacro declare+ + "Same idea as `declare`, but allows to declare type hints and arglists. + This allows CLJS to generate more efficient code when calling this fn + before it's declared" + [name & arglists] + (let [name' (vary-meta name patch-tag (cljs-env? &env)) + bodies (map #(list % `(throw (ex-info (str "Not implemented: (" ~name (clojure.string/join " " ~%)) {}))) arglists)] + `(defn ~name' ~@bodies)))) + #?(:clj (defmacro defn+ "CLJS really don’t like :declared metadata on vars (generates less @@ -100,10 +114,12 @@ `(defn ~name' ~@rest))))) (defn combine-hashes [x y] - #?(:clj (clojure.lang.Util/hashCombine x y) + #?(:cljd (hash-combine x y) + :clj (clojure.lang.Util/hashCombine x y) :cljs (hash-combine x y))) -#?(:clj +#?(:cljd nil + :clj (defn- get-sig [method] ;; expects something like '(method-symbol [arg arg arg] ...) ;; if the thing matches, returns [fully-qualified-symbol arity], otherwise nil @@ -114,7 +130,8 @@ ns (or (some->> sym resolve meta :ns str) "clojure.core")] [(symbol ns (name sym)) (-> method second count)])))) -#?(:clj +#?(:cljd nil + :clj (defn- dedupe-interfaces [deftype-form] ;; get the interfaces list, remove any duplicates, similar to remove-nil-implements in potemkin ;; verified w/ deftype impl in compiler: @@ -124,7 +141,8 @@ (throw (IllegalArgumentException. "deftype-form mismatch"))) (list* deftype* tagname classname fields implements (vec (distinct interfaces)) rest)))) -#?(:clj +#?(:cljd nil + :clj (defn- make-record-updatable-clj [name fields & impls] (let [impl-map (->> impls (map (juxt get-sig identity)) (filter first) (into {})) body (macroexpand-1 (list* 'defrecord name fields impls))] @@ -147,17 +165,21 @@ #?(:clj (defmacro defrecord-updatable [name fields & impls] - `(if-cljs - ~(apply make-record-updatable-cljs name fields impls) - ~(apply make-record-updatable-clj name fields impls)))) + #?(:cljd + `(defrecord ~name ~fields ~@(for [impl impls] + (if (seq? impl) + (cons (vary-meta (first impl) assoc :override true) (next impl)) + impl))) + :default + `(if-cljs + ~(apply make-record-updatable-cljs name fields impls) + ~(apply make-record-updatable-clj name fields impls))))) ;; ---------------------------------------------------------------------------- -#?(:clj (declare hash-datom) - :cljs (defn ^number hash-datom [d])) +(declare+ ^#?(:cljd int :default number) hash-datom [d]) -#?(:clj (declare equiv-datom) - :cljs (defn ^boolean equiv-datom [d o])) +(declare+ ^#?(:cljd bool :default boolean) equiv-datom [d o]) #?(:clj (declare seq-datom) :cljs (defn seq-datom [d])) @@ -177,7 +199,8 @@ (datom-get-idx [this]) (datom-set-idx [this value])) -(deftype Datom #?(:clj [^int e a v ^int tx ^:unsynchronized-mutable ^int idx ^:unsynchronized-mutable ^int _hash] +(deftype Datom #?(:cljd [^int e a v ^int tx ^:mutable ^int idx ^:mutable ^int _hash] + :clj [^int e a v ^int tx ^:unsynchronized-mutable ^int idx ^:unsynchronized-mutable ^int _hash] :cljs [^number e a v ^number tx ^:mutable ^number idx ^:mutable ^number _hash]) IDatom (datom-tx [d] (if (pos? tx) tx (- tx))) @@ -203,7 +226,7 @@ IIndexed (-nth [this i] (nth-datom this i)) (-nth [this i not-found] (nth-datom this i not-found)) - + IAssociative (-assoc [d k v] (assoc-datom d k v)) @@ -212,6 +235,32 @@ (pr-sequential-writer writer pr-writer "#datascript/Datom [" " " "]" opts [(.-e d) (.-a d) (.-v d) (datom-tx d) (datom-added d)]))] + :cljd + [cljd.core/IHash + (-hash [d] (if (zero? _hash) + (set! _hash (hash-datom d)) + _hash)) + cljd.core/IEquiv + (-equiv [d o] (and (dart/is? o Datom) (equiv-datom d o))) + + cljd.core/ISeqable + (-seq [d] (seq-datom d)) + + cljd.core/ILookup + (-lookup [d k] (val-at-datom d k nil)) + (-lookup [d k nf] (val-at-datom d k nf)) + + cljd.core/IIndexed + (-nth [this i] (nth-datom this i)) + (-nth [this i not-found] (nth-datom this i not-found)) + + cljd.core/IAssociative + (-assoc [d k v] (assoc-datom d k v)) + + cljd.core/IPrint + (-print [d sink] + (.write ^StringSink sink "#datascript/Datom ") + (-print [(.-e d) (.-a d) (.-v d) (datom-tx d) (datom-added d)] sink))] :clj [Object (hashCode [d] @@ -233,7 +282,7 @@ (empty [d] (throw (UnsupportedOperationException. "empty is not supported on Datom"))) (count [d] 5) (cons [d [k v]] (assoc-datom d k v)) - + clojure.lang.Indexed (nth [this i] (nth-datom this i)) (nth [this i not-found] (nth-datom this i not-found)) @@ -254,7 +303,7 @@ ([e a v tx] (Datom. e a v tx 0 0)) ([e a v tx added] (Datom. e a v (if added tx (- tx)) 0 0))) -(defn datom? [x] (instance? Datom x)) +(defn datom? [x] #?(:cljd (dart/is? x Datom) :default (instance? Datom x))) (defn+ ^:private hash-datom [^Datom d] (-> (hash (.-e d)) @@ -281,7 +330,7 @@ :tx (datom-tx d) :added (datom-added d) not-found) - + (string? k) (case k "e" (.-e d) @@ -290,21 +339,22 @@ "tx" (datom-tx d) "added" (datom-added d) not-found) - + :else not-found)) (defn+ ^:private nth-datom - ([^Datom d ^long i] + ([^Datom d ^#?(:cljd int :default long) i] (case i 0 (.-e d) 1 (.-a d) 2 (.-v d) 3 (datom-tx d) 4 (datom-added d) - #?(:clj (throw (IndexOutOfBoundsException.)) + #?(:cljd (throw (IndexError.withLength i 5)) + :clj (throw (IndexOutOfBoundsException.)) :cljs (throw (js/Error. (str "Datom/-nth: Index out of bounds: " i)))))) - ([^Datom d ^long i not-found] + ([^Datom d ^#?(:cljd int :default long) i not-found] (case i 0 (.-e d) 1 (.-a d) @@ -320,7 +370,9 @@ :v (datom (.-e d) (.-a d) v (datom-tx d) (datom-added d)) :tx (datom (.-e d) (.-a d) (.-v d) v (datom-added d)) :added (datom (.-e d) (.-a d) (.-v d) (datom-tx d) v) - (throw (IllegalArgumentException. (str "invalid key for #datascript/Datom: " k))))) + (let [msg (str "invalid key for #datascript/Datom: " k)] + (throw #?(:cljd (ArgumentError msg) + :default (IllegalArgumentException. msg)))))) ;; printing and reading ;; #datomic/DB {:schema , :datoms } @@ -328,7 +380,8 @@ (defn ^Datom datom-from-reader [vec] (apply datom vec)) -#?(:clj +#?(:cljd nil + :clj (defmethod print-method Datom [^Datom d, ^java.io.Writer w] (.write w (str "#datascript/Datom ")) (binding [*out* w] @@ -339,20 +392,20 @@ ;; #?(:clj - (defmacro combine-cmp [& comps] - (loop [comps (reverse comps) - res (num 0)] - (if (not-empty comps) - (recur - (next comps) - `(let [c# ~(first comps)] - (if (== 0 c#) - ~res - c#))) - res)))) + (defmacro combine-cmp [& comps] + (loop [comps (reverse comps) + res 0] + (if (not-empty comps) + (recur + (next comps) + `(let [c# ~(first comps)] + (if (== 0 c#) + ~res + c#))) + res)))) #?(:clj - (defn- -case-tree [queries variants] + (defn- ^:macro-support -case-tree [queries variants] (if queries (let [v1 (take (/ (count variants) 2) variants) v2 (drop (/ (count variants) 2) variants)] @@ -366,21 +419,34 @@ (-case-tree qs vs))) (defn cmp - #?(:clj + #?(:cljd {} + :clj {:inline (fn [x y] `(let [x# ~x y# ~y] (if (nil? x#) 0 (if (nil? y#) 0 (long (compare x# y#))))))}) - ^long [x y] - (if (nil? x) 0 (if (nil? y) 0 (long (compare x y))))) - -(defn class-identical? - #?(:clj {:inline (fn [x y] `(identical? (class ~x) (class ~y)))}) - [x y] - #?(:clj (identical? (class x) (class y)) - :cljs (identical? (type x) (type y)))) - -#?(:clj + ^#?(:cljd int :default long) [x y] + #?(:cljd + (cond + (identical? x y) 0 + (identical? x MIN) -1 + (identical? y MIN) 1 + (identical? y MAX) -1 + (identical? x MAX) 1 + :else (int (compare x y))) + :default + (if (nil? x) 0 (if (nil? y) 0 (long (compare x y)))))) + +#?(:cljd nil + :default + (defn class-identical? + #?(:clj {:inline (fn [x y] `(identical? (class ~x) (class ~y)))}) + [x y] + #?(:clj (identical? (class x) (class y)) + :cljs (identical? (type x) (type y))))) + +#?(:cljd nil + :clj (defn class-name {:inline (fn [x] @@ -388,12 +454,17 @@ (if (nil? x#) x# (.getName (. x# (getClass))))))} ^String [^Object x] (if (nil? x) x (.getName (. x (getClass)))))) -(defn class-compare - ^long [x y] - #?(:clj (long (compare (class-name x) (class-name y))) - :cljs (garray/defaultCompare (type->str (type x)) (type->str (type y))))) +#?(:cljd nil + :default + (defn class-compare + ^long [x y] + #?(:clj (long (compare (class-name x) (class-name y))) + :cljs (garray/defaultCompare (type->str (type x)) (type->str (type y)))))) -#?(:clj +#?(:cljd + (defmacro int-compare [x y] + `(- ~x ~y)) + :clj (defmacro int-compare [x y] `(if-cljs (- ~x ~y) @@ -401,79 +472,154 @@ (defn ihash {:inline (fn [x] `(. clojure.lang.Util (hasheq ~x)))} - ^long [x] - #?(:clj (. clojure.lang.Util (hasheq x)) + ^#?(:cljd int :default long) [x] + #?(:cljd (hash x) + :clj (. clojure.lang.Util (hasheq x)) :cljs (hash x))) +#?(:cljd + (defn- seq-compare [xs ys] + (let [cx (count xs) + cy (count ys)] + (cond + (< cx cy) -1 + (> cx cy) 1 + :else + (loop [xs xs ys ys] + (if (empty? xs) + 0 + (let [x (first xs) y (first ys)] + (cond + (and (nil? x) (nil? y)) (recur (next xs) (next ys)) + (nil? x) -1 + (nil? y) 1 + :else + (let [v (value-compare x y)] + (if (= v 0) + (recur (next xs) (next ys)) + v)))))))))) + +#?(:cljd + (defn- type-rank ^int [x] + (cond + (boolean? x) 0 + (number? x) 1 + (string? x) 2 + (keyword? x) 3 + :else 4))) + +#?(:cljd + (defn value-compare ^int [x y] + (try + (cond + (= x y) 0 + (and (sequential? x) (sequential? y)) (seq-compare x y) + :else + (let [rx (type-rank x) + ry (type-rank y)] + (if (= rx ry) + (compare x y) + (- rx ry)))) + (catch Object e + (let [rx (type-rank x) + ry (type-rank y)] + (if (= rx ry) + (- (hash x) (hash y)) + (- rx ry))))))) + #?(:clj (declare value-compare) :cljs (defn ^number value-compare [x y])) -(defn- seq-compare [xs ys] - (let [cx (count xs) - cy (count ys)] - (cond - (< cx cy) - -1 - - (> cx cy) - 1 - - :else - (loop [xs xs - ys ys] - (if (empty? xs) - 0 - (let [x (first xs) - y (first ys)] - (cond - (and (nil? x) (nil? y)) - (recur (next xs) (next ys)) - - (nil? x) - -1 - - (nil? y) - 1 - - :else - (let [v (value-compare x y)] - (if (= v 0) - (recur (next xs) (next ys)) - v))))))))) - -(defn+ ^number value-compare [x y] - (try - (cond - (= x y) 0 - (and (sequential? x) (sequential? y)) (seq-compare x y) - #?@(:clj [(instance? Number x) (clojure.lang.Numbers/compare x y)]) - #?@(:clj [(instance? Comparable x) (.compareTo ^Comparable x y)] - :cljs [(satisfies? IComparable x) (-compare x y)]) - (not (class-identical? x y)) (class-compare x y) - #?@(:cljs [(or (number? x) (string? x) (array? x) (true? x) (false? x)) (garray/defaultCompare x y)]) - :else (int-compare (ihash x) (ihash y))) - (catch #?(:clj ClassCastException :cljs js/Error) e - (if (not (class-identical? x y)) - (class-compare x y) - (throw e))))) - -(defn value-cmp - #?(:clj - {:inline - (fn [x y] - `(let [x# ~x y# ~y] - (if (nil? x#) 0 (if (nil? y#) 0 (value-compare x# y#)))))}) - ^long [x y] - (if (nil? x) - 0 - (if (nil? y) - 0 - (value-compare x y)))) +#?(:cljd nil + :default + (defn- seq-compare [xs ys] + (let [cx (count xs) + cy (count ys)] + (cond + (< cx cy) + -1 + + (> cx cy) + 1 + + :else + (loop [xs xs + ys ys] + (if (empty? xs) + 0 + (let [x (first xs) + y (first ys)] + (cond + (and (nil? x) (nil? y)) + (recur (next xs) (next ys)) + + (nil? x) + -1 + + (nil? y) + 1 + + :else + (let [v (value-compare x y)] + (if (= v 0) + (recur (next xs) (next ys)) + v)))))))))) + +#?(:cljd nil + :default + (defn+ ^number value-compare [x y] + (try + (cond + (= x y) 0 + (and (sequential? x) (sequential? y)) (seq-compare x y) + #?@(:clj [(instance? Number x) (clojure.lang.Numbers/compare x y)]) + #?@(:clj [(instance? Comparable x) (.compareTo ^Comparable x y)] + :cljs [(satisfies? IComparable x) (-compare x y)]) + (not (class-identical? x y)) (class-compare x y) + #?@(:cljs [(or (number? x) (string? x) (array? x) (true? x) (false? x)) (garray/defaultCompare x y)]) + :else (int-compare (ihash x) (ihash y))) + (catch #?(:clj ClassCastException :cljs js/Error) e + (if (not (class-identical? x y)) + (class-compare x y) + (throw e)))))) + +#?(:cljd + (do + (def MIN ^:unique (Object)) + (def MAX nil #_ ^:unique (Object)))) + +#?(:cljd + (defn value-cmp + ^int [x y] + (cond + (identical? x y) 0 + (identical? x MIN) -1 + (identical? y MIN) 1 + (identical? y MAX) -1 + (identical? x MAX) 1 + :else (value-compare x y))) + :default + (defn value-cmp + #?(:clj + {:inline + (fn [x y] + `(let [x# ~x y# ~y] + (if (nil? x#) 0 (if (nil? y#) 0 (value-compare x# y#)))))}) + ^long [x y] + (if (nil? x) + 0 + (if (nil? y) + 0 + (value-compare x y))))) ;; Slower cmp-* fns allows for datom fields to be nil. ;; Such datoms come from slice method where they are used as boundary markers. -#?(:clj +#?(:cljd + (defmacro defcomp [sym [arg1 arg2] & body] + `(defn ~sym [~arg1 ~arg2] + ~@body)) + :clj (defmacro defcomp [sym [arg1 arg2] & body] (let [a1 (with-meta arg1 {}) a2 (with-meta arg2 {})] @@ -517,16 +663,19 @@ ;; fast versions without nil checks (defn- cmp-attr-quick - #?(:clj + #?(:cljd + {} + :clj {:inline (fn [a1 a2] `(long (.compareTo ~(with-meta a1 {:tag "Comparable"}) ~a2)))}) - ^long [a1 a2] + ^#?(:cljd int :default long) [a1 a2] ;; either both are keywords or both are strings #?(:cljs (if (keyword? a1) (-compare a1 a2) (garray/defaultCompare a1 a2)) + :cljd (compare a1 a2) ; TODO take shortcuts :clj (.compareTo ^Comparable a1 a2))) @@ -571,7 +720,7 @@ first-b (first b) diff (try (cmp first-a first-b) - (catch #?(:clj ClassCastException :cljs js/Error) _ + (catch #?(:cljd Exception :clj ClassCastException :cljs js/Error) _ :incomparable))] (cond (= diff :incomparable) (recur (conj only-a first-a) (conj only-b first-b) both (next a) (next b)) @@ -581,20 +730,16 @@ ;; ---------------------------------------------------------------------------- -#?(:clj (declare hash-db) - :cljs (defn ^number hash-db [db])) +(declare+ ^#?(:cljd int :default number) hash-db [db]) -#?(:clj (declare hash-fdb) - :cljs (defn ^number hash-fdb [db])) +(declare+ ^#?(:cljd int :default number) hash-fdb [db]) -#?(:clj (declare equiv-db) - :cljs (defn ^boolean equiv-db [db other])) +(declare+ ^#?(:cljd bool :default boolean) equiv-db [db other]) #?(:clj (declare restore-db) :cljs (defn restore-db [keys])) -#?(:clj (declare indexing?) - :cljs (defn ^boolean indexing? [db attr])) +(declare+ ^#?(:cljd bool :default boolean) indexing? [db attr]) #?(:cljs (defn pr-db [db w opts])) @@ -628,7 +773,7 @@ (defprotocol ISearch (-search [data pattern])) -(defn- ^Datom fsearch [data pattern] +(defn- ^#?(:cljd Datom? :default Datom) fsearch [data pattern] (first (-search data pattern))) (defprotocol IIndexAccess @@ -662,7 +807,8 @@ (update :aevt persistent!) (update :avet persistent!))) -#?(:clj +#?(:cljd nil + :clj (defn vpred [v] (cond (string? v) (fn [x] (if (string? x) (.equals ^String v x) false)) @@ -671,8 +817,38 @@ (nil? v) (fn [x] (nil? x)) :else (fn [x] (= v x))))) +#?(:cljd + (do + (defn ^Datom min-datom [^Datom {:flds [e a v tx]}] + (datom (if (nil? e) MIN e) (if (nil? a) MIN a) (if (nil? v) MIN v) tx)) + (defn ^Datom max-datom [^Datom {:flds [e a v tx]}] + (datom (if (nil? e) MAX e) (if (nil? a) MAX a) (if (nil? v) MAX v) tx)) + (defn set-slice [s ^Datom from ^Datom to] + (subseq s >= (min-datom from) <= (max-datom to))) + (defn set-rslice [s ^Datom from ^Datom to] + (rsubseq s >= (min-datom to) <= (max-datom from))) + (defn ->Eduction [xform coll] (cljd.core/Eduction xform coll -1)))) + (defrecord-updatable DB [schema eavt aevt avet max-eid max-tx rschema pull-patterns pull-attrs hash] - #?@(:cljs + #?@(:cljd + [cljd.core/IHash (-hash [db] (hash-db db)) + cljd.core/IEquiv (-equiv [db other] (equiv-db db other)) + cljd.core/IReversible (-rseq [db] (-rseq (.-eavt db))) + cljd.core/ICounted (-count [db] (count (.-eavt db))) + cljd.core/IEmptyableCollection (-empty [db] + (-> (restore-db + {:schema (.-schema db) + :rschema (.-rschema db) + :eavt (empty (.-eavt db)) + :aevt (empty (.-aevt db)) + :avet (empty (.-avet db))}) + (with-meta (meta db)))) + cljd.core/IPrint (-print [db sink] (pr-db db sink)) + cljd.core/IEditableCollection (-as-transient [db] (db-transient db)) + cljd.core/ITransientCollection (-conj! [db key] (throw (ex-info "datascript.DB/conj! is not supported" {}))) + (-persistent! [db] (db-persistent! db))] + + :cljs [IHash (-hash [db] (hash-db db)) IEquiv (-equiv [db other] (equiv-db db other)) IReversible (-rseq [db] (-rseq (.-eavt db))) @@ -695,7 +871,7 @@ clojure.lang.IPersistentCollection (count [db] (count eavt)) (equiv [db other] (equiv-db db other)) - clojure.lang.IEditableCollection + clojure.lang.IEditableCollection (empty [db] (-> (restore-db {:schema (.-schema db) :rschema (.-rschema db) @@ -718,68 +894,70 @@ eavt (.-eavt db) aevt (.-aevt db) avet (.-avet db) - pred #?(:clj (vpred v) + pred #?(:cljd #(= v %) + :clj (vpred v) :cljs #(= v %)) - multival? (contains? (-attrs-by db :db.cardinality/many) a)] + multival? (contains? (-attrs-by db :db.cardinality/many) a) + slice #?(:cljd set-slice :default set/slice)] (case-tree [e a (some? v) tx] - [(set/slice eavt (datom e a v tx) (datom e a v tx)) ;; e a v tx - (set/slice eavt (datom e a v tx0) (datom e a v txmax)) ;; e a v _ - (->> (set/slice eavt (datom e a nil tx0) (datom e a nil txmax)) ;; e a _ tx + [(slice eavt (datom e a v tx) (datom e a v tx)) ;; e a v tx + (slice eavt (datom e a v tx0) (datom e a v txmax)) ;; e a v _ + (->> (slice eavt (datom e a nil tx0) (datom e a nil txmax)) ;; e a _ tx (->Eduction (filter (fn [^Datom d] (= tx (datom-tx d)))))) - (set/slice eavt (datom e a nil tx0) (datom e a nil txmax)) ;; e a _ _ - (->> (set/slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ v tx + (slice eavt (datom e a nil tx0) (datom e a nil txmax)) ;; e a _ _ + (->> (slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ v tx (->Eduction (filter (fn [^Datom d] (and (pred (.-v d)) (= tx (datom-tx d))))))) - (->> (set/slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ v _ + (->> (slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ v _ (->Eduction (filter (fn [^Datom d] (pred (.-v d)))))) - (->> (set/slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ _ tx + (->> (slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ _ tx (->Eduction (filter (fn [^Datom d] (= tx (datom-tx d)))))) - (set/slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ _ _ + (slice eavt (datom e nil nil tx0) (datom e nil nil txmax)) ;; e _ _ _ (if (indexing? db a) ;; _ a v tx - (->> (set/slice avet (datom e0 a v tx0) (datom emax a v txmax)) + (->> (slice avet (datom e0 a v tx0) (datom emax a v txmax)) (->Eduction (filter (fn [^Datom d] (= tx (datom-tx d)))))) - (->> (set/slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) + (->> (slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) (->Eduction (filter (fn [^Datom d] (and (pred (.-v d)) (= tx (datom-tx d)))))))) (if (indexing? db a) ;; _ a v _ - (set/slice avet (datom e0 a v tx0) (datom emax a v txmax)) - (->> (set/slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) + (slice avet (datom e0 a v tx0) (datom emax a v txmax)) + (->> (slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) (->Eduction (filter (fn [^Datom d] (pred (.-v d))))))) - (->> (set/slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) ;; _ a _ tx + (->> (slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) ;; _ a _ tx (->Eduction (filter (fn [^Datom d] (= tx (datom-tx d)))))) - (set/slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) ;; _ a _ _ + (slice aevt (datom e0 a nil tx0) (datom emax a nil txmax)) ;; _ a _ _ (filter (fn [^Datom d] (and (pred (.-v d)) (= tx (datom-tx d)))) eavt) ;; _ _ v tx - (filter (fn [^Datom d] (pred (.-v d))) eavt) ;; _ _ v + (filter (fn [^Datom d] (pred (.-v d))) eavt) ;; _ _ v (filter (fn [^Datom d] (= tx (datom-tx d))) eavt) ;; _ _ _ tx eavt]))) ;; _ _ _ _ IIndexAccess (-datoms [db index c0 c1 c2 c3] (validate-indexed db index c0 c1 c2 c3) - (set/slice (get db index) + (#?(:cljd set-slice :default set/slice) (get db index) (components->pattern db index c0 c1 c2 c3 e0 tx0) (components->pattern db index c0 c1 c2 c3 emax txmax))) (-seek-datoms [db index c0 c1 c2 c3] (validate-indexed db index c0 c1 c2 c3) - (set/slice (get db index) + (#?(:cljd set-slice :default set/slice) (get db index) (components->pattern db index c0 c1 c2 c3 e0 tx0) (datom emax nil nil txmax))) (-rseek-datoms [db index c0 c1 c2 c3] (validate-indexed db index c0 c1 c2 c3) - (set/rslice (get db index) + (#?(:cljd set-rslice :default set/rslice) (get db index) (components->pattern db index c0 c1 c2 c3 emax txmax) (datom e0 nil nil tx0))) (-index-range [db attr start end] (validate-indexed db :avet attr nil nil nil) (validate-attr attr (list '-index-range 'db attr start end)) - (set/slice (.-avet db) + (#?(:cljd set-slice :default set/slice) (.-avet db) (resolve-datom db nil attr start nil e0 tx0) (resolve-datom db nil attr end nil emax txmax))) - + clojure.data/EqualityPartition (equality-partition [x] :datascript/db) @@ -788,7 +966,11 @@ (diff-sorted (:eavt a) (:eavt b) cmp-datoms-eav-quick))) (defn db? [x] - #?(:clj + #?(:cljd + (and (satisfies? ISearch x) + (satisfies? IIndexAccess x) + (satisfies? IDB x)) + :clj (or (and x (instance? datascript.db.ISearch x) @@ -804,7 +986,21 @@ ;; ---------------------------------------------------------------------------- (defrecord-updatable FilteredDB [unfiltered-db pred hash] - #?@(:cljs + #?@(:cljd + [cljd.core/IHash (-hash [db] (hash-fdb db)) + cljd.core/IEquiv (-equiv [db other] (equiv-db db other)) + cljd.core/ICounted (-count [db] (count (-datoms db :eavt nil nil nil nil))) + cljd.core/IPrint (-print [db sink] (pr-db db sink)) + + cljd.core/IEmptyableCollection (-empty [_] (throw (UnsupportedError "-empty is not supported on FilteredDB"))) + + cljd.core/ILookup (-lookup [_ _] (throw (UnsupportedError "-lookup is not supported on FilteredDB"))) + (-lookup [_ _ _] (throw (UnsupportedError "-lookup is not supported on FilteredDB"))) + (-contains-key? [_ _] (throw (UnsupportedError "-contains-key? is not supported on FilteredDB"))) + + + cljd.core/IAssociative (-assoc [_ _ _] (throw (UnsupportedError "-assoc is not supported on FilteredDB")))] + :cljs [IHash (-hash [db] (hash-fdb db)) IEquiv (-equiv [db other] (equiv-db db other)) ICounted (-count [db] (count (-datoms db :eavt nil nil nil nil))) @@ -865,7 +1061,8 @@ (filter (.-pred db) (-index-range (.-unfiltered-db db) attr start end)))) (defn unfiltered-db ^DB [db] - (if (instance? FilteredDB db) + (if #?(:cljd (dart/is? db FilteredDB) + :default (instance? FilteredDB db)) (.-unfiltered-db ^FilteredDB db) db)) @@ -974,61 +1171,84 @@ (when (= :db.cardinality/many (:db/cardinality (get schema attr))) (util/raise a " :db/tupleAttrs can’t depend on :db.cardinality/many attribute: " attr ex-data)))))))) - + + (defn ^DB empty-db [schema opts] {:pre [(or (nil? schema) (map? schema))]} (validate-schema schema) (map->DB {:schema schema :rschema (rschema (merge implicit-schema schema)) - :eavt (set/sorted-set* (assoc opts :cmp cmp-datoms-eavt)) - :aevt (set/sorted-set* (assoc opts :cmp cmp-datoms-aevt)) - :avet (set/sorted-set* (assoc opts :cmp cmp-datoms-avet)) + :eavt #?(:cljd (sorted-set-by cmp-datoms-eavt) + :default (set/sorted-set* (assoc opts :cmp cmp-datoms-eavt))) + :aevt #?(:cljd + (sorted-set-by cmp-datoms-aevt) + :default + (set/sorted-set* (assoc opts :cmp cmp-datoms-aevt))) + :avet #?(:cljd + (sorted-set-by cmp-datoms-avet) + :default + (set/sorted-set* (assoc opts :cmp cmp-datoms-avet))) :max-eid e0 :max-tx tx0 :pull-patterns (lru/cache 100) :pull-attrs (lru/cache 100) :hash (atom 0)})) -(defn- init-max-eid [rschema eavt avet] - (let [max #(if (and %2 (> %2 %1)) %2 %1) - max-eid (some-> - (set/rslice eavt - (datom (dec tx0) nil nil txmax) - (datom e0 nil nil tx0)) - first :e) - res (max e0 max-eid) - max-ref (fn [attr] - (some-> - (set/rslice avet - (datom (dec tx0) attr (dec tx0) txmax) - (datom e0 attr e0 tx0)) - first :v)) - refs (:db.type/ref rschema) - res (reduce - (fn [res attr] - (max res (max-ref attr))) - res refs)] - res)) +#?(:cljd + (defn- init-max-eid [eavt] + (or (-> (set-rslice eavt (datom (dec tx0) nil nil txmax) (datom e0 nil nil tx0)) + (first) + (:e)) + e0)) + :default + (defn- init-max-eid [rschema eavt avet] + (let [max #(if (and %2 (> %2 %1)) %2 %1) + max-eid (some-> + (set/rslice eavt + (datom (dec tx0) nil nil txmax) + (datom e0 nil nil tx0)) + first :e) + res (max e0 max-eid) + max-ref (fn [attr] + (some-> + (set/rslice avet + (datom (dec tx0) attr (dec tx0) txmax) + (datom e0 attr e0 tx0)) + first :v)) + refs (:db.type/ref rschema) + res (reduce + (fn [res attr] + (max res (max-ref attr))) + res refs)] + res))) (defn ^DB init-db [datoms schema opts] (when-some [not-datom (first (drop-while datom? datoms))] - (util/raise "init-db expects list of Datoms, got " (type not-datom) + (util/raise "init-db expects list of Datoms, got " #?(:cljd + (some-> not-datom .-runtimeType) + :default (type not-datom)) {:error :init-db})) (validate-schema schema) (let [rschema (rschema (merge implicit-schema schema)) indexed (:db/index rschema) - arr (cond-> datoms - (not (arrays/array? datoms)) (arrays/into-array)) - _ (arrays/asort arr cmp-datoms-eavt-quick) - eavt (set/from-sorted-array cmp-datoms-eavt arr (arrays/alength arr) opts) - _ (arrays/asort arr cmp-datoms-aevt-quick) - aevt (set/from-sorted-array cmp-datoms-aevt arr (arrays/alength arr) opts) - avet-datoms (filter (fn [^Datom d] (contains? indexed (.-a d))) datoms) - avet-arr (to-array avet-datoms) - _ (arrays/asort avet-arr cmp-datoms-avet-quick) - avet (set/from-sorted-array cmp-datoms-avet avet-arr (arrays/alength avet-arr) opts) - max-eid (init-max-eid rschema eavt avet) + #?@(:cljd + [eavt (into (sorted-set-by cmp-datoms-eavt) datoms) + aevt (into (sorted-set-by cmp-datoms-aevt) datoms) + avet-datoms (filter (fn [^Datom d] (contains? indexed (.-a d))) datoms) + avet (into (sorted-set-by cmp-datoms-avet) avet-datoms)] + :default + [arr (cond-> datoms + (not (arrays/array? datoms)) (arrays/into-array)) + _ (arrays/asort arr cmp-datoms-eavt-quick) + eavt (set/from-sorted-array cmp-datoms-eavt arr (arrays/alength arr) opts) + _ (arrays/asort arr cmp-datoms-aevt-quick) + aevt (set/from-sorted-array cmp-datoms-aevt arr (arrays/alength arr) opts) + avet-datoms (filter (fn [^Datom d] (contains? indexed (.-a d))) datoms) + avet-arr (to-array avet-datoms) + _ (arrays/asort avet-arr cmp-datoms-avet-quick) + avet (set/from-sorted-array cmp-datoms-avet avet-arr (arrays/alength avet-arr) opts)]) + max-eid #?(:cljd (init-max-eid eavt) :default (init-max-eid rschema eavt avet)) max-tx (transduce (map (fn [^Datom d] (datom-tx d))) max tx0 eavt)] (map->DB {:schema schema @@ -1073,14 +1293,14 @@ (= (first xs) (first ys)) (recur (next xs) (next ys)) :else false))) -(defn+ ^:private ^number hash-db [^DB db] +(defn+ ^:private ^#?(:cljd int :default number) hash-db [^DB db] (let [h @(.-hash db)] (if (zero? h) (reset! (.-hash db) (combine-hashes (hash (.-schema db)) (hash (.-eavt db)))) h))) -(defn+ ^:private ^number hash-fdb [^FilteredDB db] +(defn+ ^:private ^#?(:cljd int :default number) hash-fdb [^FilteredDB db] (let [h @(.-hash db) datoms (or (-datoms db :eavt nil nil nil nil) #{})] (if (zero? h) @@ -1089,12 +1309,25 @@ (hash-unordered-coll datoms)))) h))) -(defn+ ^:private ^boolean equiv-db [db other] - (and (or (instance? DB other) (instance? FilteredDB other)) +(defn+ ^:private ^#?(:cljd bool :default boolean) equiv-db [db other] + (and #?(:cljd (or (dart/is? other DB) (dart/is? other FilteredDB)) + :default (or (instance? DB other) (instance? FilteredDB other))) (= (-schema db) (-schema other)) (equiv-db-index (-datoms db :eavt nil nil nil nil) (-datoms other :eavt nil nil nil nil)))) -#?(:cljs +#?(:cljd + (defn pr-db [db ^StringSink sink] + (.write sink "#datascript/DB {") + (.write sink ":schema ") + (-print (-schema db) sink) + (.write sink (str ", :datoms [")) + (loop [[^Datom d & more] (-datoms db :eavt nil nil nil nil)] + (-print [(.-e d) (.-a d) (.-v d) (datom-tx d)] sink) + (when more + (.write sink " ") + (recur more))) + (.write sink "]}")) + :cljs (defn+ pr-db [db w opts] (-write w "#datascript/DB {") (-write w ":schema ") @@ -1104,9 +1337,8 @@ (fn [d w opts] (pr-sequential-writer w pr-writer "[" " " "]" opts [(.-e d) (.-a d) (.-v d) (datom-tx d)])) "[" " " "]" opts (-datoms db :eavt nil nil nil nil)) - (-write w "}"))) - -#?(:clj + (-write w "}")) + :clj (do (defn pr-db [db, ^java.io.Writer w] (.write w (str "#datascript/DB {")) @@ -1125,11 +1357,9 @@ ;; ---------------------------------------------------------------------------- -#?(:clj (declare entid-strict) - :cljs (defn ^number entid-strict [db eid])) +(declare+ ^#?(:cljd int :default number) entid-strict [db eid]) -#?(:clj (declare ref?) - :cljs (defn ^boolean ref? [db attr])) +(declare+ ^#?(:cljd bool :default boolean) ref? [db attr]) (defn+ resolve-datom [db e a v t default-e default-tx] (when (some? a) @@ -1151,10 +1381,14 @@ (defn find-datom [db index c0 c1 c2 c3] (validate-indexed db index c0 c1 c2 c3) (let [set (get db index) - cmp #?(:clj (.comparator ^clojure.lang.Sorted set) :cljs (.-comparator set)) - from (components->pattern db index c0 c1 c2 c3 e0 tx0) - to (components->pattern db index c0 c1 c2 c3 emax txmax) - datom (some-> set seq (set/seek from) first)] + cmp #?(:cljd (.-cmpf ^HashRankedWideTreapSet set) + :clj (.comparator ^clojure.lang.Sorted set) :cljs (.-comparator set)) + from #?(:cljd (min-datom (components->pattern db index c0 c1 c2 c3 e0 tx0)) + :default (components->pattern db index c0 c1 c2 c3 e0 tx0)) + to #?(:cljd (max-datom (components->pattern db index c0 c1 c2 c3 emax txmax)) + :default (components->pattern db index c0 c1 c2 c3 emax txmax)) + datom (first #?(:cljd (subseq set >= from) + :default (some-> set seq (set/seek from))))] (when (and (some? datom) (<= 0 (cmp to datom))) datom))) @@ -1162,35 +1396,35 @@ (defrecord TxReport [db-before db-after tx-data tempids tx-meta]) -(defn+ ^boolean is-attr? [db attr property] +(defn+ ^#?(:cljd bool :default boolean) is-attr? [db attr property] (contains? (-attrs-by db property) attr)) -(defn+ ^boolean multival? [db attr] +(defn+ ^#?(:cljd bool :default boolean) multival? [db attr] (is-attr? db attr :db.cardinality/many)) -(defn+ ^boolean multi-value? [db attr value] +(defn+ ^#?(:cljd bool :default boolean) multi-value? [db attr value] (and (is-attr? db attr :db.cardinality/many) (or - (arrays/array? value) + #?(:cljd (dart/is? value List) :default (arrays/array? value)) (and (coll? value) (not (map? value)))))) -(defn+ ^boolean ref? [db attr] +(defn+ ^#?(:cljd bool :default boolean) ref? [db attr] (is-attr? db attr :db.type/ref)) -(defn+ ^boolean component? [db attr] +(defn+ ^#?(:cljd bool :default boolean) component? [db attr] (is-attr? db attr :db/isComponent)) -(defn+ ^boolean indexing? [db attr] +(defn+ ^#?(:cljd bool :default boolean) indexing? [db attr] (is-attr? db attr :db/index)) -(defn+ ^boolean tuple? [db attr] +(defn+ ^#?(:cljd bool :default boolean) tuple? [db attr] (is-attr? db attr :db.type/tuple)) -(defn+ ^boolean tuple-source? [db attr] +(defn+ ^#?(:cljd bool :default boolean) tuple-source? [db attr] (is-attr? db attr :db/attrTuples)) -(defn+ ^boolean reverse-ref? [attr] +(defn+ ^#?(:cljd bool :default boolean) reverse-ref? [attr] (cond (keyword? attr) (= \_ (nth (name attr) 0)) @@ -1227,37 +1461,39 @@ v)) (-> db -schema (get a) :db/tupleAttrs) vs)) -(defn+ ^number entid [db eid] +(defn+ ^#?(:cljd int? :default number) entid [db eid] {:pre [(db? db)]} (cond (and (number? eid) (pos? eid)) (if (> eid emax) (util/raise "Highest supported entity id is " emax ", got " eid {:error :entity-id :value eid}) - eid) - + #?(:cljd (int eid) :default eid)) + + (sequential? eid) (let [[attr value] eid] (cond (not= (count eid) 2) (util/raise "Lookup ref should contain 2 elements: " eid {:error :lookup-ref/syntax, :entity-id eid}) - + (not (is-attr? db attr :db/unique)) (util/raise "Lookup ref attribute should be marked as :db/unique: " eid {:error :lookup-ref/unique, :entity-id eid}) - + (tuple? db attr) (let [value' (resolve-tuple-refs db attr value)] (-> (-datoms db :avet attr value' nil nil) first :e)) - + (nil? value) nil - + :else (-> (-datoms db :avet attr value nil nil) first :e))) - + + #?@(:cljs [(array? eid) (recur db (array-seq eid))]) - + (keyword? eid) (-> (-datoms db :avet :db/ident eid nil nil) first :e) @@ -1265,17 +1501,17 @@ (util/raise "Expected number or lookup ref for entity id, got " eid {:error :entity-id/syntax, :entity-id eid}))) -(defn+ ^boolean numeric-eid-exists? [db eid] +(defn+ ^#?(:cljd bool :default boolean) numeric-eid-exists? [db eid] (= eid (-> (-seek-datoms db :eavt eid nil nil nil) first :e))) -(defn+ ^number entid-strict [db eid] +(defn+ ^#?(:cljd int :default number) entid-strict [db eid] (or (entid db eid) (util/raise "Nothing found for entity id " eid {:error :entity-id/missing :entity-id eid}))) -(defn+ ^number entid-some [db eid] +(defn+ ^#?(:cljd int? :default number) entid-some [db eid] (when (some? eid) (entid-strict db eid))) @@ -1357,16 +1593,26 @@ :datom datom})))) (defn- current-tx - #?(:clj {:inline (fn [report] `(-> ~report :db-before :max-tx long inc))}) - ^long [report] - (-> report :db-before :max-tx long inc)) + #?(:cljd {} + :clj {:inline (fn [report] `(-> ~report :db-before :max-tx long inc))}) + ^#?(:cljd int :default long) [report] + (-> report :db-before :max-tx #?(:cljd int :default long) inc)) (defn- next-eid - #?(:clj {:inline (fn [db] `(inc (long (:max-eid ~db))))}) - ^long [db] - (inc (long (:max-eid db)))) + #?(:cljd {} + :clj {:inline (fn [db] `(inc (long (:max-eid ~db))))}) + ^#?(:cljd int :default long) [db] + (inc (#?(:cljd int :default long) (:max-eid db)))) -#?(:clj +#?(:cljd + (defn- ^bool tx-id? + [e] + (or (identical? :db/current-tx e) + (.== ":db/current-tx" e) ;; for datascript.js interop + (.== "datomic.tx" e) + (.== "datascript.tx" e))) + + :clj (defn- ^Boolean tx-id? [e] (or (identical? :db/current-tx e) @@ -1382,8 +1628,7 @@ (= e "datomic.tx") (= e "datascript.tx")))) -(defn- #?@(:clj [^Boolean tempid?] - :cljs [^boolean tempid?]) +(defn- ^#?(:cljd bool :clj Boolean :cljs boolean) tempid? [x] (or (and (number? x) (neg? x)) @@ -1428,16 +1673,16 @@ (let [indexing? (indexing? db (.-a datom))] (if (datom-added datom) (cond-> db - true (update :eavt set/conj datom cmp-datoms-eavt-quick) - true (update :aevt set/conj datom cmp-datoms-aevt-quick) - indexing? (update :avet set/conj datom cmp-datoms-avet-quick) + true (update :eavt #?@(:cljd (conj datom) :default (set/conj datom cmp-datoms-eavt-quick))) + true (update :aevt #?@(:cljd (conj datom) :default (set/conj datom cmp-datoms-aevt-quick))) + indexing? (update :avet #?@(:cljd (conj datom) :default (set/conj datom cmp-datoms-avet-quick))) true (advance-max-eid (.-e datom)) true (assoc :hash (atom 0))) (if-some [removing (fsearch db [(.-e datom) (.-a datom) (.-v datom)])] (cond-> db - true (update :eavt set/disj removing cmp-datoms-eavt-quick) - true (update :aevt set/disj removing cmp-datoms-aevt-quick) - indexing? (update :avet set/disj removing cmp-datoms-avet-quick) + true (update :eavt #?@(:cljd (disj removing) :default (set/disj removing cmp-datoms-eavt-quick))) + true (update :aevt #?@(:cljd (disj removing) :default (set/disj removing cmp-datoms-aevt-quick))) + indexing? (update :avet #?@(:cljd (disj removing) :default (set/disj removing cmp-datoms-avet-quick))) true (assoc :hash (atom 0))) db)))) @@ -1470,6 +1715,8 @@ (update report' ::queued-tuples assoc e queue')) report'))) + + (defn- resolve-upserts "Returns [entity' upserts]. Upsert attributes that resolve to existing entities are removed from entity, rest are kept in entity for insertion. No validation is performed. @@ -1518,7 +1765,7 @@ [entity nil])) (defn validate-upserts - "Throws if not all upserts point to the same entity. + "Throws if not all upserts point to the same entity. Returns single eid that all upserts point to, or null." [entity upserts] (let [upsert-ids (reduce-kv @@ -1557,39 +1804,44 @@ [vs] ;; not a collection at all, so definitely a single value - (not (or (arrays/array? vs) + (not (or #?(:cljd (dart/is? vs List) :default (arrays/array? vs)) (and (coll? vs) (not (map? vs))))) [vs] - + ;; probably lookup ref (and (= (count vs) 2) (is-attr? db (first vs) :db.unique/identity)) [vs] - + :else vs)) (defn- explode [db entity] (let [eid (:db/id entity) - ;; sort tuple attrs after non-tuple + ;; sort: non-ref non-tuple, ref non-tuple, tuple + ;; non-ref first so entity's own tempid gets allocated before ref value tempids a+vs (apply concat (reduce (fn [acc [a vs]] - (update acc (if (tuple? db a) 1 0) conj [a vs])) - [[] []] entity))] - (for [[a vs] a+vs - :when (not= a :db/id) - :let [_ (validate-attr a {:db/id eid, a vs}) - reverse? (reverse-ref? a) - straight-a (if reverse? (reverse-ref a) a) - _ (when (and reverse? (not (ref? db straight-a))) - (util/raise "Bad attribute " a ": reverse attribute name requires {:db/valueType :db.type/ref} in schema" - {:error :transact/syntax, :attribute a, :context {:db/id eid, a vs}}))] - v (maybe-wrap-multival db a vs)] - (if (and (ref? db straight-a) (map? v)) ;; another entity specified as nested map - (assoc v (reverse-ref a) eid) - (if reverse? - [:db/add v straight-a eid] - [:db/add eid straight-a v]))))) + (update acc (cond (tuple? db a) 2 (ref? db a) 1 :else 0) conj [a vs])) + [[] [] []] entity)) + result + (for [[a vs] a+vs + :when (not= a :db/id) + :let [_ (validate-attr a {:db/id eid, a vs}) + reverse? (reverse-ref? a) + straight-a (if reverse? (reverse-ref a) a) + _ (when (and reverse? (not (ref? db straight-a))) + (util/raise "Bad attribute " a ": reverse attribute name requires {:db/valueType :db.type/ref} in schema" + {:error :transact/syntax, :attribute a, :context {:db/id eid, a vs}}))] + v (maybe-wrap-multival db a vs)] + (if (and (ref? db straight-a) (map? v)) ;; another entity specified as nested map + (assoc v (reverse-ref a) eid) + (if reverse? + [:db/add v straight-a eid] + [:db/add eid straight-a v])))] + ;; ensure non-map datom ops come before nested map entities + ;; so parent entity gets allocated an eid before children + (concat (remove map? result) (filter map? result)))) (defn- transact-add [report [_ e a v tx :as ent]] (validate-attr a ent) @@ -1600,7 +1852,7 @@ v (if (ref? db a) (entid-strict db v) v) new-datom (datom e a v tx) multival? (multival? db a) - old-datom ^Datom (if multival? + old-datom ^#?(:cljd Datom? :default Datom) (if multival? (fsearch db [e a v]) (fsearch db [e a]))] (cond @@ -1734,13 +1986,14 @@ (let [id (current-tx report)] (recur (allocate-eid report old-eid id) (cons (assoc entity :db/id id) entities))) - + ;; lookup-ref => resolved | error (sequential? old-eid) (let [id (entid-strict db old-eid)] (recur report (cons (assoc entity :db/id id) entities))) - + + ;; upserted => explode | error :let [[entity' upserts] (resolve-upserts db entity) upserted-eid (validate-upserts entity' upserts)] @@ -1756,7 +2009,7 @@ (allocate-eid old-eid upserted-eid) (update ::tx-redundant util/conjv (datom upserted-eid nil nil tx0))) (concat (explode db (assoc entity' :db/id upserted-eid)) entities))) - + ;; resolved | allocated-tempid | tempid | nil => explode (or (number? old-eid) @@ -1764,7 +2017,8 @@ (string? old-eid) (auto-tempid? old-eid)) (recur report (concat (explode db entity) entities)) - + + ;; trash => error :else (util/raise "Expected number, string or lookup ref for :db/id, got " old-eid @@ -1776,7 +2030,8 @@ (= op :db.fn/call) (let [[_ f & args] entity] (recur report (concat (assoc-auto-tempids db (apply f db args)) entities))) - + + (and (keyword? op) (not (builtin-fn? op))) (if-some [ident (entid db op)] @@ -1788,7 +2043,8 @@ {:error :transact/syntax, :operation :db.fn/call, :tx-data entity}))) (util/raise "Can’t find entity for transaction fn " op {:error :transact/syntax, :operation :db.fn/call, :tx-data entity})) - + + (and (tempid? e) (not= op :db/add)) (util/raise "Can't use tempid in '" entity "'. Tempids are allowed in :db/add only" @@ -1869,7 +2125,7 @@ (if (and (= (count tuple-attrs) (count v)) (every? some? v) - (every? + (every? (fn [[tuple-attr tuple-value]] (let [db-value (:v (first (-datoms db :eavt e tuple-attr nil nil)))] (= tuple-value db-value))) @@ -1911,7 +2167,7 @@ :else (util/raise "Unknown operation at " entity ", expected :db/add, :db/retract, :db.fn/call, :db.fn/retractAttribute, :db.fn/retractEntity or an ident corresponding to an installed transaction function (e.g. {:db/ident :db/fn }, usage of :db/ident requires {:db/unique :db.unique/identity} in schema)" {:error :transact/syntax, :operation op, :tx-data entity}))) - + (datom? entity) (let [[e a v tx added] entity] (if added diff --git a/src/datascript/impl/entity.cljc b/src/datascript/impl/entity.cljc index feed9d93..1138135f 100644 --- a/src/datascript/impl/entity.cljc +++ b/src/datascript/impl/entity.cljc @@ -1,7 +1,8 @@ (ns ^:no-doc datascript.impl.entity (:refer-clojure :exclude [keys get]) - (:require [#?(:cljs cljs.core :clj clojure.core) :as c] - [datascript.db :as db])) + (:require [#?(:cljd cljd.core :cljs cljs.core :clj clojure.core) :as c] + [datascript.db :as db] + #?(:cljd ["dart:collection" :as dart-coll]))) (declare entity ->Entity equiv-entity lookup-entity touch hash-entity) @@ -55,8 +56,73 @@ (fn [] (this-as this# this#)))) -(deftype Entity [db eid touched cache] - #?@(:cljs +(deftype #?(:cljd #/(Entity K V) :default Entity) [db eid touched cache] + #?@(:cljd + [^:mixin c/ToStringMixin + ^:mixin c/EqualsEquivMixin + + ;; dart map + ;; ^:mixin #/(dart-coll/MapMixin K V) + ^:mixin ^{:type-params [K V]} dart-coll/MapMixin + (entries [coll] + ;; ^#/(Map K V) + (let [^^{:type-params [K V]} Map m @cache] + (.-entries m))) + ("[]" [coll k] + (-lookup coll k nil)) + ("[]=" [coll key val] + (throw (UnsupportedError. "[]= not supported on Entity"))) + (remove [coll val] + (throw (UnsupportedError. "remove not supported on Entity"))) + (clear [coll] + (throw (UnsupportedError. "clear not supported on Entity"))) + (keys [coll] + (let [^^{:type-params [K V]} Map m @cache] + (.-keys m))) + (values [coll] + (let [^^{:type-params [K V]} Map m @cache] + (.-values m))) + ;; (^#/(Entity RK RV) #/(cast RK RV) [coll] + ;; (new #/(Entity RK RV) db eid touched cache)) + (^^{:type-params [RK RV]} Entity ^{:type-params [RK RV]} cast [coll] + (new #/(Entity RK RV) db eid touched cache)) + + cljd.core/IEquiv + (-equiv [this o] (equiv-entity this o)) + + cljd.core/IHash + (-hash [this] + (hash-entity this)) + + cljd.core/ISeqable + (-seq [this] + (touch this) + (seq @cache)) + + cljd.core/ICounted + (-count [this] + (touch this) + (count @cache)) + + cljd.core/ILookup + (-lookup [this attr] (lookup-entity this attr nil)) + (-lookup [this attr not-found] (lookup-entity this attr not-found)) + + cljd.core/ILookup + (-contains-key? [this k] + (not= ::nf (lookup-entity this k ::nf))) + + cljd.core/IFn + (-invoke [this k] + (lookup-entity this k)) + (-invoke [this k not-found] + (lookup-entity this k not-found)) + + cljd.core/IPrint + (-print [_ sink] + (-print (assoc @cache :db/id eid) sink))] + + :cljs [Object (toString [this] (pr-str* this)) @@ -161,7 +227,8 @@ (fn [] (this-as this# (.entries this#))))) -#?(:clj +#?(:cljd nil + :clj (defmethod print-method Entity [e, ^java.io.Writer w] (.write w (str e)))) @@ -175,7 +242,8 @@ (db/combine-hashes (hash (.-eid e)) ;; A hash compatible with `identical?`. Consistent with `=`. - (#?(:clj System/identityHashCode :cljs goog/getUid) (.-db e)))) + (#?(:cljd dart:core/identityHashCode + :clj System/identityHashCode :cljs goog/getUid) (.-db e)))) (defn- lookup-entity ([this attr] (lookup-entity this attr nil)) @@ -211,7 +279,7 @@ (assoc acc a (entity-attr db a part)))) {} (partition-by :a datoms))) -(defn touch [^Entity e] +(defn touch [^#?(:cljd Entity? :default Entity) e] {:pre [(or (nil? e) (entity? e))]} (when (some? e) (when-not @(.-touched e) diff --git a/src/datascript/lru.cljc b/src/datascript/lru.cljc index cadb89b1..e9d29771 100644 --- a/src/datascript/lru.cljc +++ b/src/datascript/lru.cljc @@ -13,6 +13,17 @@ IPrintWithWriter (-pr-writer [_ writer opts] (-pr-writer key-value writer opts))) + :cljd + (deftype LRU [key-value gen-key key-gen gen limit] + cljd.core/IAssociative + (-assoc [this k v] (assoc-lru this k v)) + cljd.core/ILookup + (-lookup [_ k] (-lookup key-value k nil)) + (-lookup [_ k nf] (-lookup key-value k nf)) + (-contains-key? [_ k] (-contains-key? key-value k)) + cljd.core/IPrint + (-print [_ sink] + (-print key-value sink))) :clj (deftype LRU [^clojure.lang.Associative key-value gen-key key-gen gen limit] clojure.lang.ILookup diff --git a/src/datascript/parser.cljc b/src/datascript/parser.cljc index 1b1447ac..58092f29 100644 --- a/src/datascript/parser.cljc +++ b/src/datascript/parser.cljc @@ -25,7 +25,7 @@ `(defrecord ~tagname ~fields ITraversable (~'-postwalk [this# ~f] - (let [new# (new ~tagname ~@(map #(list 'datascript.parser/postwalk % f) fields))] + (let [new# (~(symbol (str "->" tagname)) ~@(map #(list 'datascript.parser/postwalk % f) fields))] (if-let [meta# (meta this#)] (with-meta new# meta#) new#))) @@ -94,12 +94,12 @@ (defn parse-placeholder [form] (when (= '_ form) - (Placeholder.))) + (->Placeholder))) (defn parse-variable [form] (when (and (symbol? form) (= (first (name form)) \?)) - (Variable. form))) + (->Variable form))) (defn parse-var-required [form] (or (parse-variable form) @@ -109,16 +109,16 @@ (defn parse-src-var [form] (when (and (symbol? form) (= (first (name form)) \$)) - (SrcVar. form))) + (->SrcVar form))) (defn parse-rules-var [form] (when (= '% form) - (RulesVar.))) + (->RulesVar))) (defn parse-constant [form] (when-not (and (symbol? form) (= (first (name form)) \?)) - (Constant. form))) + (->Constant form))) (defn parse-plain-symbol [form] (when (and (symbol? form) @@ -126,11 +126,11 @@ (not (parse-src-var form)) (not (parse-rules-var form)) (not (parse-placeholder form))) - (PlainSymbol. form))) + (->PlainSymbol form))) (defn parse-plain-variable [form] (when (parse-plain-symbol form) - (Variable. form))) + (->Variable form))) @@ -158,7 +158,7 @@ (when-not (distinct? (concat required* free*)) (util/raise "Rule variables should be distinct" {:error :parser/rule-vars, :form form})) - (RuleVars. required* free*)) + (->RuleVars required* free*)) (util/raise "Cannot parse rule-vars, expected [ variable+ | ([ variable+ ] variable*) ]" {:error :parser/rule-vars, :form form}))) @@ -185,17 +185,17 @@ (defn parse-bind-ignore [form] (when (= '_ form) - (with-source (BindIgnore.) form))) + (with-source (->BindIgnore) form))) (defn parse-bind-scalar [form] (when-let [var (parse-variable form)] - (with-source (BindScalar. var) form))) + (with-source (->BindScalar var) form))) (defn parse-bind-coll [form] (when (and (of-size? form 2) (= (second form) '...)) (if-let [sub-bind (parse-binding (first form))] - (with-source (BindColl. sub-bind) form) + (with-source (->BindColl sub-bind) form) (util/raise "Cannot parse collection binding" {:error :parser/binding, :form form})))) @@ -206,7 +206,7 @@ (defn parse-bind-tuple [form] (when-let [sub-bindings (parse-seq parse-tuple-el form)] (if-not (empty? sub-bindings) - (with-source (BindTuple. sub-bindings) form) + (with-source (->BindTuple sub-bindings) form) (util/raise "Tuple binding cannot be empty" {:error :parser/binding, :form form})))) @@ -214,7 +214,7 @@ (when (and (of-size? form 1) (sequential? (first form))) ;; relation is just a sequence of tuples - (with-source (BindColl. (parse-bind-tuple (first form))) form))) + (with-source (->BindColl (parse-bind-tuple (first form))) form))) (defn parse-binding [form] (or (parse-bind-coll form) @@ -231,7 +231,7 @@ ;; find-coll = [ find-elem '...' ] ;; find-scalar = find-elem '.' ;; find-tuple = [ find-elem+ ] -;; find-elem = (variable | pull-expr | aggregate | custom-aggregate) +;; find-elem = (variable | pull-expr | aggregate | custom-aggregate) ;; pull-expr = [ 'pull' src-var? variable pull-pattern ] ;; pull-pattern = (constant | variable | plain-symbol) ;; aggregate = [ aggregate-fn fn-arg+ ] @@ -282,7 +282,7 @@ fn* (parse-plain-symbol fn) args* (parse-seq parse-fn-arg args)] (when (and fn* args*) - (Aggregate. fn* args*))))) + (->Aggregate fn* args*))))) (defn parse-aggregate-custom [form] (when (and (sequential? form) @@ -292,7 +292,7 @@ fn* (parse-variable fn) args* (parse-seq parse-fn-arg args)] (if (and fn* args*) - (Aggregate. fn* args*) + (->Aggregate fn* args*) (util/raise "Cannot parse custom aggregate call, expect ['aggregate' variable fn-arg+]" {:error :parser/find, :fragment form}))) (util/raise "Cannot parse custom aggregate call, expect ['aggregate' variable fn-arg+]" @@ -305,13 +305,13 @@ (let [long? (= (count form) 4) src (if long? (nth form 1) '$) [var pattern] (if long? (nnext form) (next form)) - src* (parse-src-var src) + src* (parse-src-var src) var* (parse-variable var) pattern* (or (parse-variable pattern) (parse-plain-variable pattern) (parse-constant pattern))] (if (and src* var* pattern*) - (Pull. src* var* pattern*) + (->Pull src* var* pattern*) (util/raise "Cannot parse pull expression, expect ['pull' src-var? variable (constant | variable | plain-symbol)]" {:error :parser/find, :fragment form}))) (util/raise "Cannot parse pull expression, expect ['pull' src-var? variable (constant | variable | plain-symbol)]" @@ -326,7 +326,7 @@ (defn parse-find-rel [form] (some-> (parse-seq parse-find-elem form) - (FindRel.))) + (->FindRel))) (defn parse-find-coll [form] (when (and (sequential? form) @@ -336,14 +336,14 @@ (= (count inner) 2) (= (second inner) '...)) (some-> (parse-find-elem (first inner)) - (FindColl.)))))) + (->FindColl)))))) (defn parse-find-scalar [form] (when (and (sequential? form) (= (count form) 2) (= (second form) '.)) (some-> (parse-find-elem (first form)) - (FindScalar.)))) + (->FindScalar)))) (defn parse-find-tuple [form] (when (and (sequential? form) @@ -351,7 +351,7 @@ (let [inner (first form)] (some-> (parse-seq parse-find-elem inner) - (FindTuple.))))) + (->FindTuple))))) (defn parse-find [form] (or (parse-find-rel form) @@ -373,9 +373,9 @@ (when (and (not (empty? form)) (every? symbol? form)) (case type - :keys (ReturnMap. type (mapv keyword form)) - :syms (ReturnMap. type (vec form)) - :strs (ReturnMap. type (mapv str form)) + :keys (->ReturnMap type (mapv keyword form)) + :syms (->ReturnMap type (vec form)) + :strs (->ReturnMap type (mapv str form)) nil))) ;; with = [ variable+ ] @@ -393,7 +393,7 @@ (if-let [var (or (parse-src-var form) (parse-rules-var form) (parse-plain-variable form))] - (with-source (BindScalar. var) form) + (with-source (->BindScalar var) form) (parse-binding form))) (defn parse-in [form] @@ -418,7 +418,7 @@ (deftrecord Pattern [source pattern]) (deftrecord Predicate [fn args]) -(deftrecord Function [fn args binding]) +(deftrecord FunctionCall [fn args binding]) (deftrecord RuleExpr [source name args]) ;; TODO rule with constant or '_' as argument (deftrecord Not [source vars clauses]) (deftrecord Or [source rule-vars clauses]) @@ -433,13 +433,13 @@ (when (sequential? form) (if-let [source* (parse-src-var (first form))] [source* (next form)] - [(DefaultSrc.) form]))) - + [(->DefaultSrc) form]))) + (defn parse-pattern [form] (when-let [[source* next-form] (take-source form)] (when-let [pattern* (parse-seq parse-pattern-el next-form)] (if-not (empty? pattern*) - (with-source (Pattern. source* pattern*) form) + (with-source (->Pattern source* pattern*) form) (util/raise "Pattern could not be empty" {:error :parser/where, :form form}))))) @@ -456,7 +456,7 @@ (defn parse-pred [form] (when (of-size? form 1) (when-let [[fn* args*] (parse-call (first form))] - (-> (Predicate. fn* args*) + (-> (->Predicate fn* args*) (with-source form))))) (defn parse-fn [form] @@ -464,7 +464,7 @@ (let [[call binding] form] (when-let [[fn* args*] (parse-call call)] (when-let [binding* (parse-binding binding)] - (-> (Function. fn* args* binding*) + (-> (->FunctionCall fn* args* binding*) (with-source form))))))) (defn parse-rule-expr [form] @@ -481,7 +481,7 @@ (util/raise "Cannot parse rule-expr arguments, expected [ (variable | constant | '_')+ ]" {:error :parser/where, :form form}) :else - (RuleExpr. source* name* args*)))))) + (->RuleExpr source* name* args*)))))) (defn- collect-vars-acc [acc form] (cond @@ -499,7 +499,7 @@ (defn- collect-vars [form] (collect-vars-acc [] form)) - + (defn collect-vars-distinct [form] (vec (distinct (collect-vars form)))) @@ -517,7 +517,7 @@ (let [[sym & clauses] next-form] (when (= 'not sym) (if-let [clauses* (parse-clauses clauses)] - (-> (Not. source* (collect-vars-distinct clauses*) clauses*) + (-> (->Not source* (collect-vars-distinct clauses*) clauses*) (with-source form) (validate-not form)) (util/raise "Cannot parse 'not' clause, expected [ src-var? 'not' clause+ ]" @@ -530,7 +530,7 @@ (let [vars* (parse-seq parse-variable vars) clauses* (parse-clauses clauses)] (if (and vars* clauses*) - (-> (Not. source* vars* clauses*) + (-> (->Not source* vars* clauses*) (with-source form) (validate-not form)) (util/raise "Cannot parse 'not-join' clause, expected [ src-var? 'not-join' [variable+] clause+ ]" @@ -547,7 +547,7 @@ (= 'and (first form))) (let [clauses* (parse-clauses (next form))] (if (not-empty clauses*) - (And. clauses*) + (->And clauses*) (util/raise "Cannot parse 'and' clause, expected [ 'and' clause+ ]" {:error :parser/where, :form form}))))) @@ -556,7 +556,7 @@ (let [[sym & clauses] next-form] (when (= 'or sym) (if-let [clauses* (parse-seq (some-fn parse-and parse-clause) clauses)] - (-> (Or. source* (RuleVars. nil (collect-vars-distinct clauses*)) clauses*) + (-> (->Or source* (->RuleVars nil (collect-vars-distinct clauses*)) clauses*) (with-source form) (validate-or form)) (util/raise "Cannot parse 'or' clause, expected [ src-var? 'or' clause+ ]" @@ -569,7 +569,7 @@ (let [vars* (parse-rule-vars vars) clauses* (parse-seq (some-fn parse-and parse-clause) clauses)] (if (and vars* clauses*) - (-> (Or. source* vars* clauses*) + (-> (->Or source* vars* clauses*) (with-source form) (validate-or form)) (util/raise "Cannot parse 'or-join' clause, expected [ src-var? 'or-join' [variable+] clause+ ]" @@ -604,7 +604,7 @@ :vars missing})))))))) (defn parse-clause [form] - (or + (or (parse-not form) (parse-not-join form) (parse-or form) @@ -666,10 +666,10 @@ (vec ;; group rule branches by name (for [[name branches] (group-by :name (parse-seq parse-rule form)) - :let [branches (mapv #(RuleBranch. (:vars %) (:clauses %)) branches)]] + :let [branches (mapv #(->RuleBranch (:vars %) (:clauses %)) branches)]] (do (validate-arity name branches) - (Rule. name branches))))) + (->Rule name branches))))) ;; query @@ -735,7 +735,7 @@ (count))) (util/raise "Only one of :keys/:syms/:strs must be present" {:error :parser/query, :form form})) - + (let [in-vars (collect-vars (:qin q)) in-sources (collect #(instance? SrcVar %) (:qin q)) in-rules (collect #(instance? RulesVar %) (:qin q))] @@ -744,19 +744,21 @@ (distinct? in-rules)) (util/raise "Vars used in :in should be distinct" {:error :parser/query, :form form}))) - + (let [with-vars (collect-vars (:qwith q))] (when-not (distinct? with-vars) (util/raise "Vars used in :with should be distinct" {:error :parser/query, :form form}))) - + + (let [in-sources (collect #(instance? SrcVar %) (:qin q) #{}) where-sources (collect #(instance? SrcVar %) (:qwhere q) #{}) unknown (set/difference where-sources in-sources)] (when-not (empty? unknown) (util/raise "Where uses unknown source vars: " (mapv :symbol unknown) {:error :parser/query, :vars unknown, :form form}))) - + + (let [rule-exprs (collect #(instance? RuleExpr %) (:qwhere q)) rules-vars (collect #(instance? RulesVar %) (:qin q))] (when (and (not (empty? rule-exprs)) diff --git a/src/datascript/pull_api.cljc b/src/datascript/pull_api.cljc index 478eda83..eacbfe60 100644 --- a/src/datascript/pull_api.cljc +++ b/src/datascript/pull_api.cljc @@ -1,12 +1,16 @@ (ns ^:no-doc datascript.pull-api (:require [clojure.string :as str] - [datascript.pull-parser :as dpp] - [datascript.db :as db #?@(:cljs [:refer [DB]])] + [datascript.pull-parser :as dpp #?@(:cljd [:refer [PullAttr PullPattern]])] + #?(:cljd [datascript.db :as db :refer [DB Datom]] + :clj [datascript.db :as db #?@(:cljs [:refer [DB]])] + :cljs [datascript.db :as db :refer [DB]]) [datascript.lru :as lru] [datascript.util :as util] - [me.tonsky.persistent-sorted-set :as set]) - #?(:clj + #?(:cljd nil + :default [me.tonsky.persistent-sorted-set :as set])) + #?(:cljd nil + :clj (:import [clojure.lang ISeq] [datascript.db Datom DB] @@ -14,20 +18,20 @@ (declare pull-impl attrs-frame ref-frame ->ReverseAttrsFrame) -(defn- first-seq [#?(:clj ^ISeq xs :cljs ^seq xs)] +(defn- first-seq [#?(:cljd ^some xs :clj ^ISeq xs :cljs ^seq xs)] (if (nil? xs) nil - #?(:clj (.first xs) :cljs (-first xs)))) + #?(:cljd (-first xs) :clj (.first xs) :cljs (-first xs)))) -(defn- next-seq [#?(:clj ^ISeq xs :cljs ^seq xs)] +(defn- next-seq [#?(:cljd ^some xs :clj ^ISeq xs :cljs ^seq xs)] (if (nil? xs) nil - #?(:clj (.next xs) :cljs (-next xs)))) + #?(:cljd (-next xs) :clj (.next xs) :cljs (-next xs)))) -(defn- conj-seq [#?(:clj ^ISeq xs :cljs ^seq xs) x] +(defn- conj-seq [#?(:cljd ^some xs :clj ^ISeq xs :cljs ^seq xs) x] (if (nil? xs) (list x) - #?(:clj (.cons xs x) :cljs (-conj xs x)))) + #?(:cljd (-conj xs x) :clj (.cons xs x) :cljs (-conj xs x)))) (defn- assoc-some! [m k v] (if (nil? v) m (assoc! m k v))) @@ -60,63 +64,63 @@ (loop [acc acc datoms datoms] (util/cond+ - :let [^Datom datom (first-seq datoms)] + :let [#?(:cljd ^Datom? datom :default ^Datom datom) (first-seq datoms)] (or (nil? datom) (not= (.-a datom) (.-name attr))) - [(ResultFrame. (not-empty (persistent! acc)) (or datoms ()))] - - ; got limit, skip rest of the datoms - (and (.-limit attr) (>= (count acc) (.-limit attr))) - (loop [datoms datoms] - (let [^Datom datom (first-seq datoms)] - (if (or (nil? datom) (not= (.-a datom) (.-name attr))) - [(ResultFrame. (persistent! acc) (or datoms ()))] - (recur (next-seq datoms))))) - - :else - (recur (conj! acc (.-v datom)) (next-seq datoms))))) - + [(#?(:cljd ->ResultFrame :default ResultFrame.) (not-empty (persistent! acc)) (or datoms ()))] + +; got limit, skip rest of the datoms + (and (.-limit attr) (>= (count acc) (.-limit attr))) + (loop [datoms datoms] + (let [#?(:cljd ^Datom? datom :default ^Datom datom) (first-seq datoms)] + (if (or (nil? datom) (not= (.-a datom) (.-name attr))) + [(#?(:cljd ->ResultFrame :default ResultFrame.) (persistent! acc) (or datoms ()))] + (recur (next-seq datoms))))) + + :else + (recur (conj! acc (.-v datom)) (next-seq datoms))))) + (-str [this] (str "MultivalAttrFrame"))) (defrecord MultivalRefAttrFrame [seen recursion-limits acc pattern ^PullAttr attr datoms] IFrame (-merge [this result] - (MultivalRefAttrFrame. - seen - recursion-limits - (conj-some! acc (.-value ^ResultFrame result)) - pattern - attr - (next-seq datoms))) - + (#?(:cljd ->MultivalRefAttrFrame :default MultivalRefAttrFrame.) + seen + recursion-limits + (conj-some! acc (.-value ^ResultFrame result)) + pattern + attr + (next-seq datoms))) + (-run [this context] (util/cond+ - :let [^Datom datom (first-seq datoms)] + :let [#?(:cljd ^Datom? datom :default ^Datom datom) (first-seq datoms)] (or (nil? datom) (not= (.-a datom) (.-name attr))) - [(ResultFrame. (not-empty (persistent! acc)) (or datoms ()))] + [(#?(:cljd ->ResultFrame :default ResultFrame.) (not-empty (persistent! acc)) (or datoms ()))] + +; got limit, skip rest of the datoms + (and (.-limit attr) (>= (count acc) (.-limit attr))) + (loop [datoms datoms] + (let [#?(:cljd ^Datom? datom :default ^Datom datom) (first-seq datoms)] + (if (or (nil? datom) (not= (.-a datom) (.-name attr))) + [(#?(:cljd ->ResultFrame :default ResultFrame.) (persistent! acc) (or datoms ()))] + (recur (next-seq datoms))))) - ; got limit, skip rest of the datoms - (and (.-limit attr) (>= (count acc) (.-limit attr))) - (loop [datoms datoms] - (let [^Datom datom (first-seq datoms)] - (if (or (nil? datom) (not= (.-a datom) (.-name attr))) - [(ResultFrame. (persistent! acc) (or datoms ()))] - (recur (next-seq datoms))))) + :let [id (if (.-reverse? attr) (.-e datom) (.-v datom))] - :let [id (if (.-reverse? attr) (.-e datom) (.-v datom))] + :else + [this (ref-frame context seen recursion-limits pattern attr id)])) - :else - [this (ref-frame context seen recursion-limits pattern attr id)])) - (-str [this] (str "MultivalAttrFrame"))) -(defrecord AttrsFrame [seen recursion-limits acc ^PullPattern pattern ^PullAttr attr attrs datoms id] +(defrecord AttrsFrame [seen recursion-limits acc ^PullPattern pattern #?(:cljd ^PullAttr? attr :default ^PullAttr attr) attrs datoms id] IFrame (-merge [this result] - (AttrsFrame. + (#?(:cljd ->AttrsFrame :default AttrsFrame.) seen recursion-limits (assoc-some! acc (.-as attr) ((.-xform attr) (.-value ^ResultFrame result))) @@ -127,7 +131,7 @@ id)) (-run [this context] (loop [acc acc - attr attr + #?(:cljd ^PullAttr? attr :default attr) attr ; cljd bug except for the nil attrs attrs datoms datoms] (util/cond+ @@ -135,71 +139,76 @@ (and (nil? datoms) (nil? attr)) [(->ReverseAttrsFrame seen recursion-limits acc pattern (first-seq (.-reverse-attrs pattern)) (next-seq (.-reverse-attrs pattern)) id)] - ;; :db/id - (and (some? attr) (= :db/id (.-name attr))) - (recur (assoc! acc (.-as attr) ((.-xform attr) id)) (first-seq attrs) (next-seq attrs) datoms) - - :let [^Datom datom (first-seq datoms) - cmp (when (and datom attr) - (compare (.-name attr) (.-a datom))) - attr-ahead? (or (nil? attr) (and cmp (pos? cmp))) - datom-ahead? (or (nil? datom) (and cmp (neg? cmp)))] - - ;; wildcard - (and (.-wildcard? pattern) (some? datom) attr-ahead?) - (let [datom-attr (lru/-get - (.-pull-attrs (db/unfiltered-db (.-db ^Context context))) - (.-a datom) - #(dpp/parse-attr-name (.-db ^Context context) (.-a datom)))] - (recur acc datom-attr (when attr (conj-seq attrs attr)) datoms)) - - ;; advance datom - attr-ahead? - (recur acc attr attrs (next-seq datoms)) - - :do (visit context :db.pull/attr id (.-name attr) nil) - - ;; advance attr - (and datom-ahead? (nil? attr)) - (recur acc (first-seq attrs) (next-seq attrs) datoms) - - ;; default - (and datom-ahead? (some? (#?(:clj .-default :cljs :default) attr))) - (recur (assoc! acc (.-as attr) (#?(:clj .-default :cljs :default) attr)) (first-seq attrs) (next-seq attrs) datoms) - - ;; xform - datom-ahead? - (if-some [value ((.-xform attr) nil)] - (recur (assoc! acc (.-as attr) value) (first-seq attrs) (next-seq attrs) datoms) - (recur acc (first-seq attrs) (next-seq attrs) datoms)) - - ;; matching attr - (and (.-multival? attr) (.-ref? attr)) - [(AttrsFrame. seen recursion-limits acc pattern attr attrs datoms id) - (MultivalRefAttrFrame. seen recursion-limits (transient []) pattern attr datoms)] - - (.-multival? attr) - [(AttrsFrame. seen recursion-limits acc pattern attr attrs datoms id) - (MultivalAttrFrame. (transient []) attr datoms)] - - (.-ref? attr) - [(AttrsFrame. seen recursion-limits acc pattern attr attrs datoms id) - (ref-frame context seen recursion-limits pattern attr (.-v datom))] - - :else - (recur - (assoc! acc (.-as attr) ((.-xform attr) (.-v datom))) - (first-seq attrs) - (next-seq attrs) - (next-seq datoms))))) - + ;; :db/id + (and (some? attr) (= :db/id (.-name attr))) + (recur (assoc! acc (.-as attr) ((.-xform attr) id)) (first-seq attrs) (next-seq attrs) datoms) + + :let [#?(:cljd ^Datom? datom :default ^Datom datom) (first-seq datoms) + cmp (when (and datom attr) + (compare (.-name attr) (.-a datom))) + attr-ahead? (or (nil? attr) (and cmp (pos? cmp))) + datom-ahead? (or (nil? datom) (and cmp (neg? cmp)))] + + ;; wildcard + (and (.-wildcard? pattern) (some? datom) attr-ahead?) + (let [datom-attr (lru/-get + (.-pull-attrs (db/unfiltered-db (.-db ^Context context))) + (.-a datom) + #(dpp/parse-attr-name (.-db ^Context context) (.-a datom)))] + (recur acc datom-attr (when attr (conj-seq attrs attr)) datoms)) + + ;; advance datom + attr-ahead? + (recur acc attr attrs (next-seq datoms)) + + :do (visit context :db.pull/attr id (.-name attr) nil) + + ;; advance attr + (and datom-ahead? (nil? attr)) + (recur acc (first-seq attrs) (next-seq attrs) datoms) + + ;; default + (and datom-ahead? (some? (#?(:cljd :default :clj .-default :cljs :default) attr))) + (recur (assoc! acc (.-as attr) (#?(:cljd :default :clj .-default :cljs :default) attr)) (first-seq attrs) (next-seq attrs) datoms) + + ;; xform + datom-ahead? + (if-some [value ((.-xform attr) nil)] + (recur (assoc! acc (.-as attr) value) (first-seq attrs) (next-seq attrs) datoms) + (recur acc (first-seq attrs) (next-seq attrs) datoms)) + + ;; matching attr + (and (.-multival? attr) (.-ref? attr)) + [(#?(:cljd ->AttrsFrame :default AttrsFrame.) + seen recursion-limits acc pattern attr attrs datoms id) + (#?(:cljd ->MultivalRefAttrFrame :default MultivalRefAttrFrame.) + seen recursion-limits (transient []) pattern attr datoms)] + + (.-multival? attr) + [(#?(:cljd ->AttrsFrame :default AttrsFrame.) + seen recursion-limits acc pattern attr attrs datoms id) + (#?(:cljd ->MultivalAttrFrame :default MultivalAttrFrame.) + (transient []) attr datoms)] + + (.-ref? attr) + [(#?(:cljd ->AttrsFrame :default AttrsFrame.) + seen recursion-limits acc pattern attr attrs datoms id) + (ref-frame context seen recursion-limits pattern attr (.-v datom))] + + :else + (recur + (assoc! acc (.-as attr) ((.-xform attr) (.-v datom))) + (first-seq attrs) + (next-seq attrs) + (next-seq datoms))))) + (-str [this] (str "AttrsFrame"))) -(defrecord ReverseAttrsFrame [seen recursion-limits acc pattern ^PullAttr attr attrs id] +(defrecord ReverseAttrsFrame [seen recursion-limits acc pattern #?(:cljd ^PullAttr? attr :default ^PullAttr attr) attrs id] IFrame (-merge [this result] - (ReverseAttrsFrame. + (#?(:cljd ->ReverseAttrsFrame :default ReverseAttrsFrame.) seen recursion-limits (assoc-some! acc (.-as attr) ((.-xform attr) (.-value ^ResultFrame result))) @@ -207,37 +216,42 @@ (first-seq attrs) (next-seq attrs) id)) - + (-run [this context] (loop [acc acc - attr attr + #?(:cljd ^PullAttr? attr :default attr) attr ; cljd loop inference bug attrs attrs] (util/cond+ (nil? attr) - [(ResultFrame. (not-empty (persistent! acc)) nil)] + [(#?(:cljd ->ResultFrame :default ResultFrame.) + (not-empty (persistent! acc)) nil)] - :let [name (.-name attr) - db (.-db ^Context context) - datoms (if (instance? DB db) - (set/slice (.-avet ^DB db) (db/datom db/e0 name id db/tx0) (db/datom db/emax name id db/txmax)) - (db/-search db [nil name id]))] + :let [name (.-name attr) + db (.-db ^Context context) + datoms (if (instance? DB db) + (#?(:cljd db/set-slice :default set/slice) + (.-avet ^DB db) (db/datom db/e0 name id db/tx0) (db/datom db/emax name id db/txmax)) + (db/-search db [nil name id]))] - :do (visit context :db.pull/reverse nil name id) + :do (visit context :db.pull/reverse nil name id) - (and (empty? datoms) (some? (#?(:clj .-default :cljs :default) attr))) - (recur (assoc! acc (.-as attr) (#?(:clj .-default :cljs :default) attr)) (first-seq attrs) (next-seq attrs)) + (and (empty? datoms) (some? (#?(:cljd :default :clj .-default :cljs :default) attr))) + (recur (assoc! acc (.-as attr) (#?(:cljd :default :clj .-default :cljs :default) attr)) (first-seq attrs) (next-seq attrs)) - (empty? datoms) - (recur acc (first-seq attrs) (next-seq attrs)) + (empty? datoms) + (recur acc (first-seq attrs) (next-seq attrs)) - (.-component? attr) - [(ReverseAttrsFrame. seen recursion-limits acc pattern attr attrs id) - (ref-frame context seen recursion-limits pattern attr (.-e ^Datom (first-seq datoms)))] + (.-component? attr) + [(#?(:cljd ->ReverseAttrsFrame :default ReverseAttrsFrame.) + seen recursion-limits acc pattern attr attrs id) + (ref-frame context seen recursion-limits pattern attr (.-e ^Datom (first-seq datoms)))] + + :else + [(#?(:cljd ->ReverseAttrsFrame :default ReverseAttrsFrame.) + seen recursion-limits acc pattern attr attrs id) + (#?(:cljd ->MultivalRefAttrFrame :default MultivalRefAttrFrame.) + seen recursion-limits (transient []) pattern attr datoms)]))) - :else - [(ReverseAttrsFrame. seen recursion-limits acc pattern attr attrs id) - (MultivalRefAttrFrame. seen recursion-limits (transient []) pattern attr datoms)]))) - (-str [this] (str "ReverseAttrsFrame"))) @@ -254,12 +268,14 @@ (attrs-frame context seen recursion-limits (.-pattern attr) id) (seen id) - (ResultFrame. {:db/id id} nil) + (#?(:cljd ->ResultFrame :default ResultFrame.) + {:db/id id} nil) :let [lim (recursion-limits attr)] (and lim (<= lim 0)) - (ResultFrame. nil nil) + (#?(:cljd ->ResultFrame :default ResultFrame.) + nil nil) :let [seen' (conj seen id) recursion-limits' (cond @@ -274,8 +290,9 @@ (let [db (.-db context) datoms (util/cond+ (and (.-wildcard? pattern) (instance? DB db)) - (set/slice (.-eavt ^DB db) (db/datom id nil nil db/tx0) (db/datom id nil nil db/txmax)) - + (#?(:cljd db/set-slice :default set/slice) + (.-eavt ^DB db) (db/datom id nil nil db/tx0) (db/datom id nil nil db/txmax)) + (.-wildcard? pattern) (db/-search db [id]) @@ -284,9 +301,10 @@ :let [from (.-name ^PullAttr (.-first-attr pattern)) to (.-name ^PullAttr (.-last-attr pattern))] - + (instance? DB db) - (set/slice (.-eavt ^DB db) (db/datom id from nil db/tx0) (db/datom id to nil db/txmax)) + #?(:cljd (db/set-slice (.-eavt ^DB db) (db/datom id from db/MIN db/tx0) (db/datom id to db/MAX db/txmax)) + :default (set/slice (.-eavt ^DB db) (db/datom id from nil db/tx0) (db/datom id to nil db/txmax))) :else (->> (db/-seek-datoms db :eavt id nil nil nil)) @@ -297,15 +315,15 @@ (<= (compare (.-a d) to) 0)))))] (when (.-wildcard? pattern) (visit context :db.pull/wildcard id nil nil)) - (AttrsFrame. - seen - recursion-limits - (transient {}) - pattern - (first-seq (.-attrs pattern)) - (next-seq (.-attrs pattern)) - datoms - id))) + (#?(:cljd ->AttrsFrame :default AttrsFrame.) + seen + recursion-limits + (transient {}) + pattern + (first-seq (.-attrs pattern)) + (next-seq (.-attrs pattern)) + datoms + id))) (defn pull-impl [parsed-opts id] (let [{^Context context :context @@ -332,7 +350,8 @@ ([db pattern] (parse-opts db pattern nil)) ([db pattern {:keys [visitor]}] {:pattern (lru/-get (.-pull-patterns (db/unfiltered-db db)) pattern #(dpp/parse-pattern db pattern)) - :context (Context. db visitor)})) + :context (#?(:cljd ->Context :default Context.) + db visitor)})) (defn pull "Supported opts: diff --git a/src/datascript/pull_parser.cljc b/src/datascript/pull_parser.cljc index abb05c56..3215b37b 100644 --- a/src/datascript/pull_parser.cljc +++ b/src/datascript/pull_parser.cljc @@ -67,7 +67,8 @@ (when (fn? sym-or-fn) sym-or-fn) (get built-ins/query-fns sym-or-fn) - #?(:clj (when (namespace sym-or-fn) + #?(:cljd nil + :clj (when (namespace sym-or-fn) (when-some [v (requiring-resolve sym-or-fn)] @v))) (util/raise "Can't resolve symbol " sym-or-fn {:error :parser/pull, :fragment sym-or-fn}))) @@ -188,11 +189,11 @@ result attr-spec)] (recur (next pattern) result')) - + :let [pull-attr (parse-attr-spec db attr-spec)] (nil? pull-attr) (check false "attr-name | attr-expr | map-spec | *" attr-spec) - + :else (recur (next pattern) (conj-attr result pull-attr))))) diff --git a/src/datascript/query.cljc b/src/datascript/query.cljc index eb434f45..fc8cbe36 100644 --- a/src/datascript/query.cljc +++ b/src/datascript/query.cljc @@ -1,20 +1,25 @@ (ns ^:no-doc datascript.query + (:refer-clojure :exclude [make-array]) (:require - [#?(:cljs cljs.reader :clj clojure.edn) :as edn] + [#?(:cljd cljd.reader :cljs cljs.reader :clj clojure.edn) :as edn] [clojure.set :as set] [clojure.string :as str] [clojure.walk :as walk] [datascript.built-ins :as built-ins] [datascript.db :as db] - [me.tonsky.persistent-sorted-set.arrays :as da] + #?(:cljd nil :default [me.tonsky.persistent-sorted-set.arrays :as da]) [datascript.lru :as lru] [datascript.impl.entity :as de] [datascript.parser :as dp #?@(:cljs [:refer [BindColl BindIgnore BindScalar BindTuple Constant + FindColl FindRel FindScalar FindTuple PlainSymbol + RulesVar SrcVar Variable]] + :cljd [:refer [BindColl BindIgnore BindScalar BindTuple Constant FindColl FindRel FindScalar FindTuple PlainSymbol RulesVar SrcVar Variable]])] [datascript.pull-api :as dpa] [datascript.util :as util]) - #?(:clj + #?(:cljd nil + :clj (:import [clojure.lang ILookup LazilyPersistentVector] [datascript.parser BindColl BindIgnore BindScalar BindTuple @@ -25,6 +30,10 @@ ;; ---------------------------------------------------------------------------- +(def #?(:cljd ^List make-array :default make-array) + #?(:cljd (fn [n] (.filled #/(List dynamic) n nil)) + :default da/make-array)) + (def ^:dynamic *query-cache* (lru/cache 100)) @@ -94,20 +103,34 @@ (defn lookup-ref? [form] (and - (or (sequential? form) (da/array? form)) + (or (sequential? form) #?(:cljd (dart/is? form List) :default (da/array? form))) (= 2 (count form)) (attr? (first form)))) ;; Relation algebra -#?(:clj (set! *unchecked-math* true)) +#?(:cljd nil + :clj (set! *unchecked-math* true)) -#?(:clj +#?(:cljd + (defn join-tuples [t1 ^List idxs1 + t2 ^List idxs2] + (let [l1 (alength idxs1) + l2 (alength idxs2) + res (make-array (+ l1 l2))] + (if (instance? List t1) + (dotimes [i l1] (aset res i (aget ^List t1 (aget idxs1 i)))) + (dotimes [i l1] (aset res i (get t1 (aget idxs1 i))))) + (if (instance? List t2) + (dotimes [i l2] (aset res (+ l1 i) (get ^List t2 (aget idxs2 i)))) + (dotimes [i l2] (aset res (+ l1 i) (get t2 (aget idxs2 i))))) + res)) + :clj (defn join-tuples [t1 ^{:tag "[[Ljava.lang.Object;"} idxs1 t2 ^{:tag "[[Ljava.lang.Object;"} idxs2] (let [l1 (alength idxs1) l2 (alength idxs2) - res (da/make-array (+ l1 l2))] + res (make-array (+ l1 l2))] (if (.isArray (.getClass ^Object t1)) (dotimes [i l1] (aset res i (aget ^objects t1 (aget idxs1 i)))) (dotimes [i l1] (aset res i (get t1 (aget idxs1 i))))) @@ -120,12 +143,13 @@ t2 idxs2] (let [l1 (alength idxs1) l2 (alength idxs2) - res (da/make-array (+ l1 l2))] + res (make-array (+ l1 l2))] (dotimes [i l1] (aset res i (da/aget t1 (aget idxs1 i)))) (dotimes [i l2] (aset res (+ l1 i) (da/aget t2 (aget idxs2 i)))) res))) -#?(:clj (set! *unchecked-math* false)) +#?(:cljd nil + :clj (set! *unchecked-math* false)) (defn- sum-rel* [attrs-a tuples-a attrs-b tuples-b] (let [idxb->idxa (vec (for [[sym idx-b] attrs-b] @@ -134,20 +158,20 @@ tuples' (persistent! (reduce (fn [acc tuple-b] - (let [tuple' (da/make-array tlen)] + (let [tuple' (make-array tlen)] (doseq [[idx-b idx-a] idxb->idxa] - (aset tuple' idx-a (#?(:cljs da/aget :clj get) tuple-b idx-b))) + (aset tuple' idx-a (#?(:cljs da/aget :default get) tuple-b idx-b))) (conj! acc tuple'))) (transient (vec tuples-a)) tuples-b))] - (Relation. attrs-a tuples'))) + (->Relation attrs-a tuples'))) (defn sum-rel [a b] (let [{attrs-a :attrs, tuples-a :tuples} a {attrs-b :attrs, tuples-b :tuples} b] (cond (= attrs-a attrs-b) - (Relation. attrs-a (into (vec tuples-a) tuples-b)) + (->Relation attrs-a (into (vec tuples-a) tuples-b)) ;; BEFORE checking same-keys ;; because one rel could have had its resolution shortcircuited @@ -158,7 +182,7 @@ (util/raise "Can’t sum relations with different attrs: " attrs-a " and " attrs-b {:error :query/where}) - (every? number? (vals attrs-a)) ;; can’t conj into BTSetIter + (every? number? (vals attrs-a)) ;; can't conj into BTSetIter (sum-rel* attrs-a tuples-a attrs-b tuples-b) :else @@ -168,13 +192,13 @@ (defn prod-rel ([] - (Relation. {} [(da/make-array 0)])) + (->Relation {} [(make-array 0)])) ([rel1 rel2] (let [attrs1 (keys (:attrs rel1)) attrs2 (keys (:attrs rel2)) idxs1 (to-array (map (:attrs rel1) attrs1)) idxs2 (to-array (map (:attrs rel2) attrs2))] - (Relation. + (->Relation (zipmap (concat attrs1 attrs2) (range)) (persistent! (reduce @@ -194,20 +218,20 @@ (defn empty-rel [binding] (let [vars (->> (dp/collect-vars-distinct binding) (map :symbol))] - (Relation. (zipmap vars (range)) []))) + (->Relation (zipmap vars (range)) []))) (defprotocol IBinding (in->rel [binding value])) (extend-protocol IBinding BindIgnore - (in->rel [_ _] + (in->rel [_ _cljd_bug] (prod-rel)) - + BindScalar (in->rel [binding value] - (Relation. {(get-in binding [:variable :symbol]) 0} [(into-array [value])])) - + (->Relation {(get-in binding [:variable :symbol]) 0} [(into-array [value])])) + BindColl (in->rel [binding coll] (cond @@ -220,7 +244,8 @@ (->> coll (map #(in->rel (:binding binding) %)) (reduce sum-rel)))) - + + BindTuple (in->rel [binding coll] (cond @@ -277,33 +302,43 @@ (let [idx (int idx)] (fn contained-int-getter-fn [tuple] (let [eid #?(:cljs (da/aget tuple idx) + :cljd (if (instance? List tuple) + (aget ^objects tuple idx) + (nth tuple idx)) :clj (if (.isArray (.getClass ^Object tuple)) (aget ^objects tuple idx) (nth tuple idx)))] (cond (number? eid) eid ;; quick path to avoid fn call (sequential? eid) (db/entid *implicit-source* eid) - (da/array? eid) (db/entid *implicit-source* eid) + #?(:cljd (dart/is? eid List) + :default (da/array? eid)) (db/entid *implicit-source* eid) :else eid)))) ;; If the index is not an int?, the target can never be an array (fn contained-getter-fn [tuple] (let [eid #?(:cljs (da/aget tuple idx) + :cljd (get tuple idx) :clj (.valAt ^ILookup tuple idx))] (cond (number? eid) eid ;; quick path to avoid fn call (sequential? eid) (db/entid *implicit-source* eid) - (da/array? eid) (db/entid *implicit-source* eid) + #?(:cljd (dart/is? eid List) + :default (da/array? eid)) (db/entid *implicit-source* eid) :else eid)))) (if (int? idx) (let [idx (int idx)] (fn int-getter [tuple] #?(:cljs (da/aget tuple idx) + :cljd (if (instance? List tuple) + (aget ^objects tuple idx) + (nth tuple idx)) :clj (if (.isArray (.getClass ^Object tuple)) (aget ^objects tuple idx) (nth tuple idx))))) ;; If the index is not an int?, the target can never be an array (fn getter [tuple] - #?(:cljs (da/aget tuple idx) + #?(:cljd (get tuple idx) + :cljs (da/aget tuple idx) :clj (.valAt ^ILookup tuple idx))))))) (defn tuple-key-fn @@ -311,16 +346,26 @@ (let [n (count common-attrs)] (if (== n 1) (getter-fn attrs (first common-attrs)) - (let [^objects getters-arr #?(:clj (into-array Object common-attrs) - :cljs (into-array common-attrs))] + (let [^#?(:cljd List :default objects) getters-arr #?(:cljd (into-array common-attrs) + :clj (into-array Object common-attrs) + :cljs (into-array common-attrs))] (loop [i 0] (if (< i n) (do (aset getters-arr i (getter-fn attrs (aget getters-arr i))) - (recur (unchecked-inc i))) - #?(:clj + (recur (#?(:cljd inc :default unchecked-inc) i))) + #?(:cljd (fn [tuple] - (let [^objects arr (make-array Object n)] + (let [^List arr (make-array n)] + (loop [i 0] + (if (< i n) + (do + (aset arr i ((aget getters-arr i) tuple)) + (recur (inc i))) + (vec arr))))) + :clj + (fn [tuple] + (let [^objects arr (clojure.core/make-array Object n)] (loop [i 0] (if (< i n) (do @@ -342,6 +387,10 @@ (defn hash-attrs [key-fn tuples] (-group-by key-fn '() tuples)) +#?(:cljd + (defn ->Eduction [xform coll] + (eduction xform coll))) + (defn hash-join [rel1 rel2] (let [tuples1 (:tuples rel1) tuples2 (:tuples rel2) @@ -365,14 +414,14 @@ tuples2 (reduce (fn outer [acc tuple2] (let [key (key-fn2 tuple2)] - (if-some [tuples1 #?(:clj (hash key) :cljs (get hash key))] + (if-some [tuples1 #?(:clj (hash key) :cljs (get hash key) :cljd (hash key))] (reduce (fn inner [acc tuple1] (conj! acc (join-tuples tuple1 keep-idxs1 tuple2 keep-idxs2))) acc tuples1) acc))) (transient [])) (persistent!))] - (Relation. (zipmap (concat keep-attrs1 keep-attrs2) (range)) + (->Relation (zipmap (concat keep-attrs1 keep-attrs2) (range)) new-tuples))) (defn subtract-rel [a b] @@ -394,7 +443,7 @@ (when-some [tuple (first (:tuples rel))] (when (nil? (fnext (:tuples rel))) (let [idx (get (:attrs rel) pattern-el)] - (#?(:cljs da/aget :clj get) tuple idx))))))) + (#?(:cljs da/aget :default get) tuple idx))))))) (defn substitute-constants [context pattern] (mapv #(or (substitute-constant context %) %) pattern)) @@ -424,7 +473,7 @@ attr->prop (->> (map vector pattern ["e" "a" "v" "tx"]) (filter (fn [[s _]] (free-var? s))) (into {}))] - (Relation. attr->prop datoms))) + (->Relation attr->prop datoms))) (defn matches-pattern? [pattern tuple] (loop [tuple tuple @@ -442,7 +491,7 @@ attr->idx (->> (map vector pattern (range)) (filter (fn [[s _]] (free-var? s))) (into {}))] - (Relation. attr->idx (mapv to-array data)))) ;; FIXME to-array + (->Relation attr->idx (mapv to-array data)))) ;; FIXME to-array (defn normalize-pattern-clause [clause] (if (source? (first clause)) @@ -467,7 +516,7 @@ (defn- context-resolve-val [context sym] (when-some [rel (rel-with-attr context sym)] (when-some [tuple (first (:tuples rel))] - (#?(:cljs da/aget :clj get) tuple ((:attrs rel) sym))))) + (#?(:cljs da/aget :clj get :cljd get) tuple ((:attrs rel) sym))))) (defn- rel-contains-attrs? [rel attrs] (some #(contains? (:attrs rel) %) attrs)) @@ -481,37 +530,39 @@ (let [sources (:sources context) attrs (:attrs rel) len (count args) - static-args (da/make-array len) - tuples-args (da/make-array len)] + static-args (make-array len) + tuples-args (make-array len)] (dotimes [i len] (let [arg (nth args i)] - (if (symbol? arg) + (if (symbol? arg) (if-some [source (get sources arg)] - (da/aset static-args i source) - (da/aset tuples-args i (get attrs arg))) - (da/aset static-args i arg)))) + (#?(:cljd aset :default da/aset) static-args i source) + (#?(:cljd aset :default da/aset) tuples-args i (get attrs arg))) + (#?(:cljd aset :default da/aset) static-args i arg)))) ;; CLJS `apply` + `vector` will hold onto mutable array of arguments directly ;; issue-262 (if #?(:clj false + :cljd false :cljs (identical? f vector)) (fn [tuple] ;; TODO raise if not all args are bound - (let [args (da/aclone static-args)] + (let [args (#?(:cljd vec :default da/aclone) static-args)] (dotimes [i len] (when-some [tuple-idx (aget tuples-args i)] - (let [v (#?(:cljs da/aget :clj get) tuple tuple-idx)] - (da/aset args i v)))) + (let [v (#?(:cljd get :cljs da/aget :clj get) tuple tuple-idx)] + (#?(:cljd aset :default da/aset) args i v)))) (apply f args))) (fn [tuple] ;; TODO raise if not all args are bound (dotimes [i len] (when-some [tuple-idx (aget tuples-args i)] - (let [v (#?(:cljs da/aget :clj get) tuple tuple-idx)] - (da/aset static-args i v)))) + (let [v (#?(:cljd get :cljs da/aget :clj get) tuple tuple-idx)] + (#?(:cljd aset :default da/aset) static-args i v)))) (apply f static-args))))) (defn- resolve-sym [sym] #?(:cljs nil + :cljd nil :clj (when (namespace sym) (when-some [v (resolve sym)] @v)))) @@ -547,7 +598,7 @@ :when (not (nil? val))] (reduce prod-rel (collapse-rels - [(Relation. (:attrs production) [tuple])] + [(->Relation (:attrs production) [tuple])] (in->rel binding val))))] (if (empty? rels) (prod-rel @@ -643,7 +694,7 @@ :clauses [clause] :used-args {} :pending-guards {}}) - rel (Relation. final-attrs-map [])] + rel (->Relation final-attrs-map [])] (if-some [frame (first stack)] (let [[clauses [rule-clause & next-clauses]] (split-with #(not (rule? context %)) (:clauses frame))] (if (nil? rule-clause) @@ -651,7 +702,7 @@ ;; no rules -> expand, collect, sum (let [context (solve (:prefix-context frame) clauses) tuples (util/distinct-by vec (-collect context final-attrs)) - new-rel (Relation. final-attrs-map tuples)] + new-rel (->Relation final-attrs-map tuples)] (recur (next stack) (sum-rel rel new-rel))) ;; has rule -> add guards -> check if dead -> expand rule -> push to stack, recur @@ -743,31 +794,31 @@ (do (check-bound (bound-vars context) (filter free-var? (nfirst clause)) clause) (filter-by-pred context clause)) - + [[symbol? '*] '_] ;; function [(fn ?a ?b) ?res] (do (check-bound (bound-vars context) (filter free-var? (nfirst clause)) clause) (bind-by-fn context clause)) - + [source? '*] ;; source + anything (let [[source-sym & rest] clause] (binding [*implicit-source* (get (:sources context) source-sym)] (-resolve-clause context rest clause))) - + '[or *] ;; (or ...) (let [[_ & branches] clause _ (check-free-same (bound-vars context) branches clause) contexts (map #(resolve-clause context %) branches) rels (map #(reduce hash-join (:rels %)) contexts)] (assoc (first contexts) :rels [(reduce sum-rel rels)])) - + '[or-join [[*] *] *] ;; (or-join [[req-vars] vars] ...) (let [[_ [req-vars & vars] & branches] clause bound (bound-vars context)] (check-bound bound req-vars orig-clause) (check-free-subset bound vars branches) (recur context (list* 'or-join (concat req-vars vars) branches) clause)) - + '[or-join [*] *] ;; (or-join [vars] ...) (let [[_ vars & branches] clause vars (set vars) @@ -777,11 +828,11 @@ rels (map #(reduce hash-join (:rels %)) contexts) sum-rel (reduce sum-rel rels)] (update context :rels collapse-rels sum-rel)) - + '[and *] ;; (and ...) (let [[_ & clauses] clause] (reduce resolve-clause context clauses)) - + '[not *] ;; (not ...) (let [[_ & clauses] clause bound (bound-vars context) @@ -796,7 +847,7 @@ (util/single (:rels context')) (reduce hash-join (:rels negation-context)))] (assoc context' :rels [negation])) - + '[not-join [*] *] ;; (not-join [vars] ...) (let [[_ vars & clauses] clause bound (bound-vars context) @@ -809,7 +860,7 @@ (util/single (:rels context')) (reduce hash-join (:rels negation-context)))] (assoc context' :rels [negation])) - + '[*] ;; pattern (let [source *implicit-source* pattern' (resolve-pattern-lookup-refs source clause) @@ -823,7 +874,7 @@ (if (some #(empty? (:tuples %)) (:rels context)) (assoc context :rels - [(Relation. + [(->Relation (zipmap (mapcat #(keys (:attrs %)) (:rels context)) (range)) [])]) context)) @@ -844,17 +895,26 @@ (reduce resolve-clause context clauses))) (defn -collect-tuples - [acc rel ^long len copy-map] + [acc rel ^#?(:cljd int :default long) len copy-map] (->Eduction (comp (map (fn [#?(:cljs t1 + :cljd t1 :clj ^{:tag "[[Ljava.lang.Object;"} t1)] (->Eduction (map (fn [t2] (let [res (aclone t1)] - #?(:clj + #?(:cljd + (if (instance? List t2) + (dotimes [i len] + (when-some [idx (aget ^objects copy-map i)] + (aset res i (aget ^objects t2 idx)))) + (dotimes [i len] + (when-some [idx (aget ^objects copy-map i)] + (aset res i (get t2 idx))))) + :clj (if (.isArray (.getClass ^Object t2)) (dotimes [i len] (when-some [idx (aget ^objects copy-map i)] @@ -874,20 +934,20 @@ (defn -collect ([context symbols] (let [rels (:rels context)] - (-collect [(da/make-array (count symbols))] rels symbols))) + (-collect [(make-array (count symbols))] rels symbols))) ([acc rels symbols] (util/cond+ :let [rel (first rels)] - + (nil? rel) acc - + ;; one empty rel means final set has to be empty (empty? (:tuples rel)) [] - + :let [keep-attrs (select-keys (:attrs rel) symbols)] - + (empty? keep-attrs) (recur acc (next rels) symbols) - + :let [copy-map (to-array (map #(get keep-attrs %) symbols)) len (count symbols)] @@ -1003,7 +1063,7 @@ q (cond-> q (sequential? q) dp/query->map) wheres (:where q) - context (-> (Context. [] {} {}) + context (-> (->Context [] {} {}) (resolve-ins (:qin parsed-q) inputs)) resultset (-> context (-q wheres) diff --git a/src/datascript/query_v3.cljc b/src/datascript/query_v3.cljc index 145d1e01..15404aae 100644 --- a/src/datascript/query_v3.cljc +++ b/src/datascript/query_v3.cljc @@ -1,4 +1,5 @@ (ns ^:no-doc datascript.query-v3 + (:refer-clojure :exclude [make-array]) (:require [clojure.set :as set] [datascript.built-ins :as built-ins] @@ -6,13 +7,17 @@ [datascript.db :as db] [datascript.query :as dq] [datascript.lru :as lru] - [me.tonsky.persistent-sorted-set.arrays :as da] + #?(:cljd nil :default [me.tonsky.persistent-sorted-set.arrays :as da]) [datascript.parser :as dp #?@(:cljs [:refer [BindColl BindIgnore BindScalar BindTuple + Constant DefaultSrc Pattern RulesVar SrcVar Variable + Not Or And Predicate PlainSymbol]] + :cljd [:refer [BindColl BindIgnore BindScalar BindTuple Constant DefaultSrc Pattern RulesVar SrcVar Variable Not Or And Predicate PlainSymbol]])] [datascript.util :as util]) - #?(:clj - (:import + #?(:cljd nil + :clj + (:import [datascript.parser BindColl BindIgnore BindScalar BindTuple Constant DefaultSrc Pattern RulesVar SrcVar Variable @@ -30,8 +35,15 @@ (defn arange [start end] (to-array (range start end))) +(def #?(:cljd ^List make-array :default make-array) + #?(:cljd (fn [n] (.filled #/(List dynamic) n nil)) + :default da/make-array)) (defn subarr [arr start end] - (da/acopy arr start end (da/make-array (- end start)) 0)) + #?(:cljd (let [len (- end start) + dest (.filled #/(List dynamic) len nil)] + (dotimes [i len] (aset dest i (get arr (+ start i)))) + dest) + :default (da/acopy arr start end (make-array (- end start)) 0))) (defn concatv [& xs] (into [] cat xs)) @@ -219,11 +231,12 @@ ;;; ArrayRelation -(defn pr-rel [rel ^java.io.Writer w] +(defn pr-rel [rel w] (doto w (.write "#") (.write #?(:clj (.getSimpleName ^Class (class rel)) - :cljs (str (type rel)))) + :cljs (str (type rel)) + :cljd "")) (.write "{:symbols ") (.write (pr-str (-symbols rel))) (.write ", :coll ") @@ -243,12 +256,12 @@ (-getter [_ symbol] (let [idx (offset-map symbol)] (fn [tuple] - (da/aget tuple idx)))) + (#?(:cljd get :default da/aget) tuple idx)))) (-indexes [_ syms] (mapa offset-map syms)) (-copy-tuple [_ tuple idxs target target-idxs] - (dotimes [i (da/alength idxs)] - (da/aset target (da/aget target-idxs i) (da/aget tuple (da/aget idxs i))))) + (dotimes [i (#?(:cljd count :default da/alength) idxs)] + (#?(:cljd aset :default da/aset) target (#?(:cljd get :default da/aget) target-idxs i) (#?(:cljd get :default da/aget) tuple (#?(:cljd get :default da/aget) idxs i))))) (-union [_ rel] (assert (instance? ArrayRelation rel)) (assert (= offset-map (:offset-map rel))) @@ -281,8 +294,8 @@ (-indexes [_ syms] (mapa offset-map syms)) (-copy-tuple [_ tuple idxs target target-idxs] - (dotimes [i (da/alength idxs)] - (da/aset target (da/aget target-idxs i) (nth tuple (da/aget idxs i))))) + (dotimes [i (#?(:cljd count :default da/alength) idxs)] + (#?(:cljd aset :default da/aset) target (#?(:cljd get :default da/aget) target-idxs i) (nth tuple (#?(:cljd get :default da/aget) idxs i))))) (-union [_ rel] (assert (instance? CollRelation rel)) (assert (= offset-map (:offset-map rel))) @@ -369,7 +382,7 @@ rel2 t2 idxs2 arity target-idxs1 target-idxs2] - (let [arr (da/make-array arity)] + (let [arr (make-array arity)] (-copy-tuple rel1 t1 idxs1 arr target-idxs1) (-copy-tuple rel2 t2 idxs2 arr target-idxs2) arr)) @@ -410,7 +423,7 @@ (let [idxs (-indexes rel syms) target-idxs (arange 0 arity)] (fn [t] - (let [arr (da/make-array arity)] + (let [arr (make-array arity)] (-copy-tuple rel t idxs arr target-idxs) (vec arr))))))) @@ -468,7 +481,7 @@ BindScalar (let [symbol (get-in binding [:variable :symbol]) idx (get indexes symbol)] - (run! #(da/aset % idx source) tuples) + (run! #(#?(:cljd aset :default da/aset) % idx source) tuples) tuples) BindColl @@ -482,7 +495,7 @@ (into [] ;; TODO fast-arr (comp (map #(bind! tuples inner-binding % indexes)) cat - (map da/aclone)) + (map #?(:cljd vec :default da/aclone))) source)))) BindTuple @@ -505,7 +518,7 @@ (defn bind [binding source] (let [syms (map :symbol (dp/collect-vars-distinct binding)) indexes (zipmap syms (range)) - tuples (bind! [(da/make-array (count syms))] binding source indexes)] + tuples (bind! [(make-array (count syms))] binding source indexes)] (array-rel syms tuples))) (defn- rel->consts [rel] @@ -599,7 +612,7 @@ (if (instance? Variable form) (let [sym (:symbol form)] (if-let [subs (get (:consts context) (:symbol form))] - (Constant. subs) + #?(:cljd (dp/->Constant subs) :default (Constant. subs)) form)) form))) clause))) @@ -756,14 +769,14 @@ (cond (instance? Variable arg) (when (contains? consts sym) - (da/aset target i (get consts sym))) + (#?(:cljd aset :default da/aset) target i (get consts sym))) (instance? SrcVar arg) (if (contains? sources sym) - (da/aset target i (get sources sym)) + (#?(:cljd aset :default da/aset) target i (get sources sym)) (throw (ex-info (str "Unbound source variable: " sym " in " form) { :error :query/where, :form form, :var sym}))) (instance? Constant arg) - (da/aset target i (:value arg)))))) + (#?(:cljd aset :default da/aset) target i (:value arg)))))) (defn get-f [context fun form] (let [sym (:symbol fun)] @@ -779,7 +792,7 @@ (let [{fun :fn, args :args} clause form (dp/source clause) f (get-f context fun form) - args-arr (da/make-array (count args)) + args-arr (make-array (count args)) _ (collect-args! context args args-arr form) consts (:consts context) sym+idx (for [[arg i] (zip args (range)) @@ -815,7 +828,7 @@ array (into (fast-arr) (apply comp (concat xfs [(filter pred)])) - [(da/make-array (count prod-syms))]) + [(make-array (count prod-syms))]) prod-rel* (array-rel prod-syms array)] (join-unrelated context* prod-rel*))))))) @@ -861,7 +874,7 @@ (doseq [[sym i] syms-indexed] (when (contains? consts sym) (let [val (get consts sym)] - (da/aset specimen i val))))) + (#?(:cljd aset :default da/aset) specimen i val))))) (defn collect-rel-xf [syms-indexed rel] @@ -877,16 +890,16 @@ ([result specimen] (-fold rel (fn [acc tuple] - (let [t (da/aclone specimen)] + (let [t (#?(:cljd vec :default da/aclone) specimen)] (-copy-tuple rel tuple idxs t target-idxs) (rf acc t))) result)))))) (defn collect-to ([context syms acc] - (collect-to context syms acc [] (da/make-array (count syms)))) + (collect-to context syms acc [] (make-array (count syms)))) ([context syms acc xfs] - (collect-to context syms acc xfs (da/make-array (count syms)))) + (collect-to context syms acc xfs (make-array (count syms)))) ([context syms acc xfs specimen] ;; TODO don't collect if array-rel and matches symbols (if (:empty? context) diff --git a/src/datascript/serialize.cljc b/src/datascript/serialize.cljc index 55c5bd9b..2bddafb5 100644 --- a/src/datascript/serialize.cljc +++ b/src/datascript/serialize.cljc @@ -1,30 +1,42 @@ (ns datascript.serialize (:refer-clojure :exclude [amap array?]) (:require - [clojure.edn :as edn] + [#?(:cljd cljd.reader :default clojure.edn) :as edn] [clojure.string :as str] - [datascript.db :as db #?@(:cljs [:refer [Datom]])] + [datascript.db :as db #?@(:cljs [:refer [Datom]] :cljd [:refer [Datom]])] [datascript.lru :as lru] [datascript.storage :as storage] [datascript.util :as util] - [me.tonsky.persistent-sorted-set :as set] - [me.tonsky.persistent-sorted-set.arrays :as arrays]) + #?(:cljd nil :default [me.tonsky.persistent-sorted-set :as set]) + #?(:cljd nil :default [me.tonsky.persistent-sorted-set.arrays :as arrays])) #?(:cljs (:require-macros [datascript.serialize :refer [array dict]])) - #?(:clj + #?(:cljd nil + :clj (:import [datascript.db Datom] [me.tonsky.persistent_sorted_set PersistentSortedSet]))) -(def ^:const ^:private marker-kw 0) -(def ^:const ^:private marker-other 1) -(def ^:const ^:private marker-inf 2) -(def ^:const ^:private marker-minus-inf 3) -(def ^:const ^:private marker-nan 4) +(def #?(:cljd ^:private marker-kw :default ^:const ^:private marker-kw) 0) +(def #?(:cljd ^:private marker-other :default ^:const ^:private marker-other) 1) +(def #?(:cljd ^:private marker-inf :default ^:const ^:private marker-inf) 2) +(def #?(:cljd ^:private marker-minus-inf :default ^:const ^:private marker-minus-inf) 3) +(def #?(:cljd ^:private marker-nan :default ^:const ^:private marker-nan) 4) (defn- if-cljs [env then else] (if (:ns env) then else)) -#?(:clj +#?(:cljd + ; cgrand: I wonder if a plain vec wouldn't work as well and that it + ; doesn't really matter if it's an "array" + (defmacro array + "Platform-native array representation (java.util.List on JVM, Array on JS)" + [& args] + `(doto (.filled #/(List dynamic) ~(count args) nil) + ~@(map-indexed + (fn [i arg] + `(. "[]=" ~i ~arg)) + args))) + :clj (defmacro array "Platform-native array representation (java.util.List on JVM, Array on JS)" [& args] @@ -32,7 +44,12 @@ (list* 'js* (str "[" (str/join "," (repeat (count args) "~{}")) "]") args) (vec args)))) -#?(:clj +#?(:cljd + (defmacro dict + "Platform-native dictionary representation (java.util.Map on JVM, Object on JS)" + [& args] + `(hash-map ~@args)) + :clj (defmacro dict "Platform-native dictionary representation (java.util.Map on JVM, Object on JS)" [& args] @@ -41,19 +58,26 @@ `(array-map ~@args)))) (defn- array-get [d i] - #?(:clj (.get ^java.util.List d (int i)) + #?(:cljd (. ^List d "[]" (int i)) + :clj (.get ^java.util.List d (int i)) :cljs (if (cljs.core/array? d) (arrays/aget d i) (nth d i)))) (defn- dict-get [d k] - #?(:clj (.get ^java.util.Map d k) + #?(:cljd (. ^Map d "[]" k) + :clj (.get ^java.util.Map d k) :cljs (if (map? d) (d k) (arrays/aget d k)))) (defn- array? [a] - #?(:clj (instance? java.util.List a) + #?(:cljd (dart/is? a List) + :clj (instance? java.util.List a) :cljs (or (cljs.core/array? a) (vector? a)))) (defn- amap [f xs] - #?(:clj + #?(:cljd + (let [arr (.filled #/(List dynamic) (count xs) nil)] + (reduce (fn [idx x] (aset arr idx (f x)) (inc idx)) 0 xs) + arr) + :clj (let [arr (java.util.ArrayList. (count xs))] (reduce (fn [idx x] (.add arr (f x)) (inc idx)) 0 xs) arr) @@ -63,7 +87,11 @@ arr))) (defn- amap-indexed [f xs] - #?(:clj + #?(:cljd + (let [arr (.filled #/(List dynamic) (count xs) nil)] + (reduce (fn [idx x] (aset arr idx (f idx x)) (inc idx)) 0 xs) + arr) + :clj (let [arr (java.util.ArrayList. (count xs))] (reduce (fn [idx x] (.add arr (f idx x)) (inc idx)) 0 xs) arr) @@ -75,7 +103,7 @@ (defn- attr-comparator "Looks for a datom with attribute exactly bigger than the given one" [^Datom d1 ^Datom d2] - (cond + (cond (nil? (.-a d2)) -1 (<= (compare (.-a d1) (.-a d2)) 0) -1 true 1)) @@ -87,9 +115,12 @@ [] (loop [attrs (transient [(:a (first (:aevt db)))])] (let [attr (nth attrs (dec (count attrs))) - left (db/datom 0 attr nil) - right (db/datom db/emax nil nil) - next-attr (:a (first (set/slice (:aevt db) left right attr-comparator)))] + left #?(:cljd (db/min-datom (db/datom db/emax attr nil)) + :default (db/datom 0 attr nil)) + right #?(:cljd (db/max-datom (db/datom db/emax nil nil)) + :default (db/datom db/emax nil nil) ) + next-attr (:a (first #?(:cljd (subseq (:aevt db) > left <= right) + :default (set/slice (:aevt db) left right attr-comparator))))] (if (some? next-attr) (recur (conj! attrs next-attr)) (persistent! attrs)))))) @@ -104,7 +135,7 @@ (defn- serializable-impl "Serialized structure breakdown: - count :: number + count :: number tx0 :: number max-eid :: number max-tx :: number @@ -138,18 +169,19 @@ write-v (fn [v] (cond (string? v) v - #?@(:clj [(or + #?@(:cljd [] + :clj [(or (instance? BigInteger v) (instance? BigDecimal v) (instance? clojure.lang.Ratio v) (instance? clojure.lang.BigInt v)) (write-other v)]) - - (number? v) + + (number? v) (cond (== ##Inf v) (array marker-inf) (== ##-Inf v) (array marker-minus-inf) - #?(:clj (Double/isNaN v) :cljs (js/isNaN v)) (array marker-nan) + #?(:cljd (not (== v v)) :clj (Double/isNaN v) :cljs (js/isNaN v)) (array marker-nan) :else v) (boolean? v) v @@ -169,7 +201,8 @@ schema (freeze-fn (:schema db)) attrs (amap freeze-kw attrs) kws (amap freeze-kw (persistent! @*kws)) - #?@(:clj + #?@(:cljd [] + :clj [settings (set/settings (:eavt db))])] (dict "count" (count (:eavt db)) @@ -182,11 +215,16 @@ "eavt" eavt "aevt" aevt "avet" avet - #?@(:clj + #?@(:cljd [] + :clj ["branching-factor" (:branching-factor settings) "ref-type" (name (:ref-type settings))])))) -#?(:clj +#?(:cljd + (defn serializable + ([db] (serializable-impl db {})) + ([db opts] (serializable-impl db opts))) + :clj (let [lock (Object.)] (defn serializable ([db] (locking lock (serializable-impl db {}))) @@ -197,7 +235,7 @@ ([db opts] (serializable-impl db opts)))) (defn from-serializable - ([from] + ([from] (from-serializable from {})) ([from {:keys [thaw-fn thaw-kw] :or {thaw-fn edn/read-string @@ -226,21 +264,25 @@ marker-nan ##NaN (util/raise "Unexpected value marker " marker " in " (pr-str v) {:error :serialize :value v}))) - true (util/raise "Unexpected value type " (type v) " (" (pr-str v) ")" + true (util/raise "Unexpected value type " #?(:cljd (.-runtimeType v) :default (type v)) " (" (pr-str v) ")" {:error :serialize :value v})) tx (+ tx0 (array-get arr 3))] (db/datom e a v tx)))) - #?(:clj arrays/into-array)) - aevt (some->> (dict-get from "aevt") (amap #(arrays/aget eavt %)) #?(:clj arrays/into-array)) - avet (some->> (dict-get from "avet") (amap #(arrays/aget eavt %)) #?(:clj arrays/into-array)) + #?(:cljd do :clj arrays/into-array)) + aevt (some->> (dict-get from "aevt") (amap #(#?(:cljd aget :default arrays/aget) eavt %)) #?(:cljd do :clj arrays/into-array)) + avet (some->> (dict-get from "avet") (amap #(#?(:cljd aget :default arrays/aget) eavt %)) #?(:cljd do :clj arrays/into-array)) settings (merge {:branching-factor (dict-get from "branching-factor") :ref-type (some-> (dict-get from "ref-type") keyword)} (select-keys opts [:branching-factor :ref-type]))] (db/restore-db {:schema schema - :eavt (set/from-sorted-array db/cmp-datoms-eavt eavt (arrays/alength eavt) settings) - :aevt (set/from-sorted-array db/cmp-datoms-aevt aevt (arrays/alength aevt) settings) - :avet (set/from-sorted-array db/cmp-datoms-avet avet (arrays/alength avet) settings) + :eavt #?(:cljd (into (sorted-set-by db/cmp-datoms-eavt) eavt) + :default (set/from-sorted-array db/cmp-datoms-eavt eavt (arrays/alength eavt) settings)) + :aevt #?(:cljd (into (sorted-set-by db/cmp-datoms-aevt) aevt) + :default (set/from-sorted-array db/cmp-datoms-aevt aevt (arrays/alength aevt) settings)) + :avet #?(:cljd (into (sorted-set-by db/cmp-datoms-avet) eavt) + :default + (set/from-sorted-array db/cmp-datoms-avet avet (arrays/alength avet) settings)) :max-eid (dict-get from "max-eid") :max-tx (dict-get from "max-tx")})))) diff --git a/src/datascript/storage.cljd b/src/datascript/storage.cljd new file mode 100644 index 00000000..58023760 --- /dev/null +++ b/src/datascript/storage.cljd @@ -0,0 +1,13 @@ +(ns datascript.storage) + +(defn storage [db] + nil) + +(defn maybe-adapt-storage [opts] + opts) + +(defn store + ([db] + (throw (ex-info "Not implemented: (storage/store db)" {}))) + ([db storage] + (throw (ex-info "Not implemented: (storage/store db storage)" {})))) diff --git a/src/datascript/util.cljc b/src/datascript/util.cljc index 6add1a23..57bdcf66 100644 --- a/src/datascript/util.cljc +++ b/src/datascript/util.cljc @@ -1,6 +1,7 @@ (ns datascript.util (:refer-clojure :exclude [find]) - #?(:clj + #?(:cljd nil + :clj (:import [java.util UUID]))) @@ -19,10 +20,10 @@ data (last fragments)] `(throw (ex-info (str ~@(map (fn [m#] (if (string? m#) m# (list 'pr-str m#))) msgs)) ~data))))) -#?(:clj +#?(:cljd nil :clj (def ^:private ^:dynamic *if+-syms)) -#?(:clj +#?(:cljd nil :clj (defn- if+-rewrite-cond-impl [cond] (clojure.core/cond (empty? cond) @@ -49,12 +50,12 @@ (first cond) (if+-rewrite-cond-impl (next cond)))))) -#?(:clj +#?(:cljd nil :clj (defn- if+-rewrite-cond [cond] (binding [*if+-syms (volatile! [])] [(if+-rewrite-cond-impl cond) @*if+-syms]))) -#?(:clj +#?(:cljd nil :clj (defn- flatten-1 [xs] (vec (mapcat identity xs)))) @@ -80,22 +81,41 @@ ;; else: no x or y 6)" [cond then else] - (if (and - (seq? cond) - (or - (= 'and (first cond)) - (= 'clojure.core/and (first cond)))) - (let [[cond' syms] (if+-rewrite-cond (next cond))] - `(let ~(flatten-1 - (for [[_ sym] syms] - [sym '(volatile! nil)])) - (if ~cond' - (let ~(flatten-1 - (for [[binding sym] syms] - [binding (list 'deref sym)])) - ~then) - ~else))) - (list 'if cond then else)))) + #?(:cljd + (if (and + (seq? cond) + (or + (= 'and (first cond)) + (= 'clojure.core/and (first cond)))) + ((fn rewrite [clauses] + (clojure.core/cond + (empty? clauses) + then + + (= :let (first clauses)) + (list 'let (second clauses) (rewrite (nnext clauses))) + + :else + (list 'if (first clauses) (rewrite (next clauses)) else))) + (next cond)) + (list 'if cond then else)) + :default + (if (and + (seq? cond) + (or + (= 'and (first cond)) + (= 'clojure.core/and (first cond)))) + (let [[cond' syms] (if+-rewrite-cond (next cond))] + `(let ~(flatten-1 + (for [[_ sym] syms] + [sym '(volatile! nil)])) + (if ~cond' + (let ~(flatten-1 + (for [[binding sym] syms] + [binding (list 'deref sym)])) + ~then) + ~else))) + (list 'if cond then else))))) #?(:clj (defmacro cond+ [& clauses] @@ -116,7 +136,12 @@ (defn- rand-bits [pow] (rand-int (bit-shift-left 1 pow))) -#?(:cljs +#?(:cljd + (defn- to-hex-string [^int n l] + (-> n (.toRadixString 16) + (.padLeft l "0") + (subs 0 l))) + :cljs (defn- to-hex-string [n l] (let [s (.toString n 16) c (count s)] @@ -127,10 +152,22 @@ (defn squuid ([] - (squuid #?(:clj (System/currentTimeMillis) + (squuid #?(:cljd (.-millisecondsSinceEpoch (DateTime/now)) + :clj (System/currentTimeMillis) :cljs (.getTime (js/Date.))))) ([msec] - #?(:clj + #?(:cljd + (uuid + (str + (-> (int (/ msec 1000)) + (to-hex-string 8)) + "-" (-> (rand-bits 16) (to-hex-string 4)) + "-" (-> (rand-bits 16) (bit-and 0x0FFF) (bit-or 0x4000) (to-hex-string 4)) + "-" (-> (rand-bits 16) (bit-and 0x3FFF) (bit-or 0x8000) (to-hex-string 4)) + "-" (-> (rand-bits 16) (to-hex-string 4)) + (-> (rand-bits 16) (to-hex-string 4)) + (-> (rand-bits 16) (to-hex-string 4)))) + :clj (let [uuid (UUID/randomUUID) time (int (/ msec 1000)) high (.getMostSignificantBits uuid) @@ -153,7 +190,10 @@ (defn squuid-time-millis "Returns time that was used in [[squuid]] call, in milliseconds, rounded to the closest second." [uuid] - #?(:clj (-> (.getMostSignificantBits ^UUID uuid) + #?(:cljd (-> (subs (str uuid) 0 8) + (int/parse .radix 16) + (* 1000)) + :clj (-> (.getMostSignificantBits ^UUID uuid) (bit-shift-right 32) (* 1000)) :cljs (-> (subs (str uuid) 0 8) diff --git a/test/datascript/test/components.cljc b/test/datascript/test/components.cljc index 310385b5..8c9d4b93 100644 --- a/test/datascript/test/components.cljc +++ b/test/datascript/test/components.cljc @@ -1,31 +1,35 @@ (ns datascript.test.components (:require - [clojure.edn :as edn] - [clojure.test :as t :refer [is are deftest testing]] + [#?(:cljd cljd.reader :default clojure.edn) :as edn] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc])) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) (t/use-fixtures :once tdc/no-namespace-maps) #?(:cljs - (def Throwable js/Error)) + (def Throwable js/Error) + :cljd + (def Throwable ExceptionInfo)) (deftest test-components (is (thrown-msg? "Bad attribute specification for :profile: {:db/isComponent true} should also have {:db/valueType :db.type/ref}" - (d/empty-db {:profile {:db/isComponent true}}))) + (d/empty-db {:profile {:db/isComponent true}}))) (is (thrown-msg? "Bad attribute specification for {:profile {:db/isComponent \"aaa\"}}, expected one of #{true false}" - (d/empty-db {:profile {:db/isComponent "aaa" :db/valueType :db.type/ref}}))) - + (d/empty-db {:profile {:db/isComponent "aaa" :db/valueType :db.type/ref}}))) + (let [db (d/db-with - (d/empty-db {:profile {:db/valueType :db.type/ref - :db/isComponent true}}) - [{:db/id 1 :name "Ivan" :profile 3} - {:db/id 3 :email "@3"} - {:db/id 4 :email "@4"}]) + (d/empty-db {:profile {:db/valueType :db.type/ref + :db/isComponent true}}) + [{:db/id 1 :name "Ivan" :profile 3} + {:db/id 3 :email "@3"} + {:db/id 4 :email "@4"}]) visible #(edn/read-string (pr-str %)) touched #(visible (d/touch %))] - + (testing "touch" (is (= (touched (d/entity db 1)) {:db/id 1 @@ -45,12 +49,12 @@ #{})) (is (= (d/q '[:find ?a ?v :where [3 ?a ?v]] db) #{})))) - + (testing "retractAttribute" (let [db (d/db-with db [[:db.fn/retractAttribute 1 :profile]])] (is (= (d/q '[:find ?a ?v :where [3 ?a ?v]] db) #{})))) - + (testing "reverse navigation" (is (= (visible (:_profile (d/entity db 3))) {:db/id 1}))))) @@ -65,24 +69,24 @@ {:db/id 4 :email "@4"}]) visible #(edn/read-string (pr-str %)) touched #(visible (d/touch %))] - + (testing "touch" (is (= (touched (d/entity db 1)) {:db/id 1 :name "Ivan" :profile #{{:db/id 3 :email "@3"} {:db/id 4 :email "@4"}}}))) - + (testing "retractEntity" (let [db (d/db-with db [[:db.fn/retractEntity 1]])] (is (= (d/q '[:find ?a ?v :in $ [?e ...] :where [?e ?a ?v]] db [1 3 4]) #{})))) - + (testing "retractAttribute" (let [db (d/db-with db [[:db.fn/retractAttribute 1 :profile]])] (is (= (d/q '[:find ?a ?v :in $ [?e ...] :where [?e ?a ?v]] db [3 4]) #{})))) - + (testing "reverse navigation" (is (= (visible (:_profile (d/entity db 3))) {:db/id 1}))))) diff --git a/test/datascript/test/conn.cljc b/test/datascript/test/conn.cljc index 26e8bea2..ff9458c9 100644 --- a/test/datascript/test/conn.cljc +++ b/test/datascript/test/conn.cljc @@ -1,6 +1,7 @@ (ns datascript.test.conn (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.test.core :as tdc])) @@ -16,23 +17,23 @@ (let [conn (d/create-conn)] (is (= #{} (set (d/datoms @conn :eavt)))) (is (= nil (:schema @conn)))) - + (let [conn (d/create-conn schema)] (is (= #{} (set (d/datoms @conn :eavt)))) (is (= schema (:schema @conn)))) - + (let [conn (d/conn-from-datoms datoms)] (is (= datoms (set (d/datoms @conn :eavt)))) (is (= nil (:schema @conn)))) - + (let [conn (d/conn-from-datoms datoms schema)] (is (= datoms (set (d/datoms @conn :eavt)))) (is (= schema (:schema @conn)))) - + (let [conn (d/conn-from-db (d/init-db datoms))] (is (= datoms (set (d/datoms @conn :eavt)))) (is (= nil (:schema @conn)))) - + (let [conn (d/conn-from-db (d/init-db datoms schema))] (is (= datoms (set (d/datoms @conn :eavt)))) (is (= schema (:schema @conn))))) @@ -48,7 +49,7 @@ (d/reset-conn! conn db' :meta) (is (= datoms' (set (d/datoms @conn :eavt)))) (is (= schema' (:schema @conn))) - + (let [{:keys [db-before db-after tx-data tx-meta]} @report] (is (= datoms (set (d/datoms db-before :eavt)))) (is (= schema (:schema db-before))) @@ -60,3 +61,4 @@ [1 :age 20 true] [1 :sex :male true]] (map (juxt :e :a :v :added) tx-data)))))) + diff --git a/test/datascript/test/core.cljc b/test/datascript/test/core.cljc index 73a5ec48..95a54d02 100644 --- a/test/datascript/test/core.cljc +++ b/test/datascript/test/core.cljc @@ -1,9 +1,11 @@ (ns datascript.test.core (:require - [clojure.edn :as edn] - [clojure.test :as t :refer [is are deftest testing]] + [#?(:cljd cljd.reader :default clojure.edn) :as edn] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [clojure.string :as str] - [cognitect.transit :as transit] + #?(:cljd [wevre.transit-cljd :as transit] + :default [cognitect.transit :as transit]) [datascript.core :as d] [datascript.impl.entity :as de] [datascript.db :as db :refer [defrecord-updatable]] @@ -12,6 +14,20 @@ #?(:cljs (enable-console-print!)) +#?(:cljd + (defmacro thrown-msg? [expected-msg & body] + `(try + ~@body + false + (catch cljd.core/ExceptionInfo e# + (let [msg# (cljd.core/ex-message e#)] + (or (and msg# + (if (instance? RegExp ~expected-msg) + (re-find ~expected-msg ^String msg#) + (.contains ^String msg# ~expected-msg))) + ;; rethrow for now to have a telling exception + (throw e#))))))) + ;; Added special case for printing ex-data of ExceptionInfo #?(:cljs (defmethod t/report [::t/default :error] [m] @@ -36,13 +52,15 @@ (reset! test-summary (dissoc m :type)))) (defn wrap-res [f] - #?(:cljs (do (f) (clj->js @test-summary)) + #?(:cljd :TOOD? + :cljs (do (f) (clj->js @test-summary)) :clj (let [res (f)] (when (pos? (+ (:fail res) (:error res))) (System/exit 1))))) ;; utils -#?(:clj +#?(:cljd nil + :clj (defmethod t/assert-expr 'thrown-msg? [msg form] (let [[_ match & body] form] `(try ~@body @@ -64,7 +82,11 @@ (defn all-datoms [db] (into #{} (map (juxt :e :a :v)) (d/datoms db :eavt))) -#?(:clj +#?(:cljd + (defn no-namespace-maps + ([]) + ([_])) + :clj (defn no-namespace-maps [t] (binding [*print-namespace-maps* false] (t))) @@ -72,7 +94,16 @@ (def no-namespace-maps {:before #(set! *print-namespace-maps* false)})) (defn transit-write [o type] - #?(:clj + #?(:cljd + (let [json-enc (.-encoder (transit/json)) + jsonv-enc (.-encoder (transit/json-verbose)) + msgpack-enc (.-encoder (transit/msgpack))] + (condp = type + :json (.convert json-enc o) + :json-verbose (.convert jsonv-enc o) + :msgpack (.convert msgpack-enc o) + (.convert json-enc o))) + :clj (with-open [os (java.io.ByteArrayOutputStream.)] (let [writer (transit/writer os type)] (transit/write writer o) @@ -80,19 +111,31 @@ :cljs (transit/write (transit/writer type) o))) + (defn transit-write-str [o] - #?(:clj (String. ^bytes (transit-write o :json) "UTF-8") + #?(:cljd (transit-write o :json) + :clj (String. ^bytes (transit-write o :json) "UTF-8") :cljs (transit-write o :json))) (defn transit-read [s type] - #?(:clj + #?(:cljd + (let [json-dec (.-decoder (transit/json)) + jsonv-dec (.-decoder (transit/json-verbose)) + msgpack-dec (.-decoder (transit/msgpack))] + (condp = type + :json (.convert json-dec s) + :json-verbose (.convert jsonv-dec s) + :msgpack (.convert msgpack-dec s) + (.convert json-dec s))) + :clj (with-open [is (java.io.ByteArrayInputStream. s)] (transit/read (transit/reader is type))) :cljs (transit/read (transit/reader type) s))) (defn transit-read-str [s] - #?(:clj (transit-read (.getBytes ^String s "UTF-8") :json) + #?(:cljd (transit-read s :json) + :clj (transit-read (.getBytes ^String s "UTF-8") :json) :cljs (transit-read s :json))) ;; Core tests diff --git a/test/datascript/test/db.cljc b/test/datascript/test/db.cljc index 625ee331..bdbdd6fb 100644 --- a/test/datascript/test/db.cljc +++ b/test/datascript/test/db.cljc @@ -12,6 +12,7 @@ ;; (defrecord-updatable HashBeef [x] #?@(:cljs [IHash (-hash [hb] 0xBEEF)] + :cljd [cljd.core/IHash (-hash [hb] 0xBEEF)] :clj [clojure.lang.IHashEq (hasheq [hb] 0xBEEF)])) (deftest test-defrecord-updatable @@ -26,7 +27,8 @@ (is (= h @(.-hash db)))))) (defn- now [] - #?(:clj (System/currentTimeMillis) + #?(:cljd (.-millisecondsSinceEpoch (DateTime/now)) + :clj (System/currentTimeMillis) :cljs (.getTime (js/Date.)))) (deftest test-uuid diff --git a/test/datascript/test/entity.cljc b/test/datascript/test/entity.cljc index b851e614..b4a6cc86 100644 --- a/test/datascript/test/entity.cljc +++ b/test/datascript/test/entity.cljc @@ -1,11 +1,12 @@ (ns datascript.test.entity (:require - [clojure.edn :as edn] + [#?(:cljd cljd.reader :default clojure.edn) :as edn] [clojure.test :as t :refer [is are deftest testing]] [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc]) - #?(:clj + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd nil :clj (:import [clojure.lang ExceptionInfo]))) @@ -101,7 +102,7 @@ (is (= nil (:comp (d/entity db 1)))) (is (= nil (:multiref (d/entity db 1)))) (is (= nil (:multicomp (d/entity db 1)))))) - + (deftest test-entity-misses (let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}}) (d/db-with [{:db/id 1, :name "Ivan"} diff --git a/test/datascript/test/explode.cljc b/test/datascript/test/explode.cljc index 028ba962..ae22d643 100644 --- a/test/datascript/test/explode.cljc +++ b/test/datascript/test/explode.cljc @@ -1,13 +1,18 @@ (ns datascript.test.explode (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc])) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) #?(:cljs (def Throwable - js/Error)) + js/Error) + :cljd + (def Throwable + ExceptionInfo)) (deftest test-explode (doseq [coll [["Devil" "Tupen"] @@ -39,25 +44,27 @@ (doseq [children [[-2 -3] #{-2 -3} (list -2 -3)]] - (testing (str "ref + many + " children) + (testing (str "ref + many + " children) (let [db (d/db-with db0 [{:db/id -1, :name "Ivan", :children children} - {:db/id -2, :name "Petr"} + {:db/id -2, :name "Petr"} {:db/id -3, :name "Evgeny"}])] (is (= #{["Petr"] ["Evgeny"]} (d/q '[:find ?n :where [_ :children ?e] [?e :name ?n]] db)))))) - + + (let [db (d/db-with db0 [{:db/id -1, :name "Ivan"} - {:db/id -2, :name "Petr", :_children -1} + {:db/id -2, :name "Petr", :_children -1} {:db/id -3, :name "Evgeny", :_children -1}])] (is (= #{["Petr"] ["Evgeny"]} (d/q '[:find ?n :where [_ :children ?e] [?e :name ?n]] db)))) - + + (is (thrown-msg? "Bad attribute :_parent: reverse attribute name requires {:db/valueType :db.type/ref} in schema" (d/db-with db0 [{:name "Sergey" :_parent 1}]))))) @@ -69,17 +76,18 @@ (d/db-with db tx))) [{:db/id 5 :name "Ivan" :profile {:db/id 7 :email "@2"}}] #{[5 :name "Ivan"] [5 :profile 7] [7 :email "@2"]} - + [{:name "Ivan" :profile {:email "@2"}}] #{[1 :name "Ivan"] [1 :profile 2] [2 :email "@2"]} - + ;; issue-59 [{:profile {:email "@2"}}] #{[2 :profile 1] [1 :email "@2"]} - + [{:email "@2" :_profile {:name "Ivan"}}] #{[1 :email "@2"] [2 :name "Ivan"] [2 :profile 1]})) - + + (testing "multi-valued" (let [schema {:profile {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many}} @@ -89,7 +97,7 @@ (d/db-with db tx))) [{:db/id 5 :name "Ivan" :profile {:db/id 7 :email "@2"}}] #{[5 :name "Ivan"] [5 :profile 7] [7 :email "@2"]} - + [{:db/id 5 :name "Ivan" :profile [{:db/id 7 :email "@2"} {:db/id 8 :email "@3"}]}] #{[5 :name "Ivan"] [5 :profile 7] [7 :email "@2"] [5 :profile 8] [8 :email "@3"]} @@ -98,14 +106,16 @@ [{:name "Ivan" :profile [{:email "@2"} {:email "@3"}]}] #{[1 :name "Ivan"] [1 :profile 2] [2 :email "@2"] [1 :profile 3] [3 :email "@3"]} - + ;; issue-467 [{:name "Ivan" :profile #{{:email "@2"} {:email "@3"}}}] - #{[1 :name "Ivan"] [1 :profile 2] [2 :email "@3"] [1 :profile 3] [3 :email "@2"]} - + ;; set iteration order is non-deterministic across platforms + #?(:cljd #{[1 :name "Ivan"] [1 :profile 2] [2 :email "@2"] [1 :profile 3] [3 :email "@3"]} + :default #{[1 :name "Ivan"] [1 :profile 2] [2 :email "@3"] [1 :profile 3] [3 :email "@2"]}) + [{:name "Ivan" :profile (list {:email "@2"} {:email "@3"})}] #{[1 :name "Ivan"] [1 :profile 2] [2 :email "@2"] [1 :profile 3] [3 :email "@3"]} - + [{:email "@2" :_profile {:name "Ivan"}}] #{[1 :email "@2"] [2 :name "Ivan"] [2 :profile 1]} @@ -123,7 +133,7 @@ [1 :name "Name"] [2 :name "C"]] (mapv (juxt :e :a :v) (d/datoms db :eavt))))) - + (let [schema {:comp {:db/valueType :db.type/ref :db/cardinality :db.cardinality/many}} db (-> (d/empty-db schema) @@ -133,7 +143,7 @@ [1 :name "Name"] [2 :name "C"]] (mapv (juxt :e :a :v) (d/datoms db :eavt))))) - + (let [schema {:comp {:db/valueType :db.type/ref :db/isComponent true}} db (-> (d/empty-db schema) @@ -143,7 +153,7 @@ [1 :name "Name"] [2 :name "C"]] (mapv (juxt :e :a :v) (d/datoms db :eavt))))) - + (let [schema {:comp {:db/valueType :db.type/ref}} db (-> (d/empty-db schema) (d/db-with [{:db/id -1 :name "Name"}]) @@ -152,4 +162,4 @@ [1 :name "Name"] [2 :name "C"]] (mapv (juxt :e :a :v) (d/datoms db :eavt)))))) - + diff --git a/test/datascript/test/index.cljc b/test/datascript/test/index.cljc index 094c30f4..213a8487 100644 --- a/test/datascript/test/index.cljc +++ b/test/datascript/test/index.cljc @@ -1,9 +1,11 @@ (ns datascript.test.index (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc])) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) (deftest test-datoms (let [dvec #(vector (:e %) (:a %) (:v %)) @@ -96,11 +98,11 @@ (is (= [1 :age 44] (dvec (d/find-datom db :eavt 1 :age)))) (is (= [1 :name "Petr"] (dvec (d/find-datom db :eavt 1 :name)))) (is (= [1 :name "Petr"] (dvec (d/find-datom db :eavt 1 :name "Petr")))) - + (is (= [2 :age 25] (dvec (d/find-datom db :eavt 2)))) (is (= [2 :age 25] (dvec (d/find-datom db :eavt 2 :age)))) (is (= [2 :name "Ivan"] (dvec (d/find-datom db :eavt 2 :name)))) - + (is (= nil (dvec (d/find-datom db :eavt 1 :name "Ivan")))) (is (= nil (dvec (d/find-datom db :eavt 4)))) diff --git a/test/datascript/test/issues.cljc b/test/datascript/test/issues.cljc index 04c11be0..7e33f391 100644 --- a/test/datascript/test/issues.cljc +++ b/test/datascript/test/issues.cljc @@ -1,14 +1,15 @@ (ns datascript.test.issues (:require [datascript.core :as ds] - [clojure.test :as t :refer [is are deftest testing]])) + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]))) (deftest ^{:doc "CLJS `apply` + `vector` will hold onto mutable array of arguments directly"} issue-262 (let [db (ds/db-with (ds/empty-db) [{:attr "A"} {:attr "B"}])] (is (= (ds/q '[:find ?a ?b - :where [_ :attr ?a] + :where [_ :attr ?a] [(vector ?a) ?b]] db) #{["A" ["A"]] ["B" ["B"]]})))) @@ -21,7 +22,8 @@ (empty))] (t/is (= m (meta db))))) -#?(:clj +#?(:cljd nil + :clj (deftest ^{:doc "Can't pprint filtered db"} issue-330 (let [base (-> (ds/empty-db {:aka {:db/cardinality :db.cardinality/many}}) diff --git a/test/datascript/test/listen.cljc b/test/datascript/test/listen.cljc index 807a7a29..61d12d98 100644 --- a/test/datascript/test/listen.cljc +++ b/test/datascript/test/listen.cljc @@ -1,6 +1,7 @@ (ns datascript.test.listen (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.test.core :as tdc])) @@ -20,7 +21,7 @@ [:db/retract 4 :name "Evgeny"]]) (d/unlisten! conn :test) (d/transact! conn [[:db/add -1 :name "Geogry"]]) - + (is (= (:tx-data (first @reports)) [(db/datom 3 :name "Dima" (+ d/tx0 2) true) (db/datom 3 :age 19 (+ d/tx0 2) true) diff --git a/test/datascript/test/lookup_refs.cljc b/test/datascript/test/lookup_refs.cljc index 29480c88..f2c5d8b6 100644 --- a/test/datascript/test/lookup_refs.cljc +++ b/test/datascript/test/lookup_refs.cljc @@ -1,10 +1,13 @@ (ns datascript.test.lookup-refs (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc]) - #?(:clj + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd nil + :clj (:import [clojure.lang ExceptionInfo]))) @@ -13,13 +16,14 @@ :email {:db/unique :db.unique/value}}) [{:db/id 1 :name "Ivan" :email "@1" :age 35} {:db/id 2 :name "Petr" :email "@2" :age 22}])] - + + (are [eid res] (= (tdc/entity-map db eid) res) [:name "Ivan"] {:db/id 1 :name "Ivan" :email "@1" :age 35} [:email "@1"] {:db/id 1 :name "Ivan" :email "@1" :age 35} [:name "Sergey"] nil [:name nil] nil) - + (are [eid msg] (thrown-msg? msg (d/entity db eid)) [:name] "Lookup ref should contain 2 elements: [:name]" [:name 1 2] "Lookup ref should contain 2 elements: [:name 1 2]" @@ -34,59 +38,59 @@ ;; Additions [[:db/add [:name "Ivan"] :age 35]] {:db/id 1 :name "Ivan" :age 35} - + [{:db/id [:name "Ivan"] :age 35}] {:db/id 1 :name "Ivan" :age 35} - + [[:db/add 1 :friend [:name "Petr"]]] {:db/id 1 :name "Ivan" :friend {:db/id 2}} [[:db/add 1 :friend [:name "Petr"]]] {:db/id 1 :name "Ivan" :friend {:db/id 2}} - + [{:db/id 1 :friend [:name "Petr"]}] {:db/id 1 :name "Ivan" :friend {:db/id 2}} - + [{:db/id 2 :_friend [:name "Ivan"]}] {:db/id 1 :name "Ivan" :friend {:db/id 2}} - + ;; lookup refs are resolved at intermediate DB value [[:db/add 3 :name "Oleg"] [:db/add 1 :friend [:name "Oleg"]]] {:db/id 1 :name "Ivan" :friend {:db/id 3}} - + ;; CAS [[:db.fn/cas [:name "Ivan"] :name "Ivan" "Oleg"]] {:db/id 1 :name "Oleg"} - + [[:db/add 1 :friend 1] [:db.fn/cas 1 :friend [:name "Ivan"] 2]] {:db/id 1 :name "Ivan" :friend {:db/id 2}} - + [[:db/add 1 :friend 1] [:db.fn/cas 1 :friend 1 [:name "Petr"]]] {:db/id 1 :name "Ivan" :friend {:db/id 2}} - + ;; Retractions [[:db/add 1 :age 35] [:db/retract [:name "Ivan"] :age 35]] {:db/id 1 :name "Ivan"} - + [[:db/add 1 :friend 2] [:db/retract 1 :friend [:name "Petr"]]] {:db/id 1 :name "Ivan"} - + [[:db/add 1 :age 35] [:db.fn/retractAttribute [:name "Ivan"] :age]] {:db/id 1 :name "Ivan"} - + [[:db.fn/retractEntity [:name "Ivan"]]] nil) - + (are [tx msg] (thrown-msg? msg (d/db-with db tx)) [{:db/id [:name "Oleg"], :age 10}] "Nothing found for entity id [:name \"Oleg\"]" - + [[:db/add [:name "Oleg"] :age 10]] "Nothing found for entity id [:name \"Oleg\"]"))) @@ -106,13 +110,13 @@ [[:db/add 1 :friends [:name "Petr"]] [:db/add 1 :friends [:name "Oleg"]]] {:db/id 1 :name "Ivan" :friends #{{:db/id 2} {:db/id 3}}} - + [{:db/id 1 :friends [:name "Petr"]}] {:db/id 1 :name "Ivan" :friends #{{:db/id 2}}} [{:db/id 1 :friends [[:name "Petr"]]}] {:db/id 1 :name "Ivan" :friends #{{:db/id 2}}} - + [{:db/id 1 :friends [[:name "Petr"] [:name "Oleg"]]}] {:db/id 1 :name "Ivan" :friends #{{:db/id 2} {:db/id 3}}} @@ -121,7 +125,7 @@ [{:db/id 1 :friends [[:name "Petr"] 3]}] {:db/id 1 :name "Ivan" :friends #{{:db/id 2} {:db/id 3}}} - + ;; reverse refs [{:db/id 2 :_friends [:name "Ivan"]}] {:db/id 1 :name "Ivan" :friends #{{:db/id 2}}} @@ -142,44 +146,44 @@ (are [index attrs datoms] (= (map (juxt :e :a :v) (apply d/datoms db index attrs)) datoms) :eavt [[:name "Ivan"]] [[1 :friends 2] [1 :friends 3] [1 :name "Ivan"]] - + :eavt [[:name "Ivan"] :friends] [[1 :friends 2] [1 :friends 3]] - + :eavt [[:name "Ivan"] :friends [:name "Petr"]] [[1 :friends 2]] - + :aevt [:friends [:name "Ivan"]] [[1 :friends 2] [1 :friends 3]] - + :aevt [:friends [:name "Ivan"] [:name "Petr"]] [[1 :friends 2]] - + :avet [:friends [:name "Oleg"]] [[1 :friends 3] [2 :friends 3]] - + :avet [:friends [:name "Oleg"] [:name "Ivan"]] [[1 :friends 3]]) - + (are [index attrs resolved-attrs] (= (vec (apply d/seek-datoms db index attrs)) (vec (apply d/seek-datoms db index resolved-attrs))) :eavt [[:name "Ivan"]] [1] :eavt [[:name "Ivan"] :name] [1 :name] :eavt [[:name "Ivan"] :friends [:name "Oleg"]] [1 :friends 3] - + :aevt [:friends [:name "Petr"]] [:friends 2] :aevt [:friends [:name "Ivan"] [:name "Oleg"]] [:friends 1 3] - + :avet [:friends [:name "Oleg"]] [:friends 3] :avet [:friends [:name "Oleg"] [:name "Petr"]] [:friends 3 2]) - + (are [attr start end datoms] (= (map (juxt :e :a :v) (d/index-range db attr start end)) datoms) :friends [:name "Oleg"] [:name "Oleg"] [[1 :friends 3] [2 :friends 3]] - + :friends [:name "Petr"] [:name "Petr"] [[1 :friends 2]] - + :friends [:name "Petr"] [:name "Oleg"] [[1 :friends 2] [1 :friends 3] [2 :friends 3]]))) @@ -195,31 +199,32 @@ :where [?e :age ?v]] db [:name "Ivan"])) #{[[:name "Ivan"] 11]})) - + (is (= (set (d/q '[:find [?v ...] :in $ [?e ...] :where [?e :age ?v]] db [[:name "Ivan"] [:name "Petr"]])) #{11 22})) - + (is (= (set (d/q '[:find [?e ...] :in $ ?v :where [?e :friend ?v]] db [:name "Petr"])) #{1})) - + (is (= (set (d/q '[:find [?e ...] :in $ [?v ...] :where [?e :friend ?v]] db [[:name "Petr"] [:name "Oleg"]])) #{1 2})) - + (is (= (d/q '[:find ?e ?v :in $ ?e ?v :where [?e :friend ?v]] db [:name "Ivan"] [:name "Petr"]) #{[[:name "Ivan"] [:name "Petr"]]})) - + + (is (= (d/q '[:find ?e ?v :in $ [?e ...] [?v ...] :where [?e :friend ?v]] @@ -234,7 +239,8 @@ :where [?e :friend 3]] db [1 2 3 "A"]) #{[2]})) - + + (let [db2 (d/db-with (d/empty-db schema) [{:db/id 3 :name "Ivan" :id 3} {:db/id 1 :name "Petr" :id 1} @@ -248,18 +254,18 @@ #{[[:name "Ivan"] 1 3] [[:name "Petr"] 2 1] [[:name "Oleg"] 3 2]}))) - + (testing "inline refs" (is (= (d/q '[:find ?v :where [[:name "Ivan"] :friend ?v]] db) #{[2]})) - + (is (= (d/q '[:find ?e :where [?e :friend [:name "Petr"]]] db) #{[1]})) - + (is (thrown-msg? "Nothing found for entity id [:name \"Valery\"]" (d/q '[:find ?e :where [[:name "Valery"] :friend ?e]] diff --git a/test/datascript/test/lru.cljc b/test/datascript/test/lru.cljc index 6f4e0163..9323fb61 100644 --- a/test/datascript/test/lru.cljc +++ b/test/datascript/test/lru.cljc @@ -1,6 +1,7 @@ (ns datascript.test.lru (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.lru :as lru])) (deftest test-lru diff --git a/test/datascript/test/parser.cljc b/test/datascript/test/parser.cljc index d7455edf..70889ea3 100644 --- a/test/datascript/test/parser.cljc +++ b/test/datascript/test/parser.cljc @@ -1,54 +1,57 @@ (ns datascript.test.parser (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.parser :as dp] - [datascript.test.core :as tdc]) - #?(:clj - (:import - [clojure.lang ExceptionInfo]))) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd (:require [cljd.core :refer [ExceptionInfo]]) + :clj + (:import [clojure.lang ExceptionInfo]))) (deftest bindings (are [form res] (= (dp/parse-binding form) res) '?x (dp/->BindScalar (dp/->Variable '?x)) - + '_ (dp/->BindIgnore) - + '[?x ...] (dp/->BindColl (dp/->BindScalar (dp/->Variable '?x))) - + '[?x] (dp/->BindTuple [(dp/->BindScalar (dp/->Variable '?x))]) - + '[?x ?y] (dp/->BindTuple [(dp/->BindScalar (dp/->Variable '?x)) (dp/->BindScalar (dp/->Variable '?y))]) - + '[_ ?y] (dp/->BindTuple [(dp/->BindIgnore) (dp/->BindScalar (dp/->Variable '?y))]) - + '[[_ [?x ...]] ...] (dp/->BindColl (dp/->BindTuple [(dp/->BindIgnore) (dp/->BindColl (dp/->BindScalar (dp/->Variable '?x)))])) - + '[[?a ?b ?c]] (dp/->BindColl (dp/->BindTuple [(dp/->BindScalar (dp/->Variable '?a)) (dp/->BindScalar (dp/->Variable '?b)) (dp/->BindScalar (dp/->Variable '?c))]))) - - (is (thrown-with-msg? ExceptionInfo #"Cannot parse binding" - (dp/parse-binding :key)))) + + (is #?(:cljd (thrown-msg? #"Cannot parse binding" + (dp/parse-binding :key)) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse binding" + (dp/parse-binding :key))))) (deftest in (are [form res] (= (dp/parse-in form) res) '[?x] [(dp/->BindScalar (dp/->Variable '?x))] - + '[$ $1 % _ ?x] [(dp/->BindScalar (dp/->SrcVar '$)) (dp/->BindScalar (dp/->SrcVar '$1)) @@ -62,13 +65,19 @@ (dp/->BindTuple [(dp/->BindIgnore) (dp/->BindColl (dp/->BindScalar (dp/->Variable '?x)))]))]) - - (is (thrown-with-msg? ExceptionInfo #"Cannot parse binding" - (dp/parse-in ['?x :key])))) + + + (is #?(:cljd (thrown-msg? #"Cannot parse binding" + (dp/parse-in ['?x :key])) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse binding" + (dp/parse-in ['?x :key]))))) (deftest with (is (= (dp/parse-with '[?x ?y]) [(dp/->Variable '?x) (dp/->Variable '?y)])) - - (is (thrown-with-msg? ExceptionInfo #"Cannot parse :with clause" - (dp/parse-with '[?x _])))) + + + (is #?(:cljd (thrown-msg? #"Cannot parse :with clause" + (dp/parse-with '[?x _])) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse :with clause" + (dp/parse-with '[?x _]))))) diff --git a/test/datascript/test/parser_find.cljc b/test/datascript/test/parser_find.cljc index bce2989d..bf9fd488 100644 --- a/test/datascript/test/parser_find.cljc +++ b/test/datascript/test/parser_find.cljc @@ -1,6 +1,8 @@ (ns datascript.test.parser-find (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] [datascript.db :as db] [datascript.parser :as dp] @@ -8,7 +10,10 @@ #?(:cljs (def Throwable - js/Error)) + js/Error) + :cljd + (def Throwable + ExceptionInfo)) (deftest test-parse-find (is (= (dp/parse-find '[?a ?b]) diff --git a/test/datascript/test/parser_query.cljc b/test/datascript/test/parser_query.cljc index 168d3a92..e23adbdb 100644 --- a/test/datascript/test/parser_query.cljc +++ b/test/datascript/test/parser_query.cljc @@ -1,11 +1,13 @@ (ns datascript.test.parser-query (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] [datascript.db :as db] [datascript.parser :as dp] - [datascript.test.core :as tdc]) - #?(:clj + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd nil :clj (:import [clojure.lang ExceptionInfo]))) @@ -16,30 +18,30 @@ '[: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")) diff --git a/test/datascript/test/parser_return_map.cljc b/test/datascript/test/parser_return_map.cljc index 0e3ad285..9045b079 100644 --- a/test/datascript/test/parser_return_map.cljc +++ b/test/datascript/test/parser_return_map.cljc @@ -1,14 +1,19 @@ (ns datascript.test.parser-return-map (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] [datascript.parser :as dp] [datascript.db :as db] - [datascript.test.core :as tdc])) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) #?(:cljs (def Throwable - js/Error)) + js/Error) + :cljd + (def Throwable + ExceptionInfo)) (deftest test-parse-return-map (is (= (:qreturn-map (dp/parse-query '[:find ?a ?b :keys x y :where [?a ?b]])) diff --git a/test/datascript/test/parser_rules.cljc b/test/datascript/test/parser_rules.cljc index a6c40a1f..9480ca06 100644 --- a/test/datascript/test/parser_rules.cljc +++ b/test/datascript/test/parser_rules.cljc @@ -1,13 +1,14 @@ (ns datascript.test.parser-rules (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.parser :as dp] - [datascript.test.core :as tdc]) - #?(:clj - (:import - [clojure.lang ExceptionInfo]))) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd (:require [cljd.core :refer [ExceptionInfo]]) + :clj + (:import [clojure.lang ExceptionInfo]))) (deftest clauses (are [form res] (= (set (dp/parse-rules form)) res) @@ -22,7 +23,7 @@ [(dp/->Variable '?x) (dp/->Constant :name) (dp/->Placeholder)])])])})) (deftest rule-vars - (are [form res] (= (set (dp/parse-rules form)) res) + (are [form res] (= (set (dp/parse-rules form)) res) '[[(rule [?x] ?y) [_]]] #{(dp/->Rule @@ -49,17 +50,25 @@ (dp/->RuleVars [(dp/->Variable '?x)] nil) [(dp/->Pattern (dp/->DefaultSrc) [(dp/->Placeholder)])])])}) - (is (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars" - (dp/parse-rules '[[(rule) [_]]]))) + (is #?(:cljd (thrown-msg? #"Cannot parse rule-vars" + (dp/parse-rules '[[(rule) [_]]])) + :default (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 #?(:cljd (thrown-msg? #"Cannot parse rule-vars" + (dp/parse-rules '[[(rule []) [_]]])) + :default (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) [_]]])))) + (is #?(:cljd (thrown-msg? #"Rule variables should be distinct" + (dp/parse-rules '[[(rule ?x ?y ?x) [_]]])) + :default (thrown-with-msg? ExceptionInfo #"Rule variables should be distinct" + (dp/parse-rules '[[(rule ?x ?y ?x) [_]]])))) + + (is #?(:cljd (thrown-msg? #"Rule variables should be distinct" + (dp/parse-rules '[[(rule [?x ?y] ?z ?x) [_]]])) + :default (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) @@ -95,13 +104,21 @@ (dp/->RuleVars nil [(dp/->Variable '?x)]) [(dp/->Pattern (dp/->DefaultSrc) [(dp/->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]) [_]]])))) + (is #?(:cljd (thrown-msg? #"Rule branch should have clauses" + (dp/parse-rules '[[(rule ?x)]])) + :default (thrown-with-msg? ExceptionInfo #"Rule branch should have clauses" + (dp/parse-rules '[[(rule ?x)]])))) + + (is #?(:cljd (thrown-msg? #"Arity mismatch" + (dp/parse-rules '[[(rule ?x) [_]] + [(rule ?x ?y) [_]]])) + :default (thrown-with-msg? ExceptionInfo #"Arity mismatch" + (dp/parse-rules '[[(rule ?x) [_]] + [(rule ?x ?y) [_]]])))) + + (is #?(:cljd (thrown-msg? #"Arity mismatch" + (dp/parse-rules '[[(rule ?x) [_]] + [(rule [?x]) [_]]])) + :default (thrown-with-msg? ExceptionInfo #"Arity mismatch" + (dp/parse-rules '[[(rule ?x) [_]] + [(rule [?x]) [_]]]))))) diff --git a/test/datascript/test/parser_where.cljc b/test/datascript/test/parser_where.cljc index 3e64723c..44542177 100644 --- a/test/datascript/test/parser_where.cljc +++ b/test/datascript/test/parser_where.cljc @@ -1,25 +1,26 @@ (ns datascript.test.parser-where (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.parser :as dp] - [datascript.test.core :as tdc]) - #?(:clj - (:import - [clojure.lang ExceptionInfo]))) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd (:require [cljd.core :refer [ExceptionInfo]]) + :clj + (:import [clojure.lang ExceptionInfo]))) (deftest pattern (are [clause pattern] (= (dp/parse-clause clause) pattern) '[?e ?a ?v] (dp/->Pattern (dp/->DefaultSrc) [(dp/->Variable '?e) (dp/->Variable '?a) (dp/->Variable '?v)]) - + '[_ ?a _ _] (dp/->Pattern (dp/->DefaultSrc) [(dp/->Placeholder) (dp/->Variable '?a) (dp/->Placeholder) (dp/->Placeholder)]) - + '[$x _ ?a _ _] (dp/->Pattern (dp/->SrcVar '$x) [(dp/->Placeholder) (dp/->Variable '?a) (dp/->Placeholder) (dp/->Placeholder)]) - + '[$x _ :name ?v] (dp/->Pattern (dp/->SrcVar '$x) [(dp/->Placeholder) (dp/->Constant :name) (dp/->Variable '?v)]) @@ -29,50 +30,57 @@ '[$x _ $src-sym ?v] (dp/->Pattern (dp/->SrcVar '$x) [(dp/->Placeholder) (dp/->Constant '$src-sym) (dp/->Variable '?v)])) - (is (thrown-with-msg? ExceptionInfo #"Pattern could not be empty" - (dp/parse-clause '[])))) + (is #?(:cljd (thrown-msg? #"Pattern could not be empty" + (dp/parse-clause '[])) + :default (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)] (dp/->Predicate (dp/->PlainSymbol 'pred) [(dp/->Variable '?a) (dp/->Constant 1)]) - + '[(pred)] (dp/->Predicate (dp/->PlainSymbol 'pred) []) - + '[(?custom-pred ?a)] (dp/->Predicate (dp/->Variable '?custom-pred) [(dp/->Variable '?a)]))) +(def ->Fn + #?(:cljd dp/->FunctionCall :default dp/->FunctionCall)) + (deftest test-fn (are [clause res] (= (dp/parse-clause clause) res) '[(fn ?a 1) ?x] - (dp/->Function (dp/->PlainSymbol 'fn) [(dp/->Variable '?a) (dp/->Constant 1)] (dp/->BindScalar (dp/->Variable '?x))) - + (->Fn (dp/->PlainSymbol 'fn) [(dp/->Variable '?a) (dp/->Constant 1)] (dp/->BindScalar (dp/->Variable '?x))) + '[(fn) ?x] - (dp/->Function (dp/->PlainSymbol 'fn) [] (dp/->BindScalar (dp/->Variable '?x))) - + (->Fn (dp/->PlainSymbol 'fn) [] (dp/->BindScalar (dp/->Variable '?x))) + '[(?custom-fn) ?x] - (dp/->Function (dp/->Variable '?custom-fn) [] (dp/->BindScalar (dp/->Variable '?x))) + (->Fn (dp/->Variable '?custom-fn) [] (dp/->BindScalar (dp/->Variable '?x))) '[(?custom-fn ?arg) ?x] - (dp/->Function (dp/->Variable '?custom-fn) [(dp/->Variable '?arg)] (dp/->BindScalar (dp/->Variable '?x))))) + (->Fn (dp/->Variable '?custom-fn) [(dp/->Variable '?arg)] (dp/->BindScalar (dp/->Variable '?x))))) (deftest rule-expr (are [clause res] (= (dp/parse-clause clause) res) '(friends ?x ?y) (dp/->RuleExpr (dp/->DefaultSrc) (dp/->PlainSymbol 'friends) [(dp/->Variable '?x) (dp/->Variable '?y)]) - + '(friends "Ivan" _) (dp/->RuleExpr (dp/->DefaultSrc) (dp/->PlainSymbol 'friends) [(dp/->Constant "Ivan") (dp/->Placeholder)]) '($1 friends ?x ?y) (dp/->RuleExpr (dp/->SrcVar '$1) (dp/->PlainSymbol 'friends) [(dp/->Variable '?x) (dp/->Variable '?y)]) - + '(friends something) (dp/->RuleExpr (dp/->DefaultSrc) (dp/->PlainSymbol 'friends) [(dp/->Constant 'something)])) - (is (thrown-with-msg? ExceptionInfo #"rule-expr requires at least one argument" - (dp/parse-clause '(friends))))) + (is #?(:cljd (thrown-msg? #"rule-expr requires at least one argument" + (dp/parse-clause '(friends))) + :default (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) @@ -96,13 +104,13 @@ (dp/->Pattern (dp/->DefaultSrc) [(dp/->Variable '?x) (dp/->Placeholder) (dp/->Variable '?y)])]) - + '($1 not [?x]) (dp/->Not (dp/->SrcVar '$1) [(dp/->Variable '?x)] [(dp/->Pattern (dp/->DefaultSrc) [(dp/->Variable '?x)])]) - + '(not-join [?e ?y] [?e :follows ?x] [?x _ ?y]) @@ -115,7 +123,7 @@ (dp/->Pattern (dp/->DefaultSrc) [(dp/->Variable '?x) (dp/->Placeholder) (dp/->Variable '?y)])]) - + '($1 not-join [?e] [?e :follows ?x]) (dp/->Not (dp/->SrcVar '$1) @@ -123,18 +131,26 @@ [(dp/->Pattern (dp/->DefaultSrc) [(dp/->Variable '?e) (dp/->Constant :follows) (dp/->Variable '?x)])])) - - (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 #?(:cljd (thrown-msg? #"Join variables should not be empty" + (dp/parse-clause '(not-join [] [?y]))) + :default (thrown-with-msg? ExceptionInfo #"Join variables should not be empty" + (dp/parse-clause '(not-join [] [?y]))))) + + (is #?(:cljd (thrown-msg? #"Join variables should not be empty" + (dp/parse-clause '(not [_]))) + :default (thrown-with-msg? ExceptionInfo #"Join variables should not be empty" + (dp/parse-clause '(not [_]))))) + + (is #?(:cljd (thrown-msg? #"Cannot parse 'not-join' clause" + (dp/parse-clause '(not-join [?x]))) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse 'not-join' clause" + (dp/parse-clause '(not-join [?x]))))) + + (is #?(:cljd (thrown-msg? #"Cannot parse 'not' clause" + (dp/parse-clause '(not))) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse 'not' clause" + (dp/parse-clause '(not)))))) (deftest or-clause (are [clause res] (= (dp/parse-clause clause) res) @@ -220,14 +236,22 @@ (dp/->DefaultSrc) [(dp/->Variable '?e) (dp/->Constant :follows) (dp/->Variable '?x)])])) - (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))))) + (is #?(:cljd (thrown-msg? #"Cannot parse rule-vars" + (dp/parse-clause '(or-join [] [?y]))) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse rule-vars" + (dp/parse-clause '(or-join [] [?y]))))) + + (is #?(:cljd (thrown-msg? #"Join variables should not be empty" + (dp/parse-clause '(or [_]))) + :default (thrown-with-msg? ExceptionInfo #"Join variables should not be empty" + (dp/parse-clause '(or [_]))))) + + (is #?(:cljd (thrown-msg? #"Cannot parse 'or-join' clause" + (dp/parse-clause '(or-join [?x]))) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse 'or-join' clause" + (dp/parse-clause '(or-join [?x]))))) + + (is #?(:cljd (thrown-msg? #"Cannot parse 'or' clause" + (dp/parse-clause '(or))) + :default (thrown-with-msg? ExceptionInfo #"Cannot parse 'or' clause" + (dp/parse-clause '(or)))))) diff --git a/test/datascript/test/pull_api.cljc b/test/datascript/test/pull_api.cljc index cac581d4..a1bf17ca 100644 --- a/test/datascript/test/pull_api.cljc +++ b/test/datascript/test/pull_api.cljc @@ -1,6 +1,7 @@ (ns datascript.test.pull-api (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.test.core :as tdc])) @@ -131,7 +132,7 @@ recdb (d/init-db (concat test-datoms [(d/datom 12 :part 10)]) test-schema)] - + (testing "Component entities are expanded recursively" (is (= parts (d/pull @*test-db '[:name :part] 10)))) @@ -196,6 +197,54 @@ (d/datom 8 :aka (str "aka-" idx)))) test-schema)] + #?(:cljd + (testing "cljd datoms count" + (is (= (+ 4 2000 (count test-datoms)) (count db))) + (let [MIN ^:unique (Object.) + MAX ^:unique (Object.) + cmp (fn [a b] + (cond + (identical? a b) 0 + (identical? a MIN) -1 + (identical? a MAX) 1 + (identical? b MIN) 1 + (identical? b MAX) -1 + :else (compare a b))) + cmp-eavt (fn [^db/Datom a ^db/Datom b] + (let [r (cmp (.-e a) (.-e b))] + (if-not (zero? r) + r + (let [r (cmp (.-a a) (.-a b))] + (if-not (zero? r) + r + (cmp (.-v a) (.-v b))))))) + datoms (into (sorted-set-by cmp-eavt) (.-eavt db)) + from (d/datom 8 :aka MIN) + to (d/datom 8 :aka MAX)] + (is (= 2000 + (count (subseq datoms >= from <= to))))) + (let [cmp (fn [a b] ; fishy cmp behaving like Datascript + (cond + (nil? a) 0 + (nil? b) 0 + :else (compare a b))) + cmp-eavt (fn [^db/Datom a ^db/Datom b] + (let [r (cmp (.-e a) (.-e b))] + (if-not (zero? r) + r + (let [r (cmp (.-a a) (.-a b))] + (if-not (zero? r) + r + (let [r (cmp (.-v a) (.-v b))] + (if-not (zero? r) + r + (cmp (.-tx a) (.-tx b))))))))) + datoms (into (sorted-set-by cmp-eavt) (.-eavt db)) + from (d/datom 8 :aka nil db/tx0) + to (d/datom 8 :aka nil db/txmax)] + (is (= 1097 + (count (subseq datoms >= from <= to))))))) + (testing "Without an explicit limit, the default is 1000" (is (= 1000 (->> (d/pull db '[:aka] 8) :aka count)))) @@ -474,11 +523,11 @@ [:aka :xform vector] {[:child :xform vector] '...}] 1))) - + (testing ":xform on cardinality/one ref issue-455" (is (= {:name "David" :father "Petr"} (d/pull @*test-db [:name {[:father :xform #(:name %)] ['*]}] 2)))) - + (testing ":xform on reverse ref" (is (= {:name "Petr" :_father ["David" "Thomas"]} (d/pull @*test-db [:name {[:_father :xform #(mapv :name %)] [:name]}] 1)))) @@ -486,7 +535,8 @@ (testing ":xform on reverse component ref" (is (= {:name "Part A.A" :_part "Part A"} (d/pull @*test-db [:name {[:_part :xform #(:name %)] [:name]}] 11)))) - + + (testing "missing attrs are processed by xform" (is (= {:normal [nil] :aka [nil] @@ -509,12 +559,12 @@ @*trace)] (is (= [[:db.pull/attr 1 :name nil]] (test-fn [:name] 1))) - + (testing "multival" (is (= [[:db.pull/attr 1 :aka nil] [:db.pull/attr 1 :name nil]] (test-fn [:name :aka] 1)))) - + (testing ":db/id is ignored" (is (= [] (test-fn [:db/id] 1))) (is (= [[:db.pull/attr 1 :name nil]] @@ -568,7 +618,7 @@ (d/filter (fn [_ datom] (not= "Tupen" (:v datom)))))] (is (= {:name "Petr" :aka ["Devil"]} (d/pull db '[:name :aka] 1)))) - (let [db (-> @*test-db d/serializable pr-str clojure.edn/read-string d/from-serializable)] + (let [db (-> @*test-db d/serializable pr-str #?(:cljd cljd.reader/read-string :default clojure.edn/read-string) d/from-serializable)] (is (= {:name "Petr" :aka ["Devil" "Tupen"]} (d/pull db '[:name :aka] 1)))) (let [db (d/init-db (d/datoms @*test-db :eavt) test-schema)] diff --git a/test/datascript/test/pull_parser.cljc b/test/datascript/test/pull_parser.cljc index c19b0ff7..c86c41d7 100644 --- a/test/datascript/test/pull_parser.cljc +++ b/test/datascript/test/pull_parser.cljc @@ -1,10 +1,12 @@ (ns datascript.test.pull-parser (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.pull-parser :as dpp] - [datascript.test.core :as tdc])) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) (def *db (delay @@ -92,7 +94,7 @@ ; xform [[:normal :xform 'inc]] (pattern :attrs [(attr :normal :xform inc)]) [[:normal :xform inc]] (pattern :attrs [(attr :normal :xform inc)]) - #?@(:clj [[[:normal :xform 'datascript.db/datom?]] (pattern :attrs [(attr :normal :xform db/datom?)])]) + #?@(:cljd [] :clj [[[:normal :xform 'datascript.db/datom?]] (pattern :attrs [(attr :normal :xform db/datom?)])]) ; combined ['(:multival :limit 100 :default :xyz :as :other :xform inc)] (pattern :attrs [(attr :multival, :multival? true, :default :xyz, :limit 100, :as :other, :xform inc)]) @@ -107,12 +109,12 @@ ['(default (:multival :limit 100) :xyz)] (pattern :attrs [(attr :multival, :multival? true, :default :xyz, :limit 100)]) ['(((limit :multival 100) :default :xyz))] (pattern :attrs [(attr :multival, :multival? true, :default :xyz, :limit 100)]) ['(((default :multival :xyz) :limit 100))] (pattern :attrs [(attr :multival, :multival? true, :default :xyz, :limit 100)]) - + ; repeated [:multival [:multival :default :xyz] [:multival :limit 100]] (pattern :attrs [(attr :multival, :multival? true, :limit 100)]) [:ref {:ref '...}] (pattern :attrs [(attr :ref, :ref? true, :pattern nil, :recursive? true, :recursion-limit nil)]) [{:ref '...} :ref] (pattern :attrs [(attr :ref, :ref? true)]) - + ; map spec [{:ref [:normal]}] (pattern :attrs [(attr :ref, :ref? true, :pattern (pattern :attrs [(attr :normal)]))]) [{:_ref [:normal]}] (pattern :reverse-attrs [(attr :ref, :as :_ref, :ref? true, :reverse? true, :pattern (pattern :attrs [(attr :normal)]))]) @@ -126,7 +128,7 @@ ; map spec limits [{:ref 100}] (pattern :attrs [(attr :ref, :ref? true, :pattern nil, :recursive? true, :recursion-limit 100)]) - [{:ref '...}] (pattern :attrs [(attr :ref, :ref? true, :pattern nil, :recursive? true, :recursion-limit nil)]) + [{:ref '...}] (pattern :attrs [(attr :ref, :ref? true, :pattern nil, :recursive? true, :recursion-limit nil)]) [{:ref "..."}] (pattern :attrs [(attr :ref, :ref? true, :pattern nil, :recursive? true, :recursion-limit nil)]) [{:_ref 100}] (pattern :reverse-attrs [(attr :ref, :as :_ref, :ref? true, :reverse? true, :pattern nil, :recursive? true, :recursion-limit 100)]) [{:_ref '...}] (pattern :reverse-attrs [(attr :ref, :as :_ref, :ref? true, :reverse? true, :pattern nil, :recursive? true, :recursion-limit nil)])) @@ -138,10 +140,10 @@ ; attr-expr ['(:multival :limit)] "Expected even number of opts, got: (:multival :limit)" - + ; limit ['(limit :multival)] "Expected ['limit attr-name (positive-number | nil)], got: (limit :multival)" - ['(:normal :limit 100)] "Expected limit attribute having :db.cardinality/many, got: :normal" + ['(:normal :limit 100)] "Expected limit attribute having :db.cardinality/many, got: :normal" ['(limit :normal 100)] "Expected limit attribute having :db.cardinality/many, got: :normal" ['(:multival :limit :abc)] "Expected (positive-number | nil), got: :abc" ['(limit :multival :abc)] "Expected (positive-number | nil), got: :abc" diff --git a/test/datascript/test/query.cljc b/test/datascript/test/query.cljc index b5b9dc45..be43b460 100644 --- a/test/datascript/test/query.cljc +++ b/test/datascript/test/query.cljc @@ -1,10 +1,12 @@ (ns datascript.test.query (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc]) - #?(:clj + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]] + #?(:cljd [cljd.core :refer [ExceptionInfo]])) + #?(:cljd nil :clj (:import [clojure.lang ExceptionInfo]))) @@ -104,7 +106,8 @@ #{[1 "ivan@mail.ru"] [2 "petr@gmail.com"] [3 "ivan@mail.ru"]}))) - + + (testing "Query without DB" (is (= (d/q '[:find ?a ?b :in ?a ?b] @@ -169,7 +172,8 @@ [2 :name "Petr"]] []) #{}))) - + + (testing "Placeholders" (is (= (d/q '[:find ?x ?z :in [?x _ ?z]] @@ -179,15 +183,23 @@ :in [[?x _ ?z]]] [[:x :y :z] [:a :b :c]]) #{[:x :z] [:a :c]}))) - + + (testing "Error reporting" - (is (thrown-with-msg? ExceptionInfo #"Cannot bind value :a to tuple \[\?a \?b\]" - (d/q '[:find ?a ?b :in [?a ?b]] :a))) - (is (thrown-with-msg? ExceptionInfo #"Cannot bind value :a to collection \[\?a \.\.\.\]" - (d/q '[:find ?a :in [?a ...]] :a))) - (is (thrown-with-msg? ExceptionInfo #"Not enough elements in a collection \[:a\] to bind tuple \[\?a \?b\]" - (d/q '[:find ?a ?b :in [?a ?b]] [:a])))))) - + (is #?(:cljd (thrown-msg? #"Cannot bind value :a to tuple \[\?a \?b\]" + (d/q '[:find ?a ?b :in [?a ?b]] :a)) + :default (thrown-with-msg? ExceptionInfo #"Cannot bind value :a to tuple \[\?a \?b\]" + (d/q '[:find ?a ?b :in [?a ?b]] :a)))) + (is #?(:cljd (thrown-msg? #"Cannot bind value :a to collection \[\?a \.\.\.\]" + (d/q '[:find ?a :in [?a ...]] :a)) + :default (thrown-with-msg? ExceptionInfo #"Cannot bind value :a to collection \[\?a \.\.\.\]" + (d/q '[:find ?a :in [?a ...]] :a)))) + (is #?(:cljd (thrown-msg? #"Not enough elements in a collection \[:a\] to bind tuple \[\?a \?b\]" + (d/q '[:find ?a ?b :in [?a ?b]] [:a])) + :default (thrown-with-msg? ExceptionInfo #"Not enough elements in a collection \[:a\] to bind tuple \[\?a \?b\]" + (d/q '[:find ?a ?b :in [?a ?b]] [:a]))))))) + + (deftest test-nested-bindings (is (= (d/q '[:find ?k ?v :in [[?k ?v] ...] diff --git a/test/datascript/test/query_aggregates.cljc b/test/datascript/test/query_aggregates.cljc index f2100fb0..e16e881f 100644 --- a/test/datascript/test/query_aggregates.cljc +++ b/test/datascript/test/query_aggregates.cljc @@ -1,6 +1,7 @@ (ns datascript.test.query-aggregates (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.test.core :as tdc])) @@ -34,7 +35,8 @@ :in [[?monster ?heads]]] monsters) [[6 1 3 4 2]]))) - + + (testing "Min and max are using comparator instead of default compare" ;; Wrong: using js '<' operator ;; (apply min [:a/b :a-/b :a/c]) => :a-/b @@ -60,8 +62,8 @@ #{[:red [3 4 5] [1 2 3]] [:blue [7 8] [7 8]]}))) - (testing "avg aggregate" - (is (= (ffirst (d/q '[:find (avg ?x) + (testing "avg aggregate" + (is (= (ffirst (d/q '[:find (avg ?x) :in [?x ...]] [10 15 20 35 75])) 31))) @@ -71,7 +73,8 @@ :in [?x ...]] [10 15 20 35 75])) 20))) - + + (testing "variance aggregate" (is (= (ffirst (d/q '[:find (variance ?x) :in [?x ...]] @@ -79,7 +82,7 @@ 554))) (testing "stddev aggregate" - (is (= (ffirst (d/q '[:find (stddev ?x) + (is (= (ffirst (d/q '[:find (stddev ?x) :in [?x ...]] [10 15 20 35 75])) 23.53720459187964))) @@ -88,15 +91,16 @@ (let [data [[:red 1] [:red 2] [:red 3] [:red 4] [:red 5] [:blue 7] [:blue 8]] result #{[:red [5 4 3 2 1]] [:blue [8 7]]}] - + (is (= (set (d/q '[:find ?color (aggregate ?agg ?x) :in [[?color ?x]] ?agg] data sort-reverse)) result)) - - #?(:clj - (is (= (set (d/q '[:find ?color (datascript.test.query-aggregates/sort-reverse ?x) - :in [[?color ?x]]] - data)) - result))))))) + + #?@(:cljd [] + :clj + [(is (= (set (d/q '[:find ?color (datascript.test.query-aggregates/sort-reverse ?x) + :in [[?color ?x]]] + data)) + result))]))))) diff --git a/test/datascript/test/query_find_specs.cljc b/test/datascript/test/query_find_specs.cljc index de064368..fc58072b 100644 --- a/test/datascript/test/query_find_specs.cljc +++ b/test/datascript/test/query_find_specs.cljc @@ -1,6 +1,7 @@ (ns datascript.test.query-find-specs (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.test.core :as tdc])) diff --git a/test/datascript/test/query_fns.cljc b/test/datascript/test/query_fns.cljc index 5561ae6b..eceeaa65 100644 --- a/test/datascript/test/query_fns.cljc +++ b/test/datascript/test/query_fns.cljc @@ -1,12 +1,13 @@ (ns datascript.test.query-fns (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc]) - #?(:clj - (:import - [clojure.lang ExceptionInfo]))) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd (:require [cljd.core :refer [ExceptionInfo]]) + :clj + (:import [clojure.lang ExceptionInfo]))) (deftest test-query-fns (testing "predicate without free variables" @@ -31,10 +32,14 @@ [(get-else $ ?e :height 300) ?height]] db) #{[1 15 300] [2 22 240] [3 37 300]})) - (is (thrown-with-msg? ExceptionInfo #"get-else: nil default value is not supported" - (d/q '[:find ?e ?height - :where [?e :age] - [(get-else $ ?e :height nil) ?height]] db)))) + (is #?(:cljd (thrown-msg? #"get-else: nil default value is not supported" + (d/q '[:find ?e ?height + :where [?e :age] + [(get-else $ ?e :height nil) ?height]] db)) + :default (thrown-with-msg? ExceptionInfo #"get-else: nil default value is not supported" + (d/q '[:find ?e ?height + :where [?e :age] + [(get-else $ ?e :height nil) ?height]] db))))) (testing "get-some" (is (= (d/q '[:find ?e ?a ?v @@ -266,7 +271,7 @@ [?e2 :name] [(< ?e ?e2)]] #{[1 2] [1 3] [1 4] [2 3] [2 4] [3 4]} - + ;; join with extra symbols [:find ?e ?e2 :where [?e :age ?a] @@ -330,7 +335,7 @@ (is (thrown-msg? "Where uses unknown source vars: [$]" (d/q '[:find ?x - :in $2 + :in $2 :where [$2 ?x] [(zero? $ ?x)]])))) (deftest test-issue-180 @@ -344,7 +349,9 @@ (defn sample-query-fn [] 42) -#?(:clj +#?(:cljd + (deftest test-symbol-resolution) + :clj (deftest test-symbol-resolution (is (= 42 (d/q '[:find ?x . :where [(datascript.test.query-fns/sample-query-fn) ?x]]))))) diff --git a/test/datascript/test/query_not.cljc b/test/datascript/test/query_not.cljc index 9855f5a6..829a4f2c 100644 --- a/test/datascript/test/query_not.cljc +++ b/test/datascript/test/query_not.cljc @@ -1,10 +1,12 @@ (ns datascript.test.query-not (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc]) - #?(:clj + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd nil :clj (:import [clojure.lang ExceptionInfo]))) @@ -24,35 +26,35 @@ [[?e :name] (not [?e :name "Ivan"])] #{3 4} - + [[?e :name] (not [?e :name "Ivan"] [?e :age 10])] #{2 3 4 6} - + [[?e :name] (not [?e :name "Ivan"]) (not [?e :age 10])] #{4} - + ;; full exclude [[?e :name] (not [?e :age])] #{} - + ;; not-intersecting rels [[?e :name "Ivan"] (not [?e :name "Oleg"])] #{1 2 5 6} - + ;; exclude empty set [[?e :name] (not [?e :name "Ivan"] [?e :name "Oleg"])] #{1 2 3 4 5 6} - + ;; nested excludes [[?e :name] (not [?e :name "Ivan"] @@ -73,7 +75,7 @@ [?e :name "Oleg"] [?e :age ?a])] #{[1 10] [2 20] [5 10] [6 20]} - + [[?e :age ?a] [?e :age 10] (not-join [?e] @@ -81,14 +83,15 @@ [?e :age ?a] [?e :age 10])] #{[1 10] [5 10]} - + ;; issue-481 [[?e :age ?a] (not-join [?a] [?e :name "Petr"] [?e :age ?a])] #{[1 10] [2 20] [3 10] [4 20] [5 10] [6 20]})) - + + (deftest test-default-source (let [db1 (d/db-with (d/empty-db) [[:db/add 1 :name "Ivan"] @@ -106,22 +109,22 @@ [[?e :name] (not [?e :name "Ivan"])] #{2} - + ;; NOT can reference any source [[?e :name] (not [$2 ?e :age 10])] #{2} - + ;; NOT can change default source [[?e :name] ($2 not [?e :age 10])] #{2} - + ;; even with another default source, it can reference any other source explicitly [[?e :name] ($2 not [$ ?e :name "Ivan"])] #{2} - + ;; nested NOT keeps the default source [[?e :name] ($2 not (not [?e :age 10]))] @@ -141,14 +144,14 @@ [?e :age 10] (not [?e :age 20])] #{[3]} - + ;; const \ const [:find ?e :where [?e :name "Oleg"] [?e :age 10] (not [?e :age 10])] #{} - + ;; rel \ const [:find ?e :where [?e :name "Oleg"] @@ -185,13 +188,13 @@ [(not [?e :name "Ivan"]) [?e :name]] "Insufficient bindings: none of #{?e} is bound in (not [?e :name \"Ivan\"])" - + [[?e :name] (not-join [?e] (not [1 :age ?a]) [?e :age ?a])] "Insufficient bindings: none of #{?a} is bound in (not [1 :age ?a])" - + [[?e :name] (not [?a :name "Ivan"])] "Insufficient bindings: none of #{?a} is bound in (not [?a :name \"Ivan\"])")) diff --git a/test/datascript/test/query_or.cljc b/test/datascript/test/query_or.cljc index 38e51da3..14d984e1 100644 --- a/test/datascript/test/query_or.cljc +++ b/test/datascript/test/query_or.cljc @@ -1,10 +1,13 @@ (ns datascript.test.query-or (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc]) - #?(:clj + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd nil + :clj (:import [clojure.lang ExceptionInfo]))) @@ -26,23 +29,23 @@ [(or [?e :name "Oleg"] [?e :age 10])] #{1 3 4 5} - + ;; one branch empty [(or [?e :name "Oleg"] [?e :age 30])] #{3 4} - + ;; both empty [(or [?e :name "Petr"] [?e :age 30])] #{} - + ;; join with 1 var [[?e :name "Ivan"] (or [?e :name "Oleg"] [?e :age 10])] #{1 5} - + ;; join with 2 vars [[?e :age ?a] (or (and [?e :name "Ivan"] @@ -84,7 +87,7 @@ (and [?e :age ?a] [?e :name ?n]))] #{1 2 3 4 5 6} - + [[?e :name ?a] [?e2 :name ?a] (or-join [?e] @@ -164,22 +167,22 @@ [[?e :name] (or [?e :name "Ivan"])] #{1} - + ;; OR can reference any source [[?e :name] (or [$2 ?e :age 10])] #{1} - + ;; OR can change default source [[?e :name] ($2 or [?e :age 10])] #{1} - + ;; even with another default source, it can reference any other source explicitly [[?e :name] ($2 or [$ ?e :name "Ivan"])] #{1} - + ;; nested OR keeps the default source [[?e :name] ($2 or (or [?e :age 10]))] @@ -220,11 +223,16 @@ db "Ivan"))))) (deftest test-errors - (is (thrown-with-msg? ExceptionInfo #"All clauses in 'or' must use same set of free vars, had \[#\{\?e\} #\{(\?a \?e|\?e \?a)\}\] in \(or \[\?e :name _\] \[\?e :age \?a\]\)" - (d/q '[:find ?e - :where (or [?e :name _] - [?e :age ?a])] - @*test-db))) + (is #?(:cljd (thrown-msg? #"All clauses in 'or' must use same set of free vars, had \[#\{\?e\} #\{(\?a \?e|\?e \?a)\}\] in \(or \[\?e :name _\] \[\?e :age \?a\]\)" + (d/q '[:find ?e + :where (or [?e :name _] + [?e :age ?a])] + @*test-db)) + :default (thrown-with-msg? ExceptionInfo #"All clauses in 'or' must use same set of free vars, had \[#\{\?e\} #\{(\?a \?e|\?e \?a)\}\] in \(or \[\?e :name _\] \[\?e :age \?a\]\)" + (d/q '[:find ?e + :where (or [?e :name _] + [?e :age ?a])] + @*test-db)))) (is (thrown-msg? "Insufficient bindings: #{?e} not bound in (or-join [[?e]] [?e :name \"Ivan\"])" (d/q '[:find ?e diff --git a/test/datascript/test/query_pull.cljc b/test/datascript/test/query_pull.cljc index 7518221a..f35b5c7b 100644 --- a/test/datascript/test/query_pull.cljc +++ b/test/datascript/test/query_pull.cljc @@ -1,6 +1,7 @@ (ns datascript.test.query-pull (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.test.core :as tdc])) @@ -42,7 +43,7 @@ res) '[(pull ?e ?pattern)] [:name] #{[{:name "Ivan"}] [{:name "Petr"}]} - + '[?e ?a ?pattern (pull ?e ?pattern)] [:name] #{[2 25 [:name] {:name "Ivan"}] [1 44 [:name] {:name "Petr"}]})) @@ -66,13 +67,14 @@ :where [$1 ?e :age 25]] db1 db2)) #{[1 {:name "Ivan"}]})) - + (is (= (set (d/q '[:find ?e (pull $2 ?e [:name]) :in $1 $2 :where [$2 ?e :age 25]] db1 db2)) #{[1 {:name "Petr"}]})) - + + (testing "$ is default source" (is (= (set (d/q '[:find ?e (pull ?e [:name]) :in $1 $ @@ -85,7 +87,8 @@ :where [?e :age 25]] @*test-db) {:name "Ivan"})) - + + (is (= (set (d/q '[:find [(pull ?e [:name]) ...] :where [?e :age ?a]] @*test-db)) diff --git a/test/datascript/test/query_return_map.cljc b/test/datascript/test/query_return_map.cljc index 90ffa248..4afccad2 100644 --- a/test/datascript/test/query_return_map.cljc +++ b/test/datascript/test/query_return_map.cljc @@ -1,6 +1,7 @@ (ns datascript.test.query-return-map (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] [datascript.test.core :as tdc])) @@ -42,5 +43,3 @@ [?e :age ?age]] @*test-db) {:n "Ivan" :a 25}))) - - diff --git a/test/datascript/test/query_rules.cljc b/test/datascript/test/query_rules.cljc index cde21f11..34d46f45 100644 --- a/test/datascript/test/query_rules.cljc +++ b/test/datascript/test/query_rules.cljc @@ -1,9 +1,11 @@ (ns datascript.test.query-rules (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc])) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) (deftest test-rules (let [db [ [5 :follow 3] @@ -16,7 +18,7 @@ '[[(follow ?x ?y) [?x :follow ?y]]]) #{[1 2] [2 3] [3 4] [2 4] [5 3] [4 6]})) - + (testing "Joining regular clauses with rule" (is (= (d/q '[:find ?y ?x :in $ % @@ -27,7 +29,7 @@ '[[(rule ?a ?b) [?a :follow ?b]]]) #{[3 2] [6 4] [4 2]}))) - + (testing "Rule context is isolated from outer context" (is (= (d/q '[:find ?x :in $ % @@ -122,7 +124,7 @@ [(?pred ?e2)]]] even?) #{[4 6] [2 4]}))) - + (testing "Using built-ins inside rule" (is (= (d/q '[:find ?x ?y :in $ % @@ -175,7 +177,7 @@ ;; issue-218 (deftest test-false-arguments - (let [db (d/db-with (d/empty-db) + (let [db (d/db-with (d/empty-db) [[:db/add 1 :attr true] [:db/add 2 :attr false]]) rules '[[(is ?id ?val) @@ -190,12 +192,14 @@ db rules))))) + ; issue-456 ; this used to stall for nearly a minute and/or fail with an OOM exception ; due to propagation of a relation with duplicate tuples during rule solving (deftest test-rule-performance-on-larger-datasets (let [now (fn [] #?(:clj (/ (System/nanoTime) 1000000.0) + :cljd (.-millisecondsSinceEpoch (DateTime/now)) :cljs (js/performance.now))) inline (fn [db] (d/q '[:find ?e diff --git a/test/datascript/test/query_v3.cljc b/test/datascript/test/query_v3.cljc index aca96359..6b912f7c 100644 --- a/test/datascript/test/query_v3.cljc +++ b/test/datascript/test/query_v3.cljc @@ -1,11 +1,14 @@ (ns datascript.test.query-v3 (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] [datascript.db :as db] [datascript.query-v3 :as dq] [datascript.test.core :as tdc]) - #?(:clj + #?(:cljd nil + :clj (:import [clojure.lang ExceptionInfo]))) @@ -16,7 +19,7 @@ '[:find ?a :where [?a]] [0 1] #"Wrong number of arguments for bindings \[\$\], 1 required, 2 provided" '[:find ?a :where [?a 1]] [:a] #"Cannot match by pattern \[\?a 1\] because source is not a collection: :a")) - + #_(deftest test-query (is (= (dq/q '[:find ?a :where [?a ?a]] [[1 2] [3 3] [4 5] [6 6]]) diff --git a/test/datascript/test/serialize.cljc b/test/datascript/test/serialize.cljc index 8a278135..0f833a14 100644 --- a/test/datascript/test/serialize.cljc +++ b/test/datascript/test/serialize.cljc @@ -1,20 +1,25 @@ (ns datascript.test.serialize (:require - [clojure.edn :as edn] - [clojure.test :as t :refer [is are deftest testing]] + [#?(:cljd cljd.reader :default clojure.edn) :as edn] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc] - #?(:clj [cheshire.core :as cheshire]) - #?(:clj [jsonista.core :as jsonista])) - #?(:clj - (:import - [clojure.lang ExceptionInfo]))) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]] + #?(:cljd ["dart:convert" :as dart:convert]) + #?(:cljd nil :clj [cheshire.core :as cheshire]) + #?(:cljd nil :clj [jsonista.core :as jsonista])) + #?(:cljd + (:require [cljd.core :refer [ExceptionInfo]]) + :clj + (:import [clojure.lang ExceptionInfo]))) (t/use-fixtures :once tdc/no-namespace-maps) (def readers - {#?@(:cljs ["cljs.reader/read-string" cljs.reader/read-string] + {#?@(:cljd ["cljd.reader/read-string" #(binding [*data-readers* (merge *data-readers* d/data-readers)] + (cljd.reader/read-string %))] + :cljs ["cljs.reader/read-string" cljs.reader/read-string] :clj ["clojure.edn/read-string" #(clojure.edn/read-string {:readers d/data-readers} %) "clojure.core/read-string" #(binding [*data-readers* (merge *data-readers* d/data-readers)] (read-string %))])}) @@ -25,11 +30,11 @@ (let [d (db/datom 1 :name "Oleg" 17 true)] (is (= (pr-str d) "#datascript/Datom [1 :name \"Oleg\" 17 true]")) (is (= d (read-fn (pr-str d))))) - + (let [d (db/datom 1 :name 3)] (is (= (pr-str d) "#datascript/Datom [1 :name 3 536870912 true]")) (is (= d (read-fn (pr-str d))))) - + (let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}}) (d/db-with [[:db/add 1 :name "Petr"] [:db/add 1 :age 44]]) @@ -74,7 +79,7 @@ [d/tx0 :txInstant 0xdeadbeef] [30 :url "https://"]]) -(def schema +(def schema {:name {} ;; nothing special about name :aka {:db/cardinality :db.cardinality/many} :age {:db/index true} @@ -84,6 +89,7 @@ :url {} ;; just a component prop :attach {}}) ;; should skip index + (deftest test-init-db (let [db-init (d/init-db (map (fn [[e a v]] (d/datom e a v)) data) @@ -99,15 +105,18 @@ (let [assertions [[:db/add -1 :name "Lex"]]] (is (= (d/db-with db-init assertions) (d/db-with db-transact assertions))))) - + + (testing "Roundtrip" (doseq [[r read-fn] readers] (testing r (is (= db-init (read-fn (pr-str db-init))))))) (testing "Reporting" - (is (thrown-with-msg? ExceptionInfo #"init-db expects list of Datoms, got " - (d/init-db [[:add -1 :name "Ivan"] {:add -1 :age 35}] schema)))))) + (is #?(:cljd (thrown-msg? #"init-db expects list of Datoms, got " + (d/init-db [[:add -1 :name "Ivan"] {:add -1 :age 35}] schema)) + :default (thrown-with-msg? ExceptionInfo #"init-db expects list of Datoms, got " + (d/init-db [[:add -1 :name "Ivan"] {:add -1 :age 35}] schema))))))) (deftest ^{:doc "issue-463"} test-max-eid-from-refs (let [db (-> (d/empty-db {:ref {:db/valueType :db.type/ref}}) @@ -125,37 +134,49 @@ (map (fn [[e a v]] [:db/add e a v]) data))] (is (= db (-> db d/serializable d/from-serializable))) (is (= db (-> db d/serializable pr-str edn/read-string d/from-serializable))) + (is (= db (-> db (d/serializable {:freeze-fn tdc/transit-write-str}) pr-str edn/read-string (d/from-serializable {:thaw-fn tdc/transit-read-str})))) (doseq [type [:json :json-verbose #?(:clj :msgpack)]] (testing type (is (= db (-> db d/serializable (tdc/transit-write type) (tdc/transit-read type) d/from-serializable))))) - #?(:clj + #?(:cljd nil + :clj (is (= db (-> db d/serializable jsonista/write-value-as-string jsonista/read-value d/from-serializable)))) - #?(:clj + #?(:cljd nil + :clj (let [mapper (com.fasterxml.jackson.databind.ObjectMapper.)] (is (= db (-> db d/serializable (jsonista/write-value-as-string mapper) (jsonista/read-value mapper) d/from-serializable))))) - #?(:clj + #?(:cljd nil + :clj (is (= db (-> db d/serializable cheshire/generate-string cheshire/parse-string d/from-serializable)))) - #?(:cljs - (is (= db (-> db d/serializable js/JSON.stringify js/JSON.parse d/from-serializable)))))) + #?(:cljd + (is (= db (-> db d/serializable dart:convert/json.encode dart:convert/json.decode d/from-serializable))) + :cljs + (is (= db (-> db d/serializable js/JSON.stringify js/JSON.parse d/from-serializable)))) + )) (deftest test-nan (let [db (d/db-with (d/empty-db schema) [[:db/add 1 :nan ##NaN]]) - valid? #(#?(:clj Double/isNaN :cljs js/isNaN) (:nan (d/entity % 1)))] + valid? #(let [v (:nan (d/entity % 1))] #?(:cljd (not (== v v)) :clj (Double/isNaN v) :cljs (js/isNaN v)))] (is (valid? (-> db d/serializable d/from-serializable))) (is (valid? (-> db d/serializable pr-str edn/read-string d/from-serializable))) (is (valid? (-> db (d/serializable {:freeze-fn tdc/transit-write-str}) pr-str edn/read-string (d/from-serializable {:thaw-fn tdc/transit-read-str})))) (doseq [type [:json :json-verbose #?(:clj :msgpack)]] (testing type (is (valid? (-> db d/serializable (tdc/transit-write type) (tdc/transit-read type) d/from-serializable))))) - #?(:clj + #?(:cljd nil + :clj (is (valid? (-> db d/serializable jsonista/write-value-as-string jsonista/read-value d/from-serializable)))) - #?(:clj + #?(:cljd nil + :clj (let [mapper (com.fasterxml.jackson.databind.ObjectMapper.)] (is (valid? (-> db d/serializable (jsonista/write-value-as-string mapper) (jsonista/read-value mapper) d/from-serializable))))) - #?(:clj + #?(:cljd nil + :clj (is (valid? (-> db d/serializable cheshire/generate-string cheshire/parse-string d/from-serializable)))) - #?(:cljs + #?(:cljd + (is (valid? (-> db d/serializable dart:convert/json.encode dart:convert/json.decode d/from-serializable))) + :cljs (is (valid? (-> db d/serializable js/JSON.stringify js/JSON.parse d/from-serializable)))))) diff --git a/test/datascript/test/transact.cljc b/test/datascript/test/transact.cljc index 9d115f4d..20250dc4 100644 --- a/test/datascript/test/transact.cljc +++ b/test/datascript/test/transact.cljc @@ -1,9 +1,11 @@ (ns datascript.test.transact (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc])) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) (deftest test-with (let [db (-> (d/empty-db {:aka {:db/cardinality :db.cardinality/many}}) @@ -123,7 +125,7 @@ (let [db' (d/db-with db [[:db/retract 2 :employed? false]])] (is (= [(db/datom 2 :employed? true)] (d/datoms db' :eavt 2 :employed?)))))) - + (deftest test-retract-fns-not-found (let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}}) (d/db-with [[:db/add 1 :name "Ivan"]])) diff --git a/test/datascript/test/tuples.cljc b/test/datascript/test/tuples.cljc index 23ba35d3..96813444 100644 --- a/test/datascript/test/tuples.cljc +++ b/test/datascript/test/tuples.cljc @@ -1,9 +1,12 @@ (ns datascript.test.tuples (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] - [datascript.test.core :as tdc]) - #?(:clj + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]]) + #?(:cljd nil + :clj (:import [clojure.lang ExceptionInfo]))) @@ -138,8 +141,10 @@ :db/unique :db.unique/identity}})] (d/transact! conn [[:db/add 1 :a "a"]]) (d/transact! conn [[:db/add 2 :a "A"]]) - (is (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" - (d/transact! conn [[:db/add 1 :a "A"]]))) + (is #?(:cljd (thrown-msg? #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :a "A"]])) + :default (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :a "A"]])))) (d/transact! conn [[:db/add 1 :b "b"] [:db/add 2 :b "b"] @@ -156,13 +161,20 @@ [3 :a+b ["a" "B"]]} (tdc/all-datoms (d/db conn)))) - (is (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" - (d/transact! conn [[:db/add 1 :a "A"]]))) - (is (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" - (d/transact! conn [[:db/add 1 :b "B"]]))) - (is (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" - (d/transact! conn [[:db/add 1 :a "A"] - [:db/add 1 :b "B"]]))) + (is #?(:cljd (thrown-msg? #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :a "A"]])) + :default (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :a "A"]])))) + (is #?(:cljd (thrown-msg? #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :b "B"]])) + :default (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :b "B"]])))) + (is #?(:cljd (thrown-msg? #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :a "A"] + [:db/add 1 :b "B"]])) + :default (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add 1 :a "A"] + [:db/add 1 :b "B"]])))) (testing "multiple tuple updates" ;; changing both tuple components in a single operation @@ -193,7 +205,7 @@ [2 :b "b"] [2 :a+b ["a" "b"]] [2 :c "c"]} - (tdc/all-datoms (d/db conn)))) + (tdc/all-datoms (d/db conn)))) (is (thrown-msg? "Conflicting upserts: [:a+b [\"A\" \"B\"]] resolves to 1, but [:c \"c\"] resolves to 2" (d/transact! conn [{:a+b ["A" "B"] :c "c"}]))) @@ -262,10 +274,12 @@ [2 :b "b"] [2 :a+b ["a" "b"]] [2 :c "c"]} - (tdc/all-datoms (d/db conn)))) + (tdc/all-datoms (d/db conn)))) - (is (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" - (d/transact! conn [[:db/add [:a+b ["A" "B"]] :c "c"]]))) + (is #?(:cljd (thrown-msg? #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add [:a+b ["A" "B"]] :c "c"]])) + :default (thrown-with-msg? ExceptionInfo #"Cannot add .* because of unique constraint: .*" + (d/transact! conn [[:db/add [:a+b ["A" "B"]] :c "c"]])))) (is (thrown-msg? "Conflicting upsert: [:c \"c\"] resolves to 2, but entity already has :db/id 1" (d/transact! conn [{:db/id [:a+b ["A" "B"]] :c "c"}]))) diff --git a/test/datascript/test/upsert.cljc b/test/datascript/test/upsert.cljc index 38cf78e8..d368a69f 100644 --- a/test/datascript/test/upsert.cljc +++ b/test/datascript/test/upsert.cljc @@ -1,13 +1,18 @@ (ns datascript.test.upsert (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) [datascript.core :as d] [datascript.db :as db] - [datascript.test.core :as tdc])) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) #?(:cljs (def Throwable - js/Error)) + js/Error) + :cljd + (def Throwable + ExceptionInfo)) (deftest test-upsert (let [ivan {:db/id 1 :name "Ivan" :email "@1"} @@ -103,12 +108,16 @@ {})))) (testing "upsert conflicts with existing id" - (is (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 2" - (d/with db [{:db/id 2 :name "Ivan" :age 36}])))) + (is #?(:cljd (thrown-msg? #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 2" + (d/with db [{:db/id 2 :name "Ivan" :age 36}])) + :default (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 2" + (d/with db [{:db/id 2 :name "Ivan" :age 36}]))))) (testing "upsert conflicts with non-existing id" - (is (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 5" - (d/with db [{:db/id 5 :name "Ivan" :age 36}])))) + (is #?(:cljd (thrown-msg? #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 5" + (d/with db [{:db/id 5 :name "Ivan" :age 36}])) + :default (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id 5" + (d/with db [{:db/id 5 :name "Ivan" :age 36}]))))) (testing "upsert by non-existing value resolves as update" (let [tx (d/with db [{:name "Ivan" :email "@5" :age 35}])] @@ -118,8 +127,10 @@ (tempids tx))))) (testing "upsert by 2 conflicting fields" - (is (thrown-with-msg? Throwable #"Conflicting upserts: \[:name \"Ivan\"\] resolves to 1, but \[:email \"@2\"\] resolves to 2" - (d/with db [{:name "Ivan" :email "@2" :age 35}])))) + (is #?(:cljd (thrown-msg? #"Conflicting upserts: \[:name \"Ivan\"\] resolves to 1, but \[:email \"@2\"\] resolves to 2" + (d/with db [{:name "Ivan" :email "@2" :age 35}])) + :default (thrown-with-msg? Throwable #"Conflicting upserts: \[:name \"Ivan\"\] resolves to 1, but \[:email \"@2\"\] resolves to 2" + (d/with db [{:name "Ivan" :email "@2" :age 35}]))))) (testing "upsert over intermediate db" (let [tx (d/with db [{:name "Igor" :age 35} @@ -146,8 +157,10 @@ {-1 5, -2 5})))) (testing "upsert and :current-tx conflict" - (is (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id \d+" - (d/with db [{:db/id :db/current-tx :name "Ivan" :age 35}])))) + (is #?(:cljd (thrown-msg? #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id \d+" + (d/with db [{:db/id :db/current-tx :name "Ivan" :age 35}])) + :default (thrown-with-msg? Throwable #"Conflicting upsert: \[:name \"Ivan\"\] resolves to 1, but entity already has :db/id \d+" + (d/with db [{:db/id :db/current-tx :name "Ivan" :age 35}]))))) (testing "upsert of unique, cardinality-many values" (let [tx (d/with db [{:name "Ivan" :slugs "ivan1"} @@ -157,8 +170,10 @@ (pull tx 1))) (is (= {:db/id 1 :name "Ivan" :email "@1" :slugs ["ivan1" "ivan2"]} (pull tx2 1))) - (is (thrown-with-msg? Throwable #"Conflicting upserts:" - (d/with (:db-after tx) [{:slugs ["ivan1" "petr1"]}]))))) + (is #?(:cljd (thrown-msg? #"Conflicting upserts:" + (d/with (:db-after tx) [{:slugs ["ivan1" "petr1"]}])) + :default (thrown-with-msg? Throwable #"Conflicting upserts:" + (d/with (:db-after tx) [{:slugs ["ivan1" "petr1"]}])))))) (testing "upsert by ref" (let [tx (d/with db [{:ref 3 :age 36}])] @@ -207,9 +222,12 @@ (let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}}) (d/db-with [{:db/id -1 :name "Ivan"} {:db/id -2 :name "Oleg"}]))] - (is (thrown-with-msg? Throwable #"Conflicting upsert: -1 resolves both to 1 and 2" - (d/with db [{:db/id -1 :name "Ivan" :age 35} - {:db/id -1 :name "Oleg" :age 36}]))))) + (is #?(:cljd (thrown-msg? #"Conflicting upsert: -1 resolves both to 1 and 2" + (d/with db [{:db/id -1 :name "Ivan" :age 35} + {:db/id -1 :name "Oleg" :age 36}])) + :default (thrown-with-msg? Throwable #"Conflicting upsert: -1 resolves both to 1 and 2" + (d/with db [{:db/id -1 :name "Ivan" :age 35} + {:db/id -1 :name "Oleg" :age 36}])))))) ;; issue-285 (deftest test-retries-order @@ -277,7 +295,7 @@ [[:db/add -1 :name "Ivan"] [:db/add -1 :age 12]] #{[1 :age 12] [1 :name "Ivan"]} - + [[:db/add -1 :age 12] [:db/add -1 :name "Ivan"]] #{[1 :age 12] [1 :name "Ivan"]})) @@ -285,8 +303,13 @@ (let [db (-> (d/empty-db {:name {:db/unique :db.unique/identity}}) (d/db-with [[:db/add -1 :name "Ivan"] [:db/add -2 :name "Oleg"]]))] - (is (thrown-with-msg? Throwable #"Conflicting upsert: -1 resolves both to 1 and 2" - (d/with db [[:db/add -1 :name "Ivan"] - [:db/add -1 :age 35] - [:db/add -1 :name "Oleg"] - [:db/add -1 :age 36]]))))) + (is #?(:cljd (thrown-msg? #"Conflicting upsert: -1 resolves both to 1 and 2" + (d/with db [[:db/add -1 :name "Ivan"] + [:db/add -1 :age 35] + [:db/add -1 :name "Oleg"] + [:db/add -1 :age 36]])) + :default (thrown-with-msg? Throwable #"Conflicting upsert: -1 resolves both to 1 and 2" + (d/with db [[:db/add -1 :name "Ivan"] + [:db/add -1 :age 35] + [:db/add -1 :name "Oleg"] + [:db/add -1 :age 36]])))))) diff --git a/test/datascript/test/validation.cljc b/test/datascript/test/validation.cljc index e8ff902f..8e391434 100644 --- a/test/datascript/test/validation.cljc +++ b/test/datascript/test/validation.cljc @@ -1,47 +1,61 @@ (ns datascript.test.validation (:require - [clojure.test :as t :refer [is are deftest testing]] + #?(:cljd [cljd.test :as t :refer [is are deftest testing]] + :default [clojure.test :as t :refer [is are deftest testing]]) [datascript.core :as d] - [datascript.test.core :as tdc])) + #?(:cljd [cljd.core :refer [ExceptionInfo]]) + [datascript.test.core :as tdc :refer [#?(:cljd thrown-msg?)]])) #?(:cljs (def Throwable - js/Error)) + js/Error) + :cljd + (def Throwable + ExceptionInfo)) (deftest test-with-validation (let [db (d/empty-db {:profile {:db/valueType :db.type/ref} :id {:db/unique :db.unique/identity}})] - (are [tx] (thrown-with-msg? Throwable #"Expected number, string or lookup ref for :db/id" (d/db-with db tx)) + (are [tx] #?(:cljd (thrown-msg? #"Expected number, string or lookup ref for :db/id" (d/db-with db tx)) + :default (thrown-with-msg? Throwable #"Expected number, string or lookup ref for :db/id" (d/db-with db tx))) [{:db/id #"" :name "Ivan"}]) - - (are [tx] (thrown-with-msg? Throwable #"Bad entity attribute" (d/db-with db tx)) + + (are [tx] #?(:cljd (thrown-msg? #"Bad entity attribute" (d/db-with db tx)) + :default (thrown-with-msg? Throwable #"Bad entity attribute" (d/db-with db tx))) [[:db/add -1 nil "Ivan"]] [[:db/add -1 17 "Ivan"]] [{:db/id -1 17 "Ivan"}]) - - (are [tx] (thrown-with-msg? Throwable #"Cannot store nil as a value" (d/db-with db tx)) + + (are [tx] #?(:cljd (thrown-msg? #"Cannot store nil as a value" (d/db-with db tx)) + :default (thrown-with-msg? Throwable #"Cannot store nil as a value" (d/db-with db tx))) [[:db/add -1 :name nil]] [{:db/id -1 :name nil}] [[:db/add -1 :id nil]] [{:db/id -1 :id "A"} {:db/id -1 :id nil}]) - - (are [tx] (thrown-with-msg? Throwable #"Expected number or lookup ref for entity id" (d/db-with db tx)) + + (are [tx] #?(:cljd (thrown-msg? #"Expected number or lookup ref for entity id" (d/db-with db tx)) + :default (thrown-with-msg? Throwable #"Expected number or lookup ref for entity id" (d/db-with db tx))) [[:db/add nil :name "Ivan"]] [[:db/add {} :name "Ivan"]] [[:db/add -1 :profile #"regexp"]] [{:db/id -1 :profile #"regexp"}]) - - (is (thrown-with-msg? Throwable #"Unknown operation" (d/db-with db [["aaa" :name "Ivan"]]))) - (is (thrown-with-msg? Throwable #"Bad entity type at" (d/db-with db [:db/add "aaa" :name "Ivan"]))) - (is (thrown-with-msg? Throwable #"Tempids are allowed in :db/add only" (d/db-with db [[:db/retract -1 :name "Ivan"]]))) - (is (thrown-with-msg? Throwable #"Bad transaction data" (d/db-with db {:profile "aaa"}))))) + + (is #?(:cljd (thrown-msg? #"Unknown operation" (d/db-with db [["aaa" :name "Ivan"]])) + :default (thrown-with-msg? Throwable #"Unknown operation" (d/db-with db [["aaa" :name "Ivan"]])))) + (is #?(:cljd (thrown-msg? #"Bad entity type at" (d/db-with db [:db/add "aaa" :name "Ivan"])) + :default (thrown-with-msg? Throwable #"Bad entity type at" (d/db-with db [:db/add "aaa" :name "Ivan"])))) + (is #?(:cljd (thrown-msg? #"Tempids are allowed in :db/add only" (d/db-with db [[:db/retract -1 :name "Ivan"]])) + :default (thrown-with-msg? Throwable #"Tempids are allowed in :db/add only" (d/db-with db [[:db/retract -1 :name "Ivan"]])))) + (is #?(:cljd (thrown-msg? #"Bad transaction data" (d/db-with db {:profile "aaa"})) + :default (thrown-with-msg? Throwable #"Bad transaction data" (d/db-with db {:profile "aaa"})))))) (deftest test-unique (let [db (d/db-with (d/empty-db {:name {:db/unique :db.unique/value}}) [[:db/add 1 :name "Ivan"] [:db/add 2 :name "Petr"]])] - (are [tx] (thrown-with-msg? Throwable #"unique constraint" (d/db-with db tx)) + (are [tx] #?(:cljd (thrown-msg? #"unique constraint" (d/db-with db tx)) + :default (thrown-with-msg? Throwable #"unique constraint" (d/db-with db tx))) [[:db/add 3 :name "Ivan"]] [{:db/add 3 :name "Petr"}]) (d/db-with db [[:db/add 3 :name "Igor"]])