Clean up dialect support; start docs
Also move old namespaces to test-only tree for reference while I continue developing V2.
This commit is contained in:
parent
6cf8fa9e45
commit
11ef895c4a
13 changed files with 756 additions and 22 deletions
24
README.md
24
README.md
|
|
@ -1,15 +1,17 @@
|
|||
# Honey SQL [](https://circleci.com/gh/seancorfield/honeysql/tree/develop)
|
||||
# Honey SQL [](https://circleci.com/gh/seancorfield/honeysql/tree/v2)
|
||||
|
||||
SQL as Clojure data structures. Build queries programmatically -- even at runtime -- without having to bash strings together.
|
||||
|
||||
## Build
|
||||
|
||||
The latest versions on Clojars and on cljdoc:
|
||||
The latest stable version (1.0.444) on Clojars and on cljdoc:
|
||||
|
||||
[](https://clojars.org/honeysql) [](https://cljdoc.org/d/honeysql/honeysql/CURRENT)
|
||||
|
||||
This project follows the version scheme MAJOR.MINOR.COMMITS where MAJOR and MINOR provide some relative indication of the size of the change, but do not follow semantic versioning. In general, all changes endeavor to be non-breaking (by moving to new names rather than by breaking existing names). COMMITS is an ever-increasing counter of commits since the beginning of this repository.
|
||||
|
||||
This is the README for the upcoming 2.x version of HoneySQL which provides a streamlined codebase and a simpler method for extending the DSL. It also supports SQL dialects out-of-the-box and will be extended to support vendor-specific language features over time (unlike the 1.x version).
|
||||
|
||||
## Note on code samples
|
||||
|
||||
All sample code in this README is automatically run as a unit test using
|
||||
|
|
@ -17,14 +19,14 @@ All sample code in this README is automatically run as a unit test using
|
|||
|
||||
Note that while some of these samples show pretty-printed SQL, this is just for
|
||||
README readability; honeysql does not generate pretty-printed SQL.
|
||||
The `#sql/regularize` directive tells the test-runner to ignore the extraneous
|
||||
whitespace.
|
||||
|
||||
_The `#sql/regularize` directive tells the test-runner to ignore the extraneous whitespace._ [TODO: replace with pretty print option!]
|
||||
|
||||
## Usage
|
||||
|
||||
```clojure
|
||||
(require '[honeysql.core :as sql]
|
||||
'[honeysql.helpers :refer :all :as helpers])
|
||||
(require '[honey.sql :as sql]
|
||||
'[honey.sql.helpers :refer :all :as helpers])
|
||||
```
|
||||
|
||||
Everything is built on top of maps representing SQL queries:
|
||||
|
|
@ -46,6 +48,8 @@ Column names can be provided as keywords or symbols (but not strings -- HoneySQL
|
|||
=> ["SELECT a, b, c FROM foo WHERE f.a = ?" "baz"]
|
||||
```
|
||||
|
||||
_The handling of namespace-qualified keywords is under review in 2.x._
|
||||
|
||||
By default, namespace-qualified keywords are treated as simple keywords: their namespace portion is ignored. This was the behavior in HoneySQL prior to the 0.9.0 release and has been restored since the 0.9.7 release as this is considered the least surprising behavior.
|
||||
As of version 0.9.7, `format` accepts `:allow-namespaced-names? true` to provide the somewhat unusual behavior of 0.9.0-0.9.6, namely that namespace-qualified keywords were passed through into the SQL "as-is", i.e., with the `/` in them (which generally required a quoting strategy as well).
|
||||
As of version 0.9.8, `format` accepts `:namespace-as-table? true` to treat namespace-qualified keywords as if the `/` were `.`, allowing `:table/column` as an alternative to `:table.column`. This approach is likely to be more compatible with code that uses libraries like [`next.jdbc`](https://github.com/seancorfield/next-jdbc) and [`seql`](https://github.com/exoscale/seql), as well as being more convenient in a world of namespace-qualified keywords, following the example of `clojure.spec` etc.
|
||||
|
|
@ -54,13 +58,13 @@ As of version 0.9.8, `format` accepts `:namespace-as-table? true` to treat names
|
|||
(def q-sqlmap {:select [:foo/a :foo/b :foo/c]
|
||||
:from [:foo]
|
||||
:where [:= :foo/a "baz"]})
|
||||
(sql/format q-sqlmap :namespace-as-table? true)
|
||||
(sql/format q-sqlmap)
|
||||
=> ["SELECT foo.a, foo.b, foo.c FROM foo WHERE foo.a = ?" "baz"]
|
||||
```
|
||||
|
||||
Honeysql is a relatively "pure" library, it does not manage your sql connection
|
||||
HoneySQL is a relatively "pure" library, it does not manage your sql connection
|
||||
or run queries for you, it simply generates SQL strings. You can then pass them
|
||||
to jdbc:
|
||||
to a JDBC library, such as [`next.jdbc`](https://github.com/seancorfield/next-jdbc):
|
||||
|
||||
```clj
|
||||
(jdbc/query conn (sql/format sqlmap))
|
||||
|
|
@ -587,6 +591,6 @@ To teach `honeysql` how to handle your datatype you need to implement [`honeysql
|
|||
|
||||
## License
|
||||
|
||||
Copyright © 2012-2017 Justin Kramer
|
||||
Copyright (c) 2020 Sean Corfield. HoneySQL 1.x was copyright (c) 2012-2020 Justin Kramer and Sean Corfield.
|
||||
|
||||
Distributed under the Eclipse Public License, the same as Clojure.
|
||||
|
|
|
|||
11
doc/cljdoc.edn
Normal file
11
doc/cljdoc.edn
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{:cljdoc.doc/tree [["Readme" {:file "README.md"}]
|
||||
["Changes" {:file "CHANGELOG.md"}]
|
||||
["Getting Started" {:file "doc/getting-started.md"}
|
||||
#_["Friendly SQL Functions" {:file "doc/friendly-sql-functions.md"}]
|
||||
#_["Tips & Tricks" {:file "doc/tips-and-tricks.md"}]
|
||||
#_["Result Set Builders" {:file "doc/result-set-builders.md"}]
|
||||
#_["Prepared Statements" {:file "doc/prepared-statements.md"}]
|
||||
#_["Transactions" {:file "doc/transactions.md"}]]
|
||||
#_["All The Options" {:file "doc/all-the-options.md"}]
|
||||
#_["datafy, nav, and :schema" {:file "doc/datafy-nav-and-schema.md"}]
|
||||
["Differences from 1.x" {:file "doc/difference-from-1-x.md"}]]}
|
||||
106
doc/differences-from-1-x.md
Normal file
106
doc/differences-from-1-x.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# 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 variadic options from 1.x have changed to an options hash map in 2.x as this is generally considered more idiomatic. See the **Option Changes** section below for the differences in the options supported.
|
||||
|
||||
## Group, Artifact, and Namespaces
|
||||
|
||||
HoneySQL 2.x uses the group ID `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/).
|
||||
|
||||
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`. A Spec for the DSL data structure is available in `honey.specs` (work in progress).
|
||||
|
||||
### HoneySQL 1.x
|
||||
|
||||
```clojure
|
||||
;; in deps.edn:
|
||||
honeysql {:mvn/version "1.0.444"}
|
||||
;; or, more correctly:
|
||||
honeysql/honeysql {:mvn/version "1.0.444"}
|
||||
|
||||
;; in use:
|
||||
(ns my.project
|
||||
(:require [honeysql.core :as sql]))
|
||||
|
||||
...
|
||||
(sql/format {:select [:*] :from [:table] :where [:= :id 1]})
|
||||
;;=> ["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).
|
||||
|
||||
### HoneySQL 2.x
|
||||
|
||||
```clojure
|
||||
;; in deps.edn:
|
||||
seancorfield/honeysql {:mvn/version "2.x"}
|
||||
|
||||
;; in use:
|
||||
(ns my.project
|
||||
(:require [honey.sql :as sql]))
|
||||
|
||||
...
|
||||
(sql/format {:select [:*] :from [:table] :where [:= :id 1]})
|
||||
;;=> ["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,
|
||||
* `honey.specs` -- a description of the DSL using `clojure.spec.alpha`.
|
||||
|
||||
## 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).
|
||||
|
||||
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.
|
||||
|
||||
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) and . Other dialects and changes may be added over time.
|
||||
|
||||
> Note: `:limit` and `:offset` are currently in the default `:ansi` dialect even though they are MySQL-specific. This is temporary as the dialects are being fleshed out. I expect to add `:top` for `:sqlserver` and `:offset` / `:fetch` for `:ansi`, at which point `:limit` / `:offset` will become MySQL-only.
|
||||
|
||||
## Option Changes
|
||||
|
||||
As noted above, the variadic options for `format` have been replaced by a single hash map as the optional second argument to `format`.
|
||||
|
||||
The `:quoting <dialect>` option has superseded by the new dialect machinery and a new `:quoted` option that turns quoting on or off. You either use `:dialect <dialect>` instead or set a default dialect (via `set-dialect!`) and then use `{:quoted true}` in `format` calls where you want quoting.
|
||||
|
||||
Identifiers are automatically quoted if you specify a `:dialect` option to `format`, unless you also specify `:quoted false`.
|
||||
|
||||
The following options are no longer supported:
|
||||
* `:namespace-as-table?` -- TODO
|
||||
* `: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).
|
||||
|
||||
## DSL Changes
|
||||
|
||||
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
|
||||
(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"]
|
||||
|
||||
```
|
||||
|
||||
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 following new syntax has been added:
|
||||
|
||||
* `:array` -- used as a function to replace the `sql/array` / `#sql/array` machinery,
|
||||
* `: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]`.
|
||||
|
||||
> Note 1: `:inline` currently inlines strings like `"foo"` as `foo` which matches the 1.x behavior but this will almost certainly change to inline as `'foo'`, i.e., a SQL string value, before release.
|
||||
|
||||
> Note 2: expect `:raw` to be added in some form before release.
|
||||
|
||||
## 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).
|
||||
3
doc/getting-started.md
Normal file
3
doc/getting-started.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Getting Started with HoneySQL
|
||||
|
||||
tbd
|
||||
|
|
@ -39,13 +39,14 @@
|
|||
(conj order clause)))
|
||||
|
||||
(def ^:private dialects
|
||||
{:ansi {:quote #(str \" % \")}
|
||||
:mssql {:quote #(str \[ % \])}
|
||||
:mysql {:quote #(str \` % \`)
|
||||
:clause-order-fn #(add-clause-before
|
||||
(filterv (complement #{:set}) %)
|
||||
:set
|
||||
:where)}})
|
||||
{:ansi {:quote #(str \" % \")}
|
||||
:sqlserver {:quote #(str \[ % \])}
|
||||
:mysql {:quote #(str \` % \`)
|
||||
:clause-order-fn #(add-clause-before
|
||||
(filterv (complement #{:set}) %)
|
||||
:set
|
||||
:where)}
|
||||
:oracle {:quote #(str \" % \")}})
|
||||
|
||||
; should become defonce
|
||||
(def ^:private default-dialect (atom (:ansi dialects)))
|
||||
|
|
@ -523,7 +524,7 @@
|
|||
(defn set-dialect!
|
||||
"Set the default dialect for formatting.
|
||||
|
||||
Can be: `:ansi` (the default), `:mssql`, `:mysql`.
|
||||
Can be: `:ansi` (the default), `:mysql`, `:oracle`, or `:sqlserver`.
|
||||
|
||||
Dialects are always applied to the base order to create the current order."
|
||||
[dialect]
|
||||
|
|
@ -544,9 +545,15 @@
|
|||
only clause so far where that would matter is `:set` which differs in
|
||||
MySQL..."
|
||||
[clause formatter before]
|
||||
(swap! base-clause-order add-clause-before clause before)
|
||||
(swap! current-clause-order add-clause-before clause before)
|
||||
(swap! clause-format assoc clause formatter))
|
||||
(let [f (if (keyword? formatter)
|
||||
(get @clause-format formatter)
|
||||
formatter)]
|
||||
(when-not (and f (fn? f))
|
||||
(throw (ex-info "The formatter must be a function or existing clause"
|
||||
{:type (type formatter)})))
|
||||
(swap! base-clause-order add-clause-before clause before)
|
||||
(swap! current-clause-order add-clause-before clause before)
|
||||
(swap! clause-format assoc clause f)))
|
||||
|
||||
(comment
|
||||
(format {:truncate :foo})
|
||||
|
|
@ -556,6 +563,7 @@
|
|||
(format-expr [:foo [:bar [:+ 2 [:g :abc]]] [:f 1 :quux]])
|
||||
(format-expr :id)
|
||||
(format-expr 1)
|
||||
(format {:select [:a [:b :c] [[:d :e]] [[:f :g] :h]]})
|
||||
(format-on-expr :where [:= :id 1])
|
||||
(format-dsl {:select [:*] :from [:table] :where [:= :id 1]})
|
||||
(format {:select [:t.*] :from [[:table :t]] :where [:= :id 1]} {})
|
||||
|
|
|
|||
6
src/honey/sql/helpers.clj
Normal file
6
src/honey/sql/helpers.clj
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
;; copyright (c) 2020 sean corfield, all rights reserved
|
||||
|
||||
(ns honey.sql.helpers
|
||||
"Macros to create consistent helpers from DSL clauses.
|
||||
|
||||
I don't know how this will work in ClojureScript yet...")
|
||||
593
src/readme.clj
Normal file
593
src/readme.clj
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
(ns readme (:require [seancorfield.readme]))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-27
|
||||
(require '[honey.sql :as sql]
|
||||
'[honey.sql.helpers :refer :all :as helpers])
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-34
|
||||
(def sqlmap {:select [:a :b :c]
|
||||
:from [:foo]
|
||||
:where [:= :f.a "baz"]})
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-46
|
||||
(sql/format sqlmap)
|
||||
=> ["SELECT a, b, c FROM foo WHERE f.a = ?" "baz"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-55
|
||||
(def q-sqlmap {:select [:foo/a :foo/b :foo/c]
|
||||
:from [:foo]
|
||||
:where [:= :foo/a "baz"]})
|
||||
(sql/format q-sqlmap :namespace-as-table? true)
|
||||
=> ["SELECT foo.a, foo.b, foo.c FROM foo WHERE foo.a = ?" "baz"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-75
|
||||
(sql/build :select :*
|
||||
:from :foo
|
||||
:where [:= :f.a "baz"])
|
||||
=> {:where [:= :f.a "baz"], :from [:foo], :select [:*]}
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-84
|
||||
(sql/build sqlmap :offset 10 :limit 10)
|
||||
=> {:limit 10
|
||||
:offset 10
|
||||
:select [:a :b :c]
|
||||
:where [:= :f.a "baz"]
|
||||
:from [:foo]}
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-97
|
||||
(-> (select :a :b :c)
|
||||
(from :foo)
|
||||
(where [:= :f.a "baz"]))
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-105
|
||||
(= (-> (select :*) (from :foo))
|
||||
(-> (from :foo) (select :*)))
|
||||
=> true
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-113
|
||||
(-> sqlmap (select :*))
|
||||
=> '{:from [:foo], :where [:= :f.a "baz"], :select (:*)}
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-120
|
||||
(-> sqlmap
|
||||
(merge-select :d :e)
|
||||
(merge-where [:> :b 10])
|
||||
sql/format)
|
||||
=> ["SELECT a, b, c, d, e FROM foo WHERE (f.a = ? AND b > ?)" "baz" 10]
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-130
|
||||
(-> (select :*)
|
||||
(from :foo)
|
||||
(where [:= :a 1] [:< :b 100])
|
||||
sql/format)
|
||||
=> ["SELECT * FROM foo WHERE (a = ? AND b < ?)" 1 100]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-141
|
||||
(-> (select :a [:b :bar] :c [:d :x])
|
||||
(from [:foo :quux])
|
||||
(where [:= :quux.a 1] [:< :bar 100])
|
||||
sql/format)
|
||||
=> ["SELECT a, b AS bar, c, d AS x FROM foo quux WHERE (quux.a = ? AND bar < ?)" 1 100]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-158
|
||||
(-> (insert-into :properties)
|
||||
(columns :name :surname :age)
|
||||
(values
|
||||
[["Jon" "Smith" 34]
|
||||
["Andrew" "Cooper" 12]
|
||||
["Jane" "Daniels" 56]])
|
||||
sql/format)
|
||||
=> [#sql/regularize
|
||||
"INSERT INTO properties (name, surname, age)
|
||||
VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)"
|
||||
"Jon" "Smith" 34 "Andrew" "Cooper" 12 "Jane" "Daniels" 56]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-176
|
||||
(-> (insert-into :properties)
|
||||
(values [{:name "John" :surname "Smith" :age 34}
|
||||
{:name "Andrew" :surname "Cooper" :age 12}
|
||||
{:name "Jane" :surname "Daniels" :age 56}])
|
||||
sql/format)
|
||||
=> [#sql/regularize
|
||||
"INSERT INTO properties (name, surname, age)
|
||||
VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)"
|
||||
"John" "Smith" 34
|
||||
"Andrew" "Cooper" 12
|
||||
"Jane" "Daniels" 56]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-194
|
||||
(let [user-id 12345
|
||||
role-name "user"]
|
||||
(-> (insert-into :user_profile_to_role)
|
||||
(values [{:user_profile_id user-id
|
||||
:role_id (-> (select :id)
|
||||
(from :role)
|
||||
(where [:= :name role-name]))}])
|
||||
sql/format))
|
||||
|
||||
=> [#sql/regularize
|
||||
"INSERT INTO user_profile_to_role (user_profile_id, role_id)
|
||||
VALUES (?, (SELECT id FROM role WHERE name = ?))"
|
||||
12345
|
||||
"user"]
|
||||
)
|
||||
|
||||
(seancorfield.readme/defreadme readme-211
|
||||
(-> (select :*)
|
||||
(from :foo)
|
||||
(where [:in :foo.a (-> (select :a) (from :bar))])
|
||||
sql/format)
|
||||
=> ["SELECT * FROM foo WHERE (foo.a in (SELECT a FROM bar))"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-223
|
||||
(-> (insert-into :comp_table)
|
||||
(columns :name :comp_column)
|
||||
(values
|
||||
[["small" (composite 1 "inch")]
|
||||
["large" (composite 10 "feet")]])
|
||||
sql/format)
|
||||
=> [#sql/regularize
|
||||
"INSERT INTO comp_table (name, comp_column)
|
||||
VALUES (?, (?, ?)), (?, (?, ?))"
|
||||
"small" 1 "inch" "large" 10 "feet"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-241
|
||||
(-> (helpers/update :films)
|
||||
(sset {:kind "dramatic"
|
||||
:watched (sql/call :+ :watched 1)})
|
||||
(where [:= :kind "drama"])
|
||||
sql/format)
|
||||
=> [#sql/regularize
|
||||
"UPDATE films SET kind = ?, watched = (watched + ?)
|
||||
WHERE kind = ?"
|
||||
"dramatic"
|
||||
1
|
||||
"drama"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-267
|
||||
(-> (delete-from :films)
|
||||
(where [:<> :kind "musical"])
|
||||
sql/format)
|
||||
=> ["DELETE FROM films WHERE kind <> ?" "musical"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-276
|
||||
(-> (delete [:films :directors])
|
||||
(from :films)
|
||||
(join :directors [:= :films.director_id :directors.id])
|
||||
(where [:<> :kind "musical"])
|
||||
sql/format)
|
||||
=> [#sql/regularize
|
||||
"DELETE films, directors
|
||||
FROM films
|
||||
INNER JOIN directors ON films.director_id = directors.id
|
||||
WHERE kind <> ?"
|
||||
"musical"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-292
|
||||
(-> (truncate :films)
|
||||
sql/format)
|
||||
=> ["TRUNCATE films"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-302
|
||||
(sql/format {:union [(-> (select :*) (from :foo))
|
||||
(-> (select :*) (from :bar))]})
|
||||
=> ["SELECT * FROM foo UNION SELECT * FROM bar"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-312
|
||||
(-> (select :%count.*) (from :foo) sql/format)
|
||||
=> ["SELECT count(*) FROM foo"]
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-316
|
||||
(-> (select :%max.id) (from :foo) sql/format)
|
||||
=> ["SELECT max(id) FROM foo"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-325
|
||||
(-> (select :id)
|
||||
(from :foo)
|
||||
(where [:= :a :?baz])
|
||||
(sql/format :params {:baz "BAZ"}))
|
||||
=> ["SELECT id FROM foo WHERE a = ?" "BAZ"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-338
|
||||
(def call-qualify-map
|
||||
(-> (select (sql/call :foo :bar) (sql/qualify :foo :a) (sql/raw "@var := foo.bar"))
|
||||
(from :foo)
|
||||
(where [:= :a (sql/param :baz)] [:= :b (sql/inline 42)])))
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-344
|
||||
call-qualify-map
|
||||
=> '{:where [:and [:= :a #sql/param :baz] [:= :b #sql/inline 42]]
|
||||
:from (:foo)
|
||||
:select (#sql/call [:foo :bar] :foo.a #sql/raw "@var := foo.bar")}
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-350
|
||||
(sql/format call-qualify-map :params {:baz "BAZ"})
|
||||
=> ["SELECT foo(bar), foo.a, @var := foo.bar FROM foo WHERE (a = ? AND b = 42)" "BAZ"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-360
|
||||
(-> (insert-into :sample)
|
||||
(values [{:location (sql/call :ST_SetSRID
|
||||
(sql/call :ST_MakePoint 0.291 32.621)
|
||||
(sql/call :cast 4326 :integer))}])
|
||||
(sql/format))
|
||||
=> [#sql/regularize
|
||||
"INSERT INTO sample (location)
|
||||
VALUES (ST_SetSRID(ST_MakePoint(?, ?), CAST(? AS integer)))"
|
||||
0.291 32.621 4326]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-383
|
||||
(-> (select :*)
|
||||
(from :foo)
|
||||
(where [:< :expired_at (sql/raw ["now() - '" 5 " seconds'"])])
|
||||
(sql/format {:foo 5}))
|
||||
=> ["SELECT * FROM foo WHERE expired_at < now() - '? seconds'" 5]
|
||||
)
|
||||
|
||||
(seancorfield.readme/defreadme readme-391
|
||||
(-> (select :*)
|
||||
(from :foo)
|
||||
(where [:< :expired_at (sql/raw ["now() - '" #sql/param :t " seconds'"])])
|
||||
(sql/format {:t 5}))
|
||||
=> ["SELECT * FROM foo WHERE expired_at < now() - '? seconds'" 5]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-403
|
||||
(-> (select :foo.a)
|
||||
(from :foo)
|
||||
(where [:= :foo.a "baz"])
|
||||
(sql/format :quoting :mysql))
|
||||
=> ["SELECT `foo`.`a` FROM `foo` WHERE `foo`.`a` = ?" "baz"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-417
|
||||
(-> (select :foo.a)
|
||||
(from :foo)
|
||||
(where [:= :foo.a "baz"])
|
||||
(lock :mode :update)
|
||||
(sql/format))
|
||||
=> ["SELECT foo.a FROM foo WHERE foo.a = ? FOR UPDATE" "baz"]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-429
|
||||
(sql/format
|
||||
{:select [:f.foo-id :f.foo-name]
|
||||
:from [[:foo-bar :f]]
|
||||
:where [:= :f.foo-id 12345]}
|
||||
:allow-dashed-names? true
|
||||
:quoting :ansi)
|
||||
=> ["SELECT \"f\".\"foo-id\", \"f\".\"foo-name\" FROM \"foo-bar\" \"f\" WHERE \"f\".\"foo-id\" = ?" 12345]
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-443
|
||||
(def big-complicated-map
|
||||
(-> (select :f.* :b.baz :c.quux [:b.bla "bla-bla"]
|
||||
(sql/call :now) (sql/raw "@x := 10"))
|
||||
(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])
|
||||
(where [:or
|
||||
[:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]]
|
||||
[:< 1 2 3]
|
||||
[:in :f.e [1 (sql/param :param2) 3]]
|
||||
[:between :f.e 10 20]])
|
||||
(group :f.a :c.e)
|
||||
(having [:< 0 :f.e])
|
||||
(order-by [:b.baz :desc] :c.quux [:f.a :nulls-first])
|
||||
(limit 50)
|
||||
(offset 10)))
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-463
|
||||
big-complicated-map
|
||||
=> {:select [:f.* :b.baz :c.quux [:b.bla "bla-bla"]
|
||||
(sql/call :now) (sql/raw "@x := 10")]
|
||||
: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]]
|
||||
:where [:or
|
||||
[:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]]
|
||||
[:< 1 2 3]
|
||||
[:in :f.e [1 (sql/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}
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-483
|
||||
(sql/format big-complicated-map {:param1 "gabba" :param2 2})
|
||||
=> [#sql/regularize
|
||||
"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
|
||||
WHERE ((f.a = ? AND b.baz <> ?)
|
||||
OR (? < ? AND ? < ?)
|
||||
OR (f.e in (?, ?, ?))
|
||||
OR f.e BETWEEN ? AND ?)
|
||||
GROUP BY f.a, c.e
|
||||
HAVING ? < f.e
|
||||
ORDER BY b.baz DESC, c.quux, f.a NULLS FIRST
|
||||
LIMIT ?
|
||||
OFFSET ? "
|
||||
"bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-502
|
||||
;; Printable and readable
|
||||
(= big-complicated-map (read-string (pr-str big-complicated-map)))
|
||||
=> true
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-512
|
||||
(require '[honeysql.format :as fmt])
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-515
|
||||
(defmethod fmt/fn-handler "betwixt" [_ field lower upper]
|
||||
(str (fmt/to-sql field) " BETWIXT "
|
||||
(fmt/to-sql lower) " AND " (fmt/to-sql upper)))
|
||||
|
||||
(-> (select :a) (where [:betwixt :a 1 10]) sql/format)
|
||||
=> ["SELECT a WHERE a BETWIXT ? AND ?" 1 10]
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-526
|
||||
;; Takes a MapEntry of the operator & clause data, plus the entire SQL map
|
||||
(defmethod fmt/format-clause :foobar [[op v] sqlmap]
|
||||
(str "FOOBAR " (fmt/to-sql v)))
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-531
|
||||
(sql/format {:select [:a :b] :foobar :baz})
|
||||
=> ["SELECT a, b FOOBAR baz"]
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-535
|
||||
(require '[honeysql.helpers :refer [defhelper]])
|
||||
|
||||
;; Defines a helper function, and allows 'build' to recognize your clause
|
||||
(defhelper foobar [m args]
|
||||
(assoc m :foobar (first args)))
|
||||
)
|
||||
(seancorfield.readme/defreadme readme-542
|
||||
(-> (select :a :b) (foobar :baz) sql/format)
|
||||
=> ["SELECT a, b FOOBAR baz"]
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
(seancorfield.readme/defreadme readme-550
|
||||
(fmt/register-clause! :foobar 110)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -108,9 +108,12 @@
|
|||
;; EXISTS should never have been implemented as SQL syntax: it's an operator!
|
||||
#_(is (= (format {:exists {:select [:a] :from [:foo]}})
|
||||
["EXISTS (SELECT a FROM foo)"]))
|
||||
;; ugly because it's hard to select just a function call without an alias:
|
||||
;; select function call with an alias:
|
||||
(is (= (format {:select [[[:exists {:select [:a] :from [:foo]}] :x]]})
|
||||
["SELECT EXISTS (SELECT a FROM foo) AS x"]))
|
||||
;; select function call with no alias required:
|
||||
(is (= (format {:select [[[:exists {:select [:a] :from [:foo]}]]]})
|
||||
["SELECT EXISTS (SELECT a FROM foo)"]))
|
||||
(is (= (format {:select [:id]
|
||||
:from [:foo]
|
||||
:where [:exists {:select [1]
|
||||
|
|
|
|||
Loading…
Reference in a new issue