github.com/unionj-cloud/go-doudou/v2@v2.3.5/toolkit/sqlext/wrapper/wrapper.go (about)

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