From bd076b193ad2b098b6a6c4c6bc34e726fe41e386 Mon Sep 17 00:00:00 2001 From: Cam Saul Date: Fri, 8 Feb 2019 19:46:57 -0800 Subject: [PATCH] Fix query generation when user.language is Turkish --- src/honeysql/format.cljc | 17 ++++++++++++++--- test/honeysql/format_test.cljc | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/honeysql/format.cljc b/src/honeysql/format.cljc index ccc07d4..bb89012 100644 --- a/src/honeysql/format.cljc +++ b/src/honeysql/format.cljc @@ -71,6 +71,17 @@ (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 [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* @@ -453,7 +464,7 @@ (->> args (remove nil?) (map format-predicate*) - (string/join (str " " (string/upper-case op-name) " ")) + (string/join (str " " (upper-case op-name) " ")) (paren-wrap)) "exists" @@ -494,7 +505,7 @@ (defmethod format-clause :select [[_ fields] sql-map] (str "SELECT " (when (:modifiers sql-map) - (str (space-join (map (comp string/upper-case name) + (str (space-join (map (comp upper-case name) (:modifiers sql-map))) " ")) (comma-join (map to-sql fields)))) @@ -507,7 +518,7 @@ (defn format-join [type table pred] (str (when type - (str (string/upper-case (name type)) " ")) + (str (upper-case (name type)) " ")) "JOIN " (to-sql table) (when pred (if (= :using (first pred)) diff --git a/test/honeysql/format_test.cljc b/test/honeysql/format_test.cljc index 4735c96..f6d1f76 100644 --- a/test/honeysql/format_test.cljc +++ b/test/honeysql/format_test.cljc @@ -2,6 +2,7 @@ (: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?* quote-identifier format-clause format @@ -228,3 +229,21 @@ (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"))))))