This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-15
Channels
- # announcements (5)
- # babashka (56)
- # beginners (24)
- # biff (15)
- # calva (7)
- # clj-kondo (12)
- # cljsrn (8)
- # clojure (68)
- # clojure-denmark (1)
- # clojure-europe (55)
- # clojure-norway (4)
- # clojure-spec (9)
- # clojure-uk (2)
- # clojurescript (8)
- # cursive (11)
- # data-science (7)
- # datahike (1)
- # datomic (66)
- # emacs (12)
- # etaoin (3)
- # fulcro (10)
- # graphql (3)
- # hyperfiddle (97)
- # jobs (1)
- # kaocha (8)
- # lsp (3)
- # malli (15)
- # meander (1)
- # off-topic (3)
- # overtone (4)
- # polylith (7)
- # rdf (25)
- # re-frame (4)
- # reagent (14)
- # remote-jobs (1)
- # shadow-cljs (126)
- # sql (30)
- # vscode (3)
- # xtdb (8)
We are still having issues with composite keys, made up of ref attrs. Filed a full example of the issue here: https://forum.datomic.com/t/upsert-tupleattrs-containing-db-type-ref-using-tempids/2181 did anyone else here encountered this problem?
Yes, and your “related literature” links show the same problem. Tempid resolution (including via upserting) happens before composite tuple re-computation; composite tuple recomputation happens at the very end after all datoms are expanded to make sure nothing else could change the values of the attributes the tuple is indexing. To resolve tempids using the value of composites which itself depends on resolved tempids would require a more complex multi-pass strategy
we are using transaction functions, but do you mean, we should hand-roll the decision, whether a certain nested entity map (or a set of positional arguments) represent an insert or an update and emit different tx-data accordingly?
yes. Something like [:myfn/make-x-y-ref tempid x y]
It can see if x and y exist already, and use an existing tuple value to resolve tempid to an existing value accordingly
> To resolve tempids using the value of composites which itself depends on resolved tempids would require a more complex multi-pass strategy
i think, i also had issues using both lookup-refs and keywords representing :db/ident
s as tuple components.
could those be the consequences of this single-pass algorithm too?
this is the problem that you need to resolve the tempid (possibly create a new entity) before you can resolve another tempid
you would need to make the tx itself multi-stage, instead of collect tempids, resolve them, commit
> ... [:myfn/make-x-y-ref tempid x y]
...
what is tempid
in this case?
u mean something like this:
[{:db/id "x" :db/ident "x"}
{:db/id "y" :db/ident "y"}
[:myfn/make-x-y-ref "?" ? ?]]
so i can still stay in a single transaction?I was thinking tempid was the tempid of the joining entity, so you could use it as a reference elsewhere in the tx
[:myfn/make-x-y-ref joining-entity-tempid {:x-tempid "x" :x-unique-value "x"} {:y-tempid "y" :y-unique-value "y"}]
and the return value of this tx-fn would be what?
different shapes, based on whether [:x-unique-value "x"]
& [:y-unique-value "y"]
exists already or not?
well, that's what i meant by "we should hand-roll the decision, whether a certain nested entity map (or a set of positional arguments) represent an insert or an update"
> yes. Something like [:myfn/make-x-y-ref tempid x y]
It can see if x and y exist already, and use an existing tuple value to resolve tempid to an existing value accordingly
yeah, I’m agreeing 🙂
okay, thanks a lot for confirming it! that's how we were doing it in the past, it's just a bit tedious to do it for every composite key 😞
If datomic were designed with lookup refs from the beginning, I’m not sure it would exist
we are mirroring data from existing systems and it seems straightforward in that case. the other use-case is storing oauth access and refresh tokens for a combination of our system's user, oauth system's user and the oauth resource they authorized. both of these would work with how datomic does the upsert anyway, if only it would resolve the tempids in tuples 🙂
using tempids in this scenario allows us to use different unique keys for representing the oauth user and the oauth resource, since it varies across systems, yet have the same uniqueness constraint.
have you considered either not having a composite tuple at all (use an entity predicate), or using a heterogenous value tuple and manually maintaining it?
yes, if i would use values, i would need to customize the same overall token refresh & revocation logic, depending on oauth provider, so i would need different heterogen tuples based on whether an oauth resource is a uuid or just a string
an entity predicate can ensure the invariant without needing a unique composite-tuple attr as an index
today was the 2nd time we considered using entity predicates for a smaller use-case. previously we were able to solve the problem without any entity predicates, just with tx-fn. but now that u are mentioning it, i will consider them more seriously.
we did consider leaving the oauth resource dimension in the uniqueness constraint nil
for one of the oauth providers, so we would be utilizing that capability.
yeah, but the uniqueness constraint on an explicit attribute makes upsert possible... :)
it comes down to whether you think races will be uncommon enough that just protecting against them causing invariant volations is enough (entity predicate), or common enough that you want to make it commute (tx fn or upsert)
(let [conn (mk-conn schema)
txr0 (tx! conn [{:db/id "x" :x/id "x"}
{:db/id "y" :y/id "y"}])
{:strs [x y]} (-> txr0 :tempids)
ent {:db/id "ent"
:ref/x [:x/id "x"]
:ref/y [:y/id "y"]
:key [[:x/id "x"] [:y/id "y"]]}]
(tx! conn [(-> ent (merge {:attr 1}))])
(tx! conn [(-> ent (merge {:attr 2}))]))
this one gives a
Execution error (ExceptionInfo) at datomic.core.error/raise (error.clj:55).
:db.error/invalid-tuple-value Invalid tuple value
is using lookup-refs like this, as tuple components a different problem, or also the consequence of this single-pass resolution algo?It’s just annoying; I don’t see any technical limitation, they probably just overlooked it
alright, i will look into it then! because it would be extremely convenient during REPL explorations...
if I were redesigning this from scratch (hah!) I would replace existing upsert behavior with something that only triggers when it gets a lookup ref in a spot where it expects an entity id
and your application should just always use lookup refs in its transactions if it wants to not care about entity id and only about “unique-identity”
and the system would emit the assertion if the lookup ref failed to resolve and the attr was an upsert type
i have the gut feeling, that we could bolt something on top of the current behaviour, but such logic should know about the schema too... which is available in tx-fns...
i really don't care about how non-performant would it be, because the convenience would trump that drawback 🙂
at least with on-prem, all that schema and ident stuff is aggressively cached in memory all the time (not object cache, faster)