@sixb/pg 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 +481 -227
- package/dist/index.js.map +13 -11
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts +3 -0
- package/dist/ontology-storage/materialization-session.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 +24 -0
- package/dist/ontology-storage/source-roots.d.ts.map +1 -0
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/pg-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-outbox-publication-order.sql +5 -0
- package/src/migrations/039-projection-source-roots.sql +46 -0
- package/src/migrations/040-connector-optional-pkce.sql +3 -0
- package/src/migrations.ts +13 -0
- package/src/ontology-storage/materialization-session.ts +24 -8
- package/src/ontology-storage/materialization-state.ts +65 -86
- package/src/ontology-storage/materializations.ts +39 -13
- package/src/ontology-storage/shared.ts +10 -0
- package/src/ontology-storage/source-cleanup.ts +61 -0
- package/src/ontology-storage/source-roots.ts +251 -0
- package/src/ontology-storage/sources.ts +21 -57
- package/src/pg-projection-run-storage.ts +8 -2
package/dist/index.js
CHANGED
|
@@ -3448,7 +3448,7 @@ class PgConnectorConnectionPersistence {
|
|
|
3448
3448
|
) VALUES (
|
|
3449
3449
|
${record.projectId}, ${record.connectorId}, ${record.id}, ${record.slot},
|
|
3450
3450
|
${record.initiatedByExecutionId}, ${record.stateHash},
|
|
3451
|
-
${jsonParameter(this.sql, record.codeVerifier)}, ${record.redirectUri},
|
|
3451
|
+
${record.codeVerifier === undefined ? null : jsonParameter(this.sql, record.codeVerifier)}, ${record.redirectUri},
|
|
3452
3452
|
${record.connectionRunId ?? null}, ${record.returnTo ?? null},
|
|
3453
3453
|
${record.callbackBindingHash ?? null}, ${record.reauthorizationId ?? null},
|
|
3454
3454
|
${record.reauthorizationRevision ?? null},
|
|
@@ -3530,6 +3530,16 @@ class PgConnectorConnectionPersistence {
|
|
|
3530
3530
|
throw error;
|
|
3531
3531
|
}
|
|
3532
3532
|
}
|
|
3533
|
+
async listPendingConnectionRuns() {
|
|
3534
|
+
if (!this.scope.connectorId)
|
|
3535
|
+
return [];
|
|
3536
|
+
const rows = await this.sql`
|
|
3537
|
+
SELECT * FROM connector_connection_runs
|
|
3538
|
+
WHERE project_id = ${this.scope.projectId} AND connector_id = ${this.scope.connectorId}
|
|
3539
|
+
AND (status = 'running' OR (status = 'waiting' AND waiting_for = 'account_selection'))
|
|
3540
|
+
`;
|
|
3541
|
+
return rows.map(connectionRunFromRow);
|
|
3542
|
+
}
|
|
3533
3543
|
async getConnectionRun(id) {
|
|
3534
3544
|
const rows = await this.runById(id);
|
|
3535
3545
|
return rows[0] ? connectionRunFromRow(rows[0]) : null;
|
|
@@ -3741,7 +3751,7 @@ function authorizationAttemptFromRow(row) {
|
|
|
3741
3751
|
slot: row.slot,
|
|
3742
3752
|
initiatedByExecutionId: row.initiated_by_execution_id,
|
|
3743
3753
|
stateHash: row.state_hash,
|
|
3744
|
-
codeVerifier: row.code_verifier,
|
|
3754
|
+
...row.code_verifier === null ? {} : { codeVerifier: row.code_verifier },
|
|
3745
3755
|
redirectUri: row.redirect_uri,
|
|
3746
3756
|
...row.connection_run_id === null ? {} : { connectionRunId: row.connection_run_id },
|
|
3747
3757
|
...row.return_to === null ? {} : { returnTo: row.return_to },
|
|
@@ -7081,6 +7091,55 @@ ALTER TABLE executions
|
|
|
7081
7091
|
ON DELETE RESTRICT;
|
|
7082
7092
|
`;
|
|
7083
7093
|
|
|
7094
|
+
// src/migrations/039-projection-source-roots.sql
|
|
7095
|
+
var _039_projection_source_roots_default = `ALTER TABLE ontology_sources ADD COLUMN base_materialization_id TEXT;
|
|
7096
|
+
ALTER TABLE ontology_sources ADD COLUMN base_commit_id TEXT;
|
|
7097
|
+
ALTER TABLE ontology_sources ADD CHECK ((base_materialization_id IS NULL) = (base_commit_id IS NULL));
|
|
7098
|
+
|
|
7099
|
+
-- A root selects one complete set of assertions. Unchanged roots keep their original version;
|
|
7100
|
+
-- activation only retires/replaces affected references, under the source commit fence.
|
|
7101
|
+
CREATE TABLE ontology_source_roots (
|
|
7102
|
+
project_id TEXT NOT NULL,
|
|
7103
|
+
source_id TEXT NOT NULL,
|
|
7104
|
+
materialization_id TEXT NOT NULL,
|
|
7105
|
+
root_sort_key TEXT NOT NULL,
|
|
7106
|
+
root_kind TEXT NOT NULL CHECK (root_kind IN ('object', 'link')),
|
|
7107
|
+
root_key JSONB NOT NULL,
|
|
7108
|
+
root JSONB NOT NULL,
|
|
7109
|
+
staging_ordinal BIGINT NOT NULL CHECK (staging_ordinal >= 0),
|
|
7110
|
+
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
|
7111
|
+
active BOOLEAN NOT NULL DEFAULT FALSE,
|
|
7112
|
+
retired_at TIMESTAMPTZ,
|
|
7113
|
+
PRIMARY KEY (project_id, source_id, materialization_id, root_sort_key),
|
|
7114
|
+
UNIQUE (project_id, source_id, materialization_id, staging_ordinal),
|
|
7115
|
+
FOREIGN KEY (project_id, source_id, materialization_id)
|
|
7116
|
+
REFERENCES ontology_sources(project_id, source_id, materialization_id) ON DELETE RESTRICT,
|
|
7117
|
+
CHECK (NOT active OR (NOT deleted AND retired_at IS NULL))
|
|
7118
|
+
);
|
|
7119
|
+
CREATE UNIQUE INDEX idx_ontology_source_roots_active
|
|
7120
|
+
ON ontology_source_roots (project_id, source_id, root_sort_key) WHERE active;
|
|
7121
|
+
CREATE INDEX idx_ontology_source_roots_lookup
|
|
7122
|
+
ON ontology_source_roots (project_id, root_sort_key, source_id) WHERE active;
|
|
7123
|
+
CREATE INDEX idx_ontology_source_roots_cleanup
|
|
7124
|
+
ON ontology_source_roots (project_id, retired_at, source_id, materialization_id, root_sort_key)
|
|
7125
|
+
WHERE NOT active AND retired_at IS NOT NULL;
|
|
7126
|
+
|
|
7127
|
+
INSERT INTO ontology_source_roots (
|
|
7128
|
+
project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root,
|
|
7129
|
+
staging_ordinal, active, retired_at
|
|
7130
|
+
)
|
|
7131
|
+
SELECT DISTINCT rows.project_id, rows.source_id, rows.materialization_id,
|
|
7132
|
+
rows.root_sort_key, rows.root_kind, rows.root_key, rows.root, rows.staging_ordinal,
|
|
7133
|
+
sources.status = 'active', sources.terminal_at
|
|
7134
|
+
FROM ontology_source_rows AS rows
|
|
7135
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id);
|
|
7136
|
+
|
|
7137
|
+
ANALYZE ontology_source_roots;
|
|
7138
|
+
|
|
7139
|
+
ALTER TABLE projection_runs ADD COLUMN source_changes_read BIGINT
|
|
7140
|
+
CHECK (source_changes_read IS NULL OR source_changes_read >= 0);
|
|
7141
|
+
`;
|
|
7142
|
+
|
|
7084
7143
|
// src/migrations/037-execution-requester-groups.sql
|
|
7085
7144
|
var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids JSONB NOT NULL DEFAULT '[]'::jsonb;
|
|
7086
7145
|
|
|
@@ -7105,6 +7164,20 @@ ALTER TABLE workflow_runs DROP COLUMN requester_group_ids;
|
|
|
7105
7164
|
ALTER TABLE agent_runs DROP COLUMN requester_group_ids;
|
|
7106
7165
|
`;
|
|
7107
7166
|
|
|
7167
|
+
// src/migrations/038-outbox-publication-order.sql
|
|
7168
|
+
var _038_outbox_publication_order_default = `-- Preserve the claim order without sorting every pending event for each batch.
|
|
7169
|
+
-- Keep the availability index for selective delayed/leased workloads.
|
|
7170
|
+
CREATE INDEX idx_ontology_outbox_publication_order
|
|
7171
|
+
ON ontology_outbox (project_id, created_at, commit_id, commit_ordinal)
|
|
7172
|
+
WHERE published_at IS NULL;
|
|
7173
|
+
`;
|
|
7174
|
+
|
|
7175
|
+
// src/migrations/040-connector-optional-pkce.sql
|
|
7176
|
+
var _040_connector_optional_pkce_default = `-- Existing attempts retain their encrypted S256 verifier. NULL denotes disabled PKCE.
|
|
7177
|
+
ALTER TABLE connector_authorization_attempts
|
|
7178
|
+
ALTER COLUMN code_verifier DROP NOT NULL;
|
|
7179
|
+
`;
|
|
7180
|
+
|
|
7108
7181
|
// src/migrations.ts
|
|
7109
7182
|
var POSTGRES_STORAGE_ADAPTER_ID = "SixbPostgresStorage";
|
|
7110
7183
|
function pgSql(id, sqlText) {
|
|
@@ -7316,7 +7389,10 @@ var postgresStorageMigrations = defineMigrations({
|
|
|
7316
7389
|
pgSql("034-device-authorizations", _034_device_authorizations_default),
|
|
7317
7390
|
pgSql("035-share-grants", _035_share_grants_default),
|
|
7318
7391
|
pgSql("036-share-sessions", _036_share_sessions_default),
|
|
7319
|
-
pgSql("037-execution-requester-groups", _037_execution_requester_groups_default)
|
|
7392
|
+
pgSql("037-execution-requester-groups", _037_execution_requester_groups_default),
|
|
7393
|
+
pgSql("038-outbox-publication-order", _038_outbox_publication_order_default),
|
|
7394
|
+
pgSql("039-projection-source-roots", _039_projection_source_roots_default),
|
|
7395
|
+
pgSql("040-connector-optional-pkce", _040_connector_optional_pkce_default)
|
|
7320
7396
|
]
|
|
7321
7397
|
});
|
|
7322
7398
|
|
|
@@ -9515,6 +9591,12 @@ function sourceRecord(row) {
|
|
|
9515
9591
|
projectionRevision: row.projection_revision,
|
|
9516
9592
|
ownershipHash: row.ownership_hash,
|
|
9517
9593
|
ontologyRevision: row.ontology_revision,
|
|
9594
|
+
...row.base_materialization_id === null ? {} : {
|
|
9595
|
+
base: {
|
|
9596
|
+
materializationId: row.base_materialization_id,
|
|
9597
|
+
lastCommitId: row.base_commit_id
|
|
9598
|
+
}
|
|
9599
|
+
},
|
|
9518
9600
|
rootCount: row.root_count === null ? null : databaseSafeInteger(row.root_count, "Source root count"),
|
|
9519
9601
|
assertionCount: row.assertion_count === null ? null : databaseSafeInteger(row.assertion_count, "Source assertion count"),
|
|
9520
9602
|
createdAt: toIsoString2(row.created_at),
|
|
@@ -9683,7 +9765,7 @@ import {
|
|
|
9683
9765
|
linkRefKey as linkRefKey3,
|
|
9684
9766
|
linkRefSortKey,
|
|
9685
9767
|
linkScopeSortKey as linkScopeSortKey2,
|
|
9686
|
-
MaterializationConflictError as
|
|
9768
|
+
MaterializationConflictError as MaterializationConflictError6,
|
|
9687
9769
|
objectRefKey as objectRefKey3,
|
|
9688
9770
|
objectRefSortKey,
|
|
9689
9771
|
telemetryPointKey as telemetryPointKey3,
|
|
@@ -9702,7 +9784,7 @@ import {
|
|
|
9702
9784
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
9703
9785
|
|
|
9704
9786
|
// src/ontology-storage/materialization-session.ts
|
|
9705
|
-
import { MaterializationConflictError as
|
|
9787
|
+
import { MaterializationConflictError as MaterializationConflictError4 } from "@sixb/core/internal/materialization";
|
|
9706
9788
|
import {
|
|
9707
9789
|
correlateMaterializationChunk,
|
|
9708
9790
|
duplicateMaterializationWork as duplicateWork,
|
|
@@ -9716,9 +9798,9 @@ import {
|
|
|
9716
9798
|
import {
|
|
9717
9799
|
linkRefKey,
|
|
9718
9800
|
linkScopeSortKey,
|
|
9719
|
-
MaterializationConflictError as
|
|
9801
|
+
MaterializationConflictError as MaterializationConflictError3,
|
|
9720
9802
|
objectRefKey,
|
|
9721
|
-
projectionEntityKey,
|
|
9803
|
+
projectionEntityKey as projectionEntityKey2,
|
|
9722
9804
|
telemetryPointKey,
|
|
9723
9805
|
telemetryPointSortKey
|
|
9724
9806
|
} from "@sixb/core/internal/materialization";
|
|
@@ -9728,6 +9810,178 @@ import {
|
|
|
9728
9810
|
linkSlotOverrideValue,
|
|
9729
9811
|
startScopeAccumulator
|
|
9730
9812
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
9813
|
+
|
|
9814
|
+
// src/ontology-storage/source-roots.ts
|
|
9815
|
+
import {
|
|
9816
|
+
MaterializationConflictError as MaterializationConflictError2,
|
|
9817
|
+
MaterializationValidationError,
|
|
9818
|
+
projectionEntityKey
|
|
9819
|
+
} from "@sixb/core/internal/materialization";
|
|
9820
|
+
import { sourceStageRoots, utf8SortKey } from "@sixb/core/internal/ontology-storage-provider";
|
|
9821
|
+
function replacementSourceRows(sql, input) {
|
|
9822
|
+
const roots = input.incremental ? sql`
|
|
9823
|
+
SELECT roots.* FROM ontology_source_roots AS changed
|
|
9824
|
+
CROSS JOIN LATERAL (
|
|
9825
|
+
SELECT roots.* FROM ontology_source_roots AS roots
|
|
9826
|
+
WHERE roots.project_id = changed.project_id AND roots.source_id = changed.source_id
|
|
9827
|
+
AND roots.root_sort_key = changed.root_sort_key AND roots.active OFFSET 0
|
|
9828
|
+
) AS roots
|
|
9829
|
+
WHERE changed.project_id = ${input.projectId} AND changed.source_id = ${input.sourceId}
|
|
9830
|
+
AND changed.materialization_id = ${input.materializationId}
|
|
9831
|
+
` : sql`SELECT * FROM ontology_source_roots WHERE project_id = ${input.projectId} AND source_id = ${input.sourceId} AND active`;
|
|
9832
|
+
return sql`
|
|
9833
|
+
SELECT * FROM ontology_source_rows WHERE project_id = ${input.projectId}
|
|
9834
|
+
AND source_id = ${input.sourceId} AND materialization_id = ${input.materializationId}
|
|
9835
|
+
UNION ALL
|
|
9836
|
+
SELECT rows.* FROM (${roots}) AS roots
|
|
9837
|
+
CROSS JOIN LATERAL (
|
|
9838
|
+
SELECT rows.* FROM ontology_source_rows AS rows WHERE rows.project_id = roots.project_id
|
|
9839
|
+
AND rows.source_id = roots.source_id AND rows.materialization_id = roots.materialization_id
|
|
9840
|
+
AND rows.root_sort_key = roots.root_sort_key OFFSET 0
|
|
9841
|
+
) AS rows
|
|
9842
|
+
`;
|
|
9843
|
+
}
|
|
9844
|
+
async function stageSourceRoots(sql, manifest, input) {
|
|
9845
|
+
const roots = sourceStageRoots(manifest.projection_kind, input);
|
|
9846
|
+
if (roots.length === 0)
|
|
9847
|
+
return;
|
|
9848
|
+
if (!manifest.base_materialization_id && roots.some((root) => root.deleted)) {
|
|
9849
|
+
throw new MaterializationValidationError("Root deletions require a source delta base.");
|
|
9850
|
+
}
|
|
9851
|
+
const values = jsonParameter2(sql, roots.map((root) => ({
|
|
9852
|
+
root_sort_key: root.sortKey,
|
|
9853
|
+
root_kind: root.root.kind,
|
|
9854
|
+
root_key: JSON.parse(root.rootKey),
|
|
9855
|
+
root: root.root,
|
|
9856
|
+
staging_ordinal: root.stagingOrdinal,
|
|
9857
|
+
deleted: root.deleted
|
|
9858
|
+
})));
|
|
9859
|
+
const inserted = await sql`
|
|
9860
|
+
INSERT INTO ontology_source_roots (
|
|
9861
|
+
project_id, source_id, materialization_id, root_sort_key, root_kind, root_key, root, staging_ordinal, deleted
|
|
9862
|
+
) SELECT ${input.projectId}, ${input.source.projectionId}, ${input.materializationId},
|
|
9863
|
+
root_sort_key, root_kind, root_key, root, staging_ordinal, deleted
|
|
9864
|
+
FROM jsonb_to_recordset(${values}) AS input(
|
|
9865
|
+
root_sort_key TEXT, root_kind TEXT, root_key JSONB, root JSONB, staging_ordinal BIGINT, deleted BOOLEAN)
|
|
9866
|
+
ON CONFLICT DO NOTHING RETURNING root_sort_key
|
|
9867
|
+
`;
|
|
9868
|
+
if (inserted.length === roots.length)
|
|
9869
|
+
return;
|
|
9870
|
+
const insertedKeys = new Set(inserted.map((row) => row.root_sort_key));
|
|
9871
|
+
const skipped = roots.filter((root) => !insertedKeys.has(root.sortKey));
|
|
9872
|
+
const stored = await sql`
|
|
9873
|
+
SELECT root_sort_key, staging_ordinal, deleted FROM ontology_source_roots
|
|
9874
|
+
WHERE project_id = ${input.projectId} AND source_id = ${input.source.projectionId}
|
|
9875
|
+
AND materialization_id = ${input.materializationId}
|
|
9876
|
+
AND root_sort_key = ANY(${sql.array(skipped.map((root) => root.sortKey))}::text[])
|
|
9877
|
+
`;
|
|
9878
|
+
const existing = new Map(stored.map((root) => [root.root_sort_key, root]));
|
|
9879
|
+
if (skipped.some((root) => {
|
|
9880
|
+
const previous = existing.get(root.sortKey);
|
|
9881
|
+
return !previous || Number(previous.staging_ordinal) !== root.stagingOrdinal || previous.deleted !== root.deleted;
|
|
9882
|
+
}))
|
|
9883
|
+
throw new MaterializationValidationError("Source materialization repeats root or stream ordinal with different content.");
|
|
9884
|
+
}
|
|
9885
|
+
async function activateSourceRoots(sql, projectId, candidate, activation) {
|
|
9886
|
+
if (candidate.base_materialization_id !== null && (candidate.base_materialization_id !== activation.expected.activeMaterializationId || candidate.base_commit_id !== activation.expected.lastCommitId))
|
|
9887
|
+
throw new MaterializationConflictError2("projection-fence", "Source delta base changed before activation.");
|
|
9888
|
+
if (candidate.base_materialization_id !== null && Number(candidate.root_count) === 0)
|
|
9889
|
+
return;
|
|
9890
|
+
const affected = candidate.base_materialization_id === null ? sql`` : sql`AND root_sort_key IN (
|
|
9891
|
+
SELECT root_sort_key FROM ontology_source_roots
|
|
9892
|
+
WHERE project_id = ${projectId} AND source_id = ${activation.source.projectionId}
|
|
9893
|
+
AND materialization_id = ${activation.materializationId}
|
|
9894
|
+
)`;
|
|
9895
|
+
await sql`
|
|
9896
|
+
UPDATE ontology_source_roots AS roots SET active = FALSE, retired_at = ${activation.updatedAt}
|
|
9897
|
+
WHERE project_id = ${projectId} AND source_id = ${activation.source.projectionId} AND active
|
|
9898
|
+
${affected}
|
|
9899
|
+
`;
|
|
9900
|
+
await sql`
|
|
9901
|
+
UPDATE ontology_source_roots SET active = NOT deleted,
|
|
9902
|
+
retired_at = CASE WHEN deleted THEN ${activation.updatedAt}::timestamptz ELSE NULL END
|
|
9903
|
+
WHERE project_id = ${projectId} AND source_id = ${activation.source.projectionId}
|
|
9904
|
+
AND materialization_id = ${activation.materializationId}
|
|
9905
|
+
`;
|
|
9906
|
+
}
|
|
9907
|
+
async function assertSourceRootCoverage(sql, manifest) {
|
|
9908
|
+
const [invalid] = await sql`
|
|
9909
|
+
SELECT 1 FROM ontology_source_roots AS roots
|
|
9910
|
+
WHERE project_id = ${manifest.project_id} AND source_id = ${manifest.source_id}
|
|
9911
|
+
AND materialization_id = ${manifest.materialization_id}
|
|
9912
|
+
AND roots.deleted = EXISTS (
|
|
9913
|
+
SELECT 1 FROM ontology_source_rows AS rows
|
|
9914
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
9915
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
9916
|
+
) LIMIT 1
|
|
9917
|
+
`;
|
|
9918
|
+
if (invalid)
|
|
9919
|
+
throw new MaterializationValidationError("Source roots must have complete assertions or an explicit deletion, never both.");
|
|
9920
|
+
}
|
|
9921
|
+
function activeSourceRows(sql, projectId, refs) {
|
|
9922
|
+
const keys = refs.flatMap((entity) => {
|
|
9923
|
+
const entitySortKey = utf8SortKey(projectionEntityKey(entity));
|
|
9924
|
+
const roots = entity.kind === "object" ? [entitySortKey] : [
|
|
9925
|
+
entitySortKey,
|
|
9926
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source }))
|
|
9927
|
+
];
|
|
9928
|
+
return roots.map((rootSortKey) => ({
|
|
9929
|
+
root_sort_key: rootSortKey,
|
|
9930
|
+
entity_sort_key: entitySortKey
|
|
9931
|
+
}));
|
|
9932
|
+
});
|
|
9933
|
+
return sql`
|
|
9934
|
+
SELECT rows.* FROM jsonb_to_recordset(${jsonParameter2(sql, keys)}) AS requested(root_sort_key TEXT, entity_sort_key TEXT)
|
|
9935
|
+
JOIN ontology_source_roots AS roots ON roots.project_id = ${projectId}
|
|
9936
|
+
AND roots.active AND roots.root_sort_key = requested.root_sort_key
|
|
9937
|
+
JOIN ontology_source_rows AS rows ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
9938
|
+
AND rows.materialization_id = roots.materialization_id AND rows.entity_sort_key = requested.entity_sort_key
|
|
9939
|
+
AND rows.root_sort_key = roots.root_sort_key
|
|
9940
|
+
`;
|
|
9941
|
+
}
|
|
9942
|
+
function replacementAssertionRows(sql, input) {
|
|
9943
|
+
const keys = input.refs.map((ref) => ({
|
|
9944
|
+
entity_kind: ref.kind,
|
|
9945
|
+
entity_key: JSON.parse(projectionEntityKey(ref)),
|
|
9946
|
+
root_keys: ref.kind === "object" ? [utf8SortKey(projectionEntityKey(ref))] : [
|
|
9947
|
+
utf8SortKey(projectionEntityKey(ref)),
|
|
9948
|
+
utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source }))
|
|
9949
|
+
]
|
|
9950
|
+
}));
|
|
9951
|
+
const previous = !input.includePrevious ? sql`` : sql`UNION ALL
|
|
9952
|
+
SELECT rows.* FROM (
|
|
9953
|
+
SELECT roots.* FROM ontology_source_roots AS roots
|
|
9954
|
+
WHERE roots.project_id = ${input.projectId} AND roots.source_id = ${input.sourceId}
|
|
9955
|
+
AND roots.active AND roots.root_sort_key = ANY(requested.root_keys)
|
|
9956
|
+
${!input.incremental ? sql`` : sql`AND EXISTS (SELECT 1 FROM ontology_source_roots AS changed
|
|
9957
|
+
WHERE changed.project_id = roots.project_id AND changed.source_id = roots.source_id
|
|
9958
|
+
AND changed.materialization_id = ${input.materializationId} AND changed.root_sort_key = roots.root_sort_key)`}
|
|
9959
|
+
|
|
9960
|
+
OFFSET 0
|
|
9961
|
+
) AS roots
|
|
9962
|
+
CROSS JOIN LATERAL (
|
|
9963
|
+
SELECT rows.* FROM ontology_source_rows AS rows
|
|
9964
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
9965
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
9966
|
+
AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
9967
|
+
OFFSET 0
|
|
9968
|
+
) AS rows
|
|
9969
|
+
`;
|
|
9970
|
+
return sql`
|
|
9971
|
+
SELECT selected.* FROM jsonb_to_recordset(${jsonParameter2(sql, keys)})
|
|
9972
|
+
AS requested(entity_kind TEXT, entity_key JSONB, root_keys TEXT[])
|
|
9973
|
+
CROSS JOIN LATERAL (
|
|
9974
|
+
SELECT rows.* FROM ontology_source_rows AS rows
|
|
9975
|
+
WHERE rows.project_id = ${input.projectId} AND rows.source_id = ${input.sourceId}
|
|
9976
|
+
AND rows.materialization_id = ${input.materializationId}
|
|
9977
|
+
AND rows.entity_kind = requested.entity_kind AND rows.entity_key = requested.entity_key
|
|
9978
|
+
${previous}
|
|
9979
|
+
OFFSET 0
|
|
9980
|
+
) AS selected
|
|
9981
|
+
`;
|
|
9982
|
+
}
|
|
9983
|
+
|
|
9984
|
+
// src/ontology-storage/materialization-state.ts
|
|
9731
9985
|
var PG_MATERIALIZATION_WORK_TABLE = "ontology_materialization_work";
|
|
9732
9986
|
var PG_REPLACEMENT_WORK_TABLE = "ontology_replacement_work";
|
|
9733
9987
|
|
|
@@ -9760,22 +10014,7 @@ class PgMaterializationStateReader {
|
|
|
9760
10014
|
JOIN requested USING (object_type_id, primary_id)
|
|
9761
10015
|
WHERE objects.project_id = ${this.projectId}
|
|
9762
10016
|
`,
|
|
9763
|
-
this.sql
|
|
9764
|
-
WITH requested AS (
|
|
9765
|
-
SELECT DISTINCT * FROM jsonb_to_recordset(${requestedParameter})
|
|
9766
|
-
AS requested_values(object_type_id TEXT, primary_id TEXT)
|
|
9767
|
-
)
|
|
9768
|
-
SELECT rows.*
|
|
9769
|
-
FROM ontology_source_rows AS rows
|
|
9770
|
-
JOIN requested USING (object_type_id, primary_id)
|
|
9771
|
-
JOIN ontology_sources AS sources
|
|
9772
|
-
ON sources.project_id = rows.project_id
|
|
9773
|
-
AND sources.source_id = rows.source_id
|
|
9774
|
-
AND sources.materialization_id = rows.materialization_id
|
|
9775
|
-
WHERE rows.project_id = ${this.projectId}
|
|
9776
|
-
AND rows.entity_kind = 'object'
|
|
9777
|
-
AND sources.status = 'active'
|
|
9778
|
-
`,
|
|
10017
|
+
activeSourceRows(this.sql, this.projectId, refs.map((ref) => ({ kind: "object", ref }))),
|
|
9779
10018
|
this.sql`
|
|
9780
10019
|
WITH requested AS (
|
|
9781
10020
|
SELECT DISTINCT * FROM jsonb_to_recordset(${requestedParameter})
|
|
@@ -9816,7 +10055,7 @@ class PgMaterializationStateReader {
|
|
|
9816
10055
|
const effectiveRow = effective.get(key);
|
|
9817
10056
|
return {
|
|
9818
10057
|
ref: structuredClone(ref),
|
|
9819
|
-
source: sourceObject(sources.get(
|
|
10058
|
+
source: sourceObject(sources.get(projectionEntityKey2({ kind: "object", ref }))),
|
|
9820
10059
|
override: overrides.get(key) ?? null,
|
|
9821
10060
|
effective: effectiveRow ? effectiveObjectSnapshot(effectiveRow) : null,
|
|
9822
10061
|
latestTelemetry: telemetry.get(key) ?? []
|
|
@@ -9852,26 +10091,7 @@ class PgMaterializationStateReader {
|
|
|
9852
10091
|
JOIN requested USING (source_type_id, source_id, link_id, target_type_id, target_id)
|
|
9853
10092
|
WHERE links.project_id = ${this.projectId}
|
|
9854
10093
|
`,
|
|
9855
|
-
this.sql
|
|
9856
|
-
WITH requested AS (
|
|
9857
|
-
SELECT DISTINCT * FROM jsonb_to_recordset(${requestedParameter}) AS requested_values(
|
|
9858
|
-
source_type_id TEXT, source_primary_id TEXT, link_id TEXT,
|
|
9859
|
-
target_type_id TEXT, target_primary_id TEXT
|
|
9860
|
-
)
|
|
9861
|
-
)
|
|
9862
|
-
SELECT rows.*
|
|
9863
|
-
FROM ontology_source_rows AS rows
|
|
9864
|
-
JOIN requested USING (
|
|
9865
|
-
source_type_id, source_primary_id, link_id, target_type_id, target_primary_id
|
|
9866
|
-
)
|
|
9867
|
-
JOIN ontology_sources AS sources
|
|
9868
|
-
ON sources.project_id = rows.project_id
|
|
9869
|
-
AND sources.source_id = rows.source_id
|
|
9870
|
-
AND sources.materialization_id = rows.materialization_id
|
|
9871
|
-
WHERE rows.project_id = ${this.projectId}
|
|
9872
|
-
AND rows.entity_kind = 'link'
|
|
9873
|
-
AND sources.status = 'active'
|
|
9874
|
-
`,
|
|
10094
|
+
activeSourceRows(this.sql, this.projectId, refs.map((ref) => ({ kind: "link", ref }))),
|
|
9875
10095
|
this.sql`
|
|
9876
10096
|
WITH requested AS (
|
|
9877
10097
|
SELECT DISTINCT * FROM jsonb_to_recordset(${requestedParameter}) AS requested_values(
|
|
@@ -9910,7 +10130,7 @@ class PgMaterializationStateReader {
|
|
|
9910
10130
|
const effectiveRow = effective.get(key);
|
|
9911
10131
|
return {
|
|
9912
10132
|
ref: structuredClone(ref),
|
|
9913
|
-
source: sourceLink(sources.get(
|
|
10133
|
+
source: sourceLink(sources.get(projectionEntityKey2({ kind: "link", ref }))),
|
|
9914
10134
|
override: overrides.get(key) ?? null,
|
|
9915
10135
|
slotOverride: slotOverrides.get(linkScopeSortKey(ref.source, ref.linkId)) ?? null,
|
|
9916
10136
|
effective: effectiveRow ? effectiveLinkSnapshot(effectiveRow) : null
|
|
@@ -9979,13 +10199,14 @@ class PgMaterializationStateReader {
|
|
|
9979
10199
|
ON requested.source_type_id = rows.source_type_id
|
|
9980
10200
|
AND requested.source_id = rows.source_primary_id
|
|
9981
10201
|
AND requested.link_id = rows.link_id
|
|
9982
|
-
JOIN
|
|
10202
|
+
JOIN ontology_source_roots AS sources
|
|
9983
10203
|
ON sources.project_id = rows.project_id
|
|
9984
10204
|
AND sources.source_id = rows.source_id
|
|
9985
10205
|
AND sources.materialization_id = rows.materialization_id
|
|
10206
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
9986
10207
|
WHERE rows.project_id = ${this.projectId}
|
|
9987
10208
|
AND rows.entity_kind = 'link'
|
|
9988
|
-
AND sources.
|
|
10209
|
+
AND sources.active
|
|
9989
10210
|
`,
|
|
9990
10211
|
this.sql`
|
|
9991
10212
|
WITH requested AS (
|
|
@@ -10058,7 +10279,7 @@ class PgMaterializationStateReader {
|
|
|
10058
10279
|
for (const row of rows) {
|
|
10059
10280
|
const accumulator = accumulators.get(row.scope_sort_key);
|
|
10060
10281
|
if (!accumulator) {
|
|
10061
|
-
throw new
|
|
10282
|
+
throw new MaterializationConflictError3("effective-state", `Unexpected link scope '${row.scope_sort_key}' returned by storage.`);
|
|
10062
10283
|
}
|
|
10063
10284
|
const snapshot = effectiveLinkSnapshot(row);
|
|
10064
10285
|
appendScopeSnapshot(accumulator, snapshot);
|
|
@@ -10127,30 +10348,32 @@ class PgMaterializationStateReader {
|
|
|
10127
10348
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
10128
10349
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
10129
10350
|
FROM ontology_source_rows AS rows
|
|
10130
|
-
JOIN
|
|
10351
|
+
JOIN ontology_source_roots AS sources
|
|
10131
10352
|
ON sources.project_id = rows.project_id
|
|
10132
10353
|
AND sources.source_id = rows.source_id
|
|
10133
10354
|
AND sources.materialization_id = rows.materialization_id
|
|
10355
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
10134
10356
|
JOIN requested
|
|
10135
10357
|
ON requested.object_type_id = rows.source_type_id
|
|
10136
10358
|
AND requested.primary_id = rows.source_primary_id
|
|
10137
10359
|
WHERE rows.project_id = ${this.projectId}
|
|
10138
10360
|
AND rows.entity_kind = 'link'
|
|
10139
|
-
AND sources.
|
|
10361
|
+
AND sources.active
|
|
10140
10362
|
UNION
|
|
10141
10363
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
10142
10364
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
10143
10365
|
FROM ontology_source_rows AS rows
|
|
10144
|
-
JOIN
|
|
10366
|
+
JOIN ontology_source_roots AS sources
|
|
10145
10367
|
ON sources.project_id = rows.project_id
|
|
10146
10368
|
AND sources.source_id = rows.source_id
|
|
10147
10369
|
AND sources.materialization_id = rows.materialization_id
|
|
10370
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
10148
10371
|
JOIN requested
|
|
10149
10372
|
ON requested.object_type_id = rows.target_type_id
|
|
10150
10373
|
AND requested.primary_id = rows.target_primary_id
|
|
10151
10374
|
WHERE rows.project_id = ${this.projectId}
|
|
10152
10375
|
AND rows.entity_kind = 'link'
|
|
10153
|
-
AND sources.
|
|
10376
|
+
AND sources.active
|
|
10154
10377
|
), selected AS (
|
|
10155
10378
|
SELECT DISTINCT links.*,
|
|
10156
10379
|
${this.sql.unsafe(linkSortExpression("links"))} AS sort_key
|
|
@@ -10169,13 +10392,15 @@ class PgMaterializationStateReader {
|
|
|
10169
10392
|
}
|
|
10170
10393
|
async* replacementIdentities(input) {
|
|
10171
10394
|
await this.prepareReplacementIdentities(input);
|
|
10395
|
+
await this.sql`ANALYZE ${this.sql(PG_REPLACEMENT_WORK_TABLE)}`;
|
|
10172
10396
|
let cursor = null;
|
|
10173
10397
|
while (true) {
|
|
10398
|
+
const after = cursor === null ? this.sql`` : this.sql`AND sort_key > ${cursor}`;
|
|
10174
10399
|
const rows = await this.sql`
|
|
10175
10400
|
SELECT entity_kind, identity_key, sort_key, diff_required
|
|
10176
10401
|
FROM ${this.sql(PG_REPLACEMENT_WORK_TABLE)}
|
|
10177
10402
|
WHERE session_id = ${input.sessionId} AND entity_kind = ${input.kind}
|
|
10178
|
-
|
|
10403
|
+
${after}
|
|
10179
10404
|
ORDER BY sort_key
|
|
10180
10405
|
LIMIT ${input.pageRows}
|
|
10181
10406
|
`;
|
|
@@ -10190,19 +10415,19 @@ class PgMaterializationStateReader {
|
|
|
10190
10415
|
const candidates = await this.replacementSources(sourceId, [candidateMaterializationId], refs.map((ref) => ({ kind: "object", ref })));
|
|
10191
10416
|
return base.map((state) => ({
|
|
10192
10417
|
ref: state.ref,
|
|
10193
|
-
candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(
|
|
10418
|
+
candidateSource: sourceObject(candidates.byMaterialization.get(candidateMaterializationId)?.get(projectionEntityKey2({ kind: "object", ref: state.ref }))),
|
|
10194
10419
|
override: state.override,
|
|
10195
10420
|
effective: state.effective,
|
|
10196
10421
|
latestTelemetry: state.latestTelemetry
|
|
10197
10422
|
}));
|
|
10198
10423
|
}
|
|
10199
|
-
async replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities) {
|
|
10424
|
+
async replacementLinkStates(sourceId, candidateMaterializationId, materializationIds, identities, incremental = false) {
|
|
10200
10425
|
const refs = identities.map((identity) => identity.ref);
|
|
10201
10426
|
const base = await this.linkStates(refs);
|
|
10202
|
-
const replacements = await this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })));
|
|
10427
|
+
const replacements = await this.replacementSources(sourceId, materializationIds, refs.map((ref) => ({ kind: "link", ref })), incremental);
|
|
10203
10428
|
const candidate = replacements.byMaterialization.get(candidateMaterializationId);
|
|
10204
10429
|
return base.map((state, index) => {
|
|
10205
|
-
const key =
|
|
10430
|
+
const key = projectionEntityKey2({ kind: "link", ref: state.ref });
|
|
10206
10431
|
return {
|
|
10207
10432
|
ref: state.ref,
|
|
10208
10433
|
candidateSource: replacements.owned.has(key) ? sourceLink(candidate?.get(key)) : state.source,
|
|
@@ -10269,33 +10494,23 @@ class PgMaterializationStateReader {
|
|
|
10269
10494
|
`;
|
|
10270
10495
|
return new Map(rows.map((row) => [linkRefKey(linkRefFromColumns(row)), row.last_commit_id]));
|
|
10271
10496
|
}
|
|
10272
|
-
async replacementSources(sourceId, materializationIds, refs) {
|
|
10497
|
+
async replacementSources(sourceId, materializationIds, refs, incremental = false) {
|
|
10273
10498
|
if (materializationIds.length === 0 || refs.length === 0) {
|
|
10274
10499
|
return { owned: new Set, byMaterialization: new Map };
|
|
10275
10500
|
}
|
|
10276
|
-
const
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
AS requested_values(entity_kind TEXT, entity_key JSONB)
|
|
10285
|
-
)
|
|
10286
|
-
SELECT rows.* FROM ontology_source_rows AS rows
|
|
10287
|
-
JOIN requested USING (entity_kind, entity_key)
|
|
10288
|
-
WHERE rows.project_id = ${this.projectId}
|
|
10289
|
-
AND rows.source_id = ${sourceId}
|
|
10290
|
-
AND rows.materialization_id = ANY(
|
|
10291
|
-
${this.sql.array([...new Set(materializationIds)])}::text[]
|
|
10292
|
-
)
|
|
10293
|
-
`;
|
|
10501
|
+
const rows = await replacementAssertionRows(this.sql, {
|
|
10502
|
+
projectId: this.projectId,
|
|
10503
|
+
sourceId,
|
|
10504
|
+
materializationId: materializationIds[0],
|
|
10505
|
+
incremental,
|
|
10506
|
+
includePrevious: materializationIds.length > 1,
|
|
10507
|
+
refs
|
|
10508
|
+
});
|
|
10294
10509
|
const owned = new Set;
|
|
10295
10510
|
const byMaterialization = new Map;
|
|
10296
10511
|
for (const row of rows) {
|
|
10297
10512
|
const source = storedSource(row);
|
|
10298
|
-
const key =
|
|
10513
|
+
const key = projectionEntityKey2(source.assertion);
|
|
10299
10514
|
owned.add(key);
|
|
10300
10515
|
const materialization = byMaterialization.get(row.materialization_id) ?? new Map;
|
|
10301
10516
|
materialization.set(key, source);
|
|
@@ -10311,20 +10526,19 @@ class PgMaterializationStateReader {
|
|
|
10311
10526
|
await this.prepareReplacementLinks(input);
|
|
10312
10527
|
}
|
|
10313
10528
|
async prepareReplacementObjects(input) {
|
|
10314
|
-
const
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10529
|
+
const rows = replacementSourceRows(this.sql, {
|
|
10530
|
+
projectId: this.projectId,
|
|
10531
|
+
sourceId: input.sourceId,
|
|
10532
|
+
materializationId: input.candidateMaterializationId,
|
|
10533
|
+
incremental: input.incremental ?? false
|
|
10534
|
+
});
|
|
10318
10535
|
await this.sql`
|
|
10319
10536
|
WITH selected AS (
|
|
10320
10537
|
SELECT DISTINCT object_type_id, primary_id,
|
|
10321
10538
|
${this.sql.unsafe(objectKeyExpression("rows"))} AS identity_key,
|
|
10322
10539
|
${this.sql.unsafe(objectSortExpression("rows"))} AS sort_key
|
|
10323
|
-
FROM
|
|
10324
|
-
WHERE
|
|
10325
|
-
AND source_id = ${input.sourceId}
|
|
10326
|
-
AND entity_kind = 'object'
|
|
10327
|
-
AND materialization_id = ANY(${this.sql.array(materializationIds)}::text[])
|
|
10540
|
+
FROM (${rows}) AS rows
|
|
10541
|
+
WHERE entity_kind = 'object'
|
|
10328
10542
|
)
|
|
10329
10543
|
INSERT INTO ${this.sql(PG_REPLACEMENT_WORK_TABLE)} (
|
|
10330
10544
|
session_id, entity_kind, identity_key, sort_key, diff_required
|
|
@@ -10337,10 +10551,13 @@ class PgMaterializationStateReader {
|
|
|
10337
10551
|
`;
|
|
10338
10552
|
}
|
|
10339
10553
|
async prepareReplacementLinks(input) {
|
|
10340
|
-
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
|
|
10554
|
+
await this.sql`ANALYZE ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}`;
|
|
10555
|
+
const rows = replacementSourceRows(this.sql, {
|
|
10556
|
+
projectId: this.projectId,
|
|
10557
|
+
sourceId: input.sourceId,
|
|
10558
|
+
materializationId: input.candidateMaterializationId,
|
|
10559
|
+
incremental: input.incremental ?? false
|
|
10560
|
+
});
|
|
10344
10561
|
await this.sql`
|
|
10345
10562
|
WITH incident_objects AS (
|
|
10346
10563
|
SELECT DISTINCT payload->'ref'->>'objectTypeId' AS object_type_id,
|
|
@@ -10350,11 +10567,8 @@ class PgMaterializationStateReader {
|
|
|
10350
10567
|
), replacement_links AS (
|
|
10351
10568
|
SELECT source_type_id, source_primary_id AS source_id, link_id,
|
|
10352
10569
|
target_type_id, target_primary_id AS target_id
|
|
10353
|
-
FROM
|
|
10354
|
-
WHERE
|
|
10355
|
-
AND source_id = ${input.sourceId}
|
|
10356
|
-
AND entity_kind = 'link'
|
|
10357
|
-
AND materialization_id = ANY(${this.sql.array(materializationIds)}::text[])
|
|
10570
|
+
FROM (${rows}) AS rows
|
|
10571
|
+
WHERE entity_kind = 'link'
|
|
10358
10572
|
), incident_links AS (
|
|
10359
10573
|
SELECT links.source_type_id, links.source_id, links.link_id,
|
|
10360
10574
|
links.target_type_id, links.target_id
|
|
@@ -10393,30 +10607,32 @@ class PgMaterializationStateReader {
|
|
|
10393
10607
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
10394
10608
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
10395
10609
|
FROM ontology_source_rows AS rows
|
|
10396
|
-
JOIN
|
|
10610
|
+
JOIN ontology_source_roots AS sources
|
|
10397
10611
|
ON sources.project_id = rows.project_id
|
|
10398
10612
|
AND sources.source_id = rows.source_id
|
|
10399
10613
|
AND sources.materialization_id = rows.materialization_id
|
|
10614
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
10400
10615
|
JOIN incident_objects
|
|
10401
10616
|
ON incident_objects.object_type_id = rows.source_type_id
|
|
10402
10617
|
AND incident_objects.primary_id = rows.source_primary_id
|
|
10403
10618
|
WHERE rows.project_id = ${this.projectId}
|
|
10404
10619
|
AND rows.entity_kind = 'link'
|
|
10405
|
-
AND sources.
|
|
10620
|
+
AND sources.active
|
|
10406
10621
|
UNION
|
|
10407
10622
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
10408
10623
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
10409
10624
|
FROM ontology_source_rows AS rows
|
|
10410
|
-
JOIN
|
|
10625
|
+
JOIN ontology_source_roots AS sources
|
|
10411
10626
|
ON sources.project_id = rows.project_id
|
|
10412
10627
|
AND sources.source_id = rows.source_id
|
|
10413
10628
|
AND sources.materialization_id = rows.materialization_id
|
|
10629
|
+
AND sources.root_sort_key = rows.root_sort_key
|
|
10414
10630
|
JOIN incident_objects
|
|
10415
10631
|
ON incident_objects.object_type_id = rows.target_type_id
|
|
10416
10632
|
AND incident_objects.primary_id = rows.target_primary_id
|
|
10417
10633
|
WHERE rows.project_id = ${this.projectId}
|
|
10418
10634
|
AND rows.entity_kind = 'link'
|
|
10419
|
-
AND sources.
|
|
10635
|
+
AND sources.active
|
|
10420
10636
|
), diff_links AS (
|
|
10421
10637
|
SELECT * FROM replacement_links
|
|
10422
10638
|
UNION SELECT * FROM incident_links
|
|
@@ -10484,9 +10700,9 @@ function activeSourceMap(rows) {
|
|
|
10484
10700
|
const result = new Map;
|
|
10485
10701
|
for (const row of rows) {
|
|
10486
10702
|
const source = storedSource(row);
|
|
10487
|
-
const key =
|
|
10703
|
+
const key = projectionEntityKey2(source.assertion);
|
|
10488
10704
|
if (result.has(key)) {
|
|
10489
|
-
throw new
|
|
10705
|
+
throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert ${key}.`);
|
|
10490
10706
|
}
|
|
10491
10707
|
result.set(key, source);
|
|
10492
10708
|
}
|
|
@@ -10501,7 +10717,7 @@ function activeScopeSourceMap(rows) {
|
|
|
10501
10717
|
const ref = source.assertion.ref;
|
|
10502
10718
|
const key = linkScopeSortKey(ref.source, ref.linkId);
|
|
10503
10719
|
if (result.has(key)) {
|
|
10504
|
-
throw new
|
|
10720
|
+
throw new MaterializationConflictError3("source-materialization", `Multiple active sources assert cardinality-one link slot ${key}.`);
|
|
10505
10721
|
}
|
|
10506
10722
|
result.set(key, source);
|
|
10507
10723
|
}
|
|
@@ -10565,7 +10781,7 @@ function storedSource(row) {
|
|
|
10565
10781
|
}
|
|
10566
10782
|
function effectiveObjectSnapshot(row) {
|
|
10567
10783
|
if (!row.last_commit_id) {
|
|
10568
|
-
throw new
|
|
10784
|
+
throw new MaterializationConflictError3("effective-state", `Effective object ${row.object_type_id}:${row.primary_id} lacks materializer provenance.`);
|
|
10569
10785
|
}
|
|
10570
10786
|
return {
|
|
10571
10787
|
ref: objectRefFromColumns(row),
|
|
@@ -10578,7 +10794,7 @@ function effectiveObjectSnapshot(row) {
|
|
|
10578
10794
|
}
|
|
10579
10795
|
function effectiveLinkSnapshot(row) {
|
|
10580
10796
|
if (!row.last_commit_id) {
|
|
10581
|
-
throw new
|
|
10797
|
+
throw new MaterializationConflictError3("effective-state", `Effective link ${linkRefKey(linkRefFromColumns(row))} lacks materializer provenance.`);
|
|
10582
10798
|
}
|
|
10583
10799
|
return {
|
|
10584
10800
|
ref: linkRefFromColumns(row),
|
|
@@ -10593,7 +10809,7 @@ function effectiveLinkSnapshot(row) {
|
|
|
10593
10809
|
function storedPoint(row) {
|
|
10594
10810
|
const at = toIsoString2(row.at);
|
|
10595
10811
|
if (!row.last_commit_id) {
|
|
10596
|
-
throw new
|
|
10812
|
+
throw new MaterializationConflictError3("timeseries-point", `Telemetry point ${telemetryPointSortKey({
|
|
10597
10813
|
object: { objectTypeId: row.object_type_id, primaryId: row.object_id },
|
|
10598
10814
|
propertyId: row.property_id
|
|
10599
10815
|
}, at)} lacks materializer provenance.`);
|
|
@@ -10652,6 +10868,9 @@ function jsonTupleExpression(parts) {
|
|
|
10652
10868
|
|
|
10653
10869
|
// src/ontology-storage/materialization-session.ts
|
|
10654
10870
|
class PgMaterializationSessionState extends ProviderMaterializationSessionState {
|
|
10871
|
+
stagedWorkCount = 0;
|
|
10872
|
+
changedObjects = 0;
|
|
10873
|
+
changedLinks = 0;
|
|
10655
10874
|
appliedPlanCursor = null;
|
|
10656
10875
|
appliedEventCursor = null;
|
|
10657
10876
|
}
|
|
@@ -10668,7 +10887,7 @@ class PgMaterializationSessions {
|
|
|
10668
10887
|
}
|
|
10669
10888
|
async create(header) {
|
|
10670
10889
|
if (!this.context?.active) {
|
|
10671
|
-
throw new
|
|
10890
|
+
throw new MaterializationConflictError4("effective-state", "Materialization sessions require an active storage transaction.");
|
|
10672
10891
|
}
|
|
10673
10892
|
await this.ensureTables();
|
|
10674
10893
|
const session = new PgMaterializationSessionState(structuredClone(header), this.context.id);
|
|
@@ -10680,7 +10899,7 @@ class PgMaterializationSessions {
|
|
|
10680
10899
|
require(session) {
|
|
10681
10900
|
const value = this.sessions.get(session.providerToken);
|
|
10682
10901
|
if (!value || !value.active || !this.context?.active || value.transactionId !== this.context.id) {
|
|
10683
|
-
throw new
|
|
10902
|
+
throw new MaterializationConflictError4("effective-state", "Materialization session is inactive.");
|
|
10684
10903
|
}
|
|
10685
10904
|
return value;
|
|
10686
10905
|
}
|
|
@@ -10738,14 +10957,18 @@ class PgMaterializationSessions {
|
|
|
10738
10957
|
}
|
|
10739
10958
|
throw error;
|
|
10740
10959
|
}
|
|
10960
|
+
session.stagedWorkCount += records.length;
|
|
10741
10961
|
}
|
|
10742
10962
|
async* stream(input) {
|
|
10743
10963
|
const session = this.require(input.session);
|
|
10744
10964
|
const stream = session.workStreams[input.order];
|
|
10745
10965
|
if (stream.started) {
|
|
10746
|
-
throw new
|
|
10966
|
+
throw new MaterializationConflictError4("effective-state", `Materialization ${input.order} work may only be streamed once per session.`);
|
|
10747
10967
|
}
|
|
10748
10968
|
stream.started = true;
|
|
10969
|
+
if (!session.workSealed && session.stagedWorkCount >= 1e4) {
|
|
10970
|
+
await this.sql`ANALYZE ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}`;
|
|
10971
|
+
}
|
|
10749
10972
|
session.workSealed = true;
|
|
10750
10973
|
let cursor = null;
|
|
10751
10974
|
while (true) {
|
|
@@ -10766,10 +10989,13 @@ class PgMaterializationSessions {
|
|
|
10766
10989
|
return [];
|
|
10767
10990
|
const keys = refs.map((ref) => `object-existence:${JSON.stringify([ref.objectTypeId, ref.primaryId])}`);
|
|
10768
10991
|
const rows = await this.sql`
|
|
10769
|
-
SELECT unique_key, payload
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10992
|
+
SELECT work.unique_key, work.payload
|
|
10993
|
+
FROM unnest(${this.sql.array(keys)}::text[]) AS requested(unique_key)
|
|
10994
|
+
CROSS JOIN LATERAL (
|
|
10995
|
+
SELECT unique_key, payload FROM ${this.sql(PG_MATERIALIZATION_WORK_TABLE)}
|
|
10996
|
+
WHERE session_id = ${session.id} AND unique_key = requested.unique_key
|
|
10997
|
+
OFFSET 0
|
|
10998
|
+
) AS work
|
|
10773
10999
|
`;
|
|
10774
11000
|
const found = new Map(rows.map((row) => [row.unique_key, row.payload]));
|
|
10775
11001
|
return keys.flatMap((key) => {
|
|
@@ -10777,6 +11003,9 @@ class PgMaterializationSessions {
|
|
|
10777
11003
|
if (!payload)
|
|
10778
11004
|
return [];
|
|
10779
11005
|
const record = structuredClone(payload);
|
|
11006
|
+
if (record.kind !== "object-existence") {
|
|
11007
|
+
invalidCorrelation("Object existence work has an invalid kind.");
|
|
11008
|
+
}
|
|
10780
11009
|
return [{ ref: record.ref, exists: record.exists }];
|
|
10781
11010
|
});
|
|
10782
11011
|
}
|
|
@@ -10931,7 +11160,7 @@ function workCursor(row) {
|
|
|
10931
11160
|
// src/ontology-storage/materialization-writer.ts
|
|
10932
11161
|
import {
|
|
10933
11162
|
linkRefKey as linkRefKey2,
|
|
10934
|
-
MaterializationConflictError as
|
|
11163
|
+
MaterializationConflictError as MaterializationConflictError5,
|
|
10935
11164
|
objectRefKey as objectRefKey2,
|
|
10936
11165
|
telemetryPointKey as telemetryPointKey2
|
|
10937
11166
|
} from "@sixb/core/internal/materialization";
|
|
@@ -11447,7 +11676,7 @@ function effectiveLinkConflict(item) {
|
|
|
11447
11676
|
return effectiveConflict(`Expected link ${linkIdentityKey(item)} changed.`);
|
|
11448
11677
|
}
|
|
11449
11678
|
function pointConflict(item) {
|
|
11450
|
-
return new
|
|
11679
|
+
return new MaterializationConflictError5("timeseries-point", `Telemetry point ${telemetryPointKey2({
|
|
11451
11680
|
object: { objectTypeId: item.objectTypeId, primaryId: item.objectId },
|
|
11452
11681
|
propertyId: item.propertyId
|
|
11453
11682
|
}, item.at)} changed.`);
|
|
@@ -11482,14 +11711,14 @@ class PgOntologyMaterializationStorage {
|
|
|
11482
11711
|
const linkScopeRevisions = await reader.linkScopeRevisions(input.expected.linkScopes);
|
|
11483
11712
|
for (const [index, expected] of input.expected.linkScopes.entries()) {
|
|
11484
11713
|
if (linkScopeRevisions[index]?.fingerprint !== expected.fingerprint) {
|
|
11485
|
-
throw new
|
|
11714
|
+
throw new MaterializationConflictError6("effective-state", `Expected link scope changed for ${expected.source.objectTypeId}:${expected.source.primaryId}.${expected.linkId}.`);
|
|
11486
11715
|
}
|
|
11487
11716
|
}
|
|
11488
11717
|
const points = await reader.exactPoints(input.expected.points, true);
|
|
11489
11718
|
const pointRevisions = new Map(points.map((point) => [telemetryPointKey3(point.series, point.at), point.lastCommitId]));
|
|
11490
11719
|
for (const expected of input.expected.points) {
|
|
11491
11720
|
if ((pointRevisions.get(telemetryPointKey3(expected.series, expected.at)) ?? null) !== expected.lastCommitId) {
|
|
11492
|
-
throw new
|
|
11721
|
+
throw new MaterializationConflictError6("timeseries-point", `Telemetry point ${telemetryPointKey3(expected.series, expected.at)} changed.`);
|
|
11493
11722
|
}
|
|
11494
11723
|
}
|
|
11495
11724
|
return session.publicSession();
|
|
@@ -11548,10 +11777,10 @@ class PgOntologyMaterializationStorage {
|
|
|
11548
11777
|
const reader = new PgMaterializationStateReader(this.sql, session.header.commit.projectId);
|
|
11549
11778
|
if (input.entityKind === "object") {
|
|
11550
11779
|
if (replacement.projectionKind !== "object") {
|
|
11551
|
-
throw new
|
|
11780
|
+
throw new MaterializationConflictError6("source-materialization", "Link projection replacement cannot stream object state.");
|
|
11552
11781
|
}
|
|
11553
11782
|
if (replacement.objectStreamStarted) {
|
|
11554
|
-
throw new
|
|
11783
|
+
throw new MaterializationConflictError6("effective-state", "Replacement object state may only be streamed once per session.");
|
|
11555
11784
|
}
|
|
11556
11785
|
replacement.objectStreamStarted = true;
|
|
11557
11786
|
for await (const identities of reader.replacementIdentities({
|
|
@@ -11559,6 +11788,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11559
11788
|
sourceId: replacement.sourceId,
|
|
11560
11789
|
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
11561
11790
|
previousMaterializationId: replacement.previousMaterializationId,
|
|
11791
|
+
incremental: replacement.incremental,
|
|
11562
11792
|
kind: "object",
|
|
11563
11793
|
pageRows: input.pageRows
|
|
11564
11794
|
})) {
|
|
@@ -11577,10 +11807,10 @@ class PgOntologyMaterializationStorage {
|
|
|
11577
11807
|
return;
|
|
11578
11808
|
}
|
|
11579
11809
|
if (replacement.projectionKind === "object" && !replacement.objectStreamCompleted) {
|
|
11580
|
-
throw new
|
|
11810
|
+
throw new MaterializationConflictError6("effective-state", "Object projection replacement must fully stream object state before link state.");
|
|
11581
11811
|
}
|
|
11582
11812
|
if (replacement.linkStreamStarted) {
|
|
11583
|
-
throw new
|
|
11813
|
+
throw new MaterializationConflictError6("effective-state", "Replacement link state may only be streamed once per session.");
|
|
11584
11814
|
}
|
|
11585
11815
|
replacement.linkStreamStarted = true;
|
|
11586
11816
|
const materializationIds = [
|
|
@@ -11592,6 +11822,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11592
11822
|
sourceId: replacement.sourceId,
|
|
11593
11823
|
candidateMaterializationId: replacement.candidateMaterializationId,
|
|
11594
11824
|
previousMaterializationId: replacement.previousMaterializationId,
|
|
11825
|
+
incremental: replacement.incremental,
|
|
11595
11826
|
kind: "link",
|
|
11596
11827
|
pageRows: input.pageRows
|
|
11597
11828
|
})) {
|
|
@@ -11602,7 +11833,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11602
11833
|
}
|
|
11603
11834
|
return identity;
|
|
11604
11835
|
});
|
|
11605
|
-
const links = await reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities);
|
|
11836
|
+
const links = await reader.replacementLinkStates(replacement.sourceId, replacement.candidateMaterializationId, materializationIds, linkIdentities, replacement.incremental);
|
|
11606
11837
|
yield { objects: [], links };
|
|
11607
11838
|
}
|
|
11608
11839
|
this.sessions.require(input.session);
|
|
@@ -11625,6 +11856,8 @@ class PgOntologyMaterializationStorage {
|
|
|
11625
11856
|
assertPlanChunkCorrelations(input.chunk, commit);
|
|
11626
11857
|
const progress = await this.sessions.prepareChunkSequence(session, input.chunk);
|
|
11627
11858
|
await this.writer.apply(commit.projectId, commit.id, input.chunk);
|
|
11859
|
+
session.changedObjects += input.chunk.effective.objectUpserts.length + input.chunk.effective.objectDeletes.length;
|
|
11860
|
+
session.changedLinks += input.chunk.effective.linkUpserts.length + input.chunk.effective.linkDeletes.length;
|
|
11628
11861
|
this.sessions.commitChunkSequence(session, progress);
|
|
11629
11862
|
}
|
|
11630
11863
|
async finalize(input) {
|
|
@@ -11634,6 +11867,10 @@ class PgOntologyMaterializationStorage {
|
|
|
11634
11867
|
for (const activation of input.finalization.sourceActivations) {
|
|
11635
11868
|
await this.activateSource(session, activation);
|
|
11636
11869
|
}
|
|
11870
|
+
if (session.changedObjects >= 1000)
|
|
11871
|
+
await this.sql`ANALYZE objects`;
|
|
11872
|
+
if (session.changedLinks >= 1000)
|
|
11873
|
+
await this.sql`ANALYZE links`;
|
|
11637
11874
|
const record = await this.insertCommit(session.header, input);
|
|
11638
11875
|
await this.sessions.release(session);
|
|
11639
11876
|
return { commit: record };
|
|
@@ -11644,14 +11881,14 @@ class PgOntologyMaterializationStorage {
|
|
|
11644
11881
|
async requireReplacement(session, input) {
|
|
11645
11882
|
if (session.replacement) {
|
|
11646
11883
|
if (session.replacement.sourceId !== input.source.projectionId || session.replacement.candidateMaterializationId !== input.candidateMaterializationId) {
|
|
11647
|
-
throw new
|
|
11884
|
+
throw new MaterializationConflictError6("source-materialization", "Materialization session already owns another replacement union.");
|
|
11648
11885
|
}
|
|
11649
11886
|
return session.replacement;
|
|
11650
11887
|
}
|
|
11651
11888
|
const projectId = session.header.commit.projectId;
|
|
11652
11889
|
const candidate = await this.getSource(projectId, input.source.projectionId, input.candidateMaterializationId, true);
|
|
11653
11890
|
if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
|
|
11654
|
-
throw new
|
|
11891
|
+
throw new MaterializationConflictError6("source-materialization", `Candidate source materialization '${input.candidateMaterializationId}' is missing or is not ready.`);
|
|
11655
11892
|
}
|
|
11656
11893
|
await assertProjectionExecution(this.sql, {
|
|
11657
11894
|
projectId,
|
|
@@ -11661,12 +11898,13 @@ class PgOntologyMaterializationStorage {
|
|
|
11661
11898
|
});
|
|
11662
11899
|
const previous = await this.getActiveSource(projectId, input.source.projectionId, true);
|
|
11663
11900
|
if (candidate.protocol !== "replacement" || previous && (previous.protocol !== candidate.protocol || previous.projection_kind !== candidate.projection_kind)) {
|
|
11664
|
-
throw new
|
|
11901
|
+
throw new MaterializationConflictError6("source-materialization", "Source replacement kind or protocol does not match its active materialization.");
|
|
11665
11902
|
}
|
|
11666
11903
|
session.replacement = {
|
|
11667
11904
|
sourceId: input.source.projectionId,
|
|
11668
11905
|
candidateMaterializationId: input.candidateMaterializationId,
|
|
11669
11906
|
previousMaterializationId: previous?.materialization_id ?? null,
|
|
11907
|
+
incremental: candidate.base_materialization_id !== null,
|
|
11670
11908
|
projectionKind: candidate.projection_kind,
|
|
11671
11909
|
objectStreamStarted: false,
|
|
11672
11910
|
objectStreamCompleted: false,
|
|
@@ -11686,7 +11924,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11686
11924
|
FOR UPDATE
|
|
11687
11925
|
`;
|
|
11688
11926
|
if (duplicateIdentity) {
|
|
11689
|
-
throw new
|
|
11927
|
+
throw new MaterializationConflictError6("idempotency", duplicateIdentity.id === header.commit.id ? `Ontology commit '${header.commit.id}' already exists.` : "Ontology idempotency key already exists.");
|
|
11690
11928
|
}
|
|
11691
11929
|
const origin = originColumns(header.commit.origin);
|
|
11692
11930
|
if (origin.runId === null)
|
|
@@ -11709,7 +11947,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11709
11947
|
FOR UPDATE
|
|
11710
11948
|
`;
|
|
11711
11949
|
if (duplicateOrigin) {
|
|
11712
|
-
throw new
|
|
11950
|
+
throw new MaterializationConflictError6("run-correlation", "Ontology commit origin already has an authoritative commit.");
|
|
11713
11951
|
}
|
|
11714
11952
|
}
|
|
11715
11953
|
async assertSources(expectedSources, projectId) {
|
|
@@ -11731,29 +11969,29 @@ class PgOntologyMaterializationStorage {
|
|
|
11731
11969
|
}
|
|
11732
11970
|
assertSourceRow(expected, active) {
|
|
11733
11971
|
if ((active?.materialization_id ?? null) !== expected.activeMaterializationId || (active?.last_commit_id ?? null) !== expected.lastCommitId) {
|
|
11734
|
-
throw new
|
|
11972
|
+
throw new MaterializationConflictError6("projection-fence", `Source '${expected.source.projectionId}' changed.`);
|
|
11735
11973
|
}
|
|
11736
11974
|
}
|
|
11737
11975
|
assertObject(row, expected) {
|
|
11738
11976
|
if (!expected.exists) {
|
|
11739
11977
|
if (row) {
|
|
11740
|
-
throw new
|
|
11978
|
+
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} to be absent.`);
|
|
11741
11979
|
}
|
|
11742
11980
|
return;
|
|
11743
11981
|
}
|
|
11744
11982
|
if (!row || row.version !== expected.version || row.lastCommitId !== expected.lastCommitId) {
|
|
11745
|
-
throw new
|
|
11983
|
+
throw new MaterializationConflictError6("effective-state", `Expected object ${objectRefKey3(expected.ref)} changed.`);
|
|
11746
11984
|
}
|
|
11747
11985
|
}
|
|
11748
11986
|
assertLink(lastCommitId, expected) {
|
|
11749
11987
|
if (!expected.exists) {
|
|
11750
11988
|
if (lastCommitId !== undefined) {
|
|
11751
|
-
throw new
|
|
11989
|
+
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} to be absent.`);
|
|
11752
11990
|
}
|
|
11753
11991
|
return;
|
|
11754
11992
|
}
|
|
11755
11993
|
if (lastCommitId === undefined || lastCommitId !== expected.lastCommitId) {
|
|
11756
|
-
throw new
|
|
11994
|
+
throw new MaterializationConflictError6("effective-state", `Expected link ${linkRefKey3(expected.ref)} changed.`);
|
|
11757
11995
|
}
|
|
11758
11996
|
}
|
|
11759
11997
|
async assertFinalization(session, input) {
|
|
@@ -11762,7 +12000,8 @@ class PgOntologyMaterializationStorage {
|
|
|
11762
12000
|
assertMaterializationFinalizationCorrelation(session, input);
|
|
11763
12001
|
const laneCounts = await this.sessions.laneCounts(session);
|
|
11764
12002
|
assertMaterializationLaneCompletion(session, laneCounts);
|
|
11765
|
-
|
|
12003
|
+
if (laneCounts.cardinality > 0)
|
|
12004
|
+
await this.assertFinalCardinality(session);
|
|
11766
12005
|
const eventCount = laneCounts.event;
|
|
11767
12006
|
const [outbox] = await this.sql`
|
|
11768
12007
|
SELECT COUNT(*) AS count, MIN(commit_ordinal) AS minimum,
|
|
@@ -11795,17 +12034,18 @@ class PgOntologyMaterializationStorage {
|
|
|
11795
12034
|
}
|
|
11796
12035
|
}
|
|
11797
12036
|
async assertFinalCardinality(session) {
|
|
11798
|
-
const
|
|
11799
|
-
|
|
11800
|
-
SELECT
|
|
11801
|
-
payload->>'linkSortKey' AS link_sort_key,
|
|
12037
|
+
const table = `ontology_cardinality_${session.id.replaceAll("-", "")}`;
|
|
12038
|
+
await this.sql.unsafe(`CREATE TEMP TABLE ${table} ON COMMIT DROP AS
|
|
12039
|
+
SELECT sort_one AS scope_sort_key, sort_two AS link_sort_key,
|
|
11802
12040
|
(payload->>'occupied')::boolean AS occupied,
|
|
11803
12041
|
payload->'ref'->'source'->>'objectTypeId' AS source_type_id,
|
|
11804
12042
|
payload->'ref'->'source'->>'primaryId' AS source_id,
|
|
11805
12043
|
payload->'ref'->>'linkId' AS link_id
|
|
11806
|
-
FROM ${
|
|
11807
|
-
WHERE session_id = $
|
|
11808
|
-
|
|
12044
|
+
FROM ${PG_MATERIALIZATION_WORK_TABLE}
|
|
12045
|
+
WHERE session_id = $1 AND lane = 'cardinality' AND major_order = 0`, [session.id]);
|
|
12046
|
+
await this.sql.unsafe(`ANALYZE ${table}`);
|
|
12047
|
+
const [violation] = await this.sql.unsafe(`WITH work AS (
|
|
12048
|
+
SELECT * FROM ${table}
|
|
11809
12049
|
), duplicate AS (
|
|
11810
12050
|
SELECT scope_sort_key FROM work WHERE occupied
|
|
11811
12051
|
GROUP BY scope_sort_key HAVING COUNT(*) > 1
|
|
@@ -11815,10 +12055,10 @@ class PgOntologyMaterializationStorage {
|
|
|
11815
12055
|
SELECT scope_sort_key, link_sort_key FROM work WHERE occupied
|
|
11816
12056
|
), actual AS (
|
|
11817
12057
|
SELECT scopes.scope_sort_key,
|
|
11818
|
-
${
|
|
12058
|
+
${linkSortExpression("links")} AS link_sort_key
|
|
11819
12059
|
FROM scopes
|
|
11820
12060
|
JOIN links USING (source_type_id, source_id, link_id)
|
|
11821
|
-
WHERE links.project_id = $
|
|
12061
|
+
WHERE links.project_id = $1
|
|
11822
12062
|
), differences AS (
|
|
11823
12063
|
(SELECT * FROM expected EXCEPT SELECT * FROM actual)
|
|
11824
12064
|
UNION ALL
|
|
@@ -11827,8 +12067,8 @@ class PgOntologyMaterializationStorage {
|
|
|
11827
12067
|
SELECT 'duplicate'::text AS reason FROM duplicate
|
|
11828
12068
|
UNION ALL
|
|
11829
12069
|
SELECT 'mismatch'::text AS reason FROM differences
|
|
11830
|
-
LIMIT 1
|
|
11831
|
-
|
|
12070
|
+
LIMIT 1`, [session.header.commit.projectId]);
|
|
12071
|
+
await this.sql.unsafe(`DROP TABLE ${table}`);
|
|
11832
12072
|
if (violation?.reason === "duplicate") {
|
|
11833
12073
|
invalidCorrelation2("Materialization cardinality work violates cardinality-one.");
|
|
11834
12074
|
}
|
|
@@ -11924,7 +12164,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11924
12164
|
assertSourceActivationCorrelation(session, activation);
|
|
11925
12165
|
const candidate = await this.getSource(commit.projectId, activation.source.projectionId, activation.materializationId, true);
|
|
11926
12166
|
if (!candidate || candidate.status !== "ready" || candidate.execution_token === null) {
|
|
11927
|
-
throw new
|
|
12167
|
+
throw new MaterializationConflictError6("source-materialization", "Source activation candidate is missing or is not ready.");
|
|
11928
12168
|
}
|
|
11929
12169
|
await assertProjectionExecution(this.sql, {
|
|
11930
12170
|
projectId: commit.projectId,
|
|
@@ -11937,6 +12177,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11937
12177
|
}
|
|
11938
12178
|
const previous = await this.getActiveSource(commit.projectId, activation.source.projectionId, true);
|
|
11939
12179
|
this.assertSourceRow(activation.expected, previous);
|
|
12180
|
+
await activateSourceRoots(this.sql, commit.projectId, candidate, activation);
|
|
11940
12181
|
if (previous) {
|
|
11941
12182
|
assertPinnedDatasetWatermark({
|
|
11942
12183
|
datasetId: previous.dataset_id,
|
|
@@ -11958,7 +12199,7 @@ class PgOntologyMaterializationStorage {
|
|
|
11958
12199
|
RETURNING materialization_id
|
|
11959
12200
|
`;
|
|
11960
12201
|
if (superseded.length !== 1) {
|
|
11961
|
-
throw new
|
|
12202
|
+
throw new MaterializationConflictError6("projection-fence", `Source '${activation.source.projectionId}' changed.`);
|
|
11962
12203
|
}
|
|
11963
12204
|
}
|
|
11964
12205
|
let activated;
|
|
@@ -11977,12 +12218,12 @@ class PgOntologyMaterializationStorage {
|
|
|
11977
12218
|
`;
|
|
11978
12219
|
} catch (error) {
|
|
11979
12220
|
if (isUniqueViolation(error)) {
|
|
11980
|
-
throw new
|
|
12221
|
+
throw new MaterializationConflictError6("projection-fence", `Source '${activation.source.projectionId}' changed.`);
|
|
11981
12222
|
}
|
|
11982
12223
|
throw error;
|
|
11983
12224
|
}
|
|
11984
12225
|
if (activated.length !== 1) {
|
|
11985
|
-
throw new
|
|
12226
|
+
throw new MaterializationConflictError6("source-materialization", "Source activation candidate changed.");
|
|
11986
12227
|
}
|
|
11987
12228
|
}
|
|
11988
12229
|
async insertCommit(header, input) {
|
|
@@ -12009,7 +12250,7 @@ class PgOntologyMaterializationStorage {
|
|
|
12009
12250
|
`;
|
|
12010
12251
|
if (!rows[0]) {
|
|
12011
12252
|
await this.assertCommitAbsent(header);
|
|
12012
|
-
throw new
|
|
12253
|
+
throw new MaterializationConflictError6("idempotency", "Ontology commit identity already exists.");
|
|
12013
12254
|
}
|
|
12014
12255
|
return commitRecord(rows[0]);
|
|
12015
12256
|
}
|
|
@@ -12057,8 +12298,8 @@ function databaseCount(value) {
|
|
|
12057
12298
|
// src/ontology-storage/outbox.ts
|
|
12058
12299
|
import { serializeSixbFailure as serializeSixbFailure2 } from "@sixb/core/internal/errors";
|
|
12059
12300
|
import {
|
|
12060
|
-
MaterializationConflictError as
|
|
12061
|
-
MaterializationValidationError
|
|
12301
|
+
MaterializationConflictError as MaterializationConflictError7,
|
|
12302
|
+
MaterializationValidationError as MaterializationValidationError2
|
|
12062
12303
|
} from "@sixb/core/internal/materialization";
|
|
12063
12304
|
import { ONTOLOGY_OUTBOX_FAILURE_CODES as ONTOLOGY_OUTBOX_FAILURE_CODES2 } from "@sixb/core/storage";
|
|
12064
12305
|
class PgOntologyOutboxStorage {
|
|
@@ -12074,7 +12315,7 @@ class PgOntologyOutboxStorage {
|
|
|
12074
12315
|
const leaseExpiry = assertTimestamp(input.leaseExpiresAt, "Ontology outbox lease expiry");
|
|
12075
12316
|
assertNonblank(input.leaseId, "Ontology outbox lease id");
|
|
12076
12317
|
if (leaseExpiry <= now) {
|
|
12077
|
-
throw new
|
|
12318
|
+
throw new MaterializationValidationError2("Ontology outbox lease expiry must be later than the claim time.");
|
|
12078
12319
|
}
|
|
12079
12320
|
const rows = await sql`
|
|
12080
12321
|
WITH candidates AS (
|
|
@@ -12104,7 +12345,7 @@ class PgOntologyOutboxStorage {
|
|
|
12104
12345
|
return rows.map((row) => {
|
|
12105
12346
|
const record = outboxRecord(row);
|
|
12106
12347
|
if (record.leaseId === null || record.leaseExpiresAt === null) {
|
|
12107
|
-
throw new
|
|
12348
|
+
throw new MaterializationConflictError7("outbox-lease", "Ontology outbox claim returned an unpaired lease.");
|
|
12108
12349
|
}
|
|
12109
12350
|
return { ...record, leaseId: record.leaseId, leaseExpiresAt: record.leaseExpiresAt };
|
|
12110
12351
|
});
|
|
@@ -12225,7 +12466,7 @@ function validateLeaseIds(ids) {
|
|
|
12225
12466
|
for (const id of ids) {
|
|
12226
12467
|
assertNonblank(id, "Ontology outbox event id");
|
|
12227
12468
|
if (seen.has(id)) {
|
|
12228
|
-
throw new
|
|
12469
|
+
throw new MaterializationValidationError2(`Ontology outbox lease batch repeats event '${id}'.`);
|
|
12229
12470
|
}
|
|
12230
12471
|
seen.add(id);
|
|
12231
12472
|
}
|
|
@@ -12236,11 +12477,11 @@ function assertLeaseInput(input) {
|
|
|
12236
12477
|
assertNonblank(input.leaseId, "Ontology outbox lease id");
|
|
12237
12478
|
}
|
|
12238
12479
|
function leaseConflict() {
|
|
12239
|
-
return new
|
|
12480
|
+
return new MaterializationConflictError7("outbox-lease", "Ontology outbox lease does not match.");
|
|
12240
12481
|
}
|
|
12241
12482
|
|
|
12242
12483
|
// src/ontology-storage/sources.ts
|
|
12243
|
-
import { MaterializationValidationError as
|
|
12484
|
+
import { MaterializationValidationError as MaterializationValidationError3 } from "@sixb/core/internal/materialization";
|
|
12244
12485
|
import {
|
|
12245
12486
|
assertSourceBeginInput as assertBeginInput,
|
|
12246
12487
|
assertSourceExecutionIdentity as assertExecutionIdentity,
|
|
@@ -12254,8 +12495,61 @@ import {
|
|
|
12254
12495
|
sourceMaterializationIdentity,
|
|
12255
12496
|
sourceStageRow,
|
|
12256
12497
|
sourceStageRows,
|
|
12257
|
-
utf8SortKey
|
|
12498
|
+
utf8SortKey as utf8SortKey2
|
|
12258
12499
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
12500
|
+
|
|
12501
|
+
// src/ontology-storage/source-cleanup.ts
|
|
12502
|
+
async function cleanupSourceVersions(sql, input) {
|
|
12503
|
+
let remaining = input.limit;
|
|
12504
|
+
const removedRows = await sql`
|
|
12505
|
+
WITH selected AS (
|
|
12506
|
+
SELECT rows.ctid FROM ontology_source_roots AS roots
|
|
12507
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
|
|
12508
|
+
JOIN ontology_source_rows AS rows
|
|
12509
|
+
ON rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
12510
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
12511
|
+
WHERE roots.project_id = ${input.projectId} AND NOT roots.active AND roots.retired_at < ${input.terminalBefore}
|
|
12512
|
+
AND sources.status IN ('superseded', 'abandoned')
|
|
12513
|
+
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key, rows.entity_sort_key
|
|
12514
|
+
LIMIT ${remaining} FOR UPDATE OF rows SKIP LOCKED
|
|
12515
|
+
) DELETE FROM ontology_source_rows AS rows USING selected WHERE rows.ctid = selected.ctid RETURNING 1
|
|
12516
|
+
`;
|
|
12517
|
+
let rowsDeleted = removedRows.length;
|
|
12518
|
+
remaining -= rowsDeleted;
|
|
12519
|
+
if (remaining > 0) {
|
|
12520
|
+
const removed = await sql`
|
|
12521
|
+
WITH selected AS (
|
|
12522
|
+
SELECT roots.ctid FROM ontology_source_roots AS roots
|
|
12523
|
+
JOIN ontology_sources AS sources USING (project_id, source_id, materialization_id)
|
|
12524
|
+
WHERE roots.project_id = ${input.projectId} AND NOT roots.active AND roots.retired_at < ${input.terminalBefore}
|
|
12525
|
+
AND sources.status IN ('superseded', 'abandoned')
|
|
12526
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
12527
|
+
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
12528
|
+
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key)
|
|
12529
|
+
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key
|
|
12530
|
+
LIMIT ${remaining} FOR UPDATE OF roots SKIP LOCKED
|
|
12531
|
+
) DELETE FROM ontology_source_roots AS roots USING selected WHERE roots.ctid = selected.ctid RETURNING 1
|
|
12532
|
+
`;
|
|
12533
|
+
rowsDeleted += removed.length;
|
|
12534
|
+
remaining -= removed.length;
|
|
12535
|
+
}
|
|
12536
|
+
const removedManifests = remaining === 0 ? [] : await sql`
|
|
12537
|
+
WITH selected AS (
|
|
12538
|
+
SELECT sources.ctid FROM ontology_sources AS sources
|
|
12539
|
+
WHERE project_id = ${input.projectId} AND status IN ('superseded', 'abandoned') AND terminal_at < ${input.terminalBefore}
|
|
12540
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_roots AS roots
|
|
12541
|
+
WHERE roots.project_id = sources.project_id AND roots.source_id = sources.source_id
|
|
12542
|
+
AND roots.materialization_id = sources.materialization_id)
|
|
12543
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
12544
|
+
WHERE rows.project_id = sources.project_id AND rows.source_id = sources.source_id
|
|
12545
|
+
AND rows.materialization_id = sources.materialization_id)
|
|
12546
|
+
ORDER BY terminal_at, source_id, materialization_id LIMIT ${remaining} FOR UPDATE SKIP LOCKED
|
|
12547
|
+
) DELETE FROM ontology_sources AS sources USING selected WHERE sources.ctid = selected.ctid RETURNING 1
|
|
12548
|
+
`;
|
|
12549
|
+
return { rowsDeleted, materializationsDeleted: removedManifests.length };
|
|
12550
|
+
}
|
|
12551
|
+
|
|
12552
|
+
// src/ontology-storage/sources.ts
|
|
12259
12553
|
class PgOntologySourceStorage {
|
|
12260
12554
|
runRootOperation;
|
|
12261
12555
|
constructor(runRootOperation) {
|
|
@@ -12286,14 +12580,15 @@ class PgOntologySourceStorage {
|
|
|
12286
12580
|
dataset_id, dataset_version_id, dataset_version_created_at,
|
|
12287
12581
|
projection_revision, ownership_hash, ontology_revision,
|
|
12288
12582
|
root_count, assertion_count, created_at, ready_at, activated_at,
|
|
12289
|
-
terminal_at, last_commit_id, updated_at
|
|
12583
|
+
terminal_at, last_commit_id, updated_at, base_materialization_id, base_commit_id
|
|
12290
12584
|
) VALUES (
|
|
12291
12585
|
${input.projectId}, ${input.source.projectionId}, ${input.materializationId},
|
|
12292
12586
|
${input.execution.projectionRunId}, ${input.projectionKind}, 'replacement',
|
|
12293
12587
|
'staging', ${input.execution.executionToken}, ${input.datasetVersion.datasetId},
|
|
12294
12588
|
${input.datasetVersion.versionId}, ${input.datasetVersion.createdAt},
|
|
12295
12589
|
${input.projectionRevision}, ${input.ownershipHash}, ${input.ontologyRevision},
|
|
12296
|
-
NULL, NULL, ${input.createdAt}, NULL, NULL, NULL, NULL, ${input.createdAt}
|
|
12590
|
+
NULL, NULL, ${input.createdAt}, NULL, NULL, NULL, NULL, ${input.createdAt},
|
|
12591
|
+
${input.base?.materializationId ?? null}, ${input.base?.lastCommitId ?? null}
|
|
12297
12592
|
)
|
|
12298
12593
|
ON CONFLICT DO NOTHING
|
|
12299
12594
|
RETURNING *
|
|
@@ -12322,6 +12617,7 @@ class PgOntologySourceStorage {
|
|
|
12322
12617
|
const rows = sourceStageRows(manifest.projection_kind, input.rows);
|
|
12323
12618
|
const existing = await this.findStageRows(sql, input, rows);
|
|
12324
12619
|
const { pending, unchanged } = reconcileSourceStageRows(rows, existing);
|
|
12620
|
+
await stageSourceRoots(sql, manifest, input);
|
|
12325
12621
|
await this.insertStageRows(sql, input, pending);
|
|
12326
12622
|
return { inserted: pending.length, unchanged };
|
|
12327
12623
|
});
|
|
@@ -12345,7 +12641,10 @@ class PgOntologySourceStorage {
|
|
|
12345
12641
|
throw sourceConflict(`Source materialization '${input.materializationId}' cannot transition from '${manifest.status}' to 'ready'.`);
|
|
12346
12642
|
}
|
|
12347
12643
|
if (input.readyAt < toIsoString2(manifest.created_at)) {
|
|
12348
|
-
throw new
|
|
12644
|
+
throw new MaterializationValidationError3("Source readyAt cannot precede source createdAt.");
|
|
12645
|
+
}
|
|
12646
|
+
if (input.assertionCount >= 1000 || input.rootCount >= 1000) {
|
|
12647
|
+
await sql`ANALYZE ontology_source_rows, ontology_source_roots`;
|
|
12349
12648
|
}
|
|
12350
12649
|
await this.assertReadyState(sql, manifest, input.rootCount, input.assertionCount);
|
|
12351
12650
|
const rows = await sql`
|
|
@@ -12392,61 +12691,7 @@ class PgOntologySourceStorage {
|
|
|
12392
12691
|
assertNonblank(input.projectId, "Terminal source cleanup project id");
|
|
12393
12692
|
assertTimestamp(input.terminalBefore, "Terminal source cleanup cutoff", true);
|
|
12394
12693
|
assertPositiveInteger(input.limit, "Terminal source cleanup limit");
|
|
12395
|
-
|
|
12396
|
-
SELECT * FROM ontology_sources
|
|
12397
|
-
WHERE project_id = ${input.projectId}
|
|
12398
|
-
AND status IN ('superseded', 'abandoned')
|
|
12399
|
-
AND terminal_at < ${input.terminalBefore}
|
|
12400
|
-
ORDER BY terminal_at, source_id, materialization_id
|
|
12401
|
-
LIMIT ${input.limit}
|
|
12402
|
-
FOR UPDATE SKIP LOCKED
|
|
12403
|
-
`;
|
|
12404
|
-
let remaining = input.limit;
|
|
12405
|
-
let rowsDeleted = 0;
|
|
12406
|
-
let materializationsDeleted = 0;
|
|
12407
|
-
for (const manifest of manifests) {
|
|
12408
|
-
if (remaining === 0)
|
|
12409
|
-
break;
|
|
12410
|
-
const deletedRows = await sql`
|
|
12411
|
-
WITH selected AS (
|
|
12412
|
-
SELECT rows.ctid
|
|
12413
|
-
FROM ontology_source_rows AS rows
|
|
12414
|
-
WHERE rows.project_id = ${manifest.project_id}
|
|
12415
|
-
AND rows.source_id = ${manifest.source_id}
|
|
12416
|
-
AND rows.materialization_id = ${manifest.materialization_id}
|
|
12417
|
-
ORDER BY rows.entity_sort_key
|
|
12418
|
-
LIMIT ${remaining}
|
|
12419
|
-
FOR UPDATE SKIP LOCKED
|
|
12420
|
-
)
|
|
12421
|
-
DELETE FROM ontology_source_rows AS rows
|
|
12422
|
-
USING selected
|
|
12423
|
-
WHERE rows.ctid = selected.ctid
|
|
12424
|
-
RETURNING rows.entity_sort_key
|
|
12425
|
-
`;
|
|
12426
|
-
rowsDeleted += deletedRows.length;
|
|
12427
|
-
remaining -= deletedRows.length;
|
|
12428
|
-
if (remaining === 0)
|
|
12429
|
-
break;
|
|
12430
|
-
const removed = await sql`
|
|
12431
|
-
DELETE FROM ontology_sources AS sources
|
|
12432
|
-
WHERE sources.project_id = ${manifest.project_id}
|
|
12433
|
-
AND sources.source_id = ${manifest.source_id}
|
|
12434
|
-
AND sources.materialization_id = ${manifest.materialization_id}
|
|
12435
|
-
AND sources.status IN ('superseded', 'abandoned')
|
|
12436
|
-
AND sources.terminal_at = ${manifest.terminal_at}
|
|
12437
|
-
AND sources.terminal_at < ${input.terminalBefore}
|
|
12438
|
-
AND NOT EXISTS (
|
|
12439
|
-
SELECT 1 FROM ontology_source_rows AS rows
|
|
12440
|
-
WHERE rows.project_id = sources.project_id
|
|
12441
|
-
AND rows.source_id = sources.source_id
|
|
12442
|
-
AND rows.materialization_id = sources.materialization_id
|
|
12443
|
-
)
|
|
12444
|
-
RETURNING sources.materialization_id
|
|
12445
|
-
`;
|
|
12446
|
-
materializationsDeleted += removed.length;
|
|
12447
|
-
remaining -= removed.length;
|
|
12448
|
-
}
|
|
12449
|
-
return { rowsDeleted, materializationsDeleted };
|
|
12694
|
+
return cleanupSourceVersions(sql, input);
|
|
12450
12695
|
});
|
|
12451
12696
|
}
|
|
12452
12697
|
async summarizeTerminal(input) {
|
|
@@ -12494,7 +12739,7 @@ class PgOntologySourceStorage {
|
|
|
12494
12739
|
}
|
|
12495
12740
|
async transitionToAbandoned(sql, manifest, abandonedAt) {
|
|
12496
12741
|
if (abandonedAt < toIsoString2(manifest.created_at) || manifest.ready_at !== null && abandonedAt < toIsoString2(manifest.ready_at)) {
|
|
12497
|
-
throw new
|
|
12742
|
+
throw new MaterializationValidationError3("Source abandonedAt cannot precede source creation or readiness.");
|
|
12498
12743
|
}
|
|
12499
12744
|
const rows = await sql`
|
|
12500
12745
|
UPDATE ontology_sources
|
|
@@ -12509,14 +12754,19 @@ class PgOntologySourceStorage {
|
|
|
12509
12754
|
if (!rows[0]) {
|
|
12510
12755
|
throw sourceConflict(`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`);
|
|
12511
12756
|
}
|
|
12757
|
+
await sql`UPDATE ontology_source_roots SET retired_at = ${abandonedAt}
|
|
12758
|
+
WHERE project_id = ${manifest.project_id} AND source_id = ${manifest.source_id}
|
|
12759
|
+
AND materialization_id = ${manifest.materialization_id} AND NOT active`;
|
|
12512
12760
|
return sourceRecord(rows[0]);
|
|
12513
12761
|
}
|
|
12514
12762
|
async assertReadyState(sql, manifest, rootCount, assertionCount) {
|
|
12515
12763
|
const [counts] = await sql`
|
|
12516
|
-
SELECT
|
|
12764
|
+
SELECT (SELECT COUNT(*) FROM ontology_source_rows
|
|
12765
|
+
WHERE project_id = ${manifest.project_id} AND source_id = ${manifest.source_id}
|
|
12766
|
+
AND materialization_id = ${manifest.materialization_id}) AS assertions, COUNT(*) AS roots,
|
|
12517
12767
|
COUNT(DISTINCT staging_ordinal) AS ordinals,
|
|
12518
12768
|
MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
|
|
12519
|
-
FROM
|
|
12769
|
+
FROM ontology_source_roots
|
|
12520
12770
|
WHERE project_id = ${manifest.project_id}
|
|
12521
12771
|
AND source_id = ${manifest.source_id}
|
|
12522
12772
|
AND materialization_id = ${manifest.materialization_id}
|
|
@@ -12527,8 +12777,9 @@ class PgOntologySourceStorage {
|
|
|
12527
12777
|
const minimum = counts?.min_ordinal === null ? null : Number(counts?.min_ordinal);
|
|
12528
12778
|
const maximum = counts?.max_ordinal === null ? null : Number(counts?.max_ordinal);
|
|
12529
12779
|
if (assertions !== assertionCount || roots !== rootCount || ordinals !== rootCount || rootCount > 0 && (minimum !== 0 || maximum !== rootCount - 1)) {
|
|
12530
|
-
throw new
|
|
12780
|
+
throw new MaterializationValidationError3("Source ready counts do not match the staged roots and assertions.");
|
|
12531
12781
|
}
|
|
12782
|
+
await assertSourceRootCoverage(sql, manifest);
|
|
12532
12783
|
const [invalid] = manifest.projection_kind === "link" ? await sql`
|
|
12533
12784
|
SELECT root_key
|
|
12534
12785
|
FROM ontology_source_rows
|
|
@@ -12562,15 +12813,15 @@ class PgOntologySourceStorage {
|
|
|
12562
12813
|
LIMIT 1
|
|
12563
12814
|
`;
|
|
12564
12815
|
if (invalid) {
|
|
12565
|
-
throw new
|
|
12816
|
+
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.");
|
|
12566
12817
|
}
|
|
12567
12818
|
}
|
|
12568
12819
|
async findStageRows(sql, input, rows) {
|
|
12569
12820
|
if (rows.length === 0)
|
|
12570
12821
|
return [];
|
|
12571
|
-
const rootSortKeys = [...new Set(rows.map((row) =>
|
|
12822
|
+
const rootSortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.rootKey)))];
|
|
12572
12823
|
const ordinals = [...new Set(rows.map((row) => row.row.stagingOrdinal))];
|
|
12573
|
-
const entitySortKeys = [...new Set(rows.map((row) =>
|
|
12824
|
+
const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.entityKey)))];
|
|
12574
12825
|
const existing = await sql`
|
|
12575
12826
|
SELECT * FROM ontology_source_rows
|
|
12576
12827
|
WHERE project_id = ${input.projectId}
|
|
@@ -12593,10 +12844,10 @@ class PgOntologySourceStorage {
|
|
|
12593
12844
|
return {
|
|
12594
12845
|
entityKind: row.assertion.kind,
|
|
12595
12846
|
entityKey: JSON.parse(entityKey),
|
|
12596
|
-
entitySortKey:
|
|
12847
|
+
entitySortKey: utf8SortKey2(entityKey),
|
|
12597
12848
|
rootKind: row.root.kind,
|
|
12598
12849
|
rootKey: JSON.parse(rootKey),
|
|
12599
|
-
rootSortKey:
|
|
12850
|
+
rootSortKey: utf8SortKey2(rootKey),
|
|
12600
12851
|
stagingOrdinal: row.stagingOrdinal,
|
|
12601
12852
|
root: row.root,
|
|
12602
12853
|
assertion: row.assertion,
|
|
@@ -15256,7 +15507,7 @@ class PgProjectionRunStorage {
|
|
|
15256
15507
|
input_exhausted = ${input.fixedBatchSize === undefined ? null : false},
|
|
15257
15508
|
missing_target_object_type_id = ${null}, missing_target_object_id = ${null},
|
|
15258
15509
|
missing_target_batch_ordinal = ${null}, missing_target_first_seen_at = ${null},
|
|
15259
|
-
source_rows_read = ${0}, source_rows_skipped = ${0}, error = ${null}
|
|
15510
|
+
source_rows_read = ${0}, source_rows_skipped = ${0}, source_changes_read = ${null}, error = ${null}
|
|
15260
15511
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
15261
15512
|
AND status = ${"failed"} AND error->>'code' = ${"queue.enqueue_failed"}
|
|
15262
15513
|
RETURNING *
|
|
@@ -15367,7 +15618,8 @@ class PgProjectionRunStorage {
|
|
|
15367
15618
|
UPDATE projection_runs
|
|
15368
15619
|
SET
|
|
15369
15620
|
source_rows_read = ${progress.sourceRowsRead},
|
|
15370
|
-
source_rows_skipped = ${progress.sourceRowsSkipped}
|
|
15621
|
+
source_rows_skipped = ${progress.sourceRowsSkipped},
|
|
15622
|
+
source_changes_read = ${progress.sourceChangesRead ?? null}
|
|
15371
15623
|
WHERE project_id = ${input.projectId}
|
|
15372
15624
|
AND id = ${input.id}
|
|
15373
15625
|
AND status = ${"running"}
|
|
@@ -15392,6 +15644,7 @@ class PgProjectionRunStorage {
|
|
|
15392
15644
|
execution_token = ${null},
|
|
15393
15645
|
source_rows_read = ${plan.progress.sourceRowsRead},
|
|
15394
15646
|
source_rows_skipped = ${plan.progress.sourceRowsSkipped},
|
|
15647
|
+
source_changes_read = ${plan.progress.sourceChangesRead ?? null},
|
|
15395
15648
|
input_exhausted = ${plan.inputExhausted ?? existingRow.input_exhausted},
|
|
15396
15649
|
error = ${plan.error === undefined ? null : serializeSixbFailure4(plan.error, PROJECTION_RUN_FAILURE_CODES)}::text::jsonb
|
|
15397
15650
|
WHERE project_id = ${input.projectId}
|
|
@@ -15634,7 +15887,8 @@ function restoreProjectionRunRow(row) {
|
|
|
15634
15887
|
missingTargetFirstSeenAt: row.missing_target_first_seen_at ? new Date(row.missing_target_first_seen_at) : undefined,
|
|
15635
15888
|
progress: {
|
|
15636
15889
|
sourceRowsRead: databaseSafeInteger2(row.source_rows_read, "sourceRowsRead"),
|
|
15637
|
-
sourceRowsSkipped: databaseSafeInteger2(row.source_rows_skipped, "sourceRowsSkipped")
|
|
15890
|
+
sourceRowsSkipped: databaseSafeInteger2(row.source_rows_skipped, "sourceRowsSkipped"),
|
|
15891
|
+
...row.source_changes_read == null ? {} : { sourceChangesRead: databaseSafeInteger2(row.source_changes_read, "sourceChangesRead") }
|
|
15638
15892
|
},
|
|
15639
15893
|
error: row.error ?? undefined
|
|
15640
15894
|
};
|
|
@@ -17866,4 +18120,4 @@ export {
|
|
|
17866
18120
|
migratePostgresStorage
|
|
17867
18121
|
};
|
|
17868
18122
|
|
|
17869
|
-
//# debugId=
|
|
18123
|
+
//# debugId=0CAAAAC02FB5CFD864756E2164756E21
|