diff --git a/README.md b/README.md index b2e6c00..74e16f2 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Keywords that begin with `?` are interpreted as bindable parameters: (-> (select :id) (from :foo) (where [:= :a :?baz]) - (sql/format {:baz "BAZ"})) + (sql/format :params {:baz "BAZ"})) => ["SELECT id FROM foo WHERE a = ?" "BAZ"] ``` @@ -116,10 +116,20 @@ There are helper functions and data literals for SQL function calls, field quali (where [:= :a (sql/param :baz)])) => {:where [:= :a #sql/param :baz], :from (:foo), :select (#sql/call [:foo :bar] :foo.a #sql/raw "@var := foo.bar")} -(sql/format *1 {:baz "BAZ"}) +(sql/format *1 :params {:baz "BAZ"}) => ["SELECT FOO(bar), foo.a, @var := foo.bar FROM foo WHERE a = ?" "BAZ"] ``` +To quote identifiers, pass the `:quoting` keyword option to `format`. Valid options are `:ansi`, `:mysql`, or `:sqlserver`: + +```clj +(-> (select :foo.a) + (from :foo) + (where [:= :foo.a "baz"]) + (sql/format :quoting :mysql)) +=> ["SELECT `foo`.`a` FROM `foo` WHERE `foo`.`a` = ?" "baz"] +``` + Here's a big, complicated query. Note that Honey SQL makes no attempt to verify that your queries make any sense. It merely renders surface syntax. ```clj diff --git a/src/honeysql/format.clj b/src/honeysql/format.clj index 227fb3b..93bb690 100644 --- a/src/honeysql/format.clj +++ b/src/honeysql/format.clj @@ -28,6 +28,20 @@ (def ^:dynamic *subquery?* false) +(def ^:private quote-fns + {:none identity + :ansi #(str \" % \") + :mysql #(str \` % \`) + :sqlserver #(str \[ % \])}) + +(def ^:dynamic *quote-identifier-fn* nil) + +(defn quote-identifier [x] + (if-not *quote-identifier-fn* + x + (let [parts (string/split (name x) #"\.")] + (string/join "." (map #(*quote-identifier-fn* %) parts))))) + (def infix-fns #{"+" "-" "*" "/" "%" "mod" "|" "&" "^" "and" "or" "xor" @@ -129,19 +143,32 @@ of a SQL string and parameters, as expected by clojure.java.jdbc. Input parameters will be filled into designated spots according to - name (if a map is provided) or by position (if a sequence is provided)." - [sql-map & [params]] - (binding [*params* (atom []) - *input-params* (atom params)] - (let [sql-str (to-sql sql-map)] - (if (seq @*params*) - (into [sql-str] @*params*) - [sql-str])))) + name (if a map is provided) or by position (if a sequence is provided). + + Instead of passing parameters, you can use keyword arguments: + :params - input parameters + :quoting - quoting styling to use for identifiers; one of :ansi, + :mysql, or :sqlserver" + [sql-map & params-or-opts] + (let [opts (when (keyword? (first params-or-opts)) + (apply hash-map params-or-opts)) + params (if (coll? (first params-or-opts)) + (first params-or-opts) + (:params opts))] + (binding [*params* (atom []) + *input-params* (atom params) + *quote-identifier-fn* (quote-fns (:quoting opts))] + (let [sql-str (to-sql sql-map)] + (if (seq @*params*) + (into [sql-str] @*params*) + [sql-str]))))) (defn format-predicate "Formats a predicate (e.g., for WHERE, JOIN, or HAVING) as a string." - [pred] - (binding [*params* (atom [])] + [pred & {:keys [quoting]}] + (binding [*params* (atom []) + *quote-identifier-fn* (or (quote-fns quoting) + *quote-identifier-fn*)] (let [sql-str (format-predicate* pred)] (if (seq @*params*) (into [sql-str] @*params*) @@ -159,7 +186,7 @@ \% (let [call-args (string/split (subs s 1) #"\." 2)] (to-sql (apply call (map keyword call-args)))) \? (to-sql (param (keyword (subs s 1)))) - (-> s (string/replace "-" "_"))))) + (-> s (string/replace "-" "_") quote-identifier)))) clojure.lang.Symbol (-to-sql [x] (-> x name (string/replace "-" "_"))) java.lang.Number @@ -173,9 +200,7 @@ ;; alias (str (to-sql (first x)) " AS " - (if (string? (second x)) - (str "\"" (second x) "\"") - (to-sql (second x)))))) + (str "\"" (name (second x)) "\"")))) SqlCall (-to-sql [x] (binding [*fn-context?* true] (let [fn-name (name (.name x))