From 18d5fa19e87d6651d133b411b4fb9703c943dc9b Mon Sep 17 00:00:00 2001 From: Justin Kramer Date: Fri, 24 Aug 2012 16:50:56 -0400 Subject: [PATCH] make clause-building extensible, move helper fns to honeysql.helpers --- src/honeysql/core.clj | 152 +----------------------------------- src/honeysql/helpers.clj | 136 ++++++++++++++++++++++++++++++++ test/honeysql/core_test.clj | 48 ++++++------ 3 files changed, 166 insertions(+), 170 deletions(-) create mode 100644 src/honeysql/helpers.clj diff --git a/src/honeysql/core.clj b/src/honeysql/core.clj index 81dadc8..a0869d5 100644 --- a/src/honeysql/core.clj +++ b/src/honeysql/core.clj @@ -2,6 +2,7 @@ (:refer-clojure :exclude [group-by format]) (:require [honeysql.format :as format] [honeysql.types :as types] + [honeysql.helpers :refer [build-clause]] [honeysql.util :refer [defalias]])) (defalias call types/call) @@ -9,157 +10,12 @@ (defalias format format/format) (defalias format-predicate format/format-predicate) -(defn select [& fields] - (let [[m fields] (if (map? (first fields)) - [(first fields) (rest fields)] - [{} fields])] - (assoc m :select fields))) - -(defn merge-select [sql-map & fields] - (update-in sql-map [:select] concat fields)) - -(defn un-select [sql-map & fields] - (update-in sql-map [:select] #(remove (set fields) %))) - -(defn from [& tables] - (let [[m tables] (if (map? (first tables)) - [(first tables) (rest tables)] - [{} tables])] - (assoc m :from tables))) - -(defn merge-from [sql-map & tables] - (update-in sql-map [:from] concat tables)) - -(defn where [& preds] - (let [[m preds] (if (map? (first preds)) - [(first preds) (rest preds)] - [{} preds])] - (assoc m :where (if (= 1 (count preds)) - (first preds) - (vec (cons :and preds)))))) - -(defn merge-where [sql-map & preds] - (if (empty? preds) - sql-map - (let [[merge-op preds] (if (keyword? (first preds)) - [(first preds) (rest preds)] - [:and preds])] - (assoc sql-map :where (if (contains? sql-map :where) - (vec (concat [merge-op (:where sql-map)] preds)) - (vec (if (= 1 (count preds)) - (first preds) - (cons merge-op preds)))))))) - -(defn join [& clauses] - (let [[m clauses] (if (map? (first clauses)) - [(first clauses) (rest clauses)] - [{} clauses])] - (assoc m :join clauses))) - -(defn merge-join [sql-map & clauses] - (update-in sql-map [:join] concat clauses)) - -(defn group-by [& fields] - (let [[m fields] (if (map? (first fields)) - [(first fields) (rest fields)] - [{} fields])] - (assoc m :group-by fields))) - -(defn merge-group-by [sql-map & fields] - (update-in sql-map [:group-by] concat fields)) - -(defn having [& preds] - (let [[m preds] (if (map? (first preds)) - [(first preds) (rest preds)] - [{} preds])] - (assoc m :having (if (= 1 (count preds)) - (first preds) - (vec (cons :and preds)))))) - -(defn merge-having [sql-map & preds] - (if (empty? preds) - sql-map - (let [[merge-op preds] (if (keyword? (first preds)) - [(first preds) (rest preds)] - [:and preds])] - (assoc sql-map :having (if (contains? sql-map :having) - (vec (concat [merge-op (:having sql-map)] preds)) - (vec (if (= 1 (count preds)) - (first preds) - (cons merge-op preds)))))))) - -(defn order-by [& fields] - (let [[m fields] (if (map? (first fields)) - [(first fields) (rest fields)] - [{} fields])] - (assoc m :order-by fields))) - -(defn merge-order-by [sql-map & fields] - (update-in sql-map [:order-by] concat fields)) - -(defn limit - ([l] - (limit {} l)) - ([sql-map l] - (assoc sql-map :limit l))) - -(defn offset - ([o] - (offset {} o)) - ([sql-map o] - (assoc sql-map :offset o))) - -(defn modifiers [& ms] - (let [[m ms] (if (map? (first ms)) - [(first ms) (rest ms)] - [{} ms])] - (assoc m :modifiers ms))) - -(defn merge-modifiers [sql-map & ms] - (update-in sql-map [:modifiers] concat ms)) - -(def ^:private handlers - {:select select - :from from - :where where - :join join - :group-by group-by - :having having - :order-by order-by - :limit limit - :offset offset - :modifiers modifiers}) - -(def ^:private merge-handlers - {:select merge-select - :from merge-from - :where merge-where - :join merge-join - :group-by merge-group-by - :having merge-having - :order-by merge-order-by - :limit limit - :offset offset - :modifiers merge-modifiers}) - -(defn- build-sql [handlers clauses] +(defn build [& clauses] (let [[base clauses] (if (map? (first clauses)) [(first clauses) (rest clauses)] [{} clauses])] (reduce (fn [sql-map [op args]] - (if-let [handler (handlers op)] - (if (or (#{:where :having} op) (not (coll? args))) - (if (nil? args) - sql-map - (handler sql-map args)) - (apply handler sql-map args)) - sql-map)) + (build-clause op sql-map args)) base - (partition 2 clauses)))) - -(defn sql [& clauses] - (build-sql handlers clauses)) - -(defn merge-sql [& clauses] - (build-sql merge-handlers clauses)) + (partition 2 clauses)))) \ No newline at end of file diff --git a/src/honeysql/helpers.clj b/src/honeysql/helpers.clj new file mode 100644 index 0000000..c38534d --- /dev/null +++ b/src/honeysql/helpers.clj @@ -0,0 +1,136 @@ +(ns honeysql.helpers) + +(defmulti build-clause (fn [name & args] + name)) + +(defmethod build-clause :default [_ m & args] + m) + +(defmacro defhelper [helper arglist & more] + (let [kw (keyword (name helper))] + `(do + (defmethod build-clause ~kw ~(into ['_] arglist) ~@more) + (defn ~helper [& args#] + (let [[m# args#] (if (map? (first args#)) + [(first args#) (rest args#)] + [{} args#])] + (build-clause ~kw m# args#)))))) + +(defn collify [x] + (if (coll? x) x [x])) + +(defhelper select [m fields] + (assoc m :select (collify fields))) + +(defhelper merge-select [m fields] + (update-in m [:select] concat (collify fields))) + +(defhelper un-select [m fields] + (update-in m [:select] #(remove (set (collify fields)) %))) + +(defhelper from [m tables] + (assoc m :from (collify tables))) + +(defhelper merge-from [m tables] + (update-in m [:from] concat (collify tables))) + +(defmethod build-clause :where [_ m pred] + (if (nil? pred) + m + (assoc m :where pred))) + +(defn- prep-where [args] + (let [[m preds] (if (map? (first args)) + [(first args) (rest args)] + [{} args]) + [logic-op preds] (if (keyword? (first preds)) + [(first preds) (rest preds)] + [:and preds]) + pred (if (= 1 (count preds)) + (first preds) + (into [logic-op] preds))] + [m pred logic-op])) + +(defn where [& args] + (let [[m pred] (prep-where args)] + (if (nil? pred) + m + (assoc m :where pred)))) + +(defmethod build-clause :merge-where [_ m pred] + (if (nil? pred) + m + (assoc m :where (if (not (nil? (:where m))) + [:and (:where m) pred] + pred)))) + +(defn merge-where [& args] + (let [[m pred logic-op] (prep-where args)] + (if (nil? pred) + m + (assoc m :where (if (not (nil? (:where m))) + [logic-op (:where m) pred] + pred))))) + +(defhelper join [m clauses] + (assoc m :join clauses)) + +(defhelper merge-join [m clauses] + (update-in m [:join] concat clauses)) + +(defmethod build-clause :group-by [_ m fields] + (assoc m :group-by (collify fields))) + +(defn group [& args] + (let [[m fields] (if (map? (first args)) + [(first args) (rest args)] + [{} args])] + (build-clause :group-by m fields))) + +(defhelper merge-group-by [m fields] + (update-in m [:group-by] concat (collify fields))) + +(defmethod build-clause :having [_ m pred] + (if (nil? pred) + m + (assoc m :having pred))) + +(defn having [& args] + (let [[m pred] (prep-where args)] + (if (nil? pred) + m + (assoc m :having pred)))) + +(defmethod build-clause :merge-having [_ m pred] + (if (nil? pred) + m + (assoc m :having (if (not (nil? (:having m))) + [:and (:having m) pred] + pred)))) + +(defn merge-having [& args] + (let [[m pred logic-op] (prep-where args)] + (if (nil? pred) + m + (assoc m :having (if (not (nil? (:having m))) + [logic-op (:having m) pred] + pred))))) + +(defhelper order-by [m fields] + (assoc m :order-by (collify fields))) + +(defhelper merge-order-by [m fields] + (update-in m [:order-by] concat (collify fields))) + +(defhelper limit [m l] + (assoc m :limit (if (coll? l) (first l) l))) + +(defhelper offset [m o] + (assoc m :offset (if (coll? o) (first o) o))) + +(defhelper modifiers [m ms] + (assoc m :modifiers (collify ms))) + +(defhelper merge-modifiers [m ms] + (update-in m [:modifiers] concat (collify ms))) + diff --git a/test/honeysql/core_test.clj b/test/honeysql/core_test.clj index 7a95326..470a766 100644 --- a/test/honeysql/core_test.clj +++ b/test/honeysql/core_test.clj @@ -1,28 +1,32 @@ (ns honeysql.core-test - (:refer-clojure :exclude [format group-by]) + (:refer-clojure :exclude [format]) (:require [clojure.test :refer [deftest testing is]] - [honeysql.core :refer :all])) + [honeysql.core :refer :all] + [honeysql.helpers :refer :all])) ;; TODO: more tests (deftest test-select - (let [sqlmap (-> (select :f.* :b.baz :c.quux (call :now) (raw "@x := 10")) - (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]]) - (group-by :f.a) - (having [:< 0 :f.e]) - (order-by [:b.baz :desc] :c.quux) - (limit 50) - (offset 10))] - (testing "sqlmap formats correctly" - (is (= (format sqlmap) - ["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"]))) - (testing "sqlmap prints and reads correctly" - (is (= sqlmap (read-string (pr-str sqlmap))))))) \ No newline at end of file + (let [m1 (-> (select :f.* :b.baz :c.quux [:b.bla "bla-bla"] + (call :now) (raw "@x := 10")) + (un-select :c.quux) + (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"]] + [:< 1 2 3] + [:in :f.e [1 2 3]] + [:between :f.e 10 20]]) + (merge-where [:not= nil :b.bla]) + (group :f.a) + (having [:< 0 :f.e]) + (order-by [:b.baz :desc] :c.quux) + (limit 50) + (offset 10))] + (testing "SQL data formats correctly" + (is (= (format m1) + ["SELECT DISTINCT f.*, b.baz, b.bla AS \"bla-bla\", 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 (1 < 2 AND 2 < 3) OR (f.e IN (1, 2, 3)) OR f.e BETWEEN 10 AND 20) AND b.bla IS NOT NULL) GROUP BY f.a HAVING 0 < f.e ORDER BY b.baz DESC, c.quux LIMIT 50 OFFSET 10" "bort" "gabba"]))) + (testing "SQL data prints and reads correctly" + (is (= m1 (read-string (pr-str m1)))))))