Fork me on GitHub
#datomic
<
2022-04-26
>
lambdam09:04:44

Hello, I declared an ident as a 2-elements unique tuple as so:

{:db/ident :foo/uname+uid
 :db/valueType :db.type/tuple
 :db/tupleAttrs [:foo/uname :foo/uid]
 :db/cardinality :db.cardinality/one
 :db/unique :db.unique/identity}
Then, when I use it as a lookup ref in a pull call, it works:
(d/pull db '[*] [:foo/uname+uid ["domain" "123456"]])
But when I use it in a transaction:
@(d/transact conn
             [{:db/id 987654321
               :bar/link [:foo/uname+uid ["domain" "123456"]]}])
I get the following error:
...
1. Caused by datomic.impl.Exceptions$IllegalArgumentExceptionInfo
   :db.error/not-a-keyword Cannot interpret as a keyword: domain, no leading :
   {:cognitect.anomalies/category :cognitect.anomalies/incorrect,
    :cognitect.anomalies/message
    "Cannot interpret as a keyword: domain, no leading :",
    :db/error :db.error/not-a-keyword}
...
Does someone understand the meaning and/or the reason of this error? Thanks a lot

favila12:04:27

Does this work? [[:db/add 987654321 :bar/link [:foo/uname+uid ["domain" "123456"]]]]. I suspect it’s just ambiguity in the map desugaring--it’s trying to interpret it as a list of entity refs instead of one large entity ref. That map->attribute stuff is not aware of what attributes mean.

lambdam12:04:47

Hello, Yes it works. Thanks. Nonetheless, it works with regular lookup refs, like [:foo/internal-name "plop"] , it works well. I tried to change the type of :foo/uname from string to keyword, and now am getting the following error:

...
Caused by datomic.impl.Exceptions$IllegalArgumentExceptionInfo
   :db.error/not-an-entity Unable to resolve entity: :domain
   {:cognitect.anomalies/category :cognitect.anomalies/incorrect,
    :cognitect.anomalies/message "Unable to resolve entity: :domain",
    :entity :domain,
    :db/error :db.error/not-an-entity}
It indeed seems to interpret the vector as a list of entities. But I don't understand why it does. Do you think that it is an inherent ambiguity coming from Datomic well defined semantics or a bug of misinterpretation ?

favila12:04:59

The map syntax is syntax sugar and the syntax is inherently ambiguous. Also on-prem has this “auto-keywordization” feature where you can put a string where a keyword is expected (e.g. “:foo”) and it will coerce to a keyword. (This was added I think so using datomic from java is easier. Cloud doesn’t have it. It makes things worse here.) So what should {:foo [:bar ["box" "baz"]]} mean? Is it [:db/add ID :foo :bar][:db/add ID foo [:box "baz"]]? (the only possible interpretation before tuples were added.) Or is it [:db/add ID :foo [:bar ["box" "baz"]]?

favila12:04:41

For backward compatibility, I think it has to be [:db/add ID :foo :bar][:db/add ID :foo [:box "baz"]]

favila12:04:44

And your puzzling stacktrace is because [:db/add ID :foo [:box "baz"]] doesn’t make sense. It tried to turn the “domain” in ["domain" "123456"] into a keyword via auto-keywordization and couldn’t.

lambdam13:04:28

But since idents have types, the [:db/add ID :foo :bar][:db/add ID foo [:box "baz"]] case couldn't be possible since :foo would have a :db/valueType :db.type/keyword type in the first assertion and a :db/valueType :db.type/ref type in the second. So the only valid case would be [:db/add ID :foo [:bar ["box" "baz"]] after tuples introduction and error before, no ... ?

favila13:04:06

the desugaring is purely syntatic

favila13:04:16

it doesn’t have a DB to introspect types

lambdam13:04:06

Ah ok, I see.

favila13:04:14

also, [:db/add ID :foo :bar][:db/add ID :foo [:box "baz"]] is indeed possible if :foo is a ref

favila13:04:29

:bar is a valid entity ref

favila13:04:38

so is [:box "baz"]

favila13:04:48

so is 12345

favila13:04:25

and in assertion contexts, so is -123 (temp id) or #db/id{:part :some-partition-kw :idx -1} (a tempid record object)

lambdam13:04:37

A keyword can be a ref? I didn't know. In which case?

favila13:04:41

so even if you used types, it wouldn’t help much

favila13:04:01

:db/ident establishes a keyword as a ref

favila13:04:08

that’s how attribute lookup works

lambdam13:04:29

Ah yes. I see.

lambdam13:04:36

Thanks a lot for all the information. I solved my problem with a little function:

(defn entity->attr-list [entity]
  (let [id (or (:db/id entity)
               (d/tempid :db.part/user))]
    (->> (dissoc entity :db/id)
         (mapv (fn [[key value]]
                 [:db/add id key value])))))

lambdam13:04:56

Humm may be not so simple for idents with "many" cardinality...

favila13:04:37

yes, nor nested maps

favila13:04:43

or reverse refs

favila13:04:43

if you are really attached to the map syntax except for this one handling of tuple values, consider annotating the tuple ref with metadata and expanding only those map entries via a postwalk

lambdam13:04:21

That's a lot of complexity for the map syntax. I'll change my code to explicitly format transaction statements without ambiguity. The standard way.

favila13:04:39

that’s what I recommend

lambdam13:04:57

👌 Thanks

favila13:04:13

sugar gives you cavities

lambdam13:04:15

😂 I see. Too much sugar at least.