A minimalist CouchDB 2.x database client.
Mango is a client library for CouchDB 2.x (it also happens to be the name of CouchDB's alternate query language)
(eval-when (:compile-toplevel :load-toplevel :execute) (setf cl-mango:*host* "127.0.0.1") (setf cl-mango:*port* 5984) (setf cl-mango:*scheme* :http) ;; or :https (setf cl-mango:*username* "user") (setf cl-mango:*password* "password"))
(defmango) macro that generates an orm-ish interface to a class.
(defmango person people ((name :json-type :string :json-key "name" :initarg :name :accessor person-name) (role :json-type :string :json-key "role" :initform "pleb" :initarg :role :accessor person-role)) => (progn (defclass person () ((cl-mango::-id :initarg :-id :json-type :string :json-key "_id" :accessor person--id) (cl-mango::-rev :initarg :-rev :json-type :string :json-key "_rev" :accessor person-rev) (type :initarg :type :json-type :string :json-key "type" :initform (string-downcase "person")) (name :json-type :string :json-key "name" :initarg :name :accessor person-name) (role :json-type :string :json-key "role" :initform "pleb" :accessor person-role)) (:metaclass json-mop:json-serializable-class)) ;; ;; Important notes about this defclass expansion: ;; ;; - type is special as it tells defmodel what class query results ;; are supposed to be. It's filled in automatically. Unless you ;; want to see things explode spectacularly, don't change it. ;; ;; - person--id and person-rev are special to CouchDB. When ;; creating a new record, *don't* fill in "_rev" as CouchDB ;; assigns it a new revision tag on insert and update. The only ;; time I ever find myself reading the rev slot in practice is ;; when I need to delete a record, in which you provide an _id and ;; _rev. ;; (defun person-get-all ()) ;; ;; Doesn't actually get all, only the first 100 as it uses ;; couchdb/database/_all_docs?include_docs=true if you truly want ;; all, I would suggest fetching only the "_id" of all with ;; (person-find) and then iterating over that list. ie. ;; ;; (person-find (list (cons "name" (alist-hash-table ;; this selector can b ;; (list (cons "$exists" 't))))) ;; :limit 1000000 ;; or some similarly large number. ;; :fields (list "_id")) (defun person-get (id)) ;; Fetch a single person record from the database with the given id. ;; CouchDB enforces that all "_id" values are unique, so there will ;; only ever be a single result from this call. (defun person-put (cl-mango::object)) (defun person-update (cl-mango::object)) ;; These two calls are identical in their implementation, however ;; there are two to help with the differences in adding a record and ;; updating a record in CouchDB. ;; - Adding a record without a value for "_rev" adds a new record to ;; the database. ;; - Adding a record with a value in "_rev" means, in CouchDB terms, ;; "This is a new revision of that document." Note that if you ;; update a record (ie. you have a value for "_rev") and it's not ;; the latest and greatest version in the database at that time, ;; you'll get a document update conflict and cl-mango will throw ;; the dreaded "unexpected-http-response" condition. So, yes, ;; it's a bit redundant, but it helps me, so it's staying. ;; (defmacro person-find (cl-mango::query &rest cl-mango::query-args)) ;; ;; This is the main interface for querying objects of your class. ;; It takes the same arguments as (make-selector) above and ;; transparently adds (cons "type" "<class name>") to all queries. ;; ;; Notes: ;; - (defmango) does *not* add an index for "type" in the database. ;; You'll have to go in to the Mango query interface in Fauxton ;; and add one. ;; - If you plan to sort by a given field, you'll need to add a ;; Mango index for that as well or cl-mango will throw an ;; "unexpected-http-response" condition. It's CouchDB, man, not ;; me. ;; (person-find (list (cons "name" "bob"))) (person-find (list (cons "role" "admin")) :limit 1 :skip 100 :stable t :update t :use-index "index-name" :r 2 :fields (list "_id" "_rev") :sort (list (alexandria:alist-hash-table (list (cons "name" "desc"))))) ;; (defun person-delete (cl-mango::object)) ;; ;; Removes the object from the database. ;; (defmacro person-create (&rest cl-mango::args)) ;; Make a new object and add it to the database. ;; There's nothing special about this, and there's nothing preventing you from ;; using (make-instance). (person-put (make-instance 'person :name "bob"))
3Lower level api
(defmango) is defined in terms of the following functions.
- make-selector selector &key limit fields sort skip
Builds a selector for doc-find.
(make-selector (list (cons "name" "mango")) :limit 10 :fields (list "_id" "_rev") :sort '(cons "name" "desc") :skip 100)
- doc-find database selector
Execute a query against <database>.
(doc-find "test" (make-selector (list (cons "name" "me")))
- doc-get database document-id
Get a single document by the
(doc-get "test" "<docid>")
- doc-delete database document-id document-rev
Delete a single document.
- doc-put database json-string
Insert a single document.
;; Assuming you use yason, but as long as the ;; string is well formed JSON, you can use ;; whatever library you want. (doc-put "test" (with-output-to-string (sink) (yason:encode (list (cons "name" "me") (cons "something" "something else")) sink)))
- query-view database view index &key parameters
(query-view "test" "reports" "by-person" (list (cons "uid" 12")))