ls 8 månader sedan
förälder
incheckning
3b94c008c5
12 ändrade filer med 431 tillägg och 181 borttagningar
  1. 18 0
      bytes.go
  2. 68 0
      db/argument.go
  3. 9 79
      db/config.go
  4. 108 94
      db/db.go
  5. 31 0
      db/error.go
  6. 68 0
      db/isolation.go
  7. 24 0
      db/pool.go
  8. 75 0
      db/reply.go
  9. 1 0
      db/struct.go
  10. 16 0
      db/vars.go
  11. 5 2
      go.mod
  12. 8 6
      go.sum

+ 18 - 0
bytes.go

@@ -0,0 +1,18 @@
+package myth
+
+import "unsafe"
+
+// StringToBytes converts string to byte slice without a memory allocation.
+func StringToBytes(s string) []byte {
+	return *(*[]byte)(unsafe.Pointer(
+		&struct {
+			string
+			Cap int
+		}{s, len(s)},
+	))
+}
+
+// BytesToString converts byte slice to string without a memory allocation.
+func BytesToString(b []byte) string {
+	return *(*string)(unsafe.Pointer(&b))
+}

+ 68 - 0
db/argument.go

@@ -0,0 +1,68 @@
+package db
+
+import (
+	"encoding/json"
+	"encoding/xml"
+	"fmt"
+	"strings"
+	"unsafe"
+)
+
+// M is a shortcut for map[string]any
+type M map[string]any
+
+// NewM new map[string]any
+func NewM() M {
+	return make(map[string]any)
+}
+
+// Set key-value to map[string]any
+func (m M) Set(k string, v any) {
+	m[k] = v
+}
+
+// Get value from map[string]interface{}
+func (m M) Get(k string) (v any, ok bool) {
+	v, ok = m[k]
+	return
+}
+
+// String map to json string
+func (m M) String() string {
+	bs, _ := json.Marshal(m)
+	return *(*string)(unsafe.Pointer(&bs))
+}
+
+// Bytes map to json bytes
+func (m M) Bytes() []byte {
+	bs, _ := json.Marshal(m)
+	return bs
+}
+
+// MarshalXML allows type Map to be used with xml.Marshal.
+func (m M) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+	start.Name = xml.Name{
+		Space: "",
+		Local: "map",
+	}
+	if err := e.EncodeToken(start); err != nil {
+		return err
+	}
+	for key, value := range m {
+		elem := xml.StartElement{
+			Name: xml.Name{Space: "", Local: key},
+			Attr: []xml.Attr{},
+		}
+		if err := e.EncodeElement(value, elem); err != nil {
+			return err
+		}
+	}
+
+	return e.EncodeToken(xml.EndElement{Name: start.Name})
+}
+
+// Int64ArrayToIn array to db IN string
+func Int64ArrayToIn(arrs []int64) string {
+	ss := fmt.Sprint(arrs)
+	return strings.Replace(strings.Trim(fmt.Sprint(ss), "[]"), " ", ",", -1)
+}

+ 9 - 79
db/config.go

@@ -2,22 +2,6 @@ package db
 
 import (
 	"time"
-
-	"git.chuangxin1.com/cx/myth"
-)
-
-// DB error code
-const (
-	ErrOK                  = 0 // ErrOK ok
-	ErrException           = 1 // ErrException exception 异常
-	ErrExists              = 2 // ErrExists exists 数据存在
-	ErrNotFound            = 3 // ErrNotFound not found 未找到关键数据
-	ErrAuthorized          = 4 // ErrAuthorized authorized 未认证
-	ErrNotConnect          = 5 // ErrNotConnect connect error 数据库内部连接错误
-	ErrDataNotFound        = 6 // data not found 数据未找到
-	ErrExpired             = 7 // expired 已过期
-	ErrInsufficientBalance = 8 // insufficient balance 余额不足
-	ErrCreditLimit         = 9 // Credit Limit 超额
 )
 
 // Config config
@@ -30,69 +14,15 @@ type Config struct {
 	MaxLifeTime  time.Duration
 }
 
