2012-07-13 01:50:13 +00:00
|
|
|
# Honey SQL
|
|
|
|
|
|
2012-08-24 22:20:58 +00:00
|
|
|
Turn Clojure data structures into SQL.
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-12-03 17:38:48 +00:00
|
|
|
Why? So that the data can be easily built, examined, and transformed.
|
|
|
|
|
|
2012-08-24 22:49:52 +00:00
|
|
|
## Leiningen Coordinate
|
|
|
|
|
|
2012-08-25 01:40:48 +00:00
|
|
|
```clj
|
2012-11-01 17:58:16 +00:00
|
|
|
[honeysql "0.2.0"]
|
2012-08-24 22:49:52 +00:00
|
|
|
```
|
|
|
|
|
|
2012-07-13 01:50:13 +00:00
|
|
|
## Usage
|
|
|
|
|
|
2012-07-13 13:57:47 +00:00
|
|
|
```clj
|
2012-08-24 22:20:58 +00:00
|
|
|
(require '[honeysql.core :as sql]
|
|
|
|
|
'[honeysql.helpers :refer :all])
|
2012-07-13 15:46:50 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Everything is built on top of maps representing SQL queries:
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-07-13 15:46:50 +00:00
|
|
|
```clj
|
2012-07-13 13:57:47 +00:00
|
|
|
(def sqlmap {:select [:a :b :c]
|
|
|
|
|
:from [:foo]
|
|
|
|
|
:where [:= :f.a "baz"]})
|
2012-07-13 15:46:50 +00:00
|
|
|
```
|
|
|
|
|
|
2012-07-13 17:13:37 +00:00
|
|
|
`format` turns maps into `clojure.java.jdbc`-compatible, parameterized SQL:
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-07-13 15:46:50 +00:00
|
|
|
```clj
|
2012-07-13 14:53:19 +00:00
|
|
|
(sql/format sqlmap)
|
2012-07-13 21:20:48 +00:00
|
|
|
=> ["SELECT a, b, c FROM foo WHERE (f.a = ?)" "baz"]
|
2012-07-13 15:46:50 +00:00
|
|
|
```
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-08-24 22:31:49 +00:00
|
|
|
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:
|
2012-08-24 22:20:58 +00:00
|
|
|
|
|
|
|
|
```clj
|
|
|
|
|
(sql/build :select :*
|
|
|
|
|
:from :foo
|
|
|
|
|
:where [:= :f.a "baz"])
|
|
|
|
|
=> {:where [:= :f.a "baz"], :from [:foo], :select [:*]}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You can provide a "base" map as the first argument to build:
|
|
|
|
|
|
|
|
|
|
```clj
|
|
|
|
|
(sql/build sqlmap :offset 10 :limit 10)
|
|
|
|
|
=> {: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:
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-07-13 15:46:50 +00:00
|
|
|
```clj
|
|
|
|
|
(-> (select :a :b :c)
|
|
|
|
|
(from :foo)
|
|
|
|
|
(where [:= :f.a "baz"]))
|
|
|
|
|
```
|
|
|
|
|
|
2012-07-13 17:13:37 +00:00
|
|
|
Order doesn't matter:
|
2012-07-13 15:46:50 +00:00
|
|
|
|
|
|
|
|
```clj
|
|
|
|
|
(= (-> (select :*) (from :foo))
|
|
|
|
|
(-> (from :foo) (select :*)))
|
2012-07-13 14:53:19 +00:00
|
|
|
=> true
|
2012-07-13 15:46:50 +00:00
|
|
|
```
|
2012-07-13 14:53:19 +00:00
|
|
|
|
2012-07-13 17:13:37 +00:00
|
|
|
When using the vanilla helper functions, new clauses will replace old clauses:
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-07-13 15:46:50 +00:00
|
|
|
```clj
|
|
|
|
|
(-> sqlmap (select :*))
|
|
|
|
|
=> {:from [:foo], :where [:= :f.a "baz"], :select (:*)}
|
|
|
|
|
```
|
|
|
|
|
|
2012-07-13 17:13:37 +00:00
|
|
|
To add to clauses instead of replacing them, use `merge-select`, `merge-where`, etc.:
|
2012-07-13 15:46:50 +00:00
|
|
|
|
|
|
|
|
```clj
|
2012-08-24 22:20:58 +00:00
|
|
|
(-> sqlmap
|
|
|
|
|
(merge-select :d :e)
|
|
|
|
|
(merge-where [:> :b 10])
|
|
|
|
|
sql/format)
|
|
|
|
|
=> ["SELECT a, b, c, d, e FROM foo WHERE (f.a = ? AND b > 10)" "baz"]
|
2012-07-13 15:46:50 +00:00
|
|
|
```
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-07-13 15:46:50 +00:00
|
|
|
Queries can be nested:
|
|
|
|
|
|
|
|
|
|
```clj
|
2012-08-24 22:20:58 +00:00
|
|
|
(-> (select :*)
|
|
|
|
|
(from :foo)
|
|
|
|
|
(where [:in :foo.a (-> (select :a) (from :bar))])
|
|
|
|
|
sql/format)
|
2012-07-13 13:57:47 +00:00
|
|
|
=> ["SELECT * FROM foo WHERE (foo.a IN (SELECT a FROM bar))"]
|
2012-07-13 15:46:50 +00:00
|
|
|
```
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-08-25 03:06:24 +00:00
|
|
|
There are helper functions and data literals for field qualifiers, SQL function
|
|
|
|
|
calls, raw SQL fragments, and named input parameters:
|
2012-07-13 15:46:50 +00:00
|
|
|
|
|
|
|
|
```clj
|
2012-08-25 03:06:24 +00:00
|
|
|
(-> (select (sql/qualify :foo :a) (sql/call :count :*) (sql/raw "@var := foo.bar"))
|
|
|
|
|
(from :foo)
|
|
|
|
|
(where [:= :a (sql/param :baz)]))
|
|
|
|
|
=> {:where [:= :a #sql/param :baz], :from (:foo), :select (#sql/call [:count :*] #sql/raw "@var := foo.bar")}
|
2012-07-13 13:57:47 +00:00
|
|
|
|
2012-08-25 03:06:24 +00:00
|
|
|
(sql/format *1 {:baz "BAZ"})
|
|
|
|
|
=> ["SELECT COUNT(*), @var := foo.bar FROM foo WHERE a = ?" "BAZ"]
|
2012-07-13 13:57:47 +00:00
|
|
|
```
|
2012-07-13 01:50:13 +00:00
|
|
|
|
2012-08-25 03:06:24 +00:00
|
|
|
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.
|
2012-07-13 15:46:50 +00:00
|
|
|
|
|
|
|
|
```clj
|
2012-10-19 16:41:26 +00:00
|
|
|
(-> (select :f.* :b.baz :c.quux [:b.bla "bla-bla"]
|
|
|
|
|
(sql/call :now) (sql/raw "@x := 10"))
|
2012-07-13 15:46:50 +00:00
|
|
|
(modifiers :distinct)
|
|
|
|
|
(from [:foo :f] [:baz :b])
|
2012-10-19 16:41:26 +00:00
|
|
|
(join :draq [:= :f.b :draq.x])
|
|
|
|
|
(left-join [:clod :c] [:= :f.a :c.d])
|
|
|
|
|
(right-join :bock [:= :bock.z :c.e])
|
2012-07-13 15:46:50 +00:00
|
|
|
(where [:or
|
2012-10-19 16:41:26 +00:00
|
|
|
[: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)
|
2012-07-13 17:09:02 +00:00
|
|
|
(having [:< 0 :f.e])
|
2012-07-13 15:46:50 +00:00
|
|
|
(order-by [:b.baz :desc] :c.quux)
|
|
|
|
|
(limit 50)
|
2012-10-19 16:41:26 +00:00
|
|
|
(offset 10))
|
|
|
|
|
=> {: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]
|
|
|
|
|
:having [:< 0 :f.e]
|
|
|
|
|
:order-by [[:b.baz :desc] :c.quux]
|
|
|
|
|
:limit 50
|
|
|
|
|
:offset 10}
|
|
|
|
|
|
|
|
|
|
(sql/format *1 {:param1 "gabba" :param2 2})
|
|
|
|
|
=> ["SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS \"bla-bla\", NOW(), @x := 10
|
|
|
|
|
FROM foo AS f, baz AS b
|
|
|
|
|
INNER JOIN draq ON f.b = draq.x
|
|
|
|
|
LEFT JOIN clod AS c ON f.a = c.d
|
|
|
|
|
RIGHT JOIN bock ON bock.z = c.e
|
|
|
|
|
WHERE ((f.a = ? AND b.baz <> ?)
|
|
|
|
|
OR (1 < 2 AND 2 < 3)
|
|
|
|
|
OR (f.e IN (1, ?, 3))
|
|
|
|
|
OR f.e BETWEEN 10 AND 20)
|
|
|
|
|
GROUP BY f.a
|
|
|
|
|
HAVING 0 < f.e
|
|
|
|
|
ORDER BY b.baz DESC, c.quux
|
|
|
|
|
LIMIT 50
|
|
|
|
|
OFFSET 10 "
|
|
|
|
|
"bort" "gabba" 2]
|
2012-07-13 17:09:02 +00:00
|
|
|
|
2012-07-13 17:13:37 +00:00
|
|
|
;; Printable and readable
|
2012-10-19 16:41:26 +00:00
|
|
|
(= *2 (read-string (pr-str *2)))
|
2012-07-13 17:09:02 +00:00
|
|
|
=> true
|
2012-07-13 15:46:50 +00:00
|
|
|
```
|
|
|
|
|
|
2012-08-24 22:20:58 +00:00
|
|
|
## Extensibility
|
|
|
|
|
|
|
|
|
|
You can define your own function handlers for use in `where`:
|
|
|
|
|
|
|
|
|
|
```clj
|
|
|
|
|
(require '[honeysql.format :as fmt])
|
|
|
|
|
|
2012-08-24 22:31:49 +00:00
|
|
|
(defmethod fmt/fn-handler "betwixt" [_ field lower upper]
|
|
|
|
|
(str (fmt/to-sql field) " BETWIXT "
|
2012-08-24 22:20:58 +00:00
|
|
|
(fmt/to-sql lower) " AND " (fmt/to-sql upper)))
|
|
|
|
|
|
2012-08-24 22:31:49 +00:00
|
|
|
(-> (select :a) (where [:betwixt :a 1 10]) sql/format)
|
|
|
|
|
=> ["SELECT a WHERE a BETWIXT 1 AND 10"]
|
|
|
|
|
|
2012-08-24 22:20:58 +00:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
You can also define your own clauses:
|
|
|
|
|
|
|
|
|
|
```clj
|
|
|
|
|
|
|
|
|
|
;; 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)))
|
|
|
|
|
|
|
|
|
|
(sql/format {:select [:a :b] :foobar :baz})
|
|
|
|
|
=> ["SELECT a, b FOOBAR baz"]
|
|
|
|
|
|
|
|
|
|
(require '[honeysql.helpers :refer [defhelper]])
|
2012-08-24 22:37:03 +00:00
|
|
|
|
|
|
|
|
;; Defines a helper function, and allows 'build' to recognize your clause
|
2012-08-24 22:20:58 +00:00
|
|
|
(defhelper foobar [m args]
|
|
|
|
|
(assoc m :foobar (first args)))
|
|
|
|
|
|
|
|
|
|
(-> (select :a :b) (foobar :baz) sql/format)
|
|
|
|
|
=> ["SELECT a, b FOOBAR baz"]
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
2012-10-22 14:20:26 +00:00
|
|
|
If you do implement a clause or function handler, consider submitting a pull request so others can use it, too.
|
|
|
|
|
|
2012-07-13 17:11:46 +00:00
|
|
|
## TODO
|
|
|
|
|
|
|
|
|
|
* Insert, update, delete
|
|
|
|
|
* Create table, etc.
|
|
|
|
|
|
2012-07-13 01:50:13 +00:00
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
Copyright © 2012 Justin Kramer
|
|
|
|
|
|
|
|
|
|
Distributed under the Eclipse Public License, the same as Clojure.
|