honeysql

Discussion of https://github.com/seancorfield/honeysql :slightly_smiling_face:
snorremd 2021-05-12T09:19:14.279900Z

Hello. What's the story around using your own Records/types with honeysql V2 . In version 1 there seems to be a honeysql.format/ToSQL protocol to tell honeysql how to deal with Record parameters. In v2 this seems to be gone? When I pass in a record type as a parameter to honeysql v2's format function it incorrectly parses the record fields as sql clauses:

:message "Unknown SQL clauses: :beginning, :end"
Any thoughts on how to solve this in v2 is much appreciated. The use case would be to pass the record as a parameter value that can then be handled by next.jdbc's settable parameter protocol which would dispatch on the Record type. Edit: Since breaking changes can be introduced with V2, perhaps this would be a good time to just not treat Records as SQL clauses maps? Edit 2: The expression in question where interval in the select statement is the record in question.
{:with [[:workday (workday-upsert m)]]
 :insert-into [[:work-intervals [:workday-id :interval]]
               {:select [:workday.id interval]
                :from :workday}]
 :returning :*}

snorremd 2021-05-12T14:00:08.281700Z

So I think the problem is related to how the format-selectable-dsl function checks if something is a map, which records are. https://github.com/seancorfield/honeysql/blob/v2/src/honey/sql.cljc#L257 It seems the code should check that something is both a map and not a record, but I'm not sure if this is then something that would have to be done a lot of places. Edit: I've not had the chance to test this in practice, but could test this after work today if you'd be interested in a pull-request for this.

seancorfield 2021-05-12T16:17:10.282700Z

@snorremd I think you can wrap such things with [:lift ..]

seancorfield 2021-05-12T16:18:10.283600Z

That’s the “escape” mechanism for passing sequential and map-like types through as parameter values, if I’m understanding what you’re trying to do…

2021-05-12T16:32:47.285600Z

Hi everybody! I begin to playing with honeysql and I wonder how I can do SELECT (first_name, last_name) as name

seancorfield 2021-05-12T16:34:31.285900Z

user=> (sql/format {:select [[[:composite :first_name :last_name] :name]]})
["SELECT (first_name, last_name) AS name"]

seancorfield 2021-05-12T16:35:04.286500Z

[:composite :first_name :last_name] is a function call/operator syntax

seancorfield 2021-05-12T16:35:19.287100Z

[[:composite :first_name :last_name] :name] is an aliased expression

seancorfield 2021-05-12T16:35:29.287600Z

and :select takes a sequence of expressions

2021-05-12T16:36:48.288100Z

Oh I had missed :composite

2021-05-12T16:36:55.288300Z

Perfect, thanks!

seancorfield 2021-05-12T16:38:30.288800Z

https://github.com/seancorfield/honeysql/tree/v2#composite-types (README) — although the usage shown is with insert rather than select

2021-05-12T16:41:37.289500Z

Last thing, is there an equivalent of "%pattern%" for a where clause?

seancorfield 2021-05-12T16:43:05.289800Z

Not sure what you mean.

seancorfield 2021-05-12T16:43:34.290600Z

[:like :first_name "%foo%"] ?

2021-05-12T16:43:50.290900Z

Yes

seancorfield 2021-05-12T16:44:10.291400Z

BTW, it can be really inefficient to query with a leading % in a regex — it will do a full table scan in all cases.

seancorfield 2021-05-12T16:44:58.292200Z

Trailing % is OK, because SQL can use the stem value (the portion before the %) to narrow down the index range, assuming you have an index on the column 🙂

snorremd 2021-05-12T16:46:08.292400Z

So something like this?

(-> {:insert-into [[:foo [:interval]]
                   {:select [[:lift (->Interval nil nil)]]
                    :from :test}]}
    hsql/format)
Produced an error:
; Execution error (ExceptionInfo) at honey.sql/format-dsl (sql.cljc:891).
; Unknown SQL clauses: :beginning, :end

2021-05-12T16:47:09.292900Z

Oh thanks, very useful information.

2021-05-12T16:50:56.294500Z

