diff --git a/internal/storage/dolt/schema.go b/internal/storage/dolt/schema.go index 14a680bb8..663d7f44f 100644 --- a/internal/storage/dolt/schema.go +++ b/internal/storage/dolt/schema.go @@ -3,7 +3,7 @@ package dolt // currentSchemaVersion is bumped whenever the schema or migrations change. // initSchemaOnDB checks this against the stored version and skips re-initialization // when they match, avoiding ~20 DDL statements per bd invocation. -const currentSchemaVersion = 7 +const currentSchemaVersion = 8 // schema defines the MySQL-compatible database schema for Dolt. const schema = ` diff --git a/internal/storage/dolt/schema_version_test.go b/internal/storage/dolt/schema_version_test.go index aa3c4f71b..62cd75fe4 100644 --- a/internal/storage/dolt/schema_version_test.go +++ b/internal/storage/dolt/schema_version_test.go @@ -24,6 +24,15 @@ func TestSchemaVersionSetAfterInit(t *testing.T) { if version != currentSchemaVersion { t.Errorf("schema_version = %d, want %d", version, currentSchemaVersion) } + + var stagedRows int + err = store.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dolt_status WHERE table_name = 'config'").Scan(&stagedRows) + if err != nil { + t.Fatalf("query dolt_status for config: %v", err) + } + if stagedRows != 0 { + t.Fatalf("config left staged in dolt_status after init: %d row(s)", stagedRows) + } } // TestSchemaVersionSkipsReinit verifies that initSchemaOnDB returns early @@ -105,6 +114,53 @@ func TestSchemaVersionRunsInitWhenStale(t *testing.T) { } } +// TestSchemaVersionRunsLatestMigrationsWhenOneVersionBehind verifies that a +// database marked one schema version behind re-enters initSchemaOnDB and picks +// up the latest migration-backed columns. +func TestSchemaVersionRunsLatestMigrationsWhenOneVersionBehind(t *testing.T) { + store, cleanup := setupTestStore(t) + defer cleanup() + + ctx, cancel := testContext(t) + defer cancel() + + for _, stmt := range []string{ + "ALTER TABLE issues DROP COLUMN no_history", + "ALTER TABLE wisps DROP COLUMN no_history", + } { + if _, err := store.db.ExecContext(ctx, stmt); err != nil { + t.Fatalf("exec %q: %v", stmt, err) + } + } + + _, err := store.db.ExecContext(ctx, + "UPDATE config SET `value` = ? WHERE `key` = 'schema_version'", + currentSchemaVersion-1, + ) + if err != nil { + t.Fatalf("failed to set prior schema_version: %v", err) + } + + if err := initSchemaOnDB(ctx, store.db); err != nil { + t.Fatalf("initSchemaOnDB failed: %v", err) + } + + for _, table := range []string{"issues", "wisps"} { + var count int + err := store.db.QueryRowContext( + ctx, + "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = ? AND column_name = 'no_history'", + table, + ).Scan(&count) + if err != nil { + t.Fatalf("check %s.no_history: %v", table, err) + } + if count != 1 { + t.Fatalf("%s.no_history missing after initSchemaOnDB", table) + } + } +} + // TestSchemaVersionRunsInitWhenMissing verifies that initSchemaOnDB runs // full initialization when the schema_version key doesn't exist (fresh db // or pre-versioning upgrade). diff --git a/internal/storage/dolt/store.go b/internal/storage/dolt/store.go index 6f9e4bb76..e3d8d1ce9 100644 --- a/internal/storage/dolt/store.go +++ b/internal/storage/dolt/store.go @@ -1121,6 +1121,12 @@ func initSchemaOnDB(ctx context.Context, db *sql.DB) error { "INSERT INTO config (`key`, `value`) VALUES ('schema_version', ?) "+ "ON DUPLICATE KEY UPDATE `value` = ?", currentSchemaVersion, currentSchemaVersion) + _, _ = db.ExecContext(ctx, "CALL DOLT_ADD('config')") + if _, err := db.ExecContext(ctx, "CALL DOLT_COMMIT('-m', 'schema: update schema_version')"); err != nil { + if !strings.Contains(strings.ToLower(err.Error()), "nothing to commit") { + return fmt.Errorf("failed to commit schema_version update: %w", err) + } + } return nil }