diff --git a/sci b/sci index 42243de7..5d958bf2 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit 42243de76af11810ab45703a414d86d61e8d94df +Subproject commit 5d958bf28319ef34bacb533282b1fdd2fe440df3 diff --git a/script/lib_tests/honeysql_test b/script/lib_tests/honeysql_test new file mode 100755 index 00000000..cafadff3 --- /dev/null +++ b/script/lib_tests/honeysql_test @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -eo pipefail + +if [ "$BABASHKA_TEST_ENV" = "native" ]; then + BB_CMD="./bb" +else + BB_CMD="lein bb" +fi + +export BABASHKA_CLASSPATH +BABASHKA_CLASSPATH=$(clojure -Sdeps '{:deps {honeysql {:mvn/version "1.0.444"}}}' -Spath) + +$BB_CMD -cp "$BABASHKA_CLASSPATH:test-resources/lib_tests" -e " +(require '[honeysql.core-test]) +(require '[honeysql.format-test]) +(require '[clojure.test :as t]) + +(let [{:keys [:test :pass :fail :error]} + (t/run-tests 'honeysql.core-test 'honeysql.format-test)] + (when-not (pos? test) + (System/exit 1)) + (System/exit (+ fail error))) +" diff --git a/script/run_lib_tests b/script/run_lib_tests index 02206f74..69fded16 100755 --- a/script/run_lib_tests +++ b/script/run_lib_tests @@ -27,3 +27,4 @@ script/lib_tests/cljc_java_time_test script/lib_tests/camel_snake_kebab_test script/lib_tests/aero_test script/lib_tests/clojure_data_generators_test +script/lib_tests/honeysql_test diff --git a/src/babashka/impl/classes.clj b/src/babashka/impl/classes.clj index 5236468c..6b52afe9 100644 --- a/src/babashka/impl/classes.clj +++ b/src/babashka/impl/classes.clj @@ -201,6 +201,7 @@ java.util.Base64$Decoder java.util.Base64$Encoder java.util.Date + java.util.Locale java.util.Map java.util.MissingResourceException java.util.Properties @@ -227,12 +228,14 @@ clojure.lang.IEditableCollection clojure.lang.IMapEntry clojure.lang.IPersistentMap + clojure.lang.IPersistentSet clojure.lang.IPersistentVector clojure.lang.IRecord clojure.lang.ISeq clojure.lang.Named clojure.lang.Keyword - clojure.lang.Symbol] + clojure.lang.Symbol + clojure.lang.Sequential] :custom ~custom-map}) (defmacro gen-class-map [] diff --git a/test-resources/lib_tests/honeysql/core_test.cljc b/test-resources/lib_tests/honeysql/core_test.cljc new file mode 100644 index 00000000..4247f1bc --- /dev/null +++ b/test-resources/lib_tests/honeysql/core_test.cljc @@ -0,0 +1,270 @@ +(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-resources/lib_tests/honeysql/format_test.cljc b/test-resources/lib_tests/honeysql/format_test.cljc new file mode 100644 index 00000000..d7fa8c7b --- /dev/null +++ b/test-resources/lib_tests/honeysql/format_test.cljc @@ -0,0 +1,322 @@ +(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]]}))))