I see the default behaviour for adding new entity with pre-existing :db/unique is upsert. It there a convenient way to turn this behaviour down? In other words I would want to insert an entity if it doesn't exist or fail.
Isn’t it so that the upsert behaviour is when :db.unique/identity
is specified
and not when :db.unique/value
(defn ensure-non-existant [db [lookup-attr lookup-val :as lookup-ref]]
(when (ffirst
(d/q [:find '?e :where ['?e lookup-attr '?val] :in '$ '?val]
db lookup-val))
(throw (ex-info "already exists"
{:lookup-ref lookup-ref}))))
you could do something like that in a tx function and call it (ensure-non-existant db [:my/id-attr "42"])
yes sorry i meaned db/identity
, so it's seems that querying the database prior to the transact is the only option we have here
You can write it as a database function to guarantee atomicity
Your described use-case sounds like you want :db.unique/value
instead, as that will throw an anomaly if you attempt to transact a new entity with an existing value for a :db.unique/value
attribute.
That seems simpler than using transaction functions; am I missing something?
I've got an attribute whose potential values are enums, so I followed the best practices guide and made its valueType a ref
; I'd like to constrain the possible values to the small set of enums I've set up as {:db/ident :some.enum/value}
, but attribute predicates don't seem like a good fit because they get the EID instead of the keyword, which requires a DB query to link the two.
That's a really interesting idea as well.
How have folks approached constraining enums-as-refs?
I've contemplated an entity-predicate, but that requires programmer involvement instead of the db automatically checking it for us (as far as I understand from the docs), and I was hoping for something that happened without programmer effort (and thus couldn't accidentally be forgotten).
If closed-set validity is more important to you than extensibility, consider just using a keyword and checking the value with an attribute predicate
The benefit of enums as refs is that you can rename them with ident semantics and you can attach additional assertions to them. If you don’t need that, that’s ok
I'd never considered attaching additional assertions to them, that's really interesting. What kind of assertions would you put on the enum itself?
Additionally, this “best practice” was formulated before attr predicates or entity predicates existed. In those days, all constraint checking was best-effort and application-enforced. Making the enums a ref gave at least some possibility of putting a queryable metaschema into your db
re “what assertions”: well some you get for free, like the transaction that introduced the enum
you could also place enums into a hierarchy (like e.g. multimethod heirarchies) or reference them from an “enum set” (i.e. the attribute, or some other enum, references the attributes as its legal value range in a metaschema) or with a :db/doc for a human to read, etc
all of this also lets your enum set be more dynamic in the application. Using an attribute predicate would require a redeployment to add or change the schema
note that neither method protects you from some schematic change that makes some existing enum values invalid
an entity predicate will catch it and fail the transaction if you use it to verify things opportunistically, but that’s the only tool available to you. For everything else you need to manually remove or change old values, make sure everyone stops writing them, then update your attribute predicate or homespun meta-schema
When you say "metaschema", do you mean that I might have an attribute called :valid-enum-vals
that contains the EIDs of the enums that are currently valid?
yes, I mean you have some attributes that describe entity-level (or higher) schema relationships
Thank you for this link, I'm gonna watch that right now
Note that it’s pretty old (2013). It’s still relevant from a data-modeling perspective, but you would probably leverage entity predicates, required attributes, and attribute predicates more now
also, you may not necessarily want to be so rigid about entity-level schemas
That was a really interesting talk, thank you 👏:skin-tone-2:
It's given me a lot to think about, and it's also gotten me curious about how the RDF world handles this problem (and whether it matches with Antonio's proposed solution)
Is there a smooth upgrade path from dev-local to datomic cloud? I see import-cloud
, but that seems to only work in the cloud->local direction.
"upgrade path"?
Well, I should rephrase. Is it easy to import data in the other direction? I imagine it’s as “simple” as streaming all the datoms in dev-local into the cloud db, but just double checking.
I have a tiny application that I’d like to use dev-local for, and one day it might need something more robust. Just weighing my options on how easy that move would be.
Is your tiny application running on the internet, or on your laptop?
Just laptop at the moment.
Ok, this is good. Once your application is bigger than your laptop, use cloud. Unfortunately it isn't as "simple" as "upload the datoms", and there are a variety of ways depending on if you need history or not, if the db already exists in c loud, etc.
Ah okay, glad I asked. So dev-local is really not meant as a standalone database that I’d run on, say, a VM exposed to the internet. I figured I might be able to get away with doing that and could move to cloud later.
Correct, its for developing, locally (and/or tests in CI)
Gotcha, thanks Joe. I’ll look into cloud.
ok sorry, I get the first answer of @tatut and the the one of @futuro now.
Using :db.unique/value
instead of :db.unique/identity
do the job, meaning we can still make lookup ref on this field (as :db.unique/identity
) but an upsert fails with an Unique conflict
exception.
Yep!
If you don’t need history, what’s the recommended approach?