// AllMetas retrieves the metadata for all work specs. This is // expected to run within a pre-existing transaction. On success, // returns maps from work spec name to work spec object and to // metadata object. func (ns *namespace) allMetas(tx *sql.Tx, withCounts bool) (map[string]*workSpec, map[string]*coordinate.WorkSpecMeta, error) { params := queryParams{} query := buildSelect([]string{ workSpecID, workSpecName, workSpecPriority, workSpecWeight, workSpecPaused, workSpecContinuous, workSpecCanBeContinuous, workSpecMinMemoryGb, workSpecInterval, workSpecNextContinuous, workSpecMaxRunning, workSpecMaxAttemptsReturned, workSpecNextWorkSpec, workSpecRuntime, }, []string{ workSpecTable, }, []string{ workSpecInNamespace(¶ms, ns.id), }) rows, err := tx.Query(query, params...) if err != nil { return nil, nil, err } specs := make(map[string]*workSpec) metas := make(map[string]*coordinate.WorkSpecMeta) err = scanRows(rows, func() error { var ( spec workSpec meta coordinate.WorkSpecMeta interval string nextContinuous pq.NullTime err error ) err = rows.Scan(&spec.id, &spec.name, &meta.Priority, &meta.Weight, &meta.Paused, &meta.Continuous, &meta.CanBeContinuous, &meta.MinMemoryGb, &interval, &nextContinuous, &meta.MaxRunning, &meta.MaxAttemptsReturned, &meta.NextWorkSpecName, &meta.Runtime) if err != nil { return err } spec.namespace = ns meta.NextContinuous = nullTimeToTime(nextContinuous) meta.Interval, err = sqlToDuration(interval) if err != nil { return err } specs[spec.name] = &spec metas[spec.name] = &meta return nil }) if err != nil { return nil, nil, err } if withCounts { // A single query that selects both "available" and // "pending" is hopelessly expensive. Also, in the // only place this is called (in RequestAttempts) we // need to know whether or not there are any available // attempts, but we don't really care how many there // are so long as there are more than zero. // // Pending: params = queryParams{} query = buildSelect([]string{workSpecName, "COUNT(*)"}, []string{workSpecTable, attemptTable}, []string{ workSpecInNamespace(¶ms, ns.id), attemptInThisSpec, attemptIsPending, }) query += " GROUP BY " + workSpecName rows, err = tx.Query(query, params...) if err != nil { return nil, nil, err } err = scanRows(rows, func() error { var name string var count int err := rows.Scan(&name, &count) if err == nil { metas[name].PendingCount = count } return err }) // Available count (0/1): now := ns.Coordinate().clock.Now() params = queryParams{} query = buildSelect([]string{ workUnitSpec, }, []string{ workUnitTable, }, []string{ workUnitHasNoAttempt, "NOT " + workUnitTooSoon(¶ms, now), }) query = buildSelect([]string{ workSpecName, }, []string{ workSpecTable, }, []string{ workSpecInNamespace(¶ms, ns.id), workSpecID + " IN (" + query + ")", }) rows, err = tx.Query(query, params...) err = scanRows(rows, func() error { var name string err := rows.Scan(&name) if err == nil { metas[name].AvailableCount = 1 } return err }) if err != nil { return nil, nil, err } } return specs, metas, nil }
func (spec *workSpec) Meta(withCounts bool) (coordinate.WorkSpecMeta, error) { // If we need counts, we need to run expiry so that the // available/pending counts are rightish if withCounts { spec.Coordinate().Expiry.Do(spec) } var meta coordinate.WorkSpecMeta err := withTx(spec, true, func(tx *sql.Tx) error { var ( params queryParams query string interval string nextContinuous pq.NullTime ) query = buildSelect([]string{ workSpecPriority, workSpecWeight, workSpecPaused, workSpecContinuous, workSpecCanBeContinuous, workSpecMinMemoryGb, workSpecInterval, workSpecNextContinuous, workSpecMaxRunning, workSpecMaxAttemptsReturned, workSpecNextWorkSpec, workSpecRuntime, }, []string{ workSpecTable, }, []string{ isWorkSpec(¶ms, spec.id), }) row := tx.QueryRow(query, params...) err := row.Scan( &meta.Priority, &meta.Weight, &meta.Paused, &meta.Continuous, &meta.CanBeContinuous, &meta.MinMemoryGb, &interval, &nextContinuous, &meta.MaxRunning, &meta.MaxAttemptsReturned, &meta.NextWorkSpecName, &meta.Runtime, ) if err == sql.ErrNoRows { return coordinate.ErrGone } if err != nil { return err } meta.NextContinuous = nullTimeToTime(nextContinuous) meta.Interval, err = sqlToDuration(interval) if err != nil { return err } // Find counts with a second query, if requested if !withCounts { return nil } params = queryParams{} query = buildSelect([]string{ attemptStatus, "COUNT(*)", }, []string{ workUnitAttemptJoin, }, []string{ workUnitInSpec(¶ms, spec.id), }) query += " GROUP BY " + attemptStatus rows, err := tx.Query(query, params...) if err != nil { return err } return scanRows(rows, func() error { var status sql.NullString var count int err := rows.Scan(&status, &count) if err != nil { return err } if !status.Valid { meta.AvailableCount += count } else { switch status.String { case "expired": meta.AvailableCount += count case "retryable": meta.AvailableCount += count case "pending": meta.PendingCount += count } } return nil }) }) return meta, err }