Support materialization control in WITH

Adds an optional third value to `with` vectors, which can be the
following:

* `:materialized` -> SQL is `WITH cte AS MATERIALIZED (...)`
* `:not-materialized` -> SQL is `WITH cte AS NOT MATERIALIZED (...)`
* omitted or anything else -> SQL is `WITH cte AS (...)`

Note that materialization control is not available on WITH RECURSIVE
CTEs, so `format-with` was modified to take a third argument that
returns the `AS ...` separator, which is constantly `"AS"` for WITH
RECURSIVE, and obeys the aforementioned rules for non-recursive CTEs.

Resolves #392.
This commit is contained in:
Rob Hanlon 2022-08-16 15:53:34 -07:00
parent d31600d2c0
commit 204f6fa72a
No known key found for this signature in database
GPG key ID: ABB8430EF3F61ABF
2 changed files with 26 additions and 11 deletions

View file

@ -521,18 +521,20 @@
(into [(str (format-entity (first x)) " " sql)] params)) (into [(str (format-entity (first x)) " " sql)] params))
[(format-entity x)])) [(format-entity x)]))
(defn- format-with [k xs] (defn- format-with [k xs as-fn]
;; TODO: a sequence of pairs -- X AS expr -- where X is either [entity expr] ;; TODO: a sequence of pairs -- X AS expr -- where X is either [entity expr]
;; or just entity, as far as I can tell... ;; or just entity, as far as I can tell...
(let [[sqls params] (let [[sqls params]
(reduce-sql (map (fn [[x expr]] (reduce-sql
(let [[sql & params] (format-with-part x) (map
[sql' & params'] (format-dsl expr)] (fn [[x expr :as with]]
;; according to docs, CTE should _always_ be wrapped: (let [[sql & params] (format-with-part x)
(cond-> [(str sql " AS " (str "(" sql' ")"))] [sql' & params'] (format-dsl expr)]
params (into params) ;; according to docs, CTE should _always_ be wrapped:
params' (into params')))) (cond-> [(str sql " " (as-fn with) " " (str "(" sql' ")"))]
xs))] params (into params)
params' (into params'))))
xs))]
(into [(str (sql-kw k) " " (str/join ", " sqls))] params))) (into [(str (sql-kw k) " " (str/join ", " sqls))] params)))
(defn- format-selector [k xs] (defn- format-selector [k xs]
@ -989,8 +991,15 @@
:nest (fn [_ x] :nest (fn [_ x]
(let [[sql & params] (format-dsl x {:nested true})] (let [[sql & params] (format-dsl x {:nested true})]
(into [sql] params))) (into [sql] params)))
:with #'format-with :with (let [as-fn
:with-recursive #'format-with (fn [[_ _ materialization]]
(condp = materialization
:materialized "AS MATERIALIZED"
:not-materialized "AS NOT MATERIALIZED"
"AS"))]
(fn [k xs] (format-with k xs as-fn)))
:with-recursive (let [as-fn (constantly "AS")]
(fn [k xs] (format-with k xs as-fn)))
:intersect #'format-on-set-op :intersect #'format-on-set-op
:union #'format-on-set-op :union #'format-on-set-op
:union-all #'format-on-set-op :union-all #'format-on-set-op

View file

@ -124,6 +124,12 @@
(deftest test-cte (deftest test-cte
(is (= (format {:with [[:query {:select [:foo] :from [:bar]}]]}) (is (= (format {:with [[:query {:select [:foo] :from [:bar]}]]})
["WITH query AS (SELECT foo FROM bar)"])) ["WITH query AS (SELECT foo FROM bar)"]))
(is (= (format {:with [[:query {:select [:foo] :from [:bar]} :materialized]]})
["WITH query AS MATERIALIZED (SELECT foo FROM bar)"]))
(is (= (format {:with [[:query {:select [:foo] :from [:bar]} :not-materialized]]})
["WITH query AS NOT MATERIALIZED (SELECT foo FROM bar)"]))
(is (= (format {:with [[:query {:select [:foo] :from [:bar]} :unknown]]})
["WITH query AS (SELECT foo FROM bar)"]))
(is (= (format {:with [[:query1 {:select [:foo] :from [:bar]}] (is (= (format {:with [[:query1 {:select [:foo] :from [:bar]}]
[:query2 {:select [:bar] :from [:quux]}]] [:query2 {:select [:bar] :from [:quux]}]]
:select [:query1.id :query2.name] :select [:query1.id :query2.name]