Fork me on GitHub
#datahike
<
2022-10-21
>
Casey19:10:11

Is it possible to upsert a entity with a composite db/tupleAttrs :db.unique/identity when the tupleAttrs are db.type/refkeys? I can't figure out the proper way to transact the upsert without getting unique constraint violations.

Casey19:10:23

Here is a self contained example:

(ns user
  (:require [datahike.api :as d]))
(defn connect
    []
    (d/delete-database) ;; deletes the 'default' db
    (d/create-database {:schema-flexibility :write})
    (d/connect))
  (def conn2 (connect))
  (d/transact conn2 [;; this will be one part of the composite
                     {:db/ident       :gig/title
                      :db/valueType   :db.type/string
                      :db/unique      :db.unique/value
                      :db/cardinality :db.cardinality/one
                      :db/doc         "The title of the gig"}
                     ;; this will be the second part of the composite
                     {:db/ident       :song/title
                      :db/valueType   :db.type/string
                      :db/unique      :db.unique/value
                      :db/cardinality :db.cardinality/one
                      :db/doc         "The title of the song"}

                     ;; here we establish the references
                     {:db/ident       :played/song
                      :db/valueType   :db.type/ref
                      :db/cardinality :db.cardinality/one
                      :db/index       true
                      :db/doc         "The song that was played"}
                     {:db/ident       :played/gig
                      :db/valueType   :db.type/ref
                      :db/cardinality :db.cardinality/one
                      :db/index       true
                      :db/doc         "The gig that the song was played at"}

                     ;; here is a simple attribute we will want to upsert
                     {:db/ident       :played/quality
                      :db/valueType   :db.type/keyword
                      :db/cardinality :db.cardinality/one
                      :db/index       true
                      :db/doc         "Quality"}

                     ;; finally our composite id
                     {:db/ident :played/gig+song
                      :db/valueType :db.type/tuple
                      :db/tupleAttrs [:played/gig :played/song]
                      :db/cardinality :db.cardinality/one
                      :db/unique :db.unique/identity}])

  ;; create the referenced entities
  (d/transact conn2 [{:song/title "A"} {:gig/title "Z"}])
  ;; create our entity that we want to upsert
  (d/transact conn2 [{:played/gig [:gig/title "Z"]
                      :played/song [:song/title "A"]
                      :played/quality :quality/good}])

  (let [ent (->> (d/q '[:find  [?e ...]
                        :where [?e :played/gig [:gig/title "Z"]]]
                      @conn2)
                 first
                 (d/entity @conn2)
                 (into {}))]

    ;; this simple upsert fails with
    ;; Cannot add #datahike/Datom [10 :played/gig+song [8 7] 536870916 true] because
    ;; of unique constraint: #datahike/Datom [9 :played/gig+song [8 7] 536870915
    ;; true]
    ;; (d/transact conn2 [{:played/gig (-> ent :played/gig :db/id)
    ;;                     :played/song (-> ent :played/song :db/id)
    ;;                     :played/quality :quality/bad}])

    ;; this works! but it is not an upsert
    (d/transact conn2 [{:db/id [:played/gig+song (:played/gig+song ent)] :played/quality :quality/bad}]))

timo21:10:38

Hey Casey, I don't really know. Maybe @UB95JRKM3 or @UQVCR784A knows.