datascript icon indicating copy to clipboard operation
datascript copied to clipboard

Upserting with unique tuple refs fails

Open ash14 opened this issue 4 years ago • 3 comments

Attempting an upsert to an entity using its unique tuple attribute does not work as expected.

Hope this example is clear:

(let [conn (d/create-conn {:player {:db/unique :db.unique/identity}
                           :home {:db/valueType :db.type/ref}
                           :away {:db/valueType :db.type/ref}
                           :players {:db/unique :db.unique/identity
                                     :db/tupleAttrs [:home :away]}})]
  
  (d/transact! conn [[:db/add -1 :player "Nadal"]
                     [:db/add -2 :player "Federer"]
                     {:home -1
                      :away -2}])
  
  (d/transact! conn [{:db/id "p1"
                      :player "Nadal"}
                     {:db/id "p2"
                      :player "Federer"}
                     {:db/id "match"
                      :players ["p1" "p2"]
                      :game 3}]))

Execution error (ClassCastException) at datascript.db/value-compare (db.cljc:339).
java.lang.String cannot be cast to java.lang.Number

Expecting a result like (= 3 (:game (d/entity @conn 3)))

I don't think this is related to #364?

Thanks. I'll try to open a PR for this if it's straightforward enough.

Edit: for what it's worth, here's the same example on Datomic 1.0.6202
@(d/transact conn [{:db/ident :player
                    :db/valueType :db.type/string
                    :db/unique :db.unique/identity
                    :db/cardinality :db.cardinality/one}

                   {:db/ident :home
                    :db/valueType :db.type/string
                    :db/cardinality :db.cardinality/one}

                   {:db/ident :away
                    :db/valueType :db.type/string
                    :db/cardinality :db.cardinality/one}

                   {:db/ident :players
                    :db/unique :db.unique/identity
                    :db/valueType :db.type/tuple
                    :db/tupleAttrs [:home :away]
                    :db/cardinality :db.cardinality/one}

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

@(d/transact conn [[:db/add "p1" :player "Nadal"]
                   [:db/add "p2" :player "Federer"]
                   {:home "p1"
                    :away "p2"}])

@(d/transact conn [{:db/id "p1" :player "Nadal"}
                   {:db/id "p2" :player "Federer"}
                   {:db/id "match"
                    :players ["p1" "p2"]
                    :game 3}])

(d/pull (d/db conn) '[*] (:e (last (d/datoms (d/db conn) :eavt))))
; => {:db/id 17592186045420, :home "p1", :away "p2", :players ["p1" "p2"], :game 3}

ash14 avatar Dec 17 '20 10:12 ash14

Closing this, Datomic doesn't exhibit the behaviour I was expecting either.

ash14 avatar Jan 14 '21 02:01 ash14

Even if Datomic doesn’t support this, it seems like a reasonable thing to do? Let’s keep it open

tonsky avatar Jan 14 '21 14:01 tonsky

EDIT: I noticed that this is specific to refs. I've been playing with this issue for a while and got my wires crossed on this issue. Do you think I should open a separate issue about this? I don't have access to Datomic so I can't test over there.

I don't know if this is the same issue but I'm getting a different error message when trying to upsert with a unique composite tuple:

;;
;; Upsert by Unique Composite Tuple
;;
(let [schema {:one+two {:db/tupleAttrs [:one :two]
                        :db/unique :db.unique/identity}}
      conn (ds/create-conn schema)]

  (ds/transact! conn [{:db/id "tmpid"
                       :some-field "first upsert"
                       :one "one"
                       :two "two"}
                      {:db/id "tmpid"
                       :other-field "first upsert"
                       :one "one"
                       :two "two"}])


  (ds/transact! conn [{:db/id "tmpid"
                       :some-field "second upsert"
                       :one "one"
                       :two "two"}
                      {:db/id "tmpid"
                       :other-field "second upsert"
                       :one "one"
                       :two "two"}])

  (ds/q '[:find [(pull ?e [*]) ...]
          :where
          [?e :one _]]
        @conn))

I get this error:

Execution error (ExceptionInfo) at datascript.db/validate-datom (db.cljc:967).
Cannot add #datascript/Datom [2 :one+two ["one" "two"] 536870914 true] because of unique constraint: (#datascript/Datom [1 :one+two ["one" "two"] 536870913 true])

Of course, when I try the same type of upsert with a non-tuple unique attribute everything works

;;
;; Upsert by non-tuple unique attribute
;;
(let [schema {:email {:db/unique :db.unique/identity}}
      conn (ds/create-conn schema)]

  (ds/transact! conn [{:db/id "tmpid"
                       :some-field "first upsert"
                       :email "[email protected]"}
                      {:db/id "tmpid"
                       :other-field "first upsert"
                       :email "[email protected]"}])

  (ds/transact! conn [{:db/id "tmpid"
                       :some-field "second upsert"
                       :email "[email protected]"}
                      {:db/id "tmpid"
                       :other-field "second upsert"
                       :email "[email protected]"}])

  (ds/q '[:find [(pull ?e [*]) ...]
          :where
          [?e :email _]]
        @conn))

Result:

[{:db/id 1,
  :email "[email protected]",
  :other-field "second upsert",
  :some-field "second upsert"}]

caleb avatar Jul 07 '21 13:07 caleb