I’m trying to understand Datomic’s upsert behavior with respect to composite tuple typed attributes when one of the tuple elements is a ref. I’ve created a minimal reproduction of two unexpected behaviors:
(def schema
;; Payload string for debugging.
[{:db/ident :string
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}
;; Top-level of hierarchy, uniquely named items.
{:db/ident :parent/name
:db/valueType :db.type/keyword
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
;; Bottom-level of hierarchy.
{:db/ident :child/parent
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/one}
{:db/ident :child/name
:db/valueType :db.type/keyword
:db/cardinality :db.cardinality/one}
{:db/ident :child/parent+name
:db/valueType :db.type/tuple
:db/tupleAttrs [:child/parent :child/name] ; Uniquely named within parent.
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}])
(d/transact conn {:tx-data schema})
(d/transact conn {:tx-data [{:parent/name :a :string "inserted a"}]})
(d/transact conn {:tx-data [{:parent/name :b :string "inserted b"}]})
(d/q '[:find ?parent-name ?string
:where [?parent :parent/name ?parent-name]
[?parent :string ?string]]
(d/db conn))
(d/transact conn {:tx-data [{:parent/name :a :string "upserted a"}]})
(d/q '[:find ?parent-name ?string
:where [?parent :parent/name ?parent-name]
[?parent :string ?string]]
(d/db conn))
(d/transact conn {:tx-data [{:child/parent [:parent/name :a]
:child/name :x
:string "inserted x"}]})
(d/q '[:find ?parent-name ?parent-string ?child-name ?child-string
:where [?parent :parent/name ?parent-name]
[?parent :string ?parent-string]
[?child :child/parent ?parent]
[?child :child/name ?child-name]
[?child :string ?child-string]]
(d/db conn))
;; ERROR: Unique conflict! But this is expected.
(d/transact conn {:tx-data [{:child/parent [:parent/name :a]
:child/name :x
:string "inserted x"}]})
;; This doesn't report any errors...
(d/transact conn {:tx-data [{:db/id "a"
:parent/name :a}
{:child/parent+name ["a" :y]
:string "inserted y"}]})
;; But "inserted y" is nowhere to be found!
(d/q '[:find ?parent-name ?parent-string ?child-name ?child-string
:where [?parent :parent/name ?parent-name]
[?parent :string ?parent-string]
[?child :child/parent ?parent]
[?child :child/name ?child-name]
[?child :string ?child-string]]
(d/db conn))
;; ERROR: Invalid tuple value! But would be nice if it worked.
(d/transact conn {:tx-data [{:child/parent+name [[:parent/name :a] :z]
:string "inserted z"}]})
To summarize, the two behaviors I’m puzzled by are:
- Lookup refs within a tuple cause transact to fail with “Invalid tuple value”.
- Upserting a tuple with a ref attr seems to silently fail, discarding data. This occurs with both tempid strings and long db ids.
My expectation was that both of these operations would succeed with the following interpretations:
- Lookup refs would be resolved to long db ids, leaning to #2.
- Composite tuples would be expanded in to their constitute datums, allowing an upsert on a multi-attribute key.
Some things that lead me to believe that this should work:
- I’m running datomic-pro 0.9.5981 locally & the changelog for that version says “Fix: resolve tempids for reference attributes inside tuples.”
- This comment: :db.unique/identity does not work for tuple attributes
Have I made some error in implementation or understanding?
Is there some other way to upsert on a multi-attribute key? Is it expected to work with ref attributes?