@sixb/sqlite 0.1.0 → 0.1.2
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/README.md +18 -13
- package/dist/action-run-storage.d.ts +5 -2
- package/dist/action-run-storage.d.ts.map +1 -1
- package/dist/agents/agent-storage.d.ts +4 -2
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +3 -10
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +3 -2
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/ai-usage-storage.d.ts +22 -0
- package/dist/ai-usage-storage.d.ts.map +1 -0
- package/dist/auth-storage/service-accounts.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +1 -10
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/connector-connection-storage.d.ts +9 -0
- package/dist/connector-connection-storage.d.ts.map +1 -0
- package/dist/execution-storage.d.ts +14 -0
- package/dist/execution-storage.d.ts.map +1 -0
- package/dist/index.d.ts +13 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4114 -1149
- package/dist/index.js.map +30 -27
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts +0 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +50 -8
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +4 -5
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts +0 -2
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +3 -3
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pipeline-run-storage.d.ts +7 -2
- package/dist/pipeline-run-storage.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts +9 -2
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/dist/run-list-query.d.ts +1 -1
- package/dist/run-list-query.d.ts.map +1 -1
- package/dist/sync-run-storage.d.ts +7 -2
- package/dist/sync-run-storage.d.ts.map +1 -1
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/transactions.d.ts +3 -0
- package/dist/transactions.d.ts.map +1 -1
- package/dist/webhook-run-storage.d.ts +9 -2
- package/dist/webhook-run-storage.d.ts.map +1 -1
- package/dist/workflow-run-storage.d.ts +7 -3
- package/dist/workflow-run-storage.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/action-run-storage.ts +51 -74
- package/src/agents/agent-storage.ts +5 -3
- package/src/agents/rows.ts +8 -40
- package/src/agents/runs.ts +27 -21
- package/src/ai-usage-storage.ts +324 -0
- package/src/auth-storage/service-accounts.ts +14 -21
- package/src/auth-storage/shared.ts +0 -22
- package/src/connector-connection-storage.ts +784 -0
- package/src/execution-storage.ts +158 -0
- package/src/index.ts +63 -22
- package/src/latest-run-query.ts +7 -1
- package/src/migrations/002-workflow-run-output.sql +27 -0
- package/src/migrations/003-merge-sync-runs.sql +71 -0
- package/src/migrations/004-executions.sql +142 -0
- package/src/migrations/005-workflow-executions.sql +7 -0
- package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
- package/src/migrations/007-split-overrides.sql +154 -0
- package/src/migrations/008-action-executions.sql +4 -0
- package/src/migrations/009-agent-executions.sql +115 -0
- package/src/migrations/010-ai-usage-accounting-foundation.sql +74 -0
- package/src/migrations/011-sync-failure-record.sql +22 -0
- package/src/migrations/012-pipeline-failure-record.sql +45 -0
- package/src/migrations/013-workflow-failure-record.sql +45 -0
- package/src/migrations/014-agent-failure-record.sql +59 -0
- package/src/migrations/015-projection-failure-record.sql +20 -0
- package/src/migrations/016-webhook-run-failure-record.sql +19 -0
- package/src/migrations/017-action-failure-record.sql +65 -0
- package/src/migrations/018-ontology-outbox-failure-record.sql +80 -0
- package/src/migrations/019-webhook-delivery-failure-record.sql +62 -0
- package/src/migrations/020-drop-run-usage-projections.sql +7 -0
- package/src/migrations/021-sync-pipeline-executions.sql +101 -0
- package/src/migrations/022-projection-executions.sql +244 -0
- package/src/migrations/023-webhook-executions.sql +51 -0
- package/src/migrations/024-ontology-commit-executions.sql +58 -0
- package/src/migrations/025-connector-connections.sql +257 -0
- package/src/migrations.ts +126 -5
- package/src/ontology-storage/materialization-session.ts +317 -143
- package/src/ontology-storage/materialization-state.ts +398 -150
- package/src/ontology-storage/materialization-writer.ts +142 -67
- package/src/ontology-storage/materializations.ts +67 -141
- package/src/ontology-storage/outbox.ts +10 -3
- package/src/ontology-storage/shared.ts +10 -4
- package/src/ontology-storage/sources.ts +4 -1
- package/src/pipeline-run-storage.ts +128 -43
- package/src/projection-run-storage.ts +209 -97
- package/src/run-list-query.ts +9 -4
- package/src/sync-run-storage.ts +120 -34
- package/src/testing.ts +20 -0
- package/src/transactions.ts +10 -3
- package/src/webhook-run-storage.ts +118 -74
- package/src/workflow-run-storage.ts +124 -133
- package/dist/validation.d.ts +0 -5
- package/dist/validation.d.ts.map +0 -1
- package/dist/webhook-delivery-storage.d.ts +0 -27
- package/dist/webhook-delivery-storage.d.ts.map +0 -1
- package/src/validation.ts +0 -30
- package/src/webhook-delivery-storage.ts +0 -272
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { Database } from "bun:sqlite"
|
|
2
2
|
import { randomUUID } from "node:crypto"
|
|
3
|
+
import { serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
3
4
|
import {
|
|
4
5
|
advanceProjectionTelemetry,
|
|
5
6
|
assertGenericProgressDoesNotAdvanceTelemetry,
|
|
6
7
|
assertProjectionMissingTarget,
|
|
7
|
-
|
|
8
|
+
assertProjectionRunAttempt,
|
|
9
|
+
assertProjectionRunDurableExecution,
|
|
8
10
|
assertProjectionRunListWindow,
|
|
9
11
|
assertProjectionRunNonEmpty,
|
|
12
|
+
assertProjectionRunQueueInput,
|
|
10
13
|
assertProjectionRunRunning,
|
|
11
14
|
assertProjectionRunStartInput,
|
|
15
|
+
canRequeueProjectionRunAfterEnqueueFailure,
|
|
12
16
|
createProjectionRunClaim,
|
|
17
|
+
createProjectionRunRecord,
|
|
18
|
+
failProjectionRunEnqueue,
|
|
13
19
|
immutableDatasetVersionConflict,
|
|
14
20
|
mergeProjectionRunProgress,
|
|
15
21
|
type PersistedProjectionRunRecord,
|
|
@@ -24,6 +30,8 @@ import {
|
|
|
24
30
|
} from "@sixb/core/internal/projection-run-storage-provider"
|
|
25
31
|
import type {
|
|
26
32
|
AdvanceProjectionTelemetryCheckpointInput,
|
|
33
|
+
ExecutionStorage,
|
|
34
|
+
FailProjectionRunEnqueueInput,
|
|
27
35
|
FinishProjectionRunInput,
|
|
28
36
|
ListLatestProjectionRunsInput,
|
|
29
37
|
ListLatestProjectionRunsResult,
|
|
@@ -36,12 +44,13 @@ import type {
|
|
|
36
44
|
ProjectionRunRecord,
|
|
37
45
|
ProjectionRunStatus,
|
|
38
46
|
ProjectionRunStorage,
|
|
47
|
+
QueueProjectionRunInput,
|
|
39
48
|
RecordProjectionMissingTargetInput,
|
|
40
49
|
StartOrReclaimProjectionRunInput,
|
|
41
50
|
TelemetryProjectionRunRecord,
|
|
42
51
|
UpdateProjectionRunInput,
|
|
43
52
|
} from "@sixb/core/storage"
|
|
44
|
-
import { ProjectionRunError } from "@sixb/core/storage"
|
|
53
|
+
import { PROJECTION_RUN_FAILURE_CODES, ProjectionRunError } from "@sixb/core/storage"
|
|
45
54
|
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
46
55
|
import { installFreshSqliteSchema } from "./migrations"
|
|
47
56
|
import {
|
|
@@ -52,6 +61,8 @@ import {
|
|
|
52
61
|
} from "./transactions"
|
|
53
62
|
|
|
54
63
|
export interface SqliteProjectionRunStorageOptions {
|
|
64
|
+
/** Execution lookup sharing the same provider transaction. */
|
|
65
|
+
executions: ExecutionStorage
|
|
55
66
|
/** Path to SQLite database file. Defaults to ':memory:' for in-memory database. */
|
|
56
67
|
path?: string
|
|
57
68
|
/** Internal shared connection used by bundled SqliteStorage. */
|
|
@@ -61,16 +72,76 @@ export interface SqliteProjectionRunStorageOptions {
|
|
|
61
72
|
export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
62
73
|
private readonly connection: SqliteStoreConnection
|
|
63
74
|
private readonly db: Database
|
|
75
|
+
private readonly executions: ExecutionStorage
|
|
64
76
|
|
|
65
|
-
constructor(options: SqliteProjectionRunStorageOptions
|
|
77
|
+
constructor(options: SqliteProjectionRunStorageOptions) {
|
|
66
78
|
this.connection = openSqliteStoreConnection(options)
|
|
67
79
|
this.db = this.connection.db
|
|
80
|
+
this.executions = options.executions
|
|
68
81
|
|
|
69
82
|
if (this.connection.installFreshSchema) {
|
|
70
83
|
installFreshSqliteSchema(this.db)
|
|
71
84
|
}
|
|
72
85
|
}
|
|
73
86
|
|
|
87
|
+
async queue(input: QueueProjectionRunInput): Promise<ProjectionRunRecord> {
|
|
88
|
+
assertProjectionRunQueueInput(input)
|
|
89
|
+
await assertProjectionRunDurableExecution({
|
|
90
|
+
executions: this.executions,
|
|
91
|
+
projectId: input.projectId,
|
|
92
|
+
executionId: input.executionId,
|
|
93
|
+
runId: input.id,
|
|
94
|
+
projectionId: input.identity.projectionId,
|
|
95
|
+
datasetId: input.identity.datasetVersion.datasetId,
|
|
96
|
+
datasetVersionId: input.identity.datasetVersion.versionId,
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
return runImmediateTransaction(this.db, () => {
|
|
100
|
+
this.assertDatasetVersionIsImmutable(input)
|
|
101
|
+
const existing = this.findRow(input.projectId, input.id)
|
|
102
|
+
if (existing) {
|
|
103
|
+
const restored = restoreProjectionRunRow(existing)
|
|
104
|
+
if (!canRequeueProjectionRunAfterEnqueueFailure(restored, input)) {
|
|
105
|
+
throw duplicateProjectionRun(input)
|
|
106
|
+
}
|
|
107
|
+
const queuedAt = input.queuedAt ?? new Date()
|
|
108
|
+
const result = this.db
|
|
109
|
+
.query(
|
|
110
|
+
`
|
|
111
|
+
UPDATE projection_runs
|
|
112
|
+
SET status = 'queued', queued_at = ?, started_at = NULL, finished_at = NULL,
|
|
113
|
+
attempt = 0, execution_token = NULL, next_batch_ordinal = ?, next_row_offset = ?,
|
|
114
|
+
input_exhausted = ?, missing_target_object_type_id = NULL,
|
|
115
|
+
missing_target_object_id = NULL, missing_target_batch_ordinal = NULL,
|
|
116
|
+
missing_target_first_seen_at = NULL, source_rows_read = 0,
|
|
117
|
+
source_rows_skipped = 0, error = NULL
|
|
118
|
+
WHERE project_id = ? AND id = ? AND status = 'failed'
|
|
119
|
+
AND json_extract(error, '$.code') = 'queue.enqueue_failed'
|
|
120
|
+
`
|
|
121
|
+
)
|
|
122
|
+
.run(
|
|
123
|
+
queuedAt.toISOString(),
|
|
124
|
+
input.fixedBatchSize === undefined ? null : 0,
|
|
125
|
+
input.fixedBatchSize === undefined ? null : 0,
|
|
126
|
+
input.fixedBatchSize === undefined ? null : 0,
|
|
127
|
+
input.projectId,
|
|
128
|
+
input.id
|
|
129
|
+
)
|
|
130
|
+
if (result.changes !== 1) throw duplicateProjectionRun(input)
|
|
131
|
+
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const record = createProjectionRunRecord(input)
|
|
135
|
+
try {
|
|
136
|
+
this.insertQueued(record)
|
|
137
|
+
} catch (error) {
|
|
138
|
+
if (isUniqueConstraintError(error)) throw duplicateProjectionRun(input)
|
|
139
|
+
throw error
|
|
140
|
+
}
|
|
141
|
+
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id))
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
74
145
|
async startOrReclaim(input: StartOrReclaimProjectionRunInput): Promise<ProjectionRunClaim> {
|
|
75
146
|
return runImmediateTransaction(this.db, () => {
|
|
76
147
|
assertProjectionRunStartInput(input)
|
|
@@ -96,98 +167,64 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
96
167
|
throw immutableDatasetVersionConflict(input.identity)
|
|
97
168
|
}
|
|
98
169
|
|
|
99
|
-
const existing = this.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const existingRecord = restoreProjectionRunRow(existing)
|
|
103
|
-
attempt = planProjectionRunReclaim(existingRecord, input).attempt
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const executionToken = createFreshExecutionToken(existing?.execution_token ?? null)
|
|
170
|
+
const existing = this.requireRow(input.projectId, input.id)
|
|
171
|
+
const existingRecord = restoreProjectionRunRow(existing)
|
|
172
|
+
const attempt = planProjectionRunReclaim(existingRecord, input).attempt
|
|
107
173
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
UPDATE projection_runs
|
|
113
|
-
SET attempt = ?, execution_token = ?
|
|
114
|
-
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
174
|
+
const executionToken = createFreshExecutionToken(existing.execution_token)
|
|
175
|
+
const result = this.db
|
|
176
|
+
.query(
|
|
115
177
|
`
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
`
|
|
124
|
-
INSERT INTO projection_runs (
|
|
125
|
-
project_id,
|
|
126
|
-
id,
|
|
127
|
-
projection_id,
|
|
128
|
-
projection_kind,
|
|
129
|
-
materialization_protocol,
|
|
130
|
-
dataset_id,
|
|
131
|
-
dataset_version_id,
|
|
132
|
-
dataset_version_created_at,
|
|
133
|
-
ontology_revision,
|
|
134
|
-
projection_revision,
|
|
135
|
-
ownership_hash,
|
|
136
|
-
object_type_id,
|
|
137
|
-
source_object_type_id,
|
|
138
|
-
target_object_type_id,
|
|
139
|
-
status,
|
|
140
|
-
started_at,
|
|
141
|
-
attempt,
|
|
142
|
-
execution_token,
|
|
143
|
-
fixed_batch_size,
|
|
144
|
-
next_batch_ordinal,
|
|
145
|
-
next_row_offset,
|
|
146
|
-
input_exhausted,
|
|
147
|
-
source_rows_read,
|
|
148
|
-
source_rows_skipped
|
|
149
|
-
) VALUES (
|
|
150
|
-
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, 1, ?, ?, ?, ?, ?,
|
|
151
|
-
0, 0
|
|
178
|
+
UPDATE projection_runs
|
|
179
|
+
SET status = 'running', started_at = COALESCE(started_at, ?), finished_at = NULL,
|
|
180
|
+
attempt = ?, execution_token = ?, error = NULL
|
|
181
|
+
WHERE project_id = ? AND id = ?
|
|
182
|
+
AND (
|
|
183
|
+
(status = 'queued' AND execution_token IS NULL)
|
|
184
|
+
OR (status = 'running' AND execution_token = ?)
|
|
152
185
|
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
input.identity.ontologyRevision,
|
|
165
|
-
input.identity.projectionRevision,
|
|
166
|
-
input.identity.ownershipHash,
|
|
167
|
-
"objectTypeId" in input.target ? input.target.objectTypeId : null,
|
|
168
|
-
"sourceObjectTypeId" in input.target ? input.target.sourceObjectTypeId : null,
|
|
169
|
-
"sourceObjectTypeId" in input.target ? input.target.targetObjectTypeId : null,
|
|
170
|
-
(input.startedAt ?? new Date()).toISOString(),
|
|
171
|
-
executionToken,
|
|
172
|
-
input.fixedBatchSize ?? null,
|
|
173
|
-
input.fixedBatchSize === undefined ? null : 0,
|
|
174
|
-
input.fixedBatchSize === undefined ? null : 0,
|
|
175
|
-
input.fixedBatchSize === undefined ? null : 0
|
|
176
|
-
)
|
|
177
|
-
} catch (error) {
|
|
178
|
-
if (isUniqueConstraintError(error)) {
|
|
179
|
-
throw new ProjectionRunError(
|
|
180
|
-
`[SixbSqlite] Projection run '${input.id}' already exists for project '${input.projectId}'.`
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
throw error
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
+
`
|
|
187
|
+
)
|
|
188
|
+
.run(
|
|
189
|
+
(input.startedAt ?? new Date()).toISOString(),
|
|
190
|
+
attempt,
|
|
191
|
+
executionToken,
|
|
192
|
+
input.projectId,
|
|
193
|
+
input.id,
|
|
194
|
+
existing.execution_token
|
|
195
|
+
)
|
|
196
|
+
if (result.changes !== 1) throw staleProjectionRunExecution(input.id)
|
|
186
197
|
|
|
187
198
|
return projectionRunClaim(this.requireRow(input.projectId, input.id))
|
|
188
199
|
})
|
|
189
200
|
}
|
|
190
201
|
|
|
202
|
+
async failEnqueue(input: FailProjectionRunEnqueueInput): Promise<ProjectionRunRecord> {
|
|
203
|
+
return runImmediateTransaction(this.db, () => {
|
|
204
|
+
const existing = restoreProjectionRunRow(this.requireRow(input.projectId, input.id))
|
|
205
|
+
const failed = failProjectionRunEnqueue(existing, input)
|
|
206
|
+
if (!failed.finishedAt || !failed.error) {
|
|
207
|
+
throw new ProjectionRunError(`[SixbSqlite] Projection run '${input.id}' did not finish.`)
|
|
208
|
+
}
|
|
209
|
+
const result = this.db
|
|
210
|
+
.query(
|
|
211
|
+
`
|
|
212
|
+
UPDATE projection_runs
|
|
213
|
+
SET status = 'failed', finished_at = ?, error = ?
|
|
214
|
+
WHERE project_id = ? AND id = ? AND status = 'queued'
|
|
215
|
+
`
|
|
216
|
+
)
|
|
217
|
+
.run(
|
|
218
|
+
failed.finishedAt.toISOString(),
|
|
219
|
+
serializeSixbFailure(failed.error, PROJECTION_RUN_FAILURE_CODES),
|
|
220
|
+
input.projectId,
|
|
221
|
+
input.id
|
|
222
|
+
)
|
|
223
|
+
if (result.changes !== 1) throw duplicateProjectionRun(input)
|
|
224
|
+
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id))
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
191
228
|
async lockForMaterialization(
|
|
192
229
|
input: LockProjectionRunForMaterializationInput
|
|
193
230
|
): Promise<ProjectionRunRecord> {
|
|
@@ -221,7 +258,7 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
221
258
|
source_rows_read = ?,
|
|
222
259
|
source_rows_skipped = ?,
|
|
223
260
|
input_exhausted = ?,
|
|
224
|
-
|
|
261
|
+
error = ?
|
|
225
262
|
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
226
263
|
`
|
|
227
264
|
)
|
|
@@ -231,7 +268,9 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
231
268
|
plan.progress.sourceRowsRead,
|
|
232
269
|
plan.progress.sourceRowsSkipped,
|
|
233
270
|
plan.inputExhausted ? 1 : existingRow.input_exhausted,
|
|
234
|
-
plan.
|
|
271
|
+
plan.error === undefined
|
|
272
|
+
? null
|
|
273
|
+
: serializeSixbFailure(plan.error, PROJECTION_RUN_FAILURE_CODES),
|
|
235
274
|
input.projectId,
|
|
236
275
|
input.id,
|
|
237
276
|
input.executionToken
|
|
@@ -366,11 +405,11 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
366
405
|
args.push(...input.statuses)
|
|
367
406
|
}
|
|
368
407
|
if (input.startedAfter) {
|
|
369
|
-
whereClauses.push("started_at >= ?")
|
|
408
|
+
whereClauses.push("COALESCE(started_at, queued_at) >= ?")
|
|
370
409
|
args.push(input.startedAfter.toISOString())
|
|
371
410
|
}
|
|
372
411
|
if (input.startedBefore) {
|
|
373
|
-
whereClauses.push("started_at <= ?")
|
|
412
|
+
whereClauses.push("COALESCE(started_at, queued_at) <= ?")
|
|
374
413
|
args.push(input.startedBefore.toISOString())
|
|
375
414
|
}
|
|
376
415
|
|
|
@@ -386,7 +425,7 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
386
425
|
let query = `
|
|
387
426
|
SELECT * FROM projection_runs
|
|
388
427
|
${where}
|
|
389
|
-
ORDER BY started_at ${order}, id ${order}
|
|
428
|
+
ORDER BY COALESCE(started_at, queued_at) ${order}, id ${order}
|
|
390
429
|
`
|
|
391
430
|
const queryArgs = [...args]
|
|
392
431
|
|
|
@@ -424,6 +463,69 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
424
463
|
closeSqliteStoreConnection(this.connection)
|
|
425
464
|
}
|
|
426
465
|
|
|
466
|
+
private assertDatasetVersionIsImmutable(input: QueueProjectionRunInput): void {
|
|
467
|
+
const conflict = this.db
|
|
468
|
+
.query(
|
|
469
|
+
`
|
|
470
|
+
SELECT 1 FROM projection_runs
|
|
471
|
+
WHERE project_id = ? AND id <> ? AND dataset_id = ? AND dataset_version_id = ?
|
|
472
|
+
AND dataset_version_created_at <> ?
|
|
473
|
+
LIMIT 1
|
|
474
|
+
`
|
|
475
|
+
)
|
|
476
|
+
.get(
|
|
477
|
+
input.projectId,
|
|
478
|
+
input.id,
|
|
479
|
+
input.identity.datasetVersion.datasetId,
|
|
480
|
+
input.identity.datasetVersion.versionId,
|
|
481
|
+
input.identity.datasetVersion.createdAt
|
|
482
|
+
)
|
|
483
|
+
if (conflict) throw immutableDatasetVersionConflict(input.identity)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
private insertQueued(record: ProjectionRunRecord): void {
|
|
487
|
+
const checkpoint = record.telemetryCheckpoint
|
|
488
|
+
this.db
|
|
489
|
+
.query(
|
|
490
|
+
`
|
|
491
|
+
INSERT INTO projection_runs (
|
|
492
|
+
project_id, id, execution_id, projection_id, projection_kind,
|
|
493
|
+
materialization_protocol, dataset_id, dataset_version_id,
|
|
494
|
+
dataset_version_created_at, ontology_revision, projection_revision, ownership_hash,
|
|
495
|
+
object_type_id, source_object_type_id, target_object_type_id, status, queued_at,
|
|
496
|
+
started_at, finished_at, attempt, execution_token, fixed_batch_size,
|
|
497
|
+
next_batch_ordinal, next_row_offset, input_exhausted, source_rows_read,
|
|
498
|
+
source_rows_skipped
|
|
499
|
+
) VALUES (
|
|
500
|
+
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', ?, NULL, NULL, 0, NULL,
|
|
501
|
+
?, ?, ?, ?, 0, 0
|
|
502
|
+
)
|
|
503
|
+
`
|
|
504
|
+
)
|
|
505
|
+
.run(
|
|
506
|
+
record.projectId,
|
|
507
|
+
record.id,
|
|
508
|
+
record.executionId,
|
|
509
|
+
record.identity.projectionId,
|
|
510
|
+
record.identity.projectionKind,
|
|
511
|
+
record.identity.protocol,
|
|
512
|
+
record.identity.datasetVersion.datasetId,
|
|
513
|
+
record.identity.datasetVersion.versionId,
|
|
514
|
+
record.identity.datasetVersion.createdAt,
|
|
515
|
+
record.identity.ontologyRevision,
|
|
516
|
+
record.identity.projectionRevision,
|
|
517
|
+
record.identity.ownershipHash,
|
|
518
|
+
"objectTypeId" in record.target ? record.target.objectTypeId : null,
|
|
519
|
+
"sourceObjectTypeId" in record.target ? record.target.sourceObjectTypeId : null,
|
|
520
|
+
"sourceObjectTypeId" in record.target ? record.target.targetObjectTypeId : null,
|
|
521
|
+
record.queuedAt.toISOString(),
|
|
522
|
+
checkpoint?.fixedBatchSize ?? null,
|
|
523
|
+
checkpoint?.nextBatchOrdinal ?? null,
|
|
524
|
+
checkpoint?.nextRowOffset ?? null,
|
|
525
|
+
checkpoint === undefined ? null : checkpoint.inputExhausted ? 1 : 0
|
|
526
|
+
)
|
|
527
|
+
}
|
|
528
|
+
|
|
427
529
|
private updateProgress(
|
|
428
530
|
existing: DatabaseRow,
|
|
429
531
|
progress: ProjectionRunProgress,
|
|
@@ -455,7 +557,7 @@ export class SqliteProjectionRunStorage implements ProjectionRunStorage {
|
|
|
455
557
|
input: LockProjectionRunForMaterializationInput
|
|
456
558
|
): DatabaseRow {
|
|
457
559
|
const row = this.requireRunning(input.projectId, input.id)
|
|
458
|
-
|
|
560
|
+
assertProjectionRunAttempt(restoreProjectionRunRow(row), input)
|
|
459
561
|
return row
|
|
460
562
|
}
|
|
461
563
|
|
|
@@ -494,6 +596,7 @@ function restoreProjectionRunRow(row: DatabaseRow): StoredProjectionRunRecord {
|
|
|
494
596
|
const persisted: PersistedProjectionRunRecord = {
|
|
495
597
|
id: row.id,
|
|
496
598
|
projectId: row.project_id,
|
|
599
|
+
executionId: row.execution_id,
|
|
497
600
|
projectionId: row.projection_id,
|
|
498
601
|
projectionKind: row.projection_kind,
|
|
499
602
|
protocol: row.materialization_protocol ?? undefined,
|
|
@@ -507,7 +610,8 @@ function restoreProjectionRunRow(row: DatabaseRow): StoredProjectionRunRecord {
|
|
|
507
610
|
sourceObjectTypeId: row.source_object_type_id ?? undefined,
|
|
508
611
|
targetObjectTypeId: row.target_object_type_id ?? undefined,
|
|
509
612
|
status: row.status,
|
|
510
|
-
|
|
613
|
+
queuedAt: new Date(row.queued_at),
|
|
614
|
+
startedAt: row.started_at ? new Date(row.started_at) : undefined,
|
|
511
615
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
512
616
|
attempt: databaseSafeInteger(row.attempt, "attempt"),
|
|
513
617
|
executionToken: row.execution_token ?? undefined,
|
|
@@ -528,7 +632,7 @@ function restoreProjectionRunRow(row: DatabaseRow): StoredProjectionRunRecord {
|
|
|
528
632
|
sourceRowsRead: databaseSafeInteger(row.source_rows_read, "sourceRowsRead"),
|
|
529
633
|
sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped"),
|
|
530
634
|
},
|
|
531
|
-
|
|
635
|
+
error: row.error ?? undefined,
|
|
532
636
|
}
|
|
533
637
|
return restoreProjectionRun(persisted)
|
|
534
638
|
}
|
|
@@ -561,6 +665,7 @@ function isUniqueConstraintError(error: unknown): boolean {
|
|
|
561
665
|
interface DatabaseRow {
|
|
562
666
|
project_id: string
|
|
563
667
|
id: string
|
|
668
|
+
execution_id: string
|
|
564
669
|
projection_id: string
|
|
565
670
|
projection_kind: ProjectionKind
|
|
566
671
|
materialization_protocol: "replacement" | "telemetry" | null
|
|
@@ -574,7 +679,8 @@ interface DatabaseRow {
|
|
|
574
679
|
source_object_type_id: string | null
|
|
575
680
|
target_object_type_id: string | null
|
|
576
681
|
status: ProjectionRunStatus
|
|
577
|
-
|
|
682
|
+
queued_at: string
|
|
683
|
+
started_at: string | null
|
|
578
684
|
finished_at: string | null
|
|
579
685
|
attempt: number
|
|
580
686
|
execution_token: string | null
|
|
@@ -588,5 +694,11 @@ interface DatabaseRow {
|
|
|
588
694
|
missing_target_first_seen_at: string | null
|
|
589
695
|
source_rows_read: number
|
|
590
696
|
source_rows_skipped: number
|
|
591
|
-
|
|
697
|
+
error: string | null
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function duplicateProjectionRun(input: { readonly projectId: string; readonly id: string }) {
|
|
701
|
+
return new ProjectionRunError(
|
|
702
|
+
`[SixbSqlite] Projection run '${input.id}' already exists for project '${input.projectId}'.`
|
|
703
|
+
)
|
|
592
704
|
}
|
package/src/run-list-query.ts
CHANGED
|
@@ -16,7 +16,10 @@ export function hasEmptyStatuses(input: { readonly statuses?: readonly unknown[]
|
|
|
16
16
|
return input.statuses !== undefined && input.statuses.length === 0
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
type StartedAtExpression =
|
|
19
|
+
type StartedAtExpression =
|
|
20
|
+
| "started_at"
|
|
21
|
+
| "COALESCE(started_at, created_at)"
|
|
22
|
+
| "COALESCE(started_at, queued_at)"
|
|
20
23
|
|
|
21
24
|
export function appendRunListFilters(
|
|
22
25
|
whereClauses: string[],
|
|
@@ -68,9 +71,11 @@ export function queryRunList<TRow>(input: {
|
|
|
68
71
|
const orderColumn =
|
|
69
72
|
input.tableName === "agent_runs"
|
|
70
73
|
? "COALESCE(started_at, created_at)"
|
|
71
|
-
: input.tableName === "
|
|
72
|
-
? "
|
|
73
|
-
: "
|
|
74
|
+
: input.tableName === "sync_runs" || input.tableName === "pipeline_runs"
|
|
75
|
+
? "COALESCE(started_at, queued_at)"
|
|
76
|
+
: input.tableName === "workflow_agent_node_runs"
|
|
77
|
+
? "created_at"
|
|
78
|
+
: "started_at"
|
|
74
79
|
const idColumn = input.tableName === "workflow_agent_node_runs" ? "node_run_id" : "id"
|
|
75
80
|
let query = `
|
|
76
81
|
SELECT * FROM ${input.tableName}
|