From 76d6ccbcdbdee41be21edc00e30b6477eedd12cf Mon Sep 17 00:00:00 2001 From: Justin Kramer Date: Fri, 19 Oct 2012 12:41:26 -0400 Subject: [PATCH] :left-join & :right-join clauses; simplifies :join syntax --- README.md | 60 ++++++++++++++++++++++++++++--------- src/honeysql/core.clj | 2 ++ src/honeysql/format.clj | 16 ++++++++-- src/honeysql/helpers.clj | 12 ++++++++ test/honeysql/core_test.clj | 12 ++++---- 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 6aeb730..54076b7 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Turn Clojure data structures into SQL. -**Work in progress** - ## Leiningen Coordinate ```clj @@ -107,26 +105,60 @@ calls, raw SQL fragments, and named input parameters: Here's a big, complicated query. Note that Honey SQL makes no attempt to verify that your queries make any sense. It merely renders surface syntax. ```clj -(-> (select :f.* :b.baz :c.quux (sql/call :now) (sql/raw "@x := 10")) +(-> (select :f.* :b.baz :c.quux [:b.bla "bla-bla"] + (sql/call :now) (sql/raw "@x := 10")) (modifiers :distinct) (from [:foo :f] [:baz :b]) - (join [[:clod :c] [:= :f.a :c.d] :left] - [:draq [:= :f.b :draq.x]]) + (join :draq [:= :f.b :draq.x]) + (left-join [:clod :c] [:= :f.a :c.d]) + (right-join :bock [:= :bock.z :c.e]) (where [:or - [:and [:= :f.a "bort"] [:not= :b.baz "gabba"]] - [:in :f.e [1 2 3]] - [:between :f.e 10 20]]) - (group :f.a) ;note the name change + [:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]] + [:< 1 2 3] + [:in :f.e [1 (sql/param :param2) 3]] + [:between :f.e 10 20]]) + (group :f.a) (having [:< 0 :f.e]) (order-by [:b.baz :desc] :c.quux) (limit 50) - (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"] + (offset 10)) +=> {:select [:f.* :b.baz :c.quux [:b.bla "bla-bla"] + (sql/call :now) (sql/raw "@x := 10")] + :modifiers [:distinct] + :from [[:foo :f] [:baz :b]] + :join [:draq [:= :f.b :draq.x]] + :left-join [[:clod :c] [:= :f.a :c.d]] + :right-join [:bock [:= :bock.z :c.e]] + :where [:or + [:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]] + [:< 1 2 3] + [:in :f.e [1 (sql/param :param2) 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} + +(sql/format *1 {:param1 "gabba" :param2 2}) +=> ["SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS \"bla-bla\", NOW(), @x := 10 + FROM foo AS f, baz AS b + INNER JOIN draq ON f.b = draq.x + LEFT JOIN clod AS c ON f.a = c.d + RIGHT JOIN bock ON bock.z = c.e + WHERE ((f.a = ? AND b.baz <> ?) + OR (1 < 2 AND 2 < 3) + OR (f.e IN (1, ?, 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" 2] ;; Printable and readable -(= *1 (read-string (pr-str *1))) +(= *2 (read-string (pr-str *2))) => true ``` diff --git a/src/honeysql/core.clj b/src/honeysql/core.clj index 2c32e96..327c37d 100644 --- a/src/honeysql/core.clj +++ b/src/honeysql/core.clj @@ -34,6 +34,8 @@ :select, :merge-select, :un-select :from, :merge-from :join, :merge-join + :left-join, :merge-left-join + :right-join, :merge-right-join :where, :merge-where :group-by, :merge-group-by :having, :merge-having diff --git a/src/honeysql/format.clj b/src/honeysql/format.clj index dd77401..04b4eb3 100644 --- a/src/honeysql/format.clj +++ b/src/honeysql/format.clj @@ -117,7 +117,8 @@ (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]) + [:select :from :join :left-join :right-join :where :group-by :having + :order-by :limit :offset]) (def known-clauses (set clause-order)) @@ -242,14 +243,23 @@ (defmethod format-clause :where [[_ pred] _] (str "WHERE " (format-predicate* pred))) -(defn format-join [table pred & [type]] +(defn format-join [type table pred] (str (when type (str (string/upper-case (name type)) " ")) "JOIN " (to-sql table) " ON " (format-predicate* pred))) (defmethod format-clause :join [[_ join-groups] _] - (space-join (map #(apply format-join %) join-groups))) + (space-join (map #(apply format-join :inner %) + (partition 2 join-groups)))) + +(defmethod format-clause :left-join [[_ join-groups] _] + (space-join (map #(apply format-join :left %) + (partition 2 join-groups)))) + +(defmethod format-clause :right-join [[_ join-groups] _] + (space-join (map #(apply format-join :right %) + (partition 2 join-groups)))) (defmethod format-clause :group-by [[_ fields] _] (str "GROUP BY " (comma-join (map to-sql fields)))) diff --git a/src/honeysql/helpers.clj b/src/honeysql/helpers.clj index 5cc9d44..a5294bb 100644 --- a/src/honeysql/helpers.clj +++ b/src/honeysql/helpers.clj @@ -78,6 +78,18 @@ (defhelper merge-join [m clauses] (update-in m [:join] concat clauses)) +(defhelper left-join [m clauses] + (assoc m :left-join clauses)) + +(defhelper merge-left-join [m clauses] + (update-in m [:left-join] concat clauses)) + +(defhelper right-join [m clauses] + (assoc m :right-join clauses)) + +(defhelper merge-right-join [m clauses] + (update-in m [:right-join] concat clauses)) + (defmethod build-clause :group-by [_ m fields] (assoc m :group-by (collify fields))) diff --git a/test/honeysql/core_test.clj b/test/honeysql/core_test.clj index 8003b0c..5e8c383 100644 --- a/test/honeysql/core_test.clj +++ b/test/honeysql/core_test.clj @@ -12,8 +12,9 @@ ;;(un-select :c.quux) (modifiers :distinct) (from [:foo :f] [:baz :b]) - (join [[:clod :c] [:= :f.a :c.d] :left] - [:draq [:= :f.b :draq.x]]) + (join :draq [:= :f.b :draq.x]) + (left-join [:clod :c] [:= :f.a :c.d]) + (right-join :bock [:= :bock.z :c.e]) (where [:or [:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]] [:< 1 2 3] @@ -30,8 +31,9 @@ ;;:un-select :c.quux :modifiers :distinct :from [[:foo :f] [:baz :b]] - :join [[[:clod :c] [:= :f.a :c.d] :left] - [:draq [:= :f.b :draq.x]]] + :join [:draq [:= :f.b :draq.x]] + :left-join [[:clod :c] [:= :f.a :c.d]] + :right-join [:bock [:= :bock.z :c.e]] :where [:or [:and [:= :f.a "bort"] [:not= :b.baz (sql/param :param1)]] [:< 1 2 3] @@ -49,7 +51,7 @@ (is (= m1 m3))) (testing "SQL data formats correctly" (is (= (sql/format m1 {:param1 "gabba" :param2 2}) - ["SELECT DISTINCT f.*, b.baz, c.quux, 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, ?, 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 " + ["SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS \"bla-bla\", NOW(), @x := 10 FROM foo AS f, baz AS b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod AS c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e WHERE ((f.a = ? AND b.baz <> ?) OR (1 < 2 AND 2 < 3) OR (f.e IN (1, ?, 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" 2]))) (testing "SQL data prints and reads correctly" (is (= m1 (read-string (pr-str m1))))))) \ No newline at end of file