attempting to cache SQL that contains IN () will throw fixes #396
This commit is contained in:
parent
03275b1035
commit
270b9439c8
3 changed files with 9 additions and 3 deletions
|
|
@ -7,6 +7,7 @@
|
||||||
* Address [#400](https://github.com/seancorfield/honeysql/issues/400) by adding `:table` clause.
|
* Address [#400](https://github.com/seancorfield/honeysql/issues/400) by adding `:table` clause.
|
||||||
* Address [#399](https://github.com/seancorfield/honeysql/issues/399) by correcting multi-column `RETURNING` clauses in docs and tests.
|
* Address [#399](https://github.com/seancorfield/honeysql/issues/399) by correcting multi-column `RETURNING` clauses in docs and tests.
|
||||||
* Fix [#398](https://github.com/seancorfield/honeysql/issues/398) by adding `honey.sql.pg-ops` namespace that registers PostgreSQL JSON and regex operators and provides symbolic names for "unwritable" operators (that contain `@`, `#`, or `~`).
|
* Fix [#398](https://github.com/seancorfield/honeysql/issues/398) by adding `honey.sql.pg-ops` namespace that registers PostgreSQL JSON and regex operators and provides symbolic names for "unwritable" operators (that contain `@`, `#`, or `~`).
|
||||||
|
* Address [#396](https://github.com/seancorfield/honeysql/issues/396) by throwing an exception if you try to cache a SQL statement that includes an `IN ()` expression.
|
||||||
* Fix [#394](https://github.com/seancorfield/honeysql/issues/394) by restoring HoneySQL 1.x's behavior when quoting.
|
* Fix [#394](https://github.com/seancorfield/honeysql/issues/394) by restoring HoneySQL 1.x's behavior when quoting.
|
||||||
* Fix [#387](https://github.com/seancorfield/honeysql/issues/387) again.
|
* Fix [#387](https://github.com/seancorfield/honeysql/issues/387) again.
|
||||||
* Update CI to reflect Clojure 1.11 release (master -> 1.11; new master is 1.12).
|
* Update CI to reflect Clojure 1.11 release (master -> 1.11; new master is 1.12).
|
||||||
|
|
|
||||||
|
|
@ -126,15 +126,13 @@ are two possible approaches:
|
||||||
|
|
||||||
As of 2.2.858, `format` can cache the SQL and parameters produced from the data structure so that it does not need to be computed on every call. This functionality is available only in Clojure and depends on [`org.clojure/core.cache`](https://github.com/clojure/core.cache) being on your classpath. If you are repeatedly building the same complex SQL statements over and over again, this can be a good way to provide a performance boost but there are some caveats.
|
As of 2.2.858, `format` can cache the SQL and parameters produced from the data structure so that it does not need to be computed on every call. This functionality is available only in Clojure and depends on [`org.clojure/core.cache`](https://github.com/clojure/core.cache) being on your classpath. If you are repeatedly building the same complex SQL statements over and over again, this can be a good way to provide a performance boost but there are some caveats.
|
||||||
|
|
||||||
> Caution: if you use `:in` or `:not-in` expressions, they will be cached with a fixed number of parameters causing errors for subsequent clauses that match that SQL. See [#396](https://github.com/seancorfield/honeysql/issues/396) for details.
|
|
||||||
|
|
||||||
* You need `core.cache` as a dependency: `org.clojure/core.cache {:mvn/version "1.0.225"}` was the latest as of January 20th, 2022,
|
* You need `core.cache` as a dependency: `org.clojure/core.cache {:mvn/version "1.0.225"}` was the latest as of January 20th, 2022,
|
||||||
* You need to create one or more caches yourself, from the various factory functions in the [`clojure.core.cache.wrapped` namespace](http://clojure.github.io/core.cache/#clojure.core.cache.wrapped),
|
* You need to create one or more caches yourself, from the various factory functions in the [`clojure.core.cache.wrapped` namespace](http://clojure.github.io/core.cache/#clojure.core.cache.wrapped),
|
||||||
* You should use named parameters in your SQL DSL data structure, e.g., `:?foo` or `'?foo`, and pass the actual parameter values via the `:params` option to `format`.
|
* You should use named parameters in your SQL DSL data structure, e.g., `:?foo` or `'?foo`, and pass the actual parameter values via the `:params` option to `format`.
|
||||||
|
|
||||||
You can then pass the (atom containing the) cache to `format` using the `:cache` option. The call to `format` then looks in that cache for a match for the data structure passed in, i.e., the entire data structure is used as a key into the cache, including any literal parameter values. If the cache contains a match, the corresponding vector of a SQL string and parameters is used, otherwise the data structure is parsed as usual and the SQL string (and parameters) generated from it (and stored in the cache for the next call). Finally, named parameters in the vector are replaced by their values from the `:params` option.
|
You can then pass the (atom containing the) cache to `format` using the `:cache` option. The call to `format` then looks in that cache for a match for the data structure passed in, i.e., the entire data structure is used as a key into the cache, including any literal parameter values. If the cache contains a match, the corresponding vector of a SQL string and parameters is used, otherwise the data structure is parsed as usual and the SQL string (and parameters) generated from it (and stored in the cache for the next call). Finally, named parameters in the vector are replaced by their values from the `:params` option.
|
||||||
|
|
||||||
The code that _builds_ the DSL data structure will be run in all cases, so any conditional logic and helper function calls will still happen, since that is how the data structure is created and then passed to `format`. If you want to avoid overhead, you'd need to take steps to build the data structure separately and store it somewhere for reuse in the call to `format`.
|
The code that _builds_ the DSL data structure will be run in all cases, so any conditional logic and helper function calls will still happen, since that is how the data structure is created and then passed to `format`. If you want to also avoid that overhead, you'd need to take steps to build the data structure separately and store it somewhere for reuse in the call to `format`.
|
||||||
|
|
||||||
Since the data structure is used as the key into the cache, literal parameter values will lead to different keys:
|
Since the data structure is used as the key into the cache, literal parameter values will lead to different keys:
|
||||||
|
|
||||||
|
|
@ -150,6 +148,8 @@ Since the data structure is used as the key into the cache, literal parameter va
|
||||||
|
|
||||||
Since HoneySQL accepts any of the `clojure.core.cache.wrapped` caches and runs every data structure through the provided `:cache`, it's up to you to ensure that your cache is appropriate for that usage: a "basic" cache will keep every entry until the cache is explicitly emptied; a TTL cache will keep each entry for a specific period of time; and so on.
|
Since HoneySQL accepts any of the `clojure.core.cache.wrapped` caches and runs every data structure through the provided `:cache`, it's up to you to ensure that your cache is appropriate for that usage: a "basic" cache will keep every entry until the cache is explicitly emptied; a TTL cache will keep each entry for a specific period of time; and so on.
|
||||||
|
|
||||||
|
> Note: because `IN ()` expressions are inlined, you cannot cache SQL that includes them. If you try to `format` a statement that includes an `IN ()` expression when you provide the `:cache` option, you will get an exception. See [#396](https://github.com/seancorfield/honeysql/issues/396) for details of why this doesn't work.
|
||||||
|
|
||||||
## Other Sections Will Be Added!
|
## Other Sections Will Be Added!
|
||||||
|
|
||||||
As questions arise about the use of HoneySQL 2.x, I will add new sections here.
|
As questions arise about the use of HoneySQL 2.x, I will add new sections here.
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,8 @@
|
||||||
(def ^:private ^:dynamic *checking* :none)
|
(def ^:private ^:dynamic *checking* :none)
|
||||||
;; the current DSL hash map being formatted (for contains-clause?):
|
;; the current DSL hash map being formatted (for contains-clause?):
|
||||||
(def ^:private ^:dynamic *dsl* nil)
|
(def ^:private ^:dynamic *dsl* nil)
|
||||||
|
;; caching data to detect expressions that cannot be cached:
|
||||||
|
(def ^:private ^:dynamic *caching* nil)
|
||||||
|
|
||||||
;; clause helpers
|
;; clause helpers
|
||||||
|
|
||||||
|
|
@ -1096,6 +1098,8 @@
|
||||||
(let [[sql-x & params-x] (format-expr x {:nested true})
|
(let [[sql-x & params-x] (format-expr x {:nested true})
|
||||||
[sql-y & params-y] (format-expr y {:nested true})
|
[sql-y & params-y] (format-expr y {:nested true})
|
||||||
values (unwrap (first params-y) {})]
|
values (unwrap (first params-y) {})]
|
||||||
|
(when *caching*
|
||||||
|
(throw (ex-info "SQL that includes IN () expressions cannot be cached" {})))
|
||||||
(when-not (= :none *checking*)
|
(when-not (= :none *checking*)
|
||||||
(when (or (and (sequential? y) (empty? y))
|
(when (or (and (sequential? y) (empty? y))
|
||||||
(and (sequential? values) (empty? values)))
|
(and (sequential? values) (empty? values)))
|
||||||
|
|
@ -1438,6 +1442,7 @@
|
||||||
dialect? (contains? opts :dialect)
|
dialect? (contains? opts :dialect)
|
||||||
dialect (when dialect? (get dialects (check-dialect (:dialect opts))))]
|
dialect (when dialect? (get dialects (check-dialect (:dialect opts))))]
|
||||||
(binding [*dialect* (if dialect? dialect @default-dialect)
|
(binding [*dialect* (if dialect? dialect @default-dialect)
|
||||||
|
*caching* cache
|
||||||
*checking* (if (contains? opts :checking)
|
*checking* (if (contains? opts :checking)
|
||||||
(:checking opts)
|
(:checking opts)
|
||||||
:none)
|
:none)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue