package db import "strings" // FailureAtlasPattern is one conditioned failure bucket in SQLite. type FailureAtlasPattern struct { FingerprintBucket string Condition string Tier string FailCount int } func (d *Database) UpsertFailureAtlasPattern(fingerprintBucket, condition, tier string) error { if d == nil { return nil } _, err := d.Exec(` INSERT INTO failure_atlas (fingerprint_bucket, condition, tier, fail_count, updated_at) VALUES (?, ?, ?, 1, CURRENT_TIMESTAMP) ON CONFLICT(fingerprint_bucket, condition, tier) DO UPDATE SET fail_count = fail_count + 1, updated_at = CURRENT_TIMESTAMP`, fingerprintBucket, condition, tier, ) return err } func (d *Database) ListFailureAtlasPatterns(fingerprintBucket, goos string) ([]FailureAtlasPattern, error) { if d == nil { return nil, nil } out, err := d.queryFailureAtlasPatterns(`fingerprint_bucket = ?`, fingerprintBucket) if err != nil { return nil, err } if len(out) > 0 { return out, nil } if strings.TrimSpace(goos) == "" { return nil, nil } return d.queryFailureAtlasPatterns(`fingerprint_bucket LIKE ?`, strings.ToLower(goos)+"|%") } func (d *Database) queryFailureAtlasPatterns(whereClause string, arg interface{}) ([]FailureAtlasPattern, error) { query := ` SELECT fingerprint_bucket, condition, tier, fail_count FROM failure_atlas WHERE ` + whereClause + ` ORDER BY fail_count DESC, tier ASC` rows, err := d.Query(query, arg) if err != nil { return nil, err } defer rows.Close() var out []FailureAtlasPattern for rows.Next() { var p FailureAtlasPattern if err := rows.Scan(&p.FingerprintBucket, &p.Condition, &p.Tier, &p.FailCount); err != nil { return nil, err } out = append(out, p) } return out, rows.Err() }