blob: b4fc17a2a66680f001917e1ac339018552c1a5af [file]
package sqlanomalygroupstore
import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sql/pool"
pb "go.skia.org/infra/perf/go/anomalygroup/proto/v1"
)
// statement is an SQL statement identifier.
type statement int
const (
// The identifiers for all the SQL statements used.
createGroup statement = iota
queryGroups
updateGroup
getGroup
)
// statements holds all the raw SQL statements used.
var statements = map[statement]string{
createGroup: `
INSERT INTO
AnomalyGroups (anomalies, group_meta_data, common_rev_start, common_rev_end, action)
VALUES
($1, $2, $3, $4, $5)
RETURNING
id
`,
}
type AnomalyGroupStore struct {
//
db pool.Pool
}
func New(db pool.Pool) (*AnomalyGroupStore, error) {
return &AnomalyGroupStore{
db: db,
}, nil
}
func (s *AnomalyGroupStore) Create(
ctx context.Context,
subscription_name string,
subscription_revision string,
domain_name string,
benchmark_name string,
start int64,
end int64,
action string) (string, error) {
// Sanity checks
if len(subscription_name) == 0 || len(subscription_revision) == 0 || len(domain_name) == 0 || len(benchmark_name) == 0 || len(action) == 0 {
return "", errors.New("empty strings found in string parameters")
}
if end <= 0 || start <= 0 {
return "", errors.New("negative commit position detected")
}
if end < start {
return "", errors.New("the end commit position is smaller than the start commit position")
}
// SQL to create a anomaly group
statement := `
INSERT INTO
AnomalyGroups (group_meta_data, common_rev_start, common_rev_end, action)
VALUES
($1, $2, $3, $4)
RETURNING
id
`
metadata := map[string]string{
"subscription_name": subscription_name,
"subscription_revision": subscription_revision,
"domain_name": domain_name,
"benchmark_name": benchmark_name,
}
metadata_string, err := json.Marshal(metadata)
if err != nil {
return "", skerr.Wrapf(err, "Failed to convert group metadata json.")
}
new_group_id := ""
err = s.db.QueryRow(
ctx, statement, metadata_string, start, end, action).Scan(&new_group_id)
if err != nil {
return "", skerr.Wrapf(err, "Failed to create new anomaly group.")
} else {
return string(new_group_id), nil
}
}
func (s *AnomalyGroupStore) LoadById(
ctx context.Context,
group_id string) (*pb.AnomalyGroup, error) {
// Sanity checks
if _, err := uuid.Parse(group_id); err != nil {
err_msg := fmt.Sprintf("group id is not a valid uuid: %s.", group_id)
return nil, skerr.Wrapf(err, err_msg)
}
statement := `
SELECT
id, action, anomaly_ids, culprit_ids, group_meta_data->>'subscription_name', group_meta_data->>'subscription_revision'
FROM
AnomalyGroups
WHERE
id=$1
`
var loaded_group_id string
var action string
var anomaly_ids []string
var culprit_ids []string
var subscription_name string
var subscription_revision string
if err := s.db.QueryRow(ctx, statement, group_id).Scan(&loaded_group_id, &action, &anomaly_ids, &culprit_ids, &subscription_name, &subscription_revision); err != nil {
err_msg := fmt.Sprintf("failed to load the anomaly group: %s", group_id)
return nil, skerr.Wrapf(err, err_msg)
}
return &pb.AnomalyGroup{
GroupId: loaded_group_id,
GroupAction: pb.GroupActionType(pb.GroupActionType_value[action]),
AnomalyIds: anomaly_ids,
CulpritIds: culprit_ids,
SubsciptionName: subscription_name,
SubscriptionRevision: subscription_revision,
}, nil
}
func (s *AnomalyGroupStore) UpdateBisectID(ctx context.Context, group_id string, bisection_id string) error {
if len(bisection_id) > 0 {
if _, err := uuid.Parse(bisection_id); err != nil {
err_msg := fmt.Sprintf("invalid UUID value for updating bisection_id column with value %s ", bisection_id)
return errors.New(err_msg)
}
}
statement := `
UPDATE
AnomalyGroups
SET
bisection_id=$1
WHERE
id=$2
`
if _, err := s.db.Exec(ctx, statement, bisection_id, group_id); err != nil {
return fmt.Errorf(
"error updating bisection id for anomaly group: %s. group_id: %s, bisect_id: %s",
err, group_id, bisection_id)
}
return nil
}
func (s *AnomalyGroupStore) UpdateReportedIssueID(ctx context.Context, group_id string, reported_issue_id string) error {
if len(reported_issue_id) > 0 {
if _, err := uuid.Parse(reported_issue_id); err != nil {
err_msg := fmt.Sprintf("invalid UUID value for updating reported_issue_id column with value %s ", reported_issue_id)
return errors.New(err_msg)
}
}
statement := `
UPDATE
AnomalyGroups
SET
reported_issue_id=$1
WHERE
id=$2
`
if _, err := s.db.Exec(ctx, statement, reported_issue_id, group_id); err != nil {
return fmt.Errorf("error updating anomaly group table: %s. %s", err, group_id)
}
return nil
}
func (s *AnomalyGroupStore) AddAnomalyID(ctx context.Context, group_id string, anomaly_id string) error {
if len(anomaly_id) > 0 {
if _, err := uuid.Parse(anomaly_id); err != nil {
err_msg := fmt.Sprintf("invalid UUID value for updating anomaly_id column with value %s ", anomaly_id)
return errors.New(err_msg)
}
}
statement := `
UPDATE
AnomalyGroups
SET
anomaly_ids=array_append(anomaly_ids, $1)
WHERE
id=$2
`
if _, err := s.db.Exec(ctx, statement, anomaly_id, group_id); err != nil {
return fmt.Errorf("error updating anomaly group table: %s. %s", err, group_id)
}
return nil
}
func (s *AnomalyGroupStore) AddCulpritIDs(ctx context.Context, group_id string, culprit_ids []string) error {
for _, v := range culprit_ids {
if _, err := uuid.Parse(v); err != nil {
err_msg := fmt.Sprintf("invalid UUID value for updating culprit_ids column with value %s ", culprit_ids)
return errors.New(err_msg)
}
}
statement := `
UPDATE
AnomalyGroups
SET
culprit_ids=array_cat(culprit_ids, $1)
WHERE
id=$2
`
if _, err := s.db.Exec(ctx, statement, culprit_ids, group_id); err != nil {
return fmt.Errorf("error updating anomaly group table: %s. %s", err, group_id)
}
return nil
}
func (s *AnomalyGroupStore) FindExistingGroup(
ctx context.Context,
subscription_name string,
subscription_revision string,
domain_name string,
benchmark_name string,
start_commit int64,
end_commit int64,
action string) ([]*pb.AnomalyGroup, error) {
// sanity check
if len(subscription_name) == 0 || len(subscription_revision) == 0 || len(domain_name) == 0 || len(benchmark_name) == 0 || len(action) == 0 || start_commit <= 0 || end_commit <= 0 {
err_msg := fmt.Sprintf(
"invalid params when finding related groups. Params: %s, %s, %s, %s, %s, %d, %d",
action, subscription_name, subscription_revision, domain_name, benchmark_name,
end_commit, start_commit)
return nil, errors.New(err_msg)
}
statement := `
SELECT
id, action, anomaly_ids, culprit_ids
FROM
AnomalyGroups
WHERE
action=$1
AND JSON_EXTRACT_PATH_TEXT(group_meta_data, 'subscription_name')=$2
AND JSON_EXTRACT_PATH_TEXT(group_meta_data, 'subscription_revision')=$3
AND JSON_EXTRACT_PATH_TEXT(group_meta_data, 'domain_name')=$4
AND JSON_EXTRACT_PATH_TEXT(group_meta_data, 'benchmark_name')=$5
AND common_rev_start<=$6
AND common_rev_end>=$7
`
rows, err := s.db.Query(ctx, statement,
action, subscription_name, subscription_revision, domain_name, benchmark_name,
end_commit, start_commit)
if err != nil {
err_msg := fmt.Sprintf(
"failed when finding related groups. Params: %s, %s, %s, %s, %s, %d, %d",
action, subscription_name, subscription_revision, domain_name, benchmark_name,
end_commit, start_commit)
return nil, skerr.Wrapf(err, err_msg)
}
var groups []*pb.AnomalyGroup
for rows.Next() {
var loaded_group_id string
var loaded_action string
var anomaly_ids []string
var culprit_ids []string
if err = rows.Scan(&loaded_group_id, &loaded_action, &anomaly_ids, &culprit_ids); err != nil {
err_msg := fmt.Sprintf(
"error parsing the returned group values: %s, %s, %s, %s",
loaded_group_id, loaded_action, anomaly_ids, culprit_ids)
return nil, skerr.Wrapf(err, err_msg)
} else {
groups = append(groups, &pb.AnomalyGroup{
GroupId: loaded_group_id,
GroupAction: pb.GroupActionType(pb.GroupActionType_value[action]),
AnomalyIds: anomaly_ids,
CulpritIds: culprit_ids,
})
}
}
return groups, nil
}