fix: 修复写队列 busy 恢复、关闭安全、备份与配置构建问题
This commit is contained in:
@@ -18,9 +18,22 @@ var migrationFS embed.FS
|
||||
|
||||
// Migrate 应用尚未执行的嵌入迁移。
|
||||
// 若 dbExisted 为 true(调用 Open/Migrate 前已有 nixmsg.db)且存在未应用版本,
|
||||
// 先 VACUUM INTO <data_dir>/backup/pre-migrate-<时间>.db,再迁移。
|
||||
// 先 VACUUM INTO <data_dir>/backup/pre-migrate-v{当前}-to-v{目标}.db,再迁移。
|
||||
// 备份文件已存在则复用,避免迁移稳定失败时反复全量复制。
|
||||
// 任一版本失败则返回错误,调用方不得继续带半新半旧库提供服务。
|
||||
func Migrate(db *sql.DB, dataDir string, dbExisted bool) error {
|
||||
if err := ensureMigrationsTable(db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pending, err := pendingMigrations(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return applyPending(db, dataDir, dbExisted, pending)
|
||||
}
|
||||
|
||||
func ensureMigrationsTable(db *sql.DB) error {
|
||||
if _, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
@@ -28,17 +41,21 @@ CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
)`); err != nil {
|
||||
return fmt.Errorf("ensure schema_migrations: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
pending, err := pendingMigrations(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func applyPending(db *sql.DB, dataDir string, dbExisted bool, pending []migrationFile) error {
|
||||
if len(pending) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if dbExisted {
|
||||
if err := backupBeforeMigrate(db, dataDir); err != nil {
|
||||
from, err := currentSchemaVersion(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
to := pending[len(pending)-1].version
|
||||
if err := backupBeforeMigrate(db, dataDir, from, to); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -98,13 +115,33 @@ func pendingMigrations(db *sql.DB) ([]migrationFile, error) {
|
||||
return pending, nil
|
||||
}
|
||||
|
||||
func backupBeforeMigrate(db *sql.DB, dataDir string) error {
|
||||
func currentSchemaVersion(db *sql.DB) (int, error) {
|
||||
var v sql.NullInt64
|
||||
if err := db.QueryRow(`SELECT MAX(version) FROM schema_migrations`).Scan(&v); err != nil {
|
||||
return 0, fmt.Errorf("current schema version: %w", err)
|
||||
}
|
||||
if !v.Valid {
|
||||
return 0, nil
|
||||
}
|
||||
return int(v.Int64), nil
|
||||
}
|
||||
|
||||
func backupBeforeMigrate(db *sql.DB, dataDir string, fromVer, toVer int) error {
|
||||
backupDir := filepath.Join(dataDir, "backup")
|
||||
if err := os.MkdirAll(backupDir, 0o755); err != nil {
|
||||
return fmt.Errorf("mkdir backup: %w", err)
|
||||
}
|
||||
stamp := time.Now().UTC().Format("20060102T150405")
|
||||
backupPath := filepath.Join(backupDir, "pre-migrate-"+stamp+".db")
|
||||
backupPath := filepath.Join(backupDir, fmt.Sprintf("pre-migrate-v%d-to-v%d.db", fromVer, toVer))
|
||||
exists, err := fileExists(backupPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return nil
|
||||
}
|
||||
if err := ensureDiskSpace(backupDir, backupNeedBytes(dataDir)); err != nil {
|
||||
return err
|
||||
}
|
||||
// SQLite VACUUM INTO 需要字面量路径;统一用斜杠,并对单引号转义。
|
||||
quoted := strings.ReplaceAll(filepath.ToSlash(backupPath), "'", "''")
|
||||
if _, err := db.Exec("VACUUM INTO '" + quoted + "'"); err != nil {
|
||||
@@ -113,6 +150,34 @@ func backupBeforeMigrate(db *sql.DB, dataDir string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func backupNeedBytes(dataDir string) int64 {
|
||||
var n int64
|
||||
for _, name := range []string{DBFileName, DBFileName + "-wal", DBFileName + "-shm"} {
|
||||
st, err := os.Stat(filepath.Join(dataDir, name))
|
||||
if err == nil {
|
||||
n += st.Size()
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func ensureDiskSpace(dir string, need int64) error {
|
||||
if need < 0 {
|
||||
need = 0
|
||||
}
|
||||
avail, err := availableBytes(dir)
|
||||
if err != nil {
|
||||
// 探测失败不阻断迁移,VACUUM INTO 自身会因空间不足报错。
|
||||
return nil
|
||||
}
|
||||
const margin = 1 << 20
|
||||
want := uint64(need) + margin
|
||||
if avail < want {
|
||||
return fmt.Errorf("insufficient disk space for migrate backup: have %d bytes, need ~%d", avail, want)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyMigration(db *sql.DB, m migrationFile) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user