// GetBuildersComments returns the comments for each of the given builders. func GetBuildersComments(builders []string) (map[string][]*BuilderComment, error) { if len(builders) == 0 { return map[string][]*BuilderComment{}, nil } buildersInterface := make([]interface{}, 0, len(builders)) for _, b := range builders { buildersInterface = append(buildersInterface, b) } tmpl := util.RepeatJoin("?", ",", len(buildersInterface)) c := []*BuilderComment{} stmt := fmt.Sprintf("SELECT * FROM %s WHERE builder IN (%s);", TABLE_BUILDER_COMMENTS, tmpl) if err := DB.Select(&c, stmt, buildersInterface...); err != nil { if err == sql.ErrNoRows { // None of these builders have comments. return map[string][]*BuilderComment{}, nil } return nil, fmt.Errorf("Unable to retrieve comment for builders: %v", err) } rv := map[string][]*BuilderComment{} for _, comment := range c { if _, ok := rv[comment.Builder]; !ok { rv[comment.Builder] = []*BuilderComment{} } rv[comment.Builder] = append(rv[comment.Builder], comment) } return rv, nil }
// GetCommitsComments returns the comments on each of the given commits. func GetCommitsComments(commits []string) (map[string][]*CommitComment, error) { if len(commits) == 0 { return map[string][]*CommitComment{}, nil } commitsInterface := make([]interface{}, 0, len(commits)) for _, c := range commits { commitsInterface = append(commitsInterface, c) } tmpl := util.RepeatJoin("?", ",", len(commitsInterface)) c := []*CommitComment{} if err := DB.Select(&c, fmt.Sprintf("SELECT * FROM %s WHERE commit IN (%s);", TABLE_COMMIT_COMMENTS, tmpl), commitsInterface...); err != nil { if err == sql.ErrNoRows { // None of these commits have comments. return map[string][]*CommitComment{}, nil } return nil, fmt.Errorf("Unable to retrieve comments for commits: %v", err) } rv := map[string][]*CommitComment{} for _, comment := range c { if _, ok := rv[comment.Commit]; ok { rv[comment.Commit] = append(rv[comment.Commit], comment) } else { rv[comment.Commit] = []*CommitComment{comment} } } return rv, nil }
// GetBuildIDsForCommits retrieves IDs for all builds which first included each // of the given commits. func GetBuildIDsForCommits(commits []string) (map[string][]int, error) { res := []struct { Revision string `db:"revision"` BuildId int `db:"buildId"` }{} commitsInterface := make([]interface{}, 0, len(commits)) for _, c := range commits { commitsInterface = append(commitsInterface, c) } tmpl := util.RepeatJoin("?", ",", len(commitsInterface)) if err := DB.Select(&res, fmt.Sprintf("SELECT revision, buildId FROM %s WHERE revision IN (%s);", TABLE_BUILD_REVISIONS, tmpl), commitsInterface...); err != nil { if err == sql.ErrNoRows { // No builds include these commits. return map[string][]int{}, nil } return nil, fmt.Errorf("Unable to retrieve builds for commits: %v", err) } rv := map[string][]int{} for _, r := range res { if v, ok := rv[r.Revision]; !ok || v == nil { rv[r.Revision] = []int{} } rv[r.Revision] = append(rv[r.Revision], r.BuildId) } return rv, nil }
// GetActiveAlerts retrieves all active alerts. func GetActiveAlerts() ([]*Alert, error) { // Get the Alerts. rv := []*Alert{} if err := DB.Select(&rv, fmt.Sprintf("SELECT id,name,category,triggered,snoozedUntil,dismissedAt,message,nag,autoDismiss,lastFired FROM %s WHERE active = 1;", TABLE_ALERTS)); err != nil { return nil, fmt.Errorf("Could not retrieve active alerts: %v", err) } if len(rv) == 0 { return []*Alert{}, nil } interfaceIds := make([]interface{}, 0, len(rv)) alertsById := map[int64]*Alert{} for _, a := range rv { interfaceIds = append(interfaceIds, a.Id) alertsById[a.Id] = a } inputTmpl := util.RepeatJoin("?", ",", len(interfaceIds)) // Get the Comments. comments := []*commentFromDB{} if err := DB.Select(&comments, fmt.Sprintf("SELECT * FROM %s WHERE alertId IN (%s);", TABLE_COMMENTS, inputTmpl), interfaceIds...); err != nil { return nil, fmt.Errorf("Could not retrieve comments for active alerts: %v", err) } for _, c := range comments { alertsById[c.AlertId].Comments = append(alertsById[c.AlertId].Comments, c.toComment()) } // Get the Actions. actions := []actionFromDB{} if err := DB.Select(&actions, fmt.Sprintf("SELECT * FROM %s WHERE alertId IN (%s);", TABLE_ACTIONS, inputTmpl), interfaceIds...); err != nil { return nil, fmt.Errorf("Could not retrieve actions for active alerts: %v", err) } for _, a := range actions { action, err := a.toAction() if err != nil { return nil, fmt.Errorf("Could not retrieve actions for active alerts: Failed to parse Action: %v", err) } alertsById[a.AlertId].Actions = append(alertsById[a.AlertId].Actions, action) } return rv, nil }
// replaceCommentsIntoDB inserts, updates, or deletes the given BuildComments // into the database. Returns a map whose keys are pointers to any // newly-inserted BuildComments and whose values are the IDs which should be // assigned to those comments. This allows us to postpone assigning any IDs // until the transaction has completed successfully. func replaceCommentsIntoDB(tx *sqlx.Tx, comments []*BuildComment, newBuildID int) (map[*BuildComment]int, error) { // First, determine which comments need to be inserted, updated, and deleted. commentsFromDB := []*BuildComment{} stmt := fmt.Sprintf("SELECT * FROM %s WHERE buildId = ?;", TABLE_BUILD_COMMENTS) if err := tx.Select(&commentsFromDB, stmt, newBuildID); err != nil { return nil, fmt.Errorf("Could not retrieve build comments from database: %v", err) } oldComments := make(map[int]bool, len(commentsFromDB)) for _, c := range commentsFromDB { oldComments[int(c.Id)] = true } newComments := make(map[int]*BuildComment, len(comments)) for _, c := range comments { newComments[c.Id] = c } update := make([]*BuildComment, 0, len(comments)) insert := make([]*BuildComment, 0, len(comments)) remove := make([]int, 0, len(comments)) for _, c := range comments { if _, ok := oldComments[c.Id]; ok { update = append(update, c) delete(oldComments, c.Id) } else { insert = append(insert, c) } } for id, _ := range oldComments { remove = append(remove, id) } rv := map[*BuildComment]int{} // Delete any no-longer-existing comments. if len(remove) > 0 { idTmpl := util.RepeatJoin("?", ",", len(remove)) removeIds := make([]interface{}, 0, len(remove)) for _, id := range remove { removeIds = append(removeIds, id) } stmt := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", TABLE_BUILD_COMMENTS, idTmpl) if _, err := tx.Exec(stmt, removeIds...); err != nil { return nil, fmt.Errorf("Could not remove old build comments.") } } // Insert any new comments. for _, c := range insert { stmt := fmt.Sprintf("INSERT INTO %s (buildId,user,timestamp,message) VALUES (?,?,?,?);", TABLE_BUILD_COMMENTS) res, err := tx.Exec(stmt, newBuildID, c.User, c.Timestamp, c.Message) if err != nil { return nil, fmt.Errorf("Unable to push build comments into database: %v", err) } id, err := res.LastInsertId() if err != nil { return nil, fmt.Errorf("Unable to get ID for inserted build comment: %v", err) } rv[c] = int(id) } // Update any already-existing comments. for _, c := range update { stmt := fmt.Sprintf("UPDATE %s SET buildId=?, user=?, timestamp=?, message=? WHERE id = ?", TABLE_BUILD_COMMENTS) if _, err := tx.Exec(stmt, c.BuildId, c.User, c.Timestamp, c.Message, c.Id); err != nil { return nil, fmt.Errorf("Failed to update build comments: %v", err) } } return rv, nil }
// replaceCommitsIntoDB inserts, updates, or deletes the commits for the // build into the database. func replaceCommitsIntoDB(tx *sqlx.Tx, commits []string, buildID int) error { // First, determine which commits need to be inserted, updated, and deleted. commitsFromDB := []*commitFromDB{} stmt := fmt.Sprintf("SELECT * FROM %s WHERE buildId = ?;", TABLE_BUILD_REVISIONS) if err := DB.Select(&commitsFromDB, stmt, buildID); err != nil { return fmt.Errorf("Could not retrieve commits from database: %v", err) } oldCommits := make(map[string]*commitFromDB, len(commitsFromDB)) for _, c := range commitsFromDB { oldCommits[c.Revision] = c } newCommits := make(map[string]bool, len(commits)) for _, c := range commits { newCommits[c] = true } update := make([]*commitFromDB, 0, len(commits)) insert := make([]*commitFromDB, 0, len(commits)) remove := make([]int, 0, len(commits)) for _, c := range commits { if old, ok := oldCommits[c]; ok { update = append(update, old) delete(oldCommits, old.Revision) } else { insert = append(insert, &commitFromDB{ BuildID: buildID, Revision: c, }) } } for _, c := range oldCommits { remove = append(remove, c.Id) } // Delete any no-longer-existing commits. if len(remove) > 0 { idTmpl := util.RepeatJoin("?", ",", len(remove)) removeIds := make([]interface{}, 0, len(remove)) for _, id := range remove { removeIds = append(removeIds, id) } stmt := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", TABLE_BUILD_REVISIONS, idTmpl) if _, err := tx.Exec(stmt, removeIds...); err != nil { return fmt.Errorf("Could not remove old build revisions.") } } // Insert any new commits. for _, c := range insert { c.BuildID = buildID stmt := fmt.Sprintf("INSERT INTO %s (buildId,revision) VALUES (?,?);", TABLE_BUILD_REVISIONS) if _, err := tx.Exec(stmt, c.BuildID, c.Revision); err != nil { return fmt.Errorf("Unable to push revisions into database: %v", err) } } // Update any already-existing commits. for _, c := range update { stmt := fmt.Sprintf("UPDATE %s SET buildId=?, revision=? WHERE id = ?", TABLE_BUILD_REVISIONS) if _, err := tx.Exec(stmt, c.BuildID, c.Revision, c.Id); err != nil { return fmt.Errorf("Failed to update build revisions: %v", err) } } return nil }
// replaceStepsIntoDB inserts, updates, or deletes the given BuildSteps into // the database. Returns a map whose keys are pointers to any newly-inserted // BuildSteps and whose values are the IDs which should be assigned to those // steps. This allows us to postpone assigning any IDs until the transaction // has completed successfully. func replaceStepsIntoDB(tx *sqlx.Tx, steps []*BuildStep, newBuildID int) (map[*BuildStep]int, error) { // First, determine which steps need to be inserted, updated, and deleted. stepsFromDB := []*buildStepFromDB{} stmt := fmt.Sprintf("SELECT * FROM %s WHERE buildId = ?;", TABLE_BUILD_STEPS) if err := tx.Select(&stepsFromDB, stmt, newBuildID); err != nil { return nil, fmt.Errorf("Could not retrieve build steps from database: %v", err) } oldSteps := make(map[int]bool, len(stepsFromDB)) for _, s := range stepsFromDB { oldSteps[int(s.Id)] = true } newSteps := make(map[int]*BuildStep, len(steps)) for _, s := range steps { newSteps[s.Id] = s } update := make([]*BuildStep, 0, len(steps)) insert := make([]*BuildStep, 0, len(steps)) remove := make([]int, 0, len(steps)) for _, s := range steps { if _, ok := oldSteps[s.Id]; ok { update = append(update, s) delete(oldSteps, s.Id) } else { insert = append(insert, s) } } for id, _ := range oldSteps { remove = append(remove, id) } rv := map[*BuildStep]int{} // Delete any no-longer-existing steps. if len(remove) > 0 { idTmpl := util.RepeatJoin("?", ",", len(remove)) removeIds := make([]interface{}, 0, len(remove)) for _, id := range remove { removeIds = append(removeIds, id) } stmt := fmt.Sprintf("DELETE FROM %s WHERE id IN (%s)", TABLE_BUILD_STEPS, idTmpl) if _, err := tx.Exec(stmt, removeIds...); err != nil { return nil, fmt.Errorf("Could not remove old build steps.") } } // Insert any new steps. for _, s := range insert { stmt := fmt.Sprintf("INSERT INTO %s (buildId,name,results,number,started,finished) VALUES (?,?,?,?,?,?);", TABLE_BUILD_STEPS) res, err := tx.Exec(stmt, newBuildID, s.Name, s.Results, s.Number, s.Started, s.Finished) if err != nil { return nil, fmt.Errorf("Unable to push buildsteps into database: %v", err) } id, err := res.LastInsertId() if err != nil { return nil, fmt.Errorf("Unable to get ID for inserted buildstep: %v", err) } rv[s] = int(id) } // Update any already-existing steps. for _, s := range update { stmt := fmt.Sprintf("UPDATE %s SET buildId=?, name=?, results=?, number=?, started=?, finished=? WHERE id = ?", TABLE_BUILD_STEPS) if _, err := tx.Exec(stmt, s.BuildID, s.Name, s.Results, s.Number, s.Started, s.Finished, s.Id); err != nil { return nil, fmt.Errorf("Failed to update build steps: %v", err) } } return rv, nil }
// GetBuildsFromDB retrieves the given builds from the database. func GetBuildsFromDB(ids []int) (map[int]*Build, error) { if len(ids) == 0 { return map[int]*Build{}, nil } interfaceIds := make([]interface{}, 0, len(ids)) for _, id := range ids { interfaceIds = append(interfaceIds, id) } inputTmpl := util.RepeatJoin("?", ",", len(interfaceIds)) var wg sync.WaitGroup // Get builds var buildsById map[int]*Build var buildsErr error wg.Add(1) go func() { defer wg.Done() b := []*buildFromDB{} if err := DB.Select(&b, fmt.Sprintf("SELECT * FROM %s WHERE id IN (%s);", TABLE_BUILDS, inputTmpl), interfaceIds...); err != nil { buildsErr = fmt.Errorf("Could not retrieve builds: %v", err) return } buildsById = map[int]*Build{} for _, buildFromDB := range b { build := buildFromDB.toBuild() // Build properties. var properties [][]interface{} if build.PropertiesStr != "" { if err := json.Unmarshal([]byte(build.PropertiesStr), &properties); err != nil { buildsErr = fmt.Errorf("Unable to parse build properties: %v", err) } } build.Properties = properties // Start and end times. build.Times = []float64{build.Started, build.Finished} buildsById[build.Id] = build } }() // Build steps. stepsFromDB := []*buildStepFromDB{} var stepsErr error wg.Add(1) go func() { defer wg.Done() if err := DB.Select(&stepsFromDB, fmt.Sprintf("SELECT * FROM %s WHERE buildId IN (%s);", TABLE_BUILD_STEPS, inputTmpl), interfaceIds...); err != nil { stepsErr = fmt.Errorf("Could not retrieve build steps from database: %v", err) return } }() // Commits for each build. commitsFromDB := []*commitFromDB{} var commitsErr error wg.Add(1) go func() { defer wg.Done() if err := DB.Select(&commitsFromDB, fmt.Sprintf("SELECT * FROM %s WHERE buildId IN (%s);", TABLE_BUILD_REVISIONS, inputTmpl), interfaceIds...); err != nil { commitsErr = fmt.Errorf("Could not retrieve revisions from database: %v", err) return } }() // Comments on each build. commentsFromDB := []*BuildComment{} var commentsErr error wg.Add(1) go func() { defer wg.Done() if err := DB.Select(&commentsFromDB, fmt.Sprintf("SELECT * FROM %s WHERE buildId IN (%s);", TABLE_BUILD_COMMENTS, inputTmpl), interfaceIds...); err != nil { commentsErr = fmt.Errorf("Could not retrieve comments from database: %v", err) return } }() wg.Wait() // Return error if applicable. if buildsErr != nil { return nil, buildsErr } if stepsErr != nil { return nil, stepsErr } if commitsErr != nil { return nil, commitsErr } if commentsErr != nil { return nil, commentsErr } // Associate steps with builds. for _, stepFromDB := range stepsFromDB { s := stepFromDB.toBuildStep() s.Times = []float64{s.Started, s.Finished} s.ResultsRaw = []interface{}{float64(s.Results), []interface{}{}} build, ok := buildsById[s.BuildID] if !ok { return nil, fmt.Errorf("Failed to retrieve builds; got a build step with no associated build.") } if build.Steps == nil { build.Steps = []*BuildStep{} } build.Steps = append(build.Steps, s) } // Associate commits with builds. for _, c := range commitsFromDB { build, ok := buildsById[c.BuildID] if !ok { return nil, fmt.Errorf("Failed to retrieve builds; got a commit with no associated build.") } if build.Commits == nil { build.Commits = []string{} } build.Commits = append(build.Commits, c.Revision) } // Associate comments with builds. for _, c := range commentsFromDB { build, ok := buildsById[c.BuildId] if !ok { return nil, fmt.Errorf("Failed to retrieve builds; got a comment with no associated build.") } if build.Comments == nil { build.Comments = []*BuildComment{} } build.Comments = append(build.Comments, c) } return buildsById, nil }
// replaceIntoDB inserts or updates the Alert in the database. func (a *Alert) replaceIntoDB() (rv error) { tx, err := DB.Beginx() if err != nil { return fmt.Errorf("Unable to push Alert into database - Could not begin transaction: %v", err) } defer func() { rv = database.CommitOrRollback(tx, rv) }() // Insert the alert itself. active := 0 if a.DismissedAt == 0 { active = 1 } res, err := tx.Exec(fmt.Sprintf("REPLACE INTO %s (id,active,name,triggered,category,message,nag,snoozedUntil,dismissedAt,autoDismiss,lastFired) VALUES (?,?,?,?,?,?,?,?,?,?,?);", TABLE_ALERTS), a.Id, active, a.Name, a.Triggered, a.Category, a.Message, a.Nag, a.SnoozedUntil, a.DismissedAt, a.AutoDismiss, a.LastFired) if err != nil { return fmt.Errorf("Failed to push alert into database: %v", err) } id, err := res.LastInsertId() if err != nil { return fmt.Errorf("Failed to push alert into database; LastInsertId failed: %v", err) } a.Id = id // Comments. // First, delete existing comments so we don't have leftovers hanging around from before. if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE alertId = ?;", TABLE_COMMENTS), a.Id); err != nil { return fmt.Errorf("Failed to delete comments from database: %v", err) } // Actually insert the comments. if len(a.Comments) > 0 { commentFields := 4 commentTmpl := util.RepeatJoin("?", ",", commentFields) commentsTmpl := util.RepeatJoin(fmt.Sprintf("(%s)", commentTmpl), ",", len(a.Comments)) flattenedComments := make([]interface{}, 0, commentFields*len(a.Comments)) for _, c := range a.Comments { flattenedComments = append(flattenedComments, a.Id, c.User, c.Time, c.Message) } if _, err := tx.Exec(fmt.Sprintf("INSERT INTO %s (alertId,user,time,message) VALUES %s;", TABLE_COMMENTS, commentsTmpl), flattenedComments...); err != nil { return fmt.Errorf("Unable to push comments into database: %v", err) } } // Actions. // First, delete existing actions so we don't have leftovers hanging around from before. if _, err := tx.Exec(fmt.Sprintf("DELETE FROM %s WHERE alertId = ?;", TABLE_ACTIONS), a.Id); err != nil { return fmt.Errorf("Failed to delete actions from database: %v", err) } // Actually insert the actions. if len(a.Actions) > 0 { actionFields := 2 actionTmpl := util.RepeatJoin("?", ",", actionFields) actionsTmpl := util.RepeatJoin(fmt.Sprintf("(%s)", actionTmpl), ",", len(a.Actions)) flattenedActions := make([]interface{}, 0, actionFields*len(a.Actions)) for _, action := range a.Actions { flattenedActions = append(flattenedActions, a.Id, action.String()) } if _, err := tx.Exec(fmt.Sprintf("INSERT INTO %s (alertId,action) VALUES %s;", TABLE_ACTIONS, actionsTmpl), flattenedActions...); err != nil { return fmt.Errorf("Unable to push actions into database: %v", err) } } // the transaction is committed during the deferred function. return nil }