honeysql/README.md

172 lines
4.2 KiB
Markdown
Raw Normal View History

# Honey SQL
2012-08-24 22:20:58 +00:00
Turn Clojure data structures into SQL.
2012-07-13 13:57:47 +00:00
**Work in progress**
## 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-07-13 15:46:50 +00:00
There are helper functions and data literals for handling SQL function
calls and raw SQL fragments:
```clj
(-> (select (sql/call :count :*) (sql/raw "@var := foo.bar"))
(from :foo))
2012-07-13 14:53:19 +00:00
=> {:from (:foo), :select (#sql/call [:count :*] #sql/raw "@var := foo.bar")}
2012-07-13 13:57:47 +00:00
2012-07-13 14:53:19 +00:00
(sql/format *1)
2012-07-13 13:57:47 +00:00
=> ["SELECT COUNT(*), @var := foo.bar FROM foo"]
```
2012-08-24 22:31:49 +00:00
Here's a big, complicated query. Note that Honey SQL makes no attempt to verify that your queries are valid.
2012-07-13 15:46:50 +00:00
```clj
(-> (select :f.* :b.baz :c.quux (sql/call :now) (sql/raw "@x := 10"))
2012-07-13 15:46:50 +00:00
(modifiers :distinct)
(from [:foo :f] [:baz :b])
(join [[:clod :c] [:= :f.a :c.d] :left]
[:draq [:= :f.b :draq.x]])
(where [:or
[:and [:= :f.a "bort"] [:not= :b.baz "gabba"]]
[:in :f.e [1 2 3]]
[:between :f.e 10 20]])
2012-08-24 22:20:58 +00:00
(group :f.a) ;note the name change
(having [:< 0 :f.e])
2012-07-13 15:46:50 +00:00
(order-by [:b.baz :desc] :c.quux)
(limit 50)
2012-08-24 22:20:58 +00:00
(offset 10)
sql/format)
=> ["SELECT DISTINCT f.*, b.baz, c.quux, NOW(), @x := 10 FROM foo AS f, baz AS b LEFT JOIN clod AS c ON f.a = c.d JOIN draq ON f.b = draq.x WHERE ((f.a = ? AND b.baz <> ?) OR (f.e IN (1, 2, 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"]
2012-07-13 17:13:37 +00:00
;; Printable and readable
2012-08-24 22:20:58 +00:00
(= *1 (read-string (pr-str *1)))
=> 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]])
(defhelper foobar [m args]
(assoc m :foobar (first args)))
(-> (select :a :b) (foobar :baz) sql/format)
=> ["SELECT a, b FOOBAR baz"]
```
2012-07-13 17:11:46 +00:00
## TODO
* Insert, update, delete
* Create table, etc.
## License
Copyright © 2012 Justin Kramer
Distributed under the Eclipse Public License, the same as Clojure.