// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: github.sql package db import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const createGitHubInstallation = `-- name: CreateGitHubInstallation :one INSERT INTO github_installation ( workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id ) VALUES ( $1, $2, $3, $4, $5, $6 ) ON CONFLICT (workspace_id, installation_id) DO UPDATE SET account_login = EXCLUDED.account_login, account_type = EXCLUDED.account_type, account_avatar_url = EXCLUDED.account_avatar_url, connected_by_id = EXCLUDED.connected_by_id, updated_at = now() RETURNING id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at ` type CreateGitHubInstallationParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` InstallationID int64 `json:"installation_id"` AccountLogin string `json:"account_login"` AccountType string `json:"account_type"` AccountAvatarUrl pgtype.Text `json:"account_avatar_url"` ConnectedByID pgtype.UUID `json:"connected_by_id"` } func (q *Queries) CreateGitHubInstallation(ctx context.Context, arg CreateGitHubInstallationParams) (GithubInstallation, error) { row := q.db.QueryRow(ctx, createGitHubInstallation, arg.WorkspaceID, arg.InstallationID, arg.AccountLogin, arg.AccountType, arg.AccountAvatarUrl, arg.ConnectedByID, ) var i GithubInstallation err := row.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.AccountLogin, &i.AccountType, &i.AccountAvatarUrl, &i.ConnectedByID, &i.CreatedAt, &i.UpdatedAt, ) return i, err } const deleteGitHubInstallation = `-- name: DeleteGitHubInstallation :exec DELETE FROM github_installation WHERE id = $1 AND workspace_id = $2 ` type DeleteGitHubInstallationParams struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } func (q *Queries) DeleteGitHubInstallation(ctx context.Context, arg DeleteGitHubInstallationParams) error { _, err := q.db.Exec(ctx, deleteGitHubInstallation, arg.ID, arg.WorkspaceID) return err } const deleteGitHubInstallationByInstallationID = `-- name: DeleteGitHubInstallationByInstallationID :many DELETE FROM github_installation WHERE installation_id = $1 RETURNING id, workspace_id ` type DeleteGitHubInstallationByInstallationIDRow struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } // GitHub-side uninstall/suspend removes trust in the installation entirely, so // drop every workspace binding. Returns one row per deleted binding so the // handler can broadcast to each affected workspace. func (q *Queries) DeleteGitHubInstallationByInstallationID(ctx context.Context, installationID int64) ([]DeleteGitHubInstallationByInstallationIDRow, error) { rows, err := q.db.Query(ctx, deleteGitHubInstallationByInstallationID, installationID) if err != nil { return nil, err } defer rows.Close() items := []DeleteGitHubInstallationByInstallationIDRow{} for rows.Next() { var i DeleteGitHubInstallationByInstallationIDRow if err := rows.Scan(&i.ID, &i.WorkspaceID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const deletePendingGitHubInstallation = `-- name: DeletePendingGitHubInstallation :exec DELETE FROM github_pending_installation WHERE installation_id = $1 ` func (q *Queries) DeletePendingGitHubInstallation(ctx context.Context, installationID int64) error { _, err := q.db.Exec(ctx, deletePendingGitHubInstallation, installationID) return err } const drainPendingCheckSuitesForPR = `-- name: DrainPendingCheckSuitesForPR :many DELETE FROM github_pending_check_suite WHERE workspace_id = $1 AND repo_owner = $2 AND repo_name = $3 AND pr_number = $4 RETURNING suite_id, head_sha, app_id, conclusion, status, suite_updated_at ` type DrainPendingCheckSuitesForPRParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` RepoOwner string `json:"repo_owner"` RepoName string `json:"repo_name"` PrNumber int32 `json:"pr_number"` } type DrainPendingCheckSuitesForPRRow struct { SuiteID int64 `json:"suite_id"` HeadSha string `json:"head_sha"` AppID int64 `json:"app_id"` Conclusion pgtype.Text `json:"conclusion"` Status string `json:"status"` SuiteUpdatedAt pgtype.Timestamptz `json:"suite_updated_at"` } // Atomically reads + deletes all pending suites for the given PR address. // Caller replays each row through UpsertPullRequestCheckSuite. RETURNING // gives us the payloads we need without a separate SELECT, so two parallel // handlers racing on the same PR can't double-apply the same row. func (q *Queries) DrainPendingCheckSuitesForPR(ctx context.Context, arg DrainPendingCheckSuitesForPRParams) ([]DrainPendingCheckSuitesForPRRow, error) { rows, err := q.db.Query(ctx, drainPendingCheckSuitesForPR, arg.WorkspaceID, arg.RepoOwner, arg.RepoName, arg.PrNumber, ) if err != nil { return nil, err } defer rows.Close() items := []DrainPendingCheckSuitesForPRRow{} for rows.Next() { var i DrainPendingCheckSuitesForPRRow if err := rows.Scan( &i.SuiteID, &i.HeadSha, &i.AppID, &i.Conclusion, &i.Status, &i.SuiteUpdatedAt, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const getGitHubInstallationByID = `-- name: GetGitHubInstallationByID :one SELECT id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at FROM github_installation WHERE id = $1 ` func (q *Queries) GetGitHubInstallationByID(ctx context.Context, id pgtype.UUID) (GithubInstallation, error) { row := q.db.QueryRow(ctx, getGitHubInstallationByID, id) var i GithubInstallation err := row.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.AccountLogin, &i.AccountType, &i.AccountAvatarUrl, &i.ConnectedByID, &i.CreatedAt, &i.UpdatedAt, ) return i, err } const getGitHubPullRequest = `-- name: GetGitHubPullRequest :one SELECT id, workspace_id, installation_id, repo_owner, repo_name, pr_number, title, state, html_url, branch, author_login, author_avatar_url, merged_at, closed_at, pr_created_at, pr_updated_at, created_at, updated_at, head_sha, mergeable_state, additions, deletions, changed_files FROM github_pull_request WHERE workspace_id = $1 AND repo_owner = $2 AND repo_name = $3 AND pr_number = $4 ` type GetGitHubPullRequestParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` RepoOwner string `json:"repo_owner"` RepoName string `json:"repo_name"` PrNumber int32 `json:"pr_number"` } func (q *Queries) GetGitHubPullRequest(ctx context.Context, arg GetGitHubPullRequestParams) (GithubPullRequest, error) { row := q.db.QueryRow(ctx, getGitHubPullRequest, arg.WorkspaceID, arg.RepoOwner, arg.RepoName, arg.PrNumber, ) var i GithubPullRequest err := row.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.RepoOwner, &i.RepoName, &i.PrNumber, &i.Title, &i.State, &i.HtmlUrl, &i.Branch, &i.AuthorLogin, &i.AuthorAvatarUrl, &i.MergedAt, &i.ClosedAt, &i.PrCreatedAt, &i.PrUpdatedAt, &i.CreatedAt, &i.UpdatedAt, &i.HeadSha, &i.MergeableState, &i.Additions, &i.Deletions, &i.ChangedFiles, ) return i, err } const getIssuePullRequestCloseAggregate = `-- name: GetIssuePullRequestCloseAggregate :one SELECT COALESCE(SUM(CASE WHEN pr.state IN ('open', 'draft') THEN 1 ELSE 0 END), 0)::bigint AS open_count, COALESCE(SUM(CASE WHEN pr.state = 'merged' AND ipr.close_intent THEN 1 ELSE 0 END), 0)::bigint AS merged_with_close_intent_count FROM github_pull_request pr JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id WHERE ipr.issue_id = $1 ` type GetIssuePullRequestCloseAggregateRow struct { OpenCount int64 `json:"open_count"` MergedWithCloseIntentCount int64 `json:"merged_with_close_intent_count"` } // Aggregates the issue's linked PRs into the two counts that gate // auto-advance: how many are still in flight (`open` or `draft`) and how // many merged PRs declared explicit closing intent on the link row. The // webhook auto-advances the issue when open_count = 0 AND // merged_with_close_intent_count > 0. Both the PR state and the link row // (with close_intent) are persisted before this query runs, so the result // is event-agnostic — a link-only sibling closing after a closing-keyword // PR has already merged still resolves the issue. func (q *Queries) GetIssuePullRequestCloseAggregate(ctx context.Context, issueID pgtype.UUID) (GetIssuePullRequestCloseAggregateRow, error) { row := q.db.QueryRow(ctx, getIssuePullRequestCloseAggregate, issueID) var i GetIssuePullRequestCloseAggregateRow err := row.Scan(&i.OpenCount, &i.MergedWithCloseIntentCount) return i, err } const getIssueReviewHeadSha = `-- name: GetIssueReviewHeadSha :one SELECT pr.head_sha FROM github_pull_request pr JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id WHERE ipr.issue_id = $1 AND pr.head_sha <> '' ORDER BY (pr.state IN ('open', 'draft')) DESC, pr.pr_updated_at DESC LIMIT 1 ` // Returns the head SHA of the commit currently "under review" for an issue: // the most-recently-updated linked PR that still has an open/draft state and a // non-empty head_sha. Used by the reviewer-loop dedup (TEN-356) so a pending // review task pinned to an old head does not satisfy a request after HEAD // advanced. Prefers in-flight PRs (open/draft) over merged/closed ones so a // stale merged sibling can't shadow the live review target; falls back to the // newest linked PR with a head_sha when none are open. Returns no rows (empty // string) when the issue has no linked PR — callers treat that as "no SHA key" // and dedup on (issue_id, agent_id) alone, preserving pre-TEN-356 behavior. func (q *Queries) GetIssueReviewHeadSha(ctx context.Context, issueID pgtype.UUID) (string, error) { row := q.db.QueryRow(ctx, getIssueReviewHeadSha, issueID) var head_sha string err := row.Scan(&head_sha) return head_sha, err } const getPendingGitHubInstallation = `-- name: GetPendingGitHubInstallation :one SELECT installation_id, account_login, account_type, account_avatar_url, received_at, updated_at FROM github_pending_installation WHERE installation_id = $1 ` func (q *Queries) GetPendingGitHubInstallation(ctx context.Context, installationID int64) (GithubPendingInstallation, error) { row := q.db.QueryRow(ctx, getPendingGitHubInstallation, installationID) var i GithubPendingInstallation err := row.Scan( &i.InstallationID, &i.AccountLogin, &i.AccountType, &i.AccountAvatarUrl, &i.ReceivedAt, &i.UpdatedAt, ) return i, err } const linkIssueToPullRequest = `-- name: LinkIssueToPullRequest :exec INSERT INTO issue_pull_request ( issue_id, pull_request_id, linked_by_type, linked_by_id, close_intent ) VALUES ( $1, $2, $4, $5, $3 ) ON CONFLICT (issue_id, pull_request_id) DO UPDATE SET close_intent = CASE WHEN $6 THEN issue_pull_request.close_intent ELSE EXCLUDED.close_intent END ` type LinkIssueToPullRequestParams struct { IssueID pgtype.UUID `json:"issue_id"` PullRequestID pgtype.UUID `json:"pull_request_id"` CloseIntent bool `json:"close_intent"` LinkedByType pgtype.Text `json:"linked_by_type"` LinkedByID pgtype.UUID `json:"linked_by_id"` PreserveCloseIntent bool `json:"preserve_close_intent"` } // ===================== // Issue ↔ Pull Request link // ===================== // close_intent reflects the PR's explicit close declaration at the moment // the webhook is allowed to update that intent. Open/edit/merge webhooks use // the current title/body parse result so authors can remove a closing keyword // before merge. Post-terminal edits can opt into preserving the stored value, // keeping the merge-time decision stable. func (q *Queries) LinkIssueToPullRequest(ctx context.Context, arg LinkIssueToPullRequestParams) error { _, err := q.db.Exec(ctx, linkIssueToPullRequest, arg.IssueID, arg.PullRequestID, arg.CloseIntent, arg.LinkedByType, arg.LinkedByID, arg.PreserveCloseIntent, ) return err } const listGitHubInstallationsByInstallationID = `-- name: ListGitHubInstallationsByInstallationID :many SELECT id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at FROM github_installation WHERE installation_id = $1 ORDER BY created_at ASC, id ASC ` // One installation_id can be bound to several workspaces; webhook routing lists // every binding and picks the target workspace via the repos registry. Ordered // so the oldest binding is the deterministic routing fallback (insts[0]). func (q *Queries) ListGitHubInstallationsByInstallationID(ctx context.Context, installationID int64) ([]GithubInstallation, error) { rows, err := q.db.Query(ctx, listGitHubInstallationsByInstallationID, installationID) if err != nil { return nil, err } defer rows.Close() items := []GithubInstallation{} for rows.Next() { var i GithubInstallation if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.AccountLogin, &i.AccountType, &i.AccountAvatarUrl, &i.ConnectedByID, &i.CreatedAt, &i.UpdatedAt, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listGitHubInstallationsByWorkspace = `-- name: ListGitHubInstallationsByWorkspace :many SELECT id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at FROM github_installation WHERE workspace_id = $1 ORDER BY created_at ASC ` // ===================== // GitHub Installation // ===================== func (q *Queries) ListGitHubInstallationsByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]GithubInstallation, error) { rows, err := q.db.Query(ctx, listGitHubInstallationsByWorkspace, workspaceID) if err != nil { return nil, err } defer rows.Close() items := []GithubInstallation{} for rows.Next() { var i GithubInstallation if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.AccountLogin, &i.AccountType, &i.AccountAvatarUrl, &i.ConnectedByID, &i.CreatedAt, &i.UpdatedAt, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listIssueIDsForPullRequest = `-- name: ListIssueIDsForPullRequest :many SELECT issue_id FROM issue_pull_request WHERE pull_request_id = $1 ` func (q *Queries) ListIssueIDsForPullRequest(ctx context.Context, pullRequestID pgtype.UUID) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listIssueIDsForPullRequest, pullRequestID) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var issue_id pgtype.UUID if err := rows.Scan(&issue_id); err != nil { return nil, err } items = append(items, issue_id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listPullRequestsByIssue = `-- name: ListPullRequestsByIssue :many WITH issue_prs AS ( SELECT pr.id, pr.head_sha FROM github_pull_request pr JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id WHERE ipr.issue_id = $1 ), per_app_latest AS ( SELECT DISTINCT ON (cs.pr_id, cs.app_id) cs.pr_id, cs.app_id, cs.conclusion, cs.status FROM github_pull_request_check_suite cs JOIN issue_prs ip ON ip.id = cs.pr_id WHERE cs.head_sha = ip.head_sha AND ip.head_sha <> '' ORDER BY cs.pr_id, cs.app_id, cs.updated_at DESC ), checks AS ( SELECT pr_id, COUNT(*)::bigint AS total, SUM(CASE WHEN status = 'completed' AND conclusion IN ('failure','cancelled','timed_out','action_required','startup_failure','stale') THEN 1 ELSE 0 END)::bigint AS failed, SUM(CASE WHEN status = 'completed' AND conclusion IN ('success','neutral','skipped') THEN 1 ELSE 0 END)::bigint AS passed, SUM(CASE WHEN status <> 'completed' OR conclusion IS NULL THEN 1 ELSE 0 END)::bigint AS pending FROM per_app_latest GROUP BY pr_id ) SELECT pr.id, pr.workspace_id, pr.installation_id, pr.repo_owner, pr.repo_name, pr.pr_number, pr.title, pr.state, pr.html_url, pr.branch, pr.author_login, pr.author_avatar_url, pr.merged_at, pr.closed_at, pr.pr_created_at, pr.pr_updated_at, pr.head_sha, pr.mergeable_state, pr.additions, pr.deletions, pr.changed_files, pr.created_at, pr.updated_at, COALESCE(c.total, 0)::bigint AS checks_total, COALESCE(c.passed, 0)::bigint AS checks_passed, COALESCE(c.failed, 0)::bigint AS checks_failed, COALESCE(c.pending, 0)::bigint AS checks_pending FROM github_pull_request pr JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id LEFT JOIN checks c ON c.pr_id = pr.id WHERE ipr.issue_id = $1 ORDER BY pr.pr_created_at DESC ` type ListPullRequestsByIssueRow struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` InstallationID int64 `json:"installation_id"` RepoOwner string `json:"repo_owner"` RepoName string `json:"repo_name"` PrNumber int32 `json:"pr_number"` Title string `json:"title"` State string `json:"state"` HtmlUrl string `json:"html_url"` Branch pgtype.Text `json:"branch"` AuthorLogin pgtype.Text `json:"author_login"` AuthorAvatarUrl pgtype.Text `json:"author_avatar_url"` MergedAt pgtype.Timestamptz `json:"merged_at"` ClosedAt pgtype.Timestamptz `json:"closed_at"` PrCreatedAt pgtype.Timestamptz `json:"pr_created_at"` PrUpdatedAt pgtype.Timestamptz `json:"pr_updated_at"` HeadSha string `json:"head_sha"` MergeableState pgtype.Text `json:"mergeable_state"` Additions int32 `json:"additions"` Deletions int32 `json:"deletions"` ChangedFiles int32 `json:"changed_files"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` ChecksTotal int64 `json:"checks_total"` ChecksPassed int64 `json:"checks_passed"` ChecksFailed int64 `json:"checks_failed"` ChecksPending int64 `json:"checks_pending"` } // Returns the issue's linked PRs with the aggregated check-suite counts for // the PR's CURRENT head SHA. The `issue_prs` CTE narrows to this issue's PR // ids first so the per-app aggregation only touches suite rows for those // PRs — without that scoping the planner has to scan/aggregate every PR's // suites in the workspace before joining on issue. Per-app latest suite is // selected so a single app firing multiple suites on the same head doesn't // get counted N times. Late-arriving suites for an OLD head are stored but // excluded by the head_sha filter, so they can't override the new head's // pending view. func (q *Queries) ListPullRequestsByIssue(ctx context.Context, issueID pgtype.UUID) ([]ListPullRequestsByIssueRow, error) { rows, err := q.db.Query(ctx, listPullRequestsByIssue, issueID) if err != nil { return nil, err } defer rows.Close() items := []ListPullRequestsByIssueRow{} for rows.Next() { var i ListPullRequestsByIssueRow if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.RepoOwner, &i.RepoName, &i.PrNumber, &i.Title, &i.State, &i.HtmlUrl, &i.Branch, &i.AuthorLogin, &i.AuthorAvatarUrl, &i.MergedAt, &i.ClosedAt, &i.PrCreatedAt, &i.PrUpdatedAt, &i.HeadSha, &i.MergeableState, &i.Additions, &i.Deletions, &i.ChangedFiles, &i.CreatedAt, &i.UpdatedAt, &i.ChecksTotal, &i.ChecksPassed, &i.ChecksFailed, &i.ChecksPending, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const unlinkIssueFromPullRequest = `-- name: UnlinkIssueFromPullRequest :exec DELETE FROM issue_pull_request WHERE issue_id = $1 AND pull_request_id = $2 ` type UnlinkIssueFromPullRequestParams struct { IssueID pgtype.UUID `json:"issue_id"` PullRequestID pgtype.UUID `json:"pull_request_id"` } func (q *Queries) UnlinkIssueFromPullRequest(ctx context.Context, arg UnlinkIssueFromPullRequestParams) error { _, err := q.db.Exec(ctx, unlinkIssueFromPullRequest, arg.IssueID, arg.PullRequestID) return err } const updateGitHubInstallationAccountByInstallationID = `-- name: UpdateGitHubInstallationAccountByInstallationID :many UPDATE github_installation SET account_login = $2, account_type = $3, account_avatar_url = $4, updated_at = now() WHERE installation_id = $1 RETURNING id, workspace_id, installation_id, account_login, account_type, account_avatar_url, connected_by_id, created_at, updated_at ` type UpdateGitHubInstallationAccountByInstallationIDParams struct { InstallationID int64 `json:"installation_id"` AccountLogin string `json:"account_login"` AccountType string `json:"account_type"` AccountAvatarUrl pgtype.Text `json:"account_avatar_url"` } // Refresh the GitHub account display metadata across every workspace binding of // an installation (fired by installation.created/new_permissions_accepted/ // unsuspend). Leaves workspace_id and connected_by_id untouched. func (q *Queries) UpdateGitHubInstallationAccountByInstallationID(ctx context.Context, arg UpdateGitHubInstallationAccountByInstallationIDParams) ([]GithubInstallation, error) { rows, err := q.db.Query(ctx, updateGitHubInstallationAccountByInstallationID, arg.InstallationID, arg.AccountLogin, arg.AccountType, arg.AccountAvatarUrl, ) if err != nil { return nil, err } defer rows.Close() items := []GithubInstallation{} for rows.Next() { var i GithubInstallation if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.AccountLogin, &i.AccountType, &i.AccountAvatarUrl, &i.ConnectedByID, &i.CreatedAt, &i.UpdatedAt, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const upsertGitHubPullRequest = `-- name: UpsertGitHubPullRequest :one INSERT INTO github_pull_request ( workspace_id, installation_id, repo_owner, repo_name, pr_number, title, state, html_url, branch, author_login, author_avatar_url, merged_at, closed_at, pr_created_at, pr_updated_at, head_sha, mergeable_state, additions, deletions, changed_files ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $15, $16, $17, $18, $19, $9, $10, $11, $20, $12, $13, $14 ) ON CONFLICT (workspace_id, repo_owner, repo_name, pr_number) DO UPDATE SET installation_id = EXCLUDED.installation_id, title = EXCLUDED.title, state = EXCLUDED.state, html_url = EXCLUDED.html_url, branch = EXCLUDED.branch, author_login = EXCLUDED.author_login, author_avatar_url = EXCLUDED.author_avatar_url, merged_at = EXCLUDED.merged_at, closed_at = EXCLUDED.closed_at, pr_updated_at = EXCLUDED.pr_updated_at, head_sha = EXCLUDED.head_sha, mergeable_state = CASE WHEN COALESCE($21::boolean, FALSE) THEN NULL WHEN EXCLUDED.mergeable_state IS NOT NULL THEN EXCLUDED.mergeable_state ELSE github_pull_request.mergeable_state END, additions = EXCLUDED.additions, deletions = EXCLUDED.deletions, changed_files = EXCLUDED.changed_files, updated_at = now() RETURNING id, workspace_id, installation_id, repo_owner, repo_name, pr_number, title, state, html_url, branch, author_login, author_avatar_url, merged_at, closed_at, pr_created_at, pr_updated_at, created_at, updated_at, head_sha, mergeable_state, additions, deletions, changed_files ` type UpsertGitHubPullRequestParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` InstallationID int64 `json:"installation_id"` RepoOwner string `json:"repo_owner"` RepoName string `json:"repo_name"` PrNumber int32 `json:"pr_number"` Title string `json:"title"` State string `json:"state"` HtmlUrl string `json:"html_url"` PrCreatedAt pgtype.Timestamptz `json:"pr_created_at"` PrUpdatedAt pgtype.Timestamptz `json:"pr_updated_at"` HeadSha string `json:"head_sha"` Additions int32 `json:"additions"` Deletions int32 `json:"deletions"` ChangedFiles int32 `json:"changed_files"` Branch pgtype.Text `json:"branch"` AuthorLogin pgtype.Text `json:"author_login"` AuthorAvatarUrl pgtype.Text `json:"author_avatar_url"` MergedAt pgtype.Timestamptz `json:"merged_at"` ClosedAt pgtype.Timestamptz `json:"closed_at"` MergeableState pgtype.Text `json:"mergeable_state"` ClearMergeableState pgtype.Bool `json:"clear_mergeable_state"` } // ===================== // GitHub Pull Request // ===================== // mergeable_state has three-state semantics on UPDATE: // 1. clear_mergeable_state=true → write NULL (state-changing actions like // opened/synchronize/reopened/edited(base) invalidate the prior verdict). // 2. clear_mergeable_state=false, mergeable_state non-null → write the value. // 3. clear_mergeable_state=false, mergeable_state null → preserve existing // column. Metadata events (labeled/assigned/etc.) ship payloads without // mergeability, and silently clobbering a known clean/dirty would lose // information that GitHub only re-computes lazily. // // INSERT path always writes the incoming value (NULL acceptable for a new row). func (q *Queries) UpsertGitHubPullRequest(ctx context.Context, arg UpsertGitHubPullRequestParams) (GithubPullRequest, error) { row := q.db.QueryRow(ctx, upsertGitHubPullRequest, arg.WorkspaceID, arg.InstallationID, arg.RepoOwner, arg.RepoName, arg.PrNumber, arg.Title, arg.State, arg.HtmlUrl, arg.PrCreatedAt, arg.PrUpdatedAt, arg.HeadSha, arg.Additions, arg.Deletions, arg.ChangedFiles, arg.Branch, arg.AuthorLogin, arg.AuthorAvatarUrl, arg.MergedAt, arg.ClosedAt, arg.MergeableState, arg.ClearMergeableState, ) var i GithubPullRequest err := row.Scan( &i.ID, &i.WorkspaceID, &i.InstallationID, &i.RepoOwner, &i.RepoName, &i.PrNumber, &i.Title, &i.State, &i.HtmlUrl, &i.Branch, &i.AuthorLogin, &i.AuthorAvatarUrl, &i.MergedAt, &i.ClosedAt, &i.PrCreatedAt, &i.PrUpdatedAt, &i.CreatedAt, &i.UpdatedAt, &i.HeadSha, &i.MergeableState, &i.Additions, &i.Deletions, &i.ChangedFiles, ) return i, err } const upsertPendingCheckSuite = `-- name: UpsertPendingCheckSuite :exec INSERT INTO github_pending_check_suite ( workspace_id, installation_id, repo_owner, repo_name, pr_number, suite_id, head_sha, app_id, conclusion, status, suite_updated_at ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $11, $9, $10 ) ON CONFLICT (workspace_id, repo_owner, repo_name, pr_number, suite_id) DO UPDATE SET installation_id = EXCLUDED.installation_id, head_sha = EXCLUDED.head_sha, app_id = EXCLUDED.app_id, conclusion = EXCLUDED.conclusion, status = EXCLUDED.status, suite_updated_at = EXCLUDED.suite_updated_at, received_at = now() WHERE EXCLUDED.suite_updated_at >= github_pending_check_suite.suite_updated_at ` type UpsertPendingCheckSuiteParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` InstallationID int64 `json:"installation_id"` RepoOwner string `json:"repo_owner"` RepoName string `json:"repo_name"` PrNumber int32 `json:"pr_number"` SuiteID int64 `json:"suite_id"` HeadSha string `json:"head_sha"` AppID int64 `json:"app_id"` Status string `json:"status"` SuiteUpdatedAt pgtype.Timestamptz `json:"suite_updated_at"` Conclusion pgtype.Text `json:"conclusion"` } // ===================== // GitHub pending check_suite (out-of-order arrival stash) // ===================== // Stashes a check_suite event whose PR row is not yet mirrored. Replayed // (and deleted) by DrainPendingCheckSuitesForPR once the matching // `pull_request` webhook lands. ON CONFLICT keeps the newest payload // for the same (workspace, repo, pr_number, suite_id) — repeated // deliveries while the PR is still missing are idempotent. The // suite_updated_at guard mirrors UpsertPullRequestCheckSuite so an older // event arriving after a newer one cannot overwrite the newer payload. func (q *Queries) UpsertPendingCheckSuite(ctx context.Context, arg UpsertPendingCheckSuiteParams) error { _, err := q.db.Exec(ctx, upsertPendingCheckSuite, arg.WorkspaceID, arg.InstallationID, arg.RepoOwner, arg.RepoName, arg.PrNumber, arg.SuiteID, arg.HeadSha, arg.AppID, arg.Status, arg.SuiteUpdatedAt, arg.Conclusion, ) return err } const upsertPendingGitHubInstallation = `-- name: UpsertPendingGitHubInstallation :one INSERT INTO github_pending_installation ( installation_id, account_login, account_type, account_avatar_url ) VALUES ( $1, $2, $3, $4 ) ON CONFLICT (installation_id) DO UPDATE SET account_login = EXCLUDED.account_login, account_type = EXCLUDED.account_type, account_avatar_url = EXCLUDED.account_avatar_url, updated_at = now() RETURNING installation_id, account_login, account_type, account_avatar_url, received_at, updated_at ` type UpsertPendingGitHubInstallationParams struct { InstallationID int64 `json:"installation_id"` AccountLogin string `json:"account_login"` AccountType string `json:"account_type"` AccountAvatarUrl pgtype.Text `json:"account_avatar_url"` } func (q *Queries) UpsertPendingGitHubInstallation(ctx context.Context, arg UpsertPendingGitHubInstallationParams) (GithubPendingInstallation, error) { row := q.db.QueryRow(ctx, upsertPendingGitHubInstallation, arg.InstallationID, arg.AccountLogin, arg.AccountType, arg.AccountAvatarUrl, ) var i GithubPendingInstallation err := row.Scan( &i.InstallationID, &i.AccountLogin, &i.AccountType, &i.AccountAvatarUrl, &i.ReceivedAt, &i.UpdatedAt, ) return i, err } const upsertPullRequestCheckSuite = `-- name: UpsertPullRequestCheckSuite :exec INSERT INTO github_pull_request_check_suite ( pr_id, suite_id, head_sha, app_id, conclusion, status, updated_at ) VALUES ( $1, $2, $3, $4, $7, $5, $6 ) ON CONFLICT (pr_id, suite_id) DO UPDATE SET head_sha = EXCLUDED.head_sha, app_id = EXCLUDED.app_id, conclusion = EXCLUDED.conclusion, status = EXCLUDED.status, updated_at = EXCLUDED.updated_at WHERE EXCLUDED.updated_at >= github_pull_request_check_suite.updated_at ` type UpsertPullRequestCheckSuiteParams struct { PrID pgtype.UUID `json:"pr_id"` SuiteID int64 `json:"suite_id"` HeadSha string `json:"head_sha"` AppID int64 `json:"app_id"` Status string `json:"status"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` Conclusion pgtype.Text `json:"conclusion"` } // ===================== // GitHub PR check suite // ===================== // Upserts a single check_suite row keyed by (pr_id, suite_id). The WHERE // clause on the DO UPDATE branch prevents a late-arriving older event from // overwriting a newer one — same-PR/same-suite ordering protection. Late // events targeting an old head still land here (their head_sha is stored // on the row); the head_sha filter in ListPullRequestsByIssue keeps them // out of the current aggregate. func (q *Queries) UpsertPullRequestCheckSuite(ctx context.Context, arg UpsertPullRequestCheckSuiteParams) error { _, err := q.db.Exec(ctx, upsertPullRequestCheckSuite, arg.PrID, arg.SuiteID, arg.HeadSha, arg.AppID, arg.Status, arg.UpdatedAt, arg.Conclusion, ) return err }