diff --git a/server/internal/store/db.go b/server/internal/store/db.go index b7c8922..23776fa 100644 --- a/server/internal/store/db.go +++ b/server/internal/store/db.go @@ -1,6 +1,7 @@ package store import ( + "fmt" "log" "os" "path/filepath" @@ -11,6 +12,12 @@ import ( gormlogger "gorm.io/gorm/logger" ) +// schemaVersion 当前 schema 版本。struct 变更(加列/改列/删列)时递增, +// 触发一次 AutoMigrate 并把新版本写入库(SQLite 用 PRAGMA user_version)。 +// AutoMigrate 对已有表的列判定不收敛(每次都重建表:CREATE __temp + INSERT SELECT + DROP), +// 大表上一次重建数十秒且每次重启重演,所以之后版本未变就直接跳过。 +const schemaVersion = 1 + // Open 打开数据库连接并自动迁移。 // 开发默认 SQLite(dsn 支持 file:...?_journal_mode=WAL),生产可切 postgres。 func Open(driver, dsn string) (*gorm.DB, error) { @@ -33,13 +40,33 @@ func Open(driver, dsn string) (*gorm.DB, error) { return nil, err } + if driver != "postgres" && currentSQLiteVersion(db) >= schemaVersion { + log.Printf("store: connected driver=%s (schema up-to-date v%d, skip migrate)", driver, schemaVersion) + return db, nil + } + if err := db.AutoMigrate(AllModels()...); err != nil { return nil, err } - log.Printf("store: connected driver=%s (migrated)", driver) + if driver != "postgres" { + setSQLiteVersion(db, schemaVersion) + } + log.Printf("store: connected driver=%s (migrated, schema v%d)", driver, schemaVersion) return db, nil } +// currentSQLiteVersion 读取 PRAGMA user_version。 +func currentSQLiteVersion(db *gorm.DB) int { + var v int + db.Raw("PRAGMA user_version").Scan(&v) + return v +} + +// setSQLiteVersion 写入 PRAGMA user_version。 +func setSQLiteVersion(db *gorm.DB, v int) { + db.Exec(fmt.Sprintf("PRAGMA user_version = %d", v)) +} + // sqliteDir 提取 SQLite DSN 中的目录部分(忽略 file: 前缀与查询参数)。 func sqliteDir(dsn string) string { d := dsn