Lisp で CouchDB の Getting Started を始めてみる

CouchDBオフィシャル・ドキュメントの Getting Started をLispを使ってやってみた.CouchDBはRESTfulなAPIを提供しているので,特別なドライバを使わなくてもHTTPクライアントがあればDBを操作できる.ここでは,LispのHTTPクライアント・ライブラリ Dexador と,高速なJSONエンコード/デコード・ライブラリ Jonathan を使って,CouchDBと通信する.どちらも日本を代表する若手Lisperによって書かれたライブラリだ.

(ql:quickload 'dexador)
(ql:quickload 'jonathan)

ダウンロードしたCouchDBをダブルクリックで起動し,ポート5984番にGETリクエストを発する.

> (dex:get "http://localhost:5984")
;=> "{\"couchdb\":\"Welcome\",\"version\":\"2.1.0\",\"features\":[\"scheduler\"],\"vendor\":{\"name\":\"The Apache Software Foundation\"}}
;   "
;   200
;   #<HASH-TABLE :TEST EQUAL :COUNT 7 {10041F28C3}>
;   #<QURI.URI.HTTP:URI-HTTP http://localhost:5984>
;   #<SB-SYS:FD-STREAM for "socket 127.0.0.1:52372, peer: 127.0.0.1:5984"
;   {1004112FE3}>

CouchDBのルートURLにアクセスするとバージョンを取得できる.エスケープされた文字列は分かり辛いので,Jonathanを使ってJSONをS式に変換する.以下は読みやすいようにこちらでインデントした.

(jonathan:parse (dex:get "http://localhost:5984"))
;=> (:|vendor|   (:|name| "The Apache Software Foundation")
;    :|features| ("scheduler")
;    :|version|  "2.1.0"
;    :|couchdb|  "Welcome")

S式のリストがJSONの1つのオブジェクトか配列を表す.リストがキーワードを含む連想配列の時はJSONのオブジェクト(key/value dictionaries)を表し,S式表現にキーワードがなければJSON内では配列データである.上のS式は以下のJSONをパースした結果である.

{
	"couchdb": "Welcome",
	"version": "2.1.0",
	"features": ["scheduler"],
	"vendor": {
		  "name": "The Apache Software Foundation"
	}
}

ここでタイプの手間を減らすためにマクロを定義する.

(defmacro couchdb (method path)
       `(jonathan:parse (
       			,(intern (symbol-name method) :dex)
			,(concatenate 'string "http://localhost:5984" path))))

引数の method はシンボルでもキーワードでもOK.

(couchdb get "/_all_dbs")   (1)
;=> nil
(couchdb put "/baseball")   (2)
;=> (:|ok| T)
(couchdb get "/_all_dbs")   (3)
;=> ("baseball")
1 /_all_dbs でデータベース一覧を取得.まだデータベースを作成していないので空のJSONオブジェクトが返る.Lispでは nil となる.
2 put メソッドで baseball データベースを作成.成功すれば,ok キーに true が返される.
3 データベース一覧を取得すると baseball データベースが作成されているのが分かる.

もう一度同じ名前のデータベースの作成を試みる.

(couchdb put "/baseball")
;=> An HTTP request to "http://localhost:5984/baseball" returned 412 precondition failed.
;
;   {"error":"file_exists","reason":"The database could not be created, the file already exists."}
;      [Condition of type DEXADOR.ERROR:HTTP-REQUEST-PRECONDITION-FAILED]
;
;   Restarts:
;    0: [RETRY-REQUEST] Retry the same request.
;    1: [IGNORE-AND-CONTINUE] Ignore the error and continue.
;    2: [RETRY] Retry SLIME REPL evaluation request.
;    3: [*ABORT] Return to SLIME's top level.
;    4: [ABORT] abort thread (#<THREAD "new-repl-thread" RUNNING {1002AB5BA3}>)

Dexador はErrorシグナルを発しデバッガが立ち上がる.エラーメッセージを見るとHTTPがStatus Code 412(Precondition Failed)を返したことがわかる.これはリクエスト先のリソースへのアクセスが拒否されたことを示すステータス・コードだ.

(define-condition http-request-failed (error)
  ((body :initarg :body
         :reader response-body)
   (status :initarg :status
           :reader response-status)
   (headers :initarg :headers
            :reader response-headers)
   (uri :initarg :uri
        :reader request-uri)
   (method :initarg :method
           :reader request-method))
  (:report (lambda (condition stream)
             (with-slots (uri status) condition
               (format stream "An HTTP request to ~S has failed (status=~D)."
                       (quri:render-uri uri)
                       status)))))

http-request-failed クラスは全てのHTTPエラー・コンディション・オブジェクトの親クラス.body スロットにサーバーから返されるbodyが,headers スロットにヘッダが格納されている.アクセサーはそれぞれ response-bodyreponse-headers

コンディション・オブジェクトのおさらい.

(handler-case 実行式
    (condition-type (condition-object) エラー処理)

まずはマクロを使わず手動接続.エラーを無視し結果のJSONだけ返すようにする.

(handler-case (jonathan:parse (dex:put "http://localhost:5984/baseball"))
  (dex:http-request-failed (err)
    (jonathan:parse (dex:response-body err))))

couchdb マクロを再定義.

(defmacro couchdb (method path)
  `(handler-case (jonathan:parse (,(intern (symbol-name method) :dex)
				                  ,(concatenate 'string "http://localhost:5984" path)))
     (dex:http-request-failed (err)
       (jonathan:parse (dex:response-body err)))))

もう一度,baseball データベースの作成を試みる.

(couchdb put "/baseball")
;=> (:|reason| "The database could not be created, the file already exists."
;    :|error| "file_exists")

次は,plankton データベースを新規作成.

(couchdb put "/plankton")
;=> (:|ok| T)

データベース一覧を取得.

(couchdb get "/_all_dbs")
;=> ("baseball" "plankton")

plankton データベースを削除し,データベース一覧を表示.

(couchdb delete "/plankton")
;=> (:|ok| T)
(couchdb get "/_all_dbs")
;=> ("baseball")

まとめ

Getting Startedでは以下の4つのAPIを学んだ.

  • "GET /" CouchDBのバージョンが得られる.

  • "PUT /database" databaseの作成.

  • "DELETE /database" databaseの削除.

  • "GET /__all_dbs" データベース一覧.

公式ドキュメントはこの後,ブラウザ・インターフェースを提供するFauxtonのハンズオンに進むが,そこは飛ばし,次回は The Core API へ進みドキュメントの操作方法を始め,様々なデータベース設定APIを学ぶ.