Skip to content

Commit

Permalink
add StoreExtended interface
Browse files Browse the repository at this point in the history
  • Loading branch information
mfridman committed Nov 23, 2024
1 parent 4796eb9 commit a14c4a6
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 7 deletions.
33 changes: 33 additions & 0 deletions database/store_extended.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package database

import "context"

// StoreExtender is an extension of the Store interface that provides optional optimizations and
// database-specific features. While not required by the core goose 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 [errors.ErrUnsupported]. This is similar to how Go's standard library
// handles interface extensions (e.g., sql.Scanner, sql.Driver).
//
// The goose package handles these extended capabilities through a [controller.StoreController],
// which automatically uses optimized methods when available while falling back to default behavior
// when they're not implemented.
//
// Example usage to verify implementation:
//
// var _ StoreExtender = (*CustomStoreExtended)(nil)
//
// In short, a compile-time to allow implementors to verify that they have implemented all methods.
type StoreExtender interface {
Store

// TableExists checks if the migrations table exists in the database. Implementing this method
// allows goose 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 [errors.ErrUnsupported] if the database does not provide an efficient way to check
// table existence.
TableExists(ctx context.Context, db DBTxConn) (bool, error)
}
5 changes: 1 addition & 4 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
// that are not part of the core Store interface.
type StoreController struct{ database.Store }

var _ database.Store = (*StoreController)(nil)
var _ database.StoreExtender = (*StoreController)(nil)

// NewStoreController returns a new StoreController that wraps the given Store.
//
Expand All @@ -27,9 +27,6 @@ func NewStoreController(store database.Store) *StoreController {
return &StoreController{store}
}

// TableExists is an optional method that checks if the version table exists in the database. It is
// recommended to implement this method if the database supports it, as it can be used to optimize
// certain operations.
func (c *StoreController) TableExists(ctx context.Context, db database.DBTxConn) (bool, error) {
if t, ok := c.Store.(interface {
TableExists(ctx context.Context, db database.DBTxConn) (bool, error)
Expand Down
6 changes: 3 additions & 3 deletions provider_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -844,9 +844,9 @@ func TestPending(t *testing.T) {
})
}

type customStoreSQLite3 struct {
database.Store
}
var _ database.StoreExtender = (*customStoreSQLite3)(nil)

type customStoreSQLite3 struct{ database.Store }

func (s *customStoreSQLite3) TableExists(ctx context.Context, db database.DBTxConn) (bool, error) {
q := `SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name=?) AS table_exists`
Expand Down

0 comments on commit a14c4a6

Please sign in to comment.