diff --git a/CHANGELOG.md b/CHANGELOG.md index 53294148..c83d437f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,12 @@ Write each change in both `### English` and `### 中文` under `## Unreleased`. ### English +- Keep the v0.2.18 bytes for request-log provider migration 006, and accept the v0.2.19 tab-indented checksum so existing databases can boot + ### 中文 +- 006 请求日志供应商迁移保持 v0.2.18 原文,并接受 v0.2.19 误改缩进后的 checksum,已有数据库可以启动 + ## 0.2.19 - 2026-08-29 ### English diff --git a/internal/accounts/backup_test.go b/internal/accounts/backup_test.go index 8c7e0c84..ddc69dea 100644 --- a/internal/accounts/backup_test.go +++ b/internal/accounts/backup_test.go @@ -116,3 +116,55 @@ func TestStoreRejectsChangedAppliedMigration(t *testing.T) { t.Fatal("expected migration checksum mismatch") } } + +const ( + requestLogProviderMigration = "006_request_log_provider.sql" + requestLogProviderChecksum = "2deb3ef3aa94df34a8ffd0ac50a69b6cb710e7bf2e9041fc1c1f0bdfd7cb0d67" + requestLogProviderV0219 = "9b96b8d63286519b20791d2a3688c0b71efba6204015250ae9864c5cbcd1f0b4" +) + +func TestRequestLogProviderMigrationKeepsV0218Bytes(t *testing.T) { + var migration sqliteMigration + for _, item := range sqliteMigrations { + if item.filename == requestLogProviderMigration { + migration = item + break + } + } + if migration.filename == "" { + t.Fatal("missing 006_request_log_provider.sql") + } + got := migrationChecksum(migration.sql) + if got != requestLogProviderChecksum { + t.Fatalf("006 checksum = %s, want v0.2.18 %s", got, requestLogProviderChecksum) + } + if !checksumAccepted(migration, requestLogProviderV0219) { + t.Fatal("expected v0.2.19 tab-indented checksum to remain accepted") + } +} + +func TestStoreOpensDatabaseWithV0219RequestLogProviderChecksum(t *testing.T) { + dbPath := filepath.Join(t.TempDir(), "qoder.db") + store, err := OpenStore(dbPath) + if err != nil { + t.Fatal(err) + } + var recorded string + if err := store.db.QueryRow("SELECT checksum FROM schema_migrations WHERE filename = ?", requestLogProviderMigration).Scan(&recorded); err != nil { + t.Fatal(err) + } + if recorded != requestLogProviderChecksum { + t.Fatalf("fresh 006 checksum = %s, want %s", recorded, requestLogProviderChecksum) + } + if _, err := store.db.Exec("UPDATE schema_migrations SET checksum = ? WHERE filename = ?", requestLogProviderV0219, requestLogProviderMigration); err != nil { + t.Fatal(err) + } + if err := store.Close(); err != nil { + t.Fatal(err) + } + reopened, err := OpenStore(dbPath) + if err != nil { + t.Fatal(err) + } + reopened.Close() +} diff --git a/internal/accounts/migrations.go b/internal/accounts/migrations.go index 6104378c..db3c1099 100644 --- a/internal/accounts/migrations.go +++ b/internal/accounts/migrations.go @@ -10,8 +10,9 @@ import ( ) type sqliteMigration struct { - filename string - sql string + filename string + sql string + legacyChecksums []string } var sqliteMigrations = []sqliteMigration{ @@ -123,8 +124,10 @@ CREATE INDEX IF NOT EXISTS request_attempts_request_id ON request_attempts(reque {filename: "005_account_drop_system_prompt.sql", sql: ` ALTER TABLE accounts ADD COLUMN drop_system_prompt INTEGER NOT NULL DEFAULT 1;`}, {filename: "006_request_log_provider.sql", sql: ` - ALTER TABLE request_logs ADD COLUMN provider TEXT NOT NULL DEFAULT ''; - CREATE INDEX IF NOT EXISTS request_logs_provider ON request_logs(provider);`}, +ALTER TABLE request_logs ADD COLUMN provider TEXT NOT NULL DEFAULT ''; +CREATE INDEX IF NOT EXISTS request_logs_provider ON request_logs(provider);`, + // v0.2.19 retabbed this SQL; keep that checksum bootable. + legacyChecksums: []string{"9b96b8d63286519b20791d2a3688c0b71efba6204015250ae9864c5cbcd1f0b4"}}, {filename: "007_provider_model_settings.sql", sql: ` CREATE TABLE IF NOT EXISTS provider_model_settings ( provider TEXT NOT NULL, @@ -166,7 +169,7 @@ func (s *Store) applyMigration(ctx context.Context, migration sqliteMigration) e err = tx.QueryRowContext(ctx, "SELECT checksum FROM schema_migrations WHERE filename = ?", migration.filename).Scan(&appliedChecksum) switch { case err == nil: - if appliedChecksum != checksum { + if !checksumAccepted(migration, appliedChecksum) { return fmt.Errorf("migration %s checksum mismatch", migration.filename) } return tx.Commit() @@ -186,6 +189,18 @@ func (s *Store) applyMigration(ctx context.Context, migration sqliteMigration) e return nil } +func checksumAccepted(migration sqliteMigration, appliedChecksum string) bool { + if appliedChecksum == migrationChecksum(migration.sql) { + return true + } + for _, legacy := range migration.legacyChecksums { + if appliedChecksum != "" && appliedChecksum == legacy { + return true + } + } + return false +} + func migrationChecksum(sqlText string) string { sum := sha256.Sum256([]byte(sqlText)) return hex.EncodeToString(sum[:])