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)