Files
orca/internal/store/migrate.go
T
Jon Chery fa35bfc106 ship(P02): doctor network + db merged into v0.3 milestone
---ci---
project: orca
phase: 2
milestone: v0.3
status: complete
requirements:
  covered: [REQ-032]
  partial: []
---/ci---

P02: orca doctor network + db full implementation.
- DB(): PRAGMA integrity_check + MigrationVersion (PASS/WARN/FAIL)
- Network(): mTLS /healthz probe per peer, 3s timeout, zero peers → WARN
- certpaths.DBPath() relocation (D-039, breaks import cycle)
- store.MigrationVersion() public API
- Deleted NetworkStub/DBStub (D-040)
- 7 doctor tests + 1 MigrationVersion test, all pass under -race
- 4-layer verification passed
2026-08-01 20:05:12 +00:00

69 lines
2.0 KiB
Go

package store
import (
"context"
"database/sql"
"embed"
"fmt"
"sort"
"strings"
)
//go:embed migrations/*.sql
var migrationsFS embed.FS
// MigrationVersion returns the name of the highest applied migration
// (e.g. "0005_node_capacity.sql"). Returns ("", nil) if no migrations
// have been applied (fresh or empty database).
func MigrationVersion(ctx context.Context, db *sql.DB) (string, error) {
var name string
err := db.QueryRowContext(ctx, `SELECT name FROM schema_migrations ORDER BY name DESC LIMIT 1`).Scan(&name)
if err == sql.ErrNoRows {
return "", nil
}
if err != nil {
return "", fmt.Errorf("query migration version: %w", err)
}
return name, nil
}
func migrate(db *sql.DB) error {
entries, err := migrationsFS.ReadDir("migrations")
if err != nil {
return fmt.Errorf("read migrations dir: %w", err)
}
names := make([]string, 0, len(entries))
for _, e := range entries {
if !e.IsDir() && strings.HasSuffix(e.Name(), ".sql") {
names = append(names, e.Name())
}
}
sort.Strings(names)
if _, err := db.ExecContext(context.Background(), `CREATE TABLE IF NOT EXISTS schema_migrations (name TEXT PRIMARY KEY, applied_at DATETIME NOT NULL)`); err != nil {
return fmt.Errorf("create schema_migrations: %w", err)
}
for _, name := range names {
var existing string
err := db.QueryRowContext(context.Background(), `SELECT name FROM schema_migrations WHERE name = ?`, name).Scan(&existing)
if err == nil {
continue
}
if err != sql.ErrNoRows {
return fmt.Errorf("check migration %s: %w", name, err)
}
sqlBytes, err := migrationsFS.ReadFile("migrations/" + name)
if err != nil {
return fmt.Errorf("read migration %s: %w", name, err)
}
if _, err := db.ExecContext(context.Background(), string(sqlBytes)); err != nil {
return fmt.Errorf("apply migration %s: %w", name, err)
}
if _, err := db.ExecContext(context.Background(), `INSERT INTO schema_migrations (name, applied_at) VALUES (?, datetime('now'))`, name); err != nil {
return fmt.Errorf("record migration %s: %w", name, err)
}
}
return nil
}