MichaelBlume's first pass at ClojureScript support.

This commit is contained in:
Mike Blume 2015-06-14 12:58:51 -07:00 committed by Richard Newman
parent 5603e9df9f
commit 7c8cdf241d
6 changed files with 149 additions and 100 deletions

View file

@ -3,15 +3,15 @@
(:require [honeysql.format :as format]
[honeysql.types :as types]
[honeysql.helpers :refer [build-clause]]
[honeysql.util :refer [defalias]]
#?(:clj [honeysql.util :refer [defalias]])
[clojure.string :as string]))
(defalias call types/call)
(defalias raw types/raw)
(defalias param types/param)
(defalias format format/format)
(defalias format-predicate format/format-predicate)
(defalias quote-identifier format/quote-identifier)
(#?(:clj defalias :cljs def) call types/call)
(#?(:clj defalias :cljs def) raw types/raw)
(#?(:clj defalias :cljs def) param types/param)
(#?(:clj defalias :cljs def) format format/format)
(#?(:clj defalias :cljs def) format-predicate format/format-predicate)
(#?(:clj defalias :cljs def) quote-identifier format/quote-identifier)
(defn qualify
"Takes one or more keyword or string qualifers and name. Returns

View file

@ -1,8 +1,9 @@
(ns honeysql.format
(:refer-clojure :exclude [format])
(:require [honeysql.types :refer [call raw param param-name]]
(:require [honeysql.types :refer [call raw param param-name
#?@(:cljs [SqlCall SqlRaw SqlParam SqlArray])]]
[clojure.string :as string])
(:import [honeysql.types SqlCall SqlRaw SqlParam SqlArray]))
#?(:clj (:import [honeysql.types SqlCall SqlRaw SqlParam SqlArray])))
;;(set! *warn-on-reflection* true)
@ -203,7 +204,10 @@
(defn sort-clauses [clauses]
(let [m @clause-store]
(sort-by #(m % Long/MAX_VALUE) clauses)))
(sort-by
(fn [c]
(m c #?(:clj Long/MAX_VALUE :cljs js/Number.MAX_VALUE)))
clauses)))
(defn format
"Takes a SQL map and optional input parameters and returns a vector
@ -243,11 +247,22 @@
(defprotocol Parameterizable
(to-params [value pname]))
(defn to-params-seq [s pname]
(paren-wrap (comma-join (mapv #(to-params % pname) s))))
(defn to-params-default [value pname]
(swap! *params* conj value)
(swap! *param-names* conj pname)
(*parameterizer*))
(extend-protocol Parameterizable
clojure.lang.Sequential
(to-params [value pname]
(paren-wrap (comma-join (mapv #(to-params % pname) value))))
clojure.lang.IPersistentSet
#?@(:clj
[clojure.lang.Sequential
(to-params [value pname]
(to-params-seq value pname))])
#?(:clj clojure.lang.IPersistentSet
:cljs cljs.core/PersistentHashSet)
(to-params [value pname]
(to-params (seq value) pname))
nil
@ -255,11 +270,14 @@
(swap! *params* conj value)
(swap! *param-names* conj pname)
(*parameterizer*))
java.lang.Object
#?(:clj Object :cljs default)
(to-params [value pname]
(swap! *params* conj value)
(swap! *param-names* conj pname)
(*parameterizer*)))
#?(:clj
(to-params-default value pname)
:cljs
(if (sequential? value)
(to-params-seq value pname)
(to-params-default value pname)))))
(defn add-param [pname pval]
(to-params pval pname))
@ -279,8 +297,34 @@
(declare -format-clause)
(defn map->sql [m]
(let [clause-ops (sort-clauses (keys m))
sql-str (binding [*subquery?* true
*fn-context?* false]
(space-join
(map (comp #(-format-clause % m) #(find m %))
clause-ops)))]
(if *subquery?*
(paren-wrap sql-str)
sql-str)))
(defn seq->sql [x]
(if *fn-context?*
;; list argument in fn call
(paren-wrap (comma-join (map to-sql x)))
;; alias
(str (to-sql (first x))
; Omit AS in FROM, JOIN, etc. - Oracle doesn't allow it
(if (= :select *clause*)
" AS "
" ")
(if (string? (second x))
(quote-identifier (second x))
(to-sql (second x))))))
(extend-protocol ToSql
clojure.lang.Keyword
#?(:clj clojure.lang.Keyword
:cljs cljs.core/Keyword)
(to-sql [x]
(let [s (name x)]
(case (.charAt s 0)
@ -288,25 +332,15 @@
(to-sql (apply call (map keyword call-args))))
\? (to-sql (param (keyword (subs s 1))))
(quote-identifier x))))
clojure.lang.Symbol
#?(:clj clojure.lang.Symbol
:cljs cljs.core/Symbol)
(to-sql [x] (quote-identifier x))
java.lang.Boolean
#?(:clj java.lang.Boolean :cljs 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))
; Omit AS in FROM, JOIN, etc. - Oracle doesn't allow it
(if (= :select *clause*)
" AS "
" ")
(if (string? (second x))
(quote-identifier (second x))
(to-sql (second x))))))
#?@(:clj
[clojure.lang.Sequential
(to-sql [x] (seq->sql x))])
SqlCall
(to-sql [x]
(binding [*fn-context?* true]
@ -315,18 +349,12 @@
(apply fn-handler fn-name (.-args x)))))
SqlRaw
(to-sql [x] (.-s x))
clojure.lang.IPersistentMap
#?(:clj clojure.lang.IPersistentMap
:cljs cljs.core/PersistentArrayMap)
(to-sql [x]
(let [clause-ops (sort-clauses (keys x))
sql-str (binding [*subquery?* true
*fn-context?* false]
(space-join
(map (comp #(-format-clause % x) #(find x %))
clause-ops)))]
(if *subquery?*
(paren-wrap sql-str)
sql-str)))
clojure.lang.IPersistentSet
(map->sql x))
#?(:clj clojure.lang.IPersistentSet
:cljs cljs.core/PersistentHashSet)
(to-sql [x]
(to-sql (seq x)))
nil
@ -342,9 +370,15 @@
SqlArray
(to-sql [x]
(str "ARRAY[" (comma-join (map to-sql (.-values x))) "]"))
Object
#?(:clj Object :cljs default)
(to-sql [x]
(add-anon-param x)))
#?(:clj (add-anon-param x)
:cljs (if (sequential? x)
(seq->sql x)
(add-anon-param x))))
#?@(:cljs
[cljs.core/PersistentHashMap
(to-sql [x] (map->sql x))]))
(defn sqlable? [x]
(satisfies? ToSql x))

View file

@ -1,5 +1,6 @@
(ns honeysql.helpers
(:refer-clojure :exclude [update]))
(:refer-clojure :exclude [update])
#?(:cljs (:require-macros [honeysql.helpers :refer [defhelper]])))
(defmulti build-clause (fn [name & args]
name))
@ -12,22 +13,23 @@
(map? m)
(not (instance? clojure.lang.IRecord m))))
(defmacro defhelper [helper arglist & more]
(let [kw (keyword (name helper))]
`(do
(defmethod build-clause ~kw ~(into ['_] arglist) ~@more)
(doto (defn ~helper [& args#]
(let [[m# args#] (if (plain-map? (first args#))
[(first args#) (rest args#)]
[{} args#])]
(build-clause ~kw m# args#)))
;; maintain the original arglist instead of getting
;; ([& args__6880__auto__])
(alter-meta!
assoc
:arglists
'(~(into [] (rest arglist))
~(into [(first arglist)] (rest arglist))))))))
#?(:clj
(defmacro defhelper [helper arglist & more]
(let [kw (keyword (name helper))]
`(do
(defmethod build-clause ~kw ~(into ['_] arglist) ~@more)
(doto (defn ~helper [& args#]
(let [[m# args#] (if (plain-map? (first args#))
[(first args#) (rest args#)]
[{} args#])]
(build-clause ~kw m# args#)))
;; maintain the original arglist instead of getting
;; ([& args__6880__auto__])
(alter-meta!
assoc
:arglists
'(~(into [] (rest arglist))
~(into [(first arglist)] (rest arglist)))))))))
(defn collify [x]
(if (coll? x) x [x]))

View file

@ -1,4 +1,5 @@
(ns honeysql.types)
(ns honeysql.types
(:refer-clojure :exclude [array]))
(defrecord SqlCall [name args])
@ -9,13 +10,7 @@
(defn read-sql-call [form]
;; late bind so that we get new class on REPL reset
(apply (resolve `call) form))
(defmethod print-method SqlCall [^SqlCall o ^java.io.Writer w]
(.write w (str "#sql/call " (pr-str (into [(.-name o)] (.-args o))))))
(defmethod print-dup SqlCall [o w]
(print-method o w))
(apply #?(:clj (resolve `call) :cljs call) form))
;;;;
@ -28,13 +23,7 @@
(defn read-sql-raw [form]
;; late bind, as above
((resolve `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))
(#?(:clj (resolve `raw) :cljs raw) form))
;;;;
@ -50,13 +39,7 @@
(defn read-sql-param [form]
;; late bind, as above
((resolve `param) form))
(defmethod print-method SqlParam [^SqlParam o ^java.io.Writer w]
(.write w (str "#sql/param " (pr-str (.-name o)))))
(defmethod print-dup SqlParam [o w]
(print-method o w))
(#?(:clj (resolve `param) :cljs param) form))
;;;;
@ -72,10 +55,30 @@
(defn read-sql-array [form]
;; late bind, as above
((resolve `array) form))
(#?(:clj (resolve `array) :cljs array) form))
(defmethod print-method SqlArray [^SqlArray a ^java.io.Writer w]
(.write w (str "#sql/array " (pr-str (.-values a)))))
#?(:clj
(do
(defmethod print-method SqlCall [^SqlCall o ^java.io.Writer w]
(.write w (str "#sql/call " (pr-str (into [(.-name o)] (.-args o))))))
(defmethod print-dup SqlArray [a w]
(print-method a w))
(defmethod print-dup SqlCall [o w]
(print-method o w))
(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))
(defmethod print-method SqlParam [^SqlParam o ^java.io.Writer w]
(.write w (str "#sql/param " (pr-str (.name o)))))
(defmethod print-dup SqlParam [o w]
(print-method o w))
(defmethod print-method SqlArray [^SqlArray a ^java.io.Writer w]
(.write w (str "#sql/array " (pr-str (.values a)))))
(defmethod print-dup SqlArray [a w]
(print-method a w))))

View file

@ -1,8 +1,13 @@
(ns honeysql.core-test
(:refer-clojure :exclude [format update])
(:require [clojure.test :refer [deftest testing is]]
(:require [#?@(:clj [clojure.test :refer]
:cljs [cljs.test :refer-macros]) [deftest testing is]]
[honeysql.core :as sql]
[honeysql.helpers :refer :all]))
[honeysql.helpers :refer [select modifiers from join left-join
right-join full-join where group having
order-by limit offset values columns
insert-into]]
honeysql.format-test))
;; TODO: more tests
@ -55,8 +60,8 @@
(is (= ["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 FULL JOIN beck ON beck.x = c.y WHERE ((f.a = ? AND b.baz <> ?) OR (? < ? AND ? < ?) OR (f.e in (?, ?, ?)) OR f.e BETWEEN ? AND ?) GROUP BY f.a 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]
(sql/format m1 {:param1 "gabba" :param2 2}))))
(testing "SQL data prints and reads correctly"
(is (= m1 (read-string (pr-str m1)))))
#?(:clj (testing "SQL data prints and reads correctly"
(is (= m1 (read-string (pr-str m1))))))
(testing "SQL data formats correctly with alternate param naming"
(is (= (sql/format m1 :params {:param1 "gabba" :param2 2} :parameterizer :postgresql)
["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 FULL JOIN beck ON beck.x = c.y WHERE ((f.a = $1 AND b.baz <> $2) OR ($3 < $4 AND $5 < $6) OR (f.e in ($7, $8, $9)) OR f.e BETWEEN $10 AND $11) GROUP BY f.a HAVING $12 < f.e ORDER BY b.baz DESC, c.quux, f.a NULLS FIRST LIMIT $13 OFFSET $14 "
@ -174,3 +179,5 @@
(from :foo)
(join :x [:= :foo.id :x.id] :y nil)
sql/format)))))
#?(:cljs (cljs.test/run-all-tests))

View file

@ -1,7 +1,10 @@
(ns honeysql.format-test
(:refer-clojure :exclude [format])
(:require [clojure.test :refer [deftest testing is are]]
[honeysql.format :refer :all]))
(:require [#?@(:clj [clojure.test :refer]
:cljs [cljs.test :refer-macros]) [deftest testing is are]]
[honeysql.types :as sql]
[honeysql.format :refer
[*allow-dashed-names?* quote-identifier format-clause format]]))
(deftest test-quote
(are
@ -66,11 +69,11 @@
(deftest array-test
(is (= (format {:insert-into :foo
:columns [:baz]
:values [[#sql/array [1 2 3 4]]]})
:values [[(sql/array [1 2 3 4])]]})
["INSERT INTO foo (baz) VALUES (ARRAY[?, ?, ?, ?])" 1 2 3 4]))
(is (= (format {:insert-into :foo
:columns [:baz]
:values [[#sql/array ["one" "two" "three"]]]})
:values [[(sql/array ["one" "two" "three"])]]})
["INSERT INTO foo (baz) VALUES (ARRAY[?, ?, ?])" "one" "two" "three"])))
(deftest union-test