Skip to content

Commit 1a32710

Browse files
Copilotcschleiden
andcommitted
Implement NewMysqlBackendWithDB constructor for existing DB connections
Co-authored-by: cschleiden <[email protected]>
1 parent 604bff3 commit 1a32710

File tree

4 files changed

+211
-14
lines changed

4 files changed

+211
-14
lines changed

backend/mysql/mysql.go

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ import (
3232
var migrationsFS embed.FS
3333

3434
func NewMysqlBackend(host string, port int, user, password, database string, opts ...option) *mysqlBackend {
35+
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&interpolateParams=true", user, password, host, port, database)
36+
37+
db, err := sql.Open("mysql", dsn)
38+
if err != nil {
39+
panic(err)
40+
}
41+
42+
return newMysqlBackend(db, dsn, opts...)
43+
}
44+
45+
// NewMysqlBackendWithDB creates a new MySQL backend using an existing database connection.
46+
// The provided database connection should already be configured and connected to the target database.
47+
// Note: Migrations will be applied using the provided connection directly, so ensure the connection
48+
// supports multiple statements if ApplyMigrations is enabled (default: true).
49+
func NewMysqlBackendWithDB(db *sql.DB, opts ...option) *mysqlBackend {
50+
return newMysqlBackend(db, "", opts...)
51+
}
52+
53+
func newMysqlBackend(db *sql.DB, dsn string, opts ...option) *mysqlBackend {
3554
options := &options{
3655
Options: backend.ApplyOptions(),
3756
ApplyMigrations: true,
@@ -41,13 +60,6 @@ func NewMysqlBackend(host string, port int, user, password, database string, opt
4160
opt(options)
4261
}
4362

44-
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&interpolateParams=true", user, password, host, port, database)
45-
46-
db, err := sql.Open("mysql", dsn)
47-
if err != nil {
48-
panic(err)
49-
}
50-
5163
if options.MySQLOptions != nil {
5264
options.MySQLOptions(db)
5365
}
@@ -85,10 +97,22 @@ func (mb *mysqlBackend) Close() error {
8597

8698
// Migrate applies any pending database migrations.
8799
func (mb *mysqlBackend) Migrate() error {
88-
schemaDsn := mb.dsn + "&multiStatements=true"
89-
db, err := sql.Open("mysql", schemaDsn)
90-
if err != nil {
91-
return fmt.Errorf("opening schema database: %w", err)
100+
var db *sql.DB
101+
var shouldCloseDb bool
102+
103+
if mb.dsn != "" {
104+
// When DSN is available, create a new connection with multiStatements support
105+
schemaDsn := mb.dsn + "&multiStatements=true"
106+
var err error
107+
db, err = sql.Open("mysql", schemaDsn)
108+
if err != nil {
109+
return fmt.Errorf("opening schema database: %w", err)
110+
}
111+
shouldCloseDb = true
112+
} else {
113+
// When using an existing DB connection, use it directly for migrations
114+
db = mb.db
115+
shouldCloseDb = false
92116
}
93117

94118
dbi, err := mysql.WithInstance(db, &mysql.Config{})
@@ -112,8 +136,10 @@ func (mb *mysqlBackend) Migrate() error {
112136
}
113137
}
114138

115-
if err := db.Close(); err != nil {
116-
return fmt.Errorf("closing schema database: %w", err)
139+
if shouldCloseDb {
140+
if err := db.Close(); err != nil {
141+
return fmt.Errorf("closing schema database: %w", err)
142+
}
117143
}
118144

119145
return nil

backend/mysql/mysql_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,58 @@ func TestMySqlBackendE2E(t *testing.T) {
109109
})
110110
}
111111

112+
func TestMySqlBackendWithDB_E2E(t *testing.T) {
113+
if testing.Short() {
114+
t.Skip()
115+
}
116+
117+
var dbName string
118+
119+
test.EndToEndBackendTest(t, func(options ...backend.BackendOption) test.TestBackend {
120+
// Create a database for testing
121+
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@/?parseTime=true&interpolateParams=true", testUser, testPassword))
122+
if err != nil {
123+
panic(err)
124+
}
125+
126+
dbName = "test_with_db_e2e_" + strings.Replace(uuid.NewString(), "-", "", -1)
127+
if _, err := db.Exec("CREATE DATABASE " + dbName); err != nil {
128+
panic(fmt.Errorf("creating database: %w", err))
129+
}
130+
131+
if err := db.Close(); err != nil {
132+
panic(err)
133+
}
134+
135+
// Create a connection to the test database
136+
testDB, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(localhost:3306)/%s?parseTime=true&interpolateParams=true&multiStatements=true", testUser, testPassword, dbName))
137+
if err != nil {
138+
panic(fmt.Errorf("connecting to test database: %w", err))
139+
}
140+
141+
options = append(options, backend.WithStickyTimeout(0))
142+
143+
return NewMysqlBackendWithDB(testDB, WithBackendOptions(options...))
144+
}, func(b test.TestBackend) {
145+
if err := b.Close(); err != nil {
146+
panic(err)
147+
}
148+
149+
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@/?parseTime=true&interpolateParams=true", testUser, testPassword))
150+
if err != nil {
151+
panic(err)
152+
}
153+
154+
if _, err := db.Exec("DROP DATABASE IF EXISTS " + dbName); err != nil {
155+
panic(fmt.Errorf("dropping database: %w", err))
156+
}
157+
158+
if err := db.Close(); err != nil {
159+
panic(err)
160+
}
161+
})
162+
}
163+
112164
var _ test.TestBackend = (*mysqlBackend)(nil)
113165

114166
func (mb *mysqlBackend) GetFutureEvents(ctx context.Context) ([]*history.Event, error) {
@@ -163,6 +215,105 @@ func (mb *mysqlBackend) GetFutureEvents(ctx context.Context) ([]*history.Event,
163215
return f, nil
164216
}
165217

218+
func TestNewMysqlBackendWithDB(t *testing.T) {
219+
if testing.Short() {
220+
t.Skip()
221+
}
222+
223+
var dbName string
224+
225+
t.Run("NewMysqlBackendWithDB_Basic", func(t *testing.T) {
226+
// Create a database for testing
227+
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@/?parseTime=true&interpolateParams=true", testUser, testPassword))
228+
if err != nil {
229+
t.Fatalf("Failed to open database: %v", err)
230+
}
231+
defer db.Close()
232+
233+
dbName = "test_with_db_" + strings.Replace(uuid.NewString(), "-", "", -1)
234+
if _, err := db.Exec("CREATE DATABASE " + dbName); err != nil {
235+
t.Fatalf("Failed to create database: %v", err)
236+
}
237+
defer func() {
238+
if _, err := db.Exec("DROP DATABASE IF EXISTS " + dbName); err != nil {
239+
t.Errorf("Failed to drop database: %v", err)
240+
}
241+
}()
242+
243+
// Create a connection to the test database
244+
testDB, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(localhost:3306)/%s?parseTime=true&interpolateParams=true&multiStatements=true", testUser, testPassword, dbName))
245+
if err != nil {
246+
t.Fatalf("Failed to connect to test database: %v", err)
247+
}
248+
defer testDB.Close()
249+
250+
// Test creating backend with existing DB
251+
mysqlBackend := NewMysqlBackendWithDB(testDB, WithBackendOptions(backend.WithStickyTimeout(0)))
252+
if mysqlBackend == nil {
253+
t.Fatal("Expected backend to be created")
254+
}
255+
256+
// Verify the backend has the correct DB instance
257+
if mysqlBackend.db != testDB {
258+
t.Error("Expected backend to use the provided DB instance")
259+
}
260+
261+
// Verify DSN is empty when using existing DB
262+
if mysqlBackend.dsn != "" {
263+
t.Errorf("Expected DSN to be empty when using existing DB, got: %s", mysqlBackend.dsn)
264+
}
265+
266+
// Test that the backend can perform basic operations
267+
if !mysqlBackend.FeatureSupported(backend.Feature_Expiration) {
268+
t.Error("Expected backend to support expiration feature")
269+
}
270+
271+
// Close the backend
272+
if err := mysqlBackend.Close(); err != nil {
273+
t.Errorf("Failed to close backend: %v", err)
274+
}
275+
})
276+
277+
t.Run("NewMysqlBackendWithDB_WithoutMigrations", func(t *testing.T) {
278+
// Create a database for testing
279+
db, err := sql.Open("mysql", fmt.Sprintf("%s:%s@/?parseTime=true&interpolateParams=true", testUser, testPassword))
280+
if err != nil {
281+
t.Fatalf("Failed to open database: %v", err)
282+
}
283+
defer db.Close()
284+
285+
dbName = "test_no_migrations_" + strings.Replace(uuid.NewString(), "-", "", -1)
286+
if _, err := db.Exec("CREATE DATABASE " + dbName); err != nil {
287+
t.Fatalf("Failed to create database: %v", err)
288+
}
289+
defer func() {
290+
if _, err := db.Exec("DROP DATABASE IF EXISTS " + dbName); err != nil {
291+
t.Errorf("Failed to drop database: %v", err)
292+
}
293+
}()
294+
295+
// Create a connection to the test database
296+
testDB, err := sql.Open("mysql", fmt.Sprintf("%s:%s@tcp(localhost:3306)/%s?parseTime=true&interpolateParams=true&multiStatements=true", testUser, testPassword, dbName))
297+
if err != nil {
298+
t.Fatalf("Failed to connect to test database: %v", err)
299+
}
300+
defer testDB.Close()
301+
302+
// Test creating backend with existing DB and migrations disabled
303+
mysqlBackend := NewMysqlBackendWithDB(testDB,
304+
WithApplyMigrations(false),
305+
WithBackendOptions(backend.WithStickyTimeout(0)))
306+
if mysqlBackend == nil {
307+
t.Fatal("Expected backend to be created")
308+
}
309+
310+
// Close the backend
311+
if err := mysqlBackend.Close(); err != nil {
312+
t.Errorf("Failed to close backend: %v", err)
313+
}
314+
})
315+
}
316+
166317
func Test_MysqlBackend_WorkerName(t *testing.T) {
167318
if testing.Short() {
168319
t.Skip()

docs/source/includes/_backends.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,29 @@ See `migrations/sqlite` for the schema and migrations. Main tables:
4040

4141
```go
4242
func NewMysqlBackend(host string, port int, user, password, database string, opts ...option)
43+
func NewMysqlBackendWithDB(db *sql.DB, opts ...option)
4344
```
4445

45-
Create a new MySQL backend instance with `NewMysqlBackend`.
46+
Create a new MySQL backend instance with `NewMysqlBackend` or `NewMysqlBackendWithDB`.
47+
48+
Use `NewMysqlBackend` when you want the backend to manage the database connection:
49+
50+
```go
51+
backend := mysql.NewMysqlBackend("localhost", 3306, "user", "password", "dbname")
52+
```
53+
54+
Use `NewMysqlBackendWithDB` when you want to provide your own database connection:
55+
56+
```go
57+
db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname?parseTime=true&interpolateParams=true&multiStatements=true")
58+
// Configure connection pool settings as needed
59+
db.SetMaxOpenConns(10)
60+
db.SetMaxIdleConns(5)
61+
62+
backend := mysql.NewMysqlBackendWithDB(db)
63+
```
64+
65+
**Note:** When using `NewMysqlBackendWithDB`, ensure your connection string includes `multiStatements=true` if you plan to use automatic migrations (which is the default).
4666

4767
### Options
4868

simple

25.1 MB
Binary file not shown.

0 commit comments

Comments
 (0)