diff --git a/ChangeLog.md b/ChangeLog.md index c4e585a..9660fcb 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,19 @@ ## Changes between 1.5.0 and 1.6.0 +### monger.multi.collection + +`monger.multi.collection` is a new namespace with functions that are very similar to those +in the `monger.collection` namespace but always take a database reference as an explicit argument. + +They are supposed to be used in cases when relying on `monger.core/*mongodb-database*` is not +enough. + + +## monger.core/drop-db + +`monger.core/drop-db` is a new function that drops a database by name. + + ### One More Cache Implementation `monger.cache/db-aware-monger-cache-factory` will return a MongoDB-backed `clojure.core.cache` diff --git a/src/clojure/monger/collection.clj b/src/clojure/monger/collection.clj index 7ab215b..0b39f25 100644 --- a/src/clojure/monger/collection.clj +++ b/src/clojure/monger/collection.clj @@ -9,36 +9,30 @@ ;; the terms of this license. ;; You must not remove this notice, or any other, from this software. -(ns ^{:doc "Provides key functionality for interaction with MongoDB: inserting, querying, updating and deleting documents, performing Aggregation Framework - queries, creating and dropping indexes, creating collections and more. +(ns monger.collection + "Provides key functionality for interaction with MongoDB: inserting, querying, updating and deleting documents, performing Aggregation Framework + queries, creating and dropping indexes, creating collections and more. - For more advanced read queries, see monger.query. + For more advanced read queries, see monger.query. - Related documentation guides: + Related documentation guides: - * http://clojuremongodb.info/articles/getting_started.html - * http://clojuremongodb.info/articles/inserting.html - * http://clojuremongodb.info/articles/querying.html - * http://clojuremongodb.info/articles/updating.html - * http://clojuremongodb.info/articles/deleting.html - * http://clojuremongodb.info/articles/aggregation.html"} - monger.collection + * http://clojuremongodb.info/articles/getting_started.html + * http://clojuremongodb.info/articles/inserting.html + * http://clojuremongodb.info/articles/querying.html + * http://clojuremongodb.info/articles/updating.html + * http://clojuremongodb.info/articles/deleting.html + * http://clojuremongodb.info/articles/aggregation.html" (:refer-clojure :exclude [find remove count drop distinct empty?]) (:import [com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern DBCursor MapReduceCommand MapReduceCommand$OutputType] [java.util List Map] [clojure.lang IPersistentMap ISeq] org.bson.types.ObjectId) - (:require [monger core result]) - (:use [monger.conversion])) + (:require monger.core + monger.result) + (:use monger.conversion + monger.constraints)) -;; -;; Implementation -;; - -(definline check-not-nil! - [ref ^String message] - `(when (nil? ~ref) - (throw (IllegalArgumentException. ~message)))) ;; diff --git a/src/clojure/monger/constraints.clj b/src/clojure/monger/constraints.clj new file mode 100644 index 0000000..88d98a2 --- /dev/null +++ b/src/clojure/monger/constraints.clj @@ -0,0 +1,11 @@ +(ns monger.constraints) + + +;; +;; API +;; + +(definline check-not-nil! + [ref ^String message] + `(when (nil? ~ref) + (throw (IllegalArgumentException. ~message)))) diff --git a/src/clojure/monger/conversion.clj b/src/clojure/monger/conversion.clj index b0bf0f9..5c2f568 100644 --- a/src/clojure/monger/conversion.clj +++ b/src/clojure/monger/conversion.clj @@ -42,7 +42,7 @@ (extend-protocol ConvertToDBObject nil (to-db-object [input] - input) + nil) String (to-db-object [^String input] diff --git a/src/clojure/monger/core.clj b/src/clojure/monger/core.clj index 97ae23f..b94ceea 100644 --- a/src/clojure/monger/core.clj +++ b/src/clojure/monger/core.clj @@ -105,6 +105,12 @@ [] *mongodb-database*) +(defn drop-db + "Drops a database" + ([^String db] + (.dropDatabase *mongodb-connection* db)) + ([^MongoClient conn ^String db] + (.dropDatabase conn db))) (defmacro with-connection @@ -243,7 +249,7 @@ Ordering of keys in the command document may matter. Please use sorted maps instead of map literals, for example: (sorted-map geoNear \"bars\" :near 50 :test 430 :num 10) - For commonly used commands (distinct, count, map/reduce, etc), use monger/command and monger/collection functions such as + For commonly used commands (distinct, count, map/reduce, etc), use monger.command and monger.collection functions such as /distinct, /count, /drop, /dropIndexes, and /mapReduce respectively." ([^Map cmd] (.command ^DB *mongodb-database* ^DBObject (to-db-object cmd))) diff --git a/src/clojure/monger/multi/collection.clj b/src/clojure/monger/multi/collection.clj new file mode 100644 index 0000000..02c0d9f --- /dev/null +++ b/src/clojure/monger/multi/collection.clj @@ -0,0 +1,139 @@ +(ns monger.multi.collection + "Includes versions of key monger.collection functions that always take a database + as explicit argument instead of relying on monger.core/*mongodb-database*. + + Use these functions when you need to work with multiple databases or manage database + and connection lifecycle explicitly." + (:refer-clojure :exclude [find remove count]) + (:import [com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern DBCursor MapReduceCommand MapReduceCommand$OutputType] + [java.util List Map] + [clojure.lang IPersistentMap ISeq] + org.bson.types.ObjectId) + (:require monger.core + monger.result) + (:use monger.conversion + monger.constraints)) + + +;; +;; API +;; + +(defn ^WriteResult insert + "Like monger.collection/insert but always takes a database as explicit argument" + ([^DB db ^String collection document] + (.insert (.getCollection db (name collection)) + (to-db-object document) + monger.core/*mongodb-write-concern*)) + ([^DB db ^String collection document ^WriteConcern concern] + (.insert (.getCollection db (name collection)) + (to-db-object document) + concern))) + + +(defn ^clojure.lang.IPersistentMap insert-and-return + "Like monger.collection/insert-and-return but always takes a database as explicit argument" + ([^DB db ^String collection document] + (let [doc (merge {:_id (ObjectId.)} document)] + (insert db collection doc monger.core/*mongodb-write-concern*) + doc)) + ([^DB db ^String collection document ^WriteConcern concern] + ;; MongoDB Java driver will generate the _id and set it but it tries to mutate the inserted DBObject + ;; and it does not work very well in our case, because that DBObject is short lived and produced + ;; from the Clojure map we are passing in. Plus, this approach is very awkward with immutable data + ;; structures being the default. MK. + (let [doc (merge {:_id (ObjectId.)} document)] + (insert db collection doc concern) + doc))) + + +(defn ^WriteResult insert-batch + "Like monger.collection/insert-batch but always takes a database as explicit argument" + ([^DB db ^String collection ^List documents] + (.insert (.getCollection db (name collection)) + ^List (to-db-object documents) + monger.core/*mongodb-write-concern*)) + ([^DB db ^String collection ^List documents ^WriteConcern concern] + (.insert (.getCollection db (name collection)) + ^List (to-db-object documents) + concern))) + + +;; +;; monger.collection/find +;; + +(defn ^DBCursor find + "Like monger.collection/find but always takes a database as explicit argument" + ([^DB db ^String collection ^Map ref] + (.find (.getCollection db (name collection)) + (to-db-object ref))) + ([^DB db ^String collection ^Map ref fields] + (.find (.getCollection db (name collection)) + (to-db-object ref) + (as-field-selector fields)))) + +(defn find-maps + "Like monger.collection/find-maps but always takes a database as explicit argument" + ([^DB db ^String collection ^Map ref] + (with-open [dbc (find db collection ref)] + (map (fn [x] (from-db-object x true)) dbc))) + ([^DB db ^String collection ^Map ref fields] + (with-open [dbc (find db collection ref fields)] + (map (fn [x] (from-db-object x true)) dbc)))) + +;; +;; monger.collection/find-one +;; + +(defn ^DBObject find-one + "Like monger.collection/find-one but always takes a database as explicit argument" + ([^DB db ^String collection ^Map ref] + (.findOne (.getCollection db (name collection)) + (to-db-object ref))) + ([^DB db ^String collection ^Map ref fields] + (.findOne (.getCollection db (name collection)) + (to-db-object ref) + ^DBObject (as-field-selector fields)))) + +(defn ^IPersistentMap find-one-as-map + "Like monger.collection/find-one-as-map but always takes a database as explicit argument" + ([^DB db ^String collection ^Map ref] + (from-db-object ^DBObject (find-one db collection ref) true)) + ([^DB db ^String collection ^Map ref fields] + (from-db-object ^DBObject (find-one db collection ref fields) true)) + ([^DB db ^String collection ^Map ref fields keywordize] + (from-db-object ^DBObject (find-one db collection ref fields) keywordize))) + +;; +;; monger.collection/find-by-id +;; + +(defn ^DBObject find-by-id + "Like monger.collection/find-by-id but always takes a database as explicit argument" + ([^DB db ^String collection id] + (check-not-nil! id "id must not be nil") + (find-one db collection {:_id id})) + ([^DB db ^String collection id fields] + (check-not-nil! id "id must not be nil") + (find-one db collection {:_id id} fields))) + +(defn ^IPersistentMap find-map-by-id + "Like monger.collection/find-map-by-id but always takes a database as explicit argument" + ([^DB db ^String collection id] + (check-not-nil! id "id must not be nil") + (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))) + +;; +;; monger.collection/count +;; + +(defn count + "Like monger.collection/count but always takes a database as explicit argument" + (^long [^DB db ^String collection] + (.count (.getCollection db (name collection)) (to-db-object {}))) + (^long [^DB db ^String collection ^Map conditions] + (.count (.getCollection db (name collection)) (to-db-object conditions)))) diff --git a/src/clojure/monger/testkit.clj b/src/clojure/monger/testkit.clj index c7bd426..0f3d740 100644 --- a/src/clojure/monger/testkit.clj +++ b/src/clojure/monger/testkit.clj @@ -26,13 +26,19 @@ (defcleaner events) ;; collection name will be taken from the events-collection var (defcleaner people \"accounts\") ;; collection name is given " - [entities & coll-name] - (let [coll-arg (if coll-name - (str (first coll-name)) - (symbol (str entities "-collection"))) - fn-name (symbol (str "purge-" entities))] - `(defn ~fn-name - [f#] - (mc/remove ~coll-arg) - (f#) - (mc/remove ~coll-arg)))) + ([entities] + (let [coll-arg (symbol (str entities "-collection")) + fn-name (symbol (str "purge-" entities))] + `(defn ~fn-name + [f#] + (mc/remove ~coll-arg) + (f#) + (mc/remove ~coll-arg)))) + ([entities coll-name] + (let [coll-arg (name coll-name) + fn-name (symbol (str "purge-" entities))] + `(defn ~fn-name + [f#] + (mc/remove ~coll-arg) + (f#) + (mc/remove ~coll-arg))))) diff --git a/test/monger/test/multi/inserting_test.clj b/test/monger/test/multi/inserting_test.clj new file mode 100644 index 0000000..bebedef --- /dev/null +++ b/test/monger/test/multi/inserting_test.clj @@ -0,0 +1,88 @@ +(ns monger.test.multi.inserting-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 mc] + [monger.test.helper :as helper]) + (:use clojure.test + monger.operators + monger.conversion + monger.test.fixtures)) + +(helper/connect!) + +(defn drop-altdb + [f] + (mg/drop-db "altdb") + (f)) + +(use-fixtures :each drop-altdb) + +;; +;; insert +;; + +(deftest insert-a-basic-document-without-id-and-with-default-write-concern + (let [db (mg/get-db "altdb") + collection "people" + doc {:name "Joe" :age 30}] + (is (monger.result/ok? (mc/insert db "people" doc))) + (is (= 1 (mc/count db collection))))) + +(deftest insert-a-basic-document-with-explicitly-passed-database-without-id-and-with-default-write-concern + (let [db (mg/get-db "altdb") + collection "people" + doc {:name "Joe" :age 30}] + (dotimes [n 5] + (is (monger.result/ok? (mc/insert db "people" doc WriteConcern/SAFE)))) + (is (= 5 (mc/count db collection))))) + +(deftest insert-a-basic-document-without-id-and-with-explicit-write-concern + (let [db (mg/get-db "altdb") + collection "people" + doc {:name "Joe" :age 30}] + (is (monger.result/ok? (mc/insert db "people" doc WriteConcern/SAFE))) + (is (= 1 (mc/count db collection))))) + +(deftest insert-a-basic-db-object-without-id-and-with-default-write-concern + (let [db (mg/get-db "altdb") + collection "people" + doc (to-db-object {:name "Joe" :age 30})] + (is (nil? (.get ^DBObject doc "_id"))) + (mc/insert db "people" doc) + (is (not (nil? (monger.util/get-id doc)))))) + +(deftest insert-a-map-with-id-and-with-default-write-concern + (let [db (mg/get-db "altdb") + collection "people" + id (ObjectId.) + doc {:name "Joe" :age 30 "_id" id} + result (mc/insert db "people" doc)] + (is (= id (monger.util/get-id doc))))) + +(deftest insert-a-document-with-clojure-ratio-in-it + (let [db (mg/get-db "altdb") + collection "widgets" + id (ObjectId.) + doc {:ratio 11/2 "_id" id} + result (mc/insert db "widgets" doc)] + (is (= 5.5 (:ratio (mc/find-map-by-id db collection id)))))) + +(deftest insert-a-document-with-clojure-keyword-in-it + (let [db (mg/get-db "altdb") + collection "widgets" + id (ObjectId.) + doc {:keyword :kwd "_id" id} + result (mc/insert db "widgets" doc)] + (is (= (name :kwd) (:keyword (mc/find-map-by-id db collection id)))))) + +(deftest insert-a-document-with-clojure-keyword-in-a-set-in-it + (let [db (mg/get-db "altdb") + collection "widgets" + id (ObjectId.) + doc {:keyword1 {:keyword2 #{:kw1 :kw2}} "_id" id} + result (mc/insert db "widgets" doc)] + (is (= (sort ["kw1" "kw2"]) + (sort (get-in (mc/find-map-by-id db collection id) [:keyword1 :keyword2]))))))