fix(chat): cast date_trunc to timestamptz, drop interval hack

This commit is contained in:
2026-05-04 16:43:54 +08:00
parent daf13fff8a
commit 98b4f01b10
3 changed files with 20 additions and 29 deletions
+6 -15
View File
@@ -25,7 +25,6 @@ import (
)
// Postgres epoch: 2000-01-01 00:00:00 UTC
var pgEpoch = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
// Repository 是 chat 模块对 PG 的全部依赖。
// Service 单元测试通过手写 fakeRepo 满足该接口。
@@ -276,17 +275,12 @@ func float64ToNumeric(f float64) pgtype.Numeric {
return n
}
// intervalToTime は date_trunc('day',...) が pgtype.Interval として返ってくる
// sqlc 型推論ドリフトへの対処。Postgres は timestamptz を binary で
// 「2000-01-01 エポックからのマイクロ秒」として送るが、pgx が pgtype.Interval
// へスキャンした場合 Microseconds フィールドに入る。
// Drift 注記: sqlc v1.31.1 は date_trunc の返り型を誤って interval と推論する。
func intervalToTime(iv pgtype.Interval) time.Time {
if !iv.Valid {
// timestamptzToTime safely extracts time.Time from pgtype.Timestamptz.
func timestamptzToTime(ts pgtype.Timestamptz) time.Time {
if !ts.Valid {
return time.Time{}
}
// Microseconds はここでは「Postgres epoch からのマイクロ秒数」として解釈する。
return pgEpoch.Add(time.Duration(iv.Microseconds) * time.Microsecond)
return ts.Time
}
// ===== PG エラー変換 =====
@@ -462,10 +456,7 @@ func modelUsageRowFromRow(r chatsqlc.SummarizeUsageByModelRow) ModelUsageRow {
func dayUsageRowFromRow(r chatsqlc.SummarizeUsageByDayRow) DayUsageRow {
return DayUsageRow{
// Drift: sqlc 把 date_trunc('day', timestamptz) 推断为 pgtype.Interval。
// pgx 在 binary 模式下把 timestamptz 值的 "Postgres epoch + microseconds"
// 送入 Interval.Microseconds 字段。用 pgEpoch + Duration 还原。
Day: intervalToTime(r.Day),
Day: timestamptzToTime(r.Day),
PromptTokens: int(r.PromptTokens),
CompletionTokens: int(r.CompletionTokens),
ThinkingTokens: int(r.ThinkingTokens),
@@ -1142,7 +1133,7 @@ func (r *pgRepo) SummarizeUserDaily(ctx context.Context, userID uuid.UUID, from,
out := make([]DayUsageRow, 0, len(rows))
for _, row := range rows {
out = append(out, DayUsageRow{
Day: intervalToTime(row.Day),
Day: timestamptzToTime(row.Day),
PromptTokens: int(row.PromptTokens),
CompletionTokens: int(row.CompletionTokens),
ThinkingTokens: int(row.ThinkingTokens),