So with your snippet, if I want pass a var so I'll do [:like :first_name (str "%" foo "%")]

2021-05-12T16:52:23.295100Z

or is a cleaner way to do it?

seancorfield 2021-05-12T16:55:24.295900Z

If you have just a string and you want it to be a SQL regex and match anywhere in the column value, you have to add the % yourself.

seancorfield 2021-05-12T16:56:09.296900Z

I think :like will be case sensitive in most cases (depends on the DB and how it is setup) so there’s also :ilike for a case-insensitive comparison

2021-05-12T16:56:51.297500Z

Great, thanks. 👍

seancorfield 2021-05-12T16:57:27.297600Z

:select expects a sequence of expressions, but those can be aliased, so a function call without an alias still needs to be written as if it did have one — the docs have examples and talk about this.

seancorfield 2021-05-12T16:57:46.297800Z

{:select [[[:lift (->Interval nil nil)]]] .. should do it.

snorremd 2021-05-12T16:58:13.298Z

Ah, thanks. That worked. 🙂

seancorfield 2021-05-12T16:58:58.298200Z

:select [:a [:b :c] [[:fun 42] :d]] => SELECT a, b AS c, FUN(42) AS d

👍 1
2021-05-12T17:02:53.298500Z

I tried and it gives me this error:

Assert failed: Alias should have two parts[:composite :first_name :last_name]
(= 2 (count x))

2021-05-12T17:09:39.298700Z

It works with the helpers function call:

(h/format {:select [[(composite :first_name :last_name) :name]]})

2021-05-12T17:10:01.298900Z

I prefer the first syntax. Any idea?

seancorfield 2021-05-12T17:53:53.299100Z

You have missed a level of nesting. Funnily enough I was just explaining this to someone else…

seancorfield 2021-05-12T17:54:05.299300Z

:select [:a :b :c] => SELECT a, b, c

seancorfield 2021-05-12T17:54:23.299500Z

:select [:a [:b :c]] => SELECT a, b AS c

seancorfield 2021-05-12T17:54:52.299700Z

:select [:a [[:foo :bar] :c]] => SELECT a, FOO(bar) AS c

seancorfield 2021-05-12T17:55:23.299900Z

And as I showed above:

user=> (sql/format {:select [[[:composite :first_name :last_name] :name]]})
["SELECT (first_name, last_name) AS name"]
Note the nesting level.

seancorfield 2021-05-12T17:56:12.300100Z

If I add spaces it might be easier to see:

user=> (sql/format {:select [    [  [:composite :first_name :last_name] :name  ]    ]})
["SELECT (first_name, last_name) AS name"]

2021-05-12T18:56:24.300400Z

No success, always same error with a simple copy paste:

user=> (sql/format {:select [[[:composite :first_name :last_name] :name]]})
Execution error (AssertionError) at honeysql.format/seq->sql (format.cljc:385).
Assert failed: Alias should have two parts[:composite :first_name :last_name]
(= 2 (count x))

seancorfield 2021-05-12T19:01:21.300600Z

You’re using HoneySQL V2?

2021-05-12T19:03:35.300800Z

No "1.0.461"

2021-05-12T19:04:19.301Z

OK I move to v2, thx

seancorfield 2021-05-12T19:27:54.301200Z

Sorry, yeah, what I suggested is for V2. On V1, you’d have to use the helper I think or sql/call.

👍 1
2021-05-12T20:14:27.301500Z

I supposed sqlite is not well supported. With particulary syntax like:

SELECT first_name || " " || last_name as name FROM users;

seancorfield 2021-05-12T20:26:12.301700Z

[:|| :first_name " " :last_name] should work.

seancorfield 2021-05-12T20:26:33.301900Z

:select [ [ [:|| ...] :name ] ]

seancorfield 2021-05-12T20:27:41.302100Z

user=> (sql/format {:select [ [ [:|| :first_name " " :last_name] :name ] ]})
["SELECT first_name || ? || last_name AS name" " "]
Several DBs have || as a string concatenation operator.

2021-05-12T20:38:42.302600Z

Great, very relevant. Thanks a lot.