Created
December 16, 2011 12:46
-
-
Save cemerick/1485920 to your computer and use it in GitHub Desktop.
Working on a CouchDB type for Clojure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
;; Clutch provides a pretty comprehensive API, but I'm frustated that 95% of database | |
;; interactions require using something other than the typical Clojure vocabulary of | |
;; assoc/conj/dissoc/get/seq/reduce/etc, even though those semantics are entirely appropriate | |
;; (modulo the whole stateful database thing). | |
;; | |
;; This is (the start of) an attempt to create a type to provide most of the | |
;; functionality of Clutch with a more pleasant, concise API (it uses the Clutch API | |
;; under the covers, and rare operations would generally remain accessible only | |
;; at that lower level). | |
;; | |
;; Would like to eventually add: | |
;; | |
;; * support for views (aside from _all_docs via seq) | |
;; * support for _changes (via a seque?), maybe a more natural place than the | |
;; (free-for-all) pool of watches in Clutch's current API | |
;; * support for bulk update, maybe via IReduce? | |
;; * Other CouchDB types: | |
;; ** to provide specialized query interfaces e.g. cloudant indexes | |
;; ** to return custom map and vector types to support e.g. | |
;; | |
;; (assoc-in! db ["ID" :key :key array-index] x) | |
;; (update-in! db ["ID" :key :key array-index] assoc :key y) | |
;; | |
;; Feedback wanted! | |
;; | |
;; Comment below | |
;; or at | |
;; http://groups.google.com/group/clojure-clutch/browse_frm/thread/4520716eb37a90cc | |
;; or | |
;; @cemerick | |
;; or | |
;; cemerick in #clojure | |
;; | |
;; *requires [com.ashafa/clutch "0.3.0-SNAPSHOT"] (latest at https://github.com/cemerick/clutch)* | |
(ns user | |
(:require [com.ashafa.clutch :as db]) | |
(:refer-clojure :exclude (conj! assoc! dissoc!))) | |
(defprotocol CouchOps | |
"Defines side-effecting operations on a CouchDB database. | |
All operations return the CouchDB database reference — | |
with the return value from the underlying clutch function | |
added to its :result metadata — for easy threading and | |
reduction usage." | |
(create! [this] "Ensures that the database exists, and returns the database's meta info.") | |
(conj! [this document] | |
"PUTs a document into CouchDB. Accepts either a document map (using an :_id value | |
if present as the document id), or a vector/map entry consisting of a | |
[id document-map] pair.") | |
(assoc! [this id document] | |
"PUTs a document into CouchDB. Equivalent to (conj! couch [id document]).") | |
(dissoc! [this id-or-doc] | |
"DELETEs a document from CouchDB. Uses a given document map's :_id and :_rev | |
if provided; alternatively, if passed a string, will blindly attempt to | |
delete the current revision of the corresponding document.")) | |
(defn- with-result-meta | |
[couch result] | |
(vary-meta couch assoc :result result)) | |
(deftype CouchDB [url meta] | |
CouchOps | |
(create! [this] (with-result-meta this (db/get-database url))) | |
(conj! [this doc] | |
(let [[id doc] (cond | |
(map? doc) [(:_id doc) doc] | |
(or (vector? doc) (instance? java.util.Map$Entry)) doc)] | |
(with-result-meta this (db/put-document url doc :id id)))) | |
(assoc! [this id document] (conj! this [id document])) | |
(dissoc! [this id] | |
(if-let [d (if (#'db/document? id) | |
id | |
(get this id))] | |
(with-result-meta this (db/delete-document url d)) | |
(with-result-meta this nil))) | |
clojure.lang.ILookup | |
(valAt [this k] (db/get-document url k)) | |
(valAt [this k default] (or (.valAt this k) default)) | |
clojure.lang.Counted | |
(count [this] (:doc_count (db/database-info url))) | |
clojure.lang.Seqable | |
(seq [this] | |
(->> (db/all-documents url {:include_docs true}) | |
(map :doc) | |
(map #(clojure.lang.MapEntry. (:_id %) %)))) | |
clojure.lang.IMeta | |
(meta [this] meta) | |
clojure.lang.IObj | |
(withMeta [this meta] (CouchDB. url meta))) | |
(defn couch | |
([url] (CouchDB. url nil)) | |
([url meta] (CouchDB. url meta))) | |
;;;; REPL demo | |
=> (def db (couch "test")) | |
#'user/db | |
=> (create! db) | |
#<CouchDB user.CouchDB@3f460a4a> | |
=> (:result (meta *1)) | |
#com.ashafa.clutch.utils.URL{:protocol "http", :username nil, :password nil, | |
:host "localhost", :port -1, :path "test", :query nil, :disk_format_version 5, | |
:db_name "test", :doc_del_count 0, :committed_update_seq 0, :disk_size 79, | |
:update_seq 0, :purge_seq 0, :compact_running false, :instance_start_time | |
"1324037686108297", :doc_count 0} | |
=> (reduce conj! db (for [x (range 5000)] | |
{:_id (str x) :a [1 2 x]})) | |
#<CouchDB user.CouchDB@71d1be4e> | |
=> (count db) | |
5000 | |
=> (get-in db ["68" :a 2]) | |
68 | |
=> (def copy (into {} db)) | |
#'user/copy | |
=> (get-in copy ["68" :a 2]) | |
68 | |
=> (first db) | |
["0" {:_id "0", :_rev "1-79fe783154bff972172bc30732783a68", :a [1 2 0]}] | |
=> (dissoc! db "68") | |
#<CouchDB user.CouchDB@48f50903> | |
=> (get db "68") | |
nil | |
=> (assoc! db :foo {:a 6 :b 7}) | |
#<CouchDB user.CouchDB@79d7999e> | |
=> (:result (meta *1)) | |
{:_rev "1-ac3fe57a7604cfd6dcca06b25204b590", :_id ":foo", :a 6, :b 7} |
Cool! And if two people independently have the same idea, it can't possibly be bad :)
This is now in clutch master, available as [com.ashafa/clutch "0.3.1-SNAPSHOT"]
. Feedback most welcome on the clutch ML:
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yup, that's exactly where I'm heading. :-)