This commit is contained in:
Oleksandr Petrov 2012-01-25 21:56:09 +01:00
commit 9af8a13ea9
12 changed files with 419 additions and 64 deletions

View file

@ -1,6 +1,7 @@
# Monger # Monger
Monger is an idiomatic Clojure wrapper around MongoDB Java driver. Monger is an idiomatic Clojure wrapper around MongoDB Java driver. It offers powerful expressive query DSL, strives to support
every MongoDB 2.0+ feature and is well maintained.
## Project Goals ## Project Goals
@ -17,11 +18,18 @@ wanted a client that will
* Learn from other clients like the Java and Ruby ones. * Learn from other clients like the Java and Ruby ones.
* Target Clojure 1.3.0 and later from the ground up. * Target Clojure 1.3.0 and later from the ground up.
## Usage ## Usage
We are working on documentation guides & examples site for the 1.0 release. Please refer to the test suite for code examples. We are working on documentation guides & examples site for the 1.0 release. In the meantime, please refer to the [test suite](https://github.com/michaelklishin/monger/tree/master/test/monger/test) for code examples.
Here is what monger.query DSL looks like right now:
## Powerful Query DSL
Every application that works with data stores has to query them. As a consequence, having an expressive powerful query DSL is a must
for client libraries like Monger.
Here is what monger.query DSL feels like:
``` clojure ``` clojure
(with-collection "docs" (with-collection "docs"
@ -34,12 +42,38 @@ Here is what monger.query DSL looks like right now:
(snapshot)) (snapshot))
``` ```
More code examples can be found in our test suite. It is easy to add new DSL elements, for example, adding pagination took literally less than 10 lines of Clojure code. Here is what
it looks like:
``` clojure
(with-collection coll
(find {})
(paginate :page 1 :per-page 3)
(sort { :title 1 })
(read-preference ReadPreference/PRIMARY))
```
Query DSL supports composition, too:
``` clojure
(let
[top3 (partial-query (limit 3))
by-population-desc (partial-query (sort { :population -1 }))
result (with-collection coll
(find {})
(merge top3)
(merge by-population-desc))]
;; ...
)
```
More code examples can be found [in our test suite](https://github.com/michaelklishin/monger/tree/master/test/monger/test).
## This is a Work In Progress ## This is a Work In Progress
Core Monger APIs are stabilized but it is still a work in progress. Keep that in mind. 1.0 will be released in late 2011. Core Monger APIs are stabilized but it is still a work in progress. Keep that in mind. 1.0 will be released in early 2012
together with documentation guides and dedicated website.
## Artifacts ## Artifacts
@ -48,7 +82,7 @@ Snapshot artifacts are [released to Clojars](https://clojars.org/com.novemberain
With Leiningen: With Leiningen:
[com.novemberain/monger "0.11.0-SNAPSHOT"] [com.novemberain/monger "1.0.0-SNAPSHOT"]
With Maven: With Maven:
@ -56,7 +90,7 @@ With Maven:
<dependency> <dependency>
<groupId>com.novemberain</groupId> <groupId>com.novemberain</groupId>
<artifactId>monger</artifactId> <artifactId>monger</artifactId>
<version>0.11.0-SNAPSHOT</version> <version>1.0.0-SNAPSHOT</version>
</dependency> </dependency>
@ -65,7 +99,7 @@ With Maven:
[![Continuous Integration status](https://secure.travis-ci.org/michaelklishin/monger.png)](http://travis-ci.org/michaelklishin/monger) [![Continuous Integration status](https://secure.travis-ci.org/michaelklishin/monger.png)](http://travis-ci.org/michaelklishin/monger)
CI is hosted by [travis-ci.org](http://travis-ci.org) CI is hosted by [travis-ci.org](http://travis-ci.org).

View file

@ -8,7 +8,7 @@
;; 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 monger.collection (ns monger.collection
(:refer-clojure :exclude [find remove count drop distinct]) (: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])

View file

@ -22,9 +22,10 @@
;; THE SOFTWARE. ;; THE SOFTWARE.
(ns monger.conversion (ns monger.conversion
(:import (com.mongodb DBObject BasicDBObject BasicDBList DBCursor) (:import [com.mongodb DBObject BasicDBObject BasicDBList DBCursor]
(clojure.lang IPersistentMap Keyword) [clojure.lang IPersistentMap Keyword]
(java.util List Map))) [java.util List Map Date]
[org.bson.types ObjectId]))
(defprotocol ConvertToDBObject (defprotocol ConvertToDBObject
(to-db-object [input] "Converts given piece of Clojure data to BasicDBObject MongoDB Java driver uses")) (to-db-object [input] "Converts given piece of Clojure data to BasicDBObject MongoDB Java driver uses"))
@ -101,3 +102,22 @@
(assoc m k (from-db-object v false)))) (assoc m k (from-db-object v false))))
{} (reverse pairs))) {} (reverse pairs)))
(defprotocol ConvertToObjectId
(to-object-id [input] "Instantiates ObjectId from input unless the input itself is an ObjectId instance. In that case, returns input as is."))
(extend-protocol ConvertToObjectId
String
(to-object-id [^String input]
(ObjectId. input))
Date
(to-object-id [^Date input]
(ObjectId. input))
ObjectId
(to-object-id [^ObjectId input]
input))

