fix #486 by support ansi/postgresl interval

This commit is contained in:
Sean Corfield 2023-04-13 22:46:37 -07:00
parent 2efe05def2
commit 1710e07231
5 changed files with 26 additions and 6 deletions

View file

@ -1,6 +1,7 @@
# Changes # Changes
* 2.4.next in progress * 2.4.next in progress
* Fix [#486](https://github.com/seancorfield/honeysql/issues/486) by supporting ANSI-style `INTERVAL` syntax.
* Fix [#484](https://github.com/seancorfield/honeysql/issues/484) by adding `TABLE` to `TRUNCATE`. * Fix [#484](https://github.com/seancorfield/honeysql/issues/484) by adding `TABLE` to `TRUNCATE`.
* Fix [#483](https://github.com/seancorfield/honeysql/issues/483) by adding a function-like `:join` syntax to produce nested `JOIN` expressions. * Fix [#483](https://github.com/seancorfield/honeysql/issues/483) by adding a function-like `:join` syntax to produce nested `JOIN` expressions.
* 2.4.1011 -- 2023-03-23 * 2.4.1011 -- 2023-03-23

View file

@ -133,7 +133,7 @@ The following new syntax has been added:
* `:default` -- for `DEFAULT` values (in inserts) and for declaring column defaults in table definitions, * `:default` -- for `DEFAULT` values (in inserts) and for declaring column defaults in table definitions,
* `:escape` -- used to wrap a regular expression so that non-standard escape characters can be provided, * `:escape` -- used to wrap a regular expression so that non-standard escape characters can be provided,
* `:inline` -- used as a function to replace the `sql/inline` / `#sql/inline` machinery, * `:inline` -- used as a function to replace the `sql/inline` / `#sql/inline` machinery,
* `:interval` -- used as a function to support `INTERVAL <n> <units>`, e.g., `[:interval 30 :days]` for databases that support it (e.g., MySQL), * `:interval` -- used as a function to support `INTERVAL <n> <units>`, e.g., `[:interval 30 :days]` for databases that support it (e.g., MySQL) and, as of 2.4.next, for `INTERVAL 'n units'`, e.g., `[:interval "24 hours"]` for ANSI/PostgreSQL.
* `:lateral` -- used to wrap a statement or expression, to provide a `LATERAL` join, * `:lateral` -- used to wrap a statement or expression, to provide a `LATERAL` join,
* `:lift` -- used as a function to prevent interpretation of a Clojure data structure as DSL syntax (e.g., when passing a vector or hash map as a parameter value) -- this should mostly be a replacement for `honeysql.format/value`, * `:lift` -- used as a function to prevent interpretation of a Clojure data structure as DSL syntax (e.g., when passing a vector or hash map as a parameter value) -- this should mostly be a replacement for `honeysql.format/value`,
* `:nest` -- used as a function to add an extra level of nesting (parentheses) around an expression, * `:nest` -- used as a function to add an extra level of nesting (parentheses) around an expression,

View file

@ -210,15 +210,18 @@ than turning it into a positional parameter:
## interval ## interval
Accepts two arguments: an expression and a keyword (or a symbol) Accepts one or two arguments: either a string or an expression and
that represents a time unit. Produces an `INTERVAL` expression: a keyword (or a symbol) that represents a time unit.
Produces an `INTERVAL` expression:
```clojure ```clojure
(sql/format-expr [:date_add [:now] [:interval 30 :days]]) (sql/format-expr [:date_add [:now] [:interval 30 :days]])
;;=> ["DATE_ADD(NOW(), INTERVAL ? DAYS)" 30] ;;=> ["DATE_ADD(NOW(), INTERVAL ? DAYS)" 30]
(sql/format-expr [:date_add [:now] [:interval "24 Hours"]])
;;=> ["DATE_ADD(NOW(), INTERVAL '24 Hours')"]
``` ```
> Note: PostgreSQL has an `INTERVAL` data type which is unrelated to this syntax. In PostgreSQL, the closet equivalent would be `[:cast "30 days" :interval]` which will lift `"30 days"` out as a parameter. In DDL, for PostgreSQL, you can use `:interval` to produce the `INTERVAL` data type (without wrapping it in a vector). > Note: PostgreSQL also has an `INTERVAL` data type which is unrelated to this syntax. In PostgreSQL, the closet equivalent would be `[:cast "30 days" :interval]` which will lift `"30 days"` out as a parameter. In DDL, for PostgreSQL, you can use `:interval` to produce the `INTERVAL` data type (without wrapping it in a vector).
## join ## join

View file

@ -1543,8 +1543,12 @@
(format-expr x))) (format-expr x)))
:interval :interval
(fn [_ [n units]] (fn [_ [n units]]
(if units
(let [[sql & params] (format-expr n)] (let [[sql & params] (format-expr n)]
(into [(str "INTERVAL " sql " " (sql-kw units))] params))) (into [(str "INTERVAL " sql " " (sql-kw units))] params))
(binding [*inline* true]
(let [[sql & params] (format-expr n)]
(into [(str "INTERVAL " sql)] params)))))
:join :join
(fn [_ [e & js]] (fn [_ [e & js]]
(let [[sqls params] (reduce-sql (cons (format-expr e) (let [[sqls params] (reduce-sql (cons (format-expr e)
@ -2010,6 +2014,9 @@
(format {:select [:*] :from [:table] (format {:select [:*] :from [:table]
:where [:< [:date_add :expiry [:interval 30 :days]] [:now]]} {}) :where [:< [:date_add :expiry [:interval 30 :days]] [:now]]} {})
(format-expr [:interval 30 :days]) (format-expr [:interval 30 :days])
(format {:select [:*] :from [:table]
:where [:< [:date_add :expiry [:interval "30 Days"]] [:now]]} {})
(format-expr [:interval "30 Days"])
(format {:select [:*] :from [:table] (format {:select [:*] :from [:table]
:where [:= :id (int 1)]} {:dialect :mysql}) :where [:= :id (int 1)]} {:dialect :mysql})
(map fn? (format {:select [:*] :from [:table] (map fn? (format {:select [:*] :from [:table]

View file

@ -68,6 +68,10 @@
(is (= ["INTERVAL ? DAYS" 30] (is (= ["INTERVAL ? DAYS" 30]
(sut/format-expr [:interval 30 :days])))) (sut/format-expr [:interval 30 :days]))))
(deftest issue-486-interval
(is (= ["INTERVAL '30 Days'"]
(sut/format-expr [:interval "30 Days"]))))
(deftest issue-455-null (deftest issue-455-null
(is (= ["WHERE (abc + ?) IS NULL" "abc"] (is (= ["WHERE (abc + ?) IS NULL" "abc"]
(sut/format {:where [:= [:+ :abc "abc"] nil]})))) (sut/format {:where [:= [:+ :abc "abc"] nil]}))))
@ -97,6 +101,8 @@
(sut/format {:select [:*] :from [:table] :order-by [[[:date :expiry] :desc] :bar]} {:quoted true}))) (sut/format {:select [:*] :from [:table] :order-by [[[:date :expiry] :desc] :bar]} {:quoted true})))
(is (= ["SELECT * FROM \"table\" WHERE DATE_ADD(\"expiry\", INTERVAL ? DAYS) < NOW()" 30] (is (= ["SELECT * FROM \"table\" WHERE DATE_ADD(\"expiry\", INTERVAL ? DAYS) < NOW()" 30]
(sut/format {:select [:*] :from [:table] :where [:< [:date_add :expiry [:interval 30 :days]] [:now]]} {:quoted true}))) (sut/format {:select [:*] :from [:table] :where [:< [:date_add :expiry [:interval 30 :days]] [:now]]} {:quoted true})))
(is (= ["SELECT * FROM \"table\" WHERE DATE_ADD(\"expiry\", INTERVAL '30 Days') < NOW()"]
(sut/format {:select [:*] :from [:table] :where [:< [:date_add :expiry [:interval "30 Days"]] [:now]]} {:quoted true})))
(is (= ["SELECT * FROM `table` WHERE `id` = ?" 1] (is (= ["SELECT * FROM `table` WHERE `id` = ?" 1]
(sut/format {:select [:*] :from [:table] :where [:= :id 1]} {:dialect :mysql}))) (sut/format {:select [:*] :from [:table] :where [:= :id 1]} {:dialect :mysql})))
(is (= ["SELECT * FROM \"table\" WHERE \"id\" IN (?, ?, ?, ?)" 1 2 3 4] (is (= ["SELECT * FROM \"table\" WHERE \"id\" IN (?, ?, ?, ?)" 1 2 3 4]
@ -127,6 +133,9 @@
(is (= ["SELECT * FROM \"table\" WHERE DATE_ADD(\"expiry\", INTERVAL $1 DAYS) < NOW()" 30] (is (= ["SELECT * FROM \"table\" WHERE DATE_ADD(\"expiry\", INTERVAL $1 DAYS) < NOW()" 30]
(sut/format {:select [:*] :from [:table] :where [:< [:date_add :expiry [:interval 30 :days]] [:now]]} (sut/format {:select [:*] :from [:table] :where [:< [:date_add :expiry [:interval 30 :days]] [:now]]}
{:quoted true :numbered true}))) {:quoted true :numbered true})))
(is (= ["SELECT * FROM \"table\" WHERE DATE_ADD(\"expiry\", INTERVAL '30 Days') < NOW()"]
(sut/format {:select [:*] :from [:table] :where [:< [:date_add :expiry [:interval "30 Days"]] [:now]]}
{:quoted true :numbered true})))
(is (= ["SELECT * FROM `table` WHERE `id` = $1" 1] (is (= ["SELECT * FROM `table` WHERE `id` = $1" 1]
(sut/format {:select [:*] :from [:table] :where [:= :id 1]} (sut/format {:select [:*] :from [:table] :where [:= :id 1]}
{:dialect :mysql :numbered true}))) {:dialect :mysql :numbered true})))