From d734767877b718244d366a7680069eb99ab723b3 Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sun, 11 Apr 2021 03:21:35 -0700 Subject: [PATCH] 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. --- test/data_readers.clj | 6 - test/honeysql/core.cljc | 76 ---- test/honeysql/core_test.cljc | 270 ------------- test/honeysql/format.cljc | 714 --------------------------------- test/honeysql/format_test.cljc | 322 --------------- test/honeysql/helpers.cljc | 330 --------------- test/honeysql/types.cljc | 105 ----- test/honeysql/util.clj | 5 - 8 files changed, 1828 deletions(-) delete mode 100644 test/data_readers.clj delete mode 100644 test/honeysql/core.cljc delete mode 100644 test/honeysql/core_test.cljc delete mode 100644 test/honeysql/format.cljc delete mode 100644 test/honeysql/format_test.cljc delete mode 100644 test/honeysql/helpers.cljc delete mode 100644 test/honeysql/types.cljc delete mode 100644 test/honeysql/util.clj diff --git a/test/data_readers.clj b/test/data_readers.clj deleted file mode 100644 index 50eb769..0000000 --- a/test/data_readers.clj +++ /dev/null @@ -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} diff --git a/test/honeysql/core.cljc b/test/honeysql/core.cljc deleted file mode 100644 index 8a2a144..0000000 --- a/test/honeysql/core.cljc +++ /dev/null @@ -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) - ,) diff --git a/test/honeysql/core_test.cljc b/test/honeysql/core_test.cljc deleted file mode 100644 index 4247f1b..0000000 --- a/test/honeysql/core_test.cljc +++ /dev/null @@ -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)) diff --git a/test/honeysql/format.cljc b/test/honeysql/format.cljc deleted file mode 100644 index 2eb9c43..0000000 --- a/test/honeysql/format.cljc +++ /dev/null @@ -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+" " ")) diff --git a/test/honeysql/format_test.cljc b/test/honeysql/format_test.cljc deleted file mode 100644 index d7fa8c7..0000000 --- a/test/honeysql/format_test.cljc +++ /dev/null @@ -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]]})))) diff --git a/test/honeysql/helpers.cljc b/test/honeysql/helpers.cljc deleted file mode 100644 index 5b5854c..0000000 --- a/test/honeysql/helpers.cljc +++ /dev/null @@ -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)) diff --git a/test/honeysql/types.cljc b/test/honeysql/types.cljc deleted file mode 100644 index 7d9fb9d..0000000 --- a/test/honeysql/types.cljc +++ /dev/null @@ -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)))) diff --git a/test/honeysql/util.clj b/test/honeysql/util.clj deleted file mode 100644 index d2393aa..0000000 --- a/test/honeysql/util.clj +++ /dev/null @@ -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#))) \ No newline at end of file