Skip to content

Commit

Permalink
Allow TRUNCATE for MySQL
Browse files Browse the repository at this point in the history
  • Loading branch information
ping-localhost committed Jan 18, 2024
1 parent a5dc5d1 commit 55c859d
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 15 deletions.
31 changes: 16 additions & 15 deletions database/mysql/README.md
Expand Up @@ -2,21 +2,22 @@

`mysql://user:password@tcp(host:port)/dbname?query`

| URL Query | WithInstance Config | Description |
|------------|---------------------|-------------|
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |
| `x-no-lock` | `NoLock` | Set to `true` to skip `GET_LOCK`/`RELEASE_LOCK` statements. Useful for [multi-master MySQL flavors](https://www.percona.com/doc/percona-xtradb-cluster/LATEST/features/pxc-strict-mode.html#explicit-table-locking). Only run migrations from one host when this is enabled. |
| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds, functionally similar to [Server-side SELECT statement timeouts](https://dev.mysql.com/blog-archive/server-side-select-statement-timeouts/) but enforced by the client. Available for all versions of MySQL, not just >=5.7. |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `user` | | The user to sign in as |
| `password` | | The user's password |
| `host` | | The host to connect to. |
| `port` | | The port to bind to. |
| `tls` | | TLS / SSL encrypted connection parameter; see [go-sql-driver](https://github.com/go-sql-driver/mysql#tls). Use any name (e.g. `migrate`) if you want to use a custom TLS config (`x-tls-` queries). |
| `x-tls-ca` | | The location of the CA (certificate authority) file. |
| `x-tls-cert` | | The location of the client certicicate file. Must be used with `x-tls-key`. |
| `x-tls-key` | | The location of the private key file. Must be used with `x-tls-cert`. |
| `x-tls-insecure-skip-verify` | | Whether or not to use SSL (true\|false) |
| URL Query | WithInstance Config | Description |
|------------------------------|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |
| `x-no-lock` | `NoLock` | Set to `true` to skip `GET_LOCK`/`RELEASE_LOCK` statements. Useful for [multi-master MySQL flavors](https://www.percona.com/doc/percona-xtradb-cluster/LATEST/features/pxc-strict-mode.html#explicit-table-locking). Only run migrations from one host when this is enabled. |
| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds, functionally similar to [Server-side SELECT statement timeouts](https://dev.mysql.com/blog-archive/server-side-select-statement-timeouts/) but enforced by the client. Available for all versions of MySQL, not just >=5.7. |
| `x-safe-update` | `SafeUpdate` | Set to `true` if we should respect safe updates (TRUNCATE vs DELETE) |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `user` | | The user to sign in as |
| `password` | | The user's password |
| `host` | | The host to connect to. |
| `port` | | The port to bind to. |
| `tls` | | TLS / SSL encrypted connection parameter; see [go-sql-driver](https://github.com/go-sql-driver/mysql#tls). Use any name (e.g. `migrate`) if you want to use a custom TLS config (`x-tls-` queries). |
| `x-tls-ca` | | The location of the CA (certificate authority) file. |
| `x-tls-cert` | | The location of the client certicicate file. Must be used with `x-tls-key`. |
| `x-tls-key` | | The location of the private key file. Must be used with `x-tls-cert`. |
| `x-tls-insecure-skip-verify` | | Whether or not to use SSL (true\|false) |

## Use with existing client

Expand Down
15 changes: 15 additions & 0 deletions database/mysql/mysql.go
Expand Up @@ -43,6 +43,7 @@ type Config struct {
MigrationsTable string
DatabaseName string
NoLock bool
SafeUpdate bool
StatementTimeout time.Duration
}

Expand Down Expand Up @@ -253,6 +254,14 @@ func (m *Mysql) Open(url string) (database.Driver, error) {
}
}

safeUpdateParam, safeUpdate := customParams["x-safe-update"], false
if safeUpdateParam != "" {
safeUpdate, err = strconv.ParseBool(safeUpdateParam)
if err != nil {
return nil, fmt.Errorf("could not parse x-safe-update as bool: %w", err)
}
}

db, err := sql.Open("mysql", config.FormatDSN())
if err != nil {
return nil, err
Expand All @@ -262,6 +271,7 @@ func (m *Mysql) Open(url string) (database.Driver, error) {
DatabaseName: config.DBName,
MigrationsTable: customParams["x-migrations-table"],
NoLock: noLock,
SafeUpdate: safeUpdate,
StatementTimeout: time.Duration(statementTimeout) * time.Millisecond,
})
if err != nil {
Expand Down Expand Up @@ -361,7 +371,12 @@ func (m *Mysql) SetVersion(version int, dirty bool) error {
return &database.Error{OrigErr: err, Err: "transaction start failed"}
}

// Either use DELETE or TRUNCATE, based on safe update selection
query := "DELETE FROM `" + m.config.MigrationsTable + "`"
if m.config.SafeUpdate {
query = "TRUNCATE `" + m.config.MigrationsTable + "`"
}

if _, err := tx.ExecContext(context.Background(), query); err != nil {
if errRollback := tx.Rollback(); errRollback != nil {
err = multierror.Append(err, errRollback)
Expand Down
43 changes: 43 additions & 0 deletions database/mysql/mysql_test.go
Expand Up @@ -152,6 +152,38 @@ func TestMigrate(t *testing.T) {
})
}

func TestMigrateSafeUpdate(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(defaultPort)
if err != nil {
t.Fatal(err)
}

addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public?x-safe-update=true", ip, port)
p := &Mysql{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()

m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "public", d)
if err != nil {
t.Fatal(err)
}
dt.TestMigrate(t, m)

// check ensureVersionTable
if err := d.(*Mysql).ensureVersionTable(); err != nil {
t.Fatal(err)
}
})
}

func TestMigrateAnsiQuotes(t *testing.T) {
// mysql.SetLogger(mysql.Logger(log.New(io.Discard, "", log.Ltime)))

Expand Down Expand Up @@ -279,6 +311,17 @@ func TestNoLockWorks(t *testing.T) {
})
}

func TestSafeUpdateParamValidation(t *testing.T) {
ip := "127.0.0.1"
port := 3306
addr := fmt.Sprintf("mysql://root:root@tcp(%v:%v)/public", ip, port)
p := &Mysql{}
_, err := p.Open(addr + "?x-safe-update=not-a-bool")
if !errors.Is(err, strconv.ErrSyntax) {
t.Fatal("Expected syntax error when passing a non-bool as x-safe-update parameter")
}
}

func TestExtractCustomQueryParams(t *testing.T) {
testcases := []struct {
name string
Expand Down

0 comments on commit 55c859d

Please sign in to comment.