From 87e44ae6a6e803e9aca838ba28faff64f145382f Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Thu, 8 Aug 2019 17:01:23 -0700 Subject: [PATCH] Fixes #52 by using US-locale lower-case function --- CHANGELOG.md | 2 +- doc/result-set-builders.md | 2 +- src/next/jdbc/optional.clj | 19 +++++++++++++------ src/next/jdbc/result_set.clj | 29 ++++++++++++++++++----------- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a29389..322a523 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Only accretive/fixative changes will be made from now on. The following changes have been committed to the **master** branch since the 1.0.5 release: -* None. +* Fix #52 by replacing `clojure.string/lower-case` with a US-locale function to avoid breakage in locales such as Turkish. ## Stable Builds diff --git a/doc/result-set-builders.md b/doc/result-set-builders.md index 96ff13a..9b5eacb 100644 --- a/doc/result-set-builders.md +++ b/doc/result-set-builders.md @@ -15,7 +15,7 @@ The default builder for rows and result sets creates qualified keywords that mat The reason behind the default is to a) be a simple transform, b) produce qualified keys in keeping with Clojure's direction (with `clojure.spec` etc), and c) not mess with the data. `as-arrays` is (slightly) faster than `as-maps` since it produces less data (vectors of values instead of vectors of hash maps), but the `lower` options will be slightly slower since they include (conditional) logic to convert strings to lower-case. The `unqualified` options may be slightly faster than their qualified equivalents but make no attempt to keep column names unique if your SQL joins across multiple tables. -In addition, the following generic builders can take `:label-fn` and `:qualifier-fn` options to control how the label and qualified are processed. The `lower` variants above are implemented in terms of these, passing `clojure.string/lower-case` for both of those options. +In addition, the following generic builders can take `:label-fn` and `:qualifier-fn` options to control how the label and qualified are processed. The `lower` variants above are implemented in terms of these, passing a `lower-case` function for both of those options. * `as-modified-maps` -- table-qualified keywords, * `as-unqualified-modified-maps` -- simple keywords, diff --git a/src/next/jdbc/optional.clj b/src/next/jdbc/optional.clj index 9eb1656..8986e56 100644 --- a/src/next/jdbc/optional.clj +++ b/src/next/jdbc/optional.clj @@ -3,9 +3,9 @@ (ns next.jdbc.optional "Builders that treat NULL SQL values as 'optional' and omit the corresponding keys from the Clojure hash maps for the rows." - (:require [clojure.string :as str] - [next.jdbc.result-set :as rs]) - (:import (java.sql ResultSet))) + (:require [next.jdbc.result-set :as rs]) + (:import (java.sql ResultSet) + (java.util Locale))) (set! *warn-on-reflection* true) @@ -69,18 +69,25 @@ cols (rs/get-unqualified-modified-column-names rsmeta opts)] (->MapResultSetOptionalBuilder rs rsmeta cols))) +(defn- lower-case + "Converts a string to lower case in the US locale to avoid problems in + locales where the lower case version of a character is not a valid SQL + entity name (e.g., Turkish)." + [^String s] + (.toLowerCase s (Locale/US))) + (defn as-lower-maps "Given a `ResultSet` and options, return a `RowBuilder` / `ResultSetBuilder` that produces bare vectors of hash map rows, with lower-case keys and nil columns omitted." [rs opts] (as-modified-maps rs (assoc opts - :qualifier-fn str/lower-case - :label-fn str/lower-case))) + :qualifier-fn lower-case + :label-fn lower-case))) (defn as-unqualified-lower-maps "Given a `ResultSet` and options, return a `RowBuilder` / `ResultSetBuilder` that produces bare vectors of hash map rows, with simple, lower-case keys and nil columns omitted." [rs opts] - (as-unqualified-modified-maps rs (assoc opts :label-fn str/lower-case))) + (as-unqualified-modified-maps rs (assoc opts :label-fn lower-case))) diff --git a/src/next/jdbc/result_set.clj b/src/next/jdbc/result_set.clj index a9fbac5..f1829ab 100644 --- a/src/next/jdbc/result_set.clj +++ b/src/next/jdbc/result_set.clj @@ -12,12 +12,12 @@ Also provides the default implemenations for `Executable` and the default `datafy`/`nav` behavior for rows from a result set." (:require [clojure.core.protocols :as core-p] - [clojure.string :as str] [next.jdbc.prepare :as prepare] [next.jdbc.protocols :as p]) (:import (java.sql PreparedStatement ResultSet ResultSetMetaData - SQLException))) + SQLException) + (java.util Locale))) (set! *warn-on-reflection* true) @@ -60,19 +60,26 @@ (mapv (fn [^Integer i] (keyword ((:label-fn opts) (.getColumnLabel rsmeta i)))) (range 1 (inc (.getColumnCount rsmeta))))) +(defn- lower-case + "Converts a string to lower case in the US locale to avoid problems in + locales where the lower case version of a character is not a valid SQL + entity name (e.g., Turkish)." + [^String s] + (.toLowerCase s (Locale/US))) + (defn get-lower-column-names "Given `ResultSetMetaData`, return a vector of lower-case column names, each qualified by the table from which it came." [rsmeta opts] (get-modified-column-names rsmeta (assoc opts - :qualifier-fn str/lower-case - :label-fn str/lower-case))) + :qualifier-fn lower-case + :label-fn lower-case))) (defn get-unqualified-lower-column-names "Given `ResultSetMetaData`, return a vector of unqualified column names." [rsmeta opts] (get-unqualified-modified-column-names rsmeta - (assoc opts :label-fn str/lower-case))) + (assoc opts :label-fn lower-case))) (defprotocol ReadableColumn "Protocol for reading objects from the `java.sql.ResultSet`. Default @@ -184,14 +191,14 @@ that produces bare vectors of hash map rows, with lower-case keys." [rs opts] (as-modified-maps rs (assoc opts - :qualifier-fn str/lower-case - :label-fn str/lower-case))) + :qualifier-fn lower-case + :label-fn lower-case))) (defn as-unqualified-lower-maps "Given a `ResultSet` and options, return a `RowBuilder` / `ResultSetBuilder` that produces bare vectors of hash map rows, with simple, lower-case keys." [rs opts] - (as-unqualified-modified-maps rs (assoc opts :label-fn str/lower-case))) + (as-unqualified-modified-maps rs (assoc opts :label-fn lower-case))) (defrecord ArrayResultSetBuilder [^ResultSet rs rsmeta cols] RowBuilder @@ -254,15 +261,15 @@ row values." [rs opts] (as-modified-arrays rs (assoc opts - :qualifier-fn str/lower-case - :label-fn str/lower-case))) + :qualifier-fn lower-case + :label-fn lower-case))) (defn as-unqualified-lower-arrays "Given a `ResultSet` and options, return a `RowBuilder` / `ResultSetBuilder` that produces a vector of simple, lower-case column names followed by vectors of row values." [rs opts] - (as-unqualified-modified-arrays rs (assoc opts :label-fn str/lower-case))) + (as-unqualified-modified-arrays rs (assoc opts :label-fn lower-case))) (declare navize-row)