Skip to content
A minimalist CouchDB 2.x database client.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore
LICENSE
README.org
cl-mango.asd
cl-mango.lisp
mango2.jpg

README.org

mango2.jpg

Mango is a client library for CouchDB 2.x (it also happens to be the name of CouchDB’s alternate query language)

Configuration

(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 …)

(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.  You can also turn on cl-mango:*explain* and it
  ;;   will complain to you when you've queried against a field that
  ;;   has no index.
  ;; - 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"))

Lower 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.

    Docs

(make-selector (list (cons "name" "mango"))
               :limit 10
               :fields (list "_id" "_rev")
               :sort '(cons "name" "desc")
               :skip 100)
  • doc-find database selector

    Docs

    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 _id.

(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

Docs

(query-view "test" "reports" "by-person" (list (cons "uid" 12")))

Issues, Project Page, etc.

See github

You can’t perform that action at this time.