-// Reply db exec return insert/update/delete
-type Reply struct {
-	OK           bool
-	Err          error
-	LastErr      error
-	ErrCode      int
-	LastID       int64
-	RowsAffected int64
-}
-
-// ReplyOk exec ok
-func ReplyOk(rowsAffected, lastID int64) Reply {
-	var reply Reply
-
-	reply.OK = true
-	reply.ErrCode = 0
-	reply.LastID = lastID
-	reply.RowsAffected = rowsAffected
-
-	return reply
-}
-
-// ReplyFaild exec faild
-func ReplyFaild(errCode int, err, errText error) (reply Reply) {
-	reply.OK = false
-	reply.ErrCode = errCode
-	reply.LastID = -1
-	reply.RowsAffected = -1
-
-	reply.Err = err
-	reply.LastErr = errText
-
-	return
-}
+// SetConfig set
+func SetConfig(cfg Config) {
+	config.Driver = cfg.Driver
+	config.DNS = cfg.DNS
 
-// ReplyToReplyData db reply to response
-func ReplyToReplyData(reply Reply) *myth.ReplyData {
-	status := myth.ErrOk
-	if !reply.OK {
-		switch reply.ErrCode {
-		case ErrException:
-			status = myth.ErrException
-		case ErrExists:
-			status = myth.ErrDataExists
-		case ErrNotFound:
-			status = myth.ErrDataNotFound
-		case ErrDataNotFound:
-			status = myth.ErrDataNotFound
-		case ErrAuthorized:
-			status = myth.ErrUnAuthorized
-		case ErrNotConnect:
-			status = myth.ErrNotFound
-		case ErrExpired:
-			status = myth.ErrNotAllowed
-		case ErrInsufficientBalance:
-			status = myth.ErrNotAllowed
-		case ErrCreditLimit:
-			status = myth.ErrNotAllowed
-		default:
-			status = myth.ErrException
-		}
-		return myth.ReplyErr(status, reply.LastErr.Error())
-	}
+	config.MaxOpenConns = cfg.MaxOpenConns
+	config.MaxIdle = cfg.MaxIdle
+	config.MaxIdleTime = cfg.MaxIdleTime * time.Second
+	config.MaxLifeTime = cfg.MaxLifeTime * time.Second
 
-	return myth.ReplyOk()
+	defaultDB = &DB{Driver: config.Driver}
 }

+ 108 - 94
db/db.go

@@ -5,8 +5,6 @@ import (
 	"database/sql"
 	"errors"
 	"fmt"
-	"sync"
-	"time"
 
 	// PostgreSQL
 	_ "github.com/lib/pq"
@@ -16,17 +14,6 @@ import (
 	_ "github.com/mattn/go-sqlite3"
 )
 
-var (
-	config Config
-	db     *sqlx.DB
-	//err    error
-	once sync.Once
-
-	defaultDB *DB
-
-	errNoneConnect = errors.New(`数据库连接错误`)
-)
-
 // DB define
 type DB struct {
 	Driver string
@@ -39,18 +26,6 @@ type Tx struct {
 	tx *sqlx.Tx
 }
 
-// SetConfig set
-func SetConfig(cfg Config) {
-	config.Driver = cfg.Driver
-	config.DNS = cfg.DNS
-	config.MaxOpenConns = cfg.MaxOpenConns
-	config.MaxIdle = cfg.MaxIdle
-	config.MaxIdleTime = cfg.MaxIdleTime * time.Second
-	config.MaxLifeTime = cfg.MaxLifeTime * time.Second
-
-	defaultDB = &DB{Driver: config.Driver}
-}
-
 // New new DB object
 func New() *DB {
 	return &DB{Driver: config.Driver}
@@ -107,7 +82,7 @@ func connect() (dbx *sqlx.DB, err error) {
 			// */
 	})
 	if db == nil {
-		err = errNoneConnect
+		err = ErrNoneConnect
 		return
 	}
 	dbx = db
@@ -127,7 +102,7 @@ func connectContext(ctx context.Context) (dbx *sqlx.DB, err error) {
 		db.DB.SetConnMaxLifetime(config.MaxLifeTime)
 	})
 	if db == nil {
-		err = errNoneConnect
+		err = ErrNoneConnect
 		return
 	}
 	dbx = db
@@ -243,21 +218,21 @@ func (d *DB) Rollback() (err error) {
 }
 
 // TransExec trans execute with named args
-func (d *DB) TransExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) TransExec(query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	var rs sql.Result
 	if rs, err = d.tx.NamedExec(query, args); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
-		LastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
 	}
 	return
 }
 
 // TransExec trans execute with named args
