github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/toolkit/sqlext/wrapper/wrapper.go (about)

     1  package wrapper
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"github.com/go-redis/cache/v8"
     7  	"github.com/jmoiron/sqlx"
     8  	"github.com/lithammer/shortuuid/v4"
     9  	"github.com/pkg/errors"
    10  	"github.com/unionj-cloud/go-doudou/toolkit/caller"
    11  	"github.com/unionj-cloud/go-doudou/toolkit/sqlext/logger"
    12  	"time"
    13  )
    14  
    15  // DB wraps sqlx.Tx and sqlx.DB https://github.com/jmoiron/sqlx/issues/344#issuecomment-318372779
    16  type DB interface {
    17  	Querier
    18  	BeginTxx(ctx context.Context, opts *sql.TxOptions) (GddTx, error)
    19  	Close() error
    20  }
    21  
    22  // Tx transaction
    23  type Tx interface {
    24  	Querier
    25  	Commit() error
    26  	Rollback() error
    27  }
    28  
    29  // Querier common operations for sqlx.Tx and sqlx.DB
    30  type Querier interface {
    31  	NamedExecContext(ctx context.Context, query string, arg interface{}) (sql.Result, error)
    32  	ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error)
    33  	GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
    34  	Rebind(query string) string
    35  	BindNamed(query string, arg interface{}) (string, []interface{}, error)
    36  	SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) error
    37  }
    38  
    39  // GddDB wraps sqlx.DB
    40  type GddDB struct {
    41  	*sqlx.DB
    42  	logger      logger.SqlLogger
    43  	cacheStore  *cache.Cache
    44  	redisKeyTTL time.Duration
    45  }
    46  
    47  type GddDBOption func(*GddDB)
    48  
    49  func WithLogger(logger logger.SqlLogger) GddDBOption {
    50  	return func(g *GddDB) {
    51  		g.logger = logger
    52  	}
    53  }
    54  
    55  func WithCache(store *cache.Cache) GddDBOption {
    56  	return func(g *GddDB) {
    57  		g.cacheStore = store
    58  	}
    59  }
    60  
    61  func WithRedisKeyTTL(ttl time.Duration) GddDBOption {
    62  	return func(g *GddDB) {
    63  		g.redisKeyTTL = ttl
    64  	}
    65  }
    66  
    67  func NewGddDB(db *sqlx.DB, options ...GddDBOption) GddDB {
    68  	g := &GddDB{
    69  		DB:          db,
    70  		logger:      logger.NewSqlLogger(),
    71  		redisKeyTTL: time.Hour,
    72  	}
    73  	for _, opt := range options {
    74  		opt(g)
    75  	}
    76  	return *g
    77  }
    78  
    79  func (g GddDB) NamedExecContext(ctx context.Context, query string, arg interface{}) (ret sql.Result, err error) {
    80  	var (
    81  		q    string
    82  		args []interface{}
    83  	)
    84  	defer func() {
    85  		g.logger.LogWithErr(ctx, err, nil, q, args...)
    86  	}()
    87  	q, args, err = g.DB.BindNamed(query, arg)
    88  	err = errors.Wrap(err, caller.NewCaller().String())
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	ret, err = g.DB.NamedExecContext(ctx, query, arg)
    93  	err = errors.Wrap(err, caller.NewCaller().String())
    94  	return
    95  }
    96  
    97  func (g GddDB) ExecContext(ctx context.Context, query string, args ...interface{}) (ret sql.Result, err error) {
    98  	defer func() {
    99  		g.logger.LogWithErr(ctx, err, nil, query, args...)
   100  	}()
   101  	ret, err = g.DB.ExecContext(ctx, query, args...)
   102  	err = errors.Wrap(err, caller.NewCaller().String())
   103  	return
   104  }
   105  
   106  func (g GddDB) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) (err error) {
   107  	hit := true
   108  	defer func() {
   109  		g.logger.LogWithErr(ctx, err, &hit, query, args...)
   110  	}()
   111  	if g.cacheStore != nil {
   112  		err = g.cacheStore.Once(&cache.Item{
   113  			Key:   shortuuid.NewWithNamespace(logger.PopulatedSql(query, args...)),
   114  			Value: dest,
   115  			TTL:   g.redisKeyTTL,
   116  			Do: func(*cache.Item) (interface{}, error) {
   117  				hit = false
   118  				err = g.DB.GetContext(ctx, dest, query, args...)
   119  				err = errors.Wrap(err, caller.NewCaller().String())
   120  				return dest, err
   121  			},
   122  		})
   123  		return
   124  	}
   125  	hit = false
   126  	err = g.DB.GetContext(ctx, dest, query, args...)
   127  	err = errors.Wrap(err, caller.NewCaller().String())
   128  	return
   129  }
   130  
   131  func (g GddDB) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) (err error) {
   132  	hit := true
   133  	defer func() {
   134  		g.logger.LogWithErr(ctx, err, &hit, query, args...)
   135  	}()
   136  	if g.cacheStore != nil {
   137  		err = g.cacheStore.Once(&cache.Item{
   138  			Key:   shortuuid.NewWithNamespace(logger.PopulatedSql(query, args...)),
   139  			Value: dest,
   140  			TTL:   g.redisKeyTTL,
   141  			Do: func(*cache.Item) (interface{}, error) {
   142  				hit = false
   143  				err = g.DB.SelectContext(ctx, dest, query, args...)
   144  				err = errors.Wrap(err, caller.NewCaller().String())
   145  				return dest, err
   146  			},
   147  		})
   148  		return
   149  	}
   150  	hit = false
   151  	err = g.DB.SelectContext(ctx, dest, query, args...)
   152  	err = errors.Wrap(err, caller.NewCaller().String())
   153  	return
   154  }
   155  
   156  // BeginTxx begins a transaction
   157  func (g GddDB) BeginTxx(ctx context.Context, opts *sql.TxOptions) (GddTx, error) {
   158  	tx, err := g.DB.BeginTxx(ctx, opts)
   159  	if err != nil {
   160  		return GddTx{}, err
   161  	}
   162  	return GddTx{tx, g.logger, g.cacheStore, g.redisKeyTTL}, nil
   163  }
   164  
   165  // GddTx wraps sqlx.Tx
   166  type GddTx struct {
   167  	*sqlx.Tx
   168  	logger      logger.SqlLogger
   169  	cacheStore  *cache.Cache
   170  	redisKeyTTL time.Duration
   171  }
   172  
   173  func (g GddTx) NamedExecContext(ctx context.Context, query string, arg interface{}) (ret sql.Result, err error) {
   174  	var (
   175  		q    string
   176  		args []interface{}
   177  	)
   178  	defer func() {
   179  		g.logger.LogWithErr(ctx, err, nil, q, args...)
   180  	}()
   181  	q, args, err = g.Tx.BindNamed(query, arg)
   182  	err = errors.Wrap(err, caller.NewCaller().String())
   183  	if err != nil {
   184  		return nil, err
   185  	}
   186  	ret, err = g.Tx.NamedExecContext(ctx, query, arg)
   187  	err = errors.Wrap(err, caller.NewCaller().String())
   188  	return
   189  }
   190  
   191  func (g GddTx) ExecContext(ctx context.Context, query string, args ...interface{}) (ret sql.Result, err error) {
   192  	defer func() {
   193  		g.logger.LogWithErr(ctx, err, nil, query, args...)
   194  	}()
   195  	ret, err = g.Tx.ExecContext(ctx, query, args...)
   196  	err = errors.Wrap(err, caller.NewCaller().String())
   197  	return
   198  }
   199  
   200  func (g GddTx) GetContext(ctx context.Context, dest interface{}, query string, args ...interface{}) (err error) {
   201  	hit := true
   202  	defer func() {
   203  		g.logger.LogWithErr(ctx, err, &hit, query, args...)
   204  	}()
   205  	if g.cacheStore != nil {
   206  		err = g.cacheStore.Once(&cache.Item{
   207  			Key:   shortuuid.NewWithNamespace(logger.PopulatedSql(query, args...)),
   208  			Value: dest,
   209  			TTL:   g.redisKeyTTL,
   210  			Do: func(*cache.Item) (interface{}, error) {
   211  				hit = false
   212  				err = g.Tx.GetContext(ctx, dest, query, args...)
   213  				err = errors.Wrap(err, caller.NewCaller().String())
   214  				return dest, err
   215  			},
   216  		})
   217  		return
   218  	}
   219  	hit = false
   220  	err = g.Tx.GetContext(ctx, dest, query, args...)
   221  	err = errors.Wrap(err, caller.NewCaller().String())
   222  	return
   223  }
   224  
   225  func (g GddTx) SelectContext(ctx context.Context, dest interface{}, query string, args ...interface{}) (err error) {
   226  	hit := true
   227  	defer func() {
   228  		g.logger.LogWithErr(ctx, err, &hit, query, args...)
   229  	}()
   230  	if g.cacheStore != nil {
   231  		err = g.cacheStore.Once(&cache.Item{
   232  			Key:   shortuuid.NewWithNamespace(logger.PopulatedSql(query, args...)),
   233  			Value: dest,
   234  			TTL:   g.redisKeyTTL,
   235  			Do: func(*cache.Item) (interface{}, error) {
   236  				hit = false
   237  				err = g.Tx.SelectContext(ctx, dest, query, args...)
   238  				err = errors.Wrap(err, caller.NewCaller().String())
   239  				return dest, err
   240  			},
   241  		})
   242  		return
   243  	}
   244  	hit = false
   245  	err = g.Tx.SelectContext(ctx, dest, query, args...)
   246  	err = errors.Wrap(err, caller.NewCaller().String())
   247  	return
   248  }