blob: 56e891901b2ef539daf04abc8b23a93a6bacbb4d [file] [log] [blame]
// Package sqlalertstore implements alerts.Store using SQL.
//
// Please see perf/sql/migrations for the database schema used.
package sqlalertstore
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/jackc/pgx/v4/pgxpool"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/perf/go/alerts"
)
// statement is an SQL statement identifier.
type statement int
const (
// The identifiers for all the SQL statements used.
insertAlert statement = iota
updateAlert
deleteAlert
listActiveAlerts
listAllAlerts
)
// statements holds all the raw SQL statements used.
var statements = map[statement]string{
insertAlert: `
INSERT INTO
Alerts (alert, last_modified)
VALUES
($1, $2)
`,
updateAlert: `
UPSERT INTO
Alerts (id, alert, config_state, last_modified)
VALUES
($1, $2, $3, $4)
`,
deleteAlert: `
UPDATE
Alerts
SET
config_state=1, -- alerts.DELETED
last_modified=$1
WHERE
id=$2
`,
listActiveAlerts: `
SELECT
id, alert
FROM
ALERTS
WHERE
config_state=0 -- alerts.ACTIVE
`,
listAllAlerts: `
SELECT
id, alert
FROM
ALERTS
`,
}
// SQLAlertStore implements the alerts.Store interface.
type SQLAlertStore struct {
// db is the database interface.
db *pgxpool.Pool
}
// New returns a new *SQLAlertStore.
//
// We presume all migrations have been run against db before this function is
// called.
func New(db *pgxpool.Pool) (*SQLAlertStore, error) {
return &SQLAlertStore{
db: db,
}, nil
}
// Save implements the alerts.Store interface.
func (s *SQLAlertStore) Save(ctx context.Context, cfg *alerts.Alert) error {
cfg.SetIDFromString(cfg.IDAsString)
b, err := json.Marshal(cfg)
if err != nil {
return skerr.Wrapf(err, "Failed to serialize Alert for saving with ID=%d", cfg.ID)
}
now := time.Now().Unix()
if cfg.ID == alerts.BadAlertID {
// Not a valid ID, so this should be an insert, not an update.
if _, err := s.db.Exec(ctx, statements[insertAlert], string(b), now); err != nil {
return skerr.Wrapf(err, "Failed to insert alert")
}
} else {
if _, err := s.db.Exec(ctx, statements[updateAlert], cfg.ID, string(b), cfg.StateToInt(), now); err != nil {
return skerr.Wrapf(err, "Failed to update Alert with ID=%d", cfg.ID)
}
}
return nil
}
// Delete implements the alerts.Store interface.
func (s *SQLAlertStore) Delete(ctx context.Context, id int) error {
now := time.Now().Unix()
if _, err := s.db.Exec(ctx, statements[deleteAlert], now, id); err != nil {
return skerr.Wrapf(err, "Failed to mark Alert as deleted with ID=%d", id)
}
return nil
}
// List implements the alerts.Store interface.
func (s *SQLAlertStore) List(ctx context.Context, includeDeleted bool) ([]*alerts.Alert, error) {
stmt := listActiveAlerts
if includeDeleted {
stmt = listAllAlerts
}
rows, err := s.db.Query(ctx, statements[stmt])
if err != nil {
return nil, err
}
ret := []*alerts.Alert{}
for rows.Next() {
var id int64
var serializedAlert string
if err := rows.Scan(&id, &serializedAlert); err != nil {
return nil, err
}
a := &alerts.Alert{}
if err := json.Unmarshal([]byte(serializedAlert), a); err != nil {
return nil, skerr.Wrapf(err, "Failed to deserialize JSON Alert.")
}
a.ID = id
a.IDAsString = fmt.Sprintf("%d", id)
ret = append(ret, a)
}
return ret, nil
}