Repository: pressly/goose

Issue:
Add WithTableName to goose provider
I keep running into this:

> custom store must not be specified when using one of the default dialects, use DialectCustom instead

And it should be much simpler to initialize a goose provider, for one of the default dialects, and change the default table name without reaching into the weeds of [database.Store](https://pkg.go.dev/github.com/pressly/goose/v3@v3.25.0/database#NewStore).

I.e., make the common case simple for end-users.

```go
provider, err := goose.NewProvider(
	database.DialectPostgres,
	db,
	fsys,
	goose.WithTableName("my_custom_table"),
)
```

Discussion:

Solution patch:
diff --git a/provider.go b/provider.go
index 35cf757..1185e15 100644
--- a/provider.go
+++ b/provider.go
@@ -1,6 +1,7 @@
 package goose
 
 import (
+	"cmp"
 	"context"
 	"database/sql"
 	"errors"
@@ -78,10 +79,14 @@ func NewProvider(dialect Dialect, db *sql.DB, fsys fs.FS, opts ...ProviderOption
 	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")
 	}
+	// Allow table name to be set only if store is not set.
+	if cfg.tableName != "" && cfg.store != nil {
+		return nil, errors.New("WithTableName cannot be used with WithStore; set the table name directly on your custom store")
+	}
 	var store database.Store
 	if dialect != "" {
 		var err error
-		store, err = database.NewStore(dialect, DefaultTablename)
+		store, err = database.NewStore(dialect, cmp.Or(cfg.tableName, DefaultTablename))
 		if err != nil {
 			return nil, err
 		}
diff --git a/provider_options.go b/provider_options.go
index 09eeec4..aa6ea1d 100644
--- a/provider_options.go
+++ b/provider_options.go
@@ -19,24 +19,15 @@ type ProviderOption interface {
 	apply(*config) error
 }
 
-// WithStore configures the provider with a custom [database.Store] implementation.
+// WithStore configures the provider with a custom [database.Store], allowing users to bring their
+// own implementation of the store interface. When this option is used, the dialect parameter of
+// [NewProvider] must be set to [DialectCustom].
 //
-// By default, the provider uses the [database.NewStore] function to create a store backed by the
-// given dialect. However, this option allows users to provide their own implementation or call
-// [database.NewStore] with custom options, such as setting the table name.
+// This option cannot be used together with [WithTableName], since the table name is set on the
+// store.
 //
-// Example:
-//
-//	// Create a store with a custom table name.
-//	store, err := database.NewStore(database.DialectPostgres, "my_custom_table_name")
-//	if err != nil {
-//	    return err
-//	}
-//	// Create a provider with the custom store.
-//	provider, err := goose.NewProvider(goose.DialectCustom, db, nil, goose.WithStore(store))
-//	if err != nil {
-//	    return err
-//	}
+// By default, the provider uses the [database.NewStore] function to create a store backed by one of
+// the officially supported dialects.
 func WithStore(store database.Store) ProviderOption {
 	return configFunc(func(c *config) error {
 		if c.store != nil {
@@ -53,6 +44,20 @@ func WithStore(store database.Store) ProviderOption {
 	})
 }
 
+// WithTableName sets the name of the database table used to track history of applied migrations.
+// This option cannot be used together with [WithStore], since the table name is set on the store.
+//
+// Default is "goose_db_version".
+func WithTableName(name string) ProviderOption {
+	return configFunc(func(c *config) error {
+		if name == "" {
+			return errors.New("table name must not be empty")
+		}
+		c.tableName = name
+		return nil
+	})
+}
+
 // WithVerbose enables verbose logging.
 func WithVerbose(b bool) ProviderOption {
 	return configFunc(func(c *config) error {
@@ -183,7 +188,8 @@ func WithIsolateDDL(b bool) ProviderOption {
 }
 
 type config struct {
-	store database.Store
+	tableName string
+	store     database.Store
 
 	verbose         bool
 	excludePaths    map[string]bool



Test patch:
diff --git a/provider_options_test.go b/provider_options_test.go
index 866498b..ee722e6 100644
--- a/provider_options_test.go
+++ b/provider_options_test.go
@@ -46,6 +46,15 @@ func TestNewProvider(t *testing.T) {
 			goose.WithStore(store),
 		)
 		require.Error(t, err)
+		// Cannot set empty table name
+		_, err = goose.NewProvider(goose.DialectSQLite3, db, nil, goose.WithTableName(""))
+		require.Error(t, err)
+		// Cannot set table name when custom store is set
+		_, err = goose.NewProvider(goose.DialectCustom, db, nil,
+			goose.WithStore(store),
+			goose.WithTableName("custom_table"),
+		)
+		require.Error(t, err)
 	})
 	t.Run("valid", func(t *testing.T) {
 		// Valid dialect, db, and fsys allowed
@@ -57,9 +66,14 @@ func TestNewProvider(t *testing.T) {
 		)
 		require.NoError(t, err)
 		// Custom store allowed
-		store, err := database.NewStore(goose.DialectSQLite3, "custom_table")
+		const tableName = "custom_table"
+		store, err := database.NewStore(goose.DialectSQLite3, tableName)
+		require.NoError(t, err)
+		require.Equal(t, tableName, store.Tablename())
+		_, err = goose.NewProvider(goose.DialectCustom, db, fsys, goose.WithStore(store))
+		require.NoError(t, err)
+		// Custom table name allowed on dialect-based store
+		_, err = goose.NewProvider(goose.DialectSQLite3, db, fsys, goose.WithTableName("some_table"))
 		require.NoError(t, err)
-		_, err = goose.NewProvider(goose.DialectCustom, db, nil, goose.WithStore(store))
-		require.Error(t, err)
 	})
 }