package oltp
import "time"
// Auto-accept (RFC 0009 §verdicts, the follow-through): a landing a
// decider has armed lands itself when its required checks go green.
// Forge-local OLTP like the check rules beside it — never part of the
// DAG or sync.
const autoSchema = `
CREATE TABLE IF NOT EXISTS landing_auto (
repo TEXT NOT NULL,
landing TEXT NOT NULL,
account TEXT NOT NULL, -- whose hand writes the deferred accept
PRIMARY KEY (repo, landing)
);`
// autoMigrations are the additive changes an existing file needs.
var autoMigrations = []string{
// What the last sweep ran into, so the PAGE can say what an armed
// landing is waiting on.
//
// Recorded rather than recomputed. The page could ask the same
// predicates the deferred accept asks, and would then be a second
// implementation of "would this land", free to drift from the one
// that matters. This is the sweep's own answer, which cannot.
//
// It also makes a stuck arm visible: the sweep logs nothing for the
// ordinary red-checks wait, correctly, so before this an arm that
// was waiting on something else looked exactly like one that was
// working.
`ALTER TABLE landing_auto ADD COLUMN waiting TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE landing_auto ADD COLUMN waiting_at TEXT NOT NULL DEFAULT ''`,
}
// AutoAccept is one armed landing.
type AutoAccept struct{ Repo, Landing, Account string }
// AutoState is an arm as a page reads it: whose hand, and what the last
// sweep ran into. Waiting is empty when no sweep has reported anything
// — a fresh arm, or one whose last try only found red checks.
type AutoState struct {
Account string
Waiting string
At string
}
// SetAutoAccept arms a landing; re-arming moves it to the new hand.
func (d *DB) SetAutoAccept(repo, landing, account string) error {
_, err := d.db.Exec(`INSERT INTO landing_auto (repo, landing, account) VALUES (?,?,?)
ON CONFLICT(repo, landing) DO UPDATE SET account=excluded.account`, repo, landing, account)
return err
}
// ClearAutoAccept disarms; disarming what is not armed is harmless.
func (d *DB) ClearAutoAccept(repo, landing string) error {
_, err := d.db.Exec(`DELETE FROM landing_auto WHERE repo=? AND landing=?`, repo, landing)
return err
}
// AutoAcceptOf reports who armed a landing, if anyone.
func (d *DB) AutoAcceptOf(repo, landing string) (string, bool) {
st, ok := d.AutoStateOf(repo, landing)
return st.Account, ok
}
// AutoStateOf is AutoAcceptOf plus what the last sweep ran into.
func (d *DB) AutoStateOf(repo, landing string) (AutoState, bool) {
var st AutoState
err := d.db.QueryRow(`SELECT account, waiting, waiting_at
FROM landing_auto WHERE repo=? AND landing=?`,
repo, landing).Scan(&st.Account, &st.Waiting, &st.At)
return st, err == nil
}
// SetAutoWaiting records what the last sweep ran into, or clears it
// when the try got further than last time. Never allowed to fail the
// sweep: this is a note for a reader, not part of landing.
func (d *DB) SetAutoWaiting(repo, landing, reason string) {
at := ""
if reason != "" {
at = time.Now().UTC().Format(time.RFC3339)
}
_, _ = d.db.Exec(`UPDATE landing_auto SET waiting=?, waiting_at=?
WHERE repo=? AND landing=?`, reason, at, repo, landing)
}
// AutoAccepts lists everything armed, for the sweep.
func (d *DB) AutoAccepts() ([]AutoAccept, error) {
rows, err := d.db.Query(`SELECT repo, landing, account FROM landing_auto ORDER BY repo, landing`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []AutoAccept
for rows.Next() {
var a AutoAccept
if err := rows.Scan(&a.Repo, &a.Landing, &a.Account); err != nil {
return nil, err
}
out = append(out, a)
}
return out, rows.Err()
}