Files
multica/server/pkg/db/generated/vcs.sql.go
Bohan Jiang 116761e505 MUL-7072 fix(github): retire reference-only PR links (#8245)
A bare body mention of an issue key claimed nothing, yet auto-link wrote a link row for it and flagged it reference_only — filtered out of the PR list, the close aggregate and the review dedup head SHA. The row had no reader, and once the PR went terminal the preserve gate froze the flag, so adding `Closes MUL-1` to a merged PR's body could never surface the PR on its issue. With no unlink surface anywhere, the one repair available from GitHub was the one that could not work.

Write no row for a passing mention, delete the historical hidden rows (migration 458), and drop the column's reads and writes; the column itself goes in a follow-up release that re-runs the delete as a mop-up. The visible set of links is unchanged, except that a post-merge `Closes` now links the PR (without retroactively completing the issue). An open PR whose claim is downgraded in place loses its link, which is what hiding used to accomplish.

Docs, the four locales, and the agent-facing platform skill are updated in step.
2026-09-10 17:16:50 +08:00

610 lines
23 KiB
Go

// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.31.1
// source: vcs.sql
package db
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const deleteVCSConnection = `-- name: DeleteVCSConnection :exec
WITH target AS (
SELECT vcs_connection.id FROM vcs_connection WHERE vcs_connection.id = $1 AND vcs_connection.workspace_id = $2
),
cleared_links AS (
DELETE FROM issue_vcs_pull_request
WHERE pull_request_id IN (
SELECT vcs_pull_request.id FROM vcs_pull_request
WHERE vcs_pull_request.connection_id IN (SELECT target.id FROM target)
)
),
cleared_statuses AS (
DELETE FROM vcs_commit_status WHERE connection_id IN (SELECT target.id FROM target)
),
cleared_prs AS (
DELETE FROM vcs_pull_request WHERE connection_id IN (SELECT target.id FROM target)
)
DELETE FROM vcs_connection WHERE vcs_connection.id = $1 AND vcs_connection.workspace_id = $2
`
type DeleteVCSConnectionParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
}
// These tables carry no FKs, so the cascade that once removed the connection's
// mirrored PRs, their issue links, and CI statuses is gone (migration 213). Do
// that cleanup explicitly here, in one statement so it commits or rolls back
// atomically with the connection row. The target CTE also scopes every child
// delete to a connection that actually belongs to the workspace, so a wrong
// workspace_id is a no-op rather than deleting another tenant's child rows.
func (q *Queries) DeleteVCSConnection(ctx context.Context, arg DeleteVCSConnectionParams) error {
_, err := q.db.Exec(ctx, deleteVCSConnection, arg.ID, arg.WorkspaceID)
return err
}
const getIssueCombinedPullRequestCloseAggregate = `-- name: GetIssueCombinedPullRequestCloseAggregate :one
WITH combined AS (
SELECT pr.state AS state, ipr.close_intent AS close_intent
FROM github_pull_request pr
JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id
WHERE ipr.issue_id = $1
UNION ALL
SELECT pr.state AS state, ipr.close_intent AS close_intent
FROM vcs_pull_request pr
JOIN issue_vcs_pull_request ipr ON ipr.pull_request_id = pr.id
WHERE ipr.issue_id = $1
)
SELECT
COALESCE(SUM(CASE WHEN state IN ('open', 'draft') THEN 1 ELSE 0 END), 0)::bigint AS open_count,
COALESCE(SUM(CASE WHEN state = 'merged' AND close_intent THEN 1 ELSE 0 END), 0)::bigint AS merged_with_close_intent_count
FROM combined
`
type GetIssueCombinedPullRequestCloseAggregateRow struct {
OpenCount int64 `json:"open_count"`
MergedWithCloseIntentCount int64 `json:"merged_with_close_intent_count"`
}
// Cross-provider close gate. An issue can carry PRs from GitHub AND a
// self-hosted VCS provider at the same time, so auto-advance has to see BOTH
// table pairs. Reading only one (as the per-provider aggregates do) lets a
// merged close-intent PR/MR on one provider advance an issue that still has an
// open PR on the other — either webhook is blind to the other's in-flight work.
// Sum the in-flight (open/draft) and merged-with-close-intent counts across
// github_pull_request+issue_pull_request and vcs_pull_request+
// issue_vcs_pull_request. A bare body mention is not linked on either side, so
// a passing reference never counts as in-flight.
func (q *Queries) GetIssueCombinedPullRequestCloseAggregate(ctx context.Context, issueID pgtype.UUID) (GetIssueCombinedPullRequestCloseAggregateRow, error) {
row := q.db.QueryRow(ctx, getIssueCombinedPullRequestCloseAggregate, issueID)
var i GetIssueCombinedPullRequestCloseAggregateRow
err := row.Scan(&i.OpenCount, &i.MergedWithCloseIntentCount)
return i, err
}
const getVCSConnectionByID = `-- name: GetVCSConnectionByID :one
SELECT id, workspace_id, provider, instance_url, account_login, access_token_encrypted, webhook_secret_encrypted, connected_by_id, created_at, updated_at FROM vcs_connection
WHERE id = $1
`
func (q *Queries) GetVCSConnectionByID(ctx context.Context, id pgtype.UUID) (VcsConnection, error) {
row := q.db.QueryRow(ctx, getVCSConnectionByID, id)
var i VcsConnection
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Provider,
&i.InstanceUrl,
&i.AccountLogin,
&i.AccessTokenEncrypted,
&i.WebhookSecretEncrypted,
&i.ConnectedByID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const linkIssueToVCSPullRequest = `-- name: LinkIssueToVCSPullRequest :exec
INSERT INTO issue_vcs_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_vcs_pull_request.close_intent
ELSE EXCLUDED.close_intent
END
`
type LinkIssueToVCSPullRequestParams 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 ↔ VCS PR link
// =====================
// Mirrors the GitHub link upsert: preserve_close_intent freezes close_intent
// once a terminal merge/close event has been recorded.
func (q *Queries) LinkIssueToVCSPullRequest(ctx context.Context, arg LinkIssueToVCSPullRequestParams) error {
_, err := q.db.Exec(ctx, linkIssueToVCSPullRequest,
arg.IssueID,
arg.PullRequestID,
arg.CloseIntent,
arg.LinkedByType,
arg.LinkedByID,
arg.PreserveCloseIntent,
)
return err
}
const listIssueIDsForVCSPRHead = `-- name: ListIssueIDsForVCSPRHead :many
SELECT DISTINCT ipr.issue_id
FROM vcs_pull_request pr
JOIN issue_vcs_pull_request ipr ON ipr.pull_request_id = pr.id
WHERE pr.connection_id = $1 AND pr.head_sha = $2 AND pr.head_sha <> ''
`
type ListIssueIDsForVCSPRHeadParams struct {
ConnectionID pgtype.UUID `json:"connection_id"`
HeadSha string `json:"head_sha"`
}
// Issues linked to any PR whose head sha matches the given status, so a
// commit-status event can fan out a PR-card refresh to the right issues.
func (q *Queries) ListIssueIDsForVCSPRHead(ctx context.Context, arg ListIssueIDsForVCSPRHeadParams) ([]pgtype.UUID, error) {
rows, err := q.db.Query(ctx, listIssueIDsForVCSPRHead, arg.ConnectionID, arg.HeadSha)
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 listVCSConnectionsByWorkspace = `-- name: ListVCSConnectionsByWorkspace :many
SELECT id, workspace_id, provider, instance_url, account_login, access_token_encrypted, webhook_secret_encrypted, connected_by_id, created_at, updated_at FROM vcs_connection
WHERE workspace_id = $1
ORDER BY created_at ASC
`
// =====================
// VCS Connection (Forgejo / Gitea / GitLab)
// =====================
func (q *Queries) ListVCSConnectionsByWorkspace(ctx context.Context, workspaceID pgtype.UUID) ([]VcsConnection, error) {
rows, err := q.db.Query(ctx, listVCSConnectionsByWorkspace, workspaceID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []VcsConnection{}
for rows.Next() {
var i VcsConnection
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.Provider,
&i.InstanceUrl,
&i.AccountLogin,
&i.AccessTokenEncrypted,
&i.WebhookSecretEncrypted,
&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 listVCSPullRequestsByIssue = `-- name: ListVCSPullRequestsByIssue :many
WITH checks AS (
SELECT
pr.id AS pr_id,
COUNT(*)::bigint AS total,
SUM(CASE WHEN cs.state = 'failed' THEN 1 ELSE 0 END)::bigint AS failed,
SUM(CASE WHEN cs.state = 'passed' THEN 1 ELSE 0 END)::bigint AS passed,
SUM(CASE WHEN cs.state = 'pending' THEN 1 ELSE 0 END)::bigint AS pending
FROM vcs_pull_request pr
JOIN issue_vcs_pull_request ipr ON ipr.pull_request_id = pr.id
JOIN vcs_commit_status cs
ON cs.connection_id = pr.connection_id
AND cs.sha = pr.head_sha
AND pr.head_sha <> ''
WHERE ipr.issue_id = $1
GROUP BY pr.id
)
SELECT
pr.id, pr.workspace_id, pr.connection_id, pr.provider, pr.repo_owner, pr.repo_name, pr.pr_number, pr.title, pr.state, pr.html_url, pr.branch, pr.head_sha, pr.author_login, pr.author_avatar_url, pr.merged_at, pr.closed_at, pr.pr_created_at, pr.pr_updated_at, 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 vcs_pull_request pr
JOIN issue_vcs_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 ListVCSPullRequestsByIssueRow struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
ConnectionID pgtype.UUID `json:"connection_id"`
Provider string `json:"provider"`
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"`
HeadSha string `json:"head_sha"`
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"`
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"`
}
// Aggregates each PR's commit statuses for its CURRENT head sha into
// passed/failed/pending counts. vcs_commit_status holds one row per
// (connection, sha, context) with a normalized state, so a count by state is
// correct. Statuses for an old head sha stay stored but are excluded by the
// head_sha join, so a stale run can't pollute the bar.
func (q *Queries) ListVCSPullRequestsByIssue(ctx context.Context, issueID pgtype.UUID) ([]ListVCSPullRequestsByIssueRow, error) {
rows, err := q.db.Query(ctx, listVCSPullRequestsByIssue, issueID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListVCSPullRequestsByIssueRow{}
for rows.Next() {
var i ListVCSPullRequestsByIssueRow
if err := rows.Scan(
&i.ID,
&i.WorkspaceID,
&i.ConnectionID,
&i.Provider,
&i.RepoOwner,
&i.RepoName,
&i.PrNumber,
&i.Title,
&i.State,
&i.HtmlUrl,
&i.Branch,
&i.HeadSha,
&i.AuthorLogin,
&i.AuthorAvatarUrl,
&i.MergedAt,
&i.ClosedAt,
&i.PrCreatedAt,
&i.PrUpdatedAt,
&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 rotateVCSConnectionWebhookSecret = `-- name: RotateVCSConnectionWebhookSecret :one
UPDATE vcs_connection
SET webhook_secret_encrypted = $3,
updated_at = now()
WHERE id = $1 AND workspace_id = $2
RETURNING id, workspace_id, provider, instance_url, account_login, access_token_encrypted, webhook_secret_encrypted, connected_by_id, created_at, updated_at
`
type RotateVCSConnectionWebhookSecretParams struct {
ID pgtype.UUID `json:"id"`
WorkspaceID pgtype.UUID `json:"workspace_id"`
WebhookSecretEncrypted string `json:"webhook_secret_encrypted"`
}
func (q *Queries) RotateVCSConnectionWebhookSecret(ctx context.Context, arg RotateVCSConnectionWebhookSecretParams) (VcsConnection, error) {
row := q.db.QueryRow(ctx, rotateVCSConnectionWebhookSecret, arg.ID, arg.WorkspaceID, arg.WebhookSecretEncrypted)
var i VcsConnection
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Provider,
&i.InstanceUrl,
&i.AccountLogin,
&i.AccessTokenEncrypted,
&i.WebhookSecretEncrypted,
&i.ConnectedByID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const unlinkIssueFromVCSPullRequest = `-- name: UnlinkIssueFromVCSPullRequest :exec
DELETE FROM issue_vcs_pull_request
WHERE issue_id = $1 AND pull_request_id = $2
`
type UnlinkIssueFromVCSPullRequestParams struct {
IssueID pgtype.UUID `json:"issue_id"`
PullRequestID pgtype.UUID `json:"pull_request_id"`
}
// Drops a link an earlier claim created, for the GitHub twin's reason: while a
// PR is still editable, the link follows the live title/body parse, so a key the
// payload still carries but no longer claims — "Closes MUL-1" edited down to
// "Related MUL-1" — loses its link. A key deleted from the PR outright is NOT
// covered: the payload keeps no trace of it, so noticing that needs the stored
// links instead, which is its own change. Callers must not run this once the PR
// has gone terminal — a post-merge edit cannot retroactively unlink a PR that
// did the work.
func (q *Queries) UnlinkIssueFromVCSPullRequest(ctx context.Context, arg UnlinkIssueFromVCSPullRequestParams) error {
_, err := q.db.Exec(ctx, unlinkIssueFromVCSPullRequest, arg.IssueID, arg.PullRequestID)
return err
}
const upsertVCSCommitStatus = `-- name: UpsertVCSCommitStatus :exec
INSERT INTO vcs_commit_status (
connection_id, sha, context, state, target_url, description, updated_at
) VALUES (
$1, $2, $3, $4, $6, $7, $5
)
ON CONFLICT (connection_id, sha, context) DO UPDATE SET
state = EXCLUDED.state,
target_url = EXCLUDED.target_url,
description = EXCLUDED.description,
updated_at = EXCLUDED.updated_at
WHERE EXCLUDED.updated_at >= vcs_commit_status.updated_at
`
type UpsertVCSCommitStatusParams struct {
ConnectionID pgtype.UUID `json:"connection_id"`
Sha string `json:"sha"`
Context string `json:"context"`
State string `json:"state"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
TargetUrl pgtype.Text `json:"target_url"`
Description pgtype.Text `json:"description"`
}
// =====================
// VCS commit status (CI)
// =====================
// One row per (connection, sha, context); a redelivery or state transition
// overwrites in place. updated_at guards against an older event overwriting a
// newer one for the same context. state is normalized (passed/failed/pending).
func (q *Queries) UpsertVCSCommitStatus(ctx context.Context, arg UpsertVCSCommitStatusParams) error {
_, err := q.db.Exec(ctx, upsertVCSCommitStatus,
arg.ConnectionID,
arg.Sha,
arg.Context,
arg.State,
arg.UpdatedAt,
arg.TargetUrl,
arg.Description,
)
return err
}
const upsertVCSConnection = `-- name: UpsertVCSConnection :one
INSERT INTO vcs_connection (
workspace_id, provider, instance_url, account_login,
access_token_encrypted, webhook_secret_encrypted, connected_by_id
) VALUES (
$1, $2, $3, $4, $5, $6, $7
)
ON CONFLICT (workspace_id, instance_url) DO UPDATE SET
provider = EXCLUDED.provider,
account_login = EXCLUDED.account_login,
access_token_encrypted = EXCLUDED.access_token_encrypted,
webhook_secret_encrypted = EXCLUDED.webhook_secret_encrypted,
connected_by_id = EXCLUDED.connected_by_id,
updated_at = now()
RETURNING id, workspace_id, provider, instance_url, account_login, access_token_encrypted, webhook_secret_encrypted, connected_by_id, created_at, updated_at
`
type UpsertVCSConnectionParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
Provider string `json:"provider"`
InstanceUrl string `json:"instance_url"`
AccountLogin string `json:"account_login"`
AccessTokenEncrypted string `json:"access_token_encrypted"`
WebhookSecretEncrypted string `json:"webhook_secret_encrypted"`
ConnectedByID pgtype.UUID `json:"connected_by_id"`
}
// Reconnecting the same instance rotates the stored token/secret, provider,
// and identity in place rather than creating a duplicate row.
func (q *Queries) UpsertVCSConnection(ctx context.Context, arg UpsertVCSConnectionParams) (VcsConnection, error) {
row := q.db.QueryRow(ctx, upsertVCSConnection,
arg.WorkspaceID,
arg.Provider,
arg.InstanceUrl,
arg.AccountLogin,
arg.AccessTokenEncrypted,
arg.WebhookSecretEncrypted,
arg.ConnectedByID,
)
var i VcsConnection
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.Provider,
&i.InstanceUrl,
&i.AccountLogin,
&i.AccessTokenEncrypted,
&i.WebhookSecretEncrypted,
&i.ConnectedByID,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const upsertVCSPullRequest = `-- name: UpsertVCSPullRequest :one
INSERT INTO vcs_pull_request (
workspace_id, connection_id, provider, 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,
additions, deletions, changed_files, head_sha
) VALUES (
$1, $2, $3, $4, $5, $6,
$7, $8, $9, $16, $17, $18,
$19, $20, $10, $11,
$12, $13, $14, $15
)
ON CONFLICT (connection_id, repo_owner, repo_name, pr_number) DO UPDATE SET
workspace_id = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.workspace_id ELSE vcs_pull_request.workspace_id END,
provider = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.provider ELSE vcs_pull_request.provider END,
title = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.title ELSE vcs_pull_request.title END,
state = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.state ELSE vcs_pull_request.state END,
html_url = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.html_url ELSE vcs_pull_request.html_url END,
branch = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.branch ELSE vcs_pull_request.branch END,
author_login = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.author_login ELSE vcs_pull_request.author_login END,
author_avatar_url = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.author_avatar_url ELSE vcs_pull_request.author_avatar_url END,
merged_at = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.merged_at ELSE vcs_pull_request.merged_at END,
closed_at = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.closed_at ELSE vcs_pull_request.closed_at END,
pr_updated_at = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.pr_updated_at ELSE vcs_pull_request.pr_updated_at END,
additions = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.additions ELSE vcs_pull_request.additions END,
deletions = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.deletions ELSE vcs_pull_request.deletions END,
changed_files = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.changed_files ELSE vcs_pull_request.changed_files END,
head_sha = CASE WHEN EXCLUDED.pr_updated_at >= vcs_pull_request.pr_updated_at THEN EXCLUDED.head_sha ELSE vcs_pull_request.head_sha END,
updated_at = now()
RETURNING id, workspace_id, connection_id, provider, repo_owner, repo_name, pr_number, title, state, html_url, branch, head_sha, author_login, author_avatar_url, merged_at, closed_at, pr_created_at, pr_updated_at, additions, deletions, changed_files, created_at, updated_at
`
type UpsertVCSPullRequestParams struct {
WorkspaceID pgtype.UUID `json:"workspace_id"`
ConnectionID pgtype.UUID `json:"connection_id"`
Provider string `json:"provider"`
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"`
Additions int32 `json:"additions"`
Deletions int32 `json:"deletions"`
ChangedFiles int32 `json:"changed_files"`
HeadSha string `json:"head_sha"`
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"`
}
// =====================
// VCS Pull Request
// =====================
// pr_updated_at guards against an out-of-order webhook redelivery regressing
// the PR state, mirroring the updated_at guard on UpsertVCSCommitStatus. Each
// mutable column is applied only when the incoming event is at least as new as
// the stored row; a stale event keeps the existing values. The row is still
// touched (and thus RETURNED) either way, so callers always get the current PR
// — the webhook needs pr.id to link the issue even on a stale redelivery.
func (q *Queries) UpsertVCSPullRequest(ctx context.Context, arg UpsertVCSPullRequestParams) (VcsPullRequest, error) {
row := q.db.QueryRow(ctx, upsertVCSPullRequest,
arg.WorkspaceID,
arg.ConnectionID,
arg.Provider,
arg.RepoOwner,
arg.RepoName,
arg.PrNumber,
arg.Title,
arg.State,
arg.HtmlUrl,
arg.PrCreatedAt,
arg.PrUpdatedAt,
arg.Additions,
arg.Deletions,
arg.ChangedFiles,
arg.HeadSha,
arg.Branch,
arg.AuthorLogin,
arg.AuthorAvatarUrl,
arg.MergedAt,
arg.ClosedAt,
)
var i VcsPullRequest
err := row.Scan(
&i.ID,
&i.WorkspaceID,
&i.ConnectionID,
&i.Provider,
&i.RepoOwner,
&i.RepoName,
&i.PrNumber,
&i.Title,
&i.State,
&i.HtmlUrl,
&i.Branch,
&i.HeadSha,
&i.AuthorLogin,
&i.AuthorAvatarUrl,
&i.MergedAt,
&i.ClosedAt,
&i.PrCreatedAt,
&i.PrUpdatedAt,
&i.Additions,
&i.Deletions,
&i.ChangedFiles,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}