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
|
||||
uses: DeLaGuardo/setup-clojure@master
|
||||
with:
|
||||
cli: '1.12.0.1488'
|
||||
cli: '1.12.0.1530'
|
||||
- name: Cache All The Things
|
||||
uses: actions/cache@v4
|
||||
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
|
||||
uses: DeLaGuardo/setup-clojure@master
|
||||
with:
|
||||
cli: '1.12.0.1488'
|
||||
cli: '1.12.0.1530'
|
||||
- name: Cache All The Things
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
|
@ -49,7 +49,7 @@ jobs:
|
|||
- name: Clojure CLI
|
||||
uses: DeLaGuardo/setup-clojure@master
|
||||
with:
|
||||
cli: '1.12.0.1488'
|
||||
cli: '1.12.0.1530'
|
||||
- name: Cache All The Things
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/test-bb.yml
vendored
2
.github/workflows/test-bb.yml
vendored
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
- name: Clojure CLI
|
||||
uses: DeLaGuardo/setup-clojure@master
|
||||
with:
|
||||
cli: '1.12.0.1488'
|
||||
cli: '1.12.0.1530'
|
||||
bb: latest
|
||||
- name: Cache All The Things
|
||||
uses: actions/cache@v4
|
||||
|
|
|
|||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
- name: Clojure CLI
|
||||
uses: DeLaGuardo/setup-clojure@master
|
||||
with:
|
||||
cli: '1.12.0.1488'
|
||||
cli: '1.12.0.1530'
|
||||
- name: Cache All The Things
|
||||
uses: actions/cache@v4
|
||||
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
|
||||
|
||||
* 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
|
||||
* 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.
|
||||
|
|
@ -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 #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.
|
||||
* 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.
|
||||
|
||||
* 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
|
||||
|
||||
[](https://clojars.org/com.github.seancorfield/honeysql)
|
||||
[](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT)
|
||||
[](https://clojars.org/com.github.seancorfield/honeysql)
|
||||
[](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT)
|
||||
[](https://clojurians.slack.com/app_redirect?channel=honeysql)
|
||||
[](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.
|
||||
|
||||
> 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.
|
||||
|
||||
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:
|
||||
<!-- {:test-doc-blocks/reader-cond :clj} -->
|
||||
```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]
|
||||
;; CAUTION: this overwrites several clojure.core fns:
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
[deps-deploy.deps-deploy :as dd]))
|
||||
|
||||
(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 snapshot (the-version "9999-SNAPSHOT"))
|
||||
(def class-dir "target/classes")
|
||||
|
|
@ -47,8 +47,7 @@
|
|||
"Generate and run doc tests.
|
||||
|
||||
Optionally specify :aliases vector:
|
||||
[:1.9] -- test against Clojure 1.9 (the default)
|
||||
[:1.10] -- test against Clojure 1.10.3
|
||||
[:1.10] -- test against Clojure 1.10.3 (the default)
|
||||
[:1.11] -- test against Clojure 1.11.0
|
||||
[:1.12] -- test against Clojure 1.12.0
|
||||
[:cljs] -- test against ClojureScript"
|
||||
|
|
@ -99,10 +98,10 @@
|
|||
(defn ci
|
||||
"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."
|
||||
[opts]
|
||||
(let [aliases [:cljs :elide :1.10 :1.11 :1.12]
|
||||
(let [aliases [:cljs :elide :1.11 :1.12]
|
||||
opts (jar-opts opts)]
|
||||
(b/delete {:path "target"})
|
||||
(doseq [alias aliases]
|
||||
|
|
|
|||
7
deps.edn
7
deps.edn
|
|
@ -1,14 +1,13 @@
|
|||
{:mvn/repos {"sonatype" {:url "https://oss.sonatype.org/content/repositories/snapshots/"}}
|
||||
:paths ["src"]
|
||||
:deps {org.clojure/clojure {:mvn/version "1.9.0"}}
|
||||
:deps {org.clojure/clojure {:mvn/version "1.10.3"}}
|
||||
:aliases
|
||||
{;; 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"}}
|
||||
:ns-default build}
|
||||
|
||||
;; 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.11 {:override-deps {org.clojure/clojure {:mvn/version "1.11.4"}}}
|
||||
:1.12 {:override-deps {org.clojure/clojure {:mvn/version "1.12.0"}}}
|
||||
|
|
@ -31,7 +30,7 @@
|
|||
:main-opts ["-m" "cljs-test-runner.main"]}
|
||||
|
||||
: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"}}
|
||||
: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)"]
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
Used with `:alter-table`,
|
||||
|
|
@ -822,13 +830,23 @@ is a "hard" delete as opposed to a temporal delete.
|
|||
## truncate
|
||||
|
||||
`: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
|
||||
user=> (sql/format '{truncate transport})
|
||||
["TRUNCATE TABLE transport"]
|
||||
user=> (sql/format '{truncate (transport)})
|
||||
["TRUNCATE TABLE transport"]
|
||||
user=> (sql/format '{truncate (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
|
||||
|
|
@ -1070,6 +1088,9 @@ The `:where` clause can have a single SQL expression, or
|
|||
a sequence of SQL expressions prefixed by either `:and`
|
||||
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
|
||||
tests several columns for equality, and you might have a Clojure
|
||||
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)"]
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
The `:having` clause works identically to `:where` above
|
||||
|
|
@ -1205,12 +1231,15 @@ user=> (sql/format {:select [[[:over
|
|||
|
||||
## 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
|
||||
SQL entity or a pair of a SQL expression and a direction
|
||||
(which can be `:asc`, `:desc`, `:nulls-first`, `:desc-null-last`,
|
||||
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
|
||||
as a pair with a direction:
|
||||
|
||||
|
|
@ -1421,7 +1450,7 @@ user=> (sql/format {:insert-into :table
|
|||
:values [{:a 1 :b 2 :c 3}
|
||||
:default
|
||||
{: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
|
||||
:values [[1 2 3] :default [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
|
||||
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
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ Supported Clojure versions: 1.7 and later.
|
|||
In `deps.edn`:
|
||||
<!-- :test-doc-blocks/skip -->
|
||||
```clojure
|
||||
com.github.seancorfield/honeysql {:mvn/version "2.6.1243"}
|
||||
com.github.seancorfield/honeysql {:mvn/version "2.7.1295"}
|
||||
```
|
||||
|
||||
Required as:
|
||||
|
|
@ -90,7 +90,7 @@ The new namespaces are:
|
|||
* `honey.sql` -- the primary API (just `format` now),
|
||||
* `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
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ For the Clojure CLI, add the following dependency to your `deps.edn` file:
|
|||
|
||||
<!-- :test-doc-blocks/skip -->
|
||||
```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:
|
||||
|
||||
<!-- :test-doc-blocks/skip -->
|
||||
```clojure
|
||||
[com.github.seancorfield/honeysql "2.6.1243"]
|
||||
[com.github.seancorfield/honeysql "2.7.1295"]
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,14 @@ and strings.
|
|||
:from :b
|
||||
:order-by [[[:alias :'some-alias]]]})
|
||||
;;=> ["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
|
||||
|
|
@ -80,6 +88,29 @@ In the subquery case, produces `ARRAY(subquery)`:
|
|||
;;=> ["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
|
||||
|
||||
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"]
|
||||
```
|
||||
|
||||
## 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
|
||||
(sql/format {: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:
|
||||
|
||||
```clojure
|
||||
|
|
@ -219,6 +258,9 @@ Can be used with `:nest` for field selection from composites:
|
|||
;;=> ["SELECT (v).*, (MYFUNC(x)).y"]
|
||||
```
|
||||
|
||||
See also [`get-in`](xtdb.md#object-navigation-expressions)
|
||||
and [`at`](#at) for additional path navigation functions.
|
||||
|
||||
## entity
|
||||
|
||||
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"}]})
|
||||
["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
|
||||
"Primary API for HoneySQL 2.x.
|
||||
|
|
@ -31,7 +31,9 @@
|
|||
(:require [clojure.string :as str]
|
||||
#?(:clj [clojure.template])
|
||||
[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
|
||||
|
||||
|
|
@ -57,7 +59,7 @@
|
|||
;; then SQL clauses in priority order:
|
||||
:setting
|
||||
: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
|
||||
:distinct :expr :exclude :rename
|
||||
:into :bulk-collect-into
|
||||
|
|
@ -112,7 +114,8 @@
|
|||
(assoc m k (assoc v :dialect k)))
|
||||
{}
|
||||
{:ansi {:quote #(strop "\"" % "\"")}
|
||||
:sqlserver {:quote #(strop "[" % "]")}
|
||||
:sqlserver {:quote #(strop "[" % "]")
|
||||
:auto-lift-boolean true}
|
||||
:mysql {:quote #(strop "`" % "`")
|
||||
:clause-order-fn
|
||||
#(add-clause-before % :set :where)}
|
||||
|
|
@ -226,7 +229,8 @@
|
|||
|
||||
Hyphens at the start or end of a string should not be touched."
|
||||
[s]
|
||||
(str/replace s #"(\w)-(?=\w)" "$1 "))
|
||||
(cond-> s
|
||||
(str/includes? s "-") (str/replace #"(\w)-(?=\w)" "$1 ")))
|
||||
|
||||
(defn- namespace-_
|
||||
"Return the namespace portion of a symbol, with dashes converted."
|
||||
|
|
@ -315,7 +319,7 @@
|
|||
[n %]
|
||||
(if aliased
|
||||
[%]
|
||||
(str/split % #"\."))))
|
||||
(split-by-separator % "."))))
|
||||
parts (parts-fn col-e)
|
||||
entity (join "." (map #(cond-> % (not= "*" %) (quote-fn))) parts)]
|
||||
(suspicious-entity-check entity)
|
||||
|
|
@ -366,6 +370,20 @@
|
|||
(keyword (name 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]]
|
||||
(str (or open "{")
|
||||
(join ", " (map (fn [[k v]]
|
||||
|
|
@ -432,8 +450,8 @@
|
|||
(defn- format-simple-var
|
||||
([x]
|
||||
(let [c (if (keyword? x)
|
||||
#?(:bb (str (symbol x))
|
||||
:clj (str (.sym ^clojure.lang.Keyword x)) ;; Omits leading colon
|
||||
#?(:bb (subs (str x) 1)
|
||||
:clj (str (.sym ^clojure.lang.Keyword x))
|
||||
:default (subs (str x) 1))
|
||||
(str x))]
|
||||
(format-simple-var x c {})))
|
||||
|
|
@ -441,8 +459,8 @@
|
|||
(if (str/starts-with? c "'")
|
||||
(do
|
||||
(reset! *formatted-column* true)
|
||||
[(subs c 1)])
|
||||
[(format-entity x opts)])))
|
||||
(subs c 1))
|
||||
(format-entity x opts))))
|
||||
|
||||
(defn- format-var
|
||||
([x] (format-var x {}))
|
||||
|
|
@ -451,12 +469,12 @@
|
|||
;; for multiple / in the %fun.call case so that
|
||||
;; qualified column names can be used:
|
||||
(let [c (if (keyword? x)
|
||||
#?(:bb (str (symbol x))
|
||||
:clj (str (.sym ^clojure.lang.Keyword x)) ;; Omits leading colon
|
||||
#?(:bb (subs (str x) 1)
|
||||
:clj (str (.sym ^clojure.lang.Keyword x))
|
||||
:default (subs (str x) 1))
|
||||
(str x))]
|
||||
(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) "("
|
||||
(join ", " (map #(format-entity (keyword %) opts)) args)
|
||||
")")])
|
||||
|
|
@ -469,7 +487,7 @@
|
|||
:else
|
||||
["?" (->param k)]))
|
||||
:else
|
||||
(format-simple-var x c opts)))))
|
||||
[(format-simple-var x c opts)]))))
|
||||
|
||||
(defn- format-entity-alias [x]
|
||||
(cond (sequential? x)
|
||||
|
|
@ -524,14 +542,10 @@
|
|||
:else
|
||||
(throw (ex-info "bigquery * only supports except and replace"
|
||||
{:clause k :arg arg})))]
|
||||
(-> [(cond->> sql' sql (str sql " "))]
|
||||
(into params)
|
||||
(into params'))))
|
||||
(into* [(cond->> sql' sql (str sql " "))] params params')))
|
||||
[]
|
||||
(partition-all 2 x))]
|
||||
(-> [(str sql " " sql')]
|
||||
(into params)
|
||||
(into params'))))
|
||||
(into* [(str sql " " sql')] params params')))
|
||||
|
||||
(comment
|
||||
(bigquery-*-except-replace? [:* :except [:a :b :c]])
|
||||
|
|
@ -675,9 +689,7 @@
|
|||
sql'))
|
||||
(when hints
|
||||
(str " WITH (" hints ")")))]
|
||||
(into params)
|
||||
(into params')
|
||||
(into params'')))))
|
||||
(into* params params' params'')))))
|
||||
|
||||
(defn- format-selectable-dsl
|
||||
([x] (format-selectable-dsl x {}))
|
||||
|
|
@ -751,9 +763,8 @@
|
|||
(let [[cur & params] (peek result)
|
||||
[sql & params'] (first exprs)]
|
||||
(recur (rest exprs) args' false (conj (pop result)
|
||||
(-> [(str cur " " sql)]
|
||||
(into params)
|
||||
(into params')))))
|
||||
(into* [(str cur " " sql)]
|
||||
params params'))))
|
||||
(recur (rest exprs) args' false (conj result (first exprs))))))
|
||||
(reduce-sql result)))))
|
||||
|
||||
|
|
@ -835,7 +846,7 @@
|
|||
(str (sql-kw :select) " " sql)
|
||||
true
|
||||
cols)]
|
||||
(-> [sql'] (into params) (into params'))))
|
||||
(into* [sql'] params params')))
|
||||
|
||||
(defn- format-select-top [k xs]
|
||||
(let [[top & cols] xs
|
||||
|
|
@ -863,7 +874,7 @@
|
|||
(join " " (map sql-kw) parts))
|
||||
true
|
||||
cols)]
|
||||
(-> [sql'] (into params) (into params'))))
|
||||
(into* [sql'] params params')))
|
||||
|
||||
(defn- format-select-into [k xs]
|
||||
(let [[v e] (ensure-sequential xs)
|
||||
|
|
@ -949,25 +960,20 @@
|
|||
(map
|
||||
(fn [[x expr & tail :as with]]
|
||||
(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?
|
||||
(format-expr expr)
|
||||
(format-dsl expr))
|
||||
[sql'' & params'' :as sql-params'']
|
||||
(if non-query-expr?
|
||||
(cond-> [(str sql' " AS " sql)]
|
||||
params' (into params')
|
||||
params (into params))
|
||||
(into* [(str sql' " AS " sql)] params' params)
|
||||
;; according to docs, CTE should _always_ be wrapped:
|
||||
(cond-> [(str sql " " (as-fn with) " " (str "(" sql' ")"))]
|
||||
params (into params)
|
||||
params' (into params')))
|
||||
(into* [(str sql " " (as-fn with) " " (str "(" sql' ")"))]
|
||||
params params'))
|
||||
[tail-sql & tail-params]
|
||||
(format-with-query-tail tail)]
|
||||
(if (seq tail-sql)
|
||||
(cond-> [(str sql'' " " tail-sql)]
|
||||
params'' (into params'')
|
||||
tail-params (into tail-params))
|
||||
(into* [(str sql'' " " tail-sql)] params'' tail-params)
|
||||
sql-params''))))
|
||||
xs)]
|
||||
(into [(str (sql-kw k) " " (join ", " sqls))] params)))
|
||||
|
|
@ -1011,10 +1017,7 @@
|
|||
(str cols-sql' " "))
|
||||
overriding
|
||||
sql)]
|
||||
(into t-params)
|
||||
(into c-params)
|
||||
(into cols-params')
|
||||
(into params)))
|
||||
(into* t-params c-params cols-params' params)))
|
||||
(sequential? (second table))
|
||||
(let [[table cols] table
|
||||
[t-sql & t-params] (format-entity-alias table)
|
||||
|
|
@ -1024,23 +1027,20 @@
|
|||
(join ", " c-sqls)
|
||||
")"
|
||||
overriding)]
|
||||
(into t-params)
|
||||
(into c-params)))
|
||||
(into* t-params c-params)))
|
||||
:else
|
||||
(let [[sql & params] (format-entity-alias table)]
|
||||
(-> [(str (sql-kw k) " " sql
|
||||
(when (seq cols')
|
||||
(str " " cols-sql'))
|
||||
overriding)]
|
||||
(into cols-params')
|
||||
(into params))))
|
||||
(into* cols-params' params))))
|
||||
(let [[sql & params] (format-entity-alias table)]
|
||||
(-> [(str (sql-kw k) " " sql
|
||||
(when (seq cols')
|
||||
(str " " cols-sql'))
|
||||
overriding)]
|
||||
(into cols-params')
|
||||
(into params))))))
|
||||
(into* cols-params' params))))))
|
||||
|
||||
(comment
|
||||
(format-insert :insert-into [[[:raw ":foo"]] {:select :bar}])
|
||||
|
|
@ -1068,12 +1068,10 @@
|
|||
(str "("
|
||||
(join ", " u-sqls)
|
||||
")"))
|
||||
(-> params (into params-j) (into u-params))])
|
||||
(into* params params-j u-params)])
|
||||
(let [[sql & params'] (when e (format-expr e))]
|
||||
[(cond-> sqls e (conj "ON" sql))
|
||||
(-> params
|
||||
(into params-j)
|
||||
(into params'))])))))
|
||||
(into* params params-j params')])))))
|
||||
[[] []]
|
||||
clauses)]
|
||||
(into [(join " " sqls)] params)))
|
||||
|
|
@ -1135,11 +1133,13 @@
|
|||
dirs (map #(when (sequential? %) (second %)) xs)
|
||||
[sqls params]
|
||||
(format-expr-list (map #(if (sequential? %) (first %) %) xs))]
|
||||
(into [(str (sql-kw k) " "
|
||||
(join ", " (map (fn [sql dir]
|
||||
(str sql " " (sql-kw (or dir :asc))))
|
||||
sqls
|
||||
dirs)))] params)))
|
||||
(if (seq sqls)
|
||||
(into [(str (sql-kw k) " "
|
||||
(join ", " (map (fn [sql dir]
|
||||
(str sql " " (sql-kw (or dir :asc))))
|
||||
sqls
|
||||
dirs)))] params)
|
||||
[])))
|
||||
|
||||
(defn- format-lock-strength [k xs]
|
||||
(let [[strength tables nowait] (ensure-sequential xs)]
|
||||
|
|
@ -1195,17 +1195,17 @@
|
|||
x))
|
||||
xs)))
|
||||
[sqls params]
|
||||
(reduce (fn [[sql params] [sqls' params']]
|
||||
[(conj sql
|
||||
(if (sequential? sqls')
|
||||
(str "(" (join ", " sqls') ")")
|
||||
sqls'))
|
||||
(into params params')])
|
||||
(reduce (fn [[sql params] x]
|
||||
(if (sequential? x)
|
||||
(let [[sqls' params'] (format-expr-list x)]
|
||||
[(conj sql
|
||||
(if (sequential? sqls')
|
||||
(str "(" (join ", " sqls') ")")
|
||||
sqls'))
|
||||
(into* params params')])
|
||||
[(conj sql (sql-kw x)) params]))
|
||||
[[] []]
|
||||
(map #(if (sequential? %)
|
||||
(format-expr-list %)
|
||||
[(sql-kw %)])
|
||||
xs'))
|
||||
xs')
|
||||
sqls (if row-ctr (map #(str "ROW" %) sqls) sqls)]
|
||||
(into [(str (sql-kw k) " " (join ", " sqls))] params))
|
||||
|
||||
|
|
@ -1217,26 +1217,26 @@
|
|||
(contains-clause? :replace-into)
|
||||
(contains-clause? :columns)))
|
||||
[sqls params]
|
||||
(reduce (fn [[sql params] [sqls' params']]
|
||||
[(conj sql
|
||||
(if (sequential? sqls')
|
||||
(str "(" (join ", " sqls') ")")
|
||||
sqls'))
|
||||
(if params' (into params params') params')])
|
||||
[[] []]
|
||||
(map (fn [m]
|
||||
(if (map? m)
|
||||
(format-expr-list
|
||||
(map #(get m
|
||||
%
|
||||
;; issue #366: use NULL or DEFAULT
|
||||
;; for missing column values:
|
||||
(if (contains? *values-default-columns* %)
|
||||
[:default]
|
||||
nil))
|
||||
cols))
|
||||
[(sql-kw m)]))
|
||||
xs))]
|
||||
(reduce
|
||||
(fn [[sql params] x]
|
||||
(if (map? x)
|
||||
(let [[sqls' params']
|
||||
(reduce-sql (map #(format-expr
|
||||
(get x %
|
||||
;; issue #366: use NULL or DEFAULT
|
||||
;; for missing column values:
|
||||
(if (contains? *values-default-columns* %)
|
||||
[:default]
|
||||
nil))))
|
||||
cols)]
|
||||
[(conj sql
|
||||
(if (sequential? sqls')
|
||||
(str "(" (join ", " sqls') ")")
|
||||
sqls'))
|
||||
(into* params params')])
|
||||
[(conj sql (sql-kw x)) params]))
|
||||
[[] []]
|
||||
xs)]
|
||||
(into [(str (when cols-sql
|
||||
(str cols-sql " "))
|
||||
(sql-kw k)
|
||||
|
|
@ -1281,8 +1281,7 @@
|
|||
(str " (" (join ", " sqls) ")"))
|
||||
(when sql
|
||||
(str " " sql)))]
|
||||
(into expr-params)
|
||||
(into clause-params)))
|
||||
(into* expr-params clause-params)))
|
||||
(format-on-conflict k [x])))
|
||||
|
||||
(defn- format-do-update-set [k x]
|
||||
|
|
@ -1301,8 +1300,7 @@
|
|||
where (or (:where x) ('where x))
|
||||
[sql & params] (when where (format-dsl {:where where}))]
|
||||
(-> [(str sets (when sql (str " " sql)))]
|
||||
(into set-params)
|
||||
(into params)))
|
||||
(into* set-params params)))
|
||||
(format-set-exprs k x))
|
||||
(sequential? x)
|
||||
(let [[cols clauses] (split-with (complement map?) x)]
|
||||
|
|
@ -1390,20 +1388,17 @@
|
|||
[(butlast coll) (last coll) nil]))]
|
||||
(into [(join " " (map sql-kw) prequel)
|
||||
(when table
|
||||
(let [[v & more] (format-simple-var table)]
|
||||
(when (seq more)
|
||||
(throw (ex-info (str "DDL syntax error at: "
|
||||
(pr-str table)
|
||||
" - expected table name")
|
||||
{:unexpected more})))
|
||||
v))
|
||||
(format-simple-var table))
|
||||
(when ine (sql-kw ine))]
|
||||
(when opts
|
||||
(format-ddl-options opts context)))))
|
||||
|
||||
(defn- format-truncate [_ 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 ine) (throw (ex-info "TRUNCATE syntax error" {:unexpected ine})))
|
||||
[(join " " (cond-> ["TRUNCATE TABLE" table]
|
||||
|
|
@ -1416,6 +1411,11 @@
|
|||
(destructure-ddl-item [:id [:int :unsigned :auto-increment]] "test")
|
||||
(destructure-ddl-item [[[:foreign-key :bar]] :quux [[:wibble :wobble]]] "test")
|
||||
(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]
|
||||
|
|
@ -1434,10 +1434,12 @@
|
|||
(defn- format-create-index [k clauses]
|
||||
(let [[index-spec [table & exprs]] clauses
|
||||
[pre entity ine & more] (destructure-ddl-item index-spec (str (sql-kw k) " options"))
|
||||
[using & exprs] (if (contains? #{:using-gin 'using-gin}
|
||||
(first exprs))
|
||||
exprs
|
||||
(cons nil exprs))
|
||||
[using & exprs]
|
||||
(let [item (first exprs)]
|
||||
(if (and (ident? item)
|
||||
(str/starts-with? (str (kw->sym item)) "using-"))
|
||||
exprs
|
||||
(cons nil exprs)))
|
||||
[sqls params] (format-expr-list exprs)]
|
||||
(into [(join " " (remove empty?)
|
||||
(-> ["CREATE" pre "INDEX" ine entity
|
||||
|
|
@ -1672,6 +1674,9 @@
|
|||
:except #'format-on-set-op
|
||||
:except-all #'format-on-set-op
|
||||
:table #'format-selector
|
||||
:assert (fn [k xs]
|
||||
(let [[sql & params] (format-expr xs)]
|
||||
(into [(str (sql-kw k) " " sql)] params)))
|
||||
:select #'format-selects
|
||||
:select-distinct #'format-selects
|
||||
:select-distinct-on #'format-selects-on
|
||||
|
|
@ -1745,16 +1750,6 @@
|
|||
(set @current-clause-order)
|
||||
(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
|
||||
"Given a hash map representing a SQL statement and a hash map
|
||||
of options, return a vector containing a string -- the formatted
|
||||
|
|
@ -1782,7 +1777,7 @@
|
|||
(if (seq leftover)
|
||||
(throw (ex-info (str "These SQL clauses are unknown or have nil values: "
|
||||
(join ", " (keys leftover))
|
||||
"(perhaps you need [:lift {"
|
||||
" (perhaps you need [:lift {"
|
||||
(first (keys leftover))
|
||||
" ...}] here?)")
|
||||
leftover))
|
||||
|
|
@ -1803,6 +1798,7 @@
|
|||
"like" "not-like" "regexp" "~" "&&"
|
||||
"ilike" "not-ilike" "similar-to" "not-similar-to"
|
||||
"is" "is-not" "not=" "!=" "regex"
|
||||
"is-distinct-from" "is-not-distinct-from"
|
||||
"with-ordinality"}
|
||||
(into (map str "+-*%|&^=<>"))
|
||||
(into (keys infix-aliases))
|
||||
|
|
@ -1848,23 +1844,18 @@
|
|||
(= 1 (count params-y))
|
||||
(coll? v1))
|
||||
(let [sql (str "(" (join ", " (repeat (count v1) "?")) ")")]
|
||||
(-> [(str sql-x " " (sql-kw in) " " sql)]
|
||||
(into params-x)
|
||||
(into v1)))
|
||||
(into* [(str sql-x " " (sql-kw in) " " sql)] params-x v1))
|
||||
(and *numbered*
|
||||
(= (str "$" (count @*numbered*)) sql-y)
|
||||
(= 1 (count params-y))
|
||||
(coll? v1))
|
||||
(let [vs (for [v v1] (->numbered v))
|
||||
sql (str "(" (join ", " (map first) vs) ")")]
|
||||
(-> [(str sql-x " " (sql-kw in) " " sql)]
|
||||
(into params-x)
|
||||
(conj nil)
|
||||
(into (map second vs))))
|
||||
(into* [(str sql-x " " (sql-kw in) " " sql)]
|
||||
params-x [nil] (map second vs)))
|
||||
:else
|
||||
(-> [(str sql-x " " (sql-kw in) " " sql-y)]
|
||||
(into params-x)
|
||||
(into (if *numbered* values params-y))))))
|
||||
(into* [(str sql-x " " (sql-kw in) " " sql-y)]
|
||||
params-x (if *numbered* values params-y)))))
|
||||
|
||||
(defn- function-0 [k xs]
|
||||
[(str (sql-kw k)
|
||||
|
|
@ -1908,7 +1899,7 @@
|
|||
(let [[sql-e & params-e] (format-expr e)
|
||||
[sql-c & params-c] (format-dsl c {:nested true})]
|
||||
[(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))]
|
||||
(into [(join ", " sqls)] params)))
|
||||
|
|
@ -1927,7 +1918,7 @@
|
|||
(= 'else condition))
|
||||
(conj sqls (sql-kw :else) 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)))]
|
||||
(-> [(str (sql-kw :case) " "
|
||||
|
|
@ -1935,8 +1926,7 @@
|
|||
(str sqlx " "))
|
||||
(join " " sqls)
|
||||
" " (sql-kw :end))]
|
||||
(into paramsx)
|
||||
(into params))))
|
||||
(into* paramsx params))))
|
||||
|
||||
(defn- between-fn
|
||||
"For both :between and :not-between"
|
||||
|
|
@ -1944,10 +1934,8 @@
|
|||
(let [[sql-x & params-x] (format-expr x {:nested true})
|
||||
[sql-a & params-a] (format-expr a {:nested true})
|
||||
[sql-b & params-b] (format-expr b {:nested true})]
|
||||
(-> [(str sql-x " " (sql-kw k) " " sql-a " AND " sql-b)]
|
||||
(into params-x)
|
||||
(into params-a)
|
||||
(into params-b))))
|
||||
(into* [(str sql-x " " (sql-kw k) " " sql-a " AND " sql-b)]
|
||||
params-x params-a params-b)))
|
||||
|
||||
(defn- object-record-literal
|
||||
[k [x]]
|
||||
|
|
@ -1955,25 +1943,36 @@
|
|||
|
||||
(defn- get-in-navigation
|
||||
"[:get-in expr key-or-index1 key-or-index2 ...]"
|
||||
[_ [expr & kix]]
|
||||
[wrap [expr & kix]]
|
||||
(let [[sql & params] (format-expr expr)
|
||||
[sqls params']
|
||||
(reduce-sql (map #(cond (number? %)
|
||||
[(str "[" % "]")]
|
||||
(string? %)
|
||||
[(str "[" (sqlize-value %) "]")]
|
||||
(ident? %)
|
||||
[(str "." (format-entity %))]
|
||||
:else
|
||||
(let [[sql' & params'] (format-expr %)]
|
||||
(cons (str "[" sql' "]") params')))
|
||||
kix))]
|
||||
(-> [(str "(" sql ")" (join "" sqls))]
|
||||
(into params)
|
||||
(into params'))))
|
||||
(into* [(str (if wrap (str "(" sql ")") sql)
|
||||
(join "" sqls))]
|
||||
params
|
||||
params')))
|
||||
|
||||
(defn ignore-respect-nulls [k [x]]
|
||||
(defn- ignore-respect-nulls [k [x]]
|
||||
(let [[sql & params] (format-expr x)]
|
||||
(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
|
||||
(atom
|
||||
{;; these "functions" are mostly used in column
|
||||
|
|
@ -1994,12 +1993,9 @@
|
|||
:references #'function-1
|
||||
:unique #'function-1-opt
|
||||
;; dynamic dotted name creation:
|
||||
:. (fn [_ [expr col subcol]]
|
||||
(let [[sql & params] (format-expr expr)]
|
||||
(into [(str sql "." (format-entity col)
|
||||
(when subcol
|
||||
(str "." (format-entity subcol))))]
|
||||
params)))
|
||||
:. (fn [_ data] (dot-navigation "." data))
|
||||
;; snowflake variant #570:
|
||||
:.:. (fn [_ data] (dot-navigation ":" data))
|
||||
;; used in DDL to force rendering as a SQL entity instead
|
||||
;; of a SQL keyword:
|
||||
:entity (fn [_ [e]] [(format-entity e)])
|
||||
|
|
@ -2024,6 +2020,7 @@
|
|||
(let [[sqls params] (format-expr-list arr)
|
||||
type-str (when type (str "::" (sql-kw type) "[]"))]
|
||||
(into [(str "ARRAY[" (join ", " sqls) "]" type-str)] params))))
|
||||
:at (fn [_ data] (get-in-navigation false data))
|
||||
:at-time-zone
|
||||
(fn [_ [expr tz]]
|
||||
(let [[sql & params] (format-expr expr {:nested true})
|
||||
|
|
@ -2041,9 +2038,7 @@
|
|||
[sql' & params'] (if (ident? type)
|
||||
[(sql-kw type)]
|
||||
(format-expr type))]
|
||||
(-> [(str "CAST(" sql " AS " sql' ")")]
|
||||
(into params)
|
||||
(into params'))))
|
||||
(into* [(str "CAST(" sql " AS " sql' ")")] params params')))
|
||||
:composite
|
||||
(fn [_ [& args]]
|
||||
(let [[sqls params] (format-expr-list args)]
|
||||
|
|
@ -2056,11 +2051,9 @@
|
|||
(fn [_ [pattern escape-chars]]
|
||||
(let [[sql-p & params-p] (format-expr pattern)
|
||||
[sql-e & params-e] (format-expr escape-chars)]
|
||||
(-> [(str sql-p " " (sql-kw :escape) " " sql-e)]
|
||||
(into params-p)
|
||||
(into params-e))))
|
||||
(into* [(str sql-p " " (sql-kw :escape) " " sql-e)] params-p params-e)))
|
||||
:filter expr-clause-pairs
|
||||
:get-in #'get-in-navigation
|
||||
:get-in (fn [_ data] (get-in-navigation true data))
|
||||
:ignore-nulls ignore-respect-nulls
|
||||
:inline
|
||||
(fn [_ xs]
|
||||
|
|
@ -2105,9 +2098,7 @@
|
|||
(fn [k [e & qs]]
|
||||
(let [[sql-e & params-e] (format-expr e)
|
||||
[sql-q & params-q] (format-dsl {k qs})]
|
||||
(-> [(str sql-e " " sql-q)]
|
||||
(into params-e)
|
||||
(into params-q))))
|
||||
(into* [(str sql-e " " sql-q)] params-e params-q)))
|
||||
:over
|
||||
(fn [_ [& args]]
|
||||
(let [[sqls params]
|
||||
|
|
@ -2118,7 +2109,7 @@
|
|||
[(format-entity p)])]
|
||||
[(conj sqls (str sql-e " OVER " sql-p
|
||||
(when a (str " AS " (format-entity a)))))
|
||||
(-> params (into params-e) (into params-p))]))
|
||||
(into* params params-e params-p)]))
|
||||
[[] []]
|
||||
args)]
|
||||
(into [(join ", " sqls)] params)))
|
||||
|
|
@ -2159,8 +2150,7 @@
|
|||
(cond-> nested
|
||||
(as-> s (str "(" s ")")))
|
||||
(vector)
|
||||
(into p1)
|
||||
(into p2))))
|
||||
(into* p1 p2))))
|
||||
|
||||
(defn- format-infix-expr [op' op expr nested]
|
||||
(let [args (cond->> (rest expr)
|
||||
|
|
@ -2233,7 +2223,9 @@
|
|||
(into [(str "(" (join ", " sqls) ")")] params))))
|
||||
|
||||
(boolean? expr)
|
||||
[(upper-case (str expr))]
|
||||
(if (:auto-lift-boolean *dialect*)
|
||||
["?" expr]
|
||||
[(upper-case (str expr))])
|
||||
|
||||
(nil? expr)
|
||||
["NULL"]
|
||||
|
|
@ -2543,6 +2535,22 @@
|
|||
(first 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
|
||||
;; so as not to encourage their use for folks starting fresh with 2.x!
|
||||
|
||||
|
|
@ -2550,6 +2558,10 @@
|
|||
|
||||
(comment
|
||||
(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 [:+ 1 [:+ 1 :quux]])
|
||||
|
|
@ -2716,4 +2728,6 @@
|
|||
:from [(keyword "'`a-b.b-c.c-d`")]}
|
||||
(sql/format))
|
||||
(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
|
||||
"Helper functions for the built-in clauses in honey.sql.
|
||||
|
|
@ -58,10 +58,12 @@
|
|||
bulk-collect-info [& args]
|
||||
|
||||
(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]
|
||||
[honey.sql :as h]))
|
||||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
;; implementation helpers:
|
||||
|
||||
(defn- default-merge [current args]
|
||||
|
|
@ -450,6 +452,14 @@
|
|||
[& clauses]
|
||||
(generic :except-all (cons {} clauses)))
|
||||
|
||||
(defn assert
|
||||
"Accepts an expression (predicate).
|
||||
|
||||
Produces: ASSERT expression"
|
||||
{:arglists '([expr])}
|
||||
[& args]
|
||||
(generic-1 :assert args))
|
||||
|
||||
(defn select
|
||||
"Accepts any number of column names, or column/alias
|
||||
pairs, or SQL expressions (optionally aliased):
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
(:refer-clojure :exclude [-> ->> -])
|
||||
(:require [honey.sql :as sql]))
|
||||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
;; see https://www.postgresql.org/docs/current/functions-json.html
|
||||
|
||||
(def ->
|
||||
|
|
|
|||
|
|
@ -4,5 +4,7 @@
|
|||
"InlineValue -- a protocol that defines how to inline
|
||||
values; (sqlize x) produces a SQL string for x.")
|
||||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
(defprotocol InlineValue :extend-via-metadata true
|
||||
(sqlize [this] "Render value inline in a SQL string."))
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
(:refer-clojure :exclude [str])
|
||||
(:require clojure.string))
|
||||
|
||||
#?(:clj (set! *warn-on-reflection* true))
|
||||
|
||||
(defn str
|
||||
"More efficient implementation of `clojure.core/str` because it has more
|
||||
non-variadic arities. Optimization is Clojure-only, on other platforms it
|
||||
|
|
@ -75,3 +77,33 @@
|
|||
|
||||
:default
|
||||
(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
|
||||
(:refer-clojure :exclude [format])
|
||||
|
|
@ -9,3 +9,11 @@
|
|||
(is (= ["SELECT a - b - c AS x"]
|
||||
(-> {:select [[[:- :a :b :c] :x]]}
|
||||
(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
|
||||
(:refer-clojure :exclude [filter for group-by partition-by set update])
|
||||
#_{:clj-kondo/ignore [:unused-namespace]}
|
||||
(:require [clojure.core :as c]
|
||||
[clojure.test :refer [deftest is testing]]
|
||||
[honey.sql :as sql]
|
||||
[honey.sql.helpers :as h
|
||||
:refer [add-column add-index alter-table columns create-table create-table-as create-view
|
||||
create-materialized-view drop-view drop-materialized-view
|
||||
:refer [add-column alter-table columns create-table create-table-as create-view
|
||||
create-materialized-view
|
||||
create-index
|
||||
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
|
||||
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
|
||||
order-by over partition-by refresh-materialized-view
|
||||
rename-column rename-table returning right-join
|
||||
select select-distinct select-top select-distinct-top
|
||||
returning right-join
|
||||
select select-distinct select-top
|
||||
values where window with with-columns
|
||||
with-data within-group]]))
|
||||
|
||||
|
|
@ -1037,11 +1038,15 @@
|
|||
(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))"]
|
||||
(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)"]
|
||||
(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)"]
|
||||
(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
|
||||
(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
|
||||
(:require [clojure.test :refer [deftest is testing]]
|
||||
[honey.sql :as sql]
|
||||
[honey.sql.helpers :as h
|
||||
:refer [select exclude rename from where]]))
|
||||
:refer [select exclude rename from]]))
|
||||
|
||||
(deftest select-tests
|
||||
(testing "select, exclude, rename"
|
||||
|
|
@ -129,3 +129,20 @@
|
|||
(sql/format '{select (((get-in (. a b) c (lift 1) d)))})))
|
||||
(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)))}))))
|
||||
|
||||
(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
|
||||
(:refer-clojure :exclude [format])
|
||||
|
|
@ -22,6 +22,8 @@
|
|||
(sut/format-expr [:is :id nil])))
|
||||
(is (= ["id = TRUE"]
|
||||
(sut/format-expr [:= :id true])))
|
||||
(is (= ["[id] = ?" true]
|
||||
(sut/format [:= :id true] {:dialect :sqlserver})))
|
||||
(is (= ["id IS TRUE"]
|
||||
(sut/format-expr [:is :id true])))
|
||||
(is (= ["id <> TRUE"]
|
||||
|
|
@ -219,7 +221,26 @@
|
|||
: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
|
||||
(is (= (format {:insert-into :foo})
|
||||
|
|
@ -554,13 +575,15 @@
|
|||
(-> {:delete-from :foo
|
||||
:where [:= :foo.id 42]}
|
||||
(format :dialect :mysql :pretty true)))))
|
||||
(when (str/starts-with? #?(:cljs *clojurescript-version*
|
||||
:default (clojure-version)) "1.11")
|
||||
(testing "format can be called with mixed arguments"
|
||||
(is (= ["\nDELETE FROM `foo`\nWHERE `foo`.`id` = ?\n" 42]
|
||||
(-> {:delete-from :foo
|
||||
:where [:= :foo.id 42]}
|
||||
(format :dialect :mysql {:pretty true})))))))
|
||||
(let [version #?(:cljs *clojurescript-version*
|
||||
: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"
|
||||
(is (= ["\nDELETE FROM `foo`\nWHERE `foo`.`id` = ?\n" 42]
|
||||
(-> {:delete-from :foo
|
||||
:where [:= :foo.id 42]}
|
||||
(format :dialect :mysql {:pretty true}))))))))
|
||||
|
||||
(deftest delete-from-test
|
||||
(is (= ["DELETE FROM `foo` WHERE `foo`.`id` = ?" 42]
|
||||
|
|
@ -591,6 +614,12 @@
|
|||
(format {:dialect :mysql}))))
|
||||
(is (= ["TRUNCATE TABLE `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})))))
|
||||
|
||||
(deftest inlined-values-are-stringified-correctly
|
||||
|
|
@ -1155,9 +1184,10 @@ ORDER BY id = ? DESC
|
|||
|
||||
(deftest issue-474-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]
|
||||
(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]"]
|
||||
(let [t :a c :d]
|
||||
(sut/format {:select [[[:. t :b]] [[:. :c c]] [[:. t c :x]]]}
|
||||
|
|
@ -1171,8 +1201,54 @@ ORDER BY id = ? DESC
|
|||
(sut/format '{select (((. (nest v) *))
|
||||
((. (nest w) x))
|
||||
((. (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})))))
|
||||
|
||||
(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
|
||||
(testing "single argument :raw"
|
||||
(is (= ["@foo := 42"]
|
||||
|
|
@ -1416,6 +1492,24 @@ ORDER BY id = ? DESC
|
|||
(h/select :*)
|
||||
(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
|
||||
;; partial (incorrect!) workaround for #407:
|
||||
(sut/format {:select :f.* :from [[:foo [:f :for :system-time]]] :where [:= :f.id 1]})
|
||||
|
|
@ -1428,4 +1522,9 @@ ORDER BY id = ? DESC
|
|||
:select [:*]
|
||||
:from [:a-b.b-c.c-d]}
|
||||
(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"
|
||||
(sut/join ", " (remove nil?) [1 nil 2 nil 3 nil nil nil 4])))
|
||||
(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