Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions database/cockroachdb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
| `x-migrations-table` | `MigrationsTable` | Name of the migrations table |
| `x-lock-table` | `LockTable` | Name of the table which maintains the migration lock |
| `x-force-lock` | `ForceLock` | Force lock acquisition to fix faulty migrations which may not have released the schema lock (Boolean, default is `false`) |
| `x-no-lock` | `NoLock` | Set to `true` to skip lock table acquisition. Useful for read-only checks. Only run migrations from one host when this is enabled. |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `user` | | The user to sign in as |
| `password` | | The user's password |
Expand Down
18 changes: 18 additions & 0 deletions database/cockroachdb/cockroachdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Config struct {
LockTable string
ForceLock bool
DatabaseName string
NoLock bool
}

type CockroachDb struct {
Expand Down Expand Up @@ -127,11 +128,20 @@ func (c *CockroachDb) Open(url string) (database.Driver, error) {
forceLock = false
}

noLock := false
if s := purl.Query().Get("x-no-lock"); len(s) > 0 {
noLock, err = strconv.ParseBool(s)
if err != nil {
return nil, fmt.Errorf("unable to parse option x-no-lock: %w", err)
}
}

px, err := WithInstance(db, &Config{
DatabaseName: purl.Path,
MigrationsTable: migrationsTable,
LockTable: lockTable,
ForceLock: forceLock,
NoLock: noLock,
})
if err != nil {
return nil, err
Expand All @@ -148,6 +158,10 @@ func (c *CockroachDb) Close() error {
// See: https://github.com/cockroachdb/cockroach/issues/13546
func (c *CockroachDb) Lock() error {
return database.CasRestoreOnErr(&c.isLocked, false, true, database.ErrLocked, func() (err error) {
if c.config.NoLock {
return nil
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi. Why don't we return error here?

}

return crdb.ExecuteTx(context.Background(), c.db, nil, func(tx *sql.Tx) (err error) {
aid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)
if err != nil {
Expand Down Expand Up @@ -185,6 +199,10 @@ func (c *CockroachDb) Lock() error {
// See: https://github.com/cockroachdb/cockroach/issues/13546
func (c *CockroachDb) Unlock() error {
return database.CasRestoreOnErr(&c.isLocked, true, false, database.ErrNotLocked, func() (err error) {
if c.config.NoLock {
return nil
}

aid, err := database.GenerateAdvisoryLockId(c.config.DatabaseName)
if err != nil {
return err
Expand Down
54 changes: 53 additions & 1 deletion database/cockroachdb/cockroachdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ package cockroachdb
import (
"context"
"database/sql"
"errors"
"fmt"
"github.com/golang-migrate/migrate/v4"
"log"
"strconv"
"strings"
"testing"

"github.com/golang-migrate/migrate/v4"
)

import (
Expand Down Expand Up @@ -155,6 +158,55 @@ func TestMultiStatement(t *testing.T) {
})
}

func TestNoLockParamValidation(t *testing.T) {
c := &CockroachDb{}
_, err := c.Open("cockroach://root@localhost/migrate?x-no-lock=not-a-bool")
if !errors.Is(err, strconv.ErrSyntax) {
t.Fatal("Expected syntax error when passing a non-bool as x-no-lock parameter")
}
}

func TestNoLockWorks(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {
createDB(t, ci)

ip, port, err := ci.Port(defaultPort)
if err != nil {
t.Fatal(err)
}

addr := fmt.Sprintf("cockroach://root@%v:%v/migrate?sslmode=disable", ip, port)
c := &CockroachDb{}
d, err := c.Open(addr)
if err != nil {
t.Fatal(err)
}

lock := d.(*CockroachDb)

c = &CockroachDb{}
d, err = c.Open(addr + "&x-no-lock=true")
if err != nil {
t.Fatal(err)
}

noLock := d.(*CockroachDb)

if err = lock.Lock(); err != nil {
t.Fatal(err)
}
if err = noLock.Lock(); err != nil {
t.Fatal(err)
}
if err = lock.Unlock(); err != nil {
t.Fatal(err)
}
if err = noLock.Unlock(); err != nil {
t.Fatal(err)
}
})
}

func TestFilterCustomQuery(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, ci dktest.ContainerInfo) {
createDB(t, ci)
Expand Down
1 change: 1 addition & 0 deletions database/pgx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ This package is for [pgx/v4](https://pkg.go.dev/github.com/jackc/pgx/v4). A back
| `x-multi-statement-max-size` | `MultiStatementMaxSize` | Maximum size of single statement in bytes (default: 10MB) |
| `x-lock-strategy` | `LockStrategy` | Strategy used for locking during migration (default: advisory) |
| `x-lock-table` | `LockTable` | Name of the table which maintains the migration lock (default: schema_lock) |
| `x-no-lock` | `NoLock` | Set to `true` to skip advisory lock/table lock calls. Useful for read-only checks or multi-master setups. Only run migrations from one host when this is enabled. |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `search_path` | | This variable specifies the order in which schemas are searched when an object is referenced by a simple name with no schema specified. |
| `user` | | The user to sign in as |
Expand Down
18 changes: 18 additions & 0 deletions database/pgx/pgx.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type Config struct {
MigrationsTableQuoted bool
MultiStatementEnabled bool
MultiStatementMaxSize int
NoLock bool
}

type Postgres struct {
Expand Down Expand Up @@ -219,6 +220,14 @@ func (p *Postgres) Open(url string) (database.Driver, error) {
lockStrategy := purl.Query().Get("x-lock-strategy")
lockTable := purl.Query().Get("x-lock-table")

noLock := false
if s := purl.Query().Get("x-no-lock"); len(s) > 0 {
noLock, err = strconv.ParseBool(s)
if err != nil {
return nil, fmt.Errorf("unable to parse option x-no-lock: %w", err)
}
}

px, err := WithInstance(db, &Config{
DatabaseName: purl.Path,
MigrationsTable: migrationsTable,
Expand All @@ -228,6 +237,7 @@ func (p *Postgres) Open(url string) (database.Driver, error) {
MultiStatementMaxSize: multiStatementMaxSize,
LockStrategy: lockStrategy,
LockTable: lockTable,
NoLock: noLock,
})

if err != nil {
Expand All @@ -248,6 +258,10 @@ func (p *Postgres) Close() error {

func (p *Postgres) Lock() error {
return database.CasRestoreOnErr(&p.isLocked, false, true, database.ErrLocked, func() error {
if p.config.NoLock {
return nil
}

switch p.config.LockStrategy {
case LockStrategyAdvisory:
return p.applyAdvisoryLock()
Expand All @@ -261,6 +275,10 @@ func (p *Postgres) Lock() error {

func (p *Postgres) Unlock() error {
return database.CasRestoreOnErr(&p.isLocked, true, false, database.ErrNotLocked, func() error {
if p.config.NoLock {
return nil
}

switch p.config.LockStrategy {
case LockStrategyAdvisory:
return p.releaseAdvisoryLock()
Expand Down
47 changes: 47 additions & 0 deletions database/pgx/pgx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -784,3 +784,50 @@ func Test_computeLineFromPos(t *testing.T) {
})
}
}

func TestNoLockParamValidation(t *testing.T) {
p := &Postgres{}
_, err := p.Open("pgx://postgres@localhost/postgres?x-no-lock=not-a-bool")
if !errors.Is(err, strconv.ErrSyntax) {
t.Fatal("Expected syntax error when passing a non-bool as x-no-lock parameter")
}
}

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

addr := pgConnectionString(ip, port)
p := &Postgres{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}

lock := d.(*Postgres)

p = &Postgres{}
d, err = p.Open(pgConnectionString(ip, port, "x-no-lock=true"))
if err != nil {
t.Fatal(err)
}

noLock := d.(*Postgres)

if err = lock.Lock(); err != nil {
t.Fatal(err)
}
if err = noLock.Lock(); err != nil {
t.Fatal(err)
}
if err = lock.Unlock(); err != nil {
t.Fatal(err)
}
if err = noLock.Unlock(); err != nil {
t.Fatal(err)
}
})
}
1 change: 1 addition & 0 deletions database/pgx/v5/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This package is for [pgx/v5](https://pkg.go.dev/github.com/jackc/pgx/v5). A back
| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds |
| `x-multi-statement` | `MultiStatementEnabled` | Enable multi-statement execution (default: false) |
| `x-multi-statement-max-size` | `MultiStatementMaxSize` | Maximum size of single statement in bytes (default: 10MB) |
| `x-no-lock` | `NoLock` | Set to `true` to skip `pg_advisory_lock`/`pg_advisory_unlock` calls. Useful for read-only checks or multi-master setups. Only run migrations from one host when this is enabled. |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `search_path` | | This variable specifies the order in which schemas are searched when an object is referenced by a simple name with no schema specified. |
| `user` | | The user to sign in as |
Expand Down
18 changes: 18 additions & 0 deletions database/pgx/v5/pgx.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ type Config struct {
MigrationsTableQuoted bool
MultiStatementEnabled bool
MultiStatementMaxSize int
NoLock bool
}

type Postgres struct {
Expand Down Expand Up @@ -192,13 +193,22 @@ func (p *Postgres) Open(url string) (database.Driver, error) {
}
}

noLock := false
if s := purl.Query().Get("x-no-lock"); len(s) > 0 {
noLock, err = strconv.ParseBool(s)
if err != nil {
return nil, fmt.Errorf("unable to parse option x-no-lock: %w", err)
}
}

px, err := WithInstance(db, &Config{
DatabaseName: purl.Path,
MigrationsTable: migrationsTable,
MigrationsTableQuoted: migrationsTableQuoted,
StatementTimeout: time.Duration(statementTimeout) * time.Millisecond,
MultiStatementEnabled: multiStatementEnabled,
MultiStatementMaxSize: multiStatementMaxSize,
NoLock: noLock,
})

if err != nil {
Expand All @@ -220,6 +230,10 @@ func (p *Postgres) Close() error {
// https://www.postgresql.org/docs/9.6/static/explicit-locking.html#ADVISORY-LOCKS
func (p *Postgres) Lock() error {
return database.CasRestoreOnErr(&p.isLocked, false, true, database.ErrLocked, func() error {
if p.config.NoLock {
return nil
}

aid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)
if err != nil {
return err
Expand All @@ -236,6 +250,10 @@ func (p *Postgres) Lock() error {

func (p *Postgres) Unlock() error {
return database.CasRestoreOnErr(&p.isLocked, true, false, database.ErrNotLocked, func() error {
if p.config.NoLock {
return nil
}

aid, err := database.GenerateAdvisoryLockId(p.config.DatabaseName, p.config.migrationsSchemaName, p.config.migrationsTableName)
if err != nil {
return err
Expand Down
47 changes: 47 additions & 0 deletions database/pgx/v5/pgx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -759,3 +759,50 @@ func Test_computeLineFromPos(t *testing.T) {
})
}
}

func TestNoLockParamValidation(t *testing.T) {
p := &Postgres{}
_, err := p.Open("pgx5://postgres@localhost/postgres?x-no-lock=not-a-bool")
if !errors.Is(err, strconv.ErrSyntax) {
t.Fatal("Expected syntax error when passing a non-bool as x-no-lock parameter")
}
}

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

addr := pgConnectionString(ip, port)
p := &Postgres{}
d, err := p.Open(addr)
if err != nil {
t.Fatal(err)
}

lock := d.(*Postgres)

p = &Postgres{}
d, err = p.Open(pgConnectionString(ip, port, "x-no-lock=true"))
if err != nil {
t.Fatal(err)
}

noLock := d.(*Postgres)

if err = lock.Lock(); err != nil {
t.Fatal(err)
}
if err = noLock.Lock(); err != nil {
t.Fatal(err)
}
if err = lock.Unlock(); err != nil {
t.Fatal(err)
}
if err = noLock.Unlock(); err != nil {
t.Fatal(err)
}
})
}
1 change: 1 addition & 0 deletions database/postgres/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| `x-statement-timeout` | `StatementTimeout` | Abort any statement that takes more than the specified number of milliseconds |
| `x-multi-statement` | `MultiStatementEnabled` | Enable multi-statement execution (default: false) |
| `x-multi-statement-max-size` | `MultiStatementMaxSize` | Maximum size of single statement in bytes (default: 10MB) |
| `x-no-lock` | `NoLock` | Set to `true` to skip `pg_advisory_lock`/`pg_advisory_unlock` calls. Useful for read-only checks or multi-master setups. Only run migrations from one host when this is enabled. |
| `dbname` | `DatabaseName` | The name of the database to connect to |
| `search_path` | | This variable specifies the order in which schemas are searched when an object is referenced by a simple name with no schema specified. |
| `user` | | The user to sign in as |
Expand Down
Loading
Loading