Remove the old 1.x code
It hasn't been part of the deployed library for quite a while because I had moved it into the test folder, while I was reaching parity.
This commit is contained in:
parent
bb9d196e3e
commit
d734767877
8 changed files with 0 additions and 1828 deletions
|
|
@ -1,6 +0,0 @@
|
||||||
{sql/call honeysql.types/read-sql-call
|
|
||||||
sql/inline honeysql.types/read-sql-inline
|
|
||||||
sql/raw honeysql.types/read-sql-raw
|
|
||||||
sql/param honeysql.types/read-sql-param
|
|
||||||
sql/array honeysql.types/read-sql-array
|
|
||||||
sql/regularize honeysql.format/regularize}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
(ns honeysql.core
|
|
||||||
(:refer-clojure :exclude [group-by format])
|
|
||||||
(:require [honeysql.format :as format]
|
|
||||||
[honeysql.types :as types]
|
|
||||||
[honeysql.helpers :refer [build-clause]]
|
|
||||||
#?(:clj [honeysql.util :refer [defalias]])
|
|
||||||
[clojure.string :as string]))
|
|
||||||
|
|
||||||
(#?(:clj defalias :cljs def) call types/call)
|
|
||||||
(#?(:clj defalias :cljs def) raw types/raw)
|
|
||||||
(#?(:clj defalias :cljs def) param types/param)
|
|
||||||
(#?(:clj defalias :cljs def) inline types/inline)
|
|
||||||
(#?(:clj defalias :cljs def) format format/format)
|
|
||||||
(#?(:clj defalias :cljs def) format-predicate format/format-predicate)
|
|
||||||
(#?(:clj defalias :cljs def) quote-identifier format/quote-identifier)
|
|
||||||
|
|
||||||
(defn qualify
|
|
||||||
"Takes one or more keyword or string qualifers and name. Returns
|
|
||||||
a keyword of the concatenated qualifiers and name separated by periods.
|
|
||||||
|
|
||||||
(qualify :foo \"bar\" :baz) => :foo.bar.baz"
|
|
||||||
[& qualifiers+name]
|
|
||||||
(keyword
|
|
||||||
(string/join "."
|
|
||||||
(for [s qualifiers+name
|
|
||||||
:when (not (nil? s))]
|
|
||||||
(if (keyword? s)
|
|
||||||
(name s)
|
|
||||||
(str s))))))
|
|
||||||
|
|
||||||
(defn build
|
|
||||||
"Takes a series of clause+data pairs and returns a SQL map. Example:
|
|
||||||
|
|
||||||
(build :select [:a :b]
|
|
||||||
:from :bar)
|
|
||||||
|
|
||||||
Clauses are defined with the honeysql.helpers/build-clause multimethod.
|
|
||||||
Built-in clauses include:
|
|
||||||
|
|
||||||
:select, :merge-select, :un-select
|
|
||||||
:from, :merge-from
|
|
||||||
:join, :merge-join
|
|
||||||
:left-join, :merge-left-join
|
|
||||||
:right-join, :merge-right-join
|
|
||||||
:full-join, :merge-full-join
|
|
||||||
:cross-join, :merge-cross-join
|
|
||||||
:where, :merge-where
|
|
||||||
:group-by, :merge-group-by
|
|
||||||
:having, :merge-having
|
|
||||||
:limit
|
|
||||||
:offset
|
|
||||||
:modifiers, :merge-modifiers
|
|
||||||
:insert-into
|
|
||||||
:columns, :merge-columns
|
|
||||||
:values, :merge-values
|
|
||||||
:query-values
|
|
||||||
:update
|
|
||||||
:set
|
|
||||||
:delete-from"
|
|
||||||
[& clauses]
|
|
||||||
(let [[base clauses] (if (map? (first clauses))
|
|
||||||
[(first clauses) (rest clauses)]
|
|
||||||
[{} clauses])]
|
|
||||||
(reduce
|
|
||||||
(fn [sql-map [op args]]
|
|
||||||
(build-clause op sql-map args))
|
|
||||||
(if (empty? base)
|
|
||||||
base
|
|
||||||
(apply build (apply concat base)))
|
|
||||||
(partition 2 clauses))))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(require '[honeysql.core :as sql])
|
|
||||||
(sql/format {:select [:*] :from [:table] :where [:= :id 1]})
|
|
||||||
(sql/format {:select [:*] :from [:table] :where [:= :id 1]} :quoting :mysql)
|
|
||||||
,)
|
|
||||||
|
|
@ -1,270 +0,0 @@
|
||||||
(ns honeysql.core-test
|
|
||||||
(:refer-clojure :exclude [format update])
|
|
||||||
(:require [#?@(:clj [clojure.test :refer]
|
|
||||||
:cljs [cljs.test :refer-macros]) [deftest testing is]]
|
|
||||||
[honeysql.core :as sql]
|
|
||||||
[honeysql.helpers :refer [select modifiers from join left-join
|
|
||||||
right-join full-join cross-join
|
|
||||||
where group having
|
|
||||||
order-by limit offset values columns
|
|
||||||
insert-into with merge-where]]
|
|
||||||
honeysql.format-test))
|
|
||||||
|
|
||||||
;; TODO: more tests
|
|
||||||
|
|
||||||
(deftest test-select
|
|
||||||
(let [m1 (-> (with [:cte (-> (select :*)
|
|
||||||
(from :example)
|
|
||||||
(where [:= :example-column 0]))])
|
|
||||||
(select :f.* :b.baz :c.quux [:b.bla :bla-bla]
|
|
||||||
:%now (sql/raw "@x := 10"))
|
|
||||||
;;(un-select :c.quux)
|
|
||||||
(modifiers :distinct)
|
|
||||||
(from [:foo :f] [:baz :b])
|
|
||||||
(join :draq [:= :f.b :draq.x])
|
|
||||||
(left-join [:clod :c] [:= :f.a :c.d])
|
|
||||||
(right-join :bock [:= :bock.z :c.e])
|
|
||||||
(full-join :beck [:= :beck.x :c.y])
|
|
||||||
(where [:or
|
|
||||||
[:and [:= :f.a "bort"] [:not= :b.baz :?param1]]
|
|
||||||
[:< 1 2 3]
|
|
||||||
[:in :f.e [1 (sql/param :param2) 3]]
|
|
||||||
[:between :f.e 10 20]])
|
|
||||||
;;(merge-where [:not= nil :b.bla])
|
|
||||||
(group :f.a)
|
|
||||||
(having [:< 0 :f.e])
|
|
||||||
(order-by [:b.baz :desc] :c.quux [:f.a :nulls-first])
|
|
||||||
(limit 50)
|
|
||||||
(offset 10))
|
|
||||||
m2 {:with [[:cte {:select [:*]
|
|
||||||
:from [:example]
|
|
||||||
:where [:= :example-column 0]}]]
|
|
||||||
:select [:f.* :b.baz :c.quux [:b.bla :bla-bla]
|
|
||||||
:%now (sql/raw "@x := 10")]
|
|
||||||
;;:un-select :c.quux
|
|
||||||
:modifiers :distinct
|
|
||||||
:from [[:foo :f] [:baz :b]]
|
|
||||||
:join [:draq [:= :f.b :draq.x]]
|
|
||||||
:left-join [[:clod :c] [:= :f.a :c.d]]
|
|
||||||
:right-join [:bock [:= :bock.z :c.e]]
|
|
||||||
:full-join [:beck [:= :beck.x :c.y]]
|
|
||||||
:where [:or
|
|
||||||
[:and [:= :f.a "bort"] [:not= :b.baz :?param1]]
|
|
||||||
[:< 1 2 3]
|
|
||||||
[:in :f.e [1 (sql/param :param2) 3]]
|
|
||||||
[:between :f.e 10 20]]
|
|
||||||
;;:merge-where [:not= nil :b.bla]
|
|
||||||
:group-by :f.a
|
|
||||||
:having [:< 0 :f.e]
|
|
||||||
:order-by [[:b.baz :desc] :c.quux [:f.a :nulls-first]]
|
|
||||||
:limit 50
|
|
||||||
:offset 10}
|
|
||||||
m3 (sql/build m2)
|
|
||||||
m4 (apply sql/build (apply concat m2))]
|
|
||||||
(testing "Various construction methods are consistent"
|
|
||||||
(is (= m1 m3 m4)))
|
|
||||||
(testing "SQL data formats correctly"
|
|
||||||
(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 f, baz b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod 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, f.a NULLS FIRST LIMIT ? OFFSET ? "
|
|
||||||
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
|
|
||||||
(sql/format m1 {:param1 "gabba" :param2 2}))))
|
|
||||||
#?(:clj (testing "SQL data prints and reads correctly"
|
|
||||||
(is (= m1 (read-string (pr-str m1))))))
|
|
||||||
(testing "SQL data formats correctly with alternate param naming"
|
|
||||||
(is (= (sql/format m1 :params {:param1 "gabba" :param2 2} :parameterizer :postgresql)
|
|
||||||
["WITH cte AS (SELECT * FROM example WHERE example_column = $1) SELECT DISTINCT f.*, b.baz, c.quux, b.bla AS bla_bla, now(), @x := 10 FROM foo f, baz b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod 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, f.a NULLS FIRST LIMIT $14 OFFSET $15 "
|
|
||||||
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10])))
|
|
||||||
(testing "Locking"
|
|
||||||
(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 f, baz b INNER JOIN draq ON f.b = draq.x LEFT JOIN clod 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, f.a NULLS FIRST LIMIT ? OFFSET ? FOR UPDATE "
|
|
||||||
0 "bort" "gabba" 1 2 2 3 1 2 3 10 20 0 50 10]
|
|
||||||
(sql/format (assoc m1 :lock {:mode :update})
|
|
||||||
{:param1 "gabba" :param2 2}))))))
|
|
||||||
|
|
||||||
(deftest test-cast
|
|
||||||
(is (= ["SELECT foo, CAST(bar AS integer)"]
|
|
||||||
(sql/format {:select [:foo (sql/call :cast :bar :integer)]})))
|
|
||||||
(is (= ["SELECT foo, CAST(bar AS integer)"]
|
|
||||||
(sql/format {:select [:foo (sql/call :cast :bar 'integer)]}))))
|
|
||||||
|
|
||||||
(deftest test-value
|
|
||||||
(is (= ["INSERT INTO foo (bar) VALUES (?)" {:baz "my-val"}]
|
|
||||||
(->
|
|
||||||
(insert-into :foo)
|
|
||||||
(columns :bar)
|
|
||||||
(values [[(honeysql.format/value {:baz "my-val"})]])
|
|
||||||
sql/format)))
|
|
||||||
(is (= ["INSERT INTO foo (a, b, c) VALUES (?, ?, ?), (?, ?, ?)"
|
|
||||||
"a" "b" "c" "a" "b" "c"]
|
|
||||||
(-> (insert-into :foo)
|
|
||||||
(values [(array-map :a "a" :b "b" :c "c")
|
|
||||||
(hash-map :a "a" :b "b" :c "c")])
|
|
||||||
sql/format))))
|
|
||||||
|
|
||||||
(deftest test-operators
|
|
||||||
(testing "="
|
|
||||||
(testing "with nil"
|
|
||||||
(is (= ["SELECT * FROM customers WHERE name IS NULL"]
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:= :name nil]})))
|
|
||||||
(is (= ["SELECT * FROM customers WHERE name = ?" nil]
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:= :name :?name]}
|
|
||||||
{:name nil})))))
|
|
||||||
(testing "in"
|
|
||||||
(doseq [[cname coll] [[:vector []] [:set #{}] [:list '()]]]
|
|
||||||
(testing (str "with values from a " (name cname))
|
|
||||||
(let [values (conj coll 1)]
|
|
||||||
(is (= ["SELECT * FROM customers WHERE (id in (?))" 1]
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:in :id values]})))
|
|
||||||
(is (= ["SELECT * FROM customers WHERE (id in (?))" 1]
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:in :id :?ids]}
|
|
||||||
{:ids values}))))))
|
|
||||||
(testing "with more than one integer"
|
|
||||||
(let [values [1 2]]
|
|
||||||
(is (= ["SELECT * FROM customers WHERE (id in (?, ?))" 1 2]
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:in :id values]})))
|
|
||||||
(is (= ["SELECT * FROM customers WHERE (id in (?, ?))" 1 2]
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:in :id :?ids]}
|
|
||||||
{:ids values})))))
|
|
||||||
(testing "with more than one string"
|
|
||||||
(let [values ["1" "2"]]
|
|
||||||
(is (= ["SELECT * FROM customers WHERE (id in (?, ?))" "1" "2"]
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:in :id values]})
|
|
||||||
(sql/format {:select [:*]
|
|
||||||
:from [:customers]
|
|
||||||
:where [:in :id :?ids]}
|
|
||||||
{:ids values})))))))
|
|
||||||
|
|
||||||
(deftest test-case
|
|
||||||
(is (= ["SELECT CASE WHEN foo < ? THEN ? WHEN (foo > ? AND (foo mod ?) = ?) THEN (foo / ?) ELSE ? END FROM bar"
|
|
||||||
0 -1 0 2 0 2 0]
|
|
||||||
(sql/format
|
|
||||||
{:select [(sql/call
|
|
||||||
:case
|
|
||||||
[:< :foo 0] -1
|
|
||||||
[:and [:> :foo 0] [:= (sql/call :mod :foo 2) 0]] (sql/call :/ :foo 2)
|
|
||||||
:else 0)]
|
|
||||||
:from [:bar]})))
|
|
||||||
(let [param1 1
|
|
||||||
param2 2
|
|
||||||
param3 "three"]
|
|
||||||
(is (= ["SELECT CASE WHEN foo = ? THEN ? WHEN foo = bar THEN ? WHEN bar = ? THEN (bar * ?) ELSE ? END FROM baz"
|
|
||||||
param1 0 param2 0 param3 "param4"]
|
|
||||||
(sql/format
|
|
||||||
{:select [(sql/call
|
|
||||||
:case
|
|
||||||
[:= :foo :?param1] 0
|
|
||||||
[:= :foo :bar] (sql/param :param2)
|
|
||||||
[:= :bar 0] (sql/call :* :bar :?param3)
|
|
||||||
:else "param4")]
|
|
||||||
:from [:baz]}
|
|
||||||
{:param1 param1
|
|
||||||
:param2 param2
|
|
||||||
:param3 param3})))))
|
|
||||||
|
|
||||||
(deftest test-raw
|
|
||||||
(is (= ["SELECT 1 + 1 FROM foo"]
|
|
||||||
(-> (select (sql/raw "1 + 1"))
|
|
||||||
(from :foo)
|
|
||||||
sql/format))))
|
|
||||||
|
|
||||||
(deftest test-call
|
|
||||||
(is (= ["SELECT min(?) FROM ?" "time" "table"]
|
|
||||||
(-> (select (sql/call :min "time"))
|
|
||||||
(from "table")
|
|
||||||
sql/format))))
|
|
||||||
|
|
||||||
(deftest join-test
|
|
||||||
(testing "nil join"
|
|
||||||
(is (= ["SELECT * FROM foo INNER JOIN x ON foo.id = x.id INNER JOIN y"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :foo)
|
|
||||||
(join :x [:= :foo.id :x.id] :y nil)
|
|
||||||
sql/format)))))
|
|
||||||
|
|
||||||
(deftest join-using-test
|
|
||||||
(testing "nil join"
|
|
||||||
(is (= ["SELECT * FROM foo INNER JOIN x USING (id) INNER JOIN y USING (foo, bar)"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :foo)
|
|
||||||
(join :x [:using :id] :y [:using :foo :bar])
|
|
||||||
sql/format)))))
|
|
||||||
|
|
||||||
(deftest inline-test
|
|
||||||
(is (= ["SELECT * FROM foo WHERE id = 5"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :foo)
|
|
||||||
(where [:= :id (sql/inline 5)])
|
|
||||||
sql/format)))
|
|
||||||
;; testing for = NULL always fails in SQL -- this test is just to show
|
|
||||||
;; that an #inline nil should render as NULL (so make sure you only use
|
|
||||||
;; it in contexts where a literal NULL is acceptable!)
|
|
||||||
(is (= ["SELECT * FROM foo WHERE id = NULL"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :foo)
|
|
||||||
(where [:= :id (sql/inline nil)])
|
|
||||||
sql/format))))
|
|
||||||
|
|
||||||
(deftest merge-where-no-params-test
|
|
||||||
(testing "merge-where called with just the map as parameter - see #228"
|
|
||||||
(let [sqlmap (-> (select :*)
|
|
||||||
(from :table)
|
|
||||||
(where [:= :foo :bar]))]
|
|
||||||
(is (= ["SELECT * FROM table WHERE foo = bar"]
|
|
||||||
(sql/format (apply merge-where sqlmap [])))))))
|
|
||||||
|
|
||||||
(deftest merge-where-test
|
|
||||||
(is (= ["SELECT * FROM table WHERE (foo = bar AND quuz = xyzzy)"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :table)
|
|
||||||
(where [:= :foo :bar] [:= :quuz :xyzzy])
|
|
||||||
sql/format)))
|
|
||||||
(is (= ["SELECT * FROM table WHERE (foo = bar AND quuz = xyzzy)"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :table)
|
|
||||||
(where [:= :foo :bar])
|
|
||||||
(merge-where [:= :quuz :xyzzy])
|
|
||||||
sql/format))))
|
|
||||||
|
|
||||||
(deftest where-nil-params-test
|
|
||||||
(testing "where called with nil parameters - see #246"
|
|
||||||
(is (= ["SELECT * FROM table WHERE (foo = bar AND quuz = xyzzy)"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :table)
|
|
||||||
(where nil [:= :foo :bar] nil [:= :quuz :xyzzy] nil)
|
|
||||||
sql/format)))
|
|
||||||
(is (= ["SELECT * FROM table"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :table)
|
|
||||||
(where)
|
|
||||||
sql/format)))
|
|
||||||
(is (= ["SELECT * FROM table"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :table)
|
|
||||||
(where nil nil nil nil)
|
|
||||||
sql/format)))))
|
|
||||||
|
|
||||||
(deftest cross-join-test
|
|
||||||
(is (= ["SELECT * FROM foo CROSS JOIN bar"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from :foo)
|
|
||||||
(cross-join :bar)
|
|
||||||
sql/format)))
|
|
||||||
(is (= ["SELECT * FROM foo f CROSS JOIN bar b"]
|
|
||||||
(-> (select :*)
|
|
||||||
(from [:foo :f])
|
|
||||||
(cross-join [:bar :b])
|
|
||||||
sql/format))))
|
|
||||||
|
|
||||||
#?(:cljs (cljs.test/run-all-tests))
|
|
||||||
|
|
@ -1,714 +0,0 @@
|
||||||
(ns honeysql.format
|
|
||||||
(:refer-clojure :exclude [format])
|
|
||||||
(:require [honeysql.types :as types
|
|
||||||
:refer [call raw param param-name inline-str
|
|
||||||
#?@(:cljs [SqlCall SqlRaw SqlParam SqlArray SqlInline])]]
|
|
||||||
[clojure.string :as string])
|
|
||||||
#?(:clj (:import [honeysql.types SqlCall SqlRaw SqlParam SqlArray SqlInline])))
|
|
||||||
|
|
||||||
;;(set! *warn-on-reflection* true)
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
|
|
||||||
(defn comma-join [s]
|
|
||||||
(string/join ", " s))
|
|
||||||
|
|
||||||
(defn space-join [s]
|
|
||||||
(string/join " " s))
|
|
||||||
|
|
||||||
(defn paren-wrap [x]
|
|
||||||
(str "(" x ")"))
|
|
||||||
|
|
||||||
(def ^:dynamic *clause*
|
|
||||||
"During formatting, *clause* is bound to :select, :from, :where, etc."
|
|
||||||
nil)
|
|
||||||
|
|
||||||
(def ^:dynamic *params*
|
|
||||||
"Will be bound to an atom-vector that accumulates SQL parameters across
|
|
||||||
possibly-recursive function calls"
|
|
||||||
nil)
|
|
||||||
|
|
||||||
(def ^:dynamic *param-names* nil)
|
|
||||||
|
|
||||||
(def ^:dynamic *param-counter* nil)
|
|
||||||
|
|
||||||
(def ^:dynamic *all-param-counter* nil)
|
|
||||||
|
|
||||||
(def ^:dynamic *input-params* nil)
|
|
||||||
|
|
||||||
(def ^:dynamic *fn-context?* false)
|
|
||||||
|
|
||||||
(def ^:dynamic *value-context?* false)
|
|
||||||
|
|
||||||
(def ^:dynamic *subquery?* false)
|
|
||||||
|
|
||||||
(def ^:dynamic *allow-dashed-names?* false)
|
|
||||||
|
|
||||||
(def ^:dynamic *allow-namespaced-names?* false)
|
|
||||||
|
|
||||||
(def ^:dynamic *namespace-as-table?* false)
|
|
||||||
|
|
||||||
(def ^:dynamic *name-transform-fn* nil)
|
|
||||||
|
|
||||||
(def ^:private quote-fns
|
|
||||||
{:ansi #(str \" (string/replace % "\"" "\"\"") \")
|
|
||||||
:mysql #(str \` (string/replace % "`" "``") \`)
|
|
||||||
:sqlserver #(str \[ (string/replace % "]" "]]") \])
|
|
||||||
:oracle #(str \" (string/replace % "\"" "\"\"") \")})
|
|
||||||
|
|
||||||
|
|
||||||
(defmulti parameterize (fn [parameterizer & args] parameterizer))
|
|
||||||
|
|
||||||
(defmethod parameterize :postgresql [_ value pname]
|
|
||||||
(str "$" (swap! *all-param-counter* inc)))
|
|
||||||
|
|
||||||
(defmethod parameterize :jdbc [_ value pname]
|
|
||||||
"?")
|
|
||||||
|
|
||||||
(defmethod parameterize :none [_ value pname]
|
|
||||||
(str (last @*params*)))
|
|
||||||
|
|
||||||
|
|
||||||
(def ^:dynamic *quote-identifier-fn* nil)
|
|
||||||
(def ^:dynamic *parameterizer* nil)
|
|
||||||
|
|
||||||
(defn- undasherize [s]
|
|
||||||
(string/replace s "-" "_"))
|
|
||||||
|
|
||||||
;; String.toUpperCase() or `string/upper-case` for that matter converts the string to uppercase for the DEFAULT
|
|
||||||
;; LOCALE. Normally this does what you'd expect but things like `inner join` get converted to `İNNER JOIN` (dot over
|
|
||||||
;; the I) when user locale is Turkish. This predictably has bad consequences for people who like their SQL queries to
|
|
||||||
;; work. The fix here is to use String.toUpperCase(Locale/US) instead which always converts things the way we'd expect.
|
|
||||||
;;
|
|
||||||
;; Use this function instead of `string/upper-case` as it will always use Locale/US.
|
|
||||||
(def ^:private ^{:arglists '([s])} upper-case
|
|
||||||
;; TODO - not sure if there's a JavaScript equivalent here we should be using as well
|
|
||||||
#?(:clj (fn [^String s] (.. s toString (toUpperCase (java.util.Locale/US))))
|
|
||||||
:cljs string/upper-case))
|
|
||||||
|
|
||||||
(defn quote-identifier [x & {:keys [style split] :or {split true}}]
|
|
||||||
(let [name-transform-fn (cond
|
|
||||||
*name-transform-fn* *name-transform-fn*
|
|
||||||
*allow-dashed-names?* identity
|
|
||||||
:else undasherize)
|
|
||||||
qf (if style
|
|
||||||
(quote-fns style)
|
|
||||||
*quote-identifier-fn*)
|
|
||||||
s (cond
|
|
||||||
(or (keyword? x) (symbol? x))
|
|
||||||
(name-transform-fn
|
|
||||||
(cond *namespace-as-table?*
|
|
||||||
(str (when-let [n (namespace x)]
|
|
||||||
(str n "."))
|
|
||||||
(name x))
|
|
||||||
*allow-namespaced-names?*
|
|
||||||
(str (when-let [n (namespace x)]
|
|
||||||
(str n "/"))
|
|
||||||
(name x))
|
|
||||||
:else
|
|
||||||
(name x)))
|
|
||||||
(string? x) (if qf x (name-transform-fn x))
|
|
||||||
:else (str x))]
|
|
||||||
(if-not qf
|
|
||||||
s
|
|
||||||
(let [qf* #(if (= "*" %) % (qf %))]
|
|
||||||
(if-not split
|
|
||||||
(qf* s)
|
|
||||||
(let [parts (string/split s #"\.")]
|
|
||||||
(string/join "." (map qf* parts))))))))
|
|
||||||
|
|
||||||
(def infix-fns
|
|
||||||
#{"+" "-" "*" "/" "%" "mod" "|" "&" "^"
|
|
||||||
"and" "or" "xor"
|
|
||||||
"in" "not in" "like" "not like" "regexp"})
|
|
||||||
|
|
||||||
(def fn-aliases
|
|
||||||
{"is" "="
|
|
||||||
"is-not" "<>"
|
|
||||||
"not=" "<>"
|
|
||||||
"!=" "<>"
|
|
||||||
"not-in" "not in"
|
|
||||||
"not-like" "not like"
|
|
||||||
"regex" "regexp"})
|
|
||||||
|
|
||||||
(defprotocol ToSql
|
|
||||||
(to-sql [x]))
|
|
||||||
|
|
||||||
(defn to-sql-value [x]
|
|
||||||
(binding [*value-context?* (sequential? x)]
|
|
||||||
(to-sql x)))
|
|
||||||
|
|
||||||
(defmulti fn-handler (fn [op & args] op))
|
|
||||||
|
|
||||||
(defn expand-binary-ops [op & args]
|
|
||||||
(str "("
|
|
||||||
(string/join " AND "
|
|
||||||
(for [[a b] (partition 2 1 args)]
|
|
||||||
(fn-handler op a b)))
|
|
||||||
")"))
|
|
||||||
|
|
||||||
(defmethod fn-handler :default [op & args]
|
|
||||||
(let [args (map to-sql args)]
|
|
||||||
(if (infix-fns op)
|
|
||||||
(paren-wrap (string/join (str " " op " ") args))
|
|
||||||
(str op (paren-wrap (comma-join args))))))
|
|
||||||
|
|
||||||
(defmethod fn-handler "count-distinct" [_ & args]
|
|
||||||
(str "COUNT(DISTINCT " (comma-join (map to-sql args)) ")"))
|
|
||||||
|
|
||||||
(defmethod fn-handler "distinct-on" [_ & args]
|
|
||||||
(str "DISTINCT ON (" (comma-join (map to-sql args)) ")"))
|
|
||||||
|
|
||||||
(defmethod fn-handler "cast" [_ field cast-to-type]
|
|
||||||
(str "CAST" (paren-wrap (str (to-sql field)
|
|
||||||
" AS "
|
|
||||||
(to-sql cast-to-type)))))
|
|
||||||
|
|
||||||
(defmethod fn-handler "=" [_ a b & more]
|
|
||||||
(if (seq more)
|
|
||||||
(apply expand-binary-ops "=" a b more)
|
|
||||||
(cond
|
|
||||||
(nil? a) (str (to-sql-value b) " IS NULL")
|
|
||||||
(nil? b) (str (to-sql-value a) " IS NULL")
|
|
||||||
:else (str (to-sql-value a) " = " (to-sql-value b)))))
|
|
||||||
|
|
||||||
(defmethod fn-handler "<>" [_ a b & more]
|
|
||||||
(if (seq more)
|
|
||||||
(apply expand-binary-ops "<>" a b more)
|
|
||||||
(cond
|
|
||||||
(nil? a) (str (to-sql-value b) " IS NOT NULL")
|
|
||||||
(nil? b) (str (to-sql-value a) " IS NOT NULL")
|
|
||||||
:else (str (to-sql-value a) " <> " (to-sql-value b)))))
|
|
||||||
|
|
||||||
(defmethod fn-handler "<" [_ a b & more]
|
|
||||||
(if (seq more)
|
|
||||||
(apply expand-binary-ops "<" a b more)
|
|
||||||
(str (to-sql-value a) " < " (to-sql-value b))))
|
|
||||||
|
|
||||||
(defmethod fn-handler "<=" [_ a b & more]
|
|
||||||
(if (seq more)
|
|
||||||
(apply expand-binary-ops "<=" a b more)
|
|
||||||
(str (to-sql-value a) " <= " (to-sql-value b))))
|
|
||||||
|
|
||||||
(defmethod fn-handler ">" [_ a b & more]
|
|
||||||
(if (seq more)
|
|
||||||
(apply expand-binary-ops ">" a b more)
|
|
||||||
(str (to-sql-value a) " > " (to-sql-value b))))
|
|
||||||
|
|
||||||
(defmethod fn-handler ">=" [_ a b & more]
|
|
||||||
(if (seq more)
|
|
||||||
(apply expand-binary-ops ">=" a b more)
|
|
||||||
(str (to-sql-value a) " >= " (to-sql-value b))))
|
|
||||||
|
|
||||||
(defmethod fn-handler "between" [_ field lower upper]
|
|
||||||
(str (to-sql-value field) " BETWEEN " (to-sql-value lower) " AND " (to-sql-value upper)))
|
|
||||||
|
|
||||||
;; Handles MySql's MATCH (field) AGAINST (pattern). The third argument
|
|
||||||
;; can be a set containing one or more of :boolean, :natural, or :expand.
|
|
||||||
(defmethod fn-handler "match" [_ fields pattern & [opts]]
|
|
||||||
(str "MATCH ("
|
|
||||||
(comma-join
|
|
||||||
(map to-sql (if (coll? fields) fields [fields])))
|
|
||||||
") AGAINST ("
|
|
||||||
(to-sql-value pattern)
|
|
||||||
(when (seq opts)
|
|
||||||
(str " " (space-join (for [opt opts]
|
|
||||||
(case opt
|
|
||||||
:boolean "IN BOOLEAN MODE"
|
|
||||||
:natural "IN NATURAL LANGUAGE MODE"
|
|
||||||
:expand "WITH QUERY EXPANSION")))))
|
|
||||||
")"))
|
|
||||||
|
|
||||||
(def default-clause-priorities
|
|
||||||
"Determines the order that clauses will be placed within generated SQL"
|
|
||||||
{:with 20
|
|
||||||
:with-recursive 30
|
|
||||||
:intersect 35
|
|
||||||
:union 40
|
|
||||||
:union-all 45
|
|
||||||
:except 47
|
|
||||||
:select 50
|
|
||||||
:insert-into 60
|
|
||||||
:update 70
|
|
||||||
:delete 75
|
|
||||||
:delete-from 80
|
|
||||||
:truncate 85
|
|
||||||
:columns 90
|
|
||||||
:composite 95
|
|
||||||
:set0 100 ; low-priority set clause
|
|
||||||
:from 110
|
|
||||||
:join 120
|
|
||||||
:left-join 130
|
|
||||||
:right-join 140
|
|
||||||
:full-join 150
|
|
||||||
:cross-join 152
|
|
||||||
:set 155
|
|
||||||
:set1 156 ; high-priority set clause (synonym for :set)
|
|
||||||
:where 160
|
|
||||||
:group-by 170
|
|
||||||
:having 180
|
|
||||||
:order-by 190
|
|
||||||
:limit 200
|
|
||||||
:offset 210
|
|
||||||
:lock 215
|
|
||||||
:values 220
|
|
||||||
:query-values 230})
|
|
||||||
|
|
||||||
(def clause-store (atom default-clause-priorities))
|
|
||||||
|
|
||||||
(defn register-clause! [clause-key priority]
|
|
||||||
(swap! clause-store assoc clause-key priority))
|
|
||||||
|
|
||||||
(defn sort-clauses [clauses]
|
|
||||||
(let [m @clause-store]
|
|
||||||
(sort-by
|
|
||||||
(fn [c]
|
|
||||||
(m c #?(:clj Long/MAX_VALUE :cljs js/Number.MAX_VALUE)))
|
|
||||||
clauses)))
|
|
||||||
|
|
||||||
(defn format
|
|
||||||
"Takes a SQL map and optional input parameters and returns a vector
|
|
||||||
of a SQL string and parameters, as expected by `next.jbc` and
|
|
||||||
`clojure.java.jdbc`.
|
|
||||||
|
|
||||||
Input parameters will be filled into designated spots according to
|
|
||||||
name (if a map is provided) or by position (if a sequence is provided).
|
|
||||||
|
|
||||||
Instead of passing parameters, you can use keyword arguments:
|
|
||||||
:params - input parameters
|
|
||||||
:quoting - quote style to use for identifiers; one of :ansi (PostgreSQL),
|
|
||||||
:mysql, :sqlserver, or :oracle. Defaults to no quoting.
|
|
||||||
:parameterizer - style of parameter naming, :postgresql,
|
|
||||||
:jdbc or :none. Defaults to :jdbc.
|
|
||||||
:return-param-names - when true, returns a vector of
|
|
||||||
[sql-str param-values param-names]"
|
|
||||||
[sql-map & params-or-opts]
|
|
||||||
(let [opts (when (keyword? (first params-or-opts))
|
|
||||||
(apply hash-map params-or-opts))
|
|
||||||
params (if (coll? (first params-or-opts))
|
|
||||||
(first params-or-opts)
|
|
||||||
(:params opts))]
|
|
||||||
(binding [*params* (atom [])
|
|
||||||
*param-counter* (atom 0)
|
|
||||||
*all-param-counter* (atom 0)
|
|
||||||
*param-names* (atom [])
|
|
||||||
*input-params* (atom params)
|
|
||||||
*quote-identifier-fn* (quote-fns (:quoting opts))
|
|
||||||
*parameterizer* (or (:parameterizer opts) :jdbc)
|
|
||||||
*allow-dashed-names?* (:allow-dashed-names? opts)
|
|
||||||
*allow-namespaced-names?* (:allow-namespaced-names? opts)
|
|
||||||
*namespace-as-table?* (:namespace-as-table? opts)]
|
|
||||||
(let [sql-str (to-sql sql-map)]
|
|
||||||
(if (and (seq @*params*) (not= :none (:parameterizer opts)))
|
|
||||||
(if (:return-param-names opts)
|
|
||||||
[sql-str @*params* @*param-names*]
|
|
||||||
(into [sql-str] @*params*))
|
|
||||||
[sql-str])))))
|
|
||||||
|
|
||||||
(defprotocol Parameterizable
|
|
||||||
(to-params [value pname]))
|
|
||||||
|
|
||||||
(defn to-params-seq [s pname]
|
|
||||||
(paren-wrap (comma-join (mapv #(to-params % pname) s))))
|
|
||||||
|
|
||||||
(defn to-params-default [value pname]
|
|
||||||
(swap! *params* conj value)
|
|
||||||
(swap! *param-names* conj pname)
|
|
||||||
(parameterize *parameterizer* value pname))
|
|
||||||
|
|
||||||
(extend-protocol Parameterizable
|
|
||||||
#?@(:clj
|
|
||||||
[clojure.lang.Sequential
|
|
||||||
|
|
||||||
(to-params [value pname]
|
|
||||||
(to-params-seq value pname))])
|
|
||||||
#?(:clj clojure.lang.IPersistentSet
|
|
||||||
:cljs cljs.core/PersistentHashSet)
|
|
||||||
(to-params [value pname]
|
|
||||||
(to-params (seq value) pname))
|
|
||||||
nil
|
|
||||||
(to-params [value pname]
|
|
||||||
(swap! *params* conj value)
|
|
||||||
(swap! *param-names* conj pname)
|
|
||||||
(parameterize *parameterizer* value pname))
|
|
||||||
#?(:clj Object :cljs default)
|
|
||||||
(to-params [value pname]
|
|
||||||
#?(:clj
|
|
||||||
(to-params-default value pname)
|
|
||||||
:cljs
|
|
||||||
(if (sequential? value)
|
|
||||||
(to-params-seq value pname)
|
|
||||||
(to-params-default value pname)))))
|
|
||||||
|
|
||||||
(defn add-param [pname pval]
|
|
||||||
(to-params pval pname))
|
|
||||||
|
|
||||||
;; Anonymous param name -- :_1, :_2, etc.
|
|
||||||
(defn add-anon-param [pval]
|
|
||||||
(add-param
|
|
||||||
(keyword (str "_" (swap! *param-counter* inc)))
|
|
||||||
pval))
|
|
||||||
|
|
||||||
(defrecord Value [v]
|
|
||||||
ToSql
|
|
||||||
(to-sql [_]
|
|
||||||
(add-anon-param v)))
|
|
||||||
|
|
||||||
(defn value [x] (Value. x))
|
|
||||||
|
|
||||||
(declare -format-clause)
|
|
||||||
|
|
||||||
(defn map->sql [m]
|
|
||||||
(let [clause-ops (sort-clauses (keys m))
|
|
||||||
sql-str (binding [*subquery?* true
|
|
||||||
*fn-context?* false]
|
|
||||||
(space-join
|
|
||||||
(map (comp #(-format-clause % m) #(find m %))
|
|
||||||
clause-ops)))]
|
|
||||||
(if *subquery?*
|
|
||||||
(paren-wrap sql-str)
|
|
||||||
sql-str)))
|
|
||||||
|
|
||||||
(declare format-predicate*)
|
|
||||||
|
|
||||||
(defn seq->sql [x]
|
|
||||||
(cond
|
|
||||||
*value-context?*
|
|
||||||
;; sequences are operators/functions
|
|
||||||
(format-predicate* x)
|
|
||||||
*fn-context?*
|
|
||||||
;; list argument in fn call
|
|
||||||
(paren-wrap (comma-join (map to-sql x)))
|
|
||||||
:else
|
|
||||||
;; alias
|
|
||||||
(do
|
|
||||||
(assert (= 2 (count x)) (str "Alias should have two parts" x))
|
|
||||||
(let [[target alias] x]
|
|
||||||
(str (to-sql target)
|
|
||||||
; Omit AS in FROM, JOIN, etc. - Oracle doesn't allow it
|
|
||||||
(if (= :select *clause*) " AS " " ")
|
|
||||||
(if (or (string? alias) (keyword? alias) (symbol? alias))
|
|
||||||
(quote-identifier alias :split false)
|
|
||||||
(binding [*subquery?* false]
|
|
||||||
(to-sql alias))))))))
|
|
||||||
|
|
||||||
(extend-protocol types/Inlinable
|
|
||||||
#?(:clj clojure.lang.Keyword
|
|
||||||
:cljs cljs.core/Keyword)
|
|
||||||
(inline-str [x]
|
|
||||||
(name x))
|
|
||||||
nil
|
|
||||||
(inline-str [_]
|
|
||||||
"NULL")
|
|
||||||
#?(:clj Object :cljs default)
|
|
||||||
(inline-str [x]
|
|
||||||
(str x)))
|
|
||||||
|
|
||||||
(extend-protocol ToSql
|
|
||||||
#?(:clj clojure.lang.Keyword
|
|
||||||
:cljs cljs.core/Keyword)
|
|
||||||
(to-sql [x]
|
|
||||||
(let [s (name x)]
|
|
||||||
(case (.charAt s 0)
|
|
||||||
\% (let [call-args (string/split (subs s 1) #"\." 2)]
|
|
||||||
(to-sql (apply call (map keyword call-args))))
|
|
||||||
\? (to-sql (param (keyword (subs s 1))))
|
|
||||||
(quote-identifier x))))
|
|
||||||
#?(:clj clojure.lang.Symbol
|
|
||||||
:cljs cljs.core/Symbol)
|
|
||||||
(to-sql [x] (quote-identifier x))
|
|
||||||
#?(:clj java.lang.Boolean :cljs boolean)
|
|
||||||
(to-sql [x]
|
|
||||||
(if x "TRUE" "FALSE"))
|
|
||||||
#?@(:clj
|
|
||||||
[clojure.lang.Sequential
|
|
||||||
(to-sql [x] (seq->sql x))])
|
|
||||||
SqlCall
|
|
||||||
(to-sql [x]
|
|
||||||
(binding [*fn-context?* true]
|
|
||||||
(let [fn-name (name (.-name x))
|
|
||||||
fn-name (fn-aliases fn-name fn-name)]
|
|
||||||
(apply fn-handler fn-name (.-args x)))))
|
|
||||||
SqlRaw
|
|
||||||
(to-sql [x]
|
|
||||||
(let [s (.-s x)]
|
|
||||||
(if (vector? s)
|
|
||||||
(string/join "" (map (fn [x] (if (string? x) x (to-sql x))) s))
|
|
||||||
s)))
|
|
||||||
#?(:clj clojure.lang.IPersistentMap
|
|
||||||
:cljs cljs.core/PersistentArrayMap)
|
|
||||||
(to-sql [x]
|
|
||||||
(map->sql x))
|
|
||||||
#?(:clj clojure.lang.IPersistentSet
|
|
||||||
:cljs cljs.core/PersistentHashSet)
|
|
||||||
(to-sql [x]
|
|
||||||
(to-sql (seq x)))
|
|
||||||
nil
|
|
||||||
(to-sql [x] "NULL")
|
|
||||||
SqlParam
|
|
||||||
(to-sql [x]
|
|
||||||
(let [pname (param-name x)]
|
|
||||||
(if (map? @*input-params*)
|
|
||||||
(add-param pname (get @*input-params* pname))
|
|
||||||
(let [x (first @*input-params*)]
|
|
||||||
(swap! *input-params* rest)
|
|
||||||
(add-param pname x)))))
|
|
||||||
SqlArray
|
|
||||||
(to-sql [x]
|
|
||||||
(str "ARRAY[" (comma-join (map to-sql (.-values x))) "]"))
|
|
||||||
SqlInline
|
|
||||||
(to-sql [x]
|
|
||||||
(inline-str (.-value x)))
|
|
||||||
#?(:clj Object :cljs default)
|
|
||||||
(to-sql [x]
|
|
||||||
#?(:clj (add-anon-param x)
|
|
||||||
:cljs (if (sequential? x)
|
|
||||||
(seq->sql x)
|
|
||||||
(add-anon-param x))))
|
|
||||||
#?@(:cljs
|
|
||||||
[cljs.core/PersistentHashMap
|
|
||||||
(to-sql [x] (map->sql x))]))
|
|
||||||
|
|
||||||
(defn sqlable? [x]
|
|
||||||
(satisfies? ToSql x))
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
|
|
||||||
(defn format-predicate* [pred]
|
|
||||||
(if-not (sequential? pred)
|
|
||||||
(to-sql pred)
|
|
||||||
(let [[op & args] pred
|
|
||||||
op-name (name op)]
|
|
||||||
(case op-name
|
|
||||||
"not" (str "NOT " (format-predicate* (first args)))
|
|
||||||
|
|
||||||
("and" "or" "xor")
|
|
||||||
(->> args
|
|
||||||
(remove nil?)
|
|
||||||
(map format-predicate*)
|
|
||||||
(string/join (str " " (upper-case op-name) " "))
|
|
||||||
(paren-wrap))
|
|
||||||
|
|
||||||
"exists"
|
|
||||||
(str "EXISTS " (to-sql (first args)))
|
|
||||||
|
|
||||||
(to-sql (apply call pred))))))
|
|
||||||
|
|
||||||
(defn format-predicate
|
|
||||||
"Formats a predicate (e.g., for WHERE, JOIN, or HAVING) as a string."
|
|
||||||
[pred & {:keys [quoting parameterizer]
|
|
||||||
:or {parameterizer :jdbc}}]
|
|
||||||
(binding [*params* (atom [])
|
|
||||||
*param-counter* (atom 0)
|
|
||||||
*param-names* (atom [])
|
|
||||||
*quote-identifier-fn* (or (quote-fns quoting)
|
|
||||||
*quote-identifier-fn*)
|
|
||||||
*parameterizer* parameterizer]
|
|
||||||
(let [sql-str (format-predicate* pred)]
|
|
||||||
(if (seq @*params*)
|
|
||||||
(into [sql-str] @*params*)
|
|
||||||
[sql-str]))))
|
|
||||||
|
|
||||||
(defmulti format-clause
|
|
||||||
"Takes a map entry representing a clause and returns an SQL string"
|
|
||||||
(fn [clause _] (key clause)))
|
|
||||||
|
|
||||||
(defn- -format-clause
|
|
||||||
[clause _]
|
|
||||||
(binding [*clause* (key clause)]
|
|
||||||
(format-clause clause _)))
|
|
||||||
|
|
||||||
(defmethod format-clause :default [& _]
|
|
||||||
"")
|
|
||||||
|
|
||||||
(defmethod format-clause :exists [[_ table-expr] _]
|
|
||||||
(str "EXISTS " (to-sql table-expr)))
|
|
||||||
|
|
||||||
(defmulti format-modifiers (fn [[op & _]] op))
|
|
||||||
|
|
||||||
(defmethod format-modifiers :distinct [_] "DISTINCT")
|
|
||||||
|
|
||||||
(defmethod format-modifiers :default [coll]
|
|
||||||
(space-join (map (comp upper-case name) coll)))
|
|
||||||
|
|
||||||
(defmethod format-clause :select [[_ fields] sql-map]
|
|
||||||
(str "SELECT "
|
|
||||||
(when (:modifiers sql-map)
|
|
||||||
(str (format-modifiers (:modifiers sql-map)) " "))
|
|
||||||
(comma-join (map to-sql fields))))
|
|
||||||
|
|
||||||
(defmethod format-clause :from [[_ tables] _]
|
|
||||||
(str "FROM " (comma-join (map to-sql tables))))
|
|
||||||
|
|
||||||
(defmethod format-clause :where [[_ pred] _]
|
|
||||||
(str "WHERE " (format-predicate* pred)))
|
|
||||||
|
|
||||||
(defn format-join [type table pred]
|
|
||||||
(str (when type
|
|
||||||
(str (upper-case (name type)) " "))
|
|
||||||
"JOIN " (to-sql table)
|
|
||||||
(when (some? pred)
|
|
||||||
(if (and (sequential? pred) (= :using (first pred)))
|
|
||||||
(str " USING (" (->> pred rest (map quote-identifier) comma-join) ")")
|
|
||||||
(str " ON " (format-predicate* pred))))))
|
|
||||||
|
|
||||||
(defmethod format-clause :join [[_ join-groups] _]
|
|
||||||
(space-join (map #(apply format-join :inner %)
|
|
||||||
(partition 2 join-groups))))
|
|
||||||
|
|
||||||
(defmethod format-clause :left-join [[_ join-groups] _]
|
|
||||||
(space-join (map #(apply format-join :left %)
|
|
||||||
(partition 2 join-groups))))
|
|
||||||
|
|
||||||
(defmethod format-clause :right-join [[_ join-groups] _]
|
|
||||||
(space-join (map #(apply format-join :right %)
|
|
||||||
(partition 2 join-groups))))
|
|
||||||
|
|
||||||
(defmethod format-clause :full-join [[_ join-groups] _]
|
|
||||||
(space-join (map #(apply format-join :full %)
|
|
||||||
(partition 2 join-groups))))
|
|
||||||
|
|
||||||
(defmethod format-clause :cross-join [[_ join-groups] _]
|
|
||||||
(space-join (map #(format-join :cross % nil) join-groups)))
|
|
||||||
|
|
||||||
(defmethod format-clause :group-by [[_ fields] _]
|
|
||||||
(str "GROUP BY " (comma-join (map to-sql fields))))
|
|
||||||
|
|
||||||
(defmethod format-clause :having [[_ pred] _]
|
|
||||||
(str "HAVING " (format-predicate* pred)))
|
|
||||||
|
|
||||||
(defmethod format-clause :order-by [[_ fields] _]
|
|
||||||
(str "ORDER BY "
|
|
||||||
(comma-join (for [field fields]
|
|
||||||
(if (sequential? field)
|
|
||||||
(let [[field & modifiers] field]
|
|
||||||
(string/join " "
|
|
||||||
(cons (to-sql field)
|
|
||||||
(for [modifier modifiers]
|
|
||||||
(case modifier
|
|
||||||
:desc "DESC"
|
|
||||||
:asc "ASC"
|
|
||||||
:nulls-first "NULLS FIRST"
|
|
||||||
:nulls-last "NULLS LAST"
|
|
||||||
"")))))
|
|
||||||
(to-sql field))))))
|
|
||||||
|
|
||||||
(defmethod format-clause :limit [[_ limit] _]
|
|
||||||
(str "LIMIT " (to-sql limit)))
|
|
||||||
|
|
||||||
(defmethod format-clause :offset [[_ offset] _]
|
|
||||||
(str "OFFSET " (to-sql offset)))
|
|
||||||
|
|
||||||
(defmulti format-lock-clause identity)
|
|
||||||
|
|
||||||
(defmethod format-lock-clause :update [_]
|
|
||||||
"FOR UPDATE")
|
|
||||||
|
|
||||||
(defmethod format-lock-clause :mysql-share [_]
|
|
||||||
"LOCK IN SHARE MODE")
|
|
||||||
|
|
||||||
(defmethod format-lock-clause :postgresql-share [_]
|
|
||||||
"FOR SHARE")
|
|
||||||
|
|
||||||
(defmethod format-clause :lock [[_ lock] _]
|
|
||||||
(let [{:keys [mode wait]} lock
|
|
||||||
clause (format-lock-clause mode)]
|
|
||||||
(str clause (when (false? wait) " NOWAIT"))))
|
|
||||||
|
|
||||||
(defmethod format-clause :insert-into [[_ table] _]
|
|
||||||
(if (and (sequential? table) (sequential? (first table)))
|
|
||||||
(str "INSERT INTO "
|
|
||||||
(to-sql (ffirst table))
|
|
||||||
(binding [*namespace-as-table?* false]
|
|
||||||
(str " (" (comma-join (map to-sql (second (first table)))) ") "))
|
|
||||||
(binding [*subquery?* false]
|
|
||||||
(to-sql (second table))))
|
|
||||||
(str "INSERT INTO " (to-sql table))))
|
|
||||||
|
|
||||||
(defmethod format-clause :columns [[_ fields] _]
|
|
||||||
(binding [*namespace-as-table?* false]
|
|
||||||
(str "(" (comma-join (map to-sql fields)) ")")))
|
|
||||||
|
|
||||||
(defmethod format-clause :composite [[_ fields] _]
|
|
||||||
(comma-join (map to-sql fields)))
|
|
||||||
|
|
||||||
(defmethod format-clause :values [[_ values] _]
|
|
||||||
(if (sequential? (first values))
|
|
||||||
(str "VALUES " (comma-join (for [x values]
|
|
||||||
(str "(" (comma-join (map to-sql x)) ")"))))
|
|
||||||
(let [cols (keys (first values))]
|
|
||||||
(str
|
|
||||||
(binding [*namespace-as-table?* false]
|
|
||||||
(str "(" (comma-join (map to-sql cols)) ")"))
|
|
||||||
" VALUES "
|
|
||||||
(comma-join (for [x values]
|
|
||||||
(str "(" (comma-join (map #(to-sql (get x %)) cols)) ")")))))))
|
|
||||||
|
|
||||||
(defmethod format-clause :query-values [[_ query-values] _]
|
|
||||||
(to-sql query-values))
|
|
||||||
|
|
||||||
(defmethod format-clause :update [[_ table] _]
|
|
||||||
(str "UPDATE " (to-sql table)))
|
|
||||||
|
|
||||||
(defmethod format-clause :set [[_ values] _]
|
|
||||||
(str "SET " (comma-join (for [[k v] values]
|
|
||||||
(str (to-sql k) " = " (to-sql v))))))
|
|
||||||
|
|
||||||
(defmethod format-clause :set0 [[_ values] _]
|
|
||||||
(str "SET " (comma-join (for [[k v] values]
|
|
||||||
(str (to-sql k) " = " (to-sql v))))))
|
|
||||||
|
|
||||||
(defmethod format-clause :set1 [[_ values] _]
|
|
||||||
(str "SET " (comma-join (for [[k v] values]
|
|
||||||
(str (to-sql k) " = " (to-sql v))))))
|
|
||||||
|
|
||||||
(defmethod format-clause :delete-from [[_ table] _]
|
|
||||||
(str "DELETE FROM " (to-sql table)))
|
|
||||||
|
|
||||||
(defmethod format-clause :delete [[_ tables] _]
|
|
||||||
(str "DELETE " (comma-join (map to-sql tables))))
|
|
||||||
|
|
||||||
(defmethod format-clause :truncate [[_ table] _]
|
|
||||||
(str "TRUNCATE " (to-sql table)))
|
|
||||||
|
|
||||||
(defn cte->sql
|
|
||||||
[[cte-name query]]
|
|
||||||
(str (binding [*subquery?* false]
|
|
||||||
(to-sql cte-name))
|
|
||||||
" AS "
|
|
||||||
(to-sql query)))
|
|
||||||
|
|
||||||
(defmethod format-clause :with [[_ ctes] _]
|
|
||||||
(str "WITH " (comma-join (map cte->sql ctes))))
|
|
||||||
|
|
||||||
(defmethod format-clause :with-recursive [[_ ctes] _]
|
|
||||||
(str "WITH RECURSIVE " (comma-join (map cte->sql ctes))))
|
|
||||||
|
|
||||||
(defmethod format-clause :union [[_ maps] _]
|
|
||||||
(binding [*subquery?* false]
|
|
||||||
(string/join " UNION " (map to-sql maps))))
|
|
||||||
|
|
||||||
(defmethod format-clause :union-all [[_ maps] _]
|
|
||||||
(binding [*subquery?* false]
|
|
||||||
(string/join " UNION ALL " (map to-sql maps))))
|
|
||||||
|
|
||||||
(defmethod format-clause :intersect [[_ maps] _]
|
|
||||||
(binding [*subquery?* false]
|
|
||||||
(string/join " INTERSECT " (map to-sql maps))))
|
|
||||||
|
|
||||||
(defmethod format-clause :except [[_ maps] _]
|
|
||||||
(binding [*subquery?* false]
|
|
||||||
(string/join " EXCEPT " (map to-sql maps))))
|
|
||||||
|
|
||||||
(defmethod fn-handler "case" [_ & clauses]
|
|
||||||
(str "CASE "
|
|
||||||
(space-join
|
|
||||||
(for [[condition result] (partition 2 clauses)]
|
|
||||||
(if (= :else condition)
|
|
||||||
(str "ELSE " (to-sql result))
|
|
||||||
(let [pred (format-predicate* condition)]
|
|
||||||
(str "WHEN " pred " THEN " (to-sql result))))))
|
|
||||||
" END"))
|
|
||||||
|
|
||||||
(defn regularize [sql-string]
|
|
||||||
(string/replace sql-string #"\s+" " "))
|
|
||||||
|
|
@ -1,322 +0,0 @@
|
||||||
(ns honeysql.format-test
|
|
||||||
(:refer-clojure :exclude [format])
|
|
||||||
(:require [#?@(:clj [clojure.test :refer]
|
|
||||||
:cljs [cljs.test :refer-macros]) [deftest testing is are]]
|
|
||||||
honeysql.core
|
|
||||||
[honeysql.types :as sql]
|
|
||||||
[honeysql.format :refer
|
|
||||||
[*allow-dashed-names?* *allow-namespaced-names?*
|
|
||||||
*namespace-as-table?*
|
|
||||||
quote-identifier format-clause format
|
|
||||||
parameterize]]))
|
|
||||||
|
|
||||||
(deftest test-quote
|
|
||||||
(are
|
|
||||||
[qx res]
|
|
||||||
(= (apply quote-identifier "foo.bar.baz" qx) res)
|
|
||||||
[] "foo.bar.baz"
|
|
||||||
[:style :mysql] "`foo`.`bar`.`baz`"
|
|
||||||
[:style :mysql :split false] "`foo.bar.baz`")
|
|
||||||
(are
|
|
||||||
[x res]
|
|
||||||
(= (quote-identifier x) res)
|
|
||||||
3 "3"
|
|
||||||
'foo "foo"
|
|
||||||
:foo-bar "foo_bar")
|
|
||||||
(is (= (quote-identifier "*" :style :ansi) "*"))
|
|
||||||
(is (= (quote-identifier "foo\"bar" :style :ansi) "\"foo\"\"bar\""))
|
|
||||||
(is (= (quote-identifier "foo\"bar" :style :oracle) "\"foo\"\"bar\""))
|
|
||||||
(is (= (quote-identifier "foo`bar" :style :mysql) "`foo``bar`"))
|
|
||||||
(is (= (quote-identifier "foo]bar" :style :sqlserver) "[foo]]bar]")))
|
|
||||||
|
|
||||||
(deftest test-dashed-quote
|
|
||||||
(binding [*allow-dashed-names?* true]
|
|
||||||
(is (= (quote-identifier :foo-bar) "foo-bar"))
|
|
||||||
(is (= (quote-identifier :foo-bar :style :ansi) "\"foo-bar\""))
|
|
||||||
(is (= (quote-identifier :foo-bar.moo-bar :style :ansi)
|
|
||||||
"\"foo-bar\".\"moo-bar\""))))
|
|
||||||
|
|
||||||
(deftest test-namespaced-identifier
|
|
||||||
(is (= (quote-identifier :foo/bar) "bar"))
|
|
||||||
(is (= (quote-identifier :foo/bar :style :ansi) "\"bar\""))
|
|
||||||
(binding [*namespace-as-table?* true]
|
|
||||||
(is (= (quote-identifier :foo/bar) "foo.bar"))
|
|
||||||
(is (= (quote-identifier :foo/bar :style :ansi) "\"foo\".\"bar\""))
|
|
||||||
(is (= (quote-identifier :foo/bar :style :ansi :split false) "\"foo.bar\"")))
|
|
||||||
(binding [*allow-namespaced-names?* true]
|
|
||||||
(is (= (quote-identifier :foo/bar) "foo/bar"))
|
|
||||||
(is (= (quote-identifier :foo/bar :style :ansi) "\"foo/bar\""))))
|
|
||||||
|
|
||||||
(deftest alias-splitting
|
|
||||||
(is (= ["SELECT `aa`.`c` AS `a.c`, `bb`.`c` AS `b.c`, `cc`.`c` AS `c.c`"]
|
|
||||||
(format {:select [[:aa.c "a.c"]
|
|
||||||
[:bb.c :b.c]
|
|
||||||
[:cc.c 'c.c]]}
|
|
||||||
:quoting :mysql))
|
|
||||||
"aliases containing \".\" are quoted as necessary but not split"))
|
|
||||||
|
|
||||||
(deftest values-alias
|
|
||||||
(is (= ["SELECT vals.a FROM (VALUES (?, ?, ?)) vals (a, b, c)" 1 2 3]
|
|
||||||
(format {:select [:vals.a]
|
|
||||||
:from [[{:values [[1 2 3]]} [:vals {:columns [:a :b :c]}]]]}))))
|
|
||||||
(deftest test-cte
|
|
||||||
(is (= (format-clause
|
|
||||||
(first {:with [[:query {:select [:foo] :from [:bar]}]]}) nil)
|
|
||||||
"WITH query AS SELECT foo FROM bar"))
|
|
||||||
(is (= (format-clause
|
|
||||||
(first {:with-recursive [[:query {:select [:foo] :from [:bar]}]]}) nil)
|
|
||||||
"WITH RECURSIVE query AS SELECT foo FROM bar"))
|
|
||||||
(is (= (format {:with [[[:static {:columns [:a :b :c]}] {:values [[1 2 3] [4 5 6]]}]]})
|
|
||||||
["WITH static (a, b, c) AS (VALUES (?, ?, ?), (?, ?, ?))" 1 2 3 4 5 6]))
|
|
||||||
(is (= (format
|
|
||||||
{:with [[[:static {:columns [:a :b :c]}]
|
|
||||||
{:values [[1 2 3] [4 5 6]]}]]
|
|
||||||
:select [:*]
|
|
||||||
:from [:static]})
|
|
||||||
["WITH static (a, b, c) AS (VALUES (?, ?, ?), (?, ?, ?)) SELECT * FROM static" 1 2 3 4 5 6])))
|
|
||||||
|
|
||||||
(deftest insert-into
|
|
||||||
(is (= (format-clause (first {:insert-into :foo}) nil)
|
|
||||||
"INSERT INTO foo"))
|
|
||||||
(is (= (format-clause (first {:insert-into [:foo {:select [:bar] :from [:baz]}]}) nil)
|
|
||||||
"INSERT INTO foo SELECT bar FROM baz"))
|
|
||||||
(is (= (format-clause (first {:insert-into [[:foo [:a :b :c]] {:select [:d :e :f] :from [:baz]}]}) nil)
|
|
||||||
"INSERT INTO foo (a, b, c) SELECT d, e, f FROM baz"))
|
|
||||||
(is (= (format {:insert-into [[:foo [:a :b :c]] {:select [:d :e :f] :from [:baz]}]})
|
|
||||||
["INSERT INTO foo (a, b, c) SELECT d, e, f FROM baz"])))
|
|
||||||
|
|
||||||
(deftest insert-into-namespaced
|
|
||||||
;; un-namespaced: works as expected:
|
|
||||||
(is (= (format {:insert-into :foo :values [{:foo/id 1}]})
|
|
||||||
["INSERT INTO foo (id) VALUES (?)" 1]))
|
|
||||||
(is (= (format {:insert-into :foo :columns [:foo/id] :values [[2]]})
|
|
||||||
["INSERT INTO foo (id) VALUES (?)" 2]))
|
|
||||||
(is (= (format {:insert-into :foo :values [{:foo/id 1}]}
|
|
||||||
:namespace-as-table? true)
|
|
||||||
["INSERT INTO foo (id) VALUES (?)" 1]))
|
|
||||||
(is (= (format {:insert-into :foo :columns [:foo/id] :values [[2]]}
|
|
||||||
:namespace-as-table? true)
|
|
||||||
["INSERT INTO foo (id) VALUES (?)" 2])))
|
|
||||||
|
|
||||||
(deftest exists-test
|
|
||||||
(is (= (format {:exists {:select [:a] :from [:foo]}})
|
|
||||||
["EXISTS (SELECT a FROM foo)"]))
|
|
||||||
(is (= (format {:select [:id]
|
|
||||||
:from [:foo]
|
|
||||||
:where [:exists {:select [1]
|
|
||||||
:from [:bar]
|
|
||||||
:where :deleted}]})
|
|
||||||
["SELECT id FROM foo WHERE EXISTS (SELECT ? FROM bar WHERE deleted)" 1])))
|
|
||||||
|
|
||||||
(deftest array-test
|
|
||||||
(is (= (format {:insert-into :foo
|
|
||||||
:columns [:baz]
|
|
||||||
:values [[(sql/array [1 2 3 4])]]})
|
|
||||||
["INSERT INTO foo (baz) VALUES (ARRAY[?, ?, ?, ?])" 1 2 3 4]))
|
|
||||||
(is (= (format {:insert-into :foo
|
|
||||||
:columns [:baz]
|
|
||||||
:values [[(sql/array ["one" "two" "three"])]]})
|
|
||||||
["INSERT INTO foo (baz) VALUES (ARRAY[?, ?, ?])" "one" "two" "three"])))
|
|
||||||
|
|
||||||
(deftest union-test
|
|
||||||
;; UNION and INTERSECT subexpressions should not be parenthesized.
|
|
||||||
;; If you need to add more complex expressions, use a subquery like this:
|
|
||||||
;; SELECT foo FROM bar1
|
|
||||||
;; UNION
|
|
||||||
;; SELECT foo FROM (SELECT foo FROM bar2 ORDER BY baz LIMIT 2)
|
|
||||||
;; ORDER BY foo ASC
|
|
||||||
(is (= (format {:union [{:select [:foo] :from [:bar1]}
|
|
||||||
{:select [:foo] :from [:bar2]}]})
|
|
||||||
["SELECT foo FROM bar1 UNION SELECT foo FROM bar2"])))
|
|
||||||
|
|
||||||
(deftest union-all-test
|
|
||||||
(is (= (format {:union-all [{:select [:foo] :from [:bar1]}
|
|
||||||
{:select [:foo] :from [:bar2]}]})
|
|
||||||
["SELECT foo FROM bar1 UNION ALL SELECT foo FROM bar2"])))
|
|
||||||
|
|
||||||
(deftest intersect-test
|
|
||||||
(is (= (format {:intersect [{:select [:foo] :from [:bar1]}
|
|
||||||
{:select [:foo] :from [:bar2]}]})
|
|
||||||
["SELECT foo FROM bar1 INTERSECT SELECT foo FROM bar2"])))
|
|
||||||
|
|
||||||
(deftest except-test
|
|
||||||
(is (= (format {:except [{:select [:foo] :from [:bar1]}
|
|
||||||
{:select [:foo] :from [:bar2]}]})
|
|
||||||
["SELECT foo FROM bar1 EXCEPT SELECT foo FROM bar2"])))
|
|
||||||
|
|
||||||
(deftest inner-parts-test
|
|
||||||
(testing "The correct way to apply ORDER BY to various parts of a UNION"
|
|
||||||
(is (= (format
|
|
||||||
{:union
|
|
||||||
[{:select [:amount :id :created_on]
|
|
||||||
:from [:transactions]}
|
|
||||||
{:select [:amount :id :created_on]
|
|
||||||
:from [{:select [:amount :id :created_on]
|
|
||||||
:from [:other_transactions]
|
|
||||||
:order-by [[:amount :desc]]
|
|
||||||
:limit 5}]}]
|
|
||||||
:order-by [[:amount :asc]]})
|
|
||||||
["SELECT amount, id, created_on FROM transactions UNION SELECT amount, id, created_on FROM (SELECT amount, id, created_on FROM other_transactions ORDER BY amount DESC LIMIT ?) ORDER BY amount ASC" 5]))))
|
|
||||||
|
|
||||||
(deftest compare-expressions-test
|
|
||||||
(testing "Sequences should be fns when in value/comparison spots"
|
|
||||||
(is (= ["SELECT foo FROM bar WHERE (col1 mod ?) = (col2 + ?)" 4 4]
|
|
||||||
(format {:select [:foo]
|
|
||||||
:from [:bar]
|
|
||||||
:where [:= [:mod :col1 4] [:+ :col2 4]]}))))
|
|
||||||
|
|
||||||
(testing "Value context only applies to sequences in value/comparison spots"
|
|
||||||
(let [sub {:select [:%sum.amount]
|
|
||||||
:from [:bar]
|
|
||||||
:where [:in :id ["id-1" "id-2"]]}]
|
|
||||||
(is (= ["SELECT total FROM foo WHERE (SELECT sum(amount) FROM bar WHERE (id in (?, ?))) = total" "id-1" "id-2"]
|
|
||||||
(format {:select [:total]
|
|
||||||
:from [:foo]
|
|
||||||
:where [:= sub :total]})))
|
|
||||||
(is (= ["WITH t AS (SELECT sum(amount) FROM bar WHERE (id in (?, ?))) SELECT total FROM foo WHERE total = t" "id-1" "id-2"]
|
|
||||||
(format {:with [[:t sub]]
|
|
||||||
:select [:total]
|
|
||||||
:from [:foo]
|
|
||||||
:where [:= :total :t]}))))))
|
|
||||||
|
|
||||||
(deftest union-with-cte
|
|
||||||
(is (= (format {:union [{:select [:foo] :from [:bar1]}
|
|
||||||
{:select [:foo] :from [:bar2]}]
|
|
||||||
:with [[[:bar {:columns [:spam :eggs]}]
|
|
||||||
{:values [[1 2] [3 4] [5 6]]}]]})
|
|
||||||
["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2" 1 2 3 4 5 6])))
|
|
||||||
|
|
||||||
|
|
||||||
(deftest union-all-with-cte
|
|
||||||
(is (= (format {:union-all [{:select [:foo] :from [:bar1]}
|
|
||||||
{:select [:foo] :from [:bar2]}]
|
|
||||||
:with [[[:bar {:columns [:spam :eggs]}]
|
|
||||||
{:values [[1 2] [3 4] [5 6]]}]]})
|
|
||||||
["WITH bar (spam, eggs) AS (VALUES (?, ?), (?, ?), (?, ?)) SELECT foo FROM bar1 UNION ALL SELECT foo FROM bar2" 1 2 3 4 5 6])))
|
|
||||||
|
|
||||||
(deftest parameterizer-none
|
|
||||||
(testing "array parameter"
|
|
||||||
(is (= (format {:insert-into :foo
|
|
||||||
:columns [:baz]
|
|
||||||
:values [[(sql/array [1 2 3 4])]]}
|
|
||||||
:parameterizer :none)
|
|
||||||
["INSERT INTO foo (baz) VALUES (ARRAY[1, 2, 3, 4])"])))
|
|
||||||
|
|
||||||
(testing "union complex values"
|
|
||||||
(is (= (format {:union [{:select [:foo] :from [:bar1]}
|
|
||||||
{:select [:foo] :from [:bar2]}]
|
|
||||||
:with [[[:bar {:columns [:spam :eggs]}]
|
|
||||||
{:values [[1 2] [3 4] [5 6]]}]]}
|
|
||||||
:parameterizer :none)
|
|
||||||
["WITH bar (spam, eggs) AS (VALUES (1, 2), (3, 4), (5, 6)) SELECT foo FROM bar1 UNION SELECT foo FROM bar2"]))))
|
|
||||||
|
|
||||||
(deftest where-and
|
|
||||||
(testing "should ignore a nil predicate"
|
|
||||||
(is (= (format {:where [:and [:= :foo "foo"] [:= :bar "bar"] nil]}
|
|
||||||
:parameterizer :postgresql)
|
|
||||||
["WHERE (foo = $1 AND bar = $2)" "foo" "bar"]))))
|
|
||||||
|
|
||||||
|
|
||||||
(defmethod parameterize :single-quote [_ value pname] (str \' value \'))
|
|
||||||
(defmethod parameterize :mysql-fill [_ value pname] "?")
|
|
||||||
|
|
||||||
(deftest customized-parameterizer
|
|
||||||
(testing "should fill param with single quote"
|
|
||||||
(is (= (format {:where [:and [:= :foo "foo"] [:= :bar "bar"] nil]}
|
|
||||||
:parameterizer :single-quote)
|
|
||||||
["WHERE (foo = 'foo' AND bar = 'bar')" "foo" "bar"])))
|
|
||||||
(testing "should fill param with ?"
|
|
||||||
(is (= (format {:where [:and [:= :foo "foo"] [:= :bar "bar"] nil]}
|
|
||||||
:parameterizer :mysql-fill)
|
|
||||||
["WHERE (foo = ? AND bar = ?)" "foo" "bar"]))))
|
|
||||||
|
|
||||||
|
|
||||||
(deftest set-before-from ; issue 235
|
|
||||||
(is (=
|
|
||||||
["UPDATE `films` `f` SET `kind` = `c`.`test` FROM (SELECT `b`.`test` FROM `bar` `b` WHERE `b`.`id` = ?) `c` WHERE `f`.`kind` = ?" 1 "drama"]
|
|
||||||
(->
|
|
||||||
{:update [:films :f]
|
|
||||||
:set0 {:kind :c.test}
|
|
||||||
:from [[{:select [:b.test]
|
|
||||||
:from [[:bar :b]]
|
|
||||||
:where [:= :b.id 1]} :c]]
|
|
||||||
:where [:= :f.kind "drama"]}
|
|
||||||
(format :quoting :mysql)))))
|
|
||||||
|
|
||||||
(deftest set-after-join
|
|
||||||
(is (=
|
|
||||||
["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `a` = ? WHERE `bar`.`b` = ?" 1 42]
|
|
||||||
(->
|
|
||||||
{:update :foo
|
|
||||||
:join [:bar [:= :bar.id :foo.bar_id]]
|
|
||||||
:set {:a 1}
|
|
||||||
:where [:= :bar.b 42]}
|
|
||||||
(format :quoting :mysql))))
|
|
||||||
(is (=
|
|
||||||
["UPDATE `foo` INNER JOIN `bar` ON `bar`.`id` = `foo`.`bar_id` SET `a` = ? WHERE `bar`.`b` = ?" 1 42]
|
|
||||||
(->
|
|
||||||
{:update :foo
|
|
||||||
:join [:bar [:= :bar.id :foo.bar_id]]
|
|
||||||
:set1 {:a 1}
|
|
||||||
:where [:= :bar.b 42]}
|
|
||||||
(format :quoting :mysql)))))
|
|
||||||
|
|
||||||
(deftest delete-from-test
|
|
||||||
(is (= ["DELETE FROM `foo` WHERE `foo`.`id` = ?" 42]
|
|
||||||
(-> {:delete-from :foo
|
|
||||||
:where [:= :foo.id 42]}
|
|
||||||
(format :quoting :mysql)))))
|
|
||||||
|
|
||||||
(deftest delete-test
|
|
||||||
(is (= ["DELETE `t1`, `t2` FROM `table1` `t1` INNER JOIN `table2` `t2` ON `t1`.`fk` = `t2`.`id` WHERE `t1`.`bar` = ?" 42]
|
|
||||||
(-> {:delete [:t1 :t2]
|
|
||||||
:from [[:table1 :t1]]
|
|
||||||
:join [[:table2 :t2] [:= :t1.fk :t2.id]]
|
|
||||||
:where [:= :t1.bar 42]}
|
|
||||||
(format :quoting :mysql)))))
|
|
||||||
|
|
||||||
(deftest truncate-test
|
|
||||||
(is (= ["TRUNCATE `foo`"]
|
|
||||||
(-> {:truncate :foo}
|
|
||||||
(format :quoting :mysql)))))
|
|
||||||
|
|
||||||
(deftest inlined-values-are-stringified-correctly
|
|
||||||
(is (= ["SELECT foo, bar, NULL"]
|
|
||||||
(format {:select [(honeysql.core/inline "foo")
|
|
||||||
(honeysql.core/inline :bar)
|
|
||||||
(honeysql.core/inline nil)]}))))
|
|
||||||
|
|
||||||
;; Make sure if Locale is Turkish we're not generating queries like İNNER JOIN (dot over the I) because
|
|
||||||
;; `string/upper-case` is converting things to upper-case using the default Locale. Generated query should be the same
|
|
||||||
;; regardless of system Locale. See #236
|
|
||||||
#?(:clj
|
|
||||||
(deftest statements-generated-correctly-with-turkish-locale
|
|
||||||
(let [format-with-locale (fn [^String language-tag]
|
|
||||||
(let [original-locale (java.util.Locale/getDefault)]
|
|
||||||
(try
|
|
||||||
(java.util.Locale/setDefault (java.util.Locale/forLanguageTag language-tag))
|
|
||||||
(format {:select [:t2.name]
|
|
||||||
:from [[:table1 :t1]]
|
|
||||||
:join [[:table2 :t2] [:= :t1.fk :t2.id]]
|
|
||||||
:where [:= :t1.id 1]})
|
|
||||||
(finally
|
|
||||||
(java.util.Locale/setDefault original-locale)))))]
|
|
||||||
(is (= (format-with-locale "en")
|
|
||||||
(format-with-locale "tr"))))))
|
|
||||||
|
|
||||||
(deftest join-on-true-253
|
|
||||||
;; used to work on honeysql 0.9.2; broke in 0.9.3
|
|
||||||
(is (= ["SELECT foo FROM bar INNER JOIN table t ON TRUE"]
|
|
||||||
(format {:select [:foo]
|
|
||||||
:from [:bar]
|
|
||||||
:join [[:table :t] true]}))))
|
|
||||||
|
|
||||||
(deftest cross-join-test
|
|
||||||
(is (= ["SELECT * FROM foo CROSS JOIN bar"]
|
|
||||||
(format {:select [:*]
|
|
||||||
:from [:foo]
|
|
||||||
:cross-join [:bar]})))
|
|
||||||
(is (= ["SELECT * FROM foo f CROSS JOIN bar b"]
|
|
||||||
(format {:select [:*]
|
|
||||||
:from [[:foo :f]]
|
|
||||||
:cross-join [[:bar :b]]}))))
|
|
||||||
|
|
@ -1,330 +0,0 @@
|
||||||
(ns honeysql.helpers
|
|
||||||
(:refer-clojure :exclude [update])
|
|
||||||
#?(:cljs (:require-macros [honeysql.helpers :refer [defhelper]])))
|
|
||||||
|
|
||||||
(defmulti build-clause (fn [name & args]
|
|
||||||
name))
|
|
||||||
|
|
||||||
(defmethod build-clause :default [_ m & args]
|
|
||||||
m)
|
|
||||||
|
|
||||||
(defn plain-map? [m]
|
|
||||||
(and
|
|
||||||
(map? m)
|
|
||||||
(not (record? m))))
|
|
||||||
|
|
||||||
#?(:clj
|
|
||||||
(defmacro defhelper [helper arglist & more]
|
|
||||||
(when-not (vector? arglist)
|
|
||||||
(throw #?(:clj (IllegalArgumentException. "arglist must be a vector")
|
|
||||||
:cljs (js/Error. "arglist must be a vector"))))
|
|
||||||
(when-not (= (count arglist) 2)
|
|
||||||
(throw #?(:clj (IllegalArgumentException. "arglist must have two entries, map and varargs")
|
|
||||||
:cljs (js/Error. "arglist must have two entries, map and varargs"))))
|
|
||||||
|
|
||||||
(let [kw (keyword (name helper))
|
|
||||||
[m-arg varargs] arglist]
|
|
||||||
`(do
|
|
||||||
(defmethod build-clause ~kw ~['_ m-arg varargs] ~@more)
|
|
||||||
(defn ~helper [& args#]
|
|
||||||
(let [[m# args#] (if (plain-map? (first args#))
|
|
||||||
[(first args#) (rest args#)]
|
|
||||||
[{} args#])]
|
|
||||||
(build-clause ~kw m# args#)))
|
|
||||||
|
|
||||||
;; maintain the original arglist instead of getting
|
|
||||||
;; ([& args__6880__auto__])
|
|
||||||
(alter-meta!
|
|
||||||
(var ~helper)
|
|
||||||
assoc
|
|
||||||
:arglists
|
|
||||||
'(~['& varargs]
|
|
||||||
~[m-arg '& varargs]))))))
|
|
||||||
|
|
||||||
(defn collify [x]
|
|
||||||
(if (coll? x) x [x]))
|
|
||||||
|
|
||||||
(defhelper select [m fields]
|
|
||||||
(assoc m :select (collify fields)))
|
|
||||||
|
|
||||||
(defhelper merge-select [m fields]
|
|
||||||
(update-in m [:select] concat (collify fields)))
|
|
||||||
|
|
||||||
(defhelper un-select [m fields]
|
|
||||||
(update-in m [:select] #(remove (set (collify fields)) %)))
|
|
||||||
|
|
||||||
(defhelper from [m tables]
|
|
||||||
(assoc m :from (collify tables)))
|
|
||||||
|
|
||||||
(defhelper merge-from [m tables]
|
|
||||||
(update-in m [:from] concat (collify tables)))
|
|
||||||
|
|
||||||
(defmethod build-clause :where [_ m pred]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :where pred)))
|
|
||||||
|
|
||||||
(defn- prep-where [args]
|
|
||||||
(let [[m preds] (if (map? (first args))
|
|
||||||
[(first args) (rest args)]
|
|
||||||
[{} args])
|
|
||||||
[logic-op preds] (if (keyword? (first preds))
|
|
||||||
[(first preds) (rest preds)]
|
|
||||||
[:and preds])
|
|
||||||
preds (remove nil? preds)
|
|
||||||
pred (if (>= 1 (count preds))
|
|
||||||
(first preds)
|
|
||||||
(into [logic-op] preds))]
|
|
||||||
[m pred logic-op]))
|
|
||||||
|
|
||||||
(defn where [& args]
|
|
||||||
(let [[m pred] (prep-where args)]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :where pred))))
|
|
||||||
|
|
||||||
(defmethod build-clause :merge-where [_ m pred]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :where (if (not (nil? (:where m)))
|
|
||||||
[:and (:where m) pred]
|
|
||||||
pred))))
|
|
||||||
|
|
||||||
(defn merge-where [& args]
|
|
||||||
(let [[m pred logic-op] (prep-where args)]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :where (if (not (nil? (:where m)))
|
|
||||||
[logic-op (:where m) pred]
|
|
||||||
pred)))))
|
|
||||||
|
|
||||||
(defhelper join [m clauses]
|
|
||||||
(assoc m :join clauses))
|
|
||||||
|
|
||||||
(defhelper merge-join [m clauses]
|
|
||||||
(update-in m [:join] concat clauses))
|
|
||||||
|
|
||||||
(defhelper left-join [m clauses]
|
|
||||||
(assoc m :left-join clauses))
|
|
||||||
|
|
||||||
(defhelper merge-left-join [m clauses]
|
|
||||||
(update-in m [:left-join] concat clauses))
|
|
||||||
|
|
||||||
(defhelper right-join [m clauses]
|
|
||||||
(assoc m :right-join clauses))
|
|
||||||
|
|
||||||
(defhelper merge-right-join [m clauses]
|
|
||||||
(update-in m [:right-join] concat clauses))
|
|
||||||
|
|
||||||
(defhelper full-join [m clauses]
|
|
||||||
(assoc m :full-join clauses))
|
|
||||||
|
|
||||||
(defhelper merge-full-join [m clauses]
|
|
||||||
(update-in m [:full-join] concat clauses))
|
|
||||||
|
|
||||||
(defhelper cross-join [m clauses]
|
|
||||||
(assoc m :cross-join clauses))
|
|
||||||
|
|
||||||
(defhelper merge-cross-join [m clauses]
|
|
||||||
(update-in m [:cross-join] concat clauses))
|
|
||||||
|
|
||||||
(defmethod build-clause :group-by [_ m fields]
|
|
||||||
(assoc m :group-by (collify fields)))
|
|
||||||
|
|
||||||
(defn group [& args]
|
|
||||||
(let [[m fields] (if (map? (first args))
|
|
||||||
[(first args) (rest args)]
|
|
||||||
[{} args])]
|
|
||||||
(build-clause :group-by m fields)))
|
|
||||||
|
|
||||||
(defhelper merge-group-by [m fields]
|
|
||||||
(update-in m [:group-by] concat (collify fields)))
|
|
||||||
|
|
||||||
(defmethod build-clause :having [_ m pred]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :having pred)))
|
|
||||||
|
|
||||||
(defn having [& args]
|
|
||||||
(let [[m pred] (prep-where args)]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :having pred))))
|
|
||||||
|
|
||||||
(defmethod build-clause :merge-having [_ m pred]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :having (if (not (nil? (:having m)))
|
|
||||||
[:and (:having m) pred]
|
|
||||||
pred))))
|
|
||||||
|
|
||||||
(defn merge-having [& args]
|
|
||||||
(let [[m pred logic-op] (prep-where args)]
|
|
||||||
(if (nil? pred)
|
|
||||||
m
|
|
||||||
(assoc m :having (if (not (nil? (:having m)))
|
|
||||||
[logic-op (:having m) pred]
|
|
||||||
pred)))))
|
|
||||||
|
|
||||||
(defhelper order-by [m fields]
|
|
||||||
(assoc m :order-by (collify fields)))
|
|
||||||
|
|
||||||
(defhelper merge-order-by [m fields]
|
|
||||||
(update-in m [:order-by] concat (collify fields)))
|
|
||||||
|
|
||||||
(defhelper limit [m l]
|
|
||||||
(if (nil? l)
|
|
||||||
m
|
|
||||||
(assoc m :limit (if (coll? l) (first l) l))))
|
|
||||||
|
|
||||||
(defhelper offset [m o]
|
|
||||||
(if (nil? o)
|
|
||||||
m
|
|
||||||
(assoc m :offset (if (coll? o) (first o) o))))
|
|
||||||
|
|
||||||
(defhelper lock [m lock]
|
|
||||||
(cond-> m
|
|
||||||
lock
|
|
||||||
(assoc :lock lock)))
|
|
||||||
|
|
||||||
(defhelper modifiers [m ms]
|
|
||||||
(if (nil? ms)
|
|
||||||
m
|
|
||||||
(assoc m :modifiers (collify ms))))
|
|
||||||
|
|
||||||
(defhelper merge-modifiers [m ms]
|
|
||||||
(if (nil? ms)
|
|
||||||
m
|
|
||||||
(update-in m [:modifiers] concat (collify ms))))
|
|
||||||
|
|
||||||
(defmethod build-clause :insert-into [_ m table]
|
|
||||||
(assoc m :insert-into table))
|
|
||||||
|
|
||||||
(defn insert-into
|
|
||||||
([table] (insert-into nil table))
|
|
||||||
([m table] (build-clause :insert-into m table)))
|
|
||||||
|
|
||||||
(defn- check-varargs
|
|
||||||
"Called for helpers that require unrolled arguments to catch the mistake
|
|
||||||
of passing a collection as a single argument."
|
|
||||||
[helper args]
|
|
||||||
(when (and (coll? args) (= 1 (count args)) (coll? (first args)))
|
|
||||||
(let [msg (str (name helper) " takes varargs, not a single collection")]
|
|
||||||
(throw #?(:clj (IllegalArgumentException. msg)
|
|
||||||
:cljs (js/Error. msg))))))
|
|
||||||
|
|
||||||
(defmethod build-clause :columns [_ m fields]
|
|
||||||
(assoc m :columns (collify fields)))
|
|
||||||
|
|
||||||
(defn columns [& args]
|
|
||||||
(let [[m fields] (if (map? (first args))
|
|
||||||
[(first args) (rest args)]
|
|
||||||
[{} args])]
|
|
||||||
(check-varargs :columns fields)
|
|
||||||
(build-clause :columns m fields)))
|
|
||||||
|
|
||||||
(defmethod build-clause :merge-columns [_ m fields]
|
|
||||||
(update-in m [:columns] concat (collify fields)))
|
|
||||||
|
|
||||||
(defn merge-columns [& args]
|
|
||||||
(let [[m fields] (if (map? (first args))
|
|
||||||
[(first args) (rest args)]
|
|
||||||
[{} args])]
|
|
||||||
(check-varargs :merge-columns fields)
|
|
||||||
(build-clause :merge-columns m fields)))
|
|
||||||
|
|
||||||
(defhelper composite [m vs]
|
|
||||||
(if (nil? vs)
|
|
||||||
m
|
|
||||||
(assoc m :composite (collify vs))))
|
|
||||||
|
|
||||||
(defmethod build-clause :values [_ m vs]
|
|
||||||
(assoc m :values vs))
|
|
||||||
|
|
||||||
(defn values
|
|
||||||
([vs] (values nil vs))
|
|
||||||
([m vs] (build-clause :values m vs)))
|
|
||||||
|
|
||||||
(defmethod build-clause :merge-values [_ m vs]
|
|
||||||
(update-in m [:values] concat vs))
|
|
||||||
|
|
||||||
(defn merge-values
|
|
||||||
([vs] (merge-values nil vs))
|
|
||||||
([m vs] (build-clause :merge-values m vs)))
|
|
||||||
|
|
||||||
(defmethod build-clause :query-values [_ m vs]
|
|
||||||
(assoc m :query-values vs))
|
|
||||||
|
|
||||||
(defn query-values
|
|
||||||
([vs] (values nil vs))
|
|
||||||
([m vs] (build-clause :query-values m vs)))
|
|
||||||
|
|
||||||
(defmethod build-clause :update [_ m table]
|
|
||||||
(assoc m :update table))
|
|
||||||
|
|
||||||
(defn update
|
|
||||||
([table] (update nil table))
|
|
||||||
([m table] (build-clause :update m table)))
|
|
||||||
|
|
||||||
(defmethod build-clause :set [_ m values]
|
|
||||||
(assoc m :set values))
|
|
||||||
|
|
||||||
;; short for sql set, to avoid name collision with clojure.core/set
|
|
||||||
(defn sset
|
|
||||||
([vs] (sset nil vs))
|
|
||||||
([m vs] (build-clause :set m vs)))
|
|
||||||
|
|
||||||
(defmethod build-clause :set0 [_ m values]
|
|
||||||
(assoc m :set0 values))
|
|
||||||
|
|
||||||
;; set with lower priority (before from)
|
|
||||||
(defn set0
|
|
||||||
([vs] (set0 nil vs))
|
|
||||||
([m vs] (build-clause :set0 m vs)))
|
|
||||||
|
|
||||||
(defmethod build-clause :set [_ m values]
|
|
||||||
(assoc m :set values))
|
|
||||||
|
|
||||||
;; set with higher priority (after join)
|
|
||||||
(defn set1
|
|
||||||
([vs] (set1 nil vs))
|
|
||||||
([m vs] (build-clause :set1 m vs)))
|
|
||||||
|
|
||||||
(defmethod build-clause :delete-from [_ m table]
|
|
||||||
(assoc m :delete-from table))
|
|
||||||
|
|
||||||
(defn delete-from
|
|
||||||
([table] (delete-from nil table))
|
|
||||||
([m table] (build-clause :delete-from m table)))
|
|
||||||
|
|
||||||
(defmethod build-clause :delete [_ m tables]
|
|
||||||
(assoc m :delete tables))
|
|
||||||
|
|
||||||
(defn delete
|
|
||||||
([tables] (delete nil tables))
|
|
||||||
([m tables] (build-clause :delete m tables)))
|
|
||||||
|
|
||||||
(defmethod build-clause :truncate [_ m table]
|
|
||||||
(assoc m :truncate table))
|
|
||||||
|
|
||||||
(defn truncate
|
|
||||||
([table] (truncate nil table))
|
|
||||||
([m table] (build-clause :truncate m table)))
|
|
||||||
|
|
||||||
(defhelper with [m ctes]
|
|
||||||
(assoc m :with ctes))
|
|
||||||
|
|
||||||
(defhelper with-recursive [m ctes]
|
|
||||||
(assoc m :with-recursive ctes))
|
|
||||||
|
|
||||||
(defmethod build-clause :union [_ m maps]
|
|
||||||
(assoc m :union maps))
|
|
||||||
|
|
||||||
(defmethod build-clause :union-all [_ m maps]
|
|
||||||
(assoc m :union-all maps))
|
|
||||||
|
|
||||||
(defmethod build-clause :intersect [_ m maps]
|
|
||||||
(assoc m :intersect maps))
|
|
||||||
|
|
||||||
(defmethod build-clause :except [_ m maps]
|
|
||||||
(assoc m :except maps))
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
(ns honeysql.types
|
|
||||||
(:refer-clojure :exclude [array]))
|
|
||||||
|
|
||||||
(defrecord SqlCall [name args])
|
|
||||||
|
|
||||||
(defn call
|
|
||||||
"Represents a SQL function call. Name should be a keyword."
|
|
||||||
[name & args]
|
|
||||||
(SqlCall. name args))
|
|
||||||
|
|
||||||
(defn read-sql-call [form]
|
|
||||||
;; late bind so that we get new class on REPL reset
|
|
||||||
(apply #?(:clj (resolve `call) :cljs call) form))
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
|
|
||||||
(defrecord SqlRaw [s])
|
|
||||||
|
|
||||||
(defn raw
|
|
||||||
"Represents a raw SQL string"
|
|
||||||
[s]
|
|
||||||
(SqlRaw. (if (vector? s) s (str s))))
|
|
||||||
|
|
||||||
(defn read-sql-raw [form]
|
|
||||||
;; late bind, as above
|
|
||||||
(#?(:clj (resolve `raw) :cljs raw) form))
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
|
|
||||||
(defrecord SqlParam [name])
|
|
||||||
|
|
||||||
(defn param
|
|
||||||
"Represents a SQL parameter which can be filled in later"
|
|
||||||
[name]
|
|
||||||
(SqlParam. name))
|
|
||||||
|
|
||||||
(defn param-name [^SqlParam param]
|
|
||||||
(.-name param))
|
|
||||||
|
|
||||||
(defn read-sql-param [form]
|
|
||||||
;; late bind, as above
|
|
||||||
(#?(:clj (resolve `param) :cljs param) form))
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
|
|
||||||
(defrecord SqlArray [values])
|
|
||||||
|
|
||||||
(defn array
|
|
||||||
"Represents a SQL array."
|
|
||||||
[values]
|
|
||||||
(SqlArray. values))
|
|
||||||
|
|
||||||
(defn array-vals [^SqlArray a]
|
|
||||||
(.-values a))
|
|
||||||
|
|
||||||
(defn read-sql-array [form]
|
|
||||||
;; late bind, as above
|
|
||||||
(#?(:clj (resolve `array) :cljs array) form))
|
|
||||||
|
|
||||||
;;;;
|
|
||||||
|
|
||||||
(defrecord SqlInline [value])
|
|
||||||
|
|
||||||
(defprotocol Inlinable
|
|
||||||
(inline-str [x]))
|
|
||||||
|
|
||||||
(defn inline
|
|
||||||
"Prevents parameterization"
|
|
||||||
[value]
|
|
||||||
(SqlInline. value))
|
|
||||||
|
|
||||||
(defn read-sql-inline [form]
|
|
||||||
(#?(:clj (resolve `inline) :cljs inline) form))
|
|
||||||
|
|
||||||
#?(:clj
|
|
||||||
(do
|
|
||||||
(defmethod print-method SqlCall [^SqlCall o ^java.io.Writer w]
|
|
||||||
(.write w (str "#sql/call " (pr-str (into [(.-name o)] (.-args o))))))
|
|
||||||
|
|
||||||
(defmethod print-dup SqlCall [o w]
|
|
||||||
(print-method o w))
|
|
||||||
|
|
||||||
(defmethod print-method SqlRaw [^SqlRaw o ^java.io.Writer w]
|
|
||||||
(.write w (str "#sql/raw " (pr-str (.s o)))))
|
|
||||||
|
|
||||||
(defmethod print-dup SqlRaw [o w]
|
|
||||||
(print-method o w))
|
|
||||||
|
|
||||||
(defmethod print-method SqlParam [^SqlParam o ^java.io.Writer w]
|
|
||||||
(.write w (str "#sql/param " (pr-str (.name o)))))
|
|
||||||
|
|
||||||
(defmethod print-dup SqlParam [o w]
|
|
||||||
(print-method o w))
|
|
||||||
|
|
||||||
(defmethod print-method SqlArray [^SqlArray a ^java.io.Writer w]
|
|
||||||
(.write w (str "#sql/array " (pr-str (.values a)))))
|
|
||||||
|
|
||||||
(defmethod print-dup SqlArray [a w]
|
|
||||||
(print-method a w))
|
|
||||||
|
|
||||||
(defmethod print-method SqlInline [^SqlInline a ^java.io.Writer w]
|
|
||||||
(.write w (str "#sql/inline " (pr-str (.value a)))))
|
|
||||||
|
|
||||||
(defmethod print-dup SqlInline [a w]
|
|
||||||
(print-method a w))))
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
(ns honeysql.util)
|
|
||||||
|
|
||||||
(defmacro defalias [sym var-sym]
|
|
||||||
`(let [v# (var ~var-sym)]
|
|
||||||
(intern *ns* (with-meta (quote ~sym) (meta v#)) @v#)))
|
|
||||||
Loading…
Reference in a new issue