github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/db/versions_db.go (about) 1 package db 2 3 import ( 4 "context" 5 "database/sql" 6 "encoding/json" 7 "fmt" 8 "sort" 9 "strconv" 10 "time" 11 12 sq "github.com/Masterminds/squirrel" 13 "github.com/pf-qiu/concourse/v6/atc" 14 "github.com/pf-qiu/concourse/v6/tracing" 15 gocache "github.com/patrickmn/go-cache" 16 "go.opentelemetry.io/otel/api/trace" 17 "go.opentelemetry.io/otel/label" 18 ) 19 20 type VersionsDB struct { 21 conn Conn 22 limitRows int 23 24 cache *gocache.Cache 25 } 26 27 func NewVersionsDB(conn Conn, limitRows int, cache *gocache.Cache) VersionsDB { 28 return VersionsDB{ 29 conn: conn, 30 limitRows: limitRows, 31 cache: cache, 32 } 33 } 34 35 func (versions VersionsDB) IsFirstOccurrence(ctx context.Context, jobID int, inputName string, versionMD5 ResourceVersion, resourceId int) (bool, error) { 36 var exists bool 37 err := versions.conn.QueryRowContext(ctx, ` 38 WITH builds_of_job AS ( 39 SELECT id FROM builds WHERE job_id = $1 40 ) 41 SELECT EXISTS ( 42 SELECT 1 43 FROM build_resource_config_version_inputs i 44 JOIN builds_of_job b ON b.id = i.build_id 45 WHERE i.name = $2 46 AND i.version_md5 = $3 47 AND i.resource_id = $4 48 )`, jobID, inputName, versionMD5, resourceId). 49 Scan(&exists) 50 if err != nil { 51 return false, err 52 } 53 54 return !exists, nil 55 } 56 57 func (versions VersionsDB) VersionIsDisabled(ctx context.Context, resourceID int, versionMD5 ResourceVersion) (bool, error) { 58 var exists bool 59 err := versions.conn.QueryRow(` 60 SELECT EXISTS ( 61 SELECT 1 62 FROM resource_disabled_versions 63 WHERE resource_id = $1 64 AND version_md5 = $2 65 )`, resourceID, versionMD5). 66 Scan(&exists) 67 if err != nil { 68 return false, err 69 } 70 71 return exists, nil 72 } 73 74 func (versions VersionsDB) LatestVersionOfResource(ctx context.Context, resourceID int) (ResourceVersion, bool, error) { 75 tx, err := versions.conn.Begin() 76 if err != nil { 77 return "", false, err 78 } 79 80 defer tx.Rollback() 81 82 version, found, err := versions.latestVersionOfResource(ctx, tx, resourceID) 83 if err != nil { 84 return "", false, err 85 } 86 87 if !found { 88 return "", false, nil 89 } 90 91 err = tx.Commit() 92 if err != nil { 93 return "", false, err 94 } 95 96 return version, true, nil 97 } 98 99 func (versions VersionsDB) SuccessfulBuilds(ctx context.Context, jobID int) PaginatedBuilds { 100 builder := psql.Select("id", "rerun_of"). 101 From("builds"). 102 Where(sq.Eq{ 103 "job_id": jobID, 104 "status": "succeeded", 105 }). 106 OrderBy("COALESCE(rerun_of, id) DESC, id DESC") 107 108 return PaginatedBuilds{ 109 builder: builder, 110 column: "id", 111 jobID: jobID, 112 113 limitRows: versions.limitRows, 114 conn: versions.conn, 115 } 116 } 117 118 func (versions VersionsDB) SuccessfulBuildsVersionConstrained( 119 ctx context.Context, 120 jobID int, 121 constrainingCandidates map[string][]string, 122 ) (PaginatedBuilds, error) { 123 versionsJSON, err := json.Marshal(constrainingCandidates) 124 if err != nil { 125 return PaginatedBuilds{}, err 126 } 127 128 builder := psql.Select("build_id", "rerun_of"). 129 From("successful_build_outputs"). 130 Where(sq.Expr("outputs @> ?::jsonb", versionsJSON)). 131 Where(sq.Eq{ 132 "job_id": jobID, 133 }). 134 OrderBy("COALESCE(rerun_of, build_id) DESC, build_id DESC") 135 136 return PaginatedBuilds{ 137 builder: builder, 138 column: "build_id", 139 jobID: jobID, 140 141 limitRows: versions.limitRows, 142 conn: versions.conn, 143 }, nil 144 } 145 146 type resourceOutputs struct { 147 ResourceID int 148 Versions []string 149 } 150 151 func (versions VersionsDB) SuccessfulBuildOutputs(ctx context.Context, buildID int) ([]AlgorithmVersion, error) { 152 cacheKey := fmt.Sprintf("o%d", buildID) 153 154 c, found := versions.cache.Get(cacheKey) 155 if found { 156 return c.([]AlgorithmVersion), nil 157 } 158 159 var outputsJSON string 160 err := psql.Select("outputs"). 161 From("successful_build_outputs"). 162 Where(sq.Eq{"build_id": buildID}). 163 RunWith(versions.conn). 164 QueryRowContext(ctx). 165 Scan(&outputsJSON) 166 if err != nil { 167 if err == sql.ErrNoRows { 168 outputsJSON, err = versions.migrateSingle(ctx, buildID) 169 if err != nil { 170 return nil, err 171 } 172 } else { 173 return nil, err 174 } 175 } 176 177 outputs := map[string][]string{} 178 err = json.Unmarshal([]byte(outputsJSON), &outputs) 179 if err != nil { 180 return nil, err 181 } 182 183 byResourceID := []resourceOutputs{} 184 for resourceIDStr, versions := range outputs { 185 resourceID, err := strconv.Atoi(resourceIDStr) 186 if err != nil { 187 return nil, err 188 } 189 190 byResourceID = append(byResourceID, resourceOutputs{ 191 ResourceID: resourceID, 192 Versions: versions, 193 }) 194 } 195 196 sort.Slice(byResourceID, func(i, j int) bool { 197 return byResourceID[i].ResourceID < byResourceID[j].ResourceID 198 }) 199 200 algorithmOutputs := []AlgorithmVersion{} 201 for _, outputs := range byResourceID { 202 for _, version := range outputs.Versions { 203 algorithmOutputs = append(algorithmOutputs, AlgorithmVersion{ 204 ResourceID: outputs.ResourceID, 205 Version: ResourceVersion(version), 206 }) 207 } 208 } 209 210 versions.cache.Set(cacheKey, algorithmOutputs, time.Hour) 211 212 return algorithmOutputs, nil 213 } 214 215 func (versions VersionsDB) VersionExists(ctx context.Context, resourceID int, versionMD5 ResourceVersion) (bool, error) { 216 var exists bool 217 err := versions.conn.QueryRowContext(ctx, ` 218 SELECT EXISTS ( 219 SELECT 1 220 FROM resource_config_versions v 221 JOIN resources r ON r.resource_config_scope_id = v.resource_config_scope_id 222 WHERE r.id = $1 223 AND v.version_md5 = $2 224 )`, resourceID, versionMD5). 225 Scan(&exists) 226 if err != nil { 227 return false, err 228 } 229 230 return exists, nil 231 } 232 233 func (versions VersionsDB) FindVersionOfResource(ctx context.Context, resourceID int, v atc.Version) (ResourceVersion, bool, error) { 234 versionJSON, err := json.Marshal(v) 235 if err != nil { 236 return "", false, nil 237 } 238 239 cacheKey := fmt.Sprintf("v%d-%s", resourceID, versionJSON) 240 241 c, found := versions.cache.Get(cacheKey) 242 if found { 243 return c.(ResourceVersion), true, nil 244 } 245 246 var version ResourceVersion 247 err = psql.Select("rcv.version_md5"). 248 From("resource_config_versions rcv"). 249 Join("resources r ON r.resource_config_scope_id = rcv.resource_config_scope_id"). 250 Where(sq.Eq{ 251 "r.id": resourceID, 252 }). 253 Where(sq.Expr("rcv.version @> ?", versionJSON)). 254 RunWith(versions.conn). 255 QueryRowContext(ctx). 256 Scan(&version) 257 if err != nil { 258 if err == sql.ErrNoRows { 259 return "", false, nil 260 } 261 return "", false, err 262 } 263 264 versions.cache.Set(cacheKey, version, time.Hour) 265 266 return version, true, err 267 } 268 269 func (versions VersionsDB) NextEveryVersion(ctx context.Context, jobID int, resourceID int) (ResourceVersion, bool, bool, error) { 270 tx, err := versions.conn.Begin() 271 if err != nil { 272 return "", false, false, err 273 } 274 275 defer tx.Rollback() 276 277 var checkOrder int 278 err = tx.QueryRowContext(ctx, ` 279 SELECT rcv.check_order 280 FROM resource_config_versions rcv 281 CROSS JOIN LATERAL ( 282 SELECT i.build_id 283 FROM build_resource_config_version_inputs i 284 CROSS JOIN LATERAL ( 285 SELECT b.id 286 FROM builds b 287 WHERE b.job_id = $1 288 AND i.build_id = b.id 289 LIMIT 1 290 ) AS build 291 WHERE i.resource_id = $2 292 AND i.version_md5 = rcv.version_md5 293 LIMIT 1 294 ) AS inputs 295 WHERE rcv.resource_config_scope_id = (SELECT resource_config_scope_id FROM resources WHERE id = $2) 296 ORDER BY rcv.check_order DESC 297 LIMIT 1;`, jobID, resourceID).Scan(&checkOrder) 298 if err != nil { 299 if err == sql.ErrNoRows { 300 version, found, err := versions.latestVersionOfResource(ctx, tx, resourceID) 301 if err != nil { 302 return "", false, false, err 303 } 304 305 if !found { 306 return "", false, false, nil 307 } 308 309 err = tx.Commit() 310 if err != nil { 311 return "", false, false, err 312 } 313 314 return version, false, true, nil 315 } 316 317 return "", false, false, err 318 } 319 320 var nextVersion ResourceVersion 321 rows, err := psql.Select("rcv.version_md5"). 322 From("resource_config_versions rcv"). 323 Where(sq.Expr("rcv.resource_config_scope_id = (SELECT resource_config_scope_id FROM resources WHERE id = ?)", resourceID)). 324 Where(sq.Expr("NOT EXISTS (SELECT 1 FROM resource_disabled_versions WHERE resource_id = ? AND version_md5 = rcv.version_md5)", resourceID)). 325 Where(sq.Gt{"rcv.check_order": checkOrder}). 326 OrderBy("rcv.check_order ASC"). 327 Limit(2). 328 RunWith(tx). 329 QueryContext(ctx) 330 if err != nil { 331 return "", false, false, err 332 } 333 334 if rows.Next() { 335 err = rows.Scan(&nextVersion) 336 if err != nil { 337 return "", false, false, err 338 } 339 340 var hasNext bool 341 if rows.Next() { 342 hasNext = true 343 } 344 345 rows.Close() 346 347 err = tx.Commit() 348 if err != nil { 349 return "", false, false, err 350 } 351 352 return nextVersion, hasNext, true, nil 353 } 354 355 err = psql.Select("rcv.version_md5"). 356 From("resource_config_versions rcv"). 357 Where(sq.Expr("rcv.resource_config_scope_id = (SELECT resource_config_scope_id FROM resources WHERE id = ?)", resourceID)). 358 Where(sq.Expr("NOT EXISTS (SELECT 1 FROM resource_disabled_versions WHERE resource_id = ? AND version_md5 = rcv.version_md5)", resourceID)). 359 Where(sq.LtOrEq{"rcv.check_order": checkOrder}). 360 OrderBy("rcv.check_order DESC"). 361 Limit(1). 362 RunWith(tx). 363 QueryRowContext(ctx). 364 Scan(&nextVersion) 365 if err != nil { 366 if err == sql.ErrNoRows { 367 return "", false, false, nil 368 } 369 return "", false, false, err 370 } 371 372 err = tx.Commit() 373 if err != nil { 374 return "", false, false, err 375 } 376 377 return nextVersion, false, true, nil 378 } 379 380 func (versions VersionsDB) LatestBuildPipes(ctx context.Context, buildID int) (map[int]BuildCursor, error) { 381 rows, err := psql.Select("p.from_build_id", "b.rerun_of", "b.job_id"). 382 From("build_pipes p"). 383 Join("builds b ON b.id = p.from_build_id"). 384 Where(sq.Eq{ 385 "p.to_build_id": buildID, 386 }). 387 RunWith(versions.conn). 388 QueryContext(ctx) 389 if err != nil { 390 return nil, err 391 } 392 393 jobToBuildPipes := map[int]BuildCursor{} 394 for rows.Next() { 395 var build BuildCursor 396 var jobID int 397 398 err = rows.Scan(&build.ID, &build.RerunOf, &jobID) 399 if err != nil { 400 return nil, err 401 } 402 403 jobToBuildPipes[jobID] = build 404 } 405 406 return jobToBuildPipes, nil 407 } 408 409 func (versions VersionsDB) LatestBuildUsingLatestVersion(ctx context.Context, jobID int, resourceID int) (int, bool, error) { 410 var buildID int 411 err := versions.conn.QueryRowContext(ctx, ` 412 SELECT inputs.build_id 413 FROM resource_config_versions rcv 414 CROSS JOIN LATERAL ( 415 SELECT i.build_id 416 FROM build_resource_config_version_inputs i 417 CROSS JOIN LATERAL ( 418 SELECT b.id 419 FROM builds b 420 WHERE b.job_id = $1 421 AND i.build_id = b.id 422 ORDER BY b.id DESC 423 LIMIT 1 424 ) AS build 425 WHERE i.resource_id = $2 426 AND i.version_md5 = rcv.version_md5 427 LIMIT 1 428 ) AS inputs 429 WHERE rcv.resource_config_scope_id = (SELECT resource_config_scope_id FROM resources WHERE id = $2) 430 ORDER BY rcv.check_order DESC 431 LIMIT 1`, jobID, resourceID).Scan(&buildID) 432 if err != nil { 433 if err == sql.ErrNoRows { 434 return 0, false, nil 435 } 436 return 0, false, err 437 } 438 439 return buildID, true, nil 440 } 441 442 func (versions VersionsDB) UnusedBuilds(ctx context.Context, jobID int, lastUsedBuild BuildCursor) (PaginatedBuilds, error) { 443 builds, err := versions.newerBuilds(ctx, jobID, lastUsedBuild) 444 if err != nil { 445 return PaginatedBuilds{}, err 446 } 447 448 builder := psql.Select("id", "rerun_of"). 449 From("builds"). 450 Where(sq.And{ 451 sq.Eq{ 452 "job_id": jobID, 453 "status": "succeeded", 454 }, 455 sq.Or{ 456 sq.Eq{"id": lastUsedBuild.ID}, 457 lastUsedBuild.OlderBuilds("id"), 458 }, 459 }). 460 OrderBy("COALESCE(rerun_of, id) DESC, id DESC") 461 462 return PaginatedBuilds{ 463 builder: builder, 464 builds: builds, 465 unusedBuilds: true, 466 467 column: "id", 468 jobID: jobID, 469 470 limitRows: versions.limitRows, 471 conn: versions.conn, 472 }, nil 473 } 474 475 func (versions VersionsDB) UnusedBuildsVersionConstrained(ctx context.Context, jobID int, lastUsedBuild BuildCursor, constrainingCandidates map[string][]string) (PaginatedBuilds, error) { 476 builds, err := versions.newerBuilds(ctx, jobID, lastUsedBuild) 477 if err != nil { 478 return PaginatedBuilds{}, err 479 } 480 481 versionsJSON, err := json.Marshal(constrainingCandidates) 482 if err != nil { 483 return PaginatedBuilds{}, err 484 } 485 486 builder := psql.Select("build_id", "rerun_of"). 487 From("successful_build_outputs"). 488 Where(sq.Expr("outputs @> ?::jsonb", versionsJSON)). 489 Where(sq.Eq{ 490 "job_id": jobID, 491 }). 492 Where(sq.Or{ 493 sq.Eq{"build_id": lastUsedBuild.ID}, 494 lastUsedBuild.OlderBuilds("build_id"), 495 }). 496 OrderBy("COALESCE(rerun_of, build_id) DESC, build_id DESC") 497 498 return PaginatedBuilds{ 499 builder: builder, 500 builds: builds, 501 unusedBuilds: true, 502 503 column: "build_id", 504 jobID: jobID, 505 506 limitRows: versions.limitRows, 507 conn: versions.conn, 508 }, nil 509 510 } 511 512 func (versions VersionsDB) newerBuilds(ctx context.Context, jobID int, lastUsedBuild BuildCursor) ([]BuildCursor, error) { 513 rows, err := psql.Select("id", "rerun_of"). 514 From("builds"). 515 Where(sq.And{ 516 sq.Eq{ 517 "job_id": jobID, 518 "status": "succeeded", 519 }, 520 lastUsedBuild.NewerBuilds("id"), 521 }). 522 OrderBy("COALESCE(rerun_of, id) ASC, id ASC"). 523 RunWith(versions.conn). 524 QueryContext(ctx) 525 if err != nil { 526 return nil, err 527 } 528 529 var builds []BuildCursor 530 for rows.Next() { 531 var build BuildCursor 532 err = rows.Scan(&build.ID, &build.RerunOf) 533 if err != nil { 534 return nil, err 535 } 536 537 builds = append(builds, build) 538 } 539 540 return builds, nil 541 } 542 543 func (versions VersionsDB) latestVersionOfResource(ctx context.Context, tx Tx, resourceID int) (ResourceVersion, bool, error) { 544 var scopeID sql.NullInt64 545 err := psql.Select("resource_config_scope_id"). 546 From("resources"). 547 Where(sq.Eq{"id": resourceID}). 548 RunWith(tx). 549 QueryRowContext(ctx). 550 Scan(&scopeID) 551 if err != nil { 552 if err == sql.ErrNoRows { 553 return "", false, nil 554 } 555 return "", false, err 556 } 557 558 if !scopeID.Valid { 559 return "", false, nil 560 } 561 562 var version ResourceVersion 563 err = psql.Select("version_md5"). 564 From("resource_config_versions"). 565 Where(sq.Eq{"resource_config_scope_id": scopeID}). 566 Where(sq.Expr("version_md5 NOT IN (SELECT version_md5 FROM resource_disabled_versions WHERE resource_id = ?)", resourceID)). 567 OrderBy("check_order DESC"). 568 Limit(1). 569 RunWith(tx). 570 QueryRowContext(ctx). 571 Scan(&version) 572 if err != nil { 573 if err == sql.ErrNoRows { 574 return "", false, nil 575 } 576 return "", false, err 577 } 578 579 return version, true, nil 580 } 581 582 func (versions VersionsDB) migrateSingle(ctx context.Context, buildID int) (string, error) { 583 ctx, span := tracing.StartSpan(ctx, "VersionsDB.migrateSingle", tracing.Attrs{}) 584 defer span.End() 585 586 span.SetAttributes(label.Int("buildID", buildID)) 587 588 var outputs string 589 err := versions.conn.QueryRowContext(ctx, ` 590 WITH builds_to_migrate AS ( 591 UPDATE builds 592 SET needs_v6_migration = false 593 WHERE id = $1 594 ) 595 INSERT INTO successful_build_outputs ( 596 SELECT b.id, b.job_id, json_object_agg(sp.resource_id, sp.v), b.rerun_of 597 FROM builds b 598 JOIN ( 599 SELECT build_id, resource_id, json_agg(version_md5) AS v 600 FROM ( 601 ( 602 SELECT build_id, resource_id, version_md5 603 FROM build_resource_config_version_outputs o 604 WHERE o.build_id = $1 605 ) 606 UNION ALL 607 ( 608 SELECT build_id, resource_id, version_md5 609 FROM build_resource_config_version_inputs i 610 WHERE i.build_id = $1 611 ) 612 ) AS agg GROUP BY build_id, resource_id) sp ON sp.build_id = b.id 613 WHERE b.id = $1 614 GROUP BY b.id, b.job_id, b.rerun_of 615 ) 616 ON CONFLICT (build_id) DO UPDATE SET outputs = EXCLUDED.outputs 617 RETURNING outputs 618 `, buildID). 619 Scan(&outputs) 620 if err != nil { 621 tracing.End(span, err) 622 return "", err 623 } 624 625 span.AddEvent(ctx, "build migrated") 626 627 return outputs, nil 628 } 629 630 type BuildCursor struct { 631 ID int 632 RerunOf sql.NullInt64 633 } 634 635 func (cursor BuildCursor) OlderBuilds(idCol string) sq.Sqlizer { 636 if cursor.RerunOf.Valid { 637 return sq.Or{ 638 sq.Expr("COALESCE(rerun_of, "+idCol+") < ?", cursor.RerunOf.Int64), 639 640 // include original build of the rerun 641 sq.Eq{idCol: cursor.RerunOf.Int64}, 642 643 // include earlier reruns of the same build 644 sq.And{ 645 sq.Eq{"rerun_of": cursor.RerunOf.Int64}, 646 sq.Lt{idCol: cursor.ID}, 647 }, 648 } 649 } else { 650 return sq.Expr("COALESCE(rerun_of, "+idCol+") < ?", cursor.ID) 651 } 652 } 653 654 func (cursor BuildCursor) NewerBuilds(idCol string) sq.Sqlizer { 655 if cursor.RerunOf.Valid { 656 return sq.Or{ 657 sq.Expr("COALESCE(rerun_of, "+idCol+") > ?", cursor.RerunOf.Int64), 658 sq.And{ 659 sq.Eq{"rerun_of": cursor.RerunOf.Int64}, 660 sq.Gt{idCol: cursor.ID}, 661 }, 662 } 663 } else { 664 return sq.Or{ 665 sq.Expr("COALESCE(rerun_of, "+idCol+") > ?", cursor.ID), 666 667 // include reruns of the build 668 sq.Eq{"rerun_of": cursor.ID}, 669 } 670 } 671 } 672 673 type PaginatedBuilds struct { 674 builder sq.SelectBuilder 675 column string 676 677 unusedBuilds bool 678 builds []BuildCursor 679 offset int 680 681 jobID int 682 683 limitRows int 684 conn Conn 685 } 686 687 func (bs *PaginatedBuilds) Next(ctx context.Context) (int, bool, error) { 688 if bs.offset+1 > len(bs.builds) { 689 for { 690 builder := bs.builder 691 692 if len(bs.builds) > 0 { 693 pageBoundary := bs.builds[len(bs.builds)-1] 694 builder = builder.Where(pageBoundary.OlderBuilds(bs.column)) 695 } 696 697 rows, err := builder. 698 Limit(uint64(bs.limitRows)). 699 RunWith(bs.conn). 700 QueryContext(ctx) 701 if err != nil { 702 return 0, false, err 703 } 704 705 builds := []BuildCursor{} 706 for rows.Next() { 707 var build BuildCursor 708 err = rows.Scan(&build.ID, &build.RerunOf) 709 if err != nil { 710 return 0, false, err 711 } 712 713 builds = append(builds, build) 714 } 715 716 if len(builds) == 0 { 717 migrated, err := bs.migrateLimit(ctx) 718 if err != nil { 719 return 0, false, err 720 } 721 722 if !migrated { 723 return 0, false, nil 724 } 725 } else { 726 bs.builds = builds 727 bs.offset = 0 728 bs.unusedBuilds = false 729 break 730 } 731 } 732 } 733 734 build := bs.builds[bs.offset] 735 bs.offset++ 736 737 return build.ID, true, nil 738 } 739 740 func (bs *PaginatedBuilds) HasNext() bool { 741 return bs.unusedBuilds && len(bs.builds)-bs.offset+1 > 0 742 } 743 744 func (bs *PaginatedBuilds) migrateLimit(ctx context.Context) (bool, error) { 745 ctx, span := tracing.StartSpan(ctx, "PaginatedBuilds.migrateLimit", tracing.Attrs{}) 746 defer span.End() 747 748 span.SetAttributes(label.Int("jobID", bs.jobID)) 749 750 buildsToMigrateQueryBuilder := psql.Select("id", "job_id", "rerun_of"). 751 From("builds"). 752 Where(sq.Eq{ 753 "job_id": bs.jobID, 754 "needs_v6_migration": true, 755 "status": "succeeded", 756 }). 757 OrderBy("COALESCE(rerun_of, id) DESC, id DESC"). 758 Limit(uint64(bs.limitRows)) 759 760 buildsToMigrateQuery, params, err := buildsToMigrateQueryBuilder.ToSql() 761 if err != nil { 762 tracing.End(span, err) 763 return false, err 764 } 765 766 results, err := bs.conn.ExecContext(ctx, ` 767 WITH builds_to_migrate AS (`+buildsToMigrateQuery+`), migrated_outputs AS ( 768 INSERT INTO successful_build_outputs ( 769 SELECT bm.id, bm.job_id, json_object_agg(sp.resource_id, sp.v), bm.rerun_of 770 FROM builds_to_migrate bm 771 JOIN ( 772 SELECT build_id, resource_id, json_agg(version_md5) AS v 773 FROM ( 774 ( 775 SELECT build_id, resource_id, version_md5 776 FROM build_resource_config_version_outputs o 777 JOIN builds_to_migrate bm ON bm.id = o.build_id 778 ) 779 UNION ALL 780 ( 781 SELECT build_id, resource_id, version_md5 782 FROM build_resource_config_version_inputs i 783 JOIN builds_to_migrate bm ON bm.id = i.build_id 784 ) 785 ) AS agg GROUP BY build_id, resource_id) sp ON sp.build_id = bm.id 786 GROUP BY bm.id, bm.job_id, bm.rerun_of 787 ) ON CONFLICT (build_id) DO NOTHING 788 ) 789 UPDATE builds 790 SET needs_v6_migration = false 791 WHERE id IN (SELECT id FROM builds_to_migrate) 792 `, params...) 793 if err != nil { 794 tracing.End(span, err) 795 return false, err 796 } 797 798 rowsAffected, err := results.RowsAffected() 799 if err != nil { 800 tracing.End(span, err) 801 return false, err 802 } 803 804 trace.SpanFromContext(ctx).AddEvent( 805 ctx, 806 "builds migrated", 807 label.Int64("rows", rowsAffected), 808 ) 809 810 if rowsAffected == 0 { 811 return false, nil 812 } 813 814 return true, nil 815 }