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:
Sean Corfield 2021-04-11 03:21:35 -07:00
parent bb9d196e3e
commit d734767877
8 changed files with 0 additions and 1828 deletions

View file

@ -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}

View file

@ -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)
,)

View file

@ -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))

View file

@ -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+" " "))

View file

@ -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]]}))))

View file

@ -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))

View file

@ -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))))

View file

@ -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#)))