datomic

Ask questions on the official Q&A site at https://ask.datomic.com!
babardo 2021-04-08T07:19:49.212Z

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.

tatut 2021-04-08T09:52:22.212300Z

Isn’t it so that the upsert behaviour is when :db.unique/identity is specified

tatut 2021-04-08T09:52:29.212500Z

and not when :db.unique/value

tatut 2021-04-08T10:10:52.212700Z

(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}))))

👍 1
tatut 2021-04-08T10:11:20.212900Z

you could do something like that in a tx function and call it (ensure-non-existant db [:my/id-attr "42"])

babardo 2021-04-08T10:13:45.213100Z

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

2021-04-08T10:44:16.214300Z

You can write it as a database function to guarantee atomicity

futuro 2021-04-08T13:54:44.214600Z

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.

futuro 2021-04-08T13:55:26.214800Z

That seems simpler than using transaction functions; am I missing something?

futuro 2021-04-08T15:21:08.219600Z

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.

futuro 2021-04-09T15:10:52.238300Z

That's a really interesting idea as well.

futuro 2021-04-08T15:21:21.219900Z

How have folks approached constraining enums-as-refs?

futuro 2021-04-08T15:23:36.222400Z

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).

favila 2021-04-08T15:32:08.222500Z

If closed-set validity is more important to you than extensibility, consider just using a keyword and checking the value with an attribute predicate

favila 2021-04-08T15:33:44.222800Z

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

futuro 2021-04-08T15:34:44.223Z

I'd never considered attaching additional assertions to them, that's really interesting. What kind of assertions would you put on the enum itself?

favila 2021-04-08T15:35:24.223200Z

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

favila 2021-04-08T15:36:17.223400Z

re “what assertions”: well some you get for free, like the transaction that introduced the enum

favila 2021-04-08T15:38:20.223600Z

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

favila 2021-04-08T15:39:38.223800Z

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

favila 2021-04-08T15:40:09.224Z

note that neither method protects you from some schematic change that makes some existing enum values invalid

favila 2021-04-08T15:41:34.224200Z

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

futuro 2021-04-08T15:42:27.224400Z

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?

favila 2021-04-08T15:43:25.224600Z

yes, I mean you have some attributes that describe entity-level (or higher) schema relationships

favila 2021-04-08T15:44:05.224800Z

E.g.: https://www.youtube.com/watch?v=sQCoTu5v1Mo

futuro 2021-04-08T15:45:55.225Z

Thank you for this link, I'm gonna watch that right now

favila 2021-04-08T15:46:26.225200Z

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

favila 2021-04-08T15:46:51.225400Z

also, you may not necessarily want to be so rigid about entity-level schemas

futuro 2021-04-08T16:16:35.225700Z

That was a really interesting talk, thank you 👏:skin-tone-2:

futuro 2021-04-08T16:17:44.225900Z

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)

cjsauer 2021-04-08T18:40:13.228200Z

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.

Joe Lane 2021-04-08T18:40:35.228400Z

"upgrade path"?

cjsauer 2021-04-08T18:41:26.229500Z

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.

cjsauer 2021-04-08T18:42:15.230100Z

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.

Joe Lane 2021-04-08T18:42:41.230600Z

Is your tiny application running on the internet, or on your laptop?

cjsauer 2021-04-08T18:43:07.230800Z

Just laptop at the moment.

Joe Lane 2021-04-08T18:45:41.232900Z

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.

cjsauer 2021-04-08T18:47:09.234100Z

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.

Joe Lane 2021-04-08T18:48:20.234600Z

Correct, its for developing, locally (and/or tests in CI)

cjsauer 2021-04-08T18:50:09.235200Z

Gotcha, thanks Joe. I’ll look into cloud.

babardo 2021-04-08T19:55:50.235600Z

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.

futuro 2021-04-08T21:18:06.236100Z

Yep!

kenny 2021-04-08T21:25:22.236600Z

If you don’t need history, what’s the recommended approach?