This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-07-10
Channels
- # beginners (15)
- # boot (15)
- # cider (6)
- # cljs-dev (231)
- # cljsjs (1)
- # cljsrn (26)
- # clojure (147)
- # clojure-argentina (1)
- # clojure-dev (8)
- # clojure-germany (1)
- # clojure-italy (26)
- # clojure-russia (2)
- # clojure-spec (83)
- # clojure-uk (154)
- # clojurescript (123)
- # conf-proposals (3)
- # core-async (5)
- # cursive (26)
- # datascript (21)
- # datomic (120)
- # emacs (2)
- # graphql (9)
- # hoplon (195)
- # instaparse (16)
- # jobs-discuss (1)
- # leiningen (8)
- # luminus (8)
- # lumo (7)
- # off-topic (17)
- # om (7)
- # om-next (3)
- # parinfer (121)
- # pedestal (5)
- # planck (13)
- # re-frame (11)
- # reagent (21)
- # ring-swagger (2)
- # spacemacs (28)
- # uncomplicate (3)
- # unrepl (7)
- # untangled (34)
- # vim (5)
I'm pretty green with datomic still, so forgive me if this is a dumb question:
Is there some set semantics I can enforce on an entity level?
Here's the example: Parsing e-mail addresses and storing them as :email/personal
and :email/address
where :email/address
is unique, however, personal
might differ. That is ok, unless I'm trying to transact this:
[{ :email/address "", :email/personal "Joe User"},
{ :email/address "", :email/personal "some other name"}]
{ :email/address "", :email/personal "some other name"}])
IllegalArgumentExceptionInfo :db.error/datoms-conflict Two datoms in the same transaction conflict
{:d1 [17592186054378 :email/personal "Joe User" 13194139543273 true],
:d2
[17592186054378 :email/personal "some other name" 13194139543273 true]}
datomic.error/deserialize-exception (error.clj:124)
Any way to get around that?@ezmiller77 1. you can programmatically http://docs.datomic.com/query.html#sec-6 construct query which would look like:
[:find (pull ?doc [*])
:in $ ?t1 ?t2
:where
[?t1 :metadata/tag :tag1]
[?t2 :metadata/tag :tag2]
[?doc :metadata/tags ?t1]
[?doc :metadata/tags ?t2]]
not sure whether it would be more readable/performant/maintainable/etc. than post processing, though.
2. I'd say, your attribute names look confusing. I'd use :metadata.tag/name
for individual tags, or even, if you need those only as enum values, – {:db/ident :metadata.tag/tag1}
Your schema makes it look (to me) like both :metadata/tag
and :metadata/tags
belong to the same entity, and do not represent relationship.
3. Also, if you supply tag ids to the query instead of actual tag keywords – you might be able to use your initial implementation. That'd be "preprocessing" with something like http://docs.datomic.com/clojure/#datomic.api/entid (which can be done inline btw.), I guess.@beders is your :email/personal
attribute marked as cardinality many? It seems that you are trying to attach multiple personal names to your entity
hmaurer: If I marked them as many, I don't get the set semantic I'd like, i.e. adding an entity with the same email/name combo twice leads to two copies of the same name.
I guess I want this:
email: e1
name: n1, n2, n3, n4
where nx are the different names being used for the same e-mail address.
I can achieve that by declaring cardinality of /personal
to many, but then I get duplicates.
I.e. I want n1, n2, n3, n4 to be unique as well
What is it precisely you want unique? entity+attribute+value is always unique, so you can't have duplicate names for the same email address if you make :email/personal
cardinality-many.
So the correct way is to look up the entity first for the e-mail address, then assert additional facts. I wanted to avoid the extra lookup, but it seems it is unavoidable.
it's unique identity.
I still will not be able to insert the example I gave above in one go, due to :db.error/datoms-conflict Two datoms in the same transaction conflict
I'll do the extra round-trip to get the e-mail's e
before inserting the actual e-mail entity (which contains attributes for :sender, :recipients, etc. )
ok, I found the error in my original schema: personal
was not set to many
(and address
was set to 'identity')
With
{:db/ident :email/address
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/identity}
{:db/ident :email/personal-m
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many}}
I can now do transact:
[{:email/address "", :email/personal-m "Bubu"} {:email/address "" :email/personal-m "Lala"}])
and it works as expected!(def uri "datomic:")
;=> #'user/uri
(d/create-database uri)
;=> true
(def c (d/connect uri))
;=> #'user/c
@(d/transact c [{:db/ident :email/address
:db/cardinality :db.cardinality/one
:db/valueType :db.type/string
:db/unique :db.unique/identity}
{:db/ident :email/personal
:db/cardinality :db.cardinality/many
:db/valueType :db.type/string}])
;=>
;{:db-before datomic.db.Db,
; @5c351827 :db-after,
; datomic.db.Db @ecb17425,
; :tx-data [#datom[13194139534312
; 50
; #inst"2017-07-10T18:29:14.565-00:00"
; 13194139534312
; true]
; #datom[63 10 :email/address 13194139534312 true]
; #datom[63 41 35 13194139534312 true]
; #datom[63 40 23 13194139534312 true]
; #datom[63 42 38 13194139534312 true]
; #datom[64 10 :email/personal 13194139534312 true]
; #datom[64 41 36 13194139534312 true]
; #datom[64 40 23 13194139534312 true]
; #datom[0 13 64 13194139534312 true]
; #datom[0 13 63 13194139534312 true]],
; :tempids {-9223301668109598144 63, -9223301668109598143 64}}
(d/transact c [{:email/address "", :email/personal "Joe User"},
{:email/address "", :email/personal "some other name"}])
;=>
;{:db-before datomic.db.Db,
; @ecb17425 :db-after,
; datomic.db.Db @1f5fd569,
; :tx-data [#datom[13194139534313
; 50
; #inst"2017-07-10T18:29:36.232-00:00"
; 13194139534313
; true]
; #datom[17592186045418 63 "" 13194139534313 true]
; #datom[17592186045418 64 "Joe User" 13194139534313 true]
; #datom[17592186045418 64 "some other name" 13194139534313 true]],
; :tempids {-9223301668109598142 17592186045418,
; -9223301668109598141 17592186045418}}
Your transaction error tells me that you definitely do not have :db.unique/identity set
@misha thanks for the helpful feedback. i think you are right that the attributes could use some simplifying/clarifying. i can't use enums for these tags because i want the tags to be definable by the end-user. what my schema describes is a simple entity, an arb
, that has three things: an id, a value, and metadata. the metadata is a ref
with a cardinality of many. its ident is :arb/metadata
. then i've defined a set of attributes that can be included as ref-ed values for :arb/metadata
, including the one we were discussing: :metadata/tags
. all the attrs meant to be refs for :arb/metadata
start with :metadata
. your comment is helpful because it makes me think that 1) i was misunderstanding the conventions for the .
and the /
in datomic, and 2) that i could have done this more simply by simply associating a series of attributes using the style you suggested :metadata.tag
, where the first part clearly indicates that it's metadata and the second indicates what kind of metadata. then i could skip the whole ref
thing. did i get you right? regarding the meaning of the .
and /
and their conventional use, is this documented somewhere? or discussed in a blog post perhaps?
x.y
(before the slash) is called the "namespace", clojure.core/namespace
function will give you this part. z
(after the slash) is the "name", get with clojrue.core/name
I usually put some indicator of the entity type the attribute appears on in the namespace part
@ezmiller77 I'm guessing you want your final result to look like {:db/id ... :metadata/tags [:tag1 :tag2]}
? You can't both reify tags and get this result directly from a pull expression. Just accept that you will post-process the result
If you don't reify tags (i.e. if metadata/tags is cardinality-many scalar type, no data on tags), you can do this.
Hi! Could someone email to me or link a good article on the performance characteristics of Datomic’s “filter” function? It takes the current db and an arbitrary predicate on datoms. How can be that executed efficiently?
Or you can weak-reference the tags with another entity (but from a data-modeling perspective, this is not a good idea)
@favila are there any predicate that cannot be used? for example, can I do a datalog query in the predicate to check an ACL or something?
right, but if I do a datalog query in the predicate I assume that will become very very slow on a large DB?
so the index segment that would be visible (without filtering) for a given clause is put through the filter
and then the datoms from the next clause are necessarily limited by whatever could bind to ?e
a bit brute-force, but conceptually it’s really neat to be able to filter the database based on security rules
and try to use the same query object with parameters, so that query plan doesn't get regenerated every time
I guess I could even pre-load the ACL for every user in memory so there are no remote calls in the filter predicate
@ezmiller77 you can think about attribute's namespace – as an sql table name, and attribute's name – as a column name.
So :metadata.tags
would be equivalent to tags
column in metadata
table.
And :metadata.tag/name
– name
column in metadata.tag
table.
(as opposed to yours name
column in the same metadata
table)
such thinking is a bit limiting though, because you can have attributes with different namespaces on the same entity (e.g. {:db/id 1 :foo/bar 2 :baz/quux 3}), but it explains my earlier comment well enough.
hmaurer: If I marked them as many, I don't get the set semantic I'd like, i.e. adding an entity with the same email/name combo twice leads to two copies of the same name.
I guess I want this:
email: e1
name: n1, n2, n3, n4
where nx are the different names being used for the same e-mail address.
I can achieve that by declaring cardinality of /personal
to many, but then I get duplicates.
I.e. I want n1, n2, n3, n4 to be unique as well
What I mean is every transaction has at least one assertion, the assertion of the time the transaction occured
“It will create a transaction entity, but no datom will be associated with that transaction”