initial commit - basic working data structure -> sql
This commit is contained in:
commit
22866a6edc
6 changed files with 223 additions and 0 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
/target
|
||||
/lib
|
||||
/classes
|
||||
/checkouts
|
||||
pom.xml
|
||||
*.jar
|
||||
*.class
|
||||
.lein-deps-sum
|
||||
.lein-failures
|
||||
.lein-plugins
|
||||
13
README.md
Normal file
13
README.md
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# Honey SQL
|
||||
|
||||
SQL as data structures. Or SQL sugar, as it were.
|
||||
|
||||
## Usage
|
||||
|
||||
FIXME
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2012 Justin Kramer
|
||||
|
||||
Distributed under the Eclipse Public License, the same as Clojure.
|
||||
5
project.clj
Normal file
5
project.clj
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
(defproject honeysql "0.1.0-SNAPSHOT"
|
||||
:description "SQL as data structures"
|
||||
:license {:name "Eclipse Public License"
|
||||
:url "http://www.eclipse.org/legal/epl-v10.html"}
|
||||
:dependencies [[org.clojure/clojure "1.4.0"]])
|
||||
2
resources/data_readers.clj
Normal file
2
resources/data_readers.clj
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
{sql/fn honeysql.format/read-sql-fn
|
||||
sql/raw honeysql.format/read-sql-raw}
|
||||
1
src/honeysql/core.clj
Normal file
1
src/honeysql/core.clj
Normal file
|
|
@ -0,0 +1 @@
|
|||
(ns honeysql.core)
|
||||
192
src/honeysql/format.clj
Normal file
192
src/honeysql/format.clj
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
(ns honeysql.format
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
;;(set! *warn-on-reflection* true)
|
||||
|
||||
;;;;
|
||||
|
||||
(deftype SqlFn [name args])
|
||||
|
||||
(defn sql-fn [name & args]
|
||||
(SqlFn. name args))
|
||||
|
||||
(defn read-sql-fn [form]
|
||||
(apply sql-fn form))
|
||||
|
||||
(defmethod print-method SqlFn [^SqlFn o ^java.io.Writer w]
|
||||
(.write w (str "#sql/fn " (pr-str (into [(.name o)] (.args o))))))
|
||||
|
||||
(defmethod print-dup SqlFn [o w]
|
||||
(print-method o w))
|
||||
|
||||
;;;;
|
||||
|
||||
(deftype SqlRaw [s])
|
||||
|
||||
(defn sql-raw [s]
|
||||
(SqlRaw. (str s)))
|
||||
|
||||
(defn read-sql-raw [form]
|
||||
(sql-raw form))
|
||||
|
||||
(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))
|
||||
|
||||
;;;;
|
||||
|
||||
(defn comma-join [s]
|
||||
(string/join ", " s))
|
||||
|
||||
(defn space-join [s]
|
||||
(string/join " " s))
|
||||
|
||||
(defn paren-wrap [x]
|
||||
(str "(" x ")"))
|
||||
|
||||
(def ^:dynamic *params*
|
||||
"Will be bound to an atom-vector that accumulates SQL parameters across
|
||||
possibly-recursive function calls"
|
||||
nil)
|
||||
|
||||
(def ^:dynamic *fn-context?* false)
|
||||
|
||||
(def infix-fns
|
||||
#{"+" "-" "*" "/" "%" "mod" "|" "&" "^"
|
||||
"is" "=" ">" ">=" "<" "<=" "<>" "!="
|
||||
"and" "or" "xor"
|
||||
"in" "like" "regexp"})
|
||||
|
||||
(def fn-handlers
|
||||
{"between" (fn [field upper lower]
|
||||
(str field " BETWEEN " upper " AND " lower))})
|
||||
|
||||
(def clause-order
|
||||
"Determines the order that clauses will be placed within generated SQL"
|
||||
[:select :from :join :where :group-by :having :order-by :limit :offset])
|
||||
|
||||
(declare to-sql)
|
||||
|
||||
(defn format-sql [sql-map]
|
||||
(binding [*params* (atom [])]
|
||||
(let [sql-str (to-sql sql-map)]
|
||||
(if (seq @*params*)
|
||||
[sql-str @*params*]
|
||||
[sql-str]))))
|
||||
|
||||
(defprotocol ToSql
|
||||
(-to-sql [x]))
|
||||
|
||||
(declare format-clause)
|
||||
|
||||
(extend-protocol ToSql
|
||||
clojure.lang.Keyword
|
||||
(-to-sql [x] (name x))
|
||||
clojure.lang.Symbol
|
||||
(-to-sql [x] (str x))
|
||||
java.lang.Number
|
||||
(-to-sql [x] (str x))
|
||||
java.lang.Boolean
|
||||
(-to-sql [x] (if x "TRUE" "FALSE"))
|
||||
clojure.lang.Sequential
|
||||
(-to-sql [x] (if *fn-context?*
|
||||
;; list argument in fn call
|
||||
(paren-wrap (comma-join (map to-sql x)))
|
||||
;; alias
|
||||
(str (to-sql (first x))
|
||||
" AS "
|
||||
(to-sql (second x)))))
|
||||
SqlFn
|
||||
(-to-sql [x] (binding [*fn-context?* true]
|
||||
(let [fn-name (name (.name x))
|
||||
fn-name-upper (string/upper-case fn-name)
|
||||
args (map to-sql (.args x))]
|
||||
(if-let [handler (fn-handlers fn-name)]
|
||||
(apply handler args)
|
||||
(if (infix-fns fn-name)
|
||||
(paren-wrap (string/join (str " " fn-name-upper " ") args))
|
||||
(str fn-name-upper (paren-wrap (comma-join args))))))))
|
||||
SqlRaw
|
||||
(-to-sql [x] (.s x))
|
||||
clojure.lang.IPersistentMap
|
||||
(-to-sql [x] (let [clause-ops (filter #(contains? x %) clause-order)]
|
||||
(paren-wrap
|
||||
(space-join (map (comp format-clause #(find x %))
|
||||
clause-ops)))))
|
||||
nil
|
||||
(-to-sql [x] "NULL"))
|
||||
|
||||
(defn sqlable? [x]
|
||||
(satisfies? ToSql x))
|
||||
|
||||
(defn to-sql [x]
|
||||
(if (satisfies? ToSql x)
|
||||
(-to-sql x)
|
||||
(do
|
||||
(swap! *params* conj x)
|
||||
"?")))
|
||||
|
||||
;;;;
|
||||
|
||||
(defn format-predicate [pred]
|
||||
(if-not (sequential? pred)
|
||||
(to-sql pred)
|
||||
(let [[op & args] pred
|
||||
op-name (name op)]
|
||||
(if (= "not" op-name)
|
||||
(str "NOT " (format-predicate (first args)))
|
||||
(if (#{"and" "or" "xor"} op-name)
|
||||
(paren-wrap
|
||||
(string/join (str " " (string/upper-case op-name) " ")
|
||||
(map format-predicate args)))
|
||||
(to-sql (apply sql-fn pred)))))))
|
||||
|
||||
(defmulti format-clause
|
||||
"Takes a map entry representing a clause and returns an SQL string"
|
||||
key)
|
||||
|
||||
(defmethod format-clause :select [[_ fields]]
|
||||
(str "SELECT " (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
|
||||
[table pred & [type]]
|
||||
(str (when type
|
||||
(str (string/upper-case (name type)) " "))
|
||||
"JOIN " (to-sql table)
|
||||
" ON " (format-predicate pred)))
|
||||
|
||||
(defmethod format-clause :join [[_ [table pred type]]]
|
||||
(format-join table pred type))
|
||||
|
||||
(defmethod format-clause :joins [[_ join-groups]]
|
||||
(space-join (map format-clause 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 order] field]
|
||||
(str (to-sql field) " " (if (= :desc order)
|
||||
"DESC" "ASC")))
|
||||
(to-sql field))))))
|
||||
|
||||
(defmethod format-clause :limit [[_ limit]]
|
||||
(str "LIMIT " (to-sql limit)))
|
||||
|
||||
(defmethod format-clause :offset [[_ offset]]
|
||||
(str "OFFSET " (to-sql offset)))
|
||||
|
||||
Loading…
Reference in a new issue