-
Notifications
You must be signed in to change notification settings - Fork 531
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(experimental): goose provider with unimplemented methods (#596)
- Loading branch information
Showing
11 changed files
with
772 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Package sqladapter provides an interface for interacting with a SQL database. | ||
// | ||
// All supported database dialects must implement the Store interface. | ||
package sqladapter | ||
|
||
import ( | ||
"context" | ||
"time" | ||
|
||
"github.com/pressly/goose/v3/internal/sqlextended" | ||
) | ||
|
||
// Store is the interface that wraps the basic methods for a database dialect. | ||
// | ||
// A dialect is a set of SQL statements that are specific to a database. | ||
// | ||
// By defining a store interface, we can support multiple databases with a single codebase. | ||
// | ||
// The underlying implementation does not modify the error. It is the callers responsibility to | ||
// assert for the correct error, such as [sql.ErrNoRows]. | ||
type Store interface { | ||
// CreateVersionTable creates the version table within a transaction. This table is used to | ||
// record applied migrations. | ||
CreateVersionTable(ctx context.Context, db sqlextended.DBTxConn) error | ||
|
||
// InsertOrDelete inserts or deletes a version id from the version table. | ||
InsertOrDelete(ctx context.Context, db sqlextended.DBTxConn, direction bool, version int64) error | ||
|
||
// GetMigration retrieves a single migration by version id. | ||
// | ||
// Returns the raw sql error if the query fails. It is the callers responsibility to assert for | ||
// the correct error, such as [sql.ErrNoRows]. | ||
GetMigration(ctx context.Context, db sqlextended.DBTxConn, version int64) (*GetMigrationResult, error) | ||
|
||
// ListMigrations retrieves all migrations sorted in descending order by id. | ||
// | ||
// If there are no migrations, an empty slice is returned with no error. | ||
ListMigrations(ctx context.Context, db sqlextended.DBTxConn) ([]*ListMigrationsResult, error) | ||
} | ||
|
||
type GetMigrationResult struct { | ||
IsApplied bool | ||
Timestamp time.Time | ||
} | ||
|
||
type ListMigrationsResult struct { | ||
Version int64 | ||
IsApplied bool | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package sqladapter | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
|
||
"github.com/pressly/goose/v3/internal/dialect/dialectquery" | ||
"github.com/pressly/goose/v3/internal/sqlextended" | ||
) | ||
|
||
var _ Store = (*store)(nil) | ||
|
||
type store struct { | ||
tablename string | ||
querier dialectquery.Querier | ||
} | ||
|
||
// NewStore returns a new [Store] backed by the given dialect. | ||
// | ||
// The dialect must match one of the supported dialects defined in dialect.go. | ||
func NewStore(dialect string, table string) (Store, error) { | ||
if table == "" { | ||
return nil, errors.New("table must not be empty") | ||
} | ||
if dialect == "" { | ||
return nil, errors.New("dialect must not be empty") | ||
} | ||
var querier dialectquery.Querier | ||
switch dialect { | ||
case "clickhouse": | ||
querier = &dialectquery.Clickhouse{} | ||
case "mssql": | ||
querier = &dialectquery.Sqlserver{} | ||
case "mysql": | ||
querier = &dialectquery.Mysql{} | ||
case "postgres": | ||
querier = &dialectquery.Postgres{} | ||
case "redshift": | ||
querier = &dialectquery.Redshift{} | ||
case "sqlite3": | ||
querier = &dialectquery.Sqlite3{} | ||
case "tidb": | ||
querier = &dialectquery.Tidb{} | ||
case "vertica": | ||
querier = &dialectquery.Vertica{} | ||
default: | ||
return nil, fmt.Errorf("unknown dialect: %q", dialect) | ||
} | ||
return &store{ | ||
tablename: table, | ||
querier: querier, | ||
}, nil | ||
} | ||
|
||
func (s *store) CreateVersionTable(ctx context.Context, db sqlextended.DBTxConn) error { | ||
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 nil | ||
} | ||
|
||
func (s *store) InsertOrDelete(ctx context.Context, db sqlextended.DBTxConn, direction bool, version int64) error { | ||
if direction { | ||
q := s.querier.InsertVersion(s.tablename) | ||
if _, err := db.ExecContext(ctx, q, version, true); err != nil { | ||
return fmt.Errorf("failed to insert version %d: %w", version, err) | ||
} | ||
return nil | ||
} | ||
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) | ||
} | ||
return nil | ||
} | ||
|
||
func (s *store) GetMigration(ctx context.Context, db sqlextended.DBTxConn, version int64) (*GetMigrationResult, error) { | ||
q := s.querier.GetMigrationByVersion(s.tablename) | ||
var result GetMigrationResult | ||
if err := db.QueryRowContext(ctx, q, version).Scan( | ||
&result.Timestamp, | ||
&result.IsApplied, | ||
); err != nil { | ||
return nil, fmt.Errorf("failed to get migration %d: %w", version, err) | ||
} | ||
return &result, nil | ||
} | ||
|
||
func (s *store) ListMigrations(ctx context.Context, db sqlextended.DBTxConn) ([]*ListMigrationsResult, error) { | ||
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) | ||
} | ||
defer rows.Close() | ||
|
||
var migrations []*ListMigrationsResult | ||
for rows.Next() { | ||
var result ListMigrationsResult | ||
if err := rows.Scan(&result.Version, &result.IsApplied); err != nil { | ||
return nil, fmt.Errorf("failed to scan list migrations result: %w", err) | ||
} | ||
migrations = append(migrations, &result) | ||
} | ||
if err := rows.Err(); err != nil { | ||
return nil, err | ||
} | ||
return migrations, nil | ||
} |
Oops, something went wrong.