diff --git a/doc/clause-reference.md b/doc/clause-reference.md index 3896503..ef5959d 100644 --- a/doc/clause-reference.md +++ b/doc/clause-reference.md @@ -321,7 +321,8 @@ order they would appear in a valid SQL statement). These provide CTE support for SQL Server. The argument to `:with` (or `:with-recursive`) is a sequences of pairs, each of -a result set name (or description) and a basic SQL statement. +a result set name (or description) and either of; a basic SQL +statement, a string, a keyword or a symbol. The result set can either be a SQL entity (a simple name) or a pair of a SQL entity and a set of column names. @@ -334,6 +335,34 @@ user=> (sql/format '{with ((stuff {select (:*) from (foo)}), ["WITH stuff AS (SELECT * FROM foo), nonsense AS (SELECT * FROM bar) SELECT foo.id, bar.name FROM stuff, nonsense WHERE status = ?" 0] ``` +When the expression is a basic SQL statement in any of the pairs, +the resulting syntax of the pair is `with ident AS expr` as shown above. +However, when the expression is a string, a keyword or a symbol, the resulting +syntax of the pair is of the form `with expr AS ident` like this: + +```clojure +user=> (sql/format '{with ((ts_upper_bound "2019-08-01 15:23:00")) + select :* + from (hits) + where (= EventDate ts_upper_bound)}) +["WITH ? AS ts_upper_bound SELECT * FROM hits WHERE EventDate = ts_upper_bound" "2019-08-01 15:23:00"] +``` + +The syntax only varies for each pair and so you can use both SQL statements +and keywords/strings/symbols in the same WITH clause like this: + +```clojure +user=> (sql/format '{with ((ts_upper_bound "2019-08-01 15:23:00") + (review :awesome) + (stuff {select (:*) from (songs)})) + select :* + from (hits, stuff) + where (and (= EventDate ts_upper_bound) + (= EventReview review))}) +["WITH ? AS ts_upper_bound, awesome AS review, stuff AS (SELECT * FROM songs) SELECT * FROM hits, stuff WHERE (EventDate = ts_upper_bound) AND (EventReview = review)" + "2019-08-01 15:23:00"] +``` + You can specify a list of columns for the CTE like this: ```clojure diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index 3f4081a..8680993 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -555,11 +555,18 @@ (map (fn [[x expr :as with]] (let [[sql & params] (format-with-part x) - [sql' & params'] (format-dsl expr)] - ;; according to docs, CTE should _always_ be wrapped: - (cond-> [(str sql " " (as-fn with) " " (str "(" sql' ")"))] - params (into params) - params' (into params')))) + non-query-expr? (or (ident? expr) (string? expr)) + [sql' & params'] (if non-query-expr? + (format-expr expr) + (format-dsl expr))] + (if non-query-expr? + (cond-> [(str sql' " AS " sql)] + params' (into params') + params (into params)) + ;; according to docs, CTE should _always_ be wrapped: + (cond-> [(str sql " " (as-fn with) " " (str "(" sql' ")"))] + params (into params) + params' (into params'))))) xs))] (into [(str (sql-kw k) " " (str/join ", " sqls))] params))) diff --git a/test/honey/sql_test.cljc b/test/honey/sql_test.cljc index 686e713..6f53613 100644 --- a/test/honey/sql_test.cljc +++ b/test/honey/sql_test.cljc @@ -144,7 +144,30 @@ {:values [[1 2] [4 5 6]]}]] :select [:*] :from [:static]}) - ["WITH static (a, b, c) AS (VALUES (?, ?, NULL), (?, ?, ?)) SELECT * FROM static" 1 2 4 5 6]))) + ["WITH static (a, b, c) AS (VALUES (?, ?, NULL), (?, ?, ?)) SELECT * FROM static" 1 2 4 5 6])) + (testing "When the expression passed to WITH clause is a string or `ident?` the syntax of WITH clause is `with expr AS ident`" + (is (= (format + {:with [[:ts_upper_bound "2019-08-01 15:23:00"]] + :select [:*] + :from [:hits] + :where [:= :EventDate :ts_upper_bound]}) + ["WITH ? AS ts_upper_bound SELECT * FROM hits WHERE EventDate = ts_upper_bound" "2019-08-01 15:23:00"])) + (is (= (format + {:with [[:ts_upper_bound :2019-08-01]] + :select [:*] + :from [:hits] + :where [:= :EventDate :ts_upper_bound]}) + ["WITH 2019_08_01 AS ts_upper_bound SELECT * FROM hits WHERE EventDate = ts_upper_bound"]))) + (testing "Mixing the syntax of WITH in the resulting clause" + (is (= (format + {:with [[:ts_upper_bound "2019-08-01 15:23:00"] + [:stuff {:select [:*] + :from [:songs]}]] + :select [:*] + :from [:hits :stuff] + :where [:= :EventDate :ts_upper_bound]}) + ["WITH ? AS ts_upper_bound, stuff AS (SELECT * FROM songs) SELECT * FROM hits, stuff WHERE EventDate = ts_upper_bound" + "2019-08-01 15:23:00"])))) (deftest insert-into (is (= (format {:insert-into :foo})