// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: channel_typing.sql package db import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const acknowledgeChannelTypingReactionCleanup = `-- name: AcknowledgeChannelTypingReactionCleanup :exec UPDATE channel_typing_reaction SET cleaned_at = CASE WHEN add_finished AND reaction_id <> '' THEN now() ELSE NULL END, retry_after = GREATEST(retry_after, now() + interval '30 seconds'), installation_snapshot = CASE WHEN add_finished AND reaction_id <> '' THEN '{}'::jsonb ELSE installation_snapshot END WHERE id = $1 AND reaction_id = $2 AND cleanup_required AND abandoned_at IS NULL ` type AcknowledgeChannelTypingReactionCleanupParams struct { ID pgtype.UUID `json:"id"` ReactionID string `json:"reaction_id"` } // An unfinished Add may still create a reaction after an empty sweep. Retain // its anchor; only a known successful Add response can close cleanup forever. func (q *Queries) AcknowledgeChannelTypingReactionCleanup(ctx context.Context, arg AcknowledgeChannelTypingReactionCleanupParams) error { _, err := q.db.Exec(ctx, acknowledgeChannelTypingReactionCleanup, arg.ID, arg.ReactionID) return err } const claimChannelTypingReactionCleanup = `-- name: ClaimChannelTypingReactionCleanup :many WITH candidates AS ( SELECT r.id FROM channel_typing_reaction r WHERE r.cleaned_at IS NULL AND r.abandoned_at IS NULL AND r.retry_after <= now() AND r.created_at > now() - interval '7 days' AND ($1::uuid IS NULL OR r.chat_session_id = $1) AND (r.cleanup_required OR (NOT r.add_finished AND r.created_at < now() - interval '1 minute') OR NOT EXISTS ( SELECT 1 FROM chat_message m JOIN chat_session s ON s.id = m.chat_session_id JOIN channel_installation i ON i.id = r.installation_id JOIN channel_chat_session_binding b ON b.chat_session_id = s.id AND b.installation_id = i.id LEFT JOIN agent_task_queue t ON t.id = m.task_id LEFT JOIN agent a ON a.id = t.agent_id WHERE m.id = r.chat_message_id AND s.id = r.chat_session_id AND s.workspace_id = r.workspace_id AND i.workspace_id = r.workspace_id AND i.channel_type = 'feishu' AND b.channel_type = 'feishu' AND s.status = 'active' AND i.status = 'active' AND b.retired_at IS NULL AND m.channel_ingested AND m.role = 'user' AND NOT m.channel_typing_settled -- A taskless badge has a bounded visual lifetime even if a crashed -- debouncer never flushes. Do not settle the input or cancel a future run. AND ((m.task_id IS NULL AND r.created_at > now() - interval '2 minutes') OR (t.chat_session_id = s.id AND a.workspace_id = r.workspace_id AND t.status IN ('queued','dispatched','running','waiting_local_directory','deferred'))) )) ORDER BY r.retry_after, r.id LIMIT 100 FOR UPDATE OF r SKIP LOCKED ) UPDATE channel_typing_reaction r SET cleanup_required = true, attempts = attempts + 1, retry_after = now() + LEAST(3600, 30 * power(2, LEAST(r.attempts, 7))) * interval '1 second' FROM candidates c WHERE r.id = c.id RETURNING r.id, r.workspace_id, r.chat_session_id, r.chat_message_id, r.installation_id, r.channel_message_id, r.installation_snapshot, r.reaction_id, r.add_finished, r.cleanup_required, r.cleaned_at, r.retry_after, r.attempts, r.created_at, r.abandoned_at, r.quota_slot ` // The DB is the shared source of eligibility. Missing source rows after a // committed deletion are terminal too. No transaction takes remote I/O locks. func (q *Queries) ClaimChannelTypingReactionCleanup(ctx context.Context, chatSessionID pgtype.UUID) ([]ChannelTypingReaction, error) { rows, err := q.db.Query(ctx, claimChannelTypingReactionCleanup, chatSessionID) if err != nil { return nil, err } defer rows.Close() items := []ChannelTypingReaction{} for rows.Next() { var i ChannelTypingReaction if err := rows.Scan( &i.ID, &i.WorkspaceID, &i.ChatSessionID, &i.ChatMessageID, &i.InstallationID, &i.ChannelMessageID, &i.InstallationSnapshot, &i.ReactionID, &i.AddFinished, &i.CleanupRequired, &i.CleanedAt, &i.RetryAfter, &i.Attempts, &i.CreatedAt, &i.AbandonedAt, &i.QuotaSlot, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const expireChannelTypingReactionCleanup = `-- name: ExpireChannelTypingReactionCleanup :many WITH candidates AS ( SELECT id FROM channel_typing_reaction WHERE cleaned_at IS NULL AND abandoned_at IS NULL AND created_at <= now() - interval '7 days' ORDER BY created_at, id LIMIT 1000 FOR UPDATE SKIP LOCKED ) UPDATE channel_typing_reaction r SET abandoned_at = now(), cleanup_required = true, installation_snapshot = '{}'::jsonb FROM candidates c WHERE r.id = c.id RETURNING r.id, r.workspace_id ` type ExpireChannelTypingReactionCleanupRow struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` } // Independent maintenance, including active/uncertain Adds: the cosmetic badge // has a seven-day maximum lifetime. Remote failure is NOT reported as success. func (q *Queries) ExpireChannelTypingReactionCleanup(ctx context.Context) ([]ExpireChannelTypingReactionCleanupRow, error) { rows, err := q.db.Query(ctx, expireChannelTypingReactionCleanup) if err != nil { return nil, err } defer rows.Close() items := []ExpireChannelTypingReactionCleanupRow{} for rows.Next() { var i ExpireChannelTypingReactionCleanupRow if err := rows.Scan(&i.ID, &i.WorkspaceID); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const finishChannelTypingReactionAdd = `-- name: FinishChannelTypingReactionAdd :one UPDATE channel_typing_reaction SET reaction_id = $1, add_finished = true, cleanup_required = cleanup_required OR $2::boolean, cleaned_at = NULL, retry_after = now() WHERE id = $3 AND abandoned_at IS NULL AND created_at > now() - interval '7 days' RETURNING id, workspace_id, chat_session_id, chat_message_id, installation_id, channel_message_id, installation_snapshot, reaction_id, add_finished, cleanup_required, cleaned_at, retry_after, attempts, created_at, abandoned_at, quota_slot ` type FinishChannelTypingReactionAddParams struct { ReactionID string `json:"reaction_id"` CleanupRequired bool `json:"cleanup_required"` ID pgtype.UUID `json:"id"` } // cleanup_required is monotonic. A stale active read cannot undo another // replica's terminal decision; late HTTP completion re-arms compensation. func (q *Queries) FinishChannelTypingReactionAdd(ctx context.Context, arg FinishChannelTypingReactionAddParams) (ChannelTypingReaction, error) { row := q.db.QueryRow(ctx, finishChannelTypingReactionAdd, arg.ReactionID, arg.CleanupRequired, arg.ID) var i ChannelTypingReaction err := row.Scan( &i.ID, &i.WorkspaceID, &i.ChatSessionID, &i.ChatMessageID, &i.InstallationID, &i.ChannelMessageID, &i.InstallationSnapshot, &i.ReactionID, &i.AddFinished, &i.CleanupRequired, &i.CleanedAt, &i.RetryAfter, &i.Attempts, &i.CreatedAt, &i.AbandonedAt, &i.QuotaSlot, ) return i, err } const listRequestedChannelTypingReactions = `-- name: ListRequestedChannelTypingReactions :many SELECT id FROM channel_typing_reaction WHERE id = ANY($1::uuid[]) AND cleanup_required ` func (q *Queries) ListRequestedChannelTypingReactions(ctx context.Context, ids []pgtype.UUID) ([]pgtype.UUID, error) { rows, err := q.db.Query(ctx, listRequestedChannelTypingReactions, ids) 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 pruneChannelTypingReactionCleanup = `-- name: PruneChannelTypingReactionCleanup :exec WITH candidates AS ( SELECT id FROM channel_typing_reaction WHERE cleaned_at < now() - interval '7 days' OR abandoned_at < now() - interval '7 days' LIMIT 1000 FOR UPDATE SKIP LOCKED ) DELETE FROM channel_typing_reaction r USING candidates c WHERE r.id = c.id ` // Each terminal outcome remains inspectable for seven days without credentials. func (q *Queries) PruneChannelTypingReactionCleanup(ctx context.Context) error { _, err := q.db.Exec(ctx, pruneChannelTypingReactionCleanup) return err } const registerChannelTypingReaction = `-- name: RegisterChannelTypingReaction :one INSERT INTO channel_typing_reaction ( id, workspace_id, chat_session_id, chat_message_id, installation_id, channel_message_id, installation_snapshot, cleanup_required, quota_slot ) SELECT $1, $2, $3, $4, $5, $6, $7, message.channel_typing_settled, slot.value FROM chat_message AS message JOIN chat_session AS session ON session.id = message.chat_session_id JOIN channel_installation AS installation ON installation.id = $5 CROSS JOIN LATERAL ( SELECT value FROM generate_series(1, 1000) AS value WHERE NOT EXISTS (SELECT 1 FROM channel_typing_reaction r WHERE r.workspace_id = $2 AND r.quota_slot = value AND r.cleaned_at IS NULL AND r.abandoned_at IS NULL) ORDER BY value LIMIT 1 ) slot WHERE message.id = $4 AND session.id = $3 AND session.workspace_id = $2 AND installation.workspace_id = $2 AND installation.channel_type = 'feishu' AND message.channel_ingested AND message.role = 'user' ON CONFLICT DO NOTHING RETURNING id, workspace_id, chat_session_id, chat_message_id, installation_id, channel_message_id, installation_snapshot, reaction_id, add_finished, cleanup_required, cleaned_at, retry_after, attempts, created_at, abandoned_at, quota_slot ` type RegisterChannelTypingReactionParams struct { ID pgtype.UUID `json:"id"` WorkspaceID pgtype.UUID `json:"workspace_id"` ChatSessionID pgtype.UUID `json:"chat_session_id"` ChatMessageID pgtype.UUID `json:"chat_message_id"` InstallationID pgtype.UUID `json:"installation_id"` ChannelMessageID string `json:"channel_message_id"` InstallationSnapshot []byte `json:"installation_snapshot"` } // Commit the complete external anchor BEFORE issuing Add. A terminal observer // can enumerate every input, independently of the one delivery trigger. // A concurrent registration can take the same slot. Fail closed: a cosmetic // badge may be skipped, but an untracked Add or a quota overrun is never safe. func (q *Queries) RegisterChannelTypingReaction(ctx context.Context, arg RegisterChannelTypingReactionParams) (ChannelTypingReaction, error) { row := q.db.QueryRow(ctx, registerChannelTypingReaction, arg.ID, arg.WorkspaceID, arg.ChatSessionID, arg.ChatMessageID, arg.InstallationID, arg.ChannelMessageID, arg.InstallationSnapshot, ) var i ChannelTypingReaction err := row.Scan( &i.ID, &i.WorkspaceID, &i.ChatSessionID, &i.ChatMessageID, &i.InstallationID, &i.ChannelMessageID, &i.InstallationSnapshot, &i.ReactionID, &i.AddFinished, &i.CleanupRequired, &i.CleanedAt, &i.RetryAfter, &i.Attempts, &i.CreatedAt, &i.AbandonedAt, &i.QuotaSlot, ) return i, err } const settleChannelTypingInputs = `-- name: SettleChannelTypingInputs :exec UPDATE chat_message AS message SET channel_typing_settled = true FROM chat_message AS cutoff, chat_session AS session, channel_installation AS installation WHERE cutoff.id = $1 AND cutoff.chat_session_id = $2 AND session.id = $2 AND session.workspace_id = $3 AND installation.id = $4 AND installation.workspace_id = $3 AND installation.channel_type = 'feishu' AND EXISTS (SELECT 1 FROM channel_chat_session_binding b WHERE b.chat_session_id = session.id AND b.installation_id = installation.id AND b.channel_type = 'feishu') AND message.chat_session_id = session.id AND message.task_id IS NULL AND message.channel_ingested AND message.role = 'user' AND COALESCE(message.channel_context_revision, 1) = $5 AND (message.created_at, message.id) <= (cutoff.created_at, cutoff.id) ` type SettleChannelTypingInputsParams struct { ThroughMessageID pgtype.UUID `json:"through_message_id"` ChatSessionID pgtype.UUID `json:"chat_session_id"` WorkspaceID pgtype.UUID `json:"workspace_id"` InstallationID pgtype.UUID `json:"installation_id"` ContextRevision pgtype.Int8 `json:"context_revision"` } // The flush owns only this context's unowned inputs up to its captured cutoff. // Later inputs, even in the same session, are not settled by this failed run. func (q *Queries) SettleChannelTypingInputs(ctx context.Context, arg SettleChannelTypingInputsParams) error { _, err := q.db.Exec(ctx, settleChannelTypingInputs, arg.ThroughMessageID, arg.ChatSessionID, arg.WorkspaceID, arg.InstallationID, arg.ContextRevision, ) return err } const skipChannelTypingReactionAdd = `-- name: SkipChannelTypingReactionAdd :exec UPDATE channel_typing_reaction SET add_finished = true, cleanup_required = true, cleaned_at = now(), installation_snapshot = '{}'::jsonb WHERE id = $1 AND abandoned_at IS NULL ` // No HTTP Add was issued for an already-settled input. func (q *Queries) SkipChannelTypingReactionAdd(ctx context.Context, id pgtype.UUID) error { _, err := q.db.Exec(ctx, skipChannelTypingReactionAdd, id) return err }