diff --git a/src/dsql/core.clj b/src/dsql/core.clj index 5b3553b..87f2f80 100644 --- a/src/dsql/core.clj +++ b/src/dsql/core.clj @@ -1,8 +1,27 @@ (ns dsql.core (:require [clojure.string :as str]) + (:import [java.util Iterator]) (:refer-clojure :exclude [format])) -(defn reduce-separated [sep acc f coll] +(defn fast-join [^String sep ^Iterable coll] + (let [^Iterator iter (.iterator coll) + builder (StringBuilder.)] + (loop [] + (when (.hasNext iter) + (let [s (.next iter)] + (.append builder (.toString s)) + (when (.hasNext iter) + (.append builder sep))) + (recur))) + (.toString builder))) + +(defmacro build-string [& strs] + (let [w (gensym)] + `(let [~w (StringBuilder.)] + ~@(map (fn [arg] `(.append ~w ~arg)) strs) + (.toString ~w)))) + +(defn reduce-separated-old [sep acc f coll] (if (empty? coll) acc (loop [[x & xs] coll @@ -12,6 +31,19 @@ acc' (recur xs (conj acc' sep))))))) +(defn reduce-separated [sep acc f ^Iterable coll] + (if coll + (let [^Iterator iter (.iterator coll)] + (loop [acc acc] + (if (.hasNext iter) + (let [x (.next iter) + acc' (f acc x)] + (if (.hasNext iter) + (recur (conj acc' sep)) + acc')) + acc))) + acc)) + (defn reduce-separated2 [acc sep f coll] (reduce-separated sep acc f coll)) @@ -40,11 +72,13 @@ (and (sequential? x) (first x)) (type x))) -(defn escape-string [s] - (str/replace s #"'" "''")) +(defn escape-string [^String s] + (when s + (.replace s "'" "''"))) + (defn string-litteral [s] - (str "'" (escape-string (if (keyword? s) (name s) s)) "'")) + (build-string "'" (escape-string (if (keyword? s) (name s) s)) "'")) (defn default-type [node tp] (if (and (not (get-type node)) (map? node)) @@ -88,7 +122,7 @@ (name node) (if (or (not (safe-identifier? norm-name)) (contains? keywords (keyword norm-name))) - (str "\"" (name node) "\"") + (build-string "\"" (name node) "\"") (name node))))) (defn parens [acc body-cb] @@ -100,7 +134,7 @@ (let [norm-name (str/upper-case (name node))] (if (or (not (safe-identifier? norm-name)) (contains? keywords (keyword norm-name))) - (str "\"" (name node) "\"") + (build-string "\"" (name node) "\"") (name node)))) (defmethod to-sql @@ -123,15 +157,20 @@ [acc _ _] (conj acc "NULL")) + (defn format [opts node] - (let [sql-vec (to-sql [] opts node)] + (let [^Iterable sql-vec (to-sql [] opts node) + ^Iterator iter (.iterator sql-vec) + builder (StringBuilder.)] (assert (sequential? sql-vec) (pr-str sql-vec)) - (loop [[x & xs] sql-vec - sql-str [] - params []] - (let [[sql-str params] (if (vector? x) - [(conj sql-str (first x)) (conj params (second x))] - [(conj sql-str x) params])] - (if (empty? xs) - (into [(str/join " " sql-str)] params) - (recur xs sql-str params)))))) + (loop [params []] + (when (.hasNext iter) + (let [x (.next iter) + params (if (vector? x) (conj params (nth x 1)) params)] + (.append builder (if (vector? x) (nth x 0) x)) + (if (.hasNext iter) + (do + (.append builder " ") + (recur params)) + (into [(.toString builder)] params) + )))))) diff --git a/src/dsql/pg.clj b/src/dsql/pg.clj index a7cd1fb..28088b6 100644 --- a/src/dsql/pg.clj +++ b/src/dsql/pg.clj @@ -3,7 +3,8 @@ [jsonista.core :as json] [clojure.string :as str]) (:import [com.fasterxml.jackson.databind.node ObjectNode ArrayNode TextNode IntNode BooleanNode] - [com.fasterxml.jackson.databind JsonNode ObjectMapper]) + [com.fasterxml.jackson.databind JsonNode ObjectMapper] + [java.util Iterator]) (:refer-clojure :exclude [format])) (def keywords @@ -389,7 +390,7 @@ (conj ")") (conj (name k)) (conj "(") - (conj (str/join " , " (map name ks))) + (conj (ql/fast-join " , " (map name ks))) (conj ")")) (-> acc (ql/to-sql opts sub-node) @@ -493,7 +494,7 @@ (if (number? x) x (assert false (pr-str x))))))) - (str/join ","))) + (ql/fast-join ","))) (defn- to-array-value [arr] @@ -744,6 +745,7 @@ (defn format [node] (ql/format {:resolve-type #'resolve-type :keywords keywords} (ql/default-type node :pg/select))) + (def keys-for-update [[:set :pg/set] [:from :pg/from] @@ -756,7 +758,7 @@ (assert (:update data) "Key :update is required!") (let [acc (conj acc "UPDATE") acc (conj acc (if (map? (:update data)) - (str/join " " (reverse (map name (first (:update data))))) + (ql/fast-join " " (reverse (map name (first (:update data))))) (name (:update data))))] (->> keys-for-update (ql/reduce-acc @@ -1117,7 +1119,7 @@ (fn [acc column val] (cond (vector? val) - (conj acc [(str "\"" (name column) "\"") (str/join " " (map name val))]) + (conj acc [(str "\"" (name column) "\"") (ql/fast-join " " (map name val))]) (map? val) (->> [(str "\"" (name column) "\"") @@ -1141,7 +1143,7 @@ (fn [acc opt val] (conj acc (str (name opt) " '" (name val) "'"))) []) - (str/join ", "))) + (ql/fast-join ", "))) (defmethod ql/to-sql :pg/create-table @@ -1221,7 +1223,7 @@ (identifier opts tbl) (cond-> (map? proj) (-> (conj "(") - (conj (->> (keys proj) (sort) (mapv #(str "\"" (name %) "\"")) (str/join ", "))) + (conj (->> (keys proj) (sort) (mapv #(str "\"" (name %) "\"")) (ql/fast-join ", "))) (conj ")"))) (ql/to-sql opts (assoc sel :ql/type :pg/sub-select)) (cond-> @@ -1377,8 +1379,18 @@ (ql/to-sql opts b))) -(defn concat-columns [cols] - (->> cols (mapv #(str "\"" (name %) "\"")) (str/join ", "))) +(defn concat-columns [^Iterable coll] + (let [^String sep ", " + ^Iterator iter (.iterator coll) + builder (StringBuilder.)] + (loop [] + (when (.hasNext iter) + (let [s (.next iter)] + (.append builder "\"") (.append builder (name s)) (.append builder "\"") + (when (.hasNext iter) + (.append builder sep))) + (recur))) + (.toString builder))) (defmethod ql/to-sql