Skip to content

Commit

Permalink
Merge pull request #432 from MawiraIke/feature/with-clause-clickhouse
Browse files Browse the repository at this point in the history
Allow `with expr AS ident` syntax in WITH clause
  • Loading branch information
seancorfield authored Sep 23, 2022
2 parents e553f4f + 73d36ab commit 9a03dde
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 7 deletions.
31 changes: 30 additions & 1 deletion doc/clause-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,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.

Expand All @@ -310,6 +311,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
Expand Down
17 changes: 12 additions & 5 deletions src/honey/sql.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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)))

Expand Down
25 changes: 24 additions & 1 deletion test/honey/sql_test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down

0 comments on commit 9a03dde

Please sign in to comment.