mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-06 14:00:09 +02:00
Addresses code review on PR #2632. 1. check_suite handler now resolves the PR through the workspace-scoped GetGitHubPullRequest query instead of GetGitHubPullRequestByRepoNumber. The (workspace_id, repo_owner, repo_name, pr_number) tuple is the real uniqueness key, so a bare (owner, repo, number) lookup could return a stale row from another workspace and either land the suite on the wrong PR or skip the right one when the installation ids drifted. The old unscoped query is removed. 2. derivePRMergeableState now returns (value, clear) and the upsert SQL distinguishes three cases: state-changing actions clear the column to NULL, non-empty payloads write the value, and metadata events with an empty payload preserve the existing column. Previously every empty payload became NULL, so a labeled/assigned event silently wiped a known clean/dirty verdict in violation of the RFC's "metadata empty payload preserves" rule. 3. ListPullRequestsByIssue narrows to the issue's PR ids before running the per-app check_suite aggregation, avoiding a full-table scan over github_pull_request_check_suite when only a handful of rows belong to the requested issue. New helper test covers labeled+empty preserves; new integration test verifies a metadata event after a known mergeable_state keeps the value. Co-authored-by: multica-agent <github@multica.ai>
616 lines
20 KiB
Go
616 lines
20 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.30.0
|
|
// 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 (installation_id) DO UPDATE SET
|
|
workspace_id = EXCLUDED.workspace_id,
|
|
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 :one
|
|
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"`
|
|
}
|
|
|
|
func (q *Queries) DeleteGitHubInstallationByInstallationID(ctx context.Context, installationID int64) (DeleteGitHubInstallationByInstallationIDRow, error) {
|
|
row := q.db.QueryRow(ctx, deleteGitHubInstallationByInstallationID, installationID)
|
|
var i DeleteGitHubInstallationByInstallationIDRow
|
|
err := row.Scan(&i.ID, &i.WorkspaceID)
|
|
return i, err
|
|
}
|
|
|
|
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 getGitHubInstallationByInstallationID = `-- name: GetGitHubInstallationByInstallationID :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 installation_id = $1
|
|
`
|
|
|
|
func (q *Queries) GetGitHubInstallationByInstallationID(ctx context.Context, installationID int64) (GithubInstallation, error) {
|
|
row := q.db.QueryRow(ctx, getGitHubInstallationByInstallationID, installationID)
|
|
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 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,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getSiblingPullRequestStateCountsForIssue = `-- name: GetSiblingPullRequestStateCountsForIssue :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' THEN 1 ELSE 0 END), 0)::bigint AS merged_count
|
|
FROM github_pull_request pr
|
|
JOIN issue_pull_request ipr ON ipr.pull_request_id = pr.id
|
|
WHERE ipr.issue_id = $1
|
|
AND pr.id <> $2
|
|
`
|
|
|
|
type GetSiblingPullRequestStateCountsForIssueParams struct {
|
|
IssueID pgtype.UUID `json:"issue_id"`
|
|
ID pgtype.UUID `json:"id"`
|
|
}
|
|
|
|
type GetSiblingPullRequestStateCountsForIssueRow struct {
|
|
OpenCount int64 `json:"open_count"`
|
|
MergedCount int64 `json:"merged_count"`
|
|
}
|
|
|
|
// Returns, for the PRs linked to an issue excluding one PR by id (the PR
|
|
// currently being processed by the webhook handler), how many are still in
|
|
// flight (open or draft) and how many have already merged. The webhook
|
|
// handler combines these with the current event's state to decide whether
|
|
// to auto-advance the issue: the issue moves to done only when there is no
|
|
// in-flight sibling AND at least one linked PR (current or sibling) merged.
|
|
func (q *Queries) GetSiblingPullRequestStateCountsForIssue(ctx context.Context, arg GetSiblingPullRequestStateCountsForIssueParams) (GetSiblingPullRequestStateCountsForIssueRow, error) {
|
|
row := q.db.QueryRow(ctx, getSiblingPullRequestStateCountsForIssue, arg.IssueID, arg.ID)
|
|
var i GetSiblingPullRequestStateCountsForIssueRow
|
|
err := row.Scan(&i.OpenCount, &i.MergedCount)
|
|
return i, err
|
|
}
|
|
|
|
const linkIssueToPullRequest = `-- name: LinkIssueToPullRequest :exec
|
|
|
|
INSERT INTO issue_pull_request (
|
|
issue_id, pull_request_id, linked_by_type, linked_by_id
|
|
) VALUES (
|
|
$1, $2, $3, $4
|
|
)
|
|
ON CONFLICT (issue_id, pull_request_id) DO NOTHING
|
|
`
|
|
|
|
type LinkIssueToPullRequestParams struct {
|
|
IssueID pgtype.UUID `json:"issue_id"`
|
|
PullRequestID pgtype.UUID `json:"pull_request_id"`
|
|
LinkedByType pgtype.Text `json:"linked_by_type"`
|
|
LinkedByID pgtype.UUID `json:"linked_by_id"`
|
|
}
|
|
|
|
// =====================
|
|
// Issue ↔ Pull Request link
|
|
// =====================
|
|
func (q *Queries) LinkIssueToPullRequest(ctx context.Context, arg LinkIssueToPullRequestParams) error {
|
|
_, err := q.db.Exec(ctx, linkIssueToPullRequest,
|
|
arg.IssueID,
|
|
arg.PullRequestID,
|
|
arg.LinkedByType,
|
|
arg.LinkedByID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
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.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"`
|
|
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.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 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
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5,
|
|
$6, $7, $8, $12, $13, $14,
|
|
$15, $16, $9, $10,
|
|
$11, $17
|
|
)
|
|
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($18::boolean, FALSE) THEN NULL
|
|
WHEN EXCLUDED.mergeable_state IS NOT NULL THEN EXCLUDED.mergeable_state
|
|
ELSE github_pull_request.mergeable_state
|
|
END,
|
|
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
|
|
`
|
|
|
|
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"`
|
|
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.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,
|
|
)
|
|
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
|
|
}
|