Skip to content

Commit 1c8d7ac

Browse files
committed
Add context support to Exec methods
1 parent 4e602c7 commit 1c8d7ac

4 files changed

Lines changed: 109 additions & 29 deletions

File tree

bindata_test.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,13 @@ var _bindata = map[string]func() ([]byte, error){
9494
// directory embedded in the file by go-bindata.
9595
// For example if you run go-bindata on data/... and data contains the
9696
// following hierarchy:
97-
// data/
98-
// foo.txt
99-
// img/
100-
// a.png
101-
// b.png
97+
//
98+
// data/
99+
// foo.txt
100+
// img/
101+
// a.png
102+
// b.png
103+
//
102104
// then AssetDir("data") would return []string{"foo.txt", "img"}
103105
// AssetDir("data/img") would return []string{"a.png", "b.png"}
104106
// AssetDir("foo.txt") and AssetDir("notexist") would return an error

doc.go

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,23 @@
11
/*
2-
32
SQL Schema migration tool for Go.
43
54
Key features:
65
7-
* Usable as a CLI tool or as a library
8-
* Supports SQLite, PostgreSQL, MySQL, MSSQL and Oracle databases (through gorp)
9-
* Can embed migrations into your application
10-
* Migrations are defined with SQL for full flexibility
11-
* Atomic migrations
12-
* Up/down migrations to allow rollback
13-
* Supports multiple database types in one project
6+
- Usable as a CLI tool or as a library
7+
- Supports SQLite, PostgreSQL, MySQL, MSSQL and Oracle databases (through gorp)
8+
- Can embed migrations into your application
9+
- Migrations are defined with SQL for full flexibility
10+
- Atomic migrations
11+
- Up/down migrations to allow rollback
12+
- Supports multiple database types in one project
1413
15-
Installation
14+
# Installation
1615
1716
To install the library and command line program, use the following:
1817
1918
go get -v github.com/rubenv/sql-migrate/...
2019
21-
Command-line tool
20+
# Command-line tool
2221
2322
The main command is called sql-migrate.
2423
@@ -77,7 +76,7 @@ Use the status command to see the state of the applied migrations:
7776
| 2_record.sql | no |
7877
+---------------+-----------------------------------------+
7978
80-
MySQL Caveat
79+
# MySQL Caveat
8180
8281
If you are using MySQL, you must append ?parseTime=true to the datasource configuration. For example:
8382
@@ -89,7 +88,7 @@ If you are using MySQL, you must append ?parseTime=true to the datasource config
8988
9089
See https://github.com/go-sql-driver/mysql#parsetime for more information.
9190
92-
Library
91+
# Library
9392
9493
Import sql-migrate into your application:
9594
@@ -137,7 +136,7 @@ Note that n can be greater than 0 even if there is an error: any migration that
137136
138137
The full set of capabilities can be found in the API docs below.
139138
140-
Writing migrations
139+
# Writing migrations
141140
142141
Migrations are defined in SQL files, which contain a set of SQL statements. Special comments are used to distinguish up and down migrations.
143142
@@ -183,7 +182,7 @@ Normally each migration is run within a transaction in order to guarantee that i
183182
-- +migrate Down
184183
DROP INDEX people_unique_id_idx;
185184
186-
Embedding migrations with packr
185+
# Embedding migrations with packr
187186
188187
If you like your Go applications self-contained (that is: a single binary): use packr (https://github.com/gobuffalo/packr) to embed the migration files.
189188
@@ -202,7 +201,7 @@ If you already have a box and would like to use a subdirectory:
202201
Dir: "./migrations",
203202
}
204203
205-
Embedding migrations with bindata
204+
# Embedding migrations with bindata
206205
207206
As an alternative, but slightly less maintained, you can use bindata (https://github.com/shuLhan/go-bindata) to embed the migration files.
208207
@@ -226,7 +225,7 @@ Both Asset and AssetDir are functions provided by bindata.
226225
227226
Then proceed as usual.
228227
229-
Extending
228+
# Extending
230229
231230
Adding a new migration source means implementing MigrationSource.
232231

migrate.go

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package migrate
22

33
import (
44
"bytes"
5+
"context"
56
"database/sql"
67
"errors"
78
"fmt"
@@ -429,12 +430,24 @@ type SqlExecutor interface {
429430
//
430431
// Returns the number of applied migrations.
431432
func Exec(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) {
432-
return ExecMax(db, dialect, m, dir, 0)
433+
return ExecMaxContext(context.Background(), db, dialect, m, dir, 0)
433434
}
434435

435436
// Returns the number of applied migrations.
436437
func (ms MigrationSet) Exec(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) {
437-
return ms.ExecMax(db, dialect, m, dir, 0)
438+
return ms.ExecMaxContext(context.Background(), db, dialect, m, dir, 0)
439+
}
440+
441+
// Execute a set of migrations with an input context.
442+
//
443+
// Returns the number of applied migrations.
444+
func ExecContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) {
445+
return ExecMaxContext(ctx, db, dialect, m, dir, 0)
446+
}
447+
448+
// Returns the number of applied migrations.
449+
func (ms MigrationSet) ExecContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection) (int, error) {
450+
return ms.ExecMaxContext(ctx, db, dialect, m, dir, 0)
438451
}
439452

440453
// Execute a set of migrations
@@ -446,50 +459,78 @@ func ExecMax(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirecti
446459
return migSet.ExecMax(db, dialect, m, dir, max)
447460
}
448461

462+
// Execute a set of migrations with an input context.
463+
//
464+
// Will apply at most `max` migrations. Pass 0 for no limit (or use Exec).
465+
//
466+
// Returns the number of applied migrations.
467+
func ExecMaxContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) {
468+
return migSet.ExecMaxContext(ctx, db, dialect, m, dir, max)
469+
}
470+
449471
// Execute a set of migrations
450472
//
451473
// Will apply at the target `version` of migration. Cannot be a negative value.
452474
//
453475
// Returns the number of applied migrations.
454476
func ExecVersion(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) {
477+
return ExecVersionContext(context.Background(), db, dialect, m, dir, version)
478+
}
479+
480+
// Execute a set of migrations with an input context.
481+
//
482+
// Will apply at the target `version` of migration. Cannot be a negative value.
483+
//
484+
// Returns the number of applied migrations.
485+
func ExecVersionContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) {
455486
if version < 0 {
456487
return 0, fmt.Errorf("target version %d should not be negative", version)
457488
}
458-
return migSet.ExecVersion(db, dialect, m, dir, version)
489+
return migSet.ExecVersionContext(ctx, db, dialect, m, dir, version)
459490
}
460491

461492
// Returns the number of applied migrations.
462493
func (ms MigrationSet) ExecMax(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) {
494+
return ms.ExecMaxContext(context.Background(), db, dialect, m, dir, max)
495+
}
496+
497+
// Returns the number of applied migrations, but applies with an input context.
498+
func (ms MigrationSet) ExecMaxContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, max int) (int, error) {
463499
migrations, dbMap, err := ms.PlanMigration(db, dialect, m, dir, max)
464500
if err != nil {
465501
return 0, err
466502
}
467-
return ms.applyMigrations(dir, migrations, dbMap)
503+
return ms.applyMigrations(ctx, dir, migrations, dbMap)
468504
}
469505

470506
// Returns the number of applied migrations.
471507
func (ms MigrationSet) ExecVersion(db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) {
508+
return ms.ExecVersionContext(context.Background(), db, dialect, m, dir, version)
509+
}
510+
511+
func (ms MigrationSet) ExecVersionContext(ctx context.Context, db *sql.DB, dialect string, m MigrationSource, dir MigrationDirection, version int64) (int, error) {
472512
migrations, dbMap, err := ms.PlanMigrationToVersion(db, dialect, m, dir, version)
473513
if err != nil {
474514
return 0, err
475515
}
476-
return ms.applyMigrations(dir, migrations, dbMap)
516+
return ms.applyMigrations(ctx, dir, migrations, dbMap)
477517
}
478518

479519
// Applies the planned migrations and returns the number of applied migrations.
480-
func (ms MigrationSet) applyMigrations(dir MigrationDirection, migrations []*PlannedMigration, dbMap *gorp.DbMap) (int, error) {
520+
func (ms MigrationSet) applyMigrations(ctx context.Context, dir MigrationDirection, migrations []*PlannedMigration, dbMap *gorp.DbMap) (int, error) {
481521
applied := 0
482522
for _, migration := range migrations {
483523
var executor SqlExecutor
484524
var err error
485525

486526
if migration.DisableTransaction {
487-
executor = dbMap
527+
executor = dbMap.WithContext(ctx)
488528
} else {
489-
executor, err = dbMap.Begin()
529+
e, err := dbMap.Begin()
490530
if err != nil {
491531
return applied, newTxError(migration, err)
492532
}
533+
executor = e.WithContext(ctx)
493534
}
494535

495536
for _, stmt := range migration.Queries {

migrate_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package migrate
22

33
import (
4+
"context"
45
"database/sql"
56
"net/http"
7+
"time"
68

79
"github.com/go-gorp/gorp/v3"
810
"github.com/gobuffalo/packr/v2"
@@ -758,3 +760,39 @@ func (s *SqliteMigrateSuite) TestGetMigrationDbMapWithDisableCreateTable(c *C) {
758760
_, err := migSet.getMigrationDbMap(s.Db, "postgres")
759761
c.Assert(err, IsNil)
760762
}
763+
764+
func (s *SqliteMigrateSuite) TestContextTimeout(c *C) {
765+
// This statement will run for a long time: 1,000,000 iterations of the fibonacci sequence
766+
fibonacciLoopStmt := `WITH RECURSIVE
767+
fibo (curr, next)
768+
AS
769+
( SELECT 1,1
770+
UNION ALL
771+
SELECT next, curr+next FROM fibo
772+
LIMIT 1000000 )
773+
SELECT group_concat(curr) FROM fibo;
774+
`
775+
migrations := &MemoryMigrationSource{
776+
Migrations: []*Migration{
777+
sqliteMigrations[0],
778+
sqliteMigrations[1],
779+
{
780+
Id: "125",
781+
Up: []string{fibonacciLoopStmt},
782+
Down: []string{}, // Not important here
783+
},
784+
{
785+
Id: "125",
786+
Up: []string{"INSERT INTO people (id, first_name) VALUES (1, 'Test')", "SELECT fail"},
787+
Down: []string{}, // Not important here
788+
},
789+
},
790+
}
791+
792+
// Should never run the insert
793+
ctx, cancelFunc := context.WithTimeout(context.Background(), 10*time.Millisecond)
794+
defer cancelFunc()
795+
n, err := ExecContext(ctx, s.Db, "sqlite3", migrations, Up)
796+
c.Assert(err, Not(IsNil))
797+
c.Assert(n, Equals, 2)
798+
}

0 commit comments

Comments
 (0)