Fork me on GitHub
#honeysql
<
2023-03-03
>
Tanner Emerson00:03:23

Hello, we are trying to migrate from honey 1.x to 2.x. We are currently hung up on one of our usages of do-update-set! from honeysql-postgres. Here is our working example.

(honeysql.core/format (-> (SQL/insert-into :foo-table)
                          (SQL/values [{:id 1 :data 42}])
                          (PSQL/upsert (-> (PSQL/on-conflict :id)
                                           (PSQL/do-update-set! [:state "enabled"]
                                                                [:modified (HSQL/call :now)])
                                           (SQL/where [:= :state "disabled"])))))
When we have a conflict, our goal is to update the state to enabled iff the current value of state is disabled. It seems like we are able to set the state when there isn’t a where clause in the upsert. Once we introduce the where, we get a ClassCastException. Is this a supported use case? I included our attempts at writing with honeysql 2.X.
;; GOAL (fails: setting values with a where clause)
(honey.sql/format (-> (sql/insert-into :foo-table)
                      (sql/values [{:id 1 :data 42}])
                      (sql/upsert (-> (sql/on-conflict :id)
                                      (sql/do-update-set {:state "enabled"
                                                          :modified [:now]})
                                      (sql/where [:= :state "disabled"])))))

;; WORKS (where but uses excluded values)
(honey.sql/format (-> (sql/insert-into :foo-table)
                      (sql/values [{:id 1 :data 42 :state "foo" :modified [:now]}])
                      (sql/upsert (-> (sql/on-conflict :id)
                                      (sql/do-update-set :state :modified)
                                      (sql/where [:= :state "disabled"])))))

;; WORKS (no where but we can set values)
(honey.sql/format (-> (sql/insert-into :foo-table)
                      (sql/values [{:id 1 :data 42}])
                      (sql/upsert (-> (sql/on-conflict :id)
                                      (sql/do-update-set {:state "enabled"
                                                          :modified [:now]})))))

seancorfield03:03:50

That sounds like a bug. Can you create a GH issue with a small repro case so I can investigate @U04DSRYBQ3H? Thanks.

βœ… 2
jaide23:03:39

Random idea: Has anyone created a spec that can validate common query types like selects, joins, create table, etc...? Or would it be a massive, sprawling undertaking?

seancorfield00:03:40

It's been discussed from time to time. There was a GH issue created years ago to discuss it (but ultimately closed): https://github.com/seancorfield/honeysql/issues/146

seancorfield00:03:35

I think "massive" and "sprawling" are apt words for this idea πŸ™‚

jaide00:03:46

I see. One thought came to mind, is this a reasonable approximation\starting place?

jaide00:03:18

Hmm that looks off

seancorfield00:03:58

Hahaha... I wouldn't trust ChatGPT any further than I could throw it... it very confidently produces incorrect code... sometimes it's obviously incorrect, sometimes it's just subtly incorrect.

jaide00:03:19

Not unreasonable, but it was worth a shot as a starting place. If I were to begin such a project I'd probably start very small with a separate library and get at least one type of query covered to publish and see community interest\attract collaborators.

seancorfield00:03:29

It's completely wrong about strings -- HoneySQL uses keywords or symbols. Strings are values that are generally lifted out as parameters.

jaide00:03:47

On the other hand, it's not like the dbs it targets doesn't tell you exactly what's wrong with the resulting sql if it's invalid

seancorfield00:03:03

DDL syntax is horrifically variable across databases and HoneySQL supports a lot of it by basically assuming "you know what you're doing" and accepting fairly arbitrary sequences of keywords and (sub-)sequences of stuff.

jaide00:03:36

I see. Probably not worth the trouble

seancorfield00:03:27

There's also the interesting issue that there are plenty of valid-to-HoneySQL data structures that produce absolute garbage SQL -- and what's valid SQL for one DB might be invalid for another DB, just to add to the fun.

jaide00:03:50

Yeesh yeah I'm tapping out. Thanks for humoring me πŸ˜›

2
p-himik09:03:10

A spec could be useful for invalid-to-HoneySQL data structures. Consider a few examples:

=> (honey.sql/format {:select [[[:array #{}]]]})
Execution error (ExceptionInfo) at honey.sql/format-expr-list (sql.cljc:483).
format-expr-list expects a sequence of expressions, found: class clojure.lang.PersistentHashSet
;; ^ Doesn't let you know the context where the set was found, could problematic to quickly find when you have a lot of dynamic parameters.

=> (honey.sql/format {:where nil})
Execution error (ExceptionInfo) at honey.sql/format-dsl (sql.cljc:1258).
These SQL clauses are unknown or have nil values: :where
;; ^ Obvious with this simple query but hard to find with deeply nested SQL with a lot of dynamic `:where`

=> (honey.sql/format {:select [[[:raw "(" 1 ")"]]]})
["SELECT ("]
;; ^ No error but still incorrect usage of `:raw` which might potentially even produce syntactically correct SQL, making it hard to debug.
But it's actually hard to come up with the examples. Before the fresh release, it was possible to create errors like Unable to convert: class clojure.lang.Keyword to Object[] but not sure how to produce those ones now. And even without a spec, potentially HoneySQL could report most of the errors itself along with the context. E.g. instead of that :where error there would something like (for a more nested query) These SQL clauses have nil values: :where (at [:from 0 0 :where]). But hard to say how useful that would be and how hard it is to implement it.

seancorfield19:03:16

To be fair with those first two, ex-data helps here (I should mention that in the docs):

user=> (honey.sql/format {:select [[[:array #{}]]]})
ExceptionInfo format-expr-list expects a sequence of expressions, found: class clojure.lang.PersistentHashSet  clojure.core/ex-info (core.clj:4739)
user=> (ex-data *e)
{:exprs #{}}
user=> (honey.sql/format {:where nil})
ExceptionInfo These SQL clauses are unknown or have nil values: :where  clojure.core/ex-info (core.clj:4739)
user=> (ex-data *e)
{:where nil}

seancorfield19:03:27

I need to think about :raw -- I suspect that was an unintended (or poorly thought out) change from 1.x πŸ˜•

p-himik19:03:57

Ah, right. But still - it shows just the value, not where it is. It's already helpful but having a "data stack trace" (i.e. a get-in-like path) would be even more helpful.

seancorfield19:03:21

I could probably create something from the stacktrace, walking back for honey.sql$format_ matches (on invokeStatic) to report a better context there, since it is full-on recursive descent formatting. :thinking_face:

seancorfield22:03:39

https://github.com/seancorfield/honeysql/issues/476 is fixed on develop -- that was an unintended change so 1.x compatibility should be better now.