@prisma/streams-server 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +8 -0
- package/package.json +2 -1
- package/src/app.ts +290 -17
- package/src/app_core.ts +1833 -698
- package/src/app_local.ts +144 -4
- package/src/auto_tune.ts +62 -0
- package/src/bootstrap.ts +159 -1
- package/src/concurrency_gate.ts +108 -0
- package/src/config.ts +116 -14
- package/src/db/db.ts +1201 -131
- package/src/db/schema.ts +308 -8
- package/src/foreground_activity.ts +55 -0
- package/src/index/indexer.ts +254 -124
- package/src/index/lexicon_file_cache.ts +261 -0
- package/src/index/lexicon_format.ts +93 -0
- package/src/index/lexicon_indexer.ts +789 -0
- package/src/index/secondary_indexer.ts +824 -0
- package/src/index/secondary_schema.ts +105 -0
- package/src/ingest.ts +10 -12
- package/src/manifest.ts +143 -8
- package/src/memory.ts +183 -8
- package/src/metrics.ts +15 -29
- package/src/metrics_emitter.ts +26 -3
- package/src/notifier.ts +121 -5
- package/src/objectstore/accounting.ts +92 -0
- package/src/objectstore/mock_r2.ts +1 -1
- package/src/objectstore/r2.ts +17 -1
- package/src/profiles/evlog/schema.ts +234 -0
- package/src/profiles/evlog.ts +299 -0
- package/src/profiles/generic.ts +47 -0
- package/src/profiles/index.ts +205 -0
- package/src/profiles/metrics/block_format.ts +109 -0
- package/src/profiles/metrics/normalize.ts +366 -0
- package/src/profiles/metrics/schema.ts +319 -0
- package/src/profiles/metrics.ts +85 -0
- package/src/profiles/profile.ts +225 -0
- package/src/{touch/engine.ts → profiles/stateProtocol/changes.ts} +3 -20
- package/src/profiles/stateProtocol/routes.ts +389 -0
- package/src/profiles/stateProtocol/types.ts +6 -0
- package/src/profiles/stateProtocol/validation.ts +51 -0
- package/src/profiles/stateProtocol.ts +100 -0
- package/src/read_filter.ts +468 -0
- package/src/reader.ts +2151 -164
- package/src/runtime/host_runtime.ts +5 -0
- package/src/runtime_memory.ts +200 -0
- package/src/runtime_memory_sampler.ts +235 -0
- package/src/schema/read_json.ts +43 -0
- package/src/schema/registry.ts +563 -59
- package/src/search/agg_format.ts +638 -0
- package/src/search/aggregate.ts +389 -0
- package/src/search/binary/codec.ts +162 -0
- package/src/search/binary/docset.ts +67 -0
- package/src/search/binary/restart_strings.ts +181 -0
- package/src/search/binary/varint.ts +34 -0
- package/src/search/bitset.ts +19 -0
- package/src/search/col_format.ts +382 -0
- package/src/search/col_runtime.ts +59 -0
- package/src/search/column_encoding.ts +43 -0
- package/src/search/companion_file_cache.ts +319 -0
- package/src/search/companion_format.ts +313 -0
- package/src/search/companion_manager.ts +1086 -0
- package/src/search/companion_plan.ts +218 -0
- package/src/search/fts_format.ts +423 -0
- package/src/search/fts_runtime.ts +333 -0
- package/src/search/query.ts +875 -0
- package/src/search/schema.ts +245 -0
- package/src/segment/cache.ts +93 -2
- package/src/segment/cached_segment.ts +89 -0
- package/src/segment/format.ts +108 -36
- package/src/segment/segmenter.ts +79 -5
- package/src/segment/segmenter_worker.ts +35 -6
- package/src/segment/segmenter_workers.ts +42 -12
- package/src/server.ts +150 -36
- package/src/sqlite/adapter.ts +185 -14
- package/src/sqlite/runtime_stats.ts +163 -0
- package/src/stats.ts +3 -3
- package/src/stream_size_reconciler.ts +100 -0
- package/src/touch/canonical_change.ts +7 -0
- package/src/touch/live_metrics.ts +94 -64
- package/src/touch/live_templates.ts +15 -1
- package/src/touch/manager.ts +166 -88
- package/src/touch/{interpreter_worker.ts → processor_worker.ts} +19 -14
- package/src/touch/spec.ts +95 -92
- package/src/touch/touch_journal.ts +4 -0
- package/src/touch/worker_pool.ts +8 -14
- package/src/touch/worker_protocol.ts +3 -3
- package/src/uploader.ts +77 -6
- package/src/util/bloom256.ts +2 -2
- package/src/util/byte_lru.ts +73 -0
- package/src/util/lru.ts +8 -0
- package/src/util/stream_paths.ts +19 -0
package/src/db/db.ts
CHANGED
|
@@ -8,9 +8,9 @@ export const STREAM_FLAG_TOUCH = 1 << 1;
|
|
|
8
8
|
|
|
9
9
|
const BASE_WAL_GC_CHUNK_OFFSETS = (() => {
|
|
10
10
|
const raw = process.env.DS_BASE_WAL_GC_CHUNK_OFFSETS;
|
|
11
|
-
if (raw == null || raw.trim() === "") return
|
|
11
|
+
if (raw == null || raw.trim() === "") return 1_000_000;
|
|
12
12
|
const n = Number(raw);
|
|
13
|
-
if (!Number.isFinite(n) || n <= 0) return
|
|
13
|
+
if (!Number.isFinite(n) || n <= 0) return 1_000_000;
|
|
14
14
|
return Math.floor(n);
|
|
15
15
|
})();
|
|
16
16
|
|
|
@@ -19,6 +19,7 @@ export type StreamRow = {
|
|
|
19
19
|
created_at_ms: bigint;
|
|
20
20
|
updated_at_ms: bigint;
|
|
21
21
|
content_type: string;
|
|
22
|
+
profile: string | null;
|
|
22
23
|
stream_seq: string | null;
|
|
23
24
|
closed: number;
|
|
24
25
|
closed_producer_id: string | null;
|
|
@@ -32,6 +33,7 @@ export type StreamRow = {
|
|
|
32
33
|
uploaded_segment_count: number;
|
|
33
34
|
pending_rows: bigint;
|
|
34
35
|
pending_bytes: bigint;
|
|
36
|
+
logical_size_bytes: bigint;
|
|
35
37
|
wal_rows: bigint;
|
|
36
38
|
wal_bytes: bigint;
|
|
37
39
|
last_append_ms: bigint;
|
|
@@ -49,6 +51,7 @@ export type SegmentRow = {
|
|
|
49
51
|
end_offset: bigint;
|
|
50
52
|
block_count: number;
|
|
51
53
|
last_append_ms: bigint;
|
|
54
|
+
payload_bytes: bigint;
|
|
52
55
|
size_bytes: number;
|
|
53
56
|
local_path: string;
|
|
54
57
|
created_at_ms: bigint;
|
|
@@ -78,12 +81,81 @@ export type IndexRunRow = {
|
|
|
78
81
|
start_segment: number;
|
|
79
82
|
end_segment: number;
|
|
80
83
|
object_key: string;
|
|
84
|
+
size_bytes: number;
|
|
85
|
+
filter_len: number;
|
|
86
|
+
record_count: number;
|
|
87
|
+
retired_gen: number | null;
|
|
88
|
+
retired_at_ms: bigint | null;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type SecondaryIndexStateRow = {
|
|
92
|
+
stream: string;
|
|
93
|
+
index_name: string;
|
|
94
|
+
index_secret: Uint8Array;
|
|
95
|
+
config_hash: string;
|
|
96
|
+
indexed_through: number;
|
|
97
|
+
updated_at_ms: bigint;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export type SecondaryIndexRunRow = {
|
|
101
|
+
run_id: string;
|
|
102
|
+
stream: string;
|
|
103
|
+
index_name: string;
|
|
104
|
+
level: number;
|
|
105
|
+
start_segment: number;
|
|
106
|
+
end_segment: number;
|
|
107
|
+
object_key: string;
|
|
108
|
+
size_bytes: number;
|
|
81
109
|
filter_len: number;
|
|
82
110
|
record_count: number;
|
|
83
111
|
retired_gen: number | null;
|
|
84
112
|
retired_at_ms: bigint | null;
|
|
85
113
|
};
|
|
86
114
|
|
|
115
|
+
export type LexiconIndexStateRow = {
|
|
116
|
+
stream: string;
|
|
117
|
+
source_kind: string;
|
|
118
|
+
source_name: string;
|
|
119
|
+
indexed_through: number;
|
|
120
|
+
updated_at_ms: bigint;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export type LexiconIndexRunRow = {
|
|
124
|
+
run_id: string;
|
|
125
|
+
stream: string;
|
|
126
|
+
source_kind: string;
|
|
127
|
+
source_name: string;
|
|
128
|
+
level: number;
|
|
129
|
+
start_segment: number;
|
|
130
|
+
end_segment: number;
|
|
131
|
+
object_key: string;
|
|
132
|
+
size_bytes: number;
|
|
133
|
+
record_count: number;
|
|
134
|
+
retired_gen: number | null;
|
|
135
|
+
retired_at_ms: bigint | null;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export type SearchCompanionPlanRow = {
|
|
139
|
+
stream: string;
|
|
140
|
+
generation: number;
|
|
141
|
+
plan_hash: string;
|
|
142
|
+
plan_json: string;
|
|
143
|
+
updated_at_ms: bigint;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export type SearchSegmentCompanionRow = {
|
|
147
|
+
stream: string;
|
|
148
|
+
segment_index: number;
|
|
149
|
+
object_key: string;
|
|
150
|
+
plan_generation: number;
|
|
151
|
+
sections_json: string;
|
|
152
|
+
section_sizes_json: string;
|
|
153
|
+
size_bytes: number;
|
|
154
|
+
primary_timestamp_min_ms: bigint | null;
|
|
155
|
+
primary_timestamp_max_ms: bigint | null;
|
|
156
|
+
updated_at_ms: bigint;
|
|
157
|
+
};
|
|
158
|
+
|
|
87
159
|
export class SqliteDurableStore {
|
|
88
160
|
public readonly db: SqliteDatabase;
|
|
89
161
|
private dbstatReady: boolean | null = null;
|
|
@@ -93,7 +165,9 @@ export class SqliteDurableStore {
|
|
|
93
165
|
getStream: SqliteStatement;
|
|
94
166
|
upsertStream: SqliteStatement;
|
|
95
167
|
listStreams: SqliteStatement;
|
|
168
|
+
listDeletedStreams: SqliteStatement;
|
|
96
169
|
setDeleted: SqliteStatement;
|
|
170
|
+
setStreamProfile: SqliteStatement;
|
|
97
171
|
|
|
98
172
|
insertWal: SqliteStatement;
|
|
99
173
|
|
|
@@ -106,6 +180,8 @@ export class SqliteDurableStore {
|
|
|
106
180
|
|
|
107
181
|
streamWalRange: SqliteStatement;
|
|
108
182
|
streamWalRangeByKey: SqliteStatement;
|
|
183
|
+
streamWalRangeDesc: SqliteStatement;
|
|
184
|
+
streamWalRangeDescByKey: SqliteStatement;
|
|
109
185
|
|
|
110
186
|
createSegment: SqliteStatement;
|
|
111
187
|
listSegmentsForStream: SqliteStatement;
|
|
@@ -113,13 +189,16 @@ export class SqliteDurableStore {
|
|
|
113
189
|
findSegmentForOffset: SqliteStatement;
|
|
114
190
|
nextSegmentIndex: SqliteStatement;
|
|
115
191
|
markSegmentUploaded: SqliteStatement;
|
|
116
|
-
|
|
192
|
+
pendingUploadHeads: SqliteStatement;
|
|
193
|
+
recentSegmentCompressionWindow: SqliteStatement;
|
|
117
194
|
countPendingSegments: SqliteStatement;
|
|
118
195
|
tryClaimSegment: SqliteStatement;
|
|
119
196
|
countSegmentsForStream: SqliteStatement;
|
|
120
197
|
|
|
121
198
|
getManifest: SqliteStatement;
|
|
122
199
|
upsertManifest: SqliteStatement;
|
|
200
|
+
setSchemaUploadedSize: SqliteStatement;
|
|
201
|
+
recordObjectStoreRequest: SqliteStatement;
|
|
123
202
|
|
|
124
203
|
getIndexState: SqliteStatement;
|
|
125
204
|
upsertIndexState: SqliteStatement;
|
|
@@ -130,6 +209,46 @@ export class SqliteDurableStore {
|
|
|
130
209
|
insertIndexRun: SqliteStatement;
|
|
131
210
|
retireIndexRun: SqliteStatement;
|
|
132
211
|
deleteIndexRun: SqliteStatement;
|
|
212
|
+
deleteIndexStateForStream: SqliteStatement;
|
|
213
|
+
deleteIndexRunsForStream: SqliteStatement;
|
|
214
|
+
getSecondaryIndexState: SqliteStatement;
|
|
215
|
+
listSecondaryIndexStates: SqliteStatement;
|
|
216
|
+
upsertSecondaryIndexState: SqliteStatement;
|
|
217
|
+
updateSecondaryIndexedThrough: SqliteStatement;
|
|
218
|
+
listSecondaryIndexRuns: SqliteStatement;
|
|
219
|
+
listSecondaryIndexRunsAll: SqliteStatement;
|
|
220
|
+
listRetiredSecondaryIndexRuns: SqliteStatement;
|
|
221
|
+
insertSecondaryIndexRun: SqliteStatement;
|
|
222
|
+
retireSecondaryIndexRun: SqliteStatement;
|
|
223
|
+
deleteSecondaryIndexRun: SqliteStatement;
|
|
224
|
+
deleteSecondaryIndexState: SqliteStatement;
|
|
225
|
+
deleteSecondaryIndexRunsForIndex: SqliteStatement;
|
|
226
|
+
deleteSecondaryIndexStatesForStream: SqliteStatement;
|
|
227
|
+
deleteSecondaryIndexRunsForStream: SqliteStatement;
|
|
228
|
+
getLexiconIndexState: SqliteStatement;
|
|
229
|
+
listLexiconIndexStates: SqliteStatement;
|
|
230
|
+
upsertLexiconIndexState: SqliteStatement;
|
|
231
|
+
updateLexiconIndexedThrough: SqliteStatement;
|
|
232
|
+
listLexiconIndexRuns: SqliteStatement;
|
|
233
|
+
listLexiconIndexRunsAll: SqliteStatement;
|
|
234
|
+
listRetiredLexiconIndexRuns: SqliteStatement;
|
|
235
|
+
insertLexiconIndexRun: SqliteStatement;
|
|
236
|
+
retireLexiconIndexRun: SqliteStatement;
|
|
237
|
+
deleteLexiconIndexRun: SqliteStatement;
|
|
238
|
+
deleteLexiconIndexState: SqliteStatement;
|
|
239
|
+
deleteLexiconIndexRunsForSource: SqliteStatement;
|
|
240
|
+
deleteLexiconIndexStatesForStream: SqliteStatement;
|
|
241
|
+
deleteLexiconIndexRunsForStream: SqliteStatement;
|
|
242
|
+
getSearchCompanionPlan: SqliteStatement;
|
|
243
|
+
listSearchCompanionPlanStreams: SqliteStatement;
|
|
244
|
+
upsertSearchCompanionPlan: SqliteStatement;
|
|
245
|
+
deleteSearchCompanionPlan: SqliteStatement;
|
|
246
|
+
listSearchSegmentCompanions: SqliteStatement;
|
|
247
|
+
getSearchSegmentCompanion: SqliteStatement;
|
|
248
|
+
upsertSearchSegmentCompanion: SqliteStatement;
|
|
249
|
+
deleteSearchSegmentCompanionsFromGeneration: SqliteStatement;
|
|
250
|
+
deleteSearchSegmentCompanionsFromIndex: SqliteStatement;
|
|
251
|
+
deleteSearchSegmentCompanions: SqliteStatement;
|
|
133
252
|
countUploadedSegments: SqliteStatement;
|
|
134
253
|
getSegmentMeta: SqliteStatement;
|
|
135
254
|
ensureSegmentMeta: SqliteStatement;
|
|
@@ -138,14 +257,17 @@ export class SqliteDurableStore {
|
|
|
138
257
|
setUploadedSegmentCount: SqliteStatement;
|
|
139
258
|
|
|
140
259
|
advanceUploadedThrough: SqliteStatement;
|
|
141
|
-
deleteWalBeforeOffset: SqliteStatement;
|
|
142
260
|
|
|
143
261
|
getSchemaRegistry: SqliteStatement;
|
|
144
262
|
upsertSchemaRegistry: SqliteStatement;
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
263
|
+
getStreamProfile: SqliteStatement;
|
|
264
|
+
upsertStreamProfile: SqliteStatement;
|
|
265
|
+
deleteStreamProfile: SqliteStatement;
|
|
266
|
+
getStreamTouchState: SqliteStatement;
|
|
267
|
+
upsertStreamTouchState: SqliteStatement;
|
|
268
|
+
deleteStreamTouchState: SqliteStatement;
|
|
269
|
+
listStreamTouchStates: SqliteStatement;
|
|
270
|
+
listStreamsByProfile: SqliteStatement;
|
|
149
271
|
countStreams: SqliteStatement;
|
|
150
272
|
sumPendingBytes: SqliteStatement;
|
|
151
273
|
sumPendingSegmentBytes: SqliteStatement;
|
|
@@ -162,31 +284,32 @@ export class SqliteDurableStore {
|
|
|
162
284
|
this.stmts = {
|
|
163
285
|
getStream: this.db.query(
|
|
164
286
|
`SELECT stream, created_at_ms, updated_at_ms,
|
|
165
|
-
content_type, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
287
|
+
content_type, profile, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
166
288
|
epoch, next_offset, sealed_through, uploaded_through, uploaded_segment_count,
|
|
167
|
-
pending_rows, pending_bytes, wal_rows, wal_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
289
|
+
pending_rows, pending_bytes, logical_size_bytes, wal_rows, wal_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
168
290
|
expires_at_ms, stream_flags
|
|
169
291
|
FROM streams WHERE stream = ? LIMIT 1;`
|
|
170
292
|
),
|
|
171
293
|
upsertStream: this.db.query(
|
|
172
294
|
`INSERT INTO streams(stream, created_at_ms, updated_at_ms,
|
|
173
|
-
content_type, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
295
|
+
content_type, profile, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
174
296
|
epoch, next_offset, sealed_through, uploaded_through, uploaded_segment_count,
|
|
175
|
-
pending_rows, pending_bytes, wal_rows, wal_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
297
|
+
pending_rows, pending_bytes, logical_size_bytes, wal_rows, wal_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
176
298
|
expires_at_ms, stream_flags)
|
|
177
|
-
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
299
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
178
300
|
ON CONFLICT(stream) DO UPDATE SET
|
|
179
301
|
updated_at_ms=excluded.updated_at_ms,
|
|
180
302
|
expires_at_ms=excluded.expires_at_ms,
|
|
181
303
|
ttl_seconds=excluded.ttl_seconds,
|
|
182
304
|
content_type=excluded.content_type,
|
|
305
|
+
profile=excluded.profile,
|
|
183
306
|
stream_flags=excluded.stream_flags;`
|
|
184
307
|
),
|
|
185
308
|
listStreams: this.db.query(
|
|
186
309
|
`SELECT stream, created_at_ms, updated_at_ms,
|
|
187
|
-
content_type, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
310
|
+
content_type, profile, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
188
311
|
epoch, next_offset, sealed_through, uploaded_through, uploaded_segment_count,
|
|
189
|
-
pending_rows, pending_bytes, wal_rows, wal_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
312
|
+
pending_rows, pending_bytes, logical_size_bytes, wal_rows, wal_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
190
313
|
expires_at_ms, stream_flags
|
|
191
314
|
FROM streams
|
|
192
315
|
WHERE (stream_flags & ?) = 0
|
|
@@ -194,7 +317,15 @@ export class SqliteDurableStore {
|
|
|
194
317
|
ORDER BY stream
|
|
195
318
|
LIMIT ? OFFSET ?;`
|
|
196
319
|
),
|
|
320
|
+
listDeletedStreams: this.db.query(
|
|
321
|
+
`SELECT stream
|
|
322
|
+
FROM streams
|
|
323
|
+
WHERE (stream_flags & ?) != 0
|
|
324
|
+
ORDER BY stream
|
|
325
|
+
LIMIT ? OFFSET ?;`
|
|
326
|
+
),
|
|
197
327
|
setDeleted: this.db.query(`UPDATE streams SET stream_flags = (stream_flags | ?), updated_at_ms=? WHERE stream=?;`),
|
|
328
|
+
setStreamProfile: this.db.query(`UPDATE streams SET profile=?, updated_at_ms=? WHERE stream=?;`),
|
|
198
329
|
|
|
199
330
|
insertWal: this.db.query(
|
|
200
331
|
`INSERT INTO wal(stream, offset, ts_ms, payload, payload_len, routing_key, content_type, flags)
|
|
@@ -205,6 +336,7 @@ export class SqliteDurableStore {
|
|
|
205
336
|
`UPDATE streams
|
|
206
337
|
SET next_offset = ?, updated_at_ms = ?, last_append_ms = ?,
|
|
207
338
|
pending_rows = pending_rows + ?, pending_bytes = pending_bytes + ?,
|
|
339
|
+
logical_size_bytes = logical_size_bytes + ?,
|
|
208
340
|
wal_rows = wal_rows + ?, wal_bytes = wal_bytes + ?
|
|
209
341
|
WHERE stream = ? AND (stream_flags & ?) = 0;`
|
|
210
342
|
),
|
|
@@ -212,6 +344,7 @@ export class SqliteDurableStore {
|
|
|
212
344
|
`UPDATE streams
|
|
213
345
|
SET next_offset = ?, updated_at_ms = ?, last_append_ms = ?,
|
|
214
346
|
pending_rows = pending_rows + ?, pending_bytes = pending_bytes + ?,
|
|
347
|
+
logical_size_bytes = logical_size_bytes + ?,
|
|
215
348
|
wal_rows = wal_rows + ?, wal_bytes = wal_bytes + ?
|
|
216
349
|
WHERE stream = ? AND (stream_flags & ?) = 0 AND next_offset = ?;`
|
|
217
350
|
),
|
|
@@ -256,24 +389,36 @@ export class SqliteDurableStore {
|
|
|
256
389
|
WHERE stream = ? AND offset >= ? AND offset <= ? AND routing_key = ?
|
|
257
390
|
ORDER BY offset ASC;`
|
|
258
391
|
),
|
|
392
|
+
streamWalRangeDesc: this.db.query(
|
|
393
|
+
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
394
|
+
FROM wal
|
|
395
|
+
WHERE stream = ? AND offset >= ? AND offset <= ?
|
|
396
|
+
ORDER BY offset DESC;`
|
|
397
|
+
),
|
|
398
|
+
streamWalRangeDescByKey: this.db.query(
|
|
399
|
+
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
400
|
+
FROM wal
|
|
401
|
+
WHERE stream = ? AND offset >= ? AND offset <= ? AND routing_key = ?
|
|
402
|
+
ORDER BY offset DESC;`
|
|
403
|
+
),
|
|
259
404
|
|
|
260
405
|
createSegment: this.db.query(
|
|
261
406
|
`INSERT INTO segments(segment_id, stream, segment_index, start_offset, end_offset, block_count,
|
|
262
|
-
last_append_ms, size_bytes, local_path, created_at_ms, uploaded_at_ms, r2_etag)
|
|
263
|
-
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL);`
|
|
407
|
+
last_append_ms, payload_bytes, size_bytes, local_path, created_at_ms, uploaded_at_ms, r2_etag)
|
|
408
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL);`
|
|
264
409
|
),
|
|
265
410
|
listSegmentsForStream: this.db.query(
|
|
266
|
-
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, size_bytes,
|
|
411
|
+
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, payload_bytes, size_bytes,
|
|
267
412
|
local_path, created_at_ms, uploaded_at_ms, r2_etag
|
|
268
413
|
FROM segments WHERE stream=? ORDER BY segment_index ASC;`
|
|
269
414
|
),
|
|
270
415
|
getSegmentByIndex: this.db.query(
|
|
271
|
-
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, size_bytes,
|
|
416
|
+
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, payload_bytes, size_bytes,
|
|
272
417
|
local_path, created_at_ms, uploaded_at_ms, r2_etag
|
|
273
418
|
FROM segments WHERE stream=? AND segment_index=? LIMIT 1;`
|
|
274
419
|
),
|
|
275
420
|
findSegmentForOffset: this.db.query(
|
|
276
|
-
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, size_bytes,
|
|
421
|
+
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, payload_bytes, size_bytes,
|
|
277
422
|
local_path, created_at_ms, uploaded_at_ms, r2_etag
|
|
278
423
|
FROM segments
|
|
279
424
|
WHERE stream=? AND start_offset <= ? AND end_offset >= ?
|
|
@@ -286,10 +431,31 @@ export class SqliteDurableStore {
|
|
|
286
431
|
markSegmentUploaded: this.db.query(
|
|
287
432
|
`UPDATE segments SET r2_etag=?, uploaded_at_ms=? WHERE segment_id=?;`
|
|
288
433
|
),
|
|
289
|
-
|
|
290
|
-
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, size_bytes,
|
|
434
|
+
pendingUploadHeads: this.db.query(
|
|
435
|
+
`SELECT segment_id, stream, segment_index, start_offset, end_offset, block_count, last_append_ms, payload_bytes, size_bytes,
|
|
291
436
|
local_path, created_at_ms, uploaded_at_ms, r2_etag
|
|
292
|
-
FROM segments
|
|
437
|
+
FROM segments s
|
|
438
|
+
WHERE s.uploaded_at_ms IS NULL
|
|
439
|
+
AND s.segment_index = (
|
|
440
|
+
SELECT MIN(s2.segment_index)
|
|
441
|
+
FROM segments s2
|
|
442
|
+
WHERE s2.stream = s.stream AND s2.uploaded_at_ms IS NULL
|
|
443
|
+
)
|
|
444
|
+
ORDER BY s.created_at_ms ASC, s.stream ASC
|
|
445
|
+
LIMIT ?;`
|
|
446
|
+
),
|
|
447
|
+
recentSegmentCompressionWindow: this.db.query(
|
|
448
|
+
`SELECT
|
|
449
|
+
COALESCE(SUM(payload_bytes), 0) AS payload_total,
|
|
450
|
+
COALESCE(SUM(size_bytes), 0) AS size_total,
|
|
451
|
+
COUNT(*) AS cnt
|
|
452
|
+
FROM (
|
|
453
|
+
SELECT payload_bytes, size_bytes
|
|
454
|
+
FROM segments
|
|
455
|
+
WHERE stream=? AND payload_bytes > 0
|
|
456
|
+
ORDER BY segment_index DESC
|
|
457
|
+
LIMIT ?
|
|
458
|
+
);`
|
|
293
459
|
),
|
|
294
460
|
countPendingSegments: this.db.query(`SELECT COUNT(*) as cnt FROM segments WHERE uploaded_at_ms IS NULL;`),
|
|
295
461
|
countSegmentsForStream: this.db.query(`SELECT COUNT(*) as cnt FROM segments WHERE stream=?;`),
|
|
@@ -297,15 +463,19 @@ export class SqliteDurableStore {
|
|
|
297
463
|
`UPDATE streams SET segment_in_progress=1, updated_at_ms=? WHERE stream=? AND segment_in_progress=0;`
|
|
298
464
|
),
|
|
299
465
|
|
|
300
|
-
getManifest: this.db.query(
|
|
466
|
+
getManifest: this.db.query(
|
|
467
|
+
`SELECT stream, generation, uploaded_generation, last_uploaded_at_ms, last_uploaded_etag, last_uploaded_size_bytes
|
|
468
|
+
FROM manifests WHERE stream=? LIMIT 1;`
|
|
469
|
+
),
|
|
301
470
|
upsertManifest: this.db.query(
|
|
302
|
-
`INSERT INTO manifests(stream, generation, uploaded_generation, last_uploaded_at_ms, last_uploaded_etag)
|
|
303
|
-
VALUES(?, ?, ?, ?, ?)
|
|
471
|
+
`INSERT INTO manifests(stream, generation, uploaded_generation, last_uploaded_at_ms, last_uploaded_etag, last_uploaded_size_bytes)
|
|
472
|
+
VALUES(?, ?, ?, ?, ?, ?)
|
|
304
473
|
ON CONFLICT(stream) DO UPDATE SET
|
|
305
474
|
generation=excluded.generation,
|
|
306
475
|
uploaded_generation=excluded.uploaded_generation,
|
|
307
476
|
last_uploaded_at_ms=excluded.last_uploaded_at_ms,
|
|
308
|
-
last_uploaded_etag=excluded.last_uploaded_etag
|
|
477
|
+
last_uploaded_etag=excluded.last_uploaded_etag,
|
|
478
|
+
last_uploaded_size_bytes=excluded.last_uploaded_size_bytes;`
|
|
309
479
|
),
|
|
310
480
|
|
|
311
481
|
getIndexState: this.db.query(
|
|
@@ -324,23 +494,23 @@ export class SqliteDurableStore {
|
|
|
324
494
|
`UPDATE index_state SET indexed_through=?, updated_at_ms=? WHERE stream=?;`
|
|
325
495
|
),
|
|
326
496
|
listIndexRuns: this.db.query(
|
|
327
|
-
`SELECT run_id, stream, level, start_segment, end_segment, object_key, filter_len, record_count, retired_gen, retired_at_ms
|
|
497
|
+
`SELECT run_id, stream, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms
|
|
328
498
|
FROM index_runs WHERE stream=? AND retired_gen IS NULL
|
|
329
499
|
ORDER BY start_segment ASC, level ASC;`
|
|
330
500
|
),
|
|
331
501
|
listIndexRunsAll: this.db.query(
|
|
332
|
-
`SELECT run_id, stream, level, start_segment, end_segment, object_key, filter_len, record_count, retired_gen, retired_at_ms
|
|
502
|
+
`SELECT run_id, stream, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms
|
|
333
503
|
FROM index_runs WHERE stream=?
|
|
334
504
|
ORDER BY start_segment ASC, level ASC;`
|
|
335
505
|
),
|
|
336
506
|
listRetiredIndexRuns: this.db.query(
|
|
337
|
-
`SELECT run_id, stream, level, start_segment, end_segment, object_key, filter_len, record_count, retired_gen, retired_at_ms
|
|
507
|
+
`SELECT run_id, stream, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms
|
|
338
508
|
FROM index_runs WHERE stream=? AND retired_gen IS NOT NULL
|
|
339
509
|
ORDER BY retired_at_ms ASC;`
|
|
340
510
|
),
|
|
341
511
|
insertIndexRun: this.db.query(
|
|
342
|
-
`INSERT OR IGNORE INTO index_runs(run_id, stream, level, start_segment, end_segment, object_key, filter_len, record_count, retired_gen, retired_at_ms)
|
|
343
|
-
VALUES(?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL);`
|
|
512
|
+
`INSERT OR IGNORE INTO index_runs(run_id, stream, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms)
|
|
513
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL);`
|
|
344
514
|
),
|
|
345
515
|
retireIndexRun: this.db.query(
|
|
346
516
|
`UPDATE index_runs SET retired_gen=?, retired_at_ms=? WHERE run_id=?;`
|
|
@@ -348,6 +518,174 @@ export class SqliteDurableStore {
|
|
|
348
518
|
deleteIndexRun: this.db.query(
|
|
349
519
|
`DELETE FROM index_runs WHERE run_id=?;`
|
|
350
520
|
),
|
|
521
|
+
deleteIndexStateForStream: this.db.query(`DELETE FROM index_state WHERE stream=?;`),
|
|
522
|
+
deleteIndexRunsForStream: this.db.query(`DELETE FROM index_runs WHERE stream=?;`),
|
|
523
|
+
getSecondaryIndexState: this.db.query(
|
|
524
|
+
`SELECT stream, index_name, index_secret, config_hash, indexed_through, updated_at_ms
|
|
525
|
+
FROM secondary_index_state WHERE stream=? AND index_name=? LIMIT 1;`
|
|
526
|
+
),
|
|
527
|
+
listSecondaryIndexStates: this.db.query(
|
|
528
|
+
`SELECT stream, index_name, index_secret, config_hash, indexed_through, updated_at_ms
|
|
529
|
+
FROM secondary_index_state WHERE stream=?
|
|
530
|
+
ORDER BY index_name ASC;`
|
|
531
|
+
),
|
|
532
|
+
upsertSecondaryIndexState: this.db.query(
|
|
533
|
+
`INSERT INTO secondary_index_state(stream, index_name, index_secret, config_hash, indexed_through, updated_at_ms)
|
|
534
|
+
VALUES(?, ?, ?, ?, ?, ?)
|
|
535
|
+
ON CONFLICT(stream, index_name) DO UPDATE SET
|
|
536
|
+
index_secret=excluded.index_secret,
|
|
537
|
+
config_hash=excluded.config_hash,
|
|
538
|
+
indexed_through=excluded.indexed_through,
|
|
539
|
+
updated_at_ms=excluded.updated_at_ms;`
|
|
540
|
+
),
|
|
541
|
+
updateSecondaryIndexedThrough: this.db.query(
|
|
542
|
+
`UPDATE secondary_index_state
|
|
543
|
+
SET indexed_through=?, updated_at_ms=?
|
|
544
|
+
WHERE stream=? AND index_name=?;`
|
|
545
|
+
),
|
|
546
|
+
listSecondaryIndexRuns: this.db.query(
|
|
547
|
+
`SELECT run_id, stream, index_name, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms
|
|
548
|
+
FROM secondary_index_runs
|
|
549
|
+
WHERE stream=? AND index_name=? AND retired_gen IS NULL
|
|
550
|
+
ORDER BY start_segment ASC, level ASC;`
|
|
551
|
+
),
|
|
552
|
+
listSecondaryIndexRunsAll: this.db.query(
|
|
553
|
+
`SELECT run_id, stream, index_name, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms
|
|
554
|
+
FROM secondary_index_runs
|
|
555
|
+
WHERE stream=? AND index_name=?
|
|
556
|
+
ORDER BY start_segment ASC, level ASC;`
|
|
557
|
+
),
|
|
558
|
+
listRetiredSecondaryIndexRuns: this.db.query(
|
|
559
|
+
`SELECT run_id, stream, index_name, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms
|
|
560
|
+
FROM secondary_index_runs
|
|
561
|
+
WHERE stream=? AND index_name=? AND retired_gen IS NOT NULL
|
|
562
|
+
ORDER BY retired_at_ms ASC;`
|
|
563
|
+
),
|
|
564
|
+
insertSecondaryIndexRun: this.db.query(
|
|
565
|
+
`INSERT OR IGNORE INTO secondary_index_runs(run_id, stream, index_name, level, start_segment, end_segment, object_key, size_bytes, filter_len, record_count, retired_gen, retired_at_ms)
|
|
566
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL);`
|
|
567
|
+
),
|
|
568
|
+
retireSecondaryIndexRun: this.db.query(
|
|
569
|
+
`UPDATE secondary_index_runs SET retired_gen=?, retired_at_ms=? WHERE run_id=?;`
|
|
570
|
+
),
|
|
571
|
+
deleteSecondaryIndexRun: this.db.query(
|
|
572
|
+
`DELETE FROM secondary_index_runs WHERE run_id=?;`
|
|
573
|
+
),
|
|
574
|
+
deleteSecondaryIndexState: this.db.query(`DELETE FROM secondary_index_state WHERE stream=? AND index_name=?;`),
|
|
575
|
+
deleteSecondaryIndexRunsForIndex: this.db.query(`DELETE FROM secondary_index_runs WHERE stream=? AND index_name=?;`),
|
|
576
|
+
deleteSecondaryIndexStatesForStream: this.db.query(`DELETE FROM secondary_index_state WHERE stream=?;`),
|
|
577
|
+
deleteSecondaryIndexRunsForStream: this.db.query(`DELETE FROM secondary_index_runs WHERE stream=?;`),
|
|
578
|
+
getLexiconIndexState: this.db.query(
|
|
579
|
+
`SELECT stream, source_kind, source_name, indexed_through, updated_at_ms
|
|
580
|
+
FROM lexicon_index_state
|
|
581
|
+
WHERE stream=? AND source_kind=? AND source_name=?
|
|
582
|
+
LIMIT 1;`
|
|
583
|
+
),
|
|
584
|
+
listLexiconIndexStates: this.db.query(
|
|
585
|
+
`SELECT stream, source_kind, source_name, indexed_through, updated_at_ms
|
|
586
|
+
FROM lexicon_index_state
|
|
587
|
+
WHERE stream=?
|
|
588
|
+
ORDER BY source_kind ASC, source_name ASC;`
|
|
589
|
+
),
|
|
590
|
+
upsertLexiconIndexState: this.db.query(
|
|
591
|
+
`INSERT INTO lexicon_index_state(stream, source_kind, source_name, indexed_through, updated_at_ms)
|
|
592
|
+
VALUES(?, ?, ?, ?, ?)
|
|
593
|
+
ON CONFLICT(stream, source_kind, source_name) DO UPDATE SET
|
|
594
|
+
indexed_through=excluded.indexed_through,
|
|
595
|
+
updated_at_ms=excluded.updated_at_ms;`
|
|
596
|
+
),
|
|
597
|
+
updateLexiconIndexedThrough: this.db.query(
|
|
598
|
+
`UPDATE lexicon_index_state
|
|
599
|
+
SET indexed_through=?, updated_at_ms=?
|
|
600
|
+
WHERE stream=? AND source_kind=? AND source_name=?;`
|
|
601
|
+
),
|
|
602
|
+
listLexiconIndexRuns: this.db.query(
|
|
603
|
+
`SELECT run_id, stream, source_kind, source_name, level, start_segment, end_segment, object_key, size_bytes, record_count, retired_gen, retired_at_ms
|
|
604
|
+
FROM lexicon_index_runs
|
|
605
|
+
WHERE stream=? AND source_kind=? AND source_name=? AND retired_gen IS NULL
|
|
606
|
+
ORDER BY start_segment ASC, level ASC;`
|
|
607
|
+
),
|
|
608
|
+
listLexiconIndexRunsAll: this.db.query(
|
|
609
|
+
`SELECT run_id, stream, source_kind, source_name, level, start_segment, end_segment, object_key, size_bytes, record_count, retired_gen, retired_at_ms
|
|
610
|
+
FROM lexicon_index_runs
|
|
611
|
+
WHERE stream=? AND source_kind=? AND source_name=?
|
|
612
|
+
ORDER BY start_segment ASC, level ASC;`
|
|
613
|
+
),
|
|
614
|
+
listRetiredLexiconIndexRuns: this.db.query(
|
|
615
|
+
`SELECT run_id, stream, source_kind, source_name, level, start_segment, end_segment, object_key, size_bytes, record_count, retired_gen, retired_at_ms
|
|
616
|
+
FROM lexicon_index_runs
|
|
617
|
+
WHERE stream=? AND source_kind=? AND source_name=? AND retired_gen IS NOT NULL
|
|
618
|
+
ORDER BY retired_at_ms ASC;`
|
|
619
|
+
),
|
|
620
|
+
insertLexiconIndexRun: this.db.query(
|
|
621
|
+
`INSERT OR IGNORE INTO lexicon_index_runs(run_id, stream, source_kind, source_name, level, start_segment, end_segment, object_key, size_bytes, record_count, retired_gen, retired_at_ms)
|
|
622
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL);`
|
|
623
|
+
),
|
|
624
|
+
retireLexiconIndexRun: this.db.query(
|
|
625
|
+
`UPDATE lexicon_index_runs SET retired_gen=?, retired_at_ms=? WHERE run_id=?;`
|
|
626
|
+
),
|
|
627
|
+
deleteLexiconIndexRun: this.db.query(
|
|
628
|
+
`DELETE FROM lexicon_index_runs WHERE run_id=?;`
|
|
629
|
+
),
|
|
630
|
+
deleteLexiconIndexState: this.db.query(
|
|
631
|
+
`DELETE FROM lexicon_index_state WHERE stream=? AND source_kind=? AND source_name=?;`
|
|
632
|
+
),
|
|
633
|
+
deleteLexiconIndexRunsForSource: this.db.query(
|
|
634
|
+
`DELETE FROM lexicon_index_runs WHERE stream=? AND source_kind=? AND source_name=?;`
|
|
635
|
+
),
|
|
636
|
+
deleteLexiconIndexStatesForStream: this.db.query(`DELETE FROM lexicon_index_state WHERE stream=?;`),
|
|
637
|
+
deleteLexiconIndexRunsForStream: this.db.query(`DELETE FROM lexicon_index_runs WHERE stream=?;`),
|
|
638
|
+
getSearchCompanionPlan: this.db.query(
|
|
639
|
+
`SELECT stream, generation, plan_hash, plan_json, updated_at_ms
|
|
640
|
+
FROM search_companion_plans WHERE stream=? LIMIT 1;`
|
|
641
|
+
),
|
|
642
|
+
listSearchCompanionPlanStreams: this.db.query(
|
|
643
|
+
`SELECT stream FROM search_companion_plans ORDER BY stream ASC;`
|
|
644
|
+
),
|
|
645
|
+
upsertSearchCompanionPlan: this.db.query(
|
|
646
|
+
`INSERT INTO search_companion_plans(stream, generation, plan_hash, plan_json, updated_at_ms)
|
|
647
|
+
VALUES(?, ?, ?, ?, ?)
|
|
648
|
+
ON CONFLICT(stream) DO UPDATE SET
|
|
649
|
+
generation=excluded.generation,
|
|
650
|
+
plan_hash=excluded.plan_hash,
|
|
651
|
+
plan_json=excluded.plan_json,
|
|
652
|
+
updated_at_ms=excluded.updated_at_ms;`
|
|
653
|
+
),
|
|
654
|
+
deleteSearchCompanionPlan: this.db.query(`DELETE FROM search_companion_plans WHERE stream=?;`),
|
|
655
|
+
listSearchSegmentCompanions: this.db.query(
|
|
656
|
+
`SELECT stream, segment_index, object_key, plan_generation, sections_json, section_sizes_json, size_bytes,
|
|
657
|
+
primary_timestamp_min_ms, primary_timestamp_max_ms, updated_at_ms
|
|
658
|
+
FROM search_segment_companions
|
|
659
|
+
WHERE stream=?
|
|
660
|
+
ORDER BY segment_index ASC;`
|
|
661
|
+
),
|
|
662
|
+
getSearchSegmentCompanion: this.db.query(
|
|
663
|
+
`SELECT stream, segment_index, object_key, plan_generation, sections_json, section_sizes_json, size_bytes,
|
|
664
|
+
primary_timestamp_min_ms, primary_timestamp_max_ms, updated_at_ms
|
|
665
|
+
FROM search_segment_companions
|
|
666
|
+
WHERE stream=? AND segment_index=? LIMIT 1;`
|
|
667
|
+
),
|
|
668
|
+
upsertSearchSegmentCompanion: this.db.query(
|
|
669
|
+
`INSERT INTO search_segment_companions(stream, segment_index, object_key, plan_generation, sections_json, section_sizes_json, size_bytes,
|
|
670
|
+
primary_timestamp_min_ms, primary_timestamp_max_ms, updated_at_ms)
|
|
671
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
672
|
+
ON CONFLICT(stream, segment_index) DO UPDATE SET
|
|
673
|
+
object_key=excluded.object_key,
|
|
674
|
+
plan_generation=excluded.plan_generation,
|
|
675
|
+
sections_json=excluded.sections_json,
|
|
676
|
+
section_sizes_json=excluded.section_sizes_json,
|
|
677
|
+
size_bytes=excluded.size_bytes,
|
|
678
|
+
primary_timestamp_min_ms=excluded.primary_timestamp_min_ms,
|
|
679
|
+
primary_timestamp_max_ms=excluded.primary_timestamp_max_ms,
|
|
680
|
+
updated_at_ms=excluded.updated_at_ms;`
|
|
681
|
+
),
|
|
682
|
+
deleteSearchSegmentCompanionsFromGeneration: this.db.query(
|
|
683
|
+
`DELETE FROM search_segment_companions WHERE stream=? AND plan_generation < ?;`
|
|
684
|
+
),
|
|
685
|
+
deleteSearchSegmentCompanionsFromIndex: this.db.query(
|
|
686
|
+
`DELETE FROM search_segment_companions WHERE stream=? AND segment_index >= ?;`
|
|
687
|
+
),
|
|
688
|
+
deleteSearchSegmentCompanions: this.db.query(`DELETE FROM search_segment_companions WHERE stream=?;`),
|
|
351
689
|
countUploadedSegments: this.db.query(
|
|
352
690
|
`SELECT COALESCE(MAX(segment_index), -1) as max_idx
|
|
353
691
|
FROM segments WHERE stream=? AND r2_etag IS NOT NULL;`
|
|
@@ -385,35 +723,48 @@ export class SqliteDurableStore {
|
|
|
385
723
|
advanceUploadedThrough: this.db.query(
|
|
386
724
|
`UPDATE streams SET uploaded_through=?, updated_at_ms=? WHERE stream=?;`
|
|
387
725
|
),
|
|
388
|
-
deleteWalBeforeOffset: this.db.query(
|
|
389
|
-
`DELETE FROM wal WHERE stream=? AND offset <= ?;`
|
|
390
|
-
),
|
|
391
726
|
|
|
392
|
-
getSchemaRegistry: this.db.query(`SELECT stream, schema_json, updated_at_ms FROM schemas WHERE stream=? LIMIT 1;`),
|
|
727
|
+
getSchemaRegistry: this.db.query(`SELECT stream, schema_json, updated_at_ms, uploaded_size_bytes FROM schemas WHERE stream=? LIMIT 1;`),
|
|
393
728
|
upsertSchemaRegistry: this.db.query(
|
|
394
729
|
`INSERT INTO schemas(stream, schema_json, updated_at_ms) VALUES(?, ?, ?)
|
|
395
730
|
ON CONFLICT(stream) DO UPDATE SET schema_json=excluded.schema_json, updated_at_ms=excluded.updated_at_ms;`
|
|
396
731
|
),
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
732
|
+
setSchemaUploadedSize: this.db.query(`UPDATE schemas SET uploaded_size_bytes=?, updated_at_ms=? WHERE stream=?;`),
|
|
733
|
+
getStreamProfile: this.db.query(`SELECT stream, profile_json, updated_at_ms FROM stream_profiles WHERE stream=? LIMIT 1;`),
|
|
734
|
+
upsertStreamProfile: this.db.query(
|
|
735
|
+
`INSERT INTO stream_profiles(stream, profile_json, updated_at_ms) VALUES(?, ?, ?)
|
|
736
|
+
ON CONFLICT(stream) DO UPDATE SET profile_json=excluded.profile_json, updated_at_ms=excluded.updated_at_ms;`
|
|
737
|
+
),
|
|
738
|
+
deleteStreamProfile: this.db.query(`DELETE FROM stream_profiles WHERE stream=?;`),
|
|
739
|
+
getStreamTouchState: this.db.query(
|
|
740
|
+
`SELECT stream, processed_through, updated_at_ms
|
|
741
|
+
FROM stream_touch_state WHERE stream=? LIMIT 1;`
|
|
400
742
|
),
|
|
401
|
-
|
|
402
|
-
`INSERT INTO
|
|
743
|
+
upsertStreamTouchState: this.db.query(
|
|
744
|
+
`INSERT INTO stream_touch_state(stream, processed_through, updated_at_ms)
|
|
403
745
|
VALUES(?, ?, ?)
|
|
404
746
|
ON CONFLICT(stream) DO UPDATE SET
|
|
405
|
-
|
|
747
|
+
processed_through=excluded.processed_through,
|
|
406
748
|
updated_at_ms=excluded.updated_at_ms;`
|
|
407
749
|
),
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
`SELECT stream,
|
|
411
|
-
FROM
|
|
750
|
+
deleteStreamTouchState: this.db.query(`DELETE FROM stream_touch_state WHERE stream=?;`),
|
|
751
|
+
listStreamTouchStates: this.db.query(
|
|
752
|
+
`SELECT stream, processed_through, updated_at_ms
|
|
753
|
+
FROM stream_touch_state
|
|
412
754
|
ORDER BY stream ASC;`
|
|
413
755
|
),
|
|
756
|
+
listStreamsByProfile: this.db.query(`SELECT stream FROM streams WHERE profile=? ORDER BY stream ASC;`),
|
|
414
757
|
countStreams: this.db.query(`SELECT COUNT(*) as cnt FROM streams WHERE (stream_flags & ?) = 0;`),
|
|
415
758
|
sumPendingBytes: this.db.query(`SELECT COALESCE(SUM(pending_bytes), 0) as total FROM streams;`),
|
|
416
759
|
sumPendingSegmentBytes: this.db.query(`SELECT COALESCE(SUM(size_bytes), 0) as total FROM segments WHERE uploaded_at_ms IS NULL;`),
|
|
760
|
+
recordObjectStoreRequest: this.db.query(
|
|
761
|
+
`INSERT INTO objectstore_request_counts(stream_hash, artifact, op, count, bytes, updated_at_ms)
|
|
762
|
+
VALUES(?, ?, ?, ?, ?, ?)
|
|
763
|
+
ON CONFLICT(stream_hash, artifact, op) DO UPDATE SET
|
|
764
|
+
count=objectstore_request_counts.count + excluded.count,
|
|
765
|
+
bytes=objectstore_request_counts.bytes + excluded.bytes,
|
|
766
|
+
updated_at_ms=excluded.updated_at_ms;`
|
|
767
|
+
),
|
|
417
768
|
};
|
|
418
769
|
}
|
|
419
770
|
|
|
@@ -428,6 +779,54 @@ export class SqliteDurableStore {
|
|
|
428
779
|
return v.toString();
|
|
429
780
|
}
|
|
430
781
|
|
|
782
|
+
private deleteWalThroughWithStats(
|
|
783
|
+
stream: string,
|
|
784
|
+
through: bigint,
|
|
785
|
+
opts?: { maxRows?: number }
|
|
786
|
+
): { deletedRows: bigint; deletedBytes: bigint } {
|
|
787
|
+
if (through < 0n) return { deletedRows: 0n, deletedBytes: 0n };
|
|
788
|
+
const bound = this.bindInt(through);
|
|
789
|
+
const maxRows = opts?.maxRows;
|
|
790
|
+
const useChunkedDelete = typeof maxRows === "number" && Number.isFinite(maxRows) && maxRows > 0;
|
|
791
|
+
const stmt = useChunkedDelete
|
|
792
|
+
? this.db.prepare(
|
|
793
|
+
`DELETE FROM wal
|
|
794
|
+
WHERE rowid IN (
|
|
795
|
+
SELECT rowid
|
|
796
|
+
FROM wal
|
|
797
|
+
WHERE stream=? AND offset <= ?
|
|
798
|
+
ORDER BY offset ASC
|
|
799
|
+
LIMIT ?
|
|
800
|
+
)
|
|
801
|
+
RETURNING payload_len;`
|
|
802
|
+
)
|
|
803
|
+
: this.db.prepare(
|
|
804
|
+
`DELETE FROM wal
|
|
805
|
+
WHERE stream=? AND offset <= ?
|
|
806
|
+
RETURNING payload_len;`
|
|
807
|
+
);
|
|
808
|
+
|
|
809
|
+
try {
|
|
810
|
+
const rows = useChunkedDelete
|
|
811
|
+
? stmt.iterate(stream, bound, Math.max(1, Math.floor(maxRows!)))
|
|
812
|
+
: stmt.iterate(stream, bound);
|
|
813
|
+
|
|
814
|
+
let deletedRows = 0n;
|
|
815
|
+
let deletedBytes = 0n;
|
|
816
|
+
for (const row of rows as any) {
|
|
817
|
+
deletedRows += 1n;
|
|
818
|
+
deletedBytes += this.toBigInt(row?.payload_len ?? 0);
|
|
819
|
+
}
|
|
820
|
+
return { deletedRows, deletedBytes };
|
|
821
|
+
} finally {
|
|
822
|
+
try {
|
|
823
|
+
stmt.finalize?.();
|
|
824
|
+
} catch {
|
|
825
|
+
// ignore
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
431
830
|
private encodeU64Le(value: bigint): Uint8Array {
|
|
432
831
|
const buf = new Uint8Array(8);
|
|
433
832
|
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
@@ -448,6 +847,7 @@ export class SqliteDurableStore {
|
|
|
448
847
|
created_at_ms: this.toBigInt(row.created_at_ms),
|
|
449
848
|
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
450
849
|
content_type: String(row.content_type),
|
|
850
|
+
profile: row.profile == null ? null : String(row.profile),
|
|
451
851
|
stream_seq: row.stream_seq == null ? null : String(row.stream_seq),
|
|
452
852
|
closed: Number(row.closed),
|
|
453
853
|
closed_producer_id: row.closed_producer_id == null ? null : String(row.closed_producer_id),
|
|
@@ -461,6 +861,7 @@ export class SqliteDurableStore {
|
|
|
461
861
|
uploaded_segment_count: Number(row.uploaded_segment_count ?? 0),
|
|
462
862
|
pending_rows: this.toBigInt(row.pending_rows),
|
|
463
863
|
pending_bytes: this.toBigInt(row.pending_bytes),
|
|
864
|
+
logical_size_bytes: this.toBigInt(row.logical_size_bytes ?? 0),
|
|
464
865
|
wal_rows: this.toBigInt(row.wal_rows ?? 0),
|
|
465
866
|
wal_bytes: this.toBigInt(row.wal_bytes ?? 0),
|
|
466
867
|
last_append_ms: this.toBigInt(row.last_append_ms),
|
|
@@ -480,6 +881,7 @@ export class SqliteDurableStore {
|
|
|
480
881
|
end_offset: this.toBigInt(row.end_offset),
|
|
481
882
|
block_count: Number(row.block_count),
|
|
482
883
|
last_append_ms: this.toBigInt(row.last_append_ms),
|
|
884
|
+
payload_bytes: this.toBigInt(row.payload_bytes ?? 0),
|
|
483
885
|
size_bytes: Number(row.size_bytes),
|
|
484
886
|
local_path: String(row.local_path),
|
|
485
887
|
created_at_ms: this.toBigInt(row.created_at_ms),
|
|
@@ -505,10 +907,45 @@ export class SqliteDurableStore {
|
|
|
505
907
|
return row ? this.coerceStreamRow(row) : null;
|
|
506
908
|
}
|
|
507
909
|
|
|
910
|
+
setStreamLogicalSizeBytes(stream: string, logicalSizeBytes: bigint): void {
|
|
911
|
+
this.db
|
|
912
|
+
.query(`UPDATE streams SET logical_size_bytes=?, updated_at_ms=? WHERE stream=?;`)
|
|
913
|
+
.run(this.bindInt(logicalSizeBytes), this.nowMs(), stream);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
listStreamsMissingLogicalSize(limit: number): string[] {
|
|
917
|
+
const now = this.nowMs();
|
|
918
|
+
const rows = this.db
|
|
919
|
+
.query(
|
|
920
|
+
`SELECT stream
|
|
921
|
+
FROM streams
|
|
922
|
+
WHERE (stream_flags & ?) = 0
|
|
923
|
+
AND (expires_at_ms IS NULL OR expires_at_ms > ?)
|
|
924
|
+
AND next_offset > 0
|
|
925
|
+
AND logical_size_bytes = 0
|
|
926
|
+
ORDER BY updated_at_ms ASC
|
|
927
|
+
LIMIT ?;`
|
|
928
|
+
)
|
|
929
|
+
.all(STREAM_FLAG_DELETED | STREAM_FLAG_TOUCH, now, limit) as any[];
|
|
930
|
+
return rows.map((row) => String(row.stream));
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
getWalBytesAfterOffset(stream: string, offset: bigint): bigint {
|
|
934
|
+
const row = this.db
|
|
935
|
+
.query(
|
|
936
|
+
`SELECT COALESCE(SUM(payload_len), 0) as bytes
|
|
937
|
+
FROM wal
|
|
938
|
+
WHERE stream=? AND offset > ?;`
|
|
939
|
+
)
|
|
940
|
+
.get(stream, this.bindInt(offset)) as any;
|
|
941
|
+
return this.toBigInt(row?.bytes ?? 0);
|
|
942
|
+
}
|
|
943
|
+
|
|
508
944
|
ensureStream(
|
|
509
945
|
stream: string,
|
|
510
946
|
opts?: {
|
|
511
947
|
contentType?: string;
|
|
948
|
+
profile?: string | null;
|
|
512
949
|
expiresAtMs?: bigint | null;
|
|
513
950
|
ttlSeconds?: number | null;
|
|
514
951
|
closed?: boolean;
|
|
@@ -523,6 +960,7 @@ export class SqliteDurableStore {
|
|
|
523
960
|
const epoch = 0;
|
|
524
961
|
const nextOffset = 0n;
|
|
525
962
|
const contentType = opts?.contentType ?? "application/octet-stream";
|
|
963
|
+
const profile = opts?.profile ?? "generic";
|
|
526
964
|
const closed = opts?.closed ? 1 : 0;
|
|
527
965
|
const closedProducer = opts?.closedProducer ?? null;
|
|
528
966
|
const expiresAtMs = opts?.expiresAtMs ?? null;
|
|
@@ -533,18 +971,19 @@ export class SqliteDurableStore {
|
|
|
533
971
|
.query(
|
|
534
972
|
`INSERT INTO streams(
|
|
535
973
|
stream, created_at_ms, updated_at_ms,
|
|
536
|
-
content_type, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
974
|
+
content_type, profile, stream_seq, closed, closed_producer_id, closed_producer_epoch, closed_producer_seq, ttl_seconds,
|
|
537
975
|
epoch, next_offset, sealed_through, uploaded_through, uploaded_segment_count,
|
|
538
|
-
pending_rows, pending_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
976
|
+
pending_rows, pending_bytes, logical_size_bytes, last_append_ms, last_segment_cut_ms, segment_in_progress,
|
|
539
977
|
expires_at_ms, stream_flags
|
|
540
978
|
)
|
|
541
|
-
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
|
|
979
|
+
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`
|
|
542
980
|
)
|
|
543
981
|
.run(
|
|
544
982
|
stream,
|
|
545
983
|
now,
|
|
546
984
|
now,
|
|
547
985
|
contentType,
|
|
986
|
+
profile,
|
|
548
987
|
null,
|
|
549
988
|
closed,
|
|
550
989
|
closedProducer ? closedProducer.id : null,
|
|
@@ -558,6 +997,7 @@ export class SqliteDurableStore {
|
|
|
558
997
|
0,
|
|
559
998
|
0n,
|
|
560
999
|
0n,
|
|
1000
|
+
0n,
|
|
561
1001
|
now,
|
|
562
1002
|
now,
|
|
563
1003
|
0,
|
|
@@ -565,7 +1005,7 @@ export class SqliteDurableStore {
|
|
|
565
1005
|
streamFlags
|
|
566
1006
|
);
|
|
567
1007
|
|
|
568
|
-
this.stmts.upsertManifest.run(stream, 0, 0, null, null);
|
|
1008
|
+
this.stmts.upsertManifest.run(stream, 0, 0, null, null, null);
|
|
569
1009
|
this.ensureSegmentMeta(stream);
|
|
570
1010
|
return this.getStream(stream)!;
|
|
571
1011
|
}
|
|
@@ -576,6 +1016,7 @@ export class SqliteDurableStore {
|
|
|
576
1016
|
row.created_at_ms,
|
|
577
1017
|
row.updated_at_ms,
|
|
578
1018
|
row.content_type,
|
|
1019
|
+
row.profile,
|
|
579
1020
|
row.stream_seq,
|
|
580
1021
|
row.closed,
|
|
581
1022
|
row.closed_producer_id,
|
|
@@ -589,6 +1030,7 @@ export class SqliteDurableStore {
|
|
|
589
1030
|
row.uploaded_segment_count,
|
|
590
1031
|
row.pending_rows,
|
|
591
1032
|
row.pending_bytes,
|
|
1033
|
+
row.logical_size_bytes,
|
|
592
1034
|
row.wal_rows,
|
|
593
1035
|
row.wal_bytes,
|
|
594
1036
|
row.last_append_ms,
|
|
@@ -605,20 +1047,55 @@ export class SqliteDurableStore {
|
|
|
605
1047
|
return rows.map((r) => this.coerceStreamRow(r));
|
|
606
1048
|
}
|
|
607
1049
|
|
|
1050
|
+
listDeletedStreams(limit: number, offset: number): string[] {
|
|
1051
|
+
const rows = this.stmts.listDeletedStreams.all(STREAM_FLAG_DELETED, limit, offset) as any[];
|
|
1052
|
+
return rows.map((row) => String(row.stream));
|
|
1053
|
+
}
|
|
1054
|
+
|
|
608
1055
|
listExpiredStreams(limit: number): string[] {
|
|
609
1056
|
const now = this.nowMs();
|
|
610
1057
|
const rows = this.stmts.listExpiredStreams.all(STREAM_FLAG_DELETED | STREAM_FLAG_TOUCH, now, limit) as any[];
|
|
611
1058
|
return rows.map((r) => String(r.stream));
|
|
612
1059
|
}
|
|
613
1060
|
|
|
1061
|
+
deleteAccelerationState(stream: string): void {
|
|
1062
|
+
const tx = this.db.transaction(() => {
|
|
1063
|
+
this.stmts.deleteIndexRunsForStream.run(stream);
|
|
1064
|
+
this.stmts.deleteIndexStateForStream.run(stream);
|
|
1065
|
+
this.stmts.deleteSecondaryIndexRunsForStream.run(stream);
|
|
1066
|
+
this.stmts.deleteSecondaryIndexStatesForStream.run(stream);
|
|
1067
|
+
this.stmts.deleteLexiconIndexRunsForStream.run(stream);
|
|
1068
|
+
this.stmts.deleteLexiconIndexStatesForStream.run(stream);
|
|
1069
|
+
this.stmts.deleteSearchSegmentCompanions.run(stream);
|
|
1070
|
+
this.stmts.deleteSearchCompanionPlan.run(stream);
|
|
1071
|
+
});
|
|
1072
|
+
tx();
|
|
1073
|
+
}
|
|
1074
|
+
|
|
614
1075
|
deleteStream(stream: string): boolean {
|
|
615
1076
|
const existing = this.getStream(stream);
|
|
616
1077
|
if (!existing) return false;
|
|
617
1078
|
const now = this.nowMs();
|
|
618
|
-
this.
|
|
1079
|
+
const tx = this.db.transaction(() => {
|
|
1080
|
+
this.stmts.setDeleted.run(STREAM_FLAG_DELETED, now, stream);
|
|
1081
|
+
this.stmts.deleteIndexRunsForStream.run(stream);
|
|
1082
|
+
this.stmts.deleteIndexStateForStream.run(stream);
|
|
1083
|
+
this.stmts.deleteSecondaryIndexRunsForStream.run(stream);
|
|
1084
|
+
this.stmts.deleteSecondaryIndexStatesForStream.run(stream);
|
|
1085
|
+
this.stmts.deleteLexiconIndexRunsForStream.run(stream);
|
|
1086
|
+
this.stmts.deleteLexiconIndexStatesForStream.run(stream);
|
|
1087
|
+
this.stmts.deleteSearchSegmentCompanions.run(stream);
|
|
1088
|
+
this.stmts.deleteSearchCompanionPlan.run(stream);
|
|
1089
|
+
});
|
|
1090
|
+
tx();
|
|
619
1091
|
return true;
|
|
620
1092
|
}
|
|
621
1093
|
|
|
1094
|
+
updateStreamProfile(stream: string, profile: string | null): StreamRow | null {
|
|
1095
|
+
this.stmts.setStreamProfile.run(profile, this.nowMs(), stream);
|
|
1096
|
+
return this.getStream(stream);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
622
1099
|
hardDeleteStream(stream: string): boolean {
|
|
623
1100
|
const tx = this.db.transaction(() => {
|
|
624
1101
|
const existing = this.getStream(stream);
|
|
@@ -627,11 +1104,18 @@ export class SqliteDurableStore {
|
|
|
627
1104
|
this.db.query(`DELETE FROM segments WHERE stream=?;`).run(stream);
|
|
628
1105
|
this.db.query(`DELETE FROM manifests WHERE stream=?;`).run(stream);
|
|
629
1106
|
this.db.query(`DELETE FROM schemas WHERE stream=?;`).run(stream);
|
|
630
|
-
this.db.query(`DELETE FROM
|
|
1107
|
+
this.db.query(`DELETE FROM stream_profiles WHERE stream=?;`).run(stream);
|
|
1108
|
+
this.db.query(`DELETE FROM stream_touch_state WHERE stream=?;`).run(stream);
|
|
631
1109
|
this.db.query(`DELETE FROM live_templates WHERE stream=?;`).run(stream);
|
|
632
1110
|
this.db.query(`DELETE FROM producer_state WHERE stream=?;`).run(stream);
|
|
633
1111
|
this.db.query(`DELETE FROM index_state WHERE stream=?;`).run(stream);
|
|
634
1112
|
this.db.query(`DELETE FROM index_runs WHERE stream=?;`).run(stream);
|
|
1113
|
+
this.db.query(`DELETE FROM secondary_index_state WHERE stream=?;`).run(stream);
|
|
1114
|
+
this.db.query(`DELETE FROM secondary_index_runs WHERE stream=?;`).run(stream);
|
|
1115
|
+
this.db.query(`DELETE FROM lexicon_index_state WHERE stream=?;`).run(stream);
|
|
1116
|
+
this.db.query(`DELETE FROM lexicon_index_runs WHERE stream=?;`).run(stream);
|
|
1117
|
+
this.db.query(`DELETE FROM search_companion_plans WHERE stream=?;`).run(stream);
|
|
1118
|
+
this.db.query(`DELETE FROM search_segment_companions WHERE stream=?;`).run(stream);
|
|
635
1119
|
this.db.query(`DELETE FROM stream_segment_meta WHERE stream=?;`).run(stream);
|
|
636
1120
|
this.db.query(`DELETE FROM streams WHERE stream=?;`).run(stream);
|
|
637
1121
|
return true;
|
|
@@ -639,49 +1123,81 @@ export class SqliteDurableStore {
|
|
|
639
1123
|
return tx();
|
|
640
1124
|
}
|
|
641
1125
|
|
|
642
|
-
getSchemaRegistry(stream: string): { stream: string; registry_json: string; updated_at_ms: bigint } | null {
|
|
1126
|
+
getSchemaRegistry(stream: string): { stream: string; registry_json: string; updated_at_ms: bigint; uploaded_size_bytes: bigint } | null {
|
|
643
1127
|
const row = this.stmts.getSchemaRegistry.get(stream) as any;
|
|
644
1128
|
if (!row) return null;
|
|
645
|
-
return {
|
|
1129
|
+
return {
|
|
1130
|
+
stream: String(row.stream),
|
|
1131
|
+
registry_json: String(row.schema_json),
|
|
1132
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
1133
|
+
uploaded_size_bytes: this.toBigInt(row.uploaded_size_bytes ?? 0),
|
|
1134
|
+
};
|
|
646
1135
|
}
|
|
647
1136
|
|
|
648
1137
|
upsertSchemaRegistry(stream: string, registryJson: string): void {
|
|
649
1138
|
this.stmts.upsertSchemaRegistry.run(stream, registryJson, this.nowMs());
|
|
650
1139
|
}
|
|
651
1140
|
|
|
652
|
-
|
|
653
|
-
|
|
1141
|
+
setSchemaUploadedSizeBytes(stream: string, sizeBytes: number): void {
|
|
1142
|
+
this.stmts.setSchemaUploadedSize.run(sizeBytes, this.nowMs(), stream);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
getStreamProfile(stream: string): { stream: string; profile_json: string; updated_at_ms: bigint } | null {
|
|
1146
|
+
const row = this.stmts.getStreamProfile.get(stream) as any;
|
|
1147
|
+
if (!row) return null;
|
|
1148
|
+
return {
|
|
1149
|
+
stream: String(row.stream),
|
|
1150
|
+
profile_json: String(row.profile_json),
|
|
1151
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
upsertStreamProfile(stream: string, profileJson: string): void {
|
|
1156
|
+
this.stmts.upsertStreamProfile.run(stream, profileJson, this.nowMs());
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
deleteStreamProfile(stream: string): void {
|
|
1160
|
+
this.stmts.deleteStreamProfile.run(stream);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
getStreamTouchState(stream: string): { stream: string; processed_through: bigint; updated_at_ms: bigint } | null {
|
|
1164
|
+
const row = this.stmts.getStreamTouchState.get(stream) as any;
|
|
654
1165
|
if (!row) return null;
|
|
655
1166
|
return {
|
|
656
1167
|
stream: String(row.stream),
|
|
657
|
-
|
|
1168
|
+
processed_through: this.toBigInt(row.processed_through),
|
|
658
1169
|
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
659
1170
|
};
|
|
660
1171
|
}
|
|
661
1172
|
|
|
662
|
-
|
|
663
|
-
const rows = this.stmts.
|
|
1173
|
+
listStreamTouchStates(): Array<{ stream: string; processed_through: bigint; updated_at_ms: bigint }> {
|
|
1174
|
+
const rows = this.stmts.listStreamTouchStates.all() as any[];
|
|
664
1175
|
return rows.map((row) => ({
|
|
665
1176
|
stream: String(row.stream),
|
|
666
|
-
|
|
1177
|
+
processed_through: this.toBigInt(row.processed_through),
|
|
667
1178
|
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
668
1179
|
}));
|
|
669
1180
|
}
|
|
670
1181
|
|
|
671
|
-
|
|
672
|
-
const
|
|
1182
|
+
listStreamsByProfile(kind: string): string[] {
|
|
1183
|
+
const rows = this.stmts.listStreamsByProfile.all(kind) as any[];
|
|
1184
|
+
return rows.map((row) => String(row.stream));
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
ensureStreamTouchState(stream: string): void {
|
|
1188
|
+
const existing = this.getStreamTouchState(stream);
|
|
673
1189
|
if (existing) return;
|
|
674
1190
|
const srow = this.getStream(stream);
|
|
675
1191
|
const initialThrough = srow ? srow.next_offset - 1n : -1n;
|
|
676
|
-
this.stmts.
|
|
1192
|
+
this.stmts.upsertStreamTouchState.run(stream, this.bindInt(initialThrough), this.nowMs());
|
|
677
1193
|
}
|
|
678
1194
|
|
|
679
|
-
|
|
680
|
-
this.stmts.
|
|
1195
|
+
updateStreamTouchStateThrough(stream: string, processedThrough: bigint): void {
|
|
1196
|
+
this.stmts.upsertStreamTouchState.run(stream, this.bindInt(processedThrough), this.nowMs());
|
|
681
1197
|
}
|
|
682
1198
|
|
|
683
|
-
|
|
684
|
-
this.stmts.
|
|
1199
|
+
deleteStreamTouchState(stream: string): void {
|
|
1200
|
+
this.stmts.deleteStreamTouchState.run(stream);
|
|
685
1201
|
}
|
|
686
1202
|
|
|
687
1203
|
addStreamFlags(stream: string, flags: number): void {
|
|
@@ -695,6 +1211,12 @@ export class SqliteDurableStore {
|
|
|
695
1211
|
return this.toBigInt(row.min_off);
|
|
696
1212
|
}
|
|
697
1213
|
|
|
1214
|
+
getWalOldestTimestampMs(stream: string): bigint | null {
|
|
1215
|
+
const row = this.db.query(`SELECT MIN(ts_ms) as min_ts FROM wal WHERE stream=?;`).get(stream) as any;
|
|
1216
|
+
if (!row || row.min_ts == null) return null;
|
|
1217
|
+
return this.toBigInt(row.min_ts);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
698
1220
|
/**
|
|
699
1221
|
* Trim a WAL-only stream by age (in ms), leaving at least 1 record if the stream is non-empty.
|
|
700
1222
|
*
|
|
@@ -723,18 +1245,9 @@ export class SqliteDurableStore {
|
|
|
723
1245
|
|
|
724
1246
|
if (keepFromOffset <= 0n) return { trimmedRows: 0, trimmedBytes: 0, keptFromOffset: keepFromOffset };
|
|
725
1247
|
|
|
726
|
-
const
|
|
727
|
-
.query(
|
|
728
|
-
`SELECT COALESCE(SUM(payload_len), 0) as bytes, COUNT(*) as rows
|
|
729
|
-
FROM wal WHERE stream=? AND offset < ?;`
|
|
730
|
-
)
|
|
731
|
-
.get(stream, this.bindInt(keepFromOffset)) as any;
|
|
732
|
-
const bytes = this.toBigInt(stats?.bytes ?? 0);
|
|
733
|
-
const rows = this.toBigInt(stats?.rows ?? 0);
|
|
1248
|
+
const { deletedRows: rows, deletedBytes: bytes } = this.deleteWalThroughWithStats(stream, keepFromOffset - 1n);
|
|
734
1249
|
if (rows <= 0n) return { trimmedRows: 0, trimmedBytes: 0, keptFromOffset: keepFromOffset };
|
|
735
1250
|
|
|
736
|
-
this.db.query(`DELETE FROM wal WHERE stream=? AND offset < ?;`).run(stream, this.bindInt(keepFromOffset));
|
|
737
|
-
|
|
738
1251
|
// Touch streams are WAL-only: pending_* tracks WAL payload bytes/rows. Keep it consistent for stats/backpressure.
|
|
739
1252
|
const now = this.nowMs();
|
|
740
1253
|
this.db.query(
|
|
@@ -922,6 +1435,7 @@ export class SqliteDurableStore {
|
|
|
922
1435
|
lastAppend,
|
|
923
1436
|
pendingRows,
|
|
924
1437
|
totalBytes,
|
|
1438
|
+
totalBytes,
|
|
925
1439
|
pendingRows,
|
|
926
1440
|
totalBytes,
|
|
927
1441
|
stream,
|
|
@@ -942,16 +1456,50 @@ export class SqliteDurableStore {
|
|
|
942
1456
|
const start = this.bindInt(startOffset);
|
|
943
1457
|
const end = this.bindInt(endOffset);
|
|
944
1458
|
const stmt = routingKey
|
|
945
|
-
? this.db.
|
|
946
|
-
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
1459
|
+
? this.db.prepare(
|
|
1460
|
+
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
1461
|
+
FROM wal
|
|
1462
|
+
WHERE stream = ? AND offset >= ? AND offset <= ? AND routing_key = ?
|
|
1463
|
+
ORDER BY offset ASC;`
|
|
947
1464
|
)
|
|
948
|
-
: this.db.
|
|
949
|
-
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
1465
|
+
: this.db.prepare(
|
|
1466
|
+
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
1467
|
+
FROM wal
|
|
1468
|
+
WHERE stream = ? AND offset >= ? AND offset <= ?
|
|
1469
|
+
ORDER BY offset ASC;`
|
|
950
1470
|
);
|
|
951
1471
|
try {
|
|
952
|
-
const it = routingKey
|
|
953
|
-
|
|
954
|
-
|
|
1472
|
+
const it = routingKey ? (stmt.iterate(stream, start, end, routingKey) as any) : (stmt.iterate(stream, start, end) as any);
|
|
1473
|
+
for (const row of it) {
|
|
1474
|
+
yield row;
|
|
1475
|
+
}
|
|
1476
|
+
} finally {
|
|
1477
|
+
try {
|
|
1478
|
+
stmt.finalize?.();
|
|
1479
|
+
} catch {
|
|
1480
|
+
// ignore
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
*iterWalRangeDesc(stream: string, startOffset: bigint, endOffset: bigint, routingKey?: Uint8Array): Generator<any, void, void> {
|
|
1486
|
+
const start = this.bindInt(startOffset);
|
|
1487
|
+
const end = this.bindInt(endOffset);
|
|
1488
|
+
const stmt = routingKey
|
|
1489
|
+
? this.db.prepare(
|
|
1490
|
+
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
1491
|
+
FROM wal
|
|
1492
|
+
WHERE stream = ? AND offset >= ? AND offset <= ? AND routing_key = ?
|
|
1493
|
+
ORDER BY offset DESC;`
|
|
1494
|
+
)
|
|
1495
|
+
: this.db.prepare(
|
|
1496
|
+
`SELECT offset, ts_ms, routing_key, content_type, payload
|
|
1497
|
+
FROM wal
|
|
1498
|
+
WHERE stream = ? AND offset >= ? AND offset <= ?
|
|
1499
|
+
ORDER BY offset DESC;`
|
|
1500
|
+
);
|
|
1501
|
+
try {
|
|
1502
|
+
const it = routingKey ? (stmt.iterate(stream, start, end, routingKey) as any) : (stmt.iterate(stream, start, end) as any);
|
|
955
1503
|
for (const row of it) {
|
|
956
1504
|
yield row;
|
|
957
1505
|
}
|
|
@@ -977,6 +1525,7 @@ export class SqliteDurableStore {
|
|
|
977
1525
|
endOffset: bigint;
|
|
978
1526
|
blockCount: number;
|
|
979
1527
|
lastAppendMs: bigint;
|
|
1528
|
+
payloadBytes: bigint;
|
|
980
1529
|
sizeBytes: number;
|
|
981
1530
|
localPath: string;
|
|
982
1531
|
}): void {
|
|
@@ -988,6 +1537,7 @@ export class SqliteDurableStore {
|
|
|
988
1537
|
row.endOffset,
|
|
989
1538
|
row.blockCount,
|
|
990
1539
|
row.lastAppendMs,
|
|
1540
|
+
row.payloadBytes,
|
|
991
1541
|
row.sizeBytes,
|
|
992
1542
|
row.localPath,
|
|
993
1543
|
this.nowMs()
|
|
@@ -1002,9 +1552,9 @@ export class SqliteDurableStore {
|
|
|
1002
1552
|
endOffset: bigint;
|
|
1003
1553
|
blockCount: number;
|
|
1004
1554
|
lastAppendMs: bigint;
|
|
1555
|
+
payloadBytes: bigint;
|
|
1005
1556
|
sizeBytes: number;
|
|
1006
1557
|
localPath: string;
|
|
1007
|
-
payloadBytes: bigint;
|
|
1008
1558
|
rowsSealed: bigint;
|
|
1009
1559
|
}): void {
|
|
1010
1560
|
const tx = this.db.transaction(() => {
|
|
@@ -1031,11 +1581,21 @@ export class SqliteDurableStore {
|
|
|
1031
1581
|
return row ? this.coerceSegmentRow(row) : null;
|
|
1032
1582
|
}
|
|
1033
1583
|
|
|
1034
|
-
|
|
1035
|
-
const rows = this.stmts.
|
|
1584
|
+
pendingUploadHeads(limit: number): SegmentRow[] {
|
|
1585
|
+
const rows = this.stmts.pendingUploadHeads.all(limit) as any[];
|
|
1036
1586
|
return rows.map((r) => this.coerceSegmentRow(r));
|
|
1037
1587
|
}
|
|
1038
1588
|
|
|
1589
|
+
recentSegmentCompressionRatio(stream: string, limit = 8): number | null {
|
|
1590
|
+
const row = this.stmts.recentSegmentCompressionWindow.get(stream, Math.max(1, limit)) as any;
|
|
1591
|
+
const count = Number(row?.cnt ?? 0);
|
|
1592
|
+
if (!Number.isFinite(count) || count <= 0) return null;
|
|
1593
|
+
const payloadTotal = this.toBigInt(row?.payload_total ?? 0);
|
|
1594
|
+
const sizeTotal = this.toBigInt(row?.size_total ?? 0);
|
|
1595
|
+
if (payloadTotal <= 0n || sizeTotal <= 0n) return null;
|
|
1596
|
+
return Number(sizeTotal) / Number(payloadTotal);
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1039
1599
|
countPendingSegments(): number {
|
|
1040
1600
|
const row = this.stmts.countPendingSegments.get() as any;
|
|
1041
1601
|
return row ? Number(row.cnt) : 0;
|
|
@@ -1158,20 +1718,10 @@ export class SqliteDurableStore {
|
|
|
1158
1718
|
}
|
|
1159
1719
|
|
|
1160
1720
|
deleteWalThrough(stream: string, uploadedThrough: bigint): { deletedRows: number; deletedBytes: number } {
|
|
1161
|
-
const through = this.bindInt(uploadedThrough);
|
|
1162
1721
|
const tx = this.db.transaction(() => {
|
|
1163
|
-
const
|
|
1164
|
-
.query(
|
|
1165
|
-
`SELECT COALESCE(SUM(payload_len), 0) as bytes, COUNT(*) as rows
|
|
1166
|
-
FROM wal WHERE stream=? AND offset <= ?;`
|
|
1167
|
-
)
|
|
1168
|
-
.get(stream, through) as any;
|
|
1169
|
-
const bytes = this.toBigInt(stats?.bytes ?? 0);
|
|
1170
|
-
const rows = this.toBigInt(stats?.rows ?? 0);
|
|
1722
|
+
const { deletedRows: rows, deletedBytes: bytes } = this.deleteWalThroughWithStats(stream, uploadedThrough);
|
|
1171
1723
|
if (rows <= 0n) return { deletedRows: 0, deletedBytes: 0 };
|
|
1172
1724
|
|
|
1173
|
-
this.stmts.deleteWalBeforeOffset.run(stream, through);
|
|
1174
|
-
|
|
1175
1725
|
const now = this.nowMs();
|
|
1176
1726
|
this.db.query(
|
|
1177
1727
|
`UPDATE streams
|
|
@@ -1188,10 +1738,17 @@ export class SqliteDurableStore {
|
|
|
1188
1738
|
return tx();
|
|
1189
1739
|
}
|
|
1190
1740
|
|
|
1191
|
-
getManifestRow(stream: string): {
|
|
1741
|
+
getManifestRow(stream: string): {
|
|
1742
|
+
stream: string;
|
|
1743
|
+
generation: number;
|
|
1744
|
+
uploaded_generation: number;
|
|
1745
|
+
last_uploaded_at_ms: bigint | null;
|
|
1746
|
+
last_uploaded_etag: string | null;
|
|
1747
|
+
last_uploaded_size_bytes: bigint | null;
|
|
1748
|
+
} {
|
|
1192
1749
|
const row = this.stmts.getManifest.get(stream) as any;
|
|
1193
1750
|
if (!row) {
|
|
1194
|
-
this.stmts.upsertManifest.run(stream, 0, 0, null, null);
|
|
1751
|
+
this.stmts.upsertManifest.run(stream, 0, 0, null, null, null);
|
|
1195
1752
|
const fresh = this.stmts.getManifest.get(stream) as any;
|
|
1196
1753
|
return {
|
|
1197
1754
|
stream: String(fresh.stream),
|
|
@@ -1199,6 +1756,7 @@ export class SqliteDurableStore {
|
|
|
1199
1756
|
uploaded_generation: Number(fresh.uploaded_generation),
|
|
1200
1757
|
last_uploaded_at_ms: fresh.last_uploaded_at_ms == null ? null : this.toBigInt(fresh.last_uploaded_at_ms),
|
|
1201
1758
|
last_uploaded_etag: fresh.last_uploaded_etag == null ? null : String(fresh.last_uploaded_etag),
|
|
1759
|
+
last_uploaded_size_bytes: fresh.last_uploaded_size_bytes == null ? null : this.toBigInt(fresh.last_uploaded_size_bytes),
|
|
1202
1760
|
};
|
|
1203
1761
|
}
|
|
1204
1762
|
return {
|
|
@@ -1207,11 +1765,19 @@ export class SqliteDurableStore {
|
|
|
1207
1765
|
uploaded_generation: Number(row.uploaded_generation),
|
|
1208
1766
|
last_uploaded_at_ms: row.last_uploaded_at_ms == null ? null : this.toBigInt(row.last_uploaded_at_ms),
|
|
1209
1767
|
last_uploaded_etag: row.last_uploaded_etag == null ? null : String(row.last_uploaded_etag),
|
|
1768
|
+
last_uploaded_size_bytes: row.last_uploaded_size_bytes == null ? null : this.toBigInt(row.last_uploaded_size_bytes),
|
|
1210
1769
|
};
|
|
1211
1770
|
}
|
|
1212
1771
|
|
|
1213
|
-
upsertManifestRow(
|
|
1214
|
-
|
|
1772
|
+
upsertManifestRow(
|
|
1773
|
+
stream: string,
|
|
1774
|
+
generation: number,
|
|
1775
|
+
uploadedGeneration: number,
|
|
1776
|
+
uploadedAtMs: bigint | null,
|
|
1777
|
+
etag: string | null,
|
|
1778
|
+
sizeBytes: number | null
|
|
1779
|
+
): void {
|
|
1780
|
+
this.stmts.upsertManifest.run(stream, generation, uploadedGeneration, uploadedAtMs, etag, sizeBytes);
|
|
1215
1781
|
}
|
|
1216
1782
|
|
|
1217
1783
|
getIndexState(stream: string): IndexStateRow | null {
|
|
@@ -1242,6 +1808,7 @@ export class SqliteDurableStore {
|
|
|
1242
1808
|
start_segment: Number(r.start_segment),
|
|
1243
1809
|
end_segment: Number(r.end_segment),
|
|
1244
1810
|
object_key: String(r.object_key),
|
|
1811
|
+
size_bytes: Number(r.size_bytes ?? 0),
|
|
1245
1812
|
filter_len: Number(r.filter_len),
|
|
1246
1813
|
record_count: Number(r.record_count),
|
|
1247
1814
|
retired_gen: r.retired_gen == null ? null : Number(r.retired_gen),
|
|
@@ -1258,6 +1825,7 @@ export class SqliteDurableStore {
|
|
|
1258
1825
|
start_segment: Number(r.start_segment),
|
|
1259
1826
|
end_segment: Number(r.end_segment),
|
|
1260
1827
|
object_key: String(r.object_key),
|
|
1828
|
+
size_bytes: Number(r.size_bytes ?? 0),
|
|
1261
1829
|
filter_len: Number(r.filter_len),
|
|
1262
1830
|
record_count: Number(r.record_count),
|
|
1263
1831
|
retired_gen: r.retired_gen == null ? null : Number(r.retired_gen),
|
|
@@ -1274,6 +1842,7 @@ export class SqliteDurableStore {
|
|
|
1274
1842
|
start_segment: Number(r.start_segment),
|
|
1275
1843
|
end_segment: Number(r.end_segment),
|
|
1276
1844
|
object_key: String(r.object_key),
|
|
1845
|
+
size_bytes: Number(r.size_bytes ?? 0),
|
|
1277
1846
|
filter_len: Number(r.filter_len),
|
|
1278
1847
|
record_count: Number(r.record_count),
|
|
1279
1848
|
retired_gen: r.retired_gen == null ? null : Number(r.retired_gen),
|
|
@@ -1289,6 +1858,7 @@ export class SqliteDurableStore {
|
|
|
1289
1858
|
row.start_segment,
|
|
1290
1859
|
row.end_segment,
|
|
1291
1860
|
row.object_key,
|
|
1861
|
+
row.size_bytes,
|
|
1292
1862
|
row.filter_len,
|
|
1293
1863
|
row.record_count
|
|
1294
1864
|
);
|
|
@@ -1314,49 +1884,403 @@ export class SqliteDurableStore {
|
|
|
1314
1884
|
tx();
|
|
1315
1885
|
}
|
|
1316
1886
|
|
|
1887
|
+
deleteIndex(stream: string): void {
|
|
1888
|
+
const tx = this.db.transaction(() => {
|
|
1889
|
+
this.db.query(`DELETE FROM index_runs WHERE stream=?;`).run(stream);
|
|
1890
|
+
this.db.query(`DELETE FROM index_state WHERE stream=?;`).run(stream);
|
|
1891
|
+
});
|
|
1892
|
+
tx();
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1317
1895
|
countUploadedSegments(stream: string): number {
|
|
1318
1896
|
const row = this.stmts.countUploadedSegments.get(stream) as any;
|
|
1319
1897
|
const maxIdx = row ? Number(row.max_idx) : -1;
|
|
1320
1898
|
return maxIdx >= 0 ? maxIdx + 1 : 0;
|
|
1321
1899
|
}
|
|
1322
1900
|
|
|
1323
|
-
|
|
1901
|
+
getSecondaryIndexState(stream: string, indexName: string): SecondaryIndexStateRow | null {
|
|
1902
|
+
const row = this.stmts.getSecondaryIndexState.get(stream, indexName) as any;
|
|
1903
|
+
if (!row) return null;
|
|
1904
|
+
return {
|
|
1905
|
+
stream: String(row.stream),
|
|
1906
|
+
index_name: String(row.index_name),
|
|
1907
|
+
index_secret: row.index_secret instanceof Uint8Array ? row.index_secret : new Uint8Array(row.index_secret),
|
|
1908
|
+
config_hash: String(row.config_hash ?? ""),
|
|
1909
|
+
indexed_through: Number(row.indexed_through),
|
|
1910
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
listSecondaryIndexStates(stream: string): SecondaryIndexStateRow[] {
|
|
1915
|
+
const rows = this.stmts.listSecondaryIndexStates.all(stream) as any[];
|
|
1916
|
+
return rows.map((row) => ({
|
|
1917
|
+
stream: String(row.stream),
|
|
1918
|
+
index_name: String(row.index_name),
|
|
1919
|
+
index_secret: row.index_secret instanceof Uint8Array ? row.index_secret : new Uint8Array(row.index_secret),
|
|
1920
|
+
config_hash: String(row.config_hash ?? ""),
|
|
1921
|
+
indexed_through: Number(row.indexed_through),
|
|
1922
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
1923
|
+
}));
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
upsertSecondaryIndexState(
|
|
1927
|
+
stream: string,
|
|
1928
|
+
indexName: string,
|
|
1929
|
+
indexSecret: Uint8Array,
|
|
1930
|
+
configHash: string,
|
|
1931
|
+
indexedThrough: number
|
|
1932
|
+
): void {
|
|
1933
|
+
this.stmts.upsertSecondaryIndexState.run(stream, indexName, indexSecret, configHash, indexedThrough, this.nowMs());
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
updateSecondaryIndexedThrough(stream: string, indexName: string, indexedThrough: number): void {
|
|
1937
|
+
this.stmts.updateSecondaryIndexedThrough.run(indexedThrough, this.nowMs(), stream, indexName);
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
listSecondaryIndexRuns(stream: string, indexName: string): SecondaryIndexRunRow[] {
|
|
1941
|
+
const rows = this.stmts.listSecondaryIndexRuns.all(stream, indexName) as any[];
|
|
1942
|
+
return rows.map((r) => ({
|
|
1943
|
+
run_id: String(r.run_id),
|
|
1944
|
+
stream: String(r.stream),
|
|
1945
|
+
index_name: String(r.index_name),
|
|
1946
|
+
level: Number(r.level),
|
|
1947
|
+
start_segment: Number(r.start_segment),
|
|
1948
|
+
end_segment: Number(r.end_segment),
|
|
1949
|
+
object_key: String(r.object_key),
|
|
1950
|
+
size_bytes: Number(r.size_bytes ?? 0),
|
|
1951
|
+
filter_len: Number(r.filter_len),
|
|
1952
|
+
record_count: Number(r.record_count),
|
|
1953
|
+
retired_gen: r.retired_gen == null ? null : Number(r.retired_gen),
|
|
1954
|
+
retired_at_ms: r.retired_at_ms == null ? null : this.toBigInt(r.retired_at_ms),
|
|
1955
|
+
}));
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
listSecondaryIndexRunsAll(stream: string, indexName: string): SecondaryIndexRunRow[] {
|
|
1959
|
+
const rows = this.stmts.listSecondaryIndexRunsAll.all(stream, indexName) as any[];
|
|
1960
|
+
return rows.map((r) => ({
|
|
1961
|
+
run_id: String(r.run_id),
|
|
1962
|
+
stream: String(r.stream),
|
|
1963
|
+
index_name: String(r.index_name),
|
|
1964
|
+
level: Number(r.level),
|
|
1965
|
+
start_segment: Number(r.start_segment),
|
|
1966
|
+
end_segment: Number(r.end_segment),
|
|
1967
|
+
object_key: String(r.object_key),
|
|
1968
|
+
size_bytes: Number(r.size_bytes ?? 0),
|
|
1969
|
+
filter_len: Number(r.filter_len),
|
|
1970
|
+
record_count: Number(r.record_count),
|
|
1971
|
+
retired_gen: r.retired_gen == null ? null : Number(r.retired_gen),
|
|
1972
|
+
retired_at_ms: r.retired_at_ms == null ? null : this.toBigInt(r.retired_at_ms),
|
|
1973
|
+
}));
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
listRetiredSecondaryIndexRuns(stream: string, indexName: string): SecondaryIndexRunRow[] {
|
|
1977
|
+
const rows = this.stmts.listRetiredSecondaryIndexRuns.all(stream, indexName) as any[];
|
|
1978
|
+
return rows.map((r) => ({
|
|
1979
|
+
run_id: String(r.run_id),
|
|
1980
|
+
stream: String(r.stream),
|
|
1981
|
+
index_name: String(r.index_name),
|
|
1982
|
+
level: Number(r.level),
|
|
1983
|
+
start_segment: Number(r.start_segment),
|
|
1984
|
+
end_segment: Number(r.end_segment),
|
|
1985
|
+
object_key: String(r.object_key),
|
|
1986
|
+
size_bytes: Number(r.size_bytes ?? 0),
|
|
1987
|
+
filter_len: Number(r.filter_len),
|
|
1988
|
+
record_count: Number(r.record_count),
|
|
1989
|
+
retired_gen: r.retired_gen == null ? null : Number(r.retired_gen),
|
|
1990
|
+
retired_at_ms: r.retired_at_ms == null ? null : this.toBigInt(r.retired_at_ms),
|
|
1991
|
+
}));
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
insertSecondaryIndexRun(row: Omit<SecondaryIndexRunRow, "retired_gen" | "retired_at_ms">): void {
|
|
1995
|
+
this.stmts.insertSecondaryIndexRun.run(
|
|
1996
|
+
row.run_id,
|
|
1997
|
+
row.stream,
|
|
1998
|
+
row.index_name,
|
|
1999
|
+
row.level,
|
|
2000
|
+
row.start_segment,
|
|
2001
|
+
row.end_segment,
|
|
2002
|
+
row.object_key,
|
|
2003
|
+
row.size_bytes,
|
|
2004
|
+
row.filter_len,
|
|
2005
|
+
row.record_count
|
|
2006
|
+
);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
retireSecondaryIndexRuns(runIds: string[], retiredGen: number, retiredAtMs: bigint): void {
|
|
2010
|
+
if (runIds.length === 0) return;
|
|
2011
|
+
const tx = this.db.transaction(() => {
|
|
2012
|
+
for (const runId of runIds) {
|
|
2013
|
+
this.stmts.retireSecondaryIndexRun.run(retiredGen, retiredAtMs, runId);
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
tx();
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
deleteSecondaryIndexRuns(runIds: string[]): void {
|
|
2020
|
+
if (runIds.length === 0) return;
|
|
2021
|
+
const tx = this.db.transaction(() => {
|
|
2022
|
+
for (const runId of runIds) {
|
|
2023
|
+
this.stmts.deleteSecondaryIndexRun.run(runId);
|
|
2024
|
+
}
|
|
2025
|
+
});
|
|
2026
|
+
tx();
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
deleteSecondaryIndex(stream: string, indexName: string): void {
|
|
1324
2030
|
const tx = this.db.transaction(() => {
|
|
1325
|
-
this.stmts.
|
|
2031
|
+
this.stmts.deleteSecondaryIndexRunsForIndex.run(stream, indexName);
|
|
2032
|
+
this.stmts.deleteSecondaryIndexState.run(stream, indexName);
|
|
2033
|
+
});
|
|
2034
|
+
tx();
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
getLexiconIndexState(stream: string, sourceKind: string, sourceName: string): LexiconIndexStateRow | null {
|
|
2038
|
+
const row = this.stmts.getLexiconIndexState.get(stream, sourceKind, sourceName) as any;
|
|
2039
|
+
if (!row) return null;
|
|
2040
|
+
return {
|
|
2041
|
+
stream: String(row.stream),
|
|
2042
|
+
source_kind: String(row.source_kind),
|
|
2043
|
+
source_name: String(row.source_name),
|
|
2044
|
+
indexed_through: Number(row.indexed_through),
|
|
2045
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
listLexiconIndexStates(stream: string): LexiconIndexStateRow[] {
|
|
2050
|
+
const rows = this.stmts.listLexiconIndexStates.all(stream) as any[];
|
|
2051
|
+
return rows.map((row) => ({
|
|
2052
|
+
stream: String(row.stream),
|
|
2053
|
+
source_kind: String(row.source_kind),
|
|
2054
|
+
source_name: String(row.source_name),
|
|
2055
|
+
indexed_through: Number(row.indexed_through),
|
|
2056
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
2057
|
+
}));
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
upsertLexiconIndexState(stream: string, sourceKind: string, sourceName: string, indexedThrough: number): void {
|
|
2061
|
+
this.stmts.upsertLexiconIndexState.run(stream, sourceKind, sourceName, indexedThrough, this.nowMs());
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
updateLexiconIndexedThrough(stream: string, sourceKind: string, sourceName: string, indexedThrough: number): void {
|
|
2065
|
+
this.stmts.updateLexiconIndexedThrough.run(indexedThrough, this.nowMs(), stream, sourceKind, sourceName);
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
listLexiconIndexRuns(stream: string, sourceKind: string, sourceName: string): LexiconIndexRunRow[] {
|
|
2069
|
+
const rows = this.stmts.listLexiconIndexRuns.all(stream, sourceKind, sourceName) as any[];
|
|
2070
|
+
return rows.map((row) => ({
|
|
2071
|
+
run_id: String(row.run_id),
|
|
2072
|
+
stream: String(row.stream),
|
|
2073
|
+
source_kind: String(row.source_kind),
|
|
2074
|
+
source_name: String(row.source_name),
|
|
2075
|
+
level: Number(row.level),
|
|
2076
|
+
start_segment: Number(row.start_segment),
|
|
2077
|
+
end_segment: Number(row.end_segment),
|
|
2078
|
+
object_key: String(row.object_key),
|
|
2079
|
+
size_bytes: Number(row.size_bytes ?? 0),
|
|
2080
|
+
record_count: Number(row.record_count ?? 0),
|
|
2081
|
+
retired_gen: row.retired_gen == null ? null : Number(row.retired_gen),
|
|
2082
|
+
retired_at_ms: row.retired_at_ms == null ? null : this.toBigInt(row.retired_at_ms),
|
|
2083
|
+
}));
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
listLexiconIndexRunsAll(stream: string, sourceKind: string, sourceName: string): LexiconIndexRunRow[] {
|
|
2087
|
+
const rows = this.stmts.listLexiconIndexRunsAll.all(stream, sourceKind, sourceName) as any[];
|
|
2088
|
+
return rows.map((row) => ({
|
|
2089
|
+
run_id: String(row.run_id),
|
|
2090
|
+
stream: String(row.stream),
|
|
2091
|
+
source_kind: String(row.source_kind),
|
|
2092
|
+
source_name: String(row.source_name),
|
|
2093
|
+
level: Number(row.level),
|
|
2094
|
+
start_segment: Number(row.start_segment),
|
|
2095
|
+
end_segment: Number(row.end_segment),
|
|
2096
|
+
object_key: String(row.object_key),
|
|
2097
|
+
size_bytes: Number(row.size_bytes ?? 0),
|
|
2098
|
+
record_count: Number(row.record_count ?? 0),
|
|
2099
|
+
retired_gen: row.retired_gen == null ? null : Number(row.retired_gen),
|
|
2100
|
+
retired_at_ms: row.retired_at_ms == null ? null : this.toBigInt(row.retired_at_ms),
|
|
2101
|
+
}));
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
listRetiredLexiconIndexRuns(stream: string, sourceKind: string, sourceName: string): LexiconIndexRunRow[] {
|
|
2105
|
+
const rows = this.stmts.listRetiredLexiconIndexRuns.all(stream, sourceKind, sourceName) as any[];
|
|
2106
|
+
return rows.map((row) => ({
|
|
2107
|
+
run_id: String(row.run_id),
|
|
2108
|
+
stream: String(row.stream),
|
|
2109
|
+
source_kind: String(row.source_kind),
|
|
2110
|
+
source_name: String(row.source_name),
|
|
2111
|
+
level: Number(row.level),
|
|
2112
|
+
start_segment: Number(row.start_segment),
|
|
2113
|
+
end_segment: Number(row.end_segment),
|
|
2114
|
+
object_key: String(row.object_key),
|
|
2115
|
+
size_bytes: Number(row.size_bytes ?? 0),
|
|
2116
|
+
record_count: Number(row.record_count ?? 0),
|
|
2117
|
+
retired_gen: row.retired_gen == null ? null : Number(row.retired_gen),
|
|
2118
|
+
retired_at_ms: row.retired_at_ms == null ? null : this.toBigInt(row.retired_at_ms),
|
|
2119
|
+
}));
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
insertLexiconIndexRun(row: Omit<LexiconIndexRunRow, "retired_gen" | "retired_at_ms">): void {
|
|
2123
|
+
this.stmts.insertLexiconIndexRun.run(
|
|
2124
|
+
row.run_id,
|
|
2125
|
+
row.stream,
|
|
2126
|
+
row.source_kind,
|
|
2127
|
+
row.source_name,
|
|
2128
|
+
row.level,
|
|
2129
|
+
row.start_segment,
|
|
2130
|
+
row.end_segment,
|
|
2131
|
+
row.object_key,
|
|
2132
|
+
row.size_bytes,
|
|
2133
|
+
row.record_count
|
|
2134
|
+
);
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
retireLexiconIndexRuns(runIds: string[], retiredGen: number, retiredAtMs: bigint): void {
|
|
2138
|
+
if (runIds.length === 0) return;
|
|
2139
|
+
const tx = this.db.transaction(() => {
|
|
2140
|
+
for (const runId of runIds) {
|
|
2141
|
+
this.stmts.retireLexiconIndexRun.run(retiredGen, retiredAtMs, runId);
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
tx();
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
deleteLexiconIndexRuns(runIds: string[]): void {
|
|
2148
|
+
if (runIds.length === 0) return;
|
|
2149
|
+
const tx = this.db.transaction(() => {
|
|
2150
|
+
for (const runId of runIds) {
|
|
2151
|
+
this.stmts.deleteLexiconIndexRun.run(runId);
|
|
2152
|
+
}
|
|
2153
|
+
});
|
|
2154
|
+
tx();
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
deleteLexiconIndexSource(stream: string, sourceKind: string, sourceName: string): void {
|
|
2158
|
+
const tx = this.db.transaction(() => {
|
|
2159
|
+
this.stmts.deleteLexiconIndexRunsForSource.run(stream, sourceKind, sourceName);
|
|
2160
|
+
this.stmts.deleteLexiconIndexState.run(stream, sourceKind, sourceName);
|
|
2161
|
+
});
|
|
2162
|
+
tx();
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
getSearchCompanionPlan(stream: string): SearchCompanionPlanRow | null {
|
|
2166
|
+
const row = this.stmts.getSearchCompanionPlan.get(stream) as any;
|
|
2167
|
+
if (!row) return null;
|
|
2168
|
+
return {
|
|
2169
|
+
stream: String(row.stream),
|
|
2170
|
+
generation: Number(row.generation),
|
|
2171
|
+
plan_hash: String(row.plan_hash),
|
|
2172
|
+
plan_json: String(row.plan_json),
|
|
2173
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
listSearchCompanionPlanStreams(): string[] {
|
|
2178
|
+
const rows = this.stmts.listSearchCompanionPlanStreams.all() as any[];
|
|
2179
|
+
return rows.map((row) => String(row.stream));
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
upsertSearchCompanionPlan(stream: string, generation: number, planHash: string, planJson: string): void {
|
|
2183
|
+
this.stmts.upsertSearchCompanionPlan.run(stream, generation, planHash, planJson, this.nowMs());
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
deleteSearchCompanionPlan(stream: string): void {
|
|
2187
|
+
this.stmts.deleteSearchCompanionPlan.run(stream);
|
|
2188
|
+
}
|
|
2189
|
+
|
|
2190
|
+
listSearchSegmentCompanions(stream: string): SearchSegmentCompanionRow[] {
|
|
2191
|
+
const rows = this.stmts.listSearchSegmentCompanions.all(stream) as any[];
|
|
2192
|
+
return rows.map((row) => ({
|
|
2193
|
+
stream: String(row.stream),
|
|
2194
|
+
segment_index: Number(row.segment_index),
|
|
2195
|
+
object_key: String(row.object_key),
|
|
2196
|
+
plan_generation: Number(row.plan_generation),
|
|
2197
|
+
sections_json: String(row.sections_json),
|
|
2198
|
+
section_sizes_json: String(row.section_sizes_json ?? "{}"),
|
|
2199
|
+
size_bytes: Number(row.size_bytes ?? 0),
|
|
2200
|
+
primary_timestamp_min_ms: row.primary_timestamp_min_ms == null ? null : this.toBigInt(row.primary_timestamp_min_ms),
|
|
2201
|
+
primary_timestamp_max_ms: row.primary_timestamp_max_ms == null ? null : this.toBigInt(row.primary_timestamp_max_ms),
|
|
2202
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
2203
|
+
}));
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
getSearchSegmentCompanion(stream: string, segmentIndex: number): SearchSegmentCompanionRow | null {
|
|
2207
|
+
const row = this.stmts.getSearchSegmentCompanion.get(stream, segmentIndex) as any;
|
|
2208
|
+
if (!row) return null;
|
|
2209
|
+
return {
|
|
2210
|
+
stream: String(row.stream),
|
|
2211
|
+
segment_index: Number(row.segment_index),
|
|
2212
|
+
object_key: String(row.object_key),
|
|
2213
|
+
plan_generation: Number(row.plan_generation),
|
|
2214
|
+
sections_json: String(row.sections_json),
|
|
2215
|
+
section_sizes_json: String(row.section_sizes_json ?? "{}"),
|
|
2216
|
+
size_bytes: Number(row.size_bytes ?? 0),
|
|
2217
|
+
primary_timestamp_min_ms: row.primary_timestamp_min_ms == null ? null : this.toBigInt(row.primary_timestamp_min_ms),
|
|
2218
|
+
primary_timestamp_max_ms: row.primary_timestamp_max_ms == null ? null : this.toBigInt(row.primary_timestamp_max_ms),
|
|
2219
|
+
updated_at_ms: this.toBigInt(row.updated_at_ms),
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
upsertSearchSegmentCompanion(
|
|
2224
|
+
stream: string,
|
|
2225
|
+
segmentIndex: number,
|
|
2226
|
+
objectKey: string,
|
|
2227
|
+
planGeneration: number,
|
|
2228
|
+
sectionsJson: string,
|
|
2229
|
+
sectionSizesJson: string,
|
|
2230
|
+
sizeBytes: number,
|
|
2231
|
+
primaryTimestampMinMs: bigint | null,
|
|
2232
|
+
primaryTimestampMaxMs: bigint | null
|
|
2233
|
+
): void {
|
|
2234
|
+
this.stmts.upsertSearchSegmentCompanion.run(
|
|
2235
|
+
stream,
|
|
2236
|
+
segmentIndex,
|
|
2237
|
+
objectKey,
|
|
2238
|
+
planGeneration,
|
|
2239
|
+
sectionsJson,
|
|
2240
|
+
sectionSizesJson,
|
|
2241
|
+
sizeBytes,
|
|
2242
|
+
primaryTimestampMinMs,
|
|
2243
|
+
primaryTimestampMaxMs,
|
|
2244
|
+
this.nowMs()
|
|
2245
|
+
);
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
deleteSearchSegmentCompanionsBeforeGeneration(stream: string, generation: number): void {
|
|
2249
|
+
this.stmts.deleteSearchSegmentCompanionsFromGeneration.run(stream, generation);
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
deleteSearchSegmentCompanionsFrom(stream: string, segmentIndex: number): void {
|
|
2253
|
+
this.stmts.deleteSearchSegmentCompanionsFromIndex.run(stream, segmentIndex);
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
deleteSearchSegmentCompanions(stream: string): void {
|
|
2257
|
+
this.stmts.deleteSearchSegmentCompanions.run(stream);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
commitManifest(
|
|
2261
|
+
stream: string,
|
|
2262
|
+
generation: number,
|
|
2263
|
+
etag: string,
|
|
2264
|
+
uploadedAtMs: bigint,
|
|
2265
|
+
uploadedThrough: bigint,
|
|
2266
|
+
sizeBytes: number
|
|
2267
|
+
): void {
|
|
2268
|
+
const tx = this.db.transaction(() => {
|
|
2269
|
+
this.stmts.upsertManifest.run(stream, generation, generation, uploadedAtMs, etag, sizeBytes);
|
|
1326
2270
|
this.stmts.advanceUploadedThrough.run(uploadedThrough, this.nowMs(), stream);
|
|
1327
2271
|
let gcThrough = uploadedThrough;
|
|
1328
|
-
const
|
|
1329
|
-
if (
|
|
1330
|
-
const
|
|
1331
|
-
gcThrough =
|
|
2272
|
+
const touchState = this.stmts.getStreamTouchState.get(stream) as any;
|
|
2273
|
+
if (touchState) {
|
|
2274
|
+
const processedThrough = this.toBigInt(touchState.processed_through);
|
|
2275
|
+
gcThrough = processedThrough < gcThrough ? processedThrough : gcThrough;
|
|
1332
2276
|
}
|
|
1333
2277
|
if (gcThrough < 0n) return;
|
|
1334
2278
|
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
if (BASE_WAL_GC_CHUNK_OFFSETS > 0) {
|
|
1339
|
-
const oldest = this.getWalOldestOffset(stream);
|
|
1340
|
-
if (oldest != null) {
|
|
1341
|
-
const maxThrough = oldest + BigInt(BASE_WAL_GC_CHUNK_OFFSETS) - 1n;
|
|
1342
|
-
if (deleteThrough > maxThrough) deleteThrough = maxThrough;
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
if (deleteThrough < 0n) return;
|
|
1346
|
-
|
|
1347
|
-
const bound = this.bindInt(deleteThrough);
|
|
1348
|
-
const stats = this.db
|
|
1349
|
-
.query(
|
|
1350
|
-
`SELECT COALESCE(SUM(payload_len), 0) as bytes, COUNT(*) as rows
|
|
1351
|
-
FROM wal WHERE stream=? AND offset <= ?;`
|
|
1352
|
-
)
|
|
1353
|
-
.get(stream, bound) as any;
|
|
1354
|
-
const bytes = this.toBigInt(stats?.bytes ?? 0);
|
|
1355
|
-
const rows = this.toBigInt(stats?.rows ?? 0);
|
|
2279
|
+
const { deletedRows: rows, deletedBytes: bytes } = this.deleteWalThroughWithStats(stream, gcThrough, {
|
|
2280
|
+
maxRows: BASE_WAL_GC_CHUNK_OFFSETS,
|
|
2281
|
+
});
|
|
1356
2282
|
if (rows <= 0n) return;
|
|
1357
2283
|
|
|
1358
|
-
this.stmts.deleteWalBeforeOffset.run(stream, bound);
|
|
1359
|
-
|
|
1360
2284
|
// Keep retained-WAL counters consistent for metrics/debugging.
|
|
1361
2285
|
const now = this.nowMs();
|
|
1362
2286
|
this.db.query(
|
|
@@ -1370,6 +2294,152 @@ export class SqliteDurableStore {
|
|
|
1370
2294
|
tx();
|
|
1371
2295
|
}
|
|
1372
2296
|
|
|
2297
|
+
recordObjectStoreRequestByHash(streamHash: string, artifact: string, op: string, bytes = 0, count = 1): void {
|
|
2298
|
+
if (!streamHash || !artifact || !op) return;
|
|
2299
|
+
this.stmts.recordObjectStoreRequest.run(streamHash, artifact, op, count, bytes, this.nowMs());
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
getObjectStoreRequestSummaryByHash(streamHash: string): {
|
|
2303
|
+
puts: bigint;
|
|
2304
|
+
reads: bigint;
|
|
2305
|
+
gets: bigint;
|
|
2306
|
+
heads: bigint;
|
|
2307
|
+
lists: bigint;
|
|
2308
|
+
deletes: bigint;
|
|
2309
|
+
by_artifact: Array<{ artifact: string; puts: bigint; gets: bigint; heads: bigint; lists: bigint; deletes: bigint; reads: bigint }>;
|
|
2310
|
+
} {
|
|
2311
|
+
const rows = this.db
|
|
2312
|
+
.query(
|
|
2313
|
+
`SELECT artifact, op, count
|
|
2314
|
+
FROM objectstore_request_counts
|
|
2315
|
+
WHERE stream_hash=?
|
|
2316
|
+
ORDER BY artifact ASC, op ASC;`
|
|
2317
|
+
)
|
|
2318
|
+
.all(streamHash) as any[];
|
|
2319
|
+
const byArtifact = new Map<string, { puts: bigint; gets: bigint; heads: bigint; lists: bigint; deletes: bigint; reads: bigint }>();
|
|
2320
|
+
let puts = 0n;
|
|
2321
|
+
let gets = 0n;
|
|
2322
|
+
let heads = 0n;
|
|
2323
|
+
let lists = 0n;
|
|
2324
|
+
let deletes = 0n;
|
|
2325
|
+
for (const row of rows) {
|
|
2326
|
+
const artifact = String(row.artifact);
|
|
2327
|
+
const op = String(row.op);
|
|
2328
|
+
const count = this.toBigInt(row.count ?? 0);
|
|
2329
|
+
const entry = byArtifact.get(artifact) ?? { puts: 0n, gets: 0n, heads: 0n, lists: 0n, deletes: 0n, reads: 0n };
|
|
2330
|
+
if (op === "put") {
|
|
2331
|
+
entry.puts += count;
|
|
2332
|
+
puts += count;
|
|
2333
|
+
} else if (op === "get") {
|
|
2334
|
+
entry.gets += count;
|
|
2335
|
+
entry.reads += count;
|
|
2336
|
+
gets += count;
|
|
2337
|
+
} else if (op === "head") {
|
|
2338
|
+
entry.heads += count;
|
|
2339
|
+
entry.reads += count;
|
|
2340
|
+
heads += count;
|
|
2341
|
+
} else if (op === "list") {
|
|
2342
|
+
entry.lists += count;
|
|
2343
|
+
entry.reads += count;
|
|
2344
|
+
lists += count;
|
|
2345
|
+
} else if (op === "delete") {
|
|
2346
|
+
entry.deletes += count;
|
|
2347
|
+
deletes += count;
|
|
2348
|
+
}
|
|
2349
|
+
byArtifact.set(artifact, entry);
|
|
2350
|
+
}
|
|
2351
|
+
return {
|
|
2352
|
+
puts,
|
|
2353
|
+
reads: gets + heads + lists,
|
|
2354
|
+
gets,
|
|
2355
|
+
heads,
|
|
2356
|
+
lists,
|
|
2357
|
+
deletes,
|
|
2358
|
+
by_artifact: Array.from(byArtifact.entries()).map(([artifact, entry]) => ({ artifact, ...entry })),
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
getUploadedSegmentBytes(stream: string): bigint {
|
|
2363
|
+
const row = this.db
|
|
2364
|
+
.query(`SELECT COALESCE(SUM(size_bytes), 0) as total FROM segments WHERE stream=? AND r2_etag IS NOT NULL;`)
|
|
2365
|
+
.get(stream) as any;
|
|
2366
|
+
return this.toBigInt(row?.total ?? 0);
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
getPendingSealedSegmentBytes(stream: string): bigint {
|
|
2370
|
+
const row = this.db
|
|
2371
|
+
.query(`SELECT COALESCE(SUM(size_bytes), 0) as total FROM segments WHERE stream=? AND uploaded_at_ms IS NULL;`)
|
|
2372
|
+
.get(stream) as any;
|
|
2373
|
+
return this.toBigInt(row?.total ?? 0);
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
getRoutingIndexStorage(stream: string): { object_count: number; bytes: bigint } {
|
|
2377
|
+
const row = this.db
|
|
2378
|
+
.query(`SELECT COUNT(*) as cnt, COALESCE(SUM(size_bytes), 0) as total FROM index_runs WHERE stream=?;`)
|
|
2379
|
+
.get(stream) as any;
|
|
2380
|
+
return {
|
|
2381
|
+
object_count: Number(row?.cnt ?? 0),
|
|
2382
|
+
bytes: this.toBigInt(row?.total ?? 0),
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
|
|
2386
|
+
getSecondaryIndexStorage(stream: string): Array<{ index_name: string; object_count: number; bytes: bigint }> {
|
|
2387
|
+
const rows = this.db
|
|
2388
|
+
.query(
|
|
2389
|
+
`SELECT index_name, COUNT(*) as cnt, COALESCE(SUM(size_bytes), 0) as total
|
|
2390
|
+
FROM secondary_index_runs
|
|
2391
|
+
WHERE stream=?
|
|
2392
|
+
GROUP BY index_name
|
|
2393
|
+
ORDER BY index_name ASC;`
|
|
2394
|
+
)
|
|
2395
|
+
.all(stream) as any[];
|
|
2396
|
+
return rows.map((row) => ({
|
|
2397
|
+
index_name: String(row.index_name),
|
|
2398
|
+
object_count: Number(row.cnt ?? 0),
|
|
2399
|
+
bytes: this.toBigInt(row.total ?? 0),
|
|
2400
|
+
}));
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
getLexiconIndexStorage(
|
|
2404
|
+
stream: string
|
|
2405
|
+
): Array<{ source_kind: string; source_name: string; object_count: number; bytes: bigint }> {
|
|
2406
|
+
const rows = this.db
|
|
2407
|
+
.query(
|
|
2408
|
+
`SELECT source_kind, source_name, COUNT(*) as cnt, COALESCE(SUM(size_bytes), 0) as total
|
|
2409
|
+
FROM lexicon_index_runs
|
|
2410
|
+
WHERE stream=?
|
|
2411
|
+
GROUP BY source_kind, source_name
|
|
2412
|
+
ORDER BY source_kind ASC, source_name ASC;`
|
|
2413
|
+
)
|
|
2414
|
+
.all(stream) as any[];
|
|
2415
|
+
return rows.map((row) => ({
|
|
2416
|
+
source_kind: String(row.source_kind),
|
|
2417
|
+
source_name: String(row.source_name),
|
|
2418
|
+
object_count: Number(row.cnt ?? 0),
|
|
2419
|
+
bytes: this.toBigInt(row.total ?? 0),
|
|
2420
|
+
}));
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
getBundledCompanionStorage(stream: string): { object_count: number; bytes: bigint } {
|
|
2424
|
+
const row = this.db
|
|
2425
|
+
.query(`SELECT COUNT(*) as cnt, COALESCE(SUM(size_bytes), 0) as total FROM search_segment_companions WHERE stream=?;`)
|
|
2426
|
+
.get(stream) as any;
|
|
2427
|
+
return {
|
|
2428
|
+
object_count: Number(row?.cnt ?? 0),
|
|
2429
|
+
bytes: this.toBigInt(row?.total ?? 0),
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
getSegmentLastAppendMsFromMeta(stream: string, segmentIndex: number): bigint | null {
|
|
2434
|
+
const meta = this.getSegmentMeta(stream);
|
|
2435
|
+
if (!meta) return null;
|
|
2436
|
+
if (segmentIndex < 0 || segmentIndex >= meta.segment_count) return null;
|
|
2437
|
+
const off = segmentIndex * 8;
|
|
2438
|
+
if (off + 8 > meta.segment_last_ts.byteLength) return null;
|
|
2439
|
+
const dv = new DataView(meta.segment_last_ts.buffer, meta.segment_last_ts.byteOffset, meta.segment_last_ts.byteLength);
|
|
2440
|
+
return dv.getBigUint64(off, true) / 1_000_000n;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
1373
2443
|
/** Find candidates by bytes/rows/interval. */
|
|
1374
2444
|
candidates(
|
|
1375
2445
|
minPendingBytes: bigint,
|