// Code generated by sqlc. DO NOT EDIT. // versions: // sqlc v1.31.1 // source: task_message.sql package db import ( "context" "github.com/jackc/pgx/v5/pgtype" ) const createTaskMessage = `-- name: CreateTaskMessage :one INSERT INTO task_message (id, task_id, seq, type, tool, content, input, output, output_truncated, call_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id, task_id, seq, type, tool, content, input, output, created_at, output_truncated, call_id ` type CreateTaskMessageParams struct { ID pgtype.UUID `json:"id"` TaskID pgtype.UUID `json:"task_id"` Seq int32 `json:"seq"` Type string `json:"type"` Tool pgtype.Text `json:"tool"` Content pgtype.Text `json:"content"` Input []byte `json:"input"` Output pgtype.Text `json:"output"` OutputTruncated pgtype.Bool `json:"output_truncated"` CallID pgtype.Text `json:"call_id"` } func (q *Queries) CreateTaskMessage(ctx context.Context, arg CreateTaskMessageParams) (TaskMessage, error) { row := q.db.QueryRow(ctx, createTaskMessage, arg.ID, arg.TaskID, arg.Seq, arg.Type, arg.Tool, arg.Content, arg.Input, arg.Output, arg.OutputTruncated, arg.CallID, ) var i TaskMessage err := row.Scan( &i.ID, &i.TaskID, &i.Seq, &i.Type, &i.Tool, &i.Content, &i.Input, &i.Output, &i.CreatedAt, &i.OutputTruncated, &i.CallID, ) return i, err } const createTaskMessages = `-- name: CreateTaskMessages :many WITH incoming AS ( -- Several single-argument unnest calls in one SELECT list expand in -- lockstep (PostgreSQL 10+ set-returning-function semantics), which is the -- same row-wise zip the multi-argument unnest(a, b, ...) form gives — but -- sqlc's analyzer only knows the single-argument signature, so this is the -- shape that survives code generation. SELECT unnest($1::uuid[]) AS id, unnest($2::int4[]) AS seq, unnest($3::text[]) AS type, unnest($4::text[]) AS tool, unnest($5::text[]) AS call_id, unnest($6::text[]) AS content, unnest($7::text[]) AS input, unnest($8::text[]) AS output, unnest($9::text[]) AS created_at, unnest($10::text[]) AS output_truncated ), inserted AS ( INSERT INTO task_message (id, task_id, seq, type, tool, content, input, output, created_at, output_truncated, call_id) SELECT m.id, $11::uuid, m.seq, m.type, NULLIF(m.tool, ''), NULLIF(m.content, ''), NULLIF(m.input, '')::jsonb, NULLIF(m.output, ''), COALESCE(NULLIF(m.created_at, '')::timestamptz, now()), NULLIF(m.output_truncated, '')::bool, NULLIF(m.call_id, '') FROM incoming AS m ON CONFLICT (id) DO NOTHING RETURNING id, task_id, seq, type, tool, content, input, output, created_at, output_truncated, call_id ) SELECT id, task_id, seq, type, tool, content, input, output, created_at, output_truncated, call_id FROM inserted ORDER BY seq ASC ` type CreateTaskMessagesParams struct { Ids []pgtype.UUID `json:"ids"` Seqs []int32 `json:"seqs"` Types []string `json:"types"` Tools []string `json:"tools"` CallIds []string `json:"call_ids"` Contents []string `json:"contents"` Inputs []string `json:"inputs"` Outputs []string `json:"outputs"` CreatedAts []string `json:"created_ats"` OutputTruncations []string `json:"output_truncations"` TaskID pgtype.UUID `json:"task_id"` } type CreateTaskMessagesRow struct { ID pgtype.UUID `json:"id"` TaskID pgtype.UUID `json:"task_id"` Seq int32 `json:"seq"` Type string `json:"type"` Tool pgtype.Text `json:"tool"` Content pgtype.Text `json:"content"` Input []byte `json:"input"` Output pgtype.Text `json:"output"` CreatedAt pgtype.Timestamptz `json:"created_at"` OutputTruncated pgtype.Bool `json:"output_truncated"` CallID pgtype.Text `json:"call_id"` } // Batch variant of CreateTaskMessage: persists a whole daemon-reported batch in // ONE statement — therefore one round trip and, more importantly, one commit // instead of one per message. Commit acknowledgement (IO:XactSync) is ~94% of // this SQL's load in production, so the commit count is the thing being // optimized; the round trip is a bonus. // // The rows arrive as parallel arrays rather than as one jsonb document, even // though a jsonb document is the tidier Go side. content and output routinely // carry tens of KB and occasionally megabytes, and wrapping them in JSON makes // the server escape every byte a second time and Postgres parse the whole // envelope back out — measured at 1.2x the old per-row insert at 128KB and // ~1.8x at 1MB, i.e. a regression on exactly the most expensive requests, which // are also the ones least likely to be batched. Native text[] elements are // length-prefixed by the wire protocol, so they cost neither pass. // // NULLIF is what makes per-row NULL expressible through a non-nullable []string // (the Go type sqlc gives a text[] parameter): it reproduces, exactly, the // `pgtype.Text{Valid: x != ""}` mapping the single-row query carries — empty // string means SQL NULL. input is passed as text and cast here for the same // reason; it is the one column that genuinely has to be parsed as JSON, because // it is a jsonb column. created_at also rides this transport so an older daemon // can omit its event time and keep the database-time fallback. output_truncated // uses the same trick because it is a genuinely tri-state boolean (NULL = the // reporting daemon did not measure it), and a bool[] parameter becomes a Go // []bool, which has no way to spell the third state. // // Callers MUST still run the Postgres text sanitizer first. A NUL anywhere in // the batch fails the whole statement (GH #7098) — that is inherent to batching // into one statement, not to the parameter shape. // // Atomicity is a deliberate side effect, not just a speedup: the per-message // loop this replaces could persist part of a batch and then fail, leaving the // transcript with a prefix of the batch and no way to complete it — the daemon // does not retry this endpoint. One statement makes the batch all-or-nothing, // which buys consistency. Callers that supply a task-scoped idempotency key // derive a stable message UUID; the primary-key conflict below deduplicates // retries without changing the append-only behavior of legacy random IDs. // // The ORDER BY is a contract, not decoration. A bare `INSERT ... RETURNING` // has no defined row order, and the caller republishes these rows as realtime // events in the order they arrive — the per-row loop this replaces implicitly // published in request order, so the ordering has to be restored explicitly or // subscribers can see a batch out of order. seq is assigned by the daemon and // increases within a batch, so it is the request order. func (q *Queries) CreateTaskMessages(ctx context.Context, arg CreateTaskMessagesParams) ([]CreateTaskMessagesRow, error) { rows, err := q.db.Query(ctx, createTaskMessages, arg.Ids, arg.Seqs, arg.Types, arg.Tools, arg.CallIds, arg.Contents, arg.Inputs, arg.Outputs, arg.CreatedAts, arg.OutputTruncations, arg.TaskID, ) if err != nil { return nil, err } defer rows.Close() items := []CreateTaskMessagesRow{} for rows.Next() { var i CreateTaskMessagesRow if err := rows.Scan( &i.ID, &i.TaskID, &i.Seq, &i.Type, &i.Tool, &i.Content, &i.Input, &i.Output, &i.CreatedAt, &i.OutputTruncated, &i.CallID, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const deleteTaskMessages = `-- name: DeleteTaskMessages :exec DELETE FROM task_message WHERE task_id = $1 ` func (q *Queries) DeleteTaskMessages(ctx context.Context, taskID pgtype.UUID) error { _, err := q.db.Exec(ctx, deleteTaskMessages, taskID) return err } const listTaskMessages = `-- name: ListTaskMessages :many SELECT id, task_id, seq, type, tool, content, input, output, created_at, output_truncated, call_id FROM task_message WHERE task_id = $1 ORDER BY seq ASC ` func (q *Queries) ListTaskMessages(ctx context.Context, taskID pgtype.UUID) ([]TaskMessage, error) { rows, err := q.db.Query(ctx, listTaskMessages, taskID) if err != nil { return nil, err } defer rows.Close() items := []TaskMessage{} for rows.Next() { var i TaskMessage if err := rows.Scan( &i.ID, &i.TaskID, &i.Seq, &i.Type, &i.Tool, &i.Content, &i.Input, &i.Output, &i.CreatedAt, &i.OutputTruncated, &i.CallID, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTaskMessagesSince = `-- name: ListTaskMessagesSince :many SELECT id, task_id, seq, type, tool, content, input, output, created_at, output_truncated, call_id FROM task_message WHERE task_id = $1 AND seq > $2 ORDER BY seq ASC ` type ListTaskMessagesSinceParams struct { TaskID pgtype.UUID `json:"task_id"` Seq int32 `json:"seq"` } func (q *Queries) ListTaskMessagesSince(ctx context.Context, arg ListTaskMessagesSinceParams) ([]TaskMessage, error) { rows, err := q.db.Query(ctx, listTaskMessagesSince, arg.TaskID, arg.Seq) if err != nil { return nil, err } defer rows.Close() items := []TaskMessage{} for rows.Next() { var i TaskMessage if err := rows.Scan( &i.ID, &i.TaskID, &i.Seq, &i.Type, &i.Tool, &i.Content, &i.Input, &i.Output, &i.CreatedAt, &i.OutputTruncated, &i.CallID, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } const listTaskProgressSince = `-- name: ListTaskProgressSince :many SELECT seq, type, tool, content, input FROM task_message WHERE task_id = $1 AND seq > $2 AND type IN ('tool_use', 'thinking', 'error') ORDER BY seq ASC ` type ListTaskProgressSinceParams struct { TaskID pgtype.UUID `json:"task_id"` AfterSeq int32 `json:"after_seq"` } type ListTaskProgressSinceRow struct { Seq int32 `json:"seq"` Type string `json:"type"` Tool pgtype.Text `json:"tool"` Content pgtype.Text `json:"content"` Input []byte `json:"input"` } // The rows a WeCom bubble shows while a run works: tool calls, reasoning and // errors past the last seq the bubble has folded in. Read by the replica that // holds the bubble, which is how a step reported to any replica reaches it // without the step's text crossing the realtime relay. Only the columns the // bubble renders: output (a tool's result) is the block the bubble never // shows, and routinely the largest column in the table. func (q *Queries) ListTaskProgressSince(ctx context.Context, arg ListTaskProgressSinceParams) ([]ListTaskProgressSinceRow, error) { rows, err := q.db.Query(ctx, listTaskProgressSince, arg.TaskID, arg.AfterSeq) if err != nil { return nil, err } defer rows.Close() items := []ListTaskProgressSinceRow{} for rows.Next() { var i ListTaskProgressSinceRow if err := rows.Scan( &i.Seq, &i.Type, &i.Tool, &i.Content, &i.Input, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil }