mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-08 01:16:45 +08:00
UpdateProjectResource derives every column it writes back from the row read at the top of the request, but the read and the write ran outside any transaction. Two concurrent partial updates could interleave as read-read-write-write, and the second writer's stale snapshot silently reverted the first writer's change: a label-only PUT reverted a racing ref update to an old Git ref, and a label-only PUT on a local_directory restored a stale execution mode (reported in #9238 with a deterministic interleaving that fails 15/15 under -race -count=3). Serialize the flow with SELECT ... FOR UPDATE inside a short transaction (GetProjectResourceInWorkspaceForUpdate + Begin/WithTx, same shape as CreateAgent). Competing updates block on the row until the write lands, then re-read fresh. This keeps the local_directory legacy label-mirror writeback intact, which a presence-aware column rewrite alone would have broken for mirror-triggered ref updates. Review follow-ups: - The regression test no longer leaves the race to the scheduler. Before resuming the paused label request, it waits until the ref request has either completed outright (the only exit on the pre-fix code, which never blocks) or is blocked waiting for the label request's row lock (the only exit with the fix, seen as wait_event_type='Lock' on the backend running the FOR UPDATE query, checked via pg_stat_activity). The two signals are mutually exclusive, so the test fails deterministically without the lock and passes with it. - findLocalDirectoryConflict and requireWorktreeCapableDaemon now read through caller-provided queries instead of the handler's pool: on the update path they run inside the transaction, so a locked request no longer borrows a second pooled connection per request. - Added a local_directory concurrency case: a label-only update racing an execution_mode update through the label-mirror ref rewrite. The regression tests fail on the pre-fix code ("stored ref = main, want the concurrent ref update (release)" / "stored execution_mode = worktree, want the concurrent mode update (cleared = in_place)") and pass with the lock, 20/20 runs each way. Fixes #9238 Generated-by: GLM-5.3-Flash (ZCode)
342 lines
9.3 KiB
Go
342 lines
9.3 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
|
// versions:
|
|
// sqlc v1.31.1
|
|
// source: project_resource.sql
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
)
|
|
|
|
const countProjectResources = `-- name: CountProjectResources :one
|
|
SELECT count(*) FROM project_resource WHERE project_id = $1
|
|
`
|
|
|
|
func (q *Queries) CountProjectResources(ctx context.Context, projectID pgtype.UUID) (int64, error) {
|
|
row := q.db.QueryRow(ctx, countProjectResources, projectID)
|
|
var count int64
|
|
err := row.Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
const createProjectResource = `-- name: CreateProjectResource :one
|
|
INSERT INTO project_resource (
|
|
project_id, workspace_id, resource_type, resource_ref, label, position, created_by
|
|
) VALUES (
|
|
$1, $2, $3, $4, $5, $6, $7
|
|
) RETURNING id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by
|
|
`
|
|
|
|
type CreateProjectResourceParams struct {
|
|
ProjectID pgtype.UUID `json:"project_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
ResourceType string `json:"resource_type"`
|
|
ResourceRef []byte `json:"resource_ref"`
|
|
Label pgtype.Text `json:"label"`
|
|
Position int32 `json:"position"`
|
|
CreatedBy pgtype.UUID `json:"created_by"`
|
|
}
|
|
|
|
func (q *Queries) CreateProjectResource(ctx context.Context, arg CreateProjectResourceParams) (ProjectResource, error) {
|
|
row := q.db.QueryRow(ctx, createProjectResource,
|
|
arg.ProjectID,
|
|
arg.WorkspaceID,
|
|
arg.ResourceType,
|
|
arg.ResourceRef,
|
|
arg.Label,
|
|
arg.Position,
|
|
arg.CreatedBy,
|
|
)
|
|
var i ProjectResource
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const deleteProjectResource = `-- name: DeleteProjectResource :exec
|
|
DELETE FROM project_resource WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) DeleteProjectResource(ctx context.Context, id pgtype.UUID) error {
|
|
_, err := q.db.Exec(ctx, deleteProjectResource, id)
|
|
return err
|
|
}
|
|
|
|
const getProjectResource = `-- name: GetProjectResource :one
|
|
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
|
|
WHERE id = $1
|
|
`
|
|
|
|
func (q *Queries) GetProjectResource(ctx context.Context, id pgtype.UUID) (ProjectResource, error) {
|
|
row := q.db.QueryRow(ctx, getProjectResource, id)
|
|
var i ProjectResource
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getProjectResourceCounts = `-- name: GetProjectResourceCounts :many
|
|
SELECT project_id, count(*)::bigint AS resource_count
|
|
FROM project_resource
|
|
WHERE project_id = ANY($1::uuid[])
|
|
GROUP BY project_id
|
|
`
|
|
|
|
type GetProjectResourceCountsRow struct {
|
|
ProjectID pgtype.UUID `json:"project_id"`
|
|
ResourceCount int64 `json:"resource_count"`
|
|
}
|
|
|
|
func (q *Queries) GetProjectResourceCounts(ctx context.Context, projectIds []pgtype.UUID) ([]GetProjectResourceCountsRow, error) {
|
|
rows, err := q.db.Query(ctx, getProjectResourceCounts, projectIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []GetProjectResourceCountsRow{}
|
|
for rows.Next() {
|
|
var i GetProjectResourceCountsRow
|
|
if err := rows.Scan(&i.ProjectID, &i.ResourceCount); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const getProjectResourceInWorkspace = `-- name: GetProjectResourceInWorkspace :one
|
|
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
|
|
WHERE id = $1 AND workspace_id = $2
|
|
`
|
|
|
|
type GetProjectResourceInWorkspaceParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
func (q *Queries) GetProjectResourceInWorkspace(ctx context.Context, arg GetProjectResourceInWorkspaceParams) (ProjectResource, error) {
|
|
row := q.db.QueryRow(ctx, getProjectResourceInWorkspace, arg.ID, arg.WorkspaceID)
|
|
var i ProjectResource
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const getProjectResourceInWorkspaceForUpdate = `-- name: GetProjectResourceInWorkspaceForUpdate :one
|
|
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
|
|
WHERE id = $1 AND workspace_id = $2
|
|
FOR UPDATE
|
|
`
|
|
|
|
type GetProjectResourceInWorkspaceForUpdateParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Same row as GetProjectResourceInWorkspace, but takes a row lock held until
|
|
// the caller's transaction ends, serializing the read-decide-write flow in
|
|
// UpdateProjectResource against competing partial updates (#9238).
|
|
func (q *Queries) GetProjectResourceInWorkspaceForUpdate(ctx context.Context, arg GetProjectResourceInWorkspaceForUpdateParams) (ProjectResource, error) {
|
|
row := q.db.QueryRow(ctx, getProjectResourceInWorkspaceForUpdate, arg.ID, arg.WorkspaceID)
|
|
var i ProjectResource
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
)
|
|
return i, err
|
|
}
|
|
|
|
const listProjectResources = `-- name: ListProjectResources :many
|
|
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
|
|
WHERE project_id = $1
|
|
ORDER BY position ASC, created_at ASC
|
|
`
|
|
|
|
func (q *Queries) ListProjectResources(ctx context.Context, projectID pgtype.UUID) ([]ProjectResource, error) {
|
|
rows, err := q.db.Query(ctx, listProjectResources, projectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ProjectResource{}
|
|
for rows.Next() {
|
|
var i ProjectResource
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listProjectResourcesForProjects = `-- name: ListProjectResourcesForProjects :many
|
|
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
|
|
WHERE project_id = ANY($1::uuid[])
|
|
ORDER BY project_id, position ASC, created_at ASC
|
|
`
|
|
|
|
func (q *Queries) ListProjectResourcesForProjects(ctx context.Context, projectIds []pgtype.UUID) ([]ProjectResource, error) {
|
|
rows, err := q.db.Query(ctx, listProjectResourcesForProjects, projectIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ProjectResource{}
|
|
for rows.Next() {
|
|
var i ProjectResource
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const listProjectResourcesInWorkspace = `-- name: ListProjectResourcesInWorkspace :many
|
|
SELECT id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by FROM project_resource
|
|
WHERE project_id = $1 AND workspace_id = $2
|
|
ORDER BY position ASC, created_at ASC
|
|
`
|
|
|
|
type ListProjectResourcesInWorkspaceParams struct {
|
|
ProjectID pgtype.UUID `json:"project_id"`
|
|
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
|
}
|
|
|
|
// Workspace-scoped read for the daemon claim path. project_resource carries its
|
|
// own workspace_id, so a corrupt project reference cannot pull another tenant's
|
|
// repository URLs or local paths into a claim response.
|
|
func (q *Queries) ListProjectResourcesInWorkspace(ctx context.Context, arg ListProjectResourcesInWorkspaceParams) ([]ProjectResource, error) {
|
|
rows, err := q.db.Query(ctx, listProjectResourcesInWorkspace, arg.ProjectID, arg.WorkspaceID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
items := []ProjectResource{}
|
|
for rows.Next() {
|
|
var i ProjectResource
|
|
if err := rows.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
items = append(items, i)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
const updateProjectResource = `-- name: UpdateProjectResource :one
|
|
UPDATE project_resource
|
|
SET resource_ref = $2,
|
|
label = $3,
|
|
position = $4
|
|
WHERE id = $1
|
|
RETURNING id, project_id, workspace_id, resource_type, resource_ref, label, position, created_at, created_by
|
|
`
|
|
|
|
type UpdateProjectResourceParams struct {
|
|
ID pgtype.UUID `json:"id"`
|
|
ResourceRef []byte `json:"resource_ref"`
|
|
Label pgtype.Text `json:"label"`
|
|
Position int32 `json:"position"`
|
|
}
|
|
|
|
func (q *Queries) UpdateProjectResource(ctx context.Context, arg UpdateProjectResourceParams) (ProjectResource, error) {
|
|
row := q.db.QueryRow(ctx, updateProjectResource,
|
|
arg.ID,
|
|
arg.ResourceRef,
|
|
arg.Label,
|
|
arg.Position,
|
|
)
|
|
var i ProjectResource
|
|
err := row.Scan(
|
|
&i.ID,
|
|
&i.ProjectID,
|
|
&i.WorkspaceID,
|
|
&i.ResourceType,
|
|
&i.ResourceRef,
|
|
&i.Label,
|
|
&i.Position,
|
|
&i.CreatedAt,
|
|
&i.CreatedBy,
|
|
)
|
|
return i, err
|
|
}
|