package oltp
// once.go runs a data migration exactly one time per database.
//
// The additive schema changes elsewhere in this package are idempotent
// by construction (CREATE IF NOT EXISTS, ALTER guarded on "duplicate
// column"), and carryPraise is idempotent because it drops the table it
// reads. A migration that REWRITES rows is neither: run twice, it would
// undo whatever an admin changed in between. So each one is named, and
// its name is recorded the first time it runs.
const onceSchema = `
CREATE TABLE IF NOT EXISTS applied_migration (
name TEXT PRIMARY KEY,
at TEXT NOT NULL
);`
// runOnce runs fn under name unless a previous open already did.
func (d *DB) runOnce(name string, fn func() error) error {
if _, err := d.db.Exec(onceSchema); err != nil {
return err
}
var one int
if err := d.db.QueryRow(`SELECT 1 FROM applied_migration WHERE name = ?`, name).Scan(&one); err == nil {
return nil
}
if err := fn(); err != nil {
return err
}
_, err := d.db.Exec(`INSERT INTO applied_migration (name, at) VALUES (?, ?)`, name, now())
return err
}
// mirrorChatOff is the one-time counterpart of SetMirror's default:
// every mirror registered before mirrors started with chat off has chat
// on, because the column defaults to 1 and nothing ever asked. A
// bridged mirror keeps it — the bridge is the syndication an admin set
// up, and it is what the channel is for.
func (d *DB) mirrorChatOff() error {
return d.runOnce("mirror-chat-off", func() error {
_, err := d.db.Exec(`UPDATE repo SET chat = 0
WHERE name IN (SELECT repo FROM mirror WHERE enabled = 1)
AND name NOT IN (SELECT repo FROM mirror_chatbridge)`)
return err
})
}