diff --git a/CHANGELOG.md b/CHANGELOG.md index eb00f3f..634c860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * The documentation continues to be expanded and clarified in response to feedback! * Tentative fix for #315 by expanding `:in` handling to deal with `nil` values. * Fix #310 by adding support for `FILTER`, `WITHIN GROUP`, and `ORDER BY` (as an expression), from [nilenso/honeysql-postgres](https://github.com/nilenso/honeysql-postgres) 0.4.112. These are [Special Syntax](doc/special-syntax.md) and there are also helpers for `filter` and `within-group` -- so **be careful about referring in all of `honey.sql.helpers`** since it will now shadow `clojure.core/filter` (it already shadows `for`, `group-by`, `into`, `partition-by`, `set`, and `update`). + * Fix #308 by supporting join clauses in `join-by`. * 2.0.0-beta1 (for testing; 2021-04-09) * Since Alpha 3, more documentation has been written and existing documentation clarified (addressing #300, #309, #313, #314). diff --git a/doc/clause-reference.md b/doc/clause-reference.md index 0e91e49..631c2cf 100644 --- a/doc/clause-reference.md +++ b/doc/clause-reference.md @@ -528,9 +528,9 @@ for more detail). ## join-by This is a convenience that allows for an arbitrary sequence of `JOIN` -operations to be performed in a specific order. It accepts a sequence -of join operation name (keyword or symbol) and the clause that join -would take: +operations to be performed in a specific order. It accepts either a sequence +of alternating join operation name (keyword or symbol) and the clause that join +would take, or a sequence of `JOIN` clauses as hash maps: ```clojure user=> (sql/format {:select [:t.ref :pp.code] @@ -540,7 +540,24 @@ user=> (sql/format {:select [:t.ref :pp.code] :join [[:logtransaction :log] [:= :t.id :log.id]]] :where [:= "settled" :pp.status]}) -["SELECT t.ref, pp.code FROM transaction AS t LEFT JOIN paypal_tx AS pp USING (id) INNER JOIN logtransaction AS log ON t.id = log.id WHERE ? = pp.status" "settled"] +;; newlines inserted for readability: +["SELECT t.ref, pp.code FROM transaction AS t + LEFT JOIN paypal_tx AS pp USING (id) + INNER JOIN logtransaction AS log ON t.id = log.id + WHERE ? = pp.status" "settled"] +;; or using helpers: +user=> (sql/format (-> (select :t.ref :pp.code) + (from [:transaction :t]) + (join-by (left-join [:paypal-tx :pp] + [:using :id]) + (join [:logtransaction :log] + [:= :t.id :log.id])) + (where := "settled" :pp.status))) +;; newlines inserted for readability: +["SELECT t.ref, pp.code FROM transaction AS t + LEFT JOIN paypal_tx AS pp USING (id) + INNER JOIN logtransaction AS log ON t.id = log.id + WHERE ? = pp.status" "settled"] ``` Without `:join-by`, a `:join` would normally be generated before a `:left-join`. diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index 26fa5ac..148db70 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -464,10 +464,16 @@ (defn- format-join-by "Clauses should be a sequence of join types followed - by their table and condition, so that you can construct - a series of joins in a specific order." + by their table and condition, or a sequence of join + clauses, so that you can construct a series of joins + in a specific order." [_ clauses] - (let [joins (partition-by ident? clauses)] + (let [joins (if (every? map? clauses) + (into [] + (comp (mapcat #(mapcat (juxt key val) %)) + (map vector)) + clauses) + (partition-by ident? clauses))] (when-not (even? (count joins)) (throw (ex-info ":join-by expects a sequence of join clauses" {:clauses clauses}))) diff --git a/src/honey/sql/helpers.cljc b/src/honey/sql/helpers.cljc index ae56ab2..230d6b9 100644 --- a/src/honey/sql/helpers.cljc +++ b/src/honey/sql/helpers.cljc @@ -470,8 +470,8 @@ (-> (select :*) (from :foo) - (join-by :left :bar [:= :foo.id :bar.id] - :join :quux [:= :bar.qid = :quux.id]) + (join-by :left [:bar [:= :foo.id :bar.id]] + :join [:quux [:= :bar.qid :quux.id]])) This produces a LEFT JOIN followed by an INNER JOIN even though the 'natural' order for `left-join` and diff --git a/test/honey/sql/helpers_test.cljc b/test/honey/sql/helpers_test.cljc index c71696e..c92591e 100644 --- a/test/honey/sql/helpers_test.cljc +++ b/test/honey/sql/helpers_test.cljc @@ -232,6 +232,21 @@ :right-join [:bock [:= :bock.z :c.e]] :left-join [[:clod :c] [:= :f.a :c.d]] :inner-join [:draq [:= :f.b :draq.x]]) + (sql/format))))) + (testing "Specific JOIN orders with join clauses" + (is (= ["SELECT * FROM foo FULL JOIN beck ON beck.x = c.y RIGHT JOIN bock ON bock.z = c.e LEFT JOIN clod AS c ON f.a = c.d INNER JOIN draq ON f.b = draq.x"] + (sql/format {:select [:*] :from [:foo] + :join-by [{:full-join [:beck [:= :beck.x :c.y]]} + {:right-join [:bock [:= :bock.z :c.e]]} + {:left-join [[:clod :c] [:= :f.a :c.d]]} + {:join [:draq [:= :f.b :draq.x]]}]}))) + (is (= ["SELECT * FROM foo FULL JOIN beck ON beck.x = c.y RIGHT JOIN bock ON bock.z = c.e LEFT JOIN clod AS c ON f.a = c.d INNER JOIN draq ON f.b = draq.x"] + (-> (select :*) + (from :foo) + (join-by (full-join :beck [:= :beck.x :c.y]) + (right-join :bock [:= :bock.z :c.e]) + (left-join [:clod :c] [:= :f.a :c.d]) + (join :draq [:= :f.b :draq.x])) (sql/format)))))) (deftest test-cast