diff --git a/go.mod b/go.mod index cb031da..d3bfca1 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,16 @@ module qoobing.com/gomod/model go 1.19.2 require ( - gorm.io/driver/postgres v1.5.9 - gorm.io/gorm v1.25.10 - qoobing.com/gomod/database v0.0.0-20240627111018-316f516e9b69 - qoobing.com/gomod/log v1.2.8 - qoobing.com/gomod/redis v1.3.4 + qoobing.com/gomod/gorm v0.0.2 + qoobing.com/gomod/log v1.4.0 + qoobing.com/gomod/redis v1.3.8 +) + +require ( + github.com/go-sql-driver/mysql v1.7.0 // indirect + gorm.io/driver/mysql v1.5.7 // indirect + gorm.io/driver/postgres v1.5.9 // indirect + gorm.io/gorm v1.25.12 // indirect ) require ( diff --git a/model.go b/model.go index a49efe1..963eb18 100644 --- a/model.go +++ b/model.go @@ -1,234 +1,176 @@ package model import ( - "strings" - "time" + "fmt" + "sync" - "gorm.io/driver/mysql" - "gorm.io/driver/postgres" - "gorm.io/gorm" - "qoobing.com/gomod/database" + "qoobing.com/gomod/gorm" "qoobing.com/gomod/log" "qoobing.com/gomod/redis" - "qoobing.com/gomod/redis/sentinel" -) - -type Model struct { - DB *gorm.DB - DbTxStatus DbTxStatus - Redis redis.Conn - RedisPool *redis.Pool -} - -type DbTxStatus int - -const ( - DBTX_STATUS_TX_NONE DbTxStatus = 0 - DBTX_STATUS_TX_DOING DbTxStatus = 1 - DBTX_STATUS_TX_SUCCESS DbTxStatus = 2 - DBTX_STATUS_TX_FAILED DbTxStatus = 3 ) var ( - defaultDb *database.Config - defaultDsn string - defaultDbDebug bool - defaultGormDB *gorm.DB - defaultRds *redis.Config - defaultRedis *redis.Pool - defaultRedisDebug bool - defaultOptions []Option + instances = map[string]instance{} + errNoModelFound = errorsWrap("named model(%s) not found") + errModeAlreadyInit = errorsWrap("named model(%s) already initilized") + errOpenDBFailed = errorsWrap("open database failed: %s") + errOpenRedisFailed = errorsWrap("open redis failed: %s") ) -func NewModel() *Model { - if len(defaultOptions) == 0 { - panic("No defualt Database&Redis been configed") +func errorsWrap(base string) func(...any) error { + return func(v ...any) error { + return fmt.Errorf(base, v...) } - return NewModelWithOption(defaultOptions...) } -func NewModelDefault() *Model { - return NewModelWithOption(OptOpenDefaultDatabase, OptOpenDefaultRedis) -} +const ( + tx_status_none = 0 + tx_status_doing = 1 + tx_status_commit = 2 + tx_status_rollback = 3 +) -func NewModelDefaultRedis() *Model { - return NewModelWithOption(OptOpenDefaultRedis) -} +type Model struct { + mu sync.Mutex + dbCfg *gorm.Config + redisCfg *redis.Config -func NewModelDefaultDatabase() *Model { - return NewModelWithOption(OptOpenDefaultDatabase) -} - -func NewModelWithOption(options ...Option) *Model { - n := 0 - m := Model{} - //log.Debugf("Start NewModelWithOption...") - for _, option := range options { - n++ - option(&m) - } - //log.Debugf("Finish NewModelWithOption(with %d options)", n) - return &m -} - -func (m *Model) Close() { - if m.Redis != nil { - m.Redis.Close() - } - if m.DB == nil { - m.DbTxStatus = DBTX_STATUS_TX_NONE - } else if m.DbTxStatus == DBTX_STATUS_TX_DOING { - m.DB.Rollback() - m.DbTxStatus = DBTX_STATUS_TX_FAILED - } + db *gorm.DB + dbTxStatus int + redis redis.Conn + redisPool *redis.Pool } func (m *Model) Begin() { - if m.DB == nil { - panic("unreachable code, m.DB is uninitialized") - } else if m.DbTxStatus != DBTX_STATUS_TX_NONE { + if m.DB() == nil { + panic("unreachable code, db is uninitialized") + } else if m.dbTxStatus != tx_status_none { panic("unreachable code, begin transaction towice???") } else { - m.DbTxStatus = DBTX_STATUS_TX_DOING - m.DB = m.DB.Begin() + m.dbTxStatus = tx_status_doing + m.db = m.db.Begin() } } func (m *Model) Commit() error { - if m.DB == nil { - panic("unreachable code, m.DB is uninitialized") - } else if m.DbTxStatus != DBTX_STATUS_TX_DOING { + if m.DB() == nil { + panic("unreachable code, db is uninitialized") + } else if m.dbTxStatus != tx_status_doing { return nil - } else if err := m.DB.Commit().Error; err != nil { - m.DbTxStatus = DBTX_STATUS_TX_FAILED + } else if err := m.db.Commit().Error; err != nil { + m.dbTxStatus = tx_status_rollback return err } else { - m.DbTxStatus = DBTX_STATUS_TX_SUCCESS + m.dbTxStatus = tx_status_commit return nil } } -// Option is model's option -type Option func(*Model) - -// OptOpenDefaultDatabase option function for open default database -func OptOpenDefaultDatabase(m *Model) { - var err error - if defaultDb == nil { - panic("defaultDb not init") +func (m *Model) Close() { + if m.redis != nil { + m.redis.Close() } - - if defaultGormDB != nil { - m.DB = defaultGormDB - //TODO: check cocurrent - m.DB = m.DB.Session(&gorm.Session{QueryFields: true}) - //log.Debugf("Opt for open default database done") - return + if m.db == nil { + m.dbTxStatus = tx_status_none + } else if m.dbTxStatus == tx_status_doing { + m.db.Rollback() + m.dbTxStatus = tx_status_rollback } - - var dialector gorm.Dialector - var gormconfig = &gorm.Config{ - NowFunc: func() time.Time { - return time.Now().UTC() - }, - } - switch defaultDb.Type { - case "mysql": - dialector = mysql.New( - mysql.Config{ - DSN: defaultDsn, - DefaultStringSize: 512, - }, - ) - case "pgsql": - dialector = postgres.New( - postgres.Config{ - DSN: defaultDsn, - PreferSimpleProtocol: true, - }, - ) - default: - panic("UNKNOWN DATABASE TYPE:" + defaultDb.Type) - } - - var db *gorm.DB - db, err = gorm.Open(dialector, gormconfig) - if err != nil { - panic("DATABASE_OPEN_ERROR") - } - - if defaultDbDebug { - m.DB = db.Debug() - } else { - m.DB = db - } - if sqlDB, err := m.DB.DB(); err != nil { - panic("DATABASE_OPEN_ERROR") - } else { - sqlDB.SetMaxIdleConns(3) - sqlDB.SetMaxOpenConns(10) - sqlDB.SetConnMaxLifetime(time.Minute) - } - defaultGormDB = m.DB - - //TODO: check cocurrent - m.DB = m.DB.Session(&gorm.Session{QueryFields: true}) - //log.Debugf("Opt for open default database done") - return } -// OptOpenDefaultRedisSentinelPool option function for open default redis sentinel -func OptOpenDefaultRedis(m *Model) { - if defaultRedis != nil { - m.Redis = defaultRedis.Get() - m.RedisPool = defaultRedis - } else if pool := sentinel.NewPool(*defaultRds); pool == nil { - panic("REDIS_ERROR") - } else { - defaultRedis = pool - m.Redis = defaultRedis.Get() - m.RedisPool = defaultRedis +func (m *Model) DB() *gorm.DB { + return m.db +} + +func (m *Model) Redis() redis.Conn { + return m.redis +} + +func (m *Model) RedisPool() *redis.Pool { + return m.redisPool +} + +type instance struct { + db *gorm.DB + dbCfg *gorm.Config + redis *redis.Pool + redisCfg *redis.Config +} + +// OpenDefault open 'default' model +func OpenDefault() (*Model, error) { + return Open("default") +} + +// OpenDefault open named model, if err is nil, caller MUST call m.Close() in defer. +func Open(name string) (m *Model, err error) { + // check initialize + cfg, ok := instances[name] + if !ok { + return nil, errNoModelFound(name) } - //log.Debugf("Opt for open default redis done") - return + // open redis + var rds redis.Conn + if cfg.redisCfg == nil { + // don't need to open redis + } else if cfg.redis != nil { + rds = cfg.redis.Get() + } else if cfg.redis = redis.NewPool(*cfg.redisCfg); cfg.redis == nil { + return nil, errOpenRedisFailed("new redis pool return nil") + } else { + rds = cfg.redis.Get() + } + defer func() { + if m == nil && rds != nil { + rds.Close() + } + }() + + // open db + var db *gorm.DB + if cfg.dbCfg == nil { + // don't need to open database + } else if cfg.db != nil { + db = gorm.NewSession(cfg.db) + } else if cfg.db, err = gorm.NewDB(*cfg.dbCfg); err != nil { + return nil, errOpenDBFailed(err) + } else { + db = gorm.NewSession(cfg.db) + } + + // success + m = &Model{ + dbCfg: cfg.dbCfg, + redisCfg: cfg.redisCfg, + db: db, + redis: rds, + redisPool: cfg.redis, + } + return m, nil } // Init init default database config & redis config -func Init(_defaultDb *database.Config, _defaultRds *redis.Config) { - defaultDb = _defaultDb - defaultRds = _defaultRds - defaultOptions = []Option{} - - if defaultDb != nil { - // database init - defaultDsn = database.GetDsn(defaultDb) - defaultDbDebug = defaultDb.Debug - - // database debug - if defaultDbDebug && len(defaultDb.Password) > 5 { - p := defaultDb.Password - l := len(p) - secDefaultDsn := strings.Replace(defaultDsn, p[2:l-3], "*****", 1) - log.Infof("defaultDsn='%s'", secDefaultDsn) - } - - defaultOptions = append(defaultOptions, OptOpenDefaultDatabase) - } - - if defaultRds != nil { - // redis init - defaultRds = defaultRds - defaultRedisDebug = defaultRds.Debug - - // redis debug - if defaultRedisDebug && len(defaultRds.Password) > 5 { - p := defaultRds.Password - l := len(p) - secDefaultRds := *defaultRds - secDefaultRds.Password = strings.Replace(p, p[2:l-3], "*****", 1) - log.PrintPretty("defaultRds=", secDefaultRds) - } - defaultOptions = append(defaultOptions, OptOpenDefaultRedis) - } +func InitDefault(_defaultDb *gorm.Config, _defaultRds *redis.Config) error { + return Init("default", _defaultDb, _defaultRds) +} + +// Init init named database config & redis config +func Init(name string, dbCfg *gorm.Config, rdsCfg *redis.Config) error { + _, ok := instances[name] + if ok { + return errModeAlreadyInit(name) + } + instances[name] = instance{ + dbCfg: dbCfg, + redisCfg: rdsCfg, + } + if dbCfg != nil && dbCfg.Debug { + log.Infof("init model(%s) database:%s", name, gorm.GetSecDsn(dbCfg)) + } + if rdsCfg != nil && rdsCfg.Debug { + log.Infof("init model(%s) redis:%s", name, redis.) + } + log.Infof("init model(%s) done", name) + return nil }