Merge branch 'master' into query-dsl

Conflicts:
	src/monger/operators.clj
This commit is contained in:
Michael S. Klishin 2011-11-14 15:23:47 +04:00
commit 0536244dce
4 changed files with 233 additions and 58 deletions

View file

@ -1,29 +1,131 @@
(ns monger.operators) (ns monger.operators)
(defmacro defoperator (defmacro ^{:private true} defoperator
[operator] [operator]
(let [op# (str operator) (let [op# (str operator)
op-sym# (symbol op#)] op-sym# (symbol op#)]
`(def ~op-sym# ~op#))) `(def ~op-sym# ~op#)))
;;
;; QUERY OPERATORS
;;
;; $gt is "greater than" comparator
;; $gte is "greater than or equals" comparator
;; $gt is "less than" comparator
;; $lte is "less than or equals" comparator
;;
;; EXAMPLES:
;; (monger.collection/find "libraries" { :users { $gt 10 } })
;; (monger.collection/find "libraries" { :users { $gte 10 } })
;; (monger.collection/find "libraries" { :users { $lt 10 } })
;; (monger.collection/find "libraries" { :users { $lte 10 } })
(defoperator $gt) (defoperator $gt)
(defoperator $gte) (defoperator $gte)
(defoperator $lt) (defoperator $lt)
(defoperator $lte) (defoperator $lte)
;; $all matches all values in the array
;;
;; EXAMPLES
;; (mgcol/find-maps "languages" { :tags { $all [ "functional" "object-oriented" ] } } )
(defoperator $all)
;; The $in operator is analogous to the SQL IN modifier, allowing you to specify an array of possible matches.
;;
;; EXAMPLES
;; (mgcol/find-maps "languages" { :tags { $in [ "functional" "object-oriented" ] } } )
(defoperator $in)
;; $ne is "non-equals" comparator
;;
;; EXAMPLES:
;; (monger.collection/find "libraries" {$ne { :language "Clojure" }})
(defoperator $ne)
;;
;; LOGIC OPERATORS
;;
(defoperator $and)
(defoperator $or)
(defoperator $nor)
;;
;; ATOMIC MODIFIERS
;;
;; $inc increments one or many fields for the given value, otherwise sets the field to value
;;
;; EXAMPLES:
;; (monger.collection/update "scores" { :_id user-id } { :score 10 } })
;; (monger.collection/update "scores" { :_id user-id } { :score 20 :bonus 10 } })
(defoperator $inc) (defoperator $inc)
;; $set sets an existing (or non-existing) field (or set of fields) to value
;; $set supports all datatypes.
;;
;; EXAMPLES:
;; (monger.collection/update "things" { :_id oid } { $set { :weight 20.5 } })
;; (monger.collection/update "things" { :_id oid } { $set { :weight 20.5 :height 12.5 } })
(defoperator $set) (defoperator $set)
;; $unset deletes a given field, non-existing fields are ignored.
;;
;; EXAMPLES:
;; (monger.collection/update "things" { :_id oid } { $unset { :weight 1 } })
(defoperator $unset) (defoperator $unset)
(defoperator $all) ;; $rename renames a given field
(defoperator $in) ;;
(defoperator $set) ;; EXAMPLES:
(defoperator $unset) ;; (monger.collection/update "things" { :_id oid } { $rename { :old_field_name "new_field_name" } })
(defoperator $inc)
(defoperator $push)
(defoperator $pushAll)
(defoperator $addToSet)
(defoperator $pop)
(defoperator $pull)
(defoperator $pullAll)
(defoperator $rename) (defoperator $rename)
;; $push appends _single_ value to field, if field is an existing array, otherwise sets field to the array [value] if field is not present.
;; If field is present but is not an array, an error condition is raised.
;;
;; EXAMPLES:
;; (mgcol/update "docs" { :_id oid } { $push { :tags "modifiers" } })
(defoperator $push)
;; $pushAll appends each value in value_array to field, if field is an existing array, otherwise sets field to the array value_array
;; if field is not present. If field is present but is not an array, an error condition is raised.
;;
;; EXAMPLES:
;; (mgcol/update coll { :_id oid } { $pushAll { :tags ["mongodb" "docs"] } })
(defoperator $pushAll)
;; $addToSet Adds value to the array only if its not in the array already, if field is an existing array, otherwise sets field to the
;; array value if field is not present. If field is present but is not an array, an error condition is raised.
;;
;; EXAMPLES:
;; (mgcol/update coll { :_id oid } { $addToSet { :tags "modifiers" } })
(defoperator $addToSet)
;; $pop removes the last element in an array, if 1 is passed.
;; if -1 is passed, removes the first element in an array
;;
;; EXAMPLES:
;; (mgcol/update coll { :_id oid } { $pop { :tags 1 } })
;; (mgcol/update coll { :_id oid } { $pop { :tags 1 :categories 1 } })
(defoperator $pop)
;; $pull removes all occurrences of value from field, if field is an array. If field is present but is not an array, an error condition
;; is raised.
;;
;; EXAMPLES:
;; (mgcol/update coll { :_id oid } { $pull { :measurements 1.2 } })
(defoperator $pull)
;; $pullAll removes all occurrences of each value in value_array from field, if field is an array. If field is present but is not an array
;; an error condition is raised.
;;
;; EXAMPLES:
;; (mgcol/update coll { :_id oid } { $pull { :measurements 1.2 } })
;; (mgcol/update coll { :_id oid } { $pull { :measurements { $gte 1.2 } } })
(defoperator $pullAll)
(defoperator $elemMatch)
(defoperator $bit)

View file

@ -34,7 +34,6 @@
(mgcol/update coll { :_id oid } { $inc { :score 20 } }) (mgcol/update coll { :_id oid } { $inc { :score 20 } })
(is (= 120 (:score (mgcol/find-map-by-id coll oid)))))) (is (= 120 (:score (mgcol/find-map-by-id coll oid))))))
(deftest set-a-single-non-existing-field-using-$inc-modifier (deftest set-a-single-non-existing-field-using-$inc-modifier
(let [coll "scores" (let [coll "scores"
oid (ObjectId.)] oid (ObjectId.)]
@ -150,8 +149,8 @@
oid (ObjectId.) oid (ObjectId.)
title "$push modifier appends value to field"] title "$push modifier appends value to field"]
(mgcol/insert coll { :_id oid :title title :tags ["mongodb"] }) (mgcol/insert coll { :_id oid :title title :tags ["mongodb"] })
(mgcol/update coll { :_id oid } { $push { :tags ["modifiers"] } }) (mgcol/update coll { :_id oid } { $push { :tags ["modifiers" "operators"] } })
(is (= { :_id oid :title title :tags ["mongodb" ["modifiers"]] } (mgcol/find-map-by-id coll oid))))) (is (= { :_id oid :title title :tags ["mongodb" ["modifiers" "operators"]] } (mgcol/find-map-by-id coll oid)))))
@ -164,7 +163,6 @@
(mgcol/update coll { :_id oid } { $push { :tags "modifiers" } }) (mgcol/update coll { :_id oid } { $push { :tags "modifiers" } })
(is (= { :_id oid :title title :tags ["mongodb" "modifiers" "modifiers"] } (mgcol/find-map-by-id coll oid))))) (is (= { :_id oid :title title :tags ["mongodb" "modifiers" "modifiers"] } (mgcol/find-map-by-id coll oid)))))
;; ;;
;; $pushAll ;; $pushAll
;; ;;
@ -267,7 +265,13 @@
(mgcol/update coll { :_id oid } { $pull { :measurements 1.2 } }) (mgcol/update coll { :_id oid } { $pull { :measurements 1.2 } })
(is (= { :_id oid :title title :measurements [1.0 1.1 1.1 1.3 1.0] } (mgcol/find-map-by-id coll oid))))) (is (= { :_id oid :title title :measurements [1.0 1.1 1.1 1.3 1.0] } (mgcol/find-map-by-id coll oid)))))
(deftest remove-all-value-entries-from-array-using-$pull-modifier-based-on-a-condition
(let [coll "docs"
oid (ObjectId.)
title "$pull modifier removes all value entries in the array"]
(mgcol/insert coll { :_id oid :title title :measurements [1.0 1.2 1.2 1.2 1.1 1.1 1.2 1.3 1.0] })
(mgcol/update coll { :_id oid } { $pull { :measurements { $gte 1.2 } } })
(is (= { :_id oid :title title :measurements [1.0 1.1 1.1 1.0] } (mgcol/find-map-by-id coll oid)))))
;; ;;
;; $pullAll ;; $pullAll
;; ;;

