git.zd.zone/hrpc/hrpc@v0.0.12/database/mysql/mysql.go (about) 1 package mysql 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "time" 10 11 "git.zd.zone/hrpc/hrpc/database" 12 driver "github.com/go-sql-driver/mysql" 13 ) 14 15 type mySQL struct { 16 conn *sql.DB 17 options Options 18 } 19 20 // TxFunc can be used for transaction operation 21 // If error returned, tx.Rollback() will be called automatically 22 // If nil returned, tx.Commit() will be called automatically, also. 23 type TxFunc func(tx *sql.Tx) error 24 25 // NextFunc is designed for scanning all rows queryed from the database 26 // If error returned, it will cancel the loop in advance, and it will return the error. 27 // If ErrBreak returned, it will also cancel the loop in advance, but it will nil. 28 // If nil returned, it represents everything is OK. 29 type NextFunc func(*sql.Rows) error 30 31 // Proxy is a abstract layer for operating the MySQL database 32 type Proxy interface { 33 // Transaction will start a transaction for the database 34 // Ex. 35 // p.Transaction(ctx, func(tx *sql.Tx) error { 36 // tx.Exec(xxx) 37 // return nil // it will commit the transaction automatically 38 // }) 39 Transaction(ctx context.Context, fn TxFunc) error 40 Query(ctx context.Context, query string, next NextFunc, args ...interface{}) error 41 // QueryRow will query a row from the database 42 // Ex. 43 // var v1 string 44 // if err := p.QueryRow(ctx, `SELECT xx FROM xx`, []interface{}{&v1}, args); err != nil { 45 // // error 46 // } 47 QueryRow(ctx context.Context, query string, dest []interface{}, args ...interface{}) error 48 Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) 49 } 50 51 func (m mySQL) Transaction(ctx context.Context, fn TxFunc) error { 52 tx, err := m.conn.BeginTx(ctx, nil) 53 if err != nil { 54 return err 55 } 56 if err := fn(tx); err != nil { 57 tx.Rollback() 58 return err 59 } 60 tx.Commit() 61 return nil 62 } 63 64 func (m mySQL) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { 65 return m.conn.ExecContext(ctx, query, args...) 66 } 67 68 var ( 69 ErrBreak = errors.New("query break") 70 ErrContinue = errors.New("query continue") 71 ErrNoRows = sql.ErrNoRows 72 ) 73 74 func (m mySQL) Query(ctx context.Context, query string, next NextFunc, args ...interface{}) error { 75 stmt, err := m.conn.PrepareContext(ctx, query) 76 if err != nil { 77 return err 78 } 79 defer stmt.Close() 80 81 rows, err := stmt.QueryContext(ctx, args...) 82 if err != nil { 83 return err 84 } 85 defer rows.Close() 86 for rows.Next() { 87 if err := next(rows); err != nil { 88 if err == ErrBreak { 89 break 90 } 91 if err == ErrContinue { 92 continue 93 } 94 return err 95 } 96 } 97 return nil 98 } 99 100 func (m mySQL) QueryRow(ctx context.Context, query string, dest []interface{}, args ...interface{}) error { 101 stmt, err := m.conn.PrepareContext(ctx, query) 102 if err != nil { 103 return err 104 } 105 defer stmt.Close() 106 107 if err := stmt.QueryRowContext(ctx, args...).Scan(dest...); err != nil { 108 return err 109 } 110 return nil 111 } 112 113 var mm *mySQL 114 115 // Client returns the handler to operate mysql if success 116 func Client() Proxy { 117 return mm 118 } 119 120 func (m *mySQL) Load(src []byte) error { 121 // If the value of customized is true (enabled), 122 // which means DOES NOT use the configurations from the configuration center. 123 if m.options.customized { 124 return nil 125 } 126 if err := json.Unmarshal(src, &m.options); err != nil { 127 return err 128 } 129 return nil 130 } 131 132 func (m mySQL) dataSource() string { 133 cfg := driver.Config{ 134 Addr: fmt.Sprintf("%s:%d", m.options.Address, m.options.Port), 135 User: m.options.Username, 136 Passwd: m.options.Password, 137 Net: "tcp", 138 DBName: m.options.DBName, 139 AllowNativePasswords: true, 140 AllowCleartextPasswords: true, 141 CheckConnLiveness: true, 142 } 143 return cfg.FormatDSN() 144 } 145 146 func (m *mySQL) Connect() error { 147 m.Destory() 148 149 db, err := sql.Open("mysql", m.dataSource()) 150 if err != nil { 151 return err 152 } 153 if err := db.Ping(); err != nil { 154 return err 155 } 156 db.SetMaxOpenConns(m.options.MaxOpenConns) 157 db.SetMaxIdleConns(m.options.MaxIdleConns) 158 db.SetConnMaxLifetime(8 * time.Minute) 159 m.conn = db 160 return nil 161 } 162 163 // Valid returns a bool valud to determine whether the connection is ready to use 164 func Valid() bool { 165 if mm == nil { 166 return false 167 } 168 if mm.conn == nil { 169 return false 170 } 171 if err := mm.conn.Ping(); err != nil { 172 return false 173 } 174 return true 175 } 176 177 func (m mySQL) Name() string { 178 return "mysql" 179 } 180 181 func (m *mySQL) Destory() { 182 if m.conn != nil { 183 m.conn.Close() 184 } 185 } 186 187 func New(opts ...Option) *mySQL { 188 var options = Options{ 189 Port: 3306, 190 MaxOpenConns: 3, 191 MaxIdleConns: 1, 192 customized: false, 193 } 194 for _, o := range opts { 195 o(&options) 196 } 197 if mm != nil { 198 mm.Destory() 199 } 200 mm = &mySQL{ 201 options: options, 202 } 203 return mm 204 } 205 206 var _ database.Database = (*mySQL)(nil)