Fork me on GitHub
#sql
<
2020-02-12
>
caio03:02:36

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?

seancorfield03:02:34

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

caio03:02:45

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

caio03:02:54

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)

seancorfield03:02:33

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.

seancorfield03:02:06

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

seancorfield03:02:37

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

caio03:02:37

(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

seancorfield03:02:50

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 🙂

seancorfield03:02:46

(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 😐 )

seancorfield03:02:53

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.

caio03:02:28

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

seancorfield03:02:03

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 🙂 )

👀 4
seancorfield03:02:08

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

dharrigan14:02:46

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

orestis15:02:05

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

orestis15:02:15

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.

orestis15:02:11

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

dharrigan15:02:10

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

dharrigan15:02:12

[{: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"}]

dharrigan15:02:00

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.

orestis15:02:49

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

dharrigan15:02:57

This isn't json

dharrigan15:02:14

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

orestis15:02:41

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

dharrigan15:02:52

But it's not json

dharrigan15:02:06

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

orestis15:02:00

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

orestis15:02:58

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

isak15:02:32

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

orestis15:02:34

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

isak15:02:18

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

dharrigan16:02:58

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

orestis16:02:26

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

orestis16:02:42

If only we could have EDN 😄

caio17:02:01

@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)

caio17:02:09

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

seancorfield18:02:38

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

seancorfield18:02:39

@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.

caio18:02:56

true dat 😕

seancorfield18:02:25

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

👍 4
✔️ 4
kulminaator18:02:22

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

kulminaator18:02:09

it sure does sound like a feasible implementation for a tool

dharrigan19:02:06

@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.

dharrigan19:02:49

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

shaun-mahood20:02:46

@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-mahood20:02:14

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

dharrigan20:02:47

that does work!

seancorfield20:02:11

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

dharrigan20:02:24

I can't believe I didn't try that one! 🙂

dharrigan20:02:31

I guess I got blinded by all the vectors 🙂

seancorfield20:02:31

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

👍 12
💯 4
dharrigan20:02:02

now, that's what I call service!

4