Fork me on GitHub
#sql
<
2021-07-21
>
kirill.salykin08:07:41

hi i have a question regarding the next.jdbc i have a setup to use default options

(next.jdbc/with-options
    (hikari/make-datasource (merge default-opts config))
    next.jdbc/unqualified-snake-kebab-opts)
but those defaults seems to be ignored if i query within the tx
(next.jdbc/with-transaction [tx ds] (execute-one! ...))
Please advice what can I do about this the kebab-case also ignored whey I try to use update! with tx

kirill.salykin09:07:29

would it be possible for transaction to copy opts from datasource?

seancorfield15:07:30

@kirill.salykin That is specifically discussed in the documentation. It explains why the options are not carried over and gives examples of code to "do the right thing" manually.

kirill.salykin15:07:22

thanks for a response, will look. of course would be nice to copy opts from the datasource into transaction, but let me read first to understand the reasoning

seancorfield15:07:59

with-transaction produces a native Connection object, as does get-connection, and user code expects to be able use that as a native Java object directly -- so auto-wrapping would break existing code.

seancorfield15:07:53

(and in fact get-connection is specifically declared to return a native Connection object)

kirill.salykin16:07:16

thanks for explanation, Sean

2
indy18:07:08

@U04V70XH6 If DefaultOptions implemented java.sql.Connection existing code won't break right?

seancorfield18:07:26

That isn't practical really (I explored that kind of approach originally).

indy18:07:03

Mind explaining why? Is it because one has to manually enumerate the methods and keep it in sync with JDBC updates?

indy18:07:29

I mean JDBC adding more methods?

indy18:07:46

Don't know much of java, but I guess it could be done programmatically, the enumeration of methods?

kirill.salykin18:07:02

Would it work to set the dynamic var that tx can read?

seancorfield18:07:16

No. No dynamic vars.

💯 2
seancorfield18:07:46

I'm on a call. This is just not happening. I looked at this problem before.

kirill.salykin18:07:39

Clear, and sad a little Would make lufe a bit easier

seancorfield18:07:59

For the well-documented edge case around with-transaction, adding the overhead of a fully-implemented, fully-delegated Connection proxy is not worth it.

seancorfield18:07:08

What I could perhaps do is provide an alternative to with-transaction that did the wrapping -- with the understanding that what it binds isn't a Connection object but a "connectable". That makes it opt in, so folks who care about performance don't get hurt by the behavior.

kirill.salykin18:07:47

Thats interesting, thanks!

kirill.salykin18:07:06

How big performance penalty may be?

indy18:07:13

Only when you're free Sean. I very much want to deal with java.sql.Connection everywhere and not a wrapped thing. Everything is very much easier that way. But that makes with-options not so useful. If I had to use with-options I would use it just once when I initiated the db-pool. But since it doesn't implement java.sql.Connection, I can't imagine a use case for it. In fact this is what I did first when I migrated to next.jdbc and found dealing with default-options object cumbersome and reverted the change and added the options manually in all functions that wrapped the jdbc execute family of functions (query, insert, et al).

indy18:07:17

I guess exposing the raw java.sql.Connection was one of the design motives of next.jdbc

seancorfield18:07:04

Yes, trafficking in native JDBC objects is an important aspect of next.jdbc's design and part of why it's so much faster than clojure.java.jdbc which passes around a hash map everywhere, which means you need to unwrap that every time you need to do something native with JDBC.

seancorfield18:07:26

It's why next.jdbc only "implements" DataSource: it's a small API and you general either get a connection once and reuse it multiple times, or you use a connection-pooled datasource (which is its own JDBC object, effectively).

seancorfield18:07:11

Wrapping a datasource with options isn't much of an overhead and doesn't affect much code that would interact with JDBC. Wrapping a connection would be a major deal -- both to implement and then to maintain (since different JDBC drivers add their own stuff), in addition to any other overhead.

seancorfield18:07:48

