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 }