diff --git a/ChangeLog.md b/ChangeLog.md index 9495e32..ac55dbd 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -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. diff --git a/src/monger/collection.clj b/src/monger/collection.clj index 0cb26d6..ce6afca 100644 --- a/src/monger/collection.clj +++ b/src/monger/collection.clj @@ -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))) diff --git a/src/monger/conversion.clj b/src/monger/conversion.clj index 76f41bb..d5176d0 100644 --- a/src/monger/conversion.clj +++ b/src/monger/conversion.clj @@ -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))) diff --git a/src/monger/query.clj b/src/monger/query.clj index 0b7a64c..a25a68d 100644 --- a/src/monger/query.clj +++ b/src/monger/query.clj @@ -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)) diff --git a/test/monger/test/conversion.clj b/test/monger/test/conversion.clj index dfa960e..945f4f5 100644 --- a/test/monger/test/conversion.clj +++ b/test/monger/test/conversion.clj @@ -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})) diff --git a/test/monger/test/regular_finders.clj b/test/monger/test/regular_finders.clj index 79f5753..f5af0eb 100644 --- a/test/monger/test/regular_finders.clj +++ b/test/monger/test/regular_finders.clj @@ -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