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 }