github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/job.go (about)

     1  package db
     2  
     3  import (
     4  	"context"
     5  	"database/sql"
     6  	"encoding/json"
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"code.cloudfoundry.org/lager"
    14  	sq "github.com/Masterminds/squirrel"
    15  	"github.com/pf-qiu/concourse/v6/atc"
    16  	"github.com/pf-qiu/concourse/v6/atc/db/lock"
    17  	"github.com/pf-qiu/concourse/v6/tracing"
    18  	"github.com/lib/pq"
    19  )
    20  
    21  type InputConfigs []InputConfig
    22  
    23  type InputConfig struct {
    24  	Name            string
    25  	Trigger         bool
    26  	Passed          JobSet
    27  	UseEveryVersion bool
    28  	PinnedVersion   atc.Version
    29  	ResourceID      int
    30  	JobID           int
    31  }
    32  
    33  func (cfgs InputConfigs) String() string {
    34  	if !tracing.Configured {
    35  		return ""
    36  	}
    37  
    38  	names := make([]string, len(cfgs))
    39  	for i, cfg := range cfgs {
    40  		names[i] = cfg.Name
    41  	}
    42  
    43  	return strings.Join(names, ",")
    44  }
    45  
    46  type InputVersionEmptyError struct {
    47  	InputName string
    48  }
    49  
    50  func (e InputVersionEmptyError) Error() string {
    51  	return fmt.Sprintf("input '%s' has successfully resolved but contains missing version information", e.InputName)
    52  }
    53  
    54  //go:generate counterfeiter . Job
    55  
    56  type Job interface {
    57  	PipelineRef
    58  
    59  	ID() int
    60  	Name() string
    61  	Paused() bool
    62  	FirstLoggedBuildID() int
    63  	TeamID() int
    64  	TeamName() string
    65  	Tags() []string
    66  	Public() bool
    67  	ScheduleRequestedTime() time.Time
    68  	MaxInFlight() int
    69  	DisableManualTrigger() bool
    70  
    71  	Config() (atc.JobConfig, error)
    72  	Inputs() ([]atc.JobInput, error)
    73  	Outputs() ([]atc.JobOutput, error)
    74  	AlgorithmInputs() (InputConfigs, error)
    75  
    76  	Reload() (bool, error)
    77  
    78  	Pause() error
    79  	Unpause() error
    80  
    81  	ScheduleBuild(Build) (bool, error)
    82  	CreateBuild() (Build, error)
    83  	RerunBuild(Build) (Build, error)
    84  
    85  	RequestSchedule() error
    86  	UpdateLastScheduled(time.Time) error
    87  
    88  	Builds(page Page) ([]Build, Pagination, error)
    89  	BuildsWithTime(page Page) ([]Build, Pagination, error)
    90  	Build(name string) (Build, bool, error)
    91  	FinishedAndNextBuild() (Build, Build, error)
    92  	UpdateFirstLoggedBuildID(newFirstLoggedBuildID int) error
    93  	EnsurePendingBuildExists(context.Context) error
    94  	GetPendingBuilds() ([]Build, error)
    95  
    96  	GetNextBuildInputs() ([]BuildInput, error)
    97  	GetFullNextBuildInputs() ([]BuildInput, bool, error)
    98  	SaveNextInputMapping(inputMapping InputMapping, inputsDetermined bool) error
    99  
   100  	ClearTaskCache(string, string) (int64, error)
   101  
   102  	AcquireSchedulingLock(lager.Logger) (lock.Lock, bool, error)
   103  
   104  	SetHasNewInputs(bool) error
   105  	HasNewInputs() bool
   106  }
   107  
   108  var jobsQuery = psql.Select("j.id", "j.name", "j.config", "j.paused", "j.public", "j.first_logged_build_id", "j.pipeline_id", "p.name", "p.instance_vars", "p.team_id", "t.name", "j.nonce", "j.tags", "j.has_new_inputs", "j.schedule_requested", "j.max_in_flight", "j.disable_manual_trigger").
   109  	From("jobs j, pipelines p").
   110  	LeftJoin("teams t ON p.team_id = t.id").
   111  	Where(sq.Expr("j.pipeline_id = p.id"))
   112  
   113  type FirstLoggedBuildIDDecreasedError struct {
   114  	Job   string
   115  	OldID int
   116  	NewID int
   117  }
   118  
   119  func (e FirstLoggedBuildIDDecreasedError) Error() string {
   120  	return fmt.Sprintf("first logged build id for job '%s' decreased from %d to %d", e.Job, e.OldID, e.NewID)
   121  }
   122  
   123  type job struct {
   124  	pipelineRef
   125  
   126  	id                    int
   127  	name                  string
   128  	paused                bool
   129  	public                bool
   130  	firstLoggedBuildID    int
   131  	teamID                int
   132  	teamName              string
   133  	tags                  []string
   134  	hasNewInputs          bool
   135  	scheduleRequestedTime time.Time
   136  	maxInFlight           int
   137  	disableManualTrigger  bool
   138  
   139  	config    *atc.JobConfig
   140  	rawConfig *string
   141  	nonce     *string
   142  }
   143  
   144  func newEmptyJob(conn Conn, lockFactory lock.LockFactory) *job {
   145  	return &job{pipelineRef: pipelineRef{conn: conn, lockFactory: lockFactory}}
   146  }
   147  
   148  func (j *job) SetHasNewInputs(hasNewInputs bool) error {
   149  	result, err := psql.Update("jobs").
   150  		Set("has_new_inputs", hasNewInputs).
   151  		Where(sq.Eq{"id": j.id}).
   152  		RunWith(j.conn).
   153  		Exec()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	rowsAffected, err := result.RowsAffected()
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	if rowsAffected != 1 {
   164  		return NonOneRowAffectedError{rowsAffected}
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  type Jobs []Job
   171  
   172  func (jobs Jobs) Configs() (atc.JobConfigs, error) {
   173  	var configs atc.JobConfigs
   174  
   175  	for _, j := range jobs {
   176  		config, err := j.Config()
   177  		if err != nil {
   178  			return nil, err
   179  		}
   180  
   181  		configs = append(configs, config)
   182  	}
   183  
   184  	return configs, nil
   185  }
   186  
   187  func (j *job) ID() int                          { return j.id }
   188  func (j *job) Name() string                     { return j.name }
   189  func (j *job) Paused() bool                     { return j.paused }
   190  func (j *job) Public() bool                     { return j.public }
   191  func (j *job) FirstLoggedBuildID() int          { return j.firstLoggedBuildID }
   192  func (j *job) TeamID() int                      { return j.teamID }
   193  func (j *job) TeamName() string                 { return j.teamName }
   194  func (j *job) Tags() []string                   { return j.tags }
   195  func (j *job) HasNewInputs() bool               { return j.hasNewInputs }
   196  func (j *job) ScheduleRequestedTime() time.Time { return j.scheduleRequestedTime }
   197  func (j *job) MaxInFlight() int                 { return j.maxInFlight }
   198  func (j *job) DisableManualTrigger() bool       { return j.disableManualTrigger }
   199  
   200  func (j *job) Config() (atc.JobConfig, error) {
   201  	if j.config != nil {
   202  		return *j.config, nil
   203  	}
   204  
   205  	es := j.conn.EncryptionStrategy()
   206  
   207  	if j.rawConfig == nil {
   208  		return atc.JobConfig{}, nil
   209  	}
   210  
   211  	decryptedConfig, err := es.Decrypt(*j.rawConfig, j.nonce)
   212  	if err != nil {
   213  		return atc.JobConfig{}, err
   214  	}
   215  
   216  	var config atc.JobConfig
   217  	err = json.Unmarshal(decryptedConfig, &config)
   218  	if err != nil {
   219  		return atc.JobConfig{}, err
   220  	}
   221  
   222  	j.config = &config
   223  	return config, nil
   224  }
   225  
   226  func (j *job) AlgorithmInputs() (InputConfigs, error) {
   227  	rows, err := psql.Select("ji.name", "ji.resource_id", "array_agg(ji.passed_job_id)", "ji.version", "rp.version", "ji.trigger").
   228  		From("job_inputs ji").
   229  		LeftJoin("resource_pins rp ON rp.resource_id = ji.resource_id").
   230  		Where(sq.Eq{
   231  			"ji.job_id": j.id,
   232  		}).
   233  		GroupBy("ji.name, ji.job_id, ji.resource_id, ji.version, rp.version, ji.trigger").
   234  		RunWith(j.conn).
   235  		Query()
   236  	if err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	var inputs InputConfigs
   241  	for rows.Next() {
   242  		var passedJobs []sql.NullInt64
   243  		var configVersionString, pinnedVersionString sql.NullString
   244  		var inputName string
   245  		var resourceID int
   246  		var trigger bool
   247  
   248  		err = rows.Scan(&inputName, &resourceID, pq.Array(&passedJobs), &configVersionString, &pinnedVersionString, &trigger)
   249  		if err != nil {
   250  			return nil, err
   251  		}
   252  
   253  		inputConfig := InputConfig{
   254  			Name:       inputName,
   255  			ResourceID: resourceID,
   256  			JobID:      j.id,
   257  			Trigger:    trigger,
   258  		}
   259  
   260  		if pinnedVersionString.Valid {
   261  			err = json.Unmarshal([]byte(pinnedVersionString.String), &inputConfig.PinnedVersion)
   262  			if err != nil {
   263  				return nil, err
   264  			}
   265  		}
   266  
   267  		var version *atc.VersionConfig
   268  		if configVersionString.Valid {
   269  			version = &atc.VersionConfig{}
   270  			err = version.UnmarshalJSON([]byte(configVersionString.String))
   271  			if err != nil {
   272  				return nil, err
   273  			}
   274  
   275  			inputConfig.UseEveryVersion = version.Every
   276  
   277  			if version.Pinned != nil {
   278  				inputConfig.PinnedVersion = version.Pinned
   279  			}
   280  		}
   281  
   282  		passed := make(JobSet)
   283  		for _, s := range passedJobs {
   284  			if s.Valid {
   285  				passed[int(s.Int64)] = true
   286  			}
   287  		}
   288  
   289  		if len(passed) > 0 {
   290  			inputConfig.Passed = passed
   291  		}
   292  
   293  		inputs = append(inputs, inputConfig)
   294  	}
   295  
   296  	return inputs, nil
   297  }
   298  
   299  func (j *job) Inputs() ([]atc.JobInput, error) {
   300  	rows, err := psql.Select("ji.name", "r.name", "array_agg(p.name ORDER BY p.id)", "ji.trigger", "ji.version").
   301  		From("job_inputs ji").
   302  		Join("resources r ON r.id = ji.resource_id").
   303  		LeftJoin("jobs p ON p.id = ji.passed_job_id").
   304  		Where(sq.Eq{
   305  			"ji.job_id": j.id,
   306  		}).
   307  		GroupBy("ji.name, ji.job_id, r.name, ji.trigger, ji.version").
   308  		RunWith(j.conn).
   309  		Query()
   310  	if err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	var inputs []atc.JobInput
   315  	for rows.Next() {
   316  		var passedString []sql.NullString
   317  		var versionString sql.NullString
   318  		var inputName, resourceName string
   319  		var trigger bool
   320  
   321  		err = rows.Scan(&inputName, &resourceName, pq.Array(&passedString), &trigger, &versionString)
   322  		if err != nil {
   323  			return nil, err
   324  		}
   325  
   326  		var version *atc.VersionConfig
   327  		if versionString.Valid {
   328  			version = &atc.VersionConfig{}
   329  			err = version.UnmarshalJSON([]byte(versionString.String))
   330  			if err != nil {
   331  				return nil, err
   332  			}
   333  		}
   334  
   335  		var passed []string
   336  		for _, s := range passedString {
   337  			if s.Valid {
   338  				passed = append(passed, s.String)
   339  			}
   340  		}
   341  
   342  		inputs = append(inputs, atc.JobInput{
   343  			Name:     inputName,
   344  			Resource: resourceName,
   345  			Trigger:  trigger,
   346  			Version:  version,
   347  			Passed:   passed,
   348  		})
   349  	}
   350  
   351  	sort.Slice(inputs, func(p, q int) bool {
   352  		return inputs[p].Name < inputs[q].Name
   353  	})
   354  
   355  	return inputs, nil
   356  }
   357  
   358  func (j *job) Outputs() ([]atc.JobOutput, error) {
   359  	rows, err := psql.Select("jo.name", "r.name").
   360  		From("job_outputs jo").
   361  		Join("resources r ON r.id = jo.resource_id").
   362  		Where(sq.Eq{
   363  			"jo.job_id": j.id,
   364  		}).
   365  		RunWith(j.conn).
   366  		Query()
   367  	if err != nil {
   368  		return nil, err
   369  	}
   370  
   371  	var outputs []atc.JobOutput
   372  	for rows.Next() {
   373  		var output atc.JobOutput
   374  		err = rows.Scan(&output.Name, &output.Resource)
   375  		if err != nil {
   376  			return nil, err
   377  		}
   378  
   379  		outputs = append(outputs, output)
   380  	}
   381  
   382  	sort.Slice(outputs, func(p, q int) bool {
   383  		return outputs[p].Name < outputs[q].Name
   384  	})
   385  
   386  	return outputs, nil
   387  }
   388  
   389  func (j *job) Reload() (bool, error) {
   390  	row := jobsQuery.Where(sq.Eq{"j.id": j.id}).
   391  		RunWith(j.conn).
   392  		QueryRow()
   393  
   394  	err := scanJob(j, row)
   395  	if err != nil {
   396  		if err == sql.ErrNoRows {
   397  			return false, nil
   398  		}
   399  		return false, err
   400  	}
   401  
   402  	return true, nil
   403  }
   404  
   405  func (j *job) Pause() error {
   406  	return j.updatePausedJob(true)
   407  }
   408  
   409  func (j *job) Unpause() error {
   410  	return j.updatePausedJob(false)
   411  }
   412  
   413  func (j *job) FinishedAndNextBuild() (Build, Build, error) {
   414  	tx, err := j.conn.Begin()
   415  	if err != nil {
   416  		return nil, nil, err
   417  	}
   418  
   419  	defer Rollback(tx)
   420  
   421  	next, err := j.nextBuild(tx)
   422  	if err != nil {
   423  		return nil, nil, err
   424  	}
   425  
   426  	finished, err := j.finishedBuild(tx)
   427  	if err != nil {
   428  		return nil, nil, err
   429  	}
   430  
   431  	// query next build again if the build state changed between the two queries
   432  	if next != nil && finished != nil && next.ID() == finished.ID() {
   433  		next = nil
   434  
   435  		next, err = j.nextBuild(tx)
   436  		if err != nil {
   437  			return nil, nil, err
   438  		}
   439  	}
   440  
   441  	err = tx.Commit()
   442  	if err != nil {
   443  		return nil, nil, err
   444  	}
   445  
   446  	return finished, next, nil
   447  }
   448  
   449  func (j *job) UpdateFirstLoggedBuildID(newFirstLoggedBuildID int) error {
   450  	if j.firstLoggedBuildID > newFirstLoggedBuildID {
   451  		return FirstLoggedBuildIDDecreasedError{
   452  			Job:   j.name,
   453  			OldID: j.firstLoggedBuildID,
   454  			NewID: newFirstLoggedBuildID,
   455  		}
   456  	}
   457  
   458  	result, err := psql.Update("jobs").
   459  		Set("first_logged_build_id", newFirstLoggedBuildID).
   460  		Where(sq.Eq{"id": j.id}).
   461  		RunWith(j.conn).
   462  		Exec()
   463  	if err != nil {
   464  		return err
   465  	}
   466  
   467  	rowsAffected, err := result.RowsAffected()
   468  	if err != nil {
   469  		return err
   470  	}
   471  
   472  	if rowsAffected != 1 {
   473  		return NonOneRowAffectedError{rowsAffected}
   474  	}
   475  
   476  	return nil
   477  }
   478  
   479  func (j *job) BuildsWithTime(page Page) ([]Build, Pagination, error) {
   480  	newBuildsQuery := buildsQuery.Where(sq.Eq{"j.id": j.id})
   481  	newMinMaxIdQuery := minMaxIdQuery.
   482  		Join("jobs j ON b.job_id = j.id").
   483  		Where(sq.Eq{
   484  			"j.name":        j.name,
   485  			"j.pipeline_id": j.pipelineID,
   486  		})
   487  	return getBuildsWithDates(newBuildsQuery, newMinMaxIdQuery, page, j.conn, j.lockFactory)
   488  }
   489  
   490  func (j *job) Builds(page Page) ([]Build, Pagination, error) {
   491  	newBuildsQuery := buildsQuery.Where(sq.Eq{"j.id": j.id})
   492  	newMinMaxIdQuery := minMaxIdQuery.
   493  		Join("jobs j ON b.job_id = j.id").
   494  		Where(sq.Eq{
   495  			"j.name":        j.name,
   496  			"j.pipeline_id": j.pipelineID,
   497  		})
   498  
   499  	return getBuildsWithPagination(newBuildsQuery, newMinMaxIdQuery, page, j.conn, j.lockFactory)
   500  }
   501  
   502  func (j *job) Build(name string) (Build, bool, error) {
   503  	var query sq.SelectBuilder
   504  
   505  	if name == "latest" {
   506  		query = buildsQuery.
   507  			Where(sq.Eq{"b.job_id": j.id}).
   508  			OrderBy("b.id DESC").
   509  			Limit(1)
   510  	} else {
   511  		query = buildsQuery.Where(sq.Eq{
   512  			"b.job_id": j.id,
   513  			"b.name":   name,
   514  		})
   515  	}
   516  
   517  	row := query.RunWith(j.conn).QueryRow()
   518  
   519  	build := newEmptyBuild(j.conn, j.lockFactory)
   520  
   521  	err := scanBuild(build, row, j.conn.EncryptionStrategy())
   522  	if err != nil {
   523  		if err == sql.ErrNoRows {
   524  			return nil, false, nil
   525  		}
   526  		return nil, false, err
   527  	}
   528  
   529  	return build, true, nil
   530  }
   531  
   532  func (j *job) ScheduleBuild(build Build) (bool, error) {
   533  	if build.IsScheduled() {
   534  		return true, nil
   535  	}
   536  
   537  	tx, err := j.conn.Begin()
   538  	if err != nil {
   539  		return false, err
   540  	}
   541  
   542  	defer tx.Rollback()
   543  
   544  	paused, err := j.isPipelineOrJobPaused(tx)
   545  	if err != nil {
   546  		return false, err
   547  	}
   548  
   549  	if paused {
   550  		return false, nil
   551  	}
   552  
   553  	reached, err := j.isMaxInFlightReached(tx, build.ID())
   554  	if err != nil {
   555  		return false, err
   556  	}
   557  
   558  	result, err := psql.Update("jobs").
   559  		Set("max_in_flight_reached", reached).
   560  		Where(sq.Eq{
   561  			"id": j.id,
   562  		}).
   563  		RunWith(tx).
   564  		Exec()
   565  	if err != nil {
   566  		return false, err
   567  	}
   568  
   569  	rowsAffected, err := result.RowsAffected()
   570  	if err != nil {
   571  		return false, err
   572  	}
   573  
   574  	if rowsAffected != 1 {
   575  		return false, NonOneRowAffectedError{rowsAffected}
   576  	}
   577  
   578  	var scheduled bool
   579  	if !reached {
   580  		result, err = psql.Update("builds").
   581  			Set("scheduled", true).
   582  			Where(sq.Eq{"id": build.ID()}).
   583  			RunWith(tx).
   584  			Exec()
   585  		if err != nil {
   586  			return false, err
   587  		}
   588  
   589  		rowsAffected, err := result.RowsAffected()
   590  		if err != nil {
   591  			return false, err
   592  		}
   593  
   594  		if rowsAffected != 1 {
   595  			return false, NonOneRowAffectedError{rowsAffected}
   596  		}
   597  
   598  		scheduled = true
   599  	}
   600  
   601  	err = tx.Commit()
   602  	if err != nil {
   603  		return false, err
   604  	}
   605  
   606  	return scheduled, nil
   607  }
   608  
   609  func (j *job) GetFullNextBuildInputs() ([]BuildInput, bool, error) {
   610  	tx, err := j.conn.Begin()
   611  	if err != nil {
   612  		return nil, false, err
   613  	}
   614  
   615  	defer tx.Rollback()
   616  
   617  	var inputsDetermined bool
   618  	err = psql.Select("inputs_determined").
   619  		From("jobs").
   620  		Where(sq.Eq{
   621  			"id": j.id,
   622  		}).
   623  		RunWith(tx).
   624  		QueryRow().
   625  		Scan(&inputsDetermined)
   626  	if err != nil {
   627  		return nil, false, err
   628  	}
   629  
   630  	if !inputsDetermined {
   631  		return nil, false, nil
   632  	}
   633  
   634  	buildInputs, err := j.getNextBuildInputs(tx)
   635  	if err != nil {
   636  		return nil, false, err
   637  	}
   638  
   639  	err = tx.Commit()
   640  	if err != nil {
   641  		return nil, false, err
   642  	}
   643  
   644  	return buildInputs, true, nil
   645  }
   646  
   647  func (j *job) GetNextBuildInputs() ([]BuildInput, error) {
   648  	tx, err := j.conn.Begin()
   649  	if err != nil {
   650  		return nil, err
   651  	}
   652  
   653  	defer tx.Rollback()
   654  
   655  	buildInputs, err := j.getNextBuildInputs(tx)
   656  	if err != nil {
   657  		return nil, err
   658  	}
   659  
   660  	err = tx.Commit()
   661  	if err != nil {
   662  		return nil, err
   663  	}
   664  
   665  	return buildInputs, nil
   666  }
   667  
   668  func (j *job) EnsurePendingBuildExists(ctx context.Context) error {
   669  	defer tracing.FromContext(ctx).End()
   670  	spanContextJSON, err := json.Marshal(NewSpanContext(ctx))
   671  	if err != nil {
   672  		return err
   673  	}
   674  
   675  	tx, err := j.conn.Begin()
   676  	if err != nil {
   677  		return err
   678  	}
   679  
   680  	defer Rollback(tx)
   681  
   682  	buildName, err := j.getNewBuildName(tx)
   683  	if err != nil {
   684  		return err
   685  	}
   686  
   687  	rows, err := tx.Query(`
   688  		INSERT INTO builds (name, job_id, pipeline_id, team_id, status, needs_v6_migration, span_context)
   689  		SELECT $1, $2, $3, $4, 'pending', false, $5
   690  		WHERE NOT EXISTS
   691  			(SELECT id FROM builds WHERE job_id = $2 AND status = 'pending')
   692  		RETURNING id
   693  	`, buildName, j.id, j.pipelineID, j.teamID, string(spanContextJSON))
   694  	if err != nil {
   695  		return err
   696  	}
   697  
   698  	defer Close(rows)
   699  
   700  	if rows.Next() {
   701  		var buildID int
   702  		err := rows.Scan(&buildID)
   703  		if err != nil {
   704  			return err
   705  		}
   706  
   707  		err = rows.Close()
   708  		if err != nil {
   709  			return err
   710  		}
   711  
   712  		err = createBuildEventSeq(tx, buildID)
   713  		if err != nil {
   714  			return err
   715  		}
   716  
   717  		latestNonRerunID, err := latestCompletedNonRerunBuild(tx, j.id)
   718  		if err != nil {
   719  			return err
   720  		}
   721  
   722  		err = updateNextBuildForJob(tx, j.id, latestNonRerunID)
   723  		if err != nil {
   724  			return err
   725  		}
   726  
   727  		return tx.Commit()
   728  	}
   729  
   730  	return nil
   731  }
   732  
   733  func (j *job) GetPendingBuilds() ([]Build, error) {
   734  	builds := []Build{}
   735  
   736  	row := jobsQuery.Where(sq.Eq{
   737  		"j.name":        j.name,
   738  		"j.active":      true,
   739  		"j.pipeline_id": j.pipelineID,
   740  	}).RunWith(j.conn).QueryRow()
   741  
   742  	job := newEmptyJob(j.conn, j.lockFactory)
   743  	err := scanJob(job, row)
   744  	if err != nil {
   745  		return nil, err
   746  	}
   747  
   748  	rows, err := buildsQuery.
   749  		Where(sq.Eq{
   750  			"b.job_id": j.id,
   751  			"b.status": BuildStatusPending,
   752  		}).
   753  		OrderBy("COALESCE(b.rerun_of, b.id) ASC, b.id ASC").
   754  		RunWith(j.conn).
   755  		Query()
   756  	if err != nil {
   757  		return nil, err
   758  	}
   759  
   760  	defer Close(rows)
   761  
   762  	for rows.Next() {
   763  		build := newEmptyBuild(j.conn, j.lockFactory)
   764  		err = scanBuild(build, rows, j.conn.EncryptionStrategy())
   765  		if err != nil {
   766  			return nil, err
   767  		}
   768  
   769  		builds = append(builds, build)
   770  	}
   771  
   772  	return builds, nil
   773  }
   774  
   775  func (j *job) CreateBuild() (Build, error) {
   776  	tx, err := j.conn.Begin()
   777  	if err != nil {
   778  		return nil, err
   779  	}
   780  
   781  	defer Rollback(tx)
   782  
   783  	buildName, err := j.getNewBuildName(tx)
   784  	if err != nil {
   785  		return nil, err
   786  	}
   787  
   788  	build := newEmptyBuild(j.conn, j.lockFactory)
   789  	err = createBuild(tx, build, map[string]interface{}{
   790  		"name":               buildName,
   791  		"job_id":             j.id,
   792  		"pipeline_id":        j.pipelineID,
   793  		"team_id":            j.teamID,
   794  		"status":             BuildStatusPending,
   795  		"manually_triggered": true,
   796  	})
   797  	if err != nil {
   798  		return nil, err
   799  	}
   800  
   801  	latestNonRerunID, err := latestCompletedNonRerunBuild(tx, j.id)
   802  	if err != nil {
   803  		return nil, err
   804  	}
   805  
   806  	err = updateNextBuildForJob(tx, j.id, latestNonRerunID)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  
   811  	err = requestSchedule(tx, j.id)
   812  	if err != nil {
   813  		return nil, err
   814  	}
   815  
   816  	err = tx.Commit()
   817  	if err != nil {
   818  		return nil, err
   819  	}
   820  
   821  	return build, nil
   822  }
   823  
   824  func (j *job) RerunBuild(buildToRerun Build) (Build, error) {
   825  	for {
   826  		rerunBuild, err := j.tryRerunBuild(buildToRerun)
   827  		if err != nil {
   828  			if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == pqUniqueViolationErrCode {
   829  				continue
   830  			}
   831  
   832  			return nil, err
   833  		}
   834  
   835  		return rerunBuild, nil
   836  	}
   837  }
   838  
   839  func (j *job) tryRerunBuild(buildToRerun Build) (Build, error) {
   840  	tx, err := j.conn.Begin()
   841  	if err != nil {
   842  		return nil, err
   843  	}
   844  
   845  	defer Rollback(tx)
   846  
   847  	buildToRerunID := buildToRerun.ID()
   848  	if buildToRerun.RerunOf() != 0 {
   849  		buildToRerunID = buildToRerun.RerunOf()
   850  	}
   851  
   852  	rerunBuildName, rerunNumber, err := j.getNewRerunBuildName(tx, buildToRerunID)
   853  	if err != nil {
   854  		return nil, err
   855  	}
   856  
   857  	rerunBuild := newEmptyBuild(j.conn, j.lockFactory)
   858  	err = createBuild(tx, rerunBuild, map[string]interface{}{
   859  		"name":         rerunBuildName,
   860  		"job_id":       j.id,
   861  		"pipeline_id":  j.pipelineID,
   862  		"team_id":      j.teamID,
   863  		"status":       BuildStatusPending,
   864  		"rerun_of":     buildToRerunID,
   865  		"rerun_number": rerunNumber,
   866  	})
   867  	if err != nil {
   868  		return nil, err
   869  	}
   870  
   871  	latestNonRerunID, err := latestCompletedNonRerunBuild(tx, j.id)
   872  	if err != nil {
   873  		return nil, err
   874  	}
   875  
   876  	err = updateNextBuildForJob(tx, j.id, latestNonRerunID)
   877  	if err != nil {
   878  		return nil, err
   879  	}
   880  
   881  	err = requestSchedule(tx, j.id)
   882  	if err != nil {
   883  		return nil, err
   884  	}
   885  
   886  	err = tx.Commit()
   887  	if err != nil {
   888  		return nil, err
   889  	}
   890  
   891  	return rerunBuild, nil
   892  }
   893  
   894  func (j *job) ClearTaskCache(stepName string, cachePath string) (int64, error) {
   895  	tx, err := j.conn.Begin()
   896  	if err != nil {
   897  		return 0, err
   898  	}
   899  
   900  	defer Rollback(tx)
   901  
   902  	var sqlBuilder sq.DeleteBuilder = psql.Delete("task_caches").
   903  		Where(sq.Eq{
   904  			"job_id":    j.id,
   905  			"step_name": stepName,
   906  		})
   907  
   908  	if len(cachePath) > 0 {
   909  		sqlBuilder = sqlBuilder.Where(sq.Eq{"path": cachePath})
   910  	}
   911  
   912  	sqlResult, err := sqlBuilder.
   913  		RunWith(tx).
   914  		Exec()
   915  
   916  	if err != nil {
   917  		return 0, err
   918  	}
   919  
   920  	rowsDeleted, err := sqlResult.RowsAffected()
   921  
   922  	if err != nil {
   923  		return 0, err
   924  	}
   925  
   926  	return rowsDeleted, tx.Commit()
   927  }
   928  
   929  func (j *job) AcquireSchedulingLock(logger lager.Logger) (lock.Lock, bool, error) {
   930  	return j.lockFactory.Acquire(
   931  		logger.Session("lock", lager.Data{
   932  			"job":      j.name,
   933  			"pipeline": j.pipelineName,
   934  		}),
   935  		lock.NewJobSchedulingLockID(j.id),
   936  	)
   937  }
   938  
   939  func (j *job) isMaxInFlightReached(tx Tx, buildID int) (bool, error) {
   940  	if j.maxInFlight == 0 {
   941  		return false, nil
   942  	}
   943  
   944  	serialGroups, err := j.getSerialGroups(tx)
   945  	if err != nil {
   946  		return false, err
   947  	}
   948  
   949  	builds, err := j.getRunningBuildsBySerialGroup(tx, serialGroups)
   950  	if err != nil {
   951  		return false, err
   952  	}
   953  
   954  	if len(builds) >= j.maxInFlight {
   955  		return true, nil
   956  	}
   957  
   958  	nextMostPendingBuild, found, err := j.getNextPendingBuildBySerialGroup(tx, serialGroups)
   959  	if err != nil {
   960  		return false, err
   961  	}
   962  
   963  	if !found {
   964  		return true, nil
   965  	}
   966  
   967  	if nextMostPendingBuild.ID() != buildID {
   968  		return true, nil
   969  	}
   970  
   971  	return false, nil
   972  }
   973  
   974  func (j *job) getSerialGroups(tx Tx) ([]string, error) {
   975  	rows, err := psql.Select("serial_group").
   976  		From("jobs_serial_groups").
   977  		Where(sq.Eq{
   978  			"job_id": j.id,
   979  		}).
   980  		RunWith(tx).
   981  		Query()
   982  	if err != nil {
   983  		return nil, err
   984  	}
   985  
   986  	var serialGroups []string
   987  	for rows.Next() {
   988  		var serialGroup string
   989  		err = rows.Scan(&serialGroup)
   990  		if err != nil {
   991  			return nil, err
   992  		}
   993  
   994  		serialGroups = append(serialGroups, serialGroup)
   995  	}
   996  
   997  	return serialGroups, nil
   998  }
   999  
  1000  func (j *job) RequestSchedule() error {
  1001  	tx, err := j.conn.Begin()
  1002  	if err != nil {
  1003  		return err
  1004  	}
  1005  
  1006  	defer tx.Rollback()
  1007  
  1008  	err = requestSchedule(tx, j.id)
  1009  	if err != nil {
  1010  		return err
  1011  	}
  1012  
  1013  	return tx.Commit()
  1014  }
  1015  
  1016  func (j *job) UpdateLastScheduled(requestedTime time.Time) error {
  1017  	_, err := psql.Update("jobs").
  1018  		Set("last_scheduled", requestedTime).
  1019  		Where(sq.Eq{
  1020  			"id": j.id,
  1021  		}).
  1022  		RunWith(j.conn).
  1023  		Exec()
  1024  
  1025  	return err
  1026  }
  1027  
  1028  func (j *job) getRunningBuildsBySerialGroup(tx Tx, serialGroups []string) ([]Build, error) {
  1029  	rows, err := buildsQuery.Options(`DISTINCT ON (b.id)`).
  1030  		Join(`jobs_serial_groups jsg ON j.id = jsg.job_id`).
  1031  		Where(sq.Eq{
  1032  			"jsg.serial_group": serialGroups,
  1033  			"j.pipeline_id":    j.pipelineID,
  1034  		}).
  1035  		Where(sq.Eq{"b.completed": false, "b.scheduled": true}).
  1036  		RunWith(tx).
  1037  		Query()
  1038  	if err != nil {
  1039  		return nil, err
  1040  	}
  1041  
  1042  	defer Close(rows)
  1043  
  1044  	bs := []Build{}
  1045  
  1046  	for rows.Next() {
  1047  		build := newEmptyBuild(j.conn, j.lockFactory)
  1048  		err = scanBuild(build, rows, j.conn.EncryptionStrategy())
  1049  		if err != nil {
  1050  			return nil, err
  1051  		}
  1052  
  1053  		bs = append(bs, build)
  1054  	}
  1055  
  1056  	return bs, nil
  1057  }
  1058  
  1059  func (j *job) getNextPendingBuildBySerialGroup(tx Tx, serialGroups []string) (Build, bool, error) {
  1060  	subQuery, params, err := buildsQuery.Options(`DISTINCT ON (b.id)`).
  1061  		Join(`jobs_serial_groups jsg ON j.id = jsg.job_id`).
  1062  		Where(sq.Eq{
  1063  			"jsg.serial_group":    serialGroups,
  1064  			"b.status":            BuildStatusPending,
  1065  			"j.paused":            false,
  1066  			"j.inputs_determined": true,
  1067  			"j.pipeline_id":       j.pipelineID}).
  1068  		ToSql()
  1069  	if err != nil {
  1070  		return nil, false, err
  1071  	}
  1072  
  1073  	row := tx.QueryRow(`
  1074  			SELECT * FROM (`+subQuery+`) j
  1075  			ORDER BY COALESCE(rerun_of, id) ASC, id ASC
  1076  			LIMIT 1`, params...)
  1077  
  1078  	build := newEmptyBuild(j.conn, j.lockFactory)
  1079  	err = scanBuild(build, row, j.conn.EncryptionStrategy())
  1080  	if err != nil {
  1081  		if err == sql.ErrNoRows {
  1082  			return nil, false, nil
  1083  		}
  1084  		return nil, false, err
  1085  	}
  1086  
  1087  	return build, true, nil
  1088  }
  1089  
  1090  func (j *job) updatePausedJob(pause bool) error {
  1091  	result, err := psql.Update("jobs").
  1092  		Set("paused", pause).
  1093  		Where(sq.Eq{"id": j.id}).
  1094  		RunWith(j.conn).
  1095  		Exec()
  1096  	if err != nil {
  1097  		return err
  1098  	}
  1099  
  1100  	rowsAffected, err := result.RowsAffected()
  1101  	if err != nil {
  1102  		return err
  1103  	}
  1104  
  1105  	if rowsAffected != 1 {
  1106  		return NonOneRowAffectedError{rowsAffected}
  1107  	}
  1108  
  1109  	if !pause {
  1110  		err = j.RequestSchedule()
  1111  		if err != nil {
  1112  			return err
  1113  		}
  1114  	}
  1115  
  1116  	return nil
  1117  }
  1118  
  1119  func (j *job) getNewBuildName(tx Tx) (string, error) {
  1120  	var buildName string
  1121  	err := psql.Update("jobs").
  1122  		Set("build_number_seq", sq.Expr("build_number_seq + 1")).
  1123  		Where(sq.Eq{
  1124  			"name":        j.name,
  1125  			"pipeline_id": j.pipelineID,
  1126  		}).
  1127  		Suffix("RETURNING build_number_seq").
  1128  		RunWith(tx).
  1129  		QueryRow().
  1130  		Scan(&buildName)
  1131  
  1132  	return buildName, err
  1133  }
  1134  
  1135  func (j *job) SaveNextInputMapping(inputMapping InputMapping, inputsDetermined bool) error {
  1136  	tx, err := j.conn.Begin()
  1137  	if err != nil {
  1138  		return err
  1139  	}
  1140  
  1141  	defer Rollback(tx)
  1142  
  1143  	_, err = psql.Update("jobs").
  1144  		Set("inputs_determined", inputsDetermined).
  1145  		Where(sq.Eq{"id": j.id}).
  1146  		RunWith(tx).
  1147  		Exec()
  1148  	if err != nil {
  1149  		return err
  1150  	}
  1151  
  1152  	_, err = psql.Delete("next_build_inputs").
  1153  		Where(sq.Eq{"job_id": j.id}).
  1154  		RunWith(tx).Exec()
  1155  	if err != nil {
  1156  		return err
  1157  	}
  1158  
  1159  	builder := psql.Insert("next_build_inputs").
  1160  		Columns("input_name", "job_id", "version_md5", "resource_id", "first_occurrence", "resolve_error")
  1161  
  1162  	for inputName, inputResult := range inputMapping {
  1163  		var resolveError sql.NullString
  1164  		var firstOccurrence sql.NullBool
  1165  		var versionMD5 sql.NullString
  1166  		var resourceID sql.NullInt64
  1167  
  1168  		if inputResult.ResolveError != "" {
  1169  			resolveError = sql.NullString{String: string(inputResult.ResolveError), Valid: true}
  1170  		} else {
  1171  			if inputResult.Input == nil {
  1172  				return InputVersionEmptyError{inputName}
  1173  			}
  1174  
  1175  			firstOccurrence = sql.NullBool{Bool: inputResult.Input.FirstOccurrence, Valid: true}
  1176  			resourceID = sql.NullInt64{Int64: int64(inputResult.Input.ResourceID), Valid: true}
  1177  			versionMD5 = sql.NullString{String: string(inputResult.Input.Version), Valid: true}
  1178  		}
  1179  
  1180  		builder = builder.Values(inputName, j.id, versionMD5, resourceID, firstOccurrence, resolveError)
  1181  	}
  1182  
  1183  	if len(inputMapping) != 0 {
  1184  		_, err = builder.RunWith(tx).Exec()
  1185  		if err != nil {
  1186  			return err
  1187  		}
  1188  	}
  1189  
  1190  	_, err = psql.Delete("next_build_pipes").
  1191  		Where(sq.Eq{"to_job_id": j.id}).
  1192  		RunWith(tx).Exec()
  1193  	if err != nil {
  1194  		return err
  1195  	}
  1196  
  1197  	pipesBuilder := psql.Insert("next_build_pipes").
  1198  		Columns("to_job_id", "from_build_id")
  1199  
  1200  	insertPipes := false
  1201  	for _, inputVersion := range inputMapping {
  1202  		for _, buildID := range inputVersion.PassedBuildIDs {
  1203  			pipesBuilder = pipesBuilder.Values(j.ID(), buildID)
  1204  			insertPipes = true
  1205  		}
  1206  	}
  1207  
  1208  	if insertPipes {
  1209  		_, err = pipesBuilder.Suffix("ON CONFLICT DO NOTHING").RunWith(tx).Exec()
  1210  		if err != nil {
  1211  			return err
  1212  		}
  1213  	}
  1214  
  1215  	return tx.Commit()
  1216  }
  1217  
  1218  func (j *job) nextBuild(tx Tx) (Build, error) {
  1219  	var next Build
  1220  
  1221  	row := buildsQuery.
  1222  		Where(sq.Eq{"j.id": j.id}).
  1223  		Where(sq.Expr("b.id = j.next_build_id")).
  1224  		RunWith(tx).
  1225  		QueryRow()
  1226  
  1227  	nextBuild := newEmptyBuild(j.conn, j.lockFactory)
  1228  	err := scanBuild(nextBuild, row, j.conn.EncryptionStrategy())
  1229  	if err == nil {
  1230  		next = nextBuild
  1231  	} else if err != sql.ErrNoRows {
  1232  		return nil, err
  1233  	}
  1234  
  1235  	return next, nil
  1236  }
  1237  
  1238  func (j *job) finishedBuild(tx Tx) (Build, error) {
  1239  	var finished Build
  1240  
  1241  	row := buildsQuery.
  1242  		Where(sq.Eq{"j.id": j.id}).
  1243  		Where(sq.Expr("b.id = j.latest_completed_build_id")).
  1244  		RunWith(tx).
  1245  		QueryRow()
  1246  
  1247  	finishedBuild := newEmptyBuild(j.conn, j.lockFactory)
  1248  	err := scanBuild(finishedBuild, row, j.conn.EncryptionStrategy())
  1249  	if err == nil {
  1250  		finished = finishedBuild
  1251  	} else if err != sql.ErrNoRows {
  1252  		return nil, err
  1253  	}
  1254  
  1255  	return finished, nil
  1256  }
  1257  
  1258  func (j *job) getNewRerunBuildName(tx Tx, buildID int) (string, int, error) {
  1259  	var rerunNum int
  1260  	var buildName string
  1261  	err := psql.Select("b.name", "( SELECT COUNT(id) FROM builds WHERE rerun_of = b.id )").
  1262  		From("builds b").
  1263  		Where(sq.Eq{
  1264  			"b.id": buildID,
  1265  		}).
  1266  		RunWith(tx).
  1267  		QueryRow().
  1268  		Scan(&buildName, &rerunNum)
  1269  	if err != nil {
  1270  		return "", 0, err
  1271  	}
  1272  
  1273  	// increment the rerun number
  1274  	rerunNum++
  1275  
  1276  	return buildName + "." + strconv.Itoa(rerunNum), rerunNum, err
  1277  }
  1278  
  1279  func (j *job) getNextBuildInputs(tx Tx) ([]BuildInput, error) {
  1280  	rows, err := psql.Select("i.input_name, i.first_occurrence, i.resource_id, v.version, i.resolve_error, v.span_context").
  1281  		From("next_build_inputs i").
  1282  		LeftJoin("resources r ON r.id = i.resource_id").
  1283  		LeftJoin("resource_config_versions v ON v.version_md5 = i.version_md5 AND r.resource_config_scope_id = v.resource_config_scope_id").
  1284  		Where(sq.Eq{
  1285  			"i.job_id": j.id,
  1286  		}).
  1287  		RunWith(tx).
  1288  		Query()
  1289  	if err != nil {
  1290  		return nil, err
  1291  	}
  1292  
  1293  	buildInputs := []BuildInput{}
  1294  	for rows.Next() {
  1295  		var (
  1296  			inputName       string
  1297  			firstOcc        sql.NullBool
  1298  			versionBlob     sql.NullString
  1299  			resID           sql.NullString
  1300  			resolveErr      sql.NullString
  1301  			spanContextJSON sql.NullString
  1302  		)
  1303  
  1304  		err := rows.Scan(&inputName, &firstOcc, &resID, &versionBlob, &resolveErr, &spanContextJSON)
  1305  		if err != nil {
  1306  			return nil, err
  1307  		}
  1308  
  1309  		var version atc.Version
  1310  		if versionBlob.Valid {
  1311  			err = json.Unmarshal([]byte(versionBlob.String), &version)
  1312  			if err != nil {
  1313  				return nil, err
  1314  			}
  1315  		}
  1316  
  1317  		var firstOccurrence bool
  1318  		if firstOcc.Valid {
  1319  			firstOccurrence = firstOcc.Bool
  1320  		}
  1321  
  1322  		var resourceID int
  1323  		if resID.Valid {
  1324  			resourceID, err = strconv.Atoi(resID.String)
  1325  			if err != nil {
  1326  				return nil, err
  1327  			}
  1328  		}
  1329  
  1330  		var resolveError string
  1331  		if resolveErr.Valid {
  1332  			resolveError = resolveErr.String
  1333  		}
  1334  
  1335  		var spanContext SpanContext
  1336  		if spanContextJSON.Valid {
  1337  			err = json.Unmarshal([]byte(spanContextJSON.String), &spanContext)
  1338  			if err != nil {
  1339  				return nil, err
  1340  			}
  1341  		}
  1342  
  1343  		buildInputs = append(buildInputs, BuildInput{
  1344  			Name:            inputName,
  1345  			ResourceID:      resourceID,
  1346  			Version:         version,
  1347  			FirstOccurrence: firstOccurrence,
  1348  			ResolveError:    resolveError,
  1349  			Context:         spanContext,
  1350  		})
  1351  	}
  1352  
  1353  	return buildInputs, err
  1354  }
  1355  
  1356  func (j *job) isPipelineOrJobPaused(tx Tx) (bool, error) {
  1357  	if j.paused {
  1358  		return true, nil
  1359  	}
  1360  
  1361  	var paused bool
  1362  	err := psql.Select("paused").
  1363  		From("pipelines").
  1364  		Where(sq.Eq{"id": j.pipelineID}).
  1365  		RunWith(tx).
  1366  		QueryRow().
  1367  		Scan(&paused)
  1368  	if err != nil {
  1369  		return false, err
  1370  	}
  1371  
  1372  	return paused, nil
  1373  }
  1374  
  1375  func scanJob(j *job, row scannable) error {
  1376  	var (
  1377  		config               sql.NullString
  1378  		nonce                sql.NullString
  1379  		pipelineInstanceVars sql.NullString
  1380  	)
  1381  
  1382  	err := row.Scan(&j.id, &j.name, &config, &j.paused, &j.public, &j.firstLoggedBuildID, &j.pipelineID, &j.pipelineName, &pipelineInstanceVars, &j.teamID, &j.teamName, &nonce, pq.Array(&j.tags), &j.hasNewInputs, &j.scheduleRequestedTime, &j.maxInFlight, &j.disableManualTrigger)
  1383  	if err != nil {
  1384  		return err
  1385  	}
  1386  
  1387  	if nonce.Valid {
  1388  		j.nonce = &nonce.String
  1389  	}
  1390  
  1391  	if config.Valid {
  1392  		j.rawConfig = &config.String
  1393  	}
  1394  
  1395  	if pipelineInstanceVars.Valid {
  1396  		err = json.Unmarshal([]byte(pipelineInstanceVars.String), &j.pipelineInstanceVars)
  1397  		if err != nil {
  1398  			return err
  1399  		}
  1400  	}
  1401  
  1402  	return nil
  1403  }
  1404  
  1405  func scanJobs(conn Conn, lockFactory lock.LockFactory, rows *sql.Rows) (Jobs, error) {
  1406  	defer Close(rows)
  1407  
  1408  	jobs := Jobs{}
  1409  
  1410  	for rows.Next() {
  1411  		job := newEmptyJob(conn, lockFactory)
  1412  		err := scanJob(job, rows)
  1413  		if err != nil {
  1414  			return nil, err
  1415  		}
  1416  
  1417  		jobs = append(jobs, job)
  1418  	}
  1419  
  1420  	return jobs, nil
  1421  }
  1422  
  1423  func requestSchedule(tx Tx, jobID int) error {
  1424  	result, err := psql.Update("jobs").
  1425  		Set("schedule_requested", sq.Expr("now()")).
  1426  		Where(sq.Eq{
  1427  			"id": jobID,
  1428  		}).
  1429  		RunWith(tx).
  1430  		Exec()
  1431  	if err != nil {
  1432  		return err
  1433  	}
  1434  
  1435  	rowsAffected, err := result.RowsAffected()
  1436  	if err != nil {
  1437  		return err
  1438  	}
  1439  
  1440  	if rowsAffected != 1 {
  1441  		return NonOneRowAffectedError{rowsAffected}
  1442  	}
  1443  
  1444  	return nil
  1445  }
  1446  
  1447  // The SELECT query orders the jobs for updating to prevent deadlocking.
  1448  // Updating multiple rows using a SELECT subquery does not preserve the same
  1449  // order for the updates, which can lead to deadlocking.
  1450  func requestScheduleOnDownstreamJobs(tx Tx, jobID int) error {
  1451  	rows, err := psql.Select("DISTINCT job_id").
  1452  		From("job_inputs").
  1453  		Where(sq.Eq{
  1454  			"passed_job_id": jobID,
  1455  		}).
  1456  		OrderBy("job_id DESC").
  1457  		RunWith(tx).
  1458  		Query()
  1459  	if err != nil {
  1460  		return err
  1461  	}
  1462  
  1463  	var jobIDs []int
  1464  	for rows.Next() {
  1465  		var id int
  1466  		err = rows.Scan(&id)
  1467  		if err != nil {
  1468  			return err
  1469  		}
  1470  
  1471  		jobIDs = append(jobIDs, id)
  1472  	}
  1473  
  1474  	for _, jID := range jobIDs {
  1475  		_, err := psql.Update("jobs").
  1476  			Set("schedule_requested", sq.Expr("now()")).
  1477  			Where(sq.Eq{
  1478  				"id": jID,
  1479  			}).
  1480  			RunWith(tx).
  1481  			Exec()
  1482  		if err != nil {
  1483  			return err
  1484  		}
  1485  	}
  1486  
  1487  	return nil
  1488  }