Initial work on monger.multi.collection

This commit is contained in:
Michael Klishin 2013-04-19 00:43:27 +04:00
parent 48be8602e3
commit 4ac8f8330a
8 changed files with 291 additions and 33 deletions

View file

@ -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`

View file

@ -9,7 +9,8 @@
;; 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
(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.
@ -21,24 +22,17 @@
* 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/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))))
;;

View file

@ -0,0 +1,11 @@
(ns monger.constraints)
;;
;; API
;;
(definline check-not-nil!
[ref ^String message]
`(when (nil? ~ref)
(throw (IllegalArgumentException. ~message))))

View file

@ -42,7 +42,7 @@
(extend-protocol ConvertToDBObject
nil
(to-db-object [input]
input)
nil)
String
(to-db-object [^String input]

View file

@ -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)))

View file

@ -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))))

View file

@ -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")))
([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)))))

View file

@ -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]))))))