Commits

Liam Staskawicz  committed e0c1391

* Collection.createIndex(), and some tests for creating and dropping indexes
* only return a single result for Cursor.findOne()

  • Participants
  • Parent commits ccecab5

Comments (0)

Files changed (3)

File fan/Collection.fan

 //
 ////////////////////////////////////////////////////////////////////////////////
 
+**
+**  Index
+**
+const class Index
+{
+  static const Int ASCENDING := 1
+  static const Int DESCENDING := -1
+}
 
 **
 **  Collection
     return "${db.name}.${name}"
   }
   
-  Void ensureIndex(Str idxName)
+  Str:Obj indexInfo()
   {
-    
+    return db.indexInfo(this.name)
   }
   
-  Void ensureIndexes(Str:Obj? keys)
+  **
+  **  Create a new index in this collection.
+  **
+  Str createIndex([Str:Int][] fields, Bool unique := false)
   {
-    
+    key := [:] { ordered = true }
+    fields.each |map| { key.addAll(map) }
+    namestr := indexName(fields)
+    selector := ["name"    : namestr,
+                  "ns"     : fullName,
+                  "key"    : key,
+                  "unique" : unique]
+                  // todo - dropDups
+    Collection(db, "system.indexes").insertDocs([selector], true) // add this in safe mode?
+    return namestr
   }
   
+  **
+  ** Creates the index name used for the given fields.
+  ** This is the same name that is returned from createIndex()
+  ** and is required by dropIndex() 
+  **
+  static Str indexName([Str:Int][] fields)
+  {
+    idxs := Str[,]
+    fields.each |map, i| {
+      k := map.keys.first
+      idxs.add("${k}_${map[k]}")
+    }
+    return idxs.join("_")
+  }
+  
+  **
+  ** Drop this entire collection, including indexes.
+  **
   Void drop()
   {
     db.dropCollection(this.name)
   }
   
+  **
+  ** Remove an index from a Collection.
+  ** The required name can be obtained as the return value of createIndex()
+  ** of indexName()
+  **
   Void dropIndex(Str idxName)
   {
     db.dropIndex(this.name, idxName)
   }
   
+  **
+  ** Remove all indexes on this Collection
+  **
   Void dropAllIndexes()
   {
     db.dropIndex(this.name, "*")
   }
   
-  Int count()
+  **
+  ** Retrieve objects matching the given selector, using the opts provided.
+  ** Note that this does not result in any communication with the DB - the 
+  ** returned Cursor will fetch the results as needed.
+  **
+  ** opts can include the following:
+  ** Key         Value Type    Description
+  ** ---         ----------    -----------
+  ** limit       Int           The maximum number of results to return
+  ** skip        Int           Skip this number of documents in the result set
+  ** batchsize   Int           The max number of documents to return in any intermediate fetch while iterating the Cursor
+  ** fields      Str[]         A List of the field names to return, such that the entire object is not retrieved
+  **
+  Cursor find(Str:Obj? query := [:], Str:Obj? opts := [:])
   {
-    return find().count
+    return Cursor(this, query, opts)
   }
   
-  Cursor find(Map selector := [:], Map opts := [:])
+  **
+  ** Find the first match only for the given query.
+  ** See find() for a description of relevant opts
+  **
+  Map? findOne(Str:Obj? query := [:], Str:Obj? opts := [:])
   {
-    return Cursor(this, selector, opts)
-  }
-  
-  Map? findOne(Map query := [:], Map opts := [:])
-  {
+    opts["batchsize"] = -1 // only return one instance and close the cursor immediately
     return find(query, opts).next
   }
   
-  Map save(Map object, Bool safe := false)
+  **
+  ** Convenience - if object has already been saved, an update() is performed, otherwise insert()
+  **
+  Void save(Str:Obj? object, Bool safe := false)
   {
     if(object.containsKey("_id"))
-      return update(["_id":object["_id"]], object, true, safe)
+      update(["_id":object["_id"]], object, true, safe)
     else
-      return insert(object, safe)
+      insert(object, safe)
   }
   
-  Map insert(Map object, Bool safe := false)
+  **
+  ** Insert a document to the DB.
+  ** Returns the inserted document.  To get the result of
+  ** the insert operation, call DB.lastErr() or set safe to true.
+  ** In the latter case, the lack of a thrown MongoOpErr represents 
+  ** a successful insert.
+  **
+  Void insert(Str:Obj? object, Bool safe := false)
   {
-    return insertDocs([object], safe).first
+    insertDocs([object], safe)
   }
   
