Compare commits
57 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5dbc274be | ||
|
|
df753e8635 | ||
|
|
b4b2ca7d79 | ||
|
|
78f7d5282f | ||
|
|
024d17b11e | ||
|
|
7611871935 | ||
|
|
1b042687f4 | ||
|
|
a981ed9171 | ||
|
|
74cf16c134 | ||
|
|
2c8fc30b1d | ||
|
|
3906aa53c0 | ||
|
|
92e4e16b45 | ||
|
|
30b5fabe58 | ||
|
|
d74046c658 | ||
|
|
44494e61c0 | ||
|
|
d70e89ae3b | ||
|
|
67ea477a5c | ||
|
|
89f39be55c | ||
|
|
0d1fd0e901 | ||
|
|
675c94b294 | ||
|
|
03d96f5747 | ||
|
|
f7dbfba57c | ||
|
|
f0eb68f151 | ||
|
|
4d1f5f83b7 | ||
|
|
c2990597f1 | ||
|
|
695351e33c | ||
|
|
0cd81b5d9b | ||
|
|
c295db44c0 | ||
|
|
44ca426b78 | ||
|
|
e3fcb3e278 | ||
|
|
7e1fe8f558 | ||
|
|
13eb8fe859 | ||
|
|
4bc1d16f24 | ||
|
|
206f980093 | ||
|
|
3beaa6b2bf | ||
|
|
30a04975f5 | ||
|
|
4f5b0ed256 | ||
|
|
1681764830 | ||
|
|
6c0c66e371 | ||
|
|
2c793ce441 | ||
|
|
f05c7051e2 | ||
|
|
d086631e54 | ||
|
|
3ecac63bea | ||
|
|
8e0d6984bd | ||
|
|
94fae3437f | ||
|
|
316f36751f | ||
|
|
8ae93d91f6 | ||
|
|
5fa85400f0 | ||
|
|
045634fd3c | ||
|
|
81167cb77e | ||
|
|
a3b79215c4 | ||
|
|
0cbe76329e | ||
|
|
c93eef06f6 | ||
|
|
60f5662d81 | ||
|
|
6531413325 | ||
|
|
fce39548d0 | ||
|
|
0f26e7d060 |
24 changed files with 525 additions and 235 deletions
2
.github/workflows/test-and-release.yml
vendored
2
.github/workflows/test-and-release.yml
vendored
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
- name: Setup Clojure
|
- name: Setup Clojure
|
||||||
uses: DeLaGuardo/setup-clojure@master
|
uses: DeLaGuardo/setup-clojure@master
|
||||||
with:
|
with:
|
||||||
cli: '1.12.0.1488'
|
cli: '1.12.0.1530'
|
||||||
- name: Cache All The Things
|
- name: Cache All The Things
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
4
.github/workflows/test-and-snapshot.yml
vendored
4
.github/workflows/test-and-snapshot.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
- name: Setup Clojure
|
- name: Setup Clojure
|
||||||
uses: DeLaGuardo/setup-clojure@master
|
uses: DeLaGuardo/setup-clojure@master
|
||||||
with:
|
with:
|
||||||
cli: '1.12.0.1488'
|
cli: '1.12.0.1530'
|
||||||
- name: Cache All The Things
|
- name: Cache All The Things
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|
@ -49,7 +49,7 @@ jobs:
|
||||||
- name: Clojure CLI
|
- name: Clojure CLI
|
||||||
uses: DeLaGuardo/setup-clojure@master
|
uses: DeLaGuardo/setup-clojure@master
|
||||||
with:
|
with:
|
||||||
cli: '1.12.0.1488'
|
cli: '1.12.0.1530'
|
||||||
- name: Cache All The Things
|
- name: Cache All The Things
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
2
.github/workflows/test-bb.yml
vendored
2
.github/workflows/test-bb.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
||||||
- name: Clojure CLI
|
- name: Clojure CLI
|
||||||
uses: DeLaGuardo/setup-clojure@master
|
uses: DeLaGuardo/setup-clojure@master
|
||||||
with:
|
with:
|
||||||
cli: '1.12.0.1488'
|
cli: '1.12.0.1530'
|
||||||
bb: latest
|
bb: latest
|
||||||
- name: Cache All The Things
|
- name: Cache All The Things
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
|
|
||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
- name: Clojure CLI
|
- name: Clojure CLI
|
||||||
uses: DeLaGuardo/setup-clojure@master
|
uses: DeLaGuardo/setup-clojure@master
|
||||||
with:
|
with:
|
||||||
cli: '1.12.0.1488'
|
cli: '1.12.0.1530'
|
||||||
- name: Cache All The Things
|
- name: Cache All The Things
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
|
|
|
||||||
23
.gitpod.yml
23
.gitpod.yml
|
|
@ -1,23 +0,0 @@
|
||||||
image:
|
|
||||||
file: .gitpod.dockerfile
|
|
||||||
|
|
||||||
vscode:
|
|
||||||
extensions:
|
|
||||||
- betterthantomorrow.calva
|
|
||||||
- mauricioszabo.clover
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
- name: Prepare deps/clover
|
|
||||||
init: |
|
|
||||||
clojure -A:test -P
|
|
||||||
echo 50505 > .socket-repl-port
|
|
||||||
mkdir ~/.config/clover
|
|
||||||
cp .clover/config.cljs ~/.config/clover/
|
|
||||||
- name: Start REPL
|
|
||||||
command: clojure -J-Dclojure.server.repl="{:address \"0.0.0.0\" :port 50505 :accept clojure.core.server/repl}" -A:test
|
|
||||||
- name: See Changes
|
|
||||||
command: code CHANGELOG.md
|
|
||||||
|
|
||||||
github:
|
|
||||||
prebuilds:
|
|
||||||
develop: true
|
|
||||||
28
CHANGELOG.md
28
CHANGELOG.md
|
|
@ -1,5 +1,31 @@
|
||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
* 2.7.next in progress
|
||||||
|
* Address [#440](https://github.com/seancorfield/honeysql/issues/440) by supporting multiple tables in `:truncate`.
|
||||||
|
* Support `USING HASH` as well as `USING GIN`.
|
||||||
|
* Fix [#571](https://github.com/seancorfield/honeysql/issues/571) by allowing `:order-by` to take an empty sequence of columns (and be omitted).
|
||||||
|
* Update dev/build deps.
|
||||||
|
|
||||||
|
* 2.7.1295 -- 2025-03-12
|
||||||
|
* Address #570 by adding `:.:.` as special syntax for Snowflake's JSON path syntax, and `:at` as special syntax for general `[`..`]` path syntax.
|
||||||
|
* Drop support for Clojure 1.9 [#561](https://github.com/seancorfield/honeysql/issues/561).
|
||||||
|
|
||||||
|
* 2.6.1281 -- 2025-03-06
|
||||||
|
* Address [#568](https://github.com/seancorfield/honeysql/issues/568) by adding `honey.sql/semicolon` to merge multiple SQL+params vectors into one (with semicolons separating the SQL statements).
|
||||||
|
* Address [#567](https://github.com/seancorfield/honeysql/issues/567) by adding support for `ASSERT` clause.
|
||||||
|
* Address [#566](https://github.com/seancorfield/honeysql/issues/566) by adding `IS [NOT] DISTINCT FROM` operators.
|
||||||
|
* Add examples of `:alias` with `:group-by` (syntax is slightly different to existing examples for `:order-by`).
|
||||||
|
|
||||||
|
* 2.6.1270 -- 2025-01-17
|
||||||
|
* Fix autoboxing introduced in 2.6.1267 via PR [#564](https://github.com/seancorfield/honeysql/pull/564) [@alexander-yakushev](https://github.com/alexander-yakushev).
|
||||||
|
|
||||||
|
* 2.6.1267 -- 2025-01-16
|
||||||
|
* Support expressions in `WITH` clauses via PR [#563](https://github.com/seancorfield/honeysql/pull/563) [@krevedkokun](https://github.com/krevedkokun).
|
||||||
|
* More performance optimizations via PRs [#560](https://github.com/seancorfield/honeysql/pull/560) and [#562](https://github.com/seancorfield/honeysql/pull/562) [@alexander-yakushev](https://github.com/alexander-yakushev).
|
||||||
|
* Fix two broken links to the [HoneySQL web app](https://john.shaffe.rs/honeysql/) via PR [#559](https://github.com/seancorfield/honeysql/pull/559) [@whatacold](https://github.com/whatacold).
|
||||||
|
* Make SQL Server dialect auto-lift Boolean values to parameters since SQL Server has no `TRUE` / `FALSE` literals.
|
||||||
|
* Fix bug in `DEFAULT` values clause (that omitted some values).
|
||||||
|
|
||||||
* 2.6.1243 -- 2024-12-13
|
* 2.6.1243 -- 2024-12-13
|
||||||
* Address [#558](https://github.com/seancorfield/honeysql/issues/558) by adding `:patch-into` (and `patch-into` helper) for XTDB (but in core).
|
* Address [#558](https://github.com/seancorfield/honeysql/issues/558) by adding `:patch-into` (and `patch-into` helper) for XTDB (but in core).
|
||||||
* Address [#556](https://github.com/seancorfield/honeysql/issues/556) by adding an XTDB section to the documentation with examples.
|
* Address [#556](https://github.com/seancorfield/honeysql/issues/556) by adding an XTDB section to the documentation with examples.
|
||||||
|
|
@ -250,7 +276,7 @@
|
||||||
* Fixes #344 by no longer dropping the qualifier on columns in a `SET` clause _for the `:mysql` dialect only_; the behavior is unchanged for all other dialects.
|
* Fixes #344 by no longer dropping the qualifier on columns in a `SET` clause _for the `:mysql` dialect only_; the behavior is unchanged for all other dialects.
|
||||||
* Fixes #340 by making the "hyphen to space" logic more general so _operators_ containing `-` should retain the hyphen without special cases.
|
* Fixes #340 by making the "hyphen to space" logic more general so _operators_ containing `-` should retain the hyphen without special cases.
|
||||||
* Documentation improvements: `:fetch`, `:lift`, `:limit`, `:offset`, `:param`, `:select`; also around JSON/PostgreSQL.
|
* Documentation improvements: `:fetch`, `:lift`, `:limit`, `:offset`, `:param`, `:select`; also around JSON/PostgreSQL.
|
||||||
* Link to the [HoneySQL web app](https://www.john-shaffer.com/honeysql/) in both the README and **Getting Started**.
|
* Link to the [HoneySQL web app](https://john.shaffe.rs/honeysql/) in both the README and **Getting Started**.
|
||||||
* Switch to `tools.build` for running tests and JAR building etc.
|
* Switch to `tools.build` for running tests and JAR building etc.
|
||||||
|
|
||||||
* 2.0.0-rc5 (for testing; 2021-07-17)
|
* 2.0.0-rc5 (for testing; 2021-07-17)
|
||||||
|
|
|
||||||
11
README.md
11
README.md
|
|
@ -4,17 +4,18 @@ SQL as Clojure data structures. Build queries programmatically -- even at runtim
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
[](https://clojars.org/com.github.seancorfield/honeysql)
|
[](https://clojars.org/com.github.seancorfield/honeysql)
|
||||||
[](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT)
|
[](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT)
|
||||||
[](https://clojurians.slack.com/app_redirect?channel=honeysql)
|
[](https://clojurians.slack.com/app_redirect?channel=honeysql)
|
||||||
[](http://clojurians.net)
|
[](http://clojurians.net)
|
||||||
|
[](https://clojurians.zulipchat.com/#narrow/channel/152091-honeysql)
|
||||||
|
|
||||||
This project follows the version scheme MAJOR.MINOR.COMMITS where MAJOR and MINOR provide some relative indication of the size of the change, but do not follow semantic versioning. In general, all changes endeavor to be non-breaking (by moving to new names rather than by breaking existing names). COMMITS is an ever-increasing counter of commits since the beginning of this repository.
|
This project follows the version scheme MAJOR.MINOR.COMMITS where MAJOR and MINOR provide some relative indication of the size of the change, but do not follow semantic versioning. In general, all changes endeavor to be non-breaking (by moving to new names rather than by breaking existing names). COMMITS is an ever-increasing counter of commits since the beginning of this repository.
|
||||||
|
|
||||||
> Note: every commit to the **develop** branch runs CI (GitHub Actions) and successful runs push a MAJOR.MINOR.9999-SNAPSHOT build to Clojars so the very latest version of HoneySQL is always available either via that [snapshot on Clojars](https://clojars.org/com.github.seancorfield/honeysql) or via a git dependency on the latest SHA.
|
> Note: every commit to the **develop** branch runs CI (GitHub Actions) and successful runs push a MAJOR.MINOR.9999-SNAPSHOT build to Clojars so the very latest version of HoneySQL is always available either via that [snapshot on Clojars](https://clojars.org/com.github.seancorfield/honeysql) or via a git dependency on the latest SHA.
|
||||||
|
|
||||||
HoneySQL 2.x requires Clojure 1.9 or later.
|
HoneySQL 2.7.y requires Clojure 1.10.3 or later.
|
||||||
|
Earlier versions of HoneySQL support Clojure 1.9.0.
|
||||||
It also supports recent versions of ClojureScript and Babashka.
|
It also supports recent versions of ClojureScript and Babashka.
|
||||||
|
|
||||||
Compared to the [legacy 1.x version](#1.x), HoneySQL 2.x provides a streamlined codebase and a simpler method for extending the DSL. It also supports SQL dialects out-of-the-box and will be extended to support vendor-specific language features over time (unlike 1.x).
|
Compared to the [legacy 1.x version](#1.x), HoneySQL 2.x provides a streamlined codebase and a simpler method for extending the DSL. It also supports SQL dialects out-of-the-box and will be extended to support vendor-specific language features over time (unlike 1.x).
|
||||||
|
|
@ -47,7 +48,7 @@ section of the documentation before trying to use HoneySQL to build your own que
|
||||||
From Clojure:
|
From Clojure:
|
||||||
<!-- {:test-doc-blocks/reader-cond :clj} -->
|
<!-- {:test-doc-blocks/reader-cond :clj} -->
|
||||||
```clojure
|
```clojure
|
||||||
(refer-clojure :exclude '[distinct filter for group-by into partition-by set update])
|
(refer-clojure :exclude '[assert distinct filter for group-by into partition-by set update])
|
||||||
(require '[honey.sql :as sql]
|
(require '[honey.sql :as sql]
|
||||||
;; CAUTION: this overwrites several clojure.core fns:
|
;; CAUTION: this overwrites several clojure.core fns:
|
||||||
;;
|
;;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
[deps-deploy.deps-deploy :as dd]))
|
[deps-deploy.deps-deploy :as dd]))
|
||||||
|
|
||||||
(def lib 'com.github.seancorfield/honeysql)
|
(def lib 'com.github.seancorfield/honeysql)
|
||||||
(defn- the-version [patch] (format "2.6.%s" patch))
|
(defn- the-version [patch] (format "2.7.%s" patch))
|
||||||
(def version (the-version (b/git-count-revs nil)))
|
(def version (the-version (b/git-count-revs nil)))
|
||||||
(def snapshot (the-version "9999-SNAPSHOT"))
|
(def snapshot (the-version "9999-SNAPSHOT"))
|
||||||
(def class-dir "target/classes")
|
(def class-dir "target/classes")
|
||||||
|
|
@ -47,8 +47,7 @@
|
||||||
"Generate and run doc tests.
|
"Generate and run doc tests.
|
||||||
|
|
||||||
Optionally specify :aliases vector:
|
Optionally specify :aliases vector:
|
||||||
[:1.9] -- test against Clojure 1.9 (the default)
|
[:1.10] -- test against Clojure 1.10.3 (the default)
|
||||||
[:1.10] -- test against Clojure 1.10.3
|
|
||||||
[:1.11] -- test against Clojure 1.11.0
|
[:1.11] -- test against Clojure 1.11.0
|
||||||
[:1.12] -- test against Clojure 1.12.0
|
[:1.12] -- test against Clojure 1.12.0
|
||||||
[:cljs] -- test against ClojureScript"
|
[:cljs] -- test against ClojureScript"
|
||||||
|
|
@ -99,10 +98,10 @@
|
||||||
(defn ci
|
(defn ci
|
||||||
"Run the CI pipeline of tests (and build the JAR).
|
"Run the CI pipeline of tests (and build the JAR).
|
||||||
|
|
||||||
Default Clojure version is 1.9.0 (:1.9) so :elide
|
Default Clojure version is 1.10.3 (:1.10) so :elide
|
||||||
tests for #409 on that version."
|
tests for #409 on that version."
|
||||||
[opts]
|
[opts]
|
||||||
(let [aliases [:cljs :elide :1.10 :1.11 :1.12]
|
(let [aliases [:cljs :elide :1.11 :1.12]
|
||||||
opts (jar-opts opts)]
|
opts (jar-opts opts)]
|
||||||
(b/delete {:path "target"})
|
(b/delete {:path "target"})
|
||||||
(doseq [alias aliases]
|
(doseq [alias aliases]
|
||||||
|
|
|
||||||
7
deps.edn
7
deps.edn
|
|
@ -1,14 +1,13 @@
|
||||||
{:mvn/repos {"sonatype" {:url "https://oss.sonatype.org/content/repositories/snapshots/"}}
|
{:mvn/repos {"sonatype" {:url "https://oss.sonatype.org/content/repositories/snapshots/"}}
|
||||||
:paths ["src"]
|
:paths ["src"]
|
||||||
:deps {org.clojure/clojure {:mvn/version "1.9.0"}}
|
:deps {org.clojure/clojure {:mvn/version "1.10.3"}}
|
||||||
:aliases
|
:aliases
|
||||||
{;; for help: clojure -A:deps -T:build help/doc
|
{;; for help: clojure -A:deps -T:build help/doc
|
||||||
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.6"}
|
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.8"}
|
||||||
slipset/deps-deploy {:mvn/version "0.2.2"}}
|
slipset/deps-deploy {:mvn/version "0.2.2"}}
|
||||||
:ns-default build}
|
:ns-default build}
|
||||||
|
|
||||||
;; versions to test against:
|
;; versions to test against:
|
||||||
:1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}}
|
|
||||||
:1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}}
|
:1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.3"}}}
|
||||||
:1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}}
|
:1.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}}
|
||||||
:1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}}
|
:1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}}
|
||||||
|
|
@ -31,7 +30,7 @@
|
||||||
:main-opts ["-m" "cljs-test-runner.main"]}
|
:main-opts ["-m" "cljs-test-runner.main"]}
|
||||||
|
|
||||||
:gen-doc-tests {:replace-paths ["build"]
|
:gen-doc-tests {:replace-paths ["build"]
|
||||||
:extra-deps {babashka/fs {:mvn/version "0.5.22"}
|
:extra-deps {babashka/fs {:mvn/version "0.5.24"}
|
||||||
com.github.lread/test-doc-blocks {:mvn/version "1.1.20"}}
|
com.github.lread/test-doc-blocks {:mvn/version "1.1.20"}}
|
||||||
:main-opts ["-m" "honey.gen-doc-tests"]}
|
:main-opts ["-m" "honey.gen-doc-tests"]}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,14 @@ user=> (sql/format {:create-index [:my-idx [:fruit :using-gin :appearance]]})
|
||||||
["CREATE INDEX my_idx ON fruit USING GIN (appearance)"]
|
["CREATE INDEX my_idx ON fruit USING GIN (appearance)"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
As of 2.7.next, `USING HASH` index creation is also possible using the keyword
|
||||||
|
`:using-hash` after the table name (or the symbol `using-hash`):
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
user=> (sql/format {:create-index [:my-idx [:fruit :using-hash :appearance]]})
|
||||||
|
["CREATE INDEX my_idx ON fruit USING HASH (appearance)"]
|
||||||
|
```
|
||||||
|
|
||||||
### rename-table
|
### rename-table
|
||||||
|
|
||||||
Used with `:alter-table`,
|
Used with `:alter-table`,
|
||||||
|
|
@ -822,13 +830,23 @@ is a "hard" delete as opposed to a temporal delete.
|
||||||
## truncate
|
## truncate
|
||||||
|
|
||||||
`:truncate` accepts a simple SQL entity (table name)
|
`:truncate` accepts a simple SQL entity (table name)
|
||||||
or a table name followed by various options:
|
or a table name followed by various options, or a
|
||||||
|
sequence that starts with a sequence of one or more table names,
|
||||||
|
optionally followed by various options:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
user=> (sql/format '{truncate transport})
|
user=> (sql/format '{truncate transport})
|
||||||
["TRUNCATE TABLE transport"]
|
["TRUNCATE TABLE transport"]
|
||||||
|
user=> (sql/format '{truncate (transport)})
|
||||||
|
["TRUNCATE TABLE transport"]
|
||||||
user=> (sql/format '{truncate (transport restart identity)})
|
user=> (sql/format '{truncate (transport restart identity)})
|
||||||
["TRUNCATE TABLE transport RESTART IDENTITY"]
|
["TRUNCATE TABLE transport RESTART IDENTITY"]
|
||||||
|
user=> (sql/format '{truncate ((transport))})
|
||||||
|
["TRUNCATE TABLE transport"]
|
||||||
|
user=> (sql/format '{truncate ((transport other))})
|
||||||
|
["TRUNCATE TABLE transport, other"]
|
||||||
|
user=> (sql/format '{truncate ((transport other) restart identity)})
|
||||||
|
["TRUNCATE TABLE transport, other RESTART IDENTITY"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## columns
|
## columns
|
||||||
|
|
@ -1070,6 +1088,9 @@ The `:where` clause can have a single SQL expression, or
|
||||||
a sequence of SQL expressions prefixed by either `:and`
|
a sequence of SQL expressions prefixed by either `:and`
|
||||||
or `:or`. See examples of `:where` in various clauses above.
|
or `:or`. See examples of `:where` in various clauses above.
|
||||||
|
|
||||||
|
If `:where` is given an empty sequence, the `WHERE` clause will
|
||||||
|
be omitted from the generated SQL.
|
||||||
|
|
||||||
Sometimes it is convenient to construct a `WHERE` clause that
|
Sometimes it is convenient to construct a `WHERE` clause that
|
||||||
tests several columns for equality, and you might have a Clojure
|
tests several columns for equality, and you might have a Clojure
|
||||||
hash map containing those values. `honey.sql/map=` exists to
|
hash map containing those values. `honey.sql/map=` exists to
|
||||||
|
|
@ -1091,6 +1112,11 @@ user=> (sql/format '{select (*) from (table)
|
||||||
["SELECT * FROM table GROUP BY status, YEAR(created_date)"]
|
["SELECT * FROM table GROUP BY status, YEAR(created_date)"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You can `GROUP BY` expressions, column names (`:col1`), or table and column (`:table.col1`),
|
||||||
|
or aliases (`:some.alias`). Since there is ambiguity between the formatting
|
||||||
|
of those, you can use the special syntax `[:alias :some.thing]` to tell
|
||||||
|
HoneySQL to treat `:some.thing` as an alias instead of a table/column name.
|
||||||
|
|
||||||
## having
|
## having
|
||||||
|
|
||||||
The `:having` clause works identically to `:where` above
|
The `:having` clause works identically to `:where` above
|
||||||
|
|
@ -1205,12 +1231,15 @@ user=> (sql/format {:select [[[:over
|
||||||
|
|
||||||
## order-by
|
## order-by
|
||||||
|
|
||||||
`:order-by` accepts a sequence of one or more ordering
|
`:order-by` accepts a sequence of zero or more ordering
|
||||||
expressions. Each ordering expression is either a simple
|
expressions. Each ordering expression is either a simple
|
||||||
SQL entity or a pair of a SQL expression and a direction
|
SQL entity or a pair of a SQL expression and a direction
|
||||||
(which can be `:asc`, `:desc`, `:nulls-first`, `:desc-null-last`,
|
(which can be `:asc`, `:desc`, `:nulls-first`, `:desc-null-last`,
|
||||||
etc -- or the symbol equivalent).
|
etc -- or the symbol equivalent).
|
||||||
|
|
||||||
|
If `:order-by` is given an empty sequence, the `ORDER BY` clause will
|
||||||
|
be omitted from the generated SQL.
|
||||||
|
|
||||||
If you want to order by an expression, you should wrap it
|
If you want to order by an expression, you should wrap it
|
||||||
as a pair with a direction:
|
as a pair with a direction:
|
||||||
|
|
||||||
|
|
@ -1421,7 +1450,7 @@ user=> (sql/format {:insert-into :table
|
||||||
:values [{:a 1 :b 2 :c 3}
|
:values [{:a 1 :b 2 :c 3}
|
||||||
:default
|
:default
|
||||||
{:a 4 :b 5 :c 6}]})
|
{:a 4 :b 5 :c 6}]})
|
||||||
["INSERT INTO table (a, b, c) VALUES (?, ?, ?), DEFAULT, (?, ?, ?)" 6 5 4]
|
["INSERT INTO table (a, b, c) VALUES (?, ?, ?), DEFAULT, (?, ?, ?)" 1 2 3 4 5 6]
|
||||||
user=> (sql/format {:insert-into :table
|
user=> (sql/format {:insert-into :table
|
||||||
:values [[1 2 3] :default [4 5 6]]})
|
:values [[1 2 3] :default [4 5 6]]})
|
||||||
["INSERT INTO table VALUES (?, ?, ?), DEFAULT, (?, ?, ?)" 1 2 3 4 5 6]
|
["INSERT INTO table VALUES (?, ?, ?), DEFAULT, (?, ?, ?)" 1 2 3 4 5 6]
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ The DSL itself -- the data structures that both versions convert to SQL and para
|
||||||
If you are using Clojure 1.11, you can invoke `format` with a mixture of named arguments and a trailing hash
|
If you are using Clojure 1.11, you can invoke `format` with a mixture of named arguments and a trailing hash
|
||||||
map of additional options, if you wish.
|
map of additional options, if you wish.
|
||||||
|
|
||||||
HoneySQL 1.x supported Clojure 1.7 and later. HoneySQL 2.x requires Clojure 1.9 or later.
|
HoneySQL 1.x supported Clojure 1.7 and later. HoneySQL 2.7.y requires Clojure 1.10.3 or later. Earlier versions of HoneySQL 2.x support Clojure 1.9.0.
|
||||||
|
|
||||||
## Group, Artifact, and Namespaces
|
## Group, Artifact, and Namespaces
|
||||||
|
|
||||||
|
|
@ -63,7 +63,7 @@ Supported Clojure versions: 1.7 and later.
|
||||||
In `deps.edn`:
|
In `deps.edn`:
|
||||||
<!-- :test-doc-blocks/skip -->
|
<!-- :test-doc-blocks/skip -->
|
||||||
```clojure
|
```clojure
|
||||||
com.github.seancorfield/honeysql {:mvn/version "2.6.1243"}
|
com.github.seancorfield/honeysql {:mvn/version "2.7.1295"}
|
||||||
```
|
```
|
||||||
|
|
||||||
Required as:
|
Required as:
|
||||||
|
|
@ -90,7 +90,7 @@ The new namespaces are:
|
||||||
* `honey.sql` -- the primary API (just `format` now),
|
* `honey.sql` -- the primary API (just `format` now),
|
||||||
* `honey.sql.helpers` -- helper functions to build the DSL.
|
* `honey.sql.helpers` -- helper functions to build the DSL.
|
||||||
|
|
||||||
Supported Clojure versions: 1.9 and later.
|
Supported Clojure versions: 1.10.3 and later.
|
||||||
|
|
||||||
## API Changes
|
## API Changes
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,14 @@ For the Clojure CLI, add the following dependency to your `deps.edn` file:
|
||||||
|
|
||||||
<!-- :test-doc-blocks/skip -->
|
<!-- :test-doc-blocks/skip -->
|
||||||
```clojure
|
```clojure
|
||||||
com.github.seancorfield/honeysql {:mvn/version "2.6.1243"}
|
com.github.seancorfield/honeysql {:mvn/version "2.7.1295"}
|
||||||
```
|
```
|
||||||
|
|
||||||
For Leiningen, add the following dependency to your `project.clj` file:
|
For Leiningen, add the following dependency to your `project.clj` file:
|
||||||
|
|
||||||
<!-- :test-doc-blocks/skip -->
|
<!-- :test-doc-blocks/skip -->
|
||||||
```clojure
|
```clojure
|
||||||
[com.github.seancorfield/honeysql "2.6.1243"]
|
[com.github.seancorfield/honeysql "2.7.1295"]
|
||||||
```
|
```
|
||||||
|
|
||||||
HoneySQL produces SQL statements but does not execute them.
|
HoneySQL produces SQL statements but does not execute them.
|
||||||
|
|
@ -26,7 +26,7 @@ To execute SQL statements, you will also need a JDBC wrapper like
|
||||||
|
|
||||||
You can also experiment with HoneySQL directly in a browser -- no installation
|
You can also experiment with HoneySQL directly in a browser -- no installation
|
||||||
required -- using [John Shaffer](https://github.com/john-shaffer)'s awesome
|
required -- using [John Shaffer](https://github.com/john-shaffer)'s awesome
|
||||||
[HoneySQL web app](https://www.john-shaffer.com/honeysql/), written in ClojureScript!
|
[HoneySQL web app](https://john.shaffe.rs/honeysql/), written in ClojureScript!
|
||||||
|
|
||||||
## Basic Concepts
|
## Basic Concepts
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,14 @@ and strings.
|
||||||
:from :b
|
:from :b
|
||||||
:order-by [[[:alias :'some-alias]]]})
|
:order-by [[[:alias :'some-alias]]]})
|
||||||
;;=> ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"]
|
;;=> ["SELECT column_name AS \"some-alias\" FROM b ORDER BY \"some-alias\" ASC"]
|
||||||
|
(sql/format {:select [[:column-name "some-alias"]]
|
||||||
|
:from :b
|
||||||
|
:group-by [[:alias "some-alias"]]})
|
||||||
|
;;=> ["SELECT column_name AS \"some-alias\" FROM b GROUP BY \"some-alias\""]
|
||||||
|
(sql/format {:select [[:column-name "some-alias"]]
|
||||||
|
:from :b
|
||||||
|
:group-by [[:alias :'some-alias]]})
|
||||||
|
;;=> ["SELECT column_name AS \"some-alias\" FROM b GROUP BY \"some-alias\""]
|
||||||
```
|
```
|
||||||
|
|
||||||
## array
|
## array
|
||||||
|
|
@ -80,6 +88,29 @@ In the subquery case, produces `ARRAY(subquery)`:
|
||||||
;;=> ["SELECT ARRAY(SELECT * FROM table) AS arr"]
|
;;=> ["SELECT ARRAY(SELECT * FROM table) AS arr"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## at
|
||||||
|
|
||||||
|
If addition to dot navigation (for JSON) -- see the `.` and `.:.` syntax below --
|
||||||
|
HoneySQL also supports bracket notation for JSON navigation.
|
||||||
|
|
||||||
|
The first argument to `:at` is treated as an expression that identifies
|
||||||
|
the column, and subsequent arguments are treated as field names or array
|
||||||
|
indices to navigate into that document.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
user=> (sql/format {:select [[[:at :col :field1 :field2]]]})
|
||||||
|
["SELECT col.field1.field2"]
|
||||||
|
user=> (sql/format {:select [[[:at :table.col 0 :field]]]})
|
||||||
|
["SELECT table.col[0].field"]
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want an array index to be a parameter, use `:lift`:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
user=> (sql/format {:select [[[:at :col [:lift 0] :field]]]})
|
||||||
|
["SELECT col[?].field" 0]
|
||||||
|
```
|
||||||
|
|
||||||
## at time zone
|
## at time zone
|
||||||
|
|
||||||
Accepts two arguments: an expression (assumed to be a date/time of some sort)
|
Accepts two arguments: an expression (assumed to be a date/time of some sort)
|
||||||
|
|
@ -203,15 +234,23 @@ Accepts a single expression and prefixes it with `DISTINCT `:
|
||||||
;;=> ["SELECT COUNT(DISTINCT status) AS n FROM table"]
|
;;=> ["SELECT COUNT(DISTINCT status) AS n FROM table"]
|
||||||
```
|
```
|
||||||
|
|
||||||
## dot .
|
## dot . .:.
|
||||||
|
|
||||||
Accepts an expression and a field (or column) selection:
|
Accepts an expression and one or more fields (or columns). Plain dot produces
|
||||||
|
plain dotted selection:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(sql/format {:select [ [[:. :t :c]] [[:. :s :t :c]] ]})
|
(sql/format {:select [ [[:. :t :c]] [[:. :s :t :c]] ]})
|
||||||
;;=> ["SELECT t.c, s.t.c"]
|
;;=> ["SELECT t.c, s.t.c"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Dot colon dot produces Snowflake-style dotted selection:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(sql/format {:select [ [[:.:. :t :c]] [[:.:. :s :t :c]] ]})
|
||||||
|
;;=> ["SELECT t:c, s:t.c"]
|
||||||
|
```
|
||||||
|
|
||||||
Can be used with `:nest` for field selection from composites:
|
Can be used with `:nest` for field selection from composites:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
|
|
@ -219,6 +258,9 @@ Can be used with `:nest` for field selection from composites:
|
||||||
;;=> ["SELECT (v).*, (MYFUNC(x)).y"]
|
;;=> ["SELECT (v).*, (MYFUNC(x)).y"]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See also [`get-in`](xtdb.md#object-navigation-expressions)
|
||||||
|
and [`at`](#at) for additional path navigation functions.
|
||||||
|
|
||||||
## entity
|
## entity
|
||||||
|
|
||||||
Accepts a single keyword or symbol argument and produces a
|
Accepts a single keyword or symbol argument and produces a
|
||||||
|
|
|
||||||
11
doc/xtdb.md
11
doc/xtdb.md
|
|
@ -207,3 +207,14 @@ user=> (sql/format {:patch-into :foo
|
||||||
:records [{:_id 1 :status "active"}]})
|
:records [{:_id 1 :status "active"}]})
|
||||||
["PATCH INTO foo RECORDS ?" {:_id 1, :status "active"}]
|
["PATCH INTO foo RECORDS ?" {:_id 1, :status "active"}]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## `assert`
|
||||||
|
|
||||||
|
XTDB supports an `ASSERT` operation that will throw an exception if the
|
||||||
|
asserted predicate is not true:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
user=> (sql/format '{assert (not-exists {select 1 from users where (= email "james @example.com")})}
|
||||||
|
:inline true)
|
||||||
|
["ASSERT NOT EXISTS (SELECT 1 FROM users WHERE email = 'james @example.com')"]
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
;; copyright (c) 2020-2024 sean corfield, all rights reserved
|
;; copyright (c) 2020-2025 sean corfield, all rights reserved
|
||||||
|
|
||||||
(ns honey.sql
|
(ns honey.sql
|
||||||
"Primary API for HoneySQL 2.x.
|
"Primary API for HoneySQL 2.x.
|
||||||
|
|
@ -31,7 +31,9 @@
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
#?(:clj [clojure.template])
|
#?(:clj [clojure.template])
|
||||||
[honey.sql.protocols :as p]
|
[honey.sql.protocols :as p]
|
||||||
[honey.sql.util :refer [str join]]))
|
[honey.sql.util :refer [str join split-by-separator into*]]))
|
||||||
|
|
||||||
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
;; default formatting for known clauses
|
;; default formatting for known clauses
|
||||||
|
|
||||||
|
|
@ -57,7 +59,7 @@
|
||||||
;; then SQL clauses in priority order:
|
;; then SQL clauses in priority order:
|
||||||
:setting
|
:setting
|
||||||
:raw :nest :with :with-recursive :intersect :union :union-all :except :except-all
|
:raw :nest :with :with-recursive :intersect :union :union-all :except :except-all
|
||||||
:table
|
:table :assert ; #567 XTDB
|
||||||
:select :select-distinct :select-distinct-on :select-top :select-distinct-top
|
:select :select-distinct :select-distinct-on :select-top :select-distinct-top
|
||||||
:distinct :expr :exclude :rename
|
:distinct :expr :exclude :rename
|
||||||
:into :bulk-collect-into
|
:into :bulk-collect-into
|
||||||
|
|
@ -112,7 +114,8 @@
|
||||||
(assoc m k (assoc v :dialect k)))
|
(assoc m k (assoc v :dialect k)))
|
||||||
{}
|
{}
|
||||||
{:ansi {:quote #(strop "\"" % "\"")}
|
{:ansi {:quote #(strop "\"" % "\"")}
|
||||||
:sqlserver {:quote #(strop "[" % "]")}
|
:sqlserver {:quote #(strop "[" % "]")
|
||||||
|
:auto-lift-boolean true}
|
||||||
:mysql {:quote #(strop "`" % "`")
|
:mysql {:quote #(strop "`" % "`")
|
||||||
:clause-order-fn
|
:clause-order-fn
|
||||||
#(add-clause-before % :set :where)}
|
#(add-clause-before % :set :where)}
|
||||||
|
|
@ -226,7 +229,8 @@
|
||||||
|
|
||||||
Hyphens at the start or end of a string should not be touched."
|
Hyphens at the start or end of a string should not be touched."
|
||||||
[s]
|
[s]
|
||||||
(str/replace s #"(\w)-(?=\w)" "$1 "))
|
(cond-> s
|
||||||
|
(str/includes? s "-") (str/replace #"(\w)-(?=\w)" "$1 ")))
|
||||||
|
|
||||||
(defn- namespace-_
|
(defn- namespace-_
|
||||||
"Return the namespace portion of a symbol, with dashes converted."
|
"Return the namespace portion of a symbol, with dashes converted."
|
||||||
|
|
@ -315,7 +319,7 @@
|
||||||
[n %]
|
[n %]
|
||||||
(if aliased
|
(if aliased
|
||||||
[%]
|
[%]
|
||||||
(str/split % #"\."))))
|
(split-by-separator % "."))))
|
||||||
parts (parts-fn col-e)
|
parts (parts-fn col-e)
|
||||||
entity (join "." (map #(cond-> % (not= "*" %) (quote-fn))) parts)]
|
entity (join "." (map #(cond-> % (not= "*" %) (quote-fn))) parts)]
|
||||||
(suspicious-entity-check entity)
|
(suspicious-entity-check entity)
|
||||||
|
|
@ -366,6 +370,20 @@
|
||||||
(keyword (name s)))
|
(keyword (name s)))
|
||||||
s))
|
s))
|
||||||
|
|
||||||
|
(defn- kw->sym
|
||||||
|
"Given a keyword, produce a symbol, retaining the namespace
|
||||||
|
qualifier, if any."
|
||||||
|
[k]
|
||||||
|
(if (keyword? k)
|
||||||
|
#?(:bb (if-let [n (namespace k)]
|
||||||
|
(symbol n (name k))
|
||||||
|
(symbol (name k)))
|
||||||
|
:clj (.sym ^clojure.lang.Keyword k)
|
||||||
|
:default (if-let [n (namespace k)]
|
||||||
|
(symbol n (name k))
|
||||||
|
(symbol (name k))))
|
||||||
|
k))
|
||||||
|
|
||||||
(defn- inline-map [x & [open close]]
|
(defn- inline-map [x & [open close]]
|
||||||
(str (or open "{")
|
(str (or open "{")
|
||||||
(join ", " (map (fn [[k v]]
|
(join ", " (map (fn [[k v]]
|
||||||
|
|
@ -432,8 +450,8 @@
|
||||||
(defn- format-simple-var
|
(defn- format-simple-var
|
||||||
([x]
|
([x]
|
||||||
(let [c (if (keyword? x)
|
(let [c (if (keyword? x)
|
||||||
#?(:bb (str (symbol x))
|
#?(:bb (subs (str x) 1)
|
||||||
:clj (str (.sym ^clojure.lang.Keyword x)) ;; Omits leading colon
|
:clj (str (.sym ^clojure.lang.Keyword x))
|
||||||
:default (subs (str x) 1))
|
:default (subs (str x) 1))
|
||||||
(str x))]
|
(str x))]
|
||||||
(format-simple-var x c {})))
|
(format-simple-var x c {})))
|
||||||
|
|
@ -441,8 +459,8 @@
|
||||||
(if (str/starts-with? c "'")
|
(if (str/starts-with? c "'")
|
||||||
(do
|
(do
|
||||||
(reset! *formatted-column* true)
|
(reset! *formatted-column* true)
|
||||||
[(subs c 1)])
|
(subs c 1))
|
||||||
[(format-entity x opts)])))
|
(format-entity x opts))))
|
||||||
|
|
||||||
(defn- format-var
|
(defn- format-var
|
||||||
([x] (format-var x {}))
|
([x] (format-var x {}))
|
||||||
|
|
@ -451,12 +469,12 @@
|
||||||
;; for multiple / in the %fun.call case so that
|
;; for multiple / in the %fun.call case so that
|
||||||
;; qualified column names can be used:
|
;; qualified column names can be used:
|
||||||
(let [c (if (keyword? x)
|
(let [c (if (keyword? x)
|
||||||
#?(:bb (str (symbol x))
|
#?(:bb (subs (str x) 1)
|
||||||
:clj (str (.sym ^clojure.lang.Keyword x)) ;; Omits leading colon
|
:clj (str (.sym ^clojure.lang.Keyword x))
|
||||||
:default (subs (str x) 1))
|
:default (subs (str x) 1))
|
||||||
(str x))]
|
(str x))]
|
||||||
(cond (str/starts-with? c "%")
|
(cond (str/starts-with? c "%")
|
||||||
(let [[f & args] (str/split (subs c 1) #"\.")]
|
(let [[f & args] (split-by-separator (subs c 1) ".")]
|
||||||
[(str (format-fn-name f) "("
|
[(str (format-fn-name f) "("
|
||||||
(join ", " (map #(format-entity (keyword %) opts)) args)
|
(join ", " (map #(format-entity (keyword %) opts)) args)
|
||||||
")")])
|
")")])
|
||||||
|
|
@ -469,7 +487,7 @@
|
||||||
:else
|
:else
|
||||||
["?" (->param k)]))
|
["?" (->param k)]))
|
||||||
:else
|
:else
|
||||||
(format-simple-var x c opts)))))
|
[(format-simple-var x c opts)]))))
|
||||||
|
|
||||||
(defn- format-entity-alias [x]
|
(defn- format-entity-alias [x]
|
||||||
(cond (sequential? x)
|
(cond (sequential? x)
|
||||||
|
|
@ -524,14 +542,10 @@
|
||||||
:else
|
:else
|
||||||
(throw (ex-info "bigquery * only supports except and replace"
|
(throw (ex-info "bigquery * only supports except and replace"
|
||||||
{:clause k :arg arg})))]
|
{:clause k :arg arg})))]
|
||||||
(-> [(cond->> sql' sql (str sql " "))]
|
(into* [(cond->> sql' sql (str sql " "))] params params')))
|
||||||
(into params)
|
|
||||||
(into params'))))
|
|
||||||
[]
|
[]
|
||||||
(partition-all 2 x))]
|
(partition-all 2 x))]
|
||||||
(-> [(str sql " " sql')]
|
(into* [(str sql " " sql')] params params')))
|
||||||
(into params)
|
|
||||||
(into params'))))
|
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(bigquery-*-except-replace? [:* :except [:a :b :c]])
|
(bigquery-*-except-replace? [:* :except [:a :b :c]])
|
||||||
|
|
@ -675,9 +689,7 @@
|
||||||
sql'))
|
sql'))
|
||||||
(when hints
|
(when hints
|
||||||
(str " WITH (" hints ")")))]
|
(str " WITH (" hints ")")))]
|
||||||
(into params)
|
(into* params params' params'')))))
|
||||||
(into params')
|
|
||||||
(into params'')))))
|
|
||||||
|
|
||||||
(defn- format-selectable-dsl
|
(defn- format-selectable-dsl
|
||||||
([x] (format-selectable-dsl x {}))
|
([x] (format-selectable-dsl x {}))
|
||||||
|
|
@ -751,9 +763,8 @@
|
||||||
(let [[cur & params] (peek result)
|
(let [[cur & params] (peek result)
|
||||||
[sql & params'] (first exprs)]
|
[sql & params'] (first exprs)]
|
||||||
(recur (rest exprs) args' false (conj (pop result)
|
(recur (rest exprs) args' false (conj (pop result)
|
||||||
(-> [(str cur " " sql)]
|
(into* [(str cur " " sql)]
|
||||||
(into params)
|
params params'))))
|
||||||
(into params')))))
|
|
||||||
(recur (rest exprs) args' false (conj result (first exprs))))))
|
(recur (rest exprs) args' false (conj result (first exprs))))))
|
||||||
(reduce-sql result)))))
|
(reduce-sql result)))))
|
||||||
|
|
||||||
|
|
@ -835,7 +846,7 @@
|
||||||
(str (sql-kw :select) " " sql)
|
(str (sql-kw :select) " " sql)
|
||||||
true
|
true
|
||||||
cols)]
|
cols)]
|
||||||
(-> [sql'] (into params) (into params'))))
|
(into* [sql'] params params')))
|
||||||
|
|
||||||
(defn- format-select-top [k xs]
|
(defn- format-select-top [k xs]
|
||||||
(let [[top & cols] xs
|
(let [[top & cols] xs
|
||||||
|
|
@ -863,7 +874,7 @@
|
||||||
(join " " (map sql-kw) parts))
|
(join " " (map sql-kw) parts))
|
||||||
true
|
true
|
||||||
cols)]
|
cols)]
|
||||||
(-> [sql'] (into params) (into params'))))
|
(into* [sql'] params params')))
|
||||||
|
|
||||||
(defn- format-select-into [k xs]
|
(defn- format-select-into [k xs]
|
||||||
(let [[v e] (ensure-sequential xs)
|
(let [[v e] (ensure-sequential xs)
|
||||||
|
|
@ -949,25 +960,20 @@
|
||||||
(map
|
(map
|
||||||
(fn [[x expr & tail :as with]]
|
(fn [[x expr & tail :as with]]
|
||||||
(let [[sql & params] (format-with-part x)
|
(let [[sql & params] (format-with-part x)
|
||||||
non-query-expr? (or (ident? expr) (string? expr))
|
non-query-expr? (not (map? expr))
|
||||||
[sql' & params'] (if non-query-expr?
|
[sql' & params'] (if non-query-expr?
|
||||||
(format-expr expr)
|
(format-expr expr)
|
||||||
(format-dsl expr))
|
(format-dsl expr))
|
||||||
[sql'' & params'' :as sql-params'']
|
[sql'' & params'' :as sql-params'']
|
||||||
(if non-query-expr?
|
(if non-query-expr?
|
||||||
(cond-> [(str sql' " AS " sql)]
|
(into* [(str sql' " AS " sql)] params' params)
|
||||||
params' (into params')
|
|
||||||
params (into params))
|
|
||||||
;; according to docs, CTE should _always_ be wrapped:
|
;; according to docs, CTE should _always_ be wrapped:
|
||||||
(cond-> [(str sql " " (as-fn with) " " (str "(" sql' ")"))]
|
(into* [(str sql " " (as-fn with) " " (str "(" sql' ")"))]
|
||||||
params (into params)
|
params params'))
|
||||||
params' (into params')))
|
|
||||||
[tail-sql & tail-params]
|
[tail-sql & tail-params]
|
||||||
(format-with-query-tail tail)]
|
(format-with-query-tail tail)]
|
||||||
(if (seq tail-sql)
|
(if (seq tail-sql)
|
||||||
(cond-> [(str sql'' " " tail-sql)]
|
(into* [(str sql'' " " tail-sql)] params'' tail-params)
|
||||||
params'' (into params'')
|
|
||||||
tail-params (into tail-params))
|
|
||||||
sql-params''))))
|
sql-params''))))
|
||||||
xs)]
|
xs)]
|
||||||
(into [(str (sql-kw k) " " (join ", " sqls))] params)))
|
(into [(str (sql-kw k) " " (join ", " sqls))] params)))
|
||||||
|
|
@ -1011,10 +1017,7 @@
|
||||||
(str cols-sql' " "))
|
(str cols-sql' " "))
|
||||||
overriding
|
overriding
|
||||||
sql)]
|
sql)]
|
||||||
(into t-params)
|
(into* t-params c-params cols-params' params)))
|
||||||
(into c-params)
|
|
||||||
(into cols-params')
|
|
||||||
(into params)))
|
|
||||||
(sequential? (second table))
|
(sequential? (second table))
|
||||||
(let [[table cols] table
|
(let [[table cols] table
|
||||||
[t-sql & t-params] (format-entity-alias table)
|
[t-sql & t-params] (format-entity-alias table)
|
||||||
|
|
@ -1024,23 +1027,20 @@
|
||||||
(join ", " c-sqls)
|
(join ", " c-sqls)
|
||||||
")"
|
")"
|
||||||
overriding)]
|
overriding)]
|
||||||
(into t-params)
|
(into* t-params c-params)))
|
||||||
(into c-params)))
|
|
||||||
:else
|
:else
|
||||||
(let [[sql & params] (format-entity-alias table)]
|
(let [[sql & params] (format-entity-alias table)]
|
||||||
(-> [(str (sql-kw k) " " sql
|
(-> [(str (sql-kw k) " " sql
|
||||||
(when (seq cols')
|
(when (seq cols')
|
||||||
(str " " cols-sql'))
|
(str " " cols-sql'))
|
||||||
overriding)]
|
overriding)]
|
||||||
(into cols-params')
|
(into* cols-params' params))))
|
||||||
(into params))))
|
|
||||||
(let [[sql & params] (format-entity-alias table)]
|
(let [[sql & params] (format-entity-alias table)]
|
||||||
(-> [(str (sql-kw k) " " sql
|
(-> [(str (sql-kw k) " " sql
|
||||||
(when (seq cols')
|
(when (seq cols')
|
||||||
(str " " cols-sql'))
|
(str " " cols-sql'))
|
||||||
overriding)]
|
overriding)]
|
||||||
(into cols-params')
|
(into* cols-params' params))))))
|
||||||
(into params))))))
|
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(format-insert :insert-into [[[:raw ":foo"]] {:select :bar}])
|
(format-insert :insert-into [[[:raw ":foo"]] {:select :bar}])
|
||||||
|
|
@ -1068,12 +1068,10 @@
|
||||||
(str "("
|
(str "("
|
||||||
(join ", " u-sqls)
|
(join ", " u-sqls)
|
||||||
")"))
|
")"))
|
||||||
(-> params (into params-j) (into u-params))])
|
(into* params params-j u-params)])
|
||||||
(let [[sql & params'] (when e (format-expr e))]
|
(let [[sql & params'] (when e (format-expr e))]
|
||||||
[(cond-> sqls e (conj "ON" sql))
|
[(cond-> sqls e (conj "ON" sql))
|
||||||
(-> params
|
(into* params params-j params')])))))
|
||||||
(into params-j)
|
|
||||||
(into params'))])))))
|
|
||||||
[[] []]
|
[[] []]
|
||||||
clauses)]
|
clauses)]
|
||||||
(into [(join " " sqls)] params)))
|
(into [(join " " sqls)] params)))
|
||||||
|
|
@ -1135,11 +1133,13 @@
|
||||||
dirs (map #(when (sequential? %) (second %)) xs)
|
dirs (map #(when (sequential? %) (second %)) xs)
|
||||||
[sqls params]
|
[sqls params]
|
||||||
(format-expr-list (map #(if (sequential? %) (first %) %) xs))]
|
(format-expr-list (map #(if (sequential? %) (first %) %) xs))]
|
||||||
|
(if (seq sqls)
|
||||||
(into [(str (sql-kw k) " "
|
(into [(str (sql-kw k) " "
|
||||||
(join ", " (map (fn [sql dir]
|
(join ", " (map (fn [sql dir]
|
||||||
(str sql " " (sql-kw (or dir :asc))))
|
(str sql " " (sql-kw (or dir :asc))))
|
||||||
sqls
|
sqls
|
||||||
dirs)))] params)))
|
dirs)))] params)
|
||||||
|
[])))
|
||||||
|
|
||||||
(defn- format-lock-strength [k xs]
|
(defn- format-lock-strength [k xs]
|
||||||
(let [[strength tables nowait] (ensure-sequential xs)]
|
(let [[strength tables nowait] (ensure-sequential xs)]
|
||||||
|
|
@ -1195,17 +1195,17 @@
|
||||||
x))
|
x))
|
||||||
xs)))
|
xs)))
|
||||||
[sqls params]
|
[sqls params]
|
||||||
(reduce (fn [[sql params] [sqls' params']]
|
(reduce (fn [[sql params] x]
|
||||||
|
(if (sequential? x)
|
||||||
|
(let [[sqls' params'] (format-expr-list x)]
|
||||||
[(conj sql
|
[(conj sql
|
||||||
(if (sequential? sqls')
|
(if (sequential? sqls')
|
||||||
(str "(" (join ", " sqls') ")")
|
(str "(" (join ", " sqls') ")")
|
||||||
sqls'))
|
sqls'))
|
||||||
(into params params')])
|
(into* params params')])
|
||||||
|
[(conj sql (sql-kw x)) params]))
|
||||||
[[] []]
|
[[] []]
|
||||||
(map #(if (sequential? %)
|
xs')
|
||||||
(format-expr-list %)
|
|
||||||
[(sql-kw %)])
|
|
||||||
xs'))
|
|
||||||
sqls (if row-ctr (map #(str "ROW" %) sqls) sqls)]
|
sqls (if row-ctr (map #(str "ROW" %) sqls) sqls)]
|
||||||
(into [(str (sql-kw k) " " (join ", " sqls))] params))
|
(into [(str (sql-kw k) " " (join ", " sqls))] params))
|
||||||
|
|
||||||
|
|
@ -1217,26 +1217,26 @@
|
||||||
(contains-clause? :replace-into)
|
(contains-clause? :replace-into)
|
||||||
(contains-clause? :columns)))
|
(contains-clause? :columns)))
|
||||||
[sqls params]
|
[sqls params]
|
||||||
(reduce (fn [[sql params] [sqls' params']]
|
(reduce
|
||||||
[(conj sql
|
(fn [[sql params] x]
|
||||||
(if (sequential? sqls')
|
(if (map? x)
|
||||||
(str "(" (join ", " sqls') ")")
|
(let [[sqls' params']
|
||||||
sqls'))
|
(reduce-sql (map #(format-expr
|
||||||
(if params' (into params params') params')])
|
(get x %
|
||||||
[[] []]
|
|
||||||
(map (fn [m]
|
|
||||||
(if (map? m)
|
|
||||||
(format-expr-list
|
|
||||||
(map #(get m
|
|
||||||
%
|
|
||||||
;; issue #366: use NULL or DEFAULT
|
;; issue #366: use NULL or DEFAULT
|
||||||
;; for missing column values:
|
;; for missing column values:
|
||||||
(if (contains? *values-default-columns* %)
|
(if (contains? *values-default-columns* %)
|
||||||
[:default]
|
[:default]
|
||||||
nil))
|
nil))))
|
||||||
cols))
|
cols)]
|
||||||
[(sql-kw m)]))
|
[(conj sql
|
||||||
xs))]
|
(if (sequential? sqls')
|
||||||
|
(str "(" (join ", " sqls') ")")
|
||||||
|
sqls'))
|
||||||
|
(into* params params')])
|
||||||
|
[(conj sql (sql-kw x)) params]))
|
||||||
|
[[] []]
|
||||||
|
xs)]
|
||||||
(into [(str (when cols-sql
|
(into [(str (when cols-sql
|
||||||
(str cols-sql " "))
|
(str cols-sql " "))
|
||||||
(sql-kw k)
|
(sql-kw k)
|
||||||
|
|
@ -1281,8 +1281,7 @@
|
||||||
(str " (" (join ", " sqls) ")"))
|
(str " (" (join ", " sqls) ")"))
|
||||||
(when sql
|
(when sql
|
||||||
(str " " sql)))]
|
(str " " sql)))]
|
||||||
(into expr-params)
|
(into* expr-params clause-params)))
|
||||||
(into clause-params)))
|
|
||||||
(format-on-conflict k [x])))
|
(format-on-conflict k [x])))
|
||||||
|
|
||||||
(defn- format-do-update-set [k x]
|
(defn- format-do-update-set [k x]
|
||||||
|
|
@ -1301,8 +1300,7 @@
|
||||||
where (or (:where x) ('where x))
|
where (or (:where x) ('where x))
|
||||||
[sql & params] (when where (format-dsl {:where where}))]
|
[sql & params] (when where (format-dsl {:where where}))]
|
||||||
(-> [(str sets (when sql (str " " sql)))]
|
(-> [(str sets (when sql (str " " sql)))]
|
||||||
(into set-params)
|
(into* set-params params)))
|
||||||
(into params)))
|
|
||||||
(format-set-exprs k x))
|
(format-set-exprs k x))
|
||||||
(sequential? x)
|
(sequential? x)
|
||||||
(let [[cols clauses] (split-with (complement map?) x)]
|
(let [[cols clauses] (split-with (complement map?) x)]
|
||||||
|
|
@ -1390,20 +1388,17 @@
|
||||||
[(butlast coll) (last coll) nil]))]
|
[(butlast coll) (last coll) nil]))]
|
||||||
(into [(join " " (map sql-kw) prequel)
|
(into [(join " " (map sql-kw) prequel)
|
||||||
(when table
|
(when table
|
||||||
(let [[v & more] (format-simple-var table)]
|
(format-simple-var table))
|
||||||
(when (seq more)
|
|
||||||
(throw (ex-info (str "DDL syntax error at: "
|
|
||||||
(pr-str table)
|
|
||||||
" - expected table name")
|
|
||||||
{:unexpected more})))
|
|
||||||
v))
|
|
||||||
(when ine (sql-kw ine))]
|
(when ine (sql-kw ine))]
|
||||||
(when opts
|
(when opts
|
||||||
(format-ddl-options opts context)))))
|
(format-ddl-options opts context)))))
|
||||||
|
|
||||||
(defn- format-truncate [_ xs]
|
(defn- format-truncate [_ xs]
|
||||||
(let [[table & options] (ensure-sequential xs)
|
(let [[table & options] (ensure-sequential xs)
|
||||||
[pre table ine options] (destructure-ddl-item [table options] "truncate")]
|
table (if (or (ident? table) (string? table))
|
||||||
|
(format-simple-var table)
|
||||||
|
(join ", " (map format-simple-var table)))
|
||||||
|
[pre _ ine options] (destructure-ddl-item [nil options] "truncate")]
|
||||||
(when (seq pre) (throw (ex-info "TRUNCATE syntax error" {:unexpected pre})))
|
(when (seq pre) (throw (ex-info "TRUNCATE syntax error" {:unexpected pre})))
|
||||||
(when (seq ine) (throw (ex-info "TRUNCATE syntax error" {:unexpected ine})))
|
(when (seq ine) (throw (ex-info "TRUNCATE syntax error" {:unexpected ine})))
|
||||||
[(join " " (cond-> ["TRUNCATE TABLE" table]
|
[(join " " (cond-> ["TRUNCATE TABLE" table]
|
||||||
|
|
@ -1416,6 +1411,11 @@
|
||||||
(destructure-ddl-item [:id [:int :unsigned :auto-increment]] "test")
|
(destructure-ddl-item [:id [:int :unsigned :auto-increment]] "test")
|
||||||
(destructure-ddl-item [[[:foreign-key :bar]] :quux [[:wibble :wobble]]] "test")
|
(destructure-ddl-item [[[:foreign-key :bar]] :quux [[:wibble :wobble]]] "test")
|
||||||
(format-truncate :truncate [:foo])
|
(format-truncate :truncate [:foo])
|
||||||
|
(format-truncate :truncate ["foo, bar"])
|
||||||
|
(format-truncate :truncate "foo, bar")
|
||||||
|
(format-truncate :truncate [[:foo :bar]])
|
||||||
|
(format-truncate :truncate :foo)
|
||||||
|
(format {:truncate [[:foo] :x]})
|
||||||
)
|
)
|
||||||
|
|
||||||
(defn- format-create [q k item as]
|
(defn- format-create [q k item as]
|
||||||
|
|
@ -1434,10 +1434,12 @@
|
||||||
(defn- format-create-index [k clauses]
|
(defn- format-create-index [k clauses]
|
||||||
(let [[index-spec [table & exprs]] clauses
|
(let [[index-spec [table & exprs]] clauses
|
||||||
[pre entity ine & more] (destructure-ddl-item index-spec (str (sql-kw k) " options"))
|
[pre entity ine & more] (destructure-ddl-item index-spec (str (sql-kw k) " options"))
|
||||||
[using & exprs] (if (contains? #{:using-gin 'using-gin}
|
[using & exprs]
|
||||||
(first exprs))
|
(let [item (first exprs)]
|
||||||
|
(if (and (ident? item)
|
||||||
|
(str/starts-with? (str (kw->sym item)) "using-"))
|
||||||
exprs
|
exprs
|
||||||
(cons nil exprs))
|
(cons nil exprs)))
|
||||||
[sqls params] (format-expr-list exprs)]
|
[sqls params] (format-expr-list exprs)]
|
||||||
(into [(join " " (remove empty?)
|
(into [(join " " (remove empty?)
|
||||||
(-> ["CREATE" pre "INDEX" ine entity
|
(-> ["CREATE" pre "INDEX" ine entity
|
||||||
|
|
@ -1672,6 +1674,9 @@
|
||||||
:except #'format-on-set-op
|
:except #'format-on-set-op
|
||||||
:except-all #'format-on-set-op
|
:except-all #'format-on-set-op
|
||||||
:table #'format-selector
|
:table #'format-selector
|
||||||
|
:assert (fn [k xs]
|
||||||
|
(let [[sql & params] (format-expr xs)]
|
||||||
|
(into [(str (sql-kw k) " " sql)] params)))
|
||||||
:select #'format-selects
|
:select #'format-selects
|
||||||
:select-distinct #'format-selects
|
:select-distinct #'format-selects
|
||||||
:select-distinct-on #'format-selects-on
|
:select-distinct-on #'format-selects-on
|
||||||
|
|
@ -1745,16 +1750,6 @@
|
||||||
(set @current-clause-order)
|
(set @current-clause-order)
|
||||||
(set (keys @clause-format))))
|
(set (keys @clause-format))))
|
||||||
|
|
||||||
(defn- kw->sym
|
|
||||||
"Given a keyword, produce a symbol, retaining the namespace
|
|
||||||
qualifier, if any."
|
|
||||||
[k]
|
|
||||||
(if (keyword? k)
|
|
||||||
(if-let [n (namespace k)]
|
|
||||||
(symbol n (name k))
|
|
||||||
(symbol (name k)))
|
|
||||||
k))
|
|
||||||
|
|
||||||
(defn format-dsl
|
(defn format-dsl
|
||||||
"Given a hash map representing a SQL statement and a hash map
|
"Given a hash map representing a SQL statement and a hash map
|
||||||
of options, return a vector containing a string -- the formatted
|
of options, return a vector containing a string -- the formatted
|
||||||
|
|
@ -1782,7 +1777,7 @@
|
||||||
(if (seq leftover)
|
(if (seq leftover)
|
||||||
(throw (ex-info (str "These SQL clauses are unknown or have nil values: "
|
(throw (ex-info (str "These SQL clauses are unknown or have nil values: "
|
||||||
(join ", " (keys leftover))
|
(join ", " (keys leftover))
|
||||||
"(perhaps you need [:lift {"
|
" (perhaps you need [:lift {"
|
||||||
(first (keys leftover))
|
(first (keys leftover))
|
||||||
" ...}] here?)")
|
" ...}] here?)")
|
||||||
leftover))
|
leftover))
|
||||||
|
|
@ -1803,6 +1798,7 @@
|
||||||
"like" "not-like" "regexp" "~" "&&"
|
"like" "not-like" "regexp" "~" "&&"
|
||||||
"ilike" "not-ilike" "similar-to" "not-similar-to"
|
"ilike" "not-ilike" "similar-to" "not-similar-to"
|
||||||
"is" "is-not" "not=" "!=" "regex"
|
"is" "is-not" "not=" "!=" "regex"
|
||||||
|
"is-distinct-from" "is-not-distinct-from"
|
||||||
"with-ordinality"}
|
"with-ordinality"}
|
||||||
(into (map str "+-*%|&^=<>"))
|
(into (map str "+-*%|&^=<>"))
|
||||||
(into (keys infix-aliases))
|
(into (keys infix-aliases))
|
||||||
|
|
@ -1848,23 +1844,18 @@
|
||||||
(= 1 (count params-y))
|
(= 1 (count params-y))
|
||||||
(coll? v1))
|
(coll? v1))
|
||||||
(let [sql (str "(" (join ", " (repeat (count v1) "?")) ")")]
|
(let [sql (str "(" (join ", " (repeat (count v1) "?")) ")")]
|
||||||
(-> [(str sql-x " " (sql-kw in) " " sql)]
|
(into* [(str sql-x " " (sql-kw in) " " sql)] params-x v1))
|
||||||
(into params-x)
|
|
||||||
(into v1)))
|
|
||||||
(and *numbered*
|
(and *numbered*
|
||||||
(= (str "$" (count @*numbered*)) sql-y)
|
(= (str "$" (count @*numbered*)) sql-y)
|
||||||
(= 1 (count params-y))
|
(= 1 (count params-y))
|
||||||
(coll? v1))
|
(coll? v1))
|
||||||
(let [vs (for [v v1] (->numbered v))
|
(let [vs (for [v v1] (->numbered v))
|
||||||
sql (str "(" (join ", " (map first) vs) ")")]
|
sql (str "(" (join ", " (map first) vs) ")")]
|
||||||
(-> [(str sql-x " " (sql-kw in) " " sql)]
|
(into* [(str sql-x " " (sql-kw in) " " sql)]
|
||||||
(into params-x)
|
params-x [nil] (map second vs)))
|
||||||
(conj nil)
|
|
||||||
(into (map second vs))))
|
|
||||||
:else
|
:else
|
||||||
(-> [(str sql-x " " (sql-kw in) " " sql-y)]
|
(into* [(str sql-x " " (sql-kw in) " " sql-y)]
|
||||||
(into params-x)
|
params-x (if *numbered* values params-y)))))
|
||||||
(into (if *numbered* values params-y))))))
|
|
||||||
|
|
||||||
(defn- function-0 [k xs]
|
(defn- function-0 [k xs]
|
||||||
[(str (sql-kw k)
|
[(str (sql-kw k)
|
||||||
|
|
@ -1908,7 +1899,7 @@
|
||||||
(let [[sql-e & params-e] (format-expr e)
|
(let [[sql-e & params-e] (format-expr e)
|
||||||
[sql-c & params-c] (format-dsl c {:nested true})]
|
[sql-c & params-c] (format-dsl c {:nested true})]
|
||||||
[(conj sqls (str sql-e " " (sql-kw k) " " sql-c))
|
[(conj sqls (str sql-e " " (sql-kw k) " " sql-c))
|
||||||
(-> params (into params-e) (into params-c))]))
|
(into* params params-e params-c)]))
|
||||||
[[] []]
|
[[] []]
|
||||||
(partition 2 pairs))]
|
(partition 2 pairs))]
|
||||||
(into [(join ", " sqls)] params)))
|
(into [(join ", " sqls)] params)))
|
||||||
|
|
@ -1927,7 +1918,7 @@
|
||||||
(= 'else condition))
|
(= 'else condition))
|
||||||
(conj sqls (sql-kw :else) sqlv)
|
(conj sqls (sql-kw :else) sqlv)
|
||||||
(conj sqls (sql-kw :when) sqlc (sql-kw :then) sqlv))
|
(conj sqls (sql-kw :when) sqlc (sql-kw :then) sqlv))
|
||||||
(-> params (into paramsc) (into paramsv))]))
|
(into* params paramsc paramsv)]))
|
||||||
[[] []]
|
[[] []]
|
||||||
(partition 2 (if case-expr? (rest clauses) clauses)))]
|
(partition 2 (if case-expr? (rest clauses) clauses)))]
|
||||||
(-> [(str (sql-kw :case) " "
|
(-> [(str (sql-kw :case) " "
|
||||||
|
|
@ -1935,8 +1926,7 @@
|
||||||
(str sqlx " "))
|
(str sqlx " "))
|
||||||
(join " " sqls)
|
(join " " sqls)
|
||||||
" " (sql-kw :end))]
|
" " (sql-kw :end))]
|
||||||
(into paramsx)
|
(into* paramsx params))))
|
||||||
(into params))))
|
|
||||||
|
|
||||||
(defn- between-fn
|
(defn- between-fn
|
||||||
"For both :between and :not-between"
|
"For both :between and :not-between"
|
||||||
|
|
@ -1944,10 +1934,8 @@
|
||||||
(let [[sql-x & params-x] (format-expr x {:nested true})
|
(let [[sql-x & params-x] (format-expr x {:nested true})
|
||||||
[sql-a & params-a] (format-expr a {:nested true})
|
[sql-a & params-a] (format-expr a {:nested true})
|
||||||
[sql-b & params-b] (format-expr b {:nested true})]
|
[sql-b & params-b] (format-expr b {:nested true})]
|
||||||
(-> [(str sql-x " " (sql-kw k) " " sql-a " AND " sql-b)]
|
(into* [(str sql-x " " (sql-kw k) " " sql-a " AND " sql-b)]
|
||||||
(into params-x)
|
params-x params-a params-b)))
|
||||||
(into params-a)
|
|
||||||
(into params-b))))
|
|
||||||
|
|
||||||
(defn- object-record-literal
|
(defn- object-record-literal
|
||||||
[k [x]]
|
[k [x]]
|
||||||
|
|
@ -1955,25 +1943,36 @@
|
||||||
|
|
||||||
(defn- get-in-navigation
|
(defn- get-in-navigation
|
||||||
"[:get-in expr key-or-index1 key-or-index2 ...]"
|
"[:get-in expr key-or-index1 key-or-index2 ...]"
|
||||||
[_ [expr & kix]]
|
[wrap [expr & kix]]
|
||||||
(let [[sql & params] (format-expr expr)
|
(let [[sql & params] (format-expr expr)
|
||||||
[sqls params']
|
[sqls params']
|
||||||
(reduce-sql (map #(cond (number? %)
|
(reduce-sql (map #(cond (number? %)
|
||||||
[(str "[" % "]")]
|
[(str "[" % "]")]
|
||||||
|
(string? %)
|
||||||
|
[(str "[" (sqlize-value %) "]")]
|
||||||
(ident? %)
|
(ident? %)
|
||||||
[(str "." (format-entity %))]
|
[(str "." (format-entity %))]
|
||||||
:else
|
:else
|
||||||
(let [[sql' & params'] (format-expr %)]
|
(let [[sql' & params'] (format-expr %)]
|
||||||
(cons (str "[" sql' "]") params')))
|
(cons (str "[" sql' "]") params')))
|
||||||
kix))]
|
kix))]
|
||||||
(-> [(str "(" sql ")" (join "" sqls))]
|
(into* [(str (if wrap (str "(" sql ")") sql)
|
||||||
(into params)
|
(join "" sqls))]
|
||||||
(into params'))))
|
params
|
||||||
|
params')))
|
||||||
|
|
||||||
(defn ignore-respect-nulls [k [x]]
|
(defn- ignore-respect-nulls [k [x]]
|
||||||
(let [[sql & params] (format-expr x)]
|
(let [[sql & params] (format-expr x)]
|
||||||
(into [(str sql " " (sql-kw k))] params)))
|
(into [(str sql " " (sql-kw k))] params)))
|
||||||
|
|
||||||
|
(defn- dot-navigation [sep [expr col & subcols]]
|
||||||
|
(let [[sql & params] (format-expr expr)]
|
||||||
|
(into [(str sql sep (format-simple-expr col "dot navigation")
|
||||||
|
(when (seq subcols)
|
||||||
|
(str "." (join "." (map #(format-simple-expr % "dot navigation")
|
||||||
|
subcols)))))]
|
||||||
|
params)))
|
||||||
|
|
||||||
(def ^:private special-syntax
|
(def ^:private special-syntax
|
||||||
(atom
|
(atom
|
||||||
{;; these "functions" are mostly used in column
|
{;; these "functions" are mostly used in column
|
||||||
|
|
@ -1994,12 +1993,9 @@
|
||||||
:references #'function-1
|
:references #'function-1
|
||||||
:unique #'function-1-opt
|
:unique #'function-1-opt
|
||||||
;; dynamic dotted name creation:
|
;; dynamic dotted name creation:
|
||||||
:. (fn [_ [expr col subcol]]
|
:. (fn [_ data] (dot-navigation "." data))
|
||||||
(let [[sql & params] (format-expr expr)]
|
;; snowflake variant #570:
|
||||||
(into [(str sql "." (format-entity col)
|
:.:. (fn [_ data] (dot-navigation ":" data))
|
||||||
(when subcol
|
|
||||||
(str "." (format-entity subcol))))]
|
|
||||||
params)))
|
|
||||||
;; used in DDL to force rendering as a SQL entity instead
|
;; used in DDL to force rendering as a SQL entity instead
|
||||||
;; of a SQL keyword:
|
;; of a SQL keyword:
|
||||||
:entity (fn [_ [e]] [(format-entity e)])
|
:entity (fn [_ [e]] [(format-entity e)])
|
||||||
|
|
@ -2024,6 +2020,7 @@
|
||||||
(let [[sqls params] (format-expr-list arr)
|
(let [[sqls params] (format-expr-list arr)
|
||||||
type-str (when type (str "::" (sql-kw type) "[]"))]
|
type-str (when type (str "::" (sql-kw type) "[]"))]
|
||||||
(into [(str "ARRAY[" (join ", " sqls) "]" type-str)] params))))
|
(into [(str "ARRAY[" (join ", " sqls) "]" type-str)] params))))
|
||||||
|
:at (fn [_ data] (get-in-navigation false data))
|
||||||
:at-time-zone
|
:at-time-zone
|
||||||
(fn [_ [expr tz]]
|
(fn [_ [expr tz]]
|
||||||
(let [[sql & params] (format-expr expr {:nested true})
|
(let [[sql & params] (format-expr expr {:nested true})
|
||||||
|
|
@ -2041,9 +2038,7 @@
|
||||||
[sql' & params'] (if (ident? type)
|
[sql' & params'] (if (ident? type)
|
||||||
[(sql-kw type)]
|
[(sql-kw type)]
|
||||||
(format-expr type))]
|
(format-expr type))]
|
||||||
(-> [(str "CAST(" sql " AS " sql' ")")]
|
(into* [(str "CAST(" sql " AS " sql' ")")] params params')))
|
||||||
(into params)
|
|
||||||
(into params'))))
|
|
||||||
:composite
|
:composite
|
||||||
(fn [_ [& args]]
|
(fn [_ [& args]]
|
||||||
(let [[sqls params] (format-expr-list args)]
|
(let [[sqls params] (format-expr-list args)]
|
||||||
|
|
@ -2056,11 +2051,9 @@
|
||||||
(fn [_ [pattern escape-chars]]
|
(fn [_ [pattern escape-chars]]
|
||||||
(let [[sql-p & params-p] (format-expr pattern)
|
(let [[sql-p & params-p] (format-expr pattern)
|
||||||
[sql-e & params-e] (format-expr escape-chars)]
|
[sql-e & params-e] (format-expr escape-chars)]
|
||||||
(-> [(str sql-p " " (sql-kw :escape) " " sql-e)]
|
(into* [(str sql-p " " (sql-kw :escape) " " sql-e)] params-p params-e)))
|
||||||
(into params-p)
|
|
||||||
(into params-e))))
|
|
||||||
:filter expr-clause-pairs
|
:filter expr-clause-pairs
|
||||||
:get-in #'get-in-navigation
|
:get-in (fn [_ data] (get-in-navigation true data))
|
||||||
:ignore-nulls ignore-respect-nulls
|
:ignore-nulls ignore-respect-nulls
|
||||||
:inline
|
:inline
|
||||||
(fn [_ xs]
|
(fn [_ xs]
|
||||||
|
|
@ -2105,9 +2098,7 @@
|
||||||
(fn [k [e & qs]]
|
(fn [k [e & qs]]
|
||||||
(let [[sql-e & params-e] (format-expr e)
|
(let [[sql-e & params-e] (format-expr e)
|
||||||
[sql-q & params-q] (format-dsl {k qs})]
|
[sql-q & params-q] (format-dsl {k qs})]
|
||||||
(-> [(str sql-e " " sql-q)]
|
(into* [(str sql-e " " sql-q)] params-e params-q)))
|
||||||
(into params-e)
|
|
||||||
(into params-q))))
|
|
||||||
:over
|
:over
|
||||||
(fn [_ [& args]]
|
(fn [_ [& args]]
|
||||||
(let [[sqls params]
|
(let [[sqls params]
|
||||||
|
|
@ -2118,7 +2109,7 @@
|
||||||
[(format-entity p)])]
|
[(format-entity p)])]
|
||||||
[(conj sqls (str sql-e " OVER " sql-p
|
[(conj sqls (str sql-e " OVER " sql-p
|
||||||
(when a (str " AS " (format-entity a)))))
|
(when a (str " AS " (format-entity a)))))
|
||||||
(-> params (into params-e) (into params-p))]))
|
(into* params params-e params-p)]))
|
||||||
[[] []]
|
[[] []]
|
||||||
args)]
|
args)]
|
||||||
(into [(join ", " sqls)] params)))
|
(into [(join ", " sqls)] params)))
|
||||||
|
|
@ -2159,8 +2150,7 @@
|
||||||
(cond-> nested
|
(cond-> nested
|
||||||
(as-> s (str "(" s ")")))
|
(as-> s (str "(" s ")")))
|
||||||
(vector)
|
(vector)
|
||||||
(into p1)
|
(into* p1 p2))))
|
||||||
(into p2))))
|
|
||||||
|
|
||||||
(defn- format-infix-expr [op' op expr nested]
|
(defn- format-infix-expr [op' op expr nested]
|
||||||
(let [args (cond->> (rest expr)
|
(let [args (cond->> (rest expr)
|
||||||
|
|
@ -2233,7 +2223,9 @@
|
||||||
(into [(str "(" (join ", " sqls) ")")] params))))
|
(into [(str "(" (join ", " sqls) ")")] params))))
|
||||||
|
|
||||||
(boolean? expr)
|
(boolean? expr)
|
||||||
[(upper-case (str expr))]
|
(if (:auto-lift-boolean *dialect*)
|
||||||
|
["?" expr]
|
||||||
|
[(upper-case (str expr))])
|
||||||
|
|
||||||
(nil? expr)
|
(nil? expr)
|
||||||
["NULL"]
|
["NULL"]
|
||||||
|
|
@ -2543,6 +2535,22 @@
|
||||||
(first clauses)
|
(first clauses)
|
||||||
(into [:and] clauses))))
|
(into [:and] clauses))))
|
||||||
|
|
||||||
|
(defn semicolon
|
||||||
|
"Given either a vector of formatted SQL+params vectors, or two or more
|
||||||
|
SQL+params vectors as arguments, merge them into a single SQL+params
|
||||||
|
vector with the SQL strings separated by semicolons."
|
||||||
|
([sql+params-vector]
|
||||||
|
(reduce into
|
||||||
|
[(str/join "; " (map first sql+params-vector))]
|
||||||
|
(map rest sql+params-vector)))
|
||||||
|
([sql+params & more]
|
||||||
|
(semicolon (cons sql+params more))))
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(semicolon [ ["foo" 1 2 3] ["bar" 4 5 6] ])
|
||||||
|
(semicolon ["foo" 1 2 3] ["bar" 4 5 6] ["baz" 7 8 9] )
|
||||||
|
)
|
||||||
|
|
||||||
;; aids to migration from HoneySQL 1.x -- these are deliberately undocumented
|
;; aids to migration from HoneySQL 1.x -- these are deliberately undocumented
|
||||||
;; so as not to encourage their use for folks starting fresh with 2.x!
|
;; so as not to encourage their use for folks starting fresh with 2.x!
|
||||||
|
|
||||||
|
|
@ -2550,6 +2558,10 @@
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(format {:truncate :foo})
|
(format {:truncate :foo})
|
||||||
|
(format [:and])
|
||||||
|
(format [:and] {:dialect :sqlserver})
|
||||||
|
(format {:select :* :from :table :where (map= {})})
|
||||||
|
(format {:select :* :from :table :where (map= {})} {:dialect :sqlserver})
|
||||||
(format-expr [:= :id 1])
|
(format-expr [:= :id 1])
|
||||||
(format-expr [:+ :id 1])
|
(format-expr [:+ :id 1])
|
||||||
(format-expr [:+ 1 [:+ 1 :quux]])
|
(format-expr [:+ 1 [:+ 1 :quux]])
|
||||||
|
|
@ -2716,4 +2728,6 @@
|
||||||
:from [(keyword "'`a-b.b-c.c-d`")]}
|
:from [(keyword "'`a-b.b-c.c-d`")]}
|
||||||
(sql/format))
|
(sql/format))
|
||||||
(sql/format {:select :* :from [[[:json_to_recordset :my-json-column] [:b {:with-columns [[:c :int] [:d :text]]}]]]})
|
(sql/format {:select :* :from [[[:json_to_recordset :my-json-column] [:b {:with-columns [[:c :int] [:d :text]]}]]]})
|
||||||
|
(sql/format {:select :* :from :my_publications :where [:= :is_published true]}
|
||||||
|
{:dialect :sqlserver})
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
;; copyright (c) 2020-2024 sean corfield, all rights reserved
|
;; copyright (c) 2020-2025 sean corfield, all rights reserved
|
||||||
|
|
||||||
(ns honey.sql.helpers
|
(ns honey.sql.helpers
|
||||||
"Helper functions for the built-in clauses in honey.sql.
|
"Helper functions for the built-in clauses in honey.sql.
|
||||||
|
|
@ -58,10 +58,12 @@
|
||||||
bulk-collect-info [& args]
|
bulk-collect-info [& args]
|
||||||
|
|
||||||
(as they are for all helper functions)."
|
(as they are for all helper functions)."
|
||||||
(:refer-clojure :exclude [distinct filter for group-by into partition-by set update])
|
(:refer-clojure :exclude [assert distinct filter for group-by into partition-by set update])
|
||||||
(:require [clojure.core :as c]
|
(:require [clojure.core :as c]
|
||||||
[honey.sql :as h]))
|
[honey.sql :as h]))
|
||||||
|
|
||||||
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
;; implementation helpers:
|
;; implementation helpers:
|
||||||
|
|
||||||
(defn- default-merge [current args]
|
(defn- default-merge [current args]
|
||||||
|
|
@ -450,6 +452,14 @@
|
||||||
[& clauses]
|
[& clauses]
|
||||||
(generic :except-all (cons {} clauses)))
|
(generic :except-all (cons {} clauses)))
|
||||||
|
|
||||||
|
(defn assert
|
||||||
|
"Accepts an expression (predicate).
|
||||||
|
|
||||||
|
Produces: ASSERT expression"
|
||||||
|
{:arglists '([expr])}
|
||||||
|
[& args]
|
||||||
|
(generic-1 :assert args))
|
||||||
|
|
||||||
(defn select
|
(defn select
|
||||||
"Accepts any number of column names, or column/alias
|
"Accepts any number of column names, or column/alias
|
||||||
pairs, or SQL expressions (optionally aliased):
|
pairs, or SQL expressions (optionally aliased):
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
(:refer-clojure :exclude [-> ->> -])
|
(:refer-clojure :exclude [-> ->> -])
|
||||||
(:require [honey.sql :as sql]))
|
(:require [honey.sql :as sql]))
|
||||||
|
|
||||||
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
;; see https://www.postgresql.org/docs/current/functions-json.html
|
;; see https://www.postgresql.org/docs/current/functions-json.html
|
||||||
|
|
||||||
(def ->
|
(def ->
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,7 @@
|
||||||
"InlineValue -- a protocol that defines how to inline
|
"InlineValue -- a protocol that defines how to inline
|
||||||
values; (sqlize x) produces a SQL string for x.")
|
values; (sqlize x) produces a SQL string for x.")
|
||||||
|
|
||||||
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
(defprotocol InlineValue :extend-via-metadata true
|
(defprotocol InlineValue :extend-via-metadata true
|
||||||
(sqlize [this] "Render value inline in a SQL string."))
|
(sqlize [this] "Render value inline in a SQL string."))
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
(:refer-clojure :exclude [str])
|
(:refer-clojure :exclude [str])
|
||||||
(:require clojure.string))
|
(:require clojure.string))
|
||||||
|
|
||||||
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
(defn str
|
(defn str
|
||||||
"More efficient implementation of `clojure.core/str` because it has more
|
"More efficient implementation of `clojure.core/str` because it has more
|
||||||
non-variadic arities. Optimization is Clojure-only, on other platforms it
|
non-variadic arities. Optimization is Clojure-only, on other platforms it
|
||||||
|
|
@ -75,3 +77,33 @@
|
||||||
|
|
||||||
:default
|
:default
|
||||||
(clojure.string/join separator (transduce xform conj [] coll)))))
|
(clojure.string/join separator (transduce xform conj [] coll)))))
|
||||||
|
|
||||||
|
(defn split-by-separator
|
||||||
|
"More efficient implementation of `clojure.string/split` for cases when a
|
||||||
|
literal string (not regex) is used as a separator, and for cases where the
|
||||||
|
separator is not present in the haystack at all."
|
||||||
|
[s sep]
|
||||||
|
(loop [start 0, res []]
|
||||||
|
(if-some [sep-idx (clojure.string/index-of s sep start)]
|
||||||
|
(let [sep-idx (long sep-idx)]
|
||||||
|
(recur (inc sep-idx) (conj res (subs s start sep-idx))))
|
||||||
|
(if (= start 0)
|
||||||
|
;; Fastpath - zero separators in s
|
||||||
|
[s]
|
||||||
|
(conj res (subs s start))))))
|
||||||
|
|
||||||
|
(defn into*
|
||||||
|
"An extension of `clojure.core/into` that accepts multiple \"from\" arguments.
|
||||||
|
Doesn't support `xform`."
|
||||||
|
([to from1] (into* to from1 nil nil nil))
|
||||||
|
([to from1 from2] (into* to from1 from2 nil nil))
|
||||||
|
([to from1 from2 from3] (into* to from1 from2 from3 nil))
|
||||||
|
([to from1 from2 from3 from4]
|
||||||
|
(if (or from1 from2 from3 from4)
|
||||||
|
(as-> (transient to) to'
|
||||||
|
(reduce conj! to' from1)
|
||||||
|
(reduce conj! to' from2)
|
||||||
|
(reduce conj! to' from3)
|
||||||
|
(reduce conj! to' from4)
|
||||||
|
(persistent! to'))
|
||||||
|
to)))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
;; copyright (c) 2023-2024 sean corfield, all rights reserved
|
;; copyright (c) 2023-2025 sean corfield, all rights reserved
|
||||||
|
|
||||||
(ns honey.ops-test
|
(ns honey.ops-test
|
||||||
(:refer-clojure :exclude [format])
|
(:refer-clojure :exclude [format])
|
||||||
|
|
@ -9,3 +9,11 @@
|
||||||
(is (= ["SELECT a - b - c AS x"]
|
(is (= ["SELECT a - b - c AS x"]
|
||||||
(-> {:select [[[:- :a :b :c] :x]]}
|
(-> {:select [[[:- :a :b :c] :x]]}
|
||||||
(sut/format)))))
|
(sut/format)))))
|
||||||
|
|
||||||
|
(deftest issue-566
|
||||||
|
(is (= ["SELECT * FROM table WHERE a IS DISTINCT FROM b"]
|
||||||
|
(-> {:select :* :from :table :where [:is-distinct-from :a :b]}
|
||||||
|
(sut/format))))
|
||||||
|
(is (= ["SELECT * FROM table WHERE a IS NOT DISTINCT FROM b"]
|
||||||
|
(-> {:select :* :from :table :where [:is-not-distinct-from :a :b]}
|
||||||
|
(sut/format)))))
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,23 @@
|
||||||
|
|
||||||
(ns honey.sql.helpers-test
|
(ns honey.sql.helpers-test
|
||||||
(:refer-clojure :exclude [filter for group-by partition-by set update])
|
(:refer-clojure :exclude [filter for group-by partition-by set update])
|
||||||
|
#_{:clj-kondo/ignore [:unused-namespace]}
|
||||||
(:require [clojure.core :as c]
|
(:require [clojure.core :as c]
|
||||||
[clojure.test :refer [deftest is testing]]
|
[clojure.test :refer [deftest is testing]]
|
||||||
[honey.sql :as sql]
|
[honey.sql :as sql]
|
||||||
[honey.sql.helpers :as h
|
[honey.sql.helpers :as h
|
||||||
:refer [add-column add-index alter-table columns create-table create-table-as create-view
|
:refer [add-column alter-table columns create-table create-table-as create-view
|
||||||
create-materialized-view drop-view drop-materialized-view
|
create-materialized-view
|
||||||
create-index
|
create-index
|
||||||
bulk-collect-into
|
bulk-collect-into
|
||||||
cross-join do-update-set drop-column drop-index drop-table
|
cross-join do-update-set drop-column drop-table
|
||||||
filter from full-join
|
filter from full-join
|
||||||
group-by having insert-into replace-into
|
group-by having insert-into replace-into
|
||||||
join-by join lateral left-join limit offset on-conflict
|
join-by join left-join limit offset on-conflict
|
||||||
on-duplicate-key-update
|
on-duplicate-key-update
|
||||||
order-by over partition-by refresh-materialized-view
|
order-by over partition-by refresh-materialized-view
|
||||||
rename-column rename-table returning right-join
|
returning right-join
|
||||||
select select-distinct select-top select-distinct-top
|
select select-distinct select-top
|
||||||
values where window with with-columns
|
values where window with with-columns
|
||||||
with-data within-group]]))
|
with-data within-group]]))
|
||||||
|
|
||||||
|
|
@ -1037,11 +1038,15 @@
|
||||||
(sql/format (create-index [:unique :my-column-idx :if-not-exists] [:my-table :my-column]))))
|
(sql/format (create-index [:unique :my-column-idx :if-not-exists] [:my-table :my-column]))))
|
||||||
(is (= ["CREATE INDEX my_column_idx ON my_table (LOWER(my_column))"]
|
(is (= ["CREATE INDEX my_column_idx ON my_table (LOWER(my_column))"]
|
||||||
(sql/format (create-index :my-column-idx [:my-table :%lower.my-column])))))
|
(sql/format (create-index :my-column-idx [:my-table :%lower.my-column])))))
|
||||||
(testing "PostgreSQL extensions (USING GIN)"
|
(testing "PostgreSQL extensions (USING GIN/HASH)"
|
||||||
(is (= ["CREATE INDEX my_column_idx ON my_table USING GIN (my_column)"]
|
(is (= ["CREATE INDEX my_column_idx ON my_table USING GIN (my_column)"]
|
||||||
(sql/format {:create-index [:my-column-idx [:my-table :using-gin :my-column]]})))
|
(sql/format {:create-index [:my-column-idx [:my-table :using-gin :my-column]]})))
|
||||||
(is (= ["CREATE INDEX my_column_idx ON my_table USING GIN (my_column)"]
|
(is (= ["CREATE INDEX my_column_idx ON my_table USING GIN (my_column)"]
|
||||||
(sql/format (create-index :my-column-idx [:my-table :using-gin :my-column]))))))
|
(sql/format (create-index :my-column-idx [:my-table :using-gin :my-column]))))
|
||||||
|
(is (= ["CREATE INDEX my_column_idx ON my_table USING HASH (my_column)"]
|
||||||
|
(sql/format {:create-index [:my-column-idx [:my-table :using-hash :my-column]]})))
|
||||||
|
(is (= ["CREATE INDEX my_column_idx ON my_table USING HASH (my_column)"]
|
||||||
|
(sql/format (create-index :my-column-idx [:my-table :using-hash :my-column]))))))
|
||||||
|
|
||||||
(deftest join-with-alias
|
(deftest join-with-alias
|
||||||
(is (= ["SELECT * FROM foo LEFT JOIN (populatons AS pm INNER JOIN customers AS pc ON (pm.id = pc.id) AND (pm.other_id = pc.other_id)) ON foo.fk_id = pm.id"]
|
(is (= ["SELECT * FROM foo LEFT JOIN (populatons AS pm INNER JOIN customers AS pc ON (pm.id = pc.id) AND (pm.other_id = pc.other_id)) ON foo.fk_id = pm.id"]
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
;; copyright (c) 2020-2024 sean corfield, all rights reserved
|
;; copyright (c) 2020-2025 sean corfield, all rights reserved
|
||||||
|
|
||||||
(ns honey.sql.xtdb-test
|
(ns honey.sql.xtdb-test
|
||||||
(:require [clojure.test :refer [deftest is testing]]
|
(:require [clojure.test :refer [deftest is testing]]
|
||||||
[honey.sql :as sql]
|
[honey.sql :as sql]
|
||||||
[honey.sql.helpers :as h
|
[honey.sql.helpers :as h
|
||||||
:refer [select exclude rename from where]]))
|
:refer [select exclude rename from]]))
|
||||||
|
|
||||||
(deftest select-tests
|
(deftest select-tests
|
||||||
(testing "select, exclude, rename"
|
(testing "select, exclude, rename"
|
||||||
|
|
@ -129,3 +129,20 @@
|
||||||
(sql/format '{select (((get-in (. a b) c (lift 1) d)))})))
|
(sql/format '{select (((get-in (. a b) c (lift 1) d)))})))
|
||||||
(is (= ["SELECT (OBJECT (_id: 1, b: 'thing').b).c[?].d" 1]
|
(is (= ["SELECT (OBJECT (_id: 1, b: 'thing').b).c[?].d" 1]
|
||||||
(sql/format '{select (((get-in (. (object {_id 1 b "thing"}) b) c (lift 1) d)))}))))
|
(sql/format '{select (((get-in (. (object {_id 1 b "thing"}) b) c (lift 1) d)))}))))
|
||||||
|
|
||||||
|
(deftest assert-statement
|
||||||
|
(testing "quoted sql"
|
||||||
|
(is (= ["ASSERT NOT EXISTS (SELECT 1 FROM users WHERE email = 'james @example.com')"]
|
||||||
|
(sql/format '{assert (not-exists {select 1 from users where (= email "james @example.com")})}
|
||||||
|
:inline true)))
|
||||||
|
(is (= ["ASSERT TRUE"]
|
||||||
|
(sql/format '{assert true}
|
||||||
|
:inline true))))
|
||||||
|
(testing "helper"
|
||||||
|
(is (= ["ASSERT NOT EXISTS (SELECT 1 FROM users WHERE email = 'james @example.com')"]
|
||||||
|
(-> (h/assert [:not-exists {:select 1 :from :users :where [:= :email "james @example.com"]}])
|
||||||
|
(sql/format {:inline true}))))
|
||||||
|
(is (= ["ASSERT NOT EXISTS (SELECT 1 FROM users WHERE email = 'james @example.com')"]
|
||||||
|
(-> {}
|
||||||
|
(h/assert [:not-exists {:select 1 :from :users :where [:= :email "james @example.com"]}])
|
||||||
|
(sql/format {:inline true}))))))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
;; copyright (c) 2021-2024 sean corfield, all rights reserved
|
;; copyright (c) 2021-2025 sean corfield, all rights reserved
|
||||||
|
|
||||||
(ns honey.sql-test
|
(ns honey.sql-test
|
||||||
(:refer-clojure :exclude [format])
|
(:refer-clojure :exclude [format])
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
(sut/format-expr [:is :id nil])))
|
(sut/format-expr [:is :id nil])))
|
||||||
(is (= ["id = TRUE"]
|
(is (= ["id = TRUE"]
|
||||||
(sut/format-expr [:= :id true])))
|
(sut/format-expr [:= :id true])))
|
||||||
|
(is (= ["[id] = ?" true]
|
||||||
|
(sut/format [:= :id true] {:dialect :sqlserver})))
|
||||||
(is (= ["id IS TRUE"]
|
(is (= ["id IS TRUE"]
|
||||||
(sut/format-expr [:is :id true])))
|
(sut/format-expr [:is :id true])))
|
||||||
(is (= ["id <> TRUE"]
|
(is (= ["id <> TRUE"]
|
||||||
|
|
@ -219,7 +221,26 @@
|
||||||
:from [:hits :stuff]
|
:from [:hits :stuff]
|
||||||
:where [:= :EventDate :ts_upper_bound]})
|
:where [:= :EventDate :ts_upper_bound]})
|
||||||
["WITH ? AS ts_upper_bound, stuff AS (SELECT * FROM songs) SELECT * FROM hits, stuff WHERE EventDate = ts_upper_bound"
|
["WITH ? AS ts_upper_bound, stuff AS (SELECT * FROM songs) SELECT * FROM hits, stuff WHERE EventDate = ts_upper_bound"
|
||||||
"2019-08-01 15:23:00"]))))
|
"2019-08-01 15:23:00"])))
|
||||||
|
(testing "Use expression in a WITH clause"
|
||||||
|
(is (= (format
|
||||||
|
{:with [[:s [:sum :bytes]]]
|
||||||
|
:select [:s]
|
||||||
|
:from [:table]})
|
||||||
|
["WITH SUM(bytes) AS s SELECT s FROM table"]))
|
||||||
|
|
||||||
|
(is (= (format
|
||||||
|
{:with [[:v [:raw "m['k']"]]]
|
||||||
|
:select [:v]
|
||||||
|
:from [:table]})
|
||||||
|
["WITH m['k'] AS v SELECT v FROM table"]))
|
||||||
|
|
||||||
|
(is (= (format
|
||||||
|
{:with [[:cond [:and [:= :a 1] [:= :b 2] [:= :c 3]]]]
|
||||||
|
:select [:v]
|
||||||
|
:from [:table]
|
||||||
|
:where :cond})
|
||||||
|
["WITH (a = ?) AND (b = ?) AND (c = ?) AS cond SELECT v FROM table WHERE cond" 1 2 3]))))
|
||||||
|
|
||||||
(deftest insert-into
|
(deftest insert-into
|
||||||
(is (= (format {:insert-into :foo})
|
(is (= (format {:insert-into :foo})
|
||||||
|
|
@ -554,13 +575,15 @@
|
||||||
(-> {:delete-from :foo
|
(-> {:delete-from :foo
|
||||||
:where [:= :foo.id 42]}
|
:where [:= :foo.id 42]}
|
||||||
(format :dialect :mysql :pretty true)))))
|
(format :dialect :mysql :pretty true)))))
|
||||||
(when (str/starts-with? #?(:cljs *clojurescript-version*
|
(let [version #?(:cljs *clojurescript-version*
|
||||||
:default (clojure-version)) "1.11")
|
:default (clojure-version))]
|
||||||
|
(when (or (str/starts-with? version "1.12")
|
||||||
|
(str/starts-with? version "1.11"))
|
||||||
(testing "format can be called with mixed arguments"
|
(testing "format can be called with mixed arguments"
|
||||||
(is (= ["\nDELETE FROM `foo`\nWHERE `foo`.`id` = ?\n" 42]
|
(is (= ["\nDELETE FROM `foo`\nWHERE `foo`.`id` = ?\n" 42]
|
||||||
(-> {:delete-from :foo
|
(-> {:delete-from :foo
|
||||||
:where [:= :foo.id 42]}
|
:where [:= :foo.id 42]}
|
||||||
(format :dialect :mysql {:pretty true})))))))
|
(format :dialect :mysql {:pretty true}))))))))
|
||||||
|
|
||||||
(deftest delete-from-test
|
(deftest delete-from-test
|
||||||
(is (= ["DELETE FROM `foo` WHERE `foo`.`id` = ?" 42]
|
(is (= ["DELETE FROM `foo` WHERE `foo`.`id` = ?" 42]
|
||||||
|
|
@ -591,6 +614,12 @@
|
||||||
(format {:dialect :mysql}))))
|
(format {:dialect :mysql}))))
|
||||||
(is (= ["TRUNCATE TABLE `foo` CONTINUE IDENTITY"]
|
(is (= ["TRUNCATE TABLE `foo` CONTINUE IDENTITY"]
|
||||||
(-> {:truncate [:foo :continue :identity]}
|
(-> {:truncate [:foo :continue :identity]}
|
||||||
|
(format {:dialect :mysql}))))
|
||||||
|
(is (= ["TRUNCATE TABLE `t1`, `t2`"]
|
||||||
|
(-> {:truncate [[:t1 :t2]]}
|
||||||
|
(format {:dialect :mysql}))))
|
||||||
|
(is (= ["TRUNCATE TABLE `t1`, `t2` CONTINUE IDENTITY"]
|
||||||
|
(-> {:truncate [[:t1 :t2] :continue :identity]}
|
||||||
(format {:dialect :mysql})))))
|
(format {:dialect :mysql})))))
|
||||||
|
|
||||||
(deftest inlined-values-are-stringified-correctly
|
(deftest inlined-values-are-stringified-correctly
|
||||||
|
|
@ -1155,9 +1184,10 @@ ORDER BY id = ? DESC
|
||||||
|
|
||||||
(deftest issue-474-dot-selection
|
(deftest issue-474-dot-selection
|
||||||
(testing "basic dot selection"
|
(testing "basic dot selection"
|
||||||
(is (= ["SELECT a.b, c.d, a.d.x"]
|
(is (= ["SELECT a.b, c.d, a.d.x, a.d.x.y"]
|
||||||
(let [t :a c :d]
|
(let [t :a c :d]
|
||||||
(sut/format {:select [[[:. t :b]] [[:. :c c]] [[:. t c :x]]]}))))
|
(sut/format {:select [[[:. t :b]] [[:. :c c]]
|
||||||
|
[[:. t c :x]] [[:. t c :x :y]]]}))))
|
||||||
(is (= ["SELECT [a].[b], [c].[d], [a].[d].[x]"]
|
(is (= ["SELECT [a].[b], [c].[d], [a].[d].[x]"]
|
||||||
(let [t :a c :d]
|
(let [t :a c :d]
|
||||||
(sut/format {:select [[[:. t :b]] [[:. :c c]] [[:. t c :x]]]}
|
(sut/format {:select [[[:. t :b]] [[:. :c c]] [[:. t c :x]]]}
|
||||||
|
|
@ -1171,8 +1201,54 @@ ORDER BY id = ? DESC
|
||||||
(sut/format '{select (((. (nest v) *))
|
(sut/format '{select (((. (nest v) *))
|
||||||
((. (nest w) x))
|
((. (nest w) x))
|
||||||
((. (nest (y z)) *)))}
|
((. (nest (y z)) *)))}
|
||||||
|
{:dialect :mysql})))
|
||||||
|
(is (= ["SELECT (v).*, (w).x, (Y(z)).*"]
|
||||||
|
(sut/format '{select (((get-in v *))
|
||||||
|
((get-in w x))
|
||||||
|
((get-in (y z) *)))})))
|
||||||
|
(is (= ["SELECT (`v`).*, (`w`).`x`, (Y(`z`)).*"]
|
||||||
|
(sut/format '{select (((get-in v *))
|
||||||
|
((get-in w x))
|
||||||
|
((get-in (y z) *)))}
|
||||||
{:dialect :mysql})))))
|
{:dialect :mysql})))))
|
||||||
|
|
||||||
|
(deftest issue-570-snowflake-dot-selection
|
||||||
|
(testing "basic colon selection"
|
||||||
|
(is (= ["SELECT a:b, c:d, a:d.x, a:d.x.y"]
|
||||||
|
(let [t :a c :d]
|
||||||
|
(sut/format {:select [[[:.:. t :b]] [[:.:. :c c]]
|
||||||
|
[[:.:. t c :x]] [[:.:. t c :x :y]]]}))))
|
||||||
|
(is (= ["SELECT [a]:[b], [c]:[d], [a]:[d].[x]"]
|
||||||
|
(let [t :a c :d]
|
||||||
|
(sut/format {:select [[[:.:. t :b]] [[:.:. :c c]] [[:.:. t c :x]]]}
|
||||||
|
{:dialect :sqlserver})))))
|
||||||
|
(testing "basic field selection from composite"
|
||||||
|
(is (= ["SELECT (v):*, (w):x, (Y(z)):*"]
|
||||||
|
(sut/format '{select (((.:. (nest v) *))
|
||||||
|
((.:. (nest w) x))
|
||||||
|
((.:. (nest (y z)) *)))})))
|
||||||
|
(is (= ["SELECT (`v`):*, (`w`):`x`, (Y(`z`)):*"]
|
||||||
|
(sut/format '{select (((.:. (nest v) *))
|
||||||
|
((.:. (nest w) x))
|
||||||
|
((.:. (nest (y z)) *)))}
|
||||||
|
{:dialect :mysql}))))
|
||||||
|
(testing "bracket selection"
|
||||||
|
(is (= ["SELECT a['b'], c['b'], a['d'].x, a:e[0].name"]
|
||||||
|
(sut/format {:select [[[:at :a [:inline "b"]]]
|
||||||
|
[[:at :c "b"]]
|
||||||
|
[[:at :a [:inline "d"] :x]]
|
||||||
|
[[:.:. :a [:at :e [:inline 0]] :name]]]})))
|
||||||
|
(is (= ["SELECT a[?].name" 0]
|
||||||
|
(sut/format '{select (((at a (lift 0) name)))})))
|
||||||
|
;; sanity check, compare with get-in:
|
||||||
|
(is (= ["SELECT (a)[?].name" 0]
|
||||||
|
(sut/format '{select (((get-in a (lift 0) name)))})))
|
||||||
|
(is (= ["SELECT (a)['b'], (c)['b'], (a)['d'].x, a:(e)[0].name"]
|
||||||
|
(sut/format {:select [[[:get-in :a [:inline "b"]]]
|
||||||
|
[[:get-in :c "b"]]
|
||||||
|
[[:get-in :a [:inline "d"] :x]]
|
||||||
|
[[:.:. :a [:get-in :e [:inline 0]] :name]]]})))))
|
||||||
|
|
||||||
(deftest issue-476-raw
|
(deftest issue-476-raw
|
||||||
(testing "single argument :raw"
|
(testing "single argument :raw"
|
||||||
(is (= ["@foo := 42"]
|
(is (= ["@foo := 42"]
|
||||||
|
|
@ -1416,6 +1492,24 @@ ORDER BY id = ? DESC
|
||||||
(h/select :*)
|
(h/select :*)
|
||||||
(h/from :table)))))))
|
(h/from :table)))))))
|
||||||
|
|
||||||
|
(deftest issue-571
|
||||||
|
(testing "an empty where clause is omitted"
|
||||||
|
(is (= ["SELECT * FROM foo"]
|
||||||
|
(sut/format {:select :* :from :foo :where []})))
|
||||||
|
#?(:clj
|
||||||
|
(is (thrown? clojure.lang.ExceptionInfo
|
||||||
|
(sut/format {:select :* :from :foo :where nil}))))
|
||||||
|
(is (= ["SELECT * FROM foo WHERE 1 = 1"]
|
||||||
|
(sut/format {:select :* :from :foo :where [:= 1 1]} {:inline true}))))
|
||||||
|
(testing "an empty order by clause is omitted"
|
||||||
|
(is (= ["SELECT * FROM foo"]
|
||||||
|
(sut/format {:select :* :from :foo :order-by []})))
|
||||||
|
#?(:clj
|
||||||
|
(is (thrown? clojure.lang.ExceptionInfo
|
||||||
|
(sut/format {:select :* :from :foo :order-by nil}))))
|
||||||
|
(is (= ["SELECT * FROM foo ORDER BY bar ASC"]
|
||||||
|
(sut/format {:select :* :from :foo :order-by [:bar]})))))
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
;; partial (incorrect!) workaround for #407:
|
;; partial (incorrect!) workaround for #407:
|
||||||
(sut/format {:select :f.* :from [[:foo [:f :for :system-time]]] :where [:= :f.id 1]})
|
(sut/format {:select :f.* :from [[:foo [:f :for :system-time]]] :where [:= :f.id 1]})
|
||||||
|
|
@ -1428,4 +1522,9 @@ ORDER BY id = ? DESC
|
||||||
:select [:*]
|
:select [:*]
|
||||||
:from [:a-b.b-c.c-d]}
|
:from [:a-b.b-c.c-d]}
|
||||||
(sut/format {:dialect :nrql}))
|
(sut/format {:dialect :nrql}))
|
||||||
|
(sut/format {:select :a:b.c}) ; quotes a:b
|
||||||
|
(sut/format [:. :a :b :c]) ; a.b.c
|
||||||
|
(sut/format [:. :a :b :c :d]) ; drops d ; a.b.c
|
||||||
|
(sut/format [:.:. :a :b :c]) ; .(a, b, c)
|
||||||
|
(sut/format '(.:. a b c)) ; .(a, b, c)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -43,3 +43,20 @@
|
||||||
(is (= "1, 2, 3, 4"
|
(is (= "1, 2, 3, 4"
|
||||||
(sut/join ", " (remove nil?) [1 nil 2 nil 3 nil nil nil 4])))
|
(sut/join ", " (remove nil?) [1 nil 2 nil 3 nil nil nil 4])))
|
||||||
(is (= "" (sut/join ", " (remove nil?) [nil nil nil nil]))))
|
(is (= "" (sut/join ", " (remove nil?) [nil nil nil nil]))))
|
||||||
|
|
||||||
|
(deftest split-by-separator-test
|
||||||
|
(is (= [""] (sut/split-by-separator "" ".")))
|
||||||
|
(is (= ["" ""] (sut/split-by-separator "." ".")))
|
||||||
|
(is (= ["hello"] (sut/split-by-separator "hello" ".")))
|
||||||
|
(is (= ["h" "e" "l" "l" "o"] (sut/split-by-separator "h.e.l.l.o" ".")))
|
||||||
|
(is (= ["" "h" "e" "" "" "l" "" "l" "o" ""]
|
||||||
|
(sut/split-by-separator ".h.e...l..l.o." "."))))
|
||||||
|
|
||||||
|
(deftest into*-test
|
||||||
|
(is (= [1] (sut/into* [1] nil)))
|
||||||
|
(is (= [1] (sut/into* [1] [])))
|
||||||
|
(is (= [1] (sut/into* [1] nil [] nil [])))
|
||||||
|
(is (= [1 2 3] (sut/into* [1] [2 3])))
|
||||||
|
(is (= [1 2 3 4 5 6] (sut/into* [1] [2 3] [4 5 6])))
|
||||||
|
(is (= [1 2 3 4 5 6 7] (sut/into* [1] [2 3] [4 5 6] [7])))
|
||||||
|
(is (= [1 2 3 4 5 6 7 8 9] (sut/into* [1] [2 3] [4 5 6] [7] [8 9]))))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue