// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: workspace_delete.sql package db import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const deleteTaskBatch = `-- name: DeleteTaskBatch :exec WITH batch AS MATERIALIZED ( SELECT id FROM unnest($1::uuid[]) AS t(id) ), deleted_task_usage AS ( DELETE FROM task_usage WHERE task_id IN (SELECT id FROM batch) ), deleted_task_messages AS ( DELETE FROM task_message WHERE task_id IN (SELECT id FROM batch) ), deleted_task_tokens AS ( DELETE FROM task_token WHERE task_id IN (SELECT id FROM batch) ), deleted_task_supplements AS ( DELETE FROM task_supplement WHERE task_id IN (SELECT id FROM batch) ), deleted_task_supplement_capabilities AS ( DELETE FROM task_supplement_capability WHERE task_id IN (SELECT id FROM batch) ), deleted_channel_outbound_cards AS ( DELETE FROM channel_outbound_card_message WHERE task_id IN (SELECT id FROM batch) ), deleted_lark_outbound_cards AS ( DELETE FROM lark_outbound_card_message WHERE task_id IN (SELECT id FROM batch) ), deleted_channel_task_deliveries AS ( DELETE FROM channel_task_delivery WHERE task_id IN (SELECT id FROM batch) ), deleted_draft_restores AS ( DELETE FROM chat_draft_restore WHERE task_id IN (SELECT id FROM batch) ) DELETE FROM agent_task_queue WHERE id IN (SELECT id FROM batch) ` // Deletes one bounded batch of tasks together with everything that hangs off // them, every arm keyed by task_id against an existing index, then the task rows // by primary key. The legacy FK cascades stay a safety net only: this statement // is what actually removes the rows, so teardown keeps working when they go. func (q *Queries) DeleteTaskBatch(ctx context.Context, taskIds []pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteTaskBatch, taskIds) return err } const deleteTaskTokensByAgent = `-- name: DeleteTaskTokensByAgent :exec DELETE FROM task_token WHERE agent_id = $1 ` // The third explicit task_token path. workspace_id (in DeleteWorkspaceLeafData) // and task_id (in DeleteTaskBatch) do not cover a token whose own workspace_id // points at a neighbour while its agent lives here, and teardown must not fall // back on the agent_id cascade for it. Uses idx_task_token_agent_id // (migration 275); one agent key per call for the same planner reason as // ListTaskIDsByAgentBatch. func (q *Queries) DeleteTaskTokensByAgent(ctx context.Context, agentID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteTaskTokensByAgent, agentID) return err } const deleteWorkspaceAdministration = `-- name: DeleteWorkspaceAdministration :exec WITH deleted_members AS ( DELETE FROM member WHERE member.workspace_id = $1 ), deleted_notification_preferences AS ( DELETE FROM notification_preference WHERE notification_preference.workspace_id = $1 ), deleted_pins AS ( DELETE FROM pinned_item WHERE pinned_item.workspace_id = $1 ), deleted_daemon_tokens AS ( DELETE FROM daemon_token WHERE daemon_token.workspace_id = $1 ), detached_feedback AS ( UPDATE feedback SET workspace_id = NULL WHERE feedback.workspace_id = $1 ), detached_client_usage AS ( UPDATE client_usage_daily SET workspace_id = NULL WHERE client_usage_daily.workspace_id = $1 ), deleted_share_links AS ( DELETE FROM workspace_share_link WHERE workspace_share_link.workspace_id = $1 ) DELETE FROM workspace_invitation WHERE workspace_invitation.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceAdministration(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceAdministration, workspaceID) return err } const deleteWorkspaceAgents = `-- name: DeleteWorkspaceAgents :exec DELETE FROM agent WHERE agent.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceAgents(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceAgents, workspaceID) return err } const deleteWorkspaceAutopilotChildren = `-- name: DeleteWorkspaceAutopilotChildren :exec WITH deleted_triggers AS ( DELETE FROM autopilot_trigger WHERE autopilot_id IN ( SELECT id FROM autopilot WHERE autopilot.workspace_id = $1 ) ) DELETE FROM autopilot_rule_version WHERE autopilot_rule_version.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceAutopilotChildren(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceAutopilotChildren, workspaceID) return err } const deleteWorkspaceAutopilotQuotaPeriods = `-- name: DeleteWorkspaceAutopilotQuotaPeriods :exec DELETE FROM autopilot_quota_period WHERE autopilot_quota_period.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceAutopilotQuotaPeriods(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceAutopilotQuotaPeriods, workspaceID) return err } const deleteWorkspaceAutopilotQuotaReservations = `-- name: DeleteWorkspaceAutopilotQuotaReservations :exec DELETE FROM autopilot_quota_reservation WHERE autopilot_quota_reservation.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceAutopilotQuotaReservations(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceAutopilotQuotaReservations, workspaceID) return err } const deleteWorkspaceAutopilotRuns = `-- name: DeleteWorkspaceAutopilotRuns :exec DELETE FROM autopilot_run WHERE autopilot_id IN ( SELECT id FROM autopilot WHERE autopilot.workspace_id = $1 ) ` func (q *Queries) DeleteWorkspaceAutopilotRuns(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceAutopilotRuns, workspaceID) return err } const deleteWorkspaceAutopilots = `-- name: DeleteWorkspaceAutopilots :exec DELETE FROM autopilot WHERE autopilot.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceAutopilots(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceAutopilots, workspaceID) return err } const deleteWorkspaceChatMessages = `-- name: DeleteWorkspaceChatMessages :exec DELETE FROM chat_message WHERE chat_session_id IN ( SELECT id FROM chat_session WHERE chat_session.workspace_id = $1 ) ` func (q *Queries) DeleteWorkspaceChatMessages(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceChatMessages, workspaceID) return err } const deleteWorkspaceComments = `-- name: DeleteWorkspaceComments :exec DELETE FROM comment WHERE comment.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceComments(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceComments, workspaceID) return err } const deleteWorkspaceCommunicationRoots = `-- name: DeleteWorkspaceCommunicationRoots :exec WITH deleted_sessions AS ( DELETE FROM chat_session WHERE chat_session.workspace_id = $1 ), deleted_dingtalk_group_presence AS ( DELETE FROM dingtalk_group_presence WHERE workspace_id = $1 ), deleted_dingtalk_bot_identity AS ( DELETE FROM dingtalk_bot_identity WHERE workspace_id = $1 ), deleted_channel_installations AS ( DELETE FROM channel_installation WHERE channel_installation.workspace_id = $1 ) DELETE FROM lark_installation WHERE lark_installation.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceCommunicationRoots(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceCommunicationRoots, workspaceID) return err } const deleteWorkspaceConnections = `-- name: DeleteWorkspaceConnections :exec WITH deleted_github_installations AS ( DELETE FROM github_installation WHERE github_installation.workspace_id = $1 ) DELETE FROM vcs_connection WHERE vcs_connection.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceConnections(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceConnections, workspaceID) return err } const deleteWorkspaceIssueRoots = `-- name: DeleteWorkspaceIssueRoots :exec WITH deleted_wakeup_receipts AS ( DELETE FROM issue_wakeup_receipt WHERE wakeup_id IN (SELECT id FROM issue_wakeup WHERE workspace_id=$1) ), deleted_wakeups AS ( DELETE FROM issue_wakeup WHERE workspace_id=$1 ), deleted_child_events AS ( DELETE FROM issue_child_event WHERE workspace_id=$1 ), deleted_issues AS ( DELETE FROM issue WHERE issue.workspace_id = $1 ), deleted_labels AS ( DELETE FROM issue_label WHERE issue_label.workspace_id = $1 ), deleted_properties AS ( DELETE FROM issue_property WHERE issue_property.workspace_id = $1 ), deleted_issue_views AS ( DELETE FROM issue_view WHERE issue_view.workspace_id = $1 ), deleted_issue_view_preferences AS ( DELETE FROM issue_view_preference WHERE issue_view_preference.workspace_id = $1 ) DELETE FROM quick_action WHERE quick_action.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceIssueRoots(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceIssueRoots, workspaceID) return err } const deleteWorkspaceLeafData = `-- name: DeleteWorkspaceLeafData :exec WITH ws_agents AS MATERIALIZED ( SELECT id FROM agent WHERE workspace_id = $1 ), ws_issues AS MATERIALIZED ( SELECT id FROM issue WHERE workspace_id = $1 ), ws_labels AS MATERIALIZED ( SELECT id FROM issue_label WHERE workspace_id = $1 ), ws_skills AS MATERIALIZED ( SELECT id FROM skill WHERE workspace_id = $1 ), ws_squads AS MATERIALIZED ( SELECT id FROM squad WHERE workspace_id = $1 ), ws_sessions AS MATERIALIZED ( SELECT id FROM chat_session WHERE workspace_id = $1 ), ws_autopilots AS MATERIALIZED ( SELECT id FROM autopilot WHERE workspace_id = $1 ), ws_github_prs AS MATERIALIZED ( SELECT id FROM github_pull_request WHERE workspace_id = $1 ), ws_vcs_prs AS MATERIALIZED ( SELECT id FROM vcs_pull_request WHERE workspace_id = $1 ), ws_vcs_connections AS MATERIALIZED ( SELECT id FROM vcs_connection WHERE workspace_id = $1 ), ws_channel_installations AS MATERIALIZED ( SELECT id FROM channel_installation WHERE workspace_id = $1 ), ws_lark_installations AS MATERIALIZED ( SELECT id FROM lark_installation WHERE workspace_id = $1 ), deleted_task_tokens AS ( DELETE FROM task_token WHERE workspace_id = $1 ), deleted_orphan_task_supplements AS ( DELETE FROM task_supplement WHERE workspace_id = $1 ), deleted_orphan_task_supplement_capabilities AS ( DELETE FROM task_supplement_capability WHERE workspace_id = $1 ), deleted_hourly_dirty AS ( DELETE FROM task_usage_hourly_dirty WHERE workspace_id = $1 ), deleted_hourly AS ( DELETE FROM task_usage_hourly WHERE workspace_id = $1 ), deleted_attachments AS ( DELETE FROM attachment WHERE workspace_id = $1 ), deleted_channel_outbound_cards AS ( DELETE FROM channel_outbound_card_message WHERE chat_session_id IN (SELECT id FROM ws_sessions) ), deleted_lark_outbound_cards AS ( DELETE FROM lark_outbound_card_message WHERE chat_session_id IN (SELECT id FROM ws_sessions) ), deleted_draft_restores AS ( DELETE FROM chat_draft_restore WHERE chat_session_id IN (SELECT id FROM ws_sessions) ), deleted_agent_builder_drafts AS ( DELETE FROM agent_builder_draft WHERE workspace_id = $1 ), deleted_comment_reactions AS ( DELETE FROM comment_reaction WHERE workspace_id = $1 ), deleted_issue_reactions AS ( DELETE FROM issue_reaction WHERE workspace_id = $1 ), deleted_activity AS ( DELETE FROM activity_log WHERE workspace_id = $1 ), deleted_inbox AS ( DELETE FROM inbox_item WHERE workspace_id = $1 ), deleted_issue_dependencies AS ( DELETE FROM issue_dependency WHERE issue_id IN (SELECT id FROM ws_issues) OR depends_on_issue_id IN (SELECT id FROM ws_issues) ), deleted_issue_subscribers AS ( DELETE FROM issue_subscriber WHERE issue_id IN (SELECT id FROM ws_issues) ), deleted_issue_labels AS ( DELETE FROM issue_to_label WHERE issue_id IN (SELECT id FROM ws_issues) OR label_id IN (SELECT id FROM ws_labels) ), deleted_agent_labels AS ( DELETE FROM agent_to_label WHERE agent_id IN (SELECT id FROM ws_agents) OR label_id IN (SELECT id FROM ws_labels) ), deleted_skill_labels AS ( DELETE FROM skill_to_label WHERE skill_id IN (SELECT id FROM ws_skills) OR label_id IN (SELECT id FROM ws_labels) ), deleted_issue_github_links AS ( DELETE FROM issue_pull_request WHERE issue_id IN (SELECT id FROM ws_issues) OR pull_request_id IN (SELECT id FROM ws_github_prs) ), deleted_issue_vcs_links AS ( DELETE FROM issue_vcs_pull_request WHERE issue_id IN (SELECT id FROM ws_issues) OR pull_request_id IN (SELECT id FROM ws_vcs_prs) ), deleted_issue_pr_automation AS ( DELETE FROM issue_pr_automation WHERE workspace_id = $1 ), deleted_issue_pr_exclusions AS ( DELETE FROM issue_pull_request_exclusion WHERE workspace_id = $1 ), deleted_agent_invocation_targets AS ( DELETE FROM agent_invocation_target WHERE agent_id IN (SELECT id FROM ws_agents) ), deleted_agent_skills AS ( DELETE FROM agent_skill WHERE agent_id IN (SELECT id FROM ws_agents) OR skill_id IN (SELECT id FROM ws_skills) ), deleted_skill_files AS ( DELETE FROM skill_file WHERE skill_id IN (SELECT id FROM ws_skills) ), deleted_daemon_connections AS ( DELETE FROM daemon_connection WHERE agent_id IN (SELECT id FROM ws_agents) ), deleted_squad_members AS ( DELETE FROM squad_member WHERE squad_id IN (SELECT id FROM ws_squads) ), deleted_project_resources AS ( DELETE FROM project_resource WHERE workspace_id = $1 ), deleted_autopilot_collaborators AS ( DELETE FROM autopilot_collaborator WHERE autopilot_id IN (SELECT id FROM ws_autopilots) ), deleted_autopilot_subscribers AS ( DELETE FROM autopilot_subscriber WHERE autopilot_id IN (SELECT id FROM ws_autopilots) ), deleted_webhook_deliveries AS ( DELETE FROM webhook_delivery WHERE workspace_id = $1 ), deleted_github_check_runs AS ( DELETE FROM github_pull_request_check_run WHERE pr_id IN (SELECT id FROM ws_github_prs) ), deleted_github_check_suites AS ( DELETE FROM github_pull_request_check_suite WHERE pr_id IN (SELECT id FROM ws_github_prs) ), deleted_pending_github_suites AS ( DELETE FROM github_pending_check_suite WHERE workspace_id = $1 ), deleted_channel_task_deliveries AS ( DELETE FROM channel_task_delivery WHERE installation_id IN (SELECT id FROM ws_channel_installations) ), deleted_channel_outbound_messages AS ( DELETE FROM channel_outbound_message WHERE installation_id IN (SELECT id FROM ws_channel_installations) ), deleted_channel_reply_deliveries AS ( DELETE FROM channel_reply_delivery WHERE installation_id IN (SELECT id FROM ws_channel_installations) ), deleted_channel_chat_contexts AS ( DELETE FROM channel_chat_context_generation WHERE chat_session_id IN (SELECT id FROM ws_sessions) ), deleted_vcs_commit_statuses AS ( DELETE FROM vcs_commit_status WHERE connection_id IN (SELECT id FROM ws_vcs_connections) ), deleted_channel_chat_bindings AS ( DELETE FROM channel_chat_session_binding WHERE installation_id IN (SELECT id FROM ws_channel_installations) OR chat_session_id IN (SELECT id FROM ws_sessions) ), deleted_dingtalk_group_routes AS ( DELETE FROM dingtalk_group_route WHERE workspace_id = $1 ), deleted_channel_inbound_dedup AS ( DELETE FROM channel_inbound_message_dedup WHERE installation_id IN (SELECT id FROM ws_channel_installations) ), deleted_channel_inbound_audit AS ( DELETE FROM channel_inbound_audit WHERE installation_id IN (SELECT id FROM ws_channel_installations) ), deleted_channel_user_bindings AS ( DELETE FROM channel_user_binding WHERE workspace_id = $1 ), deleted_channel_binding_tokens AS ( DELETE FROM channel_binding_token WHERE workspace_id = $1 ), deleted_lark_chat_bindings AS ( DELETE FROM lark_chat_session_binding WHERE installation_id IN (SELECT id FROM ws_lark_installations) OR chat_session_id IN (SELECT id FROM ws_sessions) ), deleted_lark_inbound_dedup AS ( DELETE FROM lark_inbound_message_dedup WHERE installation_id IN (SELECT id FROM ws_lark_installations) ), deleted_lark_inbound_audit AS ( DELETE FROM lark_inbound_audit WHERE installation_id IN (SELECT id FROM ws_lark_installations) ), deleted_lark_user_bindings AS ( DELETE FROM lark_user_binding WHERE workspace_id = $1 ), deleted_lark_binding_tokens AS ( DELETE FROM lark_binding_token WHERE workspace_id = $1 ) UPDATE channel_media_pending_object SET state = CASE WHEN state = 'tombstoned' THEN 'tombstoned' ELSE 'deleting' END, lease_token = NULL, lease_expires_at = NULL, next_attempt_at = now(), last_error = NULL WHERE channel_media_pending_object.workspace_id = $1 ` // Everything task-keyed moved to DeleteTaskBatch, which runs in bounded batches // before this step; what is left is keyed by the workspace or by one of the // workspace-scoped id sets below. // One of three explicit task_token paths. This is the workspace-keyed one; // DeleteTaskBatch covers task_id and DeleteTaskTokensByAgent covers agent_id, so // a token whose workspace_id points at a neighbour while its task or agent lives // here is still removed by this teardown rather than by the FK cascade. The // former single statement combined all three with OR, which cost a full scan of // task_token (MUL-5999); split, each path is an index scan. // Same no-FK chore as chat_draft_restore above. Matched on workspace_id rather // than the session set because that column exists precisely so this statement // does not have to join through chat_session, which it deletes in this same CTE. // Keep the two-system cleanup ledger until object storage has been settled. // Moving every row out of pending also prevents a concurrent media bind from // attaching an object after the workspace teardown commits. The reconciler // performs the idempotent object delete and clears the row afterwards. func (q *Queries) DeleteWorkspaceLeafData(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceLeafData, workspaceID) return err } const deleteWorkspacePluginData = `-- name: DeleteWorkspacePluginData :exec WITH installations AS MATERIALIZED ( SELECT plugin_installation.id FROM plugin_installation WHERE plugin_installation.workspace_id = $1 ), deleted_storage AS ( DELETE FROM plugin_storage WHERE installation_id IN (SELECT id FROM installations) ), deleted_secrets AS ( DELETE FROM plugin_secret WHERE installation_id IN (SELECT id FROM installations) ), deleted_hook_schedules AS ( DELETE FROM plugin_hook_schedule WHERE installation_id IN (SELECT id FROM installations) ), deleted_invocations AS ( DELETE FROM plugin_invocation WHERE workspace_id = $1 ), versions AS MATERIALIZED ( SELECT plugin_package_version.id FROM plugin_package_version WHERE plugin_package_version.workspace_id = $1 ), deleted_package_files AS ( DELETE FROM plugin_package_file WHERE version_id IN (SELECT id FROM versions) ), deleted_package_versions AS ( DELETE FROM plugin_package_version WHERE workspace_id = $1 ), deleted_packages AS ( DELETE FROM plugin_package WHERE workspace_id = $1 ) DELETE FROM plugin_installation WHERE id IN (SELECT id FROM installations) ` // Plugin relationships have no foreign keys or cascades. Storage and secrets // hang off the installation, so both leaf tables are cleared through the // workspace's installation ids before the installations themselves. // Hook call records are workspace-scoped in their own right, so this deletes by // workspace rather than through the installation ids: a row whose installation // was already uninstalled would otherwise survive the workspace it described. // Published artifacts are workspace-scoped too, and independent of whether // anything installed them. Deleting the workspace without these would leave the // stored bundles as the largest orphan the plugin surface can produce. func (q *Queries) DeleteWorkspacePluginData(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspacePluginData, workspaceID) return err } const deleteWorkspacePullRequests = `-- name: DeleteWorkspacePullRequests :exec WITH deleted_github_prs AS ( DELETE FROM github_pull_request WHERE github_pull_request.workspace_id = $1 ) DELETE FROM vcs_pull_request WHERE vcs_pull_request.workspace_id = $1 ` func (q *Queries) DeleteWorkspacePullRequests(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspacePullRequests, workspaceID) return err } const deleteWorkspaceRuntimesAndProjects = `-- name: DeleteWorkspaceRuntimesAndProjects :exec WITH deleted_runtimes AS ( DELETE FROM agent_runtime WHERE agent_runtime.workspace_id = $1 ), deleted_profiles AS ( DELETE FROM runtime_profile WHERE runtime_profile.workspace_id = $1 ) DELETE FROM project WHERE project.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceRuntimesAndProjects(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceRuntimesAndProjects, workspaceID) return err } const deleteWorkspaceSquadsAndSkills = `-- name: DeleteWorkspaceSquadsAndSkills :exec WITH deleted_squads AS ( DELETE FROM squad WHERE squad.workspace_id = $1 ) DELETE FROM skill WHERE skill.workspace_id = $1 ` func (q *Queries) DeleteWorkspaceSquadsAndSkills(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteWorkspaceSquadsAndSkills, workspaceID) return err } const detachTaskBatchReferences = `-- name: DetachTaskBatchReferences :exec WITH detached_runs AS ( UPDATE autopilot_run SET task_id = NULL WHERE task_id = ANY($1::uuid[]) ) UPDATE agent_task_queue SET parent_task_id = NULL WHERE parent_task_id = ANY($1::uuid[]) ` // Break inbound references to a task batch before deleting it, in the // application layer rather than through the FKs' ON DELETE SET NULL. Both // referencing columns are indexed (idx_agent_task_queue_parent from migration // 055, idx_autopilot_run_task_id from migration 277) so this stays a per-batch // index scan instead of the per-row full scan the FK action would do. // // Kept separate from DeleteTaskBatch: a task can be both a member of the batch // and the parent of another member, and updating plus deleting the same row // inside one statement is not well defined. func (q *Queries) DetachTaskBatchReferences(ctx context.Context, taskIds []pgtype.UUID) error { _, err := q.db.Exec(ctx, detachTaskBatchReferences, taskIds) return err } const listTaskIDsByAgentFirstPage = `-- name: ListTaskIDsByAgentFirstPage :many SELECT id FROM agent_task_queue WHERE agent_id = $1 ORDER BY id LIMIT $2 FOR UPDATE ` type ListTaskIDsByAgentFirstPageParams struct { AgentID pgtype.UUID `json:"agent_id"` Limit int32 `json:"limit"` } // First page of the same walk; see ListWorkspaceAgentIDFirstPage for why this is // a separate query rather than a zero-uuid cursor, and ListTaskIDsByAgentPage for // the single-key / keyset / FOR UPDATE rationale. func (q *Queries) ListTaskIDsByAgentFirstPage(ctx context.Context, arg ListTaskIDsByAgentFirstPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listTaskIDsByAgentFirstPage, arg.AgentID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTaskIDsByAgentPage = `-- name: ListTaskIDsByAgentPage :many SELECT id FROM agent_task_queue WHERE agent_id = $1 AND id > $2 ORDER BY id LIMIT $3 FOR UPDATE ` type ListTaskIDsByAgentPageParams struct { AgentID pgtype.UUID `json:"agent_id"` ID pgtype.UUID `json:"id"` Limit int32 `json:"limit"` } // One owner key per call, one keyset-paged chunk per call. // // Single-key equality is deliberate. All three ownership paths must stay (nothing // enforces that a task's agent/runtime/issue share a workspace), but expressing // them as `OR agent_id IN (subquery) OR …` — or as a UNION of joins, or as // `= ANY($1::uuid[])` over a whole workspace's agents — makes the planner // estimate a proportional share of the global table and fall back to a Seq Scan // on agent_task_queue. A single-key equality is the only form whose row estimate // stays small enough to be index-driven regardless of how the workspace's tasks // are distributed (MUL-5999). // // `id > $2 ORDER BY id LIMIT $3` against idx_agent_task_queue_agent_id_keyset // (migration 278) bounds the SCAN, not just the result: each page is an index // range scan that starts where the previous one stopped. Bounding only the result // — `ORDER BY id LIMIT n` against the (agent_id, status) index — puts a Sort over // the agent's entire task set in front of every page, so a busy agent costs // roughly O(N² / page) for the sweep. // // FOR UPDATE is load-bearing for correctness, not throughput. It locks the rows // this page is about to delete, and under READ COMMITTED PostgreSQL re-checks the // WHERE clause after acquiring each lock: a task a concurrent committed UPDATE // has already moved to another owner no longer matches and is skipped, and one we // did lock cannot be moved away before we delete it. func (q *Queries) ListTaskIDsByAgentPage(ctx context.Context, arg ListTaskIDsByAgentPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listTaskIDsByAgentPage, arg.AgentID, arg.ID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTaskIDsByIssueFirstPage = `-- name: ListTaskIDsByIssueFirstPage :many SELECT id FROM agent_task_queue WHERE issue_id = $1 ORDER BY id LIMIT $2 FOR UPDATE ` type ListTaskIDsByIssueFirstPageParams struct { IssueID pgtype.UUID `json:"issue_id"` Limit int32 `json:"limit"` } // First page of the same walk; see ListWorkspaceAgentIDFirstPage for why this is // a separate query rather than a zero-uuid cursor, and ListTaskIDsByAgentPage for // the single-key / keyset / FOR UPDATE rationale. func (q *Queries) ListTaskIDsByIssueFirstPage(ctx context.Context, arg ListTaskIDsByIssueFirstPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listTaskIDsByIssueFirstPage, arg.IssueID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTaskIDsByIssuePage = `-- name: ListTaskIDsByIssuePage :many SELECT id FROM agent_task_queue WHERE issue_id = $1 AND id > $2 ORDER BY id LIMIT $3 FOR UPDATE ` type ListTaskIDsByIssuePageParams struct { IssueID pgtype.UUID `json:"issue_id"` ID pgtype.UUID `json:"id"` Limit int32 `json:"limit"` } // Uses idx_agent_task_queue_issue_id_keyset (migration 279). See // ListTaskIDsByAgentPage for the single-key / keyset / FOR UPDATE rationale. func (q *Queries) ListTaskIDsByIssuePage(ctx context.Context, arg ListTaskIDsByIssuePageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listTaskIDsByIssuePage, arg.IssueID, arg.ID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTaskIDsByRuntimeFirstPage = `-- name: ListTaskIDsByRuntimeFirstPage :many SELECT id FROM agent_task_queue WHERE runtime_id = $1 ORDER BY id LIMIT $2 FOR UPDATE ` type ListTaskIDsByRuntimeFirstPageParams struct { RuntimeID pgtype.UUID `json:"runtime_id"` Limit int32 `json:"limit"` } // First page of the same walk; see ListWorkspaceAgentIDFirstPage for why this is // a separate query rather than a zero-uuid cursor, and ListTaskIDsByAgentPage for // the single-key / keyset / FOR UPDATE rationale. func (q *Queries) ListTaskIDsByRuntimeFirstPage(ctx context.Context, arg ListTaskIDsByRuntimeFirstPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listTaskIDsByRuntimeFirstPage, arg.RuntimeID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTaskIDsByRuntimePage = `-- name: ListTaskIDsByRuntimePage :many SELECT id FROM agent_task_queue WHERE runtime_id = $1 AND id > $2 ORDER BY id LIMIT $3 FOR UPDATE ` type ListTaskIDsByRuntimePageParams struct { RuntimeID pgtype.UUID `json:"runtime_id"` ID pgtype.UUID `json:"id"` Limit int32 `json:"limit"` } // Uses idx_agent_task_queue_runtime_id (migration 273, (runtime_id, id)); before // it every runtime_id index was partial, so this path had none at all. See // ListTaskIDsByAgentPage for the single-key / keyset / FOR UPDATE rationale. func (q *Queries) ListTaskIDsByRuntimePage(ctx context.Context, arg ListTaskIDsByRuntimePageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listTaskIDsByRuntimePage, arg.RuntimeID, arg.ID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceAgentIDFirstPage = `-- name: ListWorkspaceAgentIDFirstPage :many SELECT id FROM agent WHERE agent.workspace_id = $1 ORDER BY id LIMIT $2 ` type ListWorkspaceAgentIDFirstPageParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` Limit int32 `json:"limit"` } // First page of the same walk. Split from the keyset query rather than seeded // with the all-zero uuid, because that value is itself a valid uuid: a row whose // id happened to be all zeros would be skipped forever by `id > $2`. func (q *Queries) ListWorkspaceAgentIDFirstPage(ctx context.Context, arg ListWorkspaceAgentIDFirstPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listWorkspaceAgentIDFirstPage, arg.WorkspaceID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceAgentIDPage = `-- name: ListWorkspaceAgentIDPage :many SELECT id FROM agent WHERE agent.workspace_id = $1 AND id > $2 ORDER BY id LIMIT $3 ` type ListWorkspaceAgentIDPageParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` ID pgtype.UUID `json:"id"` Limit int32 `json:"limit"` } // Owner enumeration in bounded, keyset-paged chunks. The rows are already locked // by LockWorkspaceTaskOwnerAgents above; this only walks them. // // Keyset on id, not on a natural key: the cursor column must be immutable, and // renaming an agent mid-teardown would otherwise let it sort behind the cursor // and escape the sweep. Uses idx_agent_workspace_id_keyset (migration 281), which // exists so this page does not sort the whole owner set. func (q *Queries) ListWorkspaceAgentIDPage(ctx context.Context, arg ListWorkspaceAgentIDPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listWorkspaceAgentIDPage, arg.WorkspaceID, arg.ID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceIssueIDFirstPage = `-- name: ListWorkspaceIssueIDFirstPage :many SELECT id FROM issue WHERE issue.workspace_id = $1 ORDER BY id LIMIT $2 ` type ListWorkspaceIssueIDFirstPageParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` Limit int32 `json:"limit"` } // First page of the same walk. Split from the keyset query rather than seeded // with the all-zero uuid, because that value is itself a valid uuid: a row whose // id happened to be all zeros would be skipped forever by `id > $2`. func (q *Queries) ListWorkspaceIssueIDFirstPage(ctx context.Context, arg ListWorkspaceIssueIDFirstPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listWorkspaceIssueIDFirstPage, arg.WorkspaceID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceIssueIDPage = `-- name: ListWorkspaceIssueIDPage :many SELECT id FROM issue WHERE issue.workspace_id = $1 AND id > $2 ORDER BY id LIMIT $3 ` type ListWorkspaceIssueIDPageParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` ID pgtype.UUID `json:"id"` Limit int32 `json:"limit"` } // Uses idx_issue_workspace_id_keyset (migration 282). func (q *Queries) ListWorkspaceIssueIDPage(ctx context.Context, arg ListWorkspaceIssueIDPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listWorkspaceIssueIDPage, arg.WorkspaceID, arg.ID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceRuntimeIDFirstPage = `-- name: ListWorkspaceRuntimeIDFirstPage :many SELECT id FROM agent_runtime WHERE agent_runtime.workspace_id = $1 ORDER BY id LIMIT $2 ` type ListWorkspaceRuntimeIDFirstPageParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` Limit int32 `json:"limit"` } // First page of the same walk. Split from the keyset query rather than seeded // with the all-zero uuid, because that value is itself a valid uuid: a row whose // id happened to be all zeros would be skipped forever by `id > $2`. func (q *Queries) ListWorkspaceRuntimeIDFirstPage(ctx context.Context, arg ListWorkspaceRuntimeIDFirstPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listWorkspaceRuntimeIDFirstPage, arg.WorkspaceID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listWorkspaceRuntimeIDPage = `-- name: ListWorkspaceRuntimeIDPage :many SELECT id FROM agent_runtime WHERE agent_runtime.workspace_id = $1 AND id > $2 ORDER BY id LIMIT $3 ` type ListWorkspaceRuntimeIDPageParams struct { WorkspaceID pgtype.UUID `json:"workspace_id"` ID pgtype.UUID `json:"id"` Limit int32 `json:"limit"` } // Uses idx_agent_runtime_workspace_id_keyset (migration 283). func (q *Queries) ListWorkspaceRuntimeIDPage(ctx context.Context, arg ListWorkspaceRuntimeIDPageParams) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listWorkspaceRuntimeIDPage, arg.WorkspaceID, arg.ID, arg.Limit) if err != nil { return nil, err } defer rows.Close() items := []pgtype.UUID{} for rows.Next() { var id pgtype.UUID if err := rows.Scan(&id); err != nil { return nil, err } items = append(items, id) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const lockTaskUsageRollupForWorkspaceDelete = `-- name: LockTaskUsageRollupForWorkspaceDelete :exec SELECT pg_advisory_xact_lock(4246) ` // Advisory lock 4246 is the hourly rollup family's key: the rollup function // takes it per transaction (migration 272) and the backfill commands take it // per session. An in-flight rollup finishes first, then no new rollup can // write the workspace's aggregates until this delete commits. The caller // bounds this wait with SET LOCAL lock_timeout — batch jobs hold 4246 for // minutes, and an unbounded wait here hangs the delete request (MUL-5983). func (q *Queries) LockTaskUsageRollupForWorkspaceDelete(ctx context.Context) error { _, err := q.db.Exec(ctx, lockTaskUsageRollupForWorkspaceDelete) return err } const lockWorkspaceTaskOwnerAgents = `-- name: LockWorkspaceTaskOwnerAgents :exec SELECT 1 FROM agent WHERE agent.workspace_id = $1 FOR UPDATE ` // The three LockWorkspaceTaskOwner* statements are a write fence, not a lookup — // they deliberately return nothing, so a workspace with a huge owner set costs // the API process no memory at all. // // agent_task_queue has no workspace_id, so a task becomes this workspace's // problem through agent_id, issue_id or runtime_id — and inserting such a task // requires FOR KEY SHARE on the referenced owner row, which conflicts with // FOR UPDATE. Holding these locks for the rest of the teardown transaction // therefore blocks any enqueue (or reassignment) that would add a task to this // workspace after we have swept it, which row locks on the tasks alone cannot // do. Same technique the handler already uses on the workspace row to keep // CreateChatSession out of the delete window. // // This fence is defence in depth, not the only guarantee: deleteWorkspaceTasks // re-verifies every owner after sweeping it and fails closed if tasks keep // appearing, so teardown does not silently leak rows even if the fence is ever // weakened (for example by removing the legacy FKs this one leans on). func (q *Queries) LockWorkspaceTaskOwnerAgents(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, lockWorkspaceTaskOwnerAgents, workspaceID) return err } const lockWorkspaceTaskOwnerIssues = `-- name: LockWorkspaceTaskOwnerIssues :exec SELECT 1 FROM issue WHERE issue.workspace_id = $1 FOR UPDATE ` func (q *Queries) LockWorkspaceTaskOwnerIssues(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, lockWorkspaceTaskOwnerIssues, workspaceID) return err } const lockWorkspaceTaskOwnerRuntimes = `-- name: LockWorkspaceTaskOwnerRuntimes :exec SELECT 1 FROM agent_runtime WHERE agent_runtime.workspace_id = $1 FOR UPDATE ` func (q *Queries) LockWorkspaceTaskOwnerRuntimes(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, lockWorkspaceTaskOwnerRuntimes, workspaceID) return err } const prepareWorkspaceDeletionLinks = `-- name: PrepareWorkspaceDeletionLinks :exec WITH detached_comments AS ( UPDATE comment SET parent_id = NULL WHERE comment.workspace_id = $1 AND parent_id IS NOT NULL ), detached_issues AS ( UPDATE issue SET parent_issue_id = NULL WHERE issue.workspace_id = $1 AND parent_issue_id IS NOT NULL ) UPDATE webhook_delivery SET replayed_from_delivery_id = NULL WHERE webhook_delivery.workspace_id = $1 AND replayed_from_delivery_id IS NOT NULL ` // Break self-references and the task/run cycle explicitly before deleting // either side. This keeps their ordering in the application-owned graph // instead of relying on ON DELETE SET NULL actions. // // The task half of this step now lives in DetachTaskBatchReferences, which runs // once per bounded batch instead of once for a whole workspace's task set. func (q *Queries) PrepareWorkspaceDeletionLinks(ctx context.Context, workspaceID pgtype.UUID) error { _, err := q.db.Exec(ctx, prepareWorkspaceDeletionLinks, workspaceID) return err } const setWorkspaceTeardownMode = `-- name: SetWorkspaceTeardownMode :exec SELECT set_config('multica.workspace_teardown', 'on', true) ` // Workspace deletion is application-owned. These statements form a fixed, // bottom-up deletion plan; the legacy foreign keys remain only as a // compatibility safety net during the expand phase. // The setting is transaction-local. Migration 242 makes only the three DELETE // dirty triggers skip their per-row work while this flag is on. func (q *Queries) SetWorkspaceTeardownMode(ctx context.Context) error { _, err := q.db.Exec(ctx, setWorkspaceTeardownMode) return err }