-  Map[] insertDocs(Map[] objects, Bool safe := false)
+  **
+  ** Insert a List of documents to the DB.
+  ** Returns the objects inserted.  To get the result of
+  ** the insert operation, call DB.lastErr() or set safe to true.
+  ** In the latter case, the lack of a thrown MongoOpErr represents 
+  ** a successful insert.
+  **
+  Void insertDocs([Str:Obj?][] objects, Bool safe := false)
   {
     b := Buf() { endian = Endian.little }
     b.writeI4(0)                        // reserved
     s := db.connection.getSocket()
     db.connection.sendMsg(s.out, b.flip, MongoOp.INSERT)
     // todo - read last error in strict mode
-    return objects
   }
   
   Map update(Map query, Map doc, Bool upsert := false, Bool multi := false, Bool safe := false)
   internal const Connection connection
   private static const Int[] invalidNameChars := [' ', '.', '\$', '/', '\\']
   
-  internal static const Str SYS_INDEX_COLL      := "system.indexes"
-  internal static const Str SYS_PROFILE_COLL    := "system.profile"
-  
   new make(Str name, Mongo mongo)
   {
     this.name = validateName(name)
     return cmd["ok"] == 1f
   }
   
+  ** 
+  ** Get information on the indexes for the given collection.
+  ** Normally called by Collection.indexInfo. Returns a hash where
+  ** the keys are index names (as returned by Collection.createIndex) and
+  ** the values are lists of [fieldname, direction] pairs specifying the index
+  ** (as passed to Collection.createIndex).
+  **
+  Str:Obj indexInfo(Str coll)
+  {
+    info := [:]
+    idxs := collection("system.indexes").find(["ns": "${this.name}.${coll}"])
+    idxs.each |v, i| {
+      info[v["name"]] = v["key"]
+    }
+    return info
+  }
+  
   Void dropIndex(Str coll, Str idx)
   {
-    cmd := ["deleteIndexes":coll,
-            "index":name] { ordered = true }
+    cmd := Str:Obj?[:] { ordered = true }
+    cmd.set("deleteIndexes", coll).set("index", idx) 
     res := command(cmd)
     if(!cmdOk(res))
-      throw MongoOpErr("dropIndex failed: ${cmd}")
+      throw MongoOpErr("dropIndex failed: ${res}")
   }
   
   Void dropCollection(Str coll)

File test/CollectionTest.fan

   
   Void testInsert()
   {
-    verify(c.insert(["foofoo":567]).containsKey("_id"))
+    c.insert(["foofoo":567])
     verify(c.findOne()["foofoo"] == 567)
     verifyEq(1, c.find().count())
     c.insertDocs([["t":1], ["g":2], ["h":3]])
     c.drop
   }
   
+  Void testIndex()
+  {
+    c := db["idxtest"]
+    c.drop
+    ii := c.indexInfo()
+    verifyEq(0, ii.size)
+    
+    singleidx := c.createIndex([["single":Index.DESCENDING]])
+    ii = c.indexInfo()
+    verifyEq(2, ii.size) // _id is always indexed in addition to what we just added
+    verify(ii.containsKey(singleidx))
+    verifyEq(singleidx, Collection.indexName([["single":Index.DESCENDING]]))
+    
+    doubleidx := c.createIndex([["a":Index.DESCENDING], ["b":Index.ASCENDING]])
+    ii = c.indexInfo()
+    verifyEq(3, ii.size)
+    verify(ii.keys.containsAll([singleidx, doubleidx]))
+    verify((ii[doubleidx] as Map).keys.containsAll(["a", "b"]))
+    
+    uniqueidx := c.createIndex([["uni":Index.ASCENDING]], true)
+    verifyEq(uniqueidx, Collection.indexName([["uni":Index.ASCENDING]]))
+    ii = c.indexInfo()
+    verifyEq(4, ii.size)
+    verify(ii.keys.containsAll([singleidx, doubleidx, uniqueidx]))
+    
+    c.createIndex([["single":Index.DESCENDING]]) // duplicate of first index
+    ii = c.indexInfo()
+    verifyEq(4, ii.size) // shouldn't create another one
+    
+    c.dropIndex(singleidx)
+    ii = c.indexInfo()
+    verifyEq(3, ii.size)
+    
+    c.dropAllIndexes()
+    ii = c.indexInfo()
+    verifyEq(1, ii.size) // leaves the index for _id around
+    
+    c.drop
+  }
+  
+  // Void testValidate()
+  // {
+  //   c := db["testValidate"]
+  //   c.validate() // just run it, confirm no Errs
+  //   c.drop
+  // }
+  
 }