This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-05-05
Channels
- # announcements (8)
- # babashka (6)
- # beginners (55)
- # biff (8)
- # calva (11)
- # cider (4)
- # clj-kondo (6)
- # cljdoc (23)
- # cljs-dev (22)
- # clojure (89)
- # clojure-brasil (3)
- # clojure-europe (47)
- # clojure-indonesia (1)
- # clojure-nl (1)
- # clojure-spec (3)
- # clojure-uk (5)
- # clojurescript (67)
- # community-development (2)
- # conjure (29)
- # cursive (2)
- # datalog (29)
- # datomic (41)
- # defnpodcast (4)
- # emacs (15)
- # google-cloud (5)
- # holy-lambda (6)
- # hyperfiddle (3)
- # introduce-yourself (8)
- # jobs (1)
- # malli (19)
- # meander (41)
- # nrepl (1)
- # off-topic (30)
- # pathom (22)
- # polylith (30)
- # releases (1)
- # remote-jobs (4)
- # sci (4)
- # shadow-cljs (1)
- # spacemacs (7)
- # specter (3)
- # tools-build (16)
- # tools-deps (2)
Hey all: is there an easy way from the repl to see what version of the datomic peer is running?
Is that an on-prem in-process peer library?
@UGJE0MM0W, yes, on-prem in process.
How about:
(some->> (io/resource "META-INF/maven/com.datomic/datomic-pro/pom.xml")
(slurp)
(str/split-lines)
(filter #(str/includes? % "<version>"))
(first))
=> " <version>1.0.6397</version>"
yeah, that's pretty ugly...I was just experimenting on some deps aliases and wanted to confirm that the version of datomic I expected to load was in fact the one that was being loaded.
Right :thumbsup: Sometimes there is also pom.properties available. If I need something like this I just unzip -p the-jar and grep for pom https://github.com/metosin/jsonista/issues/22
(and then I know what file to look for)
For on-prem version 1.0.6397
(latest) it seems that db/cas
does not resolve tempids that are strings, only explicit (datomic.api/tempid ..)
.
Is this a bug? Could it be solved? Should I report it to support?
Reproduced in https://gist.github.com/ivarref/98537d7393d4141fb4dfb2a213756404.
The root cause I think is that d/entid
works for record tempids (resolves to a negative number, which is the lowest-level representation), but not for string tempids. It’s not clear how it could because strings don’t have enough info in them.
Thanks, but I didn't quite understand your answer.. It works (as expected) to transact:
[[:db/add "ent" :e/version 1]
{:db/id "ent" :e/id "a" :e/info "a"}]
But
[[:db/cas "ent" :e/version nil 1]
{:db/id "ent" :e/id "a" :e/info "a"}]
fails. I fail to see why :db/cas
shouldn't work, but :db/add
works.Here is another case that fails, but shouldn't:
(deftest nil-test
(let [tempid (d/tempid :db.part/user)
{:keys [db-after]} @(d/transact *conn* [[:db/cas tempid :e/version nil 1]
{:db/id tempid :e/id "a" :e/info "1"}])]
(is (= #:e{:id "a" :version 1 :info "1"} (d/pull db-after [:e/id :e/version :e/info] [:e/id "a"])))
(let [tempid (d/tempid :db.part/user)
; The following transaction success, though it shouldn't:
{:keys [db-after]} @(d/transact *conn* [[:db/cas tempid :e/version nil 2]
{:db/id tempid :e/id "a" :e/info "2"}])]
(is (= #:e{:id "a" :version 2 :info "2"} (d/pull db-after [:e/id :e/version :e/info] [:e/id "a"]))))))
For me it appears that the tempid resolving for :db/cas
is brokenactually, it doesn't fails, it just fails to assert that :e/version does not exist.
So when it gets a tempid, it does not know yet what it resolves to, so it can’t make a read
Shouldn't it complain / throw an exception if it is an invalid state/operation?
but, the reason it doesn’t for tempids is because d/entid
resolves to a long, which works for entity lookup (it just won’t find any data).
Hm, well cas does accept tempids
How would one assert that an upsertable entity does not have an attribute set?
As far as I understand it:
db/cas accepts d/tempid
as entity and nil
as old value.
This will cause it to write the new value, regardless if the attribute was already set.
For any non-nil old value db/cas with d/tempid
as entity will throw an exception.
This is a bug, no?
I added the following test to the gist linked above:
(deftest this-should-throw-but-does-not
@(d/transact *conn* [{:e/id "a" :e/version 1}])
(let [tempid (d/tempid :db.part/user)
{:keys [db-after]} @(d/transact *conn* [[:db/cas tempid :e/version nil 2]
{:db/id tempid :e/id "a" :e/info "a"}])]
; :e/version should not be 2, but it is:
(is (= 2 (:e/version (d/pull db-after [:e/version] [:e/id "a"]))))))
I am off for the evening. Thanks for your replies and time Favila 🙂
> How would one assert that an upsertable entity does not have an attribute set?
You need to parameterize the cas entity by the lookup ref for the upsert. So you can’t use stock :db/cas
because it fails if it can’t resolve a lookup ref
so you need some cas-like thing that knows both the tempid and what it should resolve to
Hm, thanks, that makes sense. I will have a look at it tomorrow
As of now I have some code like this:
(defn cas-inner [db e-or-lookup-ref a old-val new-val]
(cond
(string? e-or-lookup-ref)
(d/cancel {:cognitect.anomalies/category :cognitect.anomalies/incorrect
:cognitect.anomalies/message "Entity cannot be string"})
(instance? DbId e-or-lookup-ref)
(d/cancel {:cognitect.anomalies/category :cognitect.anomalies/incorrect
:cognitect.anomalies/message "Entity cannot be tempid/datomic.db.DbId"})
(and (vector? e-or-lookup-ref)
(= 4 (count e-or-lookup-ref))
(keyword? (first e-or-lookup-ref))
(= :as (nth e-or-lookup-ref 2))
(string? (last e-or-lookup-ref))
(is-identity? db (first e-or-lookup-ref)))
(let [e (vec (take 2 e-or-lookup-ref))]
(cond
(some? (:db/id (d/pull db [:db/id] e)))
[[:db/cas e a old-val new-val]]
(nil? old-val)
[[:db/add (last e-or-lookup-ref) a new-val]]
:else
(d/cancel {:cognitect.anomalies/category :cognitect.anomalies/incorrect
:cognitect.anomalies/message "Old-val must be nil for new entities"})))
:else
(d/cancel {:cognitect.anomalies/category :cognitect.anomalies/incorrect
:cognitect.anomalies/message "Unhandled state"})))
which means you can write transactions like this:
[[:ndt/cas [:e/id "a" :as "tempid"] :e/version nil 1]
{:db/id "tempid" :e/id "a" :e/info "1"}]
Seems to work well enough.
I'm also adding support for resolving pure strings (to e.g. [:e/id "a" :as "tempid"]
) before running that function in the transactor...Given that cas-inner
is executed by the transactor, all functions, both :ndt/cas
and :db/cas
, will operate on exactly the same datadatabase, right?
This is more complex than I expected. If d/entid on an entity identifier (of any kind) produces a nat-int? you can emit db/cas on that entity, otherwise you emit a db add on the tempid (which needn’t be a string)
It may be clearer and more flexible to keep the ref and tempid separate instead of making one vector
Thanks for nat-int, I hadn't heard about it. Thanks for your reply 🙂
Hi again @U09R86PA4 I ended up (I think!) getting done what I wanted. I wrote a library that "handles" duplicates/abort duplicate transactions: https://github.com/ivarref/double-trouble It's basically a cas function taking a checksum/sha. It also supports tempid strings for its version of cas. If you have any input, I would appreciate it. Here is the code that runs on the transactor: https://github.com/ivarref/double-trouble/blob/main/src/com/github/ivarref/double_trouble/cas.clj Thanks and kind regards.