Compare commits

...

30 commits

Author SHA1 Message Date
Sean Corfield
f5dbc274be
fixes #440 by supporting multiple tables in truncate
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-04-23 17:45:59 -04:00
Sean Corfield
df753e8635
update tools.build
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-27 23:06:49 -07:00
Sean Corfield
b4b2ca7d79
add general using-* index support
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-26 10:28:57 -07:00
Sean Corfield
78f7d5282f
update dev/build deps
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-25 17:19:42 -07:00
Sean Corfield
024d17b11e
fixes #571 by supporting empty order by clause
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-22 10:34:57 -07:00
Sean Corfield
7611871935
prep for 2.7.1295
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 16:02:47 -07:00
Sean Corfield
1b042687f4
Merge pull request #569 from seancorfield/issue-561
Fixes #561 by dropping support for clojure 1.9
2025-03-12 15:56:06 -07:00
Sean Corfield
a981ed9171
restore bb logic on sym/kw
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 15:52:44 -07:00
Sean Corfield
74cf16c134
fix kw->sym for cljs!
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 15:47:58 -07:00
Sean Corfield
2c8fc30b1d
restore some clojure-only optimizations on keywords
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 15:39:24 -07:00
Sean Corfield
3906aa53c0
remove accidentally duped test
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 15:30:30 -07:00
Sean Corfield
92e4e16b45
a couple of minor build script fixes for dropping 1.9
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 15:28:19 -07:00
Sean Corfield
30b5fabe58
Merge branch 'develop' into issue-561
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 14:51:18 -07:00
Sean Corfield
d74046c658
fix #570 by adding :at
there is also :.:.

Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 14:48:09 -07:00
Sean Corfield
44494e61c0
remove failing tests for #570
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 13:43:02 -07:00
Sean Corfield
d70e89ae3b
part of #570 -- colon path selection
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 13:40:40 -07:00
Sean Corfield
67ea477a5c
part of #570 -- colon path selection
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-12 13:36:49 -07:00
Sean Corfield
89f39be55c
clarify 2.7 versions
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-06 13:25:10 -08:00
Sean Corfield
0d1fd0e901
address #561 by dropping support for clojure 1.9
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-06 13:20:16 -08:00
Sean Corfield
675c94b294
prep for 2.6.1281
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-03-06 12:56:49 -08:00
Sean Corfield
03d96f5747
fixes #568
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-02-20 21:34:50 -08:00
Sean Corfield
f7dbfba57c
and exclude assert in readme refer
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-02-20 17:13:59 -08:00
Sean Corfield
f0eb68f151
add helper for #567 and helper-based tests
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-02-20 17:12:31 -08:00
Sean Corfield
4d1f5f83b7
fixes #567
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-02-20 17:03:44 -08:00
Sean Corfield
c2990597f1
fixes #566
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-02-20 16:47:27 -08:00
Sean Corfield
695351e33c
fix 2.6.1270/2.6.1267 reference
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-28 15:28:55 -08:00
Sean Corfield
0cd81b5d9b
note doc update in change log
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-28 12:50:04 -08:00
Sean Corfield
c295db44c0
add examples of :alias with :group-by
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-28 11:10:53 -08:00
Sean Corfield
44ca426b78
cleanup unused symbols
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-21 15:21:59 -08:00
Sean Corfield
e3fcb3e278
Delete .gitpod.yml 2025-01-20 14:36:05 -08:00
20 changed files with 343 additions and 117 deletions

View file

@ -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:

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -1,7 +1,23 @@
# 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.1270 via PR [#564](https://github.com/seancorfield/honeysql/pull/564) [@alexander-yakushev](https://github.com/alexander-yakushev).
* 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).

View file

