2 (:import [java.io InputStream
13 (defn- cr? [c] (= c *cr*))
14 (defn- lf? [c] (= c *lf*))
16 (defn- uppercase [#^String s] (.toUpperCase s))
17 (defn- trim [#^String s] (.trim s))
18 (defn- parse-int [#^String s] (Integer/parseInt s))
19 (defn- char-array [len] (make-array Character/TYPE len))
21 (def *default-host* "127.0.0.1")
22 (def *default-port* 6379)
24 (def *default-timeout* 5)
27 (defstruct server :host :port :db :timeout :socket)
29 (def *server* (struct-map server
33 :timeout *default-timeout* ;; not yet used
36 (defn connect-to-server
37 "Create a Socket connected to server"
39 (let [{:keys [host port timeout]} server
40 socket (Socket. #^String host #^Integer port)]
43 (.setKeepAlive true))))
47 (let [server (merge *server* server-spec)]
48 (with-open [#^Socket socket (connect-to-server server)]
49 (binding [*server* (assoc server :socket socket)]
53 (or (:socket *server*)
54 (throw (Exception. "Not connected to a Redis server"))))
57 "Send a command string to server"
59 (let [out (.getOutputStream (#^Socket socket*))
60 bytes (.getBytes cmd)]
65 "Read a CR+LF combination from Reader"
67 (let [cr (.read reader)
72 (throw (Exception. "Error reading CR/LF")))
76 "Read from reader until exactly a CR+LF combination is
77 found. Returns the line read without trailing CR+LF.
79 This is used instead of Reader.readLine() method since it tries to
80 read either a CR, a LF or a CR+LF, which we don't want in this
86 (throw (Exception. "Error reading line: EOF reached before CR/LF sequence")))
88 (let [next (.read reader)]
91 (throw (Exception. "Error reading line: Missing LF"))))
92 (recur (conj line (char c))
102 ([#^BufferedReader reader]
103 (char (.read reader))))
105 (defmulti parse-reply reply-type :default :unknown)
109 (let [input-stream (.getInputStream (#^Socket socket*))
110 reader (BufferedReader. (InputStreamReader. input-stream))]
111 (read-reply reader)))
112 ([#^BufferedReader reader]
113 (parse-reply reader)))
115 (defmethod parse-reply :unknown
116 [#^BufferedReader reader]
117 (throw (Exception. (str "Unknown reply type:"))))
119 (defmethod parse-reply \-
120 [#^BufferedReader reader]
121 (let [error (read-line-crlf reader)]
122 (throw (Exception. (str "Server error: " error)))))
124 (defmethod parse-reply \+
125 [#^BufferedReader reader]
126 (read-line-crlf reader))
128 (defmethod parse-reply \$
129 [#^BufferedReader reader]
130 (let [line (read-line-crlf reader)
131 length (parse-int line)]
134 (let [#^chars cbuf (char-array length)
135 nread (.read reader cbuf 0 length)]
136 (if (not= nread length)
137 (throw (Exception. "Could not read correct number of bytes"))
139 (read-crlf reader) ;; CRLF
142 (defmethod parse-reply \*
143 [#^BufferedReader reader]
144 (let [line (read-line-crlf reader)
145 count (parse-int line)]
152 (recur (dec i) (conj replies (read-reply reader))))))))
154 (defmethod parse-reply \:
155 [#^BufferedReader reader]
156 (let [line (trim (read-line-crlf reader))
157 int (parse-int line)]
163 "Join elements in sequence with separator"
165 (apply str (interpose separator sequence)))
169 "Create a string for an inline command"
171 (let [cmd (str-join " " (conj args name))]
175 "Create a string for an bulk command"
177 (let [data (str (last args))
178 data-length (count (str data))
179 args* (concat (butlast args) [data-length])
180 cmd (apply inline-command name args*)]
181 (str cmd data "\r\n")))
184 (defn- sort-command-args-to-string
186 (loop [arg-strings []
189 (str-join " " arg-strings)
190 (let [type (first args)
193 :by (let [pattern (first args)]
194 (recur (conj arg-strings "BY" pattern)
196 :limit (let [start (first args)
198 (recur (conj arg-strings "LIMIT" start end)
200 :get (let [pattern (first args)]
201 (recur (conj arg-strings "GET" pattern)
203 :alpha (recur (conj arg-strings "ALPHA") args)
204 :asc (recur (conj arg-strings "ASC") args)
205 :desc (recur (conj arg-strings "DESC") args)
206 (throw (Exception. (str "Error parsing SORT arguments: Unknown argument: " type))))))))
210 (when-not (= name "SORT")
211 (throw (Exception. "Sort command name must be 'SORT'")))
212 (let [key (first args)
213 arg-string (sort-command-args-to-string (rest args))
214 cmd (str "SORT " key)]
215 (if (empty? arg-string)
217 (str cmd " " arg-string "\r\n"))))
220 (def command-fns {:inline 'inline-command
222 :sort 'sort-command})
226 "Return a restructuring of params, which is of form:
229 [(arg1 arg2 ..) more]"
231 (let [[args rest] (split-with #(not= % '&) params)]
235 "Define a function for Redis command name with parameters
236 params. Type is one of :inline or :bulk, which determines how the
237 command string is constructued."
238 ([name params type] `(defcommand ~name ~params ~type (fn [reply#] reply#)))
239 ([name params type reply-fn] `(~name ~params ~type ~reply-fn)
241 (let [command (uppercase (str name))
242 command-fn (type command-fns)
244 command-params-rest] (parse-params params)]
247 (let [request# (apply ~command-fn
250 ~command-params-rest)]
251 (send-command request#)
252 (~reply-fn (read-reply)))))
257 (defmacro defcommands
259 `(do ~@(map (fn [command-def]
260 `(defcommand ~@command-def)) command-defs)))