Skip to content

Commit 2cbdbe1

Browse files
authored
feat: add custom opener for exql.Open (#53)
1 parent 4bcfa81 commit 2cbdbe1

9 files changed

Lines changed: 98 additions & 195 deletions

File tree

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
.idea
22
dist
33
coverage.out
4-
.vscode
54
compose.yml

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"explorer.fileNesting.enabled": true,
3+
"explorer.fileNesting.patterns": {
4+
"*.go": "${capture}_test.go",
5+
"go.mod": "go.sum"
6+
}
7+
}

db.go

Lines changed: 33 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
"log"
1010

11-
q "github.com/loilo-inc/exql/v2/query"
11+
"golang.org/x/xerrors"
1212
)
1313

1414
type DB interface {
@@ -31,35 +31,50 @@ type DB interface {
3131
}
3232

3333
type db struct {
34+
*saver
35+
*finder
36+
*mapper
3437
db *sql.DB
35-
s *saver
36-
f *finder
3738
mutex sync.Mutex
3839
}
3940

41+
// OpenFunc is an abstraction of sql.Open function.
42+
type OpenFunc func(driverName string, url string) (*sql.DB, error)
43+
4044
type OpenOptions struct {
41-
// @default "mysql"
42-
DriverName string
45+
// @required
4346
// DSN format for database connection.
4447
Url string
48+
// @default "mysql"
49+
DriverName string
4550
// @default 5
4651
MaxRetryCount int
4752
// @default 5s
4853
RetryInterval time.Duration
54+
// Custom opener function.
55+
OpenFunc OpenFunc
4956
}
5057

5158
// Open opens the connection to the database and makes exql.DB interface.
59+
func Open(opts *OpenOptions) (DB, error) {
60+
return OpenContext(context.Background(), opts)
61+
}
62+
63+
// OpenContext opens the connection to the database and makes exql.DB interface.
5264
// If something failed, it retries automatically until given retry strategies satisfied
5365
// or aborts handshaking.
5466
//
5567
// Example:
5668
//
57-
// db, err := exql.Open(&exql.OpenOptions{
69+
// db, err := exql.Open(context.Background(), &exql.OpenOptions{
5870
// Url: "user:pass@tcp(127.0.0.1:3306)/database?charset=utf8mb4&parseTime=True&loc=Local",
5971
// MaxRetryCount: 3,
6072
// RetryInterval: 10, //sec
6173
// })
62-
func Open(opts *OpenOptions) (DB, error) {
74+
func OpenContext(ctx context.Context, opts *OpenOptions) (DB, error) {
75+
if opts.Url == "" {
76+
return nil, xerrors.New("opts.Url is required")
77+
}
6378
driverName := "mysql"
6479
if opts.DriverName != "" {
6580
driverName = opts.DriverName
@@ -74,12 +89,16 @@ func Open(opts *OpenOptions) (DB, error) {
7489
}
7590
var d *sql.DB
7691
var err error
92+
var openFunc OpenFunc = sql.Open
93+
if opts.OpenFunc != nil {
94+
openFunc = opts.OpenFunc
95+
}
7796
retryCnt := 0
7897
for retryCnt < maxRetryCount {
79-
d, err = sql.Open(driverName, opts.Url)
98+
d, err = openFunc(driverName, opts.Url)
8099
if err != nil {
81100
goto retry
82-
} else if err = d.Ping(); err != nil {
101+
} else if err = d.PingContext(ctx); err != nil {
83102
goto retry
84103
} else {
85104
goto success
@@ -98,71 +117,14 @@ success:
98117

99118
func NewDB(d *sql.DB) DB {
100119
return &db{
101-
db: d,
102-
s: &saver{ex: d},
103-
f: newFinder(d),
120+
saver: newSaver(d),
121+
finder: newFinder(d),
122+
mapper: &mapper{},
123+
db: d,
104124
}
105125
}
106126

107-
func (d *db) Insert(modelPtr Model) (sql.Result, error) {
108-
return d.s.Insert(modelPtr)
109-
}
110-
111-
func (d *db) InsertContext(ctx context.Context, modelPtr Model) (sql.Result, error) {
112-
return d.s.InsertContext(ctx, modelPtr)
113-
}
114-
115-
func (d *db) Update(table string, set map[string]interface{}, where q.Condition) (sql.Result, error) {
116-
return d.s.Update(table, set, where)
117-
}
118-
119-
func (d *db) UpdateModel(ptr ModelUpdate, where q.Condition) (sql.Result, error) {
120-
return d.s.UpdateModel(ptr, where)
121-
}
122-
123-
func (d *db) UpdateContext(ctx context.Context, table string, set map[string]interface{}, where q.Condition) (sql.Result, error) {
124-
return d.s.UpdateContext(ctx, table, set, where)
125-
}
126-
127-
func (d *db) UpdateModelContext(ctx context.Context, ptr ModelUpdate, where q.Condition) (sql.Result, error) {
128-
return d.s.UpdateModelContext(ctx, ptr, where)
129-
}
130-
131-
func (d *db) Delete(table string, where q.Condition) (sql.Result, error) {
132-
return d.s.Delete(table, where)
133-
}
134-
135-
func (d *db) DeleteContext(ctx context.Context, table string, where q.Condition) (sql.Result, error) {
136-
return d.s.DeleteContext(ctx, table, where)
137-
}
138-
139-
func (d *db) Exec(query q.Query) (sql.Result, error) {
140-
return d.s.Exec(query)
141-
}
142-
143-
func (d *db) ExecContext(ctx context.Context, query q.Query) (sql.Result, error) {
144-
return d.s.ExecContext(ctx, query)
145-
}
146-
147-
func (d *db) Query(query q.Query) (*sql.Rows, error) {
148-
return d.s.Query(query)
149-
}
150-
151-
func (d *db) QueryContext(ctx context.Context, query q.Query) (*sql.Rows, error) {
152-
return d.s.QueryContext(ctx, query)
153-
}
154-
155-
func (d *db) QueryRow(query q.Query) (*sql.Row, error) {
156-
return d.s.QueryRow(query)
157-
}
158-
159-
func (d *db) QueryRowContext(ctx context.Context, query q.Query) (*sql.Row, error) {
160-
return d.s.QueryRowContext(ctx, query)
161-
}
162-
163127
func (d *db) Close() error {
164-
d.mutex.Lock()
165-
defer d.mutex.Unlock()
166128
return d.db.Close()
167129
}
168130

@@ -174,7 +136,7 @@ func (d *db) SetDB(db *sql.DB) {
174136
d.mutex.Lock()
175137
defer d.mutex.Unlock()
176138
d.db = db
177-
d.s.ex = db
139+
d.saver.ex = db
178140
}
179141

180142
func (d *db) Transaction(callback func(tx Tx) error) error {
@@ -184,33 +146,3 @@ func (d *db) Transaction(callback func(tx Tx) error) error {
184146
func (d *db) TransactionWithContext(ctx context.Context, opts *sql.TxOptions, callback func(tx Tx) error) error {
185147
return Transaction(d.db, ctx, opts, callback)
186148
}
187-
188-
// Find implements DB
189-
func (d *db) Find(q q.Query, destPtrOfStruct any) error {
190-
return d.f.Find(q, destPtrOfStruct)
191-
}
192-
193-
// FindContext implements DB
194-
func (d *db) FindContext(ctx context.Context, q q.Query, destPtrOfStruct any) error {
195-
return d.f.FindContext(ctx, q, destPtrOfStruct)
196-
}
197-
198-
// FindMany implements DB
199-
func (d *db) FindMany(q q.Query, destSlicePtrOfStruct any) error {
200-
return d.f.FindMany(q, destSlicePtrOfStruct)
201-
}
202-
203-
// FindManyContext implements DB
204-
func (d *db) FindManyContext(ctx context.Context, q q.Query, destSlicePtrOfStruct any) error {
205-
return d.f.FindManyContext(ctx, q, destSlicePtrOfStruct)
206-
}
207-
208-
// Deprecated: Use Find or MapRow. It will be removed in next version.
209-
func (d *db) Map(rows *sql.Rows, destPtr any) error {
210-
return MapRow(rows, destPtr)
211-
}
212-
213-
// Deprecated: Use FindContext or MapRows. It will be removed in next version.
214-
func (d *db) MapMany(rows *sql.Rows, destSlicePtr any) error {
215-
return MapRows(rows, destSlicePtr)
216-
}

db_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package exql_test
22

33
import (
4+
"context"
5+
"database/sql"
46
"testing"
57

68
"github.com/loilo-inc/exql/v2"
9+
"github.com/loilo-inc/exql/v2/test"
710
"github.com/stretchr/testify/assert"
811
)
912

@@ -18,3 +21,36 @@ func TestNewDB(t *testing.T) {
1821
db := exql.NewDB(d)
1922
assert.Equal(t, d, db.DB())
2023
}
24+
25+
func TestOpen(t *testing.T) {
26+
t.Run("should call OpenContext", func(t *testing.T) {
27+
d, err := exql.Open(&exql.OpenOptions{
28+
Url: test.DbUrl,
29+
})
30+
if err != nil {
31+
t.Fatal(err)
32+
}
33+
assert.NotNil(t, d)
34+
})
35+
}
36+
37+
func TestOpenContext(t *testing.T) {
38+
t.Run("should return error when url is empty", func(t *testing.T) {
39+
_, err := exql.OpenContext(context.TODO(), &exql.OpenOptions{
40+
Url: "",
41+
})
42+
assert.EqualError(t, err, "opts.Url is required")
43+
})
44+
t.Run("with custom opener", func(t *testing.T) {
45+
var called bool
46+
_, err := exql.OpenContext(context.TODO(), &exql.OpenOptions{
47+
Url: test.DbUrl,
48+
OpenFunc: func(driverName string, url string) (*sql.DB, error) {
49+
called = true
50+
return sql.Open(driverName, url)
51+
},
52+
})
53+
assert.NoError(t, err)
54+
assert.True(t, called)
55+
})
56+
}

mapper.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,18 @@ type Mapper interface {
1919
MapMany(rows *sql.Rows, destSlicePtr any) error
2020
}
2121

22+
type mapper struct{}
23+
24+
// Map reads data from single row and maps those columns into destination struct.
25+
func (m *mapper) Map(rows *sql.Rows, destPtr any) error {
26+
return MapRow(rows, destPtr)
27+
}
28+
29+
// MapMany reads all data from rows and maps those columns for each destination struct.
30+
func (m *mapper) MapMany(rows *sql.Rows, destSlicePtr any) error {
31+
return MapRows(rows, destSlicePtr)
32+
}
33+
2234
type ColumnSplitter func(i int) string
2335

2436
// SerialMapper is an interface for mapping a joined row into one or more destinations serially.

test/db.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package test
2+
3+
const DbUrl = "root:@tcp(127.0.0.1:13326)/exql?charset=utf8mb4&parseTime=True&loc=Local"

test_db_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import (
55

66
_ "github.com/go-sql-driver/mysql"
77
"github.com/loilo-inc/exql/v2"
8+
"github.com/loilo-inc/exql/v2/test"
89
)
910

1011
func testDb() exql.DB {
1112
db, err := exql.Open(&exql.OpenOptions{
12-
Url: "root:@tcp(127.0.0.1:13326)/exql?charset=utf8mb4&parseTime=True&loc=Local",
13+
Url: test.DbUrl,
1314
})
1415
if err != nil {
1516
panic(err)
@@ -18,7 +19,7 @@ func testDb() exql.DB {
1819
}
1920

2021
func testSqlDB() *sql.DB {
21-
db, err := sql.Open("mysql", "root:@tcp(127.0.0.1:13326)/exql?charset=utf8mb4&parseTime=True&loc=Local")
22+
db, err := sql.Open("mysql", test.DbUrl)
2223
if err != nil {
2324
panic(err)
2425
}

tool/composegen/main.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ func main() {
2020
log.Fatalf("unsupported arch: %s", arch)
2121
}
2222
yml := fmt.Sprintf(`
23-
version: "3.7"
2423
services:
2524
mysql:
2625
container_name: exql_mysql8

0 commit comments

Comments
 (0)