sql

Miro Bezjak 2024-07-04T14:52:36.702669Z

Just got bitten by a caveat in next.jdbc's transaction behavior. https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.939/doc/getting-started/transactions?q=nested#nesting-transactions as > Note: active-tx? only knows about next.jdbc transactions -- it cannot track any transactions that you create yourself using the underlying JDBC Connection. In addition, this is a per-thread "global" setting and not related to just a single connection, so you can't use this setting if you are working with multiple databases in the same dynamic thread context (`binding`). The exact pattern that bit me was

(jdbc/with-transaction [tx data-source-from-db-1]
  (jdbc/with-transaction [tx2 data-source-from-db-2]
    ,,,))
However, I'd be surprised even with
(jdbc/with-transaction [tx data-source]
  (jdbc/with-transaction [tx2 data-source]
    ,,,))
given that those are transactions on two different SQL connections. Is there an agreed upon way out of next.jdbc.transaction/*active-tx* ? Is there a recommendation in place? E.g. my own (extend-protocol p/Transactable java.sql.Connection) or perhaps using next.jdbc.transaction/transact* even though it's private or own tx implementation?

Mateusz Sล‚upny 2025-09-15T11:21:07.280879Z

Thank you ๐Ÿ™ We have a workaround for this in production code where I'd rather avoid pointing to a snapshot version of a dependency. Besides, it's a minor issue in any case, so we'll most likely wait with upgrading until the next stable release.

seancorfield 2025-09-15T14:18:36.759689Z

Not sure when I'll cut a new release since that's the only change since the last release. Might be a month.

๐Ÿ‘ 1
Mateusz Sล‚upny 2025-09-11T10:29:42.224649Z

https://github.com/seancorfield/next-jdbc/issues/308

seancorfield 2025-09-11T12:54:19.863009Z

Thank you!

seancorfield 2025-09-11T17:16:39.132899Z

Okay, there is a new snapshot that should address this (or you can use git deps).

seancorfield 2024-07-04T16:13:25.242419Z

By default, next.jdbc allows nested TX so both of the above will work, I believe? What exactly did you get bitten by?

seancorfield 2024-07-04T16:14:42.518779Z

*nested-tx* is set to :allow by default. Or are you relying on active-tx? which is saying, yes, you have a nested TX here when you don't really?

seancorfield 2024-07-04T16:15:43.245729Z

(inside you ,,, code you would have active-tx? returning true anyway so I'm not sure what you're saying here)

slipset 2024-07-04T16:16:53.470509Z

IIRC, the problem here is that if the body of inner transaction throws, the outer transaction get rolled back, even though theyโ€™re running on two different datasources.

slipset 2024-07-04T16:17:31.615529Z

(for clarity, I work with @miro.bezjak ๐Ÿ™‚

slipset 2024-07-04T16:19:07.693839Z

But you might want to wait for @miro.bezjak to explain this properly

slipset 2024-07-04T16:21:07.042819Z

But here is what he wrote on our internal slack:

slipset 2024-07-04T16:21:08.692919Z

Briefly, this snippet doesnโ€™t work as I would expect

(jdbc/with-transaction [tx-in-a-different-db (:ardoq-ds (lib/ctx :pp))]
   (jdbc/execute! tx-in-a-different-db ["LOCK TABLE job;"])
   (jdbc/with-transaction [completely-unrelated-tx-to-above (:org-ds (lib/ctx :pp))]
     (jdbc/execute! completely-unrelated-tx-to-above ["delete from component__history"])
     (throw (Exception. "An exception that should rollback the delete!"))))
๐Ÿ‘† those are 2 completely unrelated / isolated / non-nested transactions to me. However, due to the https://github.com/seancorfield/next-jdbc/blob/1bd4bdedce465761bd2575aa7bf10f6cc71f5067/src/next/jdbc/transaction.clj#L119-L128, they are considered nested. It seems that nested for next.jdbc is whenever with-transaction is used in other with-transaction block. Regardless of arguments to with-transaction. The end result of the snippet above is that delete from component__history is not rolled back! (edited)

seancorfield 2024-07-04T16:28:31.444199Z

Since *nested-tx* should be :allow, the locking branch should be taken here. Are you binding *nested-tx* to something else?

slipset 2024-07-04T16:30:01.093559Z

Look what I found:

;; Reuse the outer transaction (i.e. inner tx are ignored and will succeed / fail in line with the
;; whole, outermost transaction) instead of allowing a nested tx to complete successfully even if
;; the outer one fails. Thus, it is always safe to wrap code in jdbc/with-transaction and being
;; able to roll everything back if there is any error.
(alter-var-root #'jdbc.tx/*nested-tx* (constantly :ignore))

slipset 2024-07-04T16:31:08.186509Z

(also, Iโ€™m just the messenger here, in particular a messenger whoโ€™s really scared and ignorant about the finer details of transactions)

igrishaev 2024-07-04T16:42:25.072889Z

Just a note: active-tx? is just a wrapper on top of a dynamic var *active-tx* which is set inside the with-transaction macro. Thus, it doesn't correlate 100% with what happens under the wire. You may open a transaction, then pass the tx value into another thread, and the binding context will be lost. The most reliable way to check if there is a transaction is to ask the connection. Say, in Postgres, there is a trick like that:

SELECT now() = statement_timestamp();
outside a transaction, it will be true, but inside, it's false

seancorfield 2024-07-04T16:52:58.315219Z

The real issue is that transactions don't really "exist" -- they are inherently just state inside the Connection object. next.jdbc currently does its best to kind of pretend that you can execute code "inside" a TX and by default it assumes you know what you are doing. If you get it wrong, next.jdbc can't help you -- but it does provide one "knob" you can tweak, if necessary, to get you out of some holes.

Mateusz Sล‚upny 2025-09-10T08:48:07.556439Z

Sorry for resurrecting an old thread, but I think a bug might have been introduced in the https://github.com/seancorfield/next-jdbc/commit/b0a640a10150713313af4f9dd9782b4b6f0dbf78 that fixed transactions on distinct connections, and I figured it might be worth discussing it in the context of the original change. The problem is that *active-tx* always contains a connection unwrapped by raw-connection , but the new version of active-tx? just looks for con in *active-tx* without unwrapping it. One issue is that it's inconsistent with (contains? *active-tx* raw) checks in the implementation of Transactable , and from the library user's perspective it forces usage like for example (jdbc/active-tx? (#'jdbc-tx/raw-connection conn#)) because if the connection is wrapped, one cannot use the same arg for active-tx? and with-transaction. I think active-tx? should unwrap the connection, but I'm not 100% sure how the implementation should look like, especially around possibly moving or making raw-connection a public function. @seancorfield If you agree with the analysis, please advise if I should follow up with an issue or PR on GitHub, or if you'd rather look into it yourself ๐Ÿ™

seancorfield 2025-09-10T13:26:40.876169Z

@mslupny You could be right. Can you create an issue on GitHub please and I'll take a look?

Miro Bezjak 2024-07-05T14:10:13.556859Z

Apologies for not providing enough context. In my haste to meet a friend for beer, I forgot to make sure that the question makes sense. ๐Ÿ˜• Let me try again. As Erik said above, we change the default of *nested-tx*.

(alter-var-root #'jdbc.tx/*nested-tx* (constantly :ignore))
This is a saner default for us. We know what we're doing. ๐Ÿ™‚ I also read next.jdbc's transaction documentation. However, that was some time ago. Before https://github.com/seancorfield/next-jdbc/commit/06f9bae note was added. For that reason, my assumption was that with-transaction is going to work the same way it does elsewhere. I haven't done groovy and spring in a while, but I do remember it working the way I'd expect them to work. Assumption aside, let's see what I mean by "bitten". I have expected the following snippet to open two separate transactions.
(jdbc/with-transaction [tx1 data-source-1]
  (jdbc/execute! tx1 ,,,)
  (jdbc/with-transaction [tx2 data-source-2]
    (jdbc/execute! tx2 ,,,)
    (throw (Exception. "An exception happened, so rollback tx2"))))
(in the real world, those two with-transaction calls are separated by many function calls) Now that I've seen *active-tx* I know that it's not. It's equivalent to.
(jdbc/with-transaction [tx1 data-source-1]
  (jdbc/execute! tx1 ,,,)
  (jdbc/execute! data-source-2 ,,,)
  (throw (Exception. "An exception happened, so it'll rollback tx1")))
The reason it was surprising to me was that, in the snippet above, those are two different data sources; two different SQL connections; should be two different transactions, that happen to be executed on a single thread. Yes, the two with-transaction are nested, but I'm passing different arguments. It's as if, I had two nested with-open calls with different arguments, and they don't work as one would expect. ๐Ÿ™‚ In other words, we know that
(jdbc/with-transaction [tx data-source]
  (jdbc/execute! tx ,,,))
is not equivalent to
(jdbc/with-transaction [tx data-source]
  (jdbc/execute! data-source ,,,))
The argument makes all the difference! I also wouldn't expect
(jdbc/with-transaction [tx1 data-source-1]
  (jdbc/execute! tx1 ,,,)
  (jdbc/with-transaction [tx2 data-source-2]
    (jdbc/execute! tx2 ,,,)))
to be equivalent to
(jdbc/with-transaction [tx1 data-source-1]
  (jdbc/execute! tx1 ,,,)
  (jdbc/execute! data-source-2 ,,,))
The argument makes all the difference. ๐Ÿ™‚ That's what I mean by bitten. Anyway, we'll probably provide our own implementation to (extend-protocol p/Transactable java.sql.Connection) in the way that's less surprising to us. I have to see how. If .getAutoCommit is false, that might be a good indicator. Or *active-tx* being connection->boolean hash-map might do the trick. Would it be possible to make next.jdbc.transaction/transact* public as both approaches rely on it? Do you have a better recommendation for ๐Ÿ‘† ?

seancorfield 2024-07-05T17:08:54.866739Z

Auto-commit isn't a useful indicator, unfortunately (see the closed tickets that led to some of this behavior -- pretty sure auto-commit was mentioned in one or more of them). *nested-tx* was exposed mainly as a migration aid for folks coming from c.j.j ๐Ÿ˜ž Feel free to create an issue on GH that references this Slack thread. Making the TX active/nesting checks specific to Connection objects sounds like a promising approach but it will need some analysis.

๐Ÿ‘ 1
Miro Bezjak 2024-07-08T08:49:45.320569Z

Issue created: https://github.com/seancorfield/next-jdbc/issues/282 I'll see tomorrow if I can come up with the solution that will work for us. That could be the basis for the PR.

seancorfield 2024-07-08T16:22:29.613359Z

Thank you!