package database import ( "context" "database/sql" "database/sql/driver" "errors" "reflect" "time" "git.yuntuo.hk/k2safe/go-omni/internal/configdiag" ) // PoolConfig contains only driver-independent settings. Connection credentials, // TLS, logging and dial behavior belong to the application's Connector. // Start from DefaultPoolConfig; zero PingTimeout is invalid. type PoolConfig struct { MaxOpenConns int `json:"maxOpenConns"` MaxIdleConns int `json:"maxIdleConns"` ConnMaxLifetime time.Duration `json:"connMaxLifetime"` ConnMaxIdleTime time.Duration `json:"connMaxIdleTime"` PingTimeout time.Duration `json:"pingTimeout"` } func DefaultPoolConfig() PoolConfig { return PoolConfig{MaxOpenConns: 10, MaxIdleConns: 5, ConnMaxLifetime: 30 * time.Minute, ConnMaxIdleTime: 5 * time.Minute, PingTimeout: 5 * time.Second} } func (c PoolConfig) Validate() error { for _, f := range []struct { name string value int }{{"maxOpenConns", c.MaxOpenConns}, {"maxIdleConns", c.MaxIdleConns}} { if f.value < 0 { return configdiag.New(f.name, "must be nonnegative") } } if c.MaxOpenConns > 0 && c.MaxIdleConns > c.MaxOpenConns { return configdiag.New("maxIdleConns", "must not exceed maxOpenConns") } for _, f := range []struct { name string value time.Duration }{{"connMaxLifetime", c.ConnMaxLifetime}, {"connMaxIdleTime", c.ConnMaxIdleTime}} { if f.value < 0 { return configdiag.New(f.name, "must be nonnegative") } } if c.PingTimeout <= 0 { return configdiag.New("pingTimeout", "must be positive") } return nil } func (c PoolConfig) config() Config { return Config{MaxOpenConns: c.MaxOpenConns, MaxIdleConns: c.MaxIdleConns, ConnMaxLifetime: c.ConnMaxLifetime, ConnMaxIdleTime: c.ConnMaxIdleTime, PingTimeout: c.PingTimeout} } // OpenConnector opens an independent pool using the supplied standard Connector. // No registered driver name or DSN is required. It applies cfg and completes a // startup Ping bounded by ctx and PingTimeout before returning. // // Validation failures and an already-canceled ctx leave the Connector untouched. // Once sql.OpenDB is called, the pool owns its lifecycle: a failed startup closes // the pool; on success the caller must Close the returned DB. As in database/sql, // this also closes a Connector that implements io.Closer. Use a separate Connector // for each pool if it owns resources. Nil ctx means context.Background. func OpenConnector(ctx context.Context, connector driver.Connector, cfg PoolConfig) (*DB, error) { if nilConnector(connector) { return nil, errors.New("database connector is required") } if err := cfg.Validate(); err != nil { return nil, err } if ctx == nil { ctx = context.Background() } if err := ctx.Err(); err != nil { return nil, err } return initializePool(ctx, sql.OpenDB(connector), cfg.config()) } func nilConnector(connector driver.Connector) bool { if connector == nil { return true } v := reflect.ValueOf(connector) switch v.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: return v.IsNil() } return false } // PoolConfig returns a copy of the settings used to create this pool. func (db *DB) PoolConfig() PoolConfig { return db.config.PoolConfig() }