sql

All things SQL and JDBC...
2020-02-12T03:13:36.119100Z

so, started using next.jdbc today (thanks @seancorfield!) and I think it feels a bit awkward wrt the api ergonomics at times and left me wondering if I'm not doing something wrong (which I'd assume it's the case). 100% of my cases I have to use the opts to set the builder-fn (unfortunately I have to drop the kw namespace so I can spec the returns properly) and it starts getting in my way since it's the last parameter and the connectable is the first parameter so I can't use thread macros as I usually do. Am I getting something wrong? Like, is there a way of setting the default options or something like that?

seancorfield 2020-02-12T03:15:34.120Z

@caio Specs work with qualified keywords. That's part of why next.jdbc auto-qualifies column names with their table by default.

2020-02-12T03:16:45.121100Z

yeah, I know they do, but it's trickier for my case unfortunately. basically it's a (temporary) monolith connecting to different databases that may have tables with the same name across them

2020-02-12T03:17:54.122600Z

And I know I'd be able to further specify the keyword returned by prepending something like the db name with the qualify-fn , but it'd be the same issue ( opts always being necessary)

seancorfield 2020-02-12T03:19:33.124300Z

If I have a namespace that I'm migrating to next.jdbc and need non-default behavior, I put

(def ^:private jdbc-options {:builder-fn rs/as-unqualified-maps ,,,})
at the top of the namespace and then it's just adding jdbc-options as the last argument in each call which is a lot less work.

seancorfield 2020-02-12T03:20:06.125200Z

Or you could always create a wrapper namespace for next.jdbc that exposes the same primary API but adds options into all calls.

seancorfield 2020-02-12T03:20:37.126Z

Since next.jdbc works with native JDBC (Java) objects, there's nothing to hang options on.

2020-02-12T03:21:37.127500Z

(def ^:private default-jdbc-opts {:builder-fn jdbc.result-set/as-unqualified-lower-maps}) same πŸ™‚ yeah, I was thinking about going through the wrapper route, just wanted to make sure I wasn't missing anything

seancorfield 2020-02-12T03:21:50.127800Z

I have been looking at some sort of "middleware" or annotated "connectable" that can have default options but it's a much harder problem than it looks πŸ™‚

seancorfield 2020-02-12T03:23:46.129200Z

