Introduce the StoneSQL migration engine with embedded SQL migration support, scientific error tracking, and extensive documentation in the README. Include implementation of core functionality, error handling, and unit tests to validate success and failure scenarios.
107 lines
2.8 KiB
Go
107 lines
2.8 KiB
Go
package stonesql
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"io/fs"
|
|
"path/filepath"
|
|
|
|
"gitstormr.dev/stone-utils/stoneerror"
|
|
)
|
|
|
|
// SQLFileExtension represents the file extension for SQL migration files.
|
|
const (
|
|
SQLFileExtension = ".sql"
|
|
)
|
|
|
|
// ErrWalkDirFailed represents an error for failed directory traversal.
|
|
// ErrReadMigrationFailed represents an error for reading migration files.
|
|
// ErrExecuteMigrationFailed represents an error during migration execution.
|
|
// ErrMigrationProcessFailed represents a general migration process failure.
|
|
var (
|
|
ErrWalkDirFailed = stoneerror.New(
|
|
2001,
|
|
"failed to walk migration directory",
|
|
)
|
|
ErrReadMigrationFailed = stoneerror.New(
|
|
2002,
|
|
"failed to read migration file",
|
|
)
|
|
ErrExecuteMigrationFailed = stoneerror.New(
|
|
2003,
|
|
"failed to execute migration",
|
|
)
|
|
ErrMigrationProcessFailed = stoneerror.New(
|
|
2004,
|
|
"migration process failed",
|
|
)
|
|
)
|
|
|
|
// Migrator defines the interface for executing SQL migrations.
|
|
// ExecuteMigration runs a migration given its name and SQL content.
|
|
type Migrator interface {
|
|
ExecuteMigration(ctx context.Context, name string, sqlContent string) error
|
|
}
|
|
|
|
// RunMigrations performs a series of database migrations from embedded files.
|
|
// It traverses the provided filesystem path, reads SQL files, and executes them.
|
|
// If an error occurs during traversal, reading, or execution, it wraps and returns it.
|
|
// Context to be used for execution is passed via the ctx parameter.
|
|
// The migrator parameter executes individual SQL content for migrations.
|
|
// The migrationsFS parameter provides an embedded filesystem for the SQL files.
|
|
// The path parameter specifies the directory within the embedded FS to search in.
|
|
// Returns an error if any step in the migration process fails.
|
|
func RunMigrations(
|
|
ctx context.Context,
|
|
migrator Migrator,
|
|
migrationsFS embed.FS,
|
|
path string,
|
|
) error {
|
|
walkErr := fs.WalkDir(
|
|
migrationsFS,
|
|
path,
|
|
func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return stoneerror.Wrap(
|
|
err, ErrWalkDirFailed.Code,
|
|
ErrWalkDirFailed.Message,
|
|
).
|
|
WithMetadata("path", path)
|
|
}
|
|
|
|
if !d.IsDir() && filepath.Ext(path) == SQLFileExtension {
|
|
sqlContent, readErr := migrationsFS.ReadFile(path)
|
|
if readErr != nil {
|
|
return stoneerror.Wrap(
|
|
readErr, ErrReadMigrationFailed.Code,
|
|
ErrReadMigrationFailed.Message,
|
|
).
|
|
WithMetadata("file", path)
|
|
}
|
|
|
|
if err = migrator.ExecuteMigration(
|
|
ctx, path, string(sqlContent),
|
|
); err != nil {
|
|
return stoneerror.Wrap(
|
|
err, ErrExecuteMigrationFailed.Code,
|
|
ErrExecuteMigrationFailed.Message,
|
|
).
|
|
WithMetadata("migration", path)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
},
|
|
)
|
|
|
|
if walkErr != nil {
|
|
return stoneerror.Wrap(
|
|
walkErr, ErrMigrationProcessFailed.Code,
|
|
ErrMigrationProcessFailed.Message,
|
|
).
|
|
WithMetadata("rootPath", path)
|
|
}
|
|
|
|
return nil
|
|
}
|