diff --git a/database/pgx/pgx.go b/database/pgx/pgx.go index e97f15e9f..cdb18c0e0 100644 --- a/database/pgx/pgx.go +++ b/database/pgx/pgx.go @@ -129,6 +129,9 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) { if config.MigrationsTableQuoted { re := regexp.MustCompile(`"(.*?)"`) result := re.FindAllStringSubmatch(config.MigrationsTable, -1) + if len(result) == 0 { + return nil, fmt.Errorf("\"%s\" MigrationsTable is not quoted", config.MigrationsTable) + } config.migrationsTableName = result[len(result)-1][1] if len(result) == 2 { config.migrationsSchemaName = result[0][1] diff --git a/database/pgx/pgx_quoted_test.go b/database/pgx/pgx_quoted_test.go new file mode 100644 index 000000000..87193fbf7 --- /dev/null +++ b/database/pgx/pgx_quoted_test.go @@ -0,0 +1,54 @@ +package pgx + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "testing" +) + +// pingOnlyDriver lets tests build a *sql.DB that answers Ping without a real +// database, which is all WithInstance needs when DatabaseName and SchemaName +// are set in the config. +type pingOnlyDriver struct{} + +func (pingOnlyDriver) Open(string) (driver.Conn, error) { return pingOnlyConn{}, nil } + +type pingOnlyConn struct{} + +func (pingOnlyConn) Prepare(string) (driver.Stmt, error) { + return nil, errors.New("prepare not supported") +} + +func (pingOnlyConn) Close() error { return nil } + +func (pingOnlyConn) Begin() (driver.Tx, error) { + return nil, errors.New("transactions not supported") +} + +func (pingOnlyConn) Ping(context.Context) error { return nil } + +var _ driver.Pinger = pingOnlyConn{} + +func init() { sql.Register("migrate-fake-pgx", pingOnlyDriver{}) } + +// TestWithInstanceQuotedTableWithoutQuotes ensures a quoted-table config whose +// table name contains no quotes returns an error instead of panicking. +func TestWithInstanceQuotedTableWithoutQuotes(t *testing.T) { + db, err := sql.Open("migrate-fake-pgx", "") + if err != nil { + t.Fatal(err) + } + defer func() { _ = db.Close() }() + + _, err = WithInstance(db, &Config{ + DatabaseName: "testdb", + SchemaName: "public", + MigrationsTable: "schema_migrations", + MigrationsTableQuoted: true, + }) + if err == nil { + t.Fatal("expected an error when MigrationsTableQuoted is set without a quoted table name") + } +} diff --git a/database/pgx/v5/pgx.go b/database/pgx/v5/pgx.go index ef5f05674..81d2dfc4b 100644 --- a/database/pgx/v5/pgx.go +++ b/database/pgx/v5/pgx.go @@ -109,6 +109,9 @@ func WithInstance(instance *sql.DB, config *Config) (database.Driver, error) { if config.MigrationsTableQuoted { re := regexp.MustCompile(`"(.*?)"`) result := re.FindAllStringSubmatch(config.MigrationsTable, -1) + if len(result) == 0 { + return nil, fmt.Errorf("\"%s\" MigrationsTable is not quoted", config.MigrationsTable) + } config.migrationsTableName = result[len(result)-1][1] if len(result) == 2 { config.migrationsSchemaName = result[0][1] diff --git a/database/pgx/v5/pgx_quoted_test.go b/database/pgx/v5/pgx_quoted_test.go new file mode 100644 index 000000000..34014e63c --- /dev/null +++ b/database/pgx/v5/pgx_quoted_test.go @@ -0,0 +1,54 @@ +package pgx + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "testing" +) + +// pingOnlyDriver lets tests build a *sql.DB that answers Ping without a real +// database, which is all WithInstance needs when DatabaseName and SchemaName +// are set in the config. +type pingOnlyDriver struct{} + +func (pingOnlyDriver) Open(string) (driver.Conn, error) { return pingOnlyConn{}, nil } + +type pingOnlyConn struct{} + +func (pingOnlyConn) Prepare(string) (driver.Stmt, error) { + return nil, errors.New("prepare not supported") +} + +func (pingOnlyConn) Close() error { return nil } + +func (pingOnlyConn) Begin() (driver.Tx, error) { + return nil, errors.New("transactions not supported") +} + +func (pingOnlyConn) Ping(context.Context) error { return nil } + +var _ driver.Pinger = pingOnlyConn{} + +func init() { sql.Register("migrate-fake-pgx5", pingOnlyDriver{}) } + +// TestWithInstanceQuotedTableWithoutQuotes ensures a quoted-table config whose +// table name contains no quotes returns an error instead of panicking. +func TestWithInstanceQuotedTableWithoutQuotes(t *testing.T) { + db, err := sql.Open("migrate-fake-pgx5", "") + if err != nil { + t.Fatal(err) + } + defer func() { _ = db.Close() }() + + _, err = WithInstance(db, &Config{ + DatabaseName: "testdb", + SchemaName: "public", + MigrationsTable: "schema_migrations", + MigrationsTableQuoted: true, + }) + if err == nil { + t.Fatal("expected an error when MigrationsTableQuoted is set without a quoted table name") + } +} diff --git a/database/postgres/postgres.go b/database/postgres/postgres.go index ed96fe63e..2c5bcf427 100644 --- a/database/postgres/postgres.go +++ b/database/postgres/postgres.go @@ -109,6 +109,9 @@ func WithConnection(ctx context.Context, conn *sql.Conn, config *Config) (*Postg if config.MigrationsTableQuoted { re := regexp.MustCompile(`"(.*?)"`) result := re.FindAllStringSubmatch(config.MigrationsTable, -1) + if len(result) == 0 { + return nil, fmt.Errorf("\"%s\" MigrationsTable is not quoted", config.MigrationsTable) + } config.migrationsTableName = result[len(result)-1][1] if len(result) == 2 { config.migrationsSchemaName = result[0][1] diff --git a/database/postgres/postgres_quoted_test.go b/database/postgres/postgres_quoted_test.go new file mode 100644 index 000000000..594da6d19 --- /dev/null +++ b/database/postgres/postgres_quoted_test.go @@ -0,0 +1,54 @@ +package postgres + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "testing" +) + +// pingOnlyDriver lets tests build a *sql.DB that answers Ping without a real +// database, which is all WithInstance needs when DatabaseName and SchemaName +// are set in the config. +type pingOnlyDriver struct{} + +func (pingOnlyDriver) Open(string) (driver.Conn, error) { return pingOnlyConn{}, nil } + +type pingOnlyConn struct{} + +func (pingOnlyConn) Prepare(string) (driver.Stmt, error) { + return nil, errors.New("prepare not supported") +} + +func (pingOnlyConn) Close() error { return nil } + +func (pingOnlyConn) Begin() (driver.Tx, error) { + return nil, errors.New("transactions not supported") +} + +func (pingOnlyConn) Ping(context.Context) error { return nil } + +var _ driver.Pinger = pingOnlyConn{} + +func init() { sql.Register("migrate-fake-postgres", pingOnlyDriver{}) } + +// TestWithInstanceQuotedTableWithoutQuotes ensures a quoted-table config whose +// table name contains no quotes returns an error instead of panicking. +func TestWithInstanceQuotedTableWithoutQuotes(t *testing.T) { + db, err := sql.Open("migrate-fake-postgres", "") + if err != nil { + t.Fatal(err) + } + defer func() { _ = db.Close() }() + + _, err = WithInstance(db, &Config{ + DatabaseName: "testdb", + SchemaName: "public", + MigrationsTable: "schema_migrations", + MigrationsTableQuoted: true, + }) + if err == nil { + t.Fatal("expected an error when MigrationsTableQuoted is set without a quoted table name") + } +}