Support field negation in queries, closes #17

This commit is contained in:
Michael S. Klishin 2012-04-04 23:08:05 +04:00
parent a68d8652e3
commit 6282f41f06
6 changed files with 103 additions and 59 deletions

View file

@ -1,5 +1,19 @@
## Changes between 1.0.0-beta2 and 1.0.0-beta3
### Support for field negation in queries
Previously to load only a subset of document fields with Monger, one had to specify them all. Starting
with 1.0.0-beta3, Monger supports [field negation](http://www.mongodb.org/display/DOCS/Retrieving+a+Subset+of+Fields#RetrievingaSubsetofFields-FieldNegation) feature of MongoDB: it is possible to exclude
certain fields instead.
To do so, pass a map as field selector, with fields that should be omitted set to 0:
``` clojure
;; will retrieve all fields except body
(monger.collection/find-one-map "documents" {:author "John"} {:body 0})
```
### Validateur 1.1.0-beta1
[Validateur](https://github.com/michaelklishin/validateur) dependency has been upgraded to 1.1.0-beta1.

View file

@ -21,10 +21,6 @@
;; Implementation
;;
(defn- fields-to-db-object
[^List fields]
(zipmap fields (repeat 1)))
(definline check-not-nil!
[ref ^String message]
`(when (nil? ~ref)
@ -104,13 +100,13 @@
([^String collection ^Map ref]
(let [^DBCollection coll (.getCollection monger.core/*mongodb-database* collection)]
(.find ^DBCollection coll ^DBObject (to-db-object ref))))
([^String collection ^Map ref ^List fields]
([^String collection ^Map ref fields]
(let [^DBCollection coll (.getCollection monger.core/*mongodb-database* collection)
map-of-fields (fields-to-db-object fields)]
map-of-fields (as-field-selector fields)]
(.find ^DBCollection coll ^DBObject (to-db-object ref) ^DBObject (to-db-object map-of-fields))))
([^DB db ^String collection ^Map ref ^List fields]
([^DB db ^String collection ^Map ref fields]
(let [^DBCollection coll (.getCollection db collection)
map-of-fields (fields-to-db-object fields)]
map-of-fields (as-field-selector fields)]
(.find ^DBCollection coll ^DBObject (to-db-object ref) ^DBObject (to-db-object map-of-fields)))))
(defn ^ISeq find-maps
@ -122,9 +118,9 @@
(map (fn [x] (from-db-object x true)) (find collection)))
([^String collection ^Map ref]
(map (fn [x] (from-db-object x true)) (find collection ref)))
([^String collection ^Map ref ^List fields]
([^String collection ^Map ref fields]
(map (fn [x] (from-db-object x true)) (find collection ref fields)))
([^DB db ^String collection ^Map ref ^List fields]
([^DB db ^String collection ^Map ref fields]
(map (fn [x] (from-db-object x true)) (find db collection ref fields))))
(defn ^ISeq find-seq
@ -133,9 +129,9 @@
(seq (find collection)))
([^String collection ^Map ref]
(seq (find collection ref)))
([^String collection ^Map ref ^List fields]
([^String collection ^Map ref fields]
(seq (find collection ref fields)))
([^DB db ^String collection ^Map ref ^List fields]
([^DB db ^String collection ^Map ref fields]
(seq (find db collection ref fields))))
;;
@ -157,22 +153,22 @@
([^String collection ^Map ref]
(let [^DBCollection coll (.getCollection monger.core/*mongodb-database* collection)]
(.findOne ^DBCollection coll ^DBObject (to-db-object ref))))
([^String collection ^Map ref ^List fields]
([^String collection ^Map ref fields]
(let [^DBCollection coll (.getCollection monger.core/*mongodb-database* collection)
map-of-fields (fields-to-db-object fields)]
map-of-fields (as-field-selector fields)]
(.findOne ^DBCollection coll ^DBObject (to-db-object ref) ^DBObject (to-db-object map-of-fields))))
([^DB db ^String collection ^Map ref ^List fields]
([^DB db ^String collection ^Map ref fields]
(let [^DBCollection coll (.getCollection db collection)
map-of-fields (fields-to-db-object fields)]
map-of-fields (as-field-selector fields)]
(.findOne ^DBCollection coll ^DBObject (to-db-object ref) ^DBObject (to-db-object map-of-fields)))))
(defn ^IPersistentMap find-one-as-map
"Returns a single object converted to Map from this collection matching the query."
([^String collection ^Map ref]
(from-db-object ^DBObject (find-one collection ref) true))
([^String collection ^Map ref ^List fields]
([^String collection ^Map ref fields]
(from-db-object ^DBObject (find-one collection ref fields) true))
([^String collection ^Map ref ^List fields keywordize]
([^String collection ^Map ref fields keywordize]
(from-db-object ^DBObject (find-one collection ref fields) keywordize)))
@ -195,10 +191,10 @@
([^String collection id]
(check-not-nil! id "id must not be nil")
(find-one collection { :_id id }))
([^String collection id ^List fields]
([^String collection id fields]
(check-not-nil! id "id must not be nil")
(find-one collection { :_id id } fields))
([^DB db ^String collection id ^List fields]
([^DB db ^String collection id fields]
(check-not-nil! id "id must not be nil")
(find-one db collection { :_id id } fields)))
@ -207,10 +203,10 @@
([^String collection id]
(check-not-nil! id "id must not be nil")
(from-db-object ^DBObject (find-one-as-map collection { :_id id }) true))
([^String collection id ^List fields]
([^String collection id fields]
(check-not-nil! id "id must not be nil")
(from-db-object ^DBObject (find-one-as-map collection { :_id id } fields) true))
([^String collection id ^List fields keywordize]
([^String collection id fields keywordize]
(check-not-nil! id "id must not be nil")
(from-db-object ^DBObject (find-one-as-map collection { :_id id } fields) keywordize)))

View file

@ -25,10 +25,10 @@
(:import [com.mongodb DBObject BasicDBObject BasicDBList DBCursor]
[clojure.lang IPersistentMap Keyword Ratio]
[java.util List Map Date]
[org.bson.types ObjectId]))
org.bson.types.ObjectId))
(defprotocol ConvertToDBObject
(to-db-object [input] "Converts given piece of Clojure data to BasicDBObject MongoDB Java driver uses"))
(^com.mongodb.DBObject to-db-object [input] "Converts given piece of Clojure data to BasicDBObject MongoDB Java driver uses"))
(extend-protocol ConvertToDBObject
nil
@ -109,7 +109,7 @@
(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."))
(^org.bson.types.ObjectId 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
@ -125,3 +125,19 @@
input))
(defprotocol FieldSelector
(^com.mongodb.DBObject as-field-selector [input] "Converts values to DBObject that can be used to specify a list of document fields (including negation support)"))
(extend-protocol FieldSelector
DBObject
(as-field-selector [^DBObject input]
input)
List
(as-field-selector [^List input]
(to-db-object (zipmap input (repeat 1))))
Object
(as-field-selector [input]
(to-db-object input)))

View file

@ -58,13 +58,9 @@
([^DBCollection coll]
(merge (empty-query) { :collection coll })))
(defn- fields-to-db-object
[^List fields]
(to-db-object (zipmap fields (repeat 1))))
(defn exec
[{ :keys [collection query fields skip limit sort batch-size hint snapshot read-preference keywordize-fields] :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) (as-field-selector fields))
(.limit limit)
(.skip skip)
(.sort (to-db-object sort))

View file

@ -1,10 +1,9 @@
(ns monger.test.conversion
(:require [monger core collection]
[monger.conversion :as cnv])
(:require [monger core collection])
(:import [com.mongodb DBObject BasicDBObject BasicDBList]
[java.util Date Calendar List ArrayList]
[org.bson.types ObjectId])
(:use [clojure.test]))
(:use clojure.test monger.conversion))
;;
@ -13,33 +12,33 @@
(deftest convert-nil-to-dbobject
(let [input nil
output (cnv/to-db-object input)]
output (to-db-object input)]
(is (nil? output))))
(deftest convert-integer-to-dbobject
(let [input 1
output (cnv/to-db-object input)]
output (to-db-object input)]
(is (= input output))))
(deftest convert-float-to-dbobject
(let [input 11.12
output (cnv/to-db-object input)]
output (to-db-object input)]
(is (= input output))))
(deftest convert-rationale-to-dbobject
(let [input 11/2
output (cnv/to-db-object input)]
output (to-db-object input)]
(is (= 5.5 output))))
(deftest convert-string-to-dbobject
(let [input "MongoDB"
output (cnv/to-db-object input)]
output (to-db-object input)]
(is (= input output))))
(deftest convert-map-to-dbobject
(let [input { :int 1, :string "Mongo", :float 22.23 }
output ^DBObject (cnv/to-db-object input)]
output ^DBObject (to-db-object input)]
(is (= 1 (.get output "int")))
(is (= "Mongo" (.get output "string")))
(is (= 22.23 (.get output "float")))))
@ -47,7 +46,7 @@
(deftest convert-nested-map-to-dbobject
(let [input { :int 1, :string "Mongo", :float 22.23, :map { :int 10, :string "Clojure", :float 11.9, :list '(1 "a" :b), :map { :key "value" } } }
output ^DBObject (cnv/to-db-object input)
output ^DBObject (to-db-object input)
inner ^DBObject (.get output "map")]
(is (= 10 (.get inner "int")))
(is (= "Clojure" (.get inner "string")))
@ -59,19 +58,19 @@
;; to obtain _id that was generated. MK.
(deftest convert-dbobject-to-dbobject
(let [input (BasicDBObject.)
output (cnv/to-db-object input)]
output (to-db-object input)]
(is (= input output))))
(deftest convert-java-date-to-dbobject
(let [date (Date.)
input { :int 1, :string "Mongo", :date date }
output ^DBObject (cnv/to-db-object input)]
output ^DBObject (to-db-object input)]
(is (= date (.get output "date")))))
(deftest convert-java-calendar-instance-to-dbobject
(let [date (Calendar/getInstance)
input { :int 1, :string "Mongo", :date date }
output ^DBObject (cnv/to-db-object input)]
output ^DBObject (to-db-object input)]
(is (= date (.get output "date")))))
@ -82,16 +81,16 @@
;;
(deftest convert-nil-from-db-object
(is (nil? (cnv/from-db-object nil false)))
(is (nil? (cnv/from-db-object nil true))))
(is (nil? (from-db-object nil false)))
(is (nil? (from-db-object nil true))))
(deftest convert-integer-from-dbobject
(is (= 2 (cnv/from-db-object 2 false)))
(is (= 2 (cnv/from-db-object 2 true))))
(is (= 2 (from-db-object 2 false)))
(is (= 2 (from-db-object 2 true))))
(deftest convert-float-from-dbobject
(is (= 3.3 (cnv/from-db-object 3.3 false)))
(is (= 3.3 (cnv/from-db-object 3.3 true))))
(is (= 3.3 (from-db-object 3.3 false)))
(is (= 3.3 (from-db-object 3.3 true))))
(deftest convert-flat-db-object-to-map-without-keywordizing
(let [name "Michael"
@ -99,7 +98,7 @@
input (doto (BasicDBObject.)
(.put "name" name)
(.put "age" age))
output (cnv/from-db-object input false)]
output (from-db-object input false)]
(is (= (output { "name" name, "age" age })))
(is (= (output "name") name))
(is (nil? (output :name)))
@ -112,7 +111,7 @@
input (doto (BasicDBObject.)
(.put "name" name)
(.put "age" age))
output (cnv/from-db-object input true)]
output (from-db-object input true)]
(is (= (output { :name name, :age age })))
(is (= (output :name) name))
(is (nil? (output "name")))
@ -131,7 +130,7 @@
input (doto (BasicDBObject.)
(.put "_id" did)
(.put "nested" nested))
output (cnv/from-db-object input false)]
output (from-db-object input false)]
(is (= (output "_id") did))
(is (= (-> output (get "nested") (get "int")) 101))
(is (= (-> output (get "nested") (get "list")) ["red" "green" "blue"]))
@ -145,5 +144,18 @@
(deftest test-conversion-to-object-id
(let [output (ObjectId. "4efb39370364238a81020502")]
(is (= output (cnv/to-object-id "4efb39370364238a81020502")))
(is (= output (cnv/to-object-id output)))))
(is (= output (to-object-id "4efb39370364238a81020502")))
(is (= output (to-object-id output)))))
;;
;; Field selector coercion
;;
(deftest test-field-selector-coercion
(are [i o] (is (= (from-db-object (as-field-selector i) true) o))
[:a :b :c] {:a 1 :b 1 :c 1}
'(:a :b :c) {:a 1 :b 1 :c 1}
{:a 1 :b 1 :c 1} {:a 1 :b 1 :c 1}
{"a" 1 "b" 1 "c" 1} {:a 1 :b 1 :c 1}
{:comments 0} {:comments 0}))

View file

@ -54,21 +54,31 @@
(let [collection "docs"
doc-id (monger.util/random-uuid)
doc { :data-store "MongoDB", :language "Clojure", :_id doc-id }
fields [:language]
_ (mgcol/insert collection doc)
loaded (mgcol/find-one collection { :language "Clojure" } fields)]
loaded (mgcol/find-one collection { :language "Clojure" } [:language])]
(is (nil? (.get ^DBObject loaded "data-store")))
(is (= doc-id (monger.util/get-id loaded)))
(is (= "Clojure" (.get ^DBObject loaded "language")))))
(deftest find-one-partial-document-using-field-negation-when-collection-has-matches
(let [collection "docs"
doc-id (monger.util/random-uuid)
doc { :data-store "MongoDB", :language "Clojure", :_id doc-id }
_ (mgcol/insert collection doc)
^DBObject loaded (mgcol/find-one collection { :language "Clojure" } {:data-store 0 :_id 0})]
(is (nil? (.get loaded "data-store")))
(is (nil? (.get loaded "_id")))
(is (nil? (monger.util/get-id loaded)))
(is (= "Clojure" (.get loaded "language")))))
(deftest find-one-partial-document-as-map-when-collection-has-matches
(let [collection "docs"
doc-id (monger.util/random-uuid)
doc { :data-store "MongoDB", :language "Clojure", :_id doc-id }
fields [:data-store]]
doc { :data-store "MongoDB", :language "Clojure", :_id doc-id }]
(mgcol/insert collection doc)
(is (= { :data-store "MongoDB", :_id doc-id } (mgcol/find-one-as-map collection { :language "Clojure" } fields)))))
(is (= { :data-store "MongoDB", :_id doc-id } (mgcol/find-one-as-map collection { :language "Clojure" } [:data-store])))))
(deftest find-one-partial-document-as-map-when-collection-has-matches-with-keywordize