@sixb/sqlite 0.1.8 → 0.1.10
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/action-run-storage.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +0 -1
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/ai-cost-storage.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts.map +1 -1
- package/dist/execution-storage.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +495 -244
- package/dist/index.js.map +23 -21
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +2 -1
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +2 -0
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/source-cleanup.d.ts +4 -0
- package/dist/ontology-storage/source-cleanup.d.ts.map +1 -0
- package/dist/ontology-storage/source-roots.d.ts +27 -0
- package/dist/ontology-storage/source-roots.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pipeline-run-storage.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/dist/sync-run-storage.d.ts.map +1 -1
- package/dist/workflow-run-storage.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/action-run-storage.ts +6 -7
- package/src/agents/rows.ts +0 -2
- package/src/agents/runs.ts +3 -7
- package/src/ai-cost-groups.ts +3 -0
- package/src/ai-cost-storage.ts +11 -2
- package/src/ai-usage-storage.ts +1 -2
- package/src/connector-connection-storage.ts +3 -3
- package/src/execution-storage.ts +7 -5
- package/src/index.ts +4 -2
- package/src/migrations/037-execution-requester-groups.sql +24 -0
- package/src/migrations/038-projection-source-roots.sql +44 -0
- package/src/migrations/039-connector-optional-pkce.sql +52 -0
- package/src/migrations.ts +14 -0
- package/src/ontology-storage/materialization-state.ts +56 -91
- package/src/ontology-storage/materializations.ts +7 -1
- package/src/ontology-storage/shared.ts +10 -0
- package/src/ontology-storage/source-cleanup.ts +59 -0
- package/src/ontology-storage/source-roots.ts +230 -0
- package/src/ontology-storage/sources.ts +25 -78
- package/src/pipeline-run-storage.ts +2 -5
- package/src/projection-run-storage.ts +11 -3
- package/src/sync-run-storage.ts +2 -5
- package/src/workflow-run-storage.ts +1 -6
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite"
|
|
2
|
+
import type { ProjectionEntityRef } from "@sixb/core/internal/materialization"
|
|
3
|
+
import {
|
|
4
|
+
MaterializationConflictError,
|
|
5
|
+
MaterializationValidationError,
|
|
6
|
+
projectionEntityKey,
|
|
7
|
+
} from "@sixb/core/internal/materialization"
|
|
8
|
+
import { sourceStageRoots, utf8SortKey } from "@sixb/core/internal/ontology-storage-provider"
|
|
9
|
+
import type { SourceActivationWrite, StageSourceRowsInput } from "@sixb/core/storage"
|
|
10
|
+
import type { SqliteOntologySourceAssertionRow } from "./shared"
|
|
11
|
+
import { canonicalJson, type SqliteOntologySourceRow } from "./shared"
|
|
12
|
+
|
|
13
|
+
export function replacementSourceRows(input: {
|
|
14
|
+
readonly projectId: string
|
|
15
|
+
readonly sourceId: string
|
|
16
|
+
readonly materializationId: string
|
|
17
|
+
readonly incremental: boolean
|
|
18
|
+
}): { readonly sql: string; readonly values: readonly string[] } {
|
|
19
|
+
const roots = input.incremental
|
|
20
|
+
? `ontology_source_roots AS changed
|
|
21
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = changed.project_id
|
|
22
|
+
AND roots.source_id = changed.source_id AND roots.root_sort_key = changed.root_sort_key AND roots.active = 1`
|
|
23
|
+
: "ontology_source_roots AS roots"
|
|
24
|
+
const selected = input.incremental ? "changed" : "roots"
|
|
25
|
+
return {
|
|
26
|
+
sql: `SELECT * FROM ontology_source_rows WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
27
|
+
UNION ALL
|
|
28
|
+
SELECT rows.* FROM ${roots}
|
|
29
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id
|
|
30
|
+
AND rows.source_id = roots.source_id AND rows.materialization_id = roots.materialization_id
|
|
31
|
+
AND rows.root_sort_key = roots.root_sort_key
|
|
32
|
+
WHERE ${selected}.project_id = ? AND ${selected}.source_id = ?
|
|
33
|
+
AND ${input.incremental ? "changed.materialization_id = ?" : "roots.active = 1"}`,
|
|
34
|
+
values: [
|
|
35
|
+
input.projectId,
|
|
36
|
+
input.sourceId,
|
|
37
|
+
input.materializationId,
|
|
38
|
+
input.projectId,
|
|
39
|
+
input.sourceId,
|
|
40
|
+
...(input.incremental ? [input.materializationId] : []),
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function stageSourceRoots(
|
|
46
|
+
db: Database,
|
|
47
|
+
manifest: SqliteOntologySourceRow,
|
|
48
|
+
input: StageSourceRowsInput
|
|
49
|
+
): void {
|
|
50
|
+
const roots = sourceStageRoots(manifest.projection_kind, input)
|
|
51
|
+
if (!manifest.base_materialization_id && roots.some((root) => root.deleted)) {
|
|
52
|
+
throw new MaterializationValidationError("Root deletions require a source delta base.")
|
|
53
|
+
}
|
|
54
|
+
const existing = db.query(`SELECT staging_ordinal, deleted FROM ontology_source_roots
|
|
55
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND root_sort_key = ?`)
|
|
56
|
+
const insert = db.query(`INSERT INTO ontology_source_roots (
|
|
57
|
+
project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root, staging_ordinal, deleted
|
|
58
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING root_sort_key`)
|
|
59
|
+
for (const root of roots) {
|
|
60
|
+
const inserted = insert.get(
|
|
61
|
+
input.projectId,
|
|
62
|
+
input.source.projectionId,
|
|
63
|
+
input.materializationId,
|
|
64
|
+
root.sortKey,
|
|
65
|
+
root.root.kind,
|
|
66
|
+
root.rootKey,
|
|
67
|
+
canonicalJson(root.root),
|
|
68
|
+
root.stagingOrdinal,
|
|
69
|
+
Number(root.deleted)
|
|
70
|
+
)
|
|
71
|
+
if (inserted) continue
|
|
72
|
+
const previous = existing.get(
|
|
73
|
+
input.projectId,
|
|
74
|
+
input.source.projectionId,
|
|
75
|
+
input.materializationId,
|
|
76
|
+
root.sortKey
|
|
77
|
+
) as { staging_ordinal: number; deleted: number } | null
|
|
78
|
+
if (
|
|
79
|
+
!previous ||
|
|
80
|
+
previous.staging_ordinal !== root.stagingOrdinal ||
|
|
81
|
+
Boolean(previous.deleted) !== root.deleted
|
|
82
|
+
) {
|
|
83
|
+
throw new MaterializationValidationError(
|
|
84
|
+
"Source materialization repeats root or stream ordinal with different content."
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function activateSourceRoots(
|
|
91
|
+
db: Database,
|
|
92
|
+
projectId: string,
|
|
93
|
+
candidate: SqliteOntologySourceRow,
|
|
94
|
+
activation: SourceActivationWrite
|
|
95
|
+
): void {
|
|
96
|
+
if (
|
|
97
|
+
candidate.base_materialization_id !== null &&
|
|
98
|
+
(candidate.base_materialization_id !== activation.expected.activeMaterializationId ||
|
|
99
|
+
candidate.base_commit_id !== activation.expected.lastCommitId)
|
|
100
|
+
)
|
|
101
|
+
throw new MaterializationConflictError(
|
|
102
|
+
"projection-fence",
|
|
103
|
+
"Source delta base changed before activation."
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if (candidate.base_materialization_id !== null && candidate.root_count === 0) return
|
|
107
|
+
const scope = [projectId, activation.source.projectionId, activation.materializationId] as const
|
|
108
|
+
db.query(`UPDATE ontology_source_roots AS roots SET active = 0, retired_at = ?
|
|
109
|
+
WHERE project_id = ? AND source_id = ? AND active = 1
|
|
110
|
+
${
|
|
111
|
+
candidate.base_materialization_id === null
|
|
112
|
+
? ""
|
|
113
|
+
: `AND root_sort_key IN (
|
|
114
|
+
SELECT root_sort_key FROM ontology_source_roots
|
|
115
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?)`
|
|
116
|
+
}`).run(
|
|
117
|
+
activation.updatedAt,
|
|
118
|
+
projectId,
|
|
119
|
+
activation.source.projectionId,
|
|
120
|
+
...(candidate.base_materialization_id === null ? [] : [...scope])
|
|
121
|
+
)
|
|
122
|
+
db.query(`UPDATE ontology_source_roots SET active = CASE WHEN deleted = 1 THEN 0 ELSE 1 END,
|
|
123
|
+
retired_at = CASE WHEN deleted = 1 THEN ? ELSE NULL END
|
|
124
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?`).run(
|
|
125
|
+
activation.updatedAt,
|
|
126
|
+
...scope
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function assertSourceRootCoverage(db: Database, manifest: SqliteOntologySourceRow): void {
|
|
131
|
+
const invalid = db
|
|
132
|
+
.query(`SELECT 1 FROM ontology_source_roots AS roots
|
|
133
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
134
|
+
AND roots.deleted = EXISTS (
|
|
135
|
+
SELECT 1 FROM ontology_source_rows AS rows
|
|
136
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
137
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
138
|
+
) LIMIT 1`)
|
|
139
|
+
.get(manifest.project_id, manifest.source_id, manifest.materialization_id)
|
|
140
|
+
if (invalid)
|
|
141
|
+
throw new MaterializationValidationError(
|
|
142
|
+
"Source roots must have complete assertions or an explicit deletion, never both."
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function activeSourceRows(
|
|
147
|
+
db: Database,
|
|
148
|
+
projectId: string,
|
|
149
|
+
refs: readonly ProjectionEntityRef[]
|
|
150
|
+
): SqliteOntologySourceAssertionRow[] {
|
|
151
|
+
const keys = refs.flatMap((entity) => {
|
|
152
|
+
const entitySortKey = utf8SortKey(projectionEntityKey(entity))
|
|
153
|
+
const roots =
|
|
154
|
+
entity.kind === "object"
|
|
155
|
+
? [entitySortKey]
|
|
156
|
+
: [
|
|
157
|
+
entitySortKey,
|
|
158
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source })),
|
|
159
|
+
]
|
|
160
|
+
return roots.map((rootSortKey) => ({ rootSortKey, entitySortKey }))
|
|
161
|
+
})
|
|
162
|
+
return db
|
|
163
|
+
.query(`WITH requested AS (
|
|
164
|
+
SELECT DISTINCT json_extract(value, '$.rootSortKey') AS root_sort_key,
|
|
165
|
+
json_extract(value, '$.entitySortKey') AS entity_sort_key FROM json_each(?)
|
|
166
|
+
) SELECT rows.* FROM requested
|
|
167
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
168
|
+
AND roots.active = 1 AND roots.root_sort_key = requested.root_sort_key
|
|
169
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
170
|
+
AND rows.materialization_id = roots.materialization_id AND rows.entity_sort_key = requested.entity_sort_key
|
|
171
|
+
AND rows.root_sort_key = roots.root_sort_key`)
|
|
172
|
+
.all(canonicalJson(keys), projectId) as SqliteOntologySourceAssertionRow[]
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function replacementAssertionRows(
|
|
176
|
+
db: Database,
|
|
177
|
+
input: {
|
|
178
|
+
projectId: string
|
|
179
|
+
sourceId: string
|
|
180
|
+
materializationId: string
|
|
181
|
+
incremental: boolean
|
|
182
|
+
includePrevious: boolean
|
|
183
|
+
refs: readonly ProjectionEntityRef[]
|
|
184
|
+
}
|
|
185
|
+
): SqliteOntologySourceAssertionRow[] {
|
|
186
|
+
const keys = input.refs.map((ref) => ({
|
|
187
|
+
entityKind: ref.kind,
|
|
188
|
+
entityKey: projectionEntityKey(ref),
|
|
189
|
+
rootKeys:
|
|
190
|
+
ref.kind === "object"
|
|
191
|
+
? [utf8SortKey(projectionEntityKey(ref))]
|
|
192
|
+
: [
|
|
193
|
+
utf8SortKey(projectionEntityKey(ref)),
|
|
194
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source })),
|
|
195
|
+
],
|
|
196
|
+
}))
|
|
197
|
+
const previous = !input.includePrevious
|
|
198
|
+
? ""
|
|
199
|
+
: `UNION ALL
|
|
200
|
+
SELECT rows.* FROM requested_entities AS requested
|
|
201
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ? AND roots.source_id = ?
|
|
202
|
+
AND roots.active = 1 AND roots.root_sort_key IN (SELECT value FROM json_each(requested.root_keys))
|
|
203
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
204
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
205
|
+
AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
206
|
+
${
|
|
207
|
+
!input.incremental
|
|
208
|
+
? ""
|
|
209
|
+
: `WHERE EXISTS (SELECT 1 FROM ontology_source_roots AS changed
|
|
210
|
+
WHERE changed.project_id = roots.project_id AND changed.source_id = roots.source_id
|
|
211
|
+
AND changed.materialization_id = ? AND changed.root_sort_key = roots.root_sort_key)`
|
|
212
|
+
}`
|
|
213
|
+
return db
|
|
214
|
+
.query(`WITH requested_entities AS (
|
|
215
|
+
SELECT DISTINCT json_extract(value, '$.entityKind') AS entity_kind,
|
|
216
|
+
json_extract(value, '$.entityKey') AS entity_key, json_extract(value, '$.rootKeys') AS root_keys FROM json_each(?)
|
|
217
|
+
) SELECT rows.* FROM requested_entities AS requested
|
|
218
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = ? AND rows.source_id = ?
|
|
219
|
+
AND rows.materialization_id = ? AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
220
|
+
${previous}`)
|
|
221
|
+
.all(
|
|
222
|
+
canonicalJson(keys),
|
|
223
|
+
input.projectId,
|
|
224
|
+
input.sourceId,
|
|
225
|
+
input.materializationId,
|
|
226
|
+
...(input.includePrevious
|
|
227
|
+
? [input.projectId, input.sourceId, ...(input.incremental ? [input.materializationId] : [])]
|
|
228
|
+
: [])
|
|
229
|
+
) as SqliteOntologySourceAssertionRow[]
|
|
230
|
+
}
|
|
@@ -47,6 +47,8 @@ import {
|
|
|
47
47
|
sourceAssertion,
|
|
48
48
|
sourceRecord,
|
|
49
49
|
} from "./shared"
|
|
50
|
+
import { cleanupSourceVersions } from "./source-cleanup"
|
|
51
|
+
import { assertSourceRootCoverage, stageSourceRoots } from "./source-roots"
|
|
50
52
|
|
|
51
53
|
export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
52
54
|
constructor(
|
|
@@ -98,9 +100,9 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
98
100
|
dataset_id, dataset_version_id, dataset_version_created_at,
|
|
99
101
|
projection_revision, ownership_hash, ontology_revision,
|
|
100
102
|
root_count, assertion_count, created_at, ready_at, activated_at,
|
|
101
|
-
terminal_at, last_commit_id, updated_at
|
|
103
|
+
terminal_at, last_commit_id, updated_at, base_materialization_id, base_commit_id
|
|
102
104
|
) VALUES (?, ?, ?, ?, ?, 'replacement', 'staging', ?, ?, ?, ?, ?, ?, ?,
|
|
103
|
-
NULL, NULL, ?, NULL, NULL, NULL, NULL, ?)
|
|
105
|
+
NULL, NULL, ?, NULL, NULL, NULL, NULL, ?, ?, ?)
|
|
104
106
|
`
|
|
105
107
|
)
|
|
106
108
|
.run(
|
|
@@ -117,7 +119,9 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
117
119
|
input.ownershipHash,
|
|
118
120
|
input.ontologyRevision,
|
|
119
121
|
input.createdAt,
|
|
120
|
-
input.createdAt
|
|
122
|
+
input.createdAt,
|
|
123
|
+
input.base?.materializationId ?? null,
|
|
124
|
+
input.base?.lastCommitId ?? null
|
|
121
125
|
)
|
|
122
126
|
return sourceRecord(
|
|
123
127
|
this.requireManifest(input.projectId, input.source.projectionId, input.materializationId)
|
|
@@ -144,6 +148,7 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
144
148
|
const rows = sourceStageRows(manifest.projection_kind, input.rows)
|
|
145
149
|
const existing = this.findStageRows(input, rows)
|
|
146
150
|
const { pending, unchanged } = reconcileSourceStageRows(rows, existing)
|
|
151
|
+
stageSourceRoots(this.db, manifest, input)
|
|
147
152
|
this.insertStageRows(input, pending)
|
|
148
153
|
return { inserted: pending.length, unchanged }
|
|
149
154
|
})
|
|
@@ -249,78 +254,7 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
249
254
|
assertNonblank(input.projectId, "Terminal source cleanup project id")
|
|
250
255
|
assertTimestamp(input.terminalBefore, "Terminal source cleanup cutoff", true)
|
|
251
256
|
assertPositiveInteger(input.limit, "Terminal source cleanup limit")
|
|
252
|
-
|
|
253
|
-
.query(
|
|
254
|
-
`
|
|
255
|
-
SELECT * FROM ontology_sources
|
|
256
|
-
WHERE project_id = ? AND status IN ('superseded', 'abandoned')
|
|
257
|
-
AND terminal_at < ?
|
|
258
|
-
ORDER BY terminal_at, source_id, materialization_id
|
|
259
|
-
LIMIT ?
|
|
260
|
-
`
|
|
261
|
-
)
|
|
262
|
-
.all(input.projectId, input.terminalBefore, input.limit) as SqliteOntologySourceRow[]
|
|
263
|
-
let remaining = input.limit
|
|
264
|
-
let rowsDeleted = 0
|
|
265
|
-
let materializationsDeleted = 0
|
|
266
|
-
for (const manifest of manifests) {
|
|
267
|
-
if (remaining === 0) break
|
|
268
|
-
const deleted = this.db
|
|
269
|
-
.query(
|
|
270
|
-
`
|
|
271
|
-
DELETE FROM ontology_source_rows
|
|
272
|
-
WHERE rowid IN (
|
|
273
|
-
SELECT rows.rowid
|
|
274
|
-
FROM ontology_source_rows AS rows
|
|
275
|
-
JOIN ontology_sources AS sources
|
|
276
|
-
ON sources.project_id = rows.project_id
|
|
277
|
-
AND sources.source_id = rows.source_id
|
|
278
|
-
AND sources.materialization_id = rows.materialization_id
|
|
279
|
-
WHERE rows.project_id = ? AND rows.source_id = ? AND rows.materialization_id = ?
|
|
280
|
-
AND sources.status IN ('superseded', 'abandoned')
|
|
281
|
-
AND sources.terminal_at = ? AND sources.terminal_at < ?
|
|
282
|
-
ORDER BY rows.entity_sort_key
|
|
283
|
-
LIMIT ?
|
|
284
|
-
)
|
|
285
|
-
`
|
|
286
|
-
)
|
|
287
|
-
.run(
|
|
288
|
-
manifest.project_id,
|
|
289
|
-
manifest.source_id,
|
|
290
|
-
manifest.materialization_id,
|
|
291
|
-
manifest.terminal_at,
|
|
292
|
-
input.terminalBefore,
|
|
293
|
-
remaining
|
|
294
|
-
).changes
|
|
295
|
-
rowsDeleted += deleted
|
|
296
|
-
remaining -= deleted
|
|
297
|
-
if (remaining === 0) break
|
|
298
|
-
const removed = this.db
|
|
299
|
-
.query(
|
|
300
|
-
`
|
|
301
|
-
DELETE FROM ontology_sources
|
|
302
|
-
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
303
|
-
AND status IN ('superseded', 'abandoned')
|
|
304
|
-
AND terminal_at = ? AND terminal_at < ?
|
|
305
|
-
AND NOT EXISTS (
|
|
306
|
-
SELECT 1 FROM ontology_source_rows AS rows
|
|
307
|
-
WHERE rows.project_id = ontology_sources.project_id
|
|
308
|
-
AND rows.source_id = ontology_sources.source_id
|
|
309
|
-
AND rows.materialization_id = ontology_sources.materialization_id
|
|
310
|
-
)
|
|
311
|
-
`
|
|
312
|
-
)
|
|
313
|
-
.run(
|
|
314
|
-
manifest.project_id,
|
|
315
|
-
manifest.source_id,
|
|
316
|
-
manifest.materialization_id,
|
|
317
|
-
manifest.terminal_at,
|
|
318
|
-
input.terminalBefore
|
|
319
|
-
).changes
|
|
320
|
-
materializationsDeleted += removed
|
|
321
|
-
remaining -= removed
|
|
322
|
-
}
|
|
323
|
-
return { rowsDeleted, materializationsDeleted }
|
|
257
|
+
return cleanupSourceVersions(this.db, input)
|
|
324
258
|
})
|
|
325
259
|
}
|
|
326
260
|
|
|
@@ -433,6 +367,10 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
433
367
|
`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`
|
|
434
368
|
)
|
|
435
369
|
}
|
|
370
|
+
this.db
|
|
371
|
+
.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
372
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND active = 0`)
|
|
373
|
+
.run(abandonedAt, manifest.project_id, manifest.source_id, manifest.materialization_id)
|
|
436
374
|
return sourceRecord(
|
|
437
375
|
this.requireManifest(manifest.project_id, manifest.source_id, manifest.materialization_id)
|
|
438
376
|
)
|
|
@@ -446,14 +384,22 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
446
384
|
const counts = this.db
|
|
447
385
|
.query(
|
|
448
386
|
`
|
|
449
|
-
SELECT
|
|
387
|
+
SELECT (SELECT COUNT(*) FROM ontology_source_rows
|
|
388
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?) AS assertions, COUNT(*) AS roots,
|
|
450
389
|
COUNT(DISTINCT staging_ordinal) AS ordinals,
|
|
451
390
|
MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
|
|
452
|
-
FROM
|
|
391
|
+
FROM ontology_source_roots
|
|
453
392
|
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
454
393
|
`
|
|
455
394
|
)
|
|
456
|
-
.get(
|
|
395
|
+
.get(
|
|
396
|
+
manifest.project_id,
|
|
397
|
+
manifest.source_id,
|
|
398
|
+
manifest.materialization_id,
|
|
399
|
+
manifest.project_id,
|
|
400
|
+
manifest.source_id,
|
|
401
|
+
manifest.materialization_id
|
|
402
|
+
) as {
|
|
457
403
|
assertions: number
|
|
458
404
|
roots: number
|
|
459
405
|
ordinals: number
|
|
@@ -470,6 +416,7 @@ export class SqliteOntologySourceStorage implements OntologySourceStorage {
|
|
|
470
416
|
"Source ready counts do not match the staged roots and assertions."
|
|
471
417
|
)
|
|
472
418
|
}
|
|
419
|
+
assertSourceRootCoverage(this.db, manifest)
|
|
473
420
|
const invalid = this.db
|
|
474
421
|
.query(
|
|
475
422
|
manifest.projection_kind === "link"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
2
|
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
3
3
|
import { assertPipelineRunExecution } from "@sixb/core/internal/pipeline-run-storage-provider"
|
|
4
|
+
import { canRequeuePipelineRunAfterEnqueueFailure } from "@sixb/core/internal/storage"
|
|
4
5
|
import type { DatasetVersionRef } from "@sixb/core/lake-storage"
|
|
5
6
|
import type {
|
|
6
7
|
ExecutionStorage,
|
|
@@ -19,11 +20,7 @@ import type {
|
|
|
19
20
|
StartPipelineRunInput,
|
|
20
21
|
StartPipelineStepRunInput,
|
|
21
22
|
} from "@sixb/core/storage"
|
|
22
|
-
import {
|
|
23
|
-
canRequeuePipelineRunAfterEnqueueFailure,
|
|
24
|
-
PIPELINE_RUN_FAILURE_CODES,
|
|
25
|
-
PipelineRunError,
|
|
26
|
-
} from "@sixb/core/storage"
|
|
23
|
+
import { PIPELINE_RUN_FAILURE_CODES, PipelineRunError } from "@sixb/core/storage"
|
|
27
24
|
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
28
25
|
import { installFreshSqliteSchema } from "./migrations"
|
|
29
26
|
import {
|
|
@@ -114,7 +114,7 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
114
114
|
input_exhausted = ?, missing_target_object_type_id = NULL,
|
|
115
115
|
missing_target_object_id = NULL, missing_target_batch_ordinal = NULL,
|
|
116
116
|
missing_target_first_seen_at = NULL, source_rows_read = 0,
|
|
117
|
-
source_rows_skipped = 0, error = NULL
|
|
117
|
+
source_rows_skipped = 0, source_changes_read = NULL, error = NULL
|
|
118
118
|
WHERE project_id = ? AND id = ? AND status = 'failed'
|
|
119
119
|
AND json_extract(error, '$.code') = 'queue.enqueue_failed'
|
|
120
120
|
`
|
|
@@ -257,6 +257,7 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
257
257
|
execution_token = NULL,
|
|
258
258
|
source_rows_read = ?,
|
|
259
259
|
source_rows_skipped = ?,
|
|
260
|
+
source_changes_read = ?,
|
|
260
261
|
input_exhausted = ?,
|
|
261
262
|
error = ?
|
|
262
263
|
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
@@ -267,6 +268,7 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
267
268
|
plan.finishedAt.toISOString(),
|
|
268
269
|
plan.progress.sourceRowsRead,
|
|
269
270
|
plan.progress.sourceRowsSkipped,
|
|
271
|
+
plan.progress.sourceChangesRead ?? null,
|
|
270
272
|
plan.inputExhausted ? 1 : existingRow.input_exhausted,
|
|
271
273
|
plan.error === undefined
|
|
272
274
|
? null
|
|
@@ -531,9 +533,10 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
531
533
|
progress: ProjectionRunProgress,
|
|
532
534
|
executionToken: string
|
|
533
535
|
): number {
|
|
534
|
-
const args: (string | number)[] = [
|
|
536
|
+
const args: (string | number | null)[] = [
|
|
535
537
|
progress.sourceRowsRead,
|
|
536
538
|
progress.sourceRowsSkipped,
|
|
539
|
+
progress.sourceChangesRead ?? null,
|
|
537
540
|
existing.project_id,
|
|
538
541
|
existing.id,
|
|
539
542
|
executionToken,
|
|
@@ -544,7 +547,8 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
544
547
|
UPDATE projection_runs
|
|
545
548
|
SET
|
|
546
549
|
source_rows_read = ?,
|
|
547
|
-
source_rows_skipped =
|
|
550
|
+
source_rows_skipped = ?,
|
|
551
|
+
source_changes_read = ?
|
|
548
552
|
WHERE project_id = ? AND id = ? AND status = 'running'
|
|
549
553
|
AND execution_token = ?
|
|
550
554
|
`
|
|
@@ -631,6 +635,9 @@ function restoreProjectionRunRow(row: DatabaseRow): StoredProjectionRunRecord {
|
|
|
631
635
|
progress: {
|
|
632
636
|
sourceRowsRead: databaseSafeInteger(row.source_rows_read, "sourceRowsRead"),
|
|
633
637
|
sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped"),
|
|
638
|
+
...(row.source_changes_read == null
|
|
639
|
+
? {}
|
|
640
|
+
: { sourceChangesRead: databaseSafeInteger(row.source_changes_read, "sourceChangesRead") }),
|
|
634
641
|
},
|
|
635
642
|
error: row.error ?? undefined,
|
|
636
643
|
}
|
|
@@ -694,6 +701,7 @@ interface DatabaseRow {
|
|
|
694
701
|
missing_target_first_seen_at: string | null
|
|
695
702
|
source_rows_read: number
|
|
696
703
|
source_rows_skipped: number
|
|
704
|
+
source_changes_read: number | null
|
|
697
705
|
error: string | null
|
|
698
706
|
}
|
|
699
707
|
|
package/src/sync-run-storage.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
2
|
import type { JsonValue } from "@sixb/core"
|
|
3
3
|
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
4
|
+
import { canRequeueSyncRunAfterEnqueueFailure } from "@sixb/core/internal/storage"
|
|
4
5
|
import { assertSyncRunExecution } from "@sixb/core/internal/sync-run-storage-provider"
|
|
5
6
|
import type {
|
|
6
7
|
ExecutionStorage,
|
|
@@ -14,11 +15,7 @@ import type {
|
|
|
14
15
|
SyncRunRecord,
|
|
15
16
|
SyncRunStorage,
|
|
16
17
|
} from "@sixb/core/storage"
|
|
17
|
-
import {
|
|
18
|
-
canRequeueSyncRunAfterEnqueueFailure,
|
|
19
|
-
SYNC_RUN_FAILURE_CODES,
|
|
20
|
-
SyncRunError,
|
|
21
|
-
} from "@sixb/core/storage"
|
|
18
|
+
import { SYNC_RUN_FAILURE_CODES, SyncRunError } from "@sixb/core/storage"
|
|
22
19
|
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
23
20
|
import { installFreshSqliteSchema } from "./migrations"
|
|
24
21
|
import {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
|
-
import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth"
|
|
3
2
|
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
4
3
|
import {
|
|
5
4
|
assertWorkflowAgentNodeRunExecution,
|
|
@@ -112,9 +111,8 @@ export class SqliteWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
112
111
|
input,
|
|
113
112
|
queued_at,
|
|
114
113
|
started_at,
|
|
115
|
-
requester_group_ids,
|
|
116
114
|
attempt
|
|
117
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
115
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
118
116
|
`
|
|
119
117
|
)
|
|
120
118
|
.run(
|
|
@@ -126,7 +124,6 @@ export class SqliteWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
126
124
|
serializeRecord(input.input),
|
|
127
125
|
queuedAt.toISOString(),
|
|
128
126
|
queuedAt.toISOString(),
|
|
129
|
-
JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds)),
|
|
130
127
|
0
|
|
131
128
|
)
|
|
132
129
|
} catch (error) {
|
|
@@ -983,7 +980,6 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
|
|
|
983
980
|
startedAt: new Date(row.started_at),
|
|
984
981
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
985
982
|
error: row.error === null ? undefined : parseSixbFailure(row.error, WORKFLOW_RUN_FAILURE_CODES),
|
|
986
|
-
requesterGroupIds: JSON.parse(row.requester_group_ids) as string[],
|
|
987
983
|
attempt: row.attempt,
|
|
988
984
|
...(row.execution_token && row.execution_queue_lease_expires_at
|
|
989
985
|
? {
|
|
@@ -1046,7 +1042,6 @@ interface WorkflowRunDatabaseRow {
|
|
|
1046
1042
|
started_at: string
|
|
1047
1043
|
finished_at: string | null
|
|
1048
1044
|
error: string | null
|
|
1049
|
-
requester_group_ids: string
|
|
1050
1045
|
attempt: number
|
|
1051
1046
|
execution_token: string | null
|
|
1052
1047
|
execution_queue_lease_expires_at: string | null
|