diff --git a/ChangeLog.md b/ChangeLog.md index 9d3469e..03b6835 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,29 @@ ## Changes between 1.4.0 and 1.5.0 +### Full Text Search Support + +Full text search in MongoDB 2.4 can be used via commands but Monger 1.5 also provides +convenience functions in the `monger.search` namespace: + + * `monger.search/search` for performing queries + * `monger.search/results-from` for obtaining hit documents sorted by score + +``` clojure +(require '[monger.collection :as mc]) +(require '[monger.search :as ms]) + +(mc/ensure-index coll {:subject "text" :content "text"}) +(mc/insert coll {:subject "hello there" :content "this should be searchable"}) +(mc/insert coll {:subject "untitled" :content "this is just noize"}) + +(println (ms/results-from (ms/search coll "hello")) +``` + + +### MongoDB Java Driver Update + +MongoDB Java driver dependency has been [updated to 2.11.0](https://github.com/mongodb/mongo-java-driver/wiki/Release-Notes). + ### New Geospatial Operators `monger.operators` now defines a few more operators for convenience: diff --git a/src/clojure/monger/command.clj b/src/clojure/monger/command.clj index 0f8b1a9..1db27cc 100644 --- a/src/clojure/monger/command.clj +++ b/src/clojure/monger/command.clj @@ -23,6 +23,15 @@ (:import com.mongodb.DB)) +;; +;; API +;; + +(defn admin-command + "Executes a command on the admin database" + [m] + (monger.core/command (monger.core/admin-db) m)) + (defn collection-stats ([collection] (collection-stats monger.core/*mongodb-database* collection)) @@ -83,3 +92,9 @@ (defn top [] (monger.core/command (monger.core/get-db "admin") {:top 1})) + +(defn search + ([^String collection query] + (monger.core/command {"text" collection "search" query})) + ([^DB database ^String collection query] + (monger.core/command database {"text" collection "search" query}))) \ No newline at end of file diff --git a/src/clojure/monger/core.clj b/src/clojure/monger/core.clj index 6e5b704..97ae23f 100644 --- a/src/clojure/monger/core.clj +++ b/src/clojure/monger/core.clj @@ -188,6 +188,13 @@ (def ^{:doc "Combines set-db! and get-db, so (use-db \"mydb\") is the same as (set-db! (get-db \"mydb\"))"} use-db! (comp set-db! get-db)) +(def ^:const admin-db-name "admin") + +(defn ^DB admin-db + "Returns admin database" + [] + (get-db admin-db-name)) + (defn set-default-write-concern! [wc] diff --git a/src/clojure/monger/search.clj b/src/clojure/monger/search.clj new file mode 100644 index 0000000..38ee640 --- /dev/null +++ b/src/clojure/monger/search.clj @@ -0,0 +1,37 @@ +(ns monger.search + "Full text search queries support (MongoDB 2.4+)" + (:require [monger.command :as cmd] + [monger.conversion :as cnv]) + (:import [com.mongodb CommandResult BasicDBList DBObject])) + +;; +;; Implementation +;; + +(defn- convert-hit + [^DBObject dbo keywordize-keys?] + (cnv/from-db-object dbo keywordize-keys?)) + + +;; +;; API +;; + +(defn search + "Performs a full text search query" + [^String collection query] + (cmd/search collection query)) + +(defn results-from + "Returns a lazy sequence of results from a search query response, sorted by score. + + Each result is a Clojure map with two keys: :score and :obj." + ([^CommandResult res] + (results-from res true)) + ([^CommandResult res keywordize-keys?] + (let [sorter (if keywordize-keys? + :score + (fn [m] + (get m "score")))] + (sort-by sorter > + (map #(convert-hit % keywordize-keys?) ^BasicDBList (.get res "results")))))) diff --git a/test/monger/test/full_text_search_test.clj b/test/monger/test/full_text_search_test.clj new file mode 100644 index 0000000..5b61680 --- /dev/null +++ b/test/monger/test/full_text_search_test.clj @@ -0,0 +1,31 @@ +(ns monger.test.full-text-search-test + (:require [monger.core :as mg] + [monger.collection :as mc] + [monger.search :as ms] + [monger.command :as cmd] + [monger.test.helper :as helper]) + (:use [clojure.test :only [deftest is use-fixtures]] + monger.test.fixtures + [monger.result :only [ok?]])) + +(helper/connect!) + +(defn enable-search + [f] + (is (ok? (cmd/admin-command {:setParameter "*" :textSearchEnabled true}))) + (f)) + +(use-fixtures :each purge-docs) +(use-fixtures :once enable-search) + +(deftest ^{:edge-features true :search true} test-basic-full-text-search-query + (let [coll "docs"] + (mc/ensure-index coll {:subject "text" :content "text"}) + (mc/insert coll {:subject "hello there" :content "this should be searchable"}) + (mc/insert coll {:subject "untitled" :content "this is just noize"}) + (let [res (ms/search coll "hello") + xs (ms/results-from res)] + (is (ok? res)) + (println res) + (is (= "hello there" (-> xs first :obj :subject))) + (is (= 1.0 (-> xs first :score))))))