返回 DeepSeek-Reasonix
session_display_pager_event_scan.go
根目录 / internal / agent / session_display_pager_event_scan.go
1 package agent
2
3 import (
4 "context"
5 "database/sql"
6 "encoding/json"
7 "errors"
8 "io"
9 "os"
10 "strings"
11
12 "reasonix/internal/historywork"
13 "reasonix/internal/provider"
14 )
15
16 // Each committed point is either a complete record or a complete element of
17 // its messages array. The staged rows and parser state commit together, even
18 // when a legacy writer puts type/schema fields after the messages array.
19 type eventPagerScanProgress struct {
20 Version int `json:"version"`
21 SourceKey string `json:"sourceKey"`
22 Offset int64 `json:"offset"`
23 Count int `json:"count"`
24 Pending int `json:"pending"`
25 InMessages bool `json:"inMessages"`
26 Done bool `json:"done"`
27 Record sessionEventRecord `json:"record"`
28 }
29
30 type eventPagerScanner struct {
31 ctx context.Context
32 db *sql.DB
33 tx *sql.Tx
34 decoder *json.Decoder
35 base int64
36 progress eventPagerScanProgress
37 resuming bool
38 observed func(string, int)
39 records int
40 }
41
42 func (s *eventPagerScanner) commit(inMessages, done bool) error {
43 s.progress.Offset = s.base + s.decoder.InputOffset()
44 s.progress.InMessages, s.progress.Done = inMessages, done
45 body, err := json.Marshal(s.progress)
46 if err == nil {
47 _, err = s.tx.ExecContext(s.ctx, `INSERT OR REPLACE INTO metadata VALUES('event_scan_progress',?)`, string(body))
48 }
49 if err != nil {
50 return err
51 }
52 if err := s.tx.Commit(); err != nil {
53 return err
54 }
55 s.tx = nil
56 s.records = 0
57 if s.observed != nil {
58 if inMessages {
59 s.observed("messages", s.progress.Pending)
60 } else {
61 s.observed("records", s.progress.Count)
62 }
63 }
64 return s.ctx.Err()
65 }
66
67 func (s *eventPagerScanner) transaction() error {
68 if s.tx != nil {
69 return nil
70 }
71 var err error
72 s.tx, err = s.db.BeginTx(s.ctx, nil)
73 return err
74 }
75
76 func (s *eventPagerScanner) messages() error {
77 if !s.resuming {
78 s.progress.Pending = 0
79 if _, err := s.tx.ExecContext(s.ctx, `DELETE FROM event_pending`); err != nil {
80 return err
81 }
82 }
83 token, err := s.decoder.Token()
84 if err != nil || token == nil {
85 return err
86 }
87 if token != json.Delim('[') {
88 return ErrSessionDisplayReadModelDamaged
89 }
90 if s.resuming {
91 // Consume the synthetic prior element; the following separator and
92 // remainder are validated by encoding/json with their original grammar.
93 var previous any
94 if err := s.decoder.Decode(&previous); err != nil {
95 return err
96 }
97 s.resuming = false
98 }
99 for s.decoder.More() {
100 if err := s.transaction(); err != nil {
101 return err
102 }
103 start := s.base + s.decoder.InputOffset()
104 var message provider.Message
105 if err := s.decoder.Decode(&message); err != nil {
106 return err
107 }
108 length := s.base + s.decoder.InputOffset() - start
109 if _, err := s.tx.ExecContext(s.ctx, `INSERT INTO event_pending VALUES(?,?,?)`, s.progress.Pending, start, length); err != nil {
110 return err
111 }
112 s.progress.Pending++
113 if s.progress.Pending%historywork.BatchEntries == 0 {
114 if err := s.commit(true, false); err != nil {
115 return err
116 }
117 }
118 }
119 if _, err := s.decoder.Token(); err != nil {
120 return err
121 }
122 return s.transaction()
123 }
124
125 func (s *eventPagerScanner) fields() error {
126 for s.decoder.More() {
127 if err := s.ctx.Err(); err != nil {
128 return err
129 }
130 field, err := s.decoder.Token()
131 if err != nil {
132 return err
133 }
134 switch field {
135 case "schema_version":
136 err = s.decoder.Decode(&s.progress.Record.SchemaVersion)
137 case "type":
138 err = s.decoder.Decode(&s.progress.Record.Type)
139 case "message_index":
140 err = s.decoder.Decode(&s.progress.Record.MessageIndex)
141 case "created_at":
142 err = s.decoder.Decode(&s.progress.Record.CreatedAt)
143 case "messages":
144 err = s.messages()
145 default:
146 err = skipDisplayJSONValue(s.ctx, s.decoder)
147 }
148 if err != nil {
149 return err
150 }
151 }
152 _, err := s.decoder.Token()
153 return err
154 }
155
156 func (s *eventPagerScanner) applyRecord() error {
157 record := s.progress.Record
158 if record.SchemaVersion != sessionEventSchemaVersion {
159 return ErrSessionDisplayReadModelDamaged
160 }
161 at := int64(0)
162 switch record.Type {
163 case sessionEventTypeReplace:
164 if _, err := s.tx.ExecContext(s.ctx, `DELETE FROM event_locations`); err != nil {
165 return err
166 }
167 s.progress.Count = 0
168 case sessionEventTypeAppend:
169 if record.MessageIndex != s.progress.Count {
170 return ErrSessionDisplayReadModelDamaged
171 }
172 if !record.CreatedAt.IsZero() {
173 at = record.CreatedAt.UnixMilli()
174 }
175 default:
176 return ErrSessionDisplayReadModelDamaged
177 }
178 if _, err := s.tx.ExecContext(s.ctx, `INSERT INTO event_locations SELECT position+?,offset,length,? FROM event_pending`, s.progress.Count, at); err != nil {
179 return err
180 }
181 s.progress.Count += s.progress.Pending
182 s.progress.Pending, s.progress.Record = 0, sessionEventRecord{}
183 _, err := s.tx.ExecContext(s.ctx, `DELETE FROM event_pending`)
184 return err
185 }
186
187 func (s *eventPagerScanner) scan(file *os.File) (result error) {
188 if s.progress.Done {
189 return nil
190 }
191 if _, err := file.Seek(s.progress.Offset, io.SeekStart); err != nil {
192 return err
193 }
194 var reader io.Reader = &historywork.Reader{Context: s.ctx, Source: file}
195 s.base = s.progress.Offset
196 s.resuming = s.progress.InMessages
197 if s.resuming {
198 const framing = `{"messages":[null`
199 reader = io.MultiReader(strings.NewReader(framing), reader)
200 s.base -= int64(len(framing))
201 }
202 s.decoder = json.NewDecoder(reader)
203 defer func() {
204 if s.tx != nil {
205 _ = s.tx.Rollback()
206 }
207 }()
208 for {
209 if err := s.transaction(); err != nil {
210 return err
211 }
212 token, err := s.decoder.Token()
213 if errors.Is(err, io.EOF) {
214 return s.commit(false, true)
215 }
216 if err != nil || token != json.Delim('{') {
217 return errors.Join(ErrSessionDisplayReadModelDamaged, err)
218 }
219 if err := s.fields(); err != nil {
220 return errors.Join(ErrSessionDisplayReadModelDamaged, err)
221 }
222 if err := s.applyRecord(); err != nil {
223 return err
224 }
225 s.records++
226 if s.records >= historywork.BatchEntries {
227 if err := s.commit(false, false); err != nil {
228 return err
229 }
230 }
231 }
232 }
233
233 lines GO