package db import "context" // addColumn describes one ALTER TABLE ADD COLUMN migration step. type addColumn struct { table string column string ddl string } // schema.sql is the source of truth and runs first, so every table already // exists here. Column-level changes cannot be expressed with IF NOT EXISTS, // so they are applied explicitly and guarded by a pragma check. var columnMigrations = []addColumn{ {"users", "is_pending", "ALTER TABLE users ADD COLUMN is_pending INTEGER NOT NULL DEFAULT 0"}, {"users", "register_application", "ALTER TABLE users ADD COLUMN register_application TEXT"}, {"users", "passkey_setup_started_at", "ALTER TABLE users ADD COLUMN passkey_setup_started_at TEXT"}, {"patches", "version", "ALTER TABLE patches ADD COLUMN version TEXT NOT NULL DEFAULT ''"}, {"repositories", "allow_user_labels", "ALTER TABLE repositories ADD COLUMN allow_user_labels INTEGER NOT NULL DEFAULT 0"}, {"ci_runs", "repo_run_id", "ALTER TABLE ci_runs ADD COLUMN repo_run_id INTEGER"}, } // migrate brings an existing database file up to date. func (d *DB) migrate(ctx context.Context) error { for _, m := range columnMigrations { has, err := d.hasColumn(ctx, m.table, m.column) if err != nil { return err } if has { continue } if _, err := d.ExecContext(ctx, m.ddl); err != nil { return err } } // Backfill patch versions left empty by the column migration above. if _, err := d.ExecContext(ctx, "UPDATE patches SET version = lower(hex(randomblob(16))) WHERE version = ''"); err != nil { return err } // Expired sessions serve no purpose after a restart. return d.DeleteExpiredSessions(ctx) } func (d *DB) hasColumn(ctx context.Context, table, column string) (bool, error) { var n int err := d.QueryRowContext(ctx, `SELECT COUNT(*) FROM pragma_table_info(?) WHERE name = ?`, table, column).Scan(&n) return n > 0, err }