From f3ce1fd4b8acb4b1b913c008be51d73783e666aa Mon Sep 17 00:00:00 2001 From: "Michael S. Klishin" Date: Thu, 10 Nov 2011 20:50:09 +0400 Subject: [PATCH 1/4] Upgrade to MongoDB Java driver 2.7.1 --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 8765cf0..3338531 100644 --- a/project.clj +++ b/project.clj @@ -2,7 +2,7 @@ :description "Monger is an experimental idiomatic Clojure wrapper around MongoDB Java driver" :license { :name "Eclipse Public License" } :dependencies [[org.clojure/clojure "1.3.0"] - [org.mongodb/mongo-java-driver "2.7.0"] + [org.mongodb/mongo-java-driver "2.7.1"] [com.novemberain/validateur "1.0.0-SNAPSHOT"]] :dev-dependencies [[org.clojure/data.json "0.1.2"] [clj-time "0.3.2-SNAPSHOT" :exclusions [org.clojure/clojure]]] From df126867084780c595b60341ac41d759a26e6037 Mon Sep 17 00:00:00 2001 From: Oleksandr Petrov Date: Fri, 11 Nov 2011 18:54:11 +0100 Subject: [PATCH 2/4] Making defoperator private by default --- src/monger/operators.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/monger/operators.clj b/src/monger/operators.clj index ad94a82..03852f0 100644 --- a/src/monger/operators.clj +++ b/src/monger/operators.clj @@ -1,6 +1,6 @@ (ns monger.operators) -(defmacro defoperator +(defmacro ^{:private true} defoperator [operator] (let [op# (str operator) op-sym# (symbol op#)] From 047d5b6a8837aa121465c713f9ae6f9178269d89 Mon Sep 17 00:00:00 2001 From: Oleksandr Petrov Date: Fri, 11 Nov 2011 18:55:43 +0100 Subject: [PATCH 3/4] Separating operators to Logical Operators, Query Operators and Atomic Modifiers. Adding docs to most of operators (still TBC), giving more examples, improving test suite. --- src/monger/operators.clj | 128 +++++++++++++++++++++++--- test/monger/test/atomic_modifiers.clj | 14 ++- test/monger/test/collection.clj | 45 +-------- test/monger/test/query_operators.clj | 99 ++++++++++++++++++++ 4 files changed, 228 insertions(+), 58 deletions(-) create mode 100644 test/monger/test/query_operators.clj diff --git a/src/monger/operators.clj b/src/monger/operators.clj index 03852f0..fe2e3e7 100644 --- a/src/monger/operators.clj +++ b/src/monger/operators.clj @@ -6,22 +6,126 @@ op-sym# (symbol op#)] `(def ~op-sym# (str ~op#)))) -(defoperator $gt) -(defoperator $inc) -(defoperator $set) -(defoperator $unset) +;; +;; 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 $gte) (defoperator $lt) (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) -(defoperator $set) -(defoperator $unset) + +;; $ne is "non-equals" comparator +;; +;; EXAMPLES: +;; (monger.collection/find "libraries" {$ne { :language "Clojure" }}) +(defoperator $ne) + +;; +;; LOGICAL 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 $push) -(defoperator $pushAll) -(defoperator $addToSet) -(defoperator $pop) -(defoperator $pull) -(defoperator $pullAll) + +;; $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) + +;; $unset deletes a given field, non-existing fields are ignored. +;; +;; EXAMPLES: +;; (monger.collection/update "things" { :_id oid } { $unset { :weight 1 } }) +(defoperator $unset) + +;; $rename renames a given field +;; +;; EXAMPLES: +;; (monger.collection/update "things" { :_id oid } { $rename { :old_field_name "new_field_name" } }) (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) diff --git a/test/monger/test/atomic_modifiers.clj b/test/monger/test/atomic_modifiers.clj index 95e5ebb..0a12997 100644 --- a/test/monger/test/atomic_modifiers.clj +++ b/test/monger/test/atomic_modifiers.clj @@ -34,7 +34,6 @@ (mgcol/update coll { :_id oid } { $inc { :score 20 } }) (is (= 120 (:score (mgcol/find-map-by-id coll oid)))))) - (deftest set-a-single-non-existing-field-using-$inc-modifier (let [coll "scores" oid (ObjectId.)] @@ -150,8 +149,8 @@ oid (ObjectId.) title "$push modifier appends value to field"] (mgcol/insert coll { :_id oid :title title :tags ["mongodb"] }) - (mgcol/update coll { :_id oid } { $push { :tags ["modifiers"] } }) - (is (= { :_id oid :title title :tags ["mongodb" ["modifiers"]] } (mgcol/find-map-by-id coll oid))))) + (mgcol/update coll { :_id oid } { $push { :tags ["modifiers" "operators"] } }) + (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" } }) (is (= { :_id oid :title title :tags ["mongodb" "modifiers" "modifiers"] } (mgcol/find-map-by-id coll oid))))) - ;; ;; $pushAll ;; @@ -267,7 +265,13 @@ (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))))) - +(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 ;; diff --git a/test/monger/test/collection.clj b/test/monger/test/collection.clj index 94a1a67..e7b0501 100644 --- a/test/monger/test/collection.clj +++ b/test/monger/test/collection.clj @@ -11,6 +11,7 @@ [monger.conversion :as mgcnv] [monger.js :as js]) (:use [clojure.test] + [monger.operators] [monger.test.fixtures])) (monger.core/connect!) @@ -310,44 +311,6 @@ (is (= (:language doc) "Clojure")))) (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 ;; @@ -373,7 +336,7 @@ (is (= 3 (mgcol/count collection { :language "Clojure" }))) (is (= 1 (mgcol/count collection { :language "Scala" }))) (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 (= 1 (mgcol/count collection { :language "Scala" }))) (is (= 3 (mgcol/count collection { :language "Python" }))))) @@ -412,7 +375,7 @@ (is (monger.result/ok? (mgcol/insert "people" document))) (is (= 1 (mgcol/count collection))) (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 }))))) @@ -544,4 +507,4 @@ { :state "IL" :quantity 3 :price 5.50 }]] (mgcol/insert-batch collection batch) (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 } })))))) diff --git a/test/monger/test/query_operators.clj b/test/monger/test/query_operators.clj new file mode 100644 index 0000000..8268c45 --- /dev/null +++ b/test/monger/test/query_operators.clj @@ -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 } }))) \ No newline at end of file From 8c9309539383f1ac3bfdb26e459d146dca828663 Mon Sep 17 00:00:00 2001 From: Oleksandr Petrov Date: Sun, 13 Nov 2011 17:47:26 +0100 Subject: [PATCH 4/4] Adding test demonstrating regexp document search. --- test/monger/test/collection.clj | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/monger/test/collection.clj b/test/monger/test/collection.clj index e7b0501..b1c5ed7 100644 --- a/test/monger/test/collection.clj +++ b/test/monger/test/collection.clj @@ -252,6 +252,13 @@ (let [collection "libraries"] (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 (let [collection "libraries"]