View file

@ -11,6 +11,7 @@
[monger.conversion :as mgcnv] [monger.conversion :as mgcnv]
[monger.js :as js]) [monger.js :as js])
(:use [clojure.test] (:use [clojure.test]
[monger.operators]
[monger.test.fixtures])) [monger.test.fixtures]))
(monger.core/connect!) (monger.core/connect!)
@ -251,6 +252,13 @@
(let [collection "libraries"] (let [collection "libraries"]
(is (empty? (mgcol/find-maps collection { :language "Scala" }))))) (is (empty? (mgcol/find-maps collection { :language "Scala" })))))
(deftest find-multiple-documents-by-regex
(let [collection "libraries"]
(mgcol/insert-batch collection [{ :language "Clojure", :name "monger" }
{ :language "Java", :name "nhibernate" }
{ :language "JavaScript", :name "sprout-core" }])
(is (= 2 (monger.core/count (mgcol/find collection { :language #"Java*" }))))))
(deftest find-multiple-documents (deftest find-multiple-documents
(let [collection "libraries"] (let [collection "libraries"]
@ -310,44 +318,6 @@
(is (= (:language doc) "Clojure")))) (is (= (:language doc) "Clojure"))))
(is (empty? (mgcol/find collection { :language "Erlang" } [:name])))))) (is (empty? (mgcol/find collection { :language "Erlang" } [:name]))))))
;; more sophisticated examples
(deftest find-with-conditional-operators-comparison
(let [collection "libraries"]
(mgcol/insert-batch collection [{ :language "Clojure", :name "monger" :users 1}
{ :language "Clojure", :name "langohr" :users 5 }
{ :language "Clojure", :name "incanter" :users 15 }
{ :language "Scala", :name "akka" :users 150}])
(are [a b] (= a (.count (mgcol/find collection b)))
2 { :users { "$gt" 10 }}
3 { :users { "$gte" 5 }}
2 { :users { "$lt" 10 }}
2 { :users { "$lte" 5 }}
1 { :users { "$gt" 10 "$lt" 150 }})))
(deftest find-on-embedded-arrays
(let [collection "libraries"]
(mgcol/insert-batch collection [{ :language "Clojure", :tags [ "functional" ] }
{ :language "Scala", :tags [ "functional" "object-oriented" ] }
{ :language "Ruby", :tags [ "object-oriented" "dynamic" ] }])
(is (= "Scala" (:language (first (mgcol/find-maps collection { :tags { "$all" [ "functional" "object-oriented" ] } } )))))
(is (= 3 (.count (mgcol/find-maps collection { :tags { "$in" [ "functional" "object-oriented" ] } } ))))))
(deftest find-with-conditional-operators-on-embedded-documents
(let [collection "people"]
(mgcol/insert-batch collection [{ :name "Bob", :comments [ { :text "Nice!" :rating 1 }
{ :text "Love it" :rating 4 }
{ :text "What?":rating -5 } ] }
{ :name "Alice", :comments [ { :text "Yeah" :rating 2 }
{ :text "Doh" :rating 1 }
{ :text "Agreed" :rating 3 }
] } ])
(are [a b] (= a (.count (mgcol/find collection b)))
1 { :comments { "$elemMatch" { :text "Nice!" :rating { "$gte" 1 } } } }
2 { "comments.rating" 1 }
1 { "comments.rating" { "$gt" 3 } })))
;; ;;
;; update, save ;; update, save
;; ;;
@ -373,7 +343,7 @@
(is (= 3 (mgcol/count collection { :language "Clojure" }))) (is (= 3 (mgcol/count collection { :language "Clojure" })))
(is (= 1 (mgcol/count collection { :language "Scala" }))) (is (= 1 (mgcol/count collection { :language "Scala" })))
(is (= 0 (mgcol/count collection { :language "Python" }))) (is (= 0 (mgcol/count collection { :language "Python" })))
(mgcol/update collection { :language "Clojure" } { "$set" { :language "Python" } } :multi true) (mgcol/update collection { :language "Clojure" } { $set { :language "Python" } } :multi true)
(is (= 0 (mgcol/count collection { :language "Clojure" }))) (is (= 0 (mgcol/count collection { :language "Clojure" })))
(is (= 1 (mgcol/count collection { :language "Scala" }))) (is (= 1 (mgcol/count collection { :language "Scala" })))
(is (= 3 (mgcol/count collection { :language "Python" }))))) (is (= 3 (mgcol/count collection { :language "Python" })))))
@ -412,7 +382,7 @@
(is (monger.result/ok? (mgcol/insert "people" document))) (is (monger.result/ok? (mgcol/insert "people" document)))
(is (= 1 (mgcol/count collection))) (is (= 1 (mgcol/count collection)))
(is (= 0 (mgcol/count collection { :has_kids true }))) (is (= 0 (mgcol/count collection { :has_kids true })))
(mgcol/update collection { :_id doc-id } { "$set" { :has_kids true } }) (mgcol/update collection { :_id doc-id } { $set { :has_kids true } })
(is (= 1 (mgcol/count collection { :has_kids true }))))) (is (= 1 (mgcol/count collection { :has_kids true })))))
@ -544,4 +514,4 @@
{ :state "IL" :quantity 3 :price 5.50 }]] { :state "IL" :quantity 3 :price 5.50 }]]
(mgcol/insert-batch collection batch) (mgcol/insert-batch collection batch)
(is (= ["CA" "IL" "NY"] (sort (mgcol/distinct collection :state)))) (is (= ["CA" "IL" "NY"] (sort (mgcol/distinct collection :state))))
(is (= ["CA" "NY"] (sort (mgcol/distinct collection :state { :price { "$gt" 100.00 } })))))) (is (= ["CA" "NY"] (sort (mgcol/distinct collection :state { :price { $gt 100.00 } }))))))

