[2.x] “identity” key-fn for queries?

Hi all.

Amazingly, improbably, I’m back again with another question about keywords and strings in records.

Question: in clojure, how can we prevent xtdb.api/q from keywordizing the keys of the results?

Use case: we want to let users define/access custom attributes using “plain English” names.

I sussed out the (undocumented) :key-fn option:

(xt/q my-node "SELECT * FROM people" {:key-fn #xt/key-fn :camel-case-string})

But based on my reading of the (relevant?) code, there doesn’t seem to be an “identity” key-fn, and it’s not clear to me how I might go about creating one.

Any insight would be appreciated.

Thanks!

And while I’m at it:

It looks like the transaction ops (e.g. :put-docs) only support keywords as table names, even though they’re converted to strings internally.

Would it be possible/feasible/wise to relax (e.g. (some-fn keyword? string?)) type checks for table names in a future release of the Clojure client?

(With the caveat that I could absolutely be missing something, or be unaware of potential side-effects of such a decision)

Hi @tenpin :slightly_smiling_face: the code you linked to won’t actually be relevant for the Clojure API at all (those serialisers are only used on the Java/Kotlin API and for HTTP currently IIUC).

What happens when you use the :snake-case-string string option? I believe that should round-trip things verbatim, e.g. you can see that case-sensitivity and whitespace is preserved here. (i.e. snake_case is really more like sql_case, not strictly snake)

@refset thanks for the suggestion! I don’t think I provided enough info, so here’s me spilling even more digital ink.

the code you linked to won’t actually be relevant for the Clojure API at all (those serialisers are only used on the Java/Kotlin API and for HTTP currently IIUC).

Is there another place where #xt/key-fn are defined? The error I get when I try to pass my own function is Illegal argument: 'xtdb/invalid-key-fn' which (I believe!) comes from serde.clj, which references the aforementioned Kotlin code.

What happens when you use the :snake-case-string string option?

A comedy of errors, but our intended use-case does seem a bit pathological :grimacing:

(xt/submit-tx my-node [[:put-docs :people {:xt/id 5 "E-mail Address" "test@example.com"}]])
=> #xt/tx-key{:tx-id 0, :system-time #xt.time/instant"2024-07-01T21:46:57.887896Z"}
(xt/q my-node "SELECT * FROM people" {:key-fn #xt/key-fn :snake-case-string})
=> [{"_id" 5, "e_mail _address" "test@example.com”}]

The use-case is allowing for user-defined attributes. While we might not let them provide the name directly, it’d still be nice to have the ability to opt-out of the automatic casting between keywords and (native?) string keys. (See also: my reply to myself asking about string table names)

Thanks!

Hey @tenpin :wave:

I’m afraid at the moment we don’t have support for arbitrary attribute names - we normalise here so that there’s some degree of interop between SQL and Clojure keyword maps. Our issue here is that the keys are in fact stored in this (lossy) normalised form, so that they’re accessible to users who query them in a different case without needing the read-side needing to check all of the case permutations - :snake-case-string (as an output transformation) is already pretty much identity.

We’ll have a think on how we might support this use case, but for now, one option could be for you to store a small mapping of attribute keyword to rendered label?

Cheers,

James

Potentially, yes - our hang-up here is that eventually we’d like to support the distinction between Arrow ‘structs’ (keys known statically, like a Java class) and ‘maps’ (dynamic keys, like java.util.Map). Clojure doesn’t make the distinction (well, unless you count defrecord, but I don’t think I’d want to mandate that every document be a Clojure record :slight_smile: ) so for now we’re keeping our options open rather than committing ourselves to a looser API that would then be more difficult to revert.

Another option here, if more traditional SQL semantics would be closer to what you’re looking to do, would be to try interacting with XT using a Postgres driver - next.jdbc + HoneySQL, for example?