first pass of caching formatter

needs more documentation.

Initial results suggest a speedup for simple queries of 2-3x.
Complex queries can see up to 20x speedup.
This commit is contained in:
Sean Corfield 2022-01-08 00:41:21 -08:00
parent 91e054c58b
commit 826407e9db
5 changed files with 85 additions and 5 deletions

View file

@ -2,6 +2,7 @@
* 2.2.next in progress
* Address #377 by adding `honey.sql/map=` to convert a hash map into an equality condition (for a `WHERE` clause).
* Address #351 by adding a `:cache` option to `honey.sql/format` (for Clojure only, not ClojureScript).
* Address #281 by adding support for `SELECT * EXCEPT ..` and `SELECT * REPLACE ..` and `ARRAY<>` and `STRUCT<>` column types -- see [SQL Clause Reference - SELECT](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started/sql-clause-reference#select-select-distinct) and [SQL Clause Reference - DDL](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started/sql-clause-reference#ddl-clauses) respectively for more details.
* Update `build-clj` to v0.6.7.

View file

@ -16,7 +16,8 @@
:test
{:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/tag "v0.5.0" :git/sha "48c3c67"}}
{:git/tag "v0.5.0" :git/sha "48c3c67"}
org.clojure/core.cache {:mvn/version "RELEASE"}}
:exec-fn cognitect.test-runner.api/test}
;; various "runners" for tests/CI:

View file

@ -1,4 +1,4 @@
;; copyright (c) 2020-2021 sean corfield, all rights reserved
;; copyright (c) 2020-2022 sean corfield, all rights reserved
(ns honey.sql
"Primary API for HoneySQL 2.x.
@ -1374,7 +1374,8 @@
as named arguments followed by other options in a hash map."
([data] (format data {}))
([data opts]
(let [dialect? (contains? opts :dialect)
(let [cache (:cache opts)
dialect? (contains? opts :dialect)
dialect (when dialect? (get dialects (check-dialect (:dialect opts))))]
(binding [*dialect* (if dialect? dialect @default-dialect)
*checking* (if (contains? opts :checking)
@ -1397,7 +1398,16 @@
(:quoted-snake opts))
*params* (:params opts)
*values-default-columns* (:values-default-columns opts)]
(mapv #(unwrap % opts) (format-dsl data opts)))))
(if cache
#?(:clj
;; prefer requiring-resolve but that's 1.10+ only:
(let [_ (require 'clojure.core.cache.wrapped)
through (resolve 'clojure.core.cache.wrapped/lookup-or-miss)]
(->> (through cache data (fn [_] (format-dsl data (dissoc opts :cache))))
(mapv #(unwrap % opts))))
:cljs
(throw (ex-info "cached queries are not supported in ClojureScript" opts)))
(mapv #(unwrap % opts) (format-dsl data opts))))))
([data k v & {:as opts}] (format data (assoc opts k v))))
(defn set-dialect!

68
test/honey/cache_test.clj Normal file
View file

@ -0,0 +1,68 @@
;; copyright (c) 2022 sean corfield, all rights reserved
(ns honey.cache-test
(:refer-clojure :exclude [format group-by])
(:require [clojure.core.cache.wrapped :as cache]
[clojure.test :refer [deftest is]]
[honey.sql :as sut]
[honey.sql.helpers
:refer [select-distinct from join left-join right-join where
group-by having order-by limit offset]]))
(def big-complicated-map
(-> (select-distinct :f.* :b.baz :c.quux [:b.bla "bla-bla"]
[[:now]] [[:raw "@x := 10"]])
(from [:foo :f] [:baz :b])
(join :draq [:= :f.b :draq.x]
:eldr [:= :f.e :eldr.t])
(left-join [:clod :c] [:= :f.a :c.d])
(right-join :bock [:= :bock.z :c.e])
(where [:or
[:and [:= :f.a "bort"] [:not= :b.baz [:param :param1]]]
[:and [:< 1 2] [:< 2 3]]
[:in :f.e [1 [:param :param2] 3]]
[:between :f.e 10 20]])
(group-by :f.a :c.e)
(having [:< 0 :f.e])
(order-by [:b.baz :desc] :c.quux [:f.a :nulls-first])
(limit 50)
(offset 10)))
(deftest cache-tests
(let [cache (cache/basic-cache-factory {})]
(is (= ["SELECT * FROM table WHERE id = ?" 1]
(sut/format {:select [:*] :from [:table] :where [:= :id 1]}
{:cache cache})
(sut/format {:select [:*] :from [:table] :where [:= :id 1]}
{:cache cache})))
(is (= (sut/format {:select [:*] :from [:table] :where [:= :id 1]})
(sut/format {:select [:*] :from [:table] :where [:= :id 1]}
{:cache cache})))
(is (= (sut/format big-complicated-map {:params {:param1 "gabba" :param2 2}})
(sut/format big-complicated-map {:cache cache :params {:param1 "gabba" :param2 2}})
(sut/format big-complicated-map {:cache cache :params {:param1 "gabba" :param2 2}})))
(is (= (sut/format big-complicated-map {:params {:param1 "foo" :param2 42}})
(sut/format big-complicated-map {:cache cache :params {:param1 "foo" :param2 42}})
(sut/format big-complicated-map {:cache cache :params {:param1 "foo" :param2 42}})))
(println "Uncached, simple, embedded")
(time (dotimes [_ 100000]
(sut/format {:select [:*] :from [:table] :where [:= :id 1]})))
(println "Cached, simple, embedded")
(time (dotimes [_ 100000]
(sut/format {:select [:*] :from [:table] :where [:= :id 1]} {:cache cache})))
(println "Uncached, complex, mixed")
(time (dotimes [_ 10000]
(sut/format big-complicated-map {:params {:param1 "gabba" :param2 2}})))
(println "Cached, complex, mixed")
(time (dotimes [_ 10000]
(sut/format big-complicated-map {:cache cache :params {:param1 "gabba" :param2 2}}))))
(let [cache (cache/basic-cache-factory {})]
(is (= ["SELECT * FROM table WHERE id = ?" 1]
(sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:cache cache :params {:id 1}})
(sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:cache cache :params {:id 1}})))
(is (= (sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:params {:id 2}})
(sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:cache cache :params {:id 2}})))))

View file

@ -1,4 +1,4 @@
;; copyright (c) 2021 sean corfield, all rights reserved
;; copyright (c) 2021-2022 sean corfield, all rights reserved
(ns honey.sql-test
(:refer-clojure :exclude [format])