It's why next.jdbc didn't even have any sort of "default options" story for a long time (and why I generally don't encourage its use).

seancorfield18:07:52

(`with-options` came in 1.0.475, a year ago)

seancorfield18:07:33

What I'll do with #172 above is probably add with-transaction-options which will conditionally rewrap the connection -- but I'll need to clearly document that you will need to unwrap it yourself if you want to do any Connection method calls on it (you'll be able to pass it to next.jdbc functions directly, but not do save points or anything like that).

indy18:07:48

Yeah I was not missing much by not using using with-options. Had to the jdbc options in 7 places instead of one if I had to use with-options . I can very much live without with-options (pun intended I guess).

seancorfield18:07:13

One possibility would be to have the usage be:

(jdbc/with-transaction-options [[con con-opts] connectable]
  ..)
and then inside the body, you could access both the raw Connection (as con) and the wrapped connection as con-opts.

seancorfield18:07:09

Right, at work we use with-options only in one section of the code and in a handful of tests (where we specifically need to interact with older code that still uses clojure.java.jdbc and builds result sets differently).

indy19:07:41

A lot of us might not really require with-transaction-options, at least I don't given the object doesn't implement java.sql.Connection and javax.sql.Datasource . And I guess you're already cautious that the API surface area of the library remains small.

seancorfield19:07:38

@kirill.salykin would like that API though 🙂

seancorfield19:07:02

Just bear in mind you'll have the same rewrapping issue with with-logging as well and I don't plan to add a combinatorial explosion of these things 🙂

kirill.salykin19:07:56

so, how do you guys deal with it?

kirill.salykin19:07:04

pass over options everywhere?

kirill.salykin19:07:22

if i want my query/insert/update to understand kebab case (unqualified) - what shall i do? copy paste options in every tx?

kirill.salykin19:07:43

@UMPJRJU9E, you mention a lot of you might not reallt require this - how would you provide opts inside a tx? copy it over and over?

seancorfield19:07:47

Per the docs, you can manually rewrap the connection inside the tx call.

seancorfield19:07:10

(the docs literally have an example of what code to use)

kirill.salykin19:07:23

thanks, i’ll have a look

seancorfield19:07:08

Or you can just declare an options map somewhere and just add it to every next.jdbc call -- which is mostly what we do on a per-namespace basis (since we're dealing with a mix of new code trafficking in qualified hash maps and old code trafficking in unqualified lowercase hash maps and some legacy code trafficking in unqualified mixed case hash maps 👀 ) -- but the majority of our new next.jdbc code uses the defaults.

seancorfield19:07:36

And I strongly encourage people to use the defaults and not try to convert to unqualified keys.

seancorfield19:07:09

Consequently, we almost never actually pass any options -- except in legacy code/code that interacts with legacy code.

kirill.salykin19:07:08

well, defaults doesnt work for everybody 🙂 copying it to every ns seems redundant thanks, I’ll look into the rewrapping

seancorfield19:07:14

(and part of why I initially resisted adding with-options and why I didn't want to "fix" the unwrapping inherent in calling get-connection or with-transaction was because I want not using the defaults to be painful, to try to discourage people from not using the default behavior!)

kirill.salykin19:07:36

by rewrapping you meant this:

(with-open [con (jdbc/get-connection ds)]
  (let [con-opts (jdbc/with-options con some-options)]
    (jdbc/execute! con-opts ...) ; committed
    (jdbc/with-transaction [tx con-opts] ; will commit or rollback this group:
      (let [tx-opts (jdbc/with-options tx (:options con-opts)]
        (jdbc/execute! tx-opts ...)
        (jdbc/execute! tx-opts ...)
        (into [] (map :column) (jdbc/plan tx-opts ...))))
    (jdbc/execute! con-opts ...))) ; committed
so calling the with-options for every tx?

indy19:07:47

@kirill.salykin I have wrappers for jdbc.sql functions that pass in the options, 7-8, of them. And these are what are used throughout the code base instead of calling the next.jdbc functions directly.

seancorfield19:07:44

Aye, wrapping the (small) API is another possibility that I've seen quite a few folks do. Another good reason for a small API.

seancorfield19:07:59

(worth noting that we use very, very few transactions in our code -- because they are rarely needed in practice -- but I see lots of people slavishly wrapping every "group" of calls in with-transaction still)

kirill.salykin19:07:10

@UMPJRJU9E would it be possible for you to share the code)

kirill.salykin19:07:20

> because they are rarely needed in practice i would not agree here a lot

kirill.salykin19:07:34

but that other topic

kirill.salykin19:07:14

thanks for your input, time to go to bed for me

kirill.salykin19:07:27

have a great day/night!

seancorfield19:07:30

In our 113K line code base we have just 19 with-transaction calls in 12 files.

indy19:07:19

Wow! Interesting. I've seen Java folks inspired by the onion architecture open transactions and pass that around to all the subsequent functions.

indy19:07:05

I mean when they start doing functional clojure

indy19:07:27

Reminded me of RH mentioning in the HOPL talk that there is only one usage of atom in all of the Datomic codebase

seancorfield20:07:18

Connections are auto-commit by default, so -- outside explicit transactions -- everything is like its own single-operation TX. Then as soon as you intersperse any sort of external interactions around SQL operations, a SQL TX alone won't help you -- it won't roll back any of the external stuff -- and a lot of real-world stuff falls into that category in my experience. So you are much better off in general recognizing that (quote) "your coffee shop does not use two-phase commit" and planning accordingly...