@sixb/sqlite 0.1.14 → 0.1.15
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 +673 -241
- package/dist/index.js.map +10 -10
- package/dist/latest-run-query.d.ts +2 -0
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +11 -8
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/ontology-storage/source-cleanup.d.ts +7 -1
- package/dist/ontology-storage/source-cleanup.d.ts.map +1 -1
- package/dist/ontology-storage/source-roots.d.ts +14 -2
- package/dist/ontology-storage/source-roots.d.ts.map +1 -1
- package/dist/ontology-storage/sources.d.ts +8 -3
- package/dist/ontology-storage/sources.d.ts.map +1 -1
- package/dist/projection-run-storage.d.ts +3 -1
- package/dist/projection-run-storage.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/latest-run-query.ts +8 -2
- package/src/migrations/051-projection-run-supersession.sql +126 -0
- package/src/migrations/052-compact-source-storage.sql +181 -0
- package/src/migrations.ts +38 -0
- package/src/ontology-storage/materialization-state.ts +34 -44
- package/src/ontology-storage/shared.ts +20 -11
- package/src/ontology-storage/source-cleanup.ts +61 -24
- package/src/ontology-storage/source-roots.ts +166 -110
- package/src/ontology-storage/sources.ts +169 -118
- package/src/projection-run-storage.ts +59 -1
package/dist/index.js
CHANGED
|
@@ -4357,6 +4357,319 @@ CREATE INDEX idx_auth_group_memberships_group
|
|
|
4357
4357
|
ON auth_group_memberships(project_id, group_id);
|
|
4358
4358
|
`;
|
|
4359
4359
|
|
|
4360
|
+
// src/migrations/051-projection-run-supersession.sql
|
|
4361
|
+
var _051_projection_run_supersession_default = `-- Projection runs gain the terminal status \`superseded\`, and a running run may carry the failure
|
|
4362
|
+
-- of its last attempt in \`error\` while the queue retries it. SQLite cannot alter a CHECK
|
|
4363
|
+
-- constraint, so the table is rebuilt.
|
|
4364
|
+
CREATE TABLE projection_runs_v2 (
|
|
4365
|
+
project_id TEXT NOT NULL,
|
|
4366
|
+
id TEXT NOT NULL,
|
|
4367
|
+
execution_id TEXT NOT NULL,
|
|
4368
|
+
projection_id TEXT NOT NULL,
|
|
4369
|
+
projection_kind TEXT NOT NULL CHECK (projection_kind IN ('object', 'link', 'telemetry')),
|
|
4370
|
+
materialization_protocol TEXT NOT NULL CHECK (
|
|
4371
|
+
materialization_protocol IN ('replacement', 'telemetry')
|
|
4372
|
+
),
|
|
4373
|
+
dataset_id TEXT NOT NULL,
|
|
4374
|
+
dataset_version_id TEXT NOT NULL,
|
|
4375
|
+
dataset_version_created_at TEXT NOT NULL,
|
|
4376
|
+
ontology_revision TEXT NOT NULL,
|
|
4377
|
+
projection_revision TEXT NOT NULL,
|
|
4378
|
+
ownership_hash TEXT NOT NULL,
|
|
4379
|
+
object_type_id TEXT,
|
|
4380
|
+
source_object_type_id TEXT,
|
|
4381
|
+
target_object_type_id TEXT,
|
|
4382
|
+
status TEXT NOT NULL CHECK (
|
|
4383
|
+
status IN ('queued', 'running', 'succeeded', 'failed', 'cancelled', 'superseded')
|
|
4384
|
+
),
|
|
4385
|
+
queued_at TEXT NOT NULL,
|
|
4386
|
+
started_at TEXT,
|
|
4387
|
+
finished_at TEXT,
|
|
4388
|
+
attempt INTEGER NOT NULL CHECK (attempt >= 0),
|
|
4389
|
+
execution_token TEXT,
|
|
4390
|
+
fixed_batch_size INTEGER CHECK (fixed_batch_size > 0),
|
|
4391
|
+
next_batch_ordinal INTEGER CHECK (next_batch_ordinal >= 0),
|
|
4392
|
+
next_row_offset INTEGER CHECK (next_row_offset >= 0),
|
|
4393
|
+
input_exhausted INTEGER CHECK (input_exhausted IN (0, 1)),
|
|
4394
|
+
missing_target_object_type_id TEXT,
|
|
4395
|
+
missing_target_object_id TEXT,
|
|
4396
|
+
missing_target_batch_ordinal INTEGER CHECK (
|
|
4397
|
+
missing_target_batch_ordinal IS NULL OR missing_target_batch_ordinal >= 0
|
|
4398
|
+
),
|
|
4399
|
+
missing_target_first_seen_at TEXT,
|
|
4400
|
+
source_rows_read INTEGER NOT NULL DEFAULT 0 CHECK (source_rows_read >= 0),
|
|
4401
|
+
source_rows_skipped INTEGER NOT NULL DEFAULT 0 CHECK (source_rows_skipped >= 0),
|
|
4402
|
+
source_changes_read INTEGER CHECK (source_changes_read IS NULL OR source_changes_read >= 0),
|
|
4403
|
+
error TEXT CHECK (error IS NULL OR json_valid(error)),
|
|
4404
|
+
PRIMARY KEY (project_id, id),
|
|
4405
|
+
UNIQUE (project_id, execution_id),
|
|
4406
|
+
FOREIGN KEY (project_id, execution_id) REFERENCES executions (project_id, id) ON DELETE RESTRICT,
|
|
4407
|
+
CHECK (source_rows_skipped <= source_rows_read),
|
|
4408
|
+
CHECK ((status = 'running') = (execution_token IS NOT NULL)),
|
|
4409
|
+
CHECK (
|
|
4410
|
+
(status = 'queued' AND attempt = 0 AND started_at IS NULL AND finished_at IS NULL
|
|
4411
|
+
AND error IS NULL)
|
|
4412
|
+
OR (status = 'running' AND attempt >= 1 AND started_at IS NOT NULL AND finished_at IS NULL)
|
|
4413
|
+
OR (
|
|
4414
|
+
status IN ('succeeded', 'failed', 'cancelled', 'superseded')
|
|
4415
|
+
AND finished_at IS NOT NULL
|
|
4416
|
+
AND (
|
|
4417
|
+
(json_extract(error, '$.code') = 'queue.enqueue_failed'
|
|
4418
|
+
AND status = 'failed' AND attempt = 0 AND started_at IS NULL)
|
|
4419
|
+
OR (COALESCE(json_extract(error, '$.code'), '') != 'queue.enqueue_failed'
|
|
4420
|
+
AND attempt >= 1 AND started_at IS NOT NULL)
|
|
4421
|
+
)
|
|
4422
|
+
)
|
|
4423
|
+
),
|
|
4424
|
+
CHECK (status != 'succeeded' OR error IS NULL),
|
|
4425
|
+
-- A run is superseded instead of materialized, so it carries no failure, and only replacement
|
|
4426
|
+
-- runs can be: telemetry appends readings rather than replace a source.
|
|
4427
|
+
CHECK (status != 'superseded' OR (error IS NULL AND materialization_protocol = 'replacement')),
|
|
4428
|
+
CHECK ((projection_kind = 'telemetry') = (materialization_protocol = 'telemetry')),
|
|
4429
|
+
CHECK (
|
|
4430
|
+
(projection_kind = 'link' AND object_type_id IS NULL
|
|
4431
|
+
AND source_object_type_id IS NOT NULL AND target_object_type_id IS NOT NULL)
|
|
4432
|
+
OR (projection_kind IN ('object', 'telemetry') AND object_type_id IS NOT NULL
|
|
4433
|
+
AND source_object_type_id IS NULL AND target_object_type_id IS NULL)
|
|
4434
|
+
),
|
|
4435
|
+
CHECK (
|
|
4436
|
+
(materialization_protocol = 'replacement' AND fixed_batch_size IS NULL
|
|
4437
|
+
AND next_batch_ordinal IS NULL AND next_row_offset IS NULL AND input_exhausted IS NULL)
|
|
4438
|
+
OR (materialization_protocol = 'telemetry' AND fixed_batch_size IS NOT NULL
|
|
4439
|
+
AND next_batch_ordinal IS NOT NULL AND next_row_offset IS NOT NULL
|
|
4440
|
+
AND input_exhausted IS NOT NULL)
|
|
4441
|
+
),
|
|
4442
|
+
CHECK (
|
|
4443
|
+
(missing_target_object_type_id IS NULL AND missing_target_object_id IS NULL
|
|
4444
|
+
AND missing_target_batch_ordinal IS NULL AND missing_target_first_seen_at IS NULL)
|
|
4445
|
+
OR (projection_kind = 'telemetry' AND missing_target_object_type_id = object_type_id
|
|
4446
|
+
AND missing_target_object_id IS NOT NULL
|
|
4447
|
+
AND missing_target_batch_ordinal = next_batch_ordinal
|
|
4448
|
+
AND missing_target_first_seen_at IS NOT NULL)
|
|
4449
|
+
),
|
|
4450
|
+
CHECK (projection_kind != 'telemetry' OR status != 'succeeded' OR input_exhausted = 1)
|
|
4451
|
+
);
|
|
4452
|
+
|
|
4453
|
+
INSERT INTO projection_runs_v2 (
|
|
4454
|
+
project_id, id, execution_id, projection_id, projection_kind, materialization_protocol,
|
|
4455
|
+
dataset_id, dataset_version_id, dataset_version_created_at, ontology_revision,
|
|
4456
|
+
projection_revision, ownership_hash, object_type_id, source_object_type_id,
|
|
4457
|
+
target_object_type_id, status, queued_at, started_at, finished_at, attempt, execution_token,
|
|
4458
|
+
fixed_batch_size, next_batch_ordinal, next_row_offset, input_exhausted,
|
|
4459
|
+
missing_target_object_type_id, missing_target_object_id, missing_target_batch_ordinal,
|
|
4460
|
+
missing_target_first_seen_at, source_rows_read, source_rows_skipped, source_changes_read, error
|
|
4461
|
+
)
|
|
4462
|
+
SELECT
|
|
4463
|
+
project_id, id, execution_id, projection_id, projection_kind, materialization_protocol,
|
|
4464
|
+
dataset_id, dataset_version_id, dataset_version_created_at, ontology_revision,
|
|
4465
|
+
projection_revision, ownership_hash, object_type_id, source_object_type_id,
|
|
4466
|
+
target_object_type_id, status, queued_at, started_at, finished_at, attempt, execution_token,
|
|
4467
|
+
fixed_batch_size, next_batch_ordinal, next_row_offset, input_exhausted,
|
|
4468
|
+
missing_target_object_type_id, missing_target_object_id, missing_target_batch_ordinal,
|
|
4469
|
+
missing_target_first_seen_at, source_rows_read, source_rows_skipped, source_changes_read, error
|
|
4470
|
+
FROM projection_runs;
|
|
4471
|
+
|
|
4472
|
+
DROP TABLE projection_runs;
|
|
4473
|
+
ALTER TABLE projection_runs_v2 RENAME TO projection_runs;
|
|
4474
|
+
|
|
4475
|
+
CREATE INDEX idx_projection_runs_project_started
|
|
4476
|
+
ON projection_runs(project_id, COALESCE(started_at, queued_at) DESC);
|
|
4477
|
+
CREATE INDEX idx_projection_runs_project_projection_started
|
|
4478
|
+
ON projection_runs(project_id, projection_id, COALESCE(started_at, queued_at) DESC);
|
|
4479
|
+
CREATE INDEX idx_projection_runs_project_dataset_started
|
|
4480
|
+
ON projection_runs(project_id, dataset_id, COALESCE(started_at, queued_at) DESC);
|
|
4481
|
+
CREATE INDEX idx_projection_runs_project_version_started
|
|
4482
|
+
ON projection_runs(project_id, dataset_version_id, COALESCE(started_at, queued_at) DESC);
|
|
4483
|
+
CREATE INDEX idx_projection_runs_project_status_started
|
|
4484
|
+
ON projection_runs(project_id, status, COALESCE(started_at, queued_at) DESC);
|
|
4485
|
+
CREATE INDEX idx_projection_runs_project_object_type_started
|
|
4486
|
+
ON projection_runs(project_id, object_type_id, COALESCE(started_at, queued_at) DESC);
|
|
4487
|
+
`;
|
|
4488
|
+
|
|
4489
|
+
// src/migrations/052-compact-source-storage.sql
|
|
4490
|
+
var _052_compact_source_storage_default = `-- Source versions get a compact surrogate key; roots and rows stop repeating the text triple.
|
|
4491
|
+
-- SQLite cannot add a key column to a table, so the three source tables are rebuilt. Index
|
|
4492
|
+
-- names are schema-wide: the old ones are dropped first to free them.
|
|
4493
|
+
DROP INDEX idx_ontology_sources_active;
|
|
4494
|
+
DROP INDEX idx_ontology_sources_run_candidate;
|
|
4495
|
+
DROP INDEX idx_ontology_sources_cleanup;
|
|
4496
|
+
DROP INDEX idx_ontology_sources_run;
|
|
4497
|
+
DROP INDEX idx_ontology_source_roots_active;
|
|
4498
|
+
DROP INDEX idx_ontology_source_roots_lookup;
|
|
4499
|
+
DROP INDEX idx_ontology_source_roots_cleanup;
|
|
4500
|
+
DROP INDEX idx_ontology_source_rows_root;
|
|
4501
|
+
DROP INDEX idx_ontology_source_rows_staging_ordinal;
|
|
4502
|
+
DROP INDEX idx_ontology_source_rows_entity_sort;
|
|
4503
|
+
DROP INDEX idx_ontology_source_rows_object;
|
|
4504
|
+
DROP INDEX idx_ontology_source_rows_link_source;
|
|
4505
|
+
DROP INDEX idx_ontology_source_rows_link_target;
|
|
4506
|
+
ALTER TABLE ontology_source_rows RENAME TO legacy_ontology_source_rows;
|
|
4507
|
+
ALTER TABLE ontology_source_roots RENAME TO legacy_ontology_source_roots;
|
|
4508
|
+
ALTER TABLE ontology_sources RENAME TO legacy_ontology_sources;
|
|
4509
|
+
|
|
4510
|
+
CREATE TABLE ontology_sources (
|
|
4511
|
+
version_id INTEGER PRIMARY KEY,
|
|
4512
|
+
project_id TEXT NOT NULL,
|
|
4513
|
+
source_id TEXT NOT NULL,
|
|
4514
|
+
materialization_id TEXT NOT NULL,
|
|
4515
|
+
projection_run_id TEXT NOT NULL,
|
|
4516
|
+
projection_kind TEXT NOT NULL CHECK (projection_kind IN ('object', 'link')),
|
|
4517
|
+
protocol TEXT NOT NULL CHECK (protocol = 'replacement'),
|
|
4518
|
+
status TEXT NOT NULL CHECK (
|
|
4519
|
+
status IN ('staging', 'ready', 'active', 'superseded', 'abandoned')
|
|
4520
|
+
),
|
|
4521
|
+
execution_token TEXT,
|
|
4522
|
+
dataset_id TEXT NOT NULL,
|
|
4523
|
+
dataset_version_id TEXT NOT NULL,
|
|
4524
|
+
dataset_version_created_at TEXT NOT NULL,
|
|
4525
|
+
projection_revision TEXT NOT NULL,
|
|
4526
|
+
ownership_hash TEXT NOT NULL,
|
|
4527
|
+
ontology_revision TEXT NOT NULL,
|
|
4528
|
+
root_count INTEGER CHECK (root_count IS NULL OR root_count >= 0),
|
|
4529
|
+
assertion_count INTEGER CHECK (assertion_count IS NULL OR assertion_count >= 0),
|
|
4530
|
+
created_at TEXT NOT NULL,
|
|
4531
|
+
ready_at TEXT,
|
|
4532
|
+
activated_at TEXT,
|
|
4533
|
+
terminal_at TEXT,
|
|
4534
|
+
last_commit_id TEXT,
|
|
4535
|
+
updated_at TEXT NOT NULL,
|
|
4536
|
+
base_materialization_id TEXT,
|
|
4537
|
+
base_commit_id TEXT,
|
|
4538
|
+
UNIQUE (project_id, source_id, materialization_id),
|
|
4539
|
+
CHECK ((base_materialization_id IS NULL) = (base_commit_id IS NULL)),
|
|
4540
|
+
CHECK ((root_count IS NULL) = (assertion_count IS NULL)),
|
|
4541
|
+
CHECK ((root_count IS NULL) = (ready_at IS NULL)),
|
|
4542
|
+
CHECK ((status IN ('staging', 'ready')) = (execution_token IS NOT NULL)),
|
|
4543
|
+
CHECK (
|
|
4544
|
+
(status = 'staging' AND ready_at IS NULL)
|
|
4545
|
+
OR (status IN ('ready', 'active', 'superseded') AND ready_at IS NOT NULL)
|
|
4546
|
+
OR status = 'abandoned'
|
|
4547
|
+
),
|
|
4548
|
+
CHECK ((status IN ('active', 'superseded')) = (activated_at IS NOT NULL)),
|
|
4549
|
+
CHECK ((status IN ('active', 'superseded')) = (last_commit_id IS NOT NULL)),
|
|
4550
|
+
CHECK ((status IN ('superseded', 'abandoned')) = (terminal_at IS NOT NULL)),
|
|
4551
|
+
CHECK (ready_at IS NULL OR created_at <= ready_at),
|
|
4552
|
+
CHECK (activated_at IS NULL OR (ready_at IS NOT NULL AND ready_at <= activated_at)),
|
|
4553
|
+
CHECK (terminal_at IS NULL OR created_at <= terminal_at),
|
|
4554
|
+
CHECK (terminal_at IS NULL OR ready_at IS NULL OR ready_at <= terminal_at),
|
|
4555
|
+
CHECK (terminal_at IS NULL OR activated_at IS NULL OR activated_at <= terminal_at),
|
|
4556
|
+
CHECK (created_at <= updated_at)
|
|
4557
|
+
);
|
|
4558
|
+
INSERT INTO ontology_sources (
|
|
4559
|
+
project_id, source_id, materialization_id, projection_run_id, projection_kind, protocol,
|
|
4560
|
+
status, execution_token, dataset_id, dataset_version_id, dataset_version_created_at,
|
|
4561
|
+
projection_revision, ownership_hash, ontology_revision, root_count, assertion_count,
|
|
4562
|
+
created_at, ready_at, activated_at, terminal_at, last_commit_id, updated_at,
|
|
4563
|
+
base_materialization_id, base_commit_id
|
|
4564
|
+
)
|
|
4565
|
+
SELECT project_id, source_id, materialization_id, projection_run_id, projection_kind, protocol,
|
|
4566
|
+
status, execution_token, dataset_id, dataset_version_id, dataset_version_created_at,
|
|
4567
|
+
projection_revision, ownership_hash, ontology_revision, root_count, assertion_count,
|
|
4568
|
+
created_at, ready_at, activated_at, terminal_at, last_commit_id, updated_at,
|
|
4569
|
+
base_materialization_id, base_commit_id
|
|
4570
|
+
FROM legacy_ontology_sources
|
|
4571
|
+
ORDER BY rowid;
|
|
4572
|
+
CREATE UNIQUE INDEX idx_ontology_sources_active
|
|
4573
|
+
ON ontology_sources(project_id, source_id)
|
|
4574
|
+
WHERE status = 'active';
|
|
4575
|
+
CREATE UNIQUE INDEX idx_ontology_sources_run_candidate
|
|
4576
|
+
ON ontology_sources(project_id, projection_run_id)
|
|
4577
|
+
WHERE status IN ('staging', 'ready');
|
|
4578
|
+
CREATE INDEX idx_ontology_sources_cleanup
|
|
4579
|
+
ON ontology_sources(project_id, status, terminal_at, source_id, materialization_id);
|
|
4580
|
+
CREATE INDEX idx_ontology_sources_run
|
|
4581
|
+
ON ontology_sources(project_id, projection_run_id);
|
|
4582
|
+
|
|
4583
|
+
-- A root is live while its version is published (active or superseded), it is not retired and
|
|
4584
|
+
-- it is not a deletion. Activation therefore writes only the roots it replaces, never its own.
|
|
4585
|
+
-- \`root_key\` is the canonical key; SQLite's default BINARY collation orders it by its UTF-8 bytes.
|
|
4586
|
+
CREATE TABLE ontology_source_roots (
|
|
4587
|
+
id INTEGER PRIMARY KEY,
|
|
4588
|
+
version_id INTEGER NOT NULL REFERENCES ontology_sources (version_id) ON DELETE RESTRICT,
|
|
4589
|
+
project_id TEXT NOT NULL,
|
|
4590
|
+
root_key TEXT NOT NULL,
|
|
4591
|
+
staging_ordinal INTEGER NOT NULL CHECK (staging_ordinal >= 0),
|
|
4592
|
+
deleted INTEGER NOT NULL DEFAULT 0 CHECK (deleted IN (0, 1)),
|
|
4593
|
+
retired_at TEXT,
|
|
4594
|
+
CONSTRAINT ontology_source_roots_key UNIQUE (version_id, root_key),
|
|
4595
|
+
CONSTRAINT ontology_source_roots_ordinal UNIQUE (version_id, staging_ordinal)
|
|
4596
|
+
);
|
|
4597
|
+
CREATE INDEX idx_ontology_source_roots_live
|
|
4598
|
+
ON ontology_source_roots (project_id, root_key) WHERE retired_at IS NULL AND deleted = 0;
|
|
4599
|
+
CREATE INDEX idx_ontology_source_roots_retired
|
|
4600
|
+
ON ontology_source_roots (project_id, retired_at) WHERE retired_at IS NOT NULL;
|
|
4601
|
+
|
|
4602
|
+
-- A row's identity lives in its typed columns; \`payload\` is the assertion without kind and ref.
|
|
4603
|
+
CREATE TABLE ontology_source_rows (
|
|
4604
|
+
root_id INTEGER NOT NULL REFERENCES ontology_source_roots (id) ON DELETE RESTRICT,
|
|
4605
|
+
entity_kind TEXT NOT NULL CHECK (entity_kind IN ('object', 'link')),
|
|
4606
|
+
object_type_id TEXT,
|
|
4607
|
+
primary_id TEXT,
|
|
4608
|
+
source_type_id TEXT,
|
|
4609
|
+
source_primary_id TEXT,
|
|
4610
|
+
link_id TEXT,
|
|
4611
|
+
target_type_id TEXT,
|
|
4612
|
+
target_primary_id TEXT,
|
|
4613
|
+
payload TEXT CHECK (json_valid(payload)),
|
|
4614
|
+
CHECK (
|
|
4615
|
+
(
|
|
4616
|
+
entity_kind = 'object'
|
|
4617
|
+
AND object_type_id IS NOT NULL AND primary_id IS NOT NULL
|
|
4618
|
+
AND source_type_id IS NULL AND source_primary_id IS NULL AND link_id IS NULL
|
|
4619
|
+
AND target_type_id IS NULL AND target_primary_id IS NULL
|
|
4620
|
+
)
|
|
4621
|
+
OR
|
|
4622
|
+
(
|
|
4623
|
+
entity_kind = 'link'
|
|
4624
|
+
AND object_type_id IS NULL AND primary_id IS NULL
|
|
4625
|
+
AND source_type_id IS NOT NULL AND source_primary_id IS NOT NULL AND link_id IS NOT NULL
|
|
4626
|
+
AND target_type_id IS NOT NULL AND target_primary_id IS NOT NULL
|
|
4627
|
+
)
|
|
4628
|
+
)
|
|
4629
|
+
);
|
|
4630
|
+
CREATE INDEX idx_ontology_source_rows_root ON ontology_source_rows (root_id);
|
|
4631
|
+
CREATE INDEX idx_ontology_source_rows_link_source
|
|
4632
|
+
ON ontology_source_rows (source_type_id, source_primary_id, link_id)
|
|
4633
|
+
WHERE entity_kind = 'link';
|
|
4634
|
+
CREATE INDEX idx_ontology_source_rows_link_target
|
|
4635
|
+
ON ontology_source_rows (target_type_id, target_primary_id)
|
|
4636
|
+
WHERE entity_kind = 'link';
|
|
4637
|
+
|
|
4638
|
+
-- A published root that was not active is retired, so liveness stays exactly what it was.
|
|
4639
|
+
INSERT INTO ontology_source_roots (
|
|
4640
|
+
version_id, project_id, root_key, staging_ordinal, deleted, retired_at
|
|
4641
|
+
)
|
|
4642
|
+
SELECT versions.version_id, roots.project_id, roots.root_key, roots.staging_ordinal,
|
|
4643
|
+
roots.deleted,
|
|
4644
|
+
CASE
|
|
4645
|
+
WHEN roots.active = 1 THEN NULL
|
|
4646
|
+
WHEN roots.retired_at IS NOT NULL THEN roots.retired_at
|
|
4647
|
+
WHEN versions.status IN ('active', 'superseded') THEN versions.updated_at
|
|
4648
|
+
END
|
|
4649
|
+
FROM legacy_ontology_source_roots AS roots
|
|
4650
|
+
JOIN ontology_sources AS versions USING (project_id, source_id, materialization_id)
|
|
4651
|
+
ORDER BY versions.version_id, roots.staging_ordinal;
|
|
4652
|
+
|
|
4653
|
+
INSERT INTO ontology_source_rows (
|
|
4654
|
+
root_id, entity_kind, object_type_id, primary_id,
|
|
4655
|
+
source_type_id, source_primary_id, link_id, target_type_id, target_primary_id, payload
|
|
4656
|
+
)
|
|
4657
|
+
SELECT roots.id, rows.entity_kind, rows.object_type_id, rows.primary_id,
|
|
4658
|
+
rows.source_type_id, rows.source_primary_id, rows.link_id, rows.target_type_id,
|
|
4659
|
+
rows.target_primary_id, NULLIF(json_remove(rows.assertion, '$.kind', '$.ref'), '{}')
|
|
4660
|
+
FROM legacy_ontology_source_rows AS rows
|
|
4661
|
+
JOIN ontology_sources AS versions USING (project_id, source_id, materialization_id)
|
|
4662
|
+
JOIN ontology_source_roots AS roots
|
|
4663
|
+
ON roots.version_id = versions.version_id AND roots.root_key = rows.root_key
|
|
4664
|
+
ORDER BY roots.id;
|
|
4665
|
+
|
|
4666
|
+
DROP TABLE legacy_ontology_source_rows;
|
|
4667
|
+
DROP TABLE legacy_ontology_source_roots;
|
|
4668
|
+
DROP TABLE legacy_ontology_sources;
|
|
4669
|
+
-- No ANALYZE: nothing refreshes SQLite statistics afterwards, and on a fresh install they would
|
|
4670
|
+
-- describe empty tables forever and steer the planner to partial indexes over primary keys.
|
|
4671
|
+
`;
|
|
4672
|
+
|
|
4360
4673
|
// src/migrations/037-execution-requester-groups.sql
|
|
4361
4674
|
var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids TEXT NOT NULL DEFAULT '[]';
|
|
4362
4675
|
|
|
@@ -4561,7 +4874,16 @@ var sqliteStorageMigrations = defineMigrations({
|
|
|
4561
4874
|
sqliteSql("047-ontology-commit-attribution", _047_ontology_commit_attribution_default),
|
|
4562
4875
|
sqliteSql("048-file-upload-sessions", _048_file_upload_sessions_default),
|
|
4563
4876
|
sqliteSql("049-native-sessions", _049_native_sessions_default),
|
|
4564
|
-
sqliteSql("050-directory-group-memberships", _050_directory_group_memberships_default)
|
|
4877
|
+
sqliteSql("050-directory-group-memberships", _050_directory_group_memberships_default),
|
|
4878
|
+
sqliteSql("051-projection-run-supersession", _051_projection_run_supersession_default),
|
|
4879
|
+
sqliteStep("052-compact-source-storage", (db) => {
|
|
4880
|
+
const before = sourceStorageCounts(db);
|
|
4881
|
+
db.run(_052_compact_source_storage_default);
|
|
4882
|
+
const after = sourceStorageCounts(db);
|
|
4883
|
+
if (after.versions !== before.versions || after.roots !== before.roots || after.rows !== before.rows) {
|
|
4884
|
+
throw new Error("[SixbSqliteStorage] Source versions, roots or rows were lost while compacting source storage.");
|
|
4885
|
+
}
|
|
4886
|
+
}, { checksum: checksum(_052_compact_source_storage_default) })
|
|
4565
4887
|
]
|
|
4566
4888
|
});
|
|
4567
4889
|
function sqliteStoragePath(basePath) {
|
|
@@ -4676,6 +4998,11 @@ function rowToMigrationRecord(row) {
|
|
|
4676
4998
|
finishedAt: migration.finished_at ?? undefined
|
|
4677
4999
|
};
|
|
4678
5000
|
}
|
|
5001
|
+
function sourceStorageCounts(db) {
|
|
5002
|
+
return db.query(`SELECT (SELECT count(*) FROM ontology_sources) AS versions,
|
|
5003
|
+
(SELECT count(*) FROM ontology_source_roots) AS roots,
|
|
5004
|
+
(SELECT count(*) FROM ontology_source_rows) AS rows`).get();
|
|
5005
|
+
}
|
|
4679
5006
|
function checksum(value) {
|
|
4680
5007
|
return createHash("sha256").update(value).digest("hex");
|
|
4681
5008
|
}
|
|
@@ -12204,6 +12531,10 @@ function assertReconciliationPageLimit(limit) {
|
|
|
12204
12531
|
// src/ontology-storage/shared.ts
|
|
12205
12532
|
import { parseSixbFailure as parseSixbFailure2 } from "@sixb/core/internal/errors";
|
|
12206
12533
|
import { MaterializationConflictError } from "@sixb/core/internal/materialization";
|
|
12534
|
+
import {
|
|
12535
|
+
sourceAssertionFromColumns,
|
|
12536
|
+
sourceEntityFromKey
|
|
12537
|
+
} from "@sixb/core/internal/ontology-storage-provider";
|
|
12207
12538
|
import { ONTOLOGY_OUTBOX_FAILURE_CODES } from "@sixb/core/storage";
|
|
12208
12539
|
import {
|
|
12209
12540
|
assertNonblank,
|
|
@@ -12256,8 +12587,8 @@ function sourceRecord(row) {
|
|
|
12256
12587
|
}
|
|
12257
12588
|
function sourceAssertion(row) {
|
|
12258
12589
|
return {
|
|
12259
|
-
root:
|
|
12260
|
-
assertion: parseJson2(row.
|
|
12590
|
+
root: sourceEntityFromKey(row.root_key),
|
|
12591
|
+
assertion: sourceAssertionFromColumns(row, row.payload === null ? null : parseJson2(row.payload)),
|
|
12261
12592
|
stagingOrdinal: row.staging_ordinal
|
|
12262
12593
|
};
|
|
12263
12594
|
}
|
|
@@ -12475,29 +12806,68 @@ import {
|
|
|
12475
12806
|
MaterializationValidationError,
|
|
12476
12807
|
projectionEntityKey
|
|
12477
12808
|
} from "@sixb/core/internal/materialization";
|
|
12478
|
-
import {
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12809
|
+
import {
|
|
12810
|
+
sourceEntityColumns,
|
|
12811
|
+
sourceStageRoots
|
|
12812
|
+
} from "@sixb/core/internal/ontology-storage-provider";
|
|
12813
|
+
var PUBLISHED = "('active', 'superseded')";
|
|
12814
|
+
var SOURCE_ASSERTION_COLUMNS = `versions.source_id, versions.materialization_id,
|
|
12815
|
+
roots.root_key, roots.staging_ordinal, rows.entity_kind, rows.object_type_id, rows.primary_id,
|
|
12816
|
+
rows.source_type_id, rows.source_primary_id, rows.link_id, rows.target_type_id,
|
|
12817
|
+
rows.target_primary_id, rows.payload`;
|
|
12818
|
+
var LIVE_SOURCE_ROWS_JOIN = `
|
|
12819
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.id = rows.root_id
|
|
12820
|
+
AND roots.project_id = ? AND roots.retired_at IS NULL AND roots.deleted = 0
|
|
12821
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12822
|
+
AND versions.status IN ${PUBLISHED}`;
|
|
12823
|
+
var ENTITY_MATCH = `rows.entity_kind = requested.entity_kind
|
|
12824
|
+
AND rows.object_type_id IS requested.object_type_id
|
|
12825
|
+
AND rows.primary_id IS requested.primary_id
|
|
12826
|
+
AND rows.source_type_id IS requested.source_type_id
|
|
12827
|
+
AND rows.source_primary_id IS requested.source_primary_id
|
|
12828
|
+
AND rows.link_id IS requested.link_id
|
|
12829
|
+
AND rows.target_type_id IS requested.target_type_id
|
|
12830
|
+
AND rows.target_primary_id IS requested.target_primary_id`;
|
|
12831
|
+
var REQUESTED_ENTITY = `json_extract(value, '$.entityKind') AS entity_kind,
|
|
12832
|
+
json_extract(value, '$.objectTypeId') AS object_type_id,
|
|
12833
|
+
json_extract(value, '$.primaryId') AS primary_id,
|
|
12834
|
+
json_extract(value, '$.sourceTypeId') AS source_type_id,
|
|
12835
|
+
json_extract(value, '$.sourcePrimaryId') AS source_primary_id,
|
|
12836
|
+
json_extract(value, '$.linkId') AS link_id,
|
|
12837
|
+
json_extract(value, '$.targetTypeId') AS target_type_id,
|
|
12838
|
+
json_extract(value, '$.targetPrimaryId') AS target_primary_id`;
|
|
12839
|
+
function requestedEntity(ref) {
|
|
12840
|
+
const key = projectionEntityKey(ref);
|
|
12484
12841
|
return {
|
|
12485
|
-
|
|
12842
|
+
entityKind: ref.kind,
|
|
12843
|
+
...sourceEntityColumns(ref),
|
|
12844
|
+
rootKeys: ref.kind === "object" ? [key] : [key, projectionEntityKey({ kind: "object", ref: ref.ref.source })]
|
|
12845
|
+
};
|
|
12846
|
+
}
|
|
12847
|
+
function sourceVersionId(db, input) {
|
|
12848
|
+
const version = db.query(`SELECT version_id FROM ontology_sources
|
|
12849
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?`).get(input.projectId, input.sourceId, input.materializationId);
|
|
12850
|
+
return version?.version_id ?? null;
|
|
12851
|
+
}
|
|
12852
|
+
function replacementSourceRows(db, input) {
|
|
12853
|
+
const version = sourceVersionId(db, input);
|
|
12854
|
+
const live = `live.retired_at IS NULL AND live.deleted = 0
|
|
12855
|
+
AND versions.source_id = ? AND versions.status IN ${PUBLISHED}`;
|
|
12856
|
+
const replaced = input.incremental ? `SELECT live.id FROM ontology_source_roots AS changed
|
|
12857
|
+
CROSS JOIN ontology_source_roots AS live
|
|
12858
|
+
ON live.project_id = ? AND live.root_key = changed.root_key
|
|
12859
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = live.version_id
|
|
12860
|
+
WHERE changed.version_id = ? AND ${live}` : `SELECT live.id FROM ontology_sources AS versions
|
|
12861
|
+
CROSS JOIN ontology_source_roots AS live ON live.version_id = versions.version_id
|
|
12862
|
+
WHERE versions.project_id = ? AND ${live}`;
|
|
12863
|
+
return {
|
|
12864
|
+
sql: `SELECT rows.* FROM ontology_source_roots AS roots
|
|
12865
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
12866
|
+
WHERE roots.version_id = ?
|
|
12486
12867
|
UNION ALL
|
|
12487
|
-
SELECT rows.* FROM ${roots
|
|
12488
|
-
CROSS JOIN ontology_source_rows AS rows ON rows.
|
|
12489
|
-
|
|
12490
|
-
AND rows.root_sort_key = roots.root_sort_key
|
|
12491
|
-
WHERE ${selected}.project_id = ? AND ${selected}.source_id = ?
|
|
12492
|
-
AND ${input.incremental ? "changed.materialization_id = ?" : "roots.active = 1"}`,
|
|
12493
|
-
values: [
|
|
12494
|
-
input.projectId,
|
|
12495
|
-
input.sourceId,
|
|
12496
|
-
input.materializationId,
|
|
12497
|
-
input.projectId,
|
|
12498
|
-
input.sourceId,
|
|
12499
|
-
...input.incremental ? [input.materializationId] : []
|
|
12500
|
-
]
|
|
12868
|
+
SELECT rows.* FROM (${replaced}) AS roots
|
|
12869
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id`,
|
|
12870
|
+
values: [version, input.projectId, ...input.incremental ? [version] : [], input.sourceId]
|
|
12501
12871
|
};
|
|
12502
12872
|
}
|
|
12503
12873
|
function stageSourceRoots(db, manifest, input) {
|
|
@@ -12506,15 +12876,15 @@ function stageSourceRoots(db, manifest, input) {
|
|
|
12506
12876
|
throw new MaterializationValidationError("Root deletions require a source delta base.");
|
|
12507
12877
|
}
|
|
12508
12878
|
const existing = db.query(`SELECT staging_ordinal, deleted FROM ontology_source_roots
|
|
12509
|
-
WHERE
|
|
12879
|
+
WHERE version_id = ? AND root_key = ?`);
|
|
12510
12880
|
const insert = db.query(`INSERT INTO ontology_source_roots (
|
|
12511
|
-
|
|
12512
|
-
) VALUES (?, ?, ?, ?,
|
|
12881
|
+
version_id, project_id, root_key, staging_ordinal, deleted
|
|
12882
|
+
) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING id`);
|
|
12513
12883
|
for (const root of roots) {
|
|
12514
|
-
const inserted = insert.get(
|
|
12884
|
+
const inserted = insert.get(manifest.version_id, input.projectId, root.rootKey, root.stagingOrdinal, Number(root.deleted));
|
|
12515
12885
|
if (inserted)
|
|
12516
12886
|
continue;
|
|
12517
|
-
const previous = existing.get(
|
|
12887
|
+
const previous = existing.get(manifest.version_id, root.rootKey);
|
|
12518
12888
|
if (!previous || previous.staging_ordinal !== root.stagingOrdinal || Boolean(previous.deleted) !== root.deleted) {
|
|
12519
12889
|
throw new MaterializationValidationError("Source materialization repeats root or stream ordinal with different content.");
|
|
12520
12890
|
}
|
|
@@ -12525,72 +12895,61 @@ function activateSourceRoots(db, projectId, candidate, activation) {
|
|
|
12525
12895
|
throw new MaterializationConflictError2("projection-fence", "Source delta base changed before activation.");
|
|
12526
12896
|
if (candidate.base_materialization_id !== null && candidate.root_count === 0)
|
|
12527
12897
|
return;
|
|
12528
|
-
const
|
|
12529
|
-
db.query(`UPDATE ontology_source_roots
|
|
12530
|
-
WHERE
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
WHERE
|
|
12898
|
+
const delta = candidate.base_materialization_id !== null;
|
|
12899
|
+
db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
12900
|
+
WHERE retired_at IS NULL AND deleted = 0
|
|
12901
|
+
AND version_id IN (SELECT version_id FROM ontology_sources
|
|
12902
|
+
WHERE project_id = ? AND source_id = ? AND status IN ${PUBLISHED})
|
|
12903
|
+
${delta ? `AND project_id = ? AND root_key IN (
|
|
12904
|
+
SELECT root_key FROM ontology_source_roots WHERE version_id = ?)` : ""}`).run(activation.updatedAt, projectId, activation.source.projectionId, ...delta ? [projectId, candidate.version_id] : []);
|
|
12905
|
+
db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
12906
|
+
WHERE version_id = ? AND deleted = 1`).run(activation.updatedAt, candidate.version_id);
|
|
12537
12907
|
}
|
|
12538
12908
|
function assertSourceRootCoverage(db, manifest) {
|
|
12539
12909
|
const invalid = db.query(`SELECT 1 FROM ontology_source_roots AS roots
|
|
12540
|
-
WHERE
|
|
12910
|
+
WHERE roots.version_id = ?
|
|
12541
12911
|
AND roots.deleted = EXISTS (
|
|
12542
|
-
SELECT 1 FROM ontology_source_rows AS rows
|
|
12543
|
-
|
|
12544
|
-
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
12545
|
-
) LIMIT 1`).get(manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
12912
|
+
SELECT 1 FROM ontology_source_rows AS rows WHERE rows.root_id = roots.id
|
|
12913
|
+
) LIMIT 1`).get(manifest.version_id);
|
|
12546
12914
|
if (invalid)
|
|
12547
12915
|
throw new MaterializationValidationError("Source roots must have complete assertions or an explicit deletion, never both.");
|
|
12548
12916
|
}
|
|
12549
12917
|
function activeSourceRows(db, projectId, refs) {
|
|
12550
|
-
const
|
|
12551
|
-
const
|
|
12552
|
-
|
|
12553
|
-
entitySortKey,
|
|
12554
|
-
utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source }))
|
|
12555
|
-
];
|
|
12556
|
-
return roots.map((rootSortKey) => ({ rootSortKey, entitySortKey }));
|
|
12918
|
+
const requested = refs.flatMap((ref) => {
|
|
12919
|
+
const { rootKeys, ...entity } = requestedEntity(ref);
|
|
12920
|
+
return rootKeys.map((rootKey) => ({ ...entity, rootKey }));
|
|
12557
12921
|
});
|
|
12558
12922
|
return db.query(`WITH requested AS (
|
|
12559
|
-
SELECT DISTINCT json_extract(value, '$.
|
|
12560
|
-
|
|
12561
|
-
) SELECT
|
|
12923
|
+
SELECT DISTINCT json_extract(value, '$.rootKey') AS root_key, ${REQUESTED_ENTITY}
|
|
12924
|
+
FROM json_each(?)
|
|
12925
|
+
) SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested
|
|
12562
12926
|
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
12563
|
-
AND roots.
|
|
12564
|
-
CROSS JOIN
|
|
12565
|
-
AND
|
|
12566
|
-
|
|
12927
|
+
AND roots.root_key = requested.root_key AND roots.retired_at IS NULL AND roots.deleted = 0
|
|
12928
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12929
|
+
AND versions.status IN ${PUBLISHED}
|
|
12930
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${ENTITY_MATCH}`).all(canonicalJson(requested), projectId);
|
|
12567
12931
|
}
|
|
12568
12932
|
function replacementAssertionRows(db, input) {
|
|
12569
|
-
const
|
|
12570
|
-
entityKind: ref.kind,
|
|
12571
|
-
entityKey: projectionEntityKey(ref),
|
|
12572
|
-
rootKeys: ref.kind === "object" ? [utf8SortKey(projectionEntityKey(ref))] : [
|
|
12573
|
-
utf8SortKey(projectionEntityKey(ref)),
|
|
12574
|
-
utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source }))
|
|
12575
|
-
]
|
|
12576
|
-
}));
|
|
12933
|
+
const version = sourceVersionId(db, input);
|
|
12577
12934
|
const previous = !input.includePrevious ? "" : `UNION ALL
|
|
12578
|
-
SELECT
|
|
12579
|
-
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
12580
|
-
AND roots.
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
AND
|
|
12935
|
+
SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested_entities AS requested
|
|
12936
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
12937
|
+
AND roots.root_key IN (SELECT value FROM json_each(requested.root_keys))
|
|
12938
|
+
AND roots.retired_at IS NULL AND roots.deleted = 0
|
|
12939
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12940
|
+
AND versions.source_id = ? AND versions.status IN ${PUBLISHED}
|
|
12941
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${ENTITY_MATCH}
|
|
12584
12942
|
${!input.incremental ? "" : `WHERE EXISTS (SELECT 1 FROM ontology_source_roots AS changed
|
|
12585
|
-
WHERE changed.
|
|
12586
|
-
AND changed.materialization_id = ? AND changed.root_sort_key = roots.root_sort_key)`}`;
|
|
12943
|
+
WHERE changed.version_id = ? AND changed.root_key = roots.root_key)`}`;
|
|
12587
12944
|
return db.query(`WITH requested_entities AS (
|
|
12588
|
-
SELECT DISTINCT json_extract(value, '$.
|
|
12589
|
-
|
|
12590
|
-
) SELECT
|
|
12591
|
-
CROSS JOIN
|
|
12592
|
-
AND
|
|
12593
|
-
|
|
12945
|
+
SELECT DISTINCT ${REQUESTED_ENTITY}, json_extract(value, '$.rootKeys') AS root_keys
|
|
12946
|
+
FROM json_each(?)
|
|
12947
|
+
) SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested_entities AS requested
|
|
12948
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.version_id = ?
|
|
12949
|
+
AND roots.root_key IN (SELECT value FROM json_each(requested.root_keys))
|
|
12950
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12951
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${ENTITY_MATCH}
|
|
12952
|
+
${previous}`).all(canonicalJson(input.refs.map(requestedEntity)), version, ...input.includePrevious ? [input.projectId, input.sourceId, ...input.incremental ? [version] : []] : []);
|
|
12594
12953
|
}
|
|
12595
12954
|
|
|
12596
12955
|
// src/ontology-storage/materialization-state.ts
|
|
@@ -12803,19 +13162,13 @@ class SqliteMaterializationStateReader {
|
|
|
12803
13162
|
json_extract(value, '$.linkId') AS link_id
|
|
12804
13163
|
FROM json_each(?)
|
|
12805
13164
|
)
|
|
12806
|
-
SELECT
|
|
13165
|
+
SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested
|
|
12807
13166
|
CROSS JOIN ontology_source_rows AS rows
|
|
12808
|
-
ON rows.
|
|
12809
|
-
AND rows.entity_kind = 'link'
|
|
13167
|
+
ON rows.entity_kind = 'link'
|
|
12810
13168
|
AND rows.source_type_id = requested.source_type_id
|
|
12811
13169
|
AND rows.source_primary_id = requested.source_primary_id
|
|
12812
13170
|
AND rows.link_id = requested.link_id
|
|
12813
|
-
|
|
12814
|
-
ON sources.project_id = rows.project_id
|
|
12815
|
-
AND sources.source_id = rows.source_id
|
|
12816
|
-
AND sources.materialization_id = rows.materialization_id
|
|
12817
|
-
AND sources.root_sort_key = rows.root_sort_key
|
|
12818
|
-
WHERE sources.active = 1`).all(requestedJson, this.projectId);
|
|
13171
|
+
${LIVE_SOURCE_ROWS_JOIN}`).all(requestedJson, this.projectId);
|
|
12819
13172
|
const slotOverrideRows = this.db.query(`WITH requested AS (
|
|
12820
13173
|
SELECT DISTINCT
|
|
12821
13174
|
json_extract(value, '$.sourceTypeId') AS source_type_id,
|
|
@@ -12904,12 +13257,8 @@ class SqliteMaterializationStateReader {
|
|
|
12904
13257
|
SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
|
|
12905
13258
|
rows.target_type_id, rows.target_primary_id
|
|
12906
13259
|
FROM ontology_source_rows AS rows
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
AND sources.source_id = rows.source_id
|
|
12910
|
-
AND sources.materialization_id = rows.materialization_id
|
|
12911
|
-
AND sources.root_sort_key = rows.root_sort_key
|
|
12912
|
-
WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.active = 1
|
|
13260
|
+
${LIVE_SOURCE_ROWS_JOIN}
|
|
13261
|
+
WHERE rows.entity_kind = 'link'
|
|
12913
13262
|
), selected AS (
|
|
12914
13263
|
SELECT DISTINCT links.*,
|
|
12915
13264
|
${linkSortExpression("links")} AS sort_key
|
|
@@ -13020,7 +13369,7 @@ class SqliteMaterializationStateReader {
|
|
|
13020
13369
|
this.prepareReplacementLinks(input);
|
|
13021
13370
|
}
|
|
13022
13371
|
prepareReplacementObjects(input) {
|
|
13023
|
-
const rows = replacementSourceRows({
|
|
13372
|
+
const rows = replacementSourceRows(this.db, {
|
|
13024
13373
|
projectId: this.projectId,
|
|
13025
13374
|
sourceId: input.sourceId,
|
|
13026
13375
|
materializationId: input.candidateMaterializationId,
|
|
@@ -13031,14 +13380,14 @@ class SqliteMaterializationStateReader {
|
|
|
13031
13380
|
session_id, entity_kind, identity_key, sort_key, diff_required
|
|
13032
13381
|
)
|
|
13033
13382
|
SELECT ?, 'object', json_array(object_type_id, primary_id),
|
|
13034
|
-
|
|
13383
|
+
${objectSortExpression()}, 1
|
|
13035
13384
|
FROM (${rows.sql}) AS rows
|
|
13036
13385
|
WHERE entity_kind = 'object'
|
|
13037
13386
|
GROUP BY object_type_id, primary_id
|
|
13038
13387
|
`).run(input.sessionId, ...rows.values);
|
|
13039
13388
|
}
|
|
13040
13389
|
prepareReplacementLinks(input) {
|
|
13041
|
-
const rows = replacementSourceRows({
|
|
13390
|
+
const rows = replacementSourceRows(this.db, {
|
|
13042
13391
|
projectId: this.projectId,
|
|
13043
13392
|
sourceId: input.sourceId,
|
|
13044
13393
|
materializationId: input.candidateMaterializationId,
|
|
@@ -13095,31 +13444,21 @@ class SqliteMaterializationStateReader {
|
|
|
13095
13444
|
UNION
|
|
13096
13445
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
13097
13446
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
13098
|
-
FROM
|
|
13099
|
-
JOIN
|
|
13100
|
-
ON
|
|
13101
|
-
AND
|
|
13102
|
-
AND
|
|
13103
|
-
|
|
13104
|
-
JOIN incident_objects
|
|
13105
|
-
ON incident_objects.object_type_id = rows.source_type_id
|
|
13106
|
-
AND incident_objects.primary_id = rows.source_primary_id
|
|
13107
|
-
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
13108
|
-
AND sources.active = 1
|
|
13447
|
+
FROM incident_objects
|
|
13448
|
+
CROSS JOIN ontology_source_rows AS rows
|
|
13449
|
+
ON rows.entity_kind = 'link'
|
|
13450
|
+
AND rows.source_type_id = incident_objects.object_type_id
|
|
13451
|
+
AND rows.source_primary_id = incident_objects.primary_id
|
|
13452
|
+
${LIVE_SOURCE_ROWS_JOIN}
|
|
13109
13453
|
UNION
|
|
13110
13454
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
13111
13455
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
13112
|
-
FROM
|
|
13113
|
-
JOIN
|
|
13114
|
-
ON
|
|
13115
|
-
AND
|
|
13116
|
-
AND
|
|
13117
|
-
|
|
13118
|
-
JOIN incident_objects
|
|
13119
|
-
ON incident_objects.object_type_id = rows.target_type_id
|
|
13120
|
-
AND incident_objects.primary_id = rows.target_primary_id
|
|
13121
|
-
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
13122
|
-
AND sources.active = 1
|
|
13456
|
+
FROM incident_objects
|
|
13457
|
+
CROSS JOIN ontology_source_rows AS rows
|
|
13458
|
+
ON rows.entity_kind = 'link'
|
|
13459
|
+
AND rows.target_type_id = incident_objects.object_type_id
|
|
13460
|
+
AND rows.target_primary_id = incident_objects.primary_id
|
|
13461
|
+
${LIVE_SOURCE_ROWS_JOIN}
|
|
13123
13462
|
), diff_links AS (
|
|
13124
13463
|
SELECT * FROM replacement_links
|
|
13125
13464
|
UNION SELECT * FROM incident_links
|
|
@@ -13269,9 +13608,7 @@ function storedSource(row) {
|
|
|
13269
13608
|
return {
|
|
13270
13609
|
source: { projectionId: row.source_id },
|
|
13271
13610
|
materializationId: row.materialization_id,
|
|
13272
|
-
|
|
13273
|
-
assertion: parseJson2(row.assertion),
|
|
13274
|
-
stagingOrdinal: row.staging_ordinal
|
|
13611
|
+
...sourceAssertion(row)
|
|
13275
13612
|
};
|
|
13276
13613
|
}
|
|
13277
13614
|
function effectiveObjectSnapshot(row) {
|
|
@@ -13319,6 +13656,10 @@ function storedPoint(row) {
|
|
|
13319
13656
|
lastCommitId: row.last_commit_id
|
|
13320
13657
|
};
|
|
13321
13658
|
}
|
|
13659
|
+
function objectSortExpression(alias) {
|
|
13660
|
+
const prefix = alias ? `${alias}.` : "";
|
|
13661
|
+
return `LOWER(HEX(CAST(json_array(${prefix}object_type_id, ${prefix}primary_id) AS BLOB)))`;
|
|
13662
|
+
}
|
|
13322
13663
|
function linkSortExpression(alias) {
|
|
13323
13664
|
const prefix = alias ? `${alias}.` : "";
|
|
13324
13665
|
return `LOWER(HEX(CAST(json_array(${prefix}source_type_id, ${prefix}source_id, ${prefix}link_id, ${prefix}target_type_id, ${prefix}target_id) AS BLOB)))`;
|
|
@@ -14499,51 +14840,80 @@ import {
|
|
|
14499
14840
|
sourceEntityColumns as entityColumns,
|
|
14500
14841
|
isExactStagingManifest,
|
|
14501
14842
|
reconcileSourceStageRows,
|
|
14843
|
+
sourceAssertionPayload,
|
|
14502
14844
|
sourceConflict,
|
|
14503
14845
|
sourceMaterializationIdentity,
|
|
14504
14846
|
sourceStageRow,
|
|
14505
|
-
sourceStageRows
|
|
14506
|
-
utf8SortKey as utf8SortKey2
|
|
14847
|
+
sourceStageRows
|
|
14507
14848
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
14508
14849
|
|
|
14509
14850
|
// src/ontology-storage/source-cleanup.ts
|
|
14851
|
+
function purgeAbandonedSourceVersions(db, input) {
|
|
14852
|
+
let remaining = input.limit;
|
|
14853
|
+
let rowsDeleted = 0;
|
|
14854
|
+
let materializationsDeleted = 0;
|
|
14855
|
+
const nextCandidate = db.query(`
|
|
14856
|
+
SELECT version_id FROM ontology_sources
|
|
14857
|
+
WHERE project_id = ? AND status = 'abandoned'
|
|
14858
|
+
ORDER BY terminal_at, source_id, materialization_id LIMIT 1
|
|
14859
|
+
`);
|
|
14860
|
+
const deleteRows = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
14861
|
+
SELECT rows.rowid FROM ontology_source_roots AS roots
|
|
14862
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
14863
|
+
WHERE roots.version_id = ? LIMIT ?)`);
|
|
14864
|
+
const deleteRoots = db.query(`DELETE FROM ontology_source_roots WHERE id IN (
|
|
14865
|
+
SELECT id FROM ontology_source_roots WHERE version_id = ? LIMIT ?)`);
|
|
14866
|
+
const deleteManifest = db.query("DELETE FROM ontology_sources WHERE version_id = ? AND status = 'abandoned'");
|
|
14867
|
+
while (remaining > 0) {
|
|
14868
|
+
const candidate = nextCandidate.get(input.projectId);
|
|
14869
|
+
if (!candidate)
|
|
14870
|
+
break;
|
|
14871
|
+
const rows = deleteRows.run(candidate.version_id, remaining).changes;
|
|
14872
|
+
rowsDeleted += rows;
|
|
14873
|
+
remaining -= rows;
|
|
14874
|
+
if (remaining === 0)
|
|
14875
|
+
break;
|
|
14876
|
+
const roots = deleteRoots.run(candidate.version_id, remaining).changes;
|
|
14877
|
+
rowsDeleted += roots;
|
|
14878
|
+
remaining -= roots;
|
|
14879
|
+
if (remaining === 0)
|
|
14880
|
+
break;
|
|
14881
|
+
const manifest = deleteManifest.run(candidate.version_id).changes;
|
|
14882
|
+
if (manifest === 0)
|
|
14883
|
+
break;
|
|
14884
|
+
materializationsDeleted += manifest;
|
|
14885
|
+
remaining -= manifest;
|
|
14886
|
+
}
|
|
14887
|
+
return { rowsDeleted, materializationsDeleted };
|
|
14888
|
+
}
|
|
14510
14889
|
function cleanupSourceVersions(db, input) {
|
|
14511
14890
|
let remaining = input.limit;
|
|
14891
|
+
const retired = `roots.project_id = ? AND roots.retired_at < ? AND versions.status = 'superseded'`;
|
|
14512
14892
|
let rowsDeleted = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
14513
14893
|
SELECT rows.rowid FROM ontology_source_roots AS roots
|
|
14514
|
-
JOIN ontology_sources AS
|
|
14515
|
-
CROSS JOIN ontology_source_rows AS rows
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
|
|
14519
|
-
AND sources.status IN ('superseded', 'abandoned')
|
|
14520
|
-
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key, rows.entity_sort_key
|
|
14894
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
14895
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
14896
|
+
WHERE ${retired}
|
|
14897
|
+
ORDER BY roots.retired_at, roots.id
|
|
14521
14898
|
LIMIT ?
|
|
14522
14899
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14523
14900
|
remaining -= rowsDeleted;
|
|
14524
14901
|
if (remaining > 0) {
|
|
14525
|
-
const removed = db.query(`DELETE FROM ontology_source_roots WHERE
|
|
14526
|
-
SELECT roots.
|
|
14527
|
-
JOIN ontology_sources AS
|
|
14528
|
-
WHERE
|
|
14529
|
-
AND
|
|
14530
|
-
|
|
14531
|
-
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
14532
|
-
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key)
|
|
14533
|
-
ORDER BY roots.retired_at, roots.source_id, roots.materialization_id, roots.root_sort_key LIMIT ?
|
|
14902
|
+
const removed = db.query(`DELETE FROM ontology_source_roots WHERE id IN (
|
|
14903
|
+
SELECT roots.id FROM ontology_source_roots AS roots
|
|
14904
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
14905
|
+
WHERE ${retired}
|
|
14906
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows WHERE rows.root_id = roots.id)
|
|
14907
|
+
ORDER BY roots.retired_at, roots.id LIMIT ?
|
|
14534
14908
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14535
14909
|
rowsDeleted += removed;
|
|
14536
14910
|
remaining -= removed;
|
|
14537
14911
|
}
|
|
14538
|
-
const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE
|
|
14539
|
-
SELECT
|
|
14540
|
-
WHERE project_id = ? AND status
|
|
14912
|
+
const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE version_id IN (
|
|
14913
|
+
SELECT versions.version_id FROM ontology_sources AS versions
|
|
14914
|
+
WHERE project_id = ? AND status = 'superseded' AND terminal_at < ?
|
|
14541
14915
|
AND NOT EXISTS (SELECT 1 FROM ontology_source_roots AS roots
|
|
14542
|
-
WHERE roots.
|
|
14543
|
-
AND roots.materialization_id = sources.materialization_id)
|
|
14544
|
-
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
14545
|
-
WHERE rows.project_id = sources.project_id AND rows.source_id = sources.source_id
|
|
14546
|
-
AND rows.materialization_id = sources.materialization_id)
|
|
14916
|
+
WHERE roots.version_id = versions.version_id)
|
|
14547
14917
|
ORDER BY terminal_at, source_id, materialization_id LIMIT ?
|
|
14548
14918
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14549
14919
|
return { rowsDeleted, materializationsDeleted };
|
|
@@ -14575,7 +14945,7 @@ class SqliteOntologySourceStorage {
|
|
|
14575
14945
|
LIMIT 1
|
|
14576
14946
|
`).get(input.projectId, input.execution.projectionRunId);
|
|
14577
14947
|
if (candidate) {
|
|
14578
|
-
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' already has a nonterminal source materialization;
|
|
14948
|
+
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' already has a nonterminal source materialization; adopt it before beginning another.`);
|
|
14579
14949
|
}
|
|
14580
14950
|
this.db.query(`
|
|
14581
14951
|
INSERT INTO ontology_sources (
|
|
@@ -14601,10 +14971,10 @@ class SqliteOntologySourceStorage {
|
|
|
14601
14971
|
throw sourceConflict(`Source materialization '${input.materializationId}' is '${manifest.status}' and cannot accept rows.`);
|
|
14602
14972
|
}
|
|
14603
14973
|
const rows = sourceStageRows(manifest.projection_kind, input.rows);
|
|
14604
|
-
const existing = this.findStageRows(
|
|
14974
|
+
const existing = this.findStageRows(manifest, rows);
|
|
14605
14975
|
const { pending, unchanged } = reconcileSourceStageRows(rows, existing);
|
|
14606
14976
|
stageSourceRoots(this.db, manifest, input);
|
|
14607
|
-
this.insertStageRows(
|
|
14977
|
+
this.insertStageRows(manifest, pending);
|
|
14608
14978
|
return { inserted: pending.length, unchanged };
|
|
14609
14979
|
});
|
|
14610
14980
|
await yieldSqliteEventLoop();
|
|
@@ -14654,13 +15024,40 @@ class SqliteOntologySourceStorage {
|
|
|
14654
15024
|
return row ? sourceRecord(row) : null;
|
|
14655
15025
|
});
|
|
14656
15026
|
}
|
|
15027
|
+
async adopt(input) {
|
|
15028
|
+
return this.runRootOperation(() => {
|
|
15029
|
+
assertProjectAndSource(input);
|
|
15030
|
+
assertExecutionIdentity(input.execution.projectionRunId, input.execution.executionToken);
|
|
15031
|
+
assertTimestamp(input.adoptedAt, "Source adoptedAt", true);
|
|
15032
|
+
this.assertExecution(input);
|
|
15033
|
+
const candidate = this.findRunCandidate(input);
|
|
15034
|
+
if (!candidate)
|
|
15035
|
+
return null;
|
|
15036
|
+
const changed = this.db.query(`
|
|
15037
|
+
UPDATE ontology_sources SET execution_token = ?, updated_at = MAX(updated_at, ?)
|
|
15038
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
15039
|
+
AND status IN ('staging', 'ready')
|
|
15040
|
+
`).run(input.execution.executionToken, input.adoptedAt, candidate.project_id, candidate.source_id, candidate.materialization_id).changes;
|
|
15041
|
+
if (changed !== 1) {
|
|
15042
|
+
throw sourceConflict(`Source materialization '${candidate.materialization_id}' changed.`);
|
|
15043
|
+
}
|
|
15044
|
+
const adopted = this.requireManifest(candidate.project_id, candidate.source_id, candidate.materialization_id);
|
|
15045
|
+
return {
|
|
15046
|
+
record: sourceRecord(adopted),
|
|
15047
|
+
resumeStagingOrdinal: adopted.status === "ready" ? Number(adopted.root_count) : this.lastStagedOrdinal(adopted)
|
|
15048
|
+
};
|
|
15049
|
+
});
|
|
15050
|
+
}
|
|
14657
15051
|
async abandon(input) {
|
|
14658
15052
|
return this.runRootOperation(() => {
|
|
14659
15053
|
assertProjectAndSource(input);
|
|
14660
15054
|
assertExecutionIdentity(input.execution.projectionRunId, input.execution.executionToken);
|
|
14661
15055
|
assertTimestamp(input.abandonedAt, "Source abandonedAt", true);
|
|
14662
15056
|
this.assertExecution(input);
|
|
14663
|
-
|
|
15057
|
+
if (input.kind === "candidate")
|
|
15058
|
+
return this.abandonCandidate(input);
|
|
15059
|
+
const candidate = this.findRunCandidate(input);
|
|
15060
|
+
return candidate ? this.transitionToAbandoned(candidate, input.abandonedAt) : null;
|
|
14664
15061
|
});
|
|
14665
15062
|
}
|
|
14666
15063
|
async cleanupTerminal(input) {
|
|
@@ -14671,6 +15068,13 @@ class SqliteOntologySourceStorage {
|
|
|
14671
15068
|
return cleanupSourceVersions(this.db, input);
|
|
14672
15069
|
});
|
|
14673
15070
|
}
|
|
15071
|
+
async purgeAbandoned(input) {
|
|
15072
|
+
return this.runRootOperation(() => {
|
|
15073
|
+
assertNonblank(input.projectId, "Abandoned source purge project id");
|
|
15074
|
+
assertPositiveInteger(input.limit, "Abandoned source purge limit");
|
|
15075
|
+
return purgeAbandonedSourceVersions(this.db, input);
|
|
15076
|
+
});
|
|
15077
|
+
}
|
|
14674
15078
|
async summarizeTerminal(input) {
|
|
14675
15079
|
return this.runRootOperation(() => {
|
|
14676
15080
|
assertNonblank(input.projectId, "Terminal source summary project id");
|
|
@@ -14696,19 +15100,20 @@ class SqliteOntologySourceStorage {
|
|
|
14696
15100
|
assertSourceCandidateOwner(sourceRecord(manifest), input.execution);
|
|
14697
15101
|
return this.transitionToAbandoned(manifest, input.abandonedAt);
|
|
14698
15102
|
}
|
|
14699
|
-
|
|
15103
|
+
findRunCandidate(input) {
|
|
14700
15104
|
const candidates = this.db.query(`
|
|
14701
15105
|
SELECT * FROM ontology_sources
|
|
14702
15106
|
WHERE project_id = ? AND source_id = ? AND projection_run_id = ?
|
|
14703
15107
|
AND status IN ('staging', 'ready')
|
|
14704
15108
|
`).all(input.projectId, input.source.projectionId, input.execution.projectionRunId);
|
|
14705
|
-
if (candidates.some((candidate) => candidate.execution_token === input.execution.executionToken)) {
|
|
14706
|
-
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' already has a source materialization owned by the current execution.`);
|
|
14707
|
-
}
|
|
14708
15109
|
if (candidates.length > 1) {
|
|
14709
15110
|
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' has multiple nonterminal source materializations.`);
|
|
14710
15111
|
}
|
|
14711
|
-
return candidates[0]
|
|
15112
|
+
return candidates[0] ?? null;
|
|
15113
|
+
}
|
|
15114
|
+
lastStagedOrdinal(manifest) {
|
|
15115
|
+
const last = this.db.query("SELECT MAX(staging_ordinal) AS ordinal FROM ontology_source_roots WHERE version_id = ?").get(manifest.version_id);
|
|
15116
|
+
return last?.ordinal ?? 0;
|
|
14712
15117
|
}
|
|
14713
15118
|
transitionToAbandoned(manifest, abandonedAt) {
|
|
14714
15119
|
if (abandonedAt < manifest.created_at || manifest.ready_at !== null && abandonedAt < manifest.ready_at) {
|
|
@@ -14723,84 +15128,95 @@ class SqliteOntologySourceStorage {
|
|
|
14723
15128
|
if (changed !== 1) {
|
|
14724
15129
|
throw sourceConflict(`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`);
|
|
14725
15130
|
}
|
|
14726
|
-
this.db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
14727
|
-
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND active = 0`).run(abandonedAt, manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
14728
15131
|
return sourceRecord(this.requireManifest(manifest.project_id, manifest.source_id, manifest.materialization_id));
|
|
14729
15132
|
}
|
|
14730
15133
|
assertReadyState(manifest, rootCount, assertionCount) {
|
|
14731
15134
|
const counts = this.db.query(`
|
|
14732
|
-
SELECT (SELECT COUNT(*) FROM
|
|
14733
|
-
|
|
14734
|
-
|
|
15135
|
+
SELECT (SELECT COUNT(*) FROM ontology_source_roots AS roots
|
|
15136
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15137
|
+
WHERE roots.version_id = ?1) AS assertions,
|
|
15138
|
+
COUNT(*) AS roots, COUNT(DISTINCT staging_ordinal) AS ordinals,
|
|
14735
15139
|
MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
|
|
14736
15140
|
FROM ontology_source_roots
|
|
14737
|
-
WHERE
|
|
14738
|
-
`).get(manifest.
|
|
15141
|
+
WHERE version_id = ?1
|
|
15142
|
+
`).get(manifest.version_id);
|
|
14739
15143
|
if (counts.assertions !== assertionCount || counts.roots !== rootCount || counts.ordinals !== rootCount || rootCount > 0 && (counts.min_ordinal !== 0 || counts.max_ordinal !== rootCount - 1)) {
|
|
14740
15144
|
throw new MaterializationValidationError4("Source ready counts do not match the staged roots and assertions.");
|
|
14741
15145
|
}
|
|
14742
15146
|
assertSourceRootCoverage(this.db, manifest);
|
|
14743
15147
|
const invalid = this.db.query(manifest.projection_kind === "link" ? `
|
|
14744
|
-
SELECT root_key
|
|
14745
|
-
FROM
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
15148
|
+
SELECT roots.root_key
|
|
15149
|
+
FROM ontology_source_roots AS roots
|
|
15150
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15151
|
+
WHERE roots.version_id = ?
|
|
15152
|
+
GROUP BY roots.id
|
|
15153
|
+
HAVING COUNT(*) <> 1
|
|
15154
|
+
OR MAX(json_extract(roots.root_key, '$[0]') <> 'link')
|
|
15155
|
+
OR MAX(rows.entity_kind <> 'link')
|
|
15156
|
+
OR MAX(rows.source_type_id <> json_extract(roots.root_key, '$[1]')
|
|
15157
|
+
OR rows.source_primary_id <> json_extract(roots.root_key, '$[2]')
|
|
15158
|
+
OR rows.link_id <> json_extract(roots.root_key, '$[3]')
|
|
15159
|
+
OR rows.target_type_id <> json_extract(roots.root_key, '$[4]')
|
|
15160
|
+
OR rows.target_primary_id <> json_extract(roots.root_key, '$[5]'))
|
|
14750
15161
|
LIMIT 1
|
|
14751
15162
|
` : `
|
|
14752
|
-
SELECT root_key
|
|
14753
|
-
FROM
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
OR SUM(
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
15163
|
+
SELECT roots.root_key
|
|
15164
|
+
FROM ontology_source_roots AS roots
|
|
15165
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15166
|
+
WHERE roots.version_id = ?
|
|
15167
|
+
GROUP BY roots.id
|
|
15168
|
+
HAVING MAX(json_extract(roots.root_key, '$[0]') <> 'object')
|
|
15169
|
+
OR SUM(rows.entity_kind = 'object'
|
|
15170
|
+
AND rows.object_type_id = json_extract(roots.root_key, '$[1]')
|
|
15171
|
+
AND rows.primary_id = json_extract(roots.root_key, '$[2]')) <> 1
|
|
15172
|
+
OR SUM(
|
|
15173
|
+
(rows.entity_kind = 'object' AND (
|
|
15174
|
+
rows.object_type_id <> json_extract(roots.root_key, '$[1]')
|
|
15175
|
+
OR rows.primary_id <> json_extract(roots.root_key, '$[2]')))
|
|
15176
|
+
OR (rows.entity_kind = 'link' AND (
|
|
15177
|
+
rows.source_type_id <> json_extract(roots.root_key, '$[1]')
|
|
15178
|
+
OR rows.source_primary_id <> json_extract(roots.root_key, '$[2]')))
|
|
15179
|
+
) <> 0
|
|
15180
|
+
OR SUM(rows.entity_kind = 'link') <> COUNT(DISTINCT CASE
|
|
15181
|
+
WHEN rows.entity_kind = 'link'
|
|
15182
|
+
THEN json_array(rows.link_id, rows.target_type_id, rows.target_primary_id)
|
|
15183
|
+
END)
|
|
14765
15184
|
LIMIT 1
|
|
14766
|
-
`).get(manifest.
|
|
15185
|
+
`).get(manifest.version_id);
|
|
14767
15186
|
if (invalid) {
|
|
14768
15187
|
throw new MaterializationValidationError4(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.");
|
|
14769
15188
|
}
|
|
14770
15189
|
}
|
|
14771
|
-
findStageRows(
|
|
15190
|
+
findStageRows(manifest, rows) {
|
|
14772
15191
|
if (rows.length === 0)
|
|
14773
15192
|
return [];
|
|
14774
|
-
const
|
|
15193
|
+
const rootKeys = [...new Set(rows.map((row) => row.rootKey))];
|
|
14775
15194
|
const ordinals = [...new Set(rows.map((row) => row.row.stagingOrdinal))];
|
|
14776
|
-
const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.entityKey)))];
|
|
14777
15195
|
const existing = this.db.query(`
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
15196
|
+
WITH selected AS (
|
|
15197
|
+
SELECT roots.id FROM json_each(?2) AS requested
|
|
15198
|
+
CROSS JOIN ontology_source_roots AS roots
|
|
15199
|
+
ON roots.version_id = ?1 AND roots.root_key = CAST(requested.value AS TEXT)
|
|
15200
|
+
UNION
|
|
15201
|
+
SELECT roots.id FROM json_each(?3) AS requested
|
|
15202
|
+
CROSS JOIN ontology_source_roots AS roots
|
|
15203
|
+
ON roots.version_id = ?1 AND roots.staging_ordinal = CAST(requested.value AS INTEGER)
|
|
15204
|
+
)
|
|
15205
|
+
SELECT ${SOURCE_ASSERTION_COLUMNS} FROM selected
|
|
15206
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.id = selected.id
|
|
15207
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
15208
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15209
|
+
`).all(manifest.version_id, JSON.stringify(rootKeys), JSON.stringify(ordinals));
|
|
14786
15210
|
return existing.map((row) => sourceStageRow(sourceAssertion(row)));
|
|
14787
15211
|
}
|
|
14788
|
-
insertStageRows(
|
|
15212
|
+
insertStageRows(manifest, rows) {
|
|
14789
15213
|
if (rows.length === 0)
|
|
14790
15214
|
return;
|
|
14791
|
-
const payload = rows.map(({
|
|
15215
|
+
const payload = rows.map(({ rootKey, row }) => {
|
|
14792
15216
|
const entity = entityColumns(row.assertion);
|
|
14793
|
-
const root = entityColumns(row.root);
|
|
14794
15217
|
return {
|
|
14795
|
-
entityKind: row.assertion.kind,
|
|
14796
|
-
entityKey,
|
|
14797
|
-
entitySortKey: utf8SortKey2(entityKey),
|
|
14798
|
-
rootKind: row.root.kind,
|
|
14799
15218
|
rootKey,
|
|
14800
|
-
|
|
14801
|
-
stagingOrdinal: row.stagingOrdinal,
|
|
14802
|
-
root: row.root,
|
|
14803
|
-
assertion: row.assertion,
|
|
15219
|
+
entityKind: row.assertion.kind,
|
|
14804
15220
|
objectTypeId: entity.objectTypeId,
|
|
14805
15221
|
primaryId: entity.primaryId,
|
|
14806
15222
|
sourceTypeId: entity.sourceTypeId,
|
|
@@ -14808,45 +15224,27 @@ class SqliteOntologySourceStorage {
|
|
|
14808
15224
|
linkId: entity.linkId,
|
|
14809
15225
|
targetTypeId: entity.targetTypeId,
|
|
14810
15226
|
targetPrimaryId: entity.targetPrimaryId,
|
|
14811
|
-
|
|
14812
|
-
rootPrimaryId: root.primaryId,
|
|
14813
|
-
rootSourceTypeId: root.sourceTypeId,
|
|
14814
|
-
rootSourcePrimaryId: root.sourcePrimaryId,
|
|
14815
|
-
rootLinkId: root.linkId,
|
|
14816
|
-
rootTargetTypeId: root.targetTypeId,
|
|
14817
|
-
rootTargetPrimaryId: root.targetPrimaryId
|
|
15227
|
+
payload: sourceAssertionPayload(row.assertion)
|
|
14818
15228
|
};
|
|
14819
15229
|
});
|
|
14820
|
-
this.db.query(`
|
|
15230
|
+
const inserted = this.db.query(`
|
|
14821
15231
|
WITH staged(value) AS (SELECT value FROM json_each(?))
|
|
14822
15232
|
INSERT INTO ontology_source_rows (
|
|
14823
|
-
|
|
14824
|
-
|
|
14825
|
-
root_kind, root_key, root_sort_key, staging_ordinal,
|
|
14826
|
-
root, assertion,
|
|
14827
|
-
object_type_id, primary_id,
|
|
14828
|
-
source_type_id, source_primary_id, link_id, target_type_id, target_primary_id,
|
|
14829
|
-
root_object_type_id, root_primary_id,
|
|
14830
|
-
root_source_type_id, root_source_primary_id, root_link_id,
|
|
14831
|
-
root_target_type_id, root_target_primary_id
|
|
15233
|
+
root_id, entity_kind, object_type_id, primary_id,
|
|
15234
|
+
source_type_id, source_primary_id, link_id, target_type_id, target_primary_id, payload
|
|
14832
15235
|
)
|
|
14833
|
-
SELECT
|
|
14834
|
-
?, ?, ?,
|
|
14835
|
-
json_extract(value, '$.entityKind'), json_extract(value, '$.entityKey'),
|
|
14836
|
-
json_extract(value, '$.entitySortKey'),
|
|
14837
|
-
json_extract(value, '$.rootKind'), json_extract(value, '$.rootKey'),
|
|
14838
|
-
json_extract(value, '$.rootSortKey'), json_extract(value, '$.stagingOrdinal'),
|
|
14839
|
-
json(json_extract(value, '$.root')), json(json_extract(value, '$.assertion')),
|
|
15236
|
+
SELECT roots.id, json_extract(value, '$.entityKind'),
|
|
14840
15237
|
json_extract(value, '$.objectTypeId'), json_extract(value, '$.primaryId'),
|
|
14841
15238
|
json_extract(value, '$.sourceTypeId'), json_extract(value, '$.sourcePrimaryId'),
|
|
14842
15239
|
json_extract(value, '$.linkId'), json_extract(value, '$.targetTypeId'),
|
|
14843
|
-
json_extract(value, '$.targetPrimaryId'),
|
|
14844
|
-
json_extract(value, '$.rootObjectTypeId'), json_extract(value, '$.rootPrimaryId'),
|
|
14845
|
-
json_extract(value, '$.rootSourceTypeId'),
|
|
14846
|
-
json_extract(value, '$.rootSourcePrimaryId'), json_extract(value, '$.rootLinkId'),
|
|
14847
|
-
json_extract(value, '$.rootTargetTypeId'), json_extract(value, '$.rootTargetPrimaryId')
|
|
15240
|
+
json_extract(value, '$.targetPrimaryId'), json_extract(value, '$.payload')
|
|
14848
15241
|
FROM staged
|
|
14849
|
-
|
|
15242
|
+
CROSS JOIN ontology_source_roots AS roots
|
|
15243
|
+
ON roots.version_id = ? AND roots.root_key = json_extract(value, '$.rootKey')
|
|
15244
|
+
`).run(canonicalJson(payload), manifest.version_id).changes;
|
|
15245
|
+
if (inserted !== rows.length) {
|
|
15246
|
+
throw sourceConflict("Staged source rows do not all belong to a staged root.");
|
|
15247
|
+
}
|
|
14850
15248
|
}
|
|
14851
15249
|
assertExecution(input, identity) {
|
|
14852
15250
|
assertProjectionExecution(this.db, {
|
|
@@ -15113,6 +15511,8 @@ function queryLatestRunsByOwnerId(input) {
|
|
|
15113
15511
|
return [];
|
|
15114
15512
|
}
|
|
15115
15513
|
const placeholders = ownerIds.map(() => "?").join(", ");
|
|
15514
|
+
const statusFilter = input.excludedStatus === undefined ? "" : " AND status <> ?";
|
|
15515
|
+
const statusArgs = input.excludedStatus === undefined ? [] : [input.excludedStatus];
|
|
15116
15516
|
const orderColumn = input.tableName === "sync_runs" || input.tableName === "pipeline_runs" || input.tableName === "projection_runs" ? "COALESCE(started_at, queued_at)" : "started_at";
|
|
15117
15517
|
const rows = input.db.query(`
|
|
15118
15518
|
SELECT *
|
|
@@ -15124,10 +15524,10 @@ function queryLatestRunsByOwnerId(input) {
|
|
|
15124
15524
|
ORDER BY ${orderColumn} DESC, id DESC
|
|
15125
15525
|
) AS sixb_latest_rank
|
|
15126
15526
|
FROM ${input.tableName}
|
|
15127
|
-
WHERE project_id = ? AND ${input.ownerColumn} IN (${placeholders})
|
|
15527
|
+
WHERE project_id = ? AND ${input.ownerColumn} IN (${placeholders})${statusFilter}
|
|
15128
15528
|
)
|
|
15129
15529
|
WHERE sixb_latest_rank = 1
|
|
15130
|
-
`).all(input.projectId, ...ownerIds);
|
|
15530
|
+
`).all(input.projectId, ...ownerIds, ...statusArgs);
|
|
15131
15531
|
const latestByOwnerId = new Map(rows.map((row) => [input.ownerIdFor(row), row]));
|
|
15132
15532
|
return ownerIds.flatMap((ownerId) => {
|
|
15133
15533
|
const row = latestByOwnerId.get(ownerId);
|
|
@@ -15473,12 +15873,14 @@ import {
|
|
|
15473
15873
|
failProjectionRunEnqueue,
|
|
15474
15874
|
immutableDatasetVersionConflict,
|
|
15475
15875
|
mergeProjectionRunProgress,
|
|
15876
|
+
parseProjectionAttemptFailure,
|
|
15476
15877
|
planProjectionRunFinish,
|
|
15477
15878
|
planProjectionRunReclaim,
|
|
15478
15879
|
projectionRunNotFound,
|
|
15479
15880
|
publicProjectionRunRecord,
|
|
15480
15881
|
requireTelemetryProjectionRun,
|
|
15481
15882
|
restoreProjectionRun,
|
|
15883
|
+
SUPERSEDING_PROJECTION_RUN_STATUSES,
|
|
15482
15884
|
staleProjectionRunExecution
|
|
15483
15885
|
} from "@sixb/core/internal/projection-run-storage-provider";
|
|
15484
15886
|
import { PROJECTION_RUN_FAILURE_CODES, ProjectionRunError } from "@sixb/core/storage";
|
|
@@ -15560,7 +15962,7 @@ class SqliteProjectionRunStorage {
|
|
|
15560
15962
|
const result = this.db.query(`
|
|
15561
15963
|
UPDATE projection_runs
|
|
15562
15964
|
SET status = 'running', started_at = COALESCE(started_at, ?), finished_at = NULL,
|
|
15563
|
-
attempt = ?, execution_token =
|
|
15965
|
+
attempt = ?, execution_token = ?
|
|
15564
15966
|
WHERE project_id = ? AND id = ?
|
|
15565
15967
|
AND (
|
|
15566
15968
|
(status = 'queued' AND execution_token IS NULL)
|
|
@@ -15604,6 +16006,20 @@ class SqliteProjectionRunStorage {
|
|
|
15604
16006
|
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
|
|
15605
16007
|
});
|
|
15606
16008
|
}
|
|
16009
|
+
async recordAttemptFailure(input) {
|
|
16010
|
+
return runImmediateTransaction(this.db, () => {
|
|
16011
|
+
const existingRow = this.requireMaterializationExecution(input);
|
|
16012
|
+
const error = parseProjectionAttemptFailure(restoreProjectionRunRow(existingRow), input.error);
|
|
16013
|
+
const result = this.db.query(`
|
|
16014
|
+
UPDATE projection_runs
|
|
16015
|
+
SET error = ?
|
|
16016
|
+
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
16017
|
+
`).run(serializeSixbFailure4(error, PROJECTION_RUN_FAILURE_CODES), input.projectId, input.id, input.executionToken);
|
|
16018
|
+
if (result.changes !== 1)
|
|
16019
|
+
throw staleProjectionRunExecution(input.id);
|
|
16020
|
+
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
|
|
16021
|
+
});
|
|
16022
|
+
}
|
|
15607
16023
|
async finish(input) {
|
|
15608
16024
|
return runImmediateTransaction(this.db, () => {
|
|
15609
16025
|
const existingRow = this.requireMaterializationExecution(input);
|
|
@@ -15741,6 +16157,21 @@ class SqliteProjectionRunStorage {
|
|
|
15741
16157
|
const runs = rows.map(rowToProjectionRunRecord);
|
|
15742
16158
|
return { runs, hasMore: offset + runs.length < totalRow.count, total: totalRow.count };
|
|
15743
16159
|
}
|
|
16160
|
+
async findSupersedingRun(input) {
|
|
16161
|
+
const run = this.requireRow(input.projectId, input.id);
|
|
16162
|
+
if (run.materialization_protocol !== "replacement")
|
|
16163
|
+
return null;
|
|
16164
|
+
const statuses = SUPERSEDING_PROJECTION_RUN_STATUSES;
|
|
16165
|
+
const newer = this.db.query(`
|
|
16166
|
+
SELECT * FROM projection_runs
|
|
16167
|
+
WHERE project_id = ? AND projection_id = ? AND materialization_protocol = 'replacement'
|
|
16168
|
+
AND status IN (${statuses.map(() => "?").join(", ")})
|
|
16169
|
+
AND dataset_version_created_at > ?
|
|
16170
|
+
ORDER BY dataset_version_created_at DESC, id DESC
|
|
16171
|
+
LIMIT 1
|
|
16172
|
+
`).get(input.projectId, run.projection_id, ...statuses, run.dataset_version_created_at);
|
|
16173
|
+
return newer ? rowToProjectionRunRecord(newer) : null;
|
|
16174
|
+
}
|
|
15744
16175
|
async listLatestByProjectionIds(input) {
|
|
15745
16176
|
const rows = queryLatestRunsByOwnerId({
|
|
15746
16177
|
db: this.db,
|
|
@@ -15748,7 +16179,8 @@ class SqliteProjectionRunStorage {
|
|
|
15748
16179
|
ownerColumn: "projection_id",
|
|
15749
16180
|
ownerIds: input.projectionIds,
|
|
15750
16181
|
projectId: input.projectId,
|
|
15751
|
-
ownerIdFor: (row) => row.projection_id
|
|
16182
|
+
ownerIdFor: (row) => row.projection_id,
|
|
16183
|
+
excludedStatus: "superseded"
|
|
15752
16184
|
});
|
|
15753
16185
|
return { runs: rows.map(rowToProjectionRunRecord) };
|
|
15754
16186
|
}
|
|
@@ -17945,4 +18377,4 @@ export {
|
|
|
17945
18377
|
migrateSqliteStorage
|
|
17946
18378
|
};
|
|
17947
18379
|
|
|
17948
|
-
//# debugId=
|
|
18380
|
+
//# debugId=1D3AEE45CA656B6464756E2164756E21
|