Repository: pressly/goose

Issue:
Expose dialectquery for custom dialects without having to reimplement stores
Hi everyone, 

thank you for your great work to provide goose as an extensible schema migration tool! In my project, I want to use goose with an unsupported database. I know that I can use a custom store as documented at https://pressly.github.io/goose/documentation/custom-store/

However, with an SQL-like store, the implementation of a custom store is a bit tedious. Basically, I have to copy large chunks of https://github.com/pressly/goose/blob/main/database/dialect.go, and only exchange the sql queries used for the individual commands. 

It would be way easier, if goose would expose a method to create a new `dialect.store` based on a given `dialectquery.Querier`, which offers a contract that is way easier to implement without duplicating store implementations.

<details>
<summary><em>Expand to see detailed explanations on why this is helpful + links to code</em></summary>

E.g. https://github.com/pressly/goose/blob/6a70e744c8eb2dc4bb90ba641cb03b42d8eef6cd/database/dialect.go#L114-L124

```golang
func (s *store) GetLatestVersion(ctx context.Context, db DBTxConn) (int64, error) {
	q := "SELECT ..."
	var version sql.NullInt64
	if err := db.QueryRowContext(ctx, q).Scan(&version); err != nil {
		return -1, fmt.Errorf("failed to get latest version: %w", err)
	}
	if !version.Valid {
		return -1, fmt.Errorf("latest %w", ErrVersionNotFound)
	}
	return version.Int64, nil
}
```

It would be way easier, if I could instantiate a new store as with https://github.com/pressly/goose/blob/6a70e744c8eb2dc4bb90ba641cb03b42d8eef6cd/database/dialect.go#L29-L30

But passing in something that fulfills the `Querier` interface https://github.com/pressly/goose/blob/6a70e744c8eb2dc4bb90ba641cb03b42d8eef6cd/internal/dialect/dialectquery/dialectquery.go#L5-L29

</details>

This would drastically reduce the efforts for us, and not force us to duplicate code from `dialect.go`. Let me know if you'd consider something like this! I feel like the implementation would not be too difficult, basically just a new method `dialect.NewStoreFromQuerier(querier dialectquery.Querier, tablename string)`

Hints from issue comments:
@mfridman: Thanks for filing an issue. I have some thoughts on how we might solve this, will try to respond this weekend.@mfridman: Ah, okay, I see what you're asking.

> if goose would expose a method to create a new dialect.store based on a given dialectquery.Querier, which offers a contract that is way easier to implement without duplicating store implementations.

This is an internal implementation of how goose satisfies the `Store` interface. It's easy to make errors when working with this code, so we've kept it as an internal package. The main challenge is that the `Querier` contract relies on raw SQL strings with placeholders. This works because we control the supported SQL dialects, though implementing each requires careful attention.

I also worry that another interface, like `Querier`, adds a level of indirection which will make the library a bit harder to grok. Granted this could be a sub-package that only advanced users would care about.

As I type this out though, I can see how it'll be useful to have the `Querier` at least be public enabling a function like you said `NewStoreFromQuerier`.@LukasHeimann: Hm, I see where the contract of strings with placeholders is more dangerous then implementing an interface (and expecting certain exceptions to be returned).

From an implementation perspective, strings with placeholders would have been easier, though, because the simplest approach was to look at the existing store implementation in the library and replace the querier with our own queries (with placeholders).@mfridman: Yeah, I've been noodling on this. I don't necessarily hate exposing it as a subpackage. So if you know what you're doing it'll make your life a lot easier, i.e., going from raw strings with placeholders -> Store implementation.@mfridman: Took a first pass at this to expose both `Querier` and `QuerierExtender` in #939 

```go
// Querier is the interface that wraps the basic methods to create a dialect specific query.
type Querier interface {
	// CreateTable returns the SQL query string to create the db version table.
	CreateTable(tableName string) string

	// InsertVersion returns the SQL query string to insert a new version into the db version table.
	InsertVersion(tableName string) string

	// DeleteVersion returns the SQL query string to delete a version from the db version table.
	DeleteVersion(tableName string) string

	// GetMigrationByVersion returns the SQL query string to get a single migration by version.
	//
	// The query should return the timestamp and is_applied columns.
	GetMigrationByVersion(tableName string) string

	// ListMigrations returns the SQL query string to list all migrations in descending order by id.
	//
	// The query should return the version_id and is_applied columns.
	ListMigrations(tableName string) string

	// GetLatestVersion returns the SQL query string to get the last version_id from the db version
	// table. Returns a nullable int64 value.
	GetLatestVersion(tableName string) string
}
```

And this as well (mostly for compile-time checks)

```go
// QuerierExtender is an extension of the Querier interface that provides optional optimizations and
// database-specific features. While not required by the core package, implementing these methods
// can improve performance and functionality for specific databases.
//
// IMPORTANT: This interface may be expanded in future versions. Implementors MUST be prepared to
// update their implementations when new methods are added, either by implementing the new
// functionality or returning an empty string.
//
// Example usage to verify implementation:
//
//	var _ QuerierExtender = (*CustomQuerierExtended)(nil)
//
// In short, it's exported to allows implementors to have a compile-time check that they are
// implementing the interface correctly.
type QuerierExtender interface {
	Querier

	// TableExists returns the SQL query string to check if a table exists in the database.
	// Implementing this method allows the system to optimize table existence checks by using
	// database-specific system catalogs (e.g., pg_tables for PostgreSQL, sqlite_master for SQLite)
	// instead of generic SQL queries.
	//
	// Return an empty string if the database does not provide an efficient way to check table
	// existence.
	TableExists(tableName string) string
}
```@LukasHeimann: That looks like a good idea, I like this interface!@mfridman: I believe this also gets you the constructor you're looking for, correct?

```go
func NewStoreFromQuerier(tablename string, querier dialect.Querier) (Store, error) { ... }
```@LukasHeimann: Just tried it out, looks really good! Also love the addition of the `DialectCustom` constant, that makes goose provider initialization way more readable.

Thank you so much for tacking this!@mfridman: I have some cycles this weekend, so I will try to wrap this up as soon as I can. Appreciate you taking a pass over the unreleased API.@mfridman: Alright I cleaned it up and merged.

Note that I didn't expose the dialects themselves, they remain an internal implementation of goose in `internal/dialects`, basically the implementations for the new public `database/dialect.Querier` interface.@LukasHeimann: Thank you so much! Just checked against the main branch, still looks great! Looking forward to the release

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 94e26d4..1f7ecab 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,12 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
 
