diff --git a/src/honeysql/core.clj b/src/honeysql/core.clj index 6fc5168..2c32e96 100644 --- a/src/honeysql/core.clj +++ b/src/honeysql/core.clj @@ -8,6 +8,7 @@ (defalias call types/call) (defalias raw types/raw) +(defalias param types/param) (defalias format format/format) (defalias format-predicate format/format-predicate) diff --git a/src/honeysql/format.clj b/src/honeysql/format.clj index 8f599c2..2ffa08a 100644 --- a/src/honeysql/format.clj +++ b/src/honeysql/format.clj @@ -1,8 +1,8 @@ (ns honeysql.format (:refer-clojure :exclude [format]) - (:require [honeysql.types :refer [call raw]] + (:require [honeysql.types :refer [call raw param-name]] [clojure.string :as string]) - (:import [honeysql.types SqlCall SqlRaw])) + (:import [honeysql.types SqlCall SqlRaw SqlParam])) ;;(set! *warn-on-reflection* true) @@ -22,6 +22,8 @@ possibly-recursive function calls" nil) +(def ^:dynamic *input-params* nil) + (def ^:dynamic *fn-context?* false) (def ^:dynamic *subquery?* false) @@ -118,14 +120,23 @@ (def known-clauses (set clause-order)) -(defn format [sql-map] - (binding [*params* (atom [])] +(defn format + "Takes a SQL map and optional input parameters and returns a vector + 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])))) -(defn format-predicate [pred] +(defn format-predicate + "Formats a predicate (e.g., for WHERE, JOIN, or HAVING) as a string." + [pred] (binding [*params* (atom [])] (let [sql-str (format-predicate* pred)] (if (seq @*params*) @@ -183,7 +194,13 @@ (defn to-sql [x] (if (satisfies? ToSql x) (-to-sql x) - (do + (let [x (if (instance? SqlParam x) + (if (map? @*input-params*) + (get @*input-params* (param-name x)) + (let [x (first @*input-params*)] + (swap! *input-params* rest) + x)) + x)] (swap! *params* conj x) "?"))) diff --git a/src/honeysql/types.clj b/src/honeysql/types.clj index ba70349..8ab36f0 100644 --- a/src/honeysql/types.clj +++ b/src/honeysql/types.clj @@ -12,7 +12,9 @@ (meta [this] _meta) (withMeta [this m] (SqlCall. (.name this) (.args this) m))) -(defn call [name & args] +(defn call + "Represents a SQL function call. Name should be a keyword." + [name & args] (SqlCall. name args nil)) (defn read-sql-call [form] @@ -34,7 +36,9 @@ (meta [this] _meta) (withMeta [this m] (SqlRaw. (.s this) m))) -(defn raw [s] +(defn raw + "Represents a raw SQL string" + [s] (SqlRaw. (str s) nil)) (defn read-sql-raw [form] @@ -45,3 +49,30 @@ (defmethod print-dup SqlRaw [o w] (print-method o w)) + +;;;; + +(deftype SqlParam [name _meta] + Object + (hashCode [this] (hash-combine (hash (class this)) (hash (name name)))) + (equals [this x] (and (instance? SqlParam x) (= (.name this) (.name x)))) + clojure.lang.IObj + (meta [this] _meta) + (withMeta [this m] (SqlParam. (.name this) m))) + +(defn param + "Represents a SQL parameter which can be filled in later" + [name] + (SqlParam. name nil)) + +(defn param-name [^SqlParam param] + (.name param)) + +(defn read-sql-param [form] + (param form)) + +(defmethod print-method SqlParam [^SqlParam o ^java.io.Writer w] + (.write w (str "#sql/param " (pr-str (.name o))))) + +(defmethod print-dup SqlParam [o w] + (print-method o w))