(because if you have an annotated DataSource you have to ensure that all paths that produce Connection still actually produce a java.sql.Connection object and yet can still carry the options around -- JDBC doesn't make this very easy 😐 )

seancorfield 2020-02-12T03:24:53.130300Z

As for the threading macros, I'm not quite sure what you're asking there... The API of next.jdbc is very similar to clojure.java.jdbc which also has the db-spec as the first argument and the options as the last argument.

2020-02-12T03:27:28.132600Z

oh yeah, that annotated connectable would be pretty cool and does sound hard. re thread macros: yeah, that's true, I guess I just didn't have to use options as the last argument that much and got used to just (->> at the end of any transform for passing it to clojure.java.jdbc API

seancorfield 2020-02-12T03:28:30.133Z

Gotcha...

seancorfield 2020-02-12T03:34:03.133900Z

You can see my middleware experiment here https://github.com/seancorfield/next-jdbc/blob/master/test/next/jdbc/middleware.clj with tests/usages examples here https://github.com/seancorfield/next-jdbc/blob/master/test/next/jdbc/middleware_test.clj -- but it doesn't compose the way I want (which is why it's all in test and undocumented πŸ™‚ )

πŸ‘€ 1
seancorfield 2020-02-12T03:35:08.135Z

I may simplify it to just deal with options and drop the "interceptor" stuff... but that does have its uses...

dharrigan 2020-02-12T14:36:46.135700Z

Are there any examples of using next.jdbc when doing a join and then mapping the results?

orestis 2020-02-12T15:13:05.137200Z

Mapping the results? Would you get something different than a seq of maps?

orestis 2020-02-12T15:15:15.139500Z

Mini rant: JDBC is clunky and full of annoyances. I’m amazed at the rough edges of dealing with the Postgres JDBC driver, and the amount of extra work needed to cast various parameters to the correct type.

orestis 2020-02-12T15:21:11.140500Z

OTOH HugSQL is brilliant. Goes most of the way to solve the sql composability problem.

dharrigan 2020-02-12T15:45:10.141200Z

Yes, you get back a map, but if youre using a join, then you would get back duplicate data, so say given this:

dharrigan 2020-02-12T15:45:12.141400Z

[{:address/id 1
  :address/name "foo"
  :address/surname "bar"}
 {:address/id 2
  :address/name "baz"
  :address/surname "wibble"}
 {:person/name "Cuthbert"
  :person/surname "McDibble"}
 {:person/name "Cuthbert"
  :person/surname "McDibble"}]

dharrigan 2020-02-12T15:46:00.142400Z

which is a inner join between a person and an address (with a person having multiple addresses), I was wondering if there is an example of how to map that so that I can pull out the person, then all the addresses.

orestis 2020-02-12T15:47:49.143300Z

If you’re using Postgres you might use one of the jsonb agg functions.

dharrigan 2020-02-12T15:47:57.143500Z

This isn't json

dharrigan 2020-02-12T15:48:14.144200Z

What's edn that I've just put down as the result of invoking next.jdbc + honeysql πŸ™‚

orestis 2020-02-12T15:48:41.145100Z

No no I mean, the jsonb agg functions happen on the sql side

dharrigan 2020-02-12T15:48:52.145400Z

But it's not json

dharrigan 2020-02-12T15:49:06.145800Z

I'm not storing json, it's just normal relations with scalars

orestis 2020-02-12T15:51:00.146900Z

You get a jsonb column based on the scalars you have. The original data is not jsonb.

orestis 2020-02-12T15:52:58.148200Z

If that doesn’t work (you might need data that isn’t json encodable) you probably need to fall back to https://clojuredocs.org/clojure.core/group-by

isak 2020-02-12T15:53:32.149400Z

In SQL Server, you can just do this:

select *,
    (select * 
     from posts p 
     where p.user_id = a.i for json path) as posts
 from users a
 for json path
Is similar possible in postgres? Seems more logical to me @orestis

orestis 2020-02-12T15:53:34.149500Z

I’m on a mobile so can’t try any code right now...

isak 2020-02-12T15:58:18.150100Z

Checked myself, and apparently you can do this in PG:

select row_to_json(t)
from (
  select text, pronunciation,
    (
      select array_to_json(array_agg(row_to_json(d)))
      from (
        select part_of_speech, body
        from definitions
        where word_id=words.id
        order by position asc
      ) d
    ) as definitions
  from words
  where text = 'autumn'
) t

dharrigan 2020-02-12T16:03:58.150400Z

ooh, just trying out the jsonb_agg - seems to do what I want!

dharrigan 2020-02-12T16:04:01.150600Z

Never knew!

orestis 2020-02-12T16:04:26.151400Z

I pored over the postgresql documentation over the last few weeks, there’s ton of hidden gems like that

orestis 2020-02-12T16:04:42.151800Z

If only we could have EDN πŸ˜„

dharrigan 2020-02-12T16:04:53.152200Z

πŸ™‚

dharrigan 2020-02-12T16:04:54.152400Z

thanks!

2020-02-12T17:56:01.154500Z

@seancorfield I didn't have enough time to go through your experiments, but if you want to solve it just for opts, why not introducing some dynamic var like *default-opts* and an accompanying macro (with-default-opts {}) that'd just bind that var. this way all the internal next.jdbc functions that use options would just need to first (merge *default-opts* user-provided-opts)

2020-02-12T17:58:09.155Z

I can try writing a PR for that if it makes sense

seancorfield 2020-02-12T18:05:38.155800Z

@caio Dynamic vars are evil, and besides, that would introduce a runtime overhead that everyone would pay even if they never used default options.

seancorfield 2020-02-12T18:06:39.157100Z

@dharrigan I'm a bit puzzled about your join above -- that result doesn't look like a join to me since your two pairs of rows have disjoint columns.

2020-02-12T18:06:56.157300Z

true dat πŸ˜•

seancorfield 2020-02-12T18:07:25.157800Z

However default options end up being handled, the overhead they incur should be completely opt-in.

πŸ‘ 1
βœ”οΈ 1
kulminaator 2020-02-12T18:50:22.158800Z

just wondering if there's a good sql shell built on top of clojure and jdbc ...

kulminaator 2020-02-12T18:51:09.159600Z

it sure does sound like a feasible implementation for a tool

dharrigan 2020-02-12T19:57:06.161700Z

@seancorfield I'll come back to your question in a bit. Do you know what the correct way to do group (by) with honeysql, with multiple expressions? If I try (group [:a :b]) it renders as group-by a b but if I try (group [[:a] [:b]]) I get a spec error.

dharrigan 2020-02-12T19:57:49.162300Z

(there's no example in the docs that has a group with more than one expression)

shaun-mahood 2020-02-12T20:01:46.163800Z

@dharrigan I'm not sure how it works with the helper functions, but if you are building your SQL map directly then {:group-by [:a :b]} should work

shaun-mahood 2020-02-12T20:05:14.164800Z

Have you tried (group :a :b)? It looks like it should work (though I haven't tried it with a real query)

dharrigan 2020-02-12T20:05:47.165100Z

that does work!

seancorfield 2020-02-12T20:06:11.165500Z

user=> (-> (select :a) (from :table) (group :a :b) (h/format))
["SELECT a FROM table GROUP BY a, b"]

dharrigan 2020-02-12T20:06:24.166Z

I can't believe I didn't try that one! πŸ™‚

dharrigan 2020-02-12T20:06:31.166300Z

I guess I got blinded by all the vectors πŸ™‚

seancorfield 2020-02-12T20:11:31.166900Z

Updated the readme to show more than one column in group https://github.com/jkk/honeysql/blob/master/README.md#big-complicated-example

πŸ‘ 3
πŸ’― 1
dharrigan 2020-02-12T20:11:51.167200Z

w00t

dharrigan 2020-02-12T20:12:02.167700Z

now, that's what I call service!

βž• 1