diff --git a/ChangeLog.md b/ChangeLog.md index 5b63b49..fc7eb3f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,66 @@ ## Changes between 1.0.0-beta7 and 1.0.0-beta8 +### MongoDB 2.1/2.2 Aggregation Framework support + +`monger.collection/aggregate` provides a convenient way to run [aggregation queries](http://docs.mongodb.org/manual/reference/aggregation/). + +``` clojure +;; single stage pipeline +(mc/aggregate "docs" [{$project {:subtotal {$multiply ["$quantity", "$price"]} + :_id "$state"}}]) + +;; two stage pipeline +(mc/aggregate "docs" [{$project {:subtotal {$multiply ["$quantity", "$price"]} + :_id 1 + :state 1}} + {$group {:_id "$state" + :total {$sum "$subtotal"}}}]) +``` + +The following couple of tests demonstrates aggregation queries with some sample data: + +``` clojure +(deftest ^{:edge-features true} test-basic-projection-with-multiplication + (let [collection "docs" + batch [{ :state "CA" :quantity 1 :price 199.00 } + { :state "NY" :quantity 2 :price 199.00 } + { :state "NY" :quantity 1 :price 299.00 } + { :state "IL" :quantity 2 :price 11.50 } + { :state "CA" :quantity 2 :price 2.95 } + { :state "IL" :quantity 3 :price 5.50 }] + expected [{:_id "NY" :subtotal 398.0} + {:_id "NY" :subtotal 299.0} + {:_id "IL" :subtotal 23.0} + {:_id "CA" :subtotal 5.9} + {:_id "IL" :subtotal 16.5} + {:_id "CA" :subtotal 199.0}]] + (mc/insert-batch collection batch) + (let [result (vec (mc/aggregate "docs" [{$project {:subtotal {$multiply ["$quantity", "$price"]} + :_id "$state"}}]))] + (is (= expected result))))) + + +(deftest ^{:edge-features true} test-basic-total-aggregation + (let [collection "docs" + batch [{ :state "CA" :quantity 1 :price 199.00 } + { :state "NY" :quantity 2 :price 199.00 } + { :state "NY" :quantity 1 :price 299.00 } + { :state "IL" :quantity 2 :price 11.50 } + { :state "CA" :quantity 2 :price 2.95 } + { :state "IL" :quantity 3 :price 5.50 }] + expected [{:_id "CA", :total 204.9} {:_id "IL", :total 39.5} {:_id "NY", :total 697.0}]] + (mc/insert-batch collection batch) + (let [result (vec (mc/aggregate "docs" [{$project {:subtotal {$multiply ["$quantity", "$price"]} + :_id 1 + :state 1}} + {$group {:_id "$state" + :total {$sum "$subtotal"}}}]))] + (is (= expected result))))) +``` + +The aggregation framework is an edge feature that will be available in MongoDB 2.2. + + ### More Operators Two new operator macros: `$regex`, `$options` and those used by the upcoming diff --git a/project.clj b/project.clj index 67fb906..d9b9a92 100644 --- a/project.clj +++ b/project.clj @@ -6,13 +6,17 @@ [org.mongodb/mongo-java-driver "2.7.3"] [com.novemberain/validateur "1.1.0"] [clojurewerkz/support "0.4.0"]] - :test-selectors {:default (complement :performance) - :focus :focus - :indexing :indexing - :external :external - :cache :cache - :performance :performance - :all (constantly true)} + :test-selectors {:default (fn [m] + (and (not (:performance m)) + (not (:edge-features m)))) + :focus :focus + :indexing :indexing + :external :external + :cache :cache + :performance :performance + ;; as in, edge mongodb server + :edge-features :edge-features + :all (constantly true)} :codox {:exclude [monger.internal.pagination]} :mailing-list {:name "clojure-monger" :archive "https://groups.google.com/group/clojure-monger" diff --git a/src/monger/collection.clj b/src/monger/collection.clj index 44c7fbf..02a9249 100644 --- a/src/monger/collection.clj +++ b/src/monger/collection.clj @@ -596,3 +596,17 @@ and :size (max allowed size of the collection, in bytes)." [^String name options] (.createCollection ^DB monger.core/*mongodb-database* name (to-db-object options))) + +;; +;; Aggregation +;; + +(defn aggregate + "Performs aggregation query. MongoDB 2.1/2.2+ only. + + See http://docs.mongodb.org/manual/applications/aggregation/ to learn more." + [^String coll stages] + (let [res (monger.core/command {:aggregate coll :pipeline stages})] + ;; this is what DBCollection#distinct does. Turning a blind eye! + (.throwOnError res) + (map #(from-db-object % true) (.get res "result")))) diff --git a/test/monger/test/aggregation_framework_test.clj b/test/monger/test/aggregation_framework_test.clj new file mode 100644 index 0000000..43a9911 --- /dev/null +++ b/test/monger/test/aggregation_framework_test.clj @@ -0,0 +1,69 @@ +(ns monger.test.aggregation-framework-test + (:require monger.core [monger.collection :as mc] + [monger.test.helper :as helper]) + (:use clojure.test + monger.operators + monger.test.fixtures)) + + +(helper/connect!) + +(use-fixtures :each purge-docs) + +(deftest ^{:edge-features true} test-basic-single-stage-$project-aggregation + (let [collection "docs" + batch [{ :state "CA" :quantity 1 :price 199.00 } + { :state "NY" :quantity 2 :price 199.00 } + { :state "NY" :quantity 1 :price 299.00 } + { :state "IL" :quantity 2 :price 11.50 } + { :state "CA" :quantity 2 :price 2.95 } + { :state "IL" :quantity 3 :price 5.50 }] + expected [{:quantity 1 :state "CA"} + {:quantity 2 :state "NY"} + {:quantity 1 :state "NY"} + {:quantity 2 :state "IL"} + {:quantity 2 :state "CA"} + {:quantity 3 :state "IL"}]] + (mc/insert-batch collection batch) + (is (= 6 (mc/count collection))) + (let [result (vec (map #(select-keys % [:state :quantity]) + (mc/aggregate "docs" [{$project {:state 1 :quantity 1}}])))] + (is (= expected result))))) + + +(deftest ^{:edge-features true} test-basic-projection-with-multiplication + (let [collection "docs" + batch [{ :state "CA" :quantity 1 :price 199.00 } + { :state "NY" :quantity 2 :price 199.00 } + { :state "NY" :quantity 1 :price 299.00 } + { :state "IL" :quantity 2 :price 11.50 } + { :state "CA" :quantity 2 :price 2.95 } + { :state "IL" :quantity 3 :price 5.50 }] + expected [{:_id "NY" :subtotal 398.0} + {:_id "NY" :subtotal 299.0} + {:_id "IL" :subtotal 23.0} + {:_id "CA" :subtotal 5.9} + {:_id "IL" :subtotal 16.5} + {:_id "CA" :subtotal 199.0}]] + (mc/insert-batch collection batch) + (let [result (vec (mc/aggregate "docs" [{$project {:subtotal {$multiply ["$quantity", "$price"]} + :_id "$state"}}]))] + (is (= expected result))))) + + +(deftest ^{:edge-features true} test-basic-total-aggregation + (let [collection "docs" + batch [{ :state "CA" :quantity 1 :price 199.00 } + { :state "NY" :quantity 2 :price 199.00 } + { :state "NY" :quantity 1 :price 299.00 } + { :state "IL" :quantity 2 :price 11.50 } + { :state "CA" :quantity 2 :price 2.95 } + { :state "IL" :quantity 3 :price 5.50 }] + expected [{:_id "CA", :total 204.9} {:_id "IL", :total 39.5} {:_id "NY", :total 697.0}]] + (mc/insert-batch collection batch) + (let [result (vec (mc/aggregate "docs" [{$project {:subtotal {$multiply ["$quantity", "$price"]} + :_id 1 + :state 1}} + {$group {:_id "$state" + :total {$sum "$subtotal"}}}]))] + (is (= expected result)))))