From 64e6d338cc4ede8c5a035766d642d2d59f716303 Mon Sep 17 00:00:00 2001 From: Kristofer Ranstrom Date: Tue, 14 Nov 2023 14:31:58 -0500 Subject: [PATCH] Proposed opt `quoted-when` Proposing a new option, quoted-when, to allow overriding the default opinionated quoting when quoting has been disabled. This can be useful, for example, if a user wants to selectively quote fields like only quoting reserved words. --- doc/options.md | 1 + src/honey/sql.cljc | 17 +++++++++++++---- test/honey/sql/helpers_test.cljc | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/options.md b/doc/options.md index c48b2fb..a03fa4b 100644 --- a/doc/options.md +++ b/doc/options.md @@ -22,6 +22,7 @@ All options may be omitted. The default behavior of each option is described in * `:numbered` -- a Boolean indicating whether to generate numbered placeholders in the generated SQL (`$1`, `$2`, etc) or positional placeholders (`?`); the default is `false` (positional placeholders); this option was added in 2.4.962, * `:params` -- a hash map providing values for named parameters, identified by names (keywords or symbols) that start with `?` in the DSL; the default is that any such named parameters will have `nil` values, * `:quoted` -- a Boolean indicating whether or not to quote (strop) SQL entity names (table and column names); the default is `nil` -- alphanumeric SQL entity names are not quoted but (as of 2.3.928) "unusual" SQL entity names are quoted; a `false` value turns off all quoting, +* `:quoted-when` -- a function overriding the default behavior of when to quote entities when `:quoted false` (default). Function should take single argument - the entity/part being quoted - and return a boolean indicator whether to quote it or not. * `:quoted-snake` -- a Boolean indicating whether or not quoted and string SQL entity names should have `-` replaced by `_`; the default is `false` -- quoted and string SQL entity names are left exactly as-is, * `:values-default-columns` -- a sequence of column names that should have `DEFAULT` values instead of `NULL` values if used in a `VALUES` clause with no associated matching value in the hash maps passed in; the default behavior is for such missing columns to be given `NULL` values. diff --git a/src/honey/sql.cljc b/src/honey/sql.cljc index 2eb8ee9..a173ec7 100644 --- a/src/honey/sql.cljc +++ b/src/honey/sql.cljc @@ -120,6 +120,7 @@ ; should become defonce (def ^:private default-dialect (atom (:ansi @dialects))) (def ^:private default-quoted (atom nil)) +(def ^:private default-quoted-when (atom nil)) (def ^:private default-quoted-snake (atom nil)) (def ^:private default-inline (atom nil)) (def ^:private default-checking (atom :none)) @@ -130,6 +131,7 @@ ;; functions harder than necessary: (def ^:private ^:dynamic *clause-order* default-clause-order) (def ^:private ^:dynamic *quoted* @default-quoted) +(def ^:private ^:dynamic *quoted-when* @default-quoted-when) (def ^:private ^:dynamic *quoted-snake* @default-quoted-snake) (def ^:private ^:dynamic *inline* @default-inline) (def ^:private ^:dynamic *params* nil) @@ -265,9 +267,10 @@ ;; characters in entity, then quote it: (nil? *quoted*) (fn opt-quote [part] - (if (re-find #"^[A-Za-z0-9_]+$" part) - part - (dialect-q part))) + (let [quote? (or *quoted-when* #(not (re-find #"^[A-Za-z0-9_]+$" %)))] + (if (quote? part) + (dialect-q part) + part))) :else identity) parts-fn (or (:parts-fn *dialect*) @@ -2031,6 +2034,9 @@ true :else @default-quoted) + *quoted-when* (if (contains? opts :quoted-when) + (:quoted-when opts) + @default-quoted-when) *quoted-snake* (if (contains? opts :quoted-snake) (:quoted-snake opts) @default-quoted-snake) @@ -2073,10 +2079,11 @@ * :inline * :numbered * :quoted + * :quoted-when * :quoted-snake Note that calling `set-dialect!` can override the default for `:quoted`." [opts] - (let [unknowns (dissoc opts :checking :inline :numbered :quoted :quoted-snake)] + (let [unknowns (dissoc opts :checking :inline :numbered :quoted :quoted-when :quoted-snake)] (when (seq unknowns) (throw (ex-info (str (str/join ", " (keys unknowns)) " are not options that can be set globally.") @@ -2089,6 +2096,8 @@ (reset! default-numbered (:numbered opts))) (when (contains? opts :quoted) (reset! default-quoted (:quoted opts))) + (when (contains? opts :quoted-when) + (reset! default-quoted-when (:quoted-when opts))) (when (contains? opts :quoted-snake) (reset! default-quoted-snake (:quoted-snake opts))))) diff --git a/test/honey/sql/helpers_test.cljc b/test/honey/sql/helpers_test.cljc index 245f486..791cc82 100644 --- a/test/honey/sql/helpers_test.cljc +++ b/test/honey/sql/helpers_test.cljc @@ -263,6 +263,10 @@ (sql/format {:select [:foo [[:cast :bar [:char 10]]]]} {:dialect :mysql :inline true})))) +(deftest test-quoting + (is (= ["SELECT foo, \"bar\""] + (sql/format {:select [:foo :bar]} {:quoted-when #(some #{%} ["bar"])})))) ;; Example showing only quoting reserved words + (deftest test-value (is (= ["INSERT INTO foo (bar) VALUES (?)" {:baz "my-val"}] (->