PageRenderTime 25ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/couchdb/client.clj

https://github.com/ato/clojure-couchdb
Clojure | 298 lines | 234 code | 47 blank | 17 comment | 8 complexity | 9bc30580e74e508ebb703049d0b72808 MD5 | raw file
  1. (ns couchdb.client
  2. (:require [clojure.contrib [error-kit :as kit]])
  3. (:use [clojure.contrib.java-utils :only [as-str]]
  4. [clojure.contrib.json.read :only [read-json *json-keyword-keys*]]
  5. [clojure.contrib.json.write :only [json-str]]
  6. [clojure.http.client :only [request url-encode]]))
  7. (def *server* "http://localhost:5984/")
  8. (kit/deferror InvalidDatabaseName [] [database]
  9. {:msg (str "Invalid Database Name: " database)
  10. :unhandled (kit/throw-msg Exception)})
  11. (kit/deferror DatabaseNotFound [] [e]
  12. {:msg (str "Database Not Found: " e)
  13. :unhandled (kit/throw-msg java.io.FileNotFoundException)})
  14. (kit/deferror DocumentNotFound [] [e]
  15. {:msg (str "Document Not Found: " e)
  16. :unhandled (kit/throw-msg java.io.FileNotFoundException)})
  17. (kit/deferror AttachmentNotFound [] [e]
  18. {:msg (str "Attachment Not Found: " e)
  19. :unhandled (kit/throw-msg java.io.FileNotFoundException)})
  20. (kit/deferror ResourceConflict [] [e]
  21. "Raised when a 409 code is returned from the server."
  22. {:msg (str "Resource Conflict: " e)
  23. :unhandled (kit/throw-msg Exception)})
  24. (kit/deferror PreconditionFailed [] [e]
  25. "Raised when a 412 code is returned from the server."
  26. {:msg (str "Precondition Failed: " e)
  27. :unhandled (kit/throw-msg Exception)})
  28. (kit/deferror ServerError [] [e]
  29. "Raised when any unexpected code >= 400 is returned from the server."
  30. {:msg (str "Unhandled Server Error: " e)
  31. :unhandled (kit/throw-msg Exception)})
  32. (defn couch-request
  33. [& args]
  34. (let [response (apply request args)
  35. result (try (assoc response :json (binding [*json-keyword-keys* true]
  36. (read-json (apply str
  37. (:body-seq response)))))
  38. (catch Exception e ;; if there's an error reading the JSON, just don't make a :json key
  39. response))]
  40. (if (>= (:code result) 400)
  41. (kit/raise* ((condp = (:code result)
  42. 404 (condp = (:reason (:json result))
  43. "Missing" DatabaseNotFound ;; as of svn rev 775577 this should be "no_db_file"
  44. "Document is missing attachment" AttachmentNotFound
  45. DocumentNotFound)
  46. 409 ResourceConflict
  47. 412 PreconditionFailed
  48. ServerError)
  49. {:e (:json result)}))
  50. result)))
  51. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  52. ;; Utilities ;;
  53. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  54. (defn valid-dbname?
  55. [database]
  56. (boolean (re-find #"^[a-z][a-z0-9_$()+-/]*$" database)))
  57. (defn validate-dbname
  58. [database]
  59. (if (valid-dbname? database)
  60. (url-encode database)
  61. (kit/raise InvalidDatabaseName database)))
  62. (defn stringify-top-level-keys
  63. [[k v]]
  64. (if (keyword? k)
  65. [(if-let [n (namespace k)]
  66. (str n (name k))
  67. (name k))
  68. v]
  69. [k v]))
  70. (defn- vals-lift [f m]
  71. (reduce (fn [acc [k v]] (assoc acc k (f v))) {} (seq m)))
  72. (def #^{:private true} vals2json (partial vals-lift json-str))
  73. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  74. ;; Databases ;;
  75. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  76. (defn database-list
  77. []
  78. (:json (couch-request (str *server* "_all_dbs"))))
  79. (defn database-create
  80. [database]
  81. (when-let [database (validate-dbname database)]
  82. (couch-request (str *server* database) :put)
  83. database))
  84. (defn database-delete
  85. [database]
  86. (when-let [database (validate-dbname database)]
  87. (couch-request (str *server* database) :delete)
  88. true))
  89. (defn database-info
  90. [database]
  91. (when-let [database (validate-dbname database)]
  92. (:json (couch-request (str *server* database)))))
  93. (defn database-compact
  94. [database]
  95. (when-let [database (validate-dbname database)]
  96. (couch-request (str *server* database "/_compact") :post)
  97. true))
  98. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  99. ;; Documents ;;
  100. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  101. (declare document-get)
  102. (defn- do-get-doc
  103. [database document]
  104. (if (map? document)
  105. (if-let [id (:_id document)]
  106. id
  107. (kit/raise ResourceConflict "missing :_id key"))
  108. document))
  109. (defn- do-get-rev
  110. [database document]
  111. (if (map? document)
  112. (if-let [rev (:_rev document)]
  113. rev
  114. (kit/raise ResourceConflict "missing :_rev key"))
  115. (:_rev (document-get database document))))
  116. (defn- do-document-touch
  117. [database payload id method]
  118. (when-let [database (validate-dbname database)]
  119. (let [response (:json (couch-request (str *server* database (when id
  120. (str "/" (url-encode (as-str id)))))
  121. method
  122. {"Content-Type" "application/json"}
  123. {}
  124. (json-str payload)))]
  125. (merge payload
  126. {:_id (:id response)
  127. :_rev (:rev response)}))))
  128. (defn document-list
  129. ([database]
  130. (when-let [database (validate-dbname database)]
  131. (map :id (:rows (:json (couch-request (str *server* database "/_all_docs")))))))
  132. ([database options]
  133. (when-let [database (validate-dbname database)]
  134. (map (if (:include_docs options) :doc :id)
  135. (:rows (:json (couch-request
  136. (str *server* database "/_all_docs?"
  137. (url-encode options)))))))))
  138. (defn document-create
  139. ([database payload]
  140. (do-document-touch database payload nil :post))
  141. ([database id payload]
  142. (do-document-touch database payload id :put)))
  143. (defn document-update
  144. [database id payload]
  145. ;(assert (:_rev payload)) ;; payload needs to have a revision or you'll get a PreconditionFailed error
  146. (let [id (do-get-doc database id)]
  147. (do-document-touch database payload id :put)))
  148. (defn document-get
  149. ([database id]
  150. (when-let [database (validate-dbname database)]
  151. (let [id (do-get-doc database id)]
  152. (:json (couch-request (str *server* database "/" (url-encode (as-str id))))))))
  153. ([database id rev]
  154. (when-let [database (validate-dbname database)]
  155. (let [id (do-get-doc database id)]
  156. (:json (couch-request (str *server* database "/" (url-encode (as-str id)) "?rev=" rev)))))))
  157. (defn document-delete
  158. [database id]
  159. (if-not (empty? id)
  160. (when-let [database (validate-dbname database)]
  161. (let [id (do-get-doc database id)
  162. rev (do-get-rev database id)]
  163. (couch-request (str *server* database "/" (url-encode (as-str id)) "?rev=" rev)
  164. :delete)
  165. true))
  166. false))
  167. (defn document-bulk-update
  168. "Does a bulk-update to couchdb, accoding to: http://wiki.apache.org/couchdb/HTTP_Bulk_Document_API"
  169. [database document-coll & [request-options]]
  170. (when-let [database (validate-dbname database)]
  171. (let [response (:json
  172. (couch-request
  173. (str *server* database "/_bulk_docs"
  174. (url-encode (vals2json request-options)))
  175. :post
  176. {"Content-Type" "application/json"}
  177. {}
  178. (json-str {:docs document-coll})))]
  179. ;; I don't know if this is correct... I assume that the server sends the
  180. ;; ids and revs in the same order as ib my request back.
  181. (map (fn [respdoc, orgdoc]
  182. (merge orgdoc
  183. {:_id (:id respdoc)
  184. :_rev (:rev respdoc)}))
  185. response document-coll))))
  186. (defn- revision-comparator
  187. [x y]
  188. (> (Integer/decode (apply str (take-while #(not= % \-) x)))
  189. (Integer/decode (apply str (take-while #(not= % \-) y)))))
  190. (defn document-revisions
  191. [database id]
  192. (when-let [database (validate-dbname database)]
  193. (let [id (do-get-doc database id)]
  194. (apply merge (map (fn [m]
  195. (sorted-map-by revision-comparator (:rev m) (:status m)))
  196. (:_revs_info (:json (couch-request (str *server* database "/" (url-encode (as-str id)) "?revs_info=true")))))))))
  197. ;; Views
  198. (defn view-get [db design-doc view-name & [view-options]]
  199. (:json (couch-request
  200. (str *server* db "/_design/" design-doc "/_view/" view-name "?"
  201. (url-encode (vals2json view-options))))))
  202. (defn view-temp-get [db view-map & [view-options]]
  203. (:json (couch-request
  204. (str *server* db "/_temp_view?"
  205. (url-encode (vals2json view-options)))
  206. :post
  207. {"Content-Type" "application/json"}
  208. {}
  209. (json-str view-map))))
  210. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  211. ;; Attachments ;;
  212. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  213. (defn attachment-list
  214. [database document]
  215. (let [document (do-get-doc database document)]
  216. (into {} (map stringify-top-level-keys
  217. (:_attachments (document-get database document))))))
  218. (defn attachment-create
  219. [database document id payload content-type]
  220. (when-let [database (validate-dbname database)]
  221. (let [document (do-get-doc database document)
  222. rev (do-get-rev database document)]
  223. (couch-request (str *server* database "/" (url-encode (as-str document)) "/" (url-encode (as-str id)) "?rev=" rev)
  224. :put
  225. {"Content-Type" content-type}
  226. {}
  227. payload))
  228. id))
  229. (defn attachment-get
  230. [database document id]
  231. (when-let [database (validate-dbname database)]
  232. (let [document (do-get-doc database document)
  233. response (couch-request (str *server* database "/" (url-encode (as-str document)) "/" (url-encode (as-str id))))]
  234. {:body-seq (:body-seq response)
  235. :content-type ((:get-header response) "content-type")})))
  236. (defn attachment-delete
  237. [database document id]
  238. (when-let [database (validate-dbname database)]
  239. (let [document (do-get-doc database document)
  240. rev (do-get-rev database document)]
  241. (couch-request (str *server* database "/" (url-encode (as-str document)) "/" (url-encode (as-str id)) "?rev=" rev)
  242. :delete)
  243. true)))
  244. ;; Shows
  245. (defn show-get
  246. "Returns the contents of a show as a list of strings according to http://wiki.apache.org/couchdb/Formatting_with_Show_and_List"
  247. [database design-doc show-name id & [show-options]]
  248. (:body-seq
  249. (couch-request
  250. (str *server* database "/_design/" design-doc "/_show/" show-name "/" id
  251. "?" (url-encode (vals2json show-options))))))