sql

hoynk 2025-06-04T13:40:39.069709Z

Is it possible to set a default :builder-fn in next.jdbc so I don't have to specify it with every execute call?

p-himik 2025-06-04T13:50:04.729489Z

No, but you can replace direct calls to next.jdbc with a wrapper in your projects that provides all the necessary functionality in a central place.

seancorfield 2025-06-04T14:01:35.740939Z

The closest you can get is to put :builder-fn in your db-spec hash map and/or use with-options https://cljdoc.org/d/com.github.seancorfield/next.jdbc/1.3.1002/api/next.jdbc#with-options

seancorfield 2025-06-04T14:02:29.885999Z

This is part of the deliberate discouragement of using a non-default builder ๐Ÿ™‚

hoynk 2025-06-04T15:17:02.507769Z

Why is it considered bad?

seancorfield 2025-06-04T15:59:05.982199Z

I'll turn that around: why do you want to use a non-default builder?

p-himik 2025-06-04T16:53:15.544099Z

To specify as-unqualified-modified-maps absolutely everywhere. :) I also have a custom builder that preprocesses each row in a specific way before it becomes a part of the result. So basically like the default builder but with (row! [_ row] (next.jdbc.result-set/row! builder (transform row))).

seancorfield 2025-06-04T17:19:52.721029Z

Maybe use plan more and execute! less? ๐Ÿ˜„

p-himik 2025-06-04T17:28:47.133079Z

For the latter? Sure, that would make sense. But it's some old code that got translated from the old jdbc with minimal changes, so not too keen on changing it now.

hoynk 2025-06-04T17:38:47.979799Z

I want the keys to use hรญfens, that's all. To prevent people forgetting to use underscores when using maps that came from a select.

igrishaev 2025-06-05T06:55:50.374209Z

why do you want to use a non-default builder?my two cents: personally I find it quite inconvenient then the column name is profile_id and I get it as :user/profile-id . One should always keep in mind this mapping. Thus, I always specify as-unqualified-lower-maps : what you see in PGAdmin is what you get in Clojure. Also, some databases like Postgres perform an extra query to get table names for namespaces.

p-himik 2025-06-11T12:41:07.281949Z

@seancorfield Following up from the issue raised by @jrychter. > The closest you can get is to put :builder-fn in your db-spec hash map Can you give an example? I couldn't find any in the docs, couldn't make it work myself.

seancorfield 2025-06-11T14:30:48.590989Z

My bad... that's something that works with clojure.java.jdbc -- but you need with-options for in next.jdbc. I misremembered how db-spec maps were handled. In c.j.j, everything took a hash map, and the hash map contained :datasource or :connection but could contain arbitrary options that were merged with the explicit options in each call. In next.jdbc, you're generally dealing with DataSource or Connection objects, so the only way to get "default" options in calls is via the with-options machinery (and for transactions, you then need to use with-transaction+options.

p-himik 2025-06-11T14:32:52.421619Z

Right, makes sense. with-transaction+options also doesn't work because only a very limited subset of options is supported. In fact, :builder-fn in there leads to an exception.

seancorfield 2025-06-11T14:34:15.957489Z

Really? That doesn't sound right...

p-himik 2025-06-11T14:35:34.303899Z

From the docstring:

The options map supports:
  * `:isolation` -- `:none`, `:read-committed`, `:read-uncommitted`,
      `:repeatable-read`, `:serializable`,
  * `:read-only` -- `true` / `false` (`true` will make the `Connection` readonly),
  * `:rollback-only` -- `true` / `false` (`true` will make the transaction
      rollback, even if it would otherwise succeed).
The actual result:
(let [db-src {:jdbcUrl "jdbc:[...]"
              :options {:builder-fn (fn [rs opts]
                                      (
                                        rs
                                        (assoc opts :qualifier-fn unquote-ident
                                                    :label-fn unquote-ident)))}}]
  (jdbc/with-transaction+options [c db-src]
    (jdbc/execute-one! c
                       (sql/format {:select [[1 :Library-Ref]]}
                                   {:dialect :spaced-out}))))

Execution error (PSQLException) at org.postgresql.core.v3.QueryExecutorImpl/receiveErrorResponse (QueryExecutorImpl.java:2733).
FATAL: invalid command-line argument for server process: {:builder-fn
  Hint: Try "postgres --help" for more information.
Specifying :builder-fn at the top-level of the db-src map has no effect as only the :options key is read by with-transaction+options.

seancorfield 2025-06-11T14:36:16.565989Z

That's not how you use it.

seancorfield 2025-06-11T14:36:24.186739Z

(jdbc/with-transaction+options [ds' (jdbc/with-options ds-opts jdbc/snake-kebab-opts)]
        (is (= (merge (default-options) jdbc/snake-kebab-opts)
               (:options ds')))
        (is (= "red" ((col-kw :fruit/looks-like)
                      (jdbc/execute-one!
                       ds'
                       [(str "select appearance as looks_like from fruit where " (index) " = ?") 1])))))

seancorfield 2025-06-11T14:36:53.451209Z

You have to use with-options and then you can use with-transaction+options to unwrap, create a TX on a Connection, and then re-wrap it.

seancorfield 2025-06-11T14:37:32.245899Z

You can't put options in the db-spec map except for a few specific things that affect the initial creation of a Connection.

p-himik 2025-06-11T14:38:30.272759Z

Ohhh, of course. Why did it not make sense to make with-transaction always respect options explicitly created by with-options?

seancorfield 2025-06-11T14:38:44.298399Z

Because it traffics in plain Connection objects.

seancorfield 2025-06-11T14:39:24.250119Z

i.e., it guarantees that you can call JDBC methods directly on the bound symbol.

p-himik 2025-06-11T14:40:47.368879Z

I see, thanks.

seancorfield 2025-06-11T14:46:28.387629Z

The error you get, BTW, is because :options ends up getting passed as part of a Properties object to the JDBC driver -- and the value of :options is stringified.

๐Ÿ‘ 1