Compare commits
30 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 |
20 changed files with 343 additions and 117 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
|
||||
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ 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)
|
||||
|
|
@ -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:
|
||||
;;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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!
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue