Skip to content

Commit

Permalink
Added a map-reduce function.
Browse files Browse the repository at this point in the history
  • Loading branch information
christophermaier committed Aug 22, 2010
1 parent 6fc8345 commit df433fc
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 0 deletions.
57 changes: 57 additions & 0 deletions src/somnium/congomongo.clj
Expand Up @@ -323,6 +323,63 @@ When with-mongo and set-connection! interact, last one wins"
(:retval result)
(throw (Exception. (format "failure executing javascript: %s" (str result))))))))

(defunk map-reduce
"Performs a map-reduce job on the server.
Mandatory arguments
collection -> the collection to run the job on
mapfn -> a JavaScript map function, as a String. Should take no arguments.
reducefn -> a JavaScript reduce function, as a String. Should take two arguments: a key, and a corresponding array of values
See http://www.mongodb.org/display/DOCS/MapReduce for more information, as well as the test code in congomongo_test.clj.
Optional Arguments
:query -> a query map against collection; if this is specified, the map-reduce job is run on the result of this query instead of on the collection as a whole.
:query-from -> if query is supplied, specifies what form it is in (:clojure, :json, or :mongo). Defaults to :clojure.
:sort -> if you want query sorted (for optimization), specify a map of sort clauses here.
:sort-from -> if sort is supplied, specifies what form it is in (:clojure, :json, or :mongo). Defaults to :clojure.
:limit -> the number of objects to return from a query collection (defaults to 0; that is, everything). This pertains to query, NOT the result of the overall map-reduce job!
:out -> output collection name (String or keyword). If this is specified, the output collection is made permanent.
:keeptemp -> should the output collection be treated as permanent? true or false (defaults to false).
:finalize -> a finalizaton function (JavaScript, as a String). Should take two arguments: a key and a single value (not an array of values).
:scope -> a scope object; variables in the object will be available in the global scope of map, reduce, and finalize functions.
:scope-from -> if scope is supplied, specifies what form it is in (:clojure, :json, or :mongo). Defaults to :clojure.
:output -> if you want the resulting documents from the map-reduce job, specify :documents; otherwise, if you want the name of the result collection as a keyword, specify :collection. Defaults to :documents.
:as -> if :output is set to :documents, determines the form the results take (:clojure, :json, or :mongo) (has no effect if :output is set to :collection; that is always returned as a Clojure keyword).
"
{:arglists
'([collection mapfn reducefn :query :query-from :sort :sort-from :limit :out :keeptemp :finalize :scope :scope-from :output :as])}
[collection mapfn reducefn
:query {}
:query-from :clojure
:sort {}
:sort-from :clojure
:limit 0
:out nil
:keeptemp nil
:finalize ""
:scope nil
:scope-from :clojure
:output :documents
:as :clojure]
(let [mr-query {:mapreduce collection
:map mapfn
:reduce reducefn
:query (coerce query [query-from :mongo])
:sort (coerce sort [sort-from :mongo])
:limit limit
:out out
:keeptemp keeptemp
:finalize finalize
:scope (coerce scope [scope-from :mongo])}
mr-query (coerce mr-query [:clojure :mongo])
result (.mapReduce (get-coll collection) mr-query)]
(if (= output :documents)
(coerce (.results result) [:mongo as] :many :true)
(-> (.getOutputCollection result)
.getName
keyword))))

(def write-concern-map
{:none com.mongodb.DB$WriteConcern/NONE
:normal com.mongodb.DB$WriteConcern/NORMAL
Expand Down
64 changes: 64 additions & 0 deletions test/congomongo_test.clj
Expand Up @@ -194,6 +194,70 @@
(let [return (fetch-one :stuff :where {:name "name"})]
(is (vector? (:vector return))))))

(deftest test-map-reduce
(with-test-mongo
(insert! :mr {:fruit "bananas" :count 1})
(insert! :mr {:fruit "bananas" :count 2})
(insert! :mr {:fruit "plantains" :count 3})
(insert! :mr {:fruit "plantains" :count 2})
(insert! :mr {:fruit "pineapples" :count 4})
(insert! :mr {:fruit "pineapples" :count 2})
(let [mapfn
"function(){
emit(this.fruit, {count: this.count});
}"
mapfn-with-scope
"function(){
emit((adj + ' ' + this.fruit), {count: this.count});
}"
reducefn
"function(key, values){
var total = 0;
for ( var i=0; i<values.length; i++ ){
total += values[i].count;
}
return { count : total };
}"
target-collection :monkey-shopping-list]
;; See that the base case works
(is (= (map-reduce :mr mapfn reducefn)
(seq [{:_id "bananas" :value {:count 3}}
{:_id "pineapples" :value {:count 6}}
{:_id "plantains" :value {:count 5}}])))
;; See if we can assign a name to the results collection
(is (= (map-reduce :mr mapfn reducefn :out target-collection)
(seq [{:_id "bananas" :value {:count 3}}
{:_id "pineapples" :value {:count 6}}
{:_id "plantains" :value {:count 5}}])))
(is (= (fetch target-collection)
(seq [{:_id "bananas" :value {:count 3}}
{:_id "pineapples" :value {:count 6}}
{:_id "plantains" :value {:count 5}}])))
;; Make sure we get the collection name back, too
(is (= (map-reduce :mr mapfn reducefn :out target-collection :output :collection)
target-collection))
;; Check limit
(is (= (map-reduce :mr mapfn reducefn :limit 2)
(seq [{:_id "bananas" :value {:count 3}}])))
;; Check sort
(is (= (map-reduce :mr mapfn reducefn :sort {:fruit -1} :limit 2)
(seq [{:_id "plantains" :value {:count 5}}])))
;; check query
(is (= (map-reduce :mr mapfn reducefn :query {:fruit "pineapples"})
(seq [{:_id "pineapples" :value {:count 6}}])))
;; check finalize
(is (= (map-reduce :mr mapfn reducefn
:finalize "function(key, value){return 'There are ' + value.count + ' ' + key}")
(seq [{:_id "bananas" :value "There are 3 bananas"}
{:_id "pineapples" :value "There are 6 pineapples"}
{:_id "plantains" :value "There are 5 plantains"}])))
;; check scope
(is (= (map-reduce :mr mapfn-with-scope reducefn
:scope {:adj "tasty"})
(seq [{:_id "tasty bananas" :value {:count 3}}
{:_id "tasty pineapples" :value {:count 6}}
{:_id "tasty plantains" :value {:count 5}}]))))))

(deftest test-server-eval
(with-test-mongo
(is (= (server-eval
Expand Down

0 comments on commit df433fc

Please sign in to comment.