From 7c8cdf241d50f3489e7a9ff0f14c51d444673945 Mon Sep 17 00:00:00 2001 From: Mike Blume Date: Sun, 14 Jun 2015 12:58:51 -0700 Subject: [PATCH] MichaelBlume's first pass at ClojureScript support. --- src/honeysql/core.cljc | 14 ++-- src/honeysql/format.cljc | 116 +++++++++++++++++++++------------ src/honeysql/helpers.cljc | 36 +++++----- src/honeysql/types.cljc | 57 ++++++++-------- test/honeysql/core_test.cljc | 15 +++-- test/honeysql/format_test.cljc | 11 ++-- 6 files changed, 149 insertions(+), 100 deletions(-) diff --git a/src/honeysql/core.cljc b/src/honeysql/core.cljc index cb4f03a..827ac58 100644 --- a/src/honeysql/core.cljc +++ b/src/honeysql/core.cljc @@ -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 diff --git a/src/honeysql/format.cljc b/src/honeysql/format.cljc index 974819d..d9509d9 100644 --- a/src/honeysql/format.cljc +++ b/src/honeysql/format.cljc @@ -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)) diff --git a/src/honeysql/helpers.cljc b/src/honeysql/helpers.cljc index 51f11e5..3ab7c95 100644 --- a/src/honeysql/helpers.cljc +++ b/src/honeysql/helpers.cljc @@ -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])) diff --git a/src/honeysql/types.cljc b/src/honeysql/types.cljc index bc746d7..35344fc 100644 --- a/src/honeysql/types.cljc +++ b/src/honeysql/types.cljc @@ -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)))) diff --git a/test/honeysql/core_test.cljc b/test/honeysql/core_test.cljc index 61e720b..8a87a45 100644 --- a/test/honeysql/core_test.cljc +++ b/test/honeysql/core_test.cljc @@ -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)) diff --git a/test/honeysql/format_test.cljc b/test/honeysql/format_test.cljc index d35df79..5338eea 100644 --- a/test/honeysql/format_test.cljc +++ b/test/honeysql/format_test.cljc @@ -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