-func (d *DB) TransExecContext(ctx context.Context, query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) TransExecContext(ctx context.Context, query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	var rs sql.Result
 	if rs, err = d.tx.NamedExecContext(ctx, query, args); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
-		LastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
 	}
 	return
 }
@@ -280,7 +255,7 @@ func (d *DB) TransUpdate(query string, args interface{}) (reply Reply) {
 
 // TransRow trans get row
 func (d *DB) TransRow(dest interface{}, query string, args interface{}) (err error) {
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = d.tx.PrepareNamed(query)
 	if err != nil {
 		return err
@@ -324,7 +299,7 @@ func (d *DB) Query(dest interface{}, query string, args interface{}) (err error)
 	}
 	defer d.Close()
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = d.conn.PrepareNamed(query)
 	if err != nil {
 		return
@@ -343,7 +318,7 @@ func (d *DB) QueryContext(ctx context.Context, dest interface{}, query string, a
 	}
 	defer d.Close()
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = d.conn.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return
@@ -362,7 +337,7 @@ func (d *DB) Rows(dest interface{}, query string, args interface{}) (err error)
 	}
 	defer d.Close()
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = d.conn.PrepareNamed(query)
 	if err != nil {
 		return
@@ -381,7 +356,7 @@ func (d *DB) RowsContext(ctx context.Context, dest interface{}, query string, ar
 	}
 	defer d.Close()
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = d.conn.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return err
@@ -448,7 +423,7 @@ func (d *DB) Row(dest interface{}, query string, args interface{}) (err error) {
 	}
 	defer d.Close()
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = d.conn.PrepareNamed(query)
 	if err != nil {
 		return
@@ -467,7 +442,7 @@ func (d *DB) RowContext(ctx context.Context, dest interface{}, query string, arg
 	}
 	defer d.Close()
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = d.conn.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return
@@ -478,6 +453,21 @@ func (d *DB) RowContext(ctx context.Context, dest interface{}, query string, arg
 	return
 }
 
+/*
+// In expands slice values in args, returning the modified query string and a new arg list that can be executed by a database. The `query` should use the `?` bindVar. The return value uses the `?` bindVar.
+func (d *DB) In(query string, args ...interface{}) (q string, params []interface{}, err error) {
+	err = d.Connect()
+	if err != nil {
+		return
+	}
+	defer d.Close()
+	var s string
+	s, params, err = sqlx.In(query, args)
+	q = d.conn.Rebind(s)
+	return
+}
+//*/
+
 // InsertReply insert and return DbReply
 func (d *DB) InsertReply(query string, args interface{}) (reply Reply) {
 	var (
@@ -524,7 +514,7 @@ func (d *DB) UpdateReply(query string, args interface{}) (reply Reply) {
 }
 
 // Insert insert into with named args
-func (d *DB) Insert(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) Insert(query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	err = d.Connect()
 	if err != nil {
 		return
@@ -533,14 +523,14 @@ func (d *DB) Insert(query string, args interface{}) (LastInsertId, RowsAffected
 
 	var rs sql.Result
 	if rs, err = d.conn.NamedExec(query, args); err == nil {
-		LastInsertId, _ = rs.LastInsertId()
-		RowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
 
 // InsertContext insert into with named args
-func (d *DB) InsertContext(ctx context.Context, query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) InsertContext(ctx context.Context, query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	err = d.ConnectContext(ctx)
 	if err != nil {
 		return
@@ -549,14 +539,14 @@ func (d *DB) InsertContext(ctx context.Context, query string, args interface{})
 
 	var rs sql.Result
 	if rs, err = d.conn.NamedExecContext(ctx, query, args); err == nil {
-		LastInsertId, _ = rs.LastInsertId()
-		RowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
 
 // Update update/delete with named args
-func (d *DB) Update(query string, args interface{}) (RowsAffected int64, err error) {
+func (d *DB) Update(query string, args interface{}) (rowsAffected int64, err error) {
 	err = d.Connect()
 	if err != nil {
 		return
@@ -565,13 +555,13 @@ func (d *DB) Update(query string, args interface{}) (RowsAffected int64, err err
 
 	var rs sql.Result
 	if rs, err = d.conn.NamedExec(query, args); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
 
 // Update update/delete with named args
-func (d *DB) UpdateContext(ctx context.Context, query string, args interface{}) (RowsAffected int64, err error) {
+func (d *DB) UpdateContext(ctx context.Context, query string, args interface{}) (rowsAffected int64, err error) {
 	err = d.ConnectContext(ctx)
 	if err != nil {
 		return
@@ -580,13 +570,13 @@ func (d *DB) UpdateContext(ctx context.Context, query string, args interface{})
 
 	var rs sql.Result
 	if rs, err = d.conn.NamedExecContext(ctx, query, args); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
 
 // Exec exec
-func (d *DB) Exec(query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) Exec(query string, args ...interface{}) (lastInsertId, rowsAffected int64, err error) {
 	err = d.Connect()
 	if err != nil {
 		return
@@ -595,14 +585,14 @@ func (d *DB) Exec(query string, args ...interface{}) (LastInsertId, RowsAffected
 
 	var rs sql.Result
 	if rs, err = d.conn.Exec(query, args...); err == nil {
-		LastInsertId, _ = rs.LastInsertId()
-		RowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
 
 // ExecContext exec
-func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (lastInsertId, rowsAffected int64, err error) {
 	err = d.ConnectContext(ctx)
 	if err != nil {
 		return
@@ -611,14 +601,14 @@ func (d *DB) ExecContext(ctx context.Context, query string, args ...interface{})
 
 	var rs sql.Result
 	if rs, err = d.conn.ExecContext(ctx, query, args...); err == nil {
-		LastInsertId, _ = rs.LastInsertId()
-		RowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
 
 // Exec exec, with named args
-func (d *DB) NamedExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) NamedExec(query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	err = d.Connect()
 	if err != nil {
 		return
@@ -627,14 +617,14 @@ func (d *DB) NamedExec(query string, args interface{}) (LastInsertId, RowsAffect
 
 	var rs sql.Result
 	if rs, err = d.conn.NamedExec(query, args); err == nil {
-		LastInsertId, _ = rs.LastInsertId()
-		RowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
 
 // NamedExecContext exec, with named args
-func (d *DB) NamedExecContext(ctx context.Context, query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (d *DB) NamedExecContext(ctx context.Context, query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	err = d.ConnectContext(ctx)
 	if err != nil {
 		return
@@ -643,8 +633,8 @@ func (d *DB) NamedExecContext(ctx context.Context, query string, args interface{
 
 	var rs sql.Result
 	if rs, err = d.conn.NamedExecContext(ctx, query, args); err == nil {
-		LastInsertId, _ = rs.LastInsertId()
-		RowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
 	}
 	return
 }
@@ -670,48 +660,48 @@ func (t *Tx) Rollback() error {
 }
 
 // NamedExec executes a query that doesn't return rows. For example: an INSERT and UPDATE. with named args
-func (t *Tx) NamedExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (t *Tx) NamedExec(query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	var rs sql.Result
 	if rs, err = t.tx.NamedExec(query, args); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
-		LastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
 	}
 	return
 }
 
 // NamedExecContext executes a query that doesn't return rows. For example: an INSERT and UPDATE. with named args
-func (t *Tx) NamedExecContext(ctx context.Context, query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (t *Tx) NamedExecContext(ctx context.Context, query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	var rs sql.Result
 	if rs, err = t.tx.NamedExecContext(ctx, query, args); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
-		LastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
 	}
 	return
 }
 
 // Exec executes a query that doesn't return rows. For example: an INSERT and UPDATE.
-func (t *Tx) Exec(query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (t *Tx) Exec(query string, args ...interface{}) (lastInsertId, rowsAffected int64, err error) {
 	var rs sql.Result
 	if rs, err = t.tx.Exec(query, args...); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
-		LastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
 	}
 	return
 }
 
 // ExecContext executes a query that doesn't return rows. For example: an INSERT and UPDATE.
-func (t *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+func (t *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (lastInsertId, rowsAffected int64, err error) {
 	var rs sql.Result
 	if rs, err = t.tx.ExecContext(ctx, query, args...); err == nil {
-		RowsAffected, _ = rs.RowsAffected()
-		LastInsertId, _ = rs.LastInsertId()
+		rowsAffected, _ = rs.RowsAffected()
+		lastInsertId, _ = rs.LastInsertId()
 	}
 	return
 }
 
 // Query executes a query that returns rows, typically a SELECT. with named args
 func (t *Tx) Query(dest interface{}, query string, args interface{}) (err error) {
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = t.tx.PrepareNamed(query)
 	if err != nil {
 		return
@@ -724,7 +714,7 @@ func (t *Tx) Query(dest interface{}, query string, args interface{}) (err error)
 
 // QueryContext executes a query that returns rows, typically a SELECT. with named args
 func (t *Tx) QueryContext(ctx context.Context, dest interface{}, query string, args interface{}) (err error) {
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = t.tx.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return
@@ -737,7 +727,7 @@ func (t *Tx) QueryContext(ctx context.Context, dest interface{}, query string, a
 
 // QueryRow executes a query that returns rows, typically a SELECT. with named args
 func (t *Tx) QueryRow(dest interface{}, query string, args interface{}) (err error) {
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = t.tx.PrepareNamed(query)
 	if err != nil {
 		return err
@@ -750,7 +740,7 @@ func (t *Tx) QueryRow(dest interface{}, query string, args interface{}) (err err
 
 // QueryRowContext get row with named args
 func (t *Tx) QueryRowContext(ctx context.Context, dest interface{}, query string, args interface{}) (err error) {
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = t.tx.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return err
@@ -761,6 +751,17 @@ func (t *Tx) QueryRowContext(ctx context.Context, dest interface{}, query string
 	return
 }
 
+// Stats returns database statistics.
+func Stats() (s sql.DBStats, err error) {
+	defaultDB.conn, err = connect()
+	if err != nil {
+		return
+	}
+
+	s = defaultDB.Stats()
+	return
+}
+
 // Ping ping connect
 func Ping() (err error) {
 	defaultDB.conn, err = connect()
@@ -801,7 +802,7 @@ func Query(dest interface{}, query string, args interface{}) (err error) {
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamed(query)
 	if err != nil {
 		return
@@ -819,7 +820,7 @@ func QueryContext(ctx context.Context, dest interface{}, query string, args inte
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return
@@ -837,7 +838,7 @@ func Rows(dest interface{}, query string, args interface{}) (err error) {
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamed(query)
 	if err != nil {
 		return
@@ -855,7 +856,7 @@ func RowsContext(ctx context.Context, dest interface{}, query string, args inter
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return
@@ -884,7 +885,7 @@ func QueryRow(dest interface{}, query string, args interface{}) (err error) {
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamed(query)
 	if err != nil {
 		return
@@ -902,7 +903,7 @@ func QueryRowContext(ctx context.Context, dest interface{}, query string, args i
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return
@@ -920,7 +921,7 @@ func Row(dest interface{}, query string, args interface{}) (err error) {
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamed(query)
 	if err != nil {
 		return
@@ -938,7 +939,7 @@ func RowContext(ctx context.Context, dest interface{}, query string, args interf
 		return
 	}
 
-	nstmt := new(sqlx.NamedStmt)
+	nstmt := &sqlx.NamedStmt{}
 	nstmt, err = defaultDB.conn.PrepareNamedContext(ctx, query)
 	if err != nil {
 		return
@@ -949,46 +950,59 @@ func RowContext(ctx context.Context, dest interface{}, query string, args interf
 	return
 }
 
+/*
+// In expands slice values in args, returning the modified query string and a new arg list that can be executed by a database. The `query` should use the `?` bindVar. The return value uses the `?` bindVar.
+func In(query string, args ...interface{}) (q string, params []interface{}, err error) {
+	defaultDB.conn, err = connect()
+	if err != nil {
+		return
+	}
+
+	q, params, err = defaultDB.In(query, args...)
+	return
+}
+// */
+
 // Exec exec
-func Exec(query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+func Exec(query string, args ...interface{}) (lastInsertId, rowsAffected int64, err error) {
 	defaultDB.conn, err = connect()
 	if err != nil {
 		return
 	}
 
-	LastInsertId, RowsAffected, err = defaultDB.Exec(query, args...)
+	lastInsertId, rowsAffected, err = defaultDB.Exec(query, args...)
 	return
 }
 
 // Exec exec
-func ExecContext(ctx context.Context, query string, args ...interface{}) (LastInsertId, RowsAffected int64, err error) {
+func ExecContext(ctx context.Context, query string, args ...interface{}) (lastInsertId, rowsAffected int64, err error) {
 	defaultDB.conn, err = connectContext(ctx)
 	if err != nil {
 		return
 	}
 
-	LastInsertId, RowsAffected, err = defaultDB.ExecContext(ctx, query, args...)
+	lastInsertId, rowsAffected, err = defaultDB.ExecContext(ctx, query, args...)
 	return
 }
 
 // NamedExec exec with named args
-func NamedExec(query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func NamedExec(query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	defaultDB.conn, err = connect()
 	if err != nil {
 		return
 	}
 
-	LastInsertId, RowsAffected, err = defaultDB.NamedExec(query, args)
+	lastInsertId, rowsAffected, err = defaultDB.NamedExec(query, args)
 	return
 }
 
 // NamedExecContext exec with named args
-func NamedExecContext(ctx context.Context, query string, args interface{}) (LastInsertId, RowsAffected int64, err error) {
+func NamedExecContext(ctx context.Context, query string, args interface{}) (lastInsertId, rowsAffected int64, err error) {
 	defaultDB.conn, err = connectContext(ctx)
 	if err != nil {
 		return
 	}
 
-	LastInsertId, RowsAffected, err = defaultDB.NamedExecContext(ctx, query, args)
+	lastInsertId, rowsAffected, err = defaultDB.NamedExecContext(ctx, query, args)
 	return
 }

+ 31 - 0
db/error.go

@@ -0,0 +1,31 @@
+package db
+
+import "errors"
+
+var (
+	ErrNoneConnect = errors.New(`not connect to database`)
+)
+
+// DB error code
+const (
+	// ErrOK ok
+	ErrOK = 0
+	// ErrException exception 异常
+	ErrException = 1
+	// ErrExists exists 数据存在
+	ErrExists = 2
+	// ErrNotFound not found 未找到关键数据
+	ErrNotFound = 3
+	// ErrAuthorized authorized 未认证
+	ErrAuthorized = 4
+	// ErrNotConnect connect error 数据库内部连接错误
+	ErrNotConnect = 5
+	// data not found 数据未找到
+	ErrDataNotFound = 6
+	// expired 已过期
+	ErrExpired = 7
+	// insufficient balance 余额不足
+	ErrInsufficientBalance = 8
+	// Credit Limit 超额
+	ErrCreditLimit = 9
+)

+ 68 - 0
db/isolation.go

@@ -0,0 +1,68 @@
+// https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels
+
+// Isolation levels
+// Of the four ACID properties in a DBMS (Database Management System), the // isolation property is the one most often relaxed. When attempting to maintain the highest level of isolation, a DBMS usually acquires locks on data which may result in a loss of concurrency, or implements multiversion concurrency control. This requires adding logic for the application to function correctly.
+// Most DBMSs offer a number of transaction isolation levels, which control the degree of locking that occurs when selecting data. For many database applications, the majority of database transactions can be constructed to avoid requiring high isolation levels (e.g. SERIALIZABLE level), thus reducing the locking overhead for the system. The programmer must carefully analyze database access code to ensure that any relaxation of isolation does not cause software bugs that are difficult to find. Conversely, if higher isolation levels are used, the possibility of deadlock is increased, which also requires careful analysis and programming techniques to avoid.
+// Since each isolation level is stronger than those below, in that no higher isolation level allows an action forbidden by a lower one, the standard permits a DBMS to run a transaction at an isolation level stronger than that requested (e.g., a "Read committed" transaction may actually be performed at a "Repeatable read" isolation level).
+// The isolation levels defined by the ANSI/ISO SQL standard are listed as follows.
+
+// Serializable
+// This is the highest isolation level.
+// With a lock-based concurrency control DBMS implementation, serializability requires read and write locks (acquired on selected data) to be released at the end of the transaction. Also range-locks must be acquired when a SELECT query uses a ranged WHERE clause, especially to avoid the phantom reads phenomenon.
+// When using non-lock based concurrency control, no locks are acquired; however, if the system detects a write collision among several concurrent transactions, only one of them is allowed to commit. See snapshot isolation for more details on this topic.
+// From : (Second Informal Review Draft) ISO/IEC 9075:1992, Database Language SQL- July 30, 1992: The execution of concurrent SQL-transactions at isolation level SERIALIZABLE is guaranteed to be serializable. A serializable execution is defined to be an execution of the operations of concurrently executing SQL-transactions that produces the same effect as some serial execution of those same SQL-transactions. A serial execution is one in which each SQL-transaction executes to completion before the next SQL-transaction begins.
+
+// Repeatable reads
+// In this isolation level, a lock-based concurrency control DBMS implementation keeps read and write locks (acquired on selected data) until the end of the transaction. However, range-locks are not managed, so phantom reads can occur.
+// Write skew is possible at this isolation level in some systems. Write skew is a phenomenon where two writes are allowed to the same column(s) in a table by two different writers (who have previously read the columns they are updating), resulting in the column having data that is a mix of the two transactions.[3][4]
+
+// Read committed
+// In this isolation level, a lock-based concurrency control DBMS implementation keeps write locks (acquired on selected data) until the end of the transaction, but read locks are released as soon as the SELECT operation is performed (so the non-repeatable reads phenomenon can occur in this isolation level). As in the previous level, range-locks are not managed.
+// Putting it in simpler words, read committed is an isolation level that guarantees that any data read is committed at the moment it is read. It simply restricts the reader from seeing any intermediate, uncommitted, 'dirty' read. It makes no promise whatsoever that if the transaction re-issues the read, it will find the same data; data is free to change after it is read.
+
+// Read uncommitted
+// This is the lowest isolation level. In this level, dirty reads are allowed, so one transaction may see not-yet-committed changes made by other transactions.
+
+package db
+
+import "database/sql"
+
+// IsolationDefault
+func IsolationDefault(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelDefault}
+}
+
+// IsolationReadUncommitted
+func IsolationReadUncommitted(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelReadUncommitted}
+}
+
+// IsolationReadCommitted
+func IsolationReadCommitted(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelReadCommitted}
+}
+
+// IsolationWriteCommitted
+func IsolationWriteCommitted(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelWriteCommitted}
+}
+
+// IsolationRepeatableRead
+func IsolationRepeatableRead(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelRepeatableRead}
+}
+
+// IsolationSnapshot
+func IsolationSnapshot(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelSnapshot}
+}
+
+// IsolationSerializable
+func IsolationSerializable(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelSerializable}
+}
+
+// IsolationLinearizable
+func IsolationLinearizable(readOnly bool) *sql.TxOptions {
+	return &sql.TxOptions{ReadOnly: readOnly, Isolation: sql.LevelLinearizable}
+}

+ 24 - 0
db/pool.go

@@ -0,0 +1,24 @@
+package db
+
+import (
+	"sync"
+
+	"github.com/jmoiron/sqlx"
+)
+
+// *sqlx.NamedStmt pool
+var namedStmtPool = sync.Pool{
+	New: func() any {
+		return &sqlx.NamedStmt{}
+	},
+}
+
+// NewNamedStmt alloc *sqlx.NamedStmt from pool
+func NewNamedStmt() *sqlx.NamedStmt {
+	return namedStmtPool.Get().(*sqlx.NamedStmt)
+}
+
+// PutNamedStmt release *sqlx.NamedStmt to pool
+func PutNamedStmt(stmt *sqlx.NamedStmt) {
+	namedStmtPool.Put(stmt)
+}

+ 75 - 0
db/reply.go

@@ -0,0 +1,75 @@
+package db
+
+import "git.chuangxin1.com/cx/myth"
+
+// Reply db exec return insert/update/delete
+type Reply struct {
+	OK           bool
+	Err          error
+	LastErr      error
+	ErrCode      int
+	LastID       int64
+	RowsAffected int64
+}
+
+// Ok check reply true/false
+func (r Reply) Ok() bool {
+	return r.OK
+}
+
+// ReplyOk exec ok
+func ReplyOk(rowsAffected, lastID int64) Reply {
+	var reply Reply
+
+	reply.OK = true
+	reply.ErrCode = 0
+	reply.LastID = lastID
+	reply.RowsAffected = rowsAffected
+
+	return reply
+}
+
+// ReplyFaild exec faild
+func ReplyFaild(errCode int, err, errText error) (reply Reply) {
+	reply.OK = false
+	reply.ErrCode = errCode
+	reply.LastID = -1
+	reply.RowsAffected = -1
+
+	reply.Err = err
+	reply.LastErr = errText
+
+	return
+}
+
+// ReplyToReplyData db reply to response
+func ReplyToReplyData(reply Reply) *myth.ReplyData {
+	status := myth.ErrOk
+	if !reply.OK {
+		switch reply.ErrCode {
+		case ErrException:
+			status = myth.ErrException
+		case ErrExists:
+			status = myth.ErrDataExists
+		case ErrNotFound:
+			status = myth.ErrDataNotFound
+		case ErrDataNotFound:
+			status = myth.ErrDataNotFound
+		case ErrAuthorized:
+			status = myth.ErrUnAuthorized
+		case ErrNotConnect:
+			status = myth.ErrNotFound
+		case ErrExpired:
+			status = myth.ErrNotAllowed
+		case ErrInsufficientBalance:
+			status = myth.ErrNotAllowed
+		case ErrCreditLimit:
+			status = myth.ErrNotAllowed
+		default:
+			status = myth.ErrException
+		}
+		return myth.ReplyErr(status, reply.LastErr.Error())
+	}
+
+	return myth.ReplyOk()
+}

+ 1 - 0
db/struct.go

@@ -0,0 +1 @@
+package db

+ 16 - 0
db/vars.go

@@ -0,0 +1,16 @@
+package db
+
+import (
+	"sync"
+
+	"github.com/jmoiron/sqlx"
+)
+
+var (
+	config Config
+	db     *sqlx.DB
+	//err    error
+	once sync.Once
+
+	defaultDB *DB
+)

+ 5 - 2
go.mod

@@ -1,6 +1,6 @@
 module git.chuangxin1.com/cx/myth
 
-go 1.17
+go 1.18
 
 require (
 	github.com/go-redis/redis/v7 v7.4.1
@@ -13,4 +13,7 @@ require (
 	gopkg.in/yaml.v2 v2.4.0
 )
 
-require filippo.io/edwards25519 v1.1.0 // indirect
+require (
+	filippo.io/edwards25519 v1.1.0 // indirect
+	google.golang.org/protobuf v1.33.0 // indirect
+)

+ 8 - 6
go.sum

@@ -1,19 +1,18 @@
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
-github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
-github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
 github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
 github.com/go-redis/redis/v7 v7.4.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
-github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
 github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
 github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
@@ -26,7 +25,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
 github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
 github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
@@ -50,6 +48,11 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -58,7 +61,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=