restructure project, add claudemd
This commit is contained in:
260
go/cmd/migrate/engine.go
Normal file
260
go/cmd/migrate/engine.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoChange = errors.New("no change")
|
||||
)
|
||||
|
||||
type engine struct {
|
||||
source *migrationSource
|
||||
db *migrateDB
|
||||
}
|
||||
|
||||
func newEngine(migrationsDir string, connStr string, schemaName string) (*engine, error) {
|
||||
source, err := newMigrationSource(migrationsDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := openDatabase(connStr, schemaName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &engine{source: source, db: db}, nil
|
||||
}
|
||||
|
||||
func (e *engine) close() error {
|
||||
return e.db.close()
|
||||
}
|
||||
|
||||
func (e *engine) up() error {
|
||||
if err := e.db.lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer e.db.unlock()
|
||||
|
||||
curVersion, dirty, err := e.db.version()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dirty {
|
||||
return fmt.Errorf("Dirty database version %d. Fix and force version.", curVersion)
|
||||
}
|
||||
|
||||
// determine where to start
|
||||
var version uint
|
||||
if curVersion == nilVersion {
|
||||
first, err := e.source.first()
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return errNoChange
|
||||
}
|
||||
return err
|
||||
}
|
||||
version = first
|
||||
} else {
|
||||
next, err := e.source.next(uint(curVersion))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return errNoChange
|
||||
}
|
||||
return err
|
||||
}
|
||||
version = next
|
||||
}
|
||||
|
||||
// apply from starting version forward
|
||||
for {
|
||||
if err := e.applyUp(version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
next, err := e.source.next(version)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil // done, applied all
|
||||
}
|
||||
return err
|
||||
}
|
||||
version = next
|
||||
}
|
||||
}
|
||||
|
||||
func (e *engine) down() error {
|
||||
if err := e.db.lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer e.db.unlock()
|
||||
|
||||
curVersion, dirty, err := e.db.version()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dirty {
|
||||
return fmt.Errorf("Dirty database version %d. Fix and force version.", curVersion)
|
||||
}
|
||||
if curVersion == nilVersion {
|
||||
return errNoChange
|
||||
}
|
||||
|
||||
// determine target version after rollback
|
||||
targetVersion := nilVersion
|
||||
prev, err := e.source.prev(uint(curVersion))
|
||||
if err != nil {
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
// at the first migration, target is nilVersion (-1)
|
||||
} else {
|
||||
targetVersion = int(prev)
|
||||
}
|
||||
|
||||
return e.applyDown(uint(curVersion), targetVersion)
|
||||
}
|
||||
|
||||
func (e *engine) goTo(target uint) error {
|
||||
if err := e.db.lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer e.db.unlock()
|
||||
|
||||
curVersion, dirty, err := e.db.version()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dirty {
|
||||
return fmt.Errorf("Dirty database version %d. Fix and force version.", curVersion)
|
||||
}
|
||||
|
||||
if !e.source.versionExists(target) {
|
||||
return fmt.Errorf("version %d not found in migration source", target)
|
||||
}
|
||||
|
||||
intTarget := int(target)
|
||||
if intTarget == curVersion {
|
||||
return errNoChange
|
||||
}
|
||||
|
||||
if intTarget > curVersion {
|
||||
// going up
|
||||
var version uint
|
||||
if curVersion == nilVersion {
|
||||
first, err := e.source.first()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version = first
|
||||
} else {
|
||||
next, err := e.source.next(uint(curVersion))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version = next
|
||||
}
|
||||
|
||||
for {
|
||||
if err := e.applyUp(version); err != nil {
|
||||
return err
|
||||
}
|
||||
if version == target {
|
||||
return nil
|
||||
}
|
||||
next, err := e.source.next(version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
version = next
|
||||
}
|
||||
}
|
||||
|
||||
// going down
|
||||
version := uint(curVersion)
|
||||
for int(version) > intTarget {
|
||||
prev, err := e.source.prev(version)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// we're at the first version, can't go lower
|
||||
return fmt.Errorf("cannot migrate down from version %d to %d", version, target)
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := e.applyDown(version, int(prev)); err != nil {
|
||||
return err
|
||||
}
|
||||
version = prev
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *engine) force(version int) error {
|
||||
if err := e.db.lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer e.db.unlock()
|
||||
|
||||
return e.db.setVersion(version, false)
|
||||
}
|
||||
|
||||
func (e *engine) dropAll() error {
|
||||
if err := e.db.lock(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer e.db.unlock()
|
||||
|
||||
return e.db.drop()
|
||||
}
|
||||
|
||||
// applyUp applies a single up migration for the given version.
|
||||
func (e *engine) applyUp(version uint) error {
|
||||
// mark dirty before execution
|
||||
if err := e.db.setVersion(int(version), true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath, err := e.source.readUp(version)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// no up file for this version, just set clean
|
||||
return e.db.setVersion(int(version), false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.db.run(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// mark clean after successful execution
|
||||
return e.db.setVersion(int(version), false)
|
||||
}
|
||||
|
||||
// applyDown applies a single down migration from fromVersion, setting targetVersion afterward.
|
||||
func (e *engine) applyDown(fromVersion uint, targetVersion int) error {
|
||||
// mark dirty with target version before execution
|
||||
if err := e.db.setVersion(targetVersion, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
filePath, err := e.source.readDown(fromVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// no down file for this version, just set clean
|
||||
return e.db.setVersion(targetVersion, false)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.db.run(filePath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// mark clean after successful execution
|
||||
return e.db.setVersion(targetVersion, false)
|
||||
}
|
||||
Reference in New Issue
Block a user