-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdb.go
148 lines (133 loc) · 3.33 KB
/
db.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package exql
import (
"context"
"database/sql"
"sync"
"time"
"log"
"golang.org/x/xerrors"
)
type DB interface {
Saver
Mapper
Finder
// DB returns *sql.DB object.
DB() *sql.DB
// SetDB sets *sql.DB object.
SetDB(db *sql.DB)
// Transaction begins a transaction and commits after the callback is called.
// If an error is returned from the callback, it is rolled back.
// Internally call tx.BeginTx(context.Background(), nil).
Transaction(callback func(tx Tx) error) error
// TransactionWithContext is same as Transaction().
// Internally call tx.BeginTx(ctx, opts).
TransactionWithContext(ctx context.Context, opts *sql.TxOptions, callback func(tx Tx) error) error
// Close calls db.Close().
Close() error
}
type db struct {
*saver
*finder
*mapper
db *sql.DB
mutex sync.Mutex
}
// OpenFunc is an abstraction of sql.Open function.
type OpenFunc func(driverName string, url string) (*sql.DB, error)
type OpenOptions struct {
// @required
// DSN format for database connection.
Url string
// @default "mysql"
DriverName string
// @default 5
MaxRetryCount int
// @default 5s
RetryInterval time.Duration
// Custom opener function.
OpenFunc OpenFunc
}
// Open opens the connection to the database and makes exql.DB interface.
func Open(opts *OpenOptions) (DB, error) {
return OpenContext(context.Background(), opts)
}
// OpenContext opens the connection to the database and makes exql.DB interface.
// If something failed, it retries automatically until given retry strategies satisfied
// or aborts handshaking.
//
// Example:
//
// db, err := exql.Open(context.Background(), &exql.OpenOptions{
// Url: "user:pass@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local",
// MaxRetryCount: 3,
// RetryInterval: 10, //sec
// })
func OpenContext(ctx context.Context, opts *OpenOptions) (DB, error) {
if opts.Url == "" {
return nil, xerrors.New("opts.Url is required")
}
driverName := "mysql"
if opts.DriverName != "" {
driverName = opts.DriverName
}
maxRetryCount := 5
retryInterval := 5 * time.Second
if opts.MaxRetryCount > 0 {
maxRetryCount = opts.MaxRetryCount
}
if opts.RetryInterval > 0 {
retryInterval = opts.RetryInterval
}
var d *sql.DB
var err error
var openFunc OpenFunc = sql.Open
if opts.OpenFunc != nil {
openFunc = opts.OpenFunc
}
retryCnt := 0
for retryCnt < maxRetryCount {
d, err = openFunc(driverName, opts.Url)
if err != nil {
goto retry
} else if err = d.PingContext(ctx); err != nil {
goto retry
} else {
goto success
}
retry:
log.Printf("failed to connect database: %s, retrying after %ds...\n", err, int(retryInterval.Seconds()))
<-time.NewTimer(retryInterval).C
retryCnt++
}
if err != nil {
return nil, err
}
success:
return NewDB(d), nil
}
func NewDB(d *sql.DB) DB {
return &db{
saver: newSaver(d),
finder: newFinder(d),
mapper: &mapper{},
db: d,
}
}
func (d *db) Close() error {
return d.db.Close()
}
func (d *db) DB() *sql.DB {
return d.db
}
func (d *db) SetDB(db *sql.DB) {
d.mutex.Lock()
defer d.mutex.Unlock()
d.db = db
d.saver.ex = db
}
func (d *db) Transaction(callback func(tx Tx) error) error {
return d.TransactionWithContext(context.Background(), nil, callback)
}
func (d *db) TransactionWithContext(ctx context.Context, opts *sql.TxOptions, callback func(tx Tx) error) error {
return Transaction(d.db, ctx, opts, callback)
}