Although the composite tuple key does get added on the second transaction, the third transaction fails. A transacted entity won’t actually resolve to an existing entity unless I specify the :foo+bar
key explicitly, which seems like a bug (or at least undesirable behavior). So this means that if you want upsert behavior, you have to include both the composite attribute and the individual attributes whenever you transact an entity.
user=>
(let [db (d/with-db system/conn)
txes [[#:db{:ident :foo,
:valueType :db.type/string,
:cardinality :db.cardinality/one}
#:db{:ident :bar,
:valueType :db.type/string,
:cardinality :db.cardinality/one}
#:db{:ident :baz,
:valueType :db.type/string,
:cardinality :db.cardinality/one}
{:db/ident :foo+bar,
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity
:db/valueType :db.type/tuple
:db/tupleAttrs [:foo :bar]}]
[{:foo "foo"
:bar "bar"}]
; doesn't work
[{:foo "foo"
:bar "bar"
:baz "baz"}]
; works
[{:foo "foo"
:bar "bar"
:foo+bar ["foo" "bar"]
:baz "baz"}]]]
(reduce (fn [db tx]
(try
(let [db (:db-after (d/with db {:tx-data tx}))]
(println "transaction successful")
db)
(catch Exception e
(println "transaction failed:" (.getMessage e))
db)))
db txes))
transaction successful
transaction successful
transaction failed: Unique conflict: :foo+bar, value: ["foo" "bar"] already held by: 51263630133428781 asserted for: 4631142976193070
transaction successful
(Also, I noticed that the schema data reference still says:
Datomic does not provide a mechanism to declare composite uniqueness constraints; however, you can implement them (or any arbitrary functional constraint) via transaction functions.
This should probably be updated to mention composite tuples.)