diff --git a/CHANGELOG.md b/CHANGELOG.md index a5903e2..76922d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,8 @@ # Changes * 2.2.next in progress - * Fix #380 by correcting test for function type in `register-clause!`. + * Address [#382](https://github.com/seancorfield/honeysql/issues/382) by adding `:case-expr` for BigQuery support. + * Fix [#380](https://github.com/seancorfield/honeysql/issues/380) by correcting test for function type in `register-clause!` and `register-fn!`. * 2.2.858 -- 2022-01-20 * Address #377 by adding `honey.sql/map=` to convert a hash map into an equality condition (for a `WHERE` clause). diff --git a/doc/special-syntax.md b/doc/special-syntax.md index 0bfe68f..50fa7d2 100644 --- a/doc/special-syntax.md +++ b/doc/special-syntax.md @@ -41,6 +41,15 @@ may be `:else` (or `'else`) to produce `ELSE`, otherwise ;; => ["CASE WHEN a < ? THEN ? WHEN a > ? THEN ? ELSE ? END" 10 "small" 100 "big" "medium"] ``` +Google BigQuery supports a variant of `CASE` that takes an expression and then the `WHEN` +clauses contain expressions to match against, rather than conditions. HoneySQL supports +this using `:case-expr`: + +```clojure +(sql/format-expr [:case-expr :a 10 "small" 100 "big" :else "medium"]) +;; => ["CASE a WHEN ? THEN ? WHEN ? THEN ? ELSE ? END" 10 "small" 100 "big" "medium"] +``` + ## cast A SQL CAST expression. Expects an expression and something diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index a36e56b..b0268d1 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -1124,6 +1124,31 @@ (partition 2 pairs))] (into [(str/join ", " sqls)] params))) +(defn- case-clauses + "For both :case and :case-expr." + [k clauses] + (let [case-expr? (= :case-expr k) + [sqlx & paramsx] (when case-expr? (format-expr (first clauses))) + [sqls params] + (reduce (fn [[sqls params] [condition value]] + (let [[sqlc & paramsc] (when-not (= :else condition) + (format-expr condition)) + [sqlv & paramsv] (format-expr value)] + [(if (or (= :else condition) + (= 'else condition)) + (conj sqls (sql-kw :else) sqlv) + (conj sqls (sql-kw :when) sqlc (sql-kw :then) sqlv)) + (-> params (into paramsc) (into paramsv))])) + [[] []] + (partition 2 (if case-expr? (rest clauses) clauses)))] + (-> [(str (sql-kw :case) " " + (when case-expr? + (str sqlx " ")) + (str/join " " sqls) + " " (sql-kw :end))] + (into paramsx) + (into params)))) + (def ^:private special-syntax (atom {;; these "functions" are mostly used in column @@ -1168,24 +1193,8 @@ (into params-x) (into params-a) (into params-b)))) - :case - (fn [_ clauses] - (let [[sqls params] - (reduce (fn [[sqls params] [condition value]] - (let [[sqlc & paramsc] (when-not (= :else condition) - (format-expr condition)) - [sqlv & paramsv] (format-expr value)] - [(if (or (= :else condition) - (= 'else condition)) - (conj sqls (sql-kw :else) sqlv) - (conj sqls (sql-kw :when) sqlc (sql-kw :then) sqlv)) - (-> params (into paramsc) (into paramsv))])) - [[] []] - (partition 2 clauses))] - (into [(str (sql-kw :case) " " - (str/join " " sqls) - " " (sql-kw :end))] - params))) + :case #'case-clauses + :case-expr #'case-clauses :cast (fn [_ [x type]] (let [[sql & params] (format-expr x) diff --git a/test/honey/bigquery_test.cljc b/test/honey/bigquery_test.cljc index b117360..831c3d9 100644 --- a/test/honey/bigquery_test.cljc +++ b/test/honey/bigquery_test.cljc @@ -42,3 +42,13 @@ {:add-column [:name :string :if-not-exists]} {:add-column [:my_struct [:bigquery/struct [:name :string] [:description :string]] :if-not-exists]} {:add-column [:my_array [:bigquery/array :string] :if-not-exists]}]})))) + +(deftest test-case-expr + (is (= ["SELECT CASE foo WHEN ? THEN ? WHEN ? THEN foo / ? ELSE ? END FROM bar" + 1 -1 2 2 0] + (sut/format + {:select [[[:case-expr :foo + 1 -1 + 2 [:/ :foo 2] + :else 0]]] + :from [:bar]}))))