sourceafMongo::MongoConnMgr.fan

using concurrent::ActorPool
using concurrent::Future

** Manages a pool of connections to a MongoDB instance.
** 
** Connections are created on-demand and a total of 'minPoolSize' are kept in a pool when idle. 
** Once the pool is exhausted, any operation requiring a connection will block for (at most) 'waitQueueTimeout' 
** waiting for an available connection.
** 
** This connection manager is created with the standard [Mongo Connection URL]`https://docs.mongodb.org/manual/reference/connection-string/` in the format:
** 
**   mongodb://[username:password@]host[:port][/[defaultauthdb][?options]]
** 
** Examples:
** 
**   mongodb://localhost:27017
**   mongodb://username:password@example1.com/puppies?maxPoolSize=50
** 
** If connecting to a replica set then multiple hosts (with optional ports) may be specified:
** 
**   mongodb://db1.example.net,db2.example.net:2500/?connectTimeoutMS=30000
** 
** See `https://www.mongodb.com/docs/manual/reference/connection-string/` for details.
** 
** On 'startup()' the hosts are queried to find the primary / master node. 
** All read and write operations are performed on the primary node.
** 
** When a connection to the master node is lost, all hosts are re-queried to find the new master.
** 
** Note this connection manager *is* safe for multi-threaded / web-application use.
const class MongoConnMgr {
    
    ** The log instance used to report warnings.
    Log log() { pool.log }
    
    ** Basic details of where this 'ConnectionManager' connects to, for debugging purposes.
    ** When connecting to replica sets, this should indicate the primary. 
    ** 
    ** It *should not* contain any user credentials and *should* be safe to log. 
    ** 
    ** This value is unavailable (returns 'null') until 'startup()' is called. 
    Uri? mongoUrl() { pool.mongoUrl }
    
    ** The parsed Mongo Connection URL. 
    MongoConnUrl mongoConnUrl() { pool.mongoConnUrl }
    
    ** Creates the initial pool and establishes 'minPoolSize' connections with the server.
    ** 
    ** If a connection URL to a replica set is given (a connection URL with multiple hosts) then 
    ** the hosts are queried to find the primary. The primary is currently used for all read and 
    ** write operations. 
    This startup() { pool.startup; return this }
    
    ** Returns 'true' if currently connected to the Master node.
    Bool isConnected() { pool.isConnected }

    ** Makes a connection available to the given function.
    ** 
    ** What ever is returned from the func is returned from the method.
    ** 
    ** If all connections are currently in use, a truncated binary exponential backoff algorithm 
    ** is used to wait for one to become free. If, while waiting, the duration specified in 
    ** 'waitQueueTimeout' expires then a 'MongoErr' is thrown.
    ** 
    ** All leased connections are authenticated against the default credentials
    ** 
    ** Any 'IOErrs' thrown in the fn are assumed to be networking errors, and invoke a topology 
    ** rescan and a Master failover.
    Obj? leaseConn(|MongoConn->Obj?| c) { pool.leaseConn(c) }

    ** Runs the given 'fn' in a Mongo multi-cmd, multi-collection, transaction. 
    ** Should the 'fn' complete normally, the transaction is committed.
    ** If the 'fn' throws an Err, the transaction is aborted / rolled back.
    ** 
    ** pre>
    ** syntax: fantom
    ** runInTxn([
    **   "readConcern"    : [...],
    **   "writeConcern"   : [...],
    **   "timeoutMS"      : 10_000,
    ** ]) {
    **   ...
    **   // do some Mongo stuff
    **   ...
    ** }
    ** <pre
    ** 
    ** The passed function **MUST** be **idempotent** - as it will be re-executed on transient 
    ** MongoDB server errors.
    ** 
    ** Note: The obj passed to 'fn' is undefined and should not be used.
    Void runInTxn([Str:Obj?]? txnOpts, |Obj?| txnFn) { pool.runInTxn(this, txnOpts, txnFn) }
    
    ** Closes all MongoDB connections.
    This shutdown() { pool.shutdown; return this }

    ** Sets the log level to 'debug' to log all cmd request and responses. 
    virtual This setDebug(Bool debugOn := true) {
        log.level = debugOn ? LogLevel.debug : LogLevel.info
        return this
    }
    
    ** Returns properties and statistics about this connection manager.
    ** 
    ** pre>
    ** syntax: fantom
    ** stats() // --> [
    **   "mongoUrl"        : `mongodb://localhost:27017/dbName`,
    **   "hosts"           : ["localhost", "otherhost"]
    **   "maxWireVer"      : 7,
    **   "compression"     : ["zlib", "snappy"]
    **   "sessionTimeout"  : 30min,
    **   "numConns"        : 7,
    **   "numConnsInUse"   : 3,
    **   "primaryFound"    : true,
    ** ]
    ** 
    ** The keys and data returned are for debug info only and are not guaranteed to exist in 
    ** future driver versions.
    Str:Obj? props() { pool.props }
    
    ** (Advanced)
    ** Returns 'true' if the server type is standalone and does not declare any hosts.
    ** Required info for transactions and retryable writes.
    @NoDoc  // advanced
    Bool isStandalone() { pool.isStandalone }

    ** (Advanced)
    ** Authenticates the given connection against the Master, with credentials given via the Mongo connection URL.
    @NoDoc  // advanced
    Void authenticateConn(MongoConn conn) { pool.authenticateConn(conn) }
    
    ** (Advanced)
    ** To be called on a network 'IOErr'.
    ** 
    ** Searches the replica set for the Master node - throws 'MongoErr' if the primary can not be found. 
    ** 
    ** The connection pool is then cleared down and all existing connections closed.
    ** All new connections will then re-connect to their new Master.
    @NoDoc  // advanced
    Future failOver() { pool.failOver }

    ** Creates a pooled Mongo Connection Manager.
    ** 
    ** URL examples:
    **  - 'mongodb://username:password@example1.com/database?maxPoolSize=50'
    **  - 'mongodb://example2.com?minPoolSize=10&maxPoolSize=50&ssl=true'
    ** 
    ** If user credentials are supplied, they are used as default authentication for each connection. 
    ** 
    ** See `MongoConnUrl` for connection URL details.
    new make(Uri connectionUrl, Log? log := null, ActorPool? actorPool := null) { 
        this.pool = MongoConnMgrPool(connectionUrl, log)
    }
    
    internal new _forTest(MongoConnMgrPool pool) {
        this.pool = pool
    }
    
    private const MongoConnMgrPool  pool
    
    internal Int failoverCount() {
        pool->failoverCount
    }
}