sql

Ernesto Garcia 2023-08-09T11:45:28.093379Z

Hi. Just came back to using some JDBC, with next.jdbc. Is there a way to start a transaction that inherits options from the transactable? Or at least set the options succinctly inside with-transactions? Currently I'm doing:

(jdbc/with-transaction [tx* rdb/data-source]
    (let [tx (jdbc/with-options tx* jdbc/snake-kebab-opts)
          result1 (jdbc/execute-one! tx
                    (sql/format ...

Ernesto Garcia 2023-08-11T17:06:54.222459Z

Yep, working nicely

seancorfield 2023-08-11T17:17:38.138869Z

Thanks for testing it!

Ernesto Garcia 2023-08-11T17:18:20.227319Z

Np, thank you for your work!

seancorfield 2023-08-09T15:53:44.706569Z

The documentation explains why that has to be the case: with-transaction (and a few other functions in next.jdbc) produce raw Java objects for performance reasons.

seancorfield 2023-08-09T15:56:17.373479Z

tx* is a java.sql.Connection. with-options wraps it in a record, along with the options, and which implements Executable so that execute-one! knows how to unwrap the Connection and options again for use.

Ernesto Garcia 2023-08-09T15:59:31.644909Z

In my case, tx* is a data-source with-options already. It'd be nice that with-transaction allows for inheriting those options.

Ernesto Garcia 2023-08-09T15:59:50.607229Z

I could also do my own with-transaction wrapper.

seancorfield 2023-08-09T16:13:14.420939Z

No, tx* is a Connection -- that's what with-transaction does. tx is a DefaultOptions record (wrapper). Your rdb/data-source is also a DefaultOptions record, based on what you said.

seancorfield 2023-08-09T16:16:11.850529Z

with-connection follows a similar pattern, yielding a raw Connection. Since this seems to crop up fairly regularly (at least, since I grudgingly added with-options ๐Ÿ™‚ ), I might add with-transaction+options and with-connection+options that would re-wrap the Connection in DefaultOptions -- I can't change the existing functions/macros because code in the wild likely depends on the raw Connection type for interop-related calls.

Ernesto Garcia 2023-08-09T16:20:44.915229Z

It could be possible to accept more options in with-transaction?

Ernesto Garcia 2023-08-09T16:20:56.304119Z

It currently accepts transaction options

Ernesto Garcia 2023-08-09T16:22:33.426369Z

Anyway, just a possibility. Thanks for your help @seancorfield!

seancorfield 2023-08-09T16:40:00.865399Z

I want with-transaction to "look like" (and lint like) with-open -- having a third argument would look weird. I also don't like functions that "return" different types based on arity.

Ernesto Garcia 2023-08-09T16:41:57.721969Z

with-transaction already accepts 3 items in the vector

seancorfield 2023-08-09T16:42:21.487319Z

True, and I regret that ๐Ÿ™‚

Ernesto Garcia 2023-08-09T16:42:29.249649Z

But yeah, another macro would be good as well

seancorfield 2023-08-09T16:42:58.281629Z

Which is why I don't want to make it worse. I'm creating an issue to add +options versions of with-transaction and with-connection.

Ernesto Garcia 2023-08-09T16:43:13.293669Z

That'd be great, thank you!

Ernesto Garcia 2023-08-09T16:43:57.744759Z

Btw, how could you pass transaction parameters without adding a 3 item?

seancorfield 2023-08-09T16:46:04.774339Z

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

seancorfield 2023-08-09T16:49:57.252189Z

with-transaction could perhaps have been made to look more like with-open by binding a second symbol with the tx-opts:

(jdbc/with-transaction [tx db-spec tx-opts {:isolation :serializable}]
  ;; can use both tx and tx-opts here
  ;; with-transaction treats second optional binding as way to get the 3rd arg for the transact function
  ...)

seancorfield 2023-08-09T16:50:41.807309Z

I didn't think of that at the time ๐Ÿ™‚

Ernesto Garcia 2023-08-09T16:53:05.542809Z

(jdbc/with-transaction ^:{:isolation :serializable}[tx ds]
  ...)
๐Ÿซฃ

Ernesto Garcia 2023-08-09T16:53:52.342959Z

> I didn't think of that at the time Wouldn't it be feasible still? It'd be possible to detect 4 args.

seancorfield 2023-08-09T17:07:07.324479Z

Yeah, but I'd still have to support the 3-arg version so it's hardly worth it now ๐Ÿ˜ž As for metadata, since it's a macro, that wouldn't work for folks who wanted to pass tx-opts at runtime rather than just static...

๐Ÿ‘ˆ 1
seancorfield 2023-08-09T17:07:14.734359Z

(API design is hard ๐Ÿ™‚ )

๐Ÿ‘ 1
Ernesto Garcia 2023-08-09T17:19:39.804409Z

> but I'd still have to support the 3-arg version so it's hardly worth it now There could be a clear separation in macro code. No effect on runtime performance.

seancorfield 2023-08-09T17:27:44.601099Z

In this case, I'm not worried about performance. Just that I'd have to continue to support the 3-arg version "forever" so adding a 4-arg version doesn't gain anything. The 3-arg version already requires custom linting (and there's a clj-kondo hook for it already).

Ernesto Garcia 2023-08-09T17:32:28.075319Z

I see, there is more to it than just the macro

seancorfield 2023-08-10T02:48:41.832509Z

@titogarcia I just pushed a 1.3.999-SNAPSHOT version that includes with-transaction+options for you to try out (or you can use a git dep to test it). There are no doc updates yet, beyond the docstring changes I've made.

๐Ÿ‘ 1
1
๐Ÿ“Œ 1
kenny 2023-08-09T15:06:12.679109Z

I would like to issue a single DB query for a table that has a one-to-many relationship. I have yet to find a satisfactory solution. Curious if others have any ideas. ๐Ÿงต

Ernesto Garcia 2023-08-16T11:21:59.046369Z

This may help:

(defn json-agg-subquery
  "Use in a SELECT clause to obtain a given subquery as a JSON array."
  ; Wraps in a subquery for preserving SELECT aliases as JSON keys.
  ; See 
  [alias subquery]
  [{:select [[[:jsonb_agg :subquery/*] alias]]
    :from [[subquery :subquery]]}])

Ernesto Garcia 2023-08-16T11:24:36.637429Z

Then decode JSON for every resulting row:

(defn decode-json [^PGobject pgobj]
  (when pgobj
    (-> pgobj .getValue json/read-value)))

kenny 2023-08-16T17:01:51.471519Z

Well this seems awfully relevant to this discussion ๐Ÿ˜ƒ https://www.tamizhvendan.in/blog/announcing-honeyeql-1.0/

lukasz 2023-08-16T17:03:32.913509Z

Interesting, need to check it out (although I'm not looking forward to 3rd migration of the DB layer in my project ๐Ÿ˜ข ), I wonder how it avoids the N+1 problem

kenny 2023-08-16T17:03:50.271899Z

๐Ÿ˜…

kenny 2023-08-16T17:04:23.515129Z

Iโ€™m very curious about this too. I tend to get very hesitant about these magic layers, but Iโ€™m a bit optimistic here for some reason ๐Ÿ˜ƒ

kenny 2023-08-09T15:06:17.222699Z

Say I have two tables.

TABLE 1 (product):
product.id
product.title
product.description

TABLE 2 (photo)
photo.id
photo.product_id
photo.filename
photo.creation_date
I would like my output data to look like this.
{:product/id     ""
 :product/title  ""
 :product/photos [{:photo/id            ""
                   :photo/filename      ""
                   :photo/creation_date #inst""}]}
A query like this almost works.
SELECT product.*, JSON_AGG(TO_JSONB(photo.*)) AS "product/photos"
FROM product
INNER JOIN photo ON photo.product_id = product.id;
I get the product map returned correctly. I even get my photos returned in the array with all the columns present! This issue is the cast to JSON loses the creation_date timestamp type! So I get an array returned with the :photo/creation_date as a simple string. Is there a way to write this query to maintain the types? How do folks typically query for a data in a one-to-many relationship?

lukasz 2023-08-09T15:11:59.188909Z

I haven't found a workable solution because JSON_... doesn't know anything about dates/timestamps/etc. So all my query functions have a standard post-processing to convert fields in JSONB fields to proper types

kenny 2023-08-09T15:26:59.571089Z

๐Ÿ˜ข

Max 2023-08-09T15:42:41.242499Z

Why not do a query whose result has a row per photo, and then postprocess into your desired structure? group-py gets you most of the way there. Thereโ€™s been some discussion about a standard helper for this sort of thing, hereโ€™s the relevant issue: https://github.com/seancorfield/next-jdbc/issues/201

kenny 2023-08-09T15:46:45.870579Z

Itโ€™s a fair question. Cardinality explosion sucks, I suppose. Wonder if this is really a moot point due to efficient data representation. Otherwise itโ€™s possible. Iโ€™d prefer to do the aggregation at a lower level โ€” depending on keyword ns as data feels icky.

Max 2023-08-09T15:51:57.427079Z

The cardinality of your data doesnโ€™t change, just that the columns of the โ€œoneโ€ side of the one-to-many are repeated a bunch. The data still scales predominantly with the โ€œmanyโ€ side. The keyword namespaces are part of the contract provided by next.jdbc, I think itโ€™s perfectly reasonable to depend on them. Ultimately the problem is that SQL is row-oriented, even higher-level ORMs in other languages are still doing this aggregation via post-processing (or at worst, N+1 queries ๐Ÿ˜ฌ)

kenny 2023-08-09T15:55:43.015379Z

The repeated rows are the cardinality explosion to which I refer โ€” N โ€œoneโ€ * M โ€œmanyโ€. Dunno how much of a problem this is in practice. While true, it still feels icky. Iโ€™m not sure keywords were intended to be a data format. {:table "" :column-name ""} would be much nicer. More ๐Ÿ˜ข

lukasz 2023-08-09T16:02:52.247129Z

I don't use namespaced keywords either :-) eventually everything ends up being JSON or Avro so that was already a pain to deal with

kenny 2023-08-09T16:04:26.277739Z

Haha yeah. Iโ€™ve even fully embraced _ delimited keys since - is a pita in nearly every other lang.

lukasz 2023-08-09T16:04:49.566029Z

to clarify: I don't use namespaced kws in SQL/JDBC stuff

Max 2023-08-09T16:06:03.924059Z

Un-namespacing the keywords after the transformation is very easy to do, and if you donโ€™t want namespaced query results by default I think you can even set that on a query-by-query basis

lukasz 2023-08-09T16:07:24.380469Z

๐Ÿ’ฏ I have a top-level ns that wraps JDBC & HoneySQL and exposes fns like :

(defn execute! [conn query]
  (jdbc/execute! conn query
                 jdbc/unqualified-snake-kebab-opts))
and so on

Raghav 2023-08-09T16:52:21.793619Z

I don't think you can achieve this purely in SQL with aggregate result. You will have to do some sort of post processing. You can do it via Malli, if you use that. There's mt/json-transformer

๐Ÿ‘ 1