cl-mango

2019-07-10

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

Author
Clint Moore <clint@ivy.io>
License
BSD3