package registry import ( "database/sql" "fmt" "time" ) // Event represents a state-change event. type Event struct { ID int64 Service string Component string PrevState string NewState string Timestamp time.Time } // InsertEvent records a state transition. func InsertEvent(db *sql.DB, service, component, prevState, newState string) error { _, err := db.Exec( "INSERT INTO events (service, component, prev_state, new_state) VALUES (?, ?, ?, ?)", service, component, prevState, newState, ) if err != nil { return fmt.Errorf("insert event %q/%q: %w", service, component, err) } return nil } // QueryEvents returns events for a service/component within a time window. // If service is empty, returns events for all services. // If component is empty, returns events for all components in the service. func QueryEvents(db *sql.DB, service, component string, since time.Time, limit int) ([]Event, error) { query := "SELECT id, service, component, prev_state, new_state, timestamp FROM events WHERE timestamp > ?" args := []any{since.UTC().Format("2006-01-02 15:04:05")} if service != "" { query += " AND service = ?" args = append(args, service) } if component != "" { query += " AND component = ?" args = append(args, component) } query += " ORDER BY timestamp DESC" if limit > 0 { query += " LIMIT ?" args = append(args, limit) } rows, err := db.Query(query, args...) if err != nil { return nil, fmt.Errorf("query events: %w", err) } defer func() { _ = rows.Close() }() var events []Event for rows.Next() { var e Event var ts string if err := rows.Scan(&e.ID, &e.Service, &e.Component, &e.PrevState, &e.NewState, &ts); err != nil { return nil, fmt.Errorf("scan event: %w", err) } e.Timestamp, _ = time.Parse("2006-01-02 15:04:05", ts) events = append(events, e) } return events, rows.Err() } // CountEvents counts events for a component within a time window matching // a specific new_state. Used for flap detection. func CountEvents(db *sql.DB, service, component string, since time.Time) (int, error) { var count int err := db.QueryRow( "SELECT COUNT(*) FROM events WHERE service = ? AND component = ? AND timestamp > ?", service, component, since.UTC().Format("2006-01-02 15:04:05"), ).Scan(&count) if err != nil { return 0, fmt.Errorf("count events %q/%q: %w", service, component, err) } return count, nil } // PruneEvents deletes events older than the given time. func PruneEvents(db *sql.DB, before time.Time) (int64, error) { res, err := db.Exec( "DELETE FROM events WHERE timestamp < ?", before.UTC().Format("2006-01-02 15:04:05"), ) if err != nil { return 0, fmt.Errorf("prune events: %w", err) } return res.RowsAffected() }