@ -4,8 +4,8 @@ SQL as Clojure data structures. Build queries programmatically -- even at runtim
## Build
[![Clojars](https://img.shields.io/badge/clojars-com.github.seancorfield/honeysql_2.6.1270-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABjFBMVEUAAAAdCh0qDikdChwAAAAnDSY0EjM2FjUnDiYnDSYnDSYpDigyEDEEAQRGNUb///////8mDSYAAAAAAAAAAAAFAgUqEyoAAAAAAAAAAAAFAgUAAABXU1c2FjVMx+dQx+f///////9Nx+b////4/f6y4vRPt+RQtOT///9Qt+P///8oDSey4vRQr9/////3/P5hzelNx+dNx+dNx+f///8AAAAuDy0zETIAAAAoDScAAAAAAAARBREAAAAvDy40ETMwEC9gSF+Ne42ilKKuoK6Rg5B5ZXlaP1o4Gzf///9nTWZ4YncyEDF/bn/8/Pz9/P339/c1FTUlDCRRM1AbCRtlS2QyEDEuDy1gRWAxEDAzETIwEC/g4OAvDy40EjOaiZorDiq9sbzNyM3UzdQyEDE0ETMzETKflZ/UzdQ5Fzmu4fNYyuhNx+dPt+RLu9xQyOhBbo81GTuW2vCo4PJNx+c4MFE5N1lHiLFEhKQyEDGDboMzETI5Fjh5bXje2d57aHrIw8jc2NyWhJUrDioxe9o4AAAAPnRSTlMAkf+IAQj9+e7n6e31RtqAD/QAAAED+A0ZEQ8DwvkLBsmcR4aG8+cdAD6C8/MC94eP+qoTrgH+/wj1HA8eEvpXOCUAAAABYktHRA8YugDZAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wcHFjou4Z/shwAAAUpJREFUOMul0/VTwzAUB/AAwyW4y3B3h8EDNuTh7u6UDHcd8I+TbHSjWdrjju/1h77kc+3Lu5aQvyakF/r6B5wu1+DQMEBomLRtG0EpozYDCEccA4iIjIqOiY0bB5iYxHgZ4FQCpYneKmmal0aQPMOXZnUAvJhLkbpInf8NFtKCTrGImK6DJcTlDGl/BXGV6oCsrSNIYAM3aQDwl2xJYBtBB5lZAuyYgWzY3YMcNcjN2wc4EGMEFTg8+hlyfgEenygAj71Q9FBExH0wKC4p1bRTJlJWXqEAVNM05ovbXfkPAHBmAUQPAGaAsXMBLiwA8z3h0gRcsWsObuAWLJu8Awb3ZoB5T8EvS/CgBo9Y5Z8TPwXBJwlUI9Ia/yRrEZ8lID71Olrf0MiamkkL4kurDEjba+C/e2sninR0wrsH8eMTvrqIWbodjh7jyjdtCY3Aniz4jwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0wNy0wN1QyMjo1ODo0NiswMjowMCgWtSoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTUtMDctMDdUMjI6NTg6NDYrMDI6MDBZSw2WAAAAAElFTkSuQmCC)](https://clojars.org/com.github.seancorfield/honeysql)
[![cljdoc](https://cljdoc.org/badge/com.github.seancorfield/honeysql?2.6.1270)](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT)
[![Clojars](https://img.shields.io/badge/clojars-com.github.seancorfield/honeysql_2.7.1295-blue.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABjFBMVEUAAAAdCh0qDikdChwAAAAnDSY0EjM2FjUnDiYnDSYnDSYpDigyEDEEAQRGNUb///////8mDSYAAAAAAAAAAAAFAgUqEyoAAAAAAAAAAAAFAgUAAABXU1c2FjVMx+dQx+f///////9Nx+b////4/f6y4vRPt+RQtOT///9Qt+P///8oDSey4vRQr9/////3/P5hzelNx+dNx+dNx+f///8AAAAuDy0zETIAAAAoDScAAAAAAAARBREAAAAvDy40ETMwEC9gSF+Ne42ilKKuoK6Rg5B5ZXlaP1o4Gzf///9nTWZ4YncyEDF/bn/8/Pz9/P339/c1FTUlDCRRM1AbCRtlS2QyEDEuDy1gRWAxEDAzETIwEC/g4OAvDy40EjOaiZorDiq9sbzNyM3UzdQyEDE0ETMzETKflZ/UzdQ5Fzmu4fNYyuhNx+dPt+RLu9xQyOhBbo81GTuW2vCo4PJNx+c4MFE5N1lHiLFEhKQyEDGDboMzETI5Fjh5bXje2d57aHrIw8jc2NyWhJUrDioxe9o4AAAAPnRSTlMAkf+IAQj9+e7n6e31RtqAD/QAAAED+A0ZEQ8DwvkLBsmcR4aG8+cdAD6C8/MC94eP+qoTrgH+/wj1HA8eEvpXOCUAAAABYktHRA8YugDZAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wcHFjou4Z/shwAAAUpJREFUOMul0/VTwzAUB/AAwyW4y3B3h8EDNuTh7u6UDHcd8I+TbHSjWdrjju/1h77kc+3Lu5aQvyakF/r6B5wu1+DQMEBomLRtG0EpozYDCEccA4iIjIqOiY0bB5iYxHgZ4FQCpYneKmmal0aQPMOXZnUAvJhLkbpInf8NFtKCTrGImK6DJcTlDGl/BXGV6oCsrSNIYAM3aQDwl2xJYBtBB5lZAuyYgWzY3YMcNcjN2wc4EGMEFTg8+hlyfgEenygAj71Q9FBExH0wKC4p1bRTJlJWXqEAVNM05ovbXfkPAHBmAUQPAGaAsXMBLiwA8z3h0gRcsWsObuAWLJu8Awb3ZoB5T8EvS/CgBo9Y5Z8TPwXBJwlUI9Ia/yRrEZ8lID71Olrf0MiamkkL4kurDEjba+C/e2sninR0wrsH8eMTvrqIWbodjh7jyjdtCY3Aniz4jwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNS0wNy0wN1QyMjo1ODo0NiswMjowMCgWtSoAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTUtMDctMDdUMjI6NTg6NDYrMDI6MDBZSw2WAAAAAElFTkSuQmCC)](https://clojars.org/com.github.seancorfield/honeysql)
[![cljdoc](https://cljdoc.org/badge/com.github.seancorfield/honeysql?2.7.1295)](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT)
[![Slack](https://img.shields.io/badge/slack-HoneySQL-orange.svg?logo=slack)](https://clojurians.slack.com/app_redirect?channel=honeysql)
[![Join Slack](https://img.shields.io/badge/slack-join_clojurians-orange.svg?logo=slack)](http://clojurians.net)
[![Zulip](https://img.shields.io/badge/zulip-honeysql-orange.svg?logo=zulip)](https://clojurians.zulipchat.com/#narrow/channel/152091-honeysql)
@ -14,7 +14,8 @@ This project follows the version scheme MAJOR.MINOR.COMMITS where MAJOR and MINO
> 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:
;;

View file

@ -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]

View file

@ -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"]}

View file

@ -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:

View file

@ -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.1270"}
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

View file

@ -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.1270"}
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.1270"]
[com.github.seancorfield/honeysql "2.7.1295"]
```
HoneySQL produces SQL statements but does not execute them.

View file

@ -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

View file

@ -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')"]
```

View file

@ -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.
@ -59,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
@ -370,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]]
@ -436,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 {})))
@ -445,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 {}))
@ -455,8 +469,8 @@
;; 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 "%")
@ -473,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)
@ -1119,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)]
@ -1372,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]
@ -1398,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]
@ -1416,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
@ -1654,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
@ -1727,19 +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))
;; In CLJ runtime, reuse symbol that's already present in the keyword.
#?(:bb (symbol (name k))
:clj (.sym ^clojure.lang.Keyword k)
:default (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
@ -1767,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))
@ -1788,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))
@ -1932,23 +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))]
(into* [(str "(" sql ")" (join "" sqls))] params 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
@ -1969,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)])
@ -1999,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})
@ -2031,7 +2053,7 @@
[sql-e & params-e] (format-expr escape-chars)]
(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]
@ -2513,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!

View file

@ -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,7 +58,7 @@
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]))
@ -452,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):

View file

@ -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)))))

View file

@ -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"]

View file

@ -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}))))))

View file

@ -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])
@ -614,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
@ -1178,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]]]}
@ -1194,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"]
@ -1439,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]})
@ -1451,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)
)