View file

@ -0,0 +1,99 @@
(set! *warn-on-reflection* true)
(ns monger.test.collection
(:import [com.mongodb WriteResult WriteConcern DBCursor DBObject CommandResult$CommandFailure MapReduceOutput MapReduceCommand MapReduceCommand$OutputType]
[org.bson.types ObjectId]
[java.util Date])
(:require [monger core util]
[clojure stacktrace]
[monger.collection :as mgcol]
[monger.result :as mgres]
[monger.conversion :as mgcnv]
[monger.js :as js])
(:use [clojure.test]
[monger.operators]
[monger.test.fixtures]))
(monger.core/connect!)
(monger.core/set-db! (monger.core/get-db "monger-test"))
(use-fixtures :each purge-people-collection purge-docs-collection purge-things-collection purge-libraries-collection)
(monger.core/set-default-write-concern! WriteConcern/SAFE)
;;
;; $gt, $gte, $lt, lte
;;
(deftest find-with-conditional-operators-comparison
(let [collection "libraries"]
(mgcol/insert-batch collection [{ :language "Clojure", :name "monger" :users 1}
{ :language "Clojure", :name "langohr" :users 5 }
{ :language "Clojure", :name "incanter" :users 15 }
{ :language "Scala", :name "akka" :users 150}])
(are [a b] (= a (.count (mgcol/find collection b)))
2 { :users { $gt 10 }}
3 { :users { $gte 5 }}
2 { :users { $lt 10 }}
2 { :users { $lte 5 }}
1 { :users { $gt 10 $lt 150 }})))
;;
;; $ne
;;
(deftest find-with-and-or-operators
(let [collection "libraries"]
(mgcol/insert-batch collection [{ :language "Ruby", :name "mongoid" :users 1}
{ :language "Clojure", :name "langohr" :users 5 }
{ :language "Clojure", :name "incanter" :users 15 }
{ :language "Scala", :name "akka" :users 150}])
(is (= 2 (.count (mgcol/find collection {$ne { :language "Clojure" }}))))))
;;
;; $and, $or, $nor
;;
(deftest find-with-and-or-operators
(let [collection "libraries"]
(mgcol/insert-batch collection [{ :language "Ruby", :name "mongoid" :users 1}
{ :language "Clojure", :name "langohr" :users 5 }
{ :language "Clojure", :name "incanter" :users 15 }
{ :language "Scala", :name "akka" :users 150}])
(is (= 1 (.count (mgcol/find collection {$and [{ :language "Clojure" }
{ :users { $gt 10 } }]}))))
(is (= 3 (.count (mgcol/find collection {$or [{ :language "Clojure" }
{:users { $gt 10 } } ]}))))
(is (= 1 (.count (mgcol/find collection {$nor [{ :language "Clojure" }
{:users { $gt 10 } } ]}))))))
;;
;; $all, $in
;;
(deftest find-on-embedded-arrays
(let [collection "libraries"]
(mgcol/insert-batch collection [{ :language "Clojure", :tags [ "functional" ] }
{ :language "Scala", :tags [ "functional" "object-oriented" ] }
{ :language "Ruby", :tags [ "object-oriented" "dynamic" ] }])
(is (= "Scala" (:language (first (mgcol/find-maps collection { :tags { $all [ "functional" "object-oriented" ] } } )))))
(is (= 3 (.count (mgcol/find-maps collection { :tags { $in [ "functional" "object-oriented" ] } } ))))))
(deftest find-with-conditional-operators-on-embedded-documents
(let [collection "people"]
(mgcol/insert-batch collection [{ :name "Bob", :comments [ { :text "Nice!" :rating 1 }
{ :text "Love it" :rating 4 }
{ :text "What?":rating -5 } ] }
{ :name "Alice", :comments [ { :text "Yeah" :rating 2 }
{ :text "Doh" :rating 1 }
{ :text "Agreed" :rating 3 }
] } ])
(are [a b] (= a (.count (mgcol/find collection b)))
1 { :comments { $elemMatch { :text "Nice!" :rating { $gte 1 } } } }
2 { "comments.rating" 1 }
1 { "comments.rating" { $gt 3 } })))