Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upserts fail when tx-data is passed as vector rather than map #99

Closed
lynaghk opened this issue Jul 12, 2015 · 3 comments
Closed

Upserts fail when tx-data is passed as vector rather than map #99

lynaghk opened this issue Jul 12, 2015 · 3 comments

Comments

@lynaghk
Copy link
Contributor

lynaghk commented Jul 12, 2015

With

[datascript "0.11.5"]
[com.datomic/datomic-pro "0.9.4815.12"]

DataScript's behavior:

(require '[datascript :as ds])

  (def conn
    (ds/create-conn
     {:person/name {:db/unique :db.unique/identity}}))

  (ds/transact! conn [[:db/add (ds/tempid :db.part/user) :person/name "foo"]])

  (ds/transact! conn [[:db/add (ds/tempid :db.part/user) :person/name "foo"]])
  ;;second time throws error!
  ;; 1. Unhandled clojure.lang.ExceptionInfo
  ;;  Cannot add #datascript/Datom [2 :person/name "foo" 536870914 true]
  ;;  because of unique constraint: (#datascript/Datom [1 :person/name
  ;;  "foo" 536870913 true])

  ;;but the map version works fine:
  (ds/transact! conn [{:db/id (ds/tempid :db.part/user) :person/name "foo"}])

differs from Datomic's

  (require '[datomic.api :as d])

  (let [uri (str "datomic:mem://" (gensym))
        _ (d/create-database uri)]
    (def conn (d/connect uri)))


  (d/transact conn [{:db/id #db/id[:db.part/db]
                     :db/ident :person/name
                     :db/valueType :db.type/string
                     :db/cardinality :db.cardinality/one
                     :db/unique :db.unique/identity
                     :db.install/_attribute :db.part/db}])

  @(d/transact conn [[:db/add (d/tempid :db.part/user) :person/name "foo"]])
  (d/entity (d/db conn) [:person/name "foo"]) ;;=> {:db/id 17592186045418}

  @(d/transact conn [[:db/add (d/tempid :db.part/user) :person/name "foo"]])
  (d/entity (d/db conn) [:person/name "foo"]) ;;=> {:db/id 17592186045418}

Currently all of the tests in datascript/test/datascript/test/upsert.cljc use the map version only.
As far as I can tell, semantically the vector version should work as well.

@lynaghk
Copy link
Contributor Author

lynaghk commented Jul 13, 2015

Actually, there is an issue with the map version as well: It seems that if the tempid is resolved via the db.unique attribute first, then additional fields can be asserted:

(let [tid (ds/tempid :db.part/user)]
    (ds/transact! conn [{:db/id tid :person/name "foo"}
                        {:db/id tid :extra "value"}]))

works fine, but if you try to assert other fields before the db.unique attribute, the transaction will fail:

(let [tid (ds/tempid :db.part/user)]
    (ds/transact! conn [{:db/id tid :extra "value"}
                        {:db/id tid :person/name "foo"}]))

  ;; 1. Unhandled clojure.lang.ExceptionInfo
  ;;  Cannot resolve upsert for {:db/id 2, :person/name "foo"}: {:db/id
  ;;  2, :person/name "foo"} conflicts with existing #datascript/Datom [1
  ;;  :person/name "foo" 536870913 true]

@dwwoelfel
Copy link

It looks like you're going down the same path I did in this issue: #76

Are you also trying to sync datoms from a datomic db to a datascript db?

The workaround I'm using is to separate the datoms with unique attributes and transact those (in map form, of course) first. Then take the tempids from the tx-report and reduce through the rest of the datoms, replacing tempids with the ids they resolved to and building up maps from the datoms. It looks something like (I haven't actually run this):

(def datoms [#datom[tid :extra "value" tx0 true]
             #datom[tid :person/name "foo" tx0 true]])

(let [{adds true retracts false} (group-by :added datoms)
      {unique true rest-datoms false} (group-by unique-attr? adds)
      ;; first transact unique ids resolve tempids,
      ;; for our example, will look like [{:db/id tid :person/name "foo"}]
      {:keys [tempids]} (d/transact! db-conn (map (fn [d]
                                                    (assoc {:db/id (:e d)}
                                                           (:a d) (:v d)))
                                                  unique))]
  ;; Replace resolved tempids in the 2nd transaction
  ;; For our example, It will look something like [{:db/id 1 :extra "value"}]
  (d/transact! db-conn (concat retracts
                               (vals (reduce (fn [acc d]
                                               (update acc (:e d) (fn [m]
                                                                    (assoc m
                                                                           :db/id (get tempids (:e d) (:e d))
                                                                           (:a d) (:v d)))))
                                             {} rest-datoms)))))

I think that will probably fail if there are more than 1 unique attrs for a single entity.

@lynaghk
Copy link
Contributor Author

lynaghk commented Jul 13, 2015

@dwwoelfel Ah, thanks for pointing out the related issue!

I'm not trying to sync datomic and datascript --- just persist a subset of datascript datoms to disk and then load them back up again.

Based on @tonsky's comments in #76 it seems like there's a deliberate difference between datascript and datomic in this case, so I'll just need to add a special case to save/load entities with db.unique attributes.

Closing this issue as a duplicate of #76.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants