Fork me on GitHub
#datomic
<
2020-11-01
>
yubrshen02:11:38

What's the meaning of the following error message, and how can I investigate and fix it?

:db.error/lookup-ref-attr-not-unique Attribute values not unique: :user/email
Here is the source code that will recreate the error:
(ns grok.db.add-user
  (:require  [grok.db.core :as SUT]
             [grok.db.schema :refer [schema]]
             [datomic.api :as d]))

(def sample-user
  {:user/id (d/squuid)
   :user/email ""
   :user/password "password12345"})

(defn fresh-db []
  (let [db-uri (str "datomic:mem://" (gensym))
        conn (SUT/create-conn db-uri)]
    (d/transact conn schema)
    (d/transact conn [sample-user])
    conn))

(def conn (fresh-db))
conn

(d/entity (d/db conn) [:user/email ""])
The code will create a user in the mem database, and retrieve it by its email address (for the purpose of having a user for tests) Here is the related schema code for user:
[
   ;; ## User
   ;; - id        (uuid)
   ;; - full-name (string)
   ;; - username  (string)
   ;; - email     (string => unique)
   ;; - password  (string => hashed)
   ;; - token     (string)

   {:db/ident :user/id
    :db/valueType :db.type/uuid
    :db/cardinality :db.cardinality/one
    :db/unique :db.unique/identity
    :db/doc "ID of the User"}

   {:db/ident :user/email
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one
    :db/doc "Email of the User"}

   {:db/ident :user/password
    :db/valueType :db.type/string
    :db/cardinality :db.cardinality/one
    :db/doc "Hashed Password of the User"}
   ]
Here is the code of create-conn:
(defn create-conn [db-uri]
  (when db-uri
    (d/create-database db-uri)
    (let [conn (d/connect db-uri)]
      conn)))
Here is the full error trace:
2. Unhandled clojure.lang.Compiler$CompilerException
   Error compiling test/grok/db/add_user.clj at (21:1)
   #:clojure.error{:phase :compile-syntax-check,
                   :line 21,
                   :column 1,
                   :source
                   "/home/yshen/programming/clojure/learn-immutable-stack-with-live-coding-ankie/grok/server/test/grok/db/add_user.clj"}
             Compiler.java: 7648  clojure.lang.Compiler/load
                      REPL:    1  user/eval19658
                      REPL:    1  user/eval19658
             Compiler.java: 7177  clojure.lang.Compiler/eval
             Compiler.java: 7132  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj: 1973  clojure.core/with-bindings*
                  core.clj: 1973  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                regrow.clj:   20  refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  202  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  201  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  834  java.lang.Thread/run

1. Caused by datomic.impl.Exceptions$IllegalArgumentExceptionInfo
   :db.error/lookup-ref-attr-not-unique Attribute values not unique: :user/email
   {:cognitect.anomalies/category :cognitect.anomalies/incorrect,
    :cognitect.anomalies/message "Attribute values not unique: :user/email",
    :db/error :db.error/lookup-ref-attr-not-unique}
                 error.clj:   79  datomic.error/arg
                 error.clj:   74  datomic.error/arg
                 error.clj:   77  datomic.error/arg
                 error.clj:   74  datomic.error/arg
                    db.clj:  590  datomic.db/resolve-lookup-ref
                    db.clj:  569  datomic.db/resolve-lookup-ref
                    db.clj:  610  datomic.db/extended-resolve-id
                    db.clj:  606  datomic.db/extended-resolve-id
                    db.clj:  621  datomic.db/resolve-id
                    db.clj:  614  datomic.db/resolve-id
                    db.clj: 2295  datomic.db.Db/entity
                   api.clj:  171  datomic.api/entity
                   api.clj:  169  datomic.api/entity
              add_user.clj:   21  grok.db.add-user/eval19672
              add_user.clj:   21  grok.db.add-user/eval19672
             Compiler.java: 7177  clojure.lang.Compiler/eval
             Compiler.java: 7636  clojure.lang.Compiler/load
                      REPL:    1  user/eval19658
                      REPL:    1  user/eval19658
             Compiler.java: 7177  clojure.lang.Compiler/eval
             Compiler.java: 7132  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  clojure.core/eval
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn/fn
                  AFn.java:  152  clojure.lang.AFn/applyToHelper
                  AFn.java:  144  clojure.lang.AFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj: 1973  clojure.core/with-bindings*
                  core.clj: 1973  clojure.core/with-bindings*
               RestFn.java:  425  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   87  nrepl.middleware.interruptible-eval/evaluate/fn
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                regrow.clj:   20  refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   84  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   56  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  152  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  202  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  201  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  834  java.lang.Thread/run
I ran into this problem when I followed the coding example at https://www.youtube.com/watch?v=Fz6LxSSc_GE at 6:06 (The Immutable Stack - Building Anki Clone using Clojure, Datomic and ClojureScript (Part 5)). In the video, there was no error, bui I did, and could reproduce it as above.

favila03:11:14

The schema for :user/email does not include a :db/unique constraint, therefore you cannot use this attribute for lookups as you do in your d/entity call

3
👍 3
favila03:11:35

Thus “lookup ref attr not unique” in the error message

yubrshen03:11:14

@U09R86PA4 Thanks for the quick and effective help!

Jakub Holý (HolyJak)21:11:50

I guess "lookup ref attr not declared as :db/unique" would be a more helpful error message.

🙏 3
yubrshen02:11:54

It's strange that once I added :db/unique to the existing schema without transacting, but just evaluate the schema definition, it works for retrieving the user by email address. But later, when I actually transacted the updated schema, then I run into trouble of "Error: {:db/error :db.error/unique-without-index, :attribute :user/email}"

yubrshen18:11:44

How do I fix this error? "Error: {:db/error :db.error/unique-without-index, :attribute :user/email}" The error happened when I added more entities to the schema and did (d/transact conn schema) again to update the schema. This is a consequence of my prior error referred in https://clojurians.slack.com/archives/C03RZMDSH/p1604199338251700 where I had to change my schema to add :db/unique constraint to :user/email I wouldn't mind to start from scratch with a new database. But I have not learned how to do that yet. But if it were in a production system, when I modify my schema, what's the proper way to correct and update?

Jakub Holý (HolyJak)21:11:24

I wish there was a catalog of datomic errors with explanations and guidance. Searching the net for "datomic unique-wirhout-index&t" yields nothing :-(

🙏 3
favila21:11:29

You need a value index before you can make a value unique. See the docs on schema changes, it has a table of all legal transitions

🙏 3
yubrshen02:11:56

My problems are that I don't know enough Datomic to figure out how to have "a value index". I'm studying the documentation on schema change at https://docs.datomic.com/cloud/schema/schema-change.html There are two pre-conditions:In order to add a uniqueness constraint to an attribute, both of the following must be true: > The attribute must have a cardinality of `:db.cardinality/one`. > If there are values present for that attribute, they must be unique in the set of current database assertions. For the first one, my schema for :user/email already has :db.cardinality/one. For the second one, I don't know how to handle: 1. how to check if the values present for :user/email are unique or not 2. If not, how to fix them.

favila12:11:59

Are you using cloud or on prem? You cite cloud docs but this sounds like an on-prem problem. (Cloud indexs all values by default)

favila12:11:01

On on-prem, there is a :db/index true

🙏 3
yubrshen15:11:55

I'm using on-prem, actually just dev one. I'll take look of :db/index tree @U09R86PA4 Thanks for the pointer!

favila15:11:59

> All alterations happen synchronously, except for adding an AVET index. If you want to know when the AVET index is available, call https://docs.datomic.com/on-prem/javadoc/datomic/Connection.html#syncSchema(long). In order to add :db/unique, you must first have an AVET index including that attribute.

favila15:11:02

(quote from the docs)

yubrshen15:11:04

@U09R86PA4 Yes, the following worked:

(def tx-add-index @(d/transact conn [{:db/id :user/email
                                    :db/index true}]))
(def tx-fix @(d/transact conn [{:db/id :user/email
                                :db/unique :db.unique/identity}]))
where conn is a connection to the on-prem (dev) database. Thanks again for your coaching!