# Upsert tupleAttrs containing :db.type/ref, using tempids

**URL:** <https://forum.datomic.com/t/upsert-tupleattrs-containing-db-type-ref-using-tempids/2181>\
**Category:** General\
**Created:** [February 15, 2023, 2:46am UTC](https://forum.datomic.com/t/upsert-tupleattrs-containing-db-type-ref-using-tempids/2181 "2023-02-15T02:46:47Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![onetom](https://sea2.discourse-cdn.com/flex016/user_avatar/forum.datomic.com/onetom/32/430_2.png) [@onetom](https://forum.datomic.com/u/onetom)\
**Post date:** [February 15, 2023, 2:46am UTC](https://forum.datomic.com/t/upsert-tupleattrs-containing-db-type-ref-using-tempids/2181/1 "2023-02-15T02:46:47Z")

</div>

We are able to do **upserting** a single entity, having a tuple key, mad of `:db.type/ref` attrs, IF we use concrete entity IDs:

```clojure
[{:ref/x 79164837199951,
  :ref/y 79164837199952,
  :key [79164837199951 79164837199952]}]

```

However, if we are trying to **upsert** the referenced entities at the same time, hence using tempids, instead of concrete entity IDs, we are getting `:db.error/unique-conflict`:

```clojure
[{:db/id "x", :x/id "x"}
 {:db/id "y", :y/id "y"}
 {:ref/x "x", :ref/y "y", :key ["x" "y"]}]

```

# Q1. Is this expected behaviour?

# Q2. Is it some intentional restriction?

# Q3. Are there some use-cases, where this approach would be ambiguous?

This seems like a bug or at least a missing feature, unless I’m missing something.

Without this capability, we had to **break the atomicity** of our transaction, by splitting it into 2 transactions.

# Related literature

- [:db.unique/identity does not work for tuple attributes](https://forum.datomic.com/t/db-unique-identity-does-not-work-for-tuple-attributes/1072)
- [Upsert behavior with composite tuple key](https://forum.datomic.com/t/upsert-behavior-with-composite-tuple-key/1075)
- [Troubles with upsert on composite tuples](https://forum.datomic.com/t/troubles-with-upsert-on-composite-tuples/1355/)

# Complete example

Requires Datomic `dev-local`.

```clojure
(ns repl.composite-ref-key
  (:require [datomic.client.api :as d]
            [clojure.pprint :refer [pprint]]))

(defn ? [x] (println) (pprint x) x)

(defn tx! "Pretty-printing d/transact" [conn tx-data]
  (let [?? (if (-> tx-data meta :silent) identity ?)]
    (-> conn
        (d/transact {:tx-data (?? tx-data)})
        (doto (-> (dissoc :db-before :db-after)
                  (update :tx-data (partial into []))
                  ??)))))

(defn mk-conn [schema]
  (let [db-ref {:db (str (gensym "db-"))}]
    (-> {:server-type :dev-local
         :storage-dir :mem
         :system (str (gensym "in-mem-sys-"))}
        d/client
        (doto (d/create-database db-ref))
        (d/connect db-ref)
        (doto (tx! schema)))))

(def schema
  ^:silent
  [{:db/ident :x/id
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity}

   {:db/ident :y/id
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity}

   {:db/ident :ref/x
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one}

   {:db/ident :ref/y
    :db/valueType :db.type/ref
    :db/cardinality :db.cardinality/one}

   {:db/ident :key
    :db/valueType :db.type/tuple
    :db/tupleAttrs [:ref/x :ref/y]
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity}

   {:db/ident :attr
    :db/valueType :db.type/long
    :db/cardinality :db.cardinality/one}])

(comment
  ;;; Using concrete entity IDs works
  (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
             :ref/y y
             :key [x y]}]
    (tx! conn [(-> ent (merge {:attr 1}))])
    (tx! conn [(-> ent (merge {:attr 2}))]))

  ;[{:db/id "x", :x/id "x"} {:db/id "y", :y/id "y"}]
  ;
  ;{:tx-data
  ; [#datom[13194139533319 50 #inst "2023-02-15T03:24:33.321-00:00" 13194139533319 true]
  ; #datom[101155069755471 73 "x" 13194139533319 true]
  ; #datom[101155069755472 74 "y" 13194139533319 true]],
  ; :tempids {"x" 101155069755471, "y" 101155069755472}}
  ;
  ;[{:db/id "ent",
  ; :ref/x 101155069755471,
  ; :ref/y 101155069755472,
  ; :key [101155069755471 101155069755472],
  ; :attr 1}]
  ;
  ;{:tx-data
  ; [#datom[13194139533320 50 #inst "2023-02-15T03:24:33.323-00:00" 13194139533320 true]
  ; #datom[87960930222161 75 101155069755471 13194139533320 true]
  ; #datom[87960930222161 76 101155069755472 13194139533320 true]
  ; #datom[87960930222161 78 1 13194139533320 true]
  ; #datom[87960930222161 77 [101155069755471 101155069755472] 13194139533320 true]],
  ; :tempids {"ent" 87960930222161}}
  ;
  ;[{:db/id "ent",
  ; :ref/x 101155069755471,
  ; :ref/y 101155069755472,
  ; :key [101155069755471 101155069755472],
  ; :attr 2}]
  ;
  ;{:tx-data
  ; [#datom[13194139533321 50 #inst "2023-02-15T03:24:33.325-00:00" 13194139533321 true]
  ; #datom[87960930222161 78 2 13194139533321 true]
  ; #datom[87960930222161 78 1 13194139533321 false]],
  ; :tempids {"ent" 87960930222161}}

  ;;; Using tempids fails
  (let [conn (mk-conn schema)
        ent {:db/id "ent"
             :ref/x {:db/id "x" :x/id "x"}
             :ref/y {:db/id "y" :y/id "y"}
             :key ["x" "y"]}]
    (tx! conn [(-> ent (merge {:attr 1}))])
    (tx! conn [(-> ent (merge {:attr 2}))]))
  
  ;[{:db/id "ent",
  ; :ref/x {:db/id "x", :x/id "x"},
  ; :ref/y {:db/id "y", :y/id "y"},
  ; :key ["x" "y"],
  ; :attr 1}]
  ;
  ;{:tx-data
  ; [#datom[13194139533319 50 #inst "2023-02-15T03:25:49.663-00:00" 13194139533319 true]
  ; #datom[83562883711055 75 83562883711056 13194139533319 true]
  ; #datom[83562883711056 73 "x" 13194139533319 true]
  ; #datom[83562883711055 76 83562883711057 13194139533319 true]
  ; #datom[83562883711057 74 "y" 13194139533319 true]
  ; #datom[83562883711055 78 1 13194139533319 true]
  ; #datom[83562883711055 77 [83562883711056 83562883711057] 13194139533319 true]],
  ; :tempids
  ; {"x" 83562883711056, "y" 83562883711057, "ent" 83562883711055}}
  ;
  ;[{:db/id "ent",
  ; :ref/x {:db/id "x", :x/id "x"},
  ; :ref/y {:db/id "y", :y/id "y"},
  ; :key ["x" "y"],
  ; :attr 2}]
  
  ;Execution error (ExceptionInfo) at datomic.core.error/raise (error.clj:55).
  ;:db.error/unique-conflict Unique conflict: :key, 
  ; value: [83562883711056 83562883711057]
  ; already held by: 83562883711055 
  ; asserted for: 101155069755474

  (let [conn (mk-conn schema)
        x-ent {:db/id "x" :x/id "x"}
        y-ent {:db/id "y" :y/id "y"}
        ent {:db/id "ent"
             :ref/x "x"
             :ref/y "y"
             :key ["x" "y"]}]
    (tx! conn [x-ent y-ent (-> ent (merge {:attr 1}))])
    (tx! conn [x-ent y-ent (-> ent (merge {:attr 2}))]))

  ; same end result as above

  )

```

---

<div class="post-metadata">

**Author:** ![kenallen](https://avatars.discourse-cdn.com/v4/letter/k/858c86/32.png) [@kenallen](https://forum.datomic.com/u/kenallen)\
**Post date:** [November 8, 2024, 2:54pm UTC](https://forum.datomic.com/t/upsert-tupleattrs-containing-db-type-ref-using-tempids/2181/2 "2024-11-08T14:54:17Z")

</div>

+1 on this, I’ve run into the same issue. Instead of breaking atomicity I worked around it by denormalizing some data into the tuple. This really seems like a bug.

This is the obvious way to model identity for reified relations so it’s pretty unfortunate that this doesn’t work.
