@sixb/sqlite 0.1.9 → 0.1.11
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/index.js +449 -195
- package/dist/index.js.map +13 -11
- 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/outbox.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/projection-run-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/connector-connection-storage.ts +13 -3
- package/src/migrations/038-projection-source-roots.sql +44 -0
- package/src/migrations/039-connector-optional-pkce.sql +52 -0
- package/src/migrations/040-outbox-publication-order.sql +8 -0
- package/src/migrations.ts +13 -0
- package/src/ontology-storage/materialization-state.ts +56 -91
- package/src/ontology-storage/materializations.ts +7 -1
- package/src/ontology-storage/outbox.ts +33 -16
- 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/projection-run-storage.ts +11 -3
package/dist/index.js
CHANGED
|
@@ -3932,6 +3932,64 @@ DROP TABLE executions;
|
|
|
3932
3932
|
ALTER TABLE executions_v2 RENAME TO executions;
|
|
3933
3933
|
`;
|
|
3934
3934
|
|
|
3935
|
+
// src/migrations/038-projection-source-roots.sql
|
|
3936
|
+
var _038_projection_source_roots_default = `ALTER TABLE ontology_sources ADD COLUMN base_materialization_id TEXT;
|
|
3937
|
+
ALTER TABLE ontology_sources ADD COLUMN base_commit_id TEXT
|
|
3938
|
+
CHECK ((base_materialization_id IS NULL) = (base_commit_id IS NULL));
|
|
3939
|
+
|
|
3940
|
+
CREATE TABLE ontology_source_roots (
|
|
3941
|
+
project_id TEXT NOT NULL,
|
|
3942
|
+
source_id TEXT NOT NULL,
|
|
3943
|
+
materialization_id TEXT NOT NULL,
|
|
3944
|
+
root_sort_key TEXT NOT NULL,
|
|
3945
|
+
root_kind TEXT NOT NULL CHECK (root_kind IN ('object', 'link')),
|
|
3946
|
+
root_key TEXT NOT NULL CHECK (json_valid(root_key)),
|
|
3947
|
+
root TEXT NOT NULL CHECK (json_valid(root)),
|
|
3948
|
+
staging_ordinal INTEGER NOT NULL CHECK (staging_ordinal >= 0),
|
|
3949
|
+
deleted INTEGER NOT NULL DEFAULT 0 CHECK (deleted IN (0, 1)),
|
|
3950
|
+
active INTEGER NOT NULL DEFAULT 0 CHECK (active IN (0, 1)),
|
|
3951
|
+
retired_at TEXT,
|
|
3952
|
+
PRIMARY KEY (project_id, source_id, materialization_id, root_sort_key),
|
|
3953
|
+
UNIQUE (project_id, source_id, materialization_id, staging_ordinal),
|
|
3954
|
+
FOREIGN KEY (project_id, source_id, materialization_id)
|
|
3955
|
+
REFERENCES ontology_sources(project_id, source_id, materialization_id) ON DELETE RESTRICT,
|
|
3956
|
+
CHECK (active = 0 OR (deleted = 0 AND retired_at IS NULL))
|
|
3957
|
+
);
|
|
3958
|
+
CREATE UNIQUE INDEX idx_ontology_source_roots_active
|
|
3959
|
+
ON ontology_source_roots (project_id, source_id, root_sort_key) WHERE active = 1;
|
|
3960
|
+
CREATE INDEX idx_ontology_source_roots_lookup
|
|
3961
|
+
ON ontology_source_roots (project_id, root_sort_key, source_id) WHERE active = 1;
|
|
3962
|
+
CREATE INDEX idx_ontology_source_roots_cleanup
|
|
3963
|
+
ON ontology_source_roots (project_id, retired_at, source_id, materialization_id, root_sort_key)
|
|
3964
|
+
WHERE active = 0 AND retired_at IS NOT NULL;
|
|
3965
|
+
|
|
3966
|
+
INSERT INTO ontology_source_roots (
|
|
3967
|
+
project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root,
|
|
3968
|
+
staging_ordinal, active, retired_at
|
|
3969
|
+
)
|
|
3970
|
+
SELECT DISTINCT rows.project_id, rows.source_id, rows.materialization_id,
|
|
3971
|
+
rows.root_sort_key, rows.root_kind, rows.root_key, rows.root, rows.staging_ordinal,
|
|
3972
|
+
sources.status = 'active', sources.terminal_at
|
|
3973
|
+
FROM ontology_source_rows AS rows
|
|
3974
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id);
|
|
3975
|
+
|
|
3976
|
+
ANALYZE ontology_source_roots;
|
|
3977
|
+
|
|
3978
|
+
ALTER TABLE projection_runs ADD COLUMN source_changes_read INTEGER
|
|
3979
|
+
CHECK (source_changes_read IS NULL OR source_changes_read >= 0);
|
|
3980
|
+
`;
|
|
3981
|
+
|
|
3982
|
+
// src/migrations/040-outbox-publication-order.sql
|
|
3983
|
+
var _040_outbox_publication_order_default = `-- Match the bounded claim order without sorting the entire unpublished backlog.
|
|
3984
|
+
-- Cover every selection predicate so new databases also benefit before ANALYZE.
|
|
3985
|
+
CREATE INDEX idx_ontology_outbox_publication_order
|
|
3986
|
+
ON ontology_outbox(
|
|
3987
|
+
project_id, published_at, created_at, commit_id, commit_ordinal,
|
|
3988
|
+
available_at, lease_expires_at
|
|
3989
|
+
)
|
|
3990
|
+
WHERE published_at IS NULL;
|
|
3991
|
+
`;
|
|
3992
|
+
|
|
3935
3993
|
// src/migrations/037-execution-requester-groups.sql
|
|
3936
3994
|
var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids TEXT NOT NULL DEFAULT '[]';
|
|
3937
3995
|
|
|
@@ -3959,6 +4017,61 @@ ALTER TABLE workflow_runs DROP COLUMN requester_group_ids;
|
|
|
3959
4017
|
ALTER TABLE agent_runs DROP COLUMN requester_group_ids;
|
|
3960
4018
|
`;
|
|
3961
4019
|
|
|
4020
|
+
// src/migrations/039-connector-optional-pkce.sql
|
|
4021
|
+
var _039_connector_optional_pkce_default = `-- Rebuild to remove NOT NULL while preserving existing attempts and their constraints.
|
|
4022
|
+
CREATE TABLE connector_authorization_attempts_optional_pkce (
|
|
4023
|
+
project_id TEXT NOT NULL,
|
|
4024
|
+
connector_id TEXT NOT NULL,
|
|
4025
|
+
id TEXT NOT NULL,
|
|
4026
|
+
slot TEXT NOT NULL,
|
|
4027
|
+
initiated_by_execution_id TEXT NOT NULL,
|
|
4028
|
+
state_hash TEXT NOT NULL,
|
|
4029
|
+
code_verifier TEXT CHECK (
|
|
4030
|
+
code_verifier IS NULL
|
|
4031
|
+
OR (json_valid(code_verifier) AND json_type(code_verifier) = 'object')
|
|
4032
|
+
),
|
|
4033
|
+
redirect_uri TEXT NOT NULL,
|
|
4034
|
+
connection_run_id TEXT,
|
|
4035
|
+
return_to TEXT,
|
|
4036
|
+
callback_binding_hash TEXT,
|
|
4037
|
+
reauthorization_id TEXT,
|
|
4038
|
+
reauthorization_revision INTEGER CHECK (
|
|
4039
|
+
reauthorization_revision IS NULL OR reauthorization_revision >= 0
|
|
4040
|
+
),
|
|
4041
|
+
reauthorization_connection_ids TEXT CHECK (
|
|
4042
|
+
reauthorization_connection_ids IS NULL
|
|
4043
|
+
OR (json_valid(reauthorization_connection_ids) AND json_type(reauthorization_connection_ids) = 'array')
|
|
4044
|
+
),
|
|
4045
|
+
created_at TEXT NOT NULL,
|
|
4046
|
+
expires_at TEXT NOT NULL,
|
|
4047
|
+
PRIMARY KEY (project_id, connector_id, id),
|
|
4048
|
+
UNIQUE (project_id, id),
|
|
4049
|
+
UNIQUE (project_id, connector_id, connection_run_id),
|
|
4050
|
+
FOREIGN KEY (project_id, connector_id, connection_run_id)
|
|
4051
|
+
REFERENCES connector_connection_runs (project_id, connector_id, id)
|
|
4052
|
+
ON DELETE RESTRICT,
|
|
4053
|
+
CHECK (
|
|
4054
|
+
(connection_run_id IS NULL AND return_to IS NULL AND callback_binding_hash IS NULL)
|
|
4055
|
+
OR
|
|
4056
|
+
(connection_run_id IS NOT NULL AND return_to IS NOT NULL AND callback_binding_hash IS NOT NULL)
|
|
4057
|
+
),
|
|
4058
|
+
CHECK (
|
|
4059
|
+
(reauthorization_id IS NULL AND reauthorization_revision IS NULL AND reauthorization_connection_ids IS NULL)
|
|
4060
|
+
OR
|
|
4061
|
+
(reauthorization_id IS NOT NULL AND reauthorization_revision IS NOT NULL AND reauthorization_connection_ids IS NOT NULL)
|
|
4062
|
+
)
|
|
4063
|
+
);
|
|
4064
|
+
|
|
4065
|
+
INSERT INTO connector_authorization_attempts_optional_pkce
|
|
4066
|
+
SELECT * FROM connector_authorization_attempts;
|
|
4067
|
+
|
|
4068
|
+
DROP TABLE connector_authorization_attempts;
|
|
4069
|
+
ALTER TABLE connector_authorization_attempts_optional_pkce RENAME TO connector_authorization_attempts;
|
|
4070
|
+
|
|
4071
|
+
CREATE INDEX idx_connector_attempts_expiry
|
|
4072
|
+
ON connector_authorization_attempts (project_id, connector_id, expires_at);
|
|
4073
|
+
`;
|
|
4074
|
+
|
|
3962
4075
|
// src/migrations.ts
|
|
3963
4076
|
var MIGRATIONS_TABLE_SQL = `
|
|
3964
4077
|
CREATE TABLE IF NOT EXISTS sixb_migrations (
|
|
@@ -4030,7 +4143,10 @@ var sqliteStorageMigrations = defineMigrations({
|
|
|
4030
4143
|
sqliteSql("034-device-authorizations", _034_device_authorizations_default),
|
|
4031
4144
|
sqliteSql("035-share-grants", _035_share_grants_default),
|
|
4032
4145
|
sqliteSql("036-share-sessions", _036_share_sessions_default),
|
|
4033
|
-
sqliteSql("037-execution-requester-groups", _037_execution_requester_groups_default)
|
|
4146
|
+
sqliteSql("037-execution-requester-groups", _037_execution_requester_groups_default),
|
|
4147
|
+
sqliteSql("038-projection-source-roots", _038_projection_source_roots_default),
|
|
4148
|
+
sqliteSql("039-connector-optional-pkce", _039_connector_optional_pkce_default),
|
|
4149
|
+
sqliteSql("040-outbox-publication-order", _040_outbox_publication_order_default)
|
|
4034
4150
|
]
|
|
4035
4151
|
});
|
|
4036
4152
|
function sqliteStoragePath(basePath) {
|
|
@@ -9014,7 +9130,7 @@ class SqliteConnectorConnectionPersistence {
|
|
|
9014
9130
|
callback_binding_hash, reauthorization_id, reauthorization_revision,
|
|
9015
9131
|
reauthorization_connection_ids, created_at, expires_at
|
|
9016
9132
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
9017
|
-
`).run(record.projectId, record.connectorId, record.id, record.slot, record.initiatedByExecutionId, record.stateHash, json(record.codeVerifier), record.redirectUri, record.connectionRunId ?? null, record.returnTo ?? null, record.callbackBindingHash ?? null, record.reauthorizationId ?? null, record.reauthorizationRevision ?? null, record.reauthorizationConnectionIds ? json(record.reauthorizationConnectionIds) : null, iso(record.createdAt), iso(record.expiresAt));
|
|
9133
|
+
`).run(record.projectId, record.connectorId, record.id, record.slot, record.initiatedByExecutionId, record.stateHash, record.codeVerifier === undefined ? null : json(record.codeVerifier), record.redirectUri, record.connectionRunId ?? null, record.returnTo ?? null, record.callbackBindingHash ?? null, record.reauthorizationId ?? null, record.reauthorizationRevision ?? null, record.reauthorizationConnectionIds ? json(record.reauthorizationConnectionIds) : null, iso(record.createdAt), iso(record.expiresAt));
|
|
9018
9134
|
return true;
|
|
9019
9135
|
} catch (error) {
|
|
9020
9136
|
if (isUniqueConstraintError2(error))
|
|
@@ -9048,6 +9164,14 @@ class SqliteConnectorConnectionPersistence {
|
|
|
9048
9164
|
throw error;
|
|
9049
9165
|
}
|
|
9050
9166
|
}
|
|
9167
|
+
async listPendingConnectionRuns() {
|
|
9168
|
+
if (!this.scope.connectorId)
|
|
9169
|
+
return [];
|
|
9170
|
+
const rows = this.db.query(`SELECT * FROM connector_connection_runs
|
|
9171
|
+
WHERE project_id = ? AND connector_id = ?
|
|
9172
|
+
AND (status = 'running' OR (status = 'waiting' AND waiting_for = 'account_selection'))`).all(this.scope.projectId, this.scope.connectorId);
|
|
9173
|
+
return rows.map(connectionRunFromRow);
|
|
9174
|
+
}
|
|
9051
9175
|
async getConnectionRun(id) {
|
|
9052
9176
|
const row = this.getById("connector_connection_runs", id);
|
|
9053
9177
|
return row ? connectionRunFromRow(row) : null;
|
|
@@ -9195,7 +9319,7 @@ function authorizationAttemptFromRow(row) {
|
|
|
9195
9319
|
slot: row.slot,
|
|
9196
9320
|
initiatedByExecutionId: row.initiated_by_execution_id,
|
|
9197
9321
|
stateHash: row.state_hash,
|
|
9198
|
-
codeVerifier: parseJson(row.code_verifier),
|
|
9322
|
+
...row.code_verifier === null ? {} : { codeVerifier: parseJson(row.code_verifier) },
|
|
9199
9323
|
redirectUri: row.redirect_uri,
|
|
9200
9324
|
...row.connection_run_id === null ? {} : { connectionRunId: row.connection_run_id },
|
|
9201
9325
|
...row.return_to === null ? {} : { returnTo: row.return_to },
|
|
@@ -11387,6 +11511,12 @@ function sourceRecord(row) {
|
|
|
11387
11511
|
projectionRevision: row.projection_revision,
|
|
11388
11512
|
ownershipHash: row.ownership_hash,
|
|
11389
11513
|
ontologyRevision: row.ontology_revision,
|
|
11514
|
+
...row.base_materialization_id === null ? {} : {
|
|
11515
|
+
base: {
|
|
11516
|
+
materializationId: row.base_materialization_id,
|
|
11517
|
+
lastCommitId: row.base_commit_id
|
|
11518
|
+
}
|
|
11519
|
+
},
|
|
11390
11520
|
rootCount: row.root_count,
|
|
11391
11521
|
assertionCount: row.assertion_count,
|
|
11392
11522
|
createdAt: row.created_at,
|
|
@@ -11558,7 +11688,7 @@ import {
|
|
|
11558
11688
|
linkRefKey as linkRefKey3,
|
|
11559
11689
|
linkRefSortKey,
|
|
11560
11690
|
linkScopeSortKey as linkScopeSortKey2,
|
|
11561
|
-
MaterializationConflictError as
|
|
11691
|
+
MaterializationConflictError as MaterializationConflictError6,
|
|
11562
11692
|
objectRefKey as objectRefKey3,
|
|
11563
11693
|
objectRefSortKey,
|
|
11564
11694
|
telemetryPointKey as telemetryPointKey2,
|
|
@@ -11578,7 +11708,7 @@ import {
|
|
|
11578
11708
|
|
|
11579
11709
|
// src/ontology-storage/materialization-session.ts
|
|
11580
11710
|
import {
|
|
11581
|
-
MaterializationConflictError as
|
|
11711
|
+
MaterializationConflictError as MaterializationConflictError4
|
|
11582
11712
|
} from "@sixb/core/internal/materialization";
|
|
11583
11713
|
import {
|
|
11584
11714
|
correlateMaterializationChunk,
|
|
@@ -11593,9 +11723,9 @@ import {
|
|
|
11593
11723
|
import {
|
|
11594
11724
|
linkRefKey,
|
|
11595
11725
|
linkScopeSortKey,
|
|
11596
|
-
MaterializationConflictError as
|
|
11726
|
+
MaterializationConflictError as MaterializationConflictError3,
|
|
11597
11727
|
objectRefKey,
|
|
11598
|
-
projectionEntityKey,
|
|
11728
|
+
projectionEntityKey as projectionEntityKey2,
|
|
11599
11729
|
telemetryPointSortKey
|
|
11600
11730
|
} from "@sixb/core/internal/materialization";
|
|
11601
11731
|
import {
|
|
@@ -11604,6 +11734,132 @@ import {
|
|
|
11604
11734
|
linkSlotOverrideValue,
|
|
11605
11735
|
startScopeAccumulator
|
|
11606
11736
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
11737
|
+
|
|
11738
|
+
// src/ontology-storage/source-roots.ts
|
|
11739
|
+
import {
|
|
11740
|
+
MaterializationConflictError as MaterializationConflictError2,
|
|
11741
|
+
MaterializationValidationError,
|
|
11742
|
+
projectionEntityKey
|
|
11743
|
+
} from "@sixb/core/internal/materialization";
|
|
11744
|
+
import { sourceStageRoots, utf8SortKey } from "@sixb/core/internal/ontology-storage-provider";
|
|
11745
|
+
function replacementSourceRows(input) {
|
|
11746
|
+
const roots = input.incremental ? `ontology_source_roots AS changed
|
|
11747
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = changed.project_id
|
|
11748
|
+
AND roots.source_id = changed.source_id AND roots.root_sort_key = changed.root_sort_key AND roots.active = 1` : "ontology_source_roots AS roots";
|
|
11749
|
+
const selected = input.incremental ? "changed" : "roots";
|
|
11750
|
+
return {
|
|
11751
|
+
sql: `SELECT * FROM ontology_source_rows WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
11752
|
+
UNION ALL
|
|
11753
|
+
SELECT rows.* FROM ${roots}
|
|
11754
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id
|
|
11755
|
+
AND rows.source_id = roots.source_id AND rows.materialization_id = roots.materialization_id
|
|
11756
|
+
AND rows.root_sort_key = roots.root_sort_key
|
|
11757
|
+
WHERE ${selected}.project_id = ? AND ${selected}.source_id = ?
|
|
11758
|
+
AND ${input.incremental ? "changed.materialization_id = ?" : "roots.active = 1"}`,
|
|
11759
|
+
values: [
|
|
11760
|
+
input.projectId,
|
|
11761
|
+
input.sourceId,
|
|
11762
|
+
input.materializationId,
|
|
11763
|
+
input.projectId,
|
|
11764
|
+
input.sourceId,
|
|
11765
|
+
...input.incremental ? [input.materializationId] : []
|
|
11766
|
+
]
|
|
11767
|
+
};
|
|
11768
|
+
}
|
|
11769
|
+
function stageSourceRoots(db, manifest, input) {
|
|
11770
|
+
const roots = sourceStageRoots(manifest.projection_kind, input);
|
|
11771
|
+
if (!manifest.base_materialization_id && roots.some((root) => root.deleted)) {
|
|
11772
|
+
throw new MaterializationValidationError("Root deletions require a source delta base.");
|
|
11773
|
+
}
|
|
11774
|
+
const existing = db.query(`SELECT staging_ordinal, deleted FROM ontology_source_roots
|
|
11775
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND root_sort_key = ?`);
|
|
11776
|
+
const insert = db.query(`INSERT INTO ontology_source_roots (
|
|
11777
|
+
project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root, staging_ordinal, deleted
|
|
11778
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING root_sort_key`);
|
|
11779
|
+
for (const root of roots) {
|
|
11780
|
+
const inserted = insert.get(input.projectId, input.source.projectionId, input.materializationId, root.sortKey, root.root.kind, root.rootKey, canonicalJson(root.root), root.stagingOrdinal, Number(root.deleted));
|
|
11781
|
+
if (inserted)
|
|
11782
|
+
continue;
|
|
11783
|
+
const previous = existing.get(input.projectId, input.source.projectionId, input.materializationId, root.sortKey);
|
|
11784
|
+
if (!previous || previous.staging_ordinal !== root.stagingOrdinal || Boolean(previous.deleted) !== root.deleted) {
|
|
11785
|
+
throw new MaterializationValidationError("Source materialization repeats root or stream ordinal with different content.");
|
|
11786
|
+
}
|
|
11787
|
+
}
|
|
11788
|
+
}
|
|
11789
|
+
function activateSourceRoots(db, projectId, candidate, activation) {
|
|
11790
|
+
if (candidate.base_materialization_id !== null && (candidate.base_materialization_id !== activation.expected.activeMaterializationId || candidate.base_commit_id !== activation.expected.lastCommitId))
|
|
11791
|
+
throw new MaterializationConflictError2("projection-fence", "Source delta base changed before activation.");
|
|
11792
|
+
if (candidate.base_materialization_id !== null && candidate.root_count === 0)
|
|
11793
|
+
return;
|
|
11794
|
+
const scope = [projectId, activation.source.projectionId, activation.materializationId];
|
|
11795
|
+
db.query(`UPDATE ontology_source_roots AS roots SET active = 0, retired_at = ?
|
|
11796
|
+
WHERE project_id = ? AND source_id = ? AND active = 1
|
|
11797
|
+
${candidate.base_materialization_id === null ? "" : `AND root_sort_key IN (
|
|
11798
|
+
SELECT root_sort_key FROM ontology_source_roots
|
|
11799
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?)`}`).run(activation.updatedAt, projectId, activation.source.projectionId, ...candidate.base_materialization_id === null ? [] : [...scope]);
|
|
11800
|
+
db.query(`UPDATE ontology_source_roots SET active = CASE WHEN deleted = 1 THEN 0 ELSE 1 END,
|
|
11801
|
+
retired_at = CASE WHEN deleted = 1 THEN ? ELSE NULL END
|
|
11802
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?`).run(activation.updatedAt, ...scope);
|
|
11803
|
+
}
|
|
11804
|
+
function assertSourceRootCoverage(db, manifest) {
|
|
11805
|
+
const invalid = db.query(`SELECT 1 FROM ontology_source_roots AS roots
|
|
11806
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
11807
|
+
AND roots.deleted = EXISTS (
|
|
11808
|
+
SELECT 1 FROM ontology_source_rows AS rows
|
|
11809
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
11810
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
11811
|
+
) LIMIT 1`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
11812
|
+
if (invalid)
|
|
11813
|
+
throw new MaterializationValidationError("Source roots must have complete assertions or an explicit deletion, never both.");
|
|
11814
|
+
}
|
|
11815
|
+
function activeSourceRows(db, projectId, refs) {
|
|
11816
|
+
const keys = refs.flatMap((entity) => {
|
|
11817
|
+
const entitySortKey = utf8SortKey(projectionEntityKey(entity));
|
|
11818
|
+
const roots = entity.kind === "object" ? [entitySortKey] : [
|
|
11819
|
+
entitySortKey,
|
|
11820
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source }))
|
|
11821
|
+
];
|
|
11822
|
+
return roots.map((rootSortKey) => ({ rootSortKey, entitySortKey }));
|
|
11823
|
+
});
|
|
11824
|
+
return db.query(`WITH requested AS (
|
|
11825
|
+
SELECT DISTINCT json_extract(value, '$.rootSortKey') AS root_sort_key,
|
|
11826
|
+
json_extract(value, '$.entitySortKey') AS entity_sort_key FROM json_each(?)
|
|
11827
|
+
) SELECT rows.* FROM requested
|
|
11828
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
11829
|
+
AND roots.active = 1 AND roots.root_sort_key = requested.root_sort_key
|
|
11830
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
11831
|
+
AND rows.materialization_id = roots.materialization_id AND rows.entity_sort_key = requested.entity_sort_key
|
|
11832
|
+
AND rows.root_sort_key = roots.root_sort_key`).all(canonicalJson(keys), projectId);
|
|
11833
|
+
}
|
|
11834
|
+
function replacementAssertionRows(db, input) {
|
|
11835
|
+
const keys = input.refs.map((ref) => ({
|
|
11836
|
+
entityKind: ref.kind,
|
|
11837
|
+
entityKey: projectionEntityKey(ref),
|
|
11838
|
+
rootKeys: ref.kind === "object" ? [utf8SortKey(projectionEntityKey(ref))] : [
|
|
11839
|
+
utf8SortKey(projectionEntityKey(ref)),
|
|
11840
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source }))
|
|
11841
|
+
]
|
|
11842
|
+
}));
|
|
11843
|
+
const previous = !input.includePrevious ? "" : `UNION ALL
|
|
11844
|
+
SELECT rows.* FROM requested_entities AS requested
|
|
11845
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ? AND roots.source_id = ?
|
|
11846
|
+
AND roots.active = 1 AND roots.root_sort_key IN (SELECT value FROM json_each(requested.root_keys))
|
|
11847
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
11848
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
11849
|
+
AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
11850
|
+
${!input.incremental ? "" : `WHERE EXISTS (SELECT 1 FROM ontology_source_roots AS changed
|
|
11851
|
+
WHERE changed.project_id = roots.project_id AND changed.source_id = roots.source_id
|
|
11852
|
+
AND changed.materialization_id = ? AND changed.root_sort_key = roots.root_sort_key)`}`;
|
|
11853
|
+
return db.query(`WITH requested_entities AS (
|
|
11854
|
+
SELECT DISTINCT json_extract(value, '$.entityKind') AS entity_kind,
|
|
11855
|
+
json_extract(value, '$.entityKey') AS entity_key, json_extract(value, '$.rootKeys') AS root_keys FROM json_each(?)
|
|
11856
|
+
) SELECT rows.* FROM requested_entities AS requested
|
|
11857
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = ? AND rows.source_id = ?
|
|
11858
|
+
AND rows.materialization_id = ? AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
11859
|
+
${previous}`).all(canonicalJson(keys), input.projectId, input.sourceId, input.materializationId, ...input.includePrevious ? [input.projectId, input.sourceId, ...input.incremental ? [input.materializationId] : []] : []);
|
|
11860
|
+
}
|
|
11861
|
+
|
|
11862
|
+
// src/ontology-storage/materialization-state.ts
|
|
11607
11863
|
var SQLITE_MATERIALIZATION_WORK_TABLE = "ontology_materialization_work";
|
|
11608
11864
|
var SQLITE_REPLACEMENT_WORK_TABLE = "ontology_replacement_work";
|
|
11609
11865
|
function linkScopeRequestJson(scopes) {
|
|
@@ -11640,18 +11896,7 @@ class SqliteMaterializationStateReader {
|
|
|
11640
11896
|
ON objects.project_id = ?
|
|
11641
11897
|
AND objects.object_type_id = requested.object_type_id
|
|
11642
11898
|
AND objects.primary_id = requested.primary_id`).all(requested, this.projectId);
|
|
11643
|
-
const sourceRows = this.db.
|
|
11644
|
-
SELECT rows.* FROM requested
|
|
11645
|
-
CROSS JOIN ontology_source_rows AS rows
|
|
11646
|
-
ON rows.project_id = ?
|
|
11647
|
-
AND rows.entity_kind = 'object'
|
|
11648
|
-
AND rows.object_type_id = requested.object_type_id
|
|
11649
|
-
AND rows.primary_id = requested.primary_id
|
|
11650
|
-
JOIN ontology_sources AS sources
|
|
11651
|
-
ON sources.project_id = rows.project_id
|
|
11652
|
-
AND sources.source_id = rows.source_id
|
|
11653
|
-
AND sources.materialization_id = rows.materialization_id
|
|
11654
|
-
WHERE sources.status = 'active'`).all(requested, this.projectId);
|
|
11899
|
+
const sourceRows = activeSourceRows(this.db, this.projectId, refs.map((ref) => ({ kind: "object", ref })));
|
|
11655
11900
|
const overrideRows = this.db.query(`WITH requested AS (${requestedObjects})
|
|
11656
11901
|
SELECT overrides.* FROM requested
|
|
11657
11902
|
CROSS JOIN ontology_object_overrides AS overrides
|
|
@@ -11690,7 +11935,7 @@ class SqliteMaterializationStateReader {
|
|
|
11690
11935
|
const effectiveRow = effective.get(key);
|
|
11691
11936
|
return {
|
|
11692
11937
|
ref: structuredClone(ref),
|
|
11693
|
-
source: sourceObject(sources.get(
|
|
11938
|
+
source: sourceObject(sources.get(projectionEntityKey2({ kind: "object", ref }))),
|
|
11694
11939
|
override: overrides.get(key) ?? null,
|
|
11695
11940
|
effective: effectiveRow ? effectiveObjectSnapshot(effectiveRow) : null,
|
|
11696
11941
|
latestTelemetry: telemetry.get(key) ?? []
|
|
@@ -11735,21 +11980,7 @@ class SqliteMaterializationStateReader {
|
|
|
11735
11980
|
AND links.link_id = requested.link_id
|
|
11736
11981
|
AND links.target_type_id = requested.target_type_id
|
|
11737
11982
|
AND links.target_id = requested.target_id`).all(requested, this.projectId);
|
|
11738
|
-
const sourceRows = this.db.
|
|
11739
|
-
SELECT rows.* FROM requested
|
|
11740
|
-
CROSS JOIN ontology_source_rows AS rows
|
|
11741
|
-
ON rows.project_id = ?
|
|
11742
|
-
AND rows.entity_kind = 'link'
|
|
11743
|
-
AND rows.source_type_id = requested.source_type_id
|
|
11744
|
-
AND rows.source_primary_id = requested.source_primary_id
|
|
11745
|
-
AND rows.link_id = requested.link_id
|
|
11746
|
-
AND rows.target_type_id = requested.target_type_id
|
|
11747
|
-
AND rows.target_primary_id = requested.target_primary_id
|
|
11748
|
-
JOIN ontology_sources AS sources
|
|
11749
|
-
ON sources.project_id = rows.project_id
|
|
11750
|
-
AND sources.source_id = rows.source_id
|
|
11751
|
-
AND sources.materialization_id = rows.materialization_id
|
|
11752
|
-
WHERE sources.status = 'active'`).all(requested, this.projectId);
|
|
11983
|
+
const sourceRows = activeSourceRows(this.db, this.projectId, refs.map((ref) => ({ kind: "link", ref })));
|
|
11753
11984
|
const overrideRows = this.db.query(`WITH requested AS (${sourceRequest})
|
|
11754
11985
|
SELECT overrides.* FROM requested
|
|
11755
11986
|
CROSS JOIN ontology_link_overrides AS overrides
|
|
@@ -11780,7 +12011,7 @@ class SqliteMaterializationStateReader {
|
|
|
11780
12011
|
const effectiveRow = effective.get(key);
|
|
11781
12012
|
return {
|
|
11782
12013
|
ref: structuredClone(ref),
|
|
11783
|
-
source: sourceLink(sources.get(
|
|
12014
|
+
source: sourceLink(sources.get(projectionEntityKey2({ kind: "link", ref }))),
|
|
11784
12015
|
override: overrides.get(key) ?? null,
|
|
11785
12016
|
slotOverride: slotOverrides.get(linkScopeSortKey(ref.source, ref.linkId)) ?? null,
|
|
11786
12017
|
effective: effectiveRow ? effectiveLinkSnapshot(effectiveRow) : null
|
|
@@ -11845,11 +12076,12 @@ class SqliteMaterializationStateReader {
|
|
|
11845
12076
|
AND rows.source_type_id = requested.source_type_id
|
|
11846
12077
|
AND rows.source_primary_id = requested.source_primary_id
|
|
11847
12078
|
AND rows.link_id = requested.link_id
|
|
11848
|
-
JOIN
|
|
12079
|
+
JOIN ontology_source_roots AS sources
|
|
11849
12080
|
ON sources.project_id = rows.project_id
|
|
11850
12081
|
AND sources.source_id = rows.source_id
|
|
11851
12082
|
AND sources.materialization_id = rows.materialization_id
|
|
11852
|
-
|
|
12083
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12084
|
+
WHERE sources.active = 1`).all(requestedJson, this.projectId);
|
|
11853
12085
|
const slotOverrideRows = this.db.query(`WITH requested AS (
|
|
11854
12086
|
SELECT DISTINCT
|
|
11855
12087
|
json_extract(value, '$.sourceTypeId') AS source_type_id,
|
|
@@ -11907,7 +12139,7 @@ class SqliteMaterializationStateReader {
|
|
|
11907
12139
|
for (const row of rows) {
|
|
11908
12140
|
const accumulator = accumulators.get(row.scope_sort_key);
|
|
11909
12141
|
if (!accumulator) {
|
|
11910
|
-
throw new
|
|
12142
|
+
throw new MaterializationConflictError3("effective-state", `Unexpected link scope '${row.scope_sort_key}' returned by storage.`);
|
|
11911
12143
|
}
|
|
11912
12144
|
const snapshot = effectiveLinkSnapshot(row);
|
|
11913
12145
|
appendScopeSnapshot(accumulator, snapshot);
|
|
@@ -11938,11 +12170,12 @@ class SqliteMaterializationStateReader {
|
|
|
11938
12170
|
SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
|
|
11939
12171
|
rows.target_type_id, rows.target_primary_id
|
|
11940
12172
|
FROM ontology_source_rows AS rows
|
|
11941
|
-
JOIN
|
|
12173
|
+
JOIN ontology_source_roots AS sources
|
|
11942
12174
|
ON sources.project_id = rows.project_id
|
|
11943
12175
|
AND sources.source_id = rows.source_id
|
|
11944
12176
|
AND sources.materialization_id = rows.materialization_id
|
|
11945
|
-
|
|
12177
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12178
|
+
WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.active = 1
|
|
11946
12179
|
), selected AS (
|
|
11947
12180
|
SELECT DISTINCT links.*,
|
|
11948
12181
|
${linkSortExpression("links")} AS sort_key
|
|
@@ -11985,19 +12218,19 @@ class SqliteMaterializationStateReader {
|
|
|
11985
12218
|
const candidates = this.replacementSources(sourceId, [candidateMaterializationId], refs.map((ref) => ({ kind: "object", ref })));
|
|
11986
12219
|
return base.map((state) => ({
|
|
11987
12220
|
ref: state.ref,
|
|
11988
|
-
candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(
|
|
12221
|
+
candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(projectionEntityKey2({ kind: "object", ref: state.ref }))),
|
|
11989
12222
|
override: state.override,
|
|
11990
12223
|
effective: state.effective,
|
|
11991
12224
|
latestTelemetry: state.latestTelemetry
|
|
11992
12225
|
}));
|
|
11993
12226
|
}
|
|
11994
|
-
replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities) {
|
|
12227
|
+
replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities, incremental = false) {
|
|
11995
12228
|
const refs = identities.map((identity) => identity.ref);
|
|
11996
12229
|
const base = this.linkStates(refs);
|
|
11997
|
-
const replacements = this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })));
|
|
12230
|
+
const replacements = this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })), incremental);
|
|
11998
12231
|
const candidate = replacements.byMaterialization.get(candidateMaterializationId);
|
|
11999
12232
|
return base.map((state, index) => {
|
|
12000
|
-
const key =
|
|
12233
|
+
const key = projectionEntityKey2({ kind: "link", ref: state.ref });
|
|
12001
12234
|
return {
|
|
12002
12235
|
ref: state.ref,
|
|
12003
12236
|
candidateSource: replacements.owned.has(key) ? sourceLink(candidate?.get(key)) : state.source,
|
|
@@ -12021,32 +12254,23 @@ class SqliteMaterializationStateReader {
|
|
|
12021
12254
|
`).get(this.projectId, ref.source.objectTypeId, ref.source.primaryId, ref.linkId, ref.target.objectTypeId, ref.target.primaryId);
|
|
12022
12255
|
return row ? row.last_commit_id : undefined;
|
|
12023
12256
|
}
|
|
12024
|
-
replacementSources(sourceId, materializationIds, refs) {
|
|
12257
|
+
replacementSources(sourceId, materializationIds, refs, incremental = false) {
|
|
12025
12258
|
if (materializationIds.length === 0 || refs.length === 0) {
|
|
12026
12259
|
return { owned: new Set, byMaterialization: new Map };
|
|
12027
12260
|
}
|
|
12028
|
-
const
|
|
12029
|
-
|
|
12030
|
-
|
|
12031
|
-
|
|
12032
|
-
|
|
12033
|
-
|
|
12034
|
-
|
|
12035
|
-
|
|
12036
|
-
)
|
|
12037
|
-
SELECT rows.* FROM requested_materializations
|
|
12038
|
-
CROSS JOIN requested_entities
|
|
12039
|
-
CROSS JOIN ontology_source_rows AS rows
|
|
12040
|
-
ON rows.project_id = ?
|
|
12041
|
-
AND rows.source_id = ?
|
|
12042
|
-
AND rows.materialization_id = requested_materializations.materialization_id
|
|
12043
|
-
AND rows.entity_kind = requested_entities.entity_kind
|
|
12044
|
-
AND rows.entity_key = requested_entities.entity_key`).all(requestedEntities, requestedMaterializations, this.projectId, sourceId);
|
|
12261
|
+
const rows = replacementAssertionRows(this.db, {
|
|
12262
|
+
projectId: this.projectId,
|
|
12263
|
+
sourceId,
|
|
12264
|
+
materializationId: materializationIds[0],
|
|
12265
|
+
incremental,
|
|
12266
|
+
includePrevious: materializationIds.length > 1,
|
|
12267
|
+
refs
|
|
12268
|
+
});
|
|
12045
12269
|
const owned = new Set;
|
|
12046
12270
|
const byMaterialization = new Map;
|
|
12047
12271
|
for (const row of rows) {
|
|
12048
12272
|
const source = storedSource(row);
|
|
12049
|
-
const key =
|
|
12273
|
+
const key = projectionEntityKey2(source.assertion);
|
|
12050
12274
|
owned.add(key);
|
|
12051
12275
|
const materialization = byMaterialization.get(row.materialization_id) ?? new Map;
|
|
12052
12276
|
materialization.set(key, source);
|
|
@@ -12062,19 +12286,30 @@ class SqliteMaterializationStateReader {
|
|
|
12062
12286
|
this.prepareReplacementLinks(input);
|
|
12063
12287
|
}
|
|
12064
12288
|
prepareReplacementObjects(input) {
|
|
12289
|
+
const rows = replacementSourceRows({
|
|
12290
|
+
projectId: this.projectId,
|
|
12291
|
+
sourceId: input.sourceId,
|
|
12292
|
+
materializationId: input.candidateMaterializationId,
|
|
12293
|
+
incremental: input.incremental ?? false
|
|
12294
|
+
});
|
|
12065
12295
|
this.db.query(`
|
|
12066
12296
|
INSERT INTO ${SQLITE_REPLACEMENT_WORK_TABLE} (
|
|
12067
12297
|
session_id, entity_kind, identity_key, sort_key, diff_required
|
|
12068
12298
|
)
|
|
12069
12299
|
SELECT ?, 'object', json_array(object_type_id, primary_id),
|
|
12070
12300
|
MIN(entity_sort_key), 1
|
|
12071
|
-
FROM
|
|
12072
|
-
WHERE
|
|
12073
|
-
AND materialization_id IN (?, COALESCE(?, ''))
|
|
12301
|
+
FROM (${rows.sql}) AS rows
|
|
12302
|
+
WHERE entity_kind = 'object'
|
|
12074
12303
|
GROUP BY object_type_id, primary_id
|
|
12075
|
-
`).run(input.sessionId,
|
|
12304
|
+
`).run(input.sessionId, ...rows.values);
|
|
12076
12305
|
}
|
|
12077
12306
|
prepareReplacementLinks(input) {
|
|
12307
|
+
const rows = replacementSourceRows({
|
|
12308
|
+
projectId: this.projectId,
|
|
12309
|
+
sourceId: input.sourceId,
|
|
12310
|
+
materializationId: input.candidateMaterializationId,
|
|
12311
|
+
incremental: input.incremental ?? false
|
|
12312
|
+
});
|
|
12078
12313
|
this.db.query(`
|
|
12079
12314
|
WITH incident_objects AS (
|
|
12080
12315
|
SELECT DISTINCT
|
|
@@ -12085,9 +12320,8 @@ class SqliteMaterializationStateReader {
|
|
|
12085
12320
|
), replacement_links AS (
|
|
12086
12321
|
SELECT source_type_id, source_primary_id AS source_id, link_id,
|
|
12087
12322
|
target_type_id, target_primary_id AS target_id
|
|
12088
|
-
FROM
|
|
12089
|
-
WHERE
|
|
12090
|
-
AND materialization_id IN (?, COALESCE(?, ''))
|
|
12323
|
+
FROM (${rows.sql}) AS rows
|
|
12324
|
+
WHERE entity_kind = 'link'
|
|
12091
12325
|
), incident_links AS (
|
|
12092
12326
|
SELECT links.source_type_id, links.source_id, links.link_id,
|
|
12093
12327
|
links.target_type_id, links.target_id
|
|
@@ -12128,28 +12362,30 @@ class SqliteMaterializationStateReader {
|
|
|
12128
12362
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
12129
12363
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
12130
12364
|
FROM ontology_source_rows AS rows
|
|
12131
|
-
JOIN
|
|
12365
|
+
JOIN ontology_source_roots AS sources
|
|
12132
12366
|
ON sources.project_id = rows.project_id
|
|
12133
12367
|
AND sources.source_id = rows.source_id
|
|
12134
12368
|
AND sources.materialization_id = rows.materialization_id
|
|
12369
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12135
12370
|
JOIN incident_objects
|
|
12136
12371
|
ON incident_objects.object_type_id = rows.source_type_id
|
|
12137
12372
|
AND incident_objects.primary_id = rows.source_primary_id
|
|
12138
12373
|
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
12139
|
-
AND sources.
|
|
12374
|
+
AND sources.active = 1
|
|
12140
12375
|
UNION
|
|
12141
12376
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
12142
12377
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
12143
12378
|
FROM ontology_source_rows AS rows
|
|
12144
|
-
JOIN
|
|
12379
|
+
JOIN ontology_source_roots AS sources
|
|
12145
12380
|
ON sources.project_id = rows.project_id
|
|
12146
12381
|
AND sources.source_id = rows.source_id
|
|
12147
12382
|
AND sources.materialization_id = rows.materialization_id
|
|
12383
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12148
12384
|
JOIN incident_objects
|
|
12149
12385
|
ON incident_objects.object_type_id = rows.target_type_id
|
|
12150
12386
|
AND incident_objects.primary_id = rows.target_primary_id
|
|
12151
12387
|
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
12152
|
-
AND sources.
|
|
12388
|
+
AND sources.active = 1
|
|
12153
12389
|
), diff_links AS (
|
|
12154
12390
|
SELECT * FROM replacement_links
|
|
12155
12391
|
UNION SELECT * FROM incident_links
|
|
@@ -12187,7 +12423,7 @@ class SqliteMaterializationStateReader {
|
|
|
12187
12423
|
json_array(source_type_id, source_id, link_id, target_type_id, target_id),
|
|
12188
12424
|
sort_key, diff_required
|
|
12189
12425
|
FROM selected
|
|
12190
|
-
`).run(input.sessionId,
|
|
12426
|
+
`).run(input.sessionId, ...rows.values, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, input.sessionId);
|
|
12191
12427
|
}
|
|
12192
12428
|
replacementRows(input, cursor) {
|
|
12193
12429
|
return this.db.query(`
|
|
@@ -12225,9 +12461,9 @@ function activeSourceMap(rows) {
|
|
|
12225
12461
|
const result = new Map;
|
|
12226
12462
|
for (const row of rows) {
|
|
12227
12463
|
const source = storedSource(row);
|
|
12228
|
-
const key =
|
|
12464
|
+
const key = projectionEntityKey2(source.assertion);
|
|
12229
12465
|
if (result.has(key)) {
|
|
12230
|
-
throw new
|
|
12466
|
+
throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert ${key}.`);
|
|
12231
12467
|
}
|
|
12232
12468
|
result.set(key, source);
|
|
12233
12469
|
}
|
|
@@ -12242,7 +12478,7 @@ function activeScopeSourceMap(rows) {
|
|
|
12242
12478
|
const ref = source.assertion.ref;
|
|
12243
12479
|
const key = linkScopeSortKey(ref.source, ref.linkId);
|
|
12244
12480
|
if (result.has(key)) {
|
|
12245
|
-
throw new
|
|
12481
|
+
throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert cardinality-one link slot ${key}.`);
|
|
12246
12482
|
}
|
|
12247
12483
|
result.set(key, source);
|
|
12248
12484
|
}
|
|
@@ -12306,7 +12542,7 @@ function storedSource(row) {
|
|
|
12306
12542
|
}
|
|
12307
12543
|
function effectiveObjectSnapshot(row) {
|
|
12308
12544
|
if (!row.last_commit_id) {
|
|
12309
|
-
throw new
|
|
12545
|
+
throw new MaterializationConflictError3("effective-state", `Effective object ${row.object_type_id}:${row.primary_id} lacks materializer provenance.`);
|
|
12310
12546
|
}
|
|
12311
12547
|
return {
|
|
12312
12548
|
ref: objectRefFromColumns(row),
|
|
@@ -12319,7 +12555,7 @@ function effectiveObjectSnapshot(row) {
|
|
|
12319
12555
|
}
|
|
12320
12556
|
function effectiveLinkSnapshot(row) {
|
|
12321
12557
|
if (!row.last_commit_id) {
|
|
12322
|
-
throw new
|
|
12558
|
+
throw new MaterializationConflictError3("effective-state", `Effective link ${linkRefKey(linkRefFromColumns(row))} lacks materializer provenance.`);
|
|
12323
12559
|
}
|
|
12324
12560
|
return {
|
|
12325
12561
|
ref: linkRefFromColumns(row),
|
|
@@ -12333,7 +12569,7 @@ function effectiveLinkSnapshot(row) {
|
|
|
12333
12569
|
}
|
|
12334
12570
|
function storedPoint(row) {
|
|
12335
12571
|
if (!row.last_commit_id) {
|
|
12336
|
-
throw new
|
|
12572
|
+
throw new MaterializationConflictError3("timeseries-point", `Telemetry point ${telemetryPointSortKey({
|
|
12337
12573
|
object: { objectTypeId: row.object_type_id, primaryId: row.object_id },
|
|
12338
12574
|
propertyId: row.property_id
|
|
12339
12575
|
}, row.at)} lacks materializer provenance.`);
|
|
@@ -12372,7 +12608,7 @@ class SqliteMaterializationSessions {
|
|
|
12372
12608
|
}
|
|
12373
12609
|
create(header) {
|
|
12374
12610
|
if (!this.context?.active) {
|
|
12375
|
-
throw new
|
|
12611
|
+
throw new MaterializationConflictError4("effective-state", "Materialization sessions require an active storage transaction.");
|
|
12376
12612
|
}
|
|
12377
12613
|
this.ensureTables();
|
|
12378
12614
|
const session = new SqliteMaterializationSessionState(structuredClone(header), this.context.id);
|
|
@@ -12384,7 +12620,7 @@ class SqliteMaterializationSessions {
|
|
|
12384
12620
|
require(session) {
|
|
12385
12621
|
const value = this.sessions.get(session.providerToken);
|
|
12386
12622
|
if (!value || !value.active || !this.context?.active || value.transactionId !== this.context.id) {
|
|
12387
|
-
throw new
|
|
12623
|
+
throw new MaterializationConflictError4("effective-state", "Materialization session is inactive.");
|
|
12388
12624
|
}
|
|
12389
12625
|
return value;
|
|
12390
12626
|
}
|
|
@@ -12444,7 +12680,7 @@ class SqliteMaterializationSessions {
|
|
|
12444
12680
|
const session = this.require(input.session);
|
|
12445
12681
|
const stream = session.workStreams[input.order];
|
|
12446
12682
|
if (stream.started) {
|
|
12447
|
-
throw new
|
|
12683
|
+
throw new MaterializationConflictError4("effective-state", `Materialization ${input.order} work may only be streamed once per session.`);
|
|
12448
12684
|
}
|
|
12449
12685
|
stream.started = true;
|
|
12450
12686
|
session.workSealed = true;
|
|
@@ -12721,7 +12957,7 @@ function recordSummary(summary, record, columns) {
|
|
|
12721
12957
|
// src/ontology-storage/materialization-writer.ts
|
|
12722
12958
|
import {
|
|
12723
12959
|
linkRefKey as linkRefKey2,
|
|
12724
|
-
MaterializationConflictError as
|
|
12960
|
+
MaterializationConflictError as MaterializationConflictError5,
|
|
12725
12961
|
objectRefKey as objectRefKey2,
|
|
12726
12962
|
telemetryPointKey
|
|
12727
12963
|
} from "@sixb/core/internal/materialization";
|
|
@@ -12893,7 +13129,7 @@ class SqliteMaterializationWriter {
|
|
|
12893
13129
|
`).run(projectId, point.series.object.objectTypeId, point.series.object.primaryId, point.series.propertyId, canonicalJson(point.value), point.unit ?? null, point.at, point.lastCommitId);
|
|
12894
13130
|
} catch (error) {
|
|
12895
13131
|
if (isSqliteConstraintError(error)) {
|
|
12896
|
-
throw new
|
|
13132
|
+
throw new MaterializationConflictError5("timeseries-point", `Telemetry point ${telemetryPointKey(point.series, point.at)} changed.`);
|
|
12897
13133
|
}
|
|
12898
13134
|
throw error;
|
|
12899
13135
|
}
|
|
@@ -12955,13 +13191,13 @@ class SqliteOntologyMaterializationStorage {
|
|
|
12955
13191
|
const linkScopeRevisions = reader.linkScopeRevisions(input.expected.linkScopes);
|
|
12956
13192
|
for (const [index, expected] of input.expected.linkScopes.entries()) {
|
|
12957
13193
|
if (linkScopeRevisions[index]?.fingerprint !== expected.fingerprint) {
|
|
12958
|
-
throw new
|
|
13194
|
+
throw new MaterializationConflictError6("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
|
|
12959
13195
|
}
|
|
12960
13196
|
}
|
|
12961
13197
|
for (const expected of input.expected.points) {
|
|
12962
13198
|
const point = reader.exactPoint(expected.series, expected.at);
|
|
12963
13199
|
if ((point?.lastCommitId ?? null) !== expected.lastCommitId) {
|
|
12964
|
-
throw new
|
|
13200
|
+
throw new MaterializationConflictError6("timeseries-point", `Telemetry point ${telemetryPointKey2(expected.series, expected.at)} changed.`);
|
|
12965
13201
|
}
|
|
12966
13202
|
}
|
|
12967
13203
|
return session.publicSession();
|
|
@@ -13019,10 +13255,10 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13019
13255
|
const reader = new SqliteMaterializationStateReader(this.db, session.header.commit.projectId);
|
|
13020
13256
|
if (input.entityKind === "object") {
|
|
13021
13257
|
if (replacement.projectionKind !== "object") {
|
|
13022
|
-
throw new
|
|
13258
|
+
throw new MaterializationConflictError6("source-materialization", "Link projection replacement cannot stream object state.");
|
|
13023
13259
|
}
|
|
13024
13260
|
if (replacement.objectStreamStarted) {
|
|
13025
|
-
throw new
|
|
13261
|
+
throw new MaterializationConflictError6("effective-state", "Replacement object state may only be streamed once per session.");
|
|
13026
13262
|
}
|
|
13027
13263
|
replacement.objectStreamStarted = true;
|
|
13028
13264
|
for (const identities of reader.replacementIdentities({
|
|
@@ -13030,6 +13266,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13030
13266
|
sourceId: replacement.sourceId,
|
|
13031
13267
|
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
13032
13268
|
previousMaterializationId: replacement.previousMaterializationId,
|
|
13269
|
+
incremental: replacement.incremental,
|
|
13033
13270
|
kind: "object",
|
|
13034
13271
|
pageRows: input.pageRows
|
|
13035
13272
|
})) {
|
|
@@ -13048,10 +13285,10 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13048
13285
|
return;
|
|
13049
13286
|
}
|
|
13050
13287
|
if (replacement.projectionKind === "object" && !replacement.objectStreamCompleted) {
|
|
13051
|
-
throw new
|
|
13288
|
+
throw new MaterializationConflictError6("effective-state", "Object projection replacement must fully stream object state before link state.");
|
|
13052
13289
|
}
|
|
13053
13290
|
if (replacement.linkStreamStarted) {
|
|
13054
|
-
throw new
|
|
13291
|
+
throw new MaterializationConflictError6("effective-state", "Replacement link state may only be streamed once per session.");
|
|
13055
13292
|
}
|
|
13056
13293
|
replacement.linkStreamStarted = true;
|
|
13057
13294
|
const materializationIds = [
|
|
@@ -13063,6 +13300,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13063
13300
|
sourceId: replacement.sourceId,
|
|
13064
13301
|
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
13065
13302
|
previousMaterializationId: replacement.previousMaterializationId,
|
|
13303
|
+
incremental: replacement.incremental,
|
|
13066
13304
|
kind: "link",
|
|
13067
13305
|
pageRows: input.pageRows
|
|
13068
13306
|
})) {
|
|
@@ -13073,7 +13311,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13073
13311
|
}
|
|
13074
13312
|
return identity;
|
|
13075
13313
|
});
|
|
13076
|
-
const links = reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities);
|
|
13314
|
+
const links = reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities, replacement.incremental);
|
|
13077
13315
|
yield { objects: [], links };
|
|
13078
13316
|
}
|
|
13079
13317
|
this.sessions.require(input.session);
|
|
@@ -13125,14 +13363,14 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13125
13363
|
requireReplacement(session, input) {
|
|
13126
13364
|
if (session.replacement) {
|
|
13127
13365
|
if (session.replacement.sourceId !== input.source.projectionId || session.replacement.candidateMaterializationId !== input.candidateMaterializationId) {
|
|
13128
|
-
throw new
|
|
13366
|
+
throw new MaterializationConflictError6("source-materialization", "Materialization session already owns another replacement union.");
|
|
13129
13367
|
}
|
|
13130
13368
|
return session.replacement;
|
|
13131
13369
|
}
|
|
13132
13370
|
const projectId = session.header.commit.projectId;
|
|
13133
13371
|
const candidate = this.getSource(projectId, input.source.projectionId, input.candidateMaterializationId);
|
|
13134
13372
|
if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
|
|
13135
|
-
throw new
|
|
13373
|
+
throw new MaterializationConflictError6("source-materialization", `Candidate source materialization '${input.candidateMaterializationId}' is missing or is not ready.`);
|
|
13136
13374
|
}
|
|
13137
13375
|
assertProjectionExecution(this.db, {
|
|
13138
13376
|
projectId,
|
|
@@ -13142,12 +13380,13 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13142
13380
|
});
|
|
13143
13381
|
const previous = this.getActiveSource(projectId, input.source.projectionId);
|
|
13144
13382
|
if (candidate.protocol !== "replacement" || previous && (previous.protocol !== candidate.protocol || previous.projection_kind !== candidate.projection_kind)) {
|
|
13145
|
-
throw new
|
|
13383
|
+
throw new MaterializationConflictError6("source-materialization", "Source replacement kind or protocol does not match its active materialization.");
|
|
13146
13384
|
}
|
|
13147
13385
|
session.replacement = {
|
|
13148
13386
|
sourceId: input.source.projectionId,
|
|
13149
13387
|
candidateMaterializationId: input.candidateMaterializationId,
|
|
13150
13388
|
previousMaterializationId: previous?.materialization_id ?? null,
|
|
13389
|
+
incremental: candidate.base_materialization_id !== null,
|
|
13151
13390
|
projectionKind: candidate.projection_kind,
|
|
13152
13391
|
objectStreamStarted: false,
|
|
13153
13392
|
objectStreamCompleted: false,
|
|
@@ -13176,38 +13415,38 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13176
13415
|
if (!duplicate)
|
|
13177
13416
|
return;
|
|
13178
13417
|
if (duplicate.duplicate === "origin") {
|
|
13179
|
-
throw new
|
|
13418
|
+
throw new MaterializationConflictError6("run-correlation", "Ontology commit origin already has an authoritative commit.");
|
|
13180
13419
|
}
|
|
13181
|
-
throw new
|
|
13420
|
+
throw new MaterializationConflictError6("idempotency", duplicate.duplicate === "id" ? `Ontology commit '${header.commit.id}' already exists.` : "Ontology idempotency key already exists.");
|
|
13182
13421
|
}
|
|
13183
13422
|
assertSource(expected, projectId) {
|
|
13184
13423
|
const active = this.getActiveSource(projectId, expected.source.projectionId);
|
|
13185
13424
|
if ((active?.materialization_id ?? null) !== expected.activeMaterializationId || (active?.last_commit_id ?? null) !== expected.lastCommitId) {
|
|
13186
|
-
throw new
|
|
13425
|
+
throw new MaterializationConflictError6("projection-fence", `Source '${expected.source.projectionId}' changed.`);
|
|
13187
13426
|
}
|
|
13188
13427
|
}
|
|
13189
13428
|
assertObject(reader, expected) {
|
|
13190
13429
|
const row = reader.effectiveObjectRevision(expected.ref);
|
|
13191
13430
|
if (!expected.exists) {
|
|
13192
13431
|
if (row) {
|
|
13193
|
-
throw new
|
|
13432
|
+
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
|
|
13194
13433
|
}
|
|
13195
13434
|
return;
|
|
13196
13435
|
}
|
|
13197
13436
|
if (!row || row.version !== expected.version || row.lastCommitId !== expected.lastCommitId) {
|
|
13198
|
-
throw new
|
|
13437
|
+
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
|
|
13199
13438
|
}
|
|
13200
13439
|
}
|
|
13201
13440
|
assertLink(reader, expected) {
|
|
13202
13441
|
const lastCommitId = reader.effectiveLinkLastCommit(expected.ref);
|
|
13203
13442
|
if (!expected.exists) {
|
|
13204
13443
|
if (lastCommitId !== undefined) {
|
|
13205
|
-
throw new
|
|
13444
|
+
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
|
|
13206
13445
|
}
|
|
13207
13446
|
return;
|
|
13208
13447
|
}
|
|
13209
13448
|
if (lastCommitId === undefined || lastCommitId !== expected.lastCommitId) {
|
|
13210
|
-
throw new
|
|
13449
|
+
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
|
|
13211
13450
|
}
|
|
13212
13451
|
}
|
|
13213
13452
|
async assertFinalization(session, input) {
|
|
@@ -13301,7 +13540,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13301
13540
|
assertSourceActivationCorrelation(session, activation);
|
|
13302
13541
|
const candidate = this.getSource(commit.projectId, activation.source.projectionId, activation.materializationId);
|
|
13303
13542
|
if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
|
|
13304
|
-
throw new
|
|
13543
|
+
throw new MaterializationConflictError6("source-materialization", "Source activation candidate is missing or is not ready.");
|
|
13305
13544
|
}
|
|
13306
13545
|
assertProjectionExecution(this.db, {
|
|
13307
13546
|
projectId: commit.projectId,
|
|
@@ -13313,6 +13552,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13313
13552
|
invalidCorrelation2("Source activation does not match its ready candidate identity.");
|
|
13314
13553
|
}
|
|
13315
13554
|
this.assertSource(activation.expected, commit.projectId);
|
|
13555
|
+
activateSourceRoots(this.db, commit.projectId, candidate, activation);
|
|
13316
13556
|
const previous = this.getActiveSource(commit.projectId, activation.source.projectionId);
|
|
13317
13557
|
if (previous) {
|
|
13318
13558
|
assertPinnedDatasetWatermark({
|
|
@@ -13352,7 +13592,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13352
13592
|
`).run(commit.projectId, commit.id, commit.idempotencyKey, commit.requestHash, commit.executionId, origin.kind, origin.runId, origin.batchOrdinal, canonicalJson(commit.origin), commit.actor === undefined ? null : canonicalJson(commit.actor), commit.ontologyRevision, commit.projectionRevision ?? null, commit.ownershipHash ?? null, canonicalJson(commit.intent), canonicalJson(input.finalization.result), commit.committedAt);
|
|
13353
13593
|
} catch (error) {
|
|
13354
13594
|
if (isSqliteConstraintError(error)) {
|
|
13355
|
-
throw new
|
|
13595
|
+
throw new MaterializationConflictError6("idempotency", "Ontology commit identity already exists.");
|
|
13356
13596
|
}
|
|
13357
13597
|
throw error;
|
|
13358
13598
|
}
|
|
@@ -13372,8 +13612,8 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13372
13612
|
// src/ontology-storage/outbox.ts
|
|
13373
13613
|
import { serializeSixbFailure as serializeSixbFailure2 } from "@sixb/core/internal/errors";
|
|
13374
13614
|
import {
|
|
13375
|
-
MaterializationConflictError as
|
|
13376
|
-
MaterializationValidationError
|
|
13615
|
+
MaterializationConflictError as MaterializationConflictError7,
|
|
13616
|
+
MaterializationValidationError as MaterializationValidationError2
|
|
13377
13617
|
} from "@sixb/core/internal/materialization";
|
|
13378
13618
|
import { ONTOLOGY_OUTBOX_FAILURE_CODES as ONTOLOGY_OUTBOX_FAILURE_CODES2 } from "@sixb/core/storage";
|
|
13379
13619
|
class SqliteOntologyOutboxStorage {
|
|
@@ -13391,25 +13631,31 @@ class SqliteOntologyOutboxStorage {
|
|
|
13391
13631
|
const leaseExpiry = assertTimestamp(input.leaseExpiresAt, "Ontology outbox lease expiry");
|
|
13392
13632
|
assertNonblank(input.leaseId, "Ontology outbox lease id");
|
|
13393
13633
|
if (leaseExpiry <= now) {
|
|
13394
|
-
throw new
|
|
13634
|
+
throw new MaterializationValidationError2("Ontology outbox lease expiry must be later than the claim time.");
|
|
13395
13635
|
}
|
|
13636
|
+
const eligible = this.db.query(`SELECT CAST(rowid AS TEXT) AS rowId
|
|
13637
|
+
FROM ontology_outbox INDEXED BY idx_ontology_outbox_claim
|
|
13638
|
+
WHERE project_id = ? AND published_at IS NULL AND available_at <= ?
|
|
13639
|
+
AND (lease_expires_at IS NULL OR lease_expires_at <= ?)
|
|
13640
|
+
LIMIT ?`).all(input.projectId, input.now, input.now, input.limit + 1);
|
|
13641
|
+
if (eligible.length === 0)
|
|
13642
|
+
return [];
|
|
13643
|
+
const selected = eligible.length <= input.limit ? eligible : this.db.query(`SELECT CAST(rowid AS TEXT) AS rowId
|
|
13644
|
+
FROM ontology_outbox INDEXED BY idx_ontology_outbox_publication_order
|
|
13645
|
+
WHERE project_id = ? AND published_at IS NULL AND available_at <= ?
|
|
13646
|
+
AND (lease_expires_at IS NULL OR lease_expires_at <= ?)
|
|
13647
|
+
ORDER BY created_at, commit_id, commit_ordinal
|
|
13648
|
+
LIMIT ?`).all(input.projectId, input.now, input.now, input.limit);
|
|
13396
13649
|
const rows = this.db.query(`
|
|
13397
13650
|
UPDATE ontology_outbox
|
|
13398
13651
|
SET attempts = attempts + 1, lease_id = ?, lease_expires_at = ?
|
|
13399
|
-
WHERE rowid IN (
|
|
13400
|
-
SELECT rowid
|
|
13401
|
-
FROM ontology_outbox
|
|
13402
|
-
WHERE project_id = ? AND published_at IS NULL AND available_at <= ?
|
|
13403
|
-
AND (lease_expires_at IS NULL OR lease_expires_at <= ?)
|
|
13404
|
-
ORDER BY created_at, commit_id, commit_ordinal
|
|
13405
|
-
LIMIT ?
|
|
13406
|
-
)
|
|
13652
|
+
WHERE rowid IN (SELECT CAST(value AS INTEGER) FROM json_each(?))
|
|
13407
13653
|
RETURNING envelope, available_at, attempts, lease_id, lease_expires_at,
|
|
13408
13654
|
published_at, last_failure, created_at
|
|
13409
|
-
`).all(input.leaseId, input.leaseExpiresAt,
|
|
13655
|
+
`).all(input.leaseId, input.leaseExpiresAt, JSON.stringify(selected.map((row) => row.rowId)));
|
|
13410
13656
|
return rows.map((row) => outboxRecord(row)).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.envelope.id.localeCompare(right.envelope.id)).map((record) => {
|
|
13411
13657
|
if (record.leaseId === null || record.leaseExpiresAt === null) {
|
|
13412
|
-
throw new
|
|
13658
|
+
throw new MaterializationConflictError7("outbox-lease", "Ontology outbox claim returned an unpaired lease.");
|
|
13413
13659
|
}
|
|
13414
13660
|
return { ...record, leaseId: record.leaseId, leaseExpiresAt: record.leaseExpiresAt };
|
|
13415
13661
|
});
|
|
@@ -13424,8 +13670,9 @@ class SqliteOntologyOutboxStorage {
|
|
|
13424
13670
|
WITH requested(id) AS MATERIALIZED (
|
|
13425
13671
|
SELECT CAST(value AS TEXT) FROM json_each(?)
|
|
13426
13672
|
), leased(id) AS MATERIALIZED (
|
|
13427
|
-
|
|
13428
|
-
|
|
13673
|
+
-- Drive validation from the bounded ID list even before ANALYZE.
|
|
13674
|
+
SELECT outbox.id FROM requested
|
|
13675
|
+
CROSS JOIN ontology_outbox AS outbox USING (id)
|
|
13429
13676
|
WHERE outbox.project_id = ? AND outbox.lease_id = ?
|
|
13430
13677
|
AND outbox.lease_expires_at IS NOT NULL
|
|
13431
13678
|
), eligible(id) AS (
|
|
@@ -13450,8 +13697,9 @@ class SqliteOntologyOutboxStorage {
|
|
|
13450
13697
|
WITH requested(id) AS MATERIALIZED (
|
|
13451
13698
|
SELECT CAST(value AS TEXT) FROM json_each(?)
|
|
13452
13699
|
), leased(id) AS MATERIALIZED (
|
|
13453
|
-
|
|
13454
|
-
|
|
13700
|
+
-- Drive validation from the bounded ID list even before ANALYZE.
|
|
13701
|
+
SELECT outbox.id FROM requested
|
|
13702
|
+
CROSS JOIN ontology_outbox AS outbox USING (id)
|
|
13455
13703
|
WHERE outbox.project_id = ? AND outbox.lease_id = ?
|
|
13456
13704
|
AND outbox.lease_expires_at IS NOT NULL
|
|
13457
13705
|
), eligible(id) AS (
|
|
@@ -13508,7 +13756,7 @@ function validateLeaseIds(ids) {
|
|
|
13508
13756
|
for (const id of ids) {
|
|
13509
13757
|
assertNonblank(id, "Ontology outbox event id");
|
|
13510
13758
|
if (seen.has(id)) {
|
|
13511
|
-
throw new
|
|
13759
|
+
throw new MaterializationValidationError2(`Ontology outbox lease batch repeats event '${id}'.`);
|
|
13512
13760
|
}
|
|
13513
13761
|
seen.add(id);
|
|
13514
13762
|
}
|
|
@@ -13519,11 +13767,11 @@ function assertLeaseInput(input) {
|
|
|
13519
13767
|
assertNonblank(input.leaseId, "Ontology outbox lease id");
|
|
13520
13768
|
}
|
|
13521
13769
|
function leaseConflict() {
|
|
13522
|
-
return new
|
|
13770
|
+
return new MaterializationConflictError7("outbox-lease", "Ontology outbox lease does not match.");
|
|
13523
13771
|
}
|
|
13524
13772
|
|
|
13525
13773
|
// src/ontology-storage/sources.ts
|
|
13526
|
-
import { MaterializationValidationError as
|
|
13774
|
+
import { MaterializationValidationError as MaterializationValidationError3 } from "@sixb/core/internal/materialization";
|
|
13527
13775
|
import {
|
|
13528
13776
|
assertSourceBeginInput as assertBeginInput,
|
|
13529
13777
|
assertSourceExecutionIdentity as assertExecutionIdentity,
|
|
@@ -13537,8 +13785,53 @@ import {
|
|
|
13537
13785
|
sourceMaterializationIdentity,
|
|
13538
13786
|
sourceStageRow,
|
|
13539
13787
|
sourceStageRows,
|
|
13540
|
-
utf8SortKey
|
|
13788
|
+
utf8SortKey as utf8SortKey2
|
|
13541
13789
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
13790
|
+
|
|
13791
|
+
// src/ontology-storage/source-cleanup.ts
|
|
13792
|
+
function cleanupSourceVersions(db, input) {
|
|
13793
|
+
let remaining = input.limit;
|
|
13794
|
+
let rowsDeleted = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
13795
|
+
SELECT rows.rowid FROM ontology_source_roots AS roots
|
|
13796
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
|
|
13797
|
+
CROSS JOIN ontology_source_rows AS rows
|
|
13798
|
+
ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
13799
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
13800
|
+
WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
|
|
13801
|
+
AND sources.status IN ('superseded', 'abandoned')
|
|
13802
|
+
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key, rows.entity_sort_key
|
|
13803
|
+
LIMIT ?
|
|
13804
|
+
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
13805
|
+
remaining -= rowsDeleted;
|
|
13806
|
+
if (remaining > 0) {
|
|
13807
|
+
const removed = db.query(`DELETE FROM ontology_source_roots WHERE rowid IN (
|
|
13808
|
+
SELECT roots.rowid FROM ontology_source_roots AS roots
|
|
13809
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
|
|
13810
|
+
WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
|
|
13811
|
+
AND sources.status IN ('superseded', 'abandoned')
|
|
13812
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
13813
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
13814
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key)
|
|
13815
|
+
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key LIMIT ?
|
|
13816
|
+
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
13817
|
+
rowsDeleted += removed;
|
|
13818
|
+
remaining -= removed;
|
|
13819
|
+
}
|
|
13820
|
+
const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE rowid IN (
|
|
13821
|
+
SELECT sources.rowid FROM ontology_sources AS sources
|
|
13822
|
+
WHERE project_id = ? AND status IN ('superseded', 'abandoned') AND terminal_at < ?
|
|
13823
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_roots AS roots
|
|
13824
|
+
WHERE roots.project_id = sources.project_id AND roots.source_id = sources.source_id
|
|
13825
|
+
AND roots.materialization_id = sources.materialization_id)
|
|
13826
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
13827
|
+
WHERE rows.project_id = sources.project_id AND rows.source_id = sources.source_id
|
|
13828
|
+
AND rows.materialization_id = sources.materialization_id)
|
|
13829
|
+
ORDER BY terminal_at, source_id, materialization_id LIMIT ?
|
|
13830
|
+
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
13831
|
+
return { rowsDeleted, materializationsDeleted };
|
|
13832
|
+
}
|
|
13833
|
+
|
|
13834
|
+
// src/ontology-storage/sources.ts
|
|
13542
13835
|
class SqliteOntologySourceStorage {
|
|
13543
13836
|
db;
|
|
13544
13837
|
runRootOperation;
|
|
@@ -13573,10 +13866,10 @@ class SqliteOntologySourceStorage {
|
|
|
13573
13866
|
dataset_id, dataset_version_id, dataset_version_created_at,
|
|
13574
13867
|
projection_revision, ownership_hash, ontology_revision,
|
|
13575
13868
|
root_count, assertion_count, created_at, ready_at, activated_at,
|
|
13576
|
-
terminal_at, last_commit_id, updated_at
|
|
13869
|
+
terminal_at, last_commit_id, updated_at, base_materialization_id, base_commit_id
|
|
13577
13870
|
) VALUES (?, ?, ?, ?, ?, 'replacement', 'staging', ?, ?, ?, ?, ?, ?, ?,
|
|
13578
|
-
NULL, NULL, ?, NULL, NULL, NULL, NULL, ?)
|
|
13579
|
-
`).run(input.projectId, input.source.projectionId, input.materializationId, input.execution.projectionRunId, input.projectionKind, input.execution.executionToken, input.datasetVersion.datasetId, input.datasetVersion.versionId, input.datasetVersion.createdAt, input.projectionRevision, input.ownershipHash, input.ontologyRevision, input.createdAt, input.createdAt);
|
|
13871
|
+
NULL, NULL, ?, NULL, NULL, NULL, NULL, ?, ?, ?)
|
|
13872
|
+
`).run(input.projectId, input.source.projectionId, input.materializationId, input.execution.projectionRunId, input.projectionKind, input.execution.executionToken, input.datasetVersion.datasetId, input.datasetVersion.versionId, input.datasetVersion.createdAt, input.projectionRevision, input.ownershipHash, input.ontologyRevision, input.createdAt, input.createdAt, input.base?.materializationId ?? null, input.base?.lastCommitId ?? null);
|
|
13580
13873
|
return sourceRecord(this.requireManifest(input.projectId, input.source.projectionId, input.materializationId));
|
|
13581
13874
|
});
|
|
13582
13875
|
}
|
|
@@ -13592,6 +13885,7 @@ class SqliteOntologySourceStorage {
|
|
|
13592
13885
|
const rows = sourceStageRows(manifest.projection_kind, input.rows);
|
|
13593
13886
|
const existing = this.findStageRows(input, rows);
|
|
13594
13887
|
const { pending, unchanged } = reconcileSourceStageRows(rows, existing);
|
|
13888
|
+
stageSourceRoots(this.db, manifest, input);
|
|
13595
13889
|
this.insertStageRows(input, pending);
|
|
13596
13890
|
return { inserted: pending.length, unchanged };
|
|
13597
13891
|
});
|
|
@@ -13617,7 +13911,7 @@ class SqliteOntologySourceStorage {
|
|
|
13617
13911
|
throw sourceConflict(`Source materialization '${input.materializationId}' cannot transition from '${manifest.status}' to 'ready'.`);
|
|
13618
13912
|
}
|
|
13619
13913
|
if (input.readyAt < manifest.created_at) {
|
|
13620
|
-
throw new
|
|
13914
|
+
throw new MaterializationValidationError3("Source readyAt cannot precede source createdAt.");
|
|
13621
13915
|
}
|
|
13622
13916
|
this.assertReadyState(manifest, input.rootCount, input.assertionCount);
|
|
13623
13917
|
const result = this.db.query(`
|
|
@@ -13656,55 +13950,7 @@ class SqliteOntologySourceStorage {
|
|
|
13656
13950
|
assertNonblank(input.projectId, "Terminal source cleanup project id");
|
|
13657
13951
|
assertTimestamp(input.terminalBefore, "Terminal source cleanup cutoff", true);
|
|
13658
13952
|
assertPositiveInteger(input.limit, "Terminal source cleanup limit");
|
|
13659
|
-
|
|
13660
|
-
SELECT * FROM ontology_sources
|
|
13661
|
-
WHERE project_id = ? AND status IN ('superseded', 'abandoned')
|
|
13662
|
-
AND terminal_at < ?
|
|
13663
|
-
ORDER BY terminal_at, source_id, materialization_id
|
|
13664
|
-
LIMIT ?
|
|
13665
|
-
`).all(input.projectId, input.terminalBefore, input.limit);
|
|
13666
|
-
let remaining = input.limit;
|
|
13667
|
-
let rowsDeleted = 0;
|
|
13668
|
-
let materializationsDeleted = 0;
|
|
13669
|
-
for (const manifest of manifests) {
|
|
13670
|
-
if (remaining === 0)
|
|
13671
|
-
break;
|
|
13672
|
-
const deleted = this.db.query(`
|
|
13673
|
-
DELETE FROM ontology_source_rows
|
|
13674
|
-
WHERE rowid IN (
|
|
13675
|
-
SELECT rows.rowid
|
|
13676
|
-
FROM ontology_source_rows AS rows
|
|
13677
|
-
JOIN ontology_sources AS sources
|
|
13678
|
-
ON sources.project_id = rows.project_id
|
|
13679
|
-
AND sources.source_id = rows.source_id
|
|
13680
|
-
AND sources.materialization_id = rows.materialization_id
|
|
13681
|
-
WHERE rows.project_id = ? AND rows.source_id = ? AND rows.materialization_id = ?
|
|
13682
|
-
AND sources.status IN ('superseded', 'abandoned')
|
|
13683
|
-
AND sources.terminal_at = ? AND sources.terminal_at < ?
|
|
13684
|
-
ORDER BY rows.entity_sort_key
|
|
13685
|
-
LIMIT ?
|
|
13686
|
-
)
|
|
13687
|
-
`).run(manifest.project_id, manifest.source_id, manifest.materialization_id, manifest.terminal_at, input.terminalBefore, remaining).changes;
|
|
13688
|
-
rowsDeleted += deleted;
|
|
13689
|
-
remaining -= deleted;
|
|
13690
|
-
if (remaining === 0)
|
|
13691
|
-
break;
|
|
13692
|
-
const removed = this.db.query(`
|
|
13693
|
-
DELETE FROM ontology_sources
|
|
13694
|
-
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
13695
|
-
AND status IN ('superseded', 'abandoned')
|
|
13696
|
-
AND terminal_at = ? AND terminal_at < ?
|
|
13697
|
-
AND NOT EXISTS (
|
|
13698
|
-
SELECT 1 FROM ontology_source_rows AS rows
|
|
13699
|
-
WHERE rows.project_id = ontology_sources.project_id
|
|
13700
|
-
AND rows.source_id = ontology_sources.source_id
|
|
13701
|
-
AND rows.materialization_id = ontology_sources.materialization_id
|
|
13702
|
-
)
|
|
13703
|
-
`).run(manifest.project_id, manifest.source_id, manifest.materialization_id, manifest.terminal_at, input.terminalBefore).changes;
|
|
13704
|
-
materializationsDeleted += removed;
|
|
13705
|
-
remaining -= removed;
|
|
13706
|
-
}
|
|
13707
|
-
return { rowsDeleted, materializationsDeleted };
|
|
13953
|
+
return cleanupSourceVersions(this.db, input);
|
|
13708
13954
|
});
|
|
13709
13955
|
}
|
|
13710
13956
|
async summarizeTerminal(input) {
|
|
@@ -13748,7 +13994,7 @@ class SqliteOntologySourceStorage {
|
|
|
13748
13994
|
}
|
|
13749
13995
|
transitionToAbandoned(manifest, abandonedAt) {
|
|
13750
13996
|
if (abandonedAt < manifest.created_at || manifest.ready_at !== null && abandonedAt < manifest.ready_at) {
|
|
13751
|
-
throw new
|
|
13997
|
+
throw new MaterializationValidationError3("Source abandonedAt cannot precede source creation or readiness.");
|
|
13752
13998
|
}
|
|
13753
13999
|
const changed = this.db.query(`
|
|
13754
14000
|
UPDATE ontology_sources
|
|
@@ -13759,19 +14005,23 @@ class SqliteOntologySourceStorage {
|
|
|
13759
14005
|
if (changed !== 1) {
|
|
13760
14006
|
throw sourceConflict(`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`);
|
|
13761
14007
|
}
|
|
14008
|
+
this.db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
14009
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND active = 0`).run(abandonedAt, manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
13762
14010
|
return sourceRecord(this.requireManifest(manifest.project_id, manifest.source_id, manifest.materialization_id));
|
|
13763
14011
|
}
|
|
13764
14012
|
assertReadyState(manifest, rootCount, assertionCount) {
|
|
13765
14013
|
const counts = this.db.query(`
|
|
13766
|
-
SELECT
|
|
14014
|
+
SELECT (SELECT COUNT(*) FROM ontology_source_rows
|
|
14015
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?) AS assertions, COUNT(*) AS roots,
|
|
13767
14016
|
COUNT(DISTINCT staging_ordinal) AS ordinals,
|
|
13768
14017
|
MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
|
|
13769
|
-
FROM
|
|
14018
|
+
FROM ontology_source_roots
|
|
13770
14019
|
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
13771
|
-
`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
14020
|
+
`).get(manifest.project_id, manifest.source_id, manifest.materialization_id, manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
13772
14021
|
if (counts.assertions !== assertionCount || counts.roots !== rootCount || counts.ordinals !== rootCount || rootCount > 0 && (counts.min_ordinal !== 0 || counts.max_ordinal !== rootCount - 1)) {
|
|
13773
|
-
throw new
|
|
14022
|
+
throw new MaterializationValidationError3("Source ready counts do not match the staged roots and assertions.");
|
|
13774
14023
|
}
|
|
14024
|
+
assertSourceRootCoverage(this.db, manifest);
|
|
13775
14025
|
const invalid = this.db.query(manifest.projection_kind === "link" ? `
|
|
13776
14026
|
SELECT root_key
|
|
13777
14027
|
FROM ontology_source_rows
|
|
@@ -13797,15 +14047,15 @@ class SqliteOntologySourceStorage {
|
|
|
13797
14047
|
LIMIT 1
|
|
13798
14048
|
`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
13799
14049
|
if (invalid) {
|
|
13800
|
-
throw new
|
|
14050
|
+
throw new MaterializationValidationError3(manifest.projection_kind === "link" ? "Link projection roots must contain exactly their matching link assertion." : "Object projection roots must contain exactly their matching object assertion plus links sourced from that root.");
|
|
13801
14051
|
}
|
|
13802
14052
|
}
|
|
13803
14053
|
findStageRows(input, rows) {
|
|
13804
14054
|
if (rows.length === 0)
|
|
13805
14055
|
return [];
|
|
13806
|
-
const rootSortKeys = [...new Set(rows.map((row) =>
|
|
14056
|
+
const rootSortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.rootKey)))];
|
|
13807
14057
|
const ordinals = [...new Set(rows.map((row) => row.row.stagingOrdinal))];
|
|
13808
|
-
const entitySortKeys = [...new Set(rows.map((row) =>
|
|
14058
|
+
const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.entityKey)))];
|
|
13809
14059
|
const existing = this.db.query(`
|
|
13810
14060
|
SELECT * FROM ontology_source_rows
|
|
13811
14061
|
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
@@ -13826,10 +14076,10 @@ class SqliteOntologySourceStorage {
|
|
|
13826
14076
|
return {
|
|
13827
14077
|
entityKind: row.assertion.kind,
|
|
13828
14078
|
entityKey,
|
|
13829
|
-
entitySortKey:
|
|
14079
|
+
entitySortKey: utf8SortKey2(entityKey),
|
|
13830
14080
|
rootKind: row.root.kind,
|
|
13831
14081
|
rootKey,
|
|
13832
|
-
rootSortKey:
|
|
14082
|
+
rootSortKey: utf8SortKey2(rootKey),
|
|
13833
14083
|
stagingOrdinal: row.stagingOrdinal,
|
|
13834
14084
|
root: row.root,
|
|
13835
14085
|
assertion: row.assertion,
|
|
@@ -14341,7 +14591,7 @@ class SqliteProjectionRunStorage {
|
|
|
14341
14591
|
input_exhausted = ?, missing_target_object_type_id = NULL,
|
|
14342
14592
|
missing_target_object_id = NULL, missing_target_batch_ordinal = NULL,
|
|
14343
14593
|
missing_target_first_seen_at = NULL, source_rows_read = 0,
|
|
14344
|
-
source_rows_skipped = 0, error = NULL
|
|
14594
|
+
source_rows_skipped = 0, source_changes_read = NULL, error = NULL
|
|
14345
14595
|
WHERE project_id = ? AND id = ? AND status = 'failed'
|
|
14346
14596
|
AND json_extract(error, '$.code') = 'queue.enqueue_failed'
|
|
14347
14597
|
`).run(queuedAt.toISOString(), input.fixedBatchSize === undefined ? null : 0, input.fixedBatchSize === undefined ? null : 0, input.fixedBatchSize === undefined ? null : 0, input.projectId, input.id);
|
|
@@ -14436,10 +14686,11 @@ class SqliteProjectionRunStorage {
|
|
|
14436
14686
|
execution_token = NULL,
|
|
14437
14687
|
source_rows_read = ?,
|
|
14438
14688
|
source_rows_skipped = ?,
|
|
14689
|
+
source_changes_read = ?,
|
|
14439
14690
|
input_exhausted = ?,
|
|
14440
14691
|
error = ?
|
|
14441
14692
|
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
14442
|
-
`).run(input.status, plan.finishedAt.toISOString(), plan.progress.sourceRowsRead, plan.progress.sourceRowsSkipped, plan.inputExhausted ? 1 : existingRow.input_exhausted, plan.error === undefined ? null : serializeSixbFailure4(plan.error, PROJECTION_RUN_FAILURE_CODES), input.projectId, input.id, input.executionToken);
|
|
14693
|
+
`).run(input.status, plan.finishedAt.toISOString(), plan.progress.sourceRowsRead, plan.progress.sourceRowsSkipped, plan.progress.sourceChangesRead ?? null, plan.inputExhausted ? 1 : existingRow.input_exhausted, plan.error === undefined ? null : serializeSixbFailure4(plan.error, PROJECTION_RUN_FAILURE_CODES), input.projectId, input.id, input.executionToken);
|
|
14443
14694
|
if (result.changes !== 1)
|
|
14444
14695
|
throw staleProjectionRunExecution(input.id);
|
|
14445
14696
|
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
|
|
@@ -14605,6 +14856,7 @@ class SqliteProjectionRunStorage {
|
|
|
14605
14856
|
const args = [
|
|
14606
14857
|
progress.sourceRowsRead,
|
|
14607
14858
|
progress.sourceRowsSkipped,
|
|
14859
|
+
progress.sourceChangesRead ?? null,
|
|
14608
14860
|
existing.project_id,
|
|
14609
14861
|
existing.id,
|
|
14610
14862
|
executionToken
|
|
@@ -14613,7 +14865,8 @@ class SqliteProjectionRunStorage {
|
|
|
14613
14865
|
UPDATE projection_runs
|
|
14614
14866
|
SET
|
|
14615
14867
|
source_rows_read = ?,
|
|
14616
|
-
source_rows_skipped =
|
|
14868
|
+
source_rows_skipped = ?,
|
|
14869
|
+
source_changes_read = ?
|
|
14617
14870
|
WHERE project_id = ? AND id = ? AND status = 'running'
|
|
14618
14871
|
AND execution_token = ?
|
|
14619
14872
|
`).run(...args);
|
|
@@ -14683,7 +14936,8 @@ function restoreProjectionRunRow(row) {
|
|
|
14683
14936
|
missingTargetFirstSeenAt: row.missing_target_first_seen_at ? new Date(row.missing_target_first_seen_at) : undefined,
|
|
14684
14937
|
progress: {
|
|
14685
14938
|
sourceRowsRead: databaseSafeInteger(row.source_rows_read, "sourceRowsRead"),
|
|
14686
|
-
sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped")
|
|
14939
|
+
sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped"),
|
|
14940
|
+
...row.source_changes_read == null ? {} : { sourceChangesRead: databaseSafeInteger(row.source_changes_read, "sourceChangesRead") }
|
|
14687
14941
|
},
|
|
14688
14942
|
error: row.error ?? undefined
|
|
14689
14943
|
};
|
|
@@ -16758,4 +17012,4 @@ export {
|
|
|
16758
17012
|
migrateSqliteStorage
|
|
16759
17013
|
};
|
|
16760
17014
|
|
|
16761
|
-
//# debugId=
|
|
17015
|
+
//# debugId=6BCAC863183D761764756E2164756E21
|