Run all code samples from README as tests

using `lein test-readme` alias
This commit is contained in:
Michael Blume 2017-07-18 22:11:49 -07:00
parent e213364012
commit a707222d53
6 changed files with 103 additions and 64 deletions

1
.gitignore vendored
View file

@ -17,3 +17,4 @@ pom.xml*
.project .project
.nrepl-port .nrepl-port
bin bin
test/readme.clj

View file

@ -1,3 +1,3 @@
language: clojure language: clojure
lein: 2.7.1 lein: 2.7.1
script: lein do check, eastwood, test script: lein do check, eastwood, test, test-readme

148
README.md
View file

@ -11,16 +11,26 @@ SQL as Clojure data structures. Build queries programmatically -- even at runtim
[![Clojars Project](http://clojars.org/honeysql/latest-version.svg)](http://clojars.org/honeysql) [![Clojars Project](http://clojars.org/honeysql/latest-version.svg)](http://clojars.org/honeysql)
## Note on code samples
All sample code in this README is automatically run as a unit test using
[midje-readme](https://github.com/boxed/midje-readme).
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.
## Usage ## Usage
```clj ```clojure
(require '[honeysql.core :as sql] (require '[honeysql.core :as sql]
'[honeysql.helpers :refer :all]) '[honeysql.helpers :refer :all :as helpers])
``` ```
Everything is built on top of maps representing SQL queries: Everything is built on top of maps representing SQL queries:
```clj ```clojure
(def sqlmap {:select [:a :b :c] (def sqlmap {:select [:a :b :c]
:from [:foo] :from [:foo]
:where [:= :f.a "baz"]}) :where [:= :f.a "baz"]})
@ -28,7 +38,7 @@ Everything is built on top of maps representing SQL queries:
`format` turns maps into `clojure.java.jdbc`-compatible, parameterized SQL: `format` turns maps into `clojure.java.jdbc`-compatible, parameterized SQL:
```clj ```clojure
(sql/format sqlmap) (sql/format sqlmap)
=> ["SELECT a, b, c FROM foo WHERE f.a = ?" "baz"] => ["SELECT a, b, c FROM foo WHERE f.a = ?" "baz"]
``` ```
@ -43,7 +53,7 @@ to jdbc:
You can build up SQL maps yourself or use helper functions. `build` is the Swiss Army Knife helper. It lets you leave out brackets here and there: You can build up SQL maps yourself or use helper functions. `build` is the Swiss Army Knife helper. It lets you leave out brackets here and there:
```clj ```clojure
(sql/build :select :* (sql/build :select :*
:from :foo :from :foo
:where [:= :f.a "baz"]) :where [:= :f.a "baz"])
@ -52,14 +62,18 @@ You can build up SQL maps yourself or use helper functions. `build` is the Swiss
You can provide a "base" map as the first argument to build: You can provide a "base" map as the first argument to build:
```clj ```clojure
(sql/build sqlmap :offset 10 :limit 10) (sql/build sqlmap :offset 10 :limit 10)
=> {:limit 10, :offset 10, :select [:a :b :c], :where [:= :f.a "baz"], :from [:foo]} => {:limit 10
:offset 10
:select [:a :b :c]
:where [:= :f.a "baz"]
:from [:foo]}
``` ```
There are also functions for each clause type in the `honeysql.helpers` namespace: There are also functions for each clause type in the `honeysql.helpers` namespace:
```clj ```clojure
(-> (select :a :b :c) (-> (select :a :b :c)
(from :foo) (from :foo)
(where [:= :f.a "baz"])) (where [:= :f.a "baz"]))
@ -67,7 +81,7 @@ There are also functions for each clause type in the `honeysql.helpers` namespac
Order doesn't matter: Order doesn't matter:
```clj ```clojure
(= (-> (select :*) (from :foo)) (= (-> (select :*) (from :foo))
(-> (from :foo) (select :*))) (-> (from :foo) (select :*)))
=> true => true
@ -75,14 +89,14 @@ Order doesn't matter:
When using the vanilla helper functions, new clauses will replace old clauses: When using the vanilla helper functions, new clauses will replace old clauses:
```clj ```clojure
(-> sqlmap (select :*)) (-> sqlmap (select :*))
=> {:from [:foo], :where [:= :f.a "baz"], :select (:*)} => '{:from [:foo], :where [:= :f.a "baz"], :select (:*)}
``` ```
To add to clauses instead of replacing them, use `merge-select`, `merge-where`, etc.: To add to clauses instead of replacing them, use `merge-select`, `merge-where`, etc.:
```clj ```clojure
(-> sqlmap (-> sqlmap
(merge-select :d :e) (merge-select :d :e)
(merge-where [:> :b 10]) (merge-where [:> :b 10])
@ -92,7 +106,7 @@ To add to clauses instead of replacing them, use `merge-select`, `merge-where`,
`where` will combine multiple clauses together using and: `where` will combine multiple clauses together using and:
```clj ```clojure
(-> (select :*) (-> (select :*)
(from :foo) (from :foo)
(where [:= :a 1] [:< :b 100]) (where [:= :a 1] [:< :b 100])
@ -104,7 +118,7 @@ Inserts are supported in two patterns.
In the first pattern, you must explicitly specify the columns to insert, In the first pattern, you must explicitly specify the columns to insert,
then provide a collection of rows, each a collection of column values: then provide a collection of rows, each a collection of column values:
```clj ```clojure
(-> (insert-into :properties) (-> (insert-into :properties)
(columns :name :surname :age) (columns :name :surname :age)
(values (values
@ -112,7 +126,8 @@ then provide a collection of rows, each a collection of column values:
["Andrew" "Cooper" 12] ["Andrew" "Cooper" 12]
["Jane" "Daniels" 56]]) ["Jane" "Daniels" 56]])
sql/format) sql/format)
=> ["INSERT INTO properties (name, surname, age) => [#sql/regularize
"INSERT INTO properties (name, surname, age)
VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)" VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)"
"Jon" "Smith" 34 "Andrew" "Cooper" 12 "Jane" "Daniels" 56] "Jon" "Smith" 34 "Andrew" "Cooper" 12 "Jane" "Daniels" 56]
``` ```
@ -121,13 +136,14 @@ then provide a collection of rows, each a collection of column values:
Alternately, you can simply specify the values as maps; the first map defines the columns to insert, Alternately, you can simply specify the values as maps; the first map defines the columns to insert,
and the remaining maps *must* have the same set of keys and values: and the remaining maps *must* have the same set of keys and values:
```clj ```clojure
(-> (insert-into :properties) (-> (insert-into :properties)
(values [{:name "John" :surname "Smith" :age 34} (values [{:name "John" :surname "Smith" :age 34}
{:name "Andrew" :surname "Cooper" :age 12} {:name "Andrew" :surname "Cooper" :age 12}
{:name "Jane" :surname "Daniels" :age 56}]) {:name "Jane" :surname "Daniels" :age 56}])
sql/format) sql/format)
=> ["INSERT INTO properties (name, surname, age) => [#sql/regularize
"INSERT INTO properties (name, surname, age)
VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)" VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?)"
"John" "Smith" 34 "John" "Smith" 34
"Andrew" "Cooper" 12 "Andrew" "Cooper" 12
@ -136,7 +152,7 @@ and the remaining maps *must* have the same set of keys and values:
The column values do not have to be literals, they can be nested queries: The column values do not have to be literals, they can be nested queries:
```clj ```clojure
(let [user-id 12345 (let [user-id 12345
role-name "user"] role-name "user"]
(-> (insert-into :user_profile_to_role) (-> (insert-into :user_profile_to_role)
@ -146,7 +162,8 @@ The column values do not have to be literals, they can be nested queries:
(where [:= :name role-name]))}]) (where [:= :name role-name]))}])
sql/format)) sql/format))
=> ["INSERT INTO user_profile_to_role (user_profile_id, role_id) => [#sql/regularize
"INSERT INTO user_profile_to_role (user_profile_id, role_id)
VALUES (?, (SELECT id FROM role WHERE name = ?))" VALUES (?, (SELECT id FROM role WHERE name = ?))"
12345 12345
"user"] "user"]
@ -155,18 +172,18 @@ The column values do not have to be literals, they can be nested queries:
Updates are possible too (note the double S in `sset` to avoid clashing Updates are possible too (note the double S in `sset` to avoid clashing
with `clojure.core/set`): with `clojure.core/set`):
```clj ```clojure
(-> (update :films) (-> (helpers/update :films)
(sset {:kind "dramatic" (sset {:kind "dramatic"
:watched true}) :watched true})
(where [:= :kind "drama"]) (where [:= :kind "drama"])
sql/format) sql/format)
=> ["UPDATE films SET watched = TRUE, kind = ? WHERE kind = ?" "dramatic" "drama"] => ["UPDATE films SET kind = ?, watched = TRUE WHERE kind = ?" "dramatic" "drama"]
``` ```
Deletes look as you would expect: Deletes look as you would expect:
```clj ```clojure
(-> (delete-from :films) (-> (delete-from :films)
(where [:<> :kind "musical"]) (where [:<> :kind "musical"])
sql/format) sql/format)
@ -175,7 +192,7 @@ Deletes look as you would expect:
Queries can be nested: Queries can be nested:
```clj ```clojure
(-> (select :*) (-> (select :*)
(from :foo) (from :foo)
(where [:in :foo.a (-> (select :a) (from :bar))]) (where [:in :foo.a (-> (select :a) (from :bar))])
@ -185,7 +202,7 @@ Queries can be nested:
Queries may be united within a :union or :union-all keyword: Queries may be united within a :union or :union-all keyword:
```clj ```clojure
(sql/format {:union [(-> (select :*) (from :foo)) (sql/format {:union [(-> (select :*) (from :foo))
(-> (select :*) (from :bar))]}) (-> (select :*) (from :bar))]})
=> ["SELECT * FROM foo UNION SELECT * FROM bar"] => ["SELECT * FROM foo UNION SELECT * FROM bar"]
@ -193,7 +210,7 @@ Queries may be united within a :union or :union-all keyword:
Keywords that begin with `%` are interpreted as SQL function calls: Keywords that begin with `%` are interpreted as SQL function calls:
```clj ```clojure
(-> (select :%count.*) (from :foo) sql/format) (-> (select :%count.*) (from :foo) sql/format)
=> ["SELECT count(*) FROM foo"] => ["SELECT count(*) FROM foo"]
(-> (select :%max.id) (from :foo) sql/format) (-> (select :%max.id) (from :foo) sql/format)
@ -202,7 +219,7 @@ Keywords that begin with `%` are interpreted as SQL function calls:
Keywords that begin with `?` are interpreted as bindable parameters: Keywords that begin with `?` are interpreted as bindable parameters:
```clj ```clojure
(-> (select :id) (-> (select :id)
(from :foo) (from :foo)
(where [:= :a :?baz]) (where [:= :a :?baz])
@ -212,19 +229,24 @@ Keywords that begin with `?` are interpreted as bindable parameters:
There are helper functions and data literals for SQL function calls, field qualifiers, raw SQL fragments, and named input parameters: There are helper functions and data literals for SQL function calls, field qualifiers, raw SQL fragments, and named input parameters:
```clj ```clojure
(-> (select (sql/call :foo :bar) (sql/qualify :foo :a) (sql/raw "@var := foo.bar")) (def call-qualify-map
(from :foo) (-> (select (sql/call :foo :bar) (sql/qualify :foo :a) (sql/raw "@var := foo.bar"))
(where [:= :a (sql/param :baz)])) (from :foo)
=> {:where [:= :a #sql/param :baz], :from (:foo), :select (#sql/call [:foo :bar] :foo.a #sql/raw "@var := foo.bar")} (where [:= :a (sql/param :baz)])))
(sql/format *1 :params {:baz "BAZ"}) call-qualify-map
=> '{:where [:= :a #sql/param :baz]
:from (:foo)
:select (#sql/call [:foo :bar] :foo.a #sql/raw "@var := foo.bar")}
(sql/format call-qualify-map :params {:baz "BAZ"})
=> ["SELECT foo(bar), foo.a, @var := foo.bar FROM foo WHERE a = ?" "BAZ"] => ["SELECT foo(bar), foo.a, @var := foo.bar FROM foo WHERE a = ?" "BAZ"]
``` ```
To quote identifiers, pass the `:quoting` keyword option to `format`. Valid options are `:ansi` (PostgreSQL), `:mysql`, or `:sqlserver`: To quote identifiers, pass the `:quoting` keyword option to `format`. Valid options are `:ansi` (PostgreSQL), `:mysql`, or `:sqlserver`:
```clj ```clojure
(-> (select :foo.a) (-> (select :foo.a)
(from :foo) (from :foo)
(where [:= :foo.a "baz"]) (where [:= :foo.a "baz"])
@ -236,7 +258,7 @@ To issue a locking select, add a :lock to the query or use the lock helper. The
modes are the standard :update (FOR UPDATE) or the vendor-specific :mysql-share (LOCK IN SHARE MODE) or :postresql-share (FOR SHARE). The modes are the standard :update (FOR UPDATE) or the vendor-specific :mysql-share (LOCK IN SHARE MODE) or :postresql-share (FOR SHARE). The
lock map may also provide a :wait value, which if false will append the NOWAIT parameter, supported by PostgreSQL. lock map may also provide a :wait value, which if false will append the NOWAIT parameter, supported by PostgreSQL.
```clj ```clojure
(-> (select :foo.a) (-> (select :foo.a)
(from :foo) (from :foo)
(where [:= :foo.a "baz"]) (where [:= :foo.a "baz"])
@ -248,36 +270,39 @@ lock map may also provide a :wait value, which if false will append the NOWAIT p
To support novel lock modes, implement the `format-lock-clause` multimethod. To support novel lock modes, implement the `format-lock-clause` multimethod.
To be able to use dashes in quoted names, you can pass ```:allow-dashed-names true``` as an argument to the ```format``` function. To be able to use dashes in quoted names, you can pass ```:allow-dashed-names true``` as an argument to the ```format``` function.
```clj ```clojure
(format (sql/format
{:select [:f.foo-id :f.foo-name] {:select [:f.foo-id :f.foo-name]
:from [[:foo-bar :f]] :from [[:foo-bar :f]]
:where [:= :f.foo-id 12345]} :where [:= :f.foo-id 12345]}
:allow-dashed-names? true :allow-dashed-names? true
:quoting :ansi) :quoting :ansi)
=> ["SELECT \"f\".\"foo-id\", \"f\".\"foo-name\" FROM \"foo-bar\" \"f\" WHERE \"f\".\"foo-id\" = 12345"] => ["SELECT \"f\".\"foo-id\", \"f\".\"foo-name\" FROM \"foo-bar\" \"f\" WHERE \"f\".\"foo-id\" = ?" 12345]
``` ```
Here's a big, complicated query. Note that Honey SQL makes no attempt to verify that your queries make any sense. It merely renders surface syntax. Here's a big, complicated query. Note that Honey SQL makes no attempt to verify that your queries make any sense. It merely renders surface syntax.
```clj ```clojure
(-> (select :f.* :b.baz :c.quux [:b.bla "bla-bla"] (def big-complicated-map
(sql/call :now) (sql/raw "@x := 10")) (-> (select :f.* :b.baz :c.quux [:b.bla "bla-bla"]
(modifiers :distinct) (sql/call :now) (sql/raw "@x := 10"))
(from [:foo :f] [:baz :b]) (modifiers :distinct)
(join :draq [:= :f.b :draq.x]) (from [:foo :f] [:baz :b])
(left-join [:clod :c] [:= :f.a :c.d]) (join :draq [:= :f.b :draq.x])
(right-join :bock [:= :bock.z :c.e]) (left-join [:clod :c] [:= :f.a :c.d])
(where [:or (right-join :bock [:= :bock.z :c.e])
[:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]] (where [:or
[:< 1 2 3] [:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]]
[:in :f.e [1 (sql/param :param2) 3]] [:< 1 2 3]
[:between :f.e 10 20]]) [:in :f.e [1 (sql/param :param2) 3]]
(group :f.a) [:between :f.e 10 20]])
(having [:< 0 :f.e]) (group :f.a)
(order-by [:b.baz :desc] :c.quux [:f.a :nulls-first]) (having [:< 0 :f.e])
(limit 50) (order-by [:b.baz :desc] :c.quux [:f.a :nulls-first])
(offset 10)) (limit 50)
(offset 10)))
big-complicated-map
=> {:select [:f.* :b.baz :c.quux [:b.bla "bla-bla"] => {:select [:f.* :b.baz :c.quux [:b.bla "bla-bla"]
(sql/call :now) (sql/raw "@x := 10")] (sql/call :now) (sql/raw "@x := 10")]
:modifiers [:distinct] :modifiers [:distinct]
@ -296,8 +321,9 @@ Here's a big, complicated query. Note that Honey SQL makes no attempt to verify
:limit 50 :limit 50
:offset 10} :offset 10}
(sql/format *1 {:param1 "gabba" :param2 2}) (sql/format big-complicated-map {:param1 "gabba" :param2 2})
=> ["SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS bla_bla, now(), @x := 10 => [#sql/regularize
"SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS bla_bla, now(), @x := 10
FROM foo f, baz b FROM foo f, baz b
INNER JOIN draq ON f.b = draq.x INNER JOIN draq ON f.b = draq.x
LEFT JOIN clod c ON f.a = c.d LEFT JOIN clod c ON f.a = c.d
@ -314,7 +340,7 @@ Here's a big, complicated query. Note that Honey SQL makes no attempt to verify
"bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10] "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
;; Printable and readable ;; Printable and readable
(= *2 (read-string (pr-str *2))) (= big-complicated-map (read-string (pr-str big-complicated-map)))
=> true => true
``` ```
@ -322,7 +348,7 @@ Here's a big, complicated query. Note that Honey SQL makes no attempt to verify
You can define your own function handlers for use in `where`: You can define your own function handlers for use in `where`:
```clj ```clojure
(require '[honeysql.format :as fmt]) (require '[honeysql.format :as fmt])
(defmethod fmt/fn-handler "betwixt" [_ field lower upper] (defmethod fmt/fn-handler "betwixt" [_ field lower upper]
@ -336,7 +362,7 @@ You can define your own function handlers for use in `where`:
You can also define your own clauses: You can also define your own clauses:
```clj ```clojure
;; Takes a MapEntry of the operator & clause data, plus the entire SQL map ;; Takes a MapEntry of the operator & clause data, plus the entire SQL map
(defmethod fmt/format-clause :foobar [[op v] sqlmap] (defmethod fmt/format-clause :foobar [[op v] sqlmap]

View file

@ -6,6 +6,7 @@
:scm {:name "git" :scm {:name "git"
:url "https://github.com/jkk/honeysql"} :url "https://github.com/jkk/honeysql"}
:dependencies [[org.clojure/clojure "1.8.0"]] :dependencies [[org.clojure/clojure "1.8.0"]]
:aliases {"test-readme" ["with-profile" "midje" "midje"]}
:cljsbuild {:builds {:release {:source-paths ["src"] :cljsbuild {:builds {:release {:source-paths ["src"]
:compiler {:output-to "dist/honeysql.js" :compiler {:output-to "dist/honeysql.js"
:optimizations :advanced :optimizations :advanced
@ -20,7 +21,14 @@
:parallel-build true :parallel-build true
:target :nodejs}}}} :target :nodejs}}}}
:doo {:build "test"} :doo {:build "test"}
:profiles {:dev {:dependencies [[org.clojure/clojure "1.8.0"] :profiles {:midje {:dependencies [[midje "1.9.0-alpha5"]]
:plugins [[lein-midje "3.2.1"]
[midje-readme "1.0.9"]]
:midje-readme {:require "[honeysql.core :as sql]
[honeysql.helpers :refer :all :as helpers]
[honeysql.format :as fmt]
[honeysql.helpers :refer [defhelper]]"}}
:dev {:dependencies [[org.clojure/clojure "1.8.0"]
[org.clojure/clojurescript "1.9.521"] [org.clojure/clojurescript "1.9.521"]
[cljsbuild "1.1.6"]] [cljsbuild "1.1.6"]]
:plugins [[lein-cljsbuild "1.1.6"] :plugins [[lein-cljsbuild "1.1.6"]

View file

@ -1,4 +1,5 @@
{sql/call honeysql.types/read-sql-call {sql/call honeysql.types/read-sql-call
sql/raw honeysql.types/read-sql-raw sql/raw honeysql.types/read-sql-raw
sql/param honeysql.types/read-sql-param sql/param honeysql.types/read-sql-param
sql/array honeysql.types/read-sql-array} sql/array honeysql.types/read-sql-array
sql/regularize honeysql.format/regularize}

View file

@ -587,3 +587,6 @@
(let [pred (format-predicate* condition)] (let [pred (format-predicate* condition)]
(str "WHEN " pred " THEN " (to-sql result)))))) (str "WHEN " pred " THEN " (to-sql result))))))
" END")) " END"))
(defn regularize [sql-string]
(string/replace sql-string #"\s+" " "))