@sixb/sqlite 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +408 -182
- package/dist/index.js.map +12 -10
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +2 -1
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +2 -0
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/source-cleanup.d.ts +4 -0
- package/dist/ontology-storage/source-cleanup.d.ts.map +1 -0
- package/dist/ontology-storage/source-roots.d.ts +27 -0
- package/dist/ontology-storage/source-roots.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/connector-connection-storage.ts +3 -3
- package/src/migrations/038-projection-source-roots.sql +44 -0
- package/src/migrations/039-connector-optional-pkce.sql +52 -0
- package/src/migrations.ts +9 -0
- package/src/ontology-storage/materialization-state.ts +56 -91
- package/src/ontology-storage/materializations.ts +7 -1
- package/src/ontology-storage/shared.ts +10 -0
- package/src/ontology-storage/source-cleanup.ts +59 -0
- package/src/ontology-storage/source-roots.ts +230 -0
- package/src/ontology-storage/sources.ts +25 -78
- package/src/projection-run-storage.ts +11 -3
package/dist/index.js
CHANGED
|
@@ -3932,6 +3932,53 @@ 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
|
+
|
|
3935
3982
|
// src/migrations/037-execution-requester-groups.sql
|
|
3936
3983
|
var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids TEXT NOT NULL DEFAULT '[]';
|
|
3937
3984
|
|
|
@@ -3959,6 +4006,61 @@ ALTER TABLE workflow_runs DROP COLUMN requester_group_ids;
|
|
|
3959
4006
|
ALTER TABLE agent_runs DROP COLUMN requester_group_ids;
|
|
3960
4007
|
`;
|
|
3961
4008
|
|
|
4009
|
+
// src/migrations/039-connector-optional-pkce.sql
|
|
4010
|
+
var _039_connector_optional_pkce_default = `-- Rebuild to remove NOT NULL while preserving existing attempts and their constraints.
|
|
4011
|
+
CREATE TABLE connector_authorization_attempts_optional_pkce (
|
|
4012
|
+
project_id TEXT NOT NULL,
|
|
4013
|
+
connector_id TEXT NOT NULL,
|
|
4014
|
+
id TEXT NOT NULL,
|
|
4015
|
+
slot TEXT NOT NULL,
|
|
4016
|
+
initiated_by_execution_id TEXT NOT NULL,
|
|
4017
|
+
state_hash TEXT NOT NULL,
|
|
4018
|
+
code_verifier TEXT CHECK (
|
|
4019
|
+
code_verifier IS NULL
|
|
4020
|
+
OR (json_valid(code_verifier) AND json_type(code_verifier) = 'object')
|
|
4021
|
+
),
|
|
4022
|
+
redirect_uri TEXT NOT NULL,
|
|
4023
|
+
connection_run_id TEXT,
|
|
4024
|
+
return_to TEXT,
|
|
4025
|
+
callback_binding_hash TEXT,
|
|
4026
|
+
reauthorization_id TEXT,
|
|
4027
|
+
reauthorization_revision INTEGER CHECK (
|
|
4028
|
+
reauthorization_revision IS NULL OR reauthorization_revision >= 0
|
|
4029
|
+
),
|
|
4030
|
+
reauthorization_connection_ids TEXT CHECK (
|
|
4031
|
+
reauthorization_connection_ids IS NULL
|
|
4032
|
+
OR (json_valid(reauthorization_connection_ids) AND json_type(reauthorization_connection_ids) = 'array')
|
|
4033
|
+
),
|
|
4034
|
+
created_at TEXT NOT NULL,
|
|
4035
|
+
expires_at TEXT NOT NULL,
|
|
4036
|
+
PRIMARY KEY (project_id, connector_id, id),
|
|
4037
|
+
UNIQUE (project_id, id),
|
|
4038
|
+
UNIQUE (project_id, connector_id, connection_run_id),
|
|
4039
|
+
FOREIGN KEY (project_id, connector_id, connection_run_id)
|
|
4040
|
+
REFERENCES connector_connection_runs (project_id, connector_id, id)
|
|
4041
|
+
ON DELETE RESTRICT,
|
|
4042
|
+
CHECK (
|
|
4043
|
+
(connection_run_id IS NULL AND return_to IS NULL AND callback_binding_hash IS NULL)
|
|
4044
|
+
OR
|
|
4045
|
+
(connection_run_id IS NOT NULL AND return_to IS NOT NULL AND callback_binding_hash IS NOT NULL)
|
|
4046
|
+
),
|
|
4047
|
+
CHECK (
|
|
4048
|
+
(reauthorization_id IS NULL AND reauthorization_revision IS NULL AND reauthorization_connection_ids IS NULL)
|
|
4049
|
+
OR
|
|
4050
|
+
(reauthorization_id IS NOT NULL AND reauthorization_revision IS NOT NULL AND reauthorization_connection_ids IS NOT NULL)
|
|
4051
|
+
)
|
|
4052
|
+
);
|
|
4053
|
+
|
|
4054
|
+
INSERT INTO connector_authorization_attempts_optional_pkce
|
|
4055
|
+
SELECT * FROM connector_authorization_attempts;
|
|
4056
|
+
|
|
4057
|
+
DROP TABLE connector_authorization_attempts;
|
|
4058
|
+
ALTER TABLE connector_authorization_attempts_optional_pkce RENAME TO connector_authorization_attempts;
|
|
4059
|
+
|
|
4060
|
+
CREATE INDEX idx_connector_attempts_expiry
|
|
4061
|
+
ON connector_authorization_attempts (project_id, connector_id, expires_at);
|
|
4062
|
+
`;
|
|
4063
|
+
|
|
3962
4064
|
// src/migrations.ts
|
|
3963
4065
|
var MIGRATIONS_TABLE_SQL = `
|
|
3964
4066
|
CREATE TABLE IF NOT EXISTS sixb_migrations (
|
|
@@ -4030,7 +4132,9 @@ var sqliteStorageMigrations = defineMigrations({
|
|
|
4030
4132
|
sqliteSql("034-device-authorizations", _034_device_authorizations_default),
|
|
4031
4133
|
sqliteSql("035-share-grants", _035_share_grants_default),
|
|
4032
4134
|
sqliteSql("036-share-sessions", _036_share_sessions_default),
|
|
4033
|
-
sqliteSql("037-execution-requester-groups", _037_execution_requester_groups_default)
|
|
4135
|
+
sqliteSql("037-execution-requester-groups", _037_execution_requester_groups_default),
|
|
4136
|
+
sqliteSql("038-projection-source-roots", _038_projection_source_roots_default),
|
|
4137
|
+
sqliteSql("039-connector-optional-pkce", _039_connector_optional_pkce_default)
|
|
4034
4138
|
]
|
|
4035
4139
|
});
|
|
4036
4140
|
function sqliteStoragePath(basePath) {
|
|
@@ -9014,7 +9118,7 @@ class SqliteConnectorConnectionPersistence {
|
|
|
9014
9118
|
callback_binding_hash, reauthorization_id, reauthorization_revision,
|
|
9015
9119
|
reauthorization_connection_ids, created_at, expires_at
|
|
9016
9120
|
) 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));
|
|
9121
|
+
`).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
9122
|
return true;
|
|
9019
9123
|
} catch (error) {
|
|
9020
9124
|
if (isUniqueConstraintError2(error))
|
|
@@ -9195,7 +9299,7 @@ function authorizationAttemptFromRow(row) {
|
|
|
9195
9299
|
slot: row.slot,
|
|
9196
9300
|
initiatedByExecutionId: row.initiated_by_execution_id,
|
|
9197
9301
|
stateHash: row.state_hash,
|
|
9198
|
-
codeVerifier: parseJson(row.code_verifier),
|
|
9302
|
+
...row.code_verifier === null ? {} : { codeVerifier: parseJson(row.code_verifier) },
|
|
9199
9303
|
redirectUri: row.redirect_uri,
|
|
9200
9304
|
...row.connection_run_id === null ? {} : { connectionRunId: row.connection_run_id },
|
|
9201
9305
|
...row.return_to === null ? {} : { returnTo: row.return_to },
|
|
@@ -11387,6 +11491,12 @@ function sourceRecord(row) {
|
|
|
11387
11491
|
projectionRevision: row.projection_revision,
|
|
11388
11492
|
ownershipHash: row.ownership_hash,
|
|
11389
11493
|
ontologyRevision: row.ontology_revision,
|
|
11494
|
+
...row.base_materialization_id === null ? {} : {
|
|
11495
|
+
base: {
|
|
11496
|
+
materializationId: row.base_materialization_id,
|
|
11497
|
+
lastCommitId: row.base_commit_id
|
|
11498
|
+
}
|
|
11499
|
+
},
|
|
11390
11500
|
rootCount: row.root_count,
|
|
11391
11501
|
assertionCount: row.assertion_count,
|
|
11392
11502
|
createdAt: row.created_at,
|
|
@@ -11558,7 +11668,7 @@ import {
|
|
|
11558
11668
|
linkRefKey as linkRefKey3,
|
|
11559
11669
|
linkRefSortKey,
|
|
11560
11670
|
linkScopeSortKey as linkScopeSortKey2,
|
|
11561
|
-
MaterializationConflictError as
|
|
11671
|
+
MaterializationConflictError as MaterializationConflictError6,
|
|
11562
11672
|
objectRefKey as objectRefKey3,
|
|
11563
11673
|
objectRefSortKey,
|
|
11564
11674
|
telemetryPointKey as telemetryPointKey2,
|
|
@@ -11578,7 +11688,7 @@ import {
|
|
|
11578
11688
|
|
|
11579
11689
|
// src/ontology-storage/materialization-session.ts
|
|
11580
11690
|
import {
|
|
11581
|
-
MaterializationConflictError as
|
|
11691
|
+
MaterializationConflictError as MaterializationConflictError4
|
|
11582
11692
|
} from "@sixb/core/internal/materialization";
|
|
11583
11693
|
import {
|
|
11584
11694
|
correlateMaterializationChunk,
|
|
@@ -11593,9 +11703,9 @@ import {
|
|
|
11593
11703
|
import {
|
|
11594
11704
|
linkRefKey,
|
|
11595
11705
|
linkScopeSortKey,
|
|
11596
|
-
MaterializationConflictError as
|
|
11706
|
+
MaterializationConflictError as MaterializationConflictError3,
|
|
11597
11707
|
objectRefKey,
|
|
11598
|
-
projectionEntityKey,
|
|
11708
|
+
projectionEntityKey as projectionEntityKey2,
|
|
11599
11709
|
telemetryPointSortKey
|
|
11600
11710
|
} from "@sixb/core/internal/materialization";
|
|
11601
11711
|
import {
|
|
@@ -11604,6 +11714,132 @@ import {
|
|
|
11604
11714
|
linkSlotOverrideValue,
|
|
11605
11715
|
startScopeAccumulator
|
|
11606
11716
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
11717
|
+
|
|
11718
|
+
// src/ontology-storage/source-roots.ts
|
|
11719
|
+
import {
|
|
11720
|
+
MaterializationConflictError as MaterializationConflictError2,
|
|
11721
|
+
MaterializationValidationError,
|
|
11722
|
+
projectionEntityKey
|
|
11723
|
+
} from "@sixb/core/internal/materialization";
|
|
11724
|
+
import { sourceStageRoots, utf8SortKey } from "@sixb/core/internal/ontology-storage-provider";
|
|
11725
|
+
function replacementSourceRows(input) {
|
|
11726
|
+
const roots = input.incremental ? `ontology_source_roots AS changed
|
|
11727
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = changed.project_id
|
|
11728
|
+
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";
|
|
11729
|
+
const selected = input.incremental ? "changed" : "roots";
|
|
11730
|
+
return {
|
|
11731
|
+
sql: `SELECT * FROM ontology_source_rows WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
11732
|
+
UNION ALL
|
|
11733
|
+
SELECT rows.* FROM ${roots}
|
|
11734
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id
|
|
11735
|
+
AND rows.source_id = roots.source_id AND rows.materialization_id = roots.materialization_id
|
|
11736
|
+
AND rows.root_sort_key = roots.root_sort_key
|
|
11737
|
+
WHERE ${selected}.project_id = ? AND ${selected}.source_id = ?
|
|
11738
|
+
AND ${input.incremental ? "changed.materialization_id = ?" : "roots.active = 1"}`,
|
|
11739
|
+
values: [
|
|
11740
|
+
input.projectId,
|
|
11741
|
+
input.sourceId,
|
|
11742
|
+
input.materializationId,
|
|
11743
|
+
input.projectId,
|
|
11744
|
+
input.sourceId,
|
|
11745
|
+
...input.incremental ? [input.materializationId] : []
|
|
11746
|
+
]
|
|
11747
|
+
};
|
|
11748
|
+
}
|
|
11749
|
+
function stageSourceRoots(db, manifest, input) {
|
|
11750
|
+
const roots = sourceStageRoots(manifest.projection_kind, input);
|
|
11751
|
+
if (!manifest.base_materialization_id && roots.some((root) => root.deleted)) {
|
|
11752
|
+
throw new MaterializationValidationError("Root deletions require a source delta base.");
|
|
11753
|
+
}
|
|
11754
|
+
const existing = db.query(`SELECT staging_ordinal, deleted FROM ontology_source_roots
|
|
11755
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND root_sort_key = ?`);
|
|
11756
|
+
const insert = db.query(`INSERT INTO ontology_source_roots (
|
|
11757
|
+
project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root, staging_ordinal, deleted
|
|
11758
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING root_sort_key`);
|
|
11759
|
+
for (const root of roots) {
|
|
11760
|
+
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));
|
|
11761
|
+
if (inserted)
|
|
11762
|
+
continue;
|
|
11763
|
+
const previous = existing.get(input.projectId, input.source.projectionId, input.materializationId, root.sortKey);
|
|
11764
|
+
if (!previous || previous.staging_ordinal !== root.stagingOrdinal || Boolean(previous.deleted) !== root.deleted) {
|
|
11765
|
+
throw new MaterializationValidationError("Source materialization repeats root or stream ordinal with different content.");
|
|
11766
|
+
}
|
|
11767
|
+
}
|
|
11768
|
+
}
|
|
11769
|
+
function activateSourceRoots(db, projectId, candidate, activation) {
|
|
11770
|
+
if (candidate.base_materialization_id !== null && (candidate.base_materialization_id !== activation.expected.activeMaterializationId || candidate.base_commit_id !== activation.expected.lastCommitId))
|
|
11771
|
+
throw new MaterializationConflictError2("projection-fence", "Source delta base changed before activation.");
|
|
11772
|
+
if (candidate.base_materialization_id !== null && candidate.root_count === 0)
|
|
11773
|
+
return;
|
|
11774
|
+
const scope = [projectId, activation.source.projectionId, activation.materializationId];
|
|
11775
|
+
db.query(`UPDATE ontology_source_roots AS roots SET active = 0, retired_at = ?
|
|
11776
|
+
WHERE project_id = ? AND source_id = ? AND active = 1
|
|
11777
|
+
${candidate.base_materialization_id === null ? "" : `AND root_sort_key IN (
|
|
11778
|
+
SELECT root_sort_key FROM ontology_source_roots
|
|
11779
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?)`}`).run(activation.updatedAt, projectId, activation.source.projectionId, ...candidate.base_materialization_id === null ? [] : [...scope]);
|
|
11780
|
+
db.query(`UPDATE ontology_source_roots SET active = CASE WHEN deleted = 1 THEN 0 ELSE 1 END,
|
|
11781
|
+
retired_at = CASE WHEN deleted = 1 THEN ? ELSE NULL END
|
|
11782
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?`).run(activation.updatedAt, ...scope);
|
|
11783
|
+
}
|
|
11784
|
+
function assertSourceRootCoverage(db, manifest) {
|
|
11785
|
+
const invalid = db.query(`SELECT 1 FROM ontology_source_roots AS roots
|
|
11786
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
11787
|
+
AND roots.deleted = EXISTS (
|
|
11788
|
+
SELECT 1 FROM ontology_source_rows AS rows
|
|
11789
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
11790
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
11791
|
+
) LIMIT 1`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
11792
|
+
if (invalid)
|
|
11793
|
+
throw new MaterializationValidationError("Source roots must have complete assertions or an explicit deletion, never both.");
|
|
11794
|
+
}
|
|
11795
|
+
function activeSourceRows(db, projectId, refs) {
|
|
11796
|
+
const keys = refs.flatMap((entity) => {
|
|
11797
|
+
const entitySortKey = utf8SortKey(projectionEntityKey(entity));
|
|
11798
|
+
const roots = entity.kind === "object" ? [entitySortKey] : [
|
|
11799
|
+
entitySortKey,
|
|
11800
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source }))
|
|
11801
|
+
];
|
|
11802
|
+
return roots.map((rootSortKey) => ({ rootSortKey, entitySortKey }));
|
|
11803
|
+
});
|
|
11804
|
+
return db.query(`WITH requested AS (
|
|
11805
|
+
SELECT DISTINCT json_extract(value, '$.rootSortKey') AS root_sort_key,
|
|
11806
|
+
json_extract(value, '$.entitySortKey') AS entity_sort_key FROM json_each(?)
|
|
11807
|
+
) SELECT rows.* FROM requested
|
|
11808
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
11809
|
+
AND roots.active = 1 AND roots.root_sort_key = requested.root_sort_key
|
|
11810
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
11811
|
+
AND rows.materialization_id = roots.materialization_id AND rows.entity_sort_key = requested.entity_sort_key
|
|
11812
|
+
AND rows.root_sort_key = roots.root_sort_key`).all(canonicalJson(keys), projectId);
|
|
11813
|
+
}
|
|
11814
|
+
function replacementAssertionRows(db, input) {
|
|
11815
|
+
const keys = input.refs.map((ref) => ({
|
|
11816
|
+
entityKind: ref.kind,
|
|
11817
|
+
entityKey: projectionEntityKey(ref),
|
|
11818
|
+
rootKeys: ref.kind === "object" ? [utf8SortKey(projectionEntityKey(ref))] : [
|
|
11819
|
+
utf8SortKey(projectionEntityKey(ref)),
|
|
11820
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source }))
|
|
11821
|
+
]
|
|
11822
|
+
}));
|
|
11823
|
+
const previous = !input.includePrevious ? "" : `UNION ALL
|
|
11824
|
+
SELECT rows.* FROM requested_entities AS requested
|
|
11825
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ? AND roots.source_id = ?
|
|
11826
|
+
AND roots.active = 1 AND roots.root_sort_key IN (SELECT value FROM json_each(requested.root_keys))
|
|
11827
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
11828
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
11829
|
+
AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
11830
|
+
${!input.incremental ? "" : `WHERE EXISTS (SELECT 1 FROM ontology_source_roots AS changed
|
|
11831
|
+
WHERE changed.project_id = roots.project_id AND changed.source_id = roots.source_id
|
|
11832
|
+
AND changed.materialization_id = ? AND changed.root_sort_key = roots.root_sort_key)`}`;
|
|
11833
|
+
return db.query(`WITH requested_entities AS (
|
|
11834
|
+
SELECT DISTINCT json_extract(value, '$.entityKind') AS entity_kind,
|
|
11835
|
+
json_extract(value, '$.entityKey') AS entity_key, json_extract(value, '$.rootKeys') AS root_keys FROM json_each(?)
|
|
11836
|
+
) SELECT rows.* FROM requested_entities AS requested
|
|
11837
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.project_id = ? AND rows.source_id = ?
|
|
11838
|
+
AND rows.materialization_id = ? AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
11839
|
+
${previous}`).all(canonicalJson(keys), input.projectId, input.sourceId, input.materializationId, ...input.includePrevious ? [input.projectId, input.sourceId, ...input.incremental ? [input.materializationId] : []] : []);
|
|
11840
|
+
}
|
|
11841
|
+
|
|
11842
|
+
// src/ontology-storage/materialization-state.ts
|
|
11607
11843
|
var SQLITE_MATERIALIZATION_WORK_TABLE = "ontology_materialization_work";
|
|
11608
11844
|
var SQLITE_REPLACEMENT_WORK_TABLE = "ontology_replacement_work";
|
|
11609
11845
|
function linkScopeRequestJson(scopes) {
|
|
@@ -11640,18 +11876,7 @@ class SqliteMaterializationStateReader {
|
|
|
11640
11876
|
ON objects.project_id = ?
|
|
11641
11877
|
AND objects.object_type_id = requested.object_type_id
|
|
11642
11878
|
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);
|
|
11879
|
+
const sourceRows = activeSourceRows(this.db, this.projectId, refs.map((ref) => ({ kind: "object", ref })));
|
|
11655
11880
|
const overrideRows = this.db.query(`WITH requested AS (${requestedObjects})
|
|
11656
11881
|
SELECT overrides.* FROM requested
|
|
11657
11882
|
CROSS JOIN ontology_object_overrides AS overrides
|
|
@@ -11690,7 +11915,7 @@ class SqliteMaterializationStateReader {
|
|
|
11690
11915
|
const effectiveRow = effective.get(key);
|
|
11691
11916
|
return {
|
|
11692
11917
|
ref: structuredClone(ref),
|
|
11693
|
-
source: sourceObject(sources.get(
|
|
11918
|
+
source: sourceObject(sources.get(projectionEntityKey2({ kind: "object", ref }))),
|
|
11694
11919
|
override: overrides.get(key) ?? null,
|
|
11695
11920
|
effective: effectiveRow ? effectiveObjectSnapshot(effectiveRow) : null,
|
|
11696
11921
|
latestTelemetry: telemetry.get(key) ?? []
|
|
@@ -11735,21 +11960,7 @@ class SqliteMaterializationStateReader {
|
|
|
11735
11960
|
AND links.link_id = requested.link_id
|
|
11736
11961
|
AND links.target_type_id = requested.target_type_id
|
|
11737
11962
|
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);
|
|
11963
|
+
const sourceRows = activeSourceRows(this.db, this.projectId, refs.map((ref) => ({ kind: "link", ref })));
|
|
11753
11964
|
const overrideRows = this.db.query(`WITH requested AS (${sourceRequest})
|
|
11754
11965
|
SELECT overrides.* FROM requested
|
|
11755
11966
|
CROSS JOIN ontology_link_overrides AS overrides
|
|
@@ -11780,7 +11991,7 @@ class SqliteMaterializationStateReader {
|
|
|
11780
11991
|
const effectiveRow = effective.get(key);
|
|
11781
11992
|
return {
|
|
11782
11993
|
ref: structuredClone(ref),
|
|
11783
|
-
source: sourceLink(sources.get(
|
|
11994
|
+
source: sourceLink(sources.get(projectionEntityKey2({ kind: "link", ref }))),
|
|
11784
11995
|
override: overrides.get(key) ?? null,
|
|
11785
11996
|
slotOverride: slotOverrides.get(linkScopeSortKey(ref.source, ref.linkId)) ?? null,
|
|
11786
11997
|
effective: effectiveRow ? effectiveLinkSnapshot(effectiveRow) : null
|
|
@@ -11845,11 +12056,12 @@ class SqliteMaterializationStateReader {
|
|
|
11845
12056
|
AND rows.source_type_id = requested.source_type_id
|
|
11846
12057
|
AND rows.source_primary_id = requested.source_primary_id
|
|
11847
12058
|
AND rows.link_id = requested.link_id
|
|
11848
|
-
JOIN
|
|
12059
|
+
JOIN ontology_source_roots AS sources
|
|
11849
12060
|
ON sources.project_id = rows.project_id
|
|
11850
12061
|
AND sources.source_id = rows.source_id
|
|
11851
12062
|
AND sources.materialization_id = rows.materialization_id
|
|
11852
|
-
|
|
12063
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12064
|
+
WHERE sources.active = 1`).all(requestedJson, this.projectId);
|
|
11853
12065
|
const slotOverrideRows = this.db.query(`WITH requested AS (
|
|
11854
12066
|
SELECT DISTINCT
|
|
11855
12067
|
json_extract(value, '$.sourceTypeId') AS source_type_id,
|
|
@@ -11907,7 +12119,7 @@ class SqliteMaterializationStateReader {
|
|
|
11907
12119
|
for (const row of rows) {
|
|
11908
12120
|
const accumulator = accumulators.get(row.scope_sort_key);
|
|
11909
12121
|
if (!accumulator) {
|
|
11910
|
-
throw new
|
|
12122
|
+
throw new MaterializationConflictError3("effective-state", `Unexpected link scope '${row.scope_sort_key}' returned by storage.`);
|
|
11911
12123
|
}
|
|
11912
12124
|
const snapshot = effectiveLinkSnapshot(row);
|
|
11913
12125
|
appendScopeSnapshot(accumulator, snapshot);
|
|
@@ -11938,11 +12150,12 @@ class SqliteMaterializationStateReader {
|
|
|
11938
12150
|
SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
|
|
11939
12151
|
rows.target_type_id, rows.target_primary_id
|
|
11940
12152
|
FROM ontology_source_rows AS rows
|
|
11941
|
-
JOIN
|
|
12153
|
+
JOIN ontology_source_roots AS sources
|
|
11942
12154
|
ON sources.project_id = rows.project_id
|
|
11943
12155
|
AND sources.source_id = rows.source_id
|
|
11944
12156
|
AND sources.materialization_id = rows.materialization_id
|
|
11945
|
-
|
|
12157
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12158
|
+
WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.active = 1
|
|
11946
12159
|
), selected AS (
|
|
11947
12160
|
SELECT DISTINCT links.*,
|
|
11948
12161
|
${linkSortExpression("links")} AS sort_key
|
|
@@ -11985,19 +12198,19 @@ class SqliteMaterializationStateReader {
|
|
|
11985
12198
|
const candidates = this.replacementSources(sourceId, [candidateMaterializationId], refs.map((ref) => ({ kind: "object", ref })));
|
|
11986
12199
|
return base.map((state) => ({
|
|
11987
12200
|
ref: state.ref,
|
|
11988
|
-
candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(
|
|
12201
|
+
candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(projectionEntityKey2({ kind: "object", ref: state.ref }))),
|
|
11989
12202
|
override: state.override,
|
|
11990
12203
|
effective: state.effective,
|
|
11991
12204
|
latestTelemetry: state.latestTelemetry
|
|
11992
12205
|
}));
|
|
11993
12206
|
}
|
|
11994
|
-
replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities) {
|
|
12207
|
+
replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities, incremental = false) {
|
|
11995
12208
|
const refs = identities.map((identity) => identity.ref);
|
|
11996
12209
|
const base = this.linkStates(refs);
|
|
11997
|
-
const replacements = this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })));
|
|
12210
|
+
const replacements = this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })), incremental);
|
|
11998
12211
|
const candidate = replacements.byMaterialization.get(candidateMaterializationId);
|
|
11999
12212
|
return base.map((state, index) => {
|
|
12000
|
-
const key =
|
|
12213
|
+
const key = projectionEntityKey2({ kind: "link", ref: state.ref });
|
|
12001
12214
|
return {
|
|
12002
12215
|
ref: state.ref,
|
|
12003
12216
|
candidateSource: replacements.owned.has(key) ? sourceLink(candidate?.get(key)) : state.source,
|
|
@@ -12021,32 +12234,23 @@ class SqliteMaterializationStateReader {
|
|
|
12021
12234
|
`).get(this.projectId, ref.source.objectTypeId, ref.source.primaryId, ref.linkId, ref.target.objectTypeId, ref.target.primaryId);
|
|
12022
12235
|
return row ? row.last_commit_id : undefined;
|
|
12023
12236
|
}
|
|
12024
|
-
replacementSources(sourceId, materializationIds, refs) {
|
|
12237
|
+
replacementSources(sourceId, materializationIds, refs, incremental = false) {
|
|
12025
12238
|
if (materializationIds.length === 0 || refs.length === 0) {
|
|
12026
12239
|
return { owned: new Set, byMaterialization: new Map };
|
|
12027
12240
|
}
|
|
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);
|
|
12241
|
+
const rows = replacementAssertionRows(this.db, {
|
|
12242
|
+
projectId: this.projectId,
|
|
12243
|
+
sourceId,
|
|
12244
|
+
materializationId: materializationIds[0],
|
|
12245
|
+
incremental,
|
|
12246
|
+
includePrevious: materializationIds.length > 1,
|
|
12247
|
+
refs
|
|
12248
|
+
});
|
|
12045
12249
|
const owned = new Set;
|
|
12046
12250
|
const byMaterialization = new Map;
|
|
12047
12251
|
for (const row of rows) {
|
|
12048
12252
|
const source = storedSource(row);
|
|
12049
|
-
const key =
|
|
12253
|
+
const key = projectionEntityKey2(source.assertion);
|
|
12050
12254
|
owned.add(key);
|
|
12051
12255
|
const materialization = byMaterialization.get(row.materialization_id) ?? new Map;
|
|
12052
12256
|
materialization.set(key, source);
|
|
@@ -12062,19 +12266,30 @@ class SqliteMaterializationStateReader {
|
|
|
12062
12266
|
this.prepareReplacementLinks(input);
|
|
12063
12267
|
}
|
|
12064
12268
|
prepareReplacementObjects(input) {
|
|
12269
|
+
const rows = replacementSourceRows({
|
|
12270
|
+
projectId: this.projectId,
|
|
12271
|
+
sourceId: input.sourceId,
|
|
12272
|
+
materializationId: input.candidateMaterializationId,
|
|
12273
|
+
incremental: input.incremental ?? false
|
|
12274
|
+
});
|
|
12065
12275
|
this.db.query(`
|
|
12066
12276
|
INSERT INTO ${SQLITE_REPLACEMENT_WORK_TABLE} (
|
|
12067
12277
|
session_id, entity_kind, identity_key, sort_key, diff_required
|
|
12068
12278
|
)
|
|
12069
12279
|
SELECT ?, 'object', json_array(object_type_id, primary_id),
|
|
12070
12280
|
MIN(entity_sort_key), 1
|
|
12071
|
-
FROM
|
|
12072
|
-
WHERE
|
|
12073
|
-
AND materialization_id IN (?, COALESCE(?, ''))
|
|
12281
|
+
FROM (${rows.sql}) AS rows
|
|
12282
|
+
WHERE entity_kind = 'object'
|
|
12074
12283
|
GROUP BY object_type_id, primary_id
|
|
12075
|
-
`).run(input.sessionId,
|
|
12284
|
+
`).run(input.sessionId, ...rows.values);
|
|
12076
12285
|
}
|
|
12077
12286
|
prepareReplacementLinks(input) {
|
|
12287
|
+
const rows = replacementSourceRows({
|
|
12288
|
+
projectId: this.projectId,
|
|
12289
|
+
sourceId: input.sourceId,
|
|
12290
|
+
materializationId: input.candidateMaterializationId,
|
|
12291
|
+
incremental: input.incremental ?? false
|
|
12292
|
+
});
|
|
12078
12293
|
this.db.query(`
|
|
12079
12294
|
WITH incident_objects AS (
|
|
12080
12295
|
SELECT DISTINCT
|
|
@@ -12085,9 +12300,8 @@ class SqliteMaterializationStateReader {
|
|
|
12085
12300
|
), replacement_links AS (
|
|
12086
12301
|
SELECT source_type_id, source_primary_id AS source_id, link_id,
|
|
12087
12302
|
target_type_id, target_primary_id AS target_id
|
|
12088
|
-
FROM
|
|
12089
|
-
WHERE
|
|
12090
|
-
AND materialization_id IN (?, COALESCE(?, ''))
|
|
12303
|
+
FROM (${rows.sql}) AS rows
|
|
12304
|
+
WHERE entity_kind = 'link'
|
|
12091
12305
|
), incident_links AS (
|
|
12092
12306
|
SELECT links.source_type_id, links.source_id, links.link_id,
|
|
12093
12307
|
links.target_type_id, links.target_id
|
|
@@ -12128,28 +12342,30 @@ class SqliteMaterializationStateReader {
|
|
|
12128
12342
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
12129
12343
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
12130
12344
|
FROM ontology_source_rows AS rows
|
|
12131
|
-
JOIN
|
|
12345
|
+
JOIN ontology_source_roots AS sources
|
|
12132
12346
|
ON sources.project_id = rows.project_id
|
|
12133
12347
|
AND sources.source_id = rows.source_id
|
|
12134
12348
|
AND sources.materialization_id = rows.materialization_id
|
|
12349
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12135
12350
|
JOIN incident_objects
|
|
12136
12351
|
ON incident_objects.object_type_id = rows.source_type_id
|
|
12137
12352
|
AND incident_objects.primary_id = rows.source_primary_id
|
|
12138
12353
|
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
12139
|
-
AND sources.
|
|
12354
|
+
AND sources.active = 1
|
|
12140
12355
|
UNION
|
|
12141
12356
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
12142
12357
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
12143
12358
|
FROM ontology_source_rows AS rows
|
|
12144
|
-
JOIN
|
|
12359
|
+
JOIN ontology_source_roots AS sources
|
|
12145
12360
|
ON sources.project_id = rows.project_id
|
|
12146
12361
|
AND sources.source_id = rows.source_id
|
|
12147
12362
|
AND sources.materialization_id = rows.materialization_id
|
|
12363
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
12148
12364
|
JOIN incident_objects
|
|
12149
12365
|
ON incident_objects.object_type_id = rows.target_type_id
|
|
12150
12366
|
AND incident_objects.primary_id = rows.target_primary_id
|
|
12151
12367
|
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
12152
|
-
AND sources.
|
|
12368
|
+
AND sources.active = 1
|
|
12153
12369
|
), diff_links AS (
|
|
12154
12370
|
SELECT * FROM replacement_links
|
|
12155
12371
|
UNION SELECT * FROM incident_links
|
|
@@ -12187,7 +12403,7 @@ class SqliteMaterializationStateReader {
|
|
|
12187
12403
|
json_array(source_type_id, source_id, link_id, target_type_id, target_id),
|
|
12188
12404
|
sort_key, diff_required
|
|
12189
12405
|
FROM selected
|
|
12190
|
-
`).run(input.sessionId,
|
|
12406
|
+
`).run(input.sessionId, ...rows.values, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, this.projectId, input.sessionId);
|
|
12191
12407
|
}
|
|
12192
12408
|
replacementRows(input, cursor) {
|
|
12193
12409
|
return this.db.query(`
|
|
@@ -12225,9 +12441,9 @@ function activeSourceMap(rows) {
|
|
|
12225
12441
|
const result = new Map;
|
|
12226
12442
|
for (const row of rows) {
|
|
12227
12443
|
const source = storedSource(row);
|
|
12228
|
-
const key =
|
|
12444
|
+
const key = projectionEntityKey2(source.assertion);
|
|
12229
12445
|
if (result.has(key)) {
|
|
12230
|
-
throw new
|
|
12446
|
+
throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert ${key}.`);
|
|
12231
12447
|
}
|
|
12232
12448
|
result.set(key, source);
|
|
12233
12449
|
}
|
|
@@ -12242,7 +12458,7 @@ function activeScopeSourceMap(rows) {
|
|
|
12242
12458
|
const ref = source.assertion.ref;
|
|
12243
12459
|
const key = linkScopeSortKey(ref.source, ref.linkId);
|
|
12244
12460
|
if (result.has(key)) {
|
|
12245
|
-
throw new
|
|
12461
|
+
throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert cardinality-one link slot ${key}.`);
|
|
12246
12462
|
}
|
|
12247
12463
|
result.set(key, source);
|
|
12248
12464
|
}
|
|
@@ -12306,7 +12522,7 @@ function storedSource(row) {
|
|
|
12306
12522
|
}
|
|
12307
12523
|
function effectiveObjectSnapshot(row) {
|
|
12308
12524
|
if (!row.last_commit_id) {
|
|
12309
|
-
throw new
|
|
12525
|
+
throw new MaterializationConflictError3("effective-state", `Effective object ${row.object_type_id}:${row.primary_id} lacks materializer provenance.`);
|
|
12310
12526
|
}
|
|
12311
12527
|
return {
|
|
12312
12528
|
ref: objectRefFromColumns(row),
|
|
@@ -12319,7 +12535,7 @@ function effectiveObjectSnapshot(row) {
|
|
|
12319
12535
|
}
|
|
12320
12536
|
function effectiveLinkSnapshot(row) {
|
|
12321
12537
|
if (!row.last_commit_id) {
|
|
12322
|
-
throw new
|
|
12538
|
+
throw new MaterializationConflictError3("effective-state", `Effective link ${linkRefKey(linkRefFromColumns(row))} lacks materializer provenance.`);
|
|
12323
12539
|
}
|
|
12324
12540
|
return {
|
|
12325
12541
|
ref: linkRefFromColumns(row),
|
|
@@ -12333,7 +12549,7 @@ function effectiveLinkSnapshot(row) {
|
|
|
12333
12549
|
}
|
|
12334
12550
|
function storedPoint(row) {
|
|
12335
12551
|
if (!row.last_commit_id) {
|
|
12336
|
-
throw new
|
|
12552
|
+
throw new MaterializationConflictError3("timeseries-point", `Telemetry point ${telemetryPointSortKey({
|
|
12337
12553
|
object: { objectTypeId: row.object_type_id, primaryId: row.object_id },
|
|
12338
12554
|
propertyId: row.property_id
|
|
12339
12555
|
}, row.at)} lacks materializer provenance.`);
|
|
@@ -12372,7 +12588,7 @@ class SqliteMaterializationSessions {
|
|
|
12372
12588
|
}
|
|
12373
12589
|
create(header) {
|
|
12374
12590
|
if (!this.context?.active) {
|
|
12375
|
-
throw new
|
|
12591
|
+
throw new MaterializationConflictError4("effective-state", "Materialization sessions require an active storage transaction.");
|
|
12376
12592
|
}
|
|
12377
12593
|
this.ensureTables();
|
|
12378
12594
|
const session = new SqliteMaterializationSessionState(structuredClone(header), this.context.id);
|
|
@@ -12384,7 +12600,7 @@ class SqliteMaterializationSessions {
|
|
|
12384
12600
|
require(session) {
|
|
12385
12601
|
const value = this.sessions.get(session.providerToken);
|
|
12386
12602
|
if (!value || !value.active || !this.context?.active || value.transactionId !== this.context.id) {
|
|
12387
|
-
throw new
|
|
12603
|
+
throw new MaterializationConflictError4("effective-state", "Materialization session is inactive.");
|
|
12388
12604
|
}
|
|
12389
12605
|
return value;
|
|
12390
12606
|
}
|
|
@@ -12444,7 +12660,7 @@ class SqliteMaterializationSessions {
|
|
|
12444
12660
|
const session = this.require(input.session);
|
|
12445
12661
|
const stream = session.workStreams[input.order];
|
|
12446
12662
|
if (stream.started) {
|
|
12447
|
-
throw new
|
|
12663
|
+
throw new MaterializationConflictError4("effective-state", `Materialization ${input.order} work may only be streamed once per session.`);
|
|
12448
12664
|
}
|
|
12449
12665
|
stream.started = true;
|
|
12450
12666
|
session.workSealed = true;
|
|
@@ -12721,7 +12937,7 @@ function recordSummary(summary, record, columns) {
|
|
|
12721
12937
|
// src/ontology-storage/materialization-writer.ts
|
|
12722
12938
|
import {
|
|
12723
12939
|
linkRefKey as linkRefKey2,
|
|
12724
|
-
MaterializationConflictError as
|
|
12940
|
+
MaterializationConflictError as MaterializationConflictError5,
|
|
12725
12941
|
objectRefKey as objectRefKey2,
|
|
12726
12942
|
telemetryPointKey
|
|
12727
12943
|
} from "@sixb/core/internal/materialization";
|
|
@@ -12893,7 +13109,7 @@ class SqliteMaterializationWriter {
|
|
|
12893
13109
|
`).run(projectId, point.series.object.objectTypeId, point.series.object.primaryId, point.series.propertyId, canonicalJson(point.value), point.unit ?? null, point.at, point.lastCommitId);
|
|
12894
13110
|
} catch (error) {
|
|
12895
13111
|
if (isSqliteConstraintError(error)) {
|
|
12896
|
-
throw new
|
|
13112
|
+
throw new MaterializationConflictError5("timeseries-point", `Telemetry point ${telemetryPointKey(point.series, point.at)} changed.`);
|
|
12897
13113
|
}
|
|
12898
13114
|
throw error;
|
|
12899
13115
|
}
|
|
@@ -12955,13 +13171,13 @@ class SqliteOntologyMaterializationStorage {
|
|
|
12955
13171
|
const linkScopeRevisions = reader.linkScopeRevisions(input.expected.linkScopes);
|
|
12956
13172
|
for (const [index, expected] of input.expected.linkScopes.entries()) {
|
|
12957
13173
|
if (linkScopeRevisions[index]?.fingerprint !== expected.fingerprint) {
|
|
12958
|
-
throw new
|
|
13174
|
+
throw new MaterializationConflictError6("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
|
|
12959
13175
|
}
|
|
12960
13176
|
}
|
|
12961
13177
|
for (const expected of input.expected.points) {
|
|
12962
13178
|
const point = reader.exactPoint(expected.series, expected.at);
|
|
12963
13179
|
if ((point?.lastCommitId ?? null) !== expected.lastCommitId) {
|
|
12964
|
-
throw new
|
|
13180
|
+
throw new MaterializationConflictError6("timeseries-point", `Telemetry point ${telemetryPointKey2(expected.series, expected.at)} changed.`);
|
|
12965
13181
|
}
|
|
12966
13182
|
}
|
|
12967
13183
|
return session.publicSession();
|
|
@@ -13019,10 +13235,10 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13019
13235
|
const reader = new SqliteMaterializationStateReader(this.db, session.header.commit.projectId);
|
|
13020
13236
|
if (input.entityKind === "object") {
|
|
13021
13237
|
if (replacement.projectionKind !== "object") {
|
|
13022
|
-
throw new
|
|
13238
|
+
throw new MaterializationConflictError6("source-materialization", "Link projection replacement cannot stream object state.");
|
|
13023
13239
|
}
|
|
13024
13240
|
if (replacement.objectStreamStarted) {
|
|
13025
|
-
throw new
|
|
13241
|
+
throw new MaterializationConflictError6("effective-state", "Replacement object state may only be streamed once per session.");
|
|
13026
13242
|
}
|
|
13027
13243
|
replacement.objectStreamStarted = true;
|
|
13028
13244
|
for (const identities of reader.replacementIdentities({
|
|
@@ -13030,6 +13246,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13030
13246
|
sourceId: replacement.sourceId,
|
|
13031
13247
|
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
13032
13248
|
previousMaterializationId: replacement.previousMaterializationId,
|
|
13249
|
+
incremental: replacement.incremental,
|
|
13033
13250
|
kind: "object",
|
|
13034
13251
|
pageRows: input.pageRows
|
|
13035
13252
|
})) {
|
|
@@ -13048,10 +13265,10 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13048
13265
|
return;
|
|
13049
13266
|
}
|
|
13050
13267
|
if (replacement.projectionKind === "object" && !replacement.objectStreamCompleted) {
|
|
13051
|
-
throw new
|
|
13268
|
+
throw new MaterializationConflictError6("effective-state", "Object projection replacement must fully stream object state before link state.");
|
|
13052
13269
|
}
|
|
13053
13270
|
if (replacement.linkStreamStarted) {
|
|
13054
|
-
throw new
|
|
13271
|
+
throw new MaterializationConflictError6("effective-state", "Replacement link state may only be streamed once per session.");
|
|
13055
13272
|
}
|
|
13056
13273
|
replacement.linkStreamStarted = true;
|
|
13057
13274
|
const materializationIds = [
|
|
@@ -13063,6 +13280,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13063
13280
|
sourceId: replacement.sourceId,
|
|
13064
13281
|
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
13065
13282
|
previousMaterializationId: replacement.previousMaterializationId,
|
|
13283
|
+
incremental: replacement.incremental,
|
|
13066
13284
|
kind: "link",
|
|
13067
13285
|
pageRows: input.pageRows
|
|
13068
13286
|
})) {
|
|
@@ -13073,7 +13291,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13073
13291
|
}
|
|
13074
13292
|
return identity;
|
|
13075
13293
|
});
|
|
13076
|
-
const links = reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities);
|
|
13294
|
+
const links = reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities, replacement.incremental);
|
|
13077
13295
|
yield { objects: [], links };
|
|
13078
13296
|
}
|
|
13079
13297
|
this.sessions.require(input.session);
|
|
@@ -13125,14 +13343,14 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13125
13343
|
requireReplacement(session, input) {
|
|
13126
13344
|
if (session.replacement) {
|
|
13127
13345
|
if (session.replacement.sourceId !== input.source.projectionId || session.replacement.candidateMaterializationId !== input.candidateMaterializationId) {
|
|
13128
|
-
throw new
|
|
13346
|
+
throw new MaterializationConflictError6("source-materialization", "Materialization session already owns another replacement union.");
|
|
13129
13347
|
}
|
|
13130
13348
|
return session.replacement;
|
|
13131
13349
|
}
|
|
13132
13350
|
const projectId = session.header.commit.projectId;
|
|
13133
13351
|
const candidate = this.getSource(projectId, input.source.projectionId, input.candidateMaterializationId);
|
|
13134
13352
|
if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
|
|
13135
|
-
throw new
|
|
13353
|
+
throw new MaterializationConflictError6("source-materialization", `Candidate source materialization '${input.candidateMaterializationId}' is missing or is not ready.`);
|
|
13136
13354
|
}
|
|
13137
13355
|
assertProjectionExecution(this.db, {
|
|
13138
13356
|
projectId,
|
|
@@ -13142,12 +13360,13 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13142
13360
|
});
|
|
13143
13361
|
const previous = this.getActiveSource(projectId, input.source.projectionId);
|
|
13144
13362
|
if (candidate.protocol !== "replacement" || previous && (previous.protocol !== candidate.protocol || previous.projection_kind !== candidate.projection_kind)) {
|
|
13145
|
-
throw new
|
|
13363
|
+
throw new MaterializationConflictError6("source-materialization", "Source replacement kind or protocol does not match its active materialization.");
|
|
13146
13364
|
}
|
|
13147
13365
|
session.replacement = {
|
|
13148
13366
|
sourceId: input.source.projectionId,
|
|
13149
13367
|
candidateMaterializationId: input.candidateMaterializationId,
|
|
13150
13368
|
previousMaterializationId: previous?.materialization_id ?? null,
|
|
13369
|
+
incremental: candidate.base_materialization_id !== null,
|
|
13151
13370
|
projectionKind: candidate.projection_kind,
|
|
13152
13371
|
objectStreamStarted: false,
|
|
13153
13372
|
objectStreamCompleted: false,
|
|
@@ -13176,38 +13395,38 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13176
13395
|
if (!duplicate)
|
|
13177
13396
|
return;
|
|
13178
13397
|
if (duplicate.duplicate === "origin") {
|
|
13179
|
-
throw new
|
|
13398
|
+
throw new MaterializationConflictError6("run-correlation", "Ontology commit origin already has an authoritative commit.");
|
|
13180
13399
|
}
|
|
13181
|
-
throw new
|
|
13400
|
+
throw new MaterializationConflictError6("idempotency", duplicate.duplicate === "id" ? `Ontology commit '${header.commit.id}' already exists.` : "Ontology idempotency key already exists.");
|
|
13182
13401
|
}
|
|
13183
13402
|
assertSource(expected, projectId) {
|
|
13184
13403
|
const active = this.getActiveSource(projectId, expected.source.projectionId);
|
|
13185
13404
|
if ((active?.materialization_id ?? null) !== expected.activeMaterializationId || (active?.last_commit_id ?? null) !== expected.lastCommitId) {
|
|
13186
|
-
throw new
|
|
13405
|
+
throw new MaterializationConflictError6("projection-fence", `Source '${expected.source.projectionId}' changed.`);
|
|
13187
13406
|
}
|
|
13188
13407
|
}
|
|
13189
13408
|
assertObject(reader, expected) {
|
|
13190
13409
|
const row = reader.effectiveObjectRevision(expected.ref);
|
|
13191
13410
|
if (!expected.exists) {
|
|
13192
13411
|
if (row) {
|
|
13193
|
-
throw new
|
|
13412
|
+
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
|
|
13194
13413
|
}
|
|
13195
13414
|
return;
|
|
13196
13415
|
}
|
|
13197
13416
|
if (!row || row.version !== expected.version || row.lastCommitId !== expected.lastCommitId) {
|
|
13198
|
-
throw new
|
|
13417
|
+
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
|
|
13199
13418
|
}
|
|
13200
13419
|
}
|
|
13201
13420
|
assertLink(reader, expected) {
|
|
13202
13421
|
const lastCommitId = reader.effectiveLinkLastCommit(expected.ref);
|
|
13203
13422
|
if (!expected.exists) {
|
|
13204
13423
|
if (lastCommitId !== undefined) {
|
|
13205
|
-
throw new
|
|
13424
|
+
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
|
|
13206
13425
|
}
|
|
13207
13426
|
return;
|
|
13208
13427
|
}
|
|
13209
13428
|
if (lastCommitId === undefined || lastCommitId !== expected.lastCommitId) {
|
|
13210
|
-
throw new
|
|
13429
|
+
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
|
|
13211
13430
|
}
|
|
13212
13431
|
}
|
|
13213
13432
|
async assertFinalization(session, input) {
|
|
@@ -13301,7 +13520,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13301
13520
|
assertSourceActivationCorrelation(session, activation);
|
|
13302
13521
|
const candidate = this.getSource(commit.projectId, activation.source.projectionId, activation.materializationId);
|
|
13303
13522
|
if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
|
|
13304
|
-
throw new
|
|
13523
|
+
throw new MaterializationConflictError6("source-materialization", "Source activation candidate is missing or is not ready.");
|
|
13305
13524
|
}
|
|
13306
13525
|
assertProjectionExecution(this.db, {
|
|
13307
13526
|
projectId: commit.projectId,
|
|
@@ -13313,6 +13532,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13313
13532
|
invalidCorrelation2("Source activation does not match its ready candidate identity.");
|
|
13314
13533
|
}
|
|
13315
13534
|
this.assertSource(activation.expected, commit.projectId);
|
|
13535
|
+
activateSourceRoots(this.db, commit.projectId, candidate, activation);
|
|
13316
13536
|
const previous = this.getActiveSource(commit.projectId, activation.source.projectionId);
|
|
13317
13537
|
if (previous) {
|
|
13318
13538
|
assertPinnedDatasetWatermark({
|
|
@@ -13352,7 +13572,7 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13352
13572
|
`).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
13573
|
} catch (error) {
|
|
13354
13574
|
if (isSqliteConstraintError(error)) {
|
|
13355
|
-
throw new
|
|
13575
|
+
throw new MaterializationConflictError6("idempotency", "Ontology commit identity already exists.");
|
|
13356
13576
|
}
|
|
13357
13577
|
throw error;
|
|
13358
13578
|
}
|
|
@@ -13372,8 +13592,8 @@ class SqliteOntologyMaterializationStorage {
|
|
|
13372
13592
|
// src/ontology-storage/outbox.ts
|
|
13373
13593
|
import { serializeSixbFailure as serializeSixbFailure2 } from "@sixb/core/internal/errors";
|
|
13374
13594
|
import {
|
|
13375
|
-
MaterializationConflictError as
|
|
13376
|
-
MaterializationValidationError
|
|
13595
|
+
MaterializationConflictError as MaterializationConflictError7,
|
|
13596
|
+
MaterializationValidationError as MaterializationValidationError2
|
|
13377
13597
|
} from "@sixb/core/internal/materialization";
|
|
13378
13598
|
import { ONTOLOGY_OUTBOX_FAILURE_CODES as ONTOLOGY_OUTBOX_FAILURE_CODES2 } from "@sixb/core/storage";
|
|
13379
13599
|
class SqliteOntologyOutboxStorage {
|
|
@@ -13391,7 +13611,7 @@ class SqliteOntologyOutboxStorage {
|
|
|
13391
13611
|
const leaseExpiry = assertTimestamp(input.leaseExpiresAt, "Ontology outbox lease expiry");
|
|
13392
13612
|
assertNonblank(input.leaseId, "Ontology outbox lease id");
|
|
13393
13613
|
if (leaseExpiry <= now) {
|
|
13394
|
-
throw new
|
|
13614
|
+
throw new MaterializationValidationError2("Ontology outbox lease expiry must be later than the claim time.");
|
|
13395
13615
|
}
|
|
13396
13616
|
const rows = this.db.query(`
|
|
13397
13617
|
UPDATE ontology_outbox
|
|
@@ -13409,7 +13629,7 @@ class SqliteOntologyOutboxStorage {
|
|
|
13409
13629
|
`).all(input.leaseId, input.leaseExpiresAt, input.projectId, input.now, input.now, input.limit);
|
|
13410
13630
|
return rows.map((row) => outboxRecord(row)).sort((left, right) => left.createdAt.localeCompare(right.createdAt) || left.envelope.id.localeCompare(right.envelope.id)).map((record) => {
|
|
13411
13631
|
if (record.leaseId === null || record.leaseExpiresAt === null) {
|
|
13412
|
-
throw new
|
|
13632
|
+
throw new MaterializationConflictError7("outbox-lease", "Ontology outbox claim returned an unpaired lease.");
|
|
13413
13633
|
}
|
|
13414
13634
|
return { ...record, leaseId: record.leaseId, leaseExpiresAt: record.leaseExpiresAt };
|
|
13415
13635
|
});
|
|
@@ -13508,7 +13728,7 @@ function validateLeaseIds(ids) {
|
|
|
13508
13728
|
for (const id of ids) {
|
|
13509
13729
|
assertNonblank(id, "Ontology outbox event id");
|
|
13510
13730
|
if (seen.has(id)) {
|
|
13511
|
-
throw new
|
|
13731
|
+
throw new MaterializationValidationError2(`Ontology outbox lease batch repeats event '${id}'.`);
|
|
13512
13732
|
}
|
|
13513
13733
|
seen.add(id);
|
|
13514
13734
|
}
|
|
@@ -13519,11 +13739,11 @@ function assertLeaseInput(input) {
|
|
|
13519
13739
|
assertNonblank(input.leaseId, "Ontology outbox lease id");
|
|
13520
13740
|
}
|
|
13521
13741
|
function leaseConflict() {
|
|
13522
|
-
return new
|
|
13742
|
+
return new MaterializationConflictError7("outbox-lease", "Ontology outbox lease does not match.");
|
|
13523
13743
|
}
|
|
13524
13744
|
|
|
13525
13745
|
// src/ontology-storage/sources.ts
|
|
13526
|
-
import { MaterializationValidationError as
|
|
13746
|
+
import { MaterializationValidationError as MaterializationValidationError3 } from "@sixb/core/internal/materialization";
|
|
13527
13747
|
import {
|
|
13528
13748
|
assertSourceBeginInput as assertBeginInput,
|
|
13529
13749
|
assertSourceExecutionIdentity as assertExecutionIdentity,
|
|
@@ -13537,8 +13757,53 @@ import {
|
|
|
13537
13757
|
sourceMaterializationIdentity,
|
|
13538
13758
|
sourceStageRow,
|
|
13539
13759
|
sourceStageRows,
|
|
13540
|
-
utf8SortKey
|
|
13760
|
+
utf8SortKey as utf8SortKey2
|
|
13541
13761
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
13762
|
+
|
|
13763
|
+
// src/ontology-storage/source-cleanup.ts
|
|
13764
|
+
function cleanupSourceVersions(db, input) {
|
|
13765
|
+
let remaining = input.limit;
|
|
13766
|
+
let rowsDeleted = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
13767
|
+
SELECT rows.rowid FROM ontology_source_roots AS roots
|
|
13768
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
|
|
13769
|
+
CROSS JOIN ontology_source_rows AS rows
|
|
13770
|
+
ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
13771
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
13772
|
+
WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
|
|
13773
|
+
AND sources.status IN ('superseded', 'abandoned')
|
|
13774
|
+
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key, rows.entity_sort_key
|
|
13775
|
+
LIMIT ?
|
|
13776
|
+
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
13777
|
+
remaining -= rowsDeleted;
|
|
13778
|
+
if (remaining > 0) {
|
|
13779
|
+
const removed = db.query(`DELETE FROM ontology_source_roots WHERE rowid IN (
|
|
13780
|
+
SELECT roots.rowid FROM ontology_source_roots AS roots
|
|
13781
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
|
|
13782
|
+
WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
|
|
13783
|
+
AND sources.status IN ('superseded', 'abandoned')
|
|
13784
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
13785
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
13786
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key)
|
|
13787
|
+
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key LIMIT ?
|
|
13788
|
+
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
13789
|
+
rowsDeleted += removed;
|
|
13790
|
+
remaining -= removed;
|
|
13791
|
+
}
|
|
13792
|
+
const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE rowid IN (
|
|
13793
|
+
SELECT sources.rowid FROM ontology_sources AS sources
|
|
13794
|
+
WHERE project_id = ? AND status IN ('superseded', 'abandoned') AND terminal_at < ?
|
|
13795
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_roots AS roots
|
|
13796
|
+
WHERE roots.project_id = sources.project_id AND roots.source_id = sources.source_id
|
|
13797
|
+
AND roots.materialization_id = sources.materialization_id)
|
|
13798
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
13799
|
+
WHERE rows.project_id = sources.project_id AND rows.source_id = sources.source_id
|
|
13800
|
+
AND rows.materialization_id = sources.materialization_id)
|
|
13801
|
+
ORDER BY terminal_at, source_id, materialization_id LIMIT ?
|
|
13802
|
+
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
13803
|
+
return { rowsDeleted, materializationsDeleted };
|
|
13804
|
+
}
|
|
13805
|
+
|
|
13806
|
+
// src/ontology-storage/sources.ts
|
|
13542
13807
|
class SqliteOntologySourceStorage {
|
|
13543
13808
|
db;
|
|
13544
13809
|
runRootOperation;
|
|
@@ -13573,10 +13838,10 @@ class SqliteOntologySourceStorage {
|
|
|
13573
13838
|
dataset_id, dataset_version_id, dataset_version_created_at,
|
|
13574
13839
|
projection_revision, ownership_hash, ontology_revision,
|
|
13575
13840
|
root_count, assertion_count, created_at, ready_at, activated_at,
|
|
13576
|
-
terminal_at, last_commit_id, updated_at
|
|
13841
|
+
terminal_at, last_commit_id, updated_at, base_materialization_id, base_commit_id
|
|
13577
13842
|
) 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);
|
|
13843
|
+
NULL, NULL, ?, NULL, NULL, NULL, NULL, ?, ?, ?)
|
|
13844
|
+
`).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
13845
|
return sourceRecord(this.requireManifest(input.projectId, input.source.projectionId, input.materializationId));
|
|
13581
13846
|
});
|
|
13582
13847
|
}
|
|
@@ -13592,6 +13857,7 @@ class SqliteOntologySourceStorage {
|
|
|
13592
13857
|
const rows = sourceStageRows(manifest.projection_kind, input.rows);
|
|
13593
13858
|
const existing = this.findStageRows(input, rows);
|
|
13594
13859
|
const { pending, unchanged } = reconcileSourceStageRows(rows, existing);
|
|
13860
|
+
stageSourceRoots(this.db, manifest, input);
|
|
13595
13861
|
this.insertStageRows(input, pending);
|
|
13596
13862
|
return { inserted: pending.length, unchanged };
|
|
13597
13863
|
});
|
|
@@ -13617,7 +13883,7 @@ class SqliteOntologySourceStorage {
|
|
|
13617
13883
|
throw sourceConflict(`Source materialization '${input.materializationId}' cannot transition from '${manifest.status}' to 'ready'.`);
|
|
13618
13884
|
}
|
|
13619
13885
|
if (input.readyAt < manifest.created_at) {
|
|
13620
|
-
throw new
|
|
13886
|
+
throw new MaterializationValidationError3("Source readyAt cannot precede source createdAt.");
|
|
13621
13887
|
}
|
|
13622
13888
|
this.assertReadyState(manifest, input.rootCount, input.assertionCount);
|
|
13623
13889
|
const result = this.db.query(`
|
|
@@ -13656,55 +13922,7 @@ class SqliteOntologySourceStorage {
|
|
|
13656
13922
|
assertNonblank(input.projectId, "Terminal source cleanup project id");
|
|
13657
13923
|
assertTimestamp(input.terminalBefore, "Terminal source cleanup cutoff", true);
|
|
13658
13924
|
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 };
|
|
13925
|
+
return cleanupSourceVersions(this.db, input);
|
|
13708
13926
|
});
|
|
13709
13927
|
}
|
|
13710
13928
|
async summarizeTerminal(input) {
|
|
@@ -13748,7 +13966,7 @@ class SqliteOntologySourceStorage {
|
|
|
13748
13966
|
}
|
|
13749
13967
|
transitionToAbandoned(manifest, abandonedAt) {
|
|
13750
13968
|
if (abandonedAt < manifest.created_at || manifest.ready_at !== null && abandonedAt < manifest.ready_at) {
|
|
13751
|
-
throw new
|
|
13969
|
+
throw new MaterializationValidationError3("Source abandonedAt cannot precede source creation or readiness.");
|
|
13752
13970
|
}
|
|
13753
13971
|
const changed = this.db.query(`
|
|
13754
13972
|
UPDATE ontology_sources
|
|
@@ -13759,19 +13977,23 @@ class SqliteOntologySourceStorage {
|
|
|
13759
13977
|
if (changed !== 1) {
|
|
13760
13978
|
throw sourceConflict(`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`);
|
|
13761
13979
|
}
|
|
13980
|
+
this.db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
13981
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND active = 0`).run(abandonedAt, manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
13762
13982
|
return sourceRecord(this.requireManifest(manifest.project_id, manifest.source_id, manifest.materialization_id));
|
|
13763
13983
|
}
|
|
13764
13984
|
assertReadyState(manifest, rootCount, assertionCount) {
|
|
13765
13985
|
const counts = this.db.query(`
|
|
13766
|
-
SELECT
|
|
13986
|
+
SELECT (SELECT COUNT(*) FROM ontology_source_rows
|
|
13987
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?) AS assertions, COUNT(*) AS roots,
|
|
13767
13988
|
COUNT(DISTINCT staging_ordinal) AS ordinals,
|
|
13768
13989
|
MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
|
|
13769
|
-
FROM
|
|
13990
|
+
FROM ontology_source_roots
|
|
13770
13991
|
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
13771
|
-
`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
13992
|
+
`).get(manifest.project_id, manifest.source_id, manifest.materialization_id, manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
13772
13993
|
if (counts.assertions !== assertionCount || counts.roots !== rootCount || counts.ordinals !== rootCount || rootCount > 0 && (counts.min_ordinal !== 0 || counts.max_ordinal !== rootCount - 1)) {
|
|
13773
|
-
throw new
|
|
13994
|
+
throw new MaterializationValidationError3("Source ready counts do not match the staged roots and assertions.");
|
|
13774
13995
|
}
|
|
13996
|
+
assertSourceRootCoverage(this.db, manifest);
|
|
13775
13997
|
const invalid = this.db.query(manifest.projection_kind === "link" ? `
|
|
13776
13998
|
SELECT root_key
|
|
13777
13999
|
FROM ontology_source_rows
|
|
@@ -13797,15 +14019,15 @@ class SqliteOntologySourceStorage {
|
|
|
13797
14019
|
LIMIT 1
|
|
13798
14020
|
`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
13799
14021
|
if (invalid) {
|
|
13800
|
-
throw new
|
|
14022
|
+
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
14023
|
}
|
|
13802
14024
|
}
|
|
13803
14025
|
findStageRows(input, rows) {
|
|
13804
14026
|
if (rows.length === 0)
|
|
13805
14027
|
return [];
|
|
13806
|
-
const rootSortKeys = [...new Set(rows.map((row) =>
|
|
14028
|
+
const rootSortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.rootKey)))];
|
|
13807
14029
|
const ordinals = [...new Set(rows.map((row) => row.row.stagingOrdinal))];
|
|
13808
|
-
const entitySortKeys = [...new Set(rows.map((row) =>
|
|
14030
|
+
const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.entityKey)))];
|
|
13809
14031
|
const existing = this.db.query(`
|
|
13810
14032
|
SELECT * FROM ontology_source_rows
|
|
13811
14033
|
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
@@ -13826,10 +14048,10 @@ class SqliteOntologySourceStorage {
|
|
|
13826
14048
|
return {
|
|
13827
14049
|
entityKind: row.assertion.kind,
|
|
13828
14050
|
entityKey,
|
|
13829
|
-
entitySortKey:
|
|
14051
|
+
entitySortKey: utf8SortKey2(entityKey),
|
|
13830
14052
|
rootKind: row.root.kind,
|
|
13831
14053
|
rootKey,
|
|
13832
|
-
rootSortKey:
|
|
14054
|
+
rootSortKey: utf8SortKey2(rootKey),
|
|
13833
14055
|
stagingOrdinal: row.stagingOrdinal,
|
|
13834
14056
|
root: row.root,
|
|
13835
14057
|
assertion: row.assertion,
|
|
@@ -14341,7 +14563,7 @@ class SqliteProjectionRunStorage {
|
|
|
14341
14563
|
input_exhausted = ?, missing_target_object_type_id = NULL,
|
|
14342
14564
|
missing_target_object_id = NULL, missing_target_batch_ordinal = NULL,
|
|
14343
14565
|
missing_target_first_seen_at = NULL, source_rows_read = 0,
|
|
14344
|
-
source_rows_skipped = 0, error = NULL
|
|
14566
|
+
source_rows_skipped = 0, source_changes_read = NULL, error = NULL
|
|
14345
14567
|
WHERE project_id = ? AND id = ? AND status = 'failed'
|
|
14346
14568
|
AND json_extract(error, '$.code') = 'queue.enqueue_failed'
|
|
14347
14569
|
`).run(queuedAt.toISOString(), input.fixedBatchSize === undefined ? null : 0, input.fixedBatchSize === undefined ? null : 0, input.fixedBatchSize === undefined ? null : 0, input.projectId, input.id);
|
|
@@ -14436,10 +14658,11 @@ class SqliteProjectionRunStorage {
|
|
|
14436
14658
|
execution_token = NULL,
|
|
14437
14659
|
source_rows_read = ?,
|
|
14438
14660
|
source_rows_skipped = ?,
|
|
14661
|
+
source_changes_read = ?,
|
|
14439
14662
|
input_exhausted = ?,
|
|
14440
14663
|
error = ?
|
|
14441
14664
|
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);
|
|
14665
|
+
`).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
14666
|
if (result.changes !== 1)
|
|
14444
14667
|
throw staleProjectionRunExecution(input.id);
|
|
14445
14668
|
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
|
|
@@ -14605,6 +14828,7 @@ class SqliteProjectionRunStorage {
|
|
|
14605
14828
|
const args = [
|
|
14606
14829
|
progress.sourceRowsRead,
|
|
14607
14830
|
progress.sourceRowsSkipped,
|
|
14831
|
+
progress.sourceChangesRead ?? null,
|
|
14608
14832
|
existing.project_id,
|
|
14609
14833
|
existing.id,
|
|
14610
14834
|
executionToken
|
|
@@ -14613,7 +14837,8 @@ class SqliteProjectionRunStorage {
|
|
|
14613
14837
|
UPDATE projection_runs
|
|
14614
14838
|
SET
|
|
14615
14839
|
source_rows_read = ?,
|
|
14616
|
-
source_rows_skipped =
|
|
14840
|
+
source_rows_skipped = ?,
|
|
14841
|
+
source_changes_read = ?
|
|
14617
14842
|
WHERE project_id = ? AND id = ? AND status = 'running'
|
|
14618
14843
|
AND execution_token = ?
|
|
14619
14844
|
`).run(...args);
|
|
@@ -14683,7 +14908,8 @@ function restoreProjectionRunRow(row) {
|
|
|
14683
14908
|
missingTargetFirstSeenAt: row.missing_target_first_seen_at ? new Date(row.missing_target_first_seen_at) : undefined,
|
|
14684
14909
|
progress: {
|
|
14685
14910
|
sourceRowsRead: databaseSafeInteger(row.source_rows_read, "sourceRowsRead"),
|
|
14686
|
-
sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped")
|
|
14911
|
+
sourceRowsSkipped: databaseSafeInteger(row.source_rows_skipped, "sourceRowsSkipped"),
|
|
14912
|
+
...row.source_changes_read == null ? {} : { sourceChangesRead: databaseSafeInteger(row.source_changes_read, "sourceChangesRead") }
|
|
14687
14913
|
},
|
|
14688
14914
|
error: row.error ?? undefined
|
|
14689
14915
|
};
|
|
@@ -16758,4 +16984,4 @@ export {
|
|
|
16758
16984
|
migrateSqliteStorage
|
|
16759
16985
|
};
|
|
16760
16986
|
|
|
16761
|
-
//# debugId=
|
|
16987
|
+
//# debugId=B98ED3E1F74F05CB64756E2164756E21
|