Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions internal/accounts/backup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
25 changes: 20 additions & 5 deletions internal/accounts/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
)

type sqliteMigration struct {
filename string
sql string
filename string
sql string
legacyChecksums []string
}

var sqliteMigrations = []sqliteMigration{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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()
Expand All @@ -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[:])
Expand Down
Loading