Calling a transaction function that uses d/with in a dev-local, filesystem (i.e., not :storage-dir :mem) throws a ClassCastException on internal Datomic code.
Environment
- com.datomic/dev-local {:mvn/version "0.9.232"}
- client-cloud 0.8.105
History
Example with exception using file system dev-local.
(def client (d/client {:server-type :dev-local
:storage-dir "/tmp/cce-bug3"
:system "test"}))
=> #'user/client
(d/create-database client {:db-name "test"})
=> true
(def conn (d/connect client {:db-name "test"}))
=> #'user/conn
(d/transact conn {:tx-data [{:db/ident :user/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}]})
=>
{:db-before #datomic.core.db.Db{:id "test",
:basisT 5,
:indexBasisT 0,
:index-root-id nil,
:asOfT nil,
:sinceT nil,
:raw nil},
:db-after #datomic.core.db.Db{:id "test",
:basisT 6,
:indexBasisT 0,
:index-root-id nil,
:asOfT nil,
:sinceT nil,
:raw nil},
:tx-data [#datom[13194139533318 50 #inst"2021-05-09T00:49:46.400-00:00" 13194139533318 true]
#datom[73 10 :user/name 13194139533318 true]
#datom[73 40 23 13194139533318 true]
#datom[73 41 35 13194139533318 true]
#datom[0 13 73 13194139533318 true]],
:tempids {}}
(defn my-tx-fn
[db argm]
(let [tx-report (d/with db {:tx-data [{:user/name "asd"}]})]
[]))
=> #'user/my-tx-fn
(d/transact conn {:tx-data [(list 'user/my-tx-fn {})]})
Execution error (ClassCastException) at datomic.dev-local.tx/datom-lookup-valfn (tx.clj:397).
class datomic.core.db.Datum cannot be cast to class java.lang.Number (datomic.core.db.Datum is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')
Working memdb example.
(def client-memdb (d/client {:server-type :dev-local
:storage-dir :mem
:system "test"}))
=> #'user/client-memdb
(d/create-database client-memdb {:db-name "test"})
=> true
(def conn-memdb (d/connect client-memdb {:db-name "test"}))
=> #'user/conn-memdb
(d/transact conn-memdb {:tx-data [{:db/ident :user/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}]})
=>
{:db-before #datomic.core.db.Db{:id "394c7a65-bfeb-4dd4-b188-7aecd2057692",
:basisT 5,
:indexBasisT 0,
:index-root-id nil,
:asOfT nil,
:sinceT nil,
:raw nil},
:db-after #datomic.core.db.Db{:id "394c7a65-bfeb-4dd4-b188-7aecd2057692",
:basisT 6,
:indexBasisT 0,
:index-root-id nil,
:asOfT nil,
:sinceT nil,
:raw nil},
:tx-data [#datom[13194139533318 50 #inst"2021-05-09T01:02:57.965-00:00" 13194139533318 true]
#datom[73 10 :user/name 13194139533318 true]
#datom[73 40 23 13194139533318 true]
#datom[73 41 35 13194139533318 true]
#datom[0 13 73 13194139533318 true]],
:tempids {}}
(d/transact conn-memdb {:tx-data [(list 'user/my-tx-fn {})]})
=>
{:db-before #datomic.core.db.Db{:id "394c7a65-bfeb-4dd4-b188-7aecd2057692",
:basisT 6,
:indexBasisT 0,
:index-root-id nil,
:asOfT nil,
:sinceT nil,
:raw nil},
:db-after #datomic.core.db.Db{:id "394c7a65-bfeb-4dd4-b188-7aecd2057692",
:basisT 7,
:indexBasisT 0,
:index-root-id nil,
:asOfT nil,
:sinceT nil,
:raw nil},
:tx-data [#datom[13194139533319 50 #inst"2021-05-09T01:03:08.042-00:00" 13194139533319 true]],
:tempids {}}
Expectation
Transaction functions that use d/with do not throw a ClassCastException. Additionally, the behavior of d/with in all Datomic environments (Cloud, File system, Memdb) is consistent. Given a previous Datomic Cloud release fixed a problem with using with-db from an Ion, it seems using d/with in a transaction function is a supported behavior.
Actual
The final d/transact transaction function throws a ClassCastException when using d/with on some internal Datomic code. This only occurs when using a dev-local, file system environment.
Full stacktrace is pasted below.
#error{:cause "class datomic.core.db.Datum cannot be cast to class java.lang.Number (datomic.core.db.Datum is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')",
:via [{:type java.lang.ClassCastException,
:message "class datomic.core.db.Datum cannot be cast to class java.lang.Number (datomic.core.db.Datum is in unnamed module of loader 'app'; java.lang.Number is in module java.base of loader 'bootstrap')",
:at [datomic.dev_local.tx$datom_lookup_valfn invokeStatic "tx.clj" 397]}],
:trace [[datomic.dev_local.tx$datom_lookup_valfn invokeStatic "tx.clj" 397]
[datomic.dev_local.tx$datom_lookup_valfn invoke "tx.clj" 397]
[datomic.dev_local.local_log.LocalLog valAt "local_log.clj" 56]
[clojure.lang.RT get "RT.java" 760]
[datomic.dev_local.btindex.BTIndex cons "btindex.clj" 281]
[clojure.lang.RT conj "RT.java" 677]
[clojure.core$conj__5390 invokeStatic "core.clj" 85]
[clojure.core$conj__5390 invoke "core.clj" 82]
[datomic.core.db.Db addData "db.clj" 2322]
[datomic.core.db$add_ensured_data invokeStatic "db.clj" 3353]
[datomic.core.db$add_ensured_data invoke "db.clj" 3351]
[datomic.core.db$with_tx invokeStatic "db.clj" 3370]
[datomic.core.db$with_tx invoke "db.clj" 3357]
[datomic.core.db.Db with "db.clj" 2164]
[datomic.core.local_db$fn__25633 invokeStatic "local_db.clj" 67]
[datomic.core.local_db$fn__25633 invoke "local_db.clj" 24]
[datomic.client.api.protocols$fn__11959$G__11877__11966 invoke "protocols.clj" 126]
[datomic.client.api$with invokeStatic "api.clj" 363]
[datomic.client.api$with invoke "api.clj" 353]
[user$my_tx_fn invokeStatic "user.clj" 3]
[user$my_tx_fn invoke "user.clj" 20]
[clojure.lang.AFn applyToHelper "AFn.java" 156]
[clojure.lang.AFn applyTo "AFn.java" 144]
[clojure.lang.Var applyTo "Var.java" 705]
[clojure.core$apply invokeStatic "core.clj" 667]
[clojure.core$apply invoke "core.clj" 660]
[datomic.core.db.ProcessExpander inject "db.clj" 3229]
[datomic.core.db.ProcessInpoint inject "db.clj" 2950]
[datomic.dev_local.btindex_db$expand_tx$inject_all__18586$fn__18587 invoke "btindex_db.clj" 440]
[clojure.lang.PersistentVector reduce "PersistentVector.java" 343]
[clojure.core$reduce invokeStatic "core.clj" 6827]
[clojure.core$reduce invoke "core.clj" 6810]
[datomic.dev_local.btindex_db$expand_tx$inject_all__18586 invoke "btindex_db.clj" 440]
[datomic.dev_local.btindex_db$expand_tx invokeStatic "btindex_db.clj" 444]
[datomic.dev_local.btindex_db$expand_tx invoke "btindex_db.clj" 429]
[datomic.dev_local.btindex_db$with_tx invokeStatic "btindex_db.clj" 468]
[datomic.dev_local.btindex_db$with_tx invoke "btindex_db.clj" 461]
[datomic.dev_local.impl.DurableConnection transact "impl.clj" 161]
[datomic.client.api$transact invokeStatic "api.clj" 200]
[datomic.client.api$transact invoke "api.clj" 183]
[user$eval124260 invokeStatic "user.clj" 25]
[user$eval124260 invoke "user.clj" 25]
[clojure.lang.Compiler eval "Compiler.java" 7177]
[clojure.lang.Compiler eval "Compiler.java" 7132]
[clojure.core$eval invokeStatic "core.clj" 3214]
[clojure.core$eval invoke "core.clj" 3210]
[nrepl.middleware.interruptible_eval$evaluate$fn__959 invoke "interruptible_eval.clj" 91]
[clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437]
[clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437]
[clojure.main$repl$fn__9095 invoke "main.clj" 458]
[clojure.main$repl invokeStatic "main.clj" 458]
[clojure.main$repl doInvoke "main.clj" 368]
[clojure.lang.RestFn invoke "RestFn.java" 1523]
[nrepl.middleware.interruptible_eval$evaluate invokeStatic "interruptible_eval.clj" 84]
[nrepl.middleware.interruptible_eval$evaluate invoke "interruptible_eval.clj" 56]
[nrepl.middleware.interruptible_eval$interruptible_eval$fn__985$fn__989
invoke
"interruptible_eval.clj"
155]
[clojure.lang.AFn run "AFn.java" 22]
[nrepl.middleware.session$session_exec$main_loop__1086$fn__1090 invoke "session.clj" 190]
[nrepl.middleware.session$session_exec$main_loop__1086 invoke "session.clj" 189]
[clojure.lang.AFn run "AFn.java" 22]
[java.lang.Thread run "Thread.java" 829]]}
Evidence
The previously pasted example is reproducible and always throws a ClassCastException.
Impact
We are unable to test functionality in a consistent manner. Local setups using dev-local file systems will not function as expected. Developers are unable to test features in the same way they will work in production.