Commits

Liam Staskawicz  committed 0e8dff6

* add mapReduce, group, and distinct helpers

  • Participants
  • Parent commits 2849887

Comments (0)

Files changed (2)

File fan/Collection.fan

   }
   
   **
+  ** Return distinct values for a key in this Collection.
+  ** 'query' narrows the range of objects to those that match.
+  **
+  List distinct(Str key, Str:Obj? query := [:])
+  {
+    cmd := [:] { ordered = true }
+    cmd.set("distinct", name).set("key", key).set("query", query)
+    res := db.command(cmd)
+    if(!DB.cmdOk(res)) throw MongoOpErr("distinct failed - ${res}")
+    return res["values"]
+  }
+  
+  **
+  ** Perform a map reduce operation - 'map' and 'reduce' are JavaScript functions.
+  ** 'outputCollection' (optional) specifies the collection to store the results in, 
+  ** otherwise uses a temp collection.  'query' specifies a query to narrow the range 
+  ** of objects this is applied to - null will use all objects.  
+  **
+  ** Returns a Map in which the key "result" is the Collection name created for the reults.
+  ** Other info - processing time, etc is also included
+  **
+  ** 'opts' can include the following:
+  ** pre>
+  ** Key         Value Type       Description
+  ** ---         ----------       -----------
+  ** query       Str:Obj?         A query to limit the objects involved
+  ** sort        [Str:direction]  Str is field name, 'direction' is either Mongo.ASCENDING or Mongo.DESCENDING 
+  ** finalize    Str              A javascript function to apply to the result set after the map/reduce operation has finished.
+  ** out         Str              The name of the output collection. If specified, the collection will not be treated as temporary.
+  ** keeptemp    Bool             If true, the generated collection will be persisted.
+  ** verbose     Bool             if true, provides statistics on job execution time.
+  ** <pre
+  **
+  ** See http://www.mongodb.org/display/DOCS/MapReduce
+  **
+  Str:Obj? mapReduce(Str map, Str reduce, Str:Obj opts := [:])
+  {
+    mrcmd := [:] { ordered = true }
+    mrcmd.set("mapreduce", name).set("map", map).set("reduce", reduce)
+    mrcmd.addAll(opts) // should probably make sure this is merged so above is not overwritten...
+    res := db.command(mrcmd)
+    if(!DB.cmdOk(res)) throw MongoOpErr("mapReduce failed - ${res}")
+    return res
+  }
+  
+  **
+  ** 'key' - Str is key name, 
+  ** 
+  **
+  ** See http://www.mongodb.org/display/DOCS/Aggregation
+  **
+  ** TODO - support $keyf option
+  **
+  [Str:Obj?][] group(Str[] keys, Str:Obj? initial, Str reduce, Str:Obj? query := [:], Str? finalize := null)
+  {
+    Str:Bool keymap := [:]
+    keys.each |s| { keymap[s] = true }
+    args := ["ns": name, "key": keymap, "cond": query, "\$reduce": reduce, "initial": initial]
+    if (finalize != null) args["finalize"] = finalize
+    
+    res := db.command(["group": args])
+    if(!DB.cmdOk(res)) throw MongoOpErr("group failed - ${res}")
+    return res["retval"]
+  }
+  
+  **
   ** See `DB.indexInfo` for details.
   **
   Str:Obj indexInfo()

File test/CollectionTest.fan

   //   c.drop
   // }
   
+  Void testMapReduce()
+  {
+    c := db["mapreducetest"]
+    c.drop
+    
+    c.insert(["user_id": 1])
+    c.insert(["user_id": 2])
+    
+    map := "function() { emit(this.user_id, 1); }"
+    red := "function(k,vals) { return 1; }"
+    res := c.mapReduce(map, red)
+    mrcoll := db[res["result"]]
+    verifyNotNull(mrcoll.findOne(["_id": 1]))
+    verifyNotNull(mrcoll.findOne(["_id": 2]))
+    
+    c.drop
+    c.insert(["user_id": 1])
+    c.insert(["user_id": 2])
+    c.insert(["user_id": 3])
+
+    map = "function() { emit(this.user_id, 1); }"
+    red = "function(k,vals) { return 1; }"
+    res = c.mapReduce(map, red, ["query": ["user_id": ["\$gt": 1]]])
+    mrcoll = db[res["result"]]
+    verifyEq(2, mrcoll.find.count)
+    verifyNull(mrcoll.findOne(["_id": 1]))
+    verifyNotNull(mrcoll.findOne(["_id": 2]))
+    verifyNotNull(mrcoll.findOne(["_id": 3]))
+    
+    c.drop
+  }
+  
+  Void testDistinct()
+  {
+    c := db["distincttest"]
+    c.drop
+    
+    c.insertDocs([["a": 0, "b": ["c": "a"]],
+                   ["a": 1, "b": ["c": "b"]],
+                   ["a": 1, "b": ["c": "c"]],
+                   ["a": 2, "b": ["c": "a"]],
+                   ["a": 3],
+                   ["a": 3]])
+    
+    expected := [2, 3]
+    c.distinct("a", ["a": ["\$gt": 1]]).sort.each |Int i, idx| {
+      verifyEq(expected[idx], i)
+    }
+    expected2 := ["a", "b"]
+    c.distinct("b.c", ["b.c": ["\$ne": "c"]]).sort.each |Str s, idx| {
+      verifyEq(expected2[idx], s)
+    }
+    
+    c.drop
+  }
+  
+  Void testGroup()
+  {
+    c := db["grouptest"]
+    c.drop
+    
+    c.save(["a": 1])
+    c.save(["b": 1])
+    initial := ["count": 0]
+    
+    verifyEq(1f, c.group(Str[,], initial, "function (obj, prev) { prev.count += 0.5; }")[0]["count"])
+    verifyEq(2f, c.group(Str[,], initial, "function (obj, prev) { prev.count += 1; }")[0]["count"])
+    verifyEq(4f, c.group(Str[,], initial, "function (obj, prev) { prev.count += 2; }")[0]["count"])
+    
+    // with finalize
+    fin := "function(doc) {doc.f = doc.count + 200; }"
+    verifyEq(202f, c.group(Str[,], initial, "function (obj, prev) { prev.count += 1; }", [:], fin)[0]["f"])
+    
+    c.drop
+  }
+  
 }