Hugsql reloaded workflow

413 views
Skip to first unread message

Daniel Fitzpatrick

unread,
May 29, 2017, 2:29:40 AM5/29/17
to Luminus
Hi guys,

Calling (restart), or (stop) then (start), doesn't force Hugsql to reload sql/queries.sql, so currently I am totally killing my repl session in order to get around that problem.  This kills the reloaded workflow.  Any solution?

Mark Nutter

unread,
May 29, 2017, 9:14:38 AM5/29/17
to Daniel Fitzpatrick, Luminus
Here's what I use, hopefully it's not too terrible. I put it in the <project-name>.db namespace right after the (defstate...) part
; easier reloads at the repl
(defn setup-queries []
(if-not (nil? *db*)
(conman/disconnect! *db*))
(conman/bind-connection *db* "sql/queries.sql"))

; make sure we actually load it!
(setup-queries)
Then just call (setup-queries) at the repl whenever you need to refresh your queries.

Daniel Fitzpatrick

unread,
May 29, 2017, 12:59:05 PM5/29/17
to Luminus, double...@gmail.com
Doing that doesn't bind functions to the db.core namespace unless I first evaluate the ns form for db.core....

Daniel Fitzpatrick

unread,
May 29, 2017, 2:46:11 PM5/29/17
to Luminus, double...@gmail.com
This is what I came up with.

1. define a local bind-connection to designed to take an ns parameter 

(defmacro bind-connection [ns conn & filenames]
 
(let [options? (map? (first filenames))
        options
(if options? (first filenames) {})
        filenames
(if options? (rest filenames) filenames)]
   
`(let [{snips# :snips fns# :fns :as queries#} (conman.core/load-queries '~filenames ~options)]
       (doseq [[id# {fn# :fn {doc# :doc} :meta}] snips#]
         (intern ~ns (with-meta (symbol (name id#)) {:doc doc#}) fn#))
       (doseq [[id# {fn# :fn {doc# :doc} :meta}] fns#]
         (intern ~ns (with-meta (symbol (name id#)) {:doc doc#})
                 (fn
                   ([] (fn# ~conn {}))
                   ([params#] (fn# ~conn params#))
                   ([conn# params#] (fn# conn# params#))
                   ([conn# params# opts# & command-opts#]
                    (apply fn# conn# params# opts# command-opts#)))))
       queries#)))


2. Define your db connection as a simple dynamic var
(def ^:dynamic *db*)



3. Rewrite the state var to bind the sql functions to the namespace of your choice
(defstate conn
 
:start (do
           
(alter-var-root
           
#'*db*
           
(constantly
             
(conman/connect! {:jdbc-url (env :database-url)})))
           
(bind-connection 'my-namespace *db* "sql/queries.sql"))
  :stop (conman/disconnect! *db*))




Now Hugsql works with your reloaded workflow.

This may be a question for a better clojure dev.  I couldn't make
(alter-var-root #'*db* conman/connect! {:jdbc-url (env :database-url)})

work.  Also, conn isn't state anymore.  Sort of a philosophical question there.


If yogthos is interested, I can submit a pull request conman and the leiningen template.  These changes are pretty small but they make a huge difference in development experience.

Dmitri

unread,
May 29, 2017, 3:45:22 PM5/29/17
to Luminus, double...@gmail.com
It might be better to just pass a qualified name for the db, e.g:

(bind-connection'my-namespace/*db*"sql/queries.sql")

I've been thinking that it might also be better to use the query map returned by HugSQL explicitly instead of interning functions in the namespace:

(defstate queries
  :start (conman/bind-connection *db* "queries.sql"))

(defn query [id & args]
  (if-let [query (or (-> queries :fns id)
                     (-> queries :snips id))]
    (apply query args)
    (throw (Exception. (str "query " id " not found") {:cause {:missing}}))))

Daniel Fitzpatrick

unread,
May 29, 2017, 3:55:07 PM5/29/17
to Dmitri, Luminus

Agree on both counts

Dmitri

unread,
May 30, 2017, 10:32:24 AM5/30/17
to Luminus, dmitri....@gmail.com
I just released a new version of conman with a couple of helper functions to aid this https://github.com/luminus-framework/conman/blob/1740371cfef0c4df4eddd371776d42392f810c4d/test/conman/core_test.clj#L109

Now you can define the queries using defstate and access them as follows:

(defstate queries :start (load-queries ["queries.sql"]))

(db-command conn
            queries
            :add-fruit!
            {:name     "banana"
             :appearance "banana"
             :cost     1
             :grade     1})

(query conn
       queries
       :get-fruit-by
       {:by-appearance
        (snippet queries :by-appearance {:appearance "banana"})})




On Monday, May 29, 2017 at 3:55:07 PM UTC-4, Daniel Fitzpatrick wrote:

Agree on both counts

Daniel Fitzpatrick

unread,
May 30, 2017, 12:11:19 PM5/30/17
to Luminus, dmitri....@gmail.com
Thank you, this is excellent!  I'll try it out first chance I get.

Daniel Fitzpatrick

unread,
Jul 12, 2017, 5:53:16 PM7/12/17
to Luminus, dmitri....@gmail.com
I've had a chance to try this out and I greatly prefer it over the old method.  Good work.

Something which routinely bothers me ... in order to discover what parameters are required for a given hugsql function I need to have the sql file open.  I would like to perform that discovery at the repl.  For example:

```
    (hugsql/keys-required queries :add-fruit!)
    => (:name :appearance :cost :grade)
```


Any solution?

Dmitri

unread,
Jul 12, 2017, 6:06:24 PM7/12/17
to Luminus, dmitri....@gmail.com
I think that would be a useful feature, but it would need to be implemented in HugSQL itself. Probably worth opening an issue for that https://github.com/layerware/hugsql/issues

Kalle Blomström

unread,
Jul 17, 2017, 4:28:42 AM7/17/17
to Luminus
Hi and thank you for a great framework.

I have a situation where I would like to be able to use different queries and different databases in development and production.

I have the queries specified in separate files, one for production and one for development. My initial thought was to dynamically load separate hugsql files through parameters in profiles.clj but as but as bind-connection from conman is a macro this doesn't work.

Would this new approach make this scenario possible?

I am sorry if I am hijacking the thread, if so please let me know and I'll start afresh.
Again, thank you very much.

Kind regards,

Karl Blomström

Dmitri

unread,
Jul 17, 2017, 11:42:13 AM7/17/17
to Luminus
Unfortunately, the bind-connection macro needs a string, but you could do something like:

(defstate queries
  :start (if (:dev env)
           (conman/bind-connection *db* "queries-dev.sql")
           (conman/bind-connection *db* "queries-prod.sql")))

Kalle Blomström

unread,
Jul 21, 2017, 9:08:20 PM7/21/17
to Luminus
Thank you! This worked great!

stacksideflow

unread,
Apr 11, 2019, 6:50:28 AM4/11/19
to Luminus

Daniel Fitzpatrick

unread,
Apr 11, 2019, 8:47:42 AM4/11/19
to Luminus
The change he made back on 5/30/17 never made it into the Leiningen template, but you should try it. I like it *much* better than namespace binding.
Reply all
Reply to author
Forward
0 new messages