View file

@ -14,8 +14,9 @@
monger.core monger.core
(:refer-clojure :exclude [count]) (:refer-clojure :exclude [count])
(:use [monger.conversion]) (:use [monger.conversion])
(:import (com.mongodb Mongo DB WriteConcern DBObject DBCursor) (:import [com.mongodb Mongo DB WriteConcern DBObject DBCursor CommandResult]
(java.util Map))) [com.mongodb.gridfs GridFS]
[java.util Map]))
;; ;;
;; Defaults ;; Defaults
@ -28,6 +29,7 @@
(declare ^:dynamic ^DB *mongodb-database*) (declare ^:dynamic ^DB *mongodb-database*)
(def ^:dynamic ^WriteConcern *mongodb-write-concern* WriteConcern/SAFE) (def ^:dynamic ^WriteConcern *mongodb-write-concern* WriteConcern/SAFE)
(declare ^:dynamic ^GridFS *mongodb-gridfs*)
;; ;;
;; API ;; API
@ -75,33 +77,37 @@
`(binding [*mongodb-database* ~db] `(binding [*mongodb-database* ~db]
(do ~@body))) (do ~@body)))
(defmacro with-gridfs
[fs & body]
`(binding [*mongodb-gridfs* ~fs]
(do ~@body)))
(defn connect! (defn connect!
"Connect to MongoDB, save connection to *mongodb-connection* dynamic variable" "Connect to MongoDB, store connection in the *mongodb-connection* var"
^Mongo [& args] ^Mongo [& args]
(def ^:dynamic *mongodb-connection* (apply connect args))) (def ^:dynamic *mongodb-connection* (apply connect args)))
(defn set-db! (defn set-db!
"Set dynamic *mongodb-database* variable to given :db" "Sets *mongodb-database* var to given db, updates *mongodb-gridfs* var state. Recommended to be used for
applications that only use one database."
[db] [db]
(def ^:dynamic *mongodb-database* db)) (def ^:dynamic *mongodb-database* db)
(def ^:dynamic *mongodb-gridfs* (GridFS. db)))
(defn set-default-write-concern! (defn set-default-write-concern!
[wc] [wc]
"Set dynamic *mongodb-write-concert* to :wc "Set *mongodb-write-concert* var to :wc
We recommend to use WriteConcern/SAFE by default to make sure your data was written." Unlike the official Java driver, Monger uses WriteConcern/SAFE by default. We think defaults should be safe first
and WebScale fast second."
(def ^:dynamic *mongodb-write-concern* wc)) (def ^:dynamic *mongodb-write-concern* wc))
(defn command (defn ^CommandResult command
"Available commands (please check MongoDB documentation for a complete list of commands for particular DB version. "Runs a database command (please check MongoDB documentation for the complete list of commands). Some common commands
Returns CommandResult. are:
Use (.ok result) to get response status.
It implements AbstractMap interface, so you can access it's internals:
(get (monger.core/command { :collstats \"things\") \"ns\")) ;; => monger-test.things
{ :buildinfo 1 } returns version number and build information about the current MongoDB server, should be executed via admin DB. { :buildinfo 1 } returns version number and build information about the current MongoDB server, should be executed via admin DB.
@ -162,7 +168,7 @@
(extend-protocol Countable (extend-protocol Countable
DBCursor DBCursor
(count [^com.mongodb.DBCursor this] (count [^DBCursor this]
(.count this))) (.count this)))
(defn ^DBObject get-last-error (defn ^DBObject get-last-error
@ -184,5 +190,3 @@
(.getLastError ^DB database w wtimeout fsync)) (.getLastError ^DB database w wtimeout fsync))
([^DB database ^WriteConcern write-concern] ([^DB database ^WriteConcern write-concern]
(.getLastError ^DB database write-concern))) (.getLastError ^DB database write-concern)))

100
src/monger/gridfs.clj Normal file
View file

@ -0,0 +1,100 @@
(ns monger.gridfs
(:refer-clojure :exclude [remove find])
(:require [monger.core]
[clojure.java.io :as io])
(:use [monger.conversion])
(:import [com.mongodb DBObject]
[com.mongodb.gridfs GridFS GridFSInputFile]
[java.io InputStream File]))
;;
;; Implementation
;;
(def
^{:doc "Type object for a Java primitive byte array."
:private true
}
byte-array-type (class (make-array Byte/TYPE 0)))
;; ...
;;
;; API
;;
(defn remove
([]
(remove {}))
([query]
(.remove ^GridFS monger.core/*mongodb-gridfs* ^DBObject (to-db-object query))))
(defn remove-all
[]
(remove {}))
(defn all-files
([]
(.getFileList ^GridFS monger.core/*mongodb-gridfs*))
([query]
(.getFileList ^GridFS monger.core/*mongodb-gridfs* query)))
(defprotocol GridFSInputFileFactory
(^GridFSInputFile make-input-file [input] "Makes GridFSInputFile out of given input"))
(extend byte-array-type
GridFSInputFileFactory
{ :make-input-file (fn [^bytes input]
(.createFile ^GridFS monger.core/*mongodb-gridfs* input)) })
(extend-protocol GridFSInputFileFactory
String
(make-input-file [^String input]
(.createFile ^GridFS monger.core/*mongodb-gridfs* ^InputStream (io/make-input-stream input { :encoding "UTF-8" })))
File
(make-input-file [^File input]
(.createFile ^GridFS monger.core/*mongodb-gridfs* ^InputStream (io/make-input-stream input { :encoding "UTF-8" })))
InputStream
(make-input-file [^InputStream input]
(.createFile ^GridFS monger.core/*mongodb-gridfs* ^InputStream input)))
(defmacro store
[^GridFSInputFile input & body]
`(let [^GridFSInputFile f# (doto ~input ~@body)]
(.save f# GridFS/DEFAULT_CHUNKSIZE)
(from-db-object f# true)))
(defprotocol Finders
(find [input] "Finds multiple files using given input (an ObjectId, filename or query)")
(find-one [input] "Finds one file using given input (an ObjectId, filename or query)"))
(extend-protocol Finders
String
(find [^String input]
(vec (.find ^GridFS monger.core/*mongodb-gridfs* input)))
(find-one [^String input]
(.findOne ^GridFS monger.core/*mongodb-gridfs* input))
org.bson.types.ObjectId
(find-one [^org.bson.types.ObjectId input]
(.findOne ^GridFS monger.core/*mongodb-gridfs* input))
DBObject
(find [^DBObject input]
(vec (.find ^GridFS monger.core/*mongodb-gridfs* input)))
(find-one [^DBObject input]
(.findOne ^GridFS monger.core/*mongodb-gridfs* input))
clojure.lang.PersistentArrayMap
(find [^clojure.lang.PersistentArrayMap input]
(find (to-db-object input))))

View file

@ -2,7 +2,7 @@
(:refer-clojure :exclude [select find sort]) (:refer-clojure :exclude [select find sort])
(:require [monger.core] (:require [monger.core]
[monger.internal pagination]) [monger.internal pagination])
(:import [com.mongodb DB DBCollection DBObject DBCursor] (:import [com.mongodb DB DBCollection DBObject DBCursor ReadPreference]
[java.util List]) [java.util List])
(:use [monger conversion operators])) (:use [monger conversion operators]))
@ -34,34 +34,36 @@
;; deleted during the query, it may or may not be returned, even with snapshot mode). Note that short query responses ;; deleted during the query, it may or may not be returned, even with snapshot mode). Note that short query responses
;; (less than 1MB) are always effectively snapshotted. Currently, snapshot mode may not be used with sorting or explicit hints. ;; (less than 1MB) are always effectively snapshotted. Currently, snapshot mode may not be used with sorting or explicit hints.
(defn empty-query (defn empty-query
[^DBCollection coll] ([]
{ {
:collection coll :query {}
:query {} :sort {}
:sort {} :fields []
:fields [] :skip 0
:skip 0 :limit 0
:limit 0 :batch-size 256
:batch-size 256 :hint nil
:hint nil :snapshot false
:snapshot false })
}) ([^DBCollection coll]
(merge (empty-query) { :collection coll })))
(defn- fields-to-db-object (defn- fields-to-db-object
[^List fields] [^List fields]
(to-db-object (zipmap fields (repeat 1)))) (to-db-object (zipmap fields (repeat 1))))
(defn exec (defn exec
[{ :keys [collection query fields skip limit sort batch-size hint snapshot] :or { limit 0 batch-size 256 skip 0 } }] [{ :keys [collection query fields skip limit sort batch-size hint snapshot read-preference] :or { limit 0 batch-size 256 skip 0 } }]
(let [cursor (doto ^DBCursor (.find ^DBCollection collection (to-db-object query) (fields-to-db-object fields)) (let [cursor (doto ^DBCursor (.find ^DBCollection collection (to-db-object query) (fields-to-db-object fields))
(.limit limit) (.limit limit)
(.skip skip) (.skip skip)
(.sort (to-db-object sort)) (.sort (to-db-object sort))
(.batchSize batch-size) (.batchSize batch-size)
(.hint ^DBObject (to-db-object hint)) (.hint ^DBObject (to-db-object hint)))]
)] (when snapshot
(if snapshot
(.snapshot cursor)) (.snapshot cursor))
(when read-preference
(.setReadPreference cursor read-preference))
(map (fn [x] (from-db-object x true)) (map (fn [x] (from-db-object x true))
(seq cursor)))) (seq cursor))))
@ -101,6 +103,10 @@
[m] [m]
(merge m { :snapshot true })) (merge m { :snapshot true }))
(defn read-preference
[m ^ReadPreference rp]
(merge m { :read-preference rp }))
(defn paginate (defn paginate
[m & { :keys [page per-page] :or { page 1 per-page 10 } }] [m & { :keys [page per-page] :or { page 1 per-page 10 } }]
(merge m { :limit per-page :skip (monger.internal.pagination/offset-for page per-page) })) (merge m { :limit per-page :skip (monger.internal.pagination/offset-for page per-page) }))
@ -112,3 +118,7 @@
~coll)] ~coll)]
(let [query# (-> (empty-query *query-collection*) ~@body)] (let [query# (-> (empty-query *query-collection*) ~@body)]
(exec query#)))) (exec query#))))
(defmacro partial-query
[& body]
`(-> {} ~@body))

View file

@ -3,9 +3,7 @@
(ns monger.test.atomic-modifiers (ns monger.test.atomic-modifiers
(:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure] (:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure]
[org.bson.types ObjectId] [org.bson.types ObjectId]
[java.util Date] [java.util Date])
)
(:require [monger core util] (:require [monger core util]
[monger.collection :as mgcol] [monger.collection :as mgcol]
[monger.result :as mgres] [monger.result :as mgres]

View file

@ -130,9 +130,9 @@
(deftest find-one-full-document-when-collection-has-matches (deftest find-one-full-document-when-collection-has-matches
(let [collection "docs" (let [collection "docs"
doc-id (monger.util/random-uuid) doc-id (monger.util/random-uuid)
doc { :data-store "MongoDB", :language "Clojure", :_id doc-id }] doc { :data-store "MongoDB", :language "Clojure", :_id doc-id }
(mgcol/insert collection doc) _ (mgcol/insert collection doc)
(def ^DBObject found-one (mgcol/find-one collection { :language "Clojure" })) found-one (mgcol/find-one collection { :language "Clojure" })]
(is (= (:_id doc) (monger.util/get-id found-one))) (is (= (:_id doc) (monger.util/get-id found-one)))
(is (= (mgcnv/from-db-object found-one true) doc)) (is (= (mgcnv/from-db-object found-one true) doc))
(is (= (mgcnv/to-db-object doc) found-one)))) (is (= (mgcnv/to-db-object doc) found-one))))
@ -150,9 +150,9 @@
(let [collection "docs" (let [collection "docs"
doc-id (monger.util/random-uuid) doc-id (monger.util/random-uuid)
doc { :data-store "MongoDB", :language "Clojure", :_id doc-id } doc { :data-store "MongoDB", :language "Clojure", :_id doc-id }
fields [:language]] fields [:language]
(mgcol/insert collection doc) _ (mgcol/insert collection doc)
(def ^DBObject loaded (mgcol/find-one collection { :language "Clojure" } fields)) loaded (mgcol/find-one collection { :language "Clojure" } fields)]
(is (nil? (.get ^DBObject loaded "data-store"))) (is (nil? (.get ^DBObject loaded "data-store")))
(is (= doc-id (monger.util/get-id loaded))) (is (= doc-id (monger.util/get-id loaded)))
(is (= "Clojure" (.get ^DBObject loaded "language"))))) (is (= "Clojure" (.get ^DBObject loaded "language")))))

View file

@ -2,7 +2,8 @@
(:require [monger core collection] (:require [monger core collection]
[monger.conversion :as cnv]) [monger.conversion :as cnv])
(:import [com.mongodb DBObject BasicDBObject BasicDBList] (:import [com.mongodb DBObject BasicDBObject BasicDBList]
[java.util Date Calendar List ArrayList]) [java.util Date Calendar List ArrayList]
[org.bson.types ObjectId])
(:use [clojure.test])) (:use [clojure.test]))
@ -131,3 +132,13 @@
(is (= (-> output (get "nested") (get "list")) ["red" "green" "blue"])) (is (= (-> output (get "nested") (get "list")) ["red" "green" "blue"]))
(is (= (-> output (get "nested") (get "dblist")) [0 1])))) (is (= (-> output (get "nested") (get "dblist")) [0 1]))))
;;
;; ObjectId coercion
;;
(deftest test-conversion-to-object-id
(let [output (ObjectId. "4efb39370364238a81020502")]
(is (= output (cnv/to-object-id "4efb39370364238a81020502")))
(is (= output (cnv/to-object-id output)))))

130
test/monger/test/gridfs.clj Normal file
View file

@ -0,0 +1,130 @@
(ns monger.test.gridfs
(:refer-clojure :exclude [count remove find])
(:use [clojure.test]
[monger.core :only [count]]
[monger.test.fixtures]
[monger operators conversion]
[monger.gridfs :only (store make-input-file)])
(:require [monger.gridfs :as gridfs]
[monger.test.helper :as helper]
[clojure.java.io :as io])
(:import [java.io InputStream File FileInputStream]
[com.mongodb.gridfs GridFS GridFSInputFile GridFSDBFile]))
(defn purge-gridfs
[f]
(gridfs/remove-all)
(f)
(gridfs/remove-all))
(use-fixtures :each purge-gridfs)
(helper/connect!)
(deftest test-storing-files-to-gridfs-using-relative-fs-paths
(let [input "./test/resources/mongo/js/mapfun1.js"]
(is (= 0 (count (gridfs/all-files))))
(store (make-input-file input)
(.setFilename "monger.test.gridfs.file1")
(.setContentType "application/octet-stream"))
(is (= 1 (count (gridfs/all-files))))))
(deftest test-storing-files-to-gridfs-using-file-instances
(let [input (io/as-file "./test/resources/mongo/js/mapfun1.js")]
(is (= 0 (count (gridfs/all-files))))
(store (make-input-file input)
(.setFilename "monger.test.gridfs.file2")
(.setContentType "application/octet-stream"))
(is (= 1 (count (gridfs/all-files))))))
(deftest test-storing-bytes-to-gridfs
(let [input (.getBytes "A string")]
(is (= 0 (count (gridfs/all-files))))
(store (make-input-file input)
(.setFilename "monger.test.gridfs.file3")
(.setContentType "application/octet-stream"))
(is (= 1 (count (gridfs/all-files))))))
(deftest test-storing-files-to-gridfs-using-absolute-fs-paths
(let [tmp-file (File/createTempFile "monger.test.gridfs" "test-storing-files-to-gridfs-using-absolute-fs-paths")
_ (spit tmp-file "Some content")
input (.getAbsolutePath tmp-file)]
(is (= 0 (count (gridfs/all-files))))
(store (make-input-file input)
(.setFilename "monger.test.gridfs.file4")
(.setContentType "application/octet-stream"))
(is (= 1 (count (gridfs/all-files))))))
(deftest test-storing-files-to-gridfs-using-input-stream
(let [tmp-file (File/createTempFile "monger.test.gridfs" "test-storing-files-to-gridfs-using-input-stream")
_ (spit tmp-file "Some other content")]
(is (= 0 (count (gridfs/all-files))))
(store (make-input-file (FileInputStream. tmp-file))
(.setFilename "monger.test.gridfs.file4b")
(.setContentType "application/octet-stream"))
(is (= 1 (count (gridfs/all-files))))))
(deftest test-finding-individual-files-on-gridfs
(let [input "./test/resources/mongo/js/mapfun1.js"
ct "binary/octet-stream"
filename "monger.test.gridfs.file5"
md5 "14a09deabb50925a3381315149017bbd"
stored (store (make-input-file input)
(.setFilename filename)
(.setContentType ct))]
(is (= 1 (count (gridfs/all-files))))
(is (:_id stored))
(is (:uploadDate stored))
(is (= 62 (:length stored)))
(is (= md5 (:md5 stored)))
(is (= filename (:filename stored)))
(is (= ct (:contentType stored)))
(are [a b] (is (= a (:md5 (from-db-object (gridfs/find-one b) true))))
md5 (:_id stored)
md5 filename
md5 (to-db-object { :md5 md5 }))))
(deftest test-finding-multiple-files-on-gridfs
(let [input "./test/resources/mongo/js/mapfun1.js"
ct "binary/octet-stream"
md5 "14a09deabb50925a3381315149017bbd"
stored1 (store (make-input-file input)
(.setFilename "monger.test.gridfs.file6")
(.setContentType ct))
stored2 (store (make-input-file input)
(.setFilename "monger.test.gridfs.file7")
(.setContentType ct))
list1 (gridfs/find "monger.test.gridfs.file6")
list2 (gridfs/find "monger.test.gridfs.file7")
list3 (gridfs/find "888000___.monger.test.gridfs.file")
list4 (gridfs/find { :md5 md5 })]
(is (= 2 (count (gridfs/all-files))))
(are [a b] (is (= (map #(.get ^GridFSDBFile % "_id") a)
(map :_id b)))
list1 [stored1]
list2 [stored2]
list3 []
list4 [stored1 stored2])))
(deftest test-removing-multiple-files-from-gridfs
(let [input "./test/resources/mongo/js/mapfun1.js"
ct "binary/octet-stream"
md5 "14a09deabb50925a3381315149017bbd"
stored1 (store (make-input-file input)
(.setFilename "monger.test.gridfs.file8")
(.setContentType ct))
stored2 (store (make-input-file input)
(.setFilename "monger.test.gridfs.file9")
(.setContentType ct))]
(is (= 2 (count (gridfs/all-files))))
(gridfs/remove { :filename "monger.test.gridfs.file8" })
(is (= 1 (count (gridfs/all-files))))
(gridfs/remove { :md5 md5 })
(is (= 0 (count (gridfs/all-files))))))

View file

@ -6,7 +6,8 @@
(:import [org.joda.time DateTime ReadableInstant] (:import [org.joda.time DateTime ReadableInstant]
[org.joda.time.format ISODateTimeFormat] [org.joda.time.format ISODateTimeFormat]
[java.io StringWriter PrintWriter] [java.io StringWriter PrintWriter]
[org.bson.types ObjectId]) [org.bson.types ObjectId]
[com.mongodb DBObject])
(:require [clojure.data.json :as json] (:require [clojure.data.json :as json]
[clj-time.core :as t])) [clj-time.core :as t]))

View file

@ -2,7 +2,7 @@
(ns monger.test.querying (ns monger.test.querying
(:refer-clojure :exclude [select find sort]) (:refer-clojure :exclude [select find sort])
(:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure] (:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure ReadPreference]
[org.bson.types ObjectId] [org.bson.types ObjectId]
[java.util Date]) [java.util Date])
(:require [monger core util] (:require [monger core util]
@ -11,7 +11,8 @@
[monger.test.helper :as helper]) [monger.test.helper :as helper])
(:use [clojure.test] (:use [clojure.test]
[monger.test.fixtures] [monger.test.fixtures]
[monger conversion query operators])) [monger conversion query operators joda-time]
[clj-time.core :only [date-time]]))
(helper/connect!) (helper/connect!)
@ -77,7 +78,7 @@
;; < ($lt), <= ($lte), > ($gt), >= ($gte) ;; < ($lt), <= ($lte), > ($gt), >= ($gte)
(deftest query-using-dsl-and-$lt-operator (deftest query-using-dsl-and-$lt-operator-with-integers
(let [coll "docs" (let [coll "docs"
doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 } doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 }
doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 } doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 }
@ -86,7 +87,21 @@
lt-result (with-collection "docs" lt-result (with-collection "docs"
(find { :inception_year { $lt 2000 } }) (find { :inception_year { $lt 2000 } })
(limit 2))] (limit 2))]
(is (= [doc2] lt-result)))) (is (= [doc2] (vec lt-result)))))
(deftest query-using-dsl-and-$lt-operator-with-dates
(let [coll "docs"
;; these rely on monger.joda-time being loaded. MK.
doc1 { :language "Clojure" :_id (ObjectId.) :inception_year (date-time 2006 1 1) }
doc2 { :language "Java" :_id (ObjectId.) :inception_year (date-time 1992 1 2) }
doc3 { :language "Scala" :_id (ObjectId.) :inception_year (date-time 2003 3 3) }
_ (mgcol/insert-batch coll [doc1 doc2])
lt-result (with-collection "docs"
(find { :inception_year { $lt (date-time 2000 1 2) } })
(limit 2))]
(is (= (map :_id [doc2])
(map :_id (vec lt-result))))))
@ -106,8 +121,22 @@
(find { :inception_year { "$gt" 2002 } }) (find { :inception_year { "$gt" 2002 } })
(limit 1) (limit 1)
(sort { :inception_year -1 }))) (sort { :inception_year -1 })))
(doc1 (with-collection coll (doc1 (with-collection coll
(find { :inception_year { "$gte" 2006 } })))))) (find { :inception_year { "$gte" 2006 } }))))))
(deftest query-using-$gt-$lt-$gte-$lte-operators-using-dsl-composition
(let [coll "docs"
doc1 { :language "Clojure" :_id (ObjectId.) :inception_year 2006 }
doc2 { :language "Java" :_id (ObjectId.) :inception_year 1992 }
doc3 { :language "Scala" :_id (ObjectId.) :inception_year 2003 }
srt (-> {}
(limit 1)
(sort { :inception_year -1 }))
_ (mgcol/insert-batch coll [doc1 doc2 doc3])]
(is (= [doc1] (with-collection coll
(find { :inception_year { "$gt" 2002 } })
(merge srt))))))
;; $all ;; $all
@ -189,7 +218,8 @@
result1 (with-collection coll result1 (with-collection coll
(find {}) (find {})
(paginate :page 1 :per-page 3) (paginate :page 1 :per-page 3)
(sort { :title 1 })) (sort { :title 1 })
(read-preference ReadPreference/PRIMARY))
result2 (with-collection coll result2 (with-collection coll
(find {}) (find {})
(paginate :page 2 :per-page 3) (paginate :page 2 :per-page 3)
@ -201,3 +231,20 @@
(is (= [doc1 doc5 doc7] result1)) (is (= [doc1 doc5 doc7] result1))
(is (= [doc2 doc6 doc4] result2)) (is (= [doc2 doc6 doc4] result2))
(is (= [doc3] result3)))) (is (= [doc3] result3))))
(deftest combined-querying-dsl-example1
(let [coll "docs"
ma-doc { :_id (ObjectId.) :name "Massachusetts" :iso "MA" :population 6547629 :joined_in 1788 :capital "Boston" }
de-doc { :_id (ObjectId.) :name "Delaware" :iso "DE" :population 897934 :joined_in 1787 :capital "Dover" }
ny-doc { :_id (ObjectId.) :name "New York" :iso "NY" :population 19378102 :joined_in 1788 :capital "Albany" }
ca-doc { :_id (ObjectId.) :name "California" :iso "CA" :population 37253956 :joined_in 1850 :capital "Sacramento" }
tx-doc { :_id (ObjectId.) :name "Texas" :iso "TX" :population 25145561 :joined_in 1845 :capital "Austin" }
top3 (partial-query (limit 3))
by-population-desc (partial-query (sort { :population -1 }))
_ (mgcol/insert-batch coll [ma-doc de-doc ny-doc ca-doc tx-doc])
result (with-collection coll
(find {})
(merge top3)
(merge by-population-desc))]
(is (= result [ca-doc tx-doc ny-doc]))))