package memory import ( "database/sql" "encoding/json" "fmt" ) // 本文件是 memory_items 的**行映射**(scanItem / scanItems 与它们的公共接口 // rowScanner),从 store.go 拆出来只为守 AGENTS §10 的文件行数上限(≤500 行) // 与「模块按职责拆源文件」那条规范 —— 口径逐字未变。 // // 放在独立的源文件里还有一层好处:读路径的每一处「哪些列被读出来」都集中在此, // 与写路径(store.go 的 Add 与 preinsert_gate.go 的闸)对照时不必在 500 行里翻。 // rowScanner 同时被 *sql.Row 与 *sql.Rows 满足,因此单个 scanItem // 辅助函数即可服务 Get 与 ListByPersona。 type rowScanner interface { Scan(dest ...any) error } // scanItems 把每一行读入 MemoryItem 切片,关闭 rows 并用 // 操作名包裹 scan/遍历失败。成功时总是返回 // 非 nil、可能为空的切片。 func scanItems(rows *sql.Rows, op string) ([]MemoryItem, error) { defer func() { _ = rows.Close() }() items := make([]MemoryItem, 0) for rows.Next() { item, err := scanItem(rows) if err != nil { return nil, fmt.Errorf("memory: scan %s: %w", op, err) } items = append(items, item) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("memory: iterate %s: %w", op, err) } return items, nil } // scanItem 读取一行 memory_items。tags 以 JSON 数组字符串存储; // 时间戳以定宽 UTC 字符串(timeLayout)存储,因此文本 // 比较即可按时间排序。 func scanItem(sc rowScanner) (MemoryItem, error) { var ( it MemoryItem scope, kind string tagsJSON string createdAt, updatedAt string ) if err := sc.Scan(&it.ID, &it.PersonaID, &it.RoleID, &it.ProjectID, &scope, &kind, &it.Content, &tagsJSON, &it.Confidence, &it.SourceConversation, &createdAt, &updatedAt, &it.DecayWeight); err != nil { return MemoryItem{}, err } it.Scope = Scope(scope) it.Kind = Kind(kind) if err := json.Unmarshal([]byte(tagsJSON), &it.Tags); err != nil { return MemoryItem{}, fmt.Errorf("memory: decode tags: %w", err) } if it.Tags == nil { it.Tags = []string{} } var err error if it.CreatedAt, err = parseTime(createdAt); err != nil { return MemoryItem{}, err } if it.UpdatedAt, err = parseTime(updatedAt); err != nil { return MemoryItem{}, err } return it, nil }