diff --git a/src/clojure/monger/multi/collection.clj b/src/clojure/monger/multi/collection.clj index 02c0d9f..910f2f0 100644 --- a/src/clojure/monger/multi/collection.clj +++ b/src/clojure/monger/multi/collection.clj @@ -4,7 +4,7 @@ Use these functions when you need to work with multiple databases or manage database and connection lifecycle explicitly." - (:refer-clojure :exclude [find remove count]) + (:refer-clojure :exclude [find remove count empty? distinct drop]) (:import [com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern DBCursor MapReduceCommand MapReduceCommand$OutputType] [java.util List Map] [clojure.lang IPersistentMap ISeq] @@ -60,11 +60,13 @@ ;; -;; monger.collection/find +;; monger.multi.collection/find ;; (defn ^DBCursor find "Like monger.collection/find but always takes a database as explicit argument" + ([^DB db ^String collection] + (.find (.getCollection db (name collection)))) ([^DB db ^String collection ^Map ref] (.find (.getCollection db (name collection)) (to-db-object ref))) @@ -75,6 +77,9 @@ (defn find-maps "Like monger.collection/find-maps but always takes a database as explicit argument" + ([^DB db ^String collection] + (with-open [dbc (find db collection)] + (map (fn [x] (from-db-object x true)) dbc))) ([^DB db ^String collection ^Map ref] (with-open [dbc (find db collection ref)] (map (fn [x] (from-db-object x true)) dbc))) @@ -82,8 +87,20 @@ (with-open [dbc (find db collection ref fields)] (map (fn [x] (from-db-object x true)) dbc)))) +(defn find-seq + "Like monger.collection/find-seq but always takes a database as explicit argument" + ([^DB db ^String collection] + (with-open [dbc (find db collection)] + (seq dbc))) + ([^DB db ^String collection ^Map ref] + (with-open [dbc (find db collection ref)] + (seq dbc))) + ([^DB db ^String collection ^Map ref fields] + (with-open [dbc (find db collection ref fields)] + (seq dbc)))) + ;; -;; monger.collection/find-one +;; monger.multi.collection/find-one ;; (defn ^DBObject find-one @@ -106,7 +123,22 @@ (from-db-object ^DBObject (find-one db collection ref fields) keywordize))) ;; -;; monger.collection/find-by-id +;; monger.multi.collection/find-and-modify +;; + +(defn ^DBObject find-and-modify + "Like monger.collection/find-and-modify but always takes a database as explicit argument" + ([^DB db ^String collection ^Map conditions ^Map document & {:keys [fields sort remove return-new upsert keywordize] :or + {fields nil sort nil remove false return-new false upsert false keywordize true}}] + (let [coll (.getCollection db (name collection)) + maybe-fields (when fields (as-field-selector fields)) + maybe-sort (when sort (to-db-object sort))] + (from-db-object + ^DBObject (.findAndModify ^DBCollection coll (to-db-object conditions) maybe-fields maybe-sort remove + (to-db-object document) return-new upsert) keywordize)))) + +;; +;; monger.multi.collection/find-by-id ;; (defn ^DBObject find-by-id @@ -125,10 +157,13 @@ (from-db-object ^DBObject (find-one-as-map db collection {:_id id}) true)) ([^DB db ^String collection id fields] (check-not-nil! id "id must not be nil") - (from-db-object ^DBObject (find-one-as-map db collection {:_id id} fields) true))) + (from-db-object ^DBObject (find-one-as-map db collection {:_id id} fields) true)) + ([^DB db ^String collection id fields keywordize] + (check-not-nil! id "id must not be nil") + (from-db-object ^DBObject (find-one-as-map db collection {:_id id} fields) keywordize))) ;; -;; monger.collection/count +;; monger.multi.collection/count ;; (defn count @@ -137,3 +172,190 @@ (.count (.getCollection db (name collection)) (to-db-object {}))) (^long [^DB db ^String collection ^Map conditions] (.count (.getCollection db (name collection)) (to-db-object conditions)))) + +(defn any? + "Like monger.collection/any? but always takes a database as explicit argument" + ([^DB db ^String collection] + (> (count db collection) 0)) + ([^DB db ^String collection ^Map conditions] + (> (count db collection conditions) 0))) + +(defn empty? + "Like monger.collection/empty? but always takes a database as explicit argument" + ([^DB db ^String collection] + (= (count db collection {}) 0))) + +(defn ^WriteResult update + "Like monger.collection/update but always takes a database as explicit argument" + ([^DB db ^String collection ^Map conditions ^Map document & {:keys [upsert multi write-concern] :or {upsert false + multi false + write-concern monger.core/*mongodb-write-concern*}}] + (.update (.getCollection db (name collection)) + (to-db-object conditions) + (to-db-object document) + upsert + multi + write-concern))) + +(defn ^WriteResult upsert + "Like monger.collection/upsert but always takes a database as explicit argument" + [^DB db ^String collection ^Map conditions ^Map document & {:keys [multi write-concern] :or {multi false + write-concern monger.core/*mongodb-write-concern*}}] + (update db collection conditions document :multi multi :write-concern write-concern :upsert true)) + +(defn ^WriteResult update-by-id + "Like monger.collection/update-by-id but always takes a database as explicit argument" + [^DB db ^String collection id ^Map document & {:keys [upsert write-concern] :or {upsert false + write-concern monger.core/*mongodb-write-concern*}}] + (check-not-nil! id "id must not be nil") + (.update (.getCollection db (name collection)) + (to-db-object {:_id id}) + (to-db-object document) + upsert + false + write-concern)) + +(defn ^WriteResult save + "Like monger.collection/save but always takes a database as explicit argument" + ([^DB db ^String collection ^Map document] + (.save (.getCollection db (name collection)) + (to-db-object document) + monger.core/*mongodb-write-concern*)) + ([^DB db ^String collection ^Map document ^WriteConcern write-concern] + (.save (.getCollection db (name collection)) + (to-db-object document) + write-concern))) + +(defn ^clojure.lang.IPersistentMap save-and-return + "Like monger.collection/save-and-return but always takes a database as explicit argument" + ([^DB db ^String collection ^Map document] + (save-and-return ^DB db collection document ^WriteConcern monger.core/*mongodb-write-concern*)) + ([^DB db ^String collection ^Map document ^WriteConcern write-concern] + ;; see the comment in insert-and-return. Here we additionally need to make sure to not scrap the :_id key if + ;; it is already present. MK. + (let [doc (merge {:_id (ObjectId.)} document)] + (save db collection doc write-concern) + doc))) + +(defn ^WriteResult remove + "Like monger.collection/remove but always takes a database as explicit argument" + ([^DB db ^String collection] + (.remove (.getCollection db (name collection)) (to-db-object {}))) + ([^DB db ^String collection ^Map conditions] + (.remove (.getCollection db (name collection)) (to-db-object conditions)))) + +(defn ^WriteResult remove-by-id + "Like monger.collection/remove-by-id but always takes a database as explicit argument" + ([^DB db ^String collection id] + (check-not-nil! id "id must not be nil") + (let [coll (.getCollection db (name collection))] + (.remove coll (to-db-object {:_id id}))))) + +;; +;; monger.multi.collection/create-index +;; + +(defn create-index + "Like monger.collection/create-index but always takes a database as explicit argument" + ([^DB db ^String collection ^Map keys] + (.createIndex (.getCollection db (name collection)) (as-field-selector keys))) + ([^DB db ^String collection ^Map keys ^Map options] + (.createIndex (.getCollection db (name collection)) + (as-field-selector keys) + (to-db-object options)))) + +;; +;; monger.multi.collection/ensure-index +;; + +(defn ensure-index + "Like monger.collection/ensure-index but always takes a database as explicit argument" + ([^DB db ^String collection ^Map keys] + (.ensureIndex (.getCollection db (name collection)) (as-field-selector keys))) + ([^DB db ^String collection ^Map keys ^Map options] + (.ensureIndex (.getCollection db (name collection)) + (as-field-selector keys) + (to-db-object options))) + ([^DB db ^String collection ^Map keys ^String name ^Boolean unique?] + (.ensureIndex (.getCollection db (name collection)) + (as-field-selector keys) + name + unique?))) + +;; +;; monger.multi.collection/indexes-on +;; + +(defn indexes-on + "Like monger.collection/indexes-on but always takes a database as explicit argument" + [^DB db ^String collection] + (from-db-object (.getIndexInfo (.getCollection db (name collection))) true)) + + +;; +;; monger.multi.collection/drop-index +;; + +(defn drop-index + "Like monger.collection/drop-index but always takes a database as explicit argument" + ([^DB db ^String collection ^String idx-name] + (.dropIndex (.getCollection db (name collection)) idx-name))) + +(defn drop-indexes + "Like monger.collection/drop-indexes but always takes a database as explicit argument" + ([^DB db ^String collection] + (.dropIndexes (.getCollection db (name collection))))) + + +;; +;; monger.multi.collection/exists?, /create, /drop, /rename +;; + + +(defn exists? + "Like monger.collection/exists? but always takes a database as explicit argument" + ([^DB db ^String collection] + (.collectionExists db collection))) + +(defn create + "Like monger.collection/create but always takes a database as explicit argument" + ([^DB db ^String collection ^Map options] + (.createCollection db collection (to-db-object options)))) + +(defn drop + "Like monger.collection/drop but always takes a database as explicit argument" + ([^DB db ^String collection] + (.drop (.getCollection db (name collection))))) + +(defn rename + "Like monger.collection/rename but always takes a database as explicit argument" + ([^DB db ^String from, ^String to] + (.rename (.getCollection db from) to)) + ([^DB db ^String from ^String to ^Boolean drop-target] + (.rename (.getCollection db from) to drop-target))) + +;; +;; Map/Reduce +;; + +(defn map-reduce + "Like monger.collection/map-reduce but always takes a database as explicit argument" + ([^DB db ^String collection ^String js-mapper ^String js-reducer ^String output ^Map query] + (let [coll (.getCollection db (name collection))] + (.mapReduce coll js-mapper js-reducer output (to-db-object query)))) + ([^DB db ^String collection ^String js-mapper ^String js-reducer ^String output ^MapReduceCommand$OutputType output-type ^Map query] + (let [coll (.getCollection db (name collection))] + (.mapReduce coll js-mapper js-reducer output output-type (to-db-object query))))) + + +;; +;; monger.multi.collection/distinct +;; + +(defn distinct + "Like monger.collection/distinct but always takes a database as explicit argument" + ([^DB db ^String collection ^String key] + (.distinct (.getCollection db (name collection)) ^String (to-db-object key))) + ([^DB db ^String collection ^String key ^Map query] + (.distinct (.getCollection db (name collection)) ^String (to-db-object key) (to-db-object query)))) + diff --git a/test/monger/test/multi/find_test.clj b/test/monger/test/multi/find_test.clj new file mode 100644 index 0000000..5f86900 --- /dev/null +++ b/test/monger/test/multi/find_test.clj @@ -0,0 +1,145 @@ + +(ns monger.test.multi.find-test + (:import [com.mongodb WriteResult WriteConcern DBCursor DBObject DBRef] + org.bson.types.ObjectId + java.util.Date) + (:require [monger.core :as mg] + [monger.util :as mu] + [monger.multi.collection :as mgcol] + [monger.test.helper :as helper] + [monger.conversion :as mgcnv]) + (:use clojure.test + monger.operators + monger.conversion + monger.test.fixtures)) + +(helper/connect!) + + +(use-fixtures :each purge-people purge-docs purge-things purge-libraries) + +;; +;; find +;; + +(deftest find-full-document-when-collection-is-empty + (let [db (mg/get-db "monger-test") + collection "docs" + cursor (mgcol/find db collection)] + (is (empty? (iterator-seq cursor))))) + +(deftest find-document-seq-when-collection-is-empty + (let [db (mg/get-db "monger-test") + collection "docs"] + (is (empty? (mgcol/find-seq db collection))))) + +(deftest find-multiple-documents-when-collection-is-empty + (let [db (mg/get-db "monger-test") + collection "libraries"] + (is (empty? (mgcol/find db collection { :language "Scala" }))))) + +(deftest find-multiple-maps-when-collection-is-empty + (let [db (mg/get-db "monger-test") + collection "libraries"] + (is (empty? (mgcol/find-maps db collection { :language "Scala" }))))) + +(deftest find-multiple-documents-by-regex + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Java", :name "nhibernate" } + { :language "JavaScript", :name "sprout-core" }]) + (is (= 2 (monger.core/count (mgcol/find db collection { :language #"Java*" })))))) + +(deftest find-multiple-documents + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Clojure", :name "langohr" } + { :language "Clojure", :name "incanter" } + { :language "Scala", :name "akka" }]) + (is (= 1 (monger.core/count (mgcol/find db collection { :language "Scala" })))) + (is (= 3 (.count (mgcol/find db collection { :language "Clojure" })))) + (is (empty? (mgcol/find db collection { :language "Java" }))))) + + +(deftest find-document-specify-fields + (let [db (mg/get-db "monger-test") + collection "libraries" + _ (mgcol/insert db collection { :language "Clojure", :name "monger" }) + result (mgcol/find db collection { :language "Clojure"} [:language])] + (is (= (seq [:_id :language]) (keys (mgcnv/from-db-object (.next result) true)))))) + +(deftest find-and-iterate-over-multiple-documents-the-hard-way + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Clojure", :name "langohr" } + { :language "Clojure", :name "incanter" } + { :language "Scala", :name "akka" }]) + (doseq [doc (take 3 (map (fn [dbo] + (mgcnv/from-db-object dbo true)) + (mgcol/find-seq db collection { :language "Clojure" })))] + (is (= "Clojure" (:language doc)))))) + +(deftest find-and-iterate-over-multiple-documents + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Clojure", :name "langohr" } + { :language "Clojure", :name "incanter" } + { :language "Scala", :name "akka" }]) + (doseq [doc (take 3 (mgcol/find-maps db collection { :language "Clojure" }))] + (is (= "Clojure" (:language doc)))))) + + +(deftest find-multiple-maps + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Clojure", :name "langohr" } + { :language "Clojure", :name "incanter" } + { :language "Scala", :name "akka" }]) + (is (= 1 (count (mgcol/find-maps db collection { :language "Scala" })))) + (is (= 3 (count (mgcol/find-maps db collection { :language "Clojure" })))) + (is (empty? (mgcol/find-maps db collection { :language "Java" }))) + (is (empty? (mgcol/find-maps db collection { :language "Java" } [:language :name]))))) + + + +(deftest find-multiple-partial-documents + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Clojure", :name "langohr" } + { :language "Clojure", :name "incanter" } + { :language "Scala", :name "akka" }]) + (let [scala-libs (mgcol/find db collection { :language "Scala" } [:name]) + clojure-libs (mgcol/find db collection { :language "Clojure"} [:language])] + (is (= 1 (.count scala-libs))) + (is (= 3 (.count clojure-libs))) + (doseq [i clojure-libs] + (let [doc (mgcnv/from-db-object i true)] + (is (= (:language doc) "Clojure")))) + (is (empty? (mgcol/find db collection { :language "Erlang" } [:name])))))) + +(deftest finds-one-as-map + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Clojure", :name "langohr" }]) + (let [res (mgcol/find-one-as-map db collection { :name "langohr" })] + (is (map? res)) + (is (= "langohr" (:name res))) + (is (= "Clojure" (:language res)))) + (is (= 2 (count (mgcol/find-one-as-map db collection { :name "langohr" } [:name])))) + (is (= "langohr" (get (mgcol/find-one-as-map db collection { :name "langohr" } [:name] false) "name"))))) + +(deftest find-and-modify + (let [db (mg/get-db "monger-test") + collection "libraries"] + (mgcol/insert-batch db collection [{ :language "Clojure", :name "monger" } + { :language "Clojure", :name "langohr" }]))) + +(run-tests) + diff --git a/test/monger/test/multi/inserting_test.clj b/test/monger/test/multi/inserting_test.clj index bebedef..01d9532 100644 --- a/test/monger/test/multi/inserting_test.clj +++ b/test/monger/test/multi/inserting_test.clj @@ -86,3 +86,104 @@ result (mc/insert db "widgets" doc)] (is (= (sort ["kw1" "kw2"]) (sort (get-in (mc/find-map-by-id db collection id) [:keyword1 :keyword2])))))) + +(defrecord Metrics + [rps eps]) + +(deftest insert-a-document-with-clojure-record-in-it + (let [db (mg/get-db "altdb") + collection "widgets" + id (ObjectId.) + doc {:record (Metrics. 10 20) "_id" id} + result (mc/insert db "widgets" doc)] + (is (= {:rps 10 :eps 20} (:record (mc/find-map-by-id db collection id)))))) + +(deftest test-insert-a-document-with-dbref + (let [db (mg/get-db "altdb")] + (mc/remove db "widgets") + (mc/remove db "owners") + (let [coll1 "widgets" + coll2 "owners" + oid (ObjectId.) + joe (mc/insert db "owners" {:name "Joe" :_id oid}) + dbref (DBRef. (mg/current-db) coll2 oid)] + (mc/insert coll1 {:type "pentagon" :owner dbref}) + (let [fetched (mc/find-one-as-map db coll1 {:type "pentagon"}) + fo (:owner fetched)] + (is (= {:_id oid :name "Joe"} (from-db-object @fo true))))))) + + +;; +;; insert-and-return +;; + +(deftest insert-and-return-a-basic-document-without-id-and-with-default-write-concern + (let [db (mg/get-db "altdb") + collection "people" + doc {:name "Joe" :age 30} + result (mc/insert-and-return db :people doc)] + (is (= (:name doc) + (:name result))) + (is (= (:age doc) + (:age result))) + (is (:_id result)) + (is (= 1 (mc/count collection))))) + +(deftest insert-and-return-a-basic-document-without-id-but-with-a-write-concern + (let [db (mg/get-db "altdb") + collection "people" + doc {:name "Joe" :age 30 :ratio 3/4} + result (mc/insert-and-return db "people" doc WriteConcern/FSYNC_SAFE)] + (is (= (:name doc) + (:name result))) + (is (= (:age doc) + (:age result))) + (is (= (:ratio doc) + (:ratio result))) + (is (:_id result)) + (is (= 1 (mc/count collection))))) + +(deftest insert-and-return-with-a-provided-id + (let [db (mg/get-db "altdb") + collection "people" + oid (ObjectId.) + doc {:name "Joe" :age 30 :_id oid} + result (mc/insert-and-return db :people doc)] + (is (= (:_id result) (:_id doc) oid)) + (is (= 1 (mc/count collection))))) + + +;; +;; insert-batch +;; + +(deftest insert-a-batch-of-basic-documents-without-ids-and-with-default-write-concern + (let [db (mg/get-db "altdb") + collection "people" + docs [{:name "Joe" :age 30} {:name "Paul" :age 27}]] + (is (monger.result/ok? (mc/insert-batch db "people" docs))) + (is (= 2 (mc/count collection))))) + +(deftest insert-a-batch-of-basic-documents-without-ids-and-with-explicit-write-concern + (let [db (mg/get-db "altdb") + collection "people" + docs [{:name "Joe" :age 30} {:name "Paul" :age 27}]] + (is (monger.result/ok? (mc/insert-batch db "people" docs WriteConcern/NORMAL))) + (is (= 2 (mc/count collection))))) + +(deftest insert-a-batch-of-basic-documents-with-explicit-database-without-ids-and-with-explicit-write-concern + (let [db (mg/get-db "altdb") + collection "people" + docs [{:name "Joe" :age 30} {:name "Paul" :age 27}]] + (dotimes [n 44] + (is (monger.result/ok? (mc/insert-batch db "people" docs WriteConcern/NORMAL)))) + (is (= 88 (mc/count collection))))) + +(deftest insert-a-batch-of-basic-documents-from-a-lazy-sequence + (let [db (mg/get-db "altdb") + collection "people" + numbers (range 0 1000)] + (is (monger.result/ok? (mc/insert-batch db "people" (map (fn [^long l] + {:n l}) + numbers)))) + (is (= (count numbers) (mc/count collection)))))