/src/core/database.lisp

http://github.com/sykopomp/chillax · Lisp · 125 lines · 93 code · 21 blank · 11 comment · 7 complexity · 91f98ccff1dd8939aa0c7bd8beb035b6 MD5 · raw file

  1. (in-package :chillax.core)
  2. ;;;
  3. ;;; Database errors
  4. ;;;
  5. (define-condition database-error (couchdb-error)
  6. ((uri :initarg :uri :reader database-error-uri)))
  7. (define-condition db-not-found (database-error)
  8. ()
  9. (:report (lambda (condition stream)
  10. (format stream "Database ~A not found." (database-error-uri condition)))))
  11. (define-condition db-already-exists (database-error)
  12. ()
  13. (:report (lambda (condition stream)
  14. (format stream "Database ~A already exists." (database-error-uri condition)))))
  15. ;;;
  16. ;;; Basic database API
  17. ;;;
  18. ;; Database protocol
  19. (defgeneric database-server (database)
  20. (:documentation "Returns the server object with which DATABASE is associated."))
  21. (defgeneric database-name (database)
  22. (:documentation
  23. "Returns the URL-encoded name of the database, a string. Note that CouchDB accepts certain
  24. characters in database names -only- if they are URL-encoded (such as #\/). It is up to individual
  25. implementations of DATABASE-NAME to implement this encoding."))
  26. ;; Database functions
  27. (defun print-database (db stream)
  28. "Objects implementing the database protocol may use this function in their PRINT-OBJECT method."
  29. (print-unreadable-object (db stream :type t :identity t)
  30. (format stream "~A" (db-uri db))))
  31. (defun db-request (db uri &rest all-keys)
  32. "Sends a CouchDB request to DB."
  33. (apply #'couch-request (database-server db) (strcat (database-name db) "/" uri) all-keys))
  34. (defmacro handle-request ((result-var db uri &rest db-request-keys &key &allow-other-keys)
  35. &body expected-responses)
  36. "Provides a nice interface to the relatively manual, low-level status-code checking that Chillax
  37. uses to understand CouchDB's responses. The format for EXPECTED-RESPONSES is the same as the CASE
  38. macro: The keys should be either keywords, or lists o keywords (not evaluated), which correspond to
  39. translated HTTP status code names. See +status-codes+ for all the currently-recognized keywords."
  40. (let ((status-code (gensym "STATUS-CODE-")))
  41. `(multiple-value-bind (,result-var ,status-code)
  42. (db-request ,db ,uri ,@db-request-keys)
  43. (case ,status-code
  44. ,@expected-responses
  45. (:bad-request (error "Bad request: ~A" ,result-var))
  46. (otherwise (error 'unexpected-response :status-code ,status-code :response ,result-var))))))
  47. (defun db-info (db)
  48. "Fetches info about a given database from the CouchDB server."
  49. (handle-request (response db "")
  50. (:ok response)
  51. (:internal-server-error (error "Illegal database name: ~A" (database-name db)))
  52. (:not-found (error 'db-not-found :uri (db-uri db)))))
  53. (defun db-connect (server name)
  54. "Confirms that a particular CouchDB database exists. If so, returns a new database object that can
  55. be used to perform operations on it. Will signal a DB-NOT-FOUND error if the database does not
  56. already exist."
  57. (let ((db (make-db-object server name)))
  58. (when (db-info db)
  59. db)))
  60. (defun db-create (server name)
  61. "Creates a new CouchDB database. Returns a database object that can be used to operate on it. Will
  62. signal a DB-ALREADY-EXISTS error if there is already a database with the same NAME in SERVER."
  63. (let ((db (make-db-object server name)))
  64. (handle-request (response db "" :method :put)
  65. (:created db)
  66. (:internal-server-error (error "Illegal database name: ~A" name))
  67. (:precondition-failed (error 'db-already-exists :uri (db-uri db))))))
  68. (defun ensure-db (server name)
  69. "Either connects to an existing database, or creates a new one. Returns two values: If a new
  70. database was created, (DB-OBJECT T) is returned. Otherwise, (DB-OBJECT NIL)"
  71. (handler-case (values (db-create server name) t)
  72. (db-already-exists () (values (db-connect server name) nil))))
  73. (defun db-delete (db)
  74. "Deletes a CouchDB database."
  75. (handle-request (response db "" :method :delete)
  76. (:ok response)
  77. (:not-found (error 'db-not-found :uri (db-uri db)))))
  78. (defun db-compact (db)
  79. "Triggers a database compaction."
  80. (handle-request (response db "_compact" :method :post :content "")
  81. (:accepted response)))
  82. (defun db-changes (db)
  83. "Returns the changes feed for DB"
  84. (handle-request (response db "_changes")
  85. (:ok response)))
  86. (defun db-uri (db)
  87. "Returns a string representing the full URI for DB."
  88. (strcat (server-uri (database-server db)) (database-name db)))
  89. ;;;
  90. ;;; Sample protocol implementation
  91. ;;;
  92. (defclass standard-database ()
  93. ((server :reader database-server :initarg :server)
  94. (name :reader database-name))
  95. (:documentation
  96. "Minimal, class-based implementation of the database protocol."))
  97. (defun url-encode (string)
  98. (drakma:url-encode string :utf-8))
  99. (defmethod initialize-instance :after ((db standard-database) &key name)
  100. (setf (slot-value db 'name) (url-encode name)))
  101. (defmethod print-object ((db standard-database) stream)
  102. (print-database db stream))
  103. (defmethod make-db-object ((server standard-server) name)
  104. (make-instance 'standard-database :server server :name name))