+## [v3.24.3]
+
+- Add `GOOSE_TABLE` environment variable -- lower priority than `-table` flag, but higher than the
+  default table name. (#932)
+- Dependency updates
+
 ## [v3.24.2]
 
 - Add `TableExists` table existence check for the mysql dialect (#895)
@@ -266,7 +272,8 @@ Here's a quick summary:
 - Add new `context.Context`-aware functions and methods, for both sql and go migrations.
 - Return error when no migration files found or dir is not a directory.
 
-[Unreleased]: https://github.com/pressly/goose/compare/v3.24.2...HEAD
+[Unreleased]: https://github.com/pressly/goose/compare/v3.24.3...HEAD
+[v3.24.3]: https://github.com/pressly/goose/compare/v3.24.2...v3.24.3
 [v3.24.2]: https://github.com/pressly/goose/compare/v3.24.1...v3.24.2
 [v3.24.1]: https://github.com/pressly/goose/compare/v3.24.0...v3.24.1
 [v3.24.0]: https://github.com/pressly/goose/compare/v3.23.1...v3.24.0
diff --git a/internal/dialect/dialectquery/dialectquery.go b/database/dialect/querier.go
similarity index 55%
rename from internal/dialect/dialectquery/dialectquery.go
rename to database/dialect/querier.go
index 389da24..0cedcad 100644
--- a/internal/dialect/dialectquery/dialectquery.go
+++ b/database/dialect/querier.go
@@ -1,59 +1,25 @@
-package dialectquery
-
-import "strings"
+package dialect
 
 // Querier is the interface that wraps the basic methods to create a dialect specific query.
+//
+// It is intended tio be using with [database.NewStoreFromQuerier] to create a new [database.Store]
+// implementation based on a custom querier.
 type Querier interface {
 	// CreateTable returns the SQL query string to create the db version table.
 	CreateTable(tableName string) string
-
 	// InsertVersion returns the SQL query string to insert a new version into the db version table.
 	InsertVersion(tableName string) string
-
 	// DeleteVersion returns the SQL query string to delete a version from the db version table.
 	DeleteVersion(tableName string) string
-
 	// GetMigrationByVersion returns the SQL query string to get a single migration by version.
 	//
 	// The query should return the timestamp and is_applied columns.
 	GetMigrationByVersion(tableName string) string
-
 	// ListMigrations returns the SQL query string to list all migrations in descending order by id.
 	//
 	// The query should return the version_id and is_applied columns.
 	ListMigrations(tableName string) string
-
 	// GetLatestVersion returns the SQL query string to get the last version_id from the db version
 	// table. Returns a nullable int64 value.
 	GetLatestVersion(tableName string) string
 }
-
-var _ Querier = (*QueryController)(nil)
-
-type QueryController struct{ Querier }
-
-// NewQueryController returns a new QueryController that wraps the given Querier.
-func NewQueryController(querier Querier) *QueryController {
-	return &QueryController{Querier: querier}
-}
-
-// Optional methods
-
-// TableExists returns the SQL query string to check if the version table exists. If the Querier
-// does not implement this method, it will return an empty string.
-//
-// Returns a boolean value.
-func (c *QueryController) TableExists(tableName string) string {
-	if t, ok := c.Querier.(interface{ TableExists(string) string }); ok {
-		return t.TableExists(tableName)
-	}
-	return ""
-}
-
-func parseTableIdentifier(name string) (schema, table string) {
-	schema, table, found := strings.Cut(name, ".")
-	if !found {
-		return "", name
-	}
-	return schema, table
-}
diff --git a/database/dialect/querier_extended.go b/database/dialect/querier_extended.go
new file mode 100644
index 0000000..7ab1151
--- /dev/null
+++ b/database/dialect/querier_extended.go
@@ -0,0 +1,22 @@
+package dialect
+
+// QuerierExtender extends the [Querier] interface with optional database-specific optimizations.
+// While not required, implementing these methods can improve performance.
+//
+// IMPORTANT: This interface may be expanded in future versions. Implementors must be prepared to
+// update their implementations when new methods are added.
+//
+// Example compile-time check:
+//
+//	var _ QuerierExtender = (*CustomQuerierExtended)(nil)
+//
+// In short, it's exported to allow implementors to have a compile-time check that they are
+// implementing the interface correctly.
+type QuerierExtender interface {
+	Querier
+
+	// TableExists returns a database-specific SQL query to check if a table exists. For example,
+	// implementations might query system catalogs like pg_tables or sqlite_master. Return empty
+	// string if not supported.
+	TableExists(tableName string) string
+}
diff --git a/database/dialect.go b/database/dialects.go
similarity index 58%
rename from database/dialect.go
rename to database/dialects.go
index 9f138f5..643baae 100644
--- a/database/dialect.go
+++ b/database/dialects.go
@@ -6,78 +6,92 @@ import (
 	"errors"
 	"fmt"
 
-	"github.com/pressly/goose/v3/internal/dialect/dialectquery"
+	"github.com/pressly/goose/v3/database/dialect"
+	"github.com/pressly/goose/v3/internal/dialects"
 )
 
 // Dialect is the type of database dialect.
 type Dialect string
 
 const (
+	DialectCustom     Dialect = ""
 	DialectClickHouse Dialect = "clickhouse"
 	DialectMSSQL      Dialect = "mssql"
 	DialectMySQL      Dialect = "mysql"
 	DialectPostgres   Dialect = "postgres"
 	DialectRedshift   Dialect = "redshift"
 	DialectSQLite3    Dialect = "sqlite3"
+	DialectStarrocks  Dialect = "starrocks"
 	DialectTiDB       Dialect = "tidb"
 	DialectTurso      Dialect = "turso"
 	DialectVertica    Dialect = "vertica"
 	DialectYdB        Dialect = "ydb"
-	DialectStarrocks  Dialect = "starrocks"
 )
 
 // NewStore returns a new [Store] implementation for the given dialect.
-func NewStore(dialect Dialect, tablename string) (Store, error) {
-	if tablename == "" {
-		return nil, errors.New("table name must not be empty")
+func NewStore(d Dialect, tableName string) (Store, error) {
+	if d == DialectCustom {
+		return nil, errors.New("custom dialect is not supported")
 	}
-	if dialect == "" {
-		return nil, errors.New("dialect must not be empty")
-	}
-	lookup := map[Dialect]dialectquery.Querier{
-		DialectClickHouse: &dialectquery.Clickhouse{},
-		DialectMSSQL:      &dialectquery.Sqlserver{},
-		DialectMySQL:      &dialectquery.Mysql{},
-		DialectPostgres:   &dialectquery.Postgres{},
-		DialectRedshift:   &dialectquery.Redshift{},
-		DialectSQLite3:    &dialectquery.Sqlite3{},
-		DialectTiDB:       &dialectquery.Tidb{},
-		DialectVertica:    &dialectquery.Vertica{},
-		DialectYdB:        &dialectquery.Ydb{},
-		DialectTurso:      &dialectquery.Turso{},
-		DialectStarrocks:  &dialectquery.Starrocks{},
-	}
-	querier, ok := lookup[dialect]
+	lookup := map[Dialect]dialect.Querier{
+		DialectClickHouse: dialects.NewClickhouse(),
+		DialectMSSQL:      dialects.NewSqlserver(),
+		DialectMySQL:      dialects.NewMysql(),
+		DialectPostgres:   dialects.NewPostgres(),
+		DialectRedshift:   dialects.NewRedshift(),
+		DialectSQLite3:    dialects.NewSqlite3(),
+		DialectStarrocks:  dialects.NewStarrocks(),
+		DialectTiDB:       dialects.NewTidb(),
+		DialectTurso:      dialects.NewTurso(),
+		DialectVertica:    dialects.NewVertica(),
+		DialectYdB:        dialects.NewYDB(),
+	}
+	querier, ok := lookup[d]
 	if !ok {
-		return nil, fmt.Errorf("unknown dialect: %q", dialect)
+		return nil, fmt.Errorf("unknown dialect: %q", d)
+	}
+	return NewStoreFromQuerier(tableName, querier)
+}
+
+// NewStoreFromQuerier returns a new [Store] implementation for the given querier.
+//
+// Most of the time you should use [NewStore] instead of this function, as it will automatically
+// create a dialect-specific querier for you. This function is useful if you want to use a custom
+// querier that is not part of the standard dialects.
+func NewStoreFromQuerier(tableName string, querier dialect.Querier) (Store, error) {
+	if tableName == "" {
+		return nil, errors.New("table name must not be empty")
+	}
+	if querier == nil {
+		return nil, errors.New("querier must not be nil")
 	}
 	return &store{
-		tablename: tablename,
-		querier:   dialectquery.NewQueryController(querier),
+		tableName: tableName,
+		querier:   newQueryController(querier),
 	}, nil
 }
 
 type store struct {
-	tablename string
-	querier   *dialectquery.QueryController
+	tableName string
+	querier   *queryController
 }
 
 var _ Store = (*store)(nil)
 
 func (s *store) Tablename() string {
-	return s.tablename
+	return s.tableName
 }
 
 func (s *store) CreateVersionTable(ctx context.Context, db DBTxConn) error {
-	q := s.querier.CreateTable(s.tablename)
+	q := s.querier.CreateTable(s.tableName)
 	if _, err := db.ExecContext(ctx, q); err != nil {
-		return fmt.Errorf("failed to create version table %q: %w", s.tablename, err)
+		return fmt.Errorf("failed to create version table %q: %w", s.tableName, err)
 	}
 	return nil
 }
 
 func (s *store) Insert(ctx context.Context, db DBTxConn, req InsertRequest) error {
-	q := s.querier.InsertVersion(s.tablename)
+	q := s.querier.InsertVersion(s.tableName)
 	if _, err := db.ExecContext(ctx, q, req.Version, true); err != nil {
 		return fmt.Errorf("failed to insert version %d: %w", req.Version, err)
 	}
@@ -85,7 +99,7 @@ func (s *store) Insert(ctx context.Context, db DBTxConn, req InsertRequest) erro
 }
 
 func (s *store) Delete(ctx context.Context, db DBTxConn, version int64) error {
-	q := s.querier.DeleteVersion(s.tablename)
+	q := s.querier.DeleteVersion(s.tableName)
 	if _, err := db.ExecContext(ctx, q, version); err != nil {
 		return fmt.Errorf("failed to delete version %d: %w", version, err)
 	}
@@ -97,7 +111,7 @@ func (s *store) GetMigration(
 	db DBTxConn,
 	version int64,
 ) (*GetMigrationResult, error) {
-	q := s.querier.GetMigrationByVersion(s.tablename)
+	q := s.querier.GetMigrationByVersion(s.tableName)
 	var result GetMigrationResult
 	if err := db.QueryRowContext(ctx, q, version).Scan(
 		&result.Timestamp,
@@ -112,7 +126,7 @@ func (s *store) GetMigration(
 }
 
 func (s *store) GetLatestVersion(ctx context.Context, db DBTxConn) (int64, error) {
-	q := s.querier.GetLatestVersion(s.tablename)
+	q := s.querier.GetLatestVersion(s.tableName)
 	var version sql.NullInt64
 	if err := db.QueryRowContext(ctx, q).Scan(&version); err != nil {
 		return -1, fmt.Errorf("failed to get latest version: %w", err)
@@ -127,7 +141,7 @@ func (s *store) ListMigrations(
 	ctx context.Context,
 	db DBTxConn,
 ) ([]*ListMigrationsResult, error) {
-	q := s.querier.ListMigrations(s.tablename)
+	q := s.querier.ListMigrations(s.tableName)
 	rows, err := db.QueryContext(ctx, q)
 	if err != nil {
 		return nil, fmt.Errorf("failed to list migrations: %w", err)
@@ -158,7 +172,7 @@ func (s *store) ListMigrations(
 //
 
 func (s *store) TableExists(ctx context.Context, db DBTxConn) (bool, error) {
-	q := s.querier.TableExists(s.tablename)
+	q := s.querier.TableExists(s.tableName)
 	if q == "" {
 		return false, errors.ErrUnsupported
 	}
@@ -170,3 +184,25 @@ func (s *store) TableExists(ctx context.Context, db DBTxConn) (bool, error) {
 	}
 	return exists, nil
 }
+
+var _ dialect.Querier = (*queryController)(nil)
+
+type queryController struct{ dialect.Querier }
+
+// newQueryController returns a new QueryController that wraps the given Querier.
+func newQueryController(querier dialect.Querier) *queryController {
+	return &queryController{Querier: querier}
+}
+
+// Optional methods
+
+// TableExists returns the SQL query string to check if the version table exists. If the Querier
+// does not implement this method, it will return an empty string.
+//
+// Returns a boolean value.
+func (c *queryController) TableExists(tableName string) string {
+	if t, ok := c.Querier.(interface{ TableExists(string) string }); ok {
+		return t.TableExists(tableName)
+	}
+	return ""
+}
diff --git a/dialect.go b/dialect.go
index ecebd14..5fc578a 100644
--- a/dialect.go
+++ b/dialect.go
@@ -4,61 +4,63 @@ import (
 	"fmt"
 
 	"github.com/pressly/goose/v3/database"
-	"github.com/pressly/goose/v3/internal/dialect"
+	"github.com/pressly/goose/v3/internal/legacystore"
 )
 
 // Dialect is the type of database dialect. It is an alias for [database.Dialect].
 type Dialect = database.Dialect
 
 const (
+	DialectCustom     Dialect = database.DialectCustom
 	DialectClickHouse Dialect = database.DialectClickHouse
 	DialectMSSQL      Dialect = database.DialectMSSQL
 	DialectMySQL      Dialect = database.DialectMySQL
 	DialectPostgres   Dialect = database.DialectPostgres
 	DialectRedshift   Dialect = database.DialectRedshift
 	DialectSQLite3    Dialect = database.DialectSQLite3
+	DialectStarrocks  Dialect = database.DialectStarrocks
 	DialectTiDB       Dialect = database.DialectTiDB
+	DialectTurso      Dialect = database.DialectTurso
 	DialectVertica    Dialect = database.DialectVertica
 	DialectYdB        Dialect = database.DialectYdB
-	DialectStarrocks  Dialect = database.DialectStarrocks
 )
 
 func init() {
-	store, _ = dialect.NewStore(dialect.Postgres)
+	store, _ = legacystore.NewStore(DialectPostgres)
 }
 
-var store dialect.Store
+var store legacystore.Store
 
 // SetDialect sets the dialect to use for the goose package.
 func SetDialect(s string) error {
-	var d dialect.Dialect
+	var d Dialect
 	switch s {
 	case "postgres", "pgx":
-		d = dialect.Postgres
+		d = DialectPostgres
 	case "mysql":
-		d = dialect.Mysql
+		d = DialectMySQL
 	case "sqlite3", "sqlite":
-		d = dialect.Sqlite3
+		d = DialectSQLite3
 	case "mssql", "azuresql", "sqlserver":
-		d = dialect.Sqlserver
+		d = DialectMSSQL
 	case "redshift":
-		d = dialect.Redshift
+		d = DialectRedshift
 	case "tidb":
-		d = dialect.Tidb
+		d = DialectTiDB
 	case "clickhouse":
-		d = dialect.Clickhouse
+		d = DialectClickHouse
 	case "vertica":
-		d = dialect.Vertica
+		d = DialectVertica
 	case "ydb":
-		d = dialect.Ydb
+		d = DialectYdB
 	case "turso":
-		d = dialect.Turso
+		d = DialectTurso
 	case "starrocks":
-		d = dialect.Starrocks
+		d = DialectStarrocks
 	default:
 		return fmt.Errorf("%q: unknown dialect", s)
 	}
 	var err error
-	store, err = dialect.NewStore(d)
+	store, err = legacystore.NewStore(d)
 	return err
 }
diff --git a/go.mod b/go.mod
index d93deec..bd32ca9 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,7 @@ toolchain go1.24.1
 
 require (
 	github.com/ClickHouse/clickhouse-go/v2 v2.34.0
-	github.com/go-sql-driver/mysql v1.9.1
+	github.com/go-sql-driver/mysql v1.9.2
 	github.com/jackc/pgx/v5 v5.7.4
 	github.com/joho/godotenv v1.5.1
 	github.com/mfridman/interpolate v0.0.2
@@ -16,10 +16,10 @@ require (
 	github.com/stretchr/testify v1.10.0
 	github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d
 	github.com/vertica/vertica-sql-go v1.3.3
-	github.com/ydb-platform/ydb-go-sdk/v3 v3.106.1
+	github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1
 	github.com/ziutek/mymysql v1.5.4
 	go.uber.org/multierr v1.11.0
-	golang.org/x/sync v0.13.0
+	golang.org/x/sync v0.14.0
 	modernc.org/sqlite v1.37.0
 )
 
@@ -31,12 +31,11 @@ require (
 	github.com/coder/websocket v1.8.13 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
-	github.com/elastic/go-sysinfo v1.15.2 // indirect
+	github.com/elastic/go-sysinfo v1.15.3 // indirect
 	github.com/elastic/go-windows v1.0.2 // indirect
 	github.com/go-faster/city v1.0.1 // indirect
 	github.com/go-faster/errors v0.7.1 // indirect
 	github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
-	github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
 	github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
 	github.com/golang-sql/sqlexp v0.1.0 // indirect
 	github.com/google/uuid v1.6.0 // indirect
@@ -50,27 +49,27 @@ require (
 	github.com/paulmach/orb v0.11.1 // indirect
 	github.com/pierrec/lz4/v4 v4.1.22 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/procfs v0.16.0 // indirect
+	github.com/prometheus/procfs v0.16.1 // indirect
 	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
-	github.com/rogpeppe/go-internal v1.13.1 // indirect
+	github.com/rogpeppe/go-internal v1.14.1 // indirect
 	github.com/segmentio/asm v1.2.0 // indirect
 	github.com/shopspring/decimal v1.4.0 // indirect
 	github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect
 	go.opentelemetry.io/otel v1.35.0 // indirect
 	go.opentelemetry.io/otel/trace v1.35.0 // indirect
-	golang.org/x/crypto v0.36.0 // indirect
-	golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
-	golang.org/x/net v0.38.0 // indirect
-	golang.org/x/sys v0.31.0 // indirect
-	golang.org/x/text v0.23.0 // indirect
+	golang.org/x/crypto v0.38.0 // indirect
+	golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect
+	golang.org/x/net v0.40.0 // indirect
+	golang.org/x/sys v0.33.0 // indirect
+	golang.org/x/text v0.25.0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect
 	google.golang.org/grpc v1.71.0 // indirect
 	google.golang.org/protobuf v1.36.6 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	howett.net/plist v1.0.1 // indirect
-	modernc.org/libc v1.62.1 // indirect
+	modernc.org/libc v1.65.0 // indirect
 	modernc.org/mathutil v1.7.1 // indirect
-	modernc.org/memory v1.9.1 // indirect
+	modernc.org/memory v1.10.0 // indirect
 )
 
 retract (
diff --git a/go.sum b/go.sum
index f5609ad..5ad2367 100644
--- a/go.sum
+++ b/go.sum
@@ -41,8 +41,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM=
-github.com/elastic/go-sysinfo v1.15.2 h1:rgUFj4xRnxdAaxh4IhuGzHINWT8WrwUe5D338LLRC0s=
-github.com/elastic/go-sysinfo v1.15.2/go.mod h1:jPSuTgXG+dhhh0GKIyI2Cso+w5lPJ5PvVqKlL8LV/Hk=
+github.com/elastic/go-sysinfo v1.15.3 h1:W+RnmhKFkqPTCRoFq2VCTmsT4p/fwpo+3gKNQsn1XU0=
+github.com/elastic/go-sysinfo v1.15.3/go.mod h1:K/cNrqYTDrSoMh2oDkYEMS2+a72GRxMvNP+GC+vRIlo=
 github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
 github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI=
 github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8=
@@ -61,13 +61,13 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
 github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
-github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
+github.com/go-sql-driver/mysql v1.9.2 h1:4cNKDYQ1I84SXslGddlsrMhc8k4LeDVj6Ad6WRjiHuU=
+github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
 github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
-github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
-github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
+github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
+github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
 github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@@ -158,15 +158,15 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
-github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
-github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
+github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
+github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
 github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM=
 github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
-github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
-github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
 github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
 github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
 github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
@@ -192,8 +192,8 @@ github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZ
 github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
 github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 h1:LY6cI8cP4B9rrpTleZk95+08kl2gF4rixG7+V/dwL6Q=
 github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
-github.com/ydb-platform/ydb-go-sdk/v3 v3.106.1 h1:2sivB7mTtsXIrcYAwbvROJ+dwG0k/vsjf5ImnhuwwQM=
-github.com/ydb-platform/ydb-go-sdk/v3 v3.106.1/go.mod h1:l5sSv153E18VvYcsmr51hok9Sjc16tEC8AXGbwrk+ho=
+github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1 h1:ixAiqjj2S/dNuJqrz4AxSqgw2P5OBMXp68hB5nNriUk=
+github.com/ydb-platform/ydb-go-sdk/v3 v3.108.1/go.mod h1:l5sSv153E18VvYcsmr51hok9Sjc16tEC8AXGbwrk+ho=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -221,11 +221,11 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
-golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
+golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
+golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
-golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
+golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
+golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@@ -244,8 +244,8 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
+golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
+golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -255,8 +255,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
-golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
+golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -268,15 +268,15 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
-golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
+golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
+golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -285,8 +285,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
-golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
+golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
+golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -339,20 +339,20 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
 howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM=
 howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
-modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
-modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
-modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
-modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
-modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
-modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
+modernc.org/cc/v4 v4.26.0 h1:QMYvbVduUGH0rrO+5mqF/PSPPRZNpRtg2CLELy7vUpA=
+modernc.org/cc/v4 v4.26.0/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
+modernc.org/ccgo/v4 v4.26.0 h1:gVzXaDzGeBYJ2uXTOpR8FR7OlksDOe9jxnjhIKCsiTc=
+modernc.org/ccgo/v4 v4.26.0/go.mod h1:Sem8f7TFUtVXkG2fiaChQtyyfkqhJBg/zjEJBkmuAVY=
+modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
+modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
 modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
 modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
-modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
-modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
+modernc.org/libc v1.65.0 h1:e183gLDnAp9VJh6gWKdTy0CThL9Pt7MfcR/0bgb7Y1Y=
+modernc.org/libc v1.65.0/go.mod h1:7m9VzGq7APssBTydds2zBcxGREwvIGpuUBaKTXdm2Qs=
 modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
 modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
-modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
-modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
+modernc.org/memory v1.10.0 h1:fzumd51yQ1DxcOxSO+S6X7+QTuVU+n8/Aj7swYjFfC4=
+modernc.org/memory v1.10.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
 modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
 modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
 modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
diff --git a/internal/controller/controller.go b/internal/controller/store.go
similarity index 100%
rename from internal/controller/controller.go
rename to internal/controller/store.go
diff --git a/internal/dialect/dialectquery/turso.go b/internal/dialect/dialectquery/turso.go
deleted file mode 100644
index ced0f5d..0000000
--- a/internal/dialect/dialectquery/turso.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package dialectquery
-
-type Turso struct {
-	Sqlite3
-}
-
-var _ Querier = (*Turso)(nil)
diff --git a/internal/dialect/dialects.go b/internal/dialect/dialects.go
deleted file mode 100644
index acf0642..0000000
--- a/internal/dialect/dialects.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package dialect
-
-// Dialect is the type of database dialect.
-type Dialect string
-
-const (
-	Postgres   Dialect = "postgres"
-	Mysql      Dialect = "mysql"
-	Sqlite3    Dialect = "sqlite3"
-	Sqlserver  Dialect = "sqlserver"
-	Redshift   Dialect = "redshift"
-	Tidb       Dialect = "tidb"
-	Clickhouse Dialect = "clickhouse"
-	Vertica    Dialect = "vertica"
-	Ydb        Dialect = "ydb"
-	Turso      Dialect = "turso"
-	Starrocks  Dialect = "starrocks"
-)
diff --git a/internal/dialect/dialectquery/clickhouse.go b/internal/dialects/clickhouse.go
similarity index 53%
rename from internal/dialect/dialectquery/clickhouse.go
rename to internal/dialects/clickhouse.go
index 723efd4..7b676b9 100644
--- a/internal/dialect/dialectquery/clickhouse.go
+++ b/internal/dialects/clickhouse.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Clickhouse struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Clickhouse)(nil)
+// NewClickhouse returns a new [dialect.Querier] for Clickhouse dialect.
+func NewClickhouse() dialect.Querier {
+	return &clickhouse{}
+}
+
+type clickhouse struct{}
+
+var _ dialect.Querier = (*clickhouse)(nil)
 
-func (c *Clickhouse) CreateTable(tableName string) string {
+func (c *clickhouse) CreateTable(tableName string) string {
 	q := `CREATE TABLE IF NOT EXISTS %s (
 		version_id Int64,
 		is_applied UInt8,
@@ -18,27 +27,27 @@ func (c *Clickhouse) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Clickhouse) InsertVersion(tableName string) string {
+func (c *clickhouse) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES ($1, $2)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Clickhouse) DeleteVersion(tableName string) string {
+func (c *clickhouse) DeleteVersion(tableName string) string {
 	q := `ALTER TABLE %s DELETE WHERE version_id = $1 SETTINGS mutations_sync = 2`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Clickhouse) GetMigrationByVersion(tableName string) string {
+func (c *clickhouse) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id = $1 ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Clickhouse) ListMigrations(tableName string) string {
+func (c *clickhouse) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied FROM %s ORDER BY version_id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Clickhouse) GetLatestVersion(tableName string) string {
+func (c *clickhouse) GetLatestVersion(tableName string) string {
 	q := `SELECT max(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
diff --git a/internal/dialect/dialectquery/mysql.go b/internal/dialects/mysql.go
similarity index 62%
rename from internal/dialect/dialectquery/mysql.go
rename to internal/dialects/mysql.go
index ea2dcae..9341589 100644
--- a/internal/dialect/dialectquery/mysql.go
+++ b/internal/dialects/mysql.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Mysql struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Mysql)(nil)
+// NewMysql returns a new [dialect.Querier] for MySQL dialect.
+func NewMysql() dialect.QuerierExtender {
+	return &mysql{}
+}
+
+type mysql struct{}
+
+var _ dialect.QuerierExtender = (*mysql)(nil)
 
-func (m *Mysql) CreateTable(tableName string) string {
+func (m *mysql) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
 		id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
 		version_id bigint NOT NULL,
@@ -17,32 +26,32 @@ func (m *Mysql) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Mysql) InsertVersion(tableName string) string {
+func (m *mysql) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES (?, ?)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Mysql) DeleteVersion(tableName string) string {
+func (m *mysql) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id=?`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Mysql) GetMigrationByVersion(tableName string) string {
+func (m *mysql) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id=? ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Mysql) ListMigrations(tableName string) string {
+func (m *mysql) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied from %s ORDER BY id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Mysql) GetLatestVersion(tableName string) string {
+func (m *mysql) GetLatestVersion(tableName string) string {
 	q := `SELECT MAX(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Mysql) TableExists(tableName string) string {
+func (m *mysql) TableExists(tableName string) string {
 	schemaName, tableName := parseTableIdentifier(tableName)
 	if schemaName != "" {
 		q := `SELECT EXISTS ( SELECT 1 FROM information_schema.tables WHERE table_schema = '%s' AND table_name = '%s' )`
diff --git a/internal/dialect/dialectquery/postgres.go b/internal/dialects/postgres.go
similarity index 56%
rename from internal/dialect/dialectquery/postgres.go
rename to internal/dialects/postgres.go
index 9c44999..b2c7327 100644
--- a/internal/dialect/dialectquery/postgres.go
+++ b/internal/dialects/postgres.go
@@ -1,14 +1,22 @@
-package dialectquery
+package dialects
 
 import (
 	"fmt"
+	"strings"
+
+	"github.com/pressly/goose/v3/database/dialect"
 )
 
-type Postgres struct{}
+// NewPostgres returns a new [dialect.Querier] for PostgreSQL dialect.
+func NewPostgres() dialect.QuerierExtender {
+	return &postgres{}
+}
+
+type postgres struct{}
 
-var _ Querier = (*Postgres)(nil)
+var _ dialect.QuerierExtender = (*postgres)(nil)
 
-func (p *Postgres) CreateTable(tableName string) string {
+func (p *postgres) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
 		id integer PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
 		version_id bigint NOT NULL,
@@ -18,32 +26,32 @@ func (p *Postgres) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (p *Postgres) InsertVersion(tableName string) string {
+func (p *postgres) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES ($1, $2)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (p *Postgres) DeleteVersion(tableName string) string {
+func (p *postgres) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id=$1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (p *Postgres) GetMigrationByVersion(tableName string) string {
+func (p *postgres) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id=$1 ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (p *Postgres) ListMigrations(tableName string) string {
+func (p *postgres) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied from %s ORDER BY id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (p *Postgres) GetLatestVersion(tableName string) string {
+func (p *postgres) GetLatestVersion(tableName string) string {
 	q := `SELECT max(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (p *Postgres) TableExists(tableName string) string {
+func (p *postgres) TableExists(tableName string) string {
 	schemaName, tableName := parseTableIdentifier(tableName)
 	if schemaName != "" {
 		q := `SELECT EXISTS ( SELECT 1 FROM pg_tables WHERE schemaname = '%s' AND tablename = '%s' )`
@@ -52,3 +60,11 @@ func (p *Postgres) TableExists(tableName string) string {
 	q := `SELECT EXISTS ( SELECT 1 FROM pg_tables WHERE (current_schema() IS NULL OR schemaname = current_schema()) AND tablename = '%s' )`
 	return fmt.Sprintf(q, tableName)
 }
+
+func parseTableIdentifier(name string) (schema, table string) {
+	schema, table, found := strings.Cut(name, ".")
+	if !found {
+		return "", name
+	}
+	return schema, table
+}
diff --git a/internal/dialect/dialectquery/redshift.go b/internal/dialects/redshift.go
similarity index 52%
rename from internal/dialect/dialectquery/redshift.go
rename to internal/dialects/redshift.go
index 4090394..9b7dcf6 100644
--- a/internal/dialect/dialectquery/redshift.go
+++ b/internal/dialects/redshift.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Redshift struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Redshift)(nil)
+// Redshift returns a new [dialect.Querier] for Redshift dialect.
+func NewRedshift() dialect.Querier {
+	return &redshift{}
+}
+
+type redshift struct{}
+
+var _ dialect.Querier = (*redshift)(nil)
 
-func (r *Redshift) CreateTable(tableName string) string {
+func (r *redshift) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
 		id integer NOT NULL identity(1, 1),
 		version_id bigint NOT NULL,
@@ -17,27 +26,27 @@ func (r *Redshift) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (r *Redshift) InsertVersion(tableName string) string {
+func (r *redshift) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES ($1, $2)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (r *Redshift) DeleteVersion(tableName string) string {
+func (r *redshift) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id=$1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (r *Redshift) GetMigrationByVersion(tableName string) string {
+func (r *redshift) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id=$1 ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (r *Redshift) ListMigrations(tableName string) string {
+func (r *redshift) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied from %s ORDER BY id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (r *Redshift) GetLatestVersion(tableName string) string {
+func (r *redshift) GetLatestVersion(tableName string) string {
 	q := `SELECT max(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
diff --git a/internal/dialect/dialectquery/sqlite3.go b/internal/dialects/sqlite3.go
similarity index 52%
rename from internal/dialect/dialectquery/sqlite3.go
rename to internal/dialects/sqlite3.go
index 1c58a74..8cd03ec 100644
--- a/internal/dialect/dialectquery/sqlite3.go
+++ b/internal/dialects/sqlite3.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Sqlite3 struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Sqlite3)(nil)
+// NewSqlite3 returns a [dialect.Querier] for SQLite3 dialect.
+func NewSqlite3() dialect.Querier {
+	return &sqlite3{}
+}
+
+type sqlite3 struct{}
+
+var _ dialect.Querier = (*sqlite3)(nil)
 
-func (s *Sqlite3) CreateTable(tableName string) string {
+func (s *sqlite3) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
 		id INTEGER PRIMARY KEY AUTOINCREMENT,
 		version_id INTEGER NOT NULL,
@@ -16,27 +25,27 @@ func (s *Sqlite3) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlite3) InsertVersion(tableName string) string {
+func (s *sqlite3) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES (?, ?)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlite3) DeleteVersion(tableName string) string {
+func (s *sqlite3) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id=?`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlite3) GetMigrationByVersion(tableName string) string {
+func (s *sqlite3) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id=? ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlite3) ListMigrations(tableName string) string {
+func (s *sqlite3) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied from %s ORDER BY id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlite3) GetLatestVersion(tableName string) string {
+func (s *sqlite3) GetLatestVersion(tableName string) string {
 	q := `SELECT MAX(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
diff --git a/internal/dialect/dialectquery/sqlserver.go b/internal/dialects/sqlserver.go
similarity index 52%
rename from internal/dialect/dialectquery/sqlserver.go
rename to internal/dialects/sqlserver.go
index 4d172c2..c9bb0d4 100644
--- a/internal/dialect/dialectquery/sqlserver.go
+++ b/internal/dialects/sqlserver.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Sqlserver struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Sqlserver)(nil)
+// NewSqlserver returns a [dialect.Querier] for SQL Server dialect.
+func NewSqlserver() dialect.Querier {
+	return &sqlserver{}
+}
+
+type sqlserver struct{}
+
+var _ dialect.Querier = (*sqlserver)(nil)
 
-func (s *Sqlserver) CreateTable(tableName string) string {
+func (s *sqlserver) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
 		id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
 		version_id BIGINT NOT NULL,
@@ -16,27 +25,27 @@ func (s *Sqlserver) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlserver) InsertVersion(tableName string) string {
+func (s *sqlserver) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES (@p1, @p2)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlserver) DeleteVersion(tableName string) string {
+func (s *sqlserver) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id=@p1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlserver) GetMigrationByVersion(tableName string) string {
+func (s *sqlserver) GetMigrationByVersion(tableName string) string {
 	q := `SELECT TOP 1 tstamp, is_applied FROM %s WHERE version_id=@p1 ORDER BY tstamp DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlserver) ListMigrations(tableName string) string {
+func (s *sqlserver) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied FROM %s ORDER BY id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (s *Sqlserver) GetLatestVersion(tableName string) string {
+func (s *sqlserver) GetLatestVersion(tableName string) string {
 	q := `SELECT MAX(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
diff --git a/internal/dialect/dialectquery/starrocks.go b/internal/dialects/starrocks.go
similarity index 54%
rename from internal/dialect/dialectquery/starrocks.go
rename to internal/dialects/starrocks.go
index 7b35be4..1f1a20c 100644
--- a/internal/dialect/dialectquery/starrocks.go
+++ b/internal/dialects/starrocks.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Starrocks struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Starrocks)(nil)
+// NewStarrocks returns a [dialect.Querier] for StarRocks dialect.
+func NewStarrocks() dialect.Querier {
+	return &starrocks{}
+}
+
+type starrocks struct{}
+
+var _ dialect.Querier = (*starrocks)(nil)
 
-func (m *Starrocks) CreateTable(tableName string) string {
+func (m *starrocks) CreateTable(tableName string) string {
 	q := `CREATE TABLE IF NOT EXISTS %s (
 		id bigint NOT NULL AUTO_INCREMENT,
 		version_id bigint NOT NULL,
@@ -19,27 +28,27 @@ func (m *Starrocks) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Starrocks) InsertVersion(tableName string) string {
+func (m *starrocks) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES (?, ?)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Starrocks) DeleteVersion(tableName string) string {
+func (m *starrocks) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id=?`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Starrocks) GetMigrationByVersion(tableName string) string {
+func (m *starrocks) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id=? ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Starrocks) ListMigrations(tableName string) string {
+func (m *starrocks) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied from %s ORDER BY id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (m *Starrocks) GetLatestVersion(tableName string) string {
+func (m *starrocks) GetLatestVersion(tableName string) string {
 	q := `SELECT MAX(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
diff --git a/internal/dialect/dialectquery/tidb.go b/internal/dialects/tidb.go
similarity index 82%
rename from internal/dialect/dialectquery/tidb.go
rename to internal/dialects/tidb.go
index 0549e84..f5b58f2 100644
--- a/internal/dialect/dialectquery/tidb.go
+++ b/internal/dialects/tidb.go
@@ -1,10 +1,19 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
+
+	"github.com/pressly/goose/v3/database/dialect"
+)
+
+// NewTidb returns a [dialect.Querier] for TiDB dialect.
+func NewTidb() dialect.Querier {
+	return &Tidb{}
+}
 
 type Tidb struct{}
 
-var _ Querier = (*Tidb)(nil)
+var _ dialect.Querier = (*Tidb)(nil)
 
 func (t *Tidb) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
diff --git a/internal/dialects/turso.go b/internal/dialects/turso.go
new file mode 100644
index 0000000..8698e7d
--- /dev/null
+++ b/internal/dialects/turso.go
@@ -0,0 +1,14 @@
+package dialects
+
+import "github.com/pressly/goose/v3/database/dialect"
+
+// NewTurso returns a [dialect.Querier] for Turso dialect.
+func NewTurso() dialect.Querier {
+	return &turso{}
+}
+
+type turso struct {
+	sqlite3
+}
+
+var _ dialect.Querier = (*turso)(nil)
diff --git a/internal/dialect/dialectquery/vertica.go b/internal/dialects/vertica.go
similarity index 52%
rename from internal/dialect/dialectquery/vertica.go
rename to internal/dialects/vertica.go
index f4702be..2df642b 100644
--- a/internal/dialect/dialectquery/vertica.go
+++ b/internal/dialects/vertica.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Vertica struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Vertica)(nil)
+// NewVertica returns a new [dialect.Querier] for Vertica dialect.
+func NewVertica() dialect.Querier {
+	return &vertica{}
+}
+
+type vertica struct{}
+
+var _ dialect.Querier = (*vertica)(nil)
 
-func (v *Vertica) CreateTable(tableName string) string {
+func (v *vertica) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
 		id identity(1,1) NOT NULL,
 		version_id bigint NOT NULL,
@@ -17,27 +26,27 @@ func (v *Vertica) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (v *Vertica) InsertVersion(tableName string) string {
+func (v *vertica) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (version_id, is_applied) VALUES (?, ?)`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (v *Vertica) DeleteVersion(tableName string) string {
+func (v *vertica) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id=?`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (v *Vertica) GetMigrationByVersion(tableName string) string {
+func (v *vertica) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id=? ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (v *Vertica) ListMigrations(tableName string) string {
+func (v *vertica) ListMigrations(tableName string) string {
 	q := `SELECT version_id, is_applied from %s ORDER BY id DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (v *Vertica) GetLatestVersion(tableName string) string {
+func (v *vertica) GetLatestVersion(tableName string) string {
 	q := `SELECT MAX(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
diff --git a/internal/dialect/dialectquery/ydb.go b/internal/dialects/ydb.go
similarity index 56%
rename from internal/dialect/dialectquery/ydb.go
rename to internal/dialects/ydb.go
index ab5e68e..5ced114 100644
--- a/internal/dialect/dialectquery/ydb.go
+++ b/internal/dialects/ydb.go
@@ -1,12 +1,21 @@
-package dialectquery
+package dialects
 
-import "fmt"
+import (
+	"fmt"
 
-type Ydb struct{}
+	"github.com/pressly/goose/v3/database/dialect"
+)
 
-var _ Querier = (*Ydb)(nil)
+// NewVertica returns a new [dialect.Querier] for Vertica dialect.
+func NewYDB() dialect.Querier {
+	return &ydb{}
+}
+
+type ydb struct{}
+
+var _ dialect.Querier = (*ydb)(nil)
 
-func (c *Ydb) CreateTable(tableName string) string {
+func (c *ydb) CreateTable(tableName string) string {
 	q := `CREATE TABLE %s (
 		version_id Uint64,
 		is_applied Bool,
@@ -17,7 +26,7 @@ func (c *Ydb) CreateTable(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Ydb) InsertVersion(tableName string) string {
+func (c *ydb) InsertVersion(tableName string) string {
 	q := `INSERT INTO %s (
 		version_id, 
 		is_applied, 
@@ -30,24 +39,24 @@ func (c *Ydb) InsertVersion(tableName string) string {
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Ydb) DeleteVersion(tableName string) string {
+func (c *ydb) DeleteVersion(tableName string) string {
 	q := `DELETE FROM %s WHERE version_id = $1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Ydb) GetMigrationByVersion(tableName string) string {
+func (c *ydb) GetMigrationByVersion(tableName string) string {
 	q := `SELECT tstamp, is_applied FROM %s WHERE version_id = $1 ORDER BY tstamp DESC LIMIT 1`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Ydb) ListMigrations(tableName string) string {
+func (c *ydb) ListMigrations(tableName string) string {
 	q := `
 	SELECT version_id, is_applied, tstamp AS __discard_column_tstamp 
 	FROM %s ORDER BY __discard_column_tstamp DESC`
 	return fmt.Sprintf(q, tableName)
 }
 
-func (c *Ydb) GetLatestVersion(tableName string) string {
+func (c *ydb) GetLatestVersion(tableName string) string {
 	q := `SELECT MAX(version_id) FROM %s`
 	return fmt.Sprintf(q, tableName)
 }
diff --git a/internal/dialect/store.go b/internal/legacystore/legacystore.go
similarity index 82%
rename from internal/dialect/store.go
rename to internal/legacystore/legacystore.go
index e9b768f..6d9e619 100644
--- a/internal/dialect/store.go
+++ b/internal/legacystore/legacystore.go
@@ -1,4 +1,4 @@
-package dialect
+package legacystore
 
 import (
 	"context"
@@ -6,7 +6,9 @@ import (
 	"fmt"
 	"time"
 
-	"github.com/pressly/goose/v3/internal/dialect/dialectquery"
+	"github.com/pressly/goose/v3/database"
+	"github.com/pressly/goose/v3/database/dialect"
+	"github.com/pressly/goose/v3/internal/dialects"
 )
 
 // Store is the interface that wraps the basic methods for a database dialect.
@@ -46,31 +48,31 @@ type Store interface {
 }
 
 // NewStore returns a new Store for the given dialect.
-func NewStore(d Dialect) (Store, error) {
-	var querier dialectquery.Querier
+func NewStore(d database.Dialect) (Store, error) {
+	var querier dialect.Querier
 	switch d {
-	case Postgres:
-		querier = &dialectquery.Postgres{}
-	case Mysql:
-		querier = &dialectquery.Mysql{}
-	case Sqlite3:
-		querier = &dialectquery.Sqlite3{}
-	case Sqlserver:
-		querier = &dialectquery.Sqlserver{}
-	case Redshift:
-		querier = &dialectquery.Redshift{}
-	case Tidb:
-		querier = &dialectquery.Tidb{}
-	case Clickhouse:
-		querier = &dialectquery.Clickhouse{}
-	case Vertica:
-		querier = &dialectquery.Vertica{}
-	case Ydb:
-		querier = &dialectquery.Ydb{}
-	case Turso:
-		querier = &dialectquery.Turso{}
-	case Starrocks:
-		querier = &dialectquery.Starrocks{}
+	case database.DialectPostgres:
+		querier = dialects.NewPostgres()
+	case database.DialectMySQL:
+		querier = dialects.NewMysql()
+	case database.DialectSQLite3:
+		querier = dialects.NewSqlite3()
+	case database.DialectMSSQL:
+		querier = dialects.NewSqlserver()
+	case database.DialectRedshift:
+		querier = dialects.NewRedshift()
+	case database.DialectTiDB:
+		querier = dialects.NewTidb()
+	case database.DialectClickHouse:
+		querier = dialects.NewClickhouse()
+	case database.DialectVertica:
+		querier = dialects.NewVertica()
+	case database.DialectYdB:
+		querier = dialects.NewYDB()
+	case database.DialectTurso:
+		querier = dialects.NewTurso()
+	case database.DialectStarrocks:
+		querier = dialects.NewStarrocks()
 	default:
 		return nil, fmt.Errorf("unknown querier dialect: %v", d)
 	}
@@ -88,7 +90,7 @@ type ListMigrationsResult struct {
 }
 
 type store struct {
-	querier dialectquery.Querier
+	querier dialect.Querier
 }
 
 var _ Store = (*store)(nil)
diff --git a/migrate.go b/migrate.go
index 32c9e81..2050d16 100644
--- a/migrate.go
+++ b/migrate.go
@@ -5,13 +5,14 @@ import (
 	"database/sql"
 	"errors"
 	"fmt"
-	"go.uber.org/multierr"
 	"io/fs"
 	"math"
 	"path"
 	"sort"
 	"strings"
 	"time"
+
+	"go.uber.org/multierr"
 )
 
 var (
diff --git a/provider.go b/provider.go
index a4163a6..44a1afa 100644
--- a/provider.go
+++ b/provider.go
@@ -72,11 +72,11 @@ func NewProvider(dialect Dialect, db *sql.DB, fsys fs.FS, opts ...ProviderOption
 	}
 	// Allow users to specify a custom store implementation, but only if they don't specify a
 	// dialect. If they specify a dialect, we'll use the default store implementation.
-	if dialect == "" && cfg.store == nil {
-		return nil, errors.New("dialect must not be empty")
+	if dialect == DialectCustom && cfg.store == nil {
+		return nil, errors.New("custom store must be supplied when using a custom dialect, make sure to pass WithStore option")
 	}
-	if dialect != "" && cfg.store != nil {
-		return nil, errors.New("dialect must be empty when using a custom store implementation")
+	if dialect != DialectCustom && cfg.store != nil {
+		return nil, errors.New("custom store must not be specified when using one of the default dialects, use DialectCustom instead")
 	}
 	var store database.Store
 	if dialect != "" {
diff --git a/provider_options.go b/provider_options.go
index 7d53849..863ca6e 100644
--- a/provider_options.go
+++ b/provider_options.go
@@ -33,7 +33,7 @@ type ProviderOption interface {
 //	    return err
 //	}
 //	// Create a provider with the custom store.
-//	provider, err := goose.NewProvider("", db, nil, goose.WithStore(store))
+//	provider, err := goose.NewProvider(goose.DialectCustom, db, nil, goose.WithStore(store))
 //	if err != nil {
 //	    return err
 //	}



Test patch:
diff --git a/database/store_test.go b/database/store_test.go
index 7dd6c1b..c4102c0 100644
--- a/database/store_test.go
+++ b/database/store_test.go
@@ -79,9 +79,9 @@ func testStore(
 	alreadyExists func(t *testing.T, err error),
 ) {
 	const (
-		tablename = "test_goose_db_version"
+		tableName = "test_goose_db_version"
 	)
-	store, err := database.NewStore(d, tablename)
+	store, err := database.NewStore(d, tableName)
 	require.NoError(t, err)
 	// Create the version table.
 	err = runTx(ctx, db, func(tx *sql.Tx) error {
diff --git a/provider_options_test.go b/provider_options_test.go
index f22d375..866498b 100644
--- a/provider_options_test.go
+++ b/provider_options_test.go
@@ -24,7 +24,7 @@ func TestNewProvider(t *testing.T) {
 	}
 	t.Run("invalid", func(t *testing.T) {
 		// Empty dialect not allowed
-		_, err = goose.NewProvider("", db, fsys)
+		_, err = goose.NewProvider(goose.DialectCustom, db, fsys)
 		require.Error(t, err)
 		// Invalid dialect not allowed
 		_, err = goose.NewProvider("unknown-dialect", db, fsys)
@@ -59,7 +59,7 @@ func TestNewProvider(t *testing.T) {
 		// Custom store allowed
 		store, err := database.NewStore(goose.DialectSQLite3, "custom_table")
 		require.NoError(t, err)
-		_, err = goose.NewProvider("", db, nil, goose.WithStore(store))
+		_, err = goose.NewProvider(goose.DialectCustom, db, nil, goose.WithStore(store))
 		require.Error(t, err)
 	})
 }
diff --git a/provider_run_test.go b/provider_run_test.go
index 017c4e9..de09f03 100644
--- a/provider_run_test.go
+++ b/provider_run_test.go
@@ -749,7 +749,7 @@ func TestCustomStoreTableExists(t *testing.T) {
 	store, err := database.NewStore(database.DialectSQLite3, goose.DefaultTablename)
 	require.NoError(t, err)
 	for i := 0; i < 2; i++ {
-		p, err := goose.NewProvider("", db, newFsys(),
+		p, err := goose.NewProvider(goose.DialectCustom, db, newFsys(),
 			goose.WithStore(&customStoreSQLite3{store}),
 		)
 		require.NoError(t, err)