package store import ( "database/sql" "embed" "fmt" "io/fs" "sort" "strconv" "strings" "time" ) //go:embed migrations/*.sql var migrationFS embed.FS // Migrate 应用尚未执行的迁移。T0.1 仅为空执行器加 0001 占位;完整备份与表结构见 T0.3。 func Migrate(db *sql.DB) error { if _, err := db.Exec(` CREATE TABLE IF NOT EXISTS schema_migrations ( version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL )`); err != nil { return fmt.Errorf("ensure schema_migrations: %w", err) } entries, err := fs.ReadDir(migrationFS, "migrations") if err != nil { return fmt.Errorf("read migrations: %w", err) } var names []string for _, e := range entries { if e.IsDir() || !strings.HasSuffix(e.Name(), ".sql") { continue } names = append(names, e.Name()) } sort.Strings(names) for _, name := range names { version, err := parseMigrationVersion(name) if err != nil { return err } var exists int err = db.QueryRow(`SELECT 1 FROM schema_migrations WHERE version = ?`, version).Scan(&exists) if err == nil { continue } if err != sql.ErrNoRows { return fmt.Errorf("check migration %d: %w", version, err) } body, err := migrationFS.ReadFile("migrations/" + name) if err != nil { return fmt.Errorf("read migration %s: %w", name, err) } sqlText := strings.TrimSpace(string(body)) tx, err := db.Begin() if err != nil { return fmt.Errorf("begin migration %d: %w", version, err) } if sqlText != "" { if _, err := tx.Exec(sqlText); err != nil { _ = tx.Rollback() return fmt.Errorf("apply migration %d: %w", version, err) } } if _, err := tx.Exec( `INSERT INTO schema_migrations(version, applied_at) VALUES(?, ?)`, version, time.Now().UnixMilli(), ); err != nil { _ = tx.Rollback() return fmt.Errorf("record migration %d: %w", version, err) } if err := tx.Commit(); err != nil { return fmt.Errorf("commit migration %d: %w", version, err) } } return nil } func parseMigrationVersion(name string) (int, error) { base := strings.TrimSuffix(name, ".sql") parts := strings.SplitN(base, "_", 2) if len(parts) < 1 { return 0, fmt.Errorf("invalid migration name: %s", name) } v, err := strconv.Atoi(parts[0]) if err != nil { return 0, fmt.Errorf("invalid migration version in %s: %w", name, err) } return v, nil }