datalevin 2026-07-14

I sometimes experience transactions failing due to a unique-constraint on an entity containing a tupleAttrs schema An (anonymized) error message:

Cannot add #datalevin/Datom [17763
   :my-entity/parent+location+position [17568 :front-page 9]]
   because of unique constraint: 17655
All of my transactions are done using the entity-map syntax, so I originally thought that trying to transact an entity like this (supposing it already exists in the database with the same information) would effectively be a no-op (an upsert with no changes), as that tuple of values should itself act as an identifier I don't think my schema (below) is the source of the error, but maybe something pops out as odd:
{
  :my-entity/parent {:db/valueType :db.type/ref}
  :my-entity/location {:db/valueType :db.type/keyword}
  :my-entity/position {:db/valueType :db.type/long}
  :my-entity/parent+location+position {:db/unique :db.unique/identity,
                                       :db/tupleAttrs
                                       [:my-entity/parent
                                        :my-entity/location
                                        :my-entity/position]}
  :my-entity/some-other-val {:db/valueType :db.type/long}
}
I'm having difficulty creating a minimum-reproduction, as so far I only seem to encounter these errors when doing larger/more-nested* transactions, and limiting the transaction to only the 'problem' values often results in an unexpected success. • To clarify: by 'nested', I'm referring to parent-child :db.type/ref values, not idoc's Is my expectation of how the upsert behavior works incorrect, or is transacting larger/heavily-nested maps considered undefined?

We had an issue where composite attributes are incorrectly encoded. It has now been fixed in master branch. However, it requires a data migration to see the fix. I will be releasing 1.0.0 soon, so hopefully that can get your issue fixed by then.

I appreciate it (and your continued patience with my noise here <3) I may need to double-check; as I swapped to the master-branch yesterday, I did rm -rf my datalevin directory and began repopulating via transactions from 0, so I'd think I'd be up to date, but maybe I've made an error- if you're talking about issue https://github.com/datalevin/datalevin/issues/299, I do notice a slight difference in error-messages (but that might just be from overall changes to error-msg layout since then); I see a related issue https://github.com/datalevin/datalevin/issues/372 from June, but that one seemed to revolve around retraction-behavior. I'll append an actual stack-trace below just in case, as I realize I didn't actually do that yesterday~ Definitely looking forward to 1.0 either way!

Ok, I'm getting closer to a minimum reproduction, it seems to only happen when I'm upserting via a reverse-lookup list The following transactions work:

(d/transact! conn
    [{:db/id 16661
      :rel/_from [{:rel/to 336, :rel/type :alias}]}])

  (d/transact! conn
    [{:db/id 16661
      :rel/_from [{:rel/to 16666 :rel/type :alias}]}])

  (d/transact! conn
    [{:rel/from 16661, :rel/to 336, :rel/type :alias}
     {:rel/from 16661, :rel/to 16666 :rel/type :alias}])
But this one throws the error:
(d/transact! conn
    [{:db/id 16661
      :rel/_from [{:rel/to 336, :rel/type :alias}
                  {:rel/to 16666 :rel/type :alias}]}])
The initial insert via the reverse-lookup list does work, its only on the upsert that the error occurs.

Alright, this is a min repro for me:

(def tst
    (d/get-conn "/var/tmp/datalevin/tst"
      {:a/name {:db/valueType :db.type/string, :db/unique :db.unique/identity}
       :rel/type+from+to {:db/unique :db.unique/identity,
                          :db/tupleAttrs [:rel/type :rel/from :rel/to]}
       :rel/from {:db/valueType :db.type/ref}
       :rel/to {:db/valueType :db.type/ref}
       :rel/type {:db/valueType :db.type/keyword}}))

  (d/transact! tst
    [{:db/id 1 :a/name "A1"}
     {:db/id 2 :a/name "A2"}
     {:db/id 3 :a/name "A3"}
     {:rel/from 1 :rel/to 2 :rel/type :alias}
     {:rel/from 1 :rel/to 3 :rel/type :alias}])

  (d/transact! tst
    [{:rel/from 1 :rel/to 2 :rel/type :alias}
     {:rel/from 1 :rel/to 3 :rel/type :alias}]) ;; Succeeds

  (d/transact! tst
    [{:db/id 1
      :rel/_from [{:rel/to 2 :rel/type :alias}
                  {:rel/to 3 :rel/type :alias}]}]) ;; Fails
This should be on the current master-branch commit: 1f4fce0bfb03042f14880b042c9dba477c70e9b0

🙏 1

Confirmed as a bug. Thank you for the isolated case. Fixed in master.

Incredible, grateful for all your work on this project 🌟

❤️ 1