From c86fb15f4a5a78ac9f60df942aa4ff223a460e2a Mon Sep 17 00:00:00 2001 From: Walter Leibbrandt <23798+walterl@users.noreply.github.com> Date: Fri, 9 Aug 2019 03:19:39 +0200 Subject: [PATCH 1/4] Use locale agnostic `clojure.string/lower-case` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without this change `clojure.string/lower-case` uses the default locale, turning (for example) "ID" into ":ıd" in the Turkish locale. --- src/toucan/db.clj | 8 ++++++-- src/toucan/util.clj | 11 ++++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/toucan/db.clj b/src/toucan/db.clj index ba79a82..8204550 100644 --- a/src/toucan/db.clj +++ b/src/toucan/db.clj @@ -153,7 +153,7 @@ (model-symb->ns 'CardFavorite) -> 'my-project.models.card-favorite" [symb] {:pre [(symbol? symb)]} - (symbol (str (models/root-namespace) \. (s/lower-case (s/replace (name symb) #"([a-z])([A-Z])" "$1-$2"))))) + (symbol (str (models/root-namespace) \. (u/lower-case (s/replace (name symb) #"([a-z])([A-Z])" "$1-$2"))))) (defn- resolve-model-from-symbol "Resolve the model associated with SYMB, calling `require` on its namespace if needed. @@ -271,7 +271,11 @@ "Compile `honeysql-from` and call `jdbc/query` against the application database. Options are passed along to `jdbc/query`." [honeysql-form & {:as options}] - (jdbc/query (connection) (honeysql->sql honeysql-form) options)) + (jdbc/query (connection) + (honeysql->sql honeysql-form) + ; FIXME: This has already been fixed in `clojure.java.jdbc`, so + ; this option can be removed when using >= 0.7.10. + (into options {:identifiers u/lower-case}))) (defn reducible-query "Compile `honeysql-from` and call `jdbc/reducible-query` against the application database. Options are passed along diff --git a/src/toucan/util.clj b/src/toucan/util.clj index 633f2f1..9e9a855 100644 --- a/src/toucan/util.clj +++ b/src/toucan/util.clj @@ -1,6 +1,7 @@ (ns toucan.util "Utility functions used by other Toucan modules." - (:require [clojure.string :as s])) + (:require [clojure.string :as s]) + (:import java.util.Locale)) (defn keyword->qualified-name "Return keyword K as a string, including its namespace, if any (unlike `name`). @@ -9,3 +10,11 @@ [k] (when k (s/replace (str k) #"^:" ""))) + +(defn lower-case + "Locale-agnostic version of `clojure.string/lower-case`. + `clojure.string/lower-case` uses the default locale in conversions, turning + `ID` into `ıd`, in the Turkish locale. This function always uses the + `Locale/US` locale." + [s] + (.. s toString (toLowerCase (Locale/US)))) From c4608c8e20ac0a8f6b5d7a15673f39cbfa96fe4e Mon Sep 17 00:00:00 2001 From: Walter Leibbrandt <23798+walterl@users.noreply.github.com> Date: Mon, 12 Aug 2019 22:16:27 +0200 Subject: [PATCH 2/4] Fix comment style --- src/toucan/db.clj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/toucan/db.clj b/src/toucan/db.clj index 8204550..ad9a3ba 100644 --- a/src/toucan/db.clj +++ b/src/toucan/db.clj @@ -273,8 +273,8 @@ [honeysql-form & {:as options}] (jdbc/query (connection) (honeysql->sql honeysql-form) - ; FIXME: This has already been fixed in `clojure.java.jdbc`, so - ; this option can be removed when using >= 0.7.10. + ;; FIXME: This has already been fixed in `clojure.java.jdbc`, so + ;; this option can be removed when using >= 0.7.10. (into options {:identifiers u/lower-case}))) (defn reducible-query From 6e4b0e0e50f2ccade47bb12ba3c72b256d4cc04d Mon Sep 17 00:00:00 2001 From: Walter Leibbrandt <23798+walterl@users.noreply.github.com> Date: Tue, 13 Aug 2019 00:11:12 +0200 Subject: [PATCH 3/4] Tag parameter `s` as `CharSequence` --- src/toucan/util.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/toucan/util.clj b/src/toucan/util.clj index 9e9a855..2a9245e 100644 --- a/src/toucan/util.clj +++ b/src/toucan/util.clj @@ -16,5 +16,5 @@ `clojure.string/lower-case` uses the default locale in conversions, turning `ID` into `ıd`, in the Turkish locale. This function always uses the `Locale/US` locale." - [s] + [^CharSequence s] (.. s toString (toLowerCase (Locale/US)))) From 159ffb591c5c960c21805e02f0a98b70274a0690 Mon Sep 17 00:00:00 2001 From: Walter Leibbrandt <23798+walterl@users.noreply.github.com> Date: Tue, 13 Aug 2019 01:03:50 +0200 Subject: [PATCH 4/4] Add test for Turkish lower case issue --- test/toucan/db_test.clj | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/test/toucan/db_test.clj b/test/toucan/db_test.clj index 0d2d2e2..489b2c8 100644 --- a/test/toucan/db_test.clj +++ b/test/toucan/db_test.clj @@ -1,5 +1,6 @@ (ns toucan.db-test - (:require [expectations :refer :all] + (:require [clojure.java.jdbc :as jdbc] + [expectations :refer :all] [toucan [db :as db] [test-setup :as test]] @@ -8,7 +9,8 @@ [category :refer [Category]] [phone-number :refer [PhoneNumber]] [user :refer [User]] - [venue :refer [Venue]]])) + [venue :refer [Venue]]]) + (:import java.util.Locale)) ;; Test overriding quoting-style (expect @@ -162,6 +164,23 @@ :order-by [:id] :limit 1})) +;; Test that identifiers are correctly lower cased in Turkish locale (#59) +(expect + :id + (let [connection (db/connection) + original-locale (Locale/getDefault)] + (try + (Locale/setDefault (Locale/forLanguageTag "tr")) + (jdbc/execute! connection "DROP TABLE IF EXISTS heroes") + (jdbc/execute! connection "CREATE TABLE heroes (\"ID\" SERIAL PRIMARY KEY, \"NAME\" VARCHAR(256))") + (jdbc/execute! connection "INSERT INTO heroes (\"NAME\") VALUES ('Batman')") + (let [first-row (first (db/query {:select [:ID] :from [:heroes]}))] + ;; If `db/query` (jdbc) uses `clojure.string/lower-case`, `:ID` will be converted to `:ıd` in Turkish locale + (first (keys first-row))) + (finally + (jdbc/execute! connection "DROP TABLE IF EXISTS heroes") + (Locale/setDefault original-locale))))) + (defn- transduce-to-set "Process `reducible-query-result` using a transducer that puts the rows from the resultset into a set" [reducible-query-result]