Fork me on GitHub
#xtdb
<
2020-02-11
>
d0c0nnor17:02:33

Hi, just playing with crux for a side project, is there a way to enforce a uniqueness constraint ?

refset17:02:35

Hey @U4Z9Z95HQ - :crux.db/id is inherently unique and you should be able to break your data model apart to rely on this. For instance, these two documents together: {:crux.db/id :unique-val-123 :unique-val/owner :someid} {:crux.db/id :some-id :non-unique-attribute 456} capture that a certain att+val combination (`:unique-val/owner` :unique-val-123) associated with a given entity (`:some-id`) must be unique. Essentially the inverse of the reference attribute that you would intuitively model. Can you describe more about the kind of uniqueness constraint you are hoping to capture?

d0c0nnor17:02:28

Hi @U899JBRPF, thanks for your response. I'm creating a user sign-up flow in which I would rather not use the user's email as the id of the document representing them, but would still like the email address to be unique.

refset18:02:25

okay so yes in that case I would create a transformation of the email into something that can be used as an ID, then have that as a distinct entity which points to the unique user with which it is associated with

refset18:02:08

so one easy solution would be to append "mailto:" and use the URI id type e.g. `{:crux.db/id #crux/id "<mailto:[email protected]>" :email/owner :some-user}`

d0c0nnor18:02:12

Hmm, interesting, thanks!

d0c0nnor18:02:11

In terms of the constraint then, I would use a cas transaction to insert the email with 'nil' as the expected document ?

refset18:02:43

yep, that's the trick 🙂

d0c0nnor18:02:22

Great, thanks for your help!

refset18:02:23

No problem - thanks for asking - it's a good idea for inclusion in a tutorial or an entry in the docs

jarohen09:02:50

if you don't want the email address at all visible in the key, you might also be able to use one of the hashing UUID versions (3 or 5, if memory serves) - UUIDs are also valid keys in Crux 🙂

jarohen09:02:47

yep, 3 uses MD5, 5 uses SHA1

jarohen09:02:25

I've used https://github.com/danlentz/clj-uuid in the past, makes generating them much easier

d0c0nnor10:02:28

I had thought about using a hash as the id, didn't think of hashing uuid versions, thanks!

d0c0nnor11:02:29

Hi, just looking into this a little bit more, I guess the constraint failure of the cas transaction happens in another thread, so I need to wait for the transaction to be processed before I know that the constraint has failed ? This is from the test suite:

(t/testing "can create new user"
    (let [{:crux.tx/keys [tx-time
                          tx-id] :as submitted-tx}
          (api/submit-tx *api* [[:crux.tx/cas nil {:crux.db/id :ivan
                                                   :name "Ivan 1st"}]])]
      (api/await-tx *api* submitted-tx)
      (t/is (true? (api/tx-committed? *api* submitted-tx)))

      (t/is (= #{["Ivan 1st"]} (api/q (api/db *api* tx-time tx-time)
                                      '{:find [n]
                                        :where [[:ivan :name n]]})))

      (t/is (= tx-id (:crux.tx/tx-id (api/entity-tx (api/db *api* tx-time tx-time) :ivan))))

      (t/is (= {:crux.db/id :ivan
                :name "Ivan 1st"} (api/entity (api/db *api* tx-time tx-time) :ivan)))))
Have you thought about adding an async interface to submit-tx ? Maybe returning a promise or something ? I'm just playing in my code but I came across crux.bus which it seems like it could be used to build some kind of an async client - is that for external use ?

jarohen11:02:11

crux.bus is currently for internal use I'm afraid - we haven't ruled out exposing it but I wouldn't rely on it just yet there is a Kafka-based async ingest API, which you can start up using crux.api/new-ingest-client - we're considering extending this to other tx-log implementations too in the future (as part of https://github.com/juxt/crux/issues/383)

refset12:02:04

Wrapping together the existing functions into a submit-then-await-tx-promise API sounds reasonable, and we would be happy to include such a function in the docs or in a utility lib in crux-labs. At the moment we are focussed on lower-level changes so we won't be adding such APIs officially until we have greater stability and a clear window to assess what's most useful & ergonomic. This is would be a good candidate for discussion as part of the upcoming beta though: https://www.opencrux.com/support

d0c0nnor12:02:18

Sounds good. I guess the motivation for me is that if I'm using a full-async webserver like aleph, I can wrap the cas transaction in a deferred and chain that as part of a request handler, which is slightly more efficient than blocking the thread. Thanks for your help and thanks for open-sourcing crux!

👍 4
refset12:02:23

Ahhh, okay, interesting to know the context. We will take note 🙂 (and you're very welcome!)