Files
goclaw/internal/upgrade/checker.go
T

94 lines
2.4 KiB
Go

package upgrade
import (
"database/sql"
"errors"
"fmt"
)
// SchemaStatus represents the result of a schema compatibility check.
type SchemaStatus struct {
CurrentVersion uint
RequiredVersion uint
Dirty bool
Compatible bool
NeedsMigration bool
}
var (
ErrSchemaOutdated = errors.New("database schema is outdated")
ErrSchemaDirty = errors.New("database schema is dirty (failed migration)")
ErrSchemaAhead = errors.New("database schema is newer than this binary")
)
// CheckSchema queries the schema_migrations table and compares
// against RequiredSchemaVersion to determine compatibility.
func CheckSchema(db *sql.DB) (*SchemaStatus, error) {
var version uint
var dirty bool
err := db.QueryRow("SELECT version, dirty FROM schema_migrations LIMIT 1").Scan(&version, &dirty)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return &SchemaStatus{
RequiredVersion: RequiredSchemaVersion,
NeedsMigration: true,
}, nil
}
// Table might not exist (fresh DB).
return &SchemaStatus{
RequiredVersion: RequiredSchemaVersion,
NeedsMigration: true,
}, nil
}
s := &SchemaStatus{
CurrentVersion: version,
RequiredVersion: RequiredSchemaVersion,
Dirty: dirty,
}
if dirty {
return s, nil
}
switch {
case version == RequiredSchemaVersion:
s.Compatible = true
case version < RequiredSchemaVersion:
s.NeedsMigration = true
default:
// Schema is ahead — binary is too old.
}
return s, nil
}
// FormatError returns a user-friendly error message for the given status.
func FormatError(s *SchemaStatus) string {
if s.Dirty {
return fmt.Sprintf(
"Database schema is in a dirty state (version %d).\n"+
"This usually means a migration failed partway.\n\n"+
" Fix: ./goclaw migrate force %d\n"+
" Then: ./goclaw upgrade\n",
s.CurrentVersion, s.CurrentVersion-1,
)
}
if s.CurrentVersion > s.RequiredVersion {
return fmt.Sprintf(
"Database schema (v%d) is newer than this binary (requires v%d).\n"+
"You may be running an older version of goclaw.\n\n"+
" Fix: upgrade your goclaw binary to the latest version.\n",
s.CurrentVersion, s.RequiredVersion,
)
}
return fmt.Sprintf(
"Database schema is outdated: current v%d, required v%d.\n\n"+
" Run: ./goclaw upgrade\n"+
" Or: ./goclaw migrate up (SQL-only, no data hooks)\n\n"+
" Docker/CI: set GOCLAW_AUTO_UPGRADE=true to upgrade automatically on startup.\n",
s.CurrentVersion, s.RequiredVersion,
)
}