sourceafMongo::MongoCmd.fan


** The principle class that communicates with MongoDB.
class MongoCmd {
    private MongoSess?  session

    ** The connection manager that Mongo connections are leased from.
    const MongoConnMgr  connMgr

    ** The name of the database.
    const Str           dbName
    
    ** The name of this cmd.
    const Str           cmdName
    
    ** The value of this cmd.
    const Obj?          cmdVal

    ** The backing cmd document.
    Str:Obj? cmd {
        private set
    } 
    
    ** Creates a new 'MongoCmd'.
    new make(MongoConnMgr connMgr, Str dbName, Str cmdName, Obj? cmdVal := 1, Obj? session := null) {
        this.connMgr    = connMgr
        this.dbName     = dbName
        this.cmdName    = cmdName
        this.cmdVal     = cmdVal
        this.session    = session   // cursors and txns need to specify a session
        this.cmd        = Str:Obj?[:]
        this.cmd.ordered= true
        this.add(cmdName, cmdVal)
    }

    ** Adds the given 'val' - but only if it does not aleady exist in the cmd.
    ** 
    ** If 'val' is null, it is not added.
    ** 
    ** Use to chain 'add()' methods.
    This add(Str key, Obj? val) {
        if (val != null && cmd.containsKey(key) == false)
            cmd.add(key, val)
        return this
    }   

    ** Adds all the given vals - but only if they don't already exist in the cmd.
    ** 
    ** If 'all' is null, it is not added.
    ** 
    ** Use to chain 'add()' methods.
    This addAll([Str:Obj?]? all) {
        if (all != null)
            all.each |v, k| { this.add(k, v) }
        return this
    }   
    
    ** Like 'with()', but 'fn' may be 'null'.
    @NoDoc
    This withFn(|MongoCmd|? fn) {
        fn?.call(this)
        return this
    }

    ** Returns 'true' if this cmd contains the given key.
    Bool containsKey(Str key) {
        cmd.containsKey(key)
    }

    ** Extracts the given keys into a Map.
    @NoDoc
    Str:Obj? extract(Str[] keys) {
        map := Str:Obj?[:]
        map.ordered = true
        for (i := 0; i < keys.size; ++i) {
            key := keys[i]
            val := cmd.remove(key)
            if (val != null)
                map[key] = val          
        }
        return map
    }
    
    ** Returns a value from the cmd.
    @Operator
    Obj? get(Str key) {
        cmd[key]
    }
    
    ** Sets a value in the cmd.
    @Operator
    This set(Str key, Obj? val) {
        cmd[key] = val
        return this
    }   
    
    ** Trap operator for 'get()' and 'add()'.
    override Obj? trap(Str name, Obj?[]? args := null) {
        if (args == null || args.isEmpty)
            return get(name)
        if (args.size == 1)
            return add(name, args.first)
        throw UnsupportedErr("MongoCmd->${name}(${args})")
    }

    ** Executes this cmd on the MongoDB server, and returns the response as a BSON document.
    Str:Obj? run(Bool checked := true) {
        doc := (Str:Obj?) connMgr.leaseConn |conn->Str:Obj?| {
            if (this.session != null)
                conn._setSession(session)
            try return MongoOp(connMgr, conn, cmd).runCommand(dbName, checked)
            finally // don't let detatched sessions get checked back in!
                if (this.session != null || this.session?.isDetached == true)
                    conn._setSession(null)
        }
        return doc
    }
    
    ** Executes this cmd on the MongoDB server, and preemptively interprets the response as a cursor.
    MongoCur cursor() {
        connMgr.leaseConn |conn->MongoCur| {
            doc     := MongoOp(connMgr, conn, cmd).runCommand(dbName)
            cur     := doc["cursor"] as Str:Obj?
            curId   := cur["id"]
            sess    := curId == 0 ? null : conn._detachSession
            cursor  := MongoCur(connMgr, dbName, cmdVal.toStr, curId, cur["firstBatch"], sess)
            // these values need to be set per request
            if (cmd["batchSize"] != null)
                cursor.batchSize = cmd["batchSize"] as Int
            if (cmd["maxTimeMS"] != null)
                cursor.maxTime = (cmd["maxTimeMS"] as Int)?.mult(1ms.ticks)?.toDuration
            return  cursor
        }
    }
}