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 ## 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 ### One More Cache Implementation
`monger.cache/db-aware-monger-cache-factory` will return a MongoDB-backed `clojure.core.cache` `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. ;; the terms of this license.
;; You must not remove this notice, or any other, from this software. ;; 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. 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.
@ -21,24 +22,17 @@
* http://clojuremongodb.info/articles/querying.html * http://clojuremongodb.info/articles/querying.html
* http://clojuremongodb.info/articles/updating.html * http://clojuremongodb.info/articles/updating.html
* http://clojuremongodb.info/articles/deleting.html * http://clojuremongodb.info/articles/deleting.html
* http://clojuremongodb.info/articles/aggregation.html"} * http://clojuremongodb.info/articles/aggregation.html"
monger.collection
(:refer-clojure :exclude [find remove count drop distinct empty?]) (:refer-clojure :exclude [find remove count drop distinct empty?])
(:import [com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern DBCursor MapReduceCommand MapReduceCommand$OutputType] (:import [com.mongodb Mongo DB DBCollection WriteResult DBObject WriteConcern DBCursor MapReduceCommand MapReduceCommand$OutputType]
[java.util List Map] [java.util List Map]
[clojure.lang IPersistentMap ISeq] [clojure.lang IPersistentMap ISeq]
org.bson.types.ObjectId) org.bson.types.ObjectId)
(:require [monger core result]) (:require monger.core
(:use [monger.conversion])) 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 (extend-protocol ConvertToDBObject
nil nil
(to-db-object [input] (to-db-object [input]
input) nil)
String String
(to-db-object [^String input] (to-db-object [^String input]

View file

@ -105,6 +105,12 @@
[] []
*mongodb-database*) *mongodb-database*)
(defn drop-db
"Drops a database"
([^String db]
(.dropDatabase *mongodb-connection* db))
([^MongoClient conn ^String db]
(.dropDatabase conn db)))
(defmacro with-connection (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: 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) (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." /distinct, /count, /drop, /dropIndexes, and /mapReduce respectively."
([^Map cmd] ([^Map cmd]
(.command ^DB *mongodb-database* ^DBObject (to-db-object 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 events) ;; collection name will be taken from the events-collection var
(defcleaner people \"accounts\") ;; collection name is given (defcleaner people \"accounts\") ;; collection name is given
" "
[entities & coll-name] ([entities]
(let [coll-arg (if coll-name (let [coll-arg (symbol (str entities "-collection"))
(str (first coll-name))
(symbol (str entities "-collection")))
fn-name (symbol (str "purge-" entities))] fn-name (symbol (str "purge-" entities))]
`(defn ~fn-name `(defn ~fn-name
[f#] [f#]
(mc/remove ~coll-arg) (mc/remove ~coll-arg)
(f#) (f#)
(mc/remove ~coll-arg)))) (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]))))))