honeysql

Discussion of https://github.com/seancorfield/honeysql :slightly_smiling_face:
seancorfield 2021-04-10T07:01:01.124400Z

com.github.seancorfield/honeysql {:mvn/version "2.0.0-beta1"} — HoneySQL V2 moves out of Alpha! At this point I consider the documentation “complete” (it will continue to grow but I believe it now covers all V2 functionality) and the APIs are all stable. Only accretive/fixative changes will be made from this point on. See https://github.com/seancorfield/honeysql/releases/tag/v2.0.0-beta1 for details of this release. I consider this stable for production usage (we’re already using Alpha 3 at work).

🎉 1
dharrigan 2021-04-10T07:01:53.124600Z

w00t!

dharrigan 2021-04-10T07:02:21.125Z

Will you let it bed in for a couple of weeks before promoting to release?

seancorfield 2021-04-10T07:18:09.125200Z

At least a couple of weeks, yes.

seancorfield 2021-04-10T07:18:45.125900Z

I’m hoping for wider usage/testing now it’s “stable” (out of alpha) and therefore more feedback on the docs and some of the specific functionality.

seancorfield 2021-04-10T07:20:12.126500Z

(I’ve already found and pushed a bunch of small doc fixes since Beta 1 hit http://cljdoc.org!)

dharrigan 2021-04-10T07:22:48.127900Z

I'm writing a new backend service, using v2, nothing fancy, nothing pushing the boundaries, but will keep an eye on it 🙂

seancorfield 2021-04-10T07:23:20.128500Z

Of the open issues, I definitely want to get #308 and #310 done for an RC, maybe #315 as well. I still need to figure out what to do about #146 (Spec) and I need to come up with a suitable extension hook for Gungnir #276.

seancorfield 2021-04-10T07:24:22.129100Z

But those are all accretive at this point. The rest will slip to 2.1 or later.

dharrigan 2021-04-10T07:25:08.129500Z

All excellent news. It's a great library

Ben Sless 2021-04-10T08:44:20.134Z

Hi, something I've been wondering about regarding honeysql for a while is if there's any way to render a string in advance and pass it parameters as symbols. From what I've seen there's no way to avoid calling format every time, but I could have missed it. What I had in mind can best be illustrated by this little macro I wrote:

(defn parametrize-form
  [form params]
  (clojure.walk/postwalk
   (fn [e]
     (if (and (symbol? e)
              (contains? params e))
       `(sql/param ~(keyword e))
       e))
   form))

(defn format
  [args query]
  (let [form (parametrize-form query (set args))]
    (sql/format (eval form) (zipmap (map keyword args) args))))

(defmacro defsql
  [name args query]
  `(defn ~name [~@args]
     ~(format args query)))
I wouldn't feel half as bad about it if it didn't use eval thoughts?

seancorfield 2021-04-10T16:10:10.134100Z

@ben.sless yeah, this is something I've thought about often but haven't yet come up with a good way to handle in the context of HoneySQL. It's definitely still on my radar.

seancorfield 2021-04-10T16:31:38.137100Z

I’ll create a GH issue for tracking. It definitely feels like something worth doing. It’s not easy though: there are some constructs that don’t lend themselves to it in all cases — the two that spring to mind immediately are [:in :col some-vals] where the generated string depends on the number of elements in some-vals and even something as simple as [:= :col foo] because the generated string is different for foo being nil, vs foo being non-`nil`: col IS NULL vs col = ?.

seancorfield 2021-04-10T16:44:14.137300Z

https://github.com/seancorfield/honeysql/issues/320

👍 1
Ben Sless 2021-04-10T17:55:59.137700Z

Can you please tag my GH user, @bsless ?

Ben Sless 2021-04-10T17:56:24.138Z

Figured it wasn't going to be this easy 🙂

seancorfield 2021-04-10T17:58:08.138200Z

Tagged!

Ben Sless 2021-04-10T17:58:12.138400Z

thank you 🙂

seancorfield 2021-04-10T17:59:48.139900Z

I have a tentative solution for https://github.com/seancorfield/honeysql/issues/315 but I’d like some input: if you effectively say {:where [:in :id []]} what would you expect to get? Also {:where [:in :id [nil]]}?

seancorfield 2021-04-10T18:01:17.141200Z

Since id IN () is not legal SQL and no IDs are in an empty set, I would lean toward producing WHERE FALSE for that case and WHERE id IS NULL for the second case.

seancorfield 2021-04-10T18:02:00.142Z

And, just as a sanity check, do folks think adding an explicit OR id IS NULL is the correct way to handle this in the first place?

Ben Sless 2021-04-10T18:07:49.142100Z

> produce strings with special placeholders so that only those pieces of the overall string needed to be generated for a final format Wonder if it's possible to return a closure and delay the building Emit something like:

(fn [foo]
  (let [rendered-foo (if (nil? foo) " IS NULL" " = ?")]
    (let [sb (StringBuilder.)]
      (.append sb "SELECT * WHERE col")
      (.append sb rendered-foo))))

seancorfield 2021-04-10T18:22:13.142300Z

Right now, the entire formatting system expects to use str to bash pieces together under the hood. I suspect I’d need to change every piece of code that does that to allow for a sequence of strings-or-thunks and then have just the top-level format actually bash everything together — but that would break the public API functions format-expr and format-dsl, as well as making it much harder for anyone to extend HoneySQL for their own syntax.

seancorfield 2021-04-10T18:23:24.142500Z

One “easy” workaround is to build the sql+params once with dummy values and just reuse that sql string with different sets of values — but that doesn’t account for IN or the NULL stuff.

seancorfield 2021-04-10T18:26:08.142700Z

Another option is to produce a Selmer-style template that could just be formatted with the final set of parameters — but that’s still going to potentially do a bunch of string-bashing each time anyway.

seancorfield 2021-04-10T18:28:17.142900Z

As an example of the complexity of logic needed to “do the right thing”, look at how much conditional logic is in format-in to take into account empty collections and nil values: https://github.com/seancorfield/honeysql/blob/v2/src/honey/sql.cljc#L923-L951

👀 1
Ben Sless 2021-04-10T18:54:21.143300Z

That's pretty complicated

seancorfield 2021-04-10T19:02:36.143500Z

SQL is complicated, man 🙂

😄 1
Ben Sless 2021-04-10T19:10:00.143700Z

I think it was Rich in one of his talks who commented on how silly SQL was, instead of being a protocol/specification first it was a natural language first and doomed us forever to string-manipulation-purgatory

seancorfield 2021-04-10T19:14:19.144Z

Yeah, it’s been eye-opening trying to implement a much large subset of SQL for V2 than V1 supported — and the vendor-specific extensions are often completely arbitrary as far as syntax goes (in my opinion).

seancorfield 2021-04-10T19:15:01.144200Z

Every year I continue to work with relational databases is a year that I get ever closer to wanting something more like Datalog instead 🙂

seancorfield 2021-04-10T19:15:59.144400Z

That said, HoneySQL has definitely made our lives easier at World Singles Networks, where we programmatically compose some very gnarly reporting queries full of conditionals…

Ben Sless 2021-04-10T19:21:44.144600Z

Working with data is certainly better than working with strings, don't get me wrong, but at the bottom of the stack stands some poor soul who has to write a compiler for that mess

seancorfield 2021-04-10T19:22:43.144800Z

As someone who started their career in IT writing compilers, yes, I feel that pain 🙂

🙂 1
Ben Sless 2021-04-10T19:24:44.145Z

And speaking of bashing strings, I don't know if you'll find this useful, but I wrote a templating engine which bashes strings relatively fast because I needed one. Was somewhat inspired by Metosin's approach https://github.com/bsless/contextual

seancorfield 2021-04-10T19:29:38.145400Z

Interesting.

Ben Sless 2021-04-10T19:33:45.145600Z

Returning types which implement the desired behavior at run/compile/render time can speed you up significantly over runtime interpretation. Another thing I did because I was already used to that technique was unroll loops where possible, which is why I have ugly macros emitting defrecord s with 20 arities