sourceafMongo::MongoIdx.fan


** Represents a MongoDB index.
const class MongoIdx {
    
    ** Use in 'key' arguments to denote field sort order.
    static const Int ASC    := 1
    
    ** Use in 'key' arguments to denote field sort order.
    static const Int DESC   := -1
    
    ** Use in 'key' arguments to denote a text index on the field.
    static const Str TEXT   := "text"

    ** The underlying connection manager.
    const MongoConnMgr connMgr
    
    ** The name of the database.
    const Str   dbName
    
    ** The simple name of the collection.
    const Str   collName
    
    ** The simple name of this index. 
    const Str   name
    
    new make(MongoConnMgr connMgr, Str idxName, Str colName, Str? dbName := null) {
        this.connMgr    = connMgr
        this.dbName     = MongoDb.validateName(dbName ?: connMgr.mongoConnUrl.dbName)
        this.collName   = MongoColl.validateName(colName)
        this.name       = idxName
    }
    
    ** Creates an 'MongoDb' instance of the associated DB.
    MongoDb db() {
        MongoDb(connMgr, dbName)
    }
    
    ** Creates a 'MongoColl' instance of the associated Collection.
    MongoColl coll() {
        MongoColl(connMgr, collName, dbName)
    }

    ** Returns index info.
    ** 
    ** Returns 'null' if index does not exist.
    ** 
    ** @see `https://www.mongodb.com/docs/manual/reference/command/listIndexes/`
    [Str:Obj?]? info() {
        cmd("listIndexes", collName)
            .cursor
            .toList
            .find { it["name"] == name }
    }
    
    ** Returns 'true' if this index exists.
    Bool exists() {
        info != null    
    }
    
    ** Creates this index.
    ** 
    ** 'key' is a map of fields to index type. 
    ** If it contains more than 1 entry, it must be ordered.
    ** 
    ** Values should be the standard Mongo '1' and '-1' for ascending / descending, 
    ** or the string 'TEXT'.
    ** 
    ** pre>
    ** syntax: fantom
    ** index.create([
    **   "dateAdded" : MongoIdx.DESC,
    **   "name"      : MongoIdx.ASC,
    ** ], false) {
    **   it->background         = true   // build in background
    **   it->expireAfterSeconds = 60     // time in secs
    ** }
    ** <pre
    ** 
    ** Text indexes need to specify weights for each field.
    ** 
    ** pre>
    ** syntax: fantom
    ** index.create([
    **   "boringText"         : MongoIdx.TEXT,
    **   "importantText"      : MongoIdx.TEXT,
    ** ], false) {
    **   it->default_language = "english",  // optional
    **   it->collation        = [...],      // optional 
    **   it->weights          = [
    **     "boringText"       : 5,
    **     "importantText"    : 15          // is x3 more important than boring!
    **   ]  
    ** }
    ** <pre
    ** 
    ** Note that Text Indexes are NOT part of the Mongo Stable API.
    ** 
    ** @see `https://www.mongodb.com/docs/manual/reference/command/createIndexes/`
    ** @see `https://www.mongodb.com/docs/manual/core/index-text/`
    Str:Obj? create(Str:Obj key, Bool unique := false, |MongoCmd cmd|? optsFn := null) {
        if (key.size > 1 && key.ordered == false)
            throw ArgErr("Maps with more than 1 entry must be ordered: ${key}")
        createCmd := cmd("name",    name)
            .withFn(                optsFn)
            .add("key",             key)
            .add("unique",          unique)
        opts := createCmd.extract("writeConcern commitQuorum comment".split)
        return cmd("createIndexes", collName)
            .add("indexes",         [createCmd.cmd])
            .addAll(                opts)
            .add("writeConcern",    connMgr.mongoConnUrl.writeConcern)
            .run
    }
    
    ** Ensures this index exists.
    ** Returns 'true' if the index was (re)-created, 'false' if nothing changed.
    ** 
    ** If the index does not exist, it is created. 
    ** 
    ** If does exist, but with a different key, it is dropped and re-created. (Options are not checked.)
    Bool ensure(Str:Obj key, Bool unique := false, |MongoCmd cmd|? optsFn := null) {
        if (key.size > 1 && key.ordered == false)
            throw ArgErr("Maps with more than 1 entry must be ordered: ${key}")

        if (!exists) {
            create(key, unique, optsFn)
            return true
        }

        info    := this.info
        oldUni  := info["unique"] ?: false
        oldKey  := info["key"] as Str:Obj?
        newKey  := Map.make(oldKey.typeof).addAll(key)

        // some options just concern building the index, others are just not returned, 
        // so they're impossible to compare ... so lets just not try!
        if (optsFn == null && oldUni == unique && oldKey == newKey) {
            return false
        }

        drop
        create(key, unique, optsFn)
        return true
    }
    
    ** Drops this index, but only if it exists.
    ** 
    ** If 'force' is 'true' then the index is dropped regardless. 
    ** Note this may result in an error if the index does not exist.
    ** 
    ** @see `https://www.mongodb.com/docs/manual/reference/command/dropIndexes/`
    Void drop(Bool force := false) {
        if (force || exists) cmd("dropIndexes", collName).add("index", name).run
    }

    ** **For Power Users!**
    ** 
    ** Don't forget to call 'run()' on the returned command!
    private MongoCmd cmd(Str cmdName, Obj? cmdVal := 1) {
        MongoCmd(connMgr, dbName, cmdName, cmdVal)
    }
    
    @NoDoc
    override Str toStr() {
        "${dbName}.${collName}::${name}"
    }
}