mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: address review comments on InsertChatMessages (#23239)
Follow-up to #23220, addressing Cian's review comments: - **SQL casing**: Uppercase `UNNEST` to match `NULLIF`/`COALESCE` convention in the query. - **Builder pattern**: `chatMessage` struct now uses unexported fields with a `newChatMessage` constructor for required fields (role, content, visibility, modelConfigID, contentVersion) and chainable builder methods (`withCreatedBy`, `withCompressed`, `withUsage`, `withContextLimit`, `withTotalCostMicros`, `withRuntimeMs`) for optional/nullable fields. - **Batch test in chats_test**: Replaced the `for i := 0; i < 2` loop with a single batch insert of 2 messages to actually exercise the batch logic. - **Multi-message querier test**: Added `BatchInsertMultipleMessages` test verifying 3-message batch insert with role ordering, sequential IDs, nullable field semantics (NULL for zero UUIDs and zero ints), and token/cost assertions. --------- Co-authored-by: Cian Johnston <cian@coder.com>
This commit is contained in:
+166
-128
@@ -487,33 +487,30 @@ func (p *Server) CreateChat(ctx context.Context, opts CreateOptions) (database.C
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal system prompt: %w", err)
|
||||
}
|
||||
appendChatMessage(&msgParams, chatMessage{
|
||||
Role: database.ChatMessageRoleSystem,
|
||||
Content: systemContent,
|
||||
Visibility: database.ChatMessageVisibilityModel,
|
||||
ModelConfigID: opts.ModelConfigID,
|
||||
CreatedBy: uuid.Nil,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
appendChatMessage(&msgParams, newChatMessage(
|
||||
database.ChatMessageRoleSystem,
|
||||
systemContent,
|
||||
database.ChatMessageVisibilityModel,
|
||||
opts.ModelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
))
|
||||
}
|
||||
|
||||
appendChatMessage(&msgParams, chatMessage{
|
||||
Role: database.ChatMessageRoleSystem,
|
||||
Content: workspaceAwarenessContent,
|
||||
Visibility: database.ChatMessageVisibilityModel,
|
||||
ModelConfigID: opts.ModelConfigID,
|
||||
CreatedBy: uuid.Nil,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
appendChatMessage(&msgParams, newChatMessage(
|
||||
database.ChatMessageRoleSystem,
|
||||
workspaceAwarenessContent,
|
||||
database.ChatMessageVisibilityModel,
|
||||
opts.ModelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
))
|
||||
|
||||
appendChatMessage(&msgParams, chatMessage{
|
||||
Role: database.ChatMessageRoleUser,
|
||||
Content: userContent,
|
||||
Visibility: database.ChatMessageVisibilityBoth,
|
||||
ModelConfigID: opts.ModelConfigID,
|
||||
CreatedBy: opts.OwnerID,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
appendChatMessage(&msgParams, newChatMessage(
|
||||
database.ChatMessageRoleUser,
|
||||
userContent,
|
||||
database.ChatMessageVisibilityBoth,
|
||||
opts.ModelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
).withCreatedBy(opts.OwnerID))
|
||||
|
||||
_, err = tx.InsertChatMessages(ctx, msgParams)
|
||||
if err != nil {
|
||||
@@ -1109,27 +1106,82 @@ func insertChatMessageWithStore(
|
||||
}
|
||||
|
||||
// chatMessage describes a single message to insert as part of a batch.
|
||||
// For nullable UUID fields (ModelConfigID, CreatedBy), use uuid.Nil to
|
||||
// represent NULL — the SQL uses NULLIF to convert zero UUIDs to NULL.
|
||||
// For nullable int64 fields, use 0 to represent NULL — the SQL uses
|
||||
// NULLIF to convert zeros to NULL.
|
||||
// Use newChatMessage to create one, then chain builder methods for
|
||||
// optional fields. For nullable UUID fields (ModelConfigID, CreatedBy),
|
||||
// use uuid.Nil to represent NULL — the SQL uses NULLIF to convert zero
|
||||
// UUIDs to NULL. For nullable int64 fields, use 0 to represent NULL —
|
||||
// the SQL uses NULLIF to convert zeros to NULL.
|
||||
type chatMessage struct {
|
||||
Role database.ChatMessageRole
|
||||
Content pqtype.NullRawMessage
|
||||
Visibility database.ChatMessageVisibility
|
||||
ModelConfigID uuid.UUID
|
||||
CreatedBy uuid.UUID
|
||||
ContentVersion int16
|
||||
Compressed bool
|
||||
InputTokens int64
|
||||
OutputTokens int64
|
||||
TotalTokens int64
|
||||
ReasoningTokens int64
|
||||
CacheCreationTokens int64
|
||||
CacheReadTokens int64
|
||||
ContextLimit int64
|
||||
TotalCostMicros int64
|
||||
RuntimeMs int64
|
||||
role database.ChatMessageRole
|
||||
content pqtype.NullRawMessage
|
||||
visibility database.ChatMessageVisibility
|
||||
modelConfigID uuid.UUID
|
||||
createdBy uuid.UUID
|
||||
contentVersion int16
|
||||
compressed bool
|
||||
inputTokens int64
|
||||
outputTokens int64
|
||||
totalTokens int64
|
||||
reasoningTokens int64
|
||||
cacheCreationTokens int64
|
||||
cacheReadTokens int64
|
||||
contextLimit int64
|
||||
totalCostMicros int64
|
||||
runtimeMs int64
|
||||
}
|
||||
|
||||
func newChatMessage(
|
||||
role database.ChatMessageRole,
|
||||
content pqtype.NullRawMessage,
|
||||
visibility database.ChatMessageVisibility,
|
||||
modelConfigID uuid.UUID,
|
||||
contentVersion int16,
|
||||
) chatMessage {
|
||||
return chatMessage{
|
||||
role: role,
|
||||
content: content,
|
||||
visibility: visibility,
|
||||
modelConfigID: modelConfigID,
|
||||
contentVersion: contentVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (m chatMessage) withCreatedBy(id uuid.UUID) chatMessage {
|
||||
m.createdBy = id
|
||||
return m
|
||||
}
|
||||
|
||||
func (m chatMessage) withCompressed() chatMessage {
|
||||
m.compressed = true
|
||||
return m
|
||||
}
|
||||
|
||||
func (m chatMessage) withUsage(
|
||||
inputTokens, outputTokens, totalTokens, reasoningTokens,
|
||||
cacheCreationTokens, cacheReadTokens int64,
|
||||
) chatMessage {
|
||||
m.inputTokens = inputTokens
|
||||
m.outputTokens = outputTokens
|
||||
m.totalTokens = totalTokens
|
||||
m.reasoningTokens = reasoningTokens
|
||||
m.cacheCreationTokens = cacheCreationTokens
|
||||
m.cacheReadTokens = cacheReadTokens
|
||||
return m
|
||||
}
|
||||
|
||||
func (m chatMessage) withContextLimit(limit int64) chatMessage {
|
||||
m.contextLimit = limit
|
||||
return m
|
||||
}
|
||||
|
||||
func (m chatMessage) withTotalCostMicros(cost int64) chatMessage {
|
||||
m.totalCostMicros = cost
|
||||
return m
|
||||
}
|
||||
|
||||
func (m chatMessage) withRuntimeMs(ms int64) chatMessage {
|
||||
m.runtimeMs = ms
|
||||
return m
|
||||
}
|
||||
|
||||
// appendChatMessage appends a single message to the batch insert params.
|
||||
@@ -1137,22 +1189,22 @@ func appendChatMessage(
|
||||
params *database.InsertChatMessagesParams,
|
||||
msg chatMessage,
|
||||
) {
|
||||
params.CreatedBy = append(params.CreatedBy, msg.CreatedBy)
|
||||
params.ModelConfigID = append(params.ModelConfigID, msg.ModelConfigID)
|
||||
params.Role = append(params.Role, msg.Role)
|
||||
params.Content = append(params.Content, string(msg.Content.RawMessage))
|
||||
params.ContentVersion = append(params.ContentVersion, msg.ContentVersion)
|
||||
params.Visibility = append(params.Visibility, msg.Visibility)
|
||||
params.InputTokens = append(params.InputTokens, msg.InputTokens)
|
||||
params.OutputTokens = append(params.OutputTokens, msg.OutputTokens)
|
||||
params.TotalTokens = append(params.TotalTokens, msg.TotalTokens)
|
||||
params.ReasoningTokens = append(params.ReasoningTokens, msg.ReasoningTokens)
|
||||
params.CacheCreationTokens = append(params.CacheCreationTokens, msg.CacheCreationTokens)
|
||||
params.CacheReadTokens = append(params.CacheReadTokens, msg.CacheReadTokens)
|
||||
params.ContextLimit = append(params.ContextLimit, msg.ContextLimit)
|
||||
params.Compressed = append(params.Compressed, msg.Compressed)
|
||||
params.TotalCostMicros = append(params.TotalCostMicros, msg.TotalCostMicros)
|
||||
params.RuntimeMs = append(params.RuntimeMs, msg.RuntimeMs)
|
||||
params.CreatedBy = append(params.CreatedBy, msg.createdBy)
|
||||
params.ModelConfigID = append(params.ModelConfigID, msg.modelConfigID)
|
||||
params.Role = append(params.Role, msg.role)
|
||||
params.Content = append(params.Content, string(msg.content.RawMessage))
|
||||
params.ContentVersion = append(params.ContentVersion, msg.contentVersion)
|
||||
params.Visibility = append(params.Visibility, msg.visibility)
|
||||
params.InputTokens = append(params.InputTokens, msg.inputTokens)
|
||||
params.OutputTokens = append(params.OutputTokens, msg.outputTokens)
|
||||
params.TotalTokens = append(params.TotalTokens, msg.totalTokens)
|
||||
params.ReasoningTokens = append(params.ReasoningTokens, msg.reasoningTokens)
|
||||
params.CacheCreationTokens = append(params.CacheCreationTokens, msg.cacheCreationTokens)
|
||||
params.CacheReadTokens = append(params.CacheReadTokens, msg.cacheReadTokens)
|
||||
params.ContextLimit = append(params.ContextLimit, msg.contextLimit)
|
||||
params.Compressed = append(params.Compressed, msg.compressed)
|
||||
params.TotalCostMicros = append(params.TotalCostMicros, msg.totalCostMicros)
|
||||
params.RuntimeMs = append(params.RuntimeMs, msg.runtimeMs)
|
||||
}
|
||||
|
||||
func insertUserMessageAndSetPending(
|
||||
@@ -1166,14 +1218,13 @@ func insertUserMessageAndSetPending(
|
||||
msgParams := database.InsertChatMessagesParams{ //nolint:exhaustruct // Fields populated by appendChatMessage.
|
||||
ChatID: lockedChat.ID,
|
||||
}
|
||||
appendChatMessage(&msgParams, chatMessage{
|
||||
Role: database.ChatMessageRoleUser,
|
||||
Content: content,
|
||||
Visibility: database.ChatMessageVisibilityBoth,
|
||||
ModelConfigID: modelConfigID,
|
||||
CreatedBy: createdBy,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
appendChatMessage(&msgParams, newChatMessage(
|
||||
database.ChatMessageRoleUser,
|
||||
content,
|
||||
database.ChatMessageVisibilityBoth,
|
||||
modelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
).withCreatedBy(createdBy))
|
||||
messages, err := insertChatMessageWithStore(ctx, store, msgParams)
|
||||
if err != nil {
|
||||
return database.ChatMessage{}, database.Chat{}, err
|
||||
@@ -2135,17 +2186,16 @@ func (p *Server) tryAutoPromoteQueuedMessage(
|
||||
msgParams := database.InsertChatMessagesParams{ //nolint:exhaustruct // Fields populated by appendChatMessage.
|
||||
ChatID: chat.ID,
|
||||
}
|
||||
appendChatMessage(&msgParams, chatMessage{
|
||||
Role: database.ChatMessageRoleUser,
|
||||
Content: pqtype.NullRawMessage{
|
||||
appendChatMessage(&msgParams, newChatMessage(
|
||||
database.ChatMessageRoleUser,
|
||||
pqtype.NullRawMessage{
|
||||
RawMessage: nextQueued.Content,
|
||||
Valid: len(nextQueued.Content) > 0,
|
||||
},
|
||||
Visibility: database.ChatMessageVisibilityBoth,
|
||||
ModelConfigID: chat.LastModelConfigID,
|
||||
CreatedBy: chat.OwnerID,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
database.ChatMessageVisibilityBoth,
|
||||
chat.LastModelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
).withCreatedBy(chat.OwnerID))
|
||||
msgs, err := insertChatMessageWithStore(ctx, tx, msgParams)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "failed to promote queued message",
|
||||
@@ -2715,34 +2765,28 @@ func (p *Server) runChat(
|
||||
}
|
||||
|
||||
if assistantContent.Valid {
|
||||
appendChatMessage(&stepParams, chatMessage{
|
||||
Role: database.ChatMessageRoleAssistant,
|
||||
Content: assistantContent,
|
||||
Visibility: database.ChatMessageVisibilityBoth,
|
||||
ModelConfigID: modelConfig.ID,
|
||||
CreatedBy: uuid.Nil,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
InputTokens: inputTokens,
|
||||
OutputTokens: outputTokens,
|
||||
TotalTokens: totalTokens,
|
||||
ReasoningTokens: reasoningTokens,
|
||||
CacheCreationTokens: cacheCreationTokens,
|
||||
CacheReadTokens: cacheReadTokens,
|
||||
ContextLimit: contextLimit,
|
||||
TotalCostMicros: totalCostVal,
|
||||
RuntimeMs: runtimeMs,
|
||||
})
|
||||
appendChatMessage(&stepParams, newChatMessage(
|
||||
database.ChatMessageRoleAssistant,
|
||||
assistantContent,
|
||||
database.ChatMessageVisibilityBoth,
|
||||
modelConfig.ID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
).withUsage(
|
||||
inputTokens, outputTokens, totalTokens,
|
||||
reasoningTokens, cacheCreationTokens, cacheReadTokens,
|
||||
).withContextLimit(contextLimit).
|
||||
withTotalCostMicros(totalCostVal).
|
||||
withRuntimeMs(runtimeMs))
|
||||
}
|
||||
|
||||
for _, resultContent := range toolResultContents {
|
||||
appendChatMessage(&stepParams, chatMessage{
|
||||
Role: database.ChatMessageRoleTool,
|
||||
Content: resultContent,
|
||||
Visibility: database.ChatMessageVisibilityBoth,
|
||||
ModelConfigID: modelConfig.ID,
|
||||
CreatedBy: uuid.Nil,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
})
|
||||
appendChatMessage(&stepParams, newChatMessage(
|
||||
database.ChatMessageRoleTool,
|
||||
resultContent,
|
||||
database.ChatMessageVisibilityBoth,
|
||||
modelConfig.ID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
))
|
||||
}
|
||||
|
||||
if len(stepParams.Role) > 0 {
|
||||
@@ -3117,37 +3161,31 @@ func (p *Server) persistChatContextSummary(
|
||||
}
|
||||
|
||||
// Hidden summary user message (not published to subscribers).
|
||||
appendChatMessage(&summaryParams, chatMessage{
|
||||
Role: database.ChatMessageRoleUser,
|
||||
Content: systemContent,
|
||||
Visibility: database.ChatMessageVisibilityModel,
|
||||
ModelConfigID: modelConfigID,
|
||||
CreatedBy: uuid.Nil,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
Compressed: true,
|
||||
})
|
||||
appendChatMessage(&summaryParams, newChatMessage(
|
||||
database.ChatMessageRoleUser,
|
||||
systemContent,
|
||||
database.ChatMessageVisibilityModel,
|
||||
modelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
).withCompressed())
|
||||
|
||||
// Assistant tool-call message.
|
||||
appendChatMessage(&summaryParams, chatMessage{
|
||||
Role: database.ChatMessageRoleAssistant,
|
||||
Content: assistantContent,
|
||||
Visibility: database.ChatMessageVisibilityUser,
|
||||
ModelConfigID: modelConfigID,
|
||||
CreatedBy: uuid.Nil,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
Compressed: true,
|
||||
})
|
||||
appendChatMessage(&summaryParams, newChatMessage(
|
||||
database.ChatMessageRoleAssistant,
|
||||
assistantContent,
|
||||
database.ChatMessageVisibilityUser,
|
||||
modelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
).withCompressed())
|
||||
|
||||
// Tool result message.
|
||||
appendChatMessage(&summaryParams, chatMessage{
|
||||
Role: database.ChatMessageRoleTool,
|
||||
Content: toolResult,
|
||||
Visibility: database.ChatMessageVisibilityBoth,
|
||||
ModelConfigID: modelConfigID,
|
||||
CreatedBy: uuid.Nil,
|
||||
ContentVersion: chatprompt.CurrentContentVersion,
|
||||
Compressed: true,
|
||||
})
|
||||
appendChatMessage(&summaryParams, newChatMessage(
|
||||
database.ChatMessageRoleTool,
|
||||
toolResult,
|
||||
database.ChatMessageVisibilityBoth,
|
||||
modelConfigID,
|
||||
chatprompt.CurrentContentVersion,
|
||||
).withCompressed())
|
||||
|
||||
allInserted, txErr := tx.InsertChatMessages(ctx, summaryParams)
|
||||
if txErr != nil {
|
||||
|
||||
+28
-28
@@ -4059,35 +4059,35 @@ func seedChatCostFixture(t *testing.T) chatCostTestFixture {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var earliestCreatedAt time.Time
|
||||
var latestCreatedAt time.Time
|
||||
for i := 0; i < 2; i++ {
|
||||
results, err := db.InsertChatMessages(dbauthz.AsSystemRestricted(ctx), database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: []uuid.UUID{uuid.Nil},
|
||||
ModelConfigID: []uuid.UUID{modelConfig.ID},
|
||||
Role: []database.ChatMessageRole{"assistant"},
|
||||
Content: []string{"null"},
|
||||
ContentVersion: []int16{0},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{100},
|
||||
OutputTokens: []int64{50},
|
||||
TotalTokens: []int64{0},
|
||||
ReasoningTokens: []int64{0},
|
||||
CacheCreationTokens: []int64{0},
|
||||
CacheReadTokens: []int64{0},
|
||||
ContextLimit: []int64{0},
|
||||
Compressed: []bool{false},
|
||||
TotalCostMicros: []int64{500},
|
||||
RuntimeMs: []int64{0},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
message := results[0]
|
||||
if i == 0 || message.CreatedAt.Before(earliestCreatedAt) {
|
||||
earliestCreatedAt = message.CreatedAt
|
||||
results, err := db.InsertChatMessages(dbauthz.AsSystemRestricted(ctx), database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: []uuid.UUID{uuid.Nil, uuid.Nil},
|
||||
ModelConfigID: []uuid.UUID{modelConfig.ID, modelConfig.ID},
|
||||
Role: []database.ChatMessageRole{"assistant", "assistant"},
|
||||
Content: []string{"null", "null"},
|
||||
ContentVersion: []int16{0, 0},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth},
|
||||
InputTokens: []int64{100, 100},
|
||||
OutputTokens: []int64{50, 50},
|
||||
TotalTokens: []int64{0, 0},
|
||||
ReasoningTokens: []int64{0, 0},
|
||||
CacheCreationTokens: []int64{0, 0},
|
||||
CacheReadTokens: []int64{0, 0},
|
||||
ContextLimit: []int64{0, 0},
|
||||
Compressed: []bool{false, false},
|
||||
TotalCostMicros: []int64{500, 500},
|
||||
RuntimeMs: []int64{0, 0},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, results, 2)
|
||||
earliestCreatedAt := results[0].CreatedAt
|
||||
latestCreatedAt := results[0].CreatedAt
|
||||
for _, msg := range results {
|
||||
if msg.CreatedAt.Before(earliestCreatedAt) {
|
||||
earliestCreatedAt = msg.CreatedAt
|
||||
}
|
||||
if i == 0 || message.CreatedAt.After(latestCreatedAt) {
|
||||
latestCreatedAt = message.CreatedAt
|
||||
if msg.CreatedAt.After(latestCreatedAt) {
|
||||
latestCreatedAt = msg.CreatedAt
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9534,6 +9534,68 @@ func TestInsertChatMessages(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, modelConfigA.ID, gotChat.LastModelConfigID)
|
||||
})
|
||||
|
||||
t.Run("BatchInsertMultipleMessages", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store, ctx, user, chat, _, modelConfigA := setupChat(t)
|
||||
|
||||
msgs, err := store.InsertChatMessages(ctx, database.InsertChatMessagesParams{
|
||||
ChatID: chat.ID,
|
||||
CreatedBy: []uuid.UUID{user.ID, uuid.Nil, uuid.Nil},
|
||||
ModelConfigID: []uuid.UUID{modelConfigA.ID, modelConfigA.ID, modelConfigA.ID},
|
||||
Role: []database.ChatMessageRole{database.ChatMessageRoleUser, database.ChatMessageRoleAssistant, database.ChatMessageRoleTool},
|
||||
ContentVersion: []int16{chatprompt.CurrentContentVersion, chatprompt.CurrentContentVersion, chatprompt.CurrentContentVersion},
|
||||
Visibility: []database.ChatMessageVisibility{database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth, database.ChatMessageVisibilityBoth},
|
||||
Content: []string{`"hello"`, `"response"`, `"tool result"`},
|
||||
InputTokens: []int64{10, 0, 0},
|
||||
OutputTokens: []int64{0, 20, 0},
|
||||
TotalTokens: []int64{10, 20, 0},
|
||||
ReasoningTokens: []int64{0, 5, 0},
|
||||
CacheCreationTokens: []int64{0, 0, 0},
|
||||
CacheReadTokens: []int64{0, 0, 0},
|
||||
ContextLimit: []int64{0, 0, 0},
|
||||
Compressed: []bool{false, false, false},
|
||||
TotalCostMicros: []int64{0, 100, 0},
|
||||
RuntimeMs: []int64{0, 500, 0},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, msgs, 3)
|
||||
|
||||
// Verify ordering and roles.
|
||||
require.Equal(t, database.ChatMessageRoleUser, msgs[0].Role)
|
||||
require.Equal(t, database.ChatMessageRoleAssistant, msgs[1].Role)
|
||||
require.Equal(t, database.ChatMessageRoleTool, msgs[2].Role)
|
||||
|
||||
// Verify IDs are sequential.
|
||||
require.Less(t, msgs[0].ID, msgs[1].ID)
|
||||
require.Less(t, msgs[1].ID, msgs[2].ID)
|
||||
|
||||
// Verify nullable fields: user message has CreatedBy set.
|
||||
require.True(t, msgs[0].CreatedBy.Valid)
|
||||
require.Equal(t, user.ID, msgs[0].CreatedBy.UUID)
|
||||
// Assistant and tool messages have NULL CreatedBy.
|
||||
require.False(t, msgs[1].CreatedBy.Valid)
|
||||
require.False(t, msgs[2].CreatedBy.Valid)
|
||||
|
||||
// Verify token fields stored as NULL when zero.
|
||||
require.True(t, msgs[0].InputTokens.Valid)
|
||||
require.Equal(t, int64(10), msgs[0].InputTokens.Int64)
|
||||
require.False(t, msgs[0].OutputTokens.Valid) // 0 → NULL
|
||||
require.True(t, msgs[1].OutputTokens.Valid)
|
||||
require.Equal(t, int64(20), msgs[1].OutputTokens.Int64)
|
||||
|
||||
// Verify cost: assistant has cost, others NULL.
|
||||
require.True(t, msgs[1].TotalCostMicros.Valid)
|
||||
require.Equal(t, int64(100), msgs[1].TotalCostMicros.Int64)
|
||||
require.False(t, msgs[0].TotalCostMicros.Valid)
|
||||
require.False(t, msgs[2].TotalCostMicros.Valid)
|
||||
|
||||
// Verify runtime_ms on assistant message.
|
||||
require.True(t, msgs[1].RuntimeMs.Valid)
|
||||
require.Equal(t, int64(500), msgs[1].RuntimeMs.Int64)
|
||||
require.False(t, msgs[0].RuntimeMs.Valid)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetChatMessagesForPromptByChatID(t *testing.T) {
|
||||
|
||||
@@ -4848,7 +4848,7 @@ WITH updated_chat AS (
|
||||
SET
|
||||
last_model_config_id = (
|
||||
SELECT val
|
||||
FROM unnest($3::uuid[])
|
||||
FROM UNNEST($3::uuid[])
|
||||
WITH ORDINALITY AS t(val, ord)
|
||||
WHERE val != '00000000-0000-0000-0000-000000000000'::uuid
|
||||
ORDER BY ord DESC
|
||||
@@ -4858,12 +4858,12 @@ WITH updated_chat AS (
|
||||
id = $1::uuid
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest($3::uuid[])
|
||||
FROM UNNEST($3::uuid[])
|
||||
WHERE unnest != '00000000-0000-0000-0000-000000000000'::uuid
|
||||
)
|
||||
AND chats.last_model_config_id IS DISTINCT FROM (
|
||||
SELECT val
|
||||
FROM unnest($3::uuid[])
|
||||
FROM UNNEST($3::uuid[])
|
||||
WITH ORDINALITY AS t(val, ord)
|
||||
WHERE val != '00000000-0000-0000-0000-000000000000'::uuid
|
||||
ORDER BY ord DESC
|
||||
@@ -4891,22 +4891,22 @@ INSERT INTO chat_messages (
|
||||
)
|
||||
SELECT
|
||||
$1::uuid,
|
||||
NULLIF(unnest($2::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
NULLIF(unnest($3::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
unnest($4::chat_message_role[]),
|
||||
unnest($5::text[])::jsonb,
|
||||
unnest($6::smallint[]),
|
||||
unnest($7::chat_message_visibility[]),
|
||||
NULLIF(unnest($8::bigint[]), 0),
|
||||
NULLIF(unnest($9::bigint[]), 0),
|
||||
NULLIF(unnest($10::bigint[]), 0),
|
||||
NULLIF(unnest($11::bigint[]), 0),
|
||||
NULLIF(unnest($12::bigint[]), 0),
|
||||
NULLIF(unnest($13::bigint[]), 0),
|
||||
NULLIF(unnest($14::bigint[]), 0),
|
||||
unnest($15::boolean[]),
|
||||
NULLIF(unnest($16::bigint[]), 0),
|
||||
NULLIF(unnest($17::bigint[]), 0)
|
||||
NULLIF(UNNEST($2::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
NULLIF(UNNEST($3::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
UNNEST($4::chat_message_role[]),
|
||||
UNNEST($5::text[])::jsonb,
|
||||
UNNEST($6::smallint[]),
|
||||
UNNEST($7::chat_message_visibility[]),
|
||||
NULLIF(UNNEST($8::bigint[]), 0),
|
||||
NULLIF(UNNEST($9::bigint[]), 0),
|
||||
NULLIF(UNNEST($10::bigint[]), 0),
|
||||
NULLIF(UNNEST($11::bigint[]), 0),
|
||||
NULLIF(UNNEST($12::bigint[]), 0),
|
||||
NULLIF(UNNEST($13::bigint[]), 0),
|
||||
NULLIF(UNNEST($14::bigint[]), 0),
|
||||
UNNEST($15::boolean[]),
|
||||
NULLIF(UNNEST($16::bigint[]), 0),
|
||||
NULLIF(UNNEST($17::bigint[]), 0)
|
||||
RETURNING
|
||||
id, chat_id, model_config_id, created_at, role, content, visibility, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_creation_tokens, cache_read_tokens, context_limit, compressed, created_by, content_version, total_cost_micros, runtime_ms
|
||||
`
|
||||
|
||||
@@ -185,7 +185,7 @@ WITH updated_chat AS (
|
||||
SET
|
||||
last_model_config_id = (
|
||||
SELECT val
|
||||
FROM unnest(@model_config_id::uuid[])
|
||||
FROM UNNEST(@model_config_id::uuid[])
|
||||
WITH ORDINALITY AS t(val, ord)
|
||||
WHERE val != '00000000-0000-0000-0000-000000000000'::uuid
|
||||
ORDER BY ord DESC
|
||||
@@ -195,12 +195,12 @@ WITH updated_chat AS (
|
||||
id = @chat_id::uuid
|
||||
AND EXISTS (
|
||||
SELECT 1
|
||||
FROM unnest(@model_config_id::uuid[])
|
||||
FROM UNNEST(@model_config_id::uuid[])
|
||||
WHERE unnest != '00000000-0000-0000-0000-000000000000'::uuid
|
||||
)
|
||||
AND chats.last_model_config_id IS DISTINCT FROM (
|
||||
SELECT val
|
||||
FROM unnest(@model_config_id::uuid[])
|
||||
FROM UNNEST(@model_config_id::uuid[])
|
||||
WITH ORDINALITY AS t(val, ord)
|
||||
WHERE val != '00000000-0000-0000-0000-000000000000'::uuid
|
||||
ORDER BY ord DESC
|
||||
@@ -228,22 +228,22 @@ INSERT INTO chat_messages (
|
||||
)
|
||||
SELECT
|
||||
@chat_id::uuid,
|
||||
NULLIF(unnest(@created_by::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
NULLIF(unnest(@model_config_id::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
unnest(@role::chat_message_role[]),
|
||||
unnest(@content::text[])::jsonb,
|
||||
unnest(@content_version::smallint[]),
|
||||
unnest(@visibility::chat_message_visibility[]),
|
||||
NULLIF(unnest(@input_tokens::bigint[]), 0),
|
||||
NULLIF(unnest(@output_tokens::bigint[]), 0),
|
||||
NULLIF(unnest(@total_tokens::bigint[]), 0),
|
||||
NULLIF(unnest(@reasoning_tokens::bigint[]), 0),
|
||||
NULLIF(unnest(@cache_creation_tokens::bigint[]), 0),
|
||||
NULLIF(unnest(@cache_read_tokens::bigint[]), 0),
|
||||
NULLIF(unnest(@context_limit::bigint[]), 0),
|
||||
unnest(@compressed::boolean[]),
|
||||
NULLIF(unnest(@total_cost_micros::bigint[]), 0),
|
||||
NULLIF(unnest(@runtime_ms::bigint[]), 0)
|
||||
NULLIF(UNNEST(@created_by::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
NULLIF(UNNEST(@model_config_id::uuid[]), '00000000-0000-0000-0000-000000000000'::uuid),
|
||||
UNNEST(@role::chat_message_role[]),
|
||||
UNNEST(@content::text[])::jsonb,
|
||||
UNNEST(@content_version::smallint[]),
|
||||
UNNEST(@visibility::chat_message_visibility[]),
|
||||
NULLIF(UNNEST(@input_tokens::bigint[]), 0),
|
||||
NULLIF(UNNEST(@output_tokens::bigint[]), 0),
|
||||
NULLIF(UNNEST(@total_tokens::bigint[]), 0),
|
||||
NULLIF(UNNEST(@reasoning_tokens::bigint[]), 0),
|
||||
NULLIF(UNNEST(@cache_creation_tokens::bigint[]), 0),
|
||||
NULLIF(UNNEST(@cache_read_tokens::bigint[]), 0),
|
||||
NULLIF(UNNEST(@context_limit::bigint[]), 0),
|
||||
UNNEST(@compressed::boolean[]),
|
||||
NULLIF(UNNEST(@total_cost_micros::bigint[]), 0),
|
||||
NULLIF(UNNEST(@runtime_ms::bigint[]), 0)
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user