Fix select as, select distinct on

This commit is contained in:
Sean Corfield 2021-02-13 15:58:56 -08:00
parent 4ce56997c9
commit f6975ef6bd
4 changed files with 68 additions and 16 deletions

View file

@ -559,7 +559,7 @@ big-complicated-map
{:params {:param1 "gabba" :param2 2} {:params {:param1 "gabba" :param2 2}
:pretty true}) :pretty true})
=> [" => ["
SELECT DISTINCT f.*, b.baz, c.quux, b.bla \"bla-bla\", NOW(), @x := 10 SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS \"bla-bla\", NOW(), @x := 10
FROM foo AS f, baz AS b FROM foo AS f, baz AS b
INNER JOIN draq ON f.b = draq.x INNER JOIN draq ON f.b = draq.x
LEFT JOIN clod AS c ON f.a = c.d LEFT JOIN clod AS c ON f.a = c.d

View file

@ -42,7 +42,8 @@
:create-table :with-columns :create-view :drop-table :create-table :with-columns :create-view :drop-table
;; then SQL clauses in priority order: ;; then SQL clauses in priority order:
:nest :with :with-recursive :intersect :union :union-all :except :except-all :nest :with :with-recursive :intersect :union :union-all :except :except-all
:select :select-distinct :insert-into :update :delete :delete-from :truncate :select :select-distinct :select-distinct-on
:insert-into :update :delete :delete-from :truncate
:columns :set :from :using :columns :set :from :using
:join :left-join :right-join :inner-join :outer-join :full-join :join :left-join :right-join :inner-join :outer-join :full-join
:cross-join :cross-join
@ -272,16 +273,35 @@
(let [[sqls params] (format-expr-list xs {:drop-ns (= :columns k)})] (let [[sqls params] (format-expr-list xs {:drop-ns (= :columns k)})]
(into [(str "(" (str/join ", " sqls) ")")] params))) (into [(str "(" (str/join ", " sqls) ")")] params)))
(defn- format-selects [k xs] (defn- format-selects-common [prefix as xs]
(if (sequential? xs) (if (sequential? xs)
(let [[sqls params] (let [[sqls params]
(reduce (fn [[sql params] [sql' & params']] (reduce (fn [[sql params] [sql' & params']]
[(conj sql sql') (if params' (into params params') params)]) [(conj sql sql') (if params' (into params params') params)])
[[] []] [[] []]
(map #(format-selectable-dsl % {:as (#{:select :from :window} k)}) xs))] (map #(format-selectable-dsl % {:as as}) xs))]
(into [(str (sql-kw k) " " (str/join ", " sqls))] params)) (into [(str prefix " " (str/join ", " sqls))] params))
(let [[sql & params] (format-selectable-dsl xs {:as (#{:select :from} k)})] (let [[sql & params] (format-selectable-dsl xs {:as as})]
(into [(str (sql-kw k) " " sql)] params)))) (into [(str prefix " " sql)] params))))
(defn- format-selects [k xs]
(format-selects-common
(sql-kw k)
(#{:select :select-distinct :from :window
'select 'select-distinct 'from 'window}
k)
xs))
(defn- format-selects-on [k xs]
(let [[on & cols] xs
[sql & params]
(format-expr (into [:distinct-on] on))
[sql' & params']
(format-selects-common
(str (sql-kw :select) " " sql)
true
cols)]
(-> [sql'] (into params) (into params'))))
(defn- format-with-part [x] (defn- format-with-part [x]
(if (sequential? x) (if (sequential? x)
@ -457,16 +477,46 @@
(into [(str (sql-kw k) " " (str/join ", " sqls))] params))) (into [(str (sql-kw k) " " (str/join ", " sqls))] params)))
(defn- format-on-conflict [k x] (defn- format-on-conflict [k x]
(if (or (keyword? x) (symbol? x)) (cond (or (keyword? x) (symbol? x))
[(str (sql-kw k) " (" (format-entity x) ")")] [(str (sql-kw k) " (" (format-entity x) ")")]
(map? x)
(let [[sql & params] (format-dsl x)] (let [[sql & params] (format-dsl x)]
(into [(str (sql-kw k) " " sql)] params)))) (into [(str (sql-kw k) " " sql)] params))
(and (sequential? x)
(or (keyword? (first x)) (symbol? (first x)))
(map? (second x)))
(let [[sql & params] (format-dsl (second x))]
(into [(str (sql-kw k)
" (" (format-entity (first x)) ") "
sql)]
params))
:else
(throw (ex-info "unsupported :on-conflict format"
{:clause x}))))
(comment
keyword/symbol -> e = excluded.e
[k/s] -> join , e = excluded.e
{e v} -> join , e = v
{:fields f :where w} -> join , e = excluded.e (from f) where w
,)
(defn- format-do-update-set [k x] (defn- format-do-update-set [k x]
(if (or (keyword? x) (symbol? x)) (if (map? x)
(if (and (or (contains? x :fields) (contains? x 'fields))
(or (contains? x :where) (contains? x 'where)))
(let [sets (str/join ", "
(map (fn [e]
(let [e (format-entity e {:drop-ns true})]
(str e " = EXCLUDED." e)))
(or (:fields x)
('fields x))))
[sql & params] (format-dsl {:where
(or (:where x)
('where x))})]
(into [(str (sql-kw k) " " sets " " sql)] params))
(format-set-exprs k x))
(let [e (format-entity x {:drop-ns true})] (let [e (format-entity x {:drop-ns true})]
[(str (sql-kw k) " " e " = EXCLUDED." e)]) [(str (sql-kw k) " " e " = EXCLUDED." e)])))
(format-set-exprs k x)))
(defn- format-simple-clause [c] (defn- format-simple-clause [c]
(binding [*inline* true] (binding [*inline* true]
@ -561,6 +611,7 @@
:except-all #'format-on-set-op :except-all #'format-on-set-op
:select #'format-selects :select #'format-selects
:select-distinct #'format-selects :select-distinct #'format-selects
:select-distinct-on #'format-selects-on
:insert-into #'format-insert :insert-into #'format-insert
:update #'format-selector :update #'format-selector
:delete #'format-selects :delete #'format-selects

View file

@ -76,6 +76,7 @@
(defn select [& args] (generic :select args)) (defn select [& args] (generic :select args))
(defn select-distinct [& args] (generic :select-distinct args)) (defn select-distinct [& args] (generic :select-distinct args))
(defn select-distinct-on [& args] (generic :select-distinct-on args))
(defn insert-into [& args] (generic :insert-into args)) (defn insert-into [& args] (generic :insert-into args))
(defn update [& args] (generic :update args)) (defn update [& args] (generic :update args))
(defn delete [& args] (generic-1 :delete args)) (defn delete [& args] (generic-1 :delete args))

View file

@ -58,7 +58,7 @@
(testing "Various construction methods are consistent" (testing "Various construction methods are consistent"
(is (= m1 m2))) (is (= m1 m2)))
(testing "SQL data formats correctly" (testing "SQL data formats correctly"
(is (= ["WITH cte AS (SELECT * FROM example WHERE example_column = ?) SELECT DISTINCT f.*, b.baz, c.quux, b.bla \"bla-bla\", NOW(), @x := 10 FROM foo AS f, baz AS b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod AS c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = ?) AND (b.baz <> ?)) OR ((? < ?) AND (? < ?)) OR (f.e IN (?, ?, ?)) OR f.e BETWEEN ? AND ? GROUP BY f.a HAVING ? < f.e ORDER BY b.baz DESC, c.quux ASC, f.a NULLS FIRST LIMIT ? OFFSET ?" (is (= ["WITH cte AS (SELECT * FROM example WHERE example_column = ?) SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS \"bla-bla\", NOW(), @x := 10 FROM foo AS f, baz AS b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod AS c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = ?) AND (b.baz <> ?)) OR ((? < ?) AND (? < ?)) OR (f.e IN (?, ?, ?)) OR f.e BETWEEN ? AND ? GROUP BY f.a HAVING ? < f.e ORDER BY b.baz DESC, c.quux ASC, f.a NULLS FIRST LIMIT ? OFFSET ?"
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10] 0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
(sql/format m1 {:params {:param1 "gabba" :param2 2}})))) (sql/format m1 {:params {:param1 "gabba" :param2 2}}))))
#?(:clj (testing "SQL data prints and reads correctly" #?(:clj (testing "SQL data prints and reads correctly"
@ -68,7 +68,7 @@
["WITH cte AS (SELECT * FROM example WHERE example_column = $1) SELECT DISTINCT f.*, b.baz, c.quux, b.bla \"bla-bla\", NOW(), @x := 10 FROM foo AS f, baz AS b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod AS c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = $2) AND (b.baz <> $3)) OR (($4 < $5) AND ($6 < $7)) OR (f.e IN ($8, $9, $10)) OR f.e BETWEEN $11 AND $12 GROUP BY f.a HAVING $13 < f.e ORDER BY b.baz DESC, c.quux ASC, f.a NULLS FIRST LIMIT $14 OFFSET $15" ["WITH cte AS (SELECT * FROM example WHERE example_column = $1) SELECT DISTINCT f.*, b.baz, c.quux, b.bla \"bla-bla\", NOW(), @x := 10 FROM foo AS f, baz AS b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod AS c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = $2) AND (b.baz <> $3)) OR (($4 < $5) AND ($6 < $7)) OR (f.e IN ($8, $9, $10)) OR f.e BETWEEN $11 AND $12 GROUP BY f.a HAVING $13 < f.e ORDER BY b.baz DESC, c.quux ASC, f.a NULLS FIRST LIMIT $14 OFFSET $15"
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]))) 0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10])))
(testing "Locking" (testing "Locking"
(is (= ["WITH cte AS (SELECT * FROM example WHERE example_column = ?) SELECT DISTINCT f.*, b.baz, c.quux, b.bla `bla-bla`, NOW(), @x := 10 FROM foo AS f, baz AS b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod AS c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = ?) AND (b.baz <> ?)) OR ((? < ?) AND (? < ?)) OR (f.e IN (?, ?, ?)) OR f.e BETWEEN ? AND ? GROUP BY f.a HAVING ? < f.e ORDER BY b.baz DESC, c.quux ASC, f.a NULLS FIRST LIMIT ? OFFSET ? LOCK IN SHARE MODE" (is (= ["WITH cte AS (SELECT * FROM example WHERE example_column = ?) SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS `bla-bla`, NOW(), @x := 10 FROM foo AS f, baz AS b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod AS c ON f.a = c.d RIGHT JOIN bock ON bock.z = c.e FULL JOIN beck ON beck.x = c.y WHERE ((f.a = ?) AND (b.baz <> ?)) OR ((? < ?) AND (? < ?)) OR (f.e IN (?, ?, ?)) OR f.e BETWEEN ? AND ? GROUP BY f.a HAVING ? < f.e ORDER BY b.baz DESC, c.quux ASC, f.a NULLS FIRST LIMIT ? OFFSET ? LOCK IN SHARE MODE"
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10] 0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
(sql/format (assoc m1 :lock [:in-share-mode]) (sql/format (assoc m1 :lock [:in-share-mode])
{:params {:param1 "gabba" :param2 2} {:params {:param1 "gabba" :param2 2}