Compare commits

...

833 commits
v1 ... develop

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
Sean Corfield
7e1fe8f558
prep for 2.6.1270
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-17 13:23:52 -08:00
Sean Corfield
13eb8fe859
Merge pull request #564 from alexander-yakushev/fix-warning
Fix autoboxing warning
2025-01-17 21:17:18 +00:00
Oleksandr Yakushev
4bc1d16f24 Fix autoboxing warning 2025-01-17 23:10:59 +02:00
Sean Corfield
206f980093
prep for 2.6.1267
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-16 16:14:54 -08:00
Sean Corfield
3beaa6b2bf
Merge pull request #562 from alexander-yakushev/opt
More small optimizations
2025-01-17 00:05:07 +00:00
Sean Corfield
30a04975f5
Merge branch 'develop' into opt 2025-01-17 00:01:35 +00:00
Sean Corfield
4f5b0ed256
fix default values clause bug
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-16 15:57:54 -08:00
Sean Corfield
1681764830
Merge branch 'develop' into opt 2025-01-16 23:52:40 +00:00
Sean Corfield
6c0c66e371
note pr #563 in change log
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-16 12:45:24 -08:00
Sean Corfield
2c793ce441
Merge pull request #563 from krevedkokun/expressions-in-with
Support expressions in WITH clauses
2025-01-16 20:43:36 +00:00
Nikita Domnitskii
f05c7051e2
Support expressions in WITH clauses 2025-01-16 18:25:52 +06:00
Oleksandr Yakushev
d086631e54 Refactor format-values to do fewer allocations 2025-01-08 18:08:13 +02:00
Oleksandr Yakushev
3ecac63bea Check for hyphen in dehyphen before calling str/replace 2025-01-08 18:01:43 +02:00
Sean Corfield
8e0d6984bd
note optimizations in changelog
Signed-off-by: Sean Corfield <sean@corfield.org>
2025-01-01 11:02:58 -08:00
Sean Corfield
94fae3437f
Merge pull request #560 from alexander-yakushev/opt
More optimizations
2025-01-01 11:00:18 -08:00
Oleksandr Yakushev
316f36751f Add into* for concatenating multiple sequences without transient roundtrip 2024-12-30 13:19:02 +02:00
Oleksandr Yakushev
8ae93d91f6 Reuse keyword's symbol when in CLJ runtime 2024-12-30 13:19:02 +02:00
Oleksandr Yakushev
5fa85400f0 Replace clojure.string/split with a custom splitter 2024-12-30 00:29:20 +02:00
Sean Corfield
045634fd3c Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2024-12-28 11:13:20 -08:00
Sean Corfield
81167cb77e
note pr #559 in change log
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-28 11:13:17 -08:00
Sean Corfield
a3b79215c4
Merge pull request #559 from whatacold/web-app-link
fix broken links
2024-12-28 11:12:45 -08:00
Ken Huang
0cbe76329e fix broken links 2024-12-28 23:25:44 +08:00
Sean Corfield
c93eef06f6
add logo to zulip badge
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-22 10:40:22 -08:00
Sean Corfield
60f5662d81
Add Zulip badge 2024-12-19 20:41:58 -08:00
Sean Corfield
6531413325
working examples for sql server auto-lifting true
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-15 16:03:08 -08:00
Sean Corfield
fce39548d0
fix mixed arg test for Clojure 1.12
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-15 16:02:28 -08:00
Sean Corfield
0f26e7d060
SQL Server has no TRUE / FALSE literals
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-14 12:35:41 -08:00
Sean Corfield
c98df6dd97
prep for 2.6.1243
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-13 22:05:19 -08:00
Sean Corfield
7a24fd0367
incorporate feedback from @jarohen
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-12 11:07:30 -08:00
Sean Corfield
30d177165d
fixes #556 by documenting all the xtdb stuff
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-11 15:37:23 -08:00
Sean Corfield
e4762a1a70
fixes #558 by implementing patch-into
also fix records helper and document a some more xtdb support

Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-11 11:50:47 -08:00
Sean Corfield
e0356bc9c5
example of alias with columns
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-10 11:45:22 -08:00
Sean Corfield
aa1f2bc0f6
update dev/test deps
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-02 19:53:23 -08:00
Sean Corfield
0272c7b9ed
note assertion change
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-01 13:47:44 -08:00
Sean Corfield
10ec823151
switch asserts to proper validation and exceptions
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-12-01 13:24:41 -08:00
Sean Corfield
fdfc6bc997
improve xtdb test
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-29 21:49:44 -08:00
Sean Corfield
3f1677bff2
fixes #555 by implementing SETTING clause
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-24 22:40:20 -08:00
Sean Corfield
782bc4b78a Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2024-11-23 18:26:56 -08:00
Sean Corfield
f2763d5af5
remove experimental xtdb dialect - no longer needed
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 18:26:53 -08:00
Sean Corfield
573d6c75ca
new clj-kondo imports
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 15:31:58 -08:00
Sean Corfield
42d5f4baf1
prep for 2.6.1230
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 13:25:59 -08:00
Sean Corfield
8320571c4d
implement get-in #532
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 13:16:23 -08:00
Sean Corfield
559e71205d
add record/object special syntax #532
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 12:17:43 -08:00
Sean Corfield
1bac4352e3
add records support #532
still need to add record/object special syntax

Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 11:59:12 -08:00
Sean Corfield
b64ab9b0b0
remove auto-start repl/connect
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 11:03:12 -08:00
Sean Corfield
049fe5b68b
fix sqlize of maps in cljs
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-23 00:11:49 -08:00
Sean Corfield
b716d077c4
allow for inline hash maps (record syntax)
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-22 23:46:56 -08:00
Sean Corfield
09fa8afefe
add erase-from helper #532
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-22 23:17:16 -08:00
Sean Corfield
129239a742
add erase from #532
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-22 23:14:02 -08:00
Sean Corfield
ee53c54255
test xtdb dialect and quoted columns
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-22 23:03:28 -08:00
Sean Corfield
f4d212ae18
address #532 by supporting exclude/rename in alias position
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-22 22:54:42 -08:00
Sean Corfield
e2f7991ad8
fixes #552 by turning the assertion into a test
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-22 15:25:52 -08:00
Sean Corfield
c0c455358f
add #553 / #554 to change log
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-22 12:59:55 -08:00
Sean Corfield
488ddd4bcb
Merge pull request #554 from plooney81/issue-553
Fixes #553 by adding :not-between
2024-11-22 12:58:00 -08:00
Pete Looney
b2c1ae0068 Fixes #553 by adding :not-between 2024-11-22 13:24:31 -06:00
Sean Corfield
21ce3a2242
fixes #551 by supporting multiple window clauses
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-18 15:02:28 -08:00
Sean Corfield
6fa606ffd5
address #532 by adding exclude, rename
and tests for nest_one, nest_many

Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-12 15:58:32 -08:00
Sean Corfield
c1c7cba96a
new clj-kondo imports
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-11-05 09:08:30 -08:00
Sean Corfield
4992a3cb76
note support for Babashka and ClojureScript
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-29 09:28:52 -07:00
Sean Corfield
bbac863a2a
run bb tests on develop and PR; add #549 to changelog
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-29 09:19:25 -07:00
Sean Corfield
44399b1984
Merge pull request #550 from borkdude/bb-test-runner
Fix bb compat, fixes #549
2024-10-29 09:14:47 -07:00
Michiel Borkent
4288ceae56 Exclude core.cache from bb deps 2024-10-29 17:10:54 +01:00
Michiel Borkent
38080aff92 Fix java 2024-10-29 17:08:43 +01:00
Michiel Borkent
9999a90e62 Name of runner 2024-10-29 17:08:14 +01:00
Michiel Borkent
7892ec6006
Merge branch 'develop' into bb-test-runner 2024-10-29 17:07:21 +01:00
Michiel Borkent
362818530a Fix bb compat, fixes #549 2024-10-29 17:04:56 +01:00
Sean Corfield
643cea4930
address #549 by restoring bb support
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-29 08:49:19 -07:00
Sean Corfield
b271a898f5
prep for 2.6.1203
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-22 11:26:51 -07:00
Sean Corfield
35545facce
fixes #542 by documenting new clauses
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-13 12:16:59 -07:00
Sean Corfield
170602e85f
add test for #542
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-12 12:51:58 -07:00
Sean Corfield
c6e6b54b8f
address #542 by adding support for with query tail options
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-12 12:42:49 -07:00
Sean Corfield
9dba3860e2
add rcf for #526
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-11 09:17:36 -07:00
Sean Corfield
a187ba98f1
improve performance of optional arguments
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-11 09:12:07 -07:00
Sean Corfield
203e923f99
address #548 by splitting format-var
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-11 09:01:19 -07:00
Sean Corfield
ba78dc2d27
prep for 2.6.1196
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-06 11:05:13 -07:00
Sean Corfield
b55eb23edd
fixes #547 by adding examples
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-10-06 11:00:20 -07:00
Sean Corfield
e2dc330cf9
fix some symbol/keyword resolution bugs
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-29 16:12:32 -07:00
Sean Corfield
de75ace988
bug fix symbol version of values row
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-29 12:01:01 -07:00
Sean Corfield
3d48ecac37
change log updated
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-28 18:34:20 -07:00
Sean Corfield
a27f72eab9
document values row (docs also test)
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-28 18:32:09 -07:00
Sean Corfield
7c0e25f253
guard against uncomparable values
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-28 16:23:44 -07:00
Sean Corfield
3ca197b45c
tentative impl of VALUES statement in MySQL #544
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-28 14:47:15 -07:00
Sean Corfield
48edb03b32
note pr #546 in changelog
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-27 14:13:52 -07:00
Sean Corfield
40d9aee6e6
Merge pull request #546 from alexander-yakushev/misc-opt
Hodgepodge of optimizations
2024-09-27 14:13:31 -07:00
Oleksandr Yakushev
2fb4df6bdd Remove misplaced type hint 2024-09-27 23:09:14 +03:00
Oleksandr Yakushev
4c75db9a95 Miscellaneous improvements 2024-09-27 23:09:14 +03:00
Oleksandr Yakushev
18a511b1c9 Add transducer arity to reduce-sql 2024-09-27 23:09:14 +03:00
Oleksandr Yakushev
15f73f9442 Replace first character checks with starts-with? 2024-09-27 23:09:14 +03:00
Oleksandr Yakushev
bfd7eb2141 Use non-capturing match group in alphanumeric regex
If the group is non-capturing, Clojure will not form match groups with
`re-groups`. We only use this regex as a predicate and don't need those.
2024-09-27 23:09:14 +03:00
Oleksandr Yakushev
803584dc7c More efficient check for suspicious entities
re-find sets up the regex machinery which is unnecessary here. str/includes? of
a single character is more efficient.
2024-09-27 23:09:14 +03:00
Oleksandr Yakushev
ac136dab08 Pass strings to strop
Converting Character->String involves additional allocations. Passing literal
strings avoids that.
2024-09-27 12:59:10 +03:00
Sean Corfield
f31533d8d6
note #545 in changelog
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-26 14:22:23 -07:00
Sean Corfield
8c93e287ff
Merge pull request #545 from alexander-yakushev/perf-opt
Introduce more efficient implementations of str and join
2024-09-26 14:17:12 -07:00
Oleksandr Yakushev
846123c57a Introduce more efficient implementations of str and join 2024-09-26 23:15:15 +03:00
Sean Corfield
694233e2f0
fixes #539
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-22 18:19:39 -07:00
Sean Corfield
34b58e41c4
refer to examples below for set-dialect! #539
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-22 18:13:43 -07:00
Sean Corfield
a6a1272d17
missed a mod reference #538
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-22 18:09:09 -07:00
Sean Corfield
60c1549168
dev/test/ci deps updates
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-22 18:02:38 -07:00
Sean Corfield
c3f10c507d
fixes #541
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-22 17:33:47 -07:00
Sean Corfield
230cc467a1
fixes #538
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-22 17:15:47 -07:00
Sean Corfield
084c1ec5e5
fixes #543
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-22 17:11:01 -07:00
Sean Corfield
150fcda6d3
a simplification to prepare for #542
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-21 18:33:43 -07:00
Sean Corfield
dd9547bbc1
another backtick escape attempt #539
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-21 18:15:31 -07:00
Sean Corfield
acb5112f03
another strop cleanup #539
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-21 18:13:29 -07:00
Sean Corfield
53a6ea0f8a Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2024-09-21 18:12:14 -07:00
Sean Corfield
4efa9a38ef
address #539 by improving getting started
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-21 18:12:10 -07:00
Sean Corfield
de6fe93b75
Merge pull request #540 from jodleif/develop
additional docs for cte
2024-09-12 11:29:09 -07:00
Jo Øivind Gjernes
37fe8b21bc
additional docs for cte
a small example was added to illustrate how to specify a Common Table Expression (CTE) as materialized or not materialized.
2024-09-12 09:05:39 +02:00
Sean Corfield
7fceefbe5c
update test deps to 1.12.0
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-09-05 22:43:45 -07:00
Sean Corfield
434d3877d7
prep for 2.6.1161
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-08-29 10:39:41 -07:00
Sean Corfield
bf34a23e68
fixes #537 by sanitizing metadata while expanding support to numbers
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-08-28 18:50:25 -07:00
Sean Corfield
ae62d2b474
clojure 1.11.4 & 1.12.0 rc 1
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-08-03 12:33:16 -07:00
Sean Corfield
bb72885027
update clojure test version
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-07-23 22:24:37 -07:00
Sean Corfield
d24ee428f3
fixes #536
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-07-23 22:22:25 -07:00
Sean Corfield
cbb3b3e90b
Merge pull request #535 from bhlieberman/develop
Update pg_ops.cljc
2024-07-14 11:42:45 -07:00
Ben Lieberman
b531747918
Update pg_ops.cljc
Add docstring info to @@ operator pertaining to its text search usage
2024-07-14 09:43:28 -06:00
Sean Corfield
669fee5bc8
Merge pull request #534 from holyjak/patch-2
Update pg-ops/- docstring wrt array input
2024-07-04 09:23:16 -07:00
Jakub Holý
e7ef940e24
Update pg-ops/- docstring wrt array input
The `-` operator also works for arrays:

```sql
select '{"a":1,"b":2,"c":3}' - array['a','b'];
-- => {c:3}
select '["a","b","c"]'::jsonb - array['a','b']
-- => ["c"]
```
2024-07-04 17:13:24 +02:00
Sean Corfield
4e124091b4
auto-start repl
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-28 11:11:51 -07:00
Sean Corfield
a3ef215485
fixes #526 by using format-var in ddl destructuring instead of format-entity
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-24 20:30:15 -07:00
Sean Corfield
b07ac78d68
fixes #533 by adding mostly undocumented *escape-?* dynvar
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-13 09:28:06 -07:00
Sean Corfield
544992be88
drop jdk 14 testing
test on 8, 11, 17, and 21

Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-12 23:15:39 -07:00
Sean Corfield
6c2d8de53f
update java test matrix
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-12 23:06:43 -07:00
Sean Corfield
c7e7d47c4e
prep for 2.6.1147
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-12 22:58:54 -07:00
Sean Corfield
2c6bf85f7f
fixes #530 by supporting :using-gin in :create-index
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-12 22:53:40 -07:00
Sean Corfield
bab4ce4bd5
fix set-dialect! and update from values docs
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-09 11:44:21 -07:00
Sean Corfield
2f55f423b9
additional docs for #531
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-06-09 11:22:55 -07:00
Sean Corfield
1b7ade9317
clojure 1.12 alpha 12
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-05-23 19:29:07 -07:00
Sean Corfield
747383c847
fix #529 by changing how table name/expression is formatted in special join syntax
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-05-13 15:26:00 -07:00
Sean Corfield
8764759323
update dev deps
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-05-06 19:44:56 -07:00
Sean Corfield
5b04aa28c5
improve message for unlifted JSON usage
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-04-20 10:14:40 -07:00
Sean Corfield
3b72fefe23
improve do update set/excluded examples
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-04-20 10:03:06 -07:00
Sean Corfield
508158112d
update dev/test dependencies
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-04-06 19:16:45 -07:00
Sean Corfield
d1e9617eae Revert "fix #528 by not ?-escaping operators"
This reverts commit eb680a204e.
2024-04-04 09:43:59 -07:00
Sean Corfield
6c88c0ba3c Revert "update (c) date"
This reverts commit a5bdf4d13c.
2024-04-04 09:43:48 -07:00
Sean Corfield
a5bdf4d13c
update (c) date
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-04-03 17:22:05 -07:00
Sean Corfield
eb680a204e
fix #528 by not ?-escaping operators
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-04-03 16:56:13 -07:00
Sean Corfield
e27298e444
note #527 in changelog
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-29 15:17:40 -07:00
Sean Corfield
f69ee7e8de
fix #527 by adding tests for composite and documenting its use in more detail
both from clause and composite special syntax have examples now

Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-29 15:15:12 -07:00
Sean Corfield
04e7e5b3ab
bump clojure versions for testing
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-24 21:25:47 -07:00
Sean Corfield
5c58e46417
sort .gitignore
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-24 21:14:43 -07:00
Sean Corfield
582c331117
ignore new calva repl path
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-24 21:11:54 -07:00
Sean Corfield
bffcc67fa4
update clojure
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-08 09:29:58 -08:00
Sean Corfield
7a7a01eeaa
improve build status listing
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-04 13:53:41 -08:00
Sean Corfield
0f0d24b510
prep for 2.6.1126
forgot to change the build script for the minor bump to 2.6

Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-04 13:23:36 -08:00
Sean Corfield
1f37b46151
prep for 2.6.1125
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-04 13:13:21 -08:00
Sean Corfield
225c0db092
fix #495 by documenting formatv and adding tests
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-04 12:38:03 -08:00
Sean Corfield
9c29cb29ff
fix #524 by adding :nest example for :union
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-04 12:11:51 -08:00
Sean Corfield
51e64e1891
minor cleanup
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-03-03 21:56:16 -08:00
Sean Corfield
2f159ac912
reorder dialects
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-02-26 22:08:10 -08:00
Sean Corfield
c9867097e8
fix broken test
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-02-09 22:42:06 -08:00
Sean Corfield
fc983927ce
test against 1.12
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-02-09 22:41:55 -08:00
Sean Corfield
2f4792253a
clean up some lint warnings
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-02-09 22:39:38 -08:00
Sean Corfield
2149a80852
fix #523
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-02-03 12:37:43 -08:00
Sean Corfield
9b611bb7ff
document :not-in
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-02-01 10:53:00 -08:00
Sean Corfield
52ed86284a
linting
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-01-14 17:09:46 -08:00
Sean Corfield
d64177bde5
fix MS SQL table hints in FROM clause (e.g. NOLOCK) #522
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-01-14 14:48:39 -08:00
Sean Corfield
35f4c674e9
omit for .cljs
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-01-06 22:39:25 -08:00
Sean Corfield
1291b328d0
two more possible solutions to #495
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-01-06 22:35:02 -08:00
Sean Corfield
ac947b1543
move problematic test to own ns
this test is clj-only and also causes rendering problems for portal due to the #object[] in the rendered test report

Signed-off-by: Sean Corfield <sean@corfield.org>
2024-01-06 13:46:31 -08:00
Sean Corfield
9b9ec47bcf
fix #520
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-01-06 12:38:11 -08:00
Sean Corfield
2e34a9f4ea
fix #518
Signed-off-by: Sean Corfield <sean@corfield.org>
2024-01-06 11:38:45 -08:00
Sean Corfield
d0193d3c10 Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2023-12-08 13:19:04 -08:00
Sean Corfield
39e7c45b4f start 2.5.next 2023-12-08 13:19:01 -08:00
Sean Corfield
1c499ac8a7
Merge pull request #517 from dancek/create-index
Implement CREATE INDEX
2023-12-08 13:15:58 -08:00
Hannu Hartikainen
45d1230102 Add documentation for create-index 2023-12-08 13:25:10 +02:00
Hannu Hartikainen
e70e3713fc Implement CREATE INDEX
Fixes #348.
2023-12-08 13:09:39 +02:00
Sean Corfield
9c40ff4879 prep for 2.5.1103 2023-12-03 17:24:23 -08:00
Sean Corfield
b55eeef7a3 expand changelog details 2023-12-03 17:20:31 -08:00
Sean Corfield
2e8157047d add test for #515 2023-12-03 17:17:28 -08:00
Sean Corfield
1e44f82eb5 fix #515 by adding :quoted-always option 2023-12-03 16:50:16 -08:00
Sean Corfield
ba2e8ad583 revert ux change 2023-12-02 13:37:48 -08:00
Sean Corfield
b55b71ff0a improve my repl startup ux 2023-12-02 13:29:10 -08:00
Sean Corfield
9da2ccc812 fix #513 with :ignored-metadata option 2023-12-02 11:47:43 -08:00
Sean Corfield
f46dbc5ca7 #515: exclude some entities from smart quoting
this is a partial solution, intended to catch (and quote) things like
`0abc` while not changing the behavior for `80` or `2023_11_20`
2023-11-20 11:43:33 -08:00
Sean Corfield
002285a5af revert quoting changes 2023-11-16 22:36:21 -08:00
Sean Corfield
bcbaae5ef5 #514 relax rules again 2023-11-16 22:32:40 -08:00
Sean Corfield
4a7d46dd1f partial #514 solution
improve default quoting strategy (all numeric with _ is OK, leading
alpha with optional alphanumeric/_ is OK, entity with leading digit then
alphanumeric needs quoting).

plus *always-quote* dynamic var that can be bound to regex.
2023-11-16 22:30:35 -08:00
Sean Corfield
7d05220cfa partial #513 by dissoc'ing common metadata 2023-11-16 22:13:42 -08:00
Sean Corfield
18fcddfc34 fix 2.5.1091 release 2023-10-28 15:31:55 -07:00
Sean Corfield
1fe526a734 prep for 2.5.1090 2023-10-28 14:04:35 -07:00
Sean Corfield
737baa9d0e fix #512 by supporting array subquery
also adds metadata support for select to produce as struct (or distinct)
2023-10-28 13:39:26 -07:00
Sean Corfield
b3fe7c1436 #510 fix example quoting for nrql 2023-10-21 16:19:23 -07:00
Sean Corfield
e36ad64aa6 #510 document nrql dialect 2023-10-21 16:17:28 -07:00
Sean Corfield
d45e1dff0f #510 improve nrql helper docstrings 2023-10-21 15:47:33 -07:00
Sean Corfield
10b2a17718 #510 support :timeseries :auto 2023-10-21 15:42:11 -07:00
Sean Corfield
caca08fd5b #511 add tests; document bigquery create or replace 2023-10-21 14:51:13 -07:00
Sean Corfield
7d56daacca initial BigQuery :create-table-as :or-replace 2023-10-20 10:50:09 -07:00
Sean Corfield
440b86633a #510 make facet multi-arg 2023-10-17 11:59:25 -07:00
Sean Corfield
5e9bdba777 #510 basic facet support 2023-10-16 21:23:07 -07:00
Sean Corfield
2c6b89751d start work on nrql dialect #510 2023-10-16 16:35:37 -07:00
Sean Corfield
872cb1d006 fix #509 by checking for ident? first 2023-10-16 11:32:45 -07:00
Sean Corfield
0115424167 prep for 2.4.1078 2023-10-07 12:23:12 -07:00
Sean Corfield
f9811f0c59 minor scm tweak 2023-10-06 15:44:58 -07:00
Sean Corfield
8334978a43 update tools.build; drop template pom.xml 2023-10-06 15:11:43 -07:00
Sean Corfield
bf51f725e2 update CI tooling versions 2023-10-04 20:37:59 -07:00
Sean Corfield
7fc411bdd7 address #507: document change in cast formatting
change of behavior was in december 2022 (2.4.962).
2023-10-03 12:21:32 -07:00
Sean Corfield
ebd8f7ff47 address #506 try to make 1.x more obviously legacy 2023-09-28 09:06:26 -07:00
Sean Corfield
a9d33a8873 minor code dedupe 2023-09-27 23:25:31 -07:00
Sean Corfield
11e1a93c59 fix #505 by rewriting helper-merge 2023-09-27 23:13:16 -07:00
Sean Corfield
a6a69a16f7 #503 fix example 2023-09-16 11:43:08 -07:00
Sean Corfield
756ed95b43 fix #503 by adding at time zone special syntax 2023-09-16 11:34:05 -07:00
Sean Corfield
ac09fc1abd add distinct/expr clauses 2023-09-08 22:28:25 -07:00
Sean Corfield
75830df509 address #504 by adding ignore/respect null support 2023-09-08 21:55:24 -07:00
Sean Corfield
654a1cb67a prep for 2.4.1066 2023-08-27 10:18:49 -07:00
Sean Corfield
87f3e731b1 docs for #496 2023-08-27 09:53:28 -07:00
Sean Corfield
16a9708790 tests for #496 2023-08-26 16:48:59 -07:00
Sean Corfield
ad1b0f9880 address #496 by adding :overriding-value option 2023-08-26 16:45:39 -07:00
Sean Corfield
44ffd340f5 fix #501 by making insert/columns/values talk to each other 2023-08-26 16:20:32 -07:00
Sean Corfield
aa4ebf5f47 add a test (from the #407 ticket) 2023-08-26 14:56:48 -07:00
Sean Corfield
bf1517a60e fix bigquery support for #407 2023-08-26 14:52:20 -07:00
Sean Corfield
1d6ae7b376 fix #407 by adding temporal clause support 2023-08-26 14:39:15 -07:00
Sean Corfield
810e95fe11 fix #389 by showing ONLY(table) examples 2023-08-26 12:48:35 -07:00
Sean Corfield
aa5d8e094a remove stray debug println 2023-08-26 12:32:49 -07:00
Sean Corfield
686cbf7272 fix #497 by adding tests and documenting :alias 2023-08-26 12:31:38 -07:00
Sean Corfield
2f99103ed1 add matching helper 2023-08-23 12:55:21 -07:00
Sean Corfield
17dbbce0d2 remove lsp-provided "help"; update default clause order 2023-08-23 12:52:07 -07:00
Sean Corfield
664e5e2644 add create or replace view for postgresql 2023-08-23 12:45:19 -07:00
Sean Corfield
fd98efc95c record PR #502 2023-08-18 10:29:50 -07:00
Sean Corfield
2e955bfe57
Merge pull request #502 from markbastian/develop
Example in README for custom function usage in select
2023-08-18 10:20:28 -07:00
Mark Bastian
4c711d790a Adding an example to the README.md file to clarify how custom functions are used in select statements along with their corresponding column alias. 2023-08-18 10:37:45 -06:00
Sean Corfield
d187c66987 add :alias special syntax #497 2023-08-11 19:06:30 -07:00
Sean Corfield
c7ec650cfc note tools.build update 2023-08-11 10:53:18 -07:00
Sean Corfield
2c463ec517 bump to tools.build 0.9.5 2023-08-11 10:50:54 -07:00
Sean Corfield
ba336f2884 clarify :values clause behavior with columns
also add more examples to the RCF at the bottom of honey.sql.
2023-08-10 19:31:17 -07:00
Sean Corfield
c2088bff6c prep for 2.4.1045 2023-06-25 15:53:04 -07:00
Sean Corfield
5164c24342 improve helpers ns docstring 2023-06-24 10:23:07 -07:00
Sean Corfield
e679e93362 add formatf experiment #495 2023-06-23 16:05:41 -07:00
Sean Corfield
e7972ac1b4 on-conflict needs ( ) around exprs 2023-06-20 13:03:19 -07:00
Sean Corfield
290537c581 fix #494 by supporting expressions in on conflict 2023-06-20 12:11:00 -07:00
Sean Corfield
3ec884f881 update dev/test dependencies 2023-06-16 20:31:05 -07:00
Sean Corfield
19e73a3ebd fix #493 by clarifying with values 2023-06-10 19:18:36 -07:00
Sean Corfield
23c9597870 a note for #407 2023-06-10 19:13:06 -07:00
Sean Corfield
2d5e89d545 ci -> show jar version 2023-06-10 19:12:44 -07:00
Sean Corfield
445f66ae1e fix #489 2023-06-10 19:12:15 -07:00
Sean Corfield
e7737ee7af
Merge pull request #491 from Dangercoder/develop
honey.sql/upper-case for clojure-clr
2023-06-02 15:40:15 -07:00
Dangercoder
93c8ad75a6 Added honey.sql/upper-case for clojure-clr 2023-06-02 20:24:52 +02:00
Sean Corfield
6ce63cad2c prep for 2.4.1033 2023-05-22 16:02:59 -07:00
Sean Corfield
f14e95ec9d Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2023-05-21 16:07:47 -07:00
Sean Corfield
84a41cba7d Initial ClojureCLR compat changes 2023-05-21 16:05:30 -07:00
Sean Corfield
e83fa1d76a
Merge pull request #490 from holyjak/patch-1
improve on-conflict docstring
2023-05-20 14:32:48 -07:00
Jakub Holý
3d88db6f21
improve on-conflict docstring
It was unclear to me what the "hash map" where-condition meant so I tried to clarify.
2023-05-20 21:53:38 +02:00
Sean Corfield
7fcb9d97d3 auto-connect/jack-in 2023-04-16 11:29:47 -07:00
Sean Corfield
75bf60667d update badges 2023-04-15 16:54:09 -07:00
Sean Corfield
077fc3f23a prep for 2.4.1026 2023-04-15 15:35:15 -07:00
Sean Corfield
6748d86dae update test/deploy deps 2023-04-15 15:29:43 -07:00
Sean Corfield
bc66ec5aee fix #485 by adding :with-ordinality operator 2023-04-15 11:37:20 -07:00
Sean Corfield
c8d4f58f0d bump tools.build 2023-04-15 11:31:12 -07:00
Sean Corfield
3910df215a split test/runner to make project more jack-in friendly 2023-04-15 11:29:08 -07:00
Sean Corfield
e66e7dc6d5 update slack badge 2023-04-13 22:56:21 -07:00
Sean Corfield
1710e07231 fix #486 by support ansi/postgresl interval 2023-04-13 22:46:37 -07:00
Sean Corfield
2efe05def2 fix #484 by adding TABLE to TRUNCAT 2023-04-10 14:09:28 -07:00
Sean Corfield
dffedb115e
Update README.md 2023-04-09 14:20:16 -07:00
Sean Corfield
6203a88615
Update README.md 2023-04-09 14:17:15 -07:00
Sean Corfield
54c2f0960a
Update README.md 2023-04-09 14:16:35 -07:00
Sean Corfield
bb6750a982
add Slack badge 2023-04-09 14:16:01 -07:00
Sean Corfield
1310591d05 add #483 to change log 2023-04-07 11:43:45 -07:00
Sean Corfield
8cbb7f3834 address #483 by adding tests & docs for :join 2023-04-07 11:42:39 -07:00
Sean Corfield
858d157863 address #483 (still needs tests and docs) 2023-04-06 22:49:13 -07:00
Sean Corfield
025e445d5d prep for 2.4.1011 2023-03-23 20:55:12 -07:00
Sean Corfield
522849381d fix #448 by adding database-specific hints and tips
including sqlite :)
2023-03-23 20:49:34 -07:00
Sean Corfield
f5744d4f21 fix #480 by adding notes about DSL clause to/from helper mapping 2023-03-23 19:29:09 -07:00
Sean Corfield
7e67caaf0c fix #481 by adding more examples 2023-03-23 19:22:59 -07:00
Sean Corfield
18ed03985d fix snapshot version in readme 2023-03-17 16:07:40 -07:00
Sean Corfield
ece9be3a4b prep for 2.4.1006 2023-03-17 15:38:08 -07:00
Sean Corfield
e8753efa3e fix #478 by removing duplication in one branch 2023-03-13 14:24:11 -07:00
Sean Corfield
3f31e5a61f fix #478 2023-03-12 17:40:53 -07:00
Sean Corfield
f7cf5718cc fix #476 by restoring multi-argument :raw 2023-03-04 14:21:40 -08:00
Sean Corfield
bfc8ad6821 prep for 2.4.1002 2023-03-03 15:57:05 -08:00
Sean Corfield
7e35cb31bf fix #462 by adding precedence caveat to docs 2023-03-03 15:52:52 -08:00
Sean Corfield
1d7237884d fix #471 by documenting SQL kws in fn call args 2023-03-03 15:42:04 -08:00
Sean Corfield
3c65999ef1 fix #474 by documenting dot-selection
and adding support for two levels of field/column selection
2023-03-03 15:26:22 -08:00
Sean Corfield
f0ccaae192 note #463 / #475 in changelog 2023-03-03 10:16:50 -08:00
Sean Corfield
3e23ad5f2b
Merge pull request #475 from nharsch/issue-463
refs #463 - document dialect + quoted = nil behavior
2023-03-03 10:14:23 -08:00
Nigel Harsch (Vallen)
4b5e4e862b refs #463 - document dialect + quoted = nil behavior 2023-03-03 09:37:20 -08:00
Sean Corfield
dbbe40dc94 print version when building a JAR 2023-03-02 09:59:03 -08:00
Sean Corfield
c905853366 address #471 by switching prefix to :! 2023-03-02 09:56:30 -08:00
Sean Corfield
da150f03e3 address #474 2023-03-01 16:55:54 -08:00
Sean Corfield
5b4b709010 note PR #473 in changelog 2023-03-01 08:44:53 -08:00
Sean Corfield
6d618dbd35
Merge pull request #473 from holyjak/develop
pg-ops: add docs
2023-03-01 08:42:56 -08:00
Jakub Holý
e6f9ffdc32
pg-ops: add docs 2023-03-01 15:40:38 +01:00
Sean Corfield
0936095040 address #471 2023-02-28 17:38:13 -08:00
Sean Corfield
a610f256dd address #466 by collapsing 0-arity :and/:or 2023-02-27 20:07:24 -08:00
Sean Corfield
63c7a45578 add three PR-based fixes to changelog 2023-02-27 18:36:22 -08:00
Sean Corfield
44b6de4139
Merge pull request #469 from p-himik/464-typed-arrays
Add optional type argument to the :array special
2023-02-27 16:14:54 -08:00
Eugene Pakhomov
0c7642a357 Add optional type argument to the :array special 2023-02-28 02:04:05 +02:00
Sean Corfield
fc3397650e
Merge pull request #470 from p-himik/467-allow-scalar-order-by-et-al
Allow single kw in :order-by and other clauses
2023-02-27 14:00:44 -08:00
Sean Corfield
b1b7868c8b
Merge pull request #468 from p-himik/465-many-exprs-in-agg-order-by
Allow multiple expressions in special :order-by
2023-02-27 13:53:44 -08:00
Eugene Pakhomov
3fba12fbcc Allow single kw in :order-by and other clauses 2023-02-26 14:20:09 +02:00
Eugene Pakhomov
dae73c6195 Allow multiple expressions in special :order-by 2023-02-26 12:22:12 +02:00
Sean Corfield
de0adf56ef fixes #461; prep for 2.4.980 2023-02-15 22:10:45 -08:00
Sean Corfield
0a6f645d91 prep for 2.4.979 2023-02-11 13:39:18 -08:00
Sean Corfield
762252b660 fixes #459 by making all operators variadic
except for := and the various :<> variants

some operators only make sense in binary usage and will produce invalid
SQL if used in a non-binary manner
2023-02-11 13:35:55 -08:00
Sean Corfield
6324eca4fc fixes #459 by making all operators variadic
except for := and the various :<> variants

some operators only make sense in binary usage and will produce invalid
SQL if used in a non-binary manner
2023-02-11 13:34:39 -08:00
Sean Corfield
f8532dfd33 fixes #458 by adding four registered-*? predicates 2023-02-11 12:54:43 -08:00
Sean Corfield
5fb150bd53
Merge pull request #457 from camsaul/add-section-about-order-by-nulls-last
Add section about `ORDER BY ... NULLS LAST` to 1.x differences dox
2023-02-08 11:14:10 -08:00
Cam Saul
982ebd18e1
Update differences-from-1-x.md 2023-02-08 11:09:14 -08:00
Cam Saul
e783362c6a Add section about ORDER BY ... NULLS LAST to 1.x differences dox 2023-02-08 18:56:23 +00:00
Sean Corfield
ca78c7eed4 prep for 2.4.972 2023-02-02 10:35:23 -08:00
Sean Corfield
213c152fdb fixes #456 2023-02-01 22:20:14 -08:00
Sean Corfield
6ab8c6452b drop use of build-clj 2023-01-23 23:29:52 -08:00
Sean Corfield
22f259e897 prep for 2.4.969 2023-01-14 16:00:22 -08:00
Sean Corfield
4a7c3631a6 fix #454 by allowing - to be variadic 2023-01-14 15:35:03 -08:00
Sean Corfield
6bdc262e7c close #452 - support :replace-into in all dialects 2023-01-14 15:27:42 -08:00
Sean Corfield
d17d44ffcf fix #451 2023-01-14 15:11:16 -08:00
Sean Corfield
6b015400ed fix #445 #453 2023-01-14 14:58:13 -08:00
Sean Corfield
83ad5cff74 fx #447 by updating deps/actions 2023-01-14 14:18:03 -08:00
Sean Corfield
ca953e3c42 add test for #455 (which passes) 2023-01-13 13:01:35 -08:00
Sean Corfield
56823b8888 prep for 2.4.962 2022-12-17 16:18:19 -08:00
Sean Corfield
295be57bfc
Merge pull request #446 from seancorfield/issue-405
Fix Issue 405 by adding :numbered true option
2022-12-17 16:10:57 -08:00
Sean Corfield
bf96f034c3 update docs; fix set-options! 2022-12-17 16:02:02 -08:00
Sean Corfield
d0ae02a6ef more tests 2022-12-17 00:15:11 -08:00
Sean Corfield
c62f5da3f8 make numbered params work with 'in' 2022-12-16 23:56:53 -08:00
Sean Corfield
4ea630ed90 basic testing for numbered params 2022-12-16 23:20:17 -08:00
Sean Corfield
359d9de668 basic numbered parameter support 2022-12-16 23:11:45 -08:00
Sean Corfield
0faf611855 wordsmith 2.4.947 changes 2022-12-07 12:24:57 -08:00
Sean Corfield
6e5e1b100d fix #433 #432 by smithing the docs 2022-11-20 04:49:31 -08:00
Sean Corfield
b5e55cf95a note cast fix #443 in changelog 2022-11-19 19:35:44 -08:00
Sean Corfield
e4f0a5eff3
Merge pull request #443 from duddlf23/cast-type-format-fix
Improve type formatting logic in :cast function
2022-11-19 19:25:21 -08:00
Youngil Choi
a87fa0c9ab Improve type formatting logic in :cast function 2022-11-20 06:51:43 +09:00
Sean Corfield
562b20634a fix #434 by special-casing array 2022-11-17 22:39:48 -08:00
Sean Corfield
15cf3ae588 improve :values error message 2022-11-17 08:31:27 -08:00
Sean Corfield
db1b37eaa3 fix #441 2022-11-07 09:18:36 -08:00
Sean Corfield
fd64353f41 prep for 2.4.947 2022-11-05 17:24:15 -07:00
Sean Corfield
95e50a930e fixes #386 #437 #439 2022-11-05 17:15:32 -07:00
Sean Corfield
e8ea9283cc fix Support full TRUNCATE syntax (cascade, identity) #438 2022-11-04 23:40:30 -07:00
Sean Corfield
dda3aa017e Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2022-10-01 00:31:25 -07:00
Sean Corfield
6c107b7cf0 fix #435 by documenting CREATE TEMP TABLE etc 2022-10-01 00:31:20 -07:00
Sean Corfield
9a03dde4d1
Merge pull request #432 from MawiraIke/feature/with-clause-clickhouse
Allow `with expr AS ident` syntax in WITH clause
2022-09-23 09:37:09 -07:00
Ike Mawira
73d36ab2b5 Update docs and tests for WITH clause 2022-09-23 16:54:30 +03:00
Ike Mawira
30e554f31c Allow with expr AS ident syntax in WITH clause 2022-09-22 19:40:15 +03:00
Sean Corfield
e553f4f169 add a test for #431 :) 2022-09-20 04:37:43 -07:00
Sean Corfield
74b05965c1 fix #431 by using if-some instead of if-let 2022-09-20 03:51:19 -07:00
Sean Corfield
9af194f8a2 fix #415 docs already suggest this should work
so this is "just" a bug fix and there are already tests in place.
2022-09-12 18:53:33 -07:00
Sean Corfield
061288f1c0 address #415 by supporting multi-column add/alter/modify 2022-09-12 18:17:26 -07:00
Sean Corfield
5d7a3faea5 address #415 by supporting multiple drop columns 2022-09-12 13:51:54 -07:00
Sean Corfield
fb601c90d9 fix #430 by clarifying the additional escape hatch semantics 2022-09-12 12:45:50 -07:00
Sean Corfield
02d20bd78c address #430 by expanding escape hatch from #352 2022-09-11 20:52:53 -07:00
Sean Corfield
a2571ef312 close #427 by documenting the new function 2022-09-11 19:43:13 -07:00
Sean Corfield
723b134e90 fix set-dialect! reset and document it 2022-09-11 15:09:12 -07:00
Sean Corfield
63df2f3dc9 address #427 by adding set-options! 2022-09-11 14:21:39 -07:00
Sean Corfield
ae27fb75e9 note release date for 2.3.928 2022-09-03 21:43:37 -07:00
Sean Corfield
23be700b7e prep for 2.3.928 2022-09-03 21:39:37 -07:00
Sean Corfield
2f1d3ae870 fixes #421 replace into 2022-09-03 21:34:09 -07:00
Sean Corfield
d0e0badf2c add/update docs for #422 #423 2022-09-03 21:07:06 -07:00
Sean Corfield
9569b19a34 address #425 for map inserts 2022-09-02 22:35:17 -07:00
Sean Corfield
c913ffe155
Merge pull request #424 from seancorfield/issue-422-quoting
#423 #424 #425
2022-09-01 22:57:04 -07:00
Sean Corfield
3073d28525 address #423 by adding support for default
needs documentation
2022-09-01 22:54:00 -07:00
Sean Corfield
737699c11a fix #425 by clarifying MySQL vs PostgreSQL 2022-09-01 22:23:33 -07:00
Sean Corfield
e204f3b45e add security notes about quoting
also consistently use SQL entity names instead of identifiers so the
documentation is consistent in terminology.
2022-08-23 17:18:00 -07:00
Sean Corfield
a879a2d8a1 auto-quote unusual entities by default 2022-08-23 15:41:38 -07:00
Sean Corfield
7a23ab649e with materialization 2022-08-16 17:48:41 -07:00
Sean Corfield
e0157263ab
Merge pull request #420 from robhanlon22/with-materialization
Support materialization control in WITH
2022-08-16 17:45:11 -07:00
Rob Hanlon
204f6fa72a
Support materialization control in WITH
Adds an optional third value to `with` vectors, which can be the
following:

* `:materialized` -> SQL is `WITH cte AS MATERIALIZED (...)`
* `:not-materialized` -> SQL is `WITH cte AS NOT MATERIALIZED (...)`
* omitted or anything else -> SQL is `WITH cte AS (...)`

Note that materialization control is not available on WITH RECURSIVE
CTEs, so `format-with` was modified to take a third argument that
returns the `AS ...` separator, which is constantly `"AS"` for WITH
RECURSIVE, and obeys the aforementioned rules for non-recursive CTEs.

Resolves #392.
2022-08-16 16:01:52 -07:00
Sean Corfield
d31600d2c0 fix string for cljs too 2022-08-06 22:29:17 -07:00
Sean Corfield
c10a97e1ec use default instead of js/Object
This shuts the cljs compiler up
2022-08-06 22:20:29 -07:00
Sean Corfield
2cba8bc3d7 fix cljs for #419 2022-08-06 22:02:21 -07:00
Sean Corfield
99e955f420 fix #419 by adding InlineValue protocol 2022-08-06 21:49:49 -07:00
Sean Corfield
e6654f7a22 check for empty where clauses fix #413 2022-08-06 21:11:08 -07:00
Sean Corfield
061edc42f7 prep for 2.3.911 2022-07-29 18:27:02 -07:00
Sean Corfield
f0ada59fff fix #406 2022-07-29 15:41:04 -07:00
Sean Corfield
5be96ba6e3 document #416 #417 2022-07-28 20:37:34 -07:00
Sean Corfield
43096f72e6
Merge pull request #417 from corasaurus-hex/issue-416
Change public-fns-have-clauses assertion to allow custom clauses
2022-07-28 20:42:22 -07:00
Cora Sutton
79c005b765 Change public-fns-have-clauses assertion to allow custom clauses
Fixes #416
2022-07-28 22:38:01 -05:00
Sean Corfield
5c6ce8a379 update change log 2022-07-28 19:59:31 -07:00
Sean Corfield
5f54fab989 document Differences between IN queries between v1 and v2 #418 2022-07-28 19:56:52 -07:00
Sean Corfield
8e72cb8f2c fixes #414 2022-07-15 11:13:15 -07:00
Sean Corfield
16ce2e8b2d fix #412 (update change log to reflect it) 2022-07-05 22:43:05 -07:00
Sean Corfield
3def127276 address #412 by completing options docs 2022-07-05 22:41:25 -07:00
Sean Corfield
ab0a3eebae the first part of #412 2022-06-27 21:17:54 -07:00
Sean Corfield
91e75a7edf Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2022-05-20 10:46:07 -07:00
Sean Corfield
fb1decbd1e fixes #409 by making the check conditional 2022-05-20 10:46:04 -07:00
Sean Corfield
d065a04e18 placeholder (should :array unwrap params?) 2022-05-20 10:34:10 -07:00
Sean Corfield
2e9b395b31 remove old calva workaround 2022-05-19 20:29:32 -07:00
Sean Corfield
5fe73c75bc Support custom dialects fixes #401 (add docs/tests) 2022-05-01 17:34:31 -07:00
Sean Corfield
afa5c6af99 bump version to 2.3.x for next release 2022-04-30 22:04:50 -07:00
Sean Corfield
8c8b05e67f Support custom dialects: addresses #401
Still needs tests and documentation.
2022-04-30 22:03:36 -07:00
Sean Corfield
70e8afc273 use foo.a instead of confusing f.a 2022-04-23 16:24:04 -07:00
Sean Corfield
d179004180 clarify change for #396 2022-04-23 16:15:02 -07:00
Sean Corfield
796c734cba prep for 2.2.891 release 2022-04-23 16:11:52 -07:00
Sean Corfield
5e847cde0e only throw for named param #396 2022-04-23 16:03:21 -07:00
Sean Corfield
270b9439c8 attempting to cache SQL that contains IN () will throw fixes #396 2022-04-23 15:54:24 -07:00
Sean Corfield
03275b1035 add caveat for caching about #396 2022-04-23 15:45:35 -07:00
Sean Corfield
fdc70c67a9 fix typo in example 2022-04-23 15:45:10 -07:00
Sean Corfield
e45ea8586e Add PostgreSQL JSON / regex operators fixes #398 2022-04-23 15:40:47 -07:00
Sean Corfield
124fac6f28 :insert-into should support abitrary functions for table fixes #402 2022-04-23 15:12:55 -07:00
Sean Corfield
13a8aa11b2 skip the example that throws 2022-04-23 14:11:19 -07:00
Sean Corfield
7f8b7a79b1 addresses #403: improve error message; improve docs 2022-04-23 13:40:34 -07:00
Sean Corfield
9e72587bf3 Document special array constructor for PostgreSQL fixes #404 2022-04-23 13:21:07 -07:00
Sean Corfield
083833cd8b clean up CI names 2022-04-05 14:33:13 -07:00
Sean Corfield
a2e02c8a03 fix #394 by escaping quote chars
This matches the HoneySQL 1.x behavior now.
2022-03-31 17:34:51 -07:00
Sean Corfield
314f497417 fix #400 by adding :table clause 2022-03-26 16:58:28 -07:00
Sean Corfield
2e7da61f56 #398 add names for all ops; hash is mnemonic for # 2022-03-26 14:20:58 -07:00
Sean Corfield
ab7c235329 fix #399 by correcting docs and tests 2022-03-26 13:45:43 -07:00
Sean Corfield
c4efcc0cad #398 || is variadic (by default) 2022-03-26 08:33:05 -07:00
Sean Corfield
17319cdd26 #398 treat -> as variadic 2022-03-26 08:28:24 -07:00
Sean Corfield
af69f12630 address #398 (needs docs) 2022-03-25 21:48:00 -07:00
Sean Corfield
ccaf29292a CI change for Clojure 1.11 release 2022-03-25 21:47:30 -07:00
Sean Corfield
fe17b51e9a
Merge pull request #393 from frwdrik/patch-1
Fix docstring of add-clause-before
2022-03-19 22:28:36 +00:00
Fredrik Vaeng Røtnes
ca0feb57e5
Fix docstring of add-clause-before
The result of adding a clause to a list in which the it already appears, is not to remove it from the list, but to move it to the end.
2022-03-19 22:16:20 +00:00
Sean Corfield
4823ec8694 update build-clj 2022-03-06 12:09:46 -08:00
Sean Corfield
f711b934fa fix #387 properly 2022-02-22 21:27:10 -08:00
Sean Corfield
beedc35a22 prep for 2.2.868 2022-02-21 20:28:07 -08:00
Sean Corfield
803ff41dc0 Dehyphen improvements fixes #387 2022-02-21 19:09:49 -08:00
Sean Corfield
4bf76920ef fix #385 by quoting inlined uuids 2022-02-09 10:20:36 -08:00
Sean Corfield
c7c634d694 change :' to use format-entity #352 2022-02-02 23:51:01 -08:00
Sean Corfield
b7b1ebafb9 note #352 in changelog 2022-02-02 22:36:59 -08:00
Sean Corfield
04324d5002 document #352 in readme 2022-02-02 22:34:21 -08:00
Sean Corfield
1f2773bd16 address #352 by treating :'foo literally 2022-02-02 22:04:44 -08:00
Sean Corfield
1d22086fce fix #381 by adding generic helpers; prep for 2.2.861 2022-01-30 22:31:37 -08:00
Sean Corfield
8a2f447676 Fix #382 by adding :case-expr syntax 2022-01-29 17:19:17 -08:00
Sean Corfield
139de6f56c fix #380 by accounting for vars 2022-01-21 13:05:15 -08:00
Sean Corfield
30209b6cc7 prep for 2.2.858 2022-01-20 13:09:47 -08:00
Sean Corfield
f260185825
Merge pull request #378 from seancorfield/issue-351
first pass of caching formatter
2022-01-20 13:05:45 -08:00
Sean Corfield
e3de2a621d document :cache option
Also try to clear up confusion about function syntax while I'm adding
examples!
2022-01-20 13:02:17 -08:00
Sean Corfield
18adeb1048 rename to satisfy Eastwood 2022-01-10 11:12:47 -08:00
Sean Corfield
9ea822a1b2
Merge pull request #379 from borkdude/issue-351-gvm
Improve performance and GraalVM image size
2022-01-10 10:55:46 -08:00
Michiel Borkent
e331ba0a0e Improve performance and GraalVM image size 2022-01-10 13:58:25 +01:00
Sean Corfield
33ddaa74b6 verify cache entries based on varying parameter names 2022-01-08 12:22:53 -08:00
Sean Corfield
06102e9334 add asserts on cache sizes 2022-01-08 12:16:54 -08:00
Sean Corfield
0a25bc2adf drop jdk 15/16 testing 2022-01-08 00:45:39 -08:00
Sean Corfield
826407e9db first pass of caching formatter
needs more documentation.

Initial results suggest a speedup for simple queries of 2-3x.
Complex queries can see up to 20x speedup.
2022-01-08 00:41:21 -08:00
Sean Corfield
91e054c58b
Merge pull request #376 from seancorfield/except-replace-issue-281
BigQuery Support (except/replace)
2022-01-07 23:17:26 -08:00
Sean Corfield
2670abc75f fix #377 by adopting @corasaurus-hex function as map= 2022-01-07 23:16:56 -08:00
Sean Corfield
99ce051a9d add array/struct support 2022-01-07 12:53:30 -08:00
Sean Corfield
e8e6c7f932 address #281 - support select foo.* except 2022-01-07 11:35:08 -08:00
Sean Corfield
a653f9b157 address #281 add select * except / replace for BigQuery 2022-01-06 23:02:20 -08:00
Sean Corfield
499b9de0ae proposed syntax for #281 2022-01-06 20:41:51 -08:00
Sean Corfield
8976379fa6 update build-clj 2022-01-06 20:30:09 -08:00
Sean Corfield
0867ce54dc update build-clj 2022-01-04 14:00:41 -08:00
Sean Corfield
6e4e1f6928 prep for 2.2.840 2021-12-23 13:37:20 -08:00
Sean Corfield
8979e938f3 fixes #374 2021-12-23 13:32:47 -08:00
Sean Corfield
b30aa0f3f9 fix #375 2021-12-21 22:54:02 -08:00
Sean Corfield
fa0bb6830c Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2021-12-13 10:50:34 -08:00
Sean Corfield
aa8089524b update build/test dependencies 2021-12-13 10:50:29 -08:00
Sean Corfield
df7ea21e7b
update John Shaffer's URL 2021-12-04 15:36:03 -08:00
Sean Corfield
e562329669 typo in docstring 2021-12-04 09:39:43 -08:00
Sean Corfield
8118f5448e prep for 2.1.833 2021-12-03 13:23:51 -08:00
Sean Corfield
d607f01dd5 prep for 2.1.832 2021-12-03 13:19:27 -08:00
Sean Corfield
e0cafbd434 extend lint checks to several column lists 2021-12-03 13:06:34 -08:00
Sean Corfield
dc37852024 fix #372 2021-12-02 15:20:03 -08:00
Sean Corfield
718b7f036f prep for 2.1.829 2021-11-27 16:02:44 -08:00
Sean Corfield
87039e7159 clarify argument lists of helper functions 2021-11-27 15:53:36 -08:00
Sean Corfield
66fc3a68ee fix #354 2021-11-27 15:27:12 -08:00
Sean Corfield
915857754d allow for automated release 2021-11-26 22:43:51 -08:00
Sean Corfield
d660c059d4 fix #370 2021-11-26 22:40:51 -08:00
Sean Corfield
be1df97b2b fix #371 2021-11-26 22:30:36 -08:00
Sean Corfield
627bc25357 retire CircleCI; update build-clj 2021-11-26 22:06:16 -08:00
Sean Corfield
dce2269be7 use more flexible cache 2021-11-01 16:56:07 -07:00
Sean Corfield
36e0ede47e move template pom.xml 2021-11-01 12:51:18 -07:00
Sean Corfield
e9abb81892 strip pom.xml and rename
Since it isn't a real pom.xml file now.
2021-11-01 11:52:08 -07:00
Sean Corfield
ceabd49508 bump deps 2021-10-16 22:43:43 -07:00
Sean Corfield
9052626805 prep for 2.1.818; document :values-default-columns 2021-10-04 16:09:28 -07:00
Sean Corfield
3cacec9c32 addresses #366 -- needs documentation! 2021-10-03 22:32:05 -07:00
Sean Corfield
6aee04e25c fixes #365 2021-10-03 22:18:12 -07:00
Sean Corfield
3d0a9ba79c fixes #367 2021-10-03 21:59:11 -07:00
Sean Corfield
80bbf85c3a typo 2021-09-25 18:47:18 -07:00
Sean Corfield
78cc769b98 prep for 2.0.813 2021-09-25 17:58:51 -07:00
Sean Corfield
472701d728 use 17 (no ea now) 2021-09-25 17:52:19 -07:00
Sean Corfield
ed844ed057 add snapshot on deploy 2021-09-25 17:51:48 -07:00
Sean Corfield
feb0c9a6f8 fixes #347 2021-09-25 17:35:02 -07:00
Sean Corfield
92e0a04a84 fixes #353 fixes #349 2021-09-25 17:27:04 -07:00
Sean Corfield
dae09ff601 fixes #363 by improving inlining capability 2021-09-25 17:06:48 -07:00
Sean Corfield
e44a30a7fd fixes #364 2021-09-25 16:32:00 -07:00
Sean Corfield
c7c6102661 address #353 for create table/string entity 2021-09-09 10:41:37 -07:00
Sean Corfield
ec758dd818 support AS in DELETE FROM alias 2021-09-01 10:20:19 -07:00
Sean Corfield
871908d5c9 update build-clj dep 2021-08-31 14:57:12 -07:00
Sean Corfield
e19870aba7 change pom to flush GH actions cache 2021-08-30 19:43:19 -07:00
Sean Corfield
bcdd81e931 set cache key from deps.edn 2021-08-30 19:40:46 -07:00
Sean Corfield
241f0c2519 trigger new maven cache? 2021-08-30 19:36:25 -07:00
Sean Corfield
3798dca428 update change log; clean up build 2021-08-30 19:30:55 -07:00
Sean Corfield
06e9917daa
Merge pull request #346 from lread/lread-issue-290
Test code blocks in docs with test-doc-blocks
2021-08-30 19:15:16 -07:00
lread
68a63b7078 Merge remote-tracking branch 'upstream/develop' into lread-issue-290
* upstream/develop:
  update eastwood etc
  add distribution (jdk)
  add maven cache to GH Actions
2021-08-30 22:07:08 -04:00
lread
1c042c375b PR review feedback: change run-doc-tests arg to :aliases
Example usages are now:

```
clojure -T:build run-doc-tests

clojure -T:build run-doc-tests :aliases '[:cljs]'

clojure -T:build run-doc-tests :aliases '[:1.9]'

clojure -T:build run-doc-tests :aliases '[:1.10]'

clojure -T:build run-doc-tests :aliases '[:master]'
```
2021-08-30 21:50:13 -04:00
Sean Corfield
17881ef2b4 update eastwood etc 2021-08-30 18:47:10 -07:00
lread
ae29147a22 PR review feedback: move gen-doc-tests build code
Move the code that generates doc tests out from build.clj
to its own source runnable via main.

This isolates gen-doc-tests work to its own process
during the build.

I arbitrarily:
- chose 'build' as the folder for build helper sources
- namespaced our first build helper under 'honey.gen-doc-tests'
2021-08-30 21:08:28 -04:00
Sean Corfield
9b018aa4ee add distribution (jdk) 2021-08-30 18:00:33 -07:00
Sean Corfield
55b68c4918 add maven cache to GH Actions 2021-08-30 17:58:15 -07:00
lread
458cc9fa52 Tweak tests regeneration from docs
Tests will now always regenerate when:

- there is no last successful generation run
- OR when any docs, build files, or source files are newer than the
last successful generation run.
2021-08-30 17:56:42 -04:00
lread
cccd3e96fd Doc block testing now understands refer-clojure
Test-doc-blocks added support for this feature
2021-08-30 17:40:25 -04:00
lread
4eb9eca147 Merge remote-tracking branch 'upstream/develop' into lread-issue-290
Adapt to build changes.

* upstream/develop:
  Switch to build-clj lib
2021-08-28 17:14:04 -04:00
Sean Corfield
c81d27a224 Switch to build-clj lib 2021-08-27 22:51:49 -07:00
lread
35c5aee584 Test code blocks in docs with test-doc-blocks
Resolves #290

**Build**

New commands:
- `gen-doc-tests` - only regenerates tests if stale,
   use `clean` command to force regen
- `run-doc-tests` - calls gen-doc-tests then runs tests,
  accepts the same parameters as run-tests.
  Can specify `:platform`
    - `:cljs` - run tests under ClojureScript
    - otherwise Clojure where we can specify one of: `:1.9`
    `:1.10` `:master`

I'm not sure if my use of the `:platform` parameter jives with
your `:aliases` parameter used for `run-tests`.
Can adjust if you like.

Example usages:
```shell
clojure -T:build gen-doc-tests

clojure -T:build run-doc-tests :platform :cljs

clojure -T:build run-doc-tests

clojure -T:build run-doc-tests :platform :1.10
```

The `ci` command has been updated to generate and run doc tests for same
platforms as unit tests.

**Articles**

In addition to `README.md`, now testing doc blocks in all articles
under `doc` dir excepting `doc/operator-reference.md` which does not
have any runnable code blocks.

**Skipped**

Any code block that is intentionally not runnable has been marked to be
skipped via: `<!-- :test-doc-blocks/skip -->`.

**Consistency**

I noticed that some code blocks use REPL syntax:
```Clojure
user=> (+ 1 2 3)
6
```
and others use editor syntax:
```Clojure
(+ 1 2 3)
;;=> 6
```
some places also omit the comment for editor style:
```Clojure
(+ 1 2 3)
=> 6
```
All of this is just fine with test-doc-blocks.
I left the inconsistency as is, but can make a pass for consistency upon
request.

**HoneySQL state**

I noticed a code block that set the sql dialect was affecting other
tests. I simply restored the dialect to the default at the end of the
code block.

**Un-tweaked output**

Some code blocks had string output hand-tweaked for readability.
These have been adjusted to instead use `sql/format`'s `:pretty` option.
In some cases the output is not as readable as the hand-tweaked version.
I humbly suggest that perhaps `:pretty` output could perhaps be
improved (instead of having test-doc-blocks somehow adapt).

**Corrections**

There were very few code blocks that required fixing due to incorrect
output/code.  Please review the diffs carefully to make sure all is as
expected.

**refer-clojure :excludes**

Not currently supported for test-doc-blocks, not a real issue for
Clojure, we'll see warnings under Clojure, but that's probably ok.

But I might actually need it for ClojureScript.
I was finding that `for` did not get overridden by our helper
`:refer` in CloureScript.

Will add proper support to test-doc-blocks but in the short-term,
will use `h/for`.

**ns requires adjustments**

Any specific case of `(ns my-ns (require [my-require :as a]))` is now
the REPL friendly `(require '[my-require :as a])`

Any missing required `requires` were added.

The HoneySQL docs seem to encourage the use of referred vars for
helpers. Although this has the con of overlaps with Clojure core vars,
it is also convenient for Clojure when using `:refer :all`.

**ClojureScript :refer**

ClojureScript does not support `:refer :all` and each var must be
specified in place of `:all`.

I have adjusted examples accordingly to work with both Clojure and
ClojureScript.
2021-08-27 18:39:07 -04:00
Sean Corfield
584cd1c711 update tools.build; add deploy via build 2021-08-22 19:30:04 -07:00
Sean Corfield
5246fe4549 Clean up prebuild 2021-08-18 08:31:31 -07:00
Sean Corfield
943b9547c9 Add Open in Gitpod badge/link 2021-08-17 22:49:43 -07:00
Sean Corfield
eac5e933c3 Fix runner/build/gitpod REPL 2021-08-17 22:39:07 -07:00
Sean Corfield
ee65201cfc prep for 2.0 Gold (2.0.783) 2021-08-15 11:27:09 -07:00
Sean Corfield
68a1ccfa3c Bump tools.build; write scm>tag 2021-08-13 16:10:45 -07:00
Sean Corfield
68eafc0c2e clean up :deploy 2021-08-12 19:43:16 -07:00
Sean Corfield
5f68b8ca77 add build alias to cache 2021-08-12 19:24:24 -07:00
Sean Corfield
4ba9d2f6ca fix cljs test alias 2021-08-12 19:21:35 -07:00
Sean Corfield
5191abaa6c switch to tools.build 2021-08-12 19:20:02 -07:00
Sean Corfield
dabb501e4b fix typos in readme 2021-08-12 18:34:18 -07:00
Sean Corfield
9ece8972b5 fixes #344 by special-casing MySQL SET 2021-08-12 18:26:39 -07:00
Sean Corfield
249ab639ec Add links to HoneySQL web app 2021-08-06 11:35:53 -07:00
Sean Corfield
aef6e4cba7 fix cache setup 1.9/1.10 2021-07-31 19:37:22 -07:00
Sean Corfield
e51637008d update change log 2021-07-29 23:23:29 -07:00
Sean Corfield
97c42f5c78 does circleci have 1.10.3.933 yet? 2021-07-29 23:09:56 -07:00
Sean Corfield
964f9837b0 version bumps 2021-07-29 23:00:02 -07:00
Sean Corfield
10e6e755e9 fixes #340 2021-07-22 19:49:25 -07:00
Sean Corfield
3288ee7ec5 Update changelog to reflect doc changes 2021-07-18 19:02:19 -07:00
Sean Corfield
4480c9c1c7 Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2021-07-18 16:36:15 -07:00
Sean Corfield
904af73505 Fixes #339 by calling out param/lift for JSON usage 2021-07-18 16:36:11 -07:00
Sean Corfield
3680a1beae Address #332 by improving select docs 2021-07-17 18:59:26 -07:00
Sean Corfield
13044d7394 fix/improve limit, offset, fetch docs 2021-07-17 18:49:11 -07:00
Sean Corfield
01c3a555ba prep for RC 5(!) 2021-07-17 18:00:02 -07:00
Sean Corfield
50bbfef07f Fixes #338 properly by making offset/fetch smarter 2021-07-17 17:57:17 -07:00
Sean Corfield
52e2a57fca Fix fetch helper 2021-07-17 16:51:34 -07:00
Sean Corfield
1836c7bcf1 Address #332 by improving :cross-join docs 2021-07-17 14:49:18 -07:00
Sean Corfield
679a0309db prep for 2.0.0-rc4 2021-07-17 14:25:55 -07:00
Sean Corfield
7e84b58463 fixes #338 by adding ONLY to :fetch 2021-07-17 13:32:43 -07:00
Sean Corfield
994f166d40 Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2021-07-15 19:27:05 -07:00
Sean Corfield
a393cd89e2 fixes #337 by using clojure.test for cljs 2021-07-15 19:26:59 -07:00
Sean Corfield
8387dad01a
Merge pull request #336 from pinealan/remove-dup-doc
Remove duplicate docs example of insert clause
2021-07-10 15:11:42 -07:00
Alan Chan
fa5c45e985 Remove duplicate example of insert clause 2021-07-10 14:10:23 +08:00
Sean Corfield
98e94ada67 Cleanup .gitignore 2021-07-09 16:19:47 -07:00
Sean Corfield
616543c35d Update CI 2021-06-21 15:35:57 -07:00
Sean Corfield
1b2e742b38 Reflect revert of CI changes 2021-06-21 15:26:23 -07:00
Sean Corfield
1aec4c3e35 Revert run-tests.sh changes 2021-06-21 15:24:30 -07:00
Sean Corfield
14a3dea831 Revert shellcheck #331 2021-06-21 15:22:53 -07:00
Sean Corfield
8d159391c0 Accept parts of #331 2021-06-21 15:20:41 -07:00
Sean Corfield
395ba6a5bd Address #334 in part by correcting :arglist 2021-06-21 15:17:04 -07:00
Sean Corfield
8ec08ca686 Addresses #331 by changing some CI stuff
* Updated Eastwood to 0.5.1
* Added set -Eeo pipefail to run-tests.sh (I don't like -x)
* Run CI on PR
* Add shellcheck to steps

I'm not going to run Eastwood on tests at this point (see my
comments in #334 for reasons).
2021-06-21 15:16:18 -07:00
Sean Corfield
13b640d486 Note with-columns bug fix 2021-06-21 14:51:45 -07:00
Sean Corfield
1c2f1f4a99 Merge branch 'develop' of github.com:seancorfield/honeysql into develop 2021-06-21 14:50:58 -07:00
Sean Corfield
b32911d808 Fix bug in unrolling with-columns args 2021-06-21 14:50:55 -07:00
Sean Corfield
4324ed4250
Merge pull request #333 from john-shaffer/bcm-multi-join
Add multiple join to big complicated map example.
2021-06-18 20:33:29 -07:00
John Shaffer
d89cdb52d5 Add multiple join to big complicated map example. 2021-06-18 21:43:01 -05:00
Sean Corfield
cf136c2fdc cljs/1.9 compatibility 2021-06-17 13:05:49 -07:00
Sean Corfield
00b20f6bbf Address #330 by improving exception 2021-06-17 12:54:15 -07:00
Sean Corfield
a2ee638b3b prep for 2.0.0-rc3 2021-06-16 23:01:33 -07:00
Sean Corfield
3b00d5c3e6 bump :test-runner 2021-06-16 22:35:53 -07:00
Sean Corfield
0ab991d856 drop :runner now we use -X 2021-06-16 18:25:59 -07:00
Sean Corfield
bece0f1fd1 Fixes #327 by correcting generic-1 helper 2021-06-12 18:57:36 -07:00
Sean Corfield
21c59ff96d Update CHANGELOG for #327 2021-06-12 18:48:27 -07:00
Sean Corfield
228eb9fb67 Fix #327 by improving error message 2021-06-12 18:47:10 -07:00
Sean Corfield
a51cfe5a2e Fixes #328 by adding :distinct special syntax 2021-06-12 18:15:59 -07:00
Sean Corfield
f508196ba3 Address #329 2021-06-12 18:15:41 -07:00
Sean Corfield
21e59ed408 ClojureScript (not 's') 2021-06-09 17:25:45 -07:00
Sean Corfield
6b284bfc77 Note NULLS FIRST/LAST in ORDER BY 2021-05-28 18:24:54 -07:00
Sean Corfield
798219400a Add CoC to more of my projects 2021-05-21 15:40:00 -07:00
Sean Corfield
0d5f416de8 Fix cljdoc links 2021-05-20 12:05:32 -07:00
Sean Corfield
f1ed7f0c90 Add links back to 1.x in README 2021-05-20 11:54:52 -07:00
Sean Corfield
77191e26f6 Explain SELECT function expression 2021-05-19 18:12:50 -07:00
Sean Corfield
fb60113858 Merge branch 'v2' into develop 2021-05-19 17:45:13 -07:00
Sean Corfield
52308d484f Support PostgreSQL && array operator 2021-05-17 10:40:19 -07:00
Sean Corfield
5e98454f28 Merge branch 'v2' of github.com:seancorfield/honeysql into v2 2021-05-11 21:10:36 -07:00
Sean Corfield
29a060d422 Update CircleCI CLI 2021-05-11 21:10:32 -07:00
Sean Corfield
9f5b552ad0
Add GH Actions CI badge for v2 2021-05-11 18:32:43 -07:00
Sean Corfield
81fda8592d Prep for 2.0.0 RC 2 2021-05-10 22:51:41 -07:00
Sean Corfield
fe4be6cca6 Make test cljs-compatible 2021-05-10 22:46:36 -07:00
Sean Corfield
9e0b31bbd9 Fixes #325 Fixes #326 adds tests/docs 2021-05-10 22:44:57 -07:00
Sean Corfield
819c6e7c19 Additional work on #325; update CHANGELOG 2021-05-10 12:37:07 -07:00
Sean Corfield
e38bb73295
Merge pull request #325 from zugnush/quoting_fix
quoting for :%fun.col(s) syntax to match with [[:fun :col]]
2021-05-10 12:24:06 -07:00
Sean Corfield
572984a45d Addresses #326 by allowing ON/USING to be optional 2021-05-10 12:05:07 -07:00
Sean Corfield
12bfa32962 Addresses #326 by no longer dropping parameters 2021-05-10 11:55:28 -07:00
Neil McCalum
9c38554154 format with opts, hyphen to under in function names, improve and relocate tests 2021-05-11 01:07:34 +12:00
Sean Corfield
07a4d9127d Update deps 2021-05-09 16:06:49 -07:00
Sean Corfield
0f31fa331b Merge branch 'v2' of github.com:seancorfield/honeysql into v2 2021-05-09 11:36:45 -07:00
Sean Corfield
6ca391c84a Update test-runner 2021-05-09 11:35:33 -07:00
Neil McCalum
6ebc017969 quoting for :%fun.col(s) syntax to match with [[:fun :col]] 2021-05-09 17:18:39 +12:00
Sean Corfield
d73560b7e3 Add :quoted-snake option 2021-05-08 21:01:28 -07:00
Sean Corfield
11fcfd5257 Prep for 2.0.0-rc1 2021-05-06 22:12:46 -07:00
Sean Corfield
46b3c1773b Fixes #324 by correcting insert-into 2021-05-01 12:56:42 -07:00
Sean Corfield
20cba15da2 Fixes #321 by adding :checking option
Initial linting is only for IN () and IN (NULL)
2021-04-22 22:13:32 -07:00
Sean Corfield
f606dc6044 Fixes #323 by allowing multiple column names 2021-04-22 19:16:30 -07:00
Sean Corfield
e227e1b9ab Prep for 2.0.0-beta2 2021-04-13 12:55:40 -07:00
Sean Corfield
dd52ebe7e8 Fixes #322 by rewriting where/having merge 2021-04-13 12:51:21 -07:00
Sean Corfield
272b088918 Revert "Addresses #315 by expanding IN to handle nil"
This reverts commit 8a1e2cca71.
2021-04-12 15:04:17 -07:00
Sean Corfield
684b33a03a Note correction to join-by docstring 2021-04-11 04:39:35 -07:00
Sean Corfield
e6a5bdb001 Fixes #308 by adding support for clauses
This also corrects the docstring for the join-by helper.
2021-04-11 04:18:14 -07:00
Sean Corfield
d734767877 Remove the old 1.x code
It hasn't been part of the deployed library for quite a while because I
had moved it into the test folder, while I was reaching parity.
2021-04-11 03:21:35 -07:00
Sean Corfield
bb9d196e3e Document nilenso catch up to 0.4.112 2021-04-11 03:14:23 -07:00
Sean Corfield
50fd829752 Addresses #310 by adding filter, order-by, within-group syntax 2021-04-11 14:32:48 -07:00
Sean Corfield
bbc0ac8500 Documentation updates 2021-04-11 11:39:06 -07:00
Sean Corfield
84147b242d Bump depstar 2021-04-11 11:10:13 -07:00
Sean Corfield
862a2496c6 Consistent use of 1.x / 2.x 2021-04-11 11:09:47 -07:00
Sean Corfield
f393a61010 Remove honey.specs (for now) #146 2021-04-10 03:58:29 -07:00
Sean Corfield
8a1e2cca71 Addresses #315 by expanding IN to handle nil 2021-04-10 10:57:13 -07:00
Sean Corfield
f231151243 Fix column descriptors links 2021-04-10 00:17:42 -07:00
Sean Corfield
7a83bc13fe Tweak docstring 2021-04-10 00:13:04 -07:00
Sean Corfield
35c6fc58a5 Prep for 2.0.0 Beta 1 2021-04-09 23:51:31 -07:00
Sean Corfield
88282ee258 Fixes #316 by adding check on entity characters
Also record that documentation addresses #300, #309, #313, and #314.
2021-04-09 23:41:59 -07:00
Sean Corfield
cf7e36a131 Addresses #313 by documenting entity formatting in detail 2021-04-09 23:07:19 -07:00
Sean Corfield
1548433c02 WIP on entities 2021-04-09 18:39:01 -07:00
Sean Corfield
0411364ee5 Addresses #314 by documenting composite 2021-04-09 18:06:57 -07:00
Sean Corfield
c2e8bb9193 Finish off the PostgreSQL comparison docs 2021-04-09 17:48:13 -07:00
Sean Corfield
2da32c70f1 Clause reference tweaks/reordering 2021-04-09 17:35:39 -07:00
Sean Corfield
8f7c990eed Complete special syntax docs 2021-04-09 17:23:39 -07:00
Sean Corfield
2fe083f8e6 Support named arguments in format again
With the advent of Clojure 1.11 and the ability to call functions that
accept named arguments using a hash map, I have restored the named
argument version of `format` (in addition to the hash map version),
and if you are using Clojure 1.11 you can mix'n'match styles.
2021-04-09 17:04:48 -07:00
Sean Corfield
106b19bcf6 Add clause-order to see current clause order
This should help when folks are figuing out `register-clause!` calls.
2021-04-09 16:31:16 -07:00
Sean Corfield
af382708e5 Fixes #319 by making register-clause! idempotent
Technically, it removes any instance of the clause from the ordering
before it attempts to add it back in, allowing you to correct the
order if you got it wrong.
2021-04-09 15:58:56 -07:00
Sean Corfield
b321df25d9 Add note about SQL Server TOP 2021-04-09 12:50:21 -07:00
Sean Corfield
cfb001e2f5 Complete the clause documentation! 2021-04-09 12:43:09 -07:00
Sean Corfield
dbecb152a1 Documentation updates and clarifications
This adds placeholders for reference documentation.
2021-04-08 23:06:07 -07:00
Sean Corfield
06f25ed2e3 Fixes #317 by dropping qualifier in :set clause 2021-04-01 12:50:09 -07:00
Sean Corfield
b010f91586 Update Eastwood; run it on Clojure 1.9 2021-04-01 12:28:13 -07:00
Sean Corfield
24b87ebd69 Use Clojure 1.10 for Eastwood 0.3.14 2021-03-21 12:23:42 -07:00
Sean Corfield
230aa7088a Minor library updates 2021-03-21 12:07:08 -07:00
Sean Corfield
337d2c7f5d Minor library version updates 2021-03-21 11:55:47 -07:00
Sean Corfield
fde22f6bc0 Correct issue # in change log 2021-03-15 16:55:51 -07:00
Sean Corfield
13d62b4ac1 Also show quoting/not-quoting and table.col forms 2021-03-15 16:54:13 -07:00
Sean Corfield
73c2062d6e Investigation of string-as-entity behavior
This is something that should be updated in the docs.
2021-03-15 16:45:26 -07:00
Sean Corfield
9672ed3704 Fixes #312 (properly this time) 2021-03-15 15:26:22 -07:00
Sean Corfield
fd84864279 Fixes #161 by adding :raw clause support 2021-03-15 14:48:28 -07:00
Sean Corfield
d76b2d82b1 Prep for 2.0 Alpha 3 2021-03-13 15:55:31 -08:00
Sean Corfield
7c21a403c2 Fixes #295 by documenting all helpers 2021-03-13 15:35:47 -08:00
Sean Corfield
9f6393a2fd Fix README now into is a helper function 2021-03-13 13:46:47 -08:00
Sean Corfield
16d04a1dfd Fixes #297 by adding into/bulk-collect-into 2021-03-13 13:42:08 -08:00
Sean Corfield
cff1e5b43c Fixes #284 by adding lateral expression syntax 2021-03-13 13:13:35 -08:00
Sean Corfield
82ee465820 Fixes #292 by supporting offset/fetch 2021-03-13 12:36:25 -08:00
Sean Corfield
6b070df52c Finish off TOP implementation #292 2021-03-13 12:10:42 -08:00
Sean Corfield
479008c294 Address #292 by starting to support SELECT TOP 2021-03-12 18:54:19 -08:00
Sean Corfield
dc6a3662f0 Fixes #303 by adding ON DUPLICATE KEY UPDATE 2021-03-12 16:13:52 -08:00
Sean Corfield
d35d9141bc Fixes #280 by adding [:escape pattern chars] 2021-03-12 15:39:54 -08:00
Sean Corfield
ddebda9481 Fixes #301 Fixes #306 by expanding drop/create syntax 2021-03-12 11:43:21 -08:00
Sean Corfield
445fb08e2f Initial work to support #301 2021-03-11 20:07:59 -08:00
Sean Corfield
efcee05e0a Beginnings of generic create #301 #306 2021-03-07 22:13:25 -08:00
Sean Corfield
e70985e93b Fixes #277 by adding join-by 2021-03-07 19:21:13 -08:00
Sean Corfield
97c9236842 Fixes #307 by adding data DSL examples
I've added a pure data DSL version of nearly all the helper function
examples. I've added a few examples of the data DSL with symbols instead
of keywords as well.
2021-03-07 17:07:38 -08:00
Sean Corfield
d789c00f54 Reflect latest 1.x version 2021-03-07 09:45:08 -08:00
Sean Corfield
41522c89a1 Clarify 1.9+ Clojure support 2021-03-07 09:43:03 -08:00
Sean Corfield
77577517c8 Note parity with nilenso/honeysql-postgres 2021-03-06 22:27:53 -08:00
Sean Corfield
1bbdfeef75 Update changelog to reflect current state 2021-03-06 22:19:02 -08:00
Sean Corfield
b600348808 Addresses #283 on the v1 branch 2021-03-06 22:10:43 -08:00
Sean Corfield
06d90c174e Rework and-merge WIP (tests fail) 2021-03-06 18:40:23 -08:00
Sean Corfield
5e2036a922 Temporarily comment out WIP tests 2021-02-27 09:35:03 -08:00
Sean Corfield
78ca2a0530 Fixes #305 by supporting more complex JOIN source 2021-02-27 09:33:20 -08:00
Sean Corfield
770beec886 Add test for (v1) SQL Injection in insert #299 2021-02-22 10:56:32 -08:00
Sean Corfield
b0782b93dd Prep for 2.0.0-alpha2 2021-02-16 11:43:18 -08:00
Sean Corfield
cef11a761d Prep for 2.0.0-alpha2 2021-02-16 11:42:56 -08:00
Sean Corfield
df60954495 Document DDL support 2021-02-15 21:20:20 -08:00
Sean Corfield
28a4074e12 Add flexibility for on conflict / on constraint 2021-02-15 20:43:53 -08:00
Sean Corfield
2f424e0258 Add a big chunk of Postgres/nilenso docs 2021-02-15 17:57:07 -08:00
Sean Corfield
331597b22b Improve on conflict on constraint syntax 2021-02-15 17:56:47 -08:00
Sean Corfield
155ae25ad0 Clean up create extension test 2021-02-15 17:56:18 -08:00
Sean Corfield
c0dfdad19b Add modifiers to differences 2021-02-15 16:37:00 -08:00
Sean Corfield
0b687c5eb0 Add nilenso version of tests for comparison
This shows all the changes side-by-side.
2021-02-15 16:27:08 -08:00
Sean Corfield
0bb7740f69 Docstrings 2021-02-14 20:45:14 -08:00
Sean Corfield
f4137d3fc1 Enhance insert-into; document more helpers 2021-02-14 20:39:32 -08:00
Sean Corfield
05360d10d6 More helper docstrings 2021-02-14 18:28:35 -08:00
Sean Corfield
d5ab2a8d8c More helper docstrings 2021-02-14 17:54:13 -08:00
Sean Corfield
9ec447109f Oracle dialect suppresses AS 2021-02-14 17:54:00 -08:00
Sean Corfield
f6a38e4024 More helper docstrings 2021-02-14 17:20:24 -08:00
Sean Corfield
ea4e120252 Add docstrings for DDL helpers 2021-02-14 16:52:35 -08:00
Sean Corfield
63add4df3b Clean up differences code 2021-02-14 14:21:54 -08:00
Sean Corfield
2af7d0b690 Document :exists difference 2021-02-14 14:17:31 -08:00
Sean Corfield
26741450e6 Note relation between :lift and honeysql.format/value 2021-02-14 14:00:28 -08:00
Sean Corfield
67c85ba57c Oops: 1.10.2.774 (not 1.10.1.774) 2021-02-14 13:07:11 -08:00
Sean Corfield
5c8a8d0eb6 Update CLI version for CI 2021-02-14 13:05:40 -08:00
Sean Corfield
a7ee19ee2b Add cljdoc.edn check to CI 2021-02-14 12:50:24 -08:00
Sean Corfield
f84726a48b Reformatting to prepare for docstring addition 2021-02-14 11:37:27 -08:00
Sean Corfield
78fe59d98c Fix one more link! 2021-02-14 11:04:39 -08:00
Sean Corfield
efa2fe6af5 Fix v2 badge 2021-02-14 10:59:41 -08:00
Sean Corfield
66d9f9dacb Fix some version-related links 2021-02-14 10:45:49 -08:00
Sean Corfield
0029402ae7 Fix typo in cljdoc.edn 2021-02-14 10:36:37 -08:00
Sean Corfield
41b1ce0eb8 Fix some links 2021-02-13 22:20:18 -08:00
Sean Corfield
fea5c74260 Set up for 2.0 Alpha 1 2021-02-13 22:10:10 -08:00
Sean Corfield
e23502eba8 Add create/drop extension #293 2021-02-13 22:06:43 -08:00
Sean Corfield
22384b9daa Fix links 2021-02-13 21:40:32 -08:00
Sean Corfield
38da6f567b Fixes #294 by applying inline everywhere 2021-02-13 21:29:22 -08:00
Sean Corfield
645ce897c5 Document % function shorthand 2021-02-13 21:17:25 -08:00
Sean Corfield
2d9ceb73a6 Expand documentation 2021-02-13 21:10:49 -08:00
Sean Corfield
e585ded37e Restore upsert helper for #293 2021-02-13 19:08:40 -08:00
Sean Corfield
267eef778a Some more documentation! 2021-02-13 17:30:16 -08:00
Sean Corfield
6355452102 Update docs for the PG extension stuff 2021-02-13 16:17:30 -08:00
Sean Corfield
7b928fecb2 Addresses #293 by porting nilenso tests
I intended to keep all the original tests inline but it got harder than
I expected, so I'm going to make another pass over this and insert the
original tests back in later.
2021-02-13 16:02:13 -08:00
Sean Corfield
f6975ef6bd Fix select as, select distinct on 2021-02-13 15:58:56 -08:00
Sean Corfield
4ce56997c9 Fix rename table 2021-02-13 12:24:21 -08:00
Sean Corfield
4b7ded4009 WIP nilenso test (fails) 2021-02-13 10:50:36 -08:00
Sean Corfield
41ed38ea38 Finish and document DDL 2021-02-12 21:50:22 -08:00
Sean Corfield
167d7cee0c Addresses #293 by adding alter table stuff
And documenting more of the DDL.
2021-02-12 17:08:37 -08:00
Sean Corfield
49d8365bfb Addresses #293 partial with-columns implementation 2021-02-10 22:32:29 -08:00
Sean Corfield
83d4ccba38 Addresses #293 start work on DDL support 2021-02-10 20:07:05 -08:00
Sean Corfield
e157aec976 Addresses #293 by adding over, partition-by, and window 2021-02-10 16:25:31 -08:00
Sean Corfield
5318c184e6 Addresses #293 by expanding insert-into behavior
This adds alias support into `:insert-into`.
It also adds some tests for the PostgreSQL-specific
stuff currently in nilenso's library.
2021-02-10 15:07:16 -08:00
Sean Corfield
0a83601c3c Fixes #298 by restoring multi-CTE capability
Reverts code changes that stripped the sequence support.
Updates the docs to clarify how sequence support works.
2021-02-10 12:04:53 -08:00
Sean Corfield
e02b5b5c82 Revert "Document with / CTE"
This reverts commit c00fbffe30.
2021-02-10 11:50:00 -08:00
Sean Corfield
8f725abe02 Revert "Update CTE test to verify both 1.x and 2.x"
This reverts commit 16dd1fff41.
2021-02-10 11:49:32 -08:00
Sean Corfield
2c9be16d97 Finish the clause docs 2021-02-08 12:48:42 -08:00
Sean Corfield
9f579f492a Allow symbols in for/lock clauses 2021-02-08 12:48:33 -08:00
Sean Corfield
c8fe84cbb4 Document values 2021-02-07 08:41:11 -08:00
Sean Corfield
09745c7237 Document :using 2021-02-07 16:36:52 -08:00
Sean Corfield
d2a08c17ef More docs 2021-02-07 05:13:39 -08:00
Sean Corfield
c39f1b49ee More clause docs 2021-02-07 04:47:31 -08:00
Sean Corfield
16dd1fff41 Update CTE test to verify both 1.x and 2.x 2021-02-07 04:47:23 -08:00
Sean Corfield
88ec86fd18 Allow direction to default with expression 2021-02-07 04:47:08 -08:00
Sean Corfield
bb16567d50 Document more clauses 2021-02-07 03:40:29 -08:00
Sean Corfield
c00fbffe30 Document with / CTE
Change the implementation to support just a pair instead of the legacy,
weird sequence of one pair.
2021-02-06 09:00:46 -08:00
Sean Corfield
d71e1149a8 Ignore LSP folder 2021-02-03 10:59:08 -08:00
Sean Corfield
1d2b8e8203 Document special syntax 2021-02-02 16:43:09 -08:00
Sean Corfield
ea0bfbabc1 Outline of docs for clauses, operators, and special syntax 2021-02-02 14:50:12 -08:00
Sean Corfield
80c137949e Initial Getting Started/Extending HoneySQL docs 2021-02-02 12:25:26 -08:00
Sean Corfield
25acc53b24 Bump cljs-test-runner to get more recent cljs
So that `(symbol :kw)` works (although I decided to use an explcit call
to `name` instead.
2021-02-02 12:12:01 -08:00
Sean Corfield
4cbeb170dd Ensure no dependence on vector (vs sequence) 2021-02-01 16:45:35 -08:00
Sean Corfield
68eb590527 Add note about nilenso-postgres 2021-02-01 16:45:18 -08:00
Sean Corfield
1b749c1599 Overhaul differences document 2021-02-01 16:24:19 -08:00
Sean Corfield
d7c7f15a6b Final de-?-ing keyword arguments/options 2021-02-01 16:24:00 -08:00
Sean Corfield
469e5a393e Link to differences; fix clojars/cljdoc links etc 2021-02-01 15:09:27 -08:00
Sean Corfield
53bcfd8bea Move toward 2.0.0 Alpha 1 2021-02-01 14:49:17 -08:00
Sean Corfield
379d3d05c4 Drop ? from all keyword options 2021-02-01 13:10:57 -08:00
Sean Corfield
2ef9d4dad2 Docstring overhaul 2021-02-01 12:00:42 -08:00
Sean Corfield
7aab640e30 Fixes #291 by ensuring consistent values
For uneven vectors of values, pad with NULLs.

For uneven maps of values, use full set of keys across all of them.
2021-02-01 10:44:07 -08:00
Sean Corfield
3439bb6c48 More CLI tweaking for CircleCI 2021-01-31 06:08:14 -08:00
Sean Corfield
97172103b4 Update CircleCI CLI usage 2021-01-31 06:05:09 -08:00
Sean Corfield
ad42560370 CircleCI -- node symlink no longer needed? 2021-01-31 06:01:47 -08:00
Sean Corfield
3bde6cd8e7 Doh! CircleCI clojure tools was too old! 2021-01-31 06:00:27 -08:00
Sean Corfield
ed86e7837c Another attempt to fix CircleCI build 2021-01-31 05:51:55 -08:00
Sean Corfield
63e4c8523f Fix CircleCI gitconfig 2021-01-31 05:47:14 -08:00
Sean Corfield
0e2aae4a00 Fix :lift and test to use wrapper 2021-01-31 05:31:10 -08:00
Sean Corfield
c92656389b Make params dynamic/implement in/not-in 2021-01-31 05:08:21 -08:00
Sean Corfield
1f4c4ff7ba Show :lift example in :raw 2021-01-30 12:42:08 -08:00
Sean Corfield
429761f106 Fixes #261 by re-implementing :raw 2021-01-30 12:35:51 -08:00
Sean Corfield
4af4f3f7ed Fixes #274 by committing to seancorfield/honeysql for v2 2021-01-30 11:59:35 -08:00
Sean Corfield
0090bf4d1c Drop 1.7 & 1.8 from CircleCI as well 2021-01-30 11:52:07 -08:00
Sean Corfield
67c6c3a2cf Fixes #275 by dropping 1.7 & 1.8 2021-01-30 11:50:14 -08:00
Sean Corfield
8373c72f45 Fixes #286 by supporting WAIT, SKIP LOCKED
NOWAIT was already supported.
2021-01-30 11:43:48 -08:00
Sean Corfield
d2968bbfcc Fixes #289 by implementing USING 2021-01-30 11:19:12 -08:00
Sean Corfield
d5b11e2da6 Implement JOIN USING 2021-01-30 11:00:34 -08:00
Sean Corfield
d0f3068f3f Minor README update around raw 2021-01-30 11:00:22 -08:00
Sean Corfield
07eb66759a Restore cljs compatibility 2021-01-30 17:27:00 -08:00
Sean Corfield
830301c843 Ignore Rebel Readline/Socket history files 2021-01-30 16:40:01 -08:00
Sean Corfield
87eec786e1 It's 2021 2021-01-29 16:11:52 -08:00
Sean Corfield
86a4d2056b Auto-sync pom.cml 2021-01-29 14:56:06 -08:00
Sean Corfield
0880d0212f Minor version/tooling updates 2021-01-29 13:17:01 -08:00
Sean Corfield
8b70ebd83f
Update Clojure CLI versions 2020-12-15 10:34:26 -08:00
Sean Corfield
ef8fd66689 Add FUNDING.yml 2020-12-15 10:27:06 -08:00
Sean Corfield
57dbfaad48 Add example from PR #287 to v2 README 2020-12-15 10:12:39 -08:00
Sean Corfield
17c5e4b980 Add test for question in #285 2020-12-04 10:28:09 -08:00
Sean Corfield
117e1c7f64 Update to latest depstar 2020-11-08 21:17:39 -08:00
Sean Corfield
39a83ae57e Add test to V2 for issue #282 2020-10-28 16:26:35 -07:00
Sean Corfield
273732089b Improve :inline/:raw
Note: these are still in flux!
2020-10-14 11:50:32 -07:00
Sean Corfield
b762a514b6 Addresses #280 by adding similar to / not similar to 2020-10-14 11:00:39 -07:00
Sean Corfield
4dcc44d878 Fix - as operator
Not a great workaround. May rethink depending on what other operators 
get added containing `-`.
2020-10-14 11:00:20 -07:00
Sean Corfield
b711044548 Minor syntax differences
`:group-by` must take sequence.
`:<` is binary only.
`:select-distinct` instead of modifier.
2020-10-12 22:56:47 -07:00
Sean Corfield
9da9dfe399 Fix test for issue 263 2020-10-12 22:55:16 -07:00
Sean Corfield
8025d3520d Bug fixes for JOINs 2020-10-12 22:54:59 -07:00
Sean Corfield
b547a94003 Restore - -> _ translation from 1.0 2020-10-12 22:54:46 -07:00
Sean Corfield
f14b73e592 Start merging old core tests (for helpers) 2020-10-12 18:50:07 -07:00
Sean Corfield
0206eabc1e Empty SQL vector support
Drop clauses that generate no SQL
2020-10-12 18:38:35 -07:00
Sean Corfield
eff3b03a81 Support case/when/then/else 2020-10-12 18:38:05 -07:00
Sean Corfield
d1e4e196d0 Support empty conditions
Which remove the clause
2020-10-12 18:37:48 -07:00
Sean Corfield
febcf385ee Yet another where helper fix
Support nil conditions
2020-10-12 18:37:28 -07:00
Sean Corfield
96260901c2 All v1 core clauses are implemented now! 2020-10-12 14:53:52 -07:00
Sean Corfield
796f11c3f4 Fix composite by turning it into special syntax 2020-10-12 11:42:47 -07:00
Sean Corfield
ccd5447a09 Format sequential values as full SQL expressions 2020-10-12 11:33:18 -07:00
Sean Corfield
2decf35072 Fix most of the README formatting 2020-10-09 23:59:43 -07:00
Sean Corfield
9da83b5e1b Fix where merging; fix single-arg helpers 2020-10-09 23:59:30 -07:00
Sean Corfield
d04d9f600a Fix VALUES with multiple hash maps 2020-10-09 23:58:55 -07:00
Sean Corfield
15ace00e49 Fix string alias (should quote) 2020-10-09 23:58:38 -07:00
Sean Corfield
98b18bbb6e Fix where/having helpers; add select-distinct 2020-10-09 23:05:05 -07:00
Sean Corfield
a83998d354 Documented extension mechanism; updated README
Helpers are the next big piece of work.
2020-10-09 22:31:55 -07:00
Sean Corfield
97a3782112 Support :inline option
This is similar to `:parameterizer :none` (but better).
2020-10-09 21:52:18 -07:00
Sean Corfield
0c7ce43381 Address #279 by treating UNION expressions as nested 2020-10-09 21:30:45 -07:00
Sean Corfield
0255307aaf Bump to latest depstar 2020-10-09 14:53:13 -07:00
Sean Corfield
40828b2c57 Update to latest depstar; latest CLI 2020-10-08 22:29:10 -07:00
Sean Corfield
e4e8dd3342 Latest test-runner SHA 2020-10-07 23:04:52 -07:00
Sean Corfield
8db28e964d Update deps-deploy group ID (and version) 2020-10-07 22:27:13 -07:00
Sean Corfield
04724df814 Implement :lift special syntax 2020-10-02 13:43:44 -07:00
Sean Corfield
6632335384 Add nesting support in expressions and the DSL 2020-10-02 13:24:44 -07:00
Sean Corfield
9f8d1a8564 Implement :?foo [:param :foo] and primitive [:raw "sql"] 2020-10-01 23:30:18 -07:00
Sean Corfield
ddef4068ba Ensure DSL elements can be symbols (as well as keywords) 2020-09-30 13:00:09 -07:00
Sean Corfield
a789d4a4ff Support registration of new infix operators and new function/syntax 2020-09-29 14:40:03 -07:00
Sean Corfield
b94f169af3 Normalize symbol/keyword logic
We will allow symbols anywhere that we previously allowed keywords.
2020-09-29 14:20:34 -07:00
Sean Corfield
8b2f0ef292 Add locking select support via for/lock 2020-09-28 20:45:43 -07:00
Sean Corfield
867d5d3482 Implement composite; clean up readme 2020-09-28 19:24:17 -07:00
Sean Corfield
6d31c4839d Add first pass of helpers 2020-09-28 13:47:55 -07:00
Sean Corfield
1fdd50d6b0 Add more tests on insert 2020-09-28 13:18:34 -07:00
Sean Corfield
a7590e9216 Enhance special syntax
Pass op in as first argument so we can write more generic functions.
2020-09-28 13:18:21 -07:00
Sean Corfield
0706d0b7ba Improve insert into support 2020-09-28 12:12:40 -07:00
Sean Corfield
534401e5d6 Add ILIKE/NOT-ILIKE 2020-09-28 12:12:25 -07:00
Sean Corfield
ad49c4e386 Add NOT operator, improve NULL support 2020-09-28 12:12:10 -07:00
Sean Corfield
804305d3c5 Add EXCEPT ALL 2020-09-28 11:52:32 -07:00
Sean Corfield
6db2426046 Fix do update set; add on conflict tests 2020-09-28 11:49:29 -07:00
Sean Corfield
61cf6eda5a Add variadic string concatenation || 2020-09-28 11:49:13 -07:00
Sean Corfield
25097af134 First cut of on conflict support 2020-09-26 15:16:12 -07:00
Sean Corfield
9154d73678 Implement returning (postgres) 2020-09-26 00:39:54 -07:00
Sean Corfield
002523bb8c Generate AS for select/from; except for Oracle 2020-09-26 00:17:31 -07:00
Sean Corfield
6aced04179 Address #266 by adding :pretty? true option
Adds newline before, after, and between each SQL clause.
2020-09-25 23:58:51 -07:00
Sean Corfield
4d3b317520 Add meta unwrap for more feature support 2020-09-25 17:10:25 -07:00
Sean Corfield
ae6229c282 Move data_readers to test
Since we will not need/support this in V2
2020-09-25 16:44:01 -07:00
Sean Corfield
0052aade7c Implement variadic and/or/+/* 2020-09-25 16:40:15 -07:00
Sean Corfield
5449c23ede Clarify tests
Annotate parameterizer tests as known failures.

Create inline versions of :parameterizer :none tests.
2020-09-25 16:39:50 -07:00
Sean Corfield
a6c1f98b71 Fix CTE syntax
Should always be wrapped in parens
2020-09-25 16:38:38 -07:00
Sean Corfield
1ebbbc1772 Fix inline string behavior 2020-09-25 16:38:11 -07:00
Sean Corfield
63a079ca8d Bump readme version (and gen'd code) 2020-09-25 15:31:29 -07:00
Sean Corfield
9f20ade0fe Fix is/not null generation 2020-09-25 15:31:11 -07:00
Sean Corfield
1c7e08bb82 A first pass over the README for V2 2020-09-24 20:49:22 -07:00
Sean Corfield
11ef895c4a Clean up dialect support; start docs
Also move old namespaces to test-only tree for reference while I 
continue developing V2.
2020-09-24 19:07:32 -07:00
Sean Corfield
6cf8fa9e45 Implement base clause order for more predictable dialect selection 2020-09-24 11:13:08 -07:00
Sean Corfield
ba701b60cd Continue to support Clojure 1.7 for now 2020-09-24 11:04:18 -07:00
Sean Corfield
3ce47b92f2 Implement :inline syntax; allow select call without alias 2020-09-23 22:52:57 -07:00
Sean Corfield
97531fa4cf Implement array as SQL syntax instead of special HoneySQL syntax 2020-09-23 22:25:13 -07:00
Sean Corfield
4408a6e7d2 Update test runner to latest CLI 2020-09-23 20:25:38 -07:00
Sean Corfield
f7d5e3a4cf Down to just 8 failures now!
Mising: array, inline, parameterizer.
2020-09-23 18:15:20 -07:00
Sean Corfield
1a699f18ab Support more clauses; flesh out dialect and clause ordering 2020-09-23 12:55:02 -07:00
Sean Corfield
a1d90a6382 Work toward more 1.x compatibility
Temporarily disable `AS` in alias while testing compatibility; only 
quoted by default if `:dialect` specified (may revisit this).
2020-09-23 00:14:25 -07:00
Sean Corfield
834ac3a096 Rename sql-format back to format now Chlorine is updated! 2020-09-21 10:56:05 -07:00
Sean Corfield
1dc0447244 Convert more RCFs to tests 2020-09-20 22:25:28 -07:00
Sean Corfield
b6d6d2c4f4 Refactor to sql-format for now 2020-09-20 21:33:04 -07:00
Sean Corfield
2c98d35f63 Start some tests; implement more clauses 2020-09-20 19:17:37 -07:00
Sean Corfield
29b6e47a8e Experimental WIP 2020-09-20 18:48:07 -07:00
59 changed files with 13811 additions and 2405 deletions

View file

@ -1,28 +0,0 @@
version: 2
jobs:
build:
working_directory: ~/honeysql
docker:
- image: circleci/clojure:openjdk-11-tools-deps-1.10.0.442
steps:
- checkout
- restore_cache:
key: honeysql-{{ checksum "deps.edn" }}
- run:
name: Install Node
command: sudo apt-get update && sudo apt-get install -y nodejs
- run:
name: Add Node symlink
command: sudo ln -s /usr/bin/js /usr/bin/node
- run:
name: Download Dependencies
command: clojure -Spath -R:test:runner:cljs-runner:eastwood:readme && clojure -Spath -A:1.7 && clojure -Spath -A:1.8 && clojure -Spath -A:1.9
- save_cache:
paths:
- ~/.m2
- ~/.gitlibs
- ~/node_modules
key: honeysql-{{ checksum "deps.edn" }}
- run:
name: Run all the tests
command: sh run-tests.sh all

View file

@ -1,2 +1 @@
{:lint-as
{honeysql.helpers/defhelper clojure.core/defn}}
{}

View file

@ -0,0 +1 @@
{:lint-as {babashka.fs/with-temp-dir clojure.core/let}}

View file

@ -0,0 +1,3 @@
{:hooks
{:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}}

View file

@ -0,0 +1,16 @@
(ns httpkit.with-channel
(:require [clj-kondo.hooks-api :as api]))
(defn with-channel [{node :node}]
(let [[request channel & body] (rest (:children node))]
(when-not (and request channel) (throw (ex-info "No request or channel provided" {})))
(when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {})))
(let [new-node
(api/list-node
(list*
(api/token-node 'let)
(api/vector-node [channel (api/vector-node [])])
request
body))]
{:node new-node})))

View file

@ -0,0 +1,5 @@
{:lint-as
{rewrite-clj.zip/subedit-> clojure.core/->
rewrite-clj.zip/subedit->> clojure.core/->>
rewrite-clj.zip/edit-> clojure.core/->
rewrite-clj.zip/edit->> clojure.core/->>}}

View file

@ -0,0 +1 @@
{:hooks {:analyze-call {taoensso.encore/defalias taoensso.encore/defalias}}}

View file

@ -0,0 +1,16 @@
(ns taoensso.encore
(:require
[clj-kondo.hooks-api :as hooks]))
(defn defalias [{:keys [node]}]
(let [[sym-raw src-raw] (rest (:children node))
src (if src-raw src-raw sym-raw)
sym (if src-raw
sym-raw
(symbol (name (hooks/sexpr src))))]
{:node (with-meta
(hooks/list-node
[(hooks/token-node 'def)
(hooks/token-node (hooks/sexpr sym))
(hooks/token-node (hooks/sexpr src))])
(meta src))}))

38
.github/workflows/test-and-release.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: Release Version
on:
push:
tags:
- "v*"
jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
- name: Setup Clojure
uses: DeLaGuardo/setup-clojure@master
with:
cli: '1.12.0.1530'
- name: Cache All The Things
uses: actions/cache@v4
with:
path: |
~/.m2/repository
~/.gitlibs
~/.clojure
~/.cpcache
key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }}
- name: Run Tests
run: clojure -T:build ci :snapshot false
- name: Deploy Release
run: clojure -T:build deploy :snapshot false
env:
CLOJARS_PASSWORD: ${{secrets.DEPLOY_TOKEN}}
CLOJARS_USERNAME: ${{secrets.DEPLOY_USERNAME}}

65
.github/workflows/test-and-snapshot.yml vendored Normal file
View file

@ -0,0 +1,65 @@
name: Develop & Snapshot
on:
push:
branches:
- "develop"
jobs:
build-and-snapshot:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
- name: Setup Clojure
uses: DeLaGuardo/setup-clojure@master
with:
cli: '1.12.0.1530'
- name: Cache All The Things
uses: actions/cache@v4
with:
path: |
~/.m2/repository
~/.gitlibs
~/.clojure
~/.cpcache
key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }}
- name: Run Tests
run: clojure -T:build ci :snapshot true
- name: Deploy Snapshot
run: clojure -T:build deploy :snapshot true
env:
CLOJARS_PASSWORD: ${{secrets.DEPLOY_TOKEN}}
CLOJARS_USERNAME: ${{secrets.DEPLOY_USERNAME}}
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '8', '17', '21' ]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
- name: Clojure CLI
uses: DeLaGuardo/setup-clojure@master
with:
cli: '1.12.0.1530'
- name: Cache All The Things
uses: actions/cache@v4
with:
path: |
~/.m2/repository
~/.gitlibs
~/.clojure
~/.cpcache
key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }}
- name: Run Tests
run: clojure -T:build ci
- name: Check cljdoc.edn
run: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn

33
.github/workflows/test-bb.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Babashka tests
on:
pull_request:
push:
branches:
- develop
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: 21
- name: Clojure CLI
uses: DeLaGuardo/setup-clojure@master
with:
cli: '1.12.0.1530'
bb: latest
- name: Cache All The Things
uses: actions/cache@v4
with:
path: |
~/.m2/repository
~/.gitlibs
~/.clojure
~/.cpcache
key: ${{ runner.os }}-${{ hashFiles('**/deps.edn', '**/bb.edn') }}
- name: Run Tests
run: bb test

View file

@ -1,22 +1,33 @@
name: Clojure CI
name: Pull Request
on: [push]
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ '8', '11', '14' ]
java: [ '8', '11', '17', '21' ]
steps:
- uses: actions/checkout@v2
- name: Setup Java
uses: actions/setup-java@v1
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
- name: Setup Clojure
- name: Clojure CLI
uses: DeLaGuardo/setup-clojure@master
with:
tools-deps: '1.10.1.754'
cli: '1.12.0.1530'
- name: Cache All The Things
uses: actions/cache@v4
with:
path: |
~/.m2/repository
~/.gitlibs
~/.clojure
~/.cpcache
key: ${{ runner.os }}-${{ hashFiles('**/deps.edn') }}
- name: Run Tests
run: sh run-tests.sh all
run: clojure -T:build ci
- name: Check cljdoc.edn
run: curl -fsSL https://raw.githubusercontent.com/cljdoc/cljdoc/master/script/verify-cljdoc-edn | bash -s doc/cljdoc.edn

43
.gitignore vendored
View file

@ -1,22 +1,29 @@
*~
.sw*
*.swp
/target
/lib
/classes
/checkouts
/dist
*.jar
*.class
.cpcache
.clj-kondo/.cache
.eastwood
.lsp
.nrepl-port
.rebel_readline_history
.socket-repl-port
*.jar
*.swp
*~
.calva/output-window/
.calva/repl.calva-repl
.classpath
.project
.clj-kondo/.cache
.cpcache
.eastwood
.factorypath
.hg/
.hgignore
.java-version
.lein-*
.lsp/.cache
.lsp/sqlite.db
.nrepl-history
.nrepl-port
bin
.portal
.project
.rebel_readline_history
.settings
.socket-repl-port
.sw*
/checkouts
/classes
/cljs-test-runner-out
/target

View file

@ -1,5 +1,354 @@
# 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.
* Address [#555](https://github.com/seancorfield/honeysql/issues/555) by supporting `SETTING` clause for XTDB.
* Replace `assert` calls with proper validation, throwing `ex-info` on failure (like other existing validation in HoneySQL).
* Experimental `:xtdb` dialect removed (since XTDB no longer supports qualified column names).
* Update dev/test deps.
* 2.6.1230 -- 2024-11-23
* Fix [#553](https://github.com/seancorfield/honeysql/issues/553) by adding `:not-between` as special syntax via PR [#554](https://github.com/seancorfield/honeysql/pull/554) [@plooney81](https://github.com/plooney81)
* Fix [#552](https://github.com/seancorfield/honeysql/issues/552) by changing the assert-on-load behavior into an explicit test in the test suite.
* Fix [#551](https://github.com/seancorfield/honeysql/issues/551) by supporting multiple `WINDOW` clauses.
* Fix [#549](https://github.com/seancorfield/honeysql/issues/549) by using `:bb` conditionals to support Babashka (and still support Clojure 1.9.0), and add testing against Babashka so it is fully-supported as a target via PR [#550](https://github.com/seancorfield/honeysql/pull/550) [@borkdude](https://github.com/borkdude)
* Address [#532](https://github.com/seancorfield/honeysql/issues/532) by adding support for XTDB SQL extensions `ERASE`, `EXCLUDE`, `OBJECT`, `RECORD`, `RECORDS`, and `RENAME`, along with inline hash maps (as records) and `:get-in` for object navigation, and starting to write tests for XTDB compatibility.
* 2.6.1203 -- 2024-10-22
* Fix [#548](https://github.com/seancorfield/honeysql/issues/548) which was a regression introduced in [#526](https://github.com/seancorfield/honeysql/issues/526) (in 2.6.1161).
* Address [#542](https://github.com/seancorfield/honeysql/issues/542) by adding support for `WITH` query tail options for PostgreSQL.
* Replace all optional argument destructuring with multiple arities to improve performance.
* 2.6.1196 -- 2024-10-06
* Address [#547](https://github.com/seancorfield/honeysql/issues/547) by adding examples of conditional SQL building with the helpers to the README and the `honey.sql.helpers` ns docstring.
* Performance optimizations via PRs [#545](https://github.com/seancorfield/honeysql/pull/545) and [#546](https://github.com/seancorfield/honeysql/pull/546) [@alexander-yakushev](https://github.com/alexander-yakushev).
* Address [#544](https://github.com/seancorfield/honeysql/issues/544) by adding support for MySQL's `VALUES ROW(..)` syntax.
* Fix [#543](https://github.com/seancorfield/honeysql/issues/543) by supporting both symbols and keywords in named parameters.
* Address [#541](https://github.com/seancorfield/honeysql/issues/541) by specifying the expected result of a formatter function passed to `register-clause!` and adding the example from the README to **Extending HoneySQL**.
* Getting Started updated based on feedback from Los Angeles Clojure meetup walkthrough [#539](https://github.com/seancorfield/honeysql/issues/539).
* Fix [#538](https://github.com/seancorfield/honeysql/issues/538) by removing `mod` from list of infix operators.
* Fixed a few symbol/keyword resolution bugs in the formatter. Thanks to [@irigarae](https://github.com/irigarae).
* Update Clojure version to 1.12.0; update dev/test/ci deps.
* 2.6.1161 -- 2024-08-29
* Address [#537](https://github.com/seancorfield/honeysql/issues/537) by ignoring non-scalar values in metadata, and expanding support to numbers, and checking strings for suspicious characters.
* Address [#536](https://github.com/seancorfield/honeysql/issues/536) by noting what will not work with PostgreSQL (but works with other databases).
* Address [#533](https://github.com/seancorfield/honeysql/issues/533) by adding `honey.sql/*escape-?*` which can be bound to `false` to prevent `?` being escaped to `??` when used as an operator or function.
* Address [#526](https://github.com/seancorfield/honeysql/issues/526) by using `format-var` in DDL, instead of `format-entity`.
* Update JDK test matrix (adopt -> temurin, 19 -> 21).
* Update Clojure versions (to 1.11.4 & 1.12.0-rc2).
* 2.6.1147 -- 2024-06-12
* Address [#531](https://github.com/seancorfield/honeysql/issues/531) and [#527](https://github.com/seancorfield/honeysql/issues/527) by adding tests and more documentation for `:composite`; fix bug in `set-dialect!` where clause order is not restored.
* Address [#530](https://github.com/seancorfield/honeysql/issues/530) by adding support for `:using-gin` to `:create-index`.
* Address [#529](https://github.com/seancorfield/honeysql/issues/529) by fixing `:join` special syntax to support aliases and to handle expressions the same way `select` / `from` etc handle them (extra `[...]` nesting).
* Add example of mixed `DO UPDATE SET` with `EXCLUDED` and regular SQL expressions.
* Improve exception message when un-`lift`-ed JSON expressions are used in the DSL.
* Update Clojure versions (to 1.11.3 and 1.12.0-alpha12); update other dev/test dependencies.
* 2.6.1126 -- 2024-03-04
* Address [#524](https://github.com/seancorfield/honeysql/issues/524) by adding example of `{:nest ..}` in `:union` clause reference docs.
* Address [#523](https://github.com/seancorfield/honeysql/issues/523) by expanding examples in README **Functions** to show aliases.
* Address [#522](https://github.com/seancorfield/honeysql/issues/522) by supporting metadata on table specifications in `:from` and `:join` clauses to provide index hints (SQL Server).
* ~Address [#521](https://github.com/seancorfield/honeysql/issues/521) by adding initial experimental support for an XTDB dialect.~ _[This was removed in 2.6.1243 since XTDB no longer supports qualified column names]_
* Address [#520](https://github.com/seancorfield/honeysql/issues/520) by expanding how `:inline` works, to support a sequence of arguments.
* Fix [#518](https://github.com/seancorfield/honeysql/issues/518) by moving temporal clause before alias.
* Address [#495](https://github.com/seancorfield/honeysql/issues/495) by adding `formatv` macro (`.clj` only!) -- and removing the experimental `formatf` function (added for discussion in 2.4.1045).
* Implemented `CREATE INDEX` [#348](https://github.com/seancorfield/honeysql/issues/348) via PR [#517](https://github.com/seancorfield/honeysql/pull/517) [@dancek](https://github.com/dancek).
* Mention `:not-in` explicitly in the documentation.
* Code cleanup per `clj-kondo`.
* 2.5.1103 -- 2023-12-03
* Address [#515](https://github.com/seancorfield/honeysql/issues/515) by:
* Quoting entities that start with a digit but are otherwise alphanumeric. Note that entities that are all digits (optionally including underscores) will still not be quoted as in previous releases,
* Adding a new `:quoted-always` option allows users to specify a regex that matches entities that should always be quoted (stropped) regardless of the value of `:quoted` (such as reserved words that you have used as column or table names).
* Address [#513](https://github.com/seancorfield/honeysql/issues/513) by:
* Ignoring `:file`, `:line`, `:column`, `:end-line`, and `:end-column` metadata keys (previously only `:line` and `:column` were ignored),
* Adding an `:ignored-metadata` option to allow additional keys to be ignored.
* 2.5.1091 -- 2023-10-28
* Address [#512](https://github.com/seancorfield/honeysql/issues/512) by adding support for subqueries in the `:array` special syntax (for BigQuery and PostgreSQL). This also adds support for metadata on the `:select` value to produce `AS STRUCT` or `DISTINCT`.
* Address [#511](https://github.com/seancorfield/honeysql/issues/511) by adding support for BigQuery `CREATE OR REPLACE`.
* Address [#510](https://github.com/seancorfield/honeysql/issues/510) by adding initial support for an NRQL dialect.
* Fix [#509](https://github.com/seancorfield/honeysql/issues/509) by checking for `ident?` before checking keyword/symbol.
* 2.4.1078 -- 2023-10-07
* Address [#507](https://github.com/seancorfield/honeysql/issues/507) by clarifying formatting of `:cast` in **Special Syntax**.
* Fix [#505](https://github.com/seancorfield/honeysql/issues/505) by rewriting the helper merge function to handle both keywords and symbols properly.
* Address [#503](https://github.com/seancorfield/honeysql/issues/503) by adding `:at-time-zone` special syntax.
* Address [#504](https://github.com/seancorfield/honeysql/issues/504) for BigQuery support, by adding special syntax for ignore/respect nulls, as well as new `:distinct` and `:expr` clauses to allow expressions to be qualified with SQL clauses. The latter will probably be useful for other dialects too.
* Update `tools.build` to 0.9.6 (and get rid of `template/pom.xml` in favor of new `:pom-data` option to `b/write-pom`).
* 2.4.1066 -- 2023-08-27
* Add `:select` with function call and alias example to README (PR [#502](https://github.com/seancorfield/honeysql/pull/502) [@markbastian](https://github.com/markbastian)).
* Address [#501](https://github.com/seancorfield/honeysql/issues/501) by making `INSERT INTO` (and `REPLACE INTO`) use the `:columns` or `:values` clauses to produce column names (which are then omitted from those other clauses).
* Address [#497](https://github.com/seancorfield/honeysql/issues/497) by adding `:alias` special syntax.
* Address [#496](https://github.com/seancorfield/honeysql/issues/496) by adding `:overriding-value` option to `:insert` clause.
* Address [#407](https://github.com/seancorfield/honeysql/issues/407) by adding support for temporal queries (see `FROM` in [SQL Clause Reference](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started/sql-clause-reference#from)).
* Address [#389](https://github.com/seancorfield/honeysql/issues/389) by adding examples of `[:only :table]` producing `ONLY(table)`.
* Add `:create-or-replace-view` to support PostgreSQL's lack of `IF NOT EXISTS` for `CREATE VIEW`.
* Attempt to clarify the formatting behavior of the `:values` clause when used to produce column names.
* Update `tools.build` to 0.9.5 (and remove `:java-opts` setting from `build/run-task`)
* 2.4.1045 -- 2023-06-25
* Address [#495](https://github.com/seancorfield/honeysql/issues/495) by adding (experimental) `formatf` function -- _note: this was removed in 2.6.1126, in favor of the `formatv` macro._
* Fix [#494](https://github.com/seancorfield/honeysql/issues/494) by supporting expressions in `:on-conflict` instead of just entities.
* Address [#493](https://github.com/seancorfield/honeysql/issues/493) by clarifying use of `:values` in CTEs (using `:with`).
* Address [#489](https://github.com/seancorfield/honeysql/issues/489) by adding more examples around `:update`.
* Attempt to improve `honey.sql.helpers` namespace docstring (by adding a note from the relevant **Getting Started** section).
* Update dev/test dependencies.
* 2.4.1033 -- 2023-05-22
* Tentative [ClojureCLR](https://github.com/clojure/clojure-clr) support.
* Improve `on-conflict` helper docstring [#490](https://github.com/seancorfield/honeysql/pull/490) [@holyjak](https://github.com/holyjak).
* 2.4.1026 -- 2023-04-15
* Fix [#486](https://github.com/seancorfield/honeysql/issues/486) by supporting ANSI-style `INTERVAL` syntax.
* Fix [#485](https://github.com/seancorfield/honeysql/issues/485) by adding `:with-ordinality` "operator".
* Fix [#484](https://github.com/seancorfield/honeysql/issues/484) by adding `TABLE` to `TRUNCATE`.
* Fix [#483](https://github.com/seancorfield/honeysql/issues/483) by adding a function-like `:join` syntax to produce nested `JOIN` expressions.
* Update `tools.build`; split alias `:test`/`:runner` for friendlier jack-in UX while developing.
* 2.4.1011 -- 2023-03-23
* Address [#481](https://github.com/seancorfield/honeysql/issues/481) by adding more examples around `:do-update-set`.
* Address [#480](https://github.com/seancorfield/honeysql/issues/480) by clarifying the general relationship between clauses and helpers.
* Address [#448](https://github.com/seancorfield/honeysql/issues/448) by adding a new section with hints and tips for database-specific syntax and solutions.
* 2.4.1006 -- 2023-03-17
* Fix [#478](https://github.com/seancorfield/honeysql/issues/478) by handling `:do-update-set` correctly in the `upsert` helper and by handling parameters correctly in the `:do-update-set` formatter.
* Fix [#476](https://github.com/seancorfield/honeysql/issues/476) by adding support for multiple arguments to `:raw`, essentially restoring 1.x functionality (while still allowing for embedded vectors as expressions, introduced in 2.x).
* 2.4.1002 -- 2023-03-03
* Address [#474](https://github.com/seancorfield/honeysql/issues/474) by adding dot-selection special syntax.
* Improve docstrings for PostgreSQL operators via PR [#473](https://github.com/seancorfield/honeysql/pull/473) [@holyjak](https://github.com/holyjak).
* Address [#471](https://github.com/seancorfield/honeysql/issues/471) by supporting interspersed SQL keywords in function calls.
* Fix [#467](https://github.com/seancorfield/honeysql/issues/467) by allowing single keywords (symbols) as a short hand for a single-element sequence in more constructs via PR [#470](https://github.com/seancorfield/honeysql/pull/470) [@p-himik](https://github.com/p-himik).
* Address [#466](https://github.com/seancorfield/honeysql/issues/466) by treating `[:and]` as `TRUE` and `[:or]` as `FALSE`.
* Fix [#465](https://github.com/seancorfield/honeysql/issues/465) to allow multiple columns in `:order-by` special syntax via PR [#468](https://github.com/seancorfield/honeysql/pull/468) [@p-himik](https://github.com/p-himik).
* Fix [#464](https://github.com/seancorfield/honeysql/issues/464) by adding an optional type argument to `:array` via PR [#469](https://github.com/seancorfield/honeysql/pull/469) [@p-himik](https://github.com/p-himik).
* Address [#463](https://github.com/seancorfield/honeysql/issues/463) by explaining `:quoted nil` via PR [#475](https://github.com/seancorfield/honeysql/pull/475) [@nharsch](https://github.com/nharsch).
* Address [#462](https://github.com/seancorfield/honeysql/issues/462) by adding a note in the documentation for set operations, clarifying precedence issues.
* 2.4.980 -- 2023-02-15
* Fix [#461](https://github.com/seancorfield/honeysql/issues/461) -- a regression introduced in 2.4.979 -- by restricting unary operators to just `+`, `-`, and `~` (bitwise negation).
* 2.4.979 -- 2023-02-11
* Address [#459](https://github.com/seancorfield/honeysql/issues/459) by making all operators variadic (except `:=` and `:<>`).
* Address [#458](https://github.com/seancorfield/honeysql/issues/458) by adding `registered-*?` predicates.
* 2.4.972 -- 2023-02-02
* Address [#456](https://github.com/seancorfield/honeysql/issues/456) by allowing `format` to handle expressions (like 1.x could) as well as statements. This should aid with migration from 1.x to 2.x.
* 2.4.969 -- 2023-01-14
* Fix [#454](https://github.com/seancorfield/honeysql/issues/454) by allowing `-` to be variadic.
* Address [#452](https://github.com/seancorfield/honeysql/pull/452) by adding `:replace-into` to the core SQL supported, instead of just for the MySQL and SQLite dialects (so the latter is not needed yet).
* Address [#451](https://github.com/seancorfield/honeysql/issues/451) by adding a test for it, showing how `:nest` produces the desired result.
* Address [#447](https://github.com/seancorfield/honeysql/issues/447) by updating GitHub Actions and dependencies.
* Address [#445](https://github.com/seancorfield/honeysql/issues/445) and [#453](https://github.com/seancorfield/honeysql/issues/453) by adding key/constraint examples to `CREATE TABLE` docs.
* 2.4.962 -- 2022-12-17
* Fix `set-options!` (only `:checking` worked in 2.4.947).
* Fix `:cast` formatting when quoting is enabled, via PR [#443](https://github.com/seancorfield/honeysql/pull/443) [duddlf23](https://github.com/duddlf23). **This changes how type names containing `-` are formatted in a cast.** See [`cast` Special Syntax](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started/sql-special-syntax-#cast) for more details.
* Fix [#441](https://github.com/seancorfield/honeysql/issues/441) by adding `:replace-into` to in-flight clause order (as well as registering it for the `:mysql` dialect).
* Fix [#434](https://github.com/seancorfield/honeysql/issues/434) by special-casing `:'ARRAY`.
* Fix [#433](https://github.com/seancorfield/honeysql/issues/433) by supporting additional `WITH` syntax, via PR [#432](https://github.com/seancorfield/honeysql/issues/432), [@MawiraIke](https://github.com/MawiraIke). _[Technically, this was in 2.4.947, but I kept the issue open while I wordsmithed the documentation]_
* Address [#405](https://github.com/seancorfield/honeysql/issues/405) by adding `:numbered` option, which can also be set globally using `set-options!`.
* 2.4.947 -- 2022-11-05
* Fix [#439](https://github.com/seancorfield/honeysql/issues/439) by rewriting how DDL options are processed; also fixes [#386](https://github.com/seancorfield/honeysql/issues/386) and [#437](https://github.com/seancorfield/honeysql/issues/437); **Whilst this is intended to be purely a bug fix, it has the potential to be a breaking change -- hence the version jump to 2.4!**
* Fix [#438](https://github.com/seancorfield/honeysql/issues/438) by
supporting options on `TRUNCATE`.
* Address [#435](https://github.com/seancorfield/honeysql/issues/435) by showing `CREATE TEMP TABLE` etc.
* Fix [#431](https://github.com/seancorfield/honeysql/issues/431) -- `WHERE false` differed between the DSL and the `where` helper.
* Address [#430](https://github.com/seancorfield/honeysql/issues/430) by treating `:'` as introducing a name that should be treated literally and not formatted as a SQL entity (which respects quoting, dot-splitting, etc); this effectively expands the "escape hatch" introduced via [#352](https://github.com/seancorfield/honeysql/issues/352) in 2.2.868. _Note that the function context behavior formats as a SQL entity, rather than the usual SQL "keyword", whereas this new context is a literal transcription rather than as a SQL entity!_
* Address [#427](https://github.com/seancorfield/honeysql/issues/427) by adding `set-options!`.
* Address [#415](https://github.com/seancorfield/honeysql/issues/415) by supporting multiple column names in `ADD COLUMN`, `ALTER COLUMN`, `DROP COLUMN`, and `MODIFY COLUMN`.
* 2.3.928 -- 2022-09-04
* Address [#425](https://github.com/seancorfield/honeysql/issues/425) by clarifying that `INTERVAL` as special syntax may be MySQL-specific and PostgreSQL uses difference syntax (because `INTERVAL` is a data type there).
* Address [#423](https://github.com/seancorfield/honeysql/issues/423) by supporting `DEFAULT` values and `DEFAULT` rows in `VALUES`.
* Address [#422](https://github.com/seancorfield/honeysql/issues/422) by auto-quoting unusual entity names when `:quoted` (and `:dialect`) are not specified, making HoneySQL more secure by default.
* Fix [#421](https://github.com/seancorfield/honeysql/issues/421) by adding `:replace-into` for `:mysql` dialect.
* Address [#419](https://github.com/seancorfield/honeysql/issues/419) by adding `honey.sql.protocols` and `InlineValue` with a `sqlize` function.
* Address [#413](https://github.com/seancorfield/honeysql/issues/413) by flagging a lack of `WHERE` clause for `DELETE`, `DELETE FROM`, and `UPDATE` when `:checking :basic` (or `:checking :strict`).
* Fix [#392](https://github.com/seancorfield/honeysql/issues/392) by adding support for `WITH` / (`NOT`) `MATERIALIZED` -- via PR [#420](https://github.com/seancorfield/honeysql/issues/420) [@robhanlon22](https://github.com/robhanlon22).
* 2.3.911 -- 2022-07-29
* Address [#418](https://github.com/seancorfield/honeysql/issues/418) by documenting a potential "gotcha" with multi-column `IN` expressions (a change from HoneySQL 1.x).
* Fix [#416](https://github.com/seancorfield/honeysql/issues/416) via PR [#417](https://github.com/seancorfield/honeysql/issues/417) from [@corasaurus-hex](https://github.com/corasaurus-hex) -- using the internal default state for the integrity assertion.
* Address [#414](https://github.com/seancorfield/honeysql/issues/414) by providing an example of `ORDER BY` with a `CASE` expression.
* Address [#412](https://github.com/seancorfield/honeysql/issues/412) by documenting options in a separate page and reorganizing the ToC structure.
* Address [#409](https://github.com/seancorfield/honeysql/issues/409) by making docstring check for public helpers conditional.
* Fix [#406](https://github.com/seancorfield/honeysql/issues/406) by adding `:alter-column` (which produces `MODIFY COLUMN` when the MySQL dialect is selected) and deprecating `:modify-column`.
* Address [#401](https://github.com/seancorfield/honeysql/issues/401) by adding `register-dialect!` and `get-dialect`, and also making `add-clause-before`, `strop`, and `upper-case` public so that new dialects are easier to construct.
* 2.2.891 -- 2022-04-23
* Address [#404](https://github.com/seancorfield/honeysql/issues/404) by documenting PostgreSQL's `ARRAY` constructor syntax and how to produce it.
* Address parts of [#403](https://github.com/seancorfield/honeysql/issues/403) by improving the documentation for `:array` and also improving the exception that was thrown when it was misused.
* Fix [#402](https://github.com/seancorfield/honeysql/issues/402) by allowing for expressions in `:insert-into` table.
* Address [#400](https://github.com/seancorfield/honeysql/issues/400) by adding `:table` clause.
* Address [#399](https://github.com/seancorfield/honeysql/issues/399) by correcting multi-column `RETURNING` clauses in docs and tests.
* Fix [#398](https://github.com/seancorfield/honeysql/issues/398) by adding `honey.sql.pg-ops` namespace that registers PostgreSQL JSON and regex operators and provides symbolic names for "unwritable" operators (that contain `@`, `#`, or `~`).
* Address [#396](https://github.com/seancorfield/honeysql/issues/396) by throwing an exception if you try to cache a SQL statement that includes an `IN ()` expression, using a named parameter for the `IN` values.
* Fix [#394](https://github.com/seancorfield/honeysql/issues/394) by restoring HoneySQL 1.x's behavior when quoting.
* Fix [#387](https://github.com/seancorfield/honeysql/issues/387) again.
* Update CI to reflect Clojure 1.11 release (master -> 1.11; new master is 1.12).
* Update `build-clj` to v0.8.0.
* 2.2.868 -- 2022-02-21
* Address [#387](https://github.com/seancorfield/honeysql/issues/387) by making the function simpler.
* Fix [#385](https://github.com/seancorfield/honeysql/issues/385) by quoting inlined UUIDs.
* Address [#352](https://github.com/seancorfield/honeysql/issues/352) by treating `:'` as introducing a function name that should be formatted as a SQL entity (which respects quoting, dot-splitting, etc), rather than as a SQL "keyword".
* 2.2.861 -- 2022-01-30
* Address [#382](https://github.com/seancorfield/honeysql/issues/382) by adding `:case-expr` for BigQuery support.
* Address [#381](https://github.com/seancorfield/honeysql/issues/381) by adding `generic-helper-variadic` and `generic-helper-unary` to `honey.sql.helpers`.
* Fix [#380](https://github.com/seancorfield/honeysql/issues/380) by correcting test for function type in `register-clause!` and `register-fn!`.
* 2.2.858 -- 2022-01-20
* Address #377 by adding `honey.sql/map=` to convert a hash map into an equality condition (for a `WHERE` clause).
* Address #351 by adding a `:cache` option to `honey.sql/format` (for Clojure only, not ClojureScript).
* Address #281 by adding support for `SELECT * EXCEPT ..` and `SELECT * REPLACE ..` and `ARRAY<>` and `STRUCT<>` column types -- see [SQL Clause Reference - SELECT](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started/sql-clause-reference#select-select-distinct) and [SQL Clause Reference - DDL](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started/sql-clause-reference#ddl-clauses) respectively for more details.
* Update `build-clj` to v0.6.7.
* 2.2.840 -- 2021-12-23
* Fix #375 for `:nest` statement.
* Fix #374 by removing aliasing of `:is` / `:is-not` -- this changes the behavior of `[:is-not :col true/false]` to be _correct_ and _include `NULL` values_. Using `:is` / `:is-not` with values that are not Boolean and not `nil` will produce invalid SQL.
* Update test dependencies.
* Update `build-clj` to v0.6.5.
* 2.1.833 -- 2021-12-03
* Fix #372 by merging `:select-distinct-on` differently.
* Add empty column list check for `SELECT` and several other clauses, when `:checking :basic` (or `:strict`) is provided.
* Update `build-clj` to v0.6.0.
* 2.1.829 -- 2021-11-27
* Fix #371 by treating the operand of `NOT` as a nested expression (so it is parenthesized unless it is a simple value).
* Fix #370 by **always** parenthesizing the operand of `:nest`.
* Address #369 by adding a big clarifying docstring to the `honey.sql.helpers` namespace pointing out that all helper functions are variadic, they are all `[& args]`, some have `:arglists` metadata to provide a more specific usage hint but those _all omit the optional first argument (the DSL hash map)_.
* Fix #354 by supporting `DROP COLUMN IF EXISTS` / `ADD COLUMN IF NOT EXISTS`.
* Update `build-clj` to v0.5.5.
* 2.1.818 -- 2021-10-04
* Fix #367 by supporting parameters in subexpressions around `IS NULL` / `IS NOT NULL` tests.
* Address #366 by introducing `:values-default-columns` option to control whether missing columns are treated as `NULL` or `DEFAULT` in `:values` clauses with sequences of hash maps.
* Fix #365 -- a regression from 1.x -- where subclauses for `UNION`, `EXCEPT`, etc were incorrectly parenthesized.
* Update `build-clj` to v0.5.0.
* 2.0.813 -- 2021-09-25
* Address #364 by recommending how to handle PostgreSQL operators that contain `@`.
* Fix #363 and #362 by aligning more closely the semantics of `:inline` syntax with the `:inline true` option. A side effect of this is that `[:inline [:param :foo]]` will now (correctly) inline the value of the parameter `:foo` whereas it previously produced `PARAMS SOURCE`. In addition, inlining has been extended to vector values, so `[:inline ["a" "b" "c"]]` will now produce `('a', 'b', 'c')` and `[:inline [:lift ["a" "b" "c"]]]` will now produce `['a', 'b', 'c']` which is what people seemed to expect (the behavior was previously unspecified).
* Fix #353 by correcting handling of strings used as SQL entities (such as table names); this was a regression introduced by a recent enhancement to `:create-table`.
* Fix #349 by adding an optional `:quoted` argument to `set-dialect!`.
* Address #347 by adding example of adding a primary key to an existing table via `:add-index`.
* Support `AS` aliasing in `DELETE FROM`.
* Switch from `readme` to `test-doc-blocks` so all documentation is tested!
* Clean up build/update deps.
* 2.0.783 -- 2021-08-15 (a.k.a "2.0 Gold")
* 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://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)
* Fix #338 by producing `OFFSET n ROWS` (or `ROW` if `n` is 1) if `:fetch` is present or `:sqlserver` dialect is specified; and by producing `FETCH NEXT n ROWS ONLY` (or `ROW` is `n` is 1; or `FIRST` instead of `NEXT` if `:offset` is not present).
* Fix #337 by switching to `clojure.test` even for ClojureScript.
* Address #332 by improving `:cross-join` documentation.
* Address #330 by improving exception when a non-entity is encountered where an entity is expected.
* Fix `fetch` helper (it previously returned an `:offset` clause).
* Fix bug in unrolling nested argument to `with-columns` helper.
* 2.0.0-rc3 (for testing; 2021-06-16)
* Fix #328 by adding `:distinct` as special syntax, affecting an expression.
* Address #327 by changing "unknown clause" error to including mention of "nil values" (which are also illegal).
* Fix #327 by making single-argument helpers consistent with multi-argument helpers.
* Support PostgreSQL's `&&` array operator.
* Clarify how to `SELECT` a function expression (in **Getting Started**).
* Update `test-runner`.
* 2.0.0-rc2 (for testing; 2021-05-10)
* Fix #326 by allowing `ON`/`USING` to be optional and not dropping parameters on the floor.
* Fix #325 by making the `%` function call syntax respect `:quoted true` and/or `:dialect` options, and also allowing for qualified column names. (PR from @lognush)
* Add `:quoted-snake true` option to force conversion from kebab-case to snake_case when `:quoted true` or a `:dialect` is specified to `format`.
* Update `test-runner`.
* 2.0.0-rc1 (for testing; 2021-05-06)
* Fix #324 so that `insert-into` supports merging into another statement in all cases.
* Fix #323 by supporting more than one SQL entity in `:on-conflict`.
* Fix #321 by adding `:checking` mode. Currently only detects potential problems with `IN` clauses.
* 2.0.0-beta2 (for testing; 2021-04-13)
* The documentation continues to be expanded and clarified in response to feedback!
* Fix #322 by rewriting/simplifying `WHERE`/`HAVING` merge logic. **Important bug fix!**
* Fix #310 by adding support for `FILTER`, `WITHIN GROUP`, and `ORDER BY` (as an expression), from [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) 0.4.112. These are [Special Syntax](doc/special-syntax.md) and there are also helpers for `filter` and `within-group` -- so **be careful about referring in all of `honey.sql.helpers`** since it will now shadow `clojure.core/filter` (it already shadows `for`, `group-by`, `into`, `partition-by`, `set`, and `update`).
* Fix #308 by supporting join clauses in `join-by` (and correcting the helper docstring).
* 2.0.0-beta1 (for testing; 2021-04-09)
* **The merging behavior of `where`/`having` is broken in Beta 1!**
* Since Alpha 3, more documentation has been written and existing documentation clarified (addressing #300, #309, #313, #314).
* Fix #319 by ensuring `register-clause!` is idempotent.
* Fix #317 by dropping qualifiers in `:set` clauses (just like we do with `:insert` columns). Note that you can still use explicit _dotted_ names if you want table qualification.
* Fix #316 by disallowing entity names containing `;` (to avoid SQL injection risks).
* Fix #312 by adding `:raw` as a clause. There is no helper function equivalent (because it would be ambiguous whether you meant a function form -- `[:raw ..]` -- or a clause form -- `{:raw ..}`; and for the same reason, there is no `nest` helper function since that also works as a clause and as a function/special syntax).
* 2.0.0-alpha3 (for early testing; 2021-03-13)
* Change coordinates to `com.github.seancorfield/honeysql` (although new versions will continue to be deployed to `seancorfield/honeysql` for a while -- see the [Clojars Verified Group Names policy](https://github.com/clojars/clojars-web/wiki/Verified-Group-Names)).
* Support much richer range of syntax on `CREATE`/`DROP` statements in general, including columns, `TABLESPACE`, `CASCADE`, `WITH [NO] DATA`, etc.
* Fix #306 by supporting `CREATE TABLE .. AS ..`.
* Fix #305 by supporting more complex join clauses.
* Fix #303 by supporting MySQL's `ON DUPLICATE KEY UPDATE`.
* Fix #301 by adding support for `CREATE`/`DROP`/`REFRESH` on `MATERIALIZED VIEW`.
* Add tests to confirm #299 does not affect 2.x.
* Fix #297 by adding both `SELECT .. INTO ..` and `SELECT .. BULK COLLECT INTO ..`.
* Fix #295 by adding docstrings to all helper functions (and adding an assert to ensure it stays that way as more are added in future).
* Confirm the whole of the [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) is implemented out-of-the-box (#293, up to 0.3.104 -- see also #310 which brought parity up to 0.4.112).
* Fix #292 by adding support for `SELECT TOP` and `OFFSET`/`FETCH`.
* Fix #284 by adding support for `LATERAL` (as special syntax, with a helper).
* Reconcile `where` behavior with recent 1.x changes (porting #283 to 2.x).
* Fix #280 by adding `:escape` as special syntax for regular expression patterns.
* Fix #277 by adding `:join-by`/`join-by` so that you can have multiple `JOIN`'s in a specific order.
* 2.0.0-alpha2 (for early testing)
* Since Alpha 1, a lot more documentation has been written and docstrings have been added to most functions in `honey.sql.helpers`.
* Numerous small improvements have been made to clauses and helpers around insert/upsert.
* 2.0.0-alpha1 (for early testing)
* This is a complete rewrite/simplification of HoneySQL that provides just two namespaces:
* `honey.sql` -- this is the primary API via the `format` function as well as the various extension points.
* `honey.sql.helpers` -- provides a helper function for every piece of the DSL that is supported out-of-the-box.
* The coordinates for HoneySQL 2.x are `com.github.seancorfield/honeysql` so it can be added to a project that already uses HoneySQL 1.x without any conflicts, making it easier to migrate piecemeal from 1.x to 2.x.
# HoneySQL pre-2.x Changes
* 1.0.461 -- 2021-02-22
* **Fix #299 potential SQL injection vulnerability.**
* Fix/Improve `merge-where` (and `merge-having`) behavior. #282 via #283 (@camsaul)
@ -99,7 +448,7 @@ Not all of these releases were tagged on GitHub and none of them have release no
* 0.8.0
* Get arglists right for generated helpers (@camsaul, @michaelblume)
* Allow HoneySQL to be used from Clojurescript (@rnewman, @michaelblume)
* Allow HoneySQL to be used from ClojureScript (@rnewman, @michaelblume)
* BREAKING CHANGE: HoneySQL now requires Clojure 1.7.0 or above.
* 0.7.0

76
CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at sean@corfield.org. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

1011
README.md

File diff suppressed because it is too large Load diff

8
bb.edn Normal file
View file

@ -0,0 +1,8 @@
{:paths ["src"]
:tasks
{test
{:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/tag "v0.5.1" :git/sha "dfb30dd"}}
:task (exec 'cognitect.test-runner.api/test)
:exec-args {:patterns ["^(?!honey.cache).*-test$"]}}}}

125
build.clj Normal file
View file

@ -0,0 +1,125 @@
(ns build
"HoneySQL's build script.
clojure -T:build ci
clojure -T:build run-doc-tests :aliases '[:cljs]'
Run tests:
clojure -X:test
clojure -X:test:1.12
For more information, run:
clojure -A:deps -T:build help/doc"
(:refer-clojure :exclude [test])
(:require [clojure.string :as str]
[clojure.tools.build.api :as b]
[clojure.tools.deps :as t]
[deps-deploy.deps-deploy :as dd]))
(def lib 'com.github.seancorfield/honeysql)
(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")
(defn- run-task [aliases]
(println "\nRunning task for" (str/join "," (map name aliases)))
(let [basis (b/create-basis {:aliases aliases})
combined (t/combine-aliases basis aliases)
cmds (b/java-command
{:basis basis
:main 'clojure.main
:main-args (:main-opts combined)})
{:keys [exit]} (b/process cmds)]
(when-not (zero? exit) (throw (ex-info "Task failed" {})))))
(defn eastwood "Run Eastwood." [opts]
(run-task [:eastwood])
opts)
(defn gen-doc-tests "Generate tests from doc code blocks." [opts]
(run-task [:gen-doc-tests])
opts)
(defn run-doc-tests
"Generate and run doc tests.
Optionally specify :aliases vector:
[: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"
[{:keys [aliases] :as opts}]
(gen-doc-tests opts)
(run-task (-> [:test :runner :test-doc]
(into aliases)
(into (if (some #{:cljs} aliases)
[:test-doc-cljs]
[:test-doc-clj]))))
opts)
(defn test "Run basic tests." [opts]
(run-task [:test :runner :1.11])
(run-task [:test :runner :cljs])
opts)
(defn- pom-template [version]
[[:description "SQL as Clojure data structures."]
[:url "https://github.com/seancorfield/honeysql"]
[:licenses
[:license
[:name "Eclipse Public License"]
[:url "http://www.eclipse.org/legal/epl-v10.html"]]]
[:developers
[:developer
[:name "Sean Corfield"]]
[:developer
[:name "Justin Kramer"]]]
[:scm
[:url "https://github.com/seancorfield/honeysql"]
[:connection "scm:git:https://github.com/seancorfield/honeysql.git"]
[:developerConnection "scm:git:ssh:git@github.com:seancorfield/honeysql.git"]
[:tag (str "v" version)]]])
(defn- jar-opts [opts]
(let [version (if (:snapshot opts) snapshot version)]
(println "\nVersion:" version)
(assoc opts
:lib lib :version version
:jar-file (format "target/%s-%s.jar" lib version)
:basis (b/create-basis {})
:class-dir class-dir
:target "target"
:src-dirs ["src"]
:pom-data (pom-template version))))
(defn ci
"Run the CI pipeline of tests (and build the JAR).
Default Clojure version is 1.10.3 (:1.10) so :elide
tests for #409 on that version."
[opts]
(let [aliases [:cljs :elide :1.11 :1.12]
opts (jar-opts opts)]
(b/delete {:path "target"})
(doseq [alias aliases]
(run-doc-tests {:aliases [alias]}))
(eastwood opts)
(doseq [alias aliases]
(run-task [:test :runner alias]))
(b/delete {:path "target"})
(println "\nWriting pom.xml...")
(b/write-pom opts)
(println "\nCopying source...")
(b/copy-dir {:src-dirs ["src"] :target-dir class-dir})
(println "\nBuilding" (:jar-file opts) "...")
(b/jar opts))
opts)
(defn deploy "Deploy the JAR to Clojars." [opts]
(let [{:keys [jar-file] :as opts} (jar-opts opts)]
(dd/deploy {:installer :remote :artifact (b/resolve-path jar-file)
:pom-file (b/pom-path (select-keys opts [:lib :class-dir]))}))
opts)

View file

@ -0,0 +1,36 @@
(ns honey.gen-doc-tests
(:require [babashka.fs :as fs]
[lread.test-doc-blocks :as tdb]))
(defn -main [& _args]
(let [target "target/test-doc-blocks"
success-marker (fs/file target "SUCCESS")
docs ["README.md"
"doc/clause-reference.md"
"doc/databases.md"
"doc/differences-from-1-x.md"
"doc/extending-honeysql.md"
"doc/general-reference.md"
"doc/getting-started.md"
"doc/nrql.md"
;;"doc/operator-reference.md"
"doc/options.md"
"doc/postgresql.md"
"doc/special-syntax.md"
"doc/xtdb.md"]
regen-reason (if (not (fs/exists? success-marker))
"a previous successful gen result not found"
(let [newer-thans (fs/modified-since target
(concat docs
["build.clj" "deps.edn"]
(fs/glob "build" "**/*.*")
(fs/glob "src" "**/*.*")))]
(when (seq newer-thans)
(str "found files newer than last gen: " (mapv str newer-thans)))))]
(if regen-reason
(do
(fs/delete-if-exists success-marker)
(println "gen-doc-tests: Regenerating:" regen-reason)
(tdb/gen-tests {:docs docs})
(spit success-marker "SUCCESS"))
(println "gen-doc-tests: Tests already successfully generated"))))

View file

@ -1,28 +1,45 @@
{:mvn/repos {"sonatype" {:url "https://oss.sonatype.org/content/repositories/snapshots/"}}
:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.10.1"}}
:paths ["src"]
:deps {org.clojure/clojure {:mvn/version "1.10.3"}}
:aliases
{:1.7 {:override-deps {org.clojure/clojure {:mvn/version "1.7.0"}}}
:1.8 {:override-deps {org.clojure/clojure {:mvn/version "1.8.0"}}}
:1.9 {:override-deps {org.clojure/clojure {:mvn/version "1.9.0"}}}
:1.10 {:override-deps {org.clojure/clojure {:mvn/version "1.10.1"}}}
:master {:override-deps {org.clojure/clojure {:mvn/version "1.11.0-master-SNAPSHOT"}}}
:test {:extra-paths ["test"]}
{;; for help: clojure -A:deps -T:build help/doc
: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.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"}}}
:elide ; to test #409 (assertion on helper docstrings)
{:jvm-opts ["-Dclojure.compiler.elide-meta=[:doc]"]}
;; running tests/checks of various kinds:
:test
{:extra-paths ["test"]
:extra-deps {io.github.cognitect-labs/test-runner
{:git/tag "v0.5.1" :git/sha "dfb30dd"}
org.clojure/core.cache {:mvn/version "RELEASE"}}
:exec-fn cognitect.test-runner.api/test}
:runner
{:extra-deps {com.cognitect/test-runner
{:git/url "https://github.com/cognitect-labs/test-runner"
:sha "f7ef16dc3b8332b0d77bc0274578ad5270fbfedd"}}
:main-opts ["-m" "cognitect.test-runner"
"-d" "test"]}
:cljs-runner {:extra-deps {olical/cljs-test-runner {:mvn/version "3.7.0"}}
:main-opts ["-m" "cljs-test-runner.main"]}
:readme {:extra-deps {seancorfield/readme {:mvn/version "1.0.13"}}
:main-opts ["-m" "seancorfield.readme"]}
:eastwood {:extra-deps {jonase/eastwood {:mvn/version "RELEASE"}}
:main-opts ["-m" "eastwood.lint" "{:source-paths,[\"src\"]}"]}
:jar {:extra-deps {seancorfield/depstar {:mvn/version "1.1.117"}}
:main-opts ["-m" "hf.depstar.jar" "honeysql.jar"]}
:install {:extra-deps {deps-deploy/deps-deploy {:mvn/version "0.0.9"}}
:main-opts ["-m" "deps-deploy.deps-deploy" "install" "honeysql.jar"]}
:deploy {:extra-deps {deps-deploy/deps-deploy {:mvn/version "0.0.9"}}
:main-opts ["-m" "deps-deploy.deps-deploy" "deploy" "honeysql.jar"]}}}
{:main-opts ["-m" "cognitect.test-runner"]}
;; various "runners" for tests/CI:
:cljs {:extra-deps {olical/cljs-test-runner {:mvn/version "3.8.1"}}
:main-opts ["-m" "cljs-test-runner.main"]}
:gen-doc-tests {:replace-paths ["build"]
: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"]}
:test-doc {:replace-paths ["src" "target/test-doc-blocks/test"]}
:test-doc-clj {:main-opts ["-m" "cognitect.test-runner"
"-d" "target/test-doc-blocks/test"]}
:test-doc-cljs {:main-opts ["-m" "cljs-test-runner.main"
"-c" "{:warnings,{:single-segment-namespace,false}}"
"-d" "target/test-doc-blocks/test"]}
:eastwood {:extra-deps {jonase/eastwood {:mvn/version "1.4.3"}}
:main-opts ["-m" "eastwood.lint" "{:source-paths,[\"src\"]}"]}}}

1550
doc/clause-reference.md Normal file

File diff suppressed because it is too large Load diff

15
doc/cljdoc.edn Normal file
View file

@ -0,0 +1,15 @@
{:cljdoc.doc/tree
[["Readme" {:file "README.md"}]
["Changes" {:file "CHANGELOG.md"}]
["Getting Started" {:file "doc/getting-started.md"}
["General Reference" {:file "doc/general-reference.md"}]
["SQL Clause Reference" {:file "doc/clause-reference.md"}]
["SQL Operator Reference" {:file "doc/operator-reference.md"}]
["SQL 'Special Syntax'" {:file "doc/special-syntax.md"}]
["PostgreSQL Support" {:file "doc/postgresql.md"}]
["XTDB Support" {:file "doc/xtdb.md"}]
["New Relic NRQL Support" {:file "doc/nrql.md"}]
["Other Databases" {:file "doc/databases.md"}]]
["All the Options" {:file "doc/options.md"}]
["Extending HoneySQL" {:file "doc/extending-honeysql.md"}]
["Differences from 1.x" {:file "doc/differences-from-1-x.md"}]]}

86
doc/databases.md Normal file
View file

@ -0,0 +1,86 @@
# Other Databases
There are dedicated sections for [New Relic Query Language Support](nrql.md),
[PostgreSQL Support](postgres.md), and
[XTDB Support](xtdb.md).
This section provides hints and tips for generating SQL for other
databases.
As a reminder, HoneySQL supports the following dialects out of the box:
* `:ansi` -- which is the default and provides broad support for PostgreSQL as well
* `:mysql` -- which includes MariaDB and Percona
* `:nrql` -- as of 2.5.1091
* `:oracle`
* `:sqlserver` -- Microsoft SQL Server
For the most part, these dialects only change the "stropping" --
how SQL entities are quoted in the generated SQL -- but dialects
can change clause order and/or add dialect-specific clauses.
This section is a work-in-progress and more hints and tips will be
added over time for more databases.
## Precedence
The biggest difference between database dialects tends to be
precedence. MySQL actually has different precedence in the `SET`
clause but several databases disagree on the precedence of actual
"set" operations: `UNION`, `EXCEPT`, `INTERSECT`, etc.
HoneySQL tries to be fairly neutral in this area and follows ANSI SQL
precedence. This means that some databases may have problems with
complex SQL operations that combine multiple clauses with contentious
precedence. In general, you can solve this using the `:nest`
pseudo-clause in the DSL:
<!-- :test-doc-blocks/skip -->
```clojure
{:nest DSL}
;; will produce DSL wrapped in ( .. )
```
This should allow you to cater to various databases' precedence
peculiarities.
## BigQuery (Google)
Function names can be case-sensitive: you can use the "as-is" notation
for SQL entities to avoid conversion to upper-case: `[:'domain :ref]`
produces `domain(ref)` rather than `DOMAIN(ref)`.
## ClickHouse
This is another case-sensitive database than requires the "as-is"
notation described for **BigQuery** above.
`WITH expr AS ident` is supported as a core part of the DSL,
as of 2.4.962.
## MySQL
When you select the `:mysql` dialect, the precedence of `:set` is
changed. All the other databases get this correct.
`REPLACE INTO`, while specific to MySQL and SQLite, is supported as
a core part of the DSL, as `:replace-into`, as of 2.4.969.
## SQLite
Precedence of "set" operations: SQLite differs from other databases
in handling compound SQL operations that use multiple `UNION`,
`EXCEPT`, `INTERSECT` clauses. Use `:nest` to disambiguate your
intentions.
See issue [#462](https://github.com/seancorfield/honeysql/issues/462)
for some background on this.
`INSERT OR IGNORE INTO`: this syntax is specific to SQLite for
performing upserts. However, SQLite supports the PostgreSQL-style
upsert with `ON CONFLICT` so you can use that syntax instead, for
`DO NOTHING` and `DO UPDATE SET`. In addition,
`INSERT OR REPLACE INTO` can be written using just `REPLACE INTO`
(see below).
Issue [#448](https://github.com/seancorfield/honeysql/issues/448)
has more background on this.
`REPLACE INTO`, while specific to MySQL and SQLite, is supported as
a core part of the DSL, as `:replace-into`, as of 2.4.969.

232
doc/differences-from-1-x.md Normal file
View file

@ -0,0 +1,232 @@
# Differences Between 1.x and 2.x
The goal of HoneySQL 1.x and earlier was to provide a DSL for vendor-neutral SQL, with the assumption that other libraries would provide the vendor-specific extensions to HoneySQL. HoneySQL 1.x's extension mechanism required quite a bit of internal knowledge (clause priorities and multiple multimethod extension points). It also used a number of custom record types, protocols, and data readers to provide various "escape hatches" in the DSL for representing arrays, function calls (in some situations), inlined values, parameters, and raw SQL, which led to a number of inconsistencies over time, as well as making some things very hard to express while other similar things were easy to express. Addressing bugs caused by vendor-specific differences and by some quirks of how SQL was generated gradually became harder and harder.
The goal of HoneySQL 2.x is to provide an easily-extensible DSL for SQL, supporting vendor-specific differences and extensions, that is as consistent as possible. A secondary goal is to make maintenance much easier by streamlining the machinery and reducing the number of different ways to write and/or extend the DSL.
The DSL itself -- the data structures that both versions convert to SQL and parameters via the `format` function -- is almost exactly the same between the two versions so that migration is relatively painless. The primary API -- the `format` function -- is preserved in 2.x, although the options have changed between 1.x and 2.x. See the **Option Changes** section below for the differences in the options supported.
`format` can accept its options as a single hash map or as named arguments (1.x only supported the latter).
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.7.y requires Clojure 1.10.3 or later. Earlier versions of HoneySQL 2.x support Clojure 1.9.0.
## Group, Artifact, and Namespaces
HoneySQL 2.x uses the group ID `com.github.seancorfield` with the original artifact ID of `honeysql`, in line with the recommendations in Inside Clojure's post about the changes in the Clojure CLI: [Deprecated unqualified lib names](https://insideclojure.org/2020/07/28/clj-exec/); also Clojars [Verified Group Names policy](https://github.com/clojars/clojars-web/wiki/Verified-Group-Names).
In addition, HoneySQL 2.x contains different namespaces so you can have both versions on your classpath without introducing any conflicts. The primary API is now in `honey.sql` and the helpers are in `honey.sql.helpers`.
### HoneySQL 1.x
In `deps.edn`:
<!-- :test-doc-blocks/skip -->
```clojure
honeysql {:mvn/version "1.0.461"}
;; or, more correctly:
honeysql/honeysql {:mvn/version "1.0.461"}
```
Required as:
<!-- :test-doc-blocks/skip -->
```clojure
(ns my.project
(:require [honeysql.core :as sql]))
```
Or if in the REPL:
<!-- :test-doc-blocks/skip -->
```clojure
(require '[honeysql.core :as sq])
```
In use:
<!-- :test-doc-blocks/skip -->
```clojure
(sql/format {:select [:*] :from [:table] :where [:= :id 1]})
;;=> ["SELECT * FROM table WHERE id = ?" 1]
(sql/format {:select [:*] :from [:table] :where [:= :id 1]} :quoting :mysql)
;;=> ["SELECT * FROM `table` WHERE `id` = ?" 1]
```
The namespaces were:
* `honeysql.core` -- the primary API (`format`, etc),
* `honeysql.format` -- the logic for the formatting engine,
* `honeysql.helpers` -- helper functions to build the DSL,
* `honeysql.types` -- records, protocols, and data readers,
* `honeysql.util` -- internal utilities (macros).
Supported Clojure versions: 1.7 and later.
### HoneySQL 2.x
In `deps.edn`:
<!-- :test-doc-blocks/skip -->
```clojure
com.github.seancorfield/honeysql {:mvn/version "2.7.1295"}
```
Required as:
<!-- :test-doc-blocks/skip -->
```clojure
(ns my.project
(:require [honey.sql :as sql]))
```
Or if in the REPL:
```clojure
(require '[honey.sql :as sql])
```
In use:
```clojure
(sql/format {:select [:*] :from [:table] :where [:= :id 1]})
;;=> ["SELECT * FROM table WHERE id = ?" 1]
(sql/format {:select [:*] :from [:table] :where [:= :id 1]} {:dialect :mysql})
;;=> ["SELECT * FROM `table` WHERE `id` = ?" 1]
```
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.10.3 and later.
## API Changes
The primary API is just `honey.sql/format`. The `array`, `call`, `inline`, `param`, and `raw` functions have all become standard syntax in the DSL as functions (and their tagged literal equivalents have also gone away because they are no longer needed). _[As of 2.0.0-rc3, `call` has been reinstated as an undocumented function in `honey.sql` purely to aid migration from 1.x]_
Other `honeysql.core` functions that no longer exist include: `build`, `qualify`, and `quote-identifier`. Many other public functions were essentially undocumented (neither mentioned in the README nor in the tests) and also no longer exist.
> As of 2.4.1002, the functionality of `qualify` can be achieved through the `:.` dot-selection special syntax.
You can now select a non-ANSI dialect of SQL using the new `honey.sql/set-dialect!` function (which sets a default dialect for all `format` operations) or by passing the new `:dialect` option to the `format` function. `:ansi` is the default dialect (which will mostly incorporate PostgreSQL usage over time). Other dialects supported are `:mysql` (which has a different quoting strategy and uses a different ranking for the `:set` clause), `:oracle` (which is essentially the `:ansi` dialect but will control other things over time), and `:sqlserver` (which is essentially the `:ansi` dialect but with a different quoting strategy). Other dialects and changes may be added over time.
> Note: in general, all clauses are available in all dialects in HoneySQL unless the syntax of the clauses conflict between dialects (currently, no such clauses exist). The `:mysql` dialect is the only one so far that changes the priority ordering of a few clauses.
## Option Changes
The `:quoting <dialect>` option has been superseded by the new dialect machinery and a new `:quoted` option that turns quoting on or off. You either use `:dialect <dialect>` instead (which turns on quoting by default) or set a default dialect (via `set-dialect!`) and then use `:quoted true` in `format` calls where you want quoting.
SQL entity names are automatically quoted if you specify a `:dialect` option to `format`, unless you also specify `:quoted false`.
The following options are no longer supported:
* `:allow-dashed-names?` -- if you provide dashed-names in 2.x, they will be left as-is if quoting is enabled, else they will be converted to snake_case (so you will either get `"dashed-names"` with quoting or `dashed_names` without). If you want dashed-names to be converted to snake_case when `:quoted true`, you also need to specify `:quoted-snake true`.
* `:allow-namespaced-names?` -- this supported `foo/bar` column names in SQL which I'd like to discourage.
* `:namespace-as-table?` -- this is the default in 2.x: `:foo/bar` will be treated as `foo.bar` which is more in keeping with `next.jdbc`.
* `:parameterizer` -- this would add a lot of complexity to the formatting engine and I do not know how widely it was used (especially in its arbitrarily extensible form). _[As of 2.4.962, the ability to generated SQL with numbered parameters, i.e., `$1` instead of positional parameters, `?`, has been added via the `:numbered true` option]_
* `:return-param-names` -- this was added to 1.x back in 2013 without an associated issue or PR so I've no idea what use case this was intended to support.
> Note: I expect some push back on those first three options and the associated behavior changes.
## DSL Changes
The general intent is that the data structure behind the DSL is unchanged, for the most part. The main deliberate change is the removal of the reader literals (and their associated helper functions) in favor of standardized syntax, e.g., `[:array [1 2 3]]` instead of either `#sql/array [1 2 3]` or `(sql/array [1 2 3])`.
The following new syntax has been added:
* `:array` -- used as a function to replace the `sql/array` / `#sql/array` machinery,
* `:between` -- this is now explicit syntax rather than being a special case in expressions,
* `:case` -- this is now explicit syntax,
* `:cast` -- `[:cast expr :type]` => `CAST( expr AS type )`,
* `:composite` -- explicit syntax to produce a comma-separated list of expressions, wrapped in parentheses,
* `:default` -- for `DEFAULT` values (in inserts) and for declaring column defaults in table definitions,
* `:escape` -- used to wrap a regular expression so that non-standard escape characters can be provided,
* `:inline` -- used as a function to replace the `sql/inline` / `#sql/inline` machinery,
* `:interval` -- used as a function to support `INTERVAL <n> <units>`, e.g., `[:interval 30 :days]` for databases that support it (e.g., MySQL) and, as of 2.4.1026, for `INTERVAL 'n units'`, e.g., `[:interval "24 hours"]` for ANSI/PostgreSQL.
* `:lateral` -- used to wrap a statement or expression, to provide a `LATERAL` join,
* `:lift` -- used as a function to prevent interpretation of a Clojure data structure as DSL syntax (e.g., when passing a vector or hash map as a parameter value) -- this should mostly be a replacement for `honeysql.format/value`,
* `:nest` -- used as a function to add an extra level of nesting (parentheses) around an expression,
* `:not` -- this is now explicit syntax,
* `:over` -- the function-like part of a T-SQL window clause,
* `:param` -- used as a function to replace the `sql/param` / `#sql/param` machinery,
* `:raw` -- used as a function to replace the `sql/raw` / `#sql/raw` machinery. Vector subexpressions inside a `[:raw ..]` expression are formatted to SQL and parameters. Other subexpressions are just turned into strings and concatenated. This is different to the 1.x behavior but should be more flexible, since you can now embed `:inline`, `:param`, and `:lift` inside a `:raw` expression.
> Note 1: in 1.x, inlining a string `"foo"` produced `foo` but in 2.x it produces `'foo'`, i.e., string literals become SQL strings without needing internal quotes (1.x required `"'foo'"`).
Several additional pieces of syntax have also been added to support column
definitions in `CREATE TABLE` clauses, now that 2.x supports DDL statement
construction:
* `:constraint`, `:default`, `:foreign-key`, `:index`, `:primary-key`, `:references`, `:unique`,
* `:entity` -- used to force an expression to be rendered as a SQL entity (instead of a SQL keyword).
### select and function calls
You can now `SELECT` a function call more easily, using `[[...]]`. This was previously an error -- missing an alias -- but it was a commonly requested change, to avoid using `(sql/call ...)`:
```clojure
user=> (sql/format {:select [:a [:b :c] [[:d :e]] [[:f :g] :h]]})
;; select a (column), b (aliased to c), d (fn call), f (fn call, aliased to h):
["SELECT a, b AS c, D(e), F(g) AS h"]
```
On a related note, `sql/call` has been removed because it should never be needed now: `[:foo ...]` should always be treated as a function call, consistently, avoiding the special cases in 1.x that necessitated the explicit `sql/call` syntax.
### select modifiers
HoneySQL 1.x provided a `:modifiers` clause (and a `modifiers`) helper as a way to "modify"
a `SELECT` to be `DISTINCT`. The [nilenso/honeysql-helpers](https://github.com/nilenso/honeysql-postgres) library extended that to support `:distinct-on`
a group of columns. In HoneySQL 2.x, you use `:select-distinct` and `:select-distinct-on`
(and their associated helpers) for that instead. MS SQL Server's `TOP` modifier is also
supported via `:select-top` and `:select-distinct-top`.
### set vs sset, set0, set1
The `:set` clause is dialect-dependent. In `:mysql`, it is ranked just before the `:where` clause. In all other dialects, it is ranked just before the `:from` clause. Accordingly, the `:set0` and `:set1` clauses are no longer supported (because they were workarounds in 1.x for this conflict). The helper is now called
`set` rather than `sset`, `set0`, and `set1` (so be aware of the conflict with `clojure.core/set`).
### exists
HoneySQL 1.x implemented `:exists` as part of the DSL, which was incorrect:
it should have been a function, and in 2.x it is:
```clojure
;; 1.x: EXISTS should never have been implemented as SQL syntax: it's an operator!
;; (sq/format {:exists {:select [:a] :from [:foo]}})
;; -> ["EXISTS (SELECT a FROM foo)"]
;; 2.x: select function call with an alias:
user=> (sql/format {:select [[[:exists {:select [:a] :from [:foo]}] :x]]})
["SELECT EXISTS (SELECT a FROM foo) AS x"]
```
### `ORDER BY` with `NULLS FIRST` or `NULLS LAST`
In HoneySQL 1.x, if you wanted to generate SQL like
```sql
ORDER BY ... DESC NULLS LAST
```
you needed to pass `:nulls-last` as a separate keyword, after `:asc` or `:desc`:
```clj
{:order-by [[:my-column :desc :nulls-last]]}
```
In HoneySQL 2.x, the direction and the null ordering rule are now combined into a single keyword:
```clj
{:order-by [[:my-column :desc-nulls-last]]}
```
## Extensibility
The protocols and multimethods in 1.x have all gone away. The primary extension point is `honey.sql/register-clause!` which lets you specify the new clause (keyword), the formatter function for it, and the existing clause that it should be ranked before (`format` processes the DSL in clause order).
You can also register new "functions" that can implement special syntax (such as `:array`, `:inline`, `:raw` etc above) via `honey.sql/register-fn!`. This accepts a "function" name as a keyword and a formatter which will generally be a function of two arguments: the function name (so formatters can be reused across different names) and a vector of the arguments the function should accept.
And, finally, you can register new operators that will be recognized in expressions via `honey.sql/register-op!`. This accepts an operator name as a keyword and an optional named parameter to indicate whether it should ignore operands that evaluate to `nil` (via `:ignore-nil`). That can make it easier to construct complex expressions programmatically without having to worry about conditionally removing "optional" (`nil`) values.
> Note: because of the changes in the extension machinery between 1.x and 2.x, it is not possible to use the [nilenso/honeysql-postgress](https://github.com/nilenso/honeysql-postgres) library with HoneySQL 2.x but the goal is to incorporate all of the syntax from that library into the core of HoneySQL.
## Helpers
The `honey.sql.helpers` namespace includes a helper function that corresponds to every supported piece of the data DSL understood by HoneySQL (1.x only had a limited set of helper functions). Unlike 1.x helpers which sometimes had both a regular helper and a `merge-` helper, 2.x helpers will all merge clauses by default (if that makes sense for the underlying DSL): use `:dissoc` if you want to force an overwrite.
The only helpers that have non-merging behavior are:
* The SQL set operations `intersect`, `union`, `union-all`, `except`, and `except-all` which always wrap around their arguments,
* The SQL clauses `delete`, `fetch`, `for`, `limit`, `lock`, `offset`, `on-constraint`, `set`, `truncate`, `update`, and `values` which overwrite, rather than merge,
* The DDL helpers `drop-column`, `drop-index`, `rename-table`, and `with-data`,
* The function helper `composite` which is a convenience for the `:composite` syntax mentioned above: `(composite :a :b)` is the same as `[:composite :a :b]` which produces `(a, b)`.

237
doc/extending-honeysql.md Normal file
View file

@ -0,0 +1,237 @@
# Extending HoneySQL
Out of the box, HoneySQL supports most standard ANSI SQL clauses
and expressions but where it doesn't support something you need
you can add new clauses, new operators, and new "functions" (or
"special syntax").
There are three extension points in `honey.sql` that let you
register formatters or behavior corresponding to clauses,
operators, and functions.
Built in clauses include: `:select`, `:from`, `:where` and
many more. Built in operators include: `:=`, `:+`, `:%`.
Built in functions (special syntax) include: `:array`, `:case`,
`:cast`, `:inline`, `:raw` and many more.
See also the section on
[database-specific hints and tips](databases.md), which may
let you avoid extending HoneySQL.
## Extending what `:inline` can do
By default, the `:inline` option can convert a fairly
basic set of values/types to SQL strings:
* `nil`
* strings
* keywords and symbols
* vectors
* UUIDs (Clojure only)
Everything is naively converted by calling `str`.
You can extend `honey.sql.protocols/InlineValue` to
other types and defining how the `sqlize` function
should behave. It takes a single argument, the value
to be inlined (converted to a SQL string).
## Registering a New Clause Formatter
`honey.sql/register-clause!` accepts a keyword (or a symbol)
that should be treated as a new clause in a SQL statement,
a "formatter", and a keyword (or a symbol) that identifies
an existing clause that this new one should be ordered before.
The formatter can either be a function
of two arguments or a previously registered clause (so
that you can easily reuse formatters).
The formatter function will be called with:
* The clause name (always as a keyword),
* The sequence of arguments provided.
The formatter function should return a vector whose first element is the
generated SQL string and whose remaining elements (if any) are the parameters
lifted from the DSL (for which the generated SQL string should contain `?`
placeholders).
The third argument to `register-clause!` allows you to
insert your new clause formatter so that clauses are
formatted in the correct order for your SQL dialect.
For example, `:select` comes before `:from` which comes
before `:where`. You can call `clause-order` to see what the
current ordering of clauses is.
<!-- :test-doc-blocks/skip -->
```clojure
;; the formatter will be passed your new clause and the value associated
;; with that clause in the DSL (which is often a sequence but does not
;; need to be -- it can be whatever syntax you desire in the DSL):
(sql/register-clause! :foobar
(fn [clause x]
(let [[sql & params]
(if (ident? x)
(sql/format-expr x)
(sql/format-dsl x))]
(c/into [(str (sql/sql-kw clause) " " sql)] params)))
:from) ; SELECT ... FOOBAR ... FROM ...
;; example usage:
(sql/format {:select [:a :b] :foobar :baz})
=> ["SELECT a, b FOOBAR baz"]
(sql/format {:select [:a :b] :foobar {:where [:= :id 1]}})
=> ["SELECT a, b FOOBAR WHERE id = ?" 1]
```
> Note: if you call `register-clause!` more than once for the same clause, the last call "wins". This allows you to correct an incorrect clause order insertion by simply calling `register-clause!` again with a different third argument.
## Defining a Helper Function for a New Clause
Having registered a new clause, you might also want a helper function
for it, just as the built-in clauses have helpers in `honey.sql.helpers`.
Two functions exist in that namespace to make it easier for you to
define your own helpers:
* `generic-helper-variadic` -- most clauses accept an arbitrary number of items in a sequence and multiple calls in a DSL expression will merge so this is the helper you will use for most clauses,
* `generic-helper-unary` -- a handful of clauses only accept a single item and cannot be merged (they behave as "last one wins"), so this helper supports that semantic.
Each of these helper support functions should be called with the keyword that
identifies your new clause and the sequence of arguments passed to it. See
the docstrings for more detail.
You might have:
<!-- :test-doc-blocks/skip -->
```clojure
(sql/register-clause! :my-clause my-formatter :where)
(defn my-clause [& args] (h/generic-helper-variadic :my-clause args))
```
## Registering a New Operator
`honey.sql/register-op!` accepts a keyword (or a symbol) that
should be treated as a new infix operator.
All operators are treated as variadic and an exception will be
thrown if they are provided no arguments:
```clojure
(require '[honey.sql :as sql])
(sql/register-op! :<=>)
;; and then use the new operator:
(sql/format {:select [:*], :from [:table], :where [:<=> 13 :x 42]})
;; will produce:
;;=> ["SELECT * FROM table WHERE ? <=> x <=> ?" 13 42]
```
If you are building expressions programmatically, you
may want your new operator to ignore "empty" expressions,
i.e., where your expression-building code might produce
`nil`. The built-in operators `:and` and `:or` ignore
such `nil` expressions. You can specify `:ignore-nil true`
to achieve that:
```clojure
(sql/register-op! :<=> :ignore-nil true)
;; and then use the new operator:
(sql/format {:select [:*], :from [:table], :where [:<=> nil :x 42]})
;; will produce:
;;=> ["SELECT * FROM table WHERE x <=> ?" 42]
```
### PostgreSQL Operators
A number of PostgreSQL operators contain `@` which is not legal in a Clojure keyword or symbol (as literal syntax). The recommendation is to `def` your own name for these
operators, using `at` in place of `@`, with an explicit call to `keyword` (or `symbol`), and then use that `def`'d name when registering new operators and when writing
your DSL expressions:
```clojure
(def <at (keyword "<@"))
(sql/register-op! <at)
;; and use it in expressions: [<at :submitted [:range :begin :end]]
```
## Registering a New Function (Special Syntax)
`honey.sql/register-fn!` accepts a keyword (or a symbol)
that should be treated as new syntax (as a function call),
and a "formatter". The formatter can either be a function
of two arguments or a previously registered "function" (so
that you can easily reuse formatters).
The formatter function will be called with:
* The function name (always as a keyword),
* The sequence of arguments provided.
For example:
<!-- :test-doc-blocks/skip -->
```clojure
(sql/register-fn! :foo (fn [f args] ..))
(sql/format {:select [:*], :from [:table], :where [:foo 1 2 3]})
```
Your formatter function will be called with `:foo` and `(1 2 3)`.
It should return a vector containing a SQL string followed by
any parameters:
```clojure
(sql/register-fn! :foo (fn [f args] ["FOO(?)" (first args)]))
(sql/format {:select [:*], :from [:table], :where [:foo 1 2 3]})
;; produces:
;;=> ["SELECT * FROM table WHERE FOO(?)" 1]
```
In practice, it is likely that your formatter would call
`sql/sql-kw` on the function name to produce a SQL representation
of it and would call `sql/format-expr` on each argument:
```clojure
(defn- foo-formatter [f [x]]
(let [[sql & params] (sql/format-expr x)]
(into [(str (sql/sql-kw f) "(" sql ")")] params)))
(sql/register-fn! :foo foo-formatter)
(sql/format {:select [:*], :from [:table], :where [:foo [:+ :a 1]]})
;; produces:
;;=> ["SELECT * FROM table WHERE FOO(a + ?)" 1]
```
## Registering a new Dialect
_New in HoneySQL 2.3.x_
The built-in dialects that HoneySQL supports are:
* `:ansi` -- the default, that quotes SQL entity names with double-quotes, like `"this"`
* `:mysql` -- quotes SQL entity names with backticks, and changes the precedence of `SET` in `UPDATE`
* `:nrql` -- as of 2.5.1091, see [New Relic NRQL Support](nrsql.md) for more details of the NRQL dialect
* `:oracle` -- quotes SQL entity names like `:ansi`, and does not use `AS` in aliases
* `:sqlserver` -- quotes SQL entity names with brackets, like `[this]`
A dialect spec is a hash map containing at least `:quote` but also optionally `:clause-order-fn` and/or `:as`:
* `:quote` -- a unary function that takes a string and returns the quoted version of it
* `:clause-order-fn` -- a unary function that takes a sequence of clause names (keywords) and returns an updated sequence of clause names; this defines the precedence of clauses in the DSL parser
* `:as` -- a boolean that indicates whether `AS` should be present in aliases (the default, if `:as` is omitted) or not (by specifying `:as false`)
To make writing new dialects easier, the following helper functions in `honey.sql` are available:
* `add-clause-before` -- a function that accepts the sequence of clause names, the (new) clause to add, and the clause to add it before (`nil` means add at the end)
* `get-dialect` -- a function that accepts an existing dialect name (keyword) and returns its spec (hash map)
* `strop` -- a function that accepts an opening quote, a string, and a closing quote and returns the quoted string, doubling-up any closing quote characters inside the string to make it legal SQL
* `upper-case` -- a locale-insensitive version of `clojure.string/upper-case`
For example, to add a variant of the `:ansi` dialect that forces names to be upper-case as well as double-quoting them:
```clojure
(sql/register-dialect! ::ANSI (update (sql/get-dialect :ansi) :quote comp sql/upper-case))
;; or you could do this:
(sql/register-dialect! ::ANSI {:quote #(sql/strop \" (sql/upper-case %) \")})
(sql/format {:select :foo :from :bar} {:dialect :ansi})
;;=> ["SELECT \"foo\" FROM \"bar\""]
(sql/format {:select :foo :from :bar} {:dialect ::ANSI})
;;=> ["SELECT \"FOO\" FROM \"BAR\""]
```

169
doc/general-reference.md Normal file
View file

@ -0,0 +1,169 @@
# General Reference Documentation
This section provides more details about specific behavior in HoneySQL and
how to generate certain SQL constructs.
## SQL Entity Generation
HoneySQL treats keywords and symbols as SQL entities (in any context other
than function call position in a sequence). If quoting is in effect,
either because `:dialect` was specified as an option to `format` or
because `:quoted true` was specified, the literal name of an unqualified,
single-segment keyword or symbol is used as-is and quoted:
```clojure
(require '[honey.sql :as sql])
(sql/format {:select :foo-bar} {:quoted true})
;;=> ["SELECT \"foo-bar\""]
(sql/format {:select :foo-bar} {:dialect :mysql})
;;=> ["SELECT `foo-bar`"]
```
If quoting is not in effect, any dashes (`-`) in the name will be converted to underscores (`_`):
```clojure
(sql/format {:select :foo-bar})
;;=> ["SELECT foo_bar"]
(sql/format {:select :foo-bar} {:dialect :mysql :quoted false})
;;=> ["SELECT foo_bar"]
```
If a keyword or symbol contains a dot (`.`), it will be split apart
and treated as a table (or alias) name and a column name:
```clojure
(sql/format {:select :foo-bar.baz-quux} {:quoted true})
;;=> ["SELECT \"foo-bar\".\"baz-quux\""]
(sql/format {:select :foo-bar.baz-quux} {:dialect :mysql})
;;=> ["SELECT `foo-bar`.`baz-quux`"]
(sql/format {:select :foo-bar.baz-quux})
;;=> ["SELECT foo_bar.baz_quux"]
(sql/format {:select :foo-bar.baz-quux} {:dialect :mysql :quoted false})
;;=> ["SELECT foo_bar.baz_quux"]
```
A qualified keyword or symbol, will also be split apart, but dashes (`-`)
in the namespace portion _will_ be converted to underscores (`_`) even
when quoting is in effect:
```clojure
(sql/format {:select :foo-bar/baz-quux} {:quoted true})
;;=> ["SELECT \"foo_bar\".\"baz-quux\""] ; _ in table, - in column
(sql/format {:select :foo-bar/baz-quux} {:dialect :mysql})
;;=> ["SELECT `foo_bar`.`baz-quux`"] ; _ in table, - in column
(sql/format {:select :foo-bar/baz-quux})
;;=> ["SELECT foo_bar.baz_quux"] ; _ in table and _ in column
(sql/format {:select :foo-bar/baz-quux} {:dialect :mysql :quoted false})
;;=> ["SELECT foo_bar.baz_quux"] ; _ in table and _ in column
```
Finally, there are some contexts where only a SQL entity is accepted, rather than an
arbitrary SQL expression, so a string will also be treated as a SQL entity and in such cases
the entity name will always be quoted, dashes (`-`) will not be converted to
underscores (`_`), and a slash (`/`) is not treated as separating a
qualifier from the name, regardless of the `:dialect` or `:quoted` settings:
```clojure
(sql/format {:update :table :set {"foo-bar" 1 "baz/quux" 2}})
;;=> ["UPDATE table SET \"foo-bar\" = ?, \"baz/quux\" = ?" 1 2]
(sql/format {:update :table :set {"foo-bar" 1 "baz/quux" 2}} {:quoted true})
;;=> ["UPDATE \"table\" SET \"foo-bar\" = ?, \"baz/quux\" = ?" 1 2]
(sql/format {:update :table :set {"foo-bar" 1 "baz/quux" 2}} {:dialect :mysql})
;;=> ["UPDATE `table` SET `foo-bar` = ?, `baz/quux` = ?" 1 2]
(sql/format {:update :table :set {"foo-bar" 1 "baz/quux" 2}} {:dialect :sqlserver :quoted false})
;;=> ["UPDATE table SET [foo-bar] = ?, [baz/quux] = ?" 1 2]
```
## Tuples and Composite Values
Some databases support "composite values" which are usually
represented as tuples in SQL, eg., `(col1,col2)` or `(13,42,'foo')`.
In HoneySQL 1.x, you could sometimes get away with just using a
vector of entities and/or values, but it was very much dependent
on the context. HoneySQL 2.x always treats vectors (and sequences)
as function calls (which may be "special syntax" or an actual
function call).
HoneySQL provides `:composite` as special syntax to construct
these tuples:
```clojure
(sql/format-expr [:composite :col1 :col2])
;;=> ["(col1, col2)"]
(sql/format-expr [:composite 13 42 "foo"])
;;=> ["(?, ?, ?)" 13 42 "foo"]
;; or using symbols:
(sql/format-expr '(composite col1 col2))
;;=> ["(col1, col2)"]
(sql/format-expr '(composite 13 42 "foo"))
;;=> ["(?, ?, ?)" 13 42 "foo"]
```
There is also a `composite` helper function.
## Working with JSON/JSONB (PostgreSQL)
It is increasingly common for PostgreSQL users to be working with JSON columns
in their databases these days. PostgreSQL has really good support for JSON types.
When using HoneySQL to generate SQL that manipulates JSON, you need to be careful
because it is common to use regular Clojure data structures to represent the JSON
and rely on protocol extensions for the JDBC libraries to handle automatic
conversion of Clojure data structures to JSON (e.g., see
[Tips & Tricks > Working with JSON and JSONB](https://cljdoc.org/d/com.github.seancorfield/next.jdbc/CURRENT/doc/getting-started/tips-tricks#working-with-json-and-jsonb) in the `next.jdbc`
documentation).
HoneySQL also uses Clojure data structures, to represent function calls (vectors) and
SQL statements (hash maps), so if you are also using Clojure data structures for your
JSON, you need to tell HoneySQL not to interpret those values. There
are two possible approaches:
1. Use named parameters (e.g., `[:param :myval]`) instead of having the values directly in the DSL structure and then pass `{:params {:myval some-json}}` as part of the options in the call to `format`, or
2. Use `[:lift ..]` wrapped around any structured values which tells HoneySQL not to interpret the vector or hash map value as a DSL: `[:lift some-json]`.
## Caching
As of 2.2.858, `format` can cache the SQL and parameters produced from the data structure so that it does not need to be computed on every call. This functionality is available only in Clojure and depends on [`org.clojure/core.cache`](https://github.com/clojure/core.cache) being on your classpath. If you are repeatedly building the same complex SQL statements over and over again, this can be a good way to provide a performance boost but there are some caveats.
* You need `core.cache` as a dependency: `org.clojure/core.cache {:mvn/version "1.0.225"}` was the latest as of January 20th, 2022,
* You need to create one or more caches yourself, from the various factory functions in the [`clojure.core.cache.wrapped` namespace](http://clojure.github.io/core.cache/#clojure.core.cache.wrapped),
* You should use named parameters in your SQL DSL data structure, e.g., `:?foo` or `'?foo`, and pass the actual parameter values via the `:params` option to `format`.
You can then pass the (atom containing the) cache to `format` using the `:cache` option. The call to `format` then looks in that cache for a match for the data structure passed in, i.e., the entire data structure is used as a key into the cache, including any literal parameter values. If the cache contains a match, the corresponding vector of a SQL string and parameters is used, otherwise the data structure is parsed as usual and the SQL string (and parameters) generated from it (and stored in the cache for the next call). Finally, named parameters in the vector are replaced by their values from the `:params` option.
The code that _builds_ the DSL data structure will be run in all cases, so any conditional logic and helper function calls will still happen, since that is how the data structure is created and then passed to `format`. If you want to also avoid that overhead, you'd need to take steps to build the data structure separately and store it somewhere for reuse in the call to `format`.
Since the data structure is used as the key into the cache, literal parameter values will lead to different keys:
<!-- :test-doc-blocks/skip -->
```clojure
;; these are two different cache entries:
(sql/format {:select :* :from :table :where [:= :id 1]} {:cache my-cache})
(sql/format {:select :* :from :table :where [:= :id 2]} {:cache my-cache})
;; these are the same cache entry:
(sql/format {:select :* :from :table :where [:= :id :?id]} {:cache my-cache :params {:id 1}})
(sql/format {:select :* :from :table :where [:= :id :?id]} {:cache my-cache :params {:id 2}})
```
Since HoneySQL accepts any of the `clojure.core.cache.wrapped` caches and runs every data structure through the provided `:cache`, it's up to you to ensure that your cache is appropriate for that usage: a "basic" cache will keep every entry until the cache is explicitly emptied; a TTL cache will keep each entry for a specific period of time; and so on.
> Note: because `IN ()` expressions are inlined, you cannot cache SQL that includes them. If you try to `format` a statement that includes an `IN ()` expression when you provide the `:cache` option, you will get an exception. See [#396](https://github.com/seancorfield/honeysql/issues/396) for details of why this doesn't work.
## Other Sections Will Be Added!
As questions arise about the use of HoneySQL 2.x, I will add new sections here.
## Other Reference Documentation
The full list of supported SQL clauses is documented in the
[Clause Reference](clause-reference.md). The full list
of operators supported (as prefix-form "functions") is
documented in the [Operator Reference](operator-reference.md)
section. The full list
of "special syntax" functions is documented in the
[Special Syntax](special-syntax.md) section. The best
documentation for the helper functions is in the
[honey.sql.helpers](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/api/honey.sql.helpers) namespace.
If you're migrating to HoneySQL 2.x, this [overview of differences
between 1.x and 2.x](differences-from-1-x.md) should help.

442
doc/getting-started.md Normal file
View file

@ -0,0 +1,442 @@
# Getting Started with HoneySQL
HoneySQL lets you build complex SQL statements by constructing
and composing Clojure data structures and then formatting that
data to a SQL statement (string) and any parameters it needs.
## Installation
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.7.1295"}
```
For Leiningen, add the following dependency to your `project.clj` file:
<!-- :test-doc-blocks/skip -->
```clojure
[com.github.seancorfield/honeysql "2.7.1295"]
```
HoneySQL produces SQL statements but does not execute them.
To execute SQL statements, you will also need a JDBC wrapper like
[`seancorfield/next.jdbc`](https://github.com/seancorfield/next-jdbc) and a JDBC driver for the database you use.
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://john.shaffe.rs/honeysql/), written in ClojureScript!
## Basic Concepts
SQL statements are represented as hash maps, with keys that
represent clauses in SQL. SQL expressions are generally
represented as vectors, where the first element identifies
the function or operator and the remaining elements are the
arguments or operands.
`honey.sql/format` takes a hash map representing a SQL
statement and produces a vector, suitable for use with
`next.jdbc` or `clojure.java.jdbc`, that has the generated
SQL string as the first element followed by any parameter
values identified in the SQL expressions:
```clojure
(require '[honey.sql :as sql])
(sql/format {:select [:*], :from [:table], :where [:= :id 1]})
;;=> ["SELECT * FROM table WHERE id = ?" 1]
```
By default, any values found in the data structure, that are not keywords
or symbols, are treated as positional parameters and replaced
by `?` in the SQL string and lifted out into the vector that
is returned from `format`.
Most clauses expect a vector as their value, containing
either a list of SQL entities or the representation of a SQL
expression. Some clauses accept a single SQL entity. A few
accept a more specialized form (such as `:set` within an `:update` clause
accepting a hash map of SQL entities and SQL expressions).
> Note: clauses can have a list as their value, but literal vectors and keywords are easier to type without quoting.
A SQL entity can be a simple keyword (or symbol) or a pair
that represents a SQL entity and its alias (where aliases are allowed):
```clojure
(sql/format {:select [:t.id [:name :item]], :from [[:table :t]], :where [:= :id 1]})
;;=> ["SELECT t.id, name AS item FROM table AS t WHERE id = ?" 1]
```
The `FROM` clause now has a pair that identifies the SQL entity
`table` and its alias `t`. Columns can be identified either by
their qualified name (as in `:t.id`) or their unqualified name
(as in `:name`). The `SELECT` clause here identifies two SQL
entities: `t.id` and `name` with the latter aliased to `item`.
Symbols can also be used, but you need to quote them to
avoid evaluation:
```clojure
(sql/format '{select [t.id [name item]], from [[table t]], where [= id 1]})
;;=> ["SELECT t.id, name AS item FROM table AS t WHERE id = ?" 1]
;; or you can use (..) instead of [..] when quoted to produce the same result:
(sql/format '{select (t.id (name item)), from ((table t)), where (= id 1)})
;;=> ["SELECT t.id, name AS item FROM table AS t WHERE id = ?" 1]
```
> Note: these quoted forms may be appealing to users familiar with Datalog-family query languages, and they can be easier to type (and read) in some cases since you do not need to add `:` (shift-`;` on most keyboards) to the start of each SQL entity. The quoted forms do not work well in the [HoneySQL web app](https://john.shaffe.rs/honeysql/) so it's better to stick with vectors and keywords when using that.
If you wish, you can specify SQL entities as namespace-qualified
keywords (or symbols) and the namespace portion will treated as
the table name, i.e., `:foo/bar` instead of `:foo.bar`:
```clojure
;; notice the following both produce the same result:
(sql/format {:select [:t/id [:name :item]], :from [[:table :t]], :where [:= :id 1]})
;;=> ["SELECT t.id, name AS item FROM table AS t WHERE id = ?" 1]
(sql/format '{select [t/id [name item]], from [[table t]], where [= id 1]})
;;=> ["SELECT t.id, name AS item FROM table AS t WHERE id = ?" 1]
```
## SQL Expressions
In addition to using hash maps to describe SQL clauses,
HoneySQL uses vectors to describe SQL expressions. Any
vector that begins with a keyword (or symbol) is considered
to be a kind of function invocation. Certain "functions" are
considered to be "special syntax" and have custom rendering.
Some "functions" are considered to be operators. In general,
`[:foo :a 42 "c"]` will render as `FOO(a, ?, ?)` with the parameters
`42` and `"c"` lifted out into the overall vector result
(with a SQL string followed by all its parameters).
> Note: you can use the `:numbered true` option to `format` to produce SQL containing numbered placeholders, like `FOO(a, $1, $2)`, instead of positional placeholders (`?`).
As of 2.4.1002, function calls with "named" arguments are supported
which some databases support, e.g., MySQL and PostgreSQL both have
`SUBSTRING()`:
<!-- :test-doc-blocks/skip -->
```clojure
[:substring :col 3 4] ;=> SUBSTRING(col, 3, 4)
;; can also be written:
[:substring :col :!from 3 :!for 4] ;=> SUBSTRING(col FROM 3 FOR 4)
```
In a function call, any keywords (or symbols) that begin with `!` followed
by a letter are treated as inline SQL keywords to be used instead of `,`
between arguments -- or in front of arguments, such as for `TRIM()`:
<!-- :test-doc-blocks/skip -->
```clojure
[:trim :!leading "x" :!from :col] ;=> TRIM(LEADING ? FROM col), with "x" parameter
[:trim :!both :!from :col] ;=> TRIM(BOTH FROM col), trims spaces
;; adjacent inline SQL keywords can be combined with a hyphen:
[:trim :!both-from :col] ;=> TRIM(BOTH FROM col)
;; (because - in a SQL keyword is replaced by a space)
```
Operators are all treated as variadic (except for `:=` and
`:<>` / `:!=` / `:not=` which are binary and require exactly two operands).
Special syntax can have zero or more arguments and each form is
described in the [Special Syntax](special-syntax.md) section.
Some examples:
<!-- :test-doc-blocks/skip -->
```clojure
[:= :a 42] ;=> "a = ?" with a parameter of 42
[:+ 42 :a :b] ;=> "? + a + b" with a parameter of 42
[:= :x [:inline "foo"]] ;=> "x = 'foo'" -- the string is inlined
[:now] ;=> "NOW()"
[:count :*] ;=> "COUNT(*)"
[:or [:<> :name nil] [:= :status-id 0]] ;=> "(name IS NOT NULL) OR (status_id = ?)"
;; the nil value is inlined as NULL but 0 is provided as a parameter
```
`:inline` is an example of "special syntax" and it renders its
arguments as part of the SQL string generated by `format`.
Another form of special syntax that is treated as function calls
is keywords or symbols that begin with `%`. Such keywords (or quoted symbols)
are split at `.` and turned into function calls:
<!-- :test-doc-blocks/skip -->
```clojure
:%now ;=> NOW()
:%count.* ;=> COUNT(*)
:%max.foo ;=> MAX(foo)
:%f.a.b ;=> F(a,b)
```
If you need to reference a table or alias for a column, you can use
qualified names in a function invocation:
<!-- :test-doc-blocks/skip -->
```clojure
%max.foo/bar ;=> MAX(foo.bar)
```
The latter syntax can be convenient in a `SELECT` because `[:a :b]` is
otherwise taken as a column and its alias, so selecting a function call
expression requires an extra level of nesting:
```clojure
(sql/format {:select [:a]})
;;=> ["SELECT a"]
(sql/format {:select [[:a :b]]})
;;=> ["SELECT a AS b"]
(sql/format {:select [[[:a :b]]]})
;;=> ["SELECT A(b)"]
;; or use the % notification:
(sql/format {:select [:%a.b]})
;;=> ["SELECT A(b)"]
(sql/format {:select [[[:a :b] :c]]})
;;=> ["SELECT A(b) AS c"]
(sql/format {:select [[:%a.b :c]]})
;;=> ["SELECT A(b) AS c"]
;; putting it all together:
(sql/format {:select [:x [:y :d] [[:z :e]] [[:z :f] :g]]})
;;=> ["SELECT x, y AS d, Z(e), Z(f) AS g"]
(sql/format {:select [:x [:y :d] [:%z.e] [:%z.f :g]]})
;;=> ["SELECT x, y AS d, Z(e), Z(f) AS g"]
(sql/format {:select [:x [:y :d] :%z.e [:%z.f :g]]})
;;=> ["SELECT x, y AS d, Z(e), Z(f) AS g"]
```
## SQL Parameters
As indicated in the preceding sections, values found in the DSL data structure
that are not keywords or symbols are lifted out as positional parameters.
By default, they are replaced by `?` in the generated SQL string and added to the
parameter list in order:
<!-- :test-doc-blocks/skip -->
```clojure
[:between :size 10 20] ;=> "size BETWEEN ? AND ?" with parameters 10 and 20
```
If you specify the `:numbered true` option to `format`, numbered placeholders (`$1`, `$2`, etc) will be used instead of positional placeholders (`?`).
<!-- :test-doc-blocks/skip -->
```clojure
;; with :numbered true option:
[:between :size 10 20] ;=> "size BETWEEN $1 AND $2" with parameters 10 and 20
```
HoneySQL also supports named parameters. There are two ways
of identifying a named parameter:
* a keyword or symbol that begins with `?`
* the `:param` special (functional) syntax
The values of those parameters are supplied in the `format`
call as the `:params` key of the options hash map.
```clojure
(sql/format {:select [:*] :from [:table]
:where [:= :a :?x]}
{:params {:x 42}})
;;=> ["SELECT * FROM table WHERE a = ?" 42]
(sql/format {:select [:*] :from [:table]
:where [:= :a [:param :x]]}
{:params {:x 42}})
;;=> ["SELECT * FROM table WHERE a = ?" 42]
```
Or with `:numbered true`:
```clojure
(sql/format {:select [:*] :from [:table]
:where [:= :a :?x]}
{:params {:x 42} :numbered true})
;;=> ["SELECT * FROM table WHERE a = $1" 42]
(sql/format {:select [:*] :from [:table]
:where [:= :a [:param :x]]}
{:params {:x 42} :numbered true})
;;=> ["SELECT * FROM table WHERE a = $1" 42]
```
## Functional Helpers
In addition to the hash map (and vectors) approach of building
SQL queries with raw Clojure data structures, a
[namespace full of helper functions](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/api/honey.sql.helpers)
is also available. These functions are generally variadic and threadable:
```clojure
(require '[honey.sql :as sql]
'[honey.sql.helpers :refer [select from where]])
(-> (select :t/id [:name :item])
(from [:table :t])
(where [:= :id 1])
(sql/format))
;;=> ["SELECT t.id, name AS item FROM table AS t WHERE id = ?" 1]
```
There is a helper function for every single clause that HoneySQL
supports out of the box. In addition, there are helpers for
`composite`, `lateral`, `over`, and `upsert` that make it easier to construct those
parts of the SQL DSL (examples of `composite` appear in the
[README](/README.md#composite-types)
and in the [General Reference](general-reference.md#tuples-and-composite-values);
examples of `over` appear in the
[Clause Reference](clause-reference.md#window-partition-by-and-over))
In general, `(helper :foo expr)` will produce `{:helper [:foo expr]}`
(with a few exceptions -- see the docstring of the helper function
for details).
In addition to being variadic -- which often lets you omit one
level of `[`..`]` -- the helper functions merge clauses, which
can make it easier to build queries programmatically:
```clojure
(-> (select :t/id)
(from [:table :t])
(where [:= :id 1])
(select [:name :item])
(sql/format))
;;=> ["SELECT t.id, name AS item FROM table AS t WHERE id = ?" 1]
```
If you want to replace a clause with a subsequent helper call,
you need to explicitly remove the prior value:
```clojure
(-> (select :t/id)
(from [:table :t])
(where [:= :id 1])
(dissoc :select)
(select [:name :item])
(sql/format))
;;=> ["SELECT name AS item FROM table AS t WHERE id = ?" 1]
```
Helpers always use keywords when constructing clauses so you
can rely on using keywords in `dissoc`.
The following helpers shadow functions in `clojure.core` so
you need to consider this when referring symbols in from the
`honey.sql.helpers` namespace: `filter`, `for`, `group-by`, `into`, `partition-by`,
`set`, and `update`.
## DDL Statements
HoneySQL 1.x did not support any DDL statements. It was fairly
common for people to use the
[nilenso/honeysql-postgres library](https://github.com/nilenso/honeysql-postgres)
to get DDL support, even if they didn't need the PostgreSQL-specific
extensions. That library does not work with HoneySQL 2.x but all
of the functionality from it (up to 0.4.112) has been incorporated
into HoneySQL now and is described in the [PostgreSQL](postgresql.md)
section (because that covers all of the things that the nilenso
library supported and much of it was PostgreSQL-specific!).
See also the [DDL Clauses section](clause-reference.md#ddl-clauses) of
the Clause Reference for documentation about supported DDL.
## Dialects
By default, HoneySQL operates in ANSI SQL mode but it supports
a lot of PostgreSQL extensions in that mode. PostgreSQL is mostly
a superset of ANSI SQL so it makes sense to support as much as
possible of the union of ANSI SQL and PostgreSQL out of the box.
The dialects supported by HoneySQL 2.x are:
* `:ansi` -- the default, including most PostgreSQL extensions
* `:sqlserver` -- Microsoft SQL Server
* `:mysql` -- MySQL (and Percona and MariaDB)
* `:nrql` -- as of 2.5.1091
* `:oracle` -- Oracle
The most visible difference between dialects is how SQL entities
should be quoted (if the `:quoted true` option is provided to `format`).
Most databases use `"` for quoting (the `:ansi` and `:oracle` dialects).
The `:sqlserver` dialect uses `[`..`]` and the `:mysql` dialect uses
`` ` ``..`` ` ``. In addition, the `:oracle` dialect disables `AS` in aliases.
> Note: by default, quoting is **off** which produces cleaner-looking SQL and assumes you control all the symbols/keywords used as table, column, and function names -- the "SQL entities". If you are building any SQL or DDL where the table, column, or function names could be provided by an external source, **you should specify `:quoted true` to ensure all SQL entities are safely quoted**. As of 2.3.928, if you do _not_ specify `:quoted` as an option, HoneySQL will automatically quote any SQL entities that seem unusual, i.e., that contain any characters that are not alphanumeric or underscore. Purely alphanumeric entities will not be quoted (no entities were quoted by default prior to 2.3.928). You can prevent that auto-quoting by explicitly passing `:quoted false` into the `format` call but, from a security point of view, you should think very carefully before you do that: quoting entity names helps protect you from injection attacks! As of 2.4.947, you can change the default setting of `:quoted` from `nil` to `true` (or `false`) via the `set-options!` function.
Currently, the only dialect that has substantive differences from
the others is `:mysql` for which the `:set` clause
has a different precedence than ANSI SQL.
See [New Relic NRQL Support](nrsql.md) for more details of the NRQL dialect.
You can change the dialect globally using the `set-dialect!` function,
passing in one of the keywords above. You need to call this function
before you call `format` for the first time. See below for examples.
You can change the dialect for a single `format` call by
specifying the `:dialect` option in that call.
Alphanumeric SQL entities are not quoted by default but if you specify the
dialect in a `format` call, they will be quoted. If you don't
specify a dialect in the `format` call, you can specify
`:quoted true` to have SQL entities quoted. You can also enable quoting
globally via the `set-dialect!` function. See below for an example
with `:quoted true`.
If you want to use a dialect _and_ use the default quoting strategy (automatically quote any SQL entities that seem unusual), specify a `:dialect` option and set `:quoted nil`:
<!-- Reminder to doc author:
Reset dialect to default so other blocks are not affected for test-doc-blocks -->
```clojure
(sql/format '{select (id) from (table)} {:quoted true})
;;=> ["SELECT \"id\" FROM \"table\""]
(sql/format '{select (id) from (table)} {:dialect :mysql})
;;=> ["SELECT `id` FROM `table`"]
(sql/set-dialect! :sqlserver)
;;=> nil
(sql/format '{select (id) from (table)} {:quoted true})
;;=> ["SELECT [id] FROM [table]"]
;; you can also choose to enable quoting globally
;; when you set a dialect:
(sql/set-dialect! :mysql :quoted true)
(sql/format '{select (id) from (table)})
;;=> ["SELECT `id` FROM `table`"]
;; and opt out for a specific call:
(sql/format '{select (id) from (table)} {:quoted false})
;;=> ["SELECT id FROM table"]
;; and reset back to the default of :ansi
(sql/set-dialect! :ansi)
;;=> nil
;; which also resets the quoting default (back to nil)
;; so only unusual entity names get quoted:
(sql/format '{select (id) from (table)} {:quoted true})
;;=> ["SELECT \"id\" FROM \"table\""]
;; use default quoting strategy with dialect specific quotes, only unusual entities quoted
(sql/format '{select (id, iffy##field ) from (table)} {:dialect :sqlserver :quoted nil})
;; => ["SELECT id, [iffy##field] FROM table"]
```
Out of the box, as part of the extended ANSI SQL support,
HoneySQL supports quite a few [PostgreSQL extensions](postgresql.md)
and [XTDB extensions](xtdb.md).
> Note: the [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) library which provided PostgreSQL support for HoneySQL 1.x does not work with HoneySQL 2.x. However, HoneySQL 2.x includes all of the functionality from that library (up to 0.4.112) out of the box!
See also the section on
[database-specific hints and tips](databases.md) which may
provide ways to satisfy your database's needs without changing
the dialect or extending HoneySQL.
## Reference Documentation
The full list of supported SQL clauses is documented in the
[Clause Reference](clause-reference.md). The full list
of operators supported (as prefix-form "functions") is
documented in the [Operator Reference](operator-reference.md)
section. The full list
of "special syntax" functions is documented in the
[Special Syntax](special-syntax.md) section. The best
documentation for the helper functions is in the
[honey.sql.helpers](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/api/honey.sql.helpers) namespace.
More detail about certain core HoneySQL functionality can be found in the
[Reference documentation](general-reference.md).
If you're migrating to HoneySQL 2.x, this [overview of differences
between 1.x and 2.x](differences-from-1-x.md) should help.

44
doc/nrql.md Normal file
View file

@ -0,0 +1,44 @@
# New Relic NRQL Support
As of 2.5.1091, HoneySQL provides some support for New Relic's NRQL query language.
At present, the following additional SQL clauses (and their corresponding
helper functions) are supported:
* `:facet` - implemented just like `:select`
* `:since` - implemented like `:interval`
* `:until` - implemented like `:interval`
* `:compare-with` - implemented like `:interval`
* `:timeseries` - implemented like `:interval`
> Note: `:timeseries :auto` is the shortest way to specify a timeseries.
When you select the `:nrql` dialect, SQL formatting assumes `:inline true`
so that the generated SQL string can be used directly in NRQL queries.
In addition, stropping (quoting) is done using backticks, like MySQL,
but entities are not split at `/` or `.` characters, so that:
```
:foo/bar.baz ;;=> `foo/bar.baz`
```
```clojure
user=> (require '[honey.sql :as sql])
nil
```
```clojure
user=> (sql/format {:select [:mulog/timestamp :mulog/event-name]
:from :Log
:where [:= :mulog/data.account "foo-account-id"]
:since [2 :days :ago]
:limit 2000}
{:dialect :nrql :pretty true})
["
SELECT `mulog/timestamp`, `mulog/event-name`
FROM Log
WHERE `mulog/data.account` = 'foo-account-id'
LIMIT 2000
SINCE 2 DAYS AGO
"]
```

137
doc/operator-reference.md Normal file
View file

@ -0,0 +1,137 @@
# SQL Operators Supported
This section lists the operators that HoneySQL supports
out of the box. There is no operator precedence assumed
because SQL expressions are represented in prefix form,
just like Clojure expressions.
Operators can be specified as keywords or symbols. Use
`-` in the operator where the formatted SQL would have
a space (e.g., `:not-like` is formatted as `NOT LIKE`).
## and, or
Boolean operators. May take any number of expressions
as arguments. `nil` expressions are ignored which can
make it easier to programmatically build conditional
expressions (since an expression that should be omitted
can simply evaluate to `nil` instead).
```clojure
{...
:where [:and [:= :type "match"]
(when need-status [:in :status [1 5]])]
...}
;; if need-status is truthy:
;;=> ["...WHERE (type = ?) AND (status IN (?, ?))..." "match" 1 5]
;; or, if need-status is falsey:
;;=> ["...WHERE (type = ?)..." "match"]
{...
:where [:or [:= :id 42] [:= :type "match"]]
...}
;;=> ["...WHERE (id = ?) OR (type = ?)..." 42 "match"]
```
## in, not-in
Predicates for checking an expression is or is not a member of a specified set of values.
The two most common forms are:
* `[:in :col [val1 val2 ...]]` or `[:not-in :col [val1 val2 ...]]` where the `valN` can be arbitrary expressions,
* `[:in :col {:select ...}]` or `[:not-in :col {:select ...}]` where the `SELECT` specifies a single column.
`:col` could be an arbitrary SQL expression (but is most
commonly just a column name).
The former produces an inline vector expression with the
values resolved as regular SQL expressions (i.e., with
literal values lifted out as parameters): `col IN [?, ?, ...]`
or `col NOT IN [?, ?, ...]`
The latter produces a sub-select, as expected: `col IN (SELECT ...)`
or `col NOT IN (SELECT ...)`
You can also specify the set of values via a named parameter:
* `[:in :col :?values]` or `[:not-in :col :?values]` where `:params {:values [1 2 ...]}` is provided to `format` in the options.
In this case, the named parameter is expanded directly when
`:in` (or `:not-in`) is formatted to obtain the sequence of values (which
must be _sequential_, not a Clojure set). That means you
cannot use this approach and also specify `:cache` -- see
[cache in All the Options](options.md#cache) for more details.
Another supported form is checking whether a tuple is in
a selected set of values that specifies a matching number
of columns, producing `(col1, col2) IN (SELECT ...)`, but
you need to specify the columns (or expressions) using the
`:composite` special syntax:
* `[:in [:composite :col1 :col2] ...]` or `[:not-in [:composite :col1 :col2] ...]`
This produces `(col1, col2) IN ...` or `(col1, col2) NOT IN ...`
> Note: This is a change from HoneySQL 1.x which accepted a sequence of column names but required more work for arbitrary expressions.
## = <>
Binary comparison operators. These expect exactly
two arguments.
`not=` and `!=` are accepted as aliases for `<>`.
## < > <= >=
Comparison operators. These expect exactly
two arguments.
## is, is-not
Predicates for `NULL` and Boolean values:
```clojure
{...
:where [:is :id nil]
...}
;;=> ["...WHERE col IS NULL..."]
{...
:where [:is-not :id nil]
...}
;;=> ["...WHERE col IS NOT NULL..."]
{...
:where [:is :col true]
...}
;;=> ["...WHERE col IS TRUE..."]
{...
;; unlike [:<> :col false], the following will include NULLs:
:where [:is-not :col false]
...}
;;=> ["...WHERE col IS NOT FALSE..."]
```
## xor, + - * / % | & ^
Mathematical and bitwise operators.
## like, not like, ilike, not ilike, regexp
Pattern matching operators. `regex` is accepted
as an alias for `regexp`.
`similar-to` and `not-similar-to` are also supported.
## with ordinality
The ANSI SQL `WITH ORDINALITY` expression is supported as an infix operator:
```clojure
{...
[:with-ordinality [:jsonb_array_elements :j] [:arr :item :index]]
...}
;;=> ["...JSONB_ARRAY_ELEMENTS(j) WITH ORDINALITY ARR(item, index)..."]
```
## ||
String concatenation operator.

183
doc/options.md Normal file
View file

@ -0,0 +1,183 @@
# All the Options
`format` accepts options as either a single hash map argument or
as named arguments (alternating keys and values). If you are using
Clojure 1.11 (or later) you can mix'n'match, providing some options
as named arguments followed by other options in a hash map.
[**Getting Started**](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started)
talked about the `:dialect`, `:params`, and `:quoted` options,
but `format` accepts a number of other options that control
how the data structure DSL is converted to a SQL string
and the associated parameters.
## Format Options
All options may be omitted. The default behavior of each option is described in the following list, with expanded details of each option in the sections that follow.
* `:cache` -- an atom containing a [clojure.core.cache](https://github.com/clojure/core.cache) cache used to cache generated SQL; the default behavior is to generate SQL on each call to `format`,
* `:checking` -- `:none` (default), `:basic`, or `:strict` to control the amount of lint-like checking that HoneySQL performs,
* `:dialect` -- a keyword that identifies a dialect to be used for this specific call to `format`; the default is to use what was specified in `set-dialect!` or `:ansi` if no other dialect has been set,
* `:ignored-metadata` -- a sequence of metadata keys that should be ignored when formatting (in addition to `:file`, `:line`, `:column`, `:end-line` and `:end-column` which are always ignored); the default is `[]` -- no additional metadata is ignored (since 2.5.1103),
* `:inline` -- a Boolean indicating whether or not to inline parameter values, rather than use `?` placeholders and a sequence of parameter values; the default is `false` -- values are not inlined,
* `:numbered` -- a Boolean indicating whether to generate numbered placeholders in the generated SQL (`$1`, `$2`, etc) or positional placeholders (`?`); the default is `false` (positional placeholders); this option was added in 2.4.962,
* `:params` -- a hash map providing values for named parameters, identified by names (keywords or symbols) that start with `?` in the DSL; the default is that any such named parameters will have `nil` values,
* `:quoted` -- a Boolean indicating whether or not to quote (strop) SQL entity names (table and column names); the default is `nil` -- alphanumeric SQL entity names are not quoted but (as of 2.3.928) "unusual" SQL entity names are quoted; a `false` value turns off all quoting,
* `:quoted-always` -- an optional regex that matches SQL entity names that should always be quoted (stropped) regardless of the value of `:quoted`; the default is `nil` -- no SQL entity names are always quoted,
* `:quoted-snake` -- a Boolean indicating whether or not quoted and string SQL entity names should have `-` replaced by `_`; the default is `false` -- quoted and string SQL entity names are left exactly as-is,
* `:values-default-columns` -- a sequence of column names that should have `DEFAULT` values instead of `NULL` values if used in a `VALUES` clause with no associated matching value in the hash maps passed in; the default behavior is for such missing columns to be given `NULL` values.
As of 2.4.947, you can call `set-options!` with an options hash map to change the
global defaults of certain options:
* `:checking` -- can be `:basic` or `:strict`; specify `:none` to reset to the default,
* `:inline` -- can be `true` but consider the security issues this causes by not using parameterized SQL statements; specify `false` (or `nil`) to reset to the default,
* `:numbered` -- can be `true` or `false`; specify `false` to reset to the default,
* `:quoted` -- can be `true` or `false`; specify `nil` to reset to the default; calling `set-dialect!` or providing a `:dialect` option to `format` will override the global default,
* `:quoted-snake` -- can be `true`; specify `false` (or `nil`) to reset to the default.
Other options may only be specified directly in calls to `format` as they are considered
per-statement, rather than global.
See below for the interaction between `:dialect` and `:quoted`.
## `:cache`
Providing a `:cache` option -- an atom containing a `core.cache` style cache data structure -- causes `format` to try to cache the
generated SQL string, based on the value of the DSL data structure.
When you use `:cache`, you should generally use named parameters
(names that start with `?`) instead of regular values.
See the [**Caching** section of the **General Reference**](https://cljdoc.org/d/com.github.seancorfield/honeysql/CURRENT/doc/getting-started/general-reference#caching)
for details.
> Note: you cannot use named parameters with `:in` when using `:cache` because `:in` "unrolls" the parameter and that will break the cache lookup rules.
Added in 2.2.858.
## `:checking`
The `:checking` option defaults to `:none`.
If `:checking :basic` is specified, certain obvious errors
are treated as an error and an exception is thrown.
If `:checking :strict` is specified, certain dubious constructs are also treated as an error and an exception is
thrown.
It is expected that this feature will be expanded over time
to help avoid generating illegal SQL.
Here are the checks for each level:
* `:basic` -- `DELETE` and `DELETE FROM` without a `WHERE` clause; `IN` with an empty collection; `SELECT` with an empty list of columns; `UPDATE` without a `WHERE` clause.
* `:strict` -- (all the `:basic` checks plus) `IN` with a collection containing `NULL` values (since this will not match rows).
## `:dialect`
If `:dialect` is provided, `:quoted` will default to `true` for this call. You can still specify `:quoted false` to turn that back off.
Valid dialects are:
* `:ansi`
* `:mysql`
* `:oracle`
* `:sqlserver`
New dialects can be created with the `register-dialect!` call.
By default, `:ansi` is the dialect used. `set-dialect!` can
set a different default dialect. The `:dialect` option only affects
the current call to `format`.
## `:inline`
The `:inline` option suppresses the generation of parameters in
the SQL string and instead tries to inline all the values directly
into the SQL string. The behavior is as if each value in the DSL
was wrapped in `[:inline `..`]`:
* `nil` becomes the SQL value `NULL`,
* Clojure strings become inline SQL strings with single quotes (so `"foo"` becomes `'foo'`),
* keywords and symbols become SQL keywords (uppercase, with `-` replaced by a space),
* everything else is just turned into a string (by calling `str`) and added to the SQL string.
> Note: you can provide additional inline formatting by extending the `InlineValue` protocol from `honey.sql.protocols` to new types.
## `:numbered`
By default, HoneySQL generates SQL using positional placeholders (`?`).
Specifying `:numbered true` tells HoneySQL to generate SQL using
numbered placeholders instead (`$1`, `$2`, etc). This can be set
globally using `set-options!`.
## `:params`
The `:params` option provides a mapping from named parameters
to values for this call to `format`. For example:
```clojure
(require '[honey.sql :as sql])
(-> {:select :* :from :table :where [:= :id :?id]}
(sql/format {:params {:id 42}}))
;;=> ["SELECT * FROM table WHERE id = ?" 42]
(-> '{select * from table where (= id ?id)}
(sql/format {:params {:id 42}}))
;;=> ["SELECT * FROM table WHERE id = ?" 42]
```
## `:quoted`
If `:quoted true`, or `:dialect` is provided (and `:quoted` is not
specified as `false`), SQL entity names that represent
tables and columns will be quoted (stropped) according to the
selected dialect.
If `:quoted false`, SQL entity names that represent tables and columns
will not be quoted. If those SQL entity names are reserved words in
SQL, the generated SQL will be invalid. You can use the `:quoted-always`
option to specify a regex, to identify SQL entity names that should
always be quoted (stropped) regardless of the value of `:quoted`, e.g.,
reserved words that you happen to use as table or column names.
The quoting (stropping) is dialect-dependent:
* `:ansi` -- uses double quotes
* `:mysql` -- uses backticks
* `:oracle` -- uses double quotes
* `:sqlserver` -- user square brackets
As of 2.3.928, if `:quoted` and `:dialect` are not provided, and no
default quoting strategy has been specified (via `set-dialect!`) then
alphanumeric SQL entity names will not be quoted but "unusual" SQL entity names will
## `:quoted-snake`
Where strings are used to identify table or column names, they are
treated as-is. If `:quoted true` (or a `:dialect` is specified),
those SQL entity names are quoted as-is.
Where keywords or symbols are used to identify table or column
names, and `:quoted true` is provided, those SQL entity names are
quoted as-is.
If `:quoted-snake true` is provided, those SQL entity names are quoted
but any `-` in them are replaced by `_` -- that replacement is the
default in unquoted SQL entity names.
This allows quoting to be used but still maintain the Clojure
(kebab case) to SQL (snake case) mappings.
## `:values-default-columns`
This option determines the behavior of the `:values` clause, when
column values are missing from one or more of the hash maps passed
in.
By default, missing column values are replaced with `NULL` in the
generated SQL. `:values-default-columns` can specify a set of
column names that should instead be given the value `DEFAULT` if
their column value is missing from one or more hash maps.
That in turn should cause their declared default value to be used
(from the column definition in the table) and is useful for
situations where `NULL` is not an appropriate default for a missing
column value.
Added in 2.1.818.

556
doc/postgresql.md Normal file
View file

@ -0,0 +1,556 @@
# PostgreSQL Support
This section covers the PostgreSQL-specific
features that HoneySQL supports out of the box
for which you previously needed the
[nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres)
library.
Everything that the nilenso library provided (in 0.4.112) is implemented
directly in HoneySQL 2.x although a few things have a
slightly different syntax.
If you are using HoneySQL with the Node.js PostgreSQL driver, it
only accepts numbered placeholders, not positional placeholders,
so you will need to specify the `:numbered true` option that was
added in 2.4.962. You may find it convenient to set this option
globally, via `set-options!`.
## Code Examples
The code examples herein assume:
```clojure
(refer-clojure :exclude '[update set])
(require '[honey.sql :as sql]
'[honey.sql.helpers :refer [select from where
update set
insert-into values
create-table with-columns create-view create-extension
add-column alter-table add-index
alter-column rename-column rename-table
drop-table drop-column drop-index drop-extension
upsert returning on-conflict on-constraint
do-update-set do-nothing]])
```
Clojure users can opt for the shorter `(require '[honey.sql :as sql] '[honey.sql.helpers :refer :all])` but this syntax is not available to ClojureScript users.
## Working with Arrays
HoneySQL supports `:array` as special syntax to produce `ARRAY[..]` expressions:
```clojure
user=> (sql/format {:select [[[:array [1 2 3]] :a]]})
["SELECT ARRAY[?, ?, ?] AS a" 1 2 3]
```
PostgreSQL also has an "array constructor" for creating arrays from subquery results.
```sql
SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%');
```
As of 2.5.1091, HoneySQL supports this syntax directly:
```clojure
user=> (sql/format {:select [[[:array {:select :oid :from :pg_proc :where [:like :proname [:inline "bytea%"]]}]]]})
["SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%')"]
```
Prior to 2.5.1091, you had to use HoneySQL's "as-is" function syntax to circumvent
the special syntax:
```clojure
user=> (sql/format {:select [[[:'ARRAY {:select :oid :from :pg_proc :where [:like :proname [:inline "bytea%"]]}]]]})
["SELECT ARRAY (SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%')"]
```
## Operators with @, #, and ~
A number of PostgreSQL operators contain `@`, `#`, or `~` which are not legal in a Clojure keyword or symbol (as literal syntax). The namespace `honey.sql.pg-ops` provides convenient symbolic names for these JSON and regex operators, substituting `at` for `@`, `hash` for `#`, and `tilde` for `~`.
The regex operators also have more memorable aliases: `regex` for `~`, `iregex` for `~*`, `!regex` for `!~`, and `!iregex` for `!~*`.
Requiring the namespace automatically registers these operators for use in expressions:
```clojure
user=> (require '[honey.sql.pg-ops :refer [regex]])
nil
user=> (sql/format {:select [[[regex :straw [:inline "needle"]] :match]] :from :haystack})
["SELECT straw ~ 'needle' AS match FROM haystack"]
```
## JSON/JSONB
If you are using JSON with PostgreSQL, you will probably try to pass Clojure
data structures as values into your HoneySQL DSL -- but HoneySQL will see those
vectors as function calls and hash maps as SQL statements, so you need to tell
HoneySQL not to do that. There are two possible approaches:
1. Use named parameters (e.g., `[:param :myval]`) instead of having the values directly in the DSL structure and then pass `{:params {:myval some-json}}` as part of the options in the call to `format`, or
2. Use `[:lift ..]` wrapped around any structured values which tells HoneySQL not to interpret the vector or hash map value as a DSL: `[:lift some-json]`.
## Upsert
Upserting data is relatively easy in PostgreSQL
because of the `ON CONFLICT`, `ON CONSTRAINT`,
`DO NOTHING`, and `DO UPDATE SET` parts of the
`INSERT` statement.
This usage is supported identically to the nilenso library:
```clojure
user=> (-> (insert-into :distributors)
(values [{:did 5 :dname "Gizmo Transglobal"}
{:did 6 :dname "Associated Computing, Inc"}])
(upsert (-> (on-conflict :did)
(do-update-set :dname)))
(returning :*)
(sql/format {:pretty true}))
["
INSERT INTO distributors (did, dname)
VALUES (?, ?), (?, ?)
ON CONFLICT (did)
DO UPDATE SET dname = EXCLUDED.dname
RETURNING *
"
5 "Gizmo Transglobal"
6 "Associated Computing, Inc"]
```
However, the nested `upsert` helper is no longer needed
(and there is no corresponding `:upsert` clause in the DSL):
```clojure
user=> (-> (insert-into :distributors)
(values [{:did 5 :dname "Gizmo Transglobal"}
{:did 6 :dname "Associated Computing, Inc"}])
(on-conflict :did)
(do-update-set :dname)
(returning :*)
(sql/format {:pretty true}))
["
INSERT INTO distributors (did, dname)
VALUES (?, ?), (?, ?)
ON CONFLICT (did)
DO UPDATE SET dname = EXCLUDED.dname
RETURNING *
"
5 "Gizmo Transglobal"
6 "Associated Computing, Inc"]
```
Similarly, the `do-nothing` helper behaves just the same
as in the nilenso library:
```clojure
user=> (-> (insert-into :distributors)
(values [{:did 7 :dname "Redline GmbH"}])
(upsert (-> (on-conflict :did)
do-nothing))
(sql/format {:pretty true}))
["
INSERT INTO distributors (did, dname)
VALUES (?, ?)
ON CONFLICT (did)
DO NOTHING
"
7 "Redline GmbH"]
```
As above, the nested `upsert` helper is no longer needed:
```clojure
user=> (-> (insert-into :distributors)
(values [{:did 7 :dname "Redline GmbH"}])
(on-conflict :did)
do-nothing
(sql/format {:pretty true}))
["
INSERT INTO distributors (did, dname)
VALUES (?, ?)
ON CONFLICT (did)
DO NOTHING
"
7 "Redline GmbH"]
```
`ON CONSTRAINT` is handled slightly differently to the nilenso library,
which provided a single `on-conflict-constraint` helper (and clause):
```clojure
user=> (-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
;; can specify as a nested clause...
(on-conflict (on-constraint :distributors_pkey))
do-nothing
(sql/format {:pretty true}))
["
INSERT INTO distributors (did, dname)
VALUES (?, ?)
ON CONFLICT ON CONSTRAINT distributors_pkey
DO NOTHING
"
9 "Antwerp Design"]
user=> (-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
;; ...or as two separate clauses
on-conflict
(on-constraint :distributors_pkey)
do-nothing
(sql/format {:pretty true}))
["
INSERT INTO distributors (did, dname)
VALUES (?, ?)
ON CONFLICT
ON CONSTRAINT distributors_pkey
DO NOTHING
"
9 "Antwerp Design"]
```
As above, the `upsert` helper has been omitted here.
An upsert with where clauses is also possible, with a
more compact syntax than the nilenso library used:
```clojure
user=> (-> (insert-into :user)
(values [{:phone "5555555" :name "John"}])
(on-conflict :phone (where [:<> :phone nil]))
(do-update-set :phone :name (where [:= :user.active false]))
(sql/format {:pretty true}))
["
INSERT INTO user (phone, name)
VALUES (?, ?)
ON CONFLICT (phone) WHERE phone IS NOT NULL
DO UPDATE SET phone = EXCLUDED.phone, name = EXCLUDED.name WHERE user.active = FALSE
"
"5555555" "John"]
;; using the DSL directly:
user=> (sql/format
{:insert-into :user
:values [{:phone "5555555" :name "John"}]
:on-conflict [:phone
{:where [:<> :phone nil]}]
:do-update-set {:fields [:phone :name]
:where [:= :user.active false]}}
{:pretty true})
["
INSERT INTO user (phone, name)
VALUES (?, ?)
ON CONFLICT (phone) WHERE phone IS NOT NULL
DO UPDATE SET phone = EXCLUDED.phone, name = EXCLUDED.name WHERE user.active = FALSE
"
"5555555" "John"]
```
By comparison, this is the DSL structure that nilenso would have required:
<!-- :test-doc-blocks/skip -->
```clojure
;; NOT VALID FOR HONEYSQL!
{:insert-into :user
:values [{:phone "5555555" :name "John"}]
;; nested under :upsert
:upsert {:on-conflict [:phone]
;; but :where is at the same level as :on-conflict
:where [:<> :phone nil]
;; this is the same as in honeysql:
:do-update-set {:fields [:phone :name]
:where [:= :user.active false]}}}
```
All of the examples for `:do-update-set` so far provide one or
more columns and generated `SET` clauses using `EXCLUDED` columns.
You can also perform regular `SET` operations, where the right-hand
side is a full SQL expression by specifying a hash map of column /
expression pairs, like you would for a regular `:set` clause:
```clojure
user=> (-> (insert-into :table)
(values [{:id "id" :counter 1}])
(on-conflict :id)
(do-update-set {:counter [:+ :table.counter 1]})
(sql/format {:pretty true}))
["
INSERT INTO table (id, counter)
VALUES (?, ?)
ON CONFLICT (id)
DO UPDATE SET counter = table.counter + ?
" "id" 1 1]
;; using the DSL directly:
user=> (-> {:insert-into :table
:values [{:id "id" :counter 1}]
:on-conflict :id
:do-update-set {:counter [:+ :table.counter 1]}}
(sql/format {:pretty true}))
["
INSERT INTO table (id, counter)
VALUES (?, ?)
ON CONFLICT (id)
DO UPDATE SET counter = table.counter + ?
" "id" 1 1]
```
You can use `:EXCLUDED.column` in a hash map to produce the
same effect as `:column` in a vector:
```clojure
user=> (-> (insert-into :table)
(values [{:id "id" :counter 1}])
(on-conflict :id)
(do-update-set {:name :EXCLUDED.name
:counter [:+ :table.counter 1]})
(sql/format {:pretty true}))
["
INSERT INTO table (id, counter)
VALUES (?, ?)
ON CONFLICT (id)
DO UPDATE SET name = EXCLUDED.name, counter = table.counter + ?
" "id" 1 1]
```
If you need to combine a `DO UPDATE SET` hash map expression
with a `WHERE` clause, you need to explicitly use the `:fields` /
`:where` format explained above. Here's how those two examples
look with a `WHERE` clause added:
```clojure
user=> (-> (insert-into :table)
(values [{:id "id" :counter 1}])
(on-conflict :id)
(do-update-set {:fields {:counter [:+ :table.counter 1]}
:where [:> :table.counter 1]})
(sql/format {:pretty true}))
["
INSERT INTO table (id, counter)
VALUES (?, ?)
ON CONFLICT (id)
DO UPDATE SET counter = table.counter + ? WHERE table.counter > ?
" "id" 1 1 1]
;; using the DSL directly:
user=> (-> {:insert-into :table
:values [{:id "id" :counter 1}]
:on-conflict :id
:do-update-set {:fields {:counter [:+ :table.counter 1]}
:where [:> :table.counter 1]}}
(sql/format {:pretty true}))
["
INSERT INTO table (id, counter)
VALUES (?, ?)
ON CONFLICT (id)
DO UPDATE SET counter = table.counter + ? WHERE table.counter > ?
" "id" 1 1 1]
```
## INSERT INTO AS
HoneySQL supports aliases directly in `:insert-into` so no special
clause is needed for this any more:
```clojure
user=> (sql/format (-> (insert-into :table :alias)
(values [[1 2 3] [4 5 6]])))
["INSERT INTO table AS alias VALUES (?, ?, ?), (?, ?, ?)" 1 2 3 4 5 6]
user=> (sql/format {:insert-into [:table :alias],
:values [[1 2 3] [4 5 6]]})
["INSERT INTO table AS alias VALUES (?, ?, ?), (?, ?, ?)" 1 2 3 4 5 6]
```
## Returning
The `RETURNING` clause is supported identically to the nilenso library:
```clojure
;; via the DSL:
user=> (sql/format {:delete-from :distributors
:where [:> :did 10]
:returning [:*]})
["DELETE FROM distributors WHERE did > ? RETURNING *" 10]
;; via the helpers:
user=> (-> (update :distributors)
(set {:dname "Foo Bar Designs"})
(where [:= :did 2])
(returning :did :dname)
sql/format)
["UPDATE distributors SET dname = ? WHERE did = ? RETURNING did, dname"
"Foo Bar Designs" 2]
```
## DDL Support
The following DDL statements are all supported by HoneySQL
(these are mostly not PostgreSQL-specific but they were not
supported by HoneySQL 1.x):
* `CREATE VIEW`
* `CREATE TABLE`
* `DROP TABLE`
* `ALTER TABLE`
These are mostly identical to what the nilenso library provides
except that `sql/call` is never needed -- you can use the direct
`[:func ..]` function call syntax instead:
```clojure
;; create view:
user=> (-> (create-view :metro)
(select :*)
(from :cities)
(where [:= :metroflag "Y"])
sql/format)
["CREATE VIEW metro AS SELECT * FROM cities WHERE metroflag = ?" "Y"]
;; create table:
user=> (-> (create-table :cities)
(with-columns [[:city [:varchar 80] [:primary-key]]
[:location :point]])
sql/format)
;; values are inlined:
["CREATE TABLE cities (city VARCHAR(80) PRIMARY KEY, location POINT)"]
;; default values for columns:
user=> (-> (create-table :distributors)
(with-columns [[:did :integer [:primary-key]
;; "serial" is inlined as 'serial':
[:default [:nextval "serial"]]]
[:name [:varchar 40] [:not nil]]])
(sql/format {:pretty true}))
;; newlines inserted for readability:
["
CREATE TABLE distributors
(did INTEGER PRIMARY KEY DEFAULT NEXTVAL('serial'), name VARCHAR(40) NOT NULL)
"]
;; PostgreSQL CHECK constraint is supported:
user=> (-> (create-table :products)
(with-columns [[:product_no :integer]
[:name :text]
[:price :numeric [:check [:> :price 0]]]
[:discounted_price :numeric]
[[:check [:and [:> :discounted_price 0] [:> :price :discounted_price]]]]])
(sql/format {:pretty true}))
["
CREATE TABLE products
(product_no INTEGER, name TEXT, price NUMERIC CHECK(price > 0), discounted_price NUMERIC, CHECK((discounted_price > 0) AND (price > discounted_price)))
"]
;; conditional creation:
user=> (-> (create-table :products :if-not-exists)
(with-columns [[:name :text]])
sql/format)
["CREATE TABLE IF NOT EXISTS products (name TEXT)"]
;; drop table:
user=> (sql/format (drop-table :cities))
["DROP TABLE cities"]
;; drop multiple tables:
user=> (sql/format (drop-table :cities :towns :vilages))
["DROP TABLE cities, towns, vilages"]
;; conditional drop:
user=> (sql/format (drop-table :if-exists :cities :towns :vilages))
["DROP TABLE IF EXISTS cities, towns, vilages"]
;; alter table add column:
user=> (-> (alter-table :fruit)
(add-column :skin [:varchar 16] nil)
sql/format)
["ALTER TABLE fruit ADD COLUMN skin VARCHAR(16) NULL"]
;; alter table drop column:
user=> (-> (alter-table :fruit)
(drop-column :skin)
sql/format)
["ALTER TABLE fruit DROP COLUMN skin"]
;; alter table rename column:
user=> (-> (alter-table :fruit)
(rename-column :cost :price)
sql/format)
["ALTER TABLE fruit RENAME COLUMN cost TO price"]
;; rename table:
user=> (-> (alter-table :fruit)
(rename-table :vegetable)
sql/format)
["ALTER TABLE fruit RENAME TO vegetable"]
```
The following does not work for PostgreSQL, but does work for several other databases:
```clojure
;; alter table alter column:
user=> (-> (alter-table :fruit)
(alter-column :name [:varchar 64] [:not nil])
sql/format)
["ALTER TABLE fruit ALTER COLUMN name VARCHAR(64) NOT NULL"]
```
For PostgreSQL, you need separate statements:
```clojure
user=> (-> (alter-table :fruit)
(alter-column :name :type [:varchar 64])
sql/format)
["ALTER TABLE fruit ALTER COLUMN name TYPE VARCHAR(64)"]
user=> (-> (alter-table :fruit)
(alter-column :name :set [:not nil])
sql/format)
["ALTER TABLE fruit ALTER COLUMN name SET NOT NULL"]
```
The following PostgreSQL-specific DDL statements are supported
(with the same syntax as the nilenso library but `sql/format`
takes slightly different options):
```clojure
;; create extension:
user=> (-> (create-extension :uuid-ossp)
(sql/format {:quoted true}))
;; quoting is required for a name containing a hyphen:
["CREATE EXTENSION \"uuid-ossp\""]
;; conditional creation:
user=> (-> (create-extension :uuid-ossp :if-not-exists)
(sql/format {:quoted true}))
["CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\""]
;; drop extension:
user=> (-> (drop-extension :uuid-ossp)
(sql/format {:quoted true}))
["DROP EXTENSION \"uuid-ossp\""]
;; drop multiple extensions:
user=> (-> (drop-extension :uuid-ossp :postgis)
(sql/format {:quoted true}))
["DROP EXTENSION \"uuid-ossp\", \"postgis\""]
;; conditional drop:
user=> (-> (drop-extension :if-exists :uuid-ossp :postgis)
(sql/format {:quoted true}))
["DROP EXTENSION IF EXISTS \"uuid-ossp\", \"postgis\""]
```
In addition, HoneySQL supports these DDL statements that were
not supported by the nilenso library:
```clojure
;; alter table add index:
user=> (-> (alter-table :fruit)
(add-index :unique :fruit-name :name)
sql/format)
["ALTER TABLE fruit ADD UNIQUE fruit_name(name)"]
;; alter table drop index:
user=> (-> (alter-table :fruit)
(drop-index :fruit-name)
sql/format)
["ALTER TABLE fruit DROP INDEX fruit_name"]
;; alter table with multiple clauses:
user=> (sql/format (alter-table :fruit
(add-column :skin [:varchar 16] nil)
(add-index :unique :fruit-name :name)))
["ALTER TABLE fruit ADD COLUMN skin VARCHAR(16) NULL, ADD UNIQUE fruit_name(name)"]
```
## Filter / Within Group
`honeysql-postgres` added support for `FILTER` and `WITHIN GROUP`
in its 0.4.112 release. Those features have been integrated into
HoneySQL 2.x (as of 2.0.0-beta2), along with support for `ORDER BY`
in expressions. `:filter`, `:within-group`, and `:order-by` are
all available as "functions" in [Special Syntax](special-syntax.md),
and there are helpers for `filter` and `within-group`.
## Window / Partition Support
HoneySQL supports `:window`, `:partition-by`, and `:over`
directly now.
See the Clause Reference for examples of [WINDOW, PARTITION BY, and OVER](clause-reference.md#window-partition-by-and-over).

630
doc/special-syntax.md Normal file
View file

@ -0,0 +1,630 @@
# SQL Special Syntax
This section lists the function-like expressions that
HoneySQL supports out of the box which are formatted
as special syntactic forms.
The first group are used for SQL expressions. The second (last group) are used primarily in column definitions (as part of `:with-columns` and `:add-column` / `:alter-column`).
The examples in this section assume the following:
```clojure
(require '[honey.sql :as sql])
```
## alias
Accepts a single argument which should be an alias name (from an `AS` clause
elsewhere in the overall SQL statement) and uses alias formatting rules rather
than table/column formatting rules (different handling of dots and hyphens).
This allows you to override HoneySQL's default assumption about entity names
and strings.
```clojure
(sql/format {:select [[:column-name "some-alias"]]
: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
: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
Accepts either an expression (that evaluates to a sequence) or a subquery
(hash map). In the expression case, also accepts an optional second argument
that specifies the type of the array.
Produces either an `ARRAY[..]` or an `ARRAY(subquery)` expression.
In the expression case, produces `ARRAY[?, ?, ..]` for the elements of that
sequence (as SQL parameters):
```clojure
(sql/format-expr [:array (range 5)])
;;=> ["ARRAY[?, ?, ?, ?, ?]" 0 1 2 3 4]
(sql/format-expr [:array (range 3) :text])
;;=> ["ARRAY[?, ?, ?]::TEXT[]" 0 1 2]
(sql/format-expr [:array [] :integer])
;;=> ["ARRAY[]::INTEGER[]"]
```
> Note: you cannot provide a named parameter as the argument for `:array` because the generated SQL depends on the number of elements in the sequence, so the following throws an exception:
<!-- :test-doc-blocks/skip -->
```clojure
(sql/format {:select [[[:array :?tags] :arr]]} {:params {:tags [1 2 3]}})
```
You can do the following instead:
```clojure
(let [tags [1 2 3]]
(sql/format {:select [[[:array tags] :arr]]} {:inline true}))
;;=> ["SELECT ARRAY[1, 2, 3] AS arr"]
```
In addition, the argument to `:array` is treated as a literal sequence of Clojure values and is **not** interpreted as a HoneySQL expression, so you must use the `{:inline true}` formatting option as shown above rather than try to inline the values like this:
```clojure
(sql/format {:select [[[:array [:inline [1 2 3]]] :arr]]})
;;=> ["SELECT ARRAY[inline, (?, ?, ?)] AS arr" 1 2 3]
```
In the subquery case, produces `ARRAY(subquery)`:
```clojure
(sql/format {:select [[[:array {:select :* :from :table}] :arr]]})
;;=> ["SELECT ARRAY(SELECT * FROM table) AS arr"]
```
## at
If addition to dot navigation (for JSON) -- see the `.` and `.:.` syntax below --
HoneySQL also supports bracket notation for JSON navigation.
The first argument to `:at` is treated as an expression that identifies
the column, and subsequent arguments are treated as field names or array
indices to navigate into that document.
```clojure
user=> (sql/format {:select [[[:at :col :field1 :field2]]]})
["SELECT col.field1.field2"]
user=> (sql/format {:select [[[:at :table.col 0 :field]]]})
["SELECT table.col[0].field"]
```
If you want an array index to be a parameter, use `:lift`:
```clojure
user=> (sql/format {:select [[[:at :col [:lift 0] :field]]]})
["SELECT col[?].field" 0]
```
## at time zone
Accepts two arguments: an expression (assumed to be a date/time of some sort)
and a time zone name or identifier (can be a string, a symbol, or a keyword):
```clojure
(sql/format-expr [:at-time-zone [:now] :UTC])
;;=> ["NOW() AT TIME ZONE 'UTC'"]
```
The time zone name or identifier will be inlined (as a string) and therefore
cannot be an expression.
## between and not-between
Accepts three arguments: an expression, a lower bound, and
an upper bound:
```clojure
(sql/format-expr [:between :id 1 100])
;;=> ["id BETWEEN ? AND ?" 1 100]
(sql/format-expr [:not-between :id 1 100])
;;=> ["id NOT BETWEEN ? AND ?" 1 100]
```
## case
A SQL CASE expression. Expects an even number of arguments:
alternating condition and result expressions. A condition
may be `:else` (or `'else`) to produce `ELSE`, otherwise
`WHEN <condition> THEN <result>` will be produced:
```clojure
(sql/format-expr [:case [:< :a 10] "small" [:> :a 100] "big" :else "medium"])
;; => ["CASE WHEN a < ? THEN ? WHEN a > ? THEN ? ELSE ? END" 10 "small" 100 "big" "medium"]
```
Google BigQuery supports a variant of `CASE` that takes an expression and then the `WHEN`
clauses contain expressions to match against, rather than conditions. HoneySQL supports
this using `:case-expr`:
```clojure
(sql/format-expr [:case-expr :a 10 "small" 100 "big" :else "medium"])
;; => ["CASE a WHEN ? THEN ? WHEN ? THEN ? ELSE ? END" 10 "small" 100 "big" "medium"]
```
## cast
A SQL `CAST` expression. Expects an expression and something
that produces a SQL type:
```clojure
(sql/format [:cast :a :int])
;;=> ["CAST(a AS INT)"]
```
Quoting does not affect the type in a `CAST`, only the expression:
```clojure
(sql/format [:cast :a :int] {:quoted true})
;;=> ["CAST(\"a\" AS INT)"]
```
A hyphen (`-`) in the type name becomes a space:
```clojure
(sql/format [:cast :a :double-precision])
;;=> ["CAST(a AS DOUBLE PRECISION)"]
```
If you want an underscore in the type name, you have two choices:
```clojure
(sql/format [:cast :a :some_type])
;;=> ["CAST(a AS SOME_TYPE)"]
```
or:
```clojure
(sql/format [:cast :a :'some-type])
;;=> ["CAST(a AS some_type)"]
```
> Note: In HoneySQL 2.4.947 and earlier, the type name was incorrectly affected by the quoting feature, and a hyphen in a type name was incorrectly changed to underscore. This was corrected in 2.4.962.
## composite
Accepts any number of expressions and produces a composite
expression (comma-separated, wrapped in parentheses):
```clojure
(sql/format-expr [:composite :a :b "red" [:+ :x 1]])
;;=> ["(a, b, ?, x + ?)" "red" 1]
```
This can be useful in a number of situations where you want a composite
value, as above, or a composite based on or declaring columns names:
```clojure
(sql/format {:select [[[:composite :a :b] :c]] :from :table})
;;=> ["SELECT (a, b) AS c FROM table"]
```
```clojure
(sql/format {:update :table :set {:a :v.a}
:from [[{:values [[1 2 3]
[4 5 6]]}
[:v [:composite :a :b :c]]]]
:where [:and [:= :x :v.b] [:> :y :v.c]]})
;;=> ["UPDATE table SET a = v.a FROM (VALUES (?, ?, ?), (?, ?, ?)) AS v (a, b, c) WHERE (x = v.b) AND (y > v.c)" 1 2 3 4 5 6]
```
## distinct
Accepts a single expression and prefixes it with `DISTINCT `:
```clojure
(sql/format {:select [ [[:count [:distinct :status]] :n] ] :from :table})
;;=> ["SELECT COUNT(DISTINCT status) AS n FROM table"]
```
## dot . .:.
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
(sql/format {:select [ [[:. [:nest :v] :*]] [[:. [:nest [:myfunc :x]] :y]] ]})
;;=> ["SELECT (v).*, (MYFUNC(x)).y"]
```
See also [`get-in`](xtdb.md#object-navigation-expressions)
and [`at`](#at) for additional path navigation functions.
## entity
Accepts a single keyword or symbol argument and produces a
SQL entity. This is intended for use in contexts that would
otherwise produce a sequence of SQL keywords, such as when
constructing DDL statements.
<!-- :test-doc-blocks/skip -->
```clojure
[:tablespace :quux]
;;=> TABLESPACE QUUX
[:tablespace [:entity :quux]]
;;=> TABLESPACE quux
```
## escape
Intended to be used with regular expression patterns to
specify the escape characters (if any).
```clojure
(sql/format {:select :* :from :foo
:where [:similar-to :foo [:escape "bar" [:inline "*"]]]})
;;=> ["SELECT * FROM foo WHERE foo SIMILAR TO ? ESCAPE '*'" "bar"]
```
## filter, within-group
Used to produce PostgreSQL's `FILTER` and `WITHIN GROUP` expressions.
See also **order-by** below.
These both accept a SQL expression followed by a SQL clause.
Filter generally expects an aggregate expression and a `WHERE` clause.
Within group generally expects an aggregate expression and an `ORDER BY` clause.
```clojure
(sql/format {:select [:a :b [[:filter :%count.* {:where [:< :x 100]}] :c]
[[:within-group [:percentile_disc [:inline 0.25]]
{:order-by [:a]}] :inter_max]
[[:within-group [:percentile_cont [:inline 0.25]]
{:order-by [:a]}] :abs_max]]
:from :aa}
{:pretty true})
;;=> ["
SELECT a, b, COUNT(*) FILTER (WHERE x < ?) AS c, PERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY a ASC) AS inter_max, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY a ASC) AS abs_max
FROM aa
"
100]
```
There are helpers for both `filter` and `within-group`. Be careful with `filter`
since it shadows `clojure.core/filter`:
```clojure
(refer-clojure :exclude '[filter])
(require '[honey.sql.helpers :refer [select filter within-group from order-by where]])
(sql/format (-> (select :a :b [(filter :%count.* (where :< :x 100)) :c]
[(within-group [:percentile_disc [:inline 0.25]]
(order-by :a)) :inter_max]
[(within-group [:percentile_cont [:inline 0.25]]
(order-by :a)) :abs_max])
(from :aa))
{:pretty true})
;;=> ["
SELECT a, b, COUNT(*) FILTER (WHERE x < ?) AS c, PERCENTILE_DISC(0.25) WITHIN GROUP (ORDER BY a ASC) AS inter_max, PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY a ASC) AS abs_max
FROM aa
"
100]
```
## ignore/respect nulls
Both of these accept a single argument -- an expression -- and
renders that expression followed by `IGNORE NULLS` or `RESPECT NULLS`:
```clojure
(sql/format-expr [:array_agg [:ignore-nulls :a]])
;;=> ["ARRAY_AGG(a IGNORE NULLS)"]
(sql/format-expr [:array_agg [:respect-nulls :a]])
;;=> ["ARRAY_AGG(a RESPECT NULLS)"]
```
## inline
Accepts one or more arguments and tries to render them as a
SQL values directly in the formatted SQL string rather
than turning it into a positional parameter:
* `nil` becomes `NULL`
* keywords and symbols become upper case entities (with `-` replaced by space)
* strings become inline SQL strings (with single quotes)
* a sequence has each element formatted inline and then joined with spaces
* all other values are just rendered via Clojure's `str` function
```clojure
(sql/format {:where [:= :x [:inline "foo"]]})
;;=> ["WHERE x = 'foo'"]
```
If multiple arguments are provided, they are individually formatted as above
and joined into a single SQL string with spaces:
```clojure
(sql/format {:where [:= :x [:inline :DATE "2019-01-01"]]})
;;=> ["WHERE x = DATE '2019-01-01'"]
```
This is convenient for rendering DATE/TIME/TIMESTAMP literals in SQL.
If an argument is an expression, it is formatted as a regular SQL expression
except that any parameters are inlined:
```clojure
(sql/format {:where [:= :x [:inline [:date_add [:now] [:interval 30 :days]]]]})
;;=> ["WHERE x = DATE_ADD(NOW(), INTERVAL 30 DAYS)"]
```
In particular, that means that you can use `:inline` to inline a parameter
value:
```clojure
(sql/format {:where [:= :x [:inline :?foo]]} {:params {:foo "bar"}})
;;=> ["WHERE x = 'bar'"]
(sql/format {:where [:= :x [:inline [:param :foo]]]} {:params {:foo "bar"}})
;;=> ["WHERE x = 'bar'"]
```
## interval
Accepts one or two arguments: either a string or an expression and
a keyword (or a symbol) that represents a time unit.
Produces an `INTERVAL` expression:
```clojure
(sql/format-expr [:date_add [:now] [:interval 30 :days]])
;;=> ["DATE_ADD(NOW(), INTERVAL ? DAYS)" 30]
(sql/format-expr [:date_add [:now] [:interval "24 Hours"]])
;;=> ["DATE_ADD(NOW(), INTERVAL '24 Hours')"]
```
> Note: PostgreSQL also has an `INTERVAL` data type which is unrelated to this syntax. In PostgreSQL, the closet equivalent would be `[:cast "30 days" :interval]` which will lift `"30 days"` out as a parameter. In DDL, for PostgreSQL, you can use `:interval` to produce the `INTERVAL` data type (without wrapping it in a vector).
## join
Accepts a table name (or expression) followed by one or more join clauses.
Produces a nested `JOIN` expression, typically used as the table expression of
a `JOIN` clause.
```clojure
(sql/format {:join [[[:join :tbl1 {:left-join [:tbl2 [:using :id]]}]]]})
;;=> ["INNER JOIN (tbl1 LEFT JOIN tbl2 USING (id))"]
```
An alias can be provided:
```clojure
(sql/format {:join [[[:join [:tbl1 :t] {:left-join [:tbl2 [:using :id]]}]]]})
;;=> ["INNER JOIN (tbl1 AS t LEFT JOIN tbl2 USING (id))"]
```
To provide an expression, an extra level of `[...]` is needed:
```clojure
(sql/format {:join [[[:join [[:make_thing 42] :t] {:left-join [:tbl2 [:using :id]]}]]]})
;;=> ["INNER JOIN (MAKE_THING(?) AS t LEFT JOIN tbl2 USING (id))" 42]
```
## lateral
Accepts a single argument that can be a (`SELECT`) clause or
a (function call) expression. Produces a `LATERAL` subquery
clause based on the `SELECT` clause or the SQL expression.
## lift
Used to wrap a Clojure value that should be passed as a
SQL parameter but would otherwise be treated as a SQL
expression or statement, i.e., a sequence or hash map.
This can be useful when dealing with JSON types:
```clojure
(sql/format {:where [:= :json-col [:lift {:a 1 :b "two"}]]})
;;=> ["WHERE json_col = ?" {:a 1 :b "two"}]
```
> Note: HoneySQL 1.x used `honeysql.format/value` for this.
## nest
Used to wrap an expression when you want an extra
level of parentheses around it:
```clojure
(sql/format {:where [:= :x 42]})
;;=> ["WHERE x = ?" 42]
(sql/format {:where [:nest [:= :x 42]]})
;;=> ["WHERE (x = ?)" 42]
```
`:nest` is also supported as a SQL clause for the same reason.
```clojure
;; BigQuery requires UNION clauses be parenthesized:
(sql/format {:union-all [{:nest {:select :*}} {:nest {:select :*}}]})
;;=> ["(SELECT *) UNION ALL (SELECT *)"]
```
## not
Accepts a single expression and formats it with `NOT`
in front of it:
```clojure
(sql/format-expr [:not nil])
;;=> ["NOT NULL"]
(sql/format-expr [:not [:= :x 42]])
;;=> ["NOT (x = ?)" 42]
```
## order-by
In addition to the `ORDER BY` clause, HoneySQL also supports `ORDER BY`
in an expression (for PostgreSQL). It accepts a SQL expression followed
by an ordering specifier, which can be an expression or a pair of expression
and direction (`:asc` or `:desc`):
```clojure
(sql/format {:select [[[:array_agg [:order-by :a [:b :desc]]]]] :from :table})
;;=> ["SELECT ARRAY_AGG(a ORDER BY b DESC) FROM table"]
(sql/format (-> (select [[:array_agg [:order-by :a [:b :desc]]]])
(from :table)))
;;=> ["SELECT ARRAY_AGG(a ORDER BY b DESC) FROM table"]
(sql/format {:select [[[:string_agg :a [:order-by [:inline ","] :a]]]] :from :table})
;;=> ["SELECT STRING_AGG(a, ',' ORDER BY a ASC) FROM table"]
(sql/format (-> (select [[:string_agg :a [:order-by [:inline ","] :a]]])
(from :table)))
;;=> ["SELECT STRING_AGG(a, ',' ORDER BY a ASC) FROM table"]
```
There is no helper for the `ORDER BY` special syntax: the `order-by` helper
only produces a SQL clause.
## over
This is intended to be used with the `:window` and `:partition-by` clauses.
`:over` takes any number of window expressions which are either pairs or triples
that have an aggregation expression, a window function, and an optional alias.
The window function may either be a SQL entity (named in a `:window` clause)
or a SQL clause that describes the window (e.g., using `:partition-by` and/or `:order-by`).
Since a function call (using `:over`) needs to be wrapped in a sequence for a
`:select` clause, it is usually easier to use the `over` helper function
to construct this expression.
## param
Used to identify a named parameter in a SQL expression
as an alternative to a keyword (or a symbol) that begins
with `?`:
```clojure
(sql/format {:where [:= :x :?foo]} {:params {:foo 42}})
;;=> ["WHERE x = ?" 42]
(sql/format {:where [:= :x [:param :foo]]} {:params {:foo 42}})
;;=> ["WHERE x = ?" 42]
```
## raw
Accepts a single argument and renders it as literal SQL
in the formatted string:
```clojure
(sql/format {:select [:a [[:raw "@var := foo"]]]})
;;=> ["SELECT a, @var := foo"]
```
If the argument is a sequence of expressions, they
will each be rendered literally and joined together
(with no spaces):
```clojure
(sql/format {:select [:a [[:raw ["@var" " := " "foo"]]]]})
;;=> ["SELECT a, @var := foo"]
```
When a sequence of expressions is supplied, any
subexpressions that are, in turn, sequences will be
formatted as regular SQL expressions and that SQL
will be joined into the result, along with any
parameters from them:
```clojure
(sql/format {:select [:a [[:raw ["@var := " [:inline "foo"]]]]]})
;;=> ["SELECT a, @var := 'foo'"]
(sql/format {:select [:a [[:raw ["@var := " ["foo"]]]]]})
;;=> ["SELECT a, @var := (?)" "foo"]
;; when multiple expressions are provided, the enclosing
;; vector can be omitted:
(sql/format {:select [:a [[:raw "@var := " [:inline "foo"]]]]})
;;=> ["SELECT a, @var := 'foo'"]
(sql/format {:select [:a [[:raw "@var := " ["foo"]]]]})
;;=> ["SELECT a, @var := (?)" "foo"]
```
`:raw` is also supported as a SQL clause for the same reason.
## Column Descriptors
There are three types of descriptors that vary
in how they treat their first argument. All three
descriptors automatically try to inline any parameters
(and will throw an exception if they can't, since these
descriptors are meant to be used in column or index
specifications).
### foreign-key, primary-key
If no arguments are provided, these render as just SQL
keywords (uppercase):
<!-- :test-doc-blocks/skip -->
```clojure
[:foreign-key] ;=> FOREIGN KEY
[:primary-key] ;=> PRIMARY KEY
```
Otherwise, these render as regular function calls:
<!-- :test-doc-blocks/skip -->
```clojure
[:foreign-key :a] ;=> FOREIGN KEY(a)
[:primary-key :x :y] ;=> PRIMARY KEY(x, y)
```
### constraint, default, references
Although these are grouped together, they are generally
used differently. This group renders as SQL keywords if
no arguments are provided. If a single argument is
provided, this renders as a SQL keyword followed by the
argument. If two or more arguments are provided, this
renders as a SQL keyword followed by the first argument,
followed by the rest as a regular argument list:
<!-- :test-doc-blocks/skip -->
```clojure
[:default] ;=> DEFAULT
[:default 42] ;=> DEFAULT 42
[:default "str"] ;=> DEFAULT 'str'
[:constraint :name] ;=> CONSTRAINT name
[:references :foo :bar] ;=> REFERENCES foo(bar)
```
### index, unique
These behave like the group above except that if the
first argument is `nil`, it is omitted:
<!-- :test-doc-blocks/skip -->
```clojure
[:index :foo :bar :quux] ;=> INDEX foo(bar, quux)
[:index nil :bar :quux] ;=> INDEX(bar, quux)
[:unique :a :b] ;=> UNIQUE a(b)
```

220
doc/xtdb.md Normal file
View file

@ -0,0 +1,220 @@
# XTDB Support
As of 2.6.1230, HoneySQL provides support for most of XTDB's SQL
extensions, with additional support being added in subsequent releases.
For the most part, XTDB's SQL is based on
[SQL:2011](https://en.wikipedia.org/wiki/SQL:2011), including the
bitemporal features, but also includes a number of SQL extensions
to support additional XTDB-specific features.
HoneySQL attempts to support all of these XTDB features in the core
ANSI dialect, and this section documents most of those XTDB features.
For more details, see the XTDB documentation:
* [SQL Overview](https://docs.xtdb.com/quickstart/sql-overview.html)
* [SQL Queries](https://docs.xtdb.com/reference/main/sql/queries.html)
* [SQL Transactions/DML](https://docs.xtdb.com/reference/main/sql/txs.html)
## Code Examples
The code examples herein assume:
```clojure
(refer-clojure :exclude '[update set])
(require '[honey.sql :as sql]
'[honey.sql.helpers :refer [select from where
delete-from erase-from
insert-into patch-into values
records]])
```
Clojure users can opt for the shorter `(require '[honey.sql :as sql] '[honey.sql.helpers :refer :all])` but this syntax is not available to ClojureScript users.
## `select` Variations
XTDB allows you to omit `SELECT` in a query. `SELECT *` is assumed if
it is omitted. In HoneySQL, you can simply omit the `:select` clause
from the DSL to achieve this.
```clojure
user=> (sql/format '{select * from foo where (= status "active")})
["SELECT * FROM foo WHERE status = ?" "active"]
user=> (sql/format '{from foo where (= status "active")})
["FROM foo WHERE status = ?" "active"]
```
You can also `SELECT *` and then exclude columns and/or rename columns.
```clojure
user=> (sql/format {:select [[:* {:exclude :_id :rename [[:title, :name]]}]]})
["SELECT * EXCLUDE _id RENAME title AS name"]
user=> (sql/format '{select ((a.* {exclude _id})
(b.* {rename ((title, name))}))
from ((foo a))
join ((bar b) (= a._id b.foo_id))})
["SELECT a.* EXCLUDE _id, b.* RENAME title AS name FROM foo AS a INNER JOIN bar AS b ON a._id = b.foo_id"]
```
`:exclude` can accept a single column, or a sequence of columns.
`:rename` accepts a sequence of pairs (column name, new name).
```clojure
user=> (sql/format {:select [[:* {:exclude [:_id :upc]
:rename [[:title, :name]
[:price, :cost]]}]]})
["SELECT * EXCLUDE (_id, upc) RENAME (title AS name, price AS cost)"]
```
## Nested Sub-Queries
XTDB can produce structured results from `SELECT` queries containing
sub-queries, using `NEST_ONE` and `NEST_MANY`. In HoneySQL, these are
supported as regular function syntax in `:select` clauses.
```clojure
user=> (sql/format '{select (a.*
((nest_many {select * from bar where (= foo_id a._id)})
b))
from ((foo a))})
["SELECT a.*, NEST_MANY (SELECT * FROM bar WHERE foo_id = a._id) AS b FROM foo AS a"]
```
Remember that function calls in `:select` clauses need to be nested three
levels of parentheses (brackets):
`:select [:col-a [:col-b :alias-b] [[:fn-call :col-c] :alias-c]]`.
## `records` Clause
XTDB provides a `RECORDS` clause to specify a list of structured documents,
similar to `VALUES` but specifically for documents rather than a collection
of column values. HoneySQL supports a `:records` clauses and automatically
lifts hash map values to parameters (rather than treating them as DSL fragments).
You can inline a hash map to produce XTDB's inline document syntax.
See also `insert` and `patch` below.
```clojure
user=> (sql/format {:records [{:_id 1 :status "active"}]})
["RECORDS ?" {:_id 1, :status "active"}]
user=> (sql/format {:records [[:inline {:_id 1 :status "active"}]]})
["RECORDS {_id: 1, status: 'active'}"]
```
## `object` (`record`) Literals
While `RECORDS` exists in parallel to the `VALUES` clause, XTDB also provides
a syntax to construct documents in other contexts in SQL, via the `OBJECT`
literal syntax. `RECORD` is a synonym for `OBJECT`. HoneySQL supports both
`:object` and `:record` as special syntax:
```clojure
user=> (sql/format {:select [[[:object {:_id 1 :status "active"}]]]})
["SELECT OBJECT (_id: 1, status: 'active')"]
user=> (sql/format {:select [[[:record {:_id 1 :status "active"}]]]})
["SELECT RECORD (_id: 1, status: 'active')"]
```
A third option is to use `:inline` with a hash map:
```clojure
user=> (sql/format {:select [[[:inline {:_id 1 :status "active"}]]]})
["SELECT {_id: 1, status: 'active'}"]
```
## Object Navigation Expressions
In order to deal with nested documents, XTDB provides syntax to navigate
into them, via field names and/or array indices. HoneySQL supports this
via the `:get-in` special syntax, intended to be familiar to Clojure users.
The first argument to `:get-in` is treated as an expression that produces
the document, and subsequent arguments are treated as field names or array
indices to navigate into that document.
```clojure
user=> (sql/format {:select [[[:get-in :doc :field1 :field2]]]})
["SELECT (doc).field1.field2"]
user=> (sql/format {:select [[[:get-in :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 [[[:get-in :doc [:lift 0] :field]]]})
["SELECT (doc)[?].field" 0]
```
## Temporal Queries
XTDB allows any query to be run in a temporal context via the `SETTING`
clause (ahead of the `SELECT` clause). HoneySQL supports this via the
`:setting` clause. It accepts a sequence of identifiers and expressions.
An identifier ending in `-time` is assumed to be a temporal identifier
(e.g., `:system-time` mapping to `SYSTEM_TIME`). Other identifiers are assumed to
be regular SQL (so `-` is mapped to a space, e.g., `:as-of` mapping to `AS OF`).
A timestamp literal, such as `DATE '2024-11-24'` can be specified in HoneySQL
using `[:inline [:DATE "2024-11-24"]]` (note the literal case of `:DATE`
to produce `DATE`).
See [XTDB's Top-level queries documentation](https://docs.xtdb.com/reference/main/sql/queries.html#_top_level_queries) for more details.
Here's one fairly complex example:
```clojure
user=> (sql/format {:setting [[:snapshot-time :to [:inline :DATE "2024-11-24"]]
[:default :valid-time :to :between [:inline :DATE "2022"] :and [:inline :DATE "2023"]]]})
["SETTING SNAPSHOT_TIME TO DATE '2024-11-24', DEFAULT VALID_TIME TO BETWEEN DATE '2022' AND DATE '2023'"]
```
Table references (e.g., in a `FROM` clause) can also have temporal qualifiers.
See [HoneySQL's `from` clause documentation](clause-reference.md#from) for
examples of that, one of which is reproduced here:
```clojure
user=> (sql/format {:select [:username]
:from [[:user :for :system-time :as-of [:inline "2019-08-01 15:23:00"]]]
:where [:= :id 9]})
["SELECT username FROM user FOR SYSTEM_TIME AS OF '2019-08-01 15:23:00' WHERE id = ?" 9]
```
## `delete` and `erase`
In XTDB, `DELETE` is a temporal deletion -- the data remains in the database
but is no longer visible in queries that don't specify a time range prior to
the deletion. XTDB provides a similar `ERASE` operation that can permanently
delete the data. HoneySQL supports `:erase-from` with the same syntax as
`:delete-from`.
```clojure
user=> (sql/format {:delete-from :foo :where [:= :status "inactive"]})
["DELETE FROM foo WHERE status = ?" "inactive"]
user=> (sql/format {:erase-from :foo :where [:= :status "inactive"]})
["ERASE FROM foo WHERE status = ?" "inactive"]
```
## `insert` and `patch`
XTDB supports `PATCH` as an upsert operation: it will update existing
documents (via merging the new data) or insert new documents if they
don't already exist. HoneySQL supports `:patch-into` with the same syntax
as `:insert-into` with `:records`.
```clojure
user=> (sql/format {:insert-into :foo
:records [{:_id 1 :status "active"}]})
["INSERT INTO foo RECORDS ?" {:_id 1, :status "active"}]
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')"]
```

57
pom.xml
View file

@ -1,57 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>honeysql</groupId>
<artifactId>honeysql</artifactId>
<version>1.0.461</version>
<name>honeysql</name>
<description>SQL as Clojure data structures.</description>
<url>https://github.com/seancorfield/honeysql</url>
<licenses>
<license>
<name>Eclipse Public License</name>
<url>http://www.eclipse.org/legal/epl-v10.html</url>
</license>
</licenses>
<developers>
<developer>
<name>Sean Corfield</name>
</developer>
<developer>
<name>Justin Kramer</name>
</developer>
</developers>
<scm>
<url>https://github.com/seancorfield/honeysql</url>
<connection>scm:git:git://github.com/seancorfield/honeysql.git</connection>
<developerConnection>scm:git:ssh://git@github.com/seancorfield/honeysql.git</developerConnection>
<tag>v1.0.461</tag>
</scm>
<dependencies>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.10.1</version>
</dependency>
</dependencies>
<build>
<sourceDirectory>src</sourceDirectory>
</build>
<repositories>
<repository>
<id>clojars</id>
<url>https://repo.clojars.org/</url>
</repository>
<repository>
<id>sonatype</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>clojars</id>
<name>Clojars repository</name>
<url>https://clojars.org/repo</url>
</repository>
</distributionManagement>
</project>

View file

@ -1,6 +0,0 @@
{sql/call honeysql.types/read-sql-call
sql/inline honeysql.types/read-sql-inline
sql/raw honeysql.types/read-sql-raw
sql/param honeysql.types/read-sql-param
sql/array honeysql.types/read-sql-array
sql/regularize honeysql.format/regularize}

View file

@ -1,26 +0,0 @@
#!/bin/sh
echo ==== Test README.md ==== && clojure -A:readme && \
echo ==== Lint Source ==== && clojure -A:eastwood && \
echo ==== Test ClojureScript ==== && clojure -A:test:cljs-runner
if test $? -eq 0
then
if test "$1" = "all"
then
for v in 1.7 1.8 1.9 1.10 master
do
echo ==== Test Clojure $v ====
clojure -A:test:runner:$v
if test $? -ne 0
then
exit 1
fi
done
else
echo ==== Test Clojure ====
clojure -A:test:runner
fi
else
exit 1
fi

2733
src/honey/sql.cljc Normal file

File diff suppressed because it is too large Load diff

1242
src/honey/sql/helpers.cljc Normal file

File diff suppressed because it is too large Load diff

95
src/honey/sql/pg_ops.cljc Normal file
View file

@ -0,0 +1,95 @@
;; copyright (c) 2022-2024 sean corfield, all rights reserved
(ns honey.sql.pg-ops
"Register all the PostgreSQL JSON/JSONB operators
and provide convenient Clojure names for those ops.
In addition, provide names for the PostgreSQL
regex operators as well.
For the eleven that cannot be written directly as
symbols, use mnemonic names: hash for #, at for @,
and tilde for ~.
For the six of those that cannot be written as
keywords, invoke the `keyword` function instead.
Those latter eight (`at>`, `<at`, `at?`, `atat`,
`tilde`, `tilde*`, `!tilde`, and `!tilde*`) are
the only ones that should really be needed in the
DSL. The other names are provided for completeness.
`regex` and `iregex` are provided as aliases for the
regex operators `tilde` and `tilde*` respectively.
`!regex` and `!iregex` are provided as aliases for the
regex operators `!tilde` and `!tilde*` respectively."
(: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 ->
"The -> operator for accessing nested JSON(B) values as JSON(B).
Ex.:
```clojure
(sql/format {:select [[[:->> [:-> :my_column \"kids\" [:inline 0]] \"name\"]]]})
; => [\"SELECT (my_column -> ? -> 0) ->> ?\" \"kids\" \"name\"]
```
Notice we need to wrap the keys/indices with :inline if we don't want them to become parameters."
:->)
(def ->> "The ->> operator - like -> but returns the value as text instead of a JSON object." :->>)
(def hash> "The #> operator extracts JSON sub-object at the specified path." :#>)
(def hash>> "The #>> operator - like hash> but returns the value as text instead of JSON object." :#>>)
(def at> "The @> operator - does the first JSON value contain the second?" (keyword "@>"))
(def <at "The <@ operator - is the first JSON value contained in the second?" (keyword "<@"))
(def ? "The ? operator - does the text string exist as a top-level key or array element within the JSON value?" :?)
(def ?| "The ?| operator - do any of the strings in the text array exist as top-level keys or array elements?" :?|)
(def ?& "The ?& operator - do all of the strings in the text array exist as top-level keys or array elements?" :?&)
(def || "The || operator - concatenates two jsonb values (arrays or objects; anything else treated as 1-element array)." :||)
(def -
"The - operator:
- text value: deletes a key (and its value) from a JSON object, or matching string value(s) from a JSON array
- text[] array value: as above, but for all the provided keys
- int value: deletes the array element with specified index (negative integers count from the end)"
:-)
(def hash- "The #- operator - deletes the field or array element at the specified path, where path elements can be either field keys or array indexes." :#-)
(def at? "The @? operator - does JSON path return any item for the specified JSON value?" (keyword "@?"))
(def atat
"The @@ operator:
- returns the result of a JSON path predicate check for the specified JSON value. Only the first item of the result is taken into account.
If the result is not Boolean, then NULL is returned.
- checks if a text search vector (or a text value implicitly converted to a text search vector) matches a text search query. Returns a Boolean."
(keyword "@@"))
(def tilde "The case-sensitive regex match operator." (keyword "~"))
(def tilde* "The case-insensitive regex match operator." (keyword "~*"))
(def !tilde "The case-sensitive regex unmatch operator." (keyword "!~"))
(def !tilde* "The case-insensitive regex unmatch operator." (keyword "!~*"))
;; aliases:
(def regex tilde)
(def iregex tilde*)
(def !regex !tilde)
(def !iregex !tilde*)
(sql/register-op! :->)
(sql/register-op! :->>)
(sql/register-op! :#>)
(sql/register-op! :#>>)
(sql/register-op! at>)
(sql/register-op! <at)
(sql/register-op! :?)
(sql/register-op! :?|)
(sql/register-op! :?&)
;; these are already known operators:
;(sql/register-op! :||)
;(sql/register-op! :-)
(sql/register-op! :#-)
(sql/register-op! at?)
(sql/register-op! atat)
(sql/register-op! tilde)
(sql/register-op! tilde*)
(sql/register-op! !tilde)
(sql/register-op! !tilde*)

View file

@ -0,0 +1,10 @@
;; copyright (c) 2022-2024 sean corfield, all rights reserved
(ns honey.sql.protocols
"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."))

109
src/honey/sql/util.cljc Normal file
View file

@ -0,0 +1,109 @@
(ns honey.sql.util
"Utility functions for the main honey.sql namespace."
(: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
reverts back to `clojure.core/str`."
(^String [] "")
(^String [^Object a]
#?(:clj (if (nil? a) "" (.toString a))
:default (clojure.core/str a)))
(^String [^Object a, ^Object b]
#?(:clj (if (nil? a)
(str b)
(if (nil? b)
(.toString a)
(.concat (.toString a) (.toString b))))
:default (clojure.core/str a b)))
(^String [a b c]
#?(:clj (let [sb (StringBuilder.)]
(.append sb (str a))
(.append sb (str b))
(.append sb (str c))
(.toString sb))
:default (clojure.core/str a b c)))
(^String [a b c d]
#?(:clj (let [sb (StringBuilder.)]
(.append sb (str a))
(.append sb (str b))
(.append sb (str c))
(.append sb (str d))
(.toString sb))
:default (clojure.core/str a b c d)))
(^String [a b c d e]
#?(:clj (let [sb (StringBuilder.)]
(.append sb (str a))
(.append sb (str b))
(.append sb (str c))
(.append sb (str d))
(.append sb (str e))
(.toString sb))
:default (clojure.core/str a b c d e)))
(^String [a b c d e & more]
#?(:clj (let [sb (StringBuilder.)]
(.append sb (str a))
(.append sb (str b))
(.append sb (str c))
(.append sb (str d))
(.append sb (str e))
(run! #(.append sb (str %)) more)
(.toString sb))
:default (apply clojure.core/str a b c d e more))))
(defn join
"More efficient implementation of `clojure.string/join`. May accept a transducer
`xform` to perform operations on each element before combining them together
into a string. Clojure-only, delegates to `clojure.string/join` on other
platforms."
([separator coll] (join separator identity coll))
([separator xform coll]
#?(:clj
(let [sb (StringBuilder.)
sep (str separator)]
(transduce xform
(fn
([] false)
([_] (.toString sb))
([add-sep? x]
(when add-sep? (.append sb sep))
(.append sb (str x))
true))
false coll))
: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)))

View file

@ -1,70 +0,0 @@
(ns honeysql.core
(:refer-clojure :exclude [group-by format])
(:require [honeysql.format :as format]
[honeysql.types :as types]
[honeysql.helpers :refer [build-clause]]
#?(:clj [honeysql.util :refer [defalias]])
[clojure.string :as string]))
(#?(:clj defalias :cljs def) call types/call)
(#?(:clj defalias :cljs def) raw types/raw)
(#?(:clj defalias :cljs def) param types/param)
(#?(:clj defalias :cljs def) inline types/inline)
(#?(:clj defalias :cljs def) format format/format)
(#?(:clj defalias :cljs def) format-predicate format/format-predicate)
(#?(:clj defalias :cljs def) quote-identifier format/quote-identifier)
(defn qualify
"Takes one or more keyword or string qualifers and name. Returns
a keyword of the concatenated qualifiers and name separated by periods.
(qualify :foo \"bar\" :baz) => :foo.bar.baz"
[& qualifiers+name]
(keyword
(string/join "."
(for [s qualifiers+name
:when (not (nil? s))]
(if (keyword? s)
(name s)
(str s))))))
(defn build
"Takes a series of clause+data pairs and returns a SQL map. Example:
(build :select [:a :b]
:from :bar)
Clauses are defined with the honeysql.helpers/build-clause multimethod.
Built-in clauses include:
:select, :merge-select, :un-select
:from, :merge-from
:join, :merge-join
:left-join, :merge-left-join
:right-join, :merge-right-join
:full-join, :merge-full-join
:cross-join, :merge-cross-join
:where, :merge-where
:group-by, :merge-group-by
:having, :merge-having
:limit
:offset
:modifiers, :merge-modifiers
:insert-into
:columns, :merge-columns
:values, :merge-values
:query-values
:update
:set
:delete-from"
[& clauses]
(let [[base clauses] (if (map? (first clauses))
[(first clauses) (rest clauses)]
[{} clauses])]
(reduce
(fn [sql-map [op args]]
(build-clause op sql-map args))
(if (empty? base)
base
(apply build (apply concat base)))
(partition 2 clauses))))

View file

@ -1,716 +0,0 @@
(ns honeysql.format
(:refer-clojure :exclude [format])
(:require [honeysql.types :as types
:refer [call raw param param-name inline-str
#?@(:cljs [SqlCall SqlRaw SqlParam SqlArray SqlInline])]]
[clojure.string :as string])
#?(:clj (:import [honeysql.types SqlCall SqlRaw SqlParam SqlArray SqlInline])))
;;(set! *warn-on-reflection* true)
;;;;
(defn comma-join [s]
(string/join ", " s))
(defn space-join [s]
(string/join " " s))
(defn paren-wrap [x]
(str "(" x ")"))
(def ^:dynamic *clause*
"During formatting, *clause* is bound to :select, :from, :where, etc."
nil)
(def ^:dynamic *params*
"Will be bound to an atom-vector that accumulates SQL parameters across
possibly-recursive function calls"
nil)
(def ^:dynamic *param-names* nil)
(def ^:dynamic *param-counter* nil)
(def ^:dynamic *all-param-counter* nil)
(def ^:dynamic *input-params* nil)
(def ^:dynamic *fn-context?* false)
(def ^:dynamic *value-context?* false)
(def ^:dynamic *subquery?* false)
(def ^:dynamic *allow-dashed-names?* false)
(def ^:dynamic *allow-namespaced-names?* false)
(def ^:dynamic *namespace-as-table?* false)
(def ^:dynamic *name-transform-fn* nil)
(def ^:private quote-fns
{:ansi #(str \" (string/replace % "\"" "\"\"") \")
:mysql #(str \` (string/replace % "`" "``") \`)
:sqlserver #(str \[ (string/replace % "]" "]]") \])
:oracle #(str \" (string/replace % "\"" "\"\"") \")})
(defmulti parameterize (fn [parameterizer & args] parameterizer))
(defmethod parameterize :postgresql [_ value pname]
(str "$" (swap! *all-param-counter* inc)))
(defmethod parameterize :jdbc [_ value pname]
"?")
(defmethod parameterize :none [_ value pname]
(str (last @*params*)))
(def ^:dynamic *quote-identifier-fn* nil)
(def ^:dynamic *parameterizer* nil)
(defn- undasherize [s]
(string/replace s "-" "_"))
;; String.toUpperCase() or `string/upper-case` for that matter converts the string to uppercase for the DEFAULT
;; LOCALE. Normally this does what you'd expect but things like `inner join` get converted to `İNNER JOIN` (dot over
;; the I) when user locale is Turkish. This predictably has bad consequences for people who like their SQL queries to
;; work. The fix here is to use String.toUpperCase(Locale/US) instead which always converts things the way we'd expect.
;;
;; Use this function instead of `string/upper-case` as it will always use Locale/US.
(def ^:private ^{:arglists '([s])} upper-case
;; TODO - not sure if there's a JavaScript equivalent here we should be using as well
#?(:clj (fn [^String s] (.. s toString (toUpperCase (java.util.Locale/US))))
:cljs string/upper-case))
(defn quote-identifier [x & {:keys [style split] :or {split true}}]
(let [name-transform-fn (cond
*name-transform-fn* *name-transform-fn*
*allow-dashed-names?* identity
:else undasherize)
qf (if style
(quote-fns style)
*quote-identifier-fn*)
s (cond
(or (keyword? x) (symbol? x))
(name-transform-fn
(cond *namespace-as-table?*
(str (when-let [n (namespace x)]
(str n "."))
(name x))
*allow-namespaced-names?*
(str (when-let [n (namespace x)]
(str n "/"))
(name x))
:else
(name x)))
(string? x) (if qf x (name-transform-fn x))
:else (str x))]
(if-not qf
s
(let [qf* #(if (= "*" %) % (qf %))]
(if-not split
(qf* s)
(let [parts (string/split s #"\.")]
(string/join "." (map qf* parts))))))))
(def infix-fns
#{"+" "-" "*" "/" "%" "mod" "|" "&" "^"
"and" "or" "xor"
"in" "not in" "like" "not like" "regexp"})
(def fn-aliases
{"is" "="
"is-not" "<>"
"not=" "<>"
"!=" "<>"
"not-in" "not in"
"not-like" "not like"
"regex" "regexp"})
(defprotocol ToSql
(to-sql [x]))
(defn to-sql-value [x]
(binding [*value-context?* (sequential? x)]
(to-sql x)))
(defmulti fn-handler (fn [op & args] op))
(defn expand-binary-ops [op & args]
(str "("
(string/join " AND "
(for [[a b] (partition 2 1 args)]
(fn-handler op a b)))
")"))
(defmethod fn-handler :default [op & args]
(let [args (map to-sql args)]
(if (infix-fns op)
(paren-wrap (string/join (str " " op " ") args))
(str op (paren-wrap (comma-join args))))))
(defmethod fn-handler "count-distinct" [_ & args]
(str "COUNT(DISTINCT " (comma-join (map to-sql args)) ")"))
(defmethod fn-handler "distinct-on" [_ & args]
(str "DISTINCT ON (" (comma-join (map to-sql args)) ")"))
(defmethod fn-handler "cast" [_ field cast-to-type]
(str "CAST" (paren-wrap (str (to-sql field)
" AS "
(to-sql cast-to-type)))))
(defmethod fn-handler "=" [_ a b & more]
(if (seq more)
(apply expand-binary-ops "=" a b more)
(cond
(nil? a) (str (to-sql-value b) " IS NULL")
(nil? b) (str (to-sql-value a) " IS NULL")
:else (str (to-sql-value a) " = " (to-sql-value b)))))
(defmethod fn-handler "<>" [_ a b & more]
(if (seq more)
(apply expand-binary-ops "<>" a b more)
(cond
(nil? a) (str (to-sql-value b) " IS NOT NULL")
(nil? b) (str (to-sql-value a) " IS NOT NULL")
:else (str (to-sql-value a) " <> " (to-sql-value b)))))
(defmethod fn-handler "<" [_ a b & more]
(if (seq more)
(apply expand-binary-ops "<" a b more)
(str (to-sql-value a) " < " (to-sql-value b))))
(defmethod fn-handler "<=" [_ a b & more]
(if (seq more)
(apply expand-binary-ops "<=" a b more)
(str (to-sql-value a) " <= " (to-sql-value b))))
(defmethod fn-handler ">" [_ a b & more]
(if (seq more)
(apply expand-binary-ops ">" a b more)
(str (to-sql-value a) " > " (to-sql-value b))))
(defmethod fn-handler ">=" [_ a b & more]
(if (seq more)
(apply expand-binary-ops ">=" a b more)
(str (to-sql-value a) " >= " (to-sql-value b))))
(defmethod fn-handler "between" [_ field lower upper]
(str (to-sql-value field) " BETWEEN " (to-sql-value lower) " AND " (to-sql-value upper)))
;; Handles MySql's MATCH (field) AGAINST (pattern). The third argument
;; can be a set containing one or more of :boolean, :natural, or :expand.
(defmethod fn-handler "match" [_ fields pattern & [opts]]
(str "MATCH ("
(comma-join
(map to-sql (if (coll? fields) fields [fields])))
") AGAINST ("
(to-sql-value pattern)
(when (seq opts)
(str " " (space-join (for [opt opts]
(case opt
:boolean "IN BOOLEAN MODE"
:natural "IN NATURAL LANGUAGE MODE"
:expand "WITH QUERY EXPANSION")))))
")"))
(def default-clause-priorities
"Determines the order that clauses will be placed within generated SQL"
{:with 20
:with-recursive 30
:intersect 35
:union 40
:union-all 45
:except 47
:select 50
:insert-into 60
:update 70
:delete 75
:delete-from 80
:truncate 85
:columns 90
:composite 95
:set0 100 ; low-priority set clause
:from 110
:join 120
:left-join 130
:right-join 140
:full-join 150
:cross-join 152
:set 155
:set1 156 ; high-priority set clause (synonym for :set)
:where 160
:group-by 170
:having 180
:order-by 190
:limit 200
:offset 210
:lock 215
:values 220
:query-values 230})
(def clause-store (atom default-clause-priorities))
(defn register-clause! [clause-key priority]
(swap! clause-store assoc clause-key priority))
(defn sort-clauses [clauses]
(let [m @clause-store]
(sort-by
(fn [c]
(m c #?(:clj Long/MAX_VALUE :cljs js/Number.MAX_VALUE)))
clauses)))
(defn format
"Takes a SQL map and optional input parameters and returns a vector
of a SQL string and parameters, as expected by `next.jbc` and
`clojure.java.jdbc`.
Input parameters will be filled into designated spots according to
name (if a map is provided) or by position (if a sequence is provided).
Instead of passing parameters, you can use keyword arguments:
:params - input parameters
:quoting - quote style to use for identifiers; one of :ansi (PostgreSQL),
:mysql, :sqlserver, or :oracle. Defaults to no quoting.
:parameterizer - style of parameter naming, :postgresql,
:jdbc or :none. Defaults to :jdbc.
:return-param-names - when true, returns a vector of
[sql-str param-values param-names]"
[sql-map & params-or-opts]
(let [opts (when (keyword? (first params-or-opts))
(apply hash-map params-or-opts))
params (if (coll? (first params-or-opts))
(first params-or-opts)
(:params opts))]
(binding [*params* (atom [])
*param-counter* (atom 0)
*all-param-counter* (atom 0)
*param-names* (atom [])
*input-params* (atom params)
*quote-identifier-fn* (quote-fns (:quoting opts))
*parameterizer* (or (:parameterizer opts) :jdbc)
*allow-dashed-names?* (:allow-dashed-names? opts)
*allow-namespaced-names?* (:allow-namespaced-names? opts)
*namespace-as-table?* (:namespace-as-table? opts)]
(let [sql-str (to-sql sql-map)]
(if (and (seq @*params*) (not= :none (:parameterizer opts)))
(if (:return-param-names opts)
[sql-str @*params* @*param-names*]
(into [sql-str] @*params*))
[sql-str])))))
(defprotocol Parameterizable
(to-params [value pname]))
(defn to-params-seq [s pname]
(paren-wrap (comma-join (mapv #(to-params % pname) s))))
(defn to-params-default [value pname]
(swap! *params* conj value)
(swap! *param-names* conj pname)
(parameterize *parameterizer* value pname))
(extend-protocol Parameterizable
#?@(:clj
[clojure.lang.Sequential
(to-params [value pname]
(to-params-seq value pname))])
#?(:clj clojure.lang.IPersistentSet
:cljs cljs.core/PersistentHashSet)
(to-params [value pname]
(to-params (seq value) pname))
nil
(to-params [value pname]
(swap! *params* conj value)
(swap! *param-names* conj pname)
(parameterize *parameterizer* value pname))
#?(:clj Object :cljs default)
(to-params [value pname]
#?(:clj
(to-params-default value pname)
:cljs
(if (sequential? value)
(to-params-seq value pname)
(to-params-default value pname)))))
(defn add-param [pname pval]
(to-params pval pname))
;; Anonymous param name -- :_1, :_2, etc.
(defn add-anon-param [pval]
(add-param
(keyword (str "_" (swap! *param-counter* inc)))
pval))
(defrecord Value [v]
ToSql
(to-sql [_]
(add-anon-param v)))
(defn value [x] (Value. x))
(declare -format-clause)
(defn map->sql [m]
(let [clause-ops (sort-clauses (keys m))
sql-str (binding [*subquery?* true
*fn-context?* false]
(space-join
(map (comp #(-format-clause % m) #(find m %))
clause-ops)))]
(if *subquery?*
(paren-wrap sql-str)
sql-str)))
(declare format-predicate*)
(defn seq->sql [x]
(cond
*value-context?*
;; sequences are operators/functions
(format-predicate* x)
*fn-context?*
;; list argument in fn call
(paren-wrap (comma-join (map to-sql x)))
:else
;; alias
(do
(assert (= 2 (count x)) (str "Alias should have two parts" x))
(let [[target alias] x]
(str (to-sql target)
; Omit AS in FROM, JOIN, etc. - Oracle doesn't allow it
(if (= :select *clause*) " AS " " ")
(if (or (string? alias) (keyword? alias) (symbol? alias))
(quote-identifier alias :split false)
(binding [*subquery?* false]
(to-sql alias))))))))
(extend-protocol types/Inlinable
#?(:clj clojure.lang.Keyword
:cljs cljs.core/Keyword)
(inline-str [x]
(name x))
nil
(inline-str [_]
"NULL")
#?(:clj Object :cljs default)
(inline-str [x]
(str x)))
(extend-protocol ToSql
#?(:clj clojure.lang.Keyword
:cljs cljs.core/Keyword)
(to-sql [x]
(let [s (name x)]
(case (.charAt s 0)
\% (let [call-args (string/split (subs s 1) #"\." 2)]
(to-sql (apply call (map keyword call-args))))
\? (to-sql (param (keyword (subs s 1))))
(quote-identifier x))))
#?(:clj clojure.lang.Symbol
:cljs cljs.core/Symbol)
(to-sql [x] (quote-identifier x))
#?(:clj java.lang.Boolean :cljs boolean)
(to-sql [x]
(if x "TRUE" "FALSE"))
#?@(:clj
[clojure.lang.Sequential
(to-sql [x] (seq->sql x))])
SqlCall
(to-sql [x]
(binding [*fn-context?* true]
(let [fn-name (name (.-name x))
fn-name (fn-aliases fn-name fn-name)]
(apply fn-handler fn-name (.-args x)))))
SqlRaw
(to-sql [x]
(let [s (.-s x)]
(if (vector? s)
(string/join "" (map (fn [x] (if (string? x) x (to-sql x))) s))
s)))
#?(:clj clojure.lang.IPersistentMap
:cljs cljs.core/PersistentArrayMap)
(to-sql [x]
(map->sql x))
#?(:clj clojure.lang.IPersistentSet
:cljs cljs.core/PersistentHashSet)
(to-sql [x]
(to-sql (seq x)))
nil
(to-sql [x] "NULL")
SqlParam
(to-sql [x]
(let [pname (param-name x)]
(if (map? @*input-params*)
(add-param pname (get @*input-params* pname))
(let [x (first @*input-params*)]
(swap! *input-params* rest)
(add-param pname x)))))
SqlArray
(to-sql [x]
(str "ARRAY[" (comma-join (map to-sql (.-values x))) "]"))
SqlInline
(to-sql [x]
(inline-str (.-value x)))
#?(:clj Object :cljs default)
(to-sql [x]
#?(:clj (add-anon-param x)
:cljs (if (sequential? x)
(seq->sql x)
(add-anon-param x))))
#?@(:cljs
[cljs.core/PersistentHashMap
(to-sql [x] (map->sql x))]))
(defn sqlable? [x]
(satisfies? ToSql x))
;;;;
(defn format-predicate* [pred]
(if-not (sequential? pred)
(to-sql pred)
(let [[op & args] pred
op-name (name op)]
(case op-name
"not" (str "NOT " (format-predicate* (first args)))
("and" "or" "xor")
(->> args
(remove nil?)
(map format-predicate*)
(string/join (str " " (upper-case op-name) " "))
(paren-wrap))
"exists"
(str "EXISTS " (to-sql (first args)))
(to-sql (apply call pred))))))
(defn format-predicate
"Formats a predicate (e.g., for WHERE, JOIN, or HAVING) as a string."
[pred & {:keys [quoting parameterizer]
:or {parameterizer :jdbc}}]
(binding [*params* (atom [])
*param-counter* (atom 0)
*param-names* (atom [])
*quote-identifier-fn* (or (quote-fns quoting)
*quote-identifier-fn*)
*parameterizer* parameterizer]
(let [sql-str (format-predicate* pred)]
(if (seq @*params*)
(into [sql-str] @*params*)
[sql-str]))))
(defmulti format-clause
"Takes a map entry representing a clause and returns an SQL string"
(fn [clause _] (key clause)))
(defn- -format-clause
[clause _]
(binding [*clause* (key clause)]
(format-clause clause _)))
(defmethod format-clause :default [& _]
"")
(defmethod format-clause :exists [[_ table-expr] _]
(str "EXISTS " (to-sql table-expr)))
(defmulti format-modifiers (fn [[op & _]] op))
(defmethod format-modifiers :distinct [_] "DISTINCT")
(defmethod format-modifiers :default [coll]
(space-join (map (comp upper-case name) coll)))
(defmethod format-clause :select [[_ fields] sql-map]
(str "SELECT "
(when (:modifiers sql-map)
(str (format-modifiers (:modifiers sql-map)) " "))
(comma-join (map to-sql fields))))
(defmethod format-clause :from [[_ tables] _]
(str "FROM " (comma-join (map to-sql tables))))
(defmethod format-clause :where [[_ pred] _]
(str "WHERE " (format-predicate* pred)))
(defn format-join [type table pred]
(str (when type
(str (upper-case (name type)) " "))
"JOIN " (to-sql table)
(when (some? pred)
(if (and (sequential? pred) (= :using (first pred)))
(str " USING (" (->> pred rest (map quote-identifier) comma-join) ")")
(str " ON " (format-predicate* pred))))))
(defmethod format-clause :join [[_ join-groups] _]
(space-join (map #(apply format-join :inner %)
(partition 2 join-groups))))
(defmethod format-clause :left-join [[_ join-groups] _]
(space-join (map #(apply format-join :left %)
(partition 2 join-groups))))
(defmethod format-clause :right-join [[_ join-groups] _]
(space-join (map #(apply format-join :right %)
(partition 2 join-groups))))
(defmethod format-clause :full-join [[_ join-groups] _]
(space-join (map #(apply format-join :full %)
(partition 2 join-groups))))
(defmethod format-clause :cross-join [[_ join-groups] _]
(space-join (map #(format-join :cross % nil) join-groups)))
(defmethod format-clause :group-by [[_ fields] _]
(str "GROUP BY " (comma-join (map to-sql fields))))
(defmethod format-clause :having [[_ pred] _]
(str "HAVING " (format-predicate* pred)))
(defmethod format-clause :order-by [[_ fields] _]
(str "ORDER BY "
(comma-join (for [field fields]
(if (sequential? field)
(let [[field & modifiers] field]
(string/join " "
(cons (to-sql field)
(for [modifier modifiers]
(case modifier
:desc "DESC"
:asc "ASC"
:nulls-first "NULLS FIRST"
:nulls-last "NULLS LAST"
"")))))
(to-sql field))))))
(defmethod format-clause :limit [[_ limit] _]
(str "LIMIT " (to-sql limit)))
(defmethod format-clause :offset [[_ offset] _]
(str "OFFSET " (to-sql offset)))
(defmulti format-lock-clause identity)
(defmethod format-lock-clause :update [_]
"FOR UPDATE")
(defmethod format-lock-clause :mysql-share [_]
"LOCK IN SHARE MODE")
(defmethod format-lock-clause :postgresql-share [_]
"FOR SHARE")
(defmethod format-clause :lock [[_ lock] _]
(let [{:keys [mode wait]} lock
clause (format-lock-clause mode)]
(str clause (when (false? wait) " NOWAIT"))))
(defmethod format-clause :insert-into [[_ table] _]
(if (and (sequential? table) (sequential? (first table)))
(str "INSERT INTO "
(to-sql (ffirst table))
(binding [*namespace-as-table?* false]
(str " (" (comma-join (map to-sql (second (first table)))) ") "))
(binding [*subquery?* false]
(to-sql (second table))))
(str "INSERT INTO " (to-sql table))))
(defmethod format-clause :columns [[_ fields] _]
(binding [*namespace-as-table?* false]
(str "(" (comma-join (map to-sql fields)) ")")))
(defmethod format-clause :composite [[_ fields] _]
(comma-join (map to-sql fields)))
(defmethod format-clause :values [[_ values] _]
(if (sequential? (first values))
(str "VALUES " (comma-join (for [x values]
(binding [*fn-context?* true]
(str "(" (comma-join (map to-sql x)) ")")))))
(let [cols (keys (first values))]
(str
(binding [*namespace-as-table?* false]
(str "(" (comma-join (map to-sql cols)) ")"))
" VALUES "
(comma-join (for [x values]
(binding [*fn-context?* true]
(str "(" (comma-join (map #(to-sql (get x %)) cols)) ")"))))))))
(defmethod format-clause :query-values [[_ query-values] _]
(to-sql query-values))
(defmethod format-clause :update [[_ table] _]
(str "UPDATE " (to-sql table)))
(defmethod format-clause :set [[_ values] _]
(str "SET " (comma-join (for [[k v] values]
(str (to-sql k) " = " (to-sql v))))))
(defmethod format-clause :set0 [[_ values] _]
(str "SET " (comma-join (for [[k v] values]
(str (to-sql k) " = " (to-sql v))))))
(defmethod format-clause :set1 [[_ values] _]
(str "SET " (comma-join (for [[k v] values]
(str (to-sql k) " = " (to-sql v))))))
(defmethod format-clause :delete-from [[_ table] _]
(str "DELETE FROM " (to-sql table)))
(defmethod format-clause :delete [[_ tables] _]
(str "DELETE " (comma-join (map to-sql tables))))
(defmethod format-clause :truncate [[_ table] _]
(str "TRUNCATE " (to-sql table)))
(defn cte->sql
[[cte-name query]]
(str (binding [*subquery?* false]
(to-sql cte-name))
" AS "
(to-sql query)))
(defmethod format-clause :with [[_ ctes] _]
(str "WITH " (comma-join (map cte->sql ctes))))
(defmethod format-clause :with-recursive [[_ ctes] _]
(str "WITH RECURSIVE " (comma-join (map cte->sql ctes))))
(defmethod format-clause :union [[_ maps] _]
(binding [*subquery?* false]
(string/join " UNION " (map to-sql maps))))
(defmethod format-clause :union-all [[_ maps] _]
(binding [*subquery?* false]
(string/join " UNION ALL " (map to-sql maps))))
(defmethod format-clause :intersect [[_ maps] _]
(binding [*subquery?* false]
(string/join " INTERSECT " (map to-sql maps))))
(defmethod format-clause :except [[_ maps] _]
(binding [*subquery?* false]
(string/join " EXCEPT " (map to-sql maps))))
(defmethod fn-handler "case" [_ & clauses]
(str "CASE "
(space-join
(for [[condition result] (partition 2 clauses)]
(if (= :else condition)
(str "ELSE " (to-sql result))
(let [pred (format-predicate* condition)]
(str "WHEN " pred " THEN " (to-sql result))))))
" END"))
(defn regularize [sql-string]
(string/replace sql-string #"\s+" " "))

View file

@ -1,382 +0,0 @@
(ns honeysql.helpers
(:refer-clojure :exclude [update])
#?(:cljs (:require-macros [honeysql.helpers :refer [defhelper]])))
(defmulti build-clause (fn [name & args]
name))
(defmethod build-clause :default [_ m & args]
m)
(defn plain-map? [m]
(and
(map? m)
(not (record? m))))
#?(:clj
(defmacro defhelper [helper arglist & more]
(when-not (vector? arglist)
(throw #?(:clj (IllegalArgumentException. "arglist must be a vector")
:cljs (js/Error. "arglist must be a vector"))))
(when-not (= (count arglist) 2)
(throw #?(:clj (IllegalArgumentException. "arglist must have two entries, map and varargs")
:cljs (js/Error. "arglist must have two entries, map and varargs"))))
(let [kw (keyword (name helper))
[m-arg varargs] arglist]
`(do
(defmethod build-clause ~kw ~['_ m-arg varargs] ~@more)
(defn ~helper [& args#]
(let [[m# args#] (if (plain-map? (first args#))
[(first args#) (rest args#)]
[{} args#])]
(build-clause ~kw m# args#)))
;; maintain the original arglist instead of getting
;; ([& args__6880__auto__])
(alter-meta!
(var ~helper)
assoc
:arglists
'(~['& varargs]
~[m-arg '& varargs]))))))
(defn collify [x]
(if (coll? x) x [x]))
(defhelper select [m fields]
(assoc m :select (collify fields)))
(defhelper merge-select [m fields]
(update-in m [:select] concat (collify fields)))
(defhelper un-select [m fields]
(update-in m [:select] #(remove (set (collify fields)) %)))
(defhelper from [m tables]
(assoc m :from (collify tables)))
(defhelper merge-from [m tables]
(update-in m [:from] concat (collify tables)))
(defmethod build-clause :where [_ m pred]
(if (nil? pred)
m
(assoc m :where pred)))
(defn- merge-where-args
"Handle optional args passed to `merge-where` or similar functions. Returns tuple of
[m where-clauses conjunction-operator]"
[args]
(let [[m & args] (if (map? (first args))
args
(cons {} args))
[conjunction & clauses] (if (keyword? (first args))
args
(cons :and args))]
[m (filter some? clauses) conjunction]))
(defn- where-args
"Handle optional args passed to `where` or similar functions. Merges clauses together. Returns tuple of
[m merged-clause]"
[args]
(let [[m clauses conjunction] (merge-where-args args)]
[m (if (<= (count clauses) 1)
(first clauses)
(into [conjunction] clauses))]))
(defn- where-like
"Create a WHERE-style clause with key `k` (e.g. `:where` or `:having`)"
[k args]
(let [[m pred] (where-args args)]
(if (nil? pred)
m
(assoc m k pred))))
(defn where [& args]
(where-like :where args))
(defn- is-clause? [clause x]
(and (sequential? x) (= (first x) clause)))
(defn- merge-where-like
"Merge a WHERE-style clause with key `k` (e.g. `:where` or `:having`)"
[k args]
(let [[m new-clauses conjunction] (merge-where-args args)]
(reduce
(fn [m new-clause]
;; combine existing clause and new clause if they're both of the specified conjunction type, e.g.
;; [:and a b] + [:and c d] -> [:and a b c d]
(update-in m [k] (fn [existing-clause]
(let [existing-subclauses (when (some? existing-clause)
(if (is-clause? conjunction existing-clause)
(rest existing-clause)
[existing-clause]))
new-subclauses (if (is-clause? conjunction new-clause)
(rest new-clause)
[new-clause])
subclauses (concat existing-subclauses new-subclauses)]
(if (> (count subclauses) 1)
(into [conjunction] subclauses)
(first subclauses))))))
m
new-clauses)))
(defn merge-where
"Merge a series of `where-clauses` together. Supports two optional args: a map to merge the results into, and a
`conjunction` to use to combine clauses (defaults to `:and`).
(merge-where [:= :x 1] [:= :y 2])
{:where [:and [:= :x 1] [:= :y 2]]}
(merge-where {:where [:= :x 1]} [:= :y 2])
;; -> {:where [:and [:= :x 1] [:= :y 2]]}
(merge-where :or [:= :x 1] [:= :y 2])
;; -> {:where [:or [:= :x 1] [:= :y 2]]}"
{:arglists '([& where-clauses]
[m-or-conjunction & where-clauses]
[m conjunction & where-clauses])}
[& args]
(merge-where-like :where args))
(defmethod build-clause :merge-where
[_ m where-clause]
(merge-where m where-clause))
(defhelper join [m clauses]
(assoc m :join clauses))
(defhelper merge-join [m clauses]
(update-in m [:join] concat clauses))
(defhelper left-join [m clauses]
(assoc m :left-join clauses))
(defhelper merge-left-join [m clauses]
(update-in m [:left-join] concat clauses))
(defhelper right-join [m clauses]
(assoc m :right-join clauses))
(defhelper merge-right-join [m clauses]
(update-in m [:right-join] concat clauses))
(defhelper full-join [m clauses]
(assoc m :full-join clauses))
(defhelper merge-full-join [m clauses]
(update-in m [:full-join] concat clauses))
(defhelper cross-join [m clauses]
(assoc m :cross-join clauses))
(defhelper merge-cross-join [m clauses]
(update-in m [:cross-join] concat clauses))
(defmethod build-clause :group-by [_ m fields]
(assoc m :group-by (collify fields)))
(defn group [& args]
(let [[m fields] (if (map? (first args))
[(first args) (rest args)]
[{} args])]
(build-clause :group-by m fields)))
(defhelper merge-group-by [m fields]
(update-in m [:group-by] concat (collify fields)))
(defmethod build-clause :having [_ m pred]
(if (nil? pred)
m
(assoc m :having pred)))
(defn having [& args]
(where-like :having args))
(defn merge-having
"Merge a series of `having-clauses` together. Supports two optional args: a map to merge the results into, and a
`conjunction` to use to combine clauses (defaults to `:and`).
(merge-having [:= :x 1] [:= :y 2])
{:having [:and [:= :x 1] [:= :y 2]]}
(merge-having {:having [:= :x 1]} [:= :y 2])
;; -> {:having [:and [:= :x 1] [:= :y 2]]}
(merge-having :or [:= :x 1] [:= :y 2])
;; -> {:having [:or [:= :x 1] [:= :y 2]]}"
{:arglists '([& having-clauses]
[m-or-conjunction & having-clauses]
[m conjunction & having-clauses])}
[& args]
(merge-where-like :having args))
(defmethod build-clause :merge-having
[_ m where-clause]
(merge-having m where-clause))
(defhelper order-by [m fields]
(assoc m :order-by (collify fields)))
(defhelper merge-order-by [m fields]
(update-in m [:order-by] concat (collify fields)))
(defhelper limit [m l]
(if (nil? l)
m
(assoc m :limit (if (coll? l) (first l) l))))
(defhelper offset [m o]
(if (nil? o)
m
(assoc m :offset (if (coll? o) (first o) o))))
(defhelper lock [m lock]
(cond-> m
lock
(assoc :lock lock)))
(defhelper modifiers [m ms]
(if (nil? ms)
m
(assoc m :modifiers (collify ms))))
(defhelper merge-modifiers [m ms]
(if (nil? ms)
m
(update-in m [:modifiers] concat (collify ms))))
(defmethod build-clause :insert-into [_ m table]
(assoc m :insert-into table))
(defn insert-into
([table] (insert-into nil table))
([m table] (build-clause :insert-into m table)))
(defn- check-varargs
"Called for helpers that require unrolled arguments to catch the mistake
of passing a collection as a single argument."
[helper args]
(when (and (coll? args) (= 1 (count args)) (coll? (first args)))
(let [msg (str (name helper) " takes varargs, not a single collection")]
(throw #?(:clj (IllegalArgumentException. msg)
:cljs (js/Error. msg))))))
(defmethod build-clause :columns [_ m fields]
(assoc m :columns (collify fields)))
(defn columns [& args]
(let [[m fields] (if (map? (first args))
[(first args) (rest args)]
[{} args])]
(check-varargs :columns fields)
(build-clause :columns m fields)))
(defmethod build-clause :merge-columns [_ m fields]
(update-in m [:columns] concat (collify fields)))
(defn merge-columns [& args]
(let [[m fields] (if (map? (first args))
[(first args) (rest args)]
[{} args])]
(check-varargs :merge-columns fields)
(build-clause :merge-columns m fields)))
(defhelper composite [m vs]
(if (nil? vs)
m
(assoc m :composite (collify vs))))
(defmethod build-clause :values [_ m vs]
(assoc m :values vs))
(defn values
([vs] (values nil vs))
([m vs] (build-clause :values m vs)))
(defmethod build-clause :merge-values [_ m vs]
(update-in m [:values] concat vs))
(defn merge-values
([vs] (merge-values nil vs))
([m vs] (build-clause :merge-values m vs)))
(defmethod build-clause :query-values [_ m vs]
(assoc m :query-values vs))
(defn query-values
([vs] (values nil vs))
([m vs] (build-clause :query-values m vs)))
(defmethod build-clause :update [_ m table]
(assoc m :update table))
(defn update
([table] (update nil table))
([m table] (build-clause :update m table)))
(defmethod build-clause :set [_ m values]
(assoc m :set values))
;; short for sql set, to avoid name collision with clojure.core/set
(defn sset
([vs] (sset nil vs))
([m vs] (build-clause :set m vs)))
(defmethod build-clause :set0 [_ m values]
(assoc m :set0 values))
;; set with lower priority (before from)
(defn set0
([vs] (set0 nil vs))
([m vs] (build-clause :set0 m vs)))
(defmethod build-clause :set [_ m values]
(assoc m :set values))
;; set with higher priority (after join)
(defn set1
([vs] (set1 nil vs))
([m vs] (build-clause :set1 m vs)))
(defmethod build-clause :delete-from [_ m table]
(assoc m :delete-from table))
(defn delete-from
([table] (delete-from nil table))
([m table] (build-clause :delete-from m table)))
(defmethod build-clause :delete [_ m tables]
(assoc m :delete tables))
(defn delete
([tables] (delete nil tables))
([m tables] (build-clause :delete m tables)))
(defmethod build-clause :truncate [_ m table]
(assoc m :truncate table))
(defn truncate
([table] (truncate nil table))
([m table] (build-clause :truncate m table)))
(defhelper with [m ctes]
(assoc m :with ctes))
(defhelper with-recursive [m ctes]
(assoc m :with-recursive ctes))
(defmethod build-clause :union [_ m maps]
(assoc m :union maps))
(defmethod build-clause :union-all [_ m maps]
(assoc m :union-all maps))
(defmethod build-clause :intersect [_ m maps]
(assoc m :intersect maps))
(defmethod build-clause :except [_ m maps]
(assoc m :except maps))

View file

@ -1,105 +0,0 @@
(ns honeysql.types
(:refer-clojure :exclude [array]))
(defrecord SqlCall [name args])
(defn call
"Represents a SQL function call. Name should be a keyword."
[name & args]
(SqlCall. name args))
(defn read-sql-call [form]
;; late bind so that we get new class on REPL reset
(apply #?(:clj (resolve `call) :cljs call) form))
;;;;
(defrecord SqlRaw [s])
(defn raw
"Represents a raw SQL string"
[s]
(SqlRaw. (if (vector? s) s (str s))))
(defn read-sql-raw [form]
;; late bind, as above
(#?(:clj (resolve `raw) :cljs raw) form))
;;;;
(defrecord SqlParam [name])
(defn param
"Represents a SQL parameter which can be filled in later"
[name]
(SqlParam. name))
(defn param-name [^SqlParam param]
(.-name param))
(defn read-sql-param [form]
;; late bind, as above
(#?(:clj (resolve `param) :cljs param) form))
;;;;
(defrecord SqlArray [values])
(defn array
"Represents a SQL array."
[values]
(SqlArray. values))
(defn array-vals [^SqlArray a]
(.-values a))
(defn read-sql-array [form]
;; late bind, as above
(#?(:clj (resolve `array) :cljs array) form))
;;;;
(defrecord SqlInline [value])
(defprotocol Inlinable
(inline-str [x]))
(defn inline
"Prevents parameterization"
[value]
(SqlInline. value))
(defn read-sql-inline [form]
(#?(:clj (resolve `inline) :cljs inline) form))
#?(:clj
(do
(defmethod print-method SqlCall [^SqlCall o ^java.io.Writer w]
(.write w (str "#sql/call " (pr-str (into [(.-name o)] (.-args o))))))
(defmethod print-dup SqlCall [o w]
(print-method o w))
(defmethod print-method SqlRaw [^SqlRaw o ^java.io.Writer w]
(.write w (str "#sql/raw " (pr-str (.s o)))))
(defmethod print-dup SqlRaw [o w]
(print-method o w))
(defmethod print-method SqlParam [^SqlParam o ^java.io.Writer w]
(.write w (str "#sql/param " (pr-str (.name o)))))
(defmethod print-dup SqlParam [o w]
(print-method o w))
(defmethod print-method SqlArray [^SqlArray a ^java.io.Writer w]
(.write w (str "#sql/array " (pr-str (.values a)))))
(defmethod print-dup SqlArray [a w]
(print-method a w))
(defmethod print-method SqlInline [^SqlInline a ^java.io.Writer w]
(.write w (str "#sql/inline " (pr-str (.value a)))))
(defmethod print-dup SqlInline [a w]
(print-method a w))))

View file

@ -1,5 +0,0 @@
(ns honeysql.util)
(defmacro defalias [sym var-sym]
`(let [v# (var ~var-sym)]
(intern *ns* (with-meta (quote ~sym) (meta v#)) @v#)))

View file

@ -0,0 +1,54 @@
;; copyright (c) 2022-2024 sean corfield, all rights reserved
(ns honey.bigquery-test
(:refer-clojure :exclude [format])
(:require [clojure.test :refer [deftest is]]
[honey.sql :as sut])
#?(:clj (:import (clojure.lang ExceptionInfo))))
(deftest except-replace-tests
(is (= ["SELECT * FROM table WHERE id = ?" 1]
(sut/format {:select [:*] :from [:table] :where [:= :id 1]})))
(is (= ["SELECT * EXCEPT (a, b, c) FROM table WHERE id = ?" 1]
(sut/format {:select [[:* :except [:a :b :c]]] :from [:table] :where [:= :id 1]})))
(is (= ["SELECT table.* EXCEPT (a, b, c) FROM table WHERE id = ?" 1]
(sut/format {:select [[:table.* :except [:a :b :c]]] :from [:table] :where [:= :id 1]})))
(is (= ["SELECT * REPLACE (a * 100 AS b, 2 AS c) FROM table WHERE id = ?" 1]
(sut/format {:select [[:* :replace [[[:* :a [:inline 100]] :b] [[:inline 2] :c]]]] :from [:table] :where [:= :id 1]})))
(is (= ["SELECT * EXCEPT (a, b) REPLACE (2 AS c) FROM table WHERE id = ?" 1]
(sut/format {:select [[:* :except [:a :b] :replace [[[:inline 2] :c]]]] :from [:table] :where [:= :id 1]})))
(is (= ["SELECT * REPLACE (a * ? AS b, ? AS c) FROM table WHERE id = ?" 100 2 1]
(sut/format {:select [[:* :replace [[[:* :a 100] :b] [2 :c]]]] :from [:table] :where [:= :id 1]})))
(is (= ["SELECT * EXCEPT (a, b) REPLACE (? AS c) FROM table WHERE id = ?" 2 1]
(sut/format {:select [[:* :except [:a :b] :replace [[2 :c]]]] :from [:table] :where [:= :id 1]}))))
(deftest bad-select-tests
(is (thrown? ExceptionInfo
(sut/format {:select [[:* :except [:a] :bad]]})))
(is (thrown? ExceptionInfo
(sut/format {:select [[:* :except]]})))
(is (thrown? ExceptionInfo
(sut/format {:select [[:foo :bar :quux]]}))))
(deftest struct-array-tests
(is (= ["CREATE TABLE IF NOT EXISTS my_table (name STRING NOT NULL, my_struct STRUCT<name STRING NOT NULL, description STRING>, my_array ARRAY<STRING>)"]
(sut/format (-> {:create-table [:my-table :if-not-exists]
:with-columns
[[:name :string [:not nil]]
[:my_struct [:bigquery/struct [:name :string [:not nil]] [:description :string]]]
[:my_array [:bigquery/array :string]]]}))))
(is (= ["ALTER TABLE my_table ADD COLUMN IF NOT EXISTS name STRING, ADD COLUMN IF NOT EXISTS my_struct STRUCT<name STRING, description STRING>, ADD COLUMN IF NOT EXISTS my_array ARRAY<STRING>"]
(sut/format {:alter-table [:my-table
{:add-column [:name :string :if-not-exists]}
{:add-column [:my_struct [:bigquery/struct [:name :string] [:description :string]] :if-not-exists]}
{:add-column [:my_array [:bigquery/array :string] :if-not-exists]}]}))))
(deftest test-case-expr
(is (= ["SELECT CASE foo WHEN ? THEN ? WHEN ? THEN foo / ? ELSE ? END FROM bar"
1 -1 2 2 0]
(sut/format
{:select [[[:case-expr :foo
1 -1
2 [:/ :foo 2]
:else 0]]]
:from [:bar]}))))

92
test/honey/cache_test.clj Normal file
View file

@ -0,0 +1,92 @@
;; copyright (c) 2022-2024 sean corfield, all rights reserved
(ns honey.cache-test
(:refer-clojure :exclude [format group-by])
(:require [clojure.core.cache.wrapped :as cache]
[clojure.test :refer [deftest is]]
[honey.sql :as sut]
[honey.sql.helpers
:refer [select-distinct from join left-join right-join where
group-by having order-by limit offset]]))
(def big-complicated-map
(-> (select-distinct :f.* :b.baz :c.quux [:b.bla "bla-bla"]
[[:now]] [[:raw "@x := 10"]])
(from [:foo :f] [:baz :b])
(join :draq [:= :f.b :draq.x]
:eldr [:= :f.e :eldr.t])
(left-join [:clod :c] [:= :f.a :c.d])
(right-join :bock [:= :bock.z :c.e])
(where [:or
[:and [:= :f.a "bort"] [:not= :b.baz [:param :param1]]]
[:and [:< 1 2] [:< 2 3]]
[:in :f.e [1 [:param :param2] 3]]
[:between :f.e 10 20]])
(group-by :f.a :c.e)
(having [:< 0 :f.e])
(order-by [:b.baz :desc] :c.quux [:f.a :nulls-first])
(limit 50)
(offset 10)))
(defn- cache-size [cache] (-> cache (deref) (keys) (count)))
(deftest cache-tests
(let [cache (cache/basic-cache-factory {})]
(is (zero? (cache-size cache)))
(is (= ["SELECT * FROM table WHERE id = ?" 1]
(sut/format {:select [:*] :from [:table] :where [:= :id 1]}
{:cache cache})
(sut/format {:select [:*] :from [:table] :where [:= :id 1]}
{:cache cache})))
(is (= 1 (cache-size cache)))
(is (= (sut/format {:select [:*] :from [:table] :where [:= :id 2]})
(sut/format {:select [:*] :from [:table] :where [:= :id 2]}
{:cache cache})))
(is (= 2 (cache-size cache)))
(is (= (sut/format big-complicated-map {:params {:param1 "gabba" :param2 2}})
(sut/format big-complicated-map {:cache cache :params {:param1 "gabba" :param2 2}})
(sut/format big-complicated-map {:cache cache :params {:param1 "gabba" :param2 2}})))
(is (= 3 (cache-size cache)))
(is (= (sut/format big-complicated-map {:params {:param1 "foo" :param2 42}})
(sut/format big-complicated-map {:cache cache :params {:param1 "foo" :param2 42}})
(sut/format big-complicated-map {:cache cache :params {:param1 "foo" :param2 42}})))
(is (= 3 (cache-size cache)))
(println "Uncached, simple, embedded")
(time (dotimes [_ 100000]
(sut/format {:select [:*] :from [:table] :where [:= :id (rand-int 10)]})))
(println "Cached, simple, embedded")
(time (dotimes [_ 100000]
(sut/format {:select [:*] :from [:table] :where [:= :id (rand-int 10)]} {:cache cache})))
(is (= 11 (cache-size cache)))
(println "Uncached, complex, mixed")
(time (dotimes [_ 10000]
(sut/format big-complicated-map {:params {:param1 "gabba" :param2 (rand-int 10)}})))
(println "Cached, complex, mixed")
(time (dotimes [_ 10000]
(sut/format big-complicated-map {:cache cache :params {:param1 "gabba" :param2 (rand-int 10)}})))
(is (= 11 (cache-size cache))))
(let [cache (cache/basic-cache-factory {})]
(is (zero? (cache-size cache)))
(is (= ["SELECT * FROM table WHERE id = ?" 1]
(sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:cache cache :params {:id 1}})
(sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:cache cache :params {:id 1}})))
(is (= 1 (cache-size cache)))
(is (= (sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:params {:id 2}})
(sut/format {:select [:*] :from [:table] :where [:= :id :?id]}
{:cache cache :params {:id 2}})))
(is (= 1 (cache-size cache)))
;; different parameter names create different cache entries:
(is (= (sut/format {:select [:*] :from [:table] :where [:= :id :?x]}
{:cache cache :params {:x 2}})
(sut/format {:select [:*] :from [:table] :where [:= :id :?y]}
{:cache cache :params {:y 2}})))
(is (= 3 (cache-size cache)))
;; swapping parameter names creates different cache entries:
(is (= (sut/format {:select [:*] :from [:table] :where [:and [:= :id :?x] [:= :foo :?y]]}
{:cache cache :params {:x 2 :y 3}})
(sut/format {:select [:*] :from [:table] :where [:and [:= :id :?y] [:= :foo :?x]]}
{:cache cache :params {:x 3 :y 2}})))
(is (= 5 (cache-size cache)))))

19
test/honey/ops_test.cljc Normal file
View file

@ -0,0 +1,19 @@
;; copyright (c) 2023-2025 sean corfield, all rights reserved
(ns honey.ops-test
(:refer-clojure :exclude [format])
(:require [clojure.test :refer [deftest is]]
[honey.sql :as sut]))
(deftest issue-454
(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)))))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
;; copyright (c) 2022-2024 sean corfield, all rights reserved
(ns honey.sql.pg-ops-test
(:require [clojure.test :refer [deftest is testing]]
[honey.sql :as sql]
[honey.sql.pg-ops :as sut]))
(deftest pg-op-tests
(testing "built-in ops"
(is (= ["SELECT a || b AS x"]
(sql/format {:select [[[:|| :a :b] :x]]})))
(is (= ["SELECT a - b AS x"]
(sql/format {:select [[[:- :a :b] :x]]}))))
(testing "writable ops"
(is (= ["SELECT a -> b AS x"]
(sql/format {:select [[[:-> :a :b] :x]]})))
(is (= ["SELECT a ->> b AS x"]
(sql/format {:select [[[:->> :a :b] :x]]})))
(is (= ["SELECT a #> b AS x"]
(sql/format {:select [[[:#> :a :b] :x]]})))
(is (= ["SELECT a #>> b AS x"]
(sql/format {:select [[[:#>> :a :b] :x]]})))
(is (= ["SELECT a ?? b AS x"]
(sql/format {:select [[[:? :a :b] :x]]})))
(is (= ["SELECT a ??| b AS x"]
(sql/format {:select [[[:?| :a :b] :x]]})))
(is (= ["SELECT a ??& b AS x"]
(sql/format {:select [[[:?& :a :b] :x]]})))
(is (= ["SELECT a #- b AS x"]
(sql/format {:select [[[:#- :a :b] :x]]}))))
(testing "named ops"
(is (= ["SELECT a @> b AS x"]
(sql/format {:select [[[sut/at> :a :b] :x]]})))
(is (= ["SELECT a <@ b AS x"]
(sql/format {:select [[[sut/<at :a :b] :x]]})))
(is (= ["SELECT a @?? b AS x"]
(sql/format {:select [[[sut/at? :a :b] :x]]})))
(is (= ["SELECT a @@ b AS x"]
(sql/format {:select [[[sut/atat :a :b] :x]]}))))
(testing "variadic ops"
(is (= ["SELECT a -> b -> c AS x"]
(sql/format {:select [[[:-> :a :b :c] :x]]})))
(is (= ["SELECT a || b || c AS x"]
(sql/format {:select [[[:|| :a :b :c] :x]]})))))

View file

@ -0,0 +1,457 @@
;; copied from https://github.com/nilenso/honeysql-postgres
;; on 2021-02-13 to verify the completeness of support for
;; those features within HoneySQL 2.x
;; where there are differences, the original code is kept
;; with #_ and the modified code follows it (aside from
;; the ns form which has numerous changes to both match
;; the structure of HoneySQL 2.x and to work with cljs)
(ns honey.sql.postgres-test
(:refer-clojure :exclude [update partition-by set])
(:require [clojure.test :refer [deftest is testing]]
;; pull in all the PostgreSQL helpers that the nilenso
;; library provided (as well as the regular HoneySQL ones):
[honey.sql.helpers :as sqlh :refer
[upsert on-conflict do-nothing on-constraint
returning do-update-set
;; not needed because do-update-set can do this directly
#_do-update-set!
alter-table rename-column drop-column
add-column partition-by
;; not needed because insert-into can do this directly
#_insert-into-as
create-table rename-table drop-table
window create-view over with-columns
create-extension drop-extension
select-distinct-on
;; already part of HoneySQL
insert-into values where select
from order-by update set]]
[honey.sql :as sql]))
(deftest upsert-test
(testing "upsert sql generation for postgresql"
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?), (?, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname RETURNING *" 5 "Gizmo Transglobal" 6 "Associated Computing, Inc"]
;; preferred in honeysql:
(-> (insert-into :distributors)
(values [{:did 5 :dname "Gizmo Transglobal"}
{:did 6 :dname "Associated Computing, Inc"}])
(on-conflict :did)
(do-update-set :dname)
(returning :*)
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?), (?, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname RETURNING *" 5 "Gizmo Transglobal" 6 "Associated Computing, Inc"]
;; identical to nilenso version:
(-> (insert-into :distributors)
(values [{:did 5 :dname "Gizmo Transglobal"}
{:did 6 :dname "Associated Computing, Inc"}])
(upsert (-> (on-conflict :did)
(do-update-set :dname)))
(returning :*)
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did) DO NOTHING" 7 "Redline GmbH"]
;; preferred in honeysql:
(-> (insert-into :distributors)
(values [{:did 7 :dname "Redline GmbH"}])
(on-conflict :did)
do-nothing
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did) DO NOTHING" 7 "Redline GmbH"]
;; identical to nilenso version:
(-> (insert-into :distributors)
(values [{:did 7 :dname "Redline GmbH"}])
(upsert (-> (on-conflict :did)
do-nothing))
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"]
;; preferred in honeysql:
(-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
(on-conflict (on-constraint :distributors_pkey))
do-nothing
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did) ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"]
;; with both name and clause:
(-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
(on-conflict :did (on-constraint :distributors_pkey))
do-nothing
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did, dname) ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"]
;; with multiple names and a clause:
(-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
(on-conflict :did :dname (on-constraint :distributors_pkey))
do-nothing
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" 9 "Antwerp Design"]
;; almost identical to nilenso version:
(-> (insert-into :distributors)
(values [{:did 9 :dname "Antwerp Design"}])
;; in nilenso, this was (on-conflict-constraint :distributors_pkey)
(upsert (-> (on-conflict (on-constraint :distributors_pkey))
do-nothing))
sql/format)))
(is (= ["INSERT INTO foo (id, data) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET into = ((STATE(?), MODIFIED(NOW()))) WHERE state = ?" 1 42 "enabled" "disabled"]
(sql/format (-> (insert-into :foo)
(values [{:id 1 :data 42}])
(upsert (-> (on-conflict :id)
(do-update-set [:state "enabled"]
[:modified [:now]])
(where [:= :state "disabled"])))))))
(is (= ["INSERT INTO foo (id, data) VALUES (?, ?) ON CONFLICT (id) DO UPDATE SET state = ?, modified = NOW() WHERE state = ?" 1 42 "enabled" "disabled"]
(sql/format (-> (insert-into :foo)
(values [{:id 1 :data 42}])
(upsert (-> (on-conflict :id)
(do-update-set {:state "enabled"
:modified [:now]})
(where [:= :state "disabled"])))))))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?), (?, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname" 10 "Pinp Design" 11 "Foo Bar Works"]
(sql/format {:insert-into :distributors
:values [{:did 10 :dname "Pinp Design"}
{:did 11 :dname "Foo Bar Works"}]
;; in nilenso, these two were a submap under :upsert
:on-conflict :did
:do-update-set :dname})))
(is (= ["INSERT INTO distributors (did, dname) VALUES (?, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname || ? || d.dname || ?" 23 "Foo Distributors" " (formerly " ")"]
(-> (insert-into :distributors)
(values [{:did 23 :dname "Foo Distributors"}])
(on-conflict :did)
;; nilenso:
#_(do-update-set! [:dname "EXCLUDED.dname || ' (formerly ' || d.dname || ')'"])
;; honeysql
(do-update-set {:dname [:|| :EXCLUDED.dname " (formerly " :d.dname ")"]})
sql/format)))
(is (= ["INSERT INTO distributors (did, dname) SELECT ?, ? ON CONFLICT ON CONSTRAINT distributors_pkey DO NOTHING" 1 "whatever"]
;; honeysql version:
(-> (insert-into :distributors
[:did :dname]
(select 1 "whatever"))
(on-conflict (on-constraint :distributors_pkey))
do-nothing
sql/format)
;; nilenso version:
#_(-> (insert-into :distributors)
(columns :did :dname)
(query-values (select 1 "whatever"))
(upsert (-> (on-conflict-constraint :distributors_pkey)
do-nothing))
sql/format)))))
(deftest upsert-where-test
(is (= ["INSERT INTO user (phone, name) VALUES (?, ?) ON CONFLICT (phone) WHERE phone IS NOT NULL DO UPDATE SET phone = EXCLUDED.phone, name = EXCLUDED.name WHERE user.active = FALSE" "5555555" "John"]
(sql/format
{:insert-into :user
:values [{:phone "5555555" :name "John"}]
:on-conflict [:phone
{:where [:<> :phone nil]}]
:do-update-set {:fields [:phone :name]
:where [:= :user.active false]}})
;; nilenso version
#_(sql/format
{:insert-into :user
:values [{:phone "5555555" :name "John"}]
;; nested under :upsert
:upsert {:on-conflict [:phone]
;; but :where is at the same level as :on-conflict
:where [:<> :phone nil]
;; this is the same as in honeysql:
:do-update-set {:fields [:phone :name]
:where [:= :user.active false]}}}))))
(deftest returning-test
(testing "returning clause in sql generation for postgresql"
(is (= ["DELETE FROM distributors WHERE did > 10 RETURNING *"]
(sql/format {:delete-from :distributors
:where [:> :did :10]
:returning [:*]})))
(is (= ["UPDATE distributors SET dname = ? WHERE did = 2 RETURNING did, dname" "Foo Bar Designs"]
(-> (update :distributors)
(set {:dname "Foo Bar Designs"})
(where [:= :did :2])
(returning :did :dname)
sql/format)))))
(deftest create-view-test
(testing "creating a view from a table"
(is (= ["CREATE VIEW metro AS SELECT * FROM cities WHERE metroflag = ?" "Y"]
(-> (create-view :metro)
(select :*)
(from :cities)
(where [:= :metroflag "Y"])
sql/format)))))
(deftest drop-table-test
(testing "drop table sql generation for a single table"
(is (= ["DROP TABLE cities"]
(sql/format (drop-table :cities)))))
(testing "drop table sql generation for multiple tables"
(is (= ["DROP TABLE cities, towns, vilages"]
(sql/format (drop-table :cities :towns :vilages))))))
(deftest create-table-test
;; the nilenso versions of these tests required sql/call for function-like syntax
(testing "create table with two columns"
(is (= ["CREATE TABLE cities (city VARCHAR(80) PRIMARY KEY, location POINT)"]
(-> (create-table :cities)
(with-columns [[:city [:varchar 80] [:primary-key]]
[:location :point]])
sql/format))))
(testing "create table with foreign key reference"
(is (= ["CREATE TABLE weather (city VARCHAR(80) REFERENCES cities(city), temp_lo INT, temp_hi INT, prcp REAL, date DATE)"]
(-> (create-table :weather)
(with-columns [[:city [:varchar :80] [:references :cities :city]]
[:temp_lo :int]
[:temp_hi :int]
[:prcp :real]
[:date :date]])
sql/format))))
(testing "creating table with table level constraint"
(is (= ["CREATE TABLE films (code CHAR(5), title VARCHAR(40), did INTEGER, date_prod DATE, kind VARCHAR(10), CONSTRAINT code_title PRIMARY KEY(code, title))"]
(-> (create-table :films)
(with-columns [[:code [:char 5]]
[:title [:varchar 40]]
[:did :integer]
[:date_prod :date]
[:kind [:varchar 10]]
[[:constraint :code_title] [:primary-key :code :title]]])
sql/format))))
(testing "creating table with column level constraint"
(is (= ["CREATE TABLE films (code CHAR(5) CONSTRAINT firstkey PRIMARY KEY, title VARCHAR(40) NOT NULL, did INTEGER NOT NULL, date_prod DATE, kind VARCHAR(10))"]
(-> (create-table :films)
(with-columns [[:code [:char 5] [:constraint :firstkey] [:primary-key]]
[:title [:varchar 40] [:not nil]]
[:did :integer [:not nil]]
[:date_prod :date]
[:kind [:varchar 10]]])
sql/format))))
(testing "creating table with columns with default values"
(is (= ["CREATE TABLE distributors (did INTEGER PRIMARY KEY DEFAULT NEXTVAL('serial'), name VARCHAR(40) NOT NULL)"]
(-> (create-table :distributors)
(with-columns [[:did :integer [:primary-key] [:default [:nextval "serial"]]]
[:name [:varchar 40] [:not nil]]])
sql/format))))
(testing "creating table with column checks"
(is (= ["CREATE TABLE products (product_no INTEGER, name TEXT, price NUMERIC CHECK(price > 0), discounted_price NUMERIC, CHECK((discounted_price > 0) AND (price > discounted_price)))"]
(-> (create-table :products)
(with-columns [[:product_no :integer]
[:name :text]
[:price :numeric [:check [:> :price 0]]]
[:discounted_price :numeric]
[[:check [:and [:> :discounted_price 0] [:> :price :discounted_price]]]]])
sql/format)))))
(deftest references-issue-386
(is (= ["CREATE TABLE IF NOT EXISTS user (id VARCHAR(255) NOT NULL PRIMARY KEY, company_id INT NOT NULL, name VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY(company_id) REFERENCES company(id))"]
(-> {:create-table [:user :if-not-exists]
:with-columns
[[:id [:varchar 255] [:not nil] [:primary-key]]
[:company-id :int [:not nil]]
[:name [:varchar 255] [:not nil]]
[:password [:varchar 255] [:not nil]]
[:created-time :datetime [:default :CURRENT_TIMESTAMP]]
[:updated-time :datetime [:default :CURRENT_TIMESTAMP]
:on :update :CURRENT_TIMESTAMP]
[[:foreign-key :company-id] [:references :company :id]]]}
(sql/format)))))
(deftest create-table-issue-437
(is (= ["CREATE TABLE bar (did UUID DEFAULT GEN_RANDOM_UUID(), foo_id VARCHAR NOT NULL, PRIMARY KEY(did, foo_id), FOREIGN KEY(foo_id) REFERENCES foo(id) ON DELETE CASCADE)"]
(-> (create-table :bar)
(with-columns
[[:did :uuid [:default [:gen_random_uuid]]]
[:foo-id :varchar [:not nil]]
[[:primary-key :did :foo-id]]
[[:foreign-key :foo-id]
[:references :foo :id]
:on-delete :cascade]])
(sql/format)))))
(deftest over-test
(testing "simple window statement"
(is (= ["SELECT AVG(salary) OVER w FROM employee WINDOW w AS (PARTITION BY department ORDER BY salary ASC)"]
(sql/format {:select [[[:over [[:avg :salary] :w]]]]
:from :employee
:window [:w {:partition-by :department
:order-by :salary}]}))))
(testing "window function over on select statemt"
(is (= ["SELECT id, AVG(salary) OVER (PARTITION BY department ORDER BY designation ASC) AS Average, MAX(salary) OVER w AS MaxSalary FROM employee WINDOW w AS (PARTITION BY department)"]
;; honeysql treats over as a function:
(-> (select :id
(over
[[:avg :salary] (-> (partition-by :department) (order-by [:designation])) :Average]
[[:max :salary] :w :MaxSalary]))
(from :employee)
(window :w (partition-by :department))
sql/format)
;; nilenso treated over as a clause
#_(-> (select :id)
(over
[[:avg :salary] (-> (partition-by :department) (order-by [:designation])) :Average]
[[:max :salary] :w :MaxSalary])
(from :employee)
(window :w (partition-by :department))
sql/format)))))
(deftest alter-table-test
(testing "alter table add column generates the required sql"
(is (= ["ALTER TABLE employees ADD COLUMN address TEXT"]
(-> (alter-table :employees)
(add-column :address :text)
sql/format))))
(testing "alter table drop column generates the required sql"
(is (= ["ALTER TABLE employees DROP COLUMN address"]
(-> (alter-table :employees)
(drop-column :address)
sql/format))))
(testing "alter table rename column generates the requred sql"
(is (= ["ALTER TABLE employees RENAME COLUMN address TO homeaddress"]
(-> (alter-table :employees)
(rename-column :address :homeaddress)
sql/format))))
(testing "alter table rename table generates the required sql"
(is (= ["ALTER TABLE employees RENAME TO managers"]
(-> (alter-table :employees)
(rename-table :managers)
sql/format)))))
(deftest insert-into-with-alias
(testing "insert into with alias"
(is (= ["INSERT INTO distributors AS d (did, dname) VALUES (?, ?), (?, ?) ON CONFLICT (did) DO UPDATE SET dname = EXCLUDED.dname WHERE d.zipcode <> ? RETURNING d.*" 5 "Gizmo Transglobal" 6 "Associated Computing, Inc" "21201"]
;; honeysql supports alias in insert-into:
(-> (insert-into :distributors :d)
;; nilensor required insert-into-as:
#_(insert-into-as :distributors :d)
(values [{:did 5 :dname "Gizmo Transglobal"}
{:did 6 :dname "Associated Computing, Inc"}])
(on-conflict :did)
;; honeysql supports names and a where clause:
(do-update-set :dname (where [:<> :d.zipcode "21201"]))
;; nilenso nested those under upsert:
#_(upsert (-> (on-conflict :did)
(do-update-set :dname)
(where [:<> :d.zipcode "21201"])))
(returning :d.*)
sql/format)))))
(deftest create-table-if-not-exists
(testing "create a table if not exists"
(is (= ["CREATE TABLE IF NOT EXISTS tablename"]
(-> (create-table :tablename :if-not-exists)
sql/format)))))
(deftest drop-table-if-exists
(testing "drop a table if it exists"
(is (= ["DROP TABLE IF EXISTS t1, t2, t3"]
(-> (drop-table :if-exists :t1 :t2 :t3)
sql/format)))))
(deftest select-where-ilike
(testing "select from table with ILIKE operator"
(is (= ["SELECT * FROM products WHERE name ILIKE ?" "%name%"]
(-> (select :*)
(from :products)
(where [:ilike :name "%name%"])
sql/format)))))
(deftest select-where-not-ilike
(testing "select from table with NOT ILIKE operator"
(is (= ["SELECT * FROM products WHERE name NOT ILIKE ?" "%name%"]
(-> (select :*)
(from :products)
(where [:not-ilike :name "%name%"])
sql/format)))))
(deftest values-except-select
(testing "select which values are not not present in a table"
(is (= ["VALUES (?), (?), (?) EXCEPT SELECT id FROM images" 4 5 6]
(sql/format
{:except
[{:values [[4] [5] [6]]}
{:select [:id] :from [:images]}]})))))
(deftest select-except-select
(testing "select which rows are not present in another table"
(is (= ["SELECT ip EXCEPT SELECT ip FROM ip_location"]
(sql/format
{:except
[{:select [:ip]}
{:select [:ip] :from [:ip_location]}]})))))
(deftest values-except-all-select
(testing "select which values are not not present in a table"
(is (= ["VALUES (?), (?), (?) EXCEPT ALL SELECT id FROM images" 4 5 6]
(sql/format
{:except-all
[{:values [[4] [5] [6]]}
{:select [:id] :from [:images]}]})))))
(deftest select-except-all-select
(testing "select which rows are not present in another table"
(is (= ["SELECT ip EXCEPT ALL SELECT ip FROM ip_location"]
(sql/format
{:except-all
[{:select [:ip]}
{:select [:ip] :from [:ip_location]}]})))))
(deftest select-distinct-on-test
(testing "select distinct on"
(is (= ["SELECT DISTINCT ON(\"a\", \"b\") \"c\" FROM \"products\""]
;; honeysql has select-distinct-on:
(-> (select-distinct-on [:a :b] :c)
(from :products)
(sql/format {:quoted true}))
;; nilenso handled that via modifiers:
#_(-> (select :c)
(from :products)
(modifiers :distinct-on :a :b)
(sql/format :quoting :ansi))))))
(deftest select-agg-order-by-test
(testing "single expression in order by"
(is (= ["SELECT ARRAY_AGG(a ORDER BY x ASC) FROM products"]
(sql/format
{:select [[[:array_agg [:order-by :a :x]]]]
:from :products}))))
(testing "multiple expressions in order by"
(is (= ["SELECT ARRAY_AGG(a ORDER BY x ASC, y DESC, z ASC) FROM products"]
(sql/format
{:select [[[:array_agg [:order-by :a [:x :asc] [:y :desc] :z]]]]
:from :products})))))
(deftest create-extension-test
;; previously, honeysql required :allow-dashed-names? true
(testing "create extension"
(is (= ["CREATE EXTENSION \"uuid-ossp\""]
(-> (create-extension :uuid-ossp)
(sql/format {:quoted true})))))
(testing "create extension if not exists"
(is (= ["CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\""]
(-> (create-extension :uuid-ossp :if-not-exists)
(sql/format {:quoted true}))))))
(deftest drop-extension-test
;; previously, honeysql required :allow-dashed-names? true
(testing "create extension"
(is (= ["DROP EXTENSION \"uuid-ossp\""]
(-> (drop-extension :uuid-ossp)
(sql/format {:quoted true}))))))
(deftest issue-453-constraint
(testing "standalone constraint"
(is (= ["CREATE TABLE bar (a INTEGER, b INTEGER, CONSTRAINT foo_natural_key UNIQUE (a, b))"]
(-> {:create-table [:bar]
:with-columns
[[:a :integer]
[:b :integer]
[[:constraint :foo_natural_key] :unique [:composite :a :b]]]}
(sql/format)))))
(testing "inline constraint"
(is (= ["CREATE TABLE foo (a INTEGER CONSTRAINT a_pos CHECK(a > 0), b INTEGER, CONSTRAINT a_bigger CHECK(b < a))"]
(-> '{create-table foo
with-columns
((a integer (constraint a_pos) (check (> a 0)))
(b integer)
((constraint a_bigger) (check (< b a))))}
(sql/format))))))

View file

@ -0,0 +1,148 @@
;; 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]]))
(deftest select-tests
(testing "select, exclude, rename"
(is (= ["SELECT * EXCLUDE _id RENAME value AS foo_value FROM foo"]
(sql/format (-> (select :*) (exclude :_id) (rename [:value :foo_value])
(from :foo)))))
(is (= ["SELECT * EXCLUDE (_id, a) RENAME value AS foo_value FROM foo"]
(sql/format (-> (select :*) (exclude :_id :a) (rename [:value :foo_value])
(from :foo)))))
(is (= ["SELECT * EXCLUDE _id RENAME (value AS foo_value, a AS b) FROM foo"]
(sql/format (-> (select :*) (exclude :_id)
(rename [:value :foo_value]
[:a :b])
(from :foo)))))
(is (= ["SELECT * EXCLUDE _id RENAME value AS foo_value, c.x FROM foo"]
(sql/format (-> (select [:* (-> (exclude :_id) (rename [:value :foo_value]))]
:c.x)
(from :foo)))))
(is (= ["SELECT * EXCLUDE (_id, a) RENAME value AS foo_value, c.x FROM foo"]
(sql/format (-> (select [:* (-> (exclude :_id :a) (rename [:value :foo_value]))]
:c.x)
(from :foo)))))
(is (= ["SELECT * EXCLUDE _id RENAME (value AS foo_value, a AS b), c.x FROM foo"]
(sql/format (-> (select [:* (-> (exclude :_id)
(rename [:value :foo_value]
[:a :b]))]
:c.x)
(from :foo))))))
(testing "select, nest_one, nest_many"
(is (= ["SELECT a._id, NEST_ONE (SELECT * FROM foo AS b WHERE b_id = a._id) FROM bar AS a"]
(sql/format '{select (a._id,
((nest_one {select * from ((foo b)) where (= b_id a._id)})))
from ((bar a))})))
(is (= ["SELECT a._id, NEST_MANY (SELECT * FROM foo AS b) FROM bar AS a"]
(sql/format '{select (a._id,
((nest_many {select * from ((foo b))})))
from ((bar a))})))))
(deftest dotted-array-access-tests
(is (= ["SELECT (a.b).c"] ; old, partial support:
(sql/format '{select (((. (nest :a.b) :c)))})))
(is (= ["SELECT (a.b).c"] ; new, complete support:
(sql/format '{select (((:get-in :a.b :c)))})))
(is (= ["SELECT (a).b.c"] ; the first expression is always parenthesized:
(sql/format '{select (((:get-in :a :b :c)))}))))
(deftest erase-from-test
(is (= ["ERASE FROM foo WHERE foo.id = ?" 42]
(-> {:erase-from :foo
:where [:= :foo.id 42]}
(sql/format))))
(is (= ["ERASE FROM foo WHERE foo.id = ?" 42]
(-> (h/erase-from :foo)
(h/where [:= :foo.id 42])
(sql/format)))))
(deftest inline-record-body
(is (= ["{_id: 1, name: 'foo', info: {contact: [{loc: 'home', tel: '123'}, {loc: 'work', tel: '456'}]}}"]
(sql/format [:inline {:_id 1 :name "foo"
:info {:contact [{:loc "home" :tel "123"}
{:loc "work" :tel "456"}]}}]))))
(deftest records-statement
(testing "auto-lift maps"
(is (= ["RECORDS ?, ?" {:_id 1 :name "cat"} {:_id 2 :name "dog"}]
(sql/format {:records [{:_id 1 :name "cat"}
{:_id 2 :name "dog"}]}))))
(testing "explicit inline"
(is (= ["RECORDS {_id: 1, name: 'cat'}, {_id: 2, name: 'dog'}"]
(sql/format {:records [[:inline {:_id 1 :name "cat"}]
[:inline {:_id 2 :name "dog"}]]}))))
(testing "insert with records"
(is (= ["INSERT INTO foo RECORDS {_id: 1, name: 'cat'}, {_id: 2, name: 'dog'}"]
(sql/format {:insert-into :foo
:records [[:inline {:_id 1 :name "cat"}]
[:inline {:_id 2 :name "dog"}]]})))
(is (= ["INSERT INTO foo RECORDS {_id: 1, name: 'cat'}, {_id: 2, name: 'dog'}"]
(sql/format {:insert-into :foo
:records [[:inline {:_id 1 :name "cat"}]
[:inline {:_id 2 :name "dog"}]]})))
(is (= ["INSERT INTO foo RECORDS ?, ?" {:_id 1 :name "cat"} {:_id 2 :name "dog"}]
(sql/format {:insert-into [:foo ; as a sub-clause
{:records [{:_id 1 :name "cat"}
{:_id 2 :name "dog"}]}]})))))
(deftest patch-statement
(testing "patch with records"
(is (= ["PATCH INTO foo RECORDS {_id: 1, name: 'cat'}, {_id: 2, name: 'dog'}"]
(sql/format {:patch-into [:foo]
:records [[:inline {:_id 1 :name "cat"}]
[:inline {:_id 2 :name "dog"}]]})))
(is (= ["PATCH INTO foo RECORDS ?, ?" {:_id 1 :name "cat"} {:_id 2 :name "dog"}]
(sql/format {:patch-into [:foo ; as a sub-clause
{:records [{:_id 1 :name "cat"}
{:_id 2 :name "dog"}]}]})))
(is (= ["PATCH INTO foo RECORDS ?, ?" {:_id 1 :name "cat"} {:_id 2 :name "dog"}]
(sql/format (h/patch-into :foo
(h/records [{:_id 1 :name "cat"}
{:_id 2 :name "dog"}])))))))
(deftest object-record-expr
(testing "object literal"
(is (= ["SELECT OBJECT (_id: 1, name: 'foo')"]
(sql/format {:select [[[:object {:_id 1 :name "foo"}]]]})))
(is (= ["SELECT OBJECT (_id: 1, name: 'foo')"]
(sql/format '{select (((:object {:_id 1 :name "foo"})))}))))
(testing "record literal"
(is (= ["SELECT RECORD (_id: 1, name: 'foo')"]
(sql/format {:select [[[:record {:_id 1 :name "foo"}]]]})))
(is (= ["SELECT RECORD (_id: 1, name: 'foo')"]
(sql/format '{select (((:record {:_id 1 :name "foo"})))}))))
(testing "inline map literal"
(is (= ["SELECT {_id: 1, name: 'foo'}"]
(sql/format {:select [[[:inline {:_id 1 :name "foo"}]]]})))))
(deftest navigation-dot-index
(is (= ["SELECT (a.b).c[1].d"]
(sql/format '{select (((get-in a.b c 1 d)))})))
(is (= ["SELECT (a.b).c[?].d" 1]
(sql/format '{select (((get-in a.b c (lift 1) d)))})))
(is (= ["SELECT (a.b).c[?].d" 1]
(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}))))))

1530
test/honey/sql_test.cljc Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
(ns honey.unhashable-test
(:require [clojure.test :refer [deftest is]]
[honey.sql :as sut]))
(deftest unhashable-value-509
(let [unhashable (reify Object
(toString [_] "unhashable")
(hashCode [_] (throw (ex-info "Unsupported" {}))))]
(is (= ["INSERT INTO table VALUES (?)" unhashable]
(sut/format {:insert-into :table :values [[unhashable]]})))))

View file

@ -0,0 +1,51 @@
;; copyright (c) 2023-2024 sean corfield, all rights reserved
(ns honey.union-test
(:refer-clojure :exclude [format])
(:require [clojure.test :refer [deftest is]]
[honey.sql :as sut]))
(deftest issue-451
(is (= [(str "SELECT ids.id AS id"
" FROM ((SELECT dimension.human_readable_field_id AS id"
" FROM dimension AS dimension"
" WHERE (dimension.field_id = ?) AND (dimension.human_readable_field_id IS NOT NULL)"
" LIMIT ?)"
" UNION"
" (SELECT dest.id AS id"
" FROM field AS source"
" LEFT JOIN table AS table ON source.table_id = table.id"
" LEFT JOIN field AS dest ON dest.table_id = table.id"
" WHERE (source.id = ?) AND (source.semantic_type IN (?)) AND (dest.semantic_type IN (?))"
" LIMIT ?)) AS ids"
" LIMIT ?")
1
1
1
"type/PK"
"type/Name"
1
1]
(-> {:select [[:ids.id :id]]
:from [[{:union
[{:nest
{:select [[:dimension.human_readable_field_id :id]]
:from [[:dimension :dimension]]
:where [:and
[:= :dimension.field_id 1]
[:not= :dimension.human_readable_field_id nil]]
:limit 1}}
{:nest
{:select [[:dest.id :id]]
:from [[:field :source]]
:left-join [[:table :table] [:= :source.table_id :table.id] [:field :dest] [:= :dest.table_id :table.id]]
:where [:and
[:= :source.id 1]
[:in :source.semantic_type #{"type/PK"}]
[:in :dest.semantic_type #{"type/Name"}]]
:limit 1}}]}
:ids]]
:limit 1}
(sut/format))))
)

62
test/honey/util_test.cljc Normal file
View file

@ -0,0 +1,62 @@
(ns honey.util-test
(:refer-clojure :exclude [str])
(:require [clojure.test :refer [deftest is are]]
[honey.sql.util :as sut]))
(deftest str-test
(are [arg1 result] (= result (sut/str arg1))
nil ""
1 "1"
"foo" "foo"
:foo ":foo")
(are [arg1 arg2 result] (= result (sut/str arg1 arg2))
nil nil ""
nil 1 "1"
1 nil "1"
1 2 "12"
:foo "bar" ":foobar")
(are [arg1 arg2 arg3 result] (= result (sut/str arg1 arg2 arg3))
nil nil nil ""
nil 1 nil "1"
1 nil nil "1"
1 nil 2 "12"
:foo "bar" 'baz ":foobarbaz")
(are [args result] (= result (apply sut/str args))
(range 10) "0123456789"
[] ""))
(deftest join-test
(is (= "0123456789" (sut/join "" (range 10))))
(is (= "1" (sut/join "" [1])))
(is (= "" (sut/join "" [])))
(is (= "0, 1, 2, 3, 4, 5, 6, 7, 8, 9" (sut/join ", " (range 10))))
(is (= "1" (sut/join ", " [1])))
(is (= "" (sut/join ", " [])))
(is (= "0_0, 1_1, 2_2, 3_3, 4_4, 5_5, 6_6, 7_7, 8_8, 9_9"
(sut/join ", " (map #(sut/str % "_" %)) (range 10))))
(is (= "1_1"
(sut/join ", " (map #(sut/str % "_" %)) [1])))
(is (= ""
(sut/join ", " (map #(sut/str % "_" %)) [])))
(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]))))

View file

@ -1,341 +0,0 @@
(ns honeysql.core-test
(:refer-clojure :exclude [format update])
(:require [#?@(:clj [clojure.test :refer]
:cljs [cljs.test :refer-macros]) [deftest testing is]]
[honeysql.core :as sql]
[honeysql.format :as sql-f]
[honeysql.helpers :refer [select modifiers from join left-join
right-join full-join cross-join
where group having
order-by limit offset values columns
insert-into with merge-where merge-having]]
honeysql.format-test))
;; TODO: more tests
(deftest test-select
(let [m1 (-> (with [:cte (-> (select :*)
(from :example)
(where [:= :example-column 0]))])
(select :f.* :b.baz :c.quux [:b.bla :bla-bla]
:%now (sql/raw "@x := 10"))
;;(un-select :c.quux)
(modifiers :distinct)
(from [:foo :f] [:baz :b])
(join :draq [:= :f.b :draq.x])
(left-join [:clod :c] [:= :f.a :c.d])
(right-join :bock [:= :bock.z :c.e])
(full-join :beck [:= :beck.x :c.y])
(where [:or
[:and [:= :f.a "bort"] [:not= :b.baz :?param1]]
[:< 1 2 3]
[:in :f.e [1 (sql/param :param2) 3]]
[:between :f.e 10 20]])
;;(merge-where [:not= nil :b.bla])
(group :f.a)
(having [:< 0 :f.e])
(order-by [:b.baz :desc] :c.quux [:f.a :nulls-first])
(limit 50)
(offset 10))
m2 {:with [[:cte {:select [:*]
:from [:example]
:where [:= :example-column 0]}]]
:select [:f.* :b.baz :c.quux [:b.bla :bla-bla]
:%now (sql/raw "@x := 10")]
;;:un-select :c.quux
:modifiers :distinct
:from [[:foo :f] [:baz :b]]
:join [:draq [:= :f.b :draq.x]]
:left-join [[:clod :c] [:= :f.a :c.d]]
:right-join [:bock [:= :bock.z :c.e]]
:full-join [:beck [:= :beck.x :c.y]]
:where [:or
[:and [:= :f.a "bort"] [:not= :b.baz :?param1]]
[:< 1 2 3]
[:in :f.e [1 (sql/param :param2) 3]]
[:between :f.e 10 20]]
;;:merge-where [:not= nil :b.bla]
:group-by :f.a
:having [:< 0 :f.e]
:order-by [[:b.baz :desc] :c.quux [:f.a :nulls-first]]
:limit 50
:offset 10}
m3 (sql/build m2)
m4 (apply sql/build (apply concat m2))]
(testing "Various construction methods are consistent"
(is (= m1 m3 m4)))
(testing "SQL data formats correctly"
(is (= ["WITH cte AS (SELECT * FROM example WHERE example_column = ?) SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS bla_bla, now(), @x := 10 FROM foo f, baz b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = ? AND b.baz <> ?) OR (? < ? AND ? < ?) OR (f.e in (?, ?, ?)) OR f.e BETWEEN ? AND ?) GROUP BY f.a HAVING ? < f.e ORDER BY b.baz DESC, c.quux, f.a NULLS FIRST LIMIT ? OFFSET ? "
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
(sql/format m1 {:param1 "gabba" :param2 2}))))
#?(:clj (testing "SQL data prints and reads correctly"
(is (= m1 (read-string (pr-str m1))))))
(testing "SQL data formats correctly with alternate param naming"
(is (= (sql/format m1 :params {:param1 "gabba" :param2 2} :parameterizer :postgresql)
["WITH cte AS (SELECT * FROM example WHERE example_column = $1) SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS bla_bla, now(), @x := 10 FROM foo f, baz b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = $2 AND b.baz <> $3) OR ($4 < $5 AND $6 < $7) OR (f.e in ($8, $9, $10)) OR f.e BETWEEN $11 AND $12) GROUP BY f.a HAVING $13 < f.e ORDER BY b.baz DESC, c.quux, f.a NULLS FIRST LIMIT $14 OFFSET $15 "
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10])))
(testing "Locking"
(is (= ["WITH cte AS (SELECT * FROM example WHERE example_column = ?) SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS bla_bla, now(), @x := 10 FROM foo f, baz b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = ? AND b.baz <> ?) OR (? < ? AND ? < ?) OR (f.e in (?, ?, ?)) OR f.e BETWEEN ? AND ?) GROUP BY f.a HAVING ? < f.e ORDER BY b.baz DESC, c.quux, f.a NULLS FIRST LIMIT ? OFFSET ? FOR UPDATE "
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
(sql/format (assoc m1 :lock {:mode :update})
{:param1 "gabba" :param2 2}))))))
(deftest test-cast
(is (= ["SELECT foo, CAST(bar AS integer)"]
(sql/format {:select [:foo (sql/call :cast :bar :integer)]})))
(is (= ["SELECT foo, CAST(bar AS integer)"]
(sql/format {:select [:foo (sql/call :cast :bar 'integer)]}))))
(deftest test-value
(is (= ["INSERT INTO foo (bar) VALUES (?)" {:baz "my-val"}]
(->
(insert-into :foo)
(columns :bar)
(values [[(sql-f/value {:baz "my-val"})]])
sql/format)))
(is (= ["INSERT INTO foo (a, b, c) VALUES (?, ?, ?), (?, ?, ?)"
"a" "b" "c" "a" "b" "c"]
(-> (insert-into :foo)
(values [(array-map :a "a" :b "b" :c "c")
(hash-map :a "a" :b "b" :c "c")])
sql/format))))
(deftest test-operators
(testing "="
(testing "with nil"
(is (= ["SELECT * FROM customers WHERE name IS NULL"]
(sql/format {:select [:*]
:from [:customers]
:where [:= :name nil]})))
(is (= ["SELECT * FROM customers WHERE name = ?" nil]
(sql/format {:select [:*]
:from [:customers]
:where [:= :name :?name]}
{:name nil})))))
(testing "in"
(doseq [[cname coll] [[:vector []] [:set #{}] [:list '()]]]
(testing (str "with values from a " (name cname))
(let [values (conj coll 1)]
(is (= ["SELECT * FROM customers WHERE (id in (?))" 1]
(sql/format {:select [:*]
:from [:customers]
:where [:in :id values]})))
(is (= ["SELECT * FROM customers WHERE (id in (?))" 1]
(sql/format {:select [:*]
:from [:customers]
:where [:in :id :?ids]}
{:ids values}))))))
(testing "with more than one integer"
(let [values [1 2]]
(is (= ["SELECT * FROM customers WHERE (id in (?, ?))" 1 2]
(sql/format {:select [:*]
:from [:customers]
:where [:in :id values]})))
(is (= ["SELECT * FROM customers WHERE (id in (?, ?))" 1 2]
(sql/format {:select [:*]
:from [:customers]
:where [:in :id :?ids]}
{:ids values})))))
(testing "with more than one string"
(let [values ["1" "2"]]
(is (= ["SELECT * FROM customers WHERE (id in (?, ?))" "1" "2"]
(sql/format {:select [:*]
:from [:customers]
:where [:in :id values]})
(sql/format {:select [:*]
:from [:customers]
:where [:in :id :?ids]}
{:ids values})))))))
(deftest test-case
(is (= ["SELECT CASE WHEN foo < ? THEN ? WHEN (foo > ? AND (foo mod ?) = ?) THEN (foo / ?) ELSE ? END FROM bar"
0 -1 0 2 0 2 0]
(sql/format
{:select [(sql/call
:case
[:< :foo 0] -1
[:and [:> :foo 0] [:= (sql/call :mod :foo 2) 0]] (sql/call :/ :foo 2)
:else 0)]
:from [:bar]})))
(let [param1 1
param2 2
param3 "three"]
(is (= ["SELECT CASE WHEN foo = ? THEN ? WHEN foo = bar THEN ? WHEN bar = ? THEN (bar * ?) ELSE ? END FROM baz"
param1 0 param2 0 param3 "param4"]
(sql/format
{:select [(sql/call
:case
[:= :foo :?param1] 0
[:= :foo :bar] (sql/param :param2)
[:= :bar 0] (sql/call :* :bar :?param3)
:else "param4")]
:from [:baz]}
{:param1 param1
:param2 param2
:param3 param3})))))
(deftest test-raw
(is (= ["SELECT 1 + 1 FROM foo"]
(-> (select (sql/raw "1 + 1"))
(from :foo)
sql/format))))
(deftest test-call
(is (= ["SELECT min(?) FROM ?" "time" "table"]
(-> (select (sql/call :min "time"))
(from "table")
sql/format))))
(deftest join-test
(testing "nil join"
(is (= ["SELECT * FROM foo INNER JOIN x ON foo.id = x.id INNER JOIN y"]
(-> (select :*)
(from :foo)
(join :x [:= :foo.id :x.id] :y nil)
sql/format)))))
(deftest join-using-test
(testing "nil join"
(is (= ["SELECT * FROM foo INNER JOIN x USING (id) INNER JOIN y USING (foo, bar)"]
(-> (select :*)
(from :foo)
(join :x [:using :id] :y [:using :foo :bar])
sql/format)))))
(deftest inline-test
(is (= ["SELECT * FROM foo WHERE id = 5"]
(-> (select :*)
(from :foo)
(where [:= :id (sql/inline 5)])
sql/format)))
;; testing for = NULL always fails in SQL -- this test is just to show
;; that an #inline nil should render as NULL (so make sure you only use
;; it in contexts where a literal NULL is acceptable!)
(is (= ["SELECT * FROM foo WHERE id = NULL"]
(-> (select :*)
(from :foo)
(where [:= :id (sql/inline nil)])
sql/format))))
(deftest merge-where-no-params-test
(doseq [[k [f merge-f]] {"WHERE" [where merge-where]
"HAVING" [having merge-having]}]
(testing "merge-where called with just the map as parameter - see #228"
(let [sqlmap (-> (select :*)
(from :table)
(f [:= :foo :bar]))]
(is (= [(str "SELECT * FROM table " k " foo = bar")]
(sql/format (apply merge-f sqlmap []))))))))
(deftest merge-where-test
(doseq [[k sql-keyword f merge-f] [[:where "WHERE" where merge-where]
[:having "HAVING" having merge-having]]]
(is (= [(str "SELECT * FROM table " sql-keyword " (foo = bar AND quuz = xyzzy)")]
(-> (select :*)
(from :table)
(f [:= :foo :bar] [:= :quuz :xyzzy])
sql/format)))
(is (= [(str "SELECT * FROM table " sql-keyword " (foo = bar AND quuz = xyzzy)")]
(-> (select :*)
(from :table)
(f [:= :foo :bar])
(merge-f [:= :quuz :xyzzy])
sql/format)))
(testing "Should work when first arg isn't a map"
(is (= {k [:and [:x] [:y]]}
(merge-f [:x] [:y]))))
(testing "Shouldn't use conjunction if there is only one clause in the result"
(is (= {k [:x]}
(merge-f {} [:x]))))
(testing "Should be able to specify the conjunction type"
(is (= {k [:or [:x] [:y]]}
(merge-f {}
:or
[:x] [:y]))))
(testing "Should ignore nil clauses"
(is (= {k [:or [:x] [:y]]}
(merge-f {}
:or
[:x] nil [:y]))))))
(deftest merge-where-build-clause-test
(doseq [k [:where :having]]
(testing (str "Should be able to build a " k " clause with sql/build")
(is (= {k [:and [:a] [:x] [:y]]}
(sql/build
k [:a]
(keyword (str "merge-" (name k))) [:and [:x] [:y]]))))))
(deftest merge-where-combine-clauses-test
(doseq [[k f] {:where merge-where
:having merge-having}]
(testing (str "Combine new " k " clauses into the existing clause when appropriate. (#282)")
(testing "No existing clause"
(is (= {k [:and [:x] [:y]]}
(f {}
[:x] [:y]))))
(testing "Existing clause is not a conjunction."
(is (= {k [:and [:a] [:x] [:y]]}
(f {k [:a]}
[:x] [:y]))))
(testing "Existing clause IS a conjunction."
(testing "New clause(s) are not conjunctions"
(is (= {k [:and [:a] [:b] [:x] [:y]]}
(f {k [:and [:a] [:b]]}
[:x] [:y]))))
(testing "New clauses(s) ARE conjunction(s)"
(is (= {k [:and [:a] [:b] [:x] [:y]]}
(f {k [:and [:a] [:b]]}
[:and [:x] [:y]])))
(is (= {k [:and [:a] [:b] [:x] [:y]]}
(f {k [:and [:a] [:b]]}
[:and [:x]]
[:y])))
(is (= {k [:and [:a] [:b] [:x] [:y]]}
(f {k [:and [:a] [:b]]}
[:and [:x]]
[:and [:y]])))))
(testing "if existing clause isn't the same conjunction, don't merge into it"
(testing "existing conjunction is `:or`"
(is (= {k [:and [:or [:a] [:b]] [:x] [:y]]}
(f {k [:or [:a] [:b]]}
[:x] [:y]))))
(testing "pass conjunction type as a param (override default of :and)"
(is (= {k [:or [:and [:a] [:b]] [:x] [:y]]}
(f {k [:and [:a] [:b]]}
:or
[:x] [:y]))))))))
(deftest where-nil-params-test
(doseq [[_ sql-keyword f] [[:where "WHERE" where]
[:having "HAVING" having]]]
(testing (str sql-keyword " called with nil parameters - see #246")
(is (= [(str "SELECT * FROM table " sql-keyword " (foo = bar AND quuz = xyzzy)")]
(-> (select :*)
(from :table)
(f nil [:= :foo :bar] nil [:= :quuz :xyzzy] nil)
sql/format)))
(is (= ["SELECT * FROM table"]
(-> (select :*)
(from :table)
(f)
sql/format)))
(is (= ["SELECT * FROM table"]
(-> (select :*)
(from :table)
(f nil nil nil nil)
sql/format))))))
(deftest cross-join-test
(is (= ["SELECT * FROM foo CROSS JOIN bar"]
(-> (select :*)
(from :foo)
(cross-join :bar)
sql/format)))
(is (= ["SELECT * FROM foo f CROSS JOIN bar b"]
(-> (select :*)
(from [:foo :f])
(cross-join [:bar :b])
sql/format))))
#?(:cljs (cljs.test/run-all-tests))

View file

@ -1,336 +0,0 @@
(ns honeysql.format-test
(:refer-clojure :exclude [format])
(:require [#?@(:clj [clojure.test :refer]
:cljs [cljs.test :refer-macros]) [deftest testing is are]]
honeysql.core
[honeysql.types :as sql]
[honeysql.format :refer
[*allow-dashed-names?* *allow-namespaced-names?*
*namespace-as-table?*
quote-identifier format-clause format
parameterize]]))
(deftest test-quote
(are
[qx res]
(= (apply quote-identifier "foo.bar.baz" qx) res)
[] "foo.bar.baz"
[:style :mysql] "`foo`.`bar`.`baz`"
[:style :mysql :split false] "`foo.bar.baz`")
(are
[x res]
(= (quote-identifier x) res)
3 "3"
'foo "foo"
:foo-bar "foo_bar")
(is (= (quote-identifier "*" :style :ansi) "*"))
(is (= (quote-identifier "foo\"bar" :style :ansi) "\"foo\"\"bar\""))
(is (= (quote-identifier "foo\"bar" :style :oracle) "\"foo\"\"bar\""))
(is (= (quote-identifier "foo`bar" :style :mysql) "`foo``bar`"))
(is (= (quote-identifier "foo]bar" :style :sqlserver) "[foo]]bar]")))
(deftest test-dashed-quote
(binding [*allow-dashed-names?* true]
(is (= (quote-identifier :foo-bar) "foo-bar"))
(is (= (quote-identifier :foo-bar :style :ansi) "\"foo-bar\""))
(is (= (quote-identifier :foo-bar.moo-bar :style :ansi)
"\"foo-bar\".\"moo-bar\""))))
(deftest test-namespaced-identifier
(is (= (quote-identifier :foo/bar) "bar"))
(is (= (quote-identifier :foo/bar :style :ansi) "\"bar\""))
(binding [*namespace-as-table?* true]
(is (= (quote-identifier :foo/bar) "foo.bar"))
(is (= (quote-identifier :foo/bar :style :ansi) "\"foo\".\"bar\""))
(is (= (quote-identifier :foo/bar :style :ansi :split false) "\"foo.bar\"")))
(binding [*allow-namespaced-names?* true]
(is (= (quote-identifier :foo/bar) "foo/bar"))
(is (= (quote-identifier :foo/bar :style :ansi) "\"foo/bar\""))))
(deftest alias-splitting
(is (= ["SELECT `aa`.`c` AS `a.c`, `bb`.`c` AS `b.c`, `cc`.`c` AS `c.c`"]
(format {:select [[:aa.c "a.c"]
[:bb.c :b.c]
[:cc.c 'c.c]]}
:quoting :mysql))
"aliases containing \".\" are quoted as necessary but not split"))
(deftest values-alias
(is (= ["SELECT vals.a FROM (VALUES (?, ?, ?)) vals (a, b, c)" 1 2 3]
(format {:select [:vals.a]
:from [[{:values [[1 2 3]]} [:vals {:columns [:a :b :c]}]]]}))))
(deftest test-cte
(is (= (format-clause
(first {:with [[:query {:select [:foo] :from [:bar]}]]}) nil)
"WITH query AS SELECT foo FROM bar"))
(is (= (format-clause
(first {:with-recursive [[:query {:select [:foo] :from [:bar]}]]}) nil)
"WITH RECURSIVE query AS SELECT foo FROM bar"))
(is (= (format {:with [[[:static {:columns [:a :b :c]}] {:values [[1 2 3] [4 5 6]]}]]})
["WITH static (a, b, c) AS (VALUES (?, ?, ?), (?, ?, ?))" 1 2 3 4 5 6]))
(is (= (format
{:with [[[:static {:columns [:a :b :c]}]
{:values [[1 2 3] [4 5 6]]}]]
:select [:*]
:from [:static]})
["WITH static (a, b, c) AS (VALUES (?, ?, ?), (?, ?, ?)) SELECT * FROM static" 1 2 3 4 5 6])))
(deftest insert-into
(is (= (format-clause (first {:insert-into :foo}) nil)
"INSERT INTO foo"))
(is (= (format-clause (first {:insert-into [:foo {:select [:bar] :from [:baz]}]}) nil)
"INSERT INTO foo SELECT bar FROM baz"))
(is (= (format-clause (first {:insert-into [[:foo [:a :b :c]] {:select [:d :e :f] :from [:baz]}]}) nil)
"INSERT INTO foo (a, b, c) SELECT d, e, f FROM baz"))
(is (= (format {:insert-into [[:foo [:a :b :c]] {:select [:d :e :f] :from [:baz]}]})
["INSERT INTO foo (a, b, c) SELECT d, e, f FROM baz"])))
(deftest insert-into-namespaced
;; un-namespaced: works as expected:
(is (= (format {:insert-into :foo :values [{:foo/id 1}]})
["INSERT INTO foo (id) VALUES (?)" 1]))
(is (= (format {:insert-into :foo :columns [:foo/id] :values [[2]]})
["INSERT INTO foo (id) VALUES (?)" 2]))
(is (= (format {:insert-into :foo :values [{:foo/id 1}]}
:namespace-as-table? true)
["INSERT INTO foo (id) VALUES (?)" 1]))
(is (= (format {:insert-into :foo :columns [:foo/id] :values [[2]]}
:namespace-as-table? true)
["INSERT INTO foo (id) VALUES (?)" 2])))
(deftest exists-test
(is (= (format {:exists {:select [:a] :from [:foo]}})
["EXISTS (SELECT a FROM foo)"]))
(is (= (format {:select [:id]
:from [:foo]
:where [:exists {:select [1]
:from [:bar]
:where :deleted}]})
["SELECT id FROM foo WHERE EXISTS (SELECT ? FROM bar WHERE deleted)" 1])))
(deftest array-test
(is (= (format {:insert-into :foo
:columns [:baz]
:values [[(sql/array [1 2 3 4])]]})
["INSERT INTO foo (baz) VALUES (ARRAY[?, ?, ?, ?])" 1 2 3 4]))
(is (= (format {:insert-into :foo
:columns [:baz]
:values [[(sql/array ["one" "two" "three"])]]})
["INSERT INTO foo (baz) VALUES (ARRAY[?, ?, ?])" "one" "two" "three"])))
(deftest union-test
;; UNION and INTERSECT subexpressions should not be parenthesized.
;; If you need to add more complex expressions, use a subquery like this:
;; SELECT foo FROM bar1
;; UNION
;; SELECT foo FROM (SELECT foo FROM bar2 ORDER BY baz LIMIT 2)
;; ORDER BY foo ASC
(is (= (format {:union [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
["SELECT foo FROM bar1 UNION SELECT foo FROM bar2"])))
(deftest union-all-test
(is (= (format {:union-all [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
["SELECT foo FROM bar1 UNION ALL SELECT foo FROM bar2"])))
(deftest intersect-test
(is (= (format {:intersect [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
["SELECT foo FROM bar1 INTERSECT SELECT foo FROM bar2"])))
(deftest except-test
(is (= (format {:except [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]})
["SELECT foo FROM bar1 EXCEPT SELECT foo FROM bar2"])))
(deftest inner-parts-test
(testing "The correct way to apply ORDER BY to various parts of a UNION"
(is (= (format
{:union
[{:select [:amount :id :created_on]
:from [:transactions]}
{:select [:amount :id :created_on]
:from [{:select [:amount :id :created_on]
:from [:other_transactions]
:order-by [[:amount :desc]]
:limit 5}]}]
:order-by [[:amount :asc]]})
["SELECT amount, id, created_on FROM transactions UNION SELECT amount, id, created_on FROM (SELECT amount, id, created_on FROM other_transactions ORDER BY amount DESC LIMIT ?) ORDER BY amount ASC" 5]))))
(deftest compare-expressions-test
(testing "Sequences should be fns when in value/comparison spots"
(is (= ["SELECT foo FROM bar WHERE (col1 mod ?) = (col2 + ?)" 4 4]
(format {:select [:foo]
:from [:bar]
:where [:= [:mod :col1 4] [:+ :col2 4]]}))))
(testing "Value context only applies to sequences in value/comparison spots"
(let [sub {:select [:%sum.amount]
:from [:bar]
:where [:in :id ["id-1" "id-2"]]}]
(is (= ["SELECT total FROM foo WHERE (SELECT sum(amount) FROM bar WHERE (id in (?, ?))) = total" "id-1" "id-2"]
(format {:select [:total]
:from [:foo]
:where [:= sub :total]})))
(is (= ["WITH t AS (SELECT sum(amount) FROM bar WHERE (id in (?, ?))) SELECT total FROM foo WHERE total = t" "id-1" "id-2"]
(format {:with [[:t sub]]
:select [:total]
:from [:foo]
:where [:= :total :t]}))))))
(deftest union-with-cte
(is (= (format {:union [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]
:with [[[:bar {:columns [:spam :eggs]}]
{:values [[1 2] [3 4] [5 6]]}]]})
["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2" 1 2 3 4 5 6])))
(deftest union-all-with-cte
(is (= (format {:union-all [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]
:with [[[:bar {:columns [:spam :eggs]}]
{:values [[1 2] [3 4] [5 6]]}]]})
["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) SELECT foo FROM bar1 UNION ALL SELECT foo FROM bar2" 1 2 3 4 5 6])))
(deftest parameterizer-none
(testing "array parameter"
(is (= (format {:insert-into :foo
:columns [:baz]
:values [[(sql/array [1 2 3 4])]]}
:parameterizer :none)
["INSERT INTO foo (baz) VALUES (ARRAY[1, 2, 3, 4])"])))
(testing "union complex values"
(is (= (format {:union [{:select [:foo] :from [:bar1]}
{:select [:foo] :from [:bar2]}]
:with [[[:bar {:columns [:spam :eggs]}]
{:values [[1 2] [3 4] [5 6]]}]]}
:parameterizer :none)
["WITH bar (spam, eggs) AS (VALUES (1, 2), (3, 4), (5, 6)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2"]))))
(deftest where-and
(testing "should ignore a nil predicate"
(is (= (format {:where [:and [:= :foo "foo"] [:= :bar "bar"] nil]}
:parameterizer :postgresql)
["WHERE (foo = $1 AND bar = $2)" "foo" "bar"]))))
(defmethod parameterize :single-quote [_ value pname] (str \' value \'))
(defmethod parameterize :mysql-fill [_ value pname] "?")
(deftest customized-parameterizer
(testing "should fill param with single quote"
(is (= (format {:where [:and [:= :foo "foo"] [:= :bar "bar"] nil]}
:parameterizer :single-quote)
["WHERE (foo = 'foo' AND bar = 'bar')" "foo" "bar"])))
(testing "should fill param with ?"
(is (= (format {:where [:and [:= :foo "foo"] [:= :bar "bar"] nil]}
:parameterizer :mysql-fill)
["WHERE (foo = ? AND bar = ?)" "foo" "bar"]))))
(deftest set-before-from ; issue 235
(is (=
["UPDATE `films` `f` SET `kind` = `c`.`test` FROM (SELECT `b`.`test` FROM `bar` `b` WHERE `b`.`id` = ?) `c` WHERE `f`.`kind` = ?" 1 "drama"]
(->
{:update [:films :f]
:set0 {:kind :c.test}
:from [[{:select [:b.test]
:from [[:bar :b]]
:where [:= :b.id 1]} :c]]
:where [:= :f.kind "drama"]}
(format :quoting :mysql)))))
(deftest set-after-join
(is (=
["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `a` = ? WHERE `bar`.`b` = ?" 1 42]
(->
{:update :foo
:join [:bar [:= :bar.id :foo.bar_id]]
:set {:a 1}
:where [:= :bar.b 42]}
(format :quoting :mysql))))
(is (=
["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `a` = ? WHERE `bar`.`b` = ?" 1 42]
(->
{:update :foo
:join [:bar [:= :bar.id :foo.bar_id]]
:set1 {:a 1}
:where [:= :bar.b 42]}
(format :quoting :mysql)))))
(deftest delete-from-test
(is (= ["DELETE FROM `foo` WHERE `foo`.`id` = ?" 42]
(-> {:delete-from :foo
:where [:= :foo.id 42]}
(format :quoting :mysql)))))
(deftest delete-test
(is (= ["DELETE `t1`, `t2` FROM `table1` `t1` INNER JOIN `table2` `t2` ON `t1`.`fk` = `t2`.`id` WHERE `t1`.`bar` = ?" 42]
(-> {:delete [:t1 :t2]
:from [[:table1 :t1]]
:join [[:table2 :t2] [:= :t1.fk :t2.id]]
:where [:= :t1.bar 42]}
(format :quoting :mysql)))))
(deftest truncate-test
(is (= ["TRUNCATE `foo`"]
(-> {:truncate :foo}
(format :quoting :mysql)))))
(deftest inlined-values-are-stringified-correctly
(is (= ["SELECT foo, bar, NULL"]
(format {:select [(honeysql.core/inline "foo")
(honeysql.core/inline :bar)
(honeysql.core/inline nil)]}))))
;; Make sure if Locale is Turkish we're not generating queries like İNNER JOIN (dot over the I) because
;; `string/upper-case` is converting things to upper-case using the default Locale. Generated query should be the same
;; regardless of system Locale. See #236
#?(:clj
(deftest statements-generated-correctly-with-turkish-locale
(let [format-with-locale (fn [^String language-tag]
(let [original-locale (java.util.Locale/getDefault)]
(try
(java.util.Locale/setDefault (java.util.Locale/forLanguageTag language-tag))
(format {:select [:t2.name]
:from [[:table1 :t1]]
:join [[:table2 :t2] [:= :t1.fk :t2.id]]
:where [:= :t1.id 1]})
(finally
(java.util.Locale/setDefault original-locale)))))]
(is (= (format-with-locale "en")
(format-with-locale "tr"))))))
(deftest join-on-true-253
;; used to work on honeysql 0.9.2; broke in 0.9.3
(is (= ["SELECT foo FROM bar INNER JOIN table t ON TRUE"]
(format {:select [:foo]
:from [:bar]
:join [[:table :t] true]}))))
(deftest cross-join-test
(is (= ["SELECT * FROM foo CROSS JOIN bar"]
(format {:select [:*]
:from [:foo]
:cross-join [:bar]})))
(is (= ["SELECT * FROM foo f CROSS JOIN bar b"]
(format {:select [:*]
:from [[:foo :f]]
:cross-join [[:bar :b]]}))))
(deftest issue-299-test
(let [name "test field"
;; this was being rendered inline into the SQL
;; creating an injection vulnerability (v1 only)
;; the context for seq->sql here seems to be the
;; 'regular' one so it tries to treat this as an
;; alias: 'value alias' -- the fix was to make it
;; a function context so it becomes (TRUE, ?):
enabled [true, "); SELECT case when (SELECT current_setting('is_superuser'))='off' then pg_sleep(0.2) end; -- "]]
(is (= ["INSERT INTO table (name, enabled) VALUES (?, (TRUE, ?))" name (second enabled)]
(format {:insert-into :table
:values [{:name name
:enabled enabled}]})))))