From ac8a9b5620b425a7f3eee77e9aa0d31a1b31ddbd Mon Sep 17 00:00:00 2001 From: Calvin Froedge Date: Fri, 10 Apr 2015 15:49:36 -0400 Subject: [PATCH] Added support for search --- src/com/ashafa/clutch.clj | 44 ++++++++++++++---------- src/com/ashafa/clutch/http_client.clj | 48 ++++++++++++++++++++------- 2 files changed, 63 insertions(+), 29 deletions(-) diff --git a/src/com/ashafa/clutch.clj b/src/com/ashafa/clutch.clj index 40db01a..b169c0a 100644 --- a/src/com/ashafa/clutch.clj +++ b/src/com/ashafa/clutch.clj @@ -72,7 +72,7 @@ "Returns a database meta information if it already exists else creates a new database and returns the meta information for the new database." [db] - (merge db + (merge db (or (database-info db) (and (create-database db) (database-info db))))) @@ -111,14 +111,14 @@ (.length ^File data) (or filename (.getName ^File data)) (or mime-type (utils/get-mime-type data))] - + (instance? InputStream data) [data (check :data-length data-length) (check :filename filename) (check :mime-type mime-type)] - + (= byte-array-class (class data)) [(java.io.ByteArrayInputStream. data) (count data) (check :filename filename) (check :mime-type mime-type)] - + :default (throw (IllegalArgumentException. (str "Cannot handle attachment data of type " (class data)))))))) @@ -309,14 +309,24 @@ url :data (when (seq post-data-map) post-data-map)))) +(defn- get-or-search-view* + [action [db design-document view-key & [query-params-map post-data-map :as args]]] + (apply get-view* db + ["_design" design-document action (name view-key)] + args)) + (defdbop get-view "Get documents associated with a design document. Also takes an optional map for querying options, and a second map of {:key [keys]} to be POSTed. (see: http://wiki.apache.org/couchdb/HTTP_view_API)." - [db design-document view-key & [query-params-map post-data-map :as args]] - (apply get-view* db - ["_design" design-document "_view" (name view-key)] - args)) + [& args] + (get-or-search-view* "_view" args)) + +(defdbop search-view + "Search for documents associated with a design document, per CloudAnt search + options: https://cloudant.com/for-developers/search/" + [& args] + (get-or-search-view* "_search" args)) (defdbop all-documents "Returns the meta (_id and _rev) of all documents in a database. By adding @@ -348,14 +358,14 @@ (defdbop put-attachment "Updates (or creates) the attachment for the given document. `data` can be a string path to a file, a java.io.File, a byte array, or an InputStream. - + If `data` is a byte array or InputStream, you _must_ include the following otherwise-optional kwargs: :filename — name to be given to the attachment in the document :mime-type — type of attachment data - These are derived from a file path or File if not provided. (Mime types are derived from + These are derived from a file path or File if not provided. (Mime types are derived from filename extensions; see com.ashafa.clutch.utils/get-mime-type for determining mime type yourself from a File object.)" [db document data & {:keys [filename mime-type data-length]}] @@ -424,7 +434,7 @@ `add-watch` in order to be notified of changes. The returned change agent is entirely 'managed', with `start-changes` and `stop-changes` controlling its operation - and sent actions. If you send actions to a change agent, + and sent actions. If you send actions to a change agent, bad things will likely happen." [db & {:as opts}] (agent nil :meta {::changes-config (atom (change-agent-config db opts))})) @@ -503,7 +513,7 @@ "PUTs a document into CouchDB. Equivalent to (conj! couch [id document]).") (dissoc! [this id-or-doc] "DELETEs a document from CouchDB. Uses a given document map's :_id and :_rev - if provided; alternatively, if passed a string, will blindly attempt to + if provided; alternatively, if passed a string, will blindly attempt to delete the current revision of the corresponding document.")) (defn- with-result-meta @@ -527,24 +537,24 @@ (this id))] (with-result-meta this (delete-document url d)) (with-result-meta this nil))) - + clojure.lang.ILookup (valAt [this k] (get-document url k)) (valAt [this k default] (or (.valAt this k) default)) - + clojure.lang.Counted (count [this] (->> (database-info url) (fail-on-404 url) :doc_count)) - + clojure.lang.Seqable (seq [this] (->> (all-documents url {:include_docs true}) (map :doc) (map #(clojure.lang.MapEntry. (:_id %) %)))) - + clojure.lang.IFn (invoke [this key] (.valAt this key)) (invoke [this key default] (.valAt this key default)) - + clojure.lang.IMeta (meta [this] meta) clojure.lang.IObj diff --git a/src/com/ashafa/clutch/http_client.clj b/src/com/ashafa/clutch/http_client.clj index 3c52c2d..d229d20 100644 --- a/src/com/ashafa/clutch/http_client.clj +++ b/src/com/ashafa/clutch/http_client.clj @@ -1,7 +1,7 @@ (ns com.ashafa.clutch.http-client (:require [clj-http.client :as http] [cheshire.core :as json] - + [clojure.java.io :as io] [clojure.string :as str] [com.ashafa.clutch.utils :as utils]) @@ -79,6 +79,34 @@ complete HTTP response of the last couchdb request." [& args] (:body (apply couchdb-request* args))) +(defn response-handle-if-parsed* + "Was the response already parsed as JSON? send that. Otherwise, + parse the line as JSON." + [line] + (if (and (string? line) (.startsWith line "{")) + (json/parse-string line true) + (if (not (string? line)) + line) + )) + +(defn response-parse* + "Separate rows|results from the headers" + [s] + (json/parse-string (str/replace s #",?\"(rows|results)\":\[\s*$" "}") true)) ; TODO this is going to break eventually :-/ + +(defn response-with-header* + "Handle sorting out what is the response." + [lines] + [(if (empty? (rest lines)) (:rows (response-parse* (first lines))) (rest lines)) + (-> (first lines) + (response-parse*))] + ) + +(defn response-no-header* + "No header? Just send back lines." + [lines] + [lines nil]) + (defn lazy-view-seq "Given the body of a view result or _changes feed (should be an InputStream), returns a lazy seq of each row of data therein. @@ -92,18 +120,14 @@ complete HTTP response of the last couchdb request." [response-body header?] (let [lines (utils/read-lines response-body) [lines meta] (if header? - [(rest lines) - (-> (first lines) - (str/replace #",?\"(rows|results)\":\[\s*$" "}") ; TODO this is going to break eventually :-/ - (json/parse-string true))] - [lines nil])] + (response-with-header* lines) + (response-no-header* lines))] (with-meta (->> lines - (map (fn [^String line] - (when (.startsWith line "{") - (json/parse-string line true)))) - (remove nil?)) - ;; TODO why are we dissoc'ing :rows here? - (dissoc meta :rows)))) + (map (fn [^String line] + (response-handle-if-parsed* line))) + (remove nil?)) + ;; TODO why are we dissoc'ing :rows here? + (dissoc meta :rows)))) (defn view-request "Accepts the same arguments as couchdb-request*, but processes the result assuming that the