@sixb/sqlite 0.1.15 → 0.1.17
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/dist/ai-limit-storage.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/execution-storage.d.ts +28 -0
- package/dist/execution-storage.d.ts.map +1 -1
- package/dist/file-download-grant-storage.d.ts +11 -0
- package/dist/file-download-grant-storage.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1428 -599
- package/dist/index.js.map +29 -27
- package/dist/migrations.d.ts.map +1 -1
- package/dist/objects/query-compiler.d.ts +3 -0
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/read-scope.d.ts +2 -0
- package/dist/objects/read-scope.d.ts.map +1 -1
- package/dist/objects/reader.d.ts +1 -0
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/rows.d.ts +1 -0
- package/dist/objects/rows.d.ts.map +1 -1
- package/dist/objects/storage.d.ts.map +1 -1
- package/dist/ontology-storage/index.d.ts +2 -0
- package/dist/ontology-storage/index.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +17 -37
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +3 -26
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +3 -6
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/replacement-plans.d.ts +88 -0
- package/dist/ontology-storage/replacement-plans.d.ts.map +1 -0
- package/dist/ontology-storage/shared.d.ts +8 -5
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/source-cleanup.d.ts.map +1 -1
- package/dist/ontology-storage/source-roots.d.ts +15 -2
- package/dist/ontology-storage/source-roots.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts +1 -2
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/dist/rules-storage.d.ts.map +1 -1
- package/dist/testing.d.ts +4 -1
- package/dist/testing.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/ai-cost-storage.ts +10 -0
- package/src/ai-limit-storage.ts +13 -2
- package/src/ai-usage-storage.ts +9 -1
- package/src/execution-storage.ts +7 -2
- package/src/file-download-grant-storage.ts +76 -0
- package/src/index.ts +10 -3
- package/src/migrations/053-replacement-plans.sql +73 -0
- package/src/migrations/054-slim-ontology-outbox.sql +21 -0
- package/src/migrations/055-commit-touches.sql +31 -0
- package/src/migrations/056-audio-transcription.sql +10 -0
- package/src/migrations/057-reranking-model-kind.sql +16 -0
- package/src/migrations/058-file-download-grants.sql +21 -0
- package/src/migrations/059-drained-source-versions.sql +11 -0
- package/src/migrations.ts +115 -0
- package/src/objects/README.md +19 -0
- package/src/objects/query-compiler.ts +49 -7
- package/src/objects/read-scope.ts +10 -8
- package/src/objects/reader.ts +44 -8
- package/src/objects/rows.ts +1 -0
- package/src/objects/storage.ts +5 -11
- package/src/ontology-storage/index.ts +6 -1
- package/src/ontology-storage/materialization-session.ts +120 -211
- package/src/ontology-storage/materialization-state.ts +5 -261
- package/src/ontology-storage/materialization-writer.ts +11 -13
- package/src/ontology-storage/materializations.ts +133 -210
- package/src/ontology-storage/outbox.ts +50 -16
- package/src/ontology-storage/replacement-plans.ts +1064 -0
- package/src/ontology-storage/shared.ts +17 -12
- package/src/ontology-storage/source-cleanup.ts +24 -9
- package/src/ontology-storage/source-roots.ts +38 -14
- package/src/ontology-storage/sources.ts +0 -26
- package/src/projection-run-storage.ts +7 -1
- package/src/rules-storage.ts +9 -14
- package/src/testing.ts +6 -4
|
@@ -17,32 +17,33 @@ import {
|
|
|
17
17
|
assertExpectedObjectRevision,
|
|
18
18
|
assertMaterializationFinalizationCorrelation,
|
|
19
19
|
assertMaterializationHeader,
|
|
20
|
-
assertMaterializationLaneCompletion,
|
|
21
20
|
assertPageRows,
|
|
22
|
-
assertPlanChunkCorrelations,
|
|
23
21
|
assertSourceActivationCorrelation,
|
|
22
|
+
beginMaterializationApply,
|
|
23
|
+
beginMaterializationVectorChanges,
|
|
24
|
+
createCardinalityValidator,
|
|
24
25
|
invalidCorrelation,
|
|
26
|
+
materializationOutboxWrite,
|
|
27
|
+
materializationPlanChunk,
|
|
25
28
|
sameNonnegativeCounts as sameCounts,
|
|
26
29
|
uniqueSorted,
|
|
27
30
|
} from "@sixb/core/internal/ontology-storage-provider"
|
|
28
31
|
import type {
|
|
29
|
-
|
|
32
|
+
AppliedMaterialization,
|
|
33
|
+
ApplyMaterializationInput,
|
|
30
34
|
ApplyMaterializationResult,
|
|
31
35
|
FinalizeMaterializationInput,
|
|
32
|
-
MaterializationObjectExistence,
|
|
33
36
|
MaterializationPlanHeader,
|
|
37
|
+
MaterializationPlanWorkRecord,
|
|
34
38
|
MaterializationSession,
|
|
35
39
|
MaterializationStatePage,
|
|
36
|
-
|
|
40
|
+
MaterializationVectorChangePage,
|
|
37
41
|
OntologyCommitRecord,
|
|
38
42
|
OntologyMaterializationStorage,
|
|
39
|
-
ReadMaterializationObjectExistenceInput,
|
|
40
43
|
SourceActivationWrite,
|
|
41
|
-
SourceReplacementStatePage,
|
|
42
44
|
StageMaterializationWorkInput,
|
|
43
45
|
StreamMaterializationStateInput,
|
|
44
|
-
|
|
45
|
-
StreamSourceReplacementStateInput,
|
|
46
|
+
StreamMaterializationVectorChangesInput,
|
|
46
47
|
} from "@sixb/core/storage"
|
|
47
48
|
import { yieldSqliteEventLoop } from "../transactions"
|
|
48
49
|
import {
|
|
@@ -50,11 +51,9 @@ import {
|
|
|
50
51
|
SqliteMaterializationSessions,
|
|
51
52
|
type SqliteOntologyTransactionContext,
|
|
52
53
|
} from "./materialization-session"
|
|
53
|
-
import {
|
|
54
|
-
SQLITE_MATERIALIZATION_WORK_TABLE,
|
|
55
|
-
SqliteMaterializationStateReader,
|
|
56
|
-
} from "./materialization-state"
|
|
54
|
+
import { SqliteMaterializationStateReader } from "./materialization-state"
|
|
57
55
|
import { SqliteMaterializationWriter } from "./materialization-writer"
|
|
56
|
+
import { boundReplacementPlan } from "./replacement-plans"
|
|
58
57
|
import {
|
|
59
58
|
assertProjectionExecution,
|
|
60
59
|
canonicalJson,
|
|
@@ -67,6 +66,54 @@ import {
|
|
|
67
66
|
} from "./shared"
|
|
68
67
|
import { activateSourceRoots } from "./source-roots"
|
|
69
68
|
|
|
69
|
+
/** Rows the provider reads and writes at once while it applies a staged plan. */
|
|
70
|
+
const APPLY_PAGE_ROWS = 1_000
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Records the entities whose plan inputs a commit's applied work changed, once each, as the
|
|
74
|
+
* in-memory `touchPlanItem` does: a replacement plan's refresh rechecks only those.
|
|
75
|
+
*/
|
|
76
|
+
function touchAppliedWorkStatement(workTable: string): string {
|
|
77
|
+
return `
|
|
78
|
+
INSERT INTO ontology_commit_touches (project_id, commit_id, entity_kind, identity_key)
|
|
79
|
+
SELECT DISTINCT $projectId, $commitId, kind, CASE kind
|
|
80
|
+
WHEN 'object' THEN
|
|
81
|
+
json_array(json_extract(ref, '$.objectTypeId'), json_extract(ref, '$.primaryId'))
|
|
82
|
+
WHEN 'scope' THEN json_array(
|
|
83
|
+
json_extract(ref, '$.source.objectTypeId'), json_extract(ref, '$.source.primaryId'),
|
|
84
|
+
json_extract(ref, '$.linkId')
|
|
85
|
+
)
|
|
86
|
+
ELSE json_array(
|
|
87
|
+
json_extract(ref, '$.source.objectTypeId'), json_extract(ref, '$.source.primaryId'),
|
|
88
|
+
json_extract(ref, '$.linkId'), json_extract(ref, '$.target.objectTypeId'),
|
|
89
|
+
json_extract(ref, '$.target.primaryId')
|
|
90
|
+
)
|
|
91
|
+
END
|
|
92
|
+
FROM (
|
|
93
|
+
SELECT CASE json_extract(payload, '$.item.kind')
|
|
94
|
+
WHEN 'object-upsert' THEN 'object'
|
|
95
|
+
WHEN 'object-delete' THEN 'object'
|
|
96
|
+
WHEN 'object-override-upsert' THEN 'object'
|
|
97
|
+
WHEN 'object-override-delete' THEN 'object'
|
|
98
|
+
WHEN 'point-upsert' THEN 'object'
|
|
99
|
+
WHEN 'link-upsert' THEN 'link'
|
|
100
|
+
WHEN 'link-delete' THEN 'link'
|
|
101
|
+
WHEN 'link-override-upsert' THEN 'link'
|
|
102
|
+
WHEN 'link-override-delete' THEN 'link'
|
|
103
|
+
WHEN 'link-slot-override-upsert' THEN 'scope'
|
|
104
|
+
WHEN 'link-slot-override-delete' THEN 'scope'
|
|
105
|
+
END AS kind,
|
|
106
|
+
CASE json_extract(payload, '$.item.kind')
|
|
107
|
+
WHEN 'object-upsert' THEN json_extract(payload, '$.item.value.row.ref')
|
|
108
|
+
WHEN 'link-upsert' THEN json_extract(payload, '$.item.value.row.ref')
|
|
109
|
+
WHEN 'point-upsert' THEN json_extract(payload, '$.item.value.point.series.object')
|
|
110
|
+
ELSE json_extract(payload, '$.item.value.ref')
|
|
111
|
+
END AS ref
|
|
112
|
+
FROM ${workTable}
|
|
113
|
+
WHERE work_id = $workId AND lane = 'apply'
|
|
114
|
+
)`
|
|
115
|
+
}
|
|
116
|
+
|
|
70
117
|
export class SqliteOntologyMaterializationStorage implements OntologyMaterializationStorage {
|
|
71
118
|
private readonly sessions: SqliteMaterializationSessions
|
|
72
119
|
private readonly writer: SqliteMaterializationWriter
|
|
@@ -93,7 +140,10 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
93
140
|
|
|
94
141
|
async begin(input: MaterializationPlanHeader): Promise<MaterializationSession> {
|
|
95
142
|
assertMaterializationHeader(input)
|
|
96
|
-
const session = this.sessions.create(
|
|
143
|
+
const session = this.sessions.create(
|
|
144
|
+
input,
|
|
145
|
+
input.plan ? boundReplacementPlan(this.db, input) : null
|
|
146
|
+
)
|
|
97
147
|
try {
|
|
98
148
|
this.assertCommitAbsent(input)
|
|
99
149
|
const reader = new SqliteMaterializationStateReader(this.db, input.commit.projectId)
|
|
@@ -178,134 +228,67 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
178
228
|
}
|
|
179
229
|
}
|
|
180
230
|
|
|
181
|
-
async *streamSourceReplacementState(
|
|
182
|
-
input: StreamSourceReplacementStateInput
|
|
183
|
-
): AsyncIterable<SourceReplacementStatePage> {
|
|
184
|
-
const session = this.sessions.require(input.session)
|
|
185
|
-
assertPageRows(input.pageRows)
|
|
186
|
-
const replacement = this.requireReplacement(session, input)
|
|
187
|
-
const reader = new SqliteMaterializationStateReader(this.db, session.header.commit.projectId)
|
|
188
|
-
if (input.entityKind === "object") {
|
|
189
|
-
if (replacement.projectionKind !== "object") {
|
|
190
|
-
throw new MaterializationConflictError(
|
|
191
|
-
"source-materialization",
|
|
192
|
-
"Link projection replacement cannot stream object state."
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
if (replacement.objectStreamStarted) {
|
|
196
|
-
throw new MaterializationConflictError(
|
|
197
|
-
"effective-state",
|
|
198
|
-
"Replacement object state may only be streamed once per session."
|
|
199
|
-
)
|
|
200
|
-
}
|
|
201
|
-
replacement.objectStreamStarted = true
|
|
202
|
-
for (const identities of reader.replacementIdentities({
|
|
203
|
-
sessionId: session.id,
|
|
204
|
-
sourceId: replacement.sourceId,
|
|
205
|
-
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
206
|
-
previousMaterializationId: replacement.previousMaterializationId,
|
|
207
|
-
incremental: replacement.incremental,
|
|
208
|
-
kind: "object",
|
|
209
|
-
pageRows: input.pageRows,
|
|
210
|
-
})) {
|
|
211
|
-
this.sessions.require(input.session)
|
|
212
|
-
const refs = identities.map((identity) => {
|
|
213
|
-
if (identity.kind !== "object") {
|
|
214
|
-
invalidCorrelation("Object replacement returned a link identity.")
|
|
215
|
-
}
|
|
216
|
-
return identity.ref
|
|
217
|
-
})
|
|
218
|
-
const objects = reader.replacementObjectStates(
|
|
219
|
-
replacement.sourceId,
|
|
220
|
-
replacement.candidateMaterializationId,
|
|
221
|
-
refs
|
|
222
|
-
)
|
|
223
|
-
yield { objects, links: [] }
|
|
224
|
-
}
|
|
225
|
-
this.sessions.require(input.session)
|
|
226
|
-
replacement.objectStreamCompleted = true
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (replacement.projectionKind === "object" && !replacement.objectStreamCompleted) {
|
|
231
|
-
throw new MaterializationConflictError(
|
|
232
|
-
"effective-state",
|
|
233
|
-
"Object projection replacement must fully stream object state before link state."
|
|
234
|
-
)
|
|
235
|
-
}
|
|
236
|
-
if (replacement.linkStreamStarted) {
|
|
237
|
-
throw new MaterializationConflictError(
|
|
238
|
-
"effective-state",
|
|
239
|
-
"Replacement link state may only be streamed once per session."
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
replacement.linkStreamStarted = true
|
|
243
|
-
const materializationIds = [
|
|
244
|
-
replacement.candidateMaterializationId,
|
|
245
|
-
...(replacement.previousMaterializationId ? [replacement.previousMaterializationId] : []),
|
|
246
|
-
]
|
|
247
|
-
for (const identities of reader.replacementIdentities({
|
|
248
|
-
sessionId: session.id,
|
|
249
|
-
sourceId: replacement.sourceId,
|
|
250
|
-
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
251
|
-
previousMaterializationId: replacement.previousMaterializationId,
|
|
252
|
-
incremental: replacement.incremental,
|
|
253
|
-
kind: "link",
|
|
254
|
-
pageRows: input.pageRows,
|
|
255
|
-
})) {
|
|
256
|
-
this.sessions.require(input.session)
|
|
257
|
-
const linkIdentities = identities.map((identity) => {
|
|
258
|
-
if (identity.kind !== "link") {
|
|
259
|
-
invalidCorrelation("Link replacement returned an object identity.")
|
|
260
|
-
}
|
|
261
|
-
return identity
|
|
262
|
-
})
|
|
263
|
-
const links = reader.replacementLinkStates(
|
|
264
|
-
replacement.sourceId,
|
|
265
|
-
replacement.candidateMaterializationId,
|
|
266
|
-
materializationIds,
|
|
267
|
-
linkIdentities,
|
|
268
|
-
replacement.incremental
|
|
269
|
-
)
|
|
270
|
-
yield { objects: [], links }
|
|
271
|
-
}
|
|
272
|
-
this.sessions.require(input.session)
|
|
273
|
-
replacement.linkStreamCompleted = true
|
|
274
|
-
}
|
|
275
|
-
|
|
276
231
|
async stageWork(input: StageMaterializationWorkInput): Promise<void> {
|
|
277
232
|
this.sessions.stage(input)
|
|
278
233
|
await yieldSqliteEventLoop()
|
|
279
234
|
}
|
|
280
235
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async readObjectExistence(
|
|
287
|
-
input: ReadMaterializationObjectExistenceInput
|
|
288
|
-
): Promise<readonly MaterializationObjectExistence[]> {
|
|
236
|
+
async *streamVectorChanges(
|
|
237
|
+
input: StreamMaterializationVectorChangesInput
|
|
238
|
+
): AsyncIterable<MaterializationVectorChangePage> {
|
|
289
239
|
const session = this.sessions.require(input.session)
|
|
290
|
-
|
|
240
|
+
assertPageRows(input.pageRows)
|
|
241
|
+
beginMaterializationVectorChanges(session)
|
|
242
|
+
for (const items of this.sessions.vectorChangePages(
|
|
243
|
+
session,
|
|
244
|
+
input.objectTypeIds,
|
|
245
|
+
input.pageRows
|
|
246
|
+
)) {
|
|
247
|
+
yield { items }
|
|
248
|
+
}
|
|
291
249
|
}
|
|
292
250
|
|
|
293
|
-
async
|
|
251
|
+
async apply(input: ApplyMaterializationInput): Promise<AppliedMaterialization> {
|
|
294
252
|
const session = this.sessions.require(input.session)
|
|
253
|
+
beginMaterializationApply(session)
|
|
295
254
|
const { commit } = session.header
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
255
|
+
const cardinality = createCardinalityValidator()
|
|
256
|
+
for (const records of this.sessions.lanePages(session, "cardinality", APPLY_PAGE_ROWS)) {
|
|
257
|
+
for (const record of records) cardinality.accept(record)
|
|
258
|
+
}
|
|
259
|
+
let eventCount = 0
|
|
260
|
+
this.db.run("SAVEPOINT sixb_ontology_apply")
|
|
299
261
|
try {
|
|
300
|
-
this.
|
|
301
|
-
|
|
302
|
-
|
|
262
|
+
for (const records of this.sessions.lanePages(session, "apply", APPLY_PAGE_ROWS)) {
|
|
263
|
+
// The writer orders tables its own way within a chunk; one phase per chunk keeps the
|
|
264
|
+
// canonical phase order.
|
|
265
|
+
for (const phase of splitByPhase(records)) {
|
|
266
|
+
this.writer.apply(
|
|
267
|
+
commit.projectId,
|
|
268
|
+
commit.id,
|
|
269
|
+
materializationPlanChunk(phase.map((record) => record.item))
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
await yieldSqliteEventLoop()
|
|
273
|
+
}
|
|
274
|
+
for (const records of this.sessions.lanePages(session, "event", APPLY_PAGE_ROWS)) {
|
|
275
|
+
const outbox = records.map((record) =>
|
|
276
|
+
materializationOutboxWrite(commit, record.draft, eventCount++)
|
|
277
|
+
)
|
|
278
|
+
this.writer.apply(commit.projectId, commit.id, materializationPlanChunk([], outbox))
|
|
279
|
+
await yieldSqliteEventLoop()
|
|
280
|
+
}
|
|
281
|
+
this.db
|
|
282
|
+
.query(touchAppliedWorkStatement(session.workTable))
|
|
283
|
+
.run({ $projectId: commit.projectId, $commitId: commit.id, $workId: session.workId })
|
|
284
|
+
this.db.run("RELEASE SAVEPOINT sixb_ontology_apply")
|
|
303
285
|
} catch (error) {
|
|
304
|
-
this.db.run("ROLLBACK TO SAVEPOINT
|
|
305
|
-
this.db.run("RELEASE SAVEPOINT
|
|
286
|
+
this.db.run("ROLLBACK TO SAVEPOINT sixb_ontology_apply")
|
|
287
|
+
this.db.run("RELEASE SAVEPOINT sixb_ontology_apply")
|
|
306
288
|
throw error
|
|
307
289
|
}
|
|
308
|
-
|
|
290
|
+
session.appliedEventCount = eventCount
|
|
291
|
+
return { eventCount }
|
|
309
292
|
}
|
|
310
293
|
|
|
311
294
|
async finalize(input: FinalizeMaterializationInput): Promise<ApplyMaterializationResult> {
|
|
@@ -324,66 +307,6 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
324
307
|
this.sessions.deactivateAll()
|
|
325
308
|
}
|
|
326
309
|
|
|
327
|
-
private requireReplacement(
|
|
328
|
-
session: SqliteMaterializationSessionState,
|
|
329
|
-
input: StreamSourceReplacementStateInput
|
|
330
|
-
): NonNullable<SqliteMaterializationSessionState["replacement"]> {
|
|
331
|
-
if (session.replacement) {
|
|
332
|
-
if (
|
|
333
|
-
session.replacement.sourceId !== input.source.projectionId ||
|
|
334
|
-
session.replacement.candidateMaterializationId !== input.candidateMaterializationId
|
|
335
|
-
) {
|
|
336
|
-
throw new MaterializationConflictError(
|
|
337
|
-
"source-materialization",
|
|
338
|
-
"Materialization session already owns another replacement union."
|
|
339
|
-
)
|
|
340
|
-
}
|
|
341
|
-
return session.replacement
|
|
342
|
-
}
|
|
343
|
-
const projectId = session.header.commit.projectId
|
|
344
|
-
const candidate = this.getSource(
|
|
345
|
-
projectId,
|
|
346
|
-
input.source.projectionId,
|
|
347
|
-
input.candidateMaterializationId
|
|
348
|
-
)
|
|
349
|
-
if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
|
|
350
|
-
throw new MaterializationConflictError(
|
|
351
|
-
"source-materialization",
|
|
352
|
-
`Candidate source materialization '${input.candidateMaterializationId}' is missing or is not ready.`
|
|
353
|
-
)
|
|
354
|
-
}
|
|
355
|
-
assertProjectionExecution(this.db, {
|
|
356
|
-
projectId,
|
|
357
|
-
sourceId: input.source.projectionId,
|
|
358
|
-
projectionRunId: candidate.projection_run_id,
|
|
359
|
-
executionToken: candidate.execution_token,
|
|
360
|
-
})
|
|
361
|
-
const previous = this.getActiveSource(projectId, input.source.projectionId)
|
|
362
|
-
if (
|
|
363
|
-
candidate.protocol !== "replacement" ||
|
|
364
|
-
(previous &&
|
|
365
|
-
(previous.protocol !== candidate.protocol ||
|
|
366
|
-
previous.projection_kind !== candidate.projection_kind))
|
|
367
|
-
) {
|
|
368
|
-
throw new MaterializationConflictError(
|
|
369
|
-
"source-materialization",
|
|
370
|
-
"Source replacement kind or protocol does not match its active materialization."
|
|
371
|
-
)
|
|
372
|
-
}
|
|
373
|
-
session.replacement = {
|
|
374
|
-
sourceId: input.source.projectionId,
|
|
375
|
-
candidateMaterializationId: input.candidateMaterializationId,
|
|
376
|
-
previousMaterializationId: previous?.materialization_id ?? null,
|
|
377
|
-
incremental: candidate.base_materialization_id !== null,
|
|
378
|
-
projectionKind: candidate.projection_kind,
|
|
379
|
-
objectStreamStarted: false,
|
|
380
|
-
objectStreamCompleted: false,
|
|
381
|
-
linkStreamStarted: false,
|
|
382
|
-
linkStreamCompleted: false,
|
|
383
|
-
}
|
|
384
|
-
return session.replacement
|
|
385
|
-
}
|
|
386
|
-
|
|
387
310
|
private assertCommitAbsent(header: MaterializationPlanHeader): void {
|
|
388
311
|
const origin = originColumns(header.commit.origin)
|
|
389
312
|
const duplicate = this.db
|
|
@@ -454,11 +377,9 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
454
377
|
const { commit } = session.header
|
|
455
378
|
const { result } = input.finalization
|
|
456
379
|
assertMaterializationFinalizationCorrelation(session, input)
|
|
457
|
-
const laneCounts = this.sessions.laneCounts(session)
|
|
458
|
-
assertMaterializationLaneCompletion(session, laneCounts)
|
|
459
380
|
this.assertFinalCardinality(session)
|
|
460
381
|
await yieldSqliteEventLoop()
|
|
461
|
-
const eventCount =
|
|
382
|
+
const eventCount = session.appliedEventCount
|
|
462
383
|
const outbox = this.db
|
|
463
384
|
.query(
|
|
464
385
|
`
|
|
@@ -517,8 +438,8 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
517
438
|
THEN cardinality_target_type_id END) AS expected_target_type_id,
|
|
518
439
|
MAX(CASE WHEN cardinality_occupied = 1
|
|
519
440
|
THEN cardinality_target_primary_id END) AS expected_target_id
|
|
520
|
-
FROM ${
|
|
521
|
-
WHERE
|
|
441
|
+
FROM ${session.workTable}
|
|
442
|
+
WHERE work_id = ? AND kind = 'cardinality'
|
|
522
443
|
AND cardinality_view = 'effective'
|
|
523
444
|
GROUP BY sort_one, cardinality_source_type_id,
|
|
524
445
|
cardinality_source_primary_id, cardinality_link_id
|
|
@@ -538,20 +459,14 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
538
459
|
GROUP BY scopes.scope_sort_key, scopes.expected_count,
|
|
539
460
|
scopes.expected_target_type_id, scopes.expected_target_id
|
|
540
461
|
)
|
|
541
|
-
SELECT
|
|
462
|
+
SELECT 1 AS mismatch
|
|
542
463
|
FROM validated
|
|
543
|
-
WHERE expected_count
|
|
544
|
-
OR actual_count <> expected_count
|
|
545
|
-
OR matching_count <> expected_count
|
|
464
|
+
WHERE actual_count <> expected_count OR matching_count <> expected_count
|
|
546
465
|
LIMIT 1
|
|
547
466
|
`
|
|
548
467
|
)
|
|
549
|
-
.get(session.
|
|
550
|
-
|
|
551
|
-
} | null
|
|
552
|
-
if (violation?.reason === "duplicate") {
|
|
553
|
-
invalidCorrelation("Materialization cardinality work violates cardinality-one.")
|
|
554
|
-
}
|
|
468
|
+
.get(session.workId, session.header.commit.projectId)
|
|
469
|
+
// Apply already rejected a scope with two staged occupants; this checks what it wrote.
|
|
555
470
|
if (violation) {
|
|
556
471
|
invalidCorrelation(
|
|
557
472
|
"Materialization cardinality work does not match the final effective link scope."
|
|
@@ -599,7 +514,7 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
599
514
|
invalidCorrelation("Source activation does not match its ready candidate identity.")
|
|
600
515
|
}
|
|
601
516
|
this.assertSource(activation.expected, commit.projectId)
|
|
602
|
-
activateSourceRoots(this.db, commit.projectId, candidate, activation)
|
|
517
|
+
activateSourceRoots(this.db, commit.projectId, commit.id, candidate, activation)
|
|
603
518
|
const previous = this.getActiveSource(commit.projectId, activation.source.projectionId)
|
|
604
519
|
if (previous) {
|
|
605
520
|
assertPinnedDatasetWatermark(
|
|
@@ -673,12 +588,10 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
673
588
|
`
|
|
674
589
|
INSERT INTO ontology_commits (
|
|
675
590
|
project_id, id, idempotency_key, request_hash, execution_id,
|
|
676
|
-
origin_kind, origin_run_id, origin_batch_ordinal, origin,
|
|
591
|
+
origin_kind, origin_run_id, origin_batch_ordinal, origin,
|
|
677
592
|
ontology_revision, projection_revision, ownership_hash,
|
|
678
593
|
intent, result, committed_at
|
|
679
|
-
) VALUES (
|
|
680
|
-
?, ?, ?, ?, ?, ?, ?, ?, json(?), json(?), json(?), ?, ?, ?, json(?), json(?), ?
|
|
681
|
-
)
|
|
594
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, json(?), ?, ?, ?, json(?), json(?), ?)
|
|
682
595
|
`
|
|
683
596
|
)
|
|
684
597
|
.run(
|
|
@@ -691,8 +604,6 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
691
604
|
origin.runId,
|
|
692
605
|
origin.batchOrdinal,
|
|
693
606
|
canonicalJson(commit.origin),
|
|
694
|
-
commit.requestedBy === undefined ? null : canonicalJson(commit.requestedBy),
|
|
695
|
-
canonicalJson(commit.executor),
|
|
696
607
|
commit.ontologyRevision,
|
|
697
608
|
commit.projectionRevision ?? null,
|
|
698
609
|
commit.ownershipHash ?? null,
|
|
@@ -737,3 +648,15 @@ export class SqliteOntologyMaterializationStorage implements OntologyMaterializa
|
|
|
737
648
|
.get(projectId, sourceId, materializationId) as SqliteOntologySourceRow | null
|
|
738
649
|
}
|
|
739
650
|
}
|
|
651
|
+
|
|
652
|
+
function splitByPhase(
|
|
653
|
+
records: readonly MaterializationPlanWorkRecord[]
|
|
654
|
+
): MaterializationPlanWorkRecord[][] {
|
|
655
|
+
const phases: MaterializationPlanWorkRecord[][] = []
|
|
656
|
+
for (const record of records) {
|
|
657
|
+
const current = phases[phases.length - 1]
|
|
658
|
+
if (current && current[0]!.applyPhase === record.applyPhase) current.push(record)
|
|
659
|
+
else phases.push([record])
|
|
660
|
+
}
|
|
661
|
+
return phases
|
|
662
|
+
}
|
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
MaterializationConflictError,
|
|
5
5
|
MaterializationValidationError,
|
|
6
6
|
} from "@sixb/core/internal/materialization"
|
|
7
|
+
import {
|
|
8
|
+
claimedOutboxRows,
|
|
9
|
+
type OutboxEventContext,
|
|
10
|
+
} from "@sixb/core/internal/ontology-storage-provider"
|
|
7
11
|
import type {
|
|
8
12
|
ClaimedOntologyOutboxRow,
|
|
9
13
|
ClaimOntologyOutboxInput,
|
|
@@ -15,15 +19,24 @@ import type {
|
|
|
15
19
|
SummarizeOntologyOutboxInput,
|
|
16
20
|
} from "@sixb/core/storage"
|
|
17
21
|
import { ONTOLOGY_OUTBOX_FAILURE_CODES } from "@sixb/core/storage"
|
|
22
|
+
import { type SqliteExecutionRow, sqliteExecutionRecord } from "../execution-storage"
|
|
18
23
|
import {
|
|
19
24
|
assertNonblank,
|
|
20
25
|
assertPositiveInteger,
|
|
21
26
|
assertTimestamp,
|
|
22
|
-
|
|
27
|
+
claimedOutboxDraft,
|
|
28
|
+
parseJson,
|
|
23
29
|
type SqliteOntologyOutboxRow,
|
|
24
30
|
type SqliteRootOperation,
|
|
25
31
|
} from "./shared"
|
|
26
32
|
|
|
33
|
+
/** A commit's own fields, prefixed, beside every column of the execution it ran under. */
|
|
34
|
+
interface SqliteOutboxContextRow extends SqliteExecutionRow {
|
|
35
|
+
readonly commit_id: string
|
|
36
|
+
readonly commit_committed_at: string
|
|
37
|
+
readonly commit_origin: string
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
28
41
|
constructor(
|
|
29
42
|
private readonly db: Database,
|
|
@@ -74,8 +87,8 @@ export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
|
74
87
|
UPDATE ontology_outbox
|
|
75
88
|
SET attempts = attempts + 1, lease_id = ?, lease_expires_at = ?
|
|
76
89
|
WHERE rowid IN (SELECT CAST(value AS INTEGER) FROM json_each(?))
|
|
77
|
-
RETURNING
|
|
78
|
-
published_at, last_failure, created_at
|
|
90
|
+
RETURNING id, commit_id, commit_ordinal, event, available_at, attempts, lease_id,
|
|
91
|
+
lease_expires_at, published_at, last_failure, created_at
|
|
79
92
|
`
|
|
80
93
|
)
|
|
81
94
|
.all(
|
|
@@ -83,22 +96,31 @@ export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
|
83
96
|
input.leaseExpiresAt,
|
|
84
97
|
JSON.stringify(selected.map((row) => row.rowId))
|
|
85
98
|
) as SqliteOntologyOutboxRow[]
|
|
86
|
-
|
|
87
|
-
|
|
99
|
+
// Each commit and its execution are read once, however many of its events the batch holds.
|
|
100
|
+
const contexts = this.db
|
|
101
|
+
.query(
|
|
102
|
+
`SELECT commits.id AS commit_id, commits.committed_at AS commit_committed_at,
|
|
103
|
+
commits.origin AS commit_origin, executions.*
|
|
104
|
+
FROM json_each(?) AS requested
|
|
105
|
+
CROSS JOIN ontology_commits AS commits
|
|
106
|
+
ON commits.project_id = ? AND commits.id = requested.value
|
|
107
|
+
CROSS JOIN executions
|
|
108
|
+
ON executions.project_id = commits.project_id AND executions.id = commits.execution_id`
|
|
109
|
+
)
|
|
110
|
+
.all(
|
|
111
|
+
JSON.stringify([...new Set(rows.map((row) => row.commit_id))]),
|
|
112
|
+
input.projectId
|
|
113
|
+
) as SqliteOutboxContextRow[]
|
|
114
|
+
const contextOf = new Map(
|
|
115
|
+
contexts.map((row) => [row.commit_id, outboxEventContext(input.projectId, row)])
|
|
116
|
+
)
|
|
117
|
+
const drafts = rows
|
|
118
|
+
.map(claimedOutboxDraft)
|
|
88
119
|
.sort(
|
|
89
120
|
(left, right) =>
|
|
90
|
-
left.createdAt.localeCompare(right.createdAt) ||
|
|
91
|
-
left.envelope.id.localeCompare(right.envelope.id)
|
|
121
|
+
left.createdAt.localeCompare(right.createdAt) || left.id.localeCompare(right.id)
|
|
92
122
|
)
|
|
93
|
-
|
|
94
|
-
if (record.leaseId === null || record.leaseExpiresAt === null) {
|
|
95
|
-
throw new MaterializationConflictError(
|
|
96
|
-
"outbox-lease",
|
|
97
|
-
"Ontology outbox claim returned an unpaired lease."
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
return { ...record, leaseId: record.leaseId, leaseExpiresAt: record.leaseExpiresAt }
|
|
101
|
-
})
|
|
123
|
+
return claimedOutboxRows(drafts, (commitId) => contextOf.get(commitId))
|
|
102
124
|
})
|
|
103
125
|
}
|
|
104
126
|
|
|
@@ -235,6 +257,18 @@ export class SqliteOntologyOutboxStorage implements OntologyOutboxStorage {
|
|
|
235
257
|
}
|
|
236
258
|
}
|
|
237
259
|
|
|
260
|
+
function outboxEventContext(projectId: string, row: SqliteOutboxContextRow): OutboxEventContext {
|
|
261
|
+
return {
|
|
262
|
+
commit: {
|
|
263
|
+
projectId,
|
|
264
|
+
id: row.commit_id,
|
|
265
|
+
committedAt: row.commit_committed_at,
|
|
266
|
+
origin: parseJson<OutboxEventContext["commit"]["origin"]>(row.commit_origin),
|
|
267
|
+
},
|
|
268
|
+
execution: sqliteExecutionRecord(row),
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
238
272
|
function validateLeaseIds(ids: readonly string[]): string[] {
|
|
239
273
|
const seen = new Set<string>()
|
|
240
274
|
for (const id of ids) {
|