fix(persist,channel): 同类定宽时间戳修复 — checkpoint 恢复与渠道会话列表排序
- persist: formatTime 定宽;loadLatestSQL 补 rowid 兜底;New 时归一化历史行(避免 LoadLatest 挑到更旧检查点) - channel/session: 写入定宽;listSessionSQL 补 rowid 兜底;核实无 INSERT 吃 DDL 默认值,故只加注释不改默认值 - 新增回归测试并做变异验证(改回 RFC3339Nano 即 FAIL)
This commit is contained in:
1 parent
31086ed1ab
commit
fbbfcc95ee
7 files changed
+445
-14
No files matched your search
@@ -94,9 +94,13 @@ const (
|
||||
loadSQL = `SELECT id, agent_id, step, state, created_at, updated_at
|
||||
FROM checkpoints WHERE id = ?`
|
||||
|
||||
// updated_at is stored as fixed-width UTC text (see formatTime), so its text
|
||||
// order is its time order. step breaks ties between checkpoints written in
|
||||
// the same tick, and rowid makes that tiebreak deterministic (checkpoints is
|
||||
// an ordinary rowid table, not WITHOUT ROWID).
|
||||
loadLatestSQL = `SELECT id, agent_id, step, state, created_at, updated_at
|
||||
FROM checkpoints WHERE agent_id = ?
|
||||
ORDER BY updated_at DESC, step DESC LIMIT 1`
|
||||
ORDER BY updated_at DESC, step DESC, rowid DESC LIMIT 1`
|
||||
|
||||
listSQL = `SELECT id, agent_id, step, state, created_at, updated_at
|
||||
FROM checkpoints WHERE agent_id = ?
|
||||
@@ -112,6 +116,8 @@ const (
|
||||
// which already constrains the pool to a single connection for write
|
||||
// serialization. The caller owns db and must Close it when done. log may be nil
|
||||
// and is replaced with a discard logger so the Store can always log safely.
|
||||
// Legacy variable-width timestamps are normalized in place (see
|
||||
// normalizeTimestamps) so text ordering matches time ordering.
|
||||
func New(ctx context.Context, db *sql.DB, log *slog.Logger) (*Store, error) {
|
||||
if db == nil {
|
||||
return nil, fmt.Errorf("persist: new: %w", ErrNilDB)
|
||||
@@ -124,6 +130,9 @@ func New(ctx context.Context, db *sql.DB, log *slog.Logger) (*Store, error) {
|
||||
if _, err := db.ExecContext(ctx, createIndexSQL); err != nil {
|
||||
return nil, fmt.Errorf("persist: create index: %w", err)
|
||||
}
|
||||
if err := normalizeTimestamps(ctx, db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Store{db: db, log: log}, nil
|
||||
}
|
||||
|
||||
@@ -270,16 +279,6 @@ func scanCheckpoint(sc rowScanner) (*Checkpoint, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// formatTime renders t as a UTC RFC3339 timestamp with nanosecond precision.
|
||||
func formatTime(t time.Time) string {
|
||||
return t.UTC().Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
// parseTime parses a timestamp written by formatTime.
|
||||
func parseTime(s string) (time.Time, error) {
|
||||
return time.Parse(time.RFC3339Nano, s)
|
||||
}
|
||||
|
||||
// discardLogger returns log if non-nil, otherwise a logger that writes nowhere,
|
||||
// so the Store can always log safely.
|
||||
func discardLogger(log *slog.Logger) *slog.Logger {
|
||||
|
||||
Reference in new issue
Block a user