@sixb/sqlite 0.1.14 → 0.1.16
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 +739 -261
- package/dist/index.js.map +14 -14
- 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/objects/query-compiler.d.ts +3 -0
- package/dist/objects/query-compiler.d.ts.map +1 -1
- package/dist/objects/read-scope.d.ts +2 -0
- package/dist/objects/read-scope.d.ts.map +1 -1
- package/dist/objects/reader.d.ts +1 -0
- package/dist/objects/reader.d.ts.map +1 -1
- package/dist/objects/rows.d.ts +1 -0
- package/dist/objects/rows.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/objects/README.md +19 -0
- package/src/objects/query-compiler.ts +44 -7
- package/src/objects/read-scope.ts +10 -8
- package/src/objects/reader.ts +43 -8
- package/src/objects/rows.ts +1 -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
|
}
|
|
@@ -10240,7 +10567,7 @@ var PAGE_TOKEN_PREFIX = "keyset:";
|
|
|
10240
10567
|
var DEFAULT_EXPANSION_FANOUT = 1000;
|
|
10241
10568
|
function compileObjectQuery(projectId, query, options = {}) {
|
|
10242
10569
|
if (hasVectorProfile(query) && !isVectorProfileQuery(query)) {
|
|
10243
|
-
throw new ObjectQueryExecutionError("unsupported_vector_composition", "Vector search supports one profile with filters before ranking and limit/project after.");
|
|
10570
|
+
throw new ObjectQueryExecutionError("unsupported_vector_composition", "Vector search supports one profile with filters, traversals or sets before ranking and limit/project after.");
|
|
10244
10571
|
}
|
|
10245
10572
|
const source = options.source ?? DEFAULT_OBJECT_QUERY_SOURCE;
|
|
10246
10573
|
const ctx = { probeLimit: options.includeTotal === false, source };
|
|
@@ -10313,9 +10640,29 @@ function compileVector(projectId, query, ctx) {
|
|
|
10313
10640
|
{ kind: "column", column: "_vector_score", direction: "desc" },
|
|
10314
10641
|
...identityOrderFields().map((field) => ({ ...field, binary: true }))
|
|
10315
10642
|
]);
|
|
10316
|
-
const
|
|
10317
|
-
|
|
10318
|
-
|
|
10643
|
+
const rankedOrder = compileOrder(order.fields, "ranked");
|
|
10644
|
+
const sql = `SELECT stored.project_id, stored.object_type_id, stored.primary_id,
|
|
10645
|
+
stored.properties, stored.properties AS _cursor_properties,
|
|
10646
|
+
stored.created_at, stored.updated_at, stored.version, stored.last_commit_id,
|
|
10647
|
+
ranked._vector_candidate_count, ranked._vector_score
|
|
10648
|
+
FROM (
|
|
10649
|
+
WITH _sixb_vector_candidates AS MATERIALIZED (
|
|
10650
|
+
SELECT input.project_id, input.object_type_id, input.primary_id,
|
|
10651
|
+
vectors.embedding AS _vector_embedding ${candidates} LIMIT ?
|
|
10652
|
+
), _sixb_vector_budget AS (
|
|
10653
|
+
SELECT COUNT(*) AS _vector_candidate_count FROM _sixb_vector_candidates
|
|
10654
|
+
)
|
|
10655
|
+
SELECT input.project_id, input.object_type_id, input.primary_id, budget._vector_candidate_count,
|
|
10656
|
+
CASE WHEN budget._vector_candidate_count <= ?
|
|
10657
|
+
THEN max(-1, min(1, 1 - vec_distance_cosine(input._vector_embedding, ?)))
|
|
10658
|
+
END AS _vector_score
|
|
10659
|
+
FROM _sixb_vector_candidates AS input CROSS JOIN _sixb_vector_budget AS budget
|
|
10660
|
+
ORDER BY ${order.sql} LIMIT ?
|
|
10661
|
+
) AS ranked CROSS JOIN ${ctx.source.objectsTable} AS stored
|
|
10662
|
+
ON stored.project_id = ranked.project_id AND stored.object_type_id = ranked.object_type_id
|
|
10663
|
+
AND stored.primary_id = ranked.primary_id
|
|
10664
|
+
ORDER BY ${rankedOrder.sql}`;
|
|
10665
|
+
const args = [...candidateArgs, limit + 1, limit, encodeSqliteVector(values), query.k];
|
|
10319
10666
|
return {
|
|
10320
10667
|
sql,
|
|
10321
10668
|
args,
|
|
@@ -10325,7 +10672,9 @@ function compileVector(projectId, query, ctx) {
|
|
|
10325
10672
|
vectorProbe: {
|
|
10326
10673
|
sql: `SELECT COUNT(*) AS total FROM (SELECT 1 ${candidates} LIMIT ?) AS candidates`,
|
|
10327
10674
|
args: [...candidateArgs, limit + 1],
|
|
10328
|
-
limit
|
|
10675
|
+
limit,
|
|
10676
|
+
rowLimit: query.k,
|
|
10677
|
+
totalLimit: query.k
|
|
10329
10678
|
},
|
|
10330
10679
|
hasMore: () => false,
|
|
10331
10680
|
trimRows: identityRows,
|
|
@@ -10484,7 +10833,11 @@ function compileLimit(projectId, inputQuery, rawLimit, ctx) {
|
|
|
10484
10833
|
FROM (${input.sql}) AS input
|
|
10485
10834
|
`,
|
|
10486
10835
|
totalArgs: input.args,
|
|
10487
|
-
vectorProbe: input.vectorProbe
|
|
10836
|
+
vectorProbe: input.vectorProbe ? {
|
|
10837
|
+
...input.vectorProbe,
|
|
10838
|
+
rowLimit: Math.min(input.vectorProbe.rowLimit, rowLimit),
|
|
10839
|
+
totalLimit: input.vectorProbe.rowLimit
|
|
10840
|
+
} : undefined,
|
|
10488
10841
|
order: input.order,
|
|
10489
10842
|
hasMore: (rowCount, total) => total === undefined ? ctx.probeLimit && rowCount > limit : limit < total,
|
|
10490
10843
|
trimRows: ctx.probeLimit ? (rows) => rows.slice(0, limit) : identityRows,
|
|
@@ -10761,7 +11114,7 @@ function compileProject(projectId, inputQuery, properties, ctx) {
|
|
|
10761
11114
|
input.created_at,
|
|
10762
11115
|
input.updated_at,
|
|
10763
11116
|
input.version,
|
|
10764
|
-
input.last_commit_id${input.vectorProbe ? ", input._vector_score" : ""}
|
|
11117
|
+
input.last_commit_id${input.vectorProbe ? ", input._vector_score, input._vector_candidate_count" : ""}
|
|
10765
11118
|
FROM (${input.sql}) AS input
|
|
10766
11119
|
ORDER BY ${inputOrder.sql}
|
|
10767
11120
|
`,
|
|
@@ -11305,19 +11658,20 @@ class SqliteObjectReader {
|
|
|
11305
11658
|
ensureSqliteVectorSearch(this.db);
|
|
11306
11659
|
const probe = compiled.vectorProbe;
|
|
11307
11660
|
const row = this.db.query(probe.sql).get(...probe.args);
|
|
11308
|
-
|
|
11309
|
-
throw new ObjectQueryExecutionError3("vector_work_limit_exceeded", `Exact vector search supports at most ${probe.limit} eligible vectors at this dimension. Narrow the query filters.`);
|
|
11310
|
-
}
|
|
11661
|
+
assertVectorCandidateCount(row?.total, probe.limit);
|
|
11311
11662
|
}
|
|
11312
11663
|
queryObjects(params) {
|
|
11313
|
-
this.assertVectorSearch(params);
|
|
11314
11664
|
const source = this.source;
|
|
11315
11665
|
const compiled = compileObjectQuery(params.projectId, params.query, {
|
|
11316
11666
|
includeTotal: params.includeTotal,
|
|
11317
11667
|
source
|
|
11318
11668
|
});
|
|
11319
|
-
|
|
11669
|
+
if (compiled.vectorProbe)
|
|
11670
|
+
ensureSqliteVectorSearch(this.db);
|
|
11671
|
+
const ordinaryTotal = params.includeTotal === false || compiled.vectorProbe ? undefined : readTotal(this.db, compiled);
|
|
11320
11672
|
const rawRows = this.db.query(compiled.sql).all(...compiled.args);
|
|
11673
|
+
const vectorTotal = this.vectorResultTotal(compiled, rawRows);
|
|
11674
|
+
const total = params.includeTotal === false ? undefined : vectorTotal ?? ordinaryTotal;
|
|
11321
11675
|
const rows = compiled.trimRows(rawRows);
|
|
11322
11676
|
const hasMore = total === undefined && compiled.hasMoreProbe ? compiled.hasMoreProbe.hasMore(this.db.query(compiled.hasMoreProbe.sql).all(...compiled.hasMoreProbe.args).length) : compiled.hasMore(rawRows.length, total);
|
|
11323
11677
|
return {
|
|
@@ -11327,6 +11681,17 @@ class SqliteObjectReader {
|
|
|
11327
11681
|
...total === undefined ? {} : { total }
|
|
11328
11682
|
};
|
|
11329
11683
|
}
|
|
11684
|
+
vectorResultTotal(compiled, rows) {
|
|
11685
|
+
const probe = compiled.vectorProbe;
|
|
11686
|
+
if (!probe)
|
|
11687
|
+
return;
|
|
11688
|
+
let count = rows[0]?._vector_candidate_count;
|
|
11689
|
+
if (rows.length === 0) {
|
|
11690
|
+
count = probe.rowLimit === 0 ? this.db.query(probe.sql).get(...probe.args)?.total : 0;
|
|
11691
|
+
}
|
|
11692
|
+
assertVectorCandidateCount(count, probe.limit);
|
|
11693
|
+
return Math.min(count, probe.totalLimit);
|
|
11694
|
+
}
|
|
11330
11695
|
countObjects(params) {
|
|
11331
11696
|
this.assertVectorSearch(params);
|
|
11332
11697
|
const source = this.source;
|
|
@@ -11640,6 +12005,14 @@ function sqliteFacetValue(valueType, value) {
|
|
|
11640
12005
|
}
|
|
11641
12006
|
return value;
|
|
11642
12007
|
}
|
|
12008
|
+
function assertVectorCandidateCount(count, limit) {
|
|
12009
|
+
if (typeof count !== "number" || !Number.isSafeInteger(count) || count < 0) {
|
|
12010
|
+
throw new ObjectQueryExecutionError3("invalid_vector_candidate_count", "Storage did not return a valid vector candidate count.");
|
|
12011
|
+
}
|
|
12012
|
+
if (count > limit) {
|
|
12013
|
+
throw new ObjectQueryExecutionError3("vector_work_limit_exceeded", `Exact vector search supports at most ${limit} eligible vectors at this dimension. Narrow the query filters.`);
|
|
12014
|
+
}
|
|
12015
|
+
}
|
|
11643
12016
|
function existsProbeQuery(query) {
|
|
11644
12017
|
return { kind: "limit", limit: 1, input: stripOuterRowShape(query) };
|
|
11645
12018
|
}
|
|
@@ -11731,7 +12104,7 @@ function compileSelectedReadScopeCte(scopeJson, projectId) {
|
|
|
11731
12104
|
_sixb_scope_root_facts(project_id, root_id, node_id, object_type_id, primary_id) AS (
|
|
11732
12105
|
SELECT anchor.project_id, root.root_id, root.node_id, root.object_type_id, root.primary_id
|
|
11733
12106
|
FROM _sixb_scope_roots AS root
|
|
11734
|
-
JOIN objects AS anchor
|
|
12107
|
+
CROSS JOIN objects AS anchor
|
|
11735
12108
|
ON anchor.project_id = ?
|
|
11736
12109
|
AND anchor.object_type_id = root.object_type_id
|
|
11737
12110
|
AND anchor.primary_id = root.primary_id
|
|
@@ -11813,7 +12186,7 @@ function compileSelectedReadScopeCte(scopeJson, projectId) {
|
|
|
11813
12186
|
GROUP BY reachable.project_id, reachable.object_type_id, reachable.primary_id
|
|
11814
12187
|
),
|
|
11815
12188
|
_sixb_scope_objects AS (
|
|
11816
|
-
SELECT
|
|
12189
|
+
SELECT grants.project_id, grants.object_type_id, grants.primary_id,
|
|
11817
12190
|
COALESCE((
|
|
11818
12191
|
SELECT json_group_object(property.key, ${sqliteJsonEachValue("property")})
|
|
11819
12192
|
FROM json_each(stored.properties) AS property
|
|
@@ -11903,12 +12276,12 @@ function compileSelectedReadScopeCte(scopeJson, projectId) {
|
|
|
11903
12276
|
),
|
|
11904
12277
|
_sixb_scope_links AS (
|
|
11905
12278
|
SELECT
|
|
11906
|
-
|
|
11907
|
-
|
|
11908
|
-
|
|
11909
|
-
|
|
11910
|
-
|
|
11911
|
-
|
|
12279
|
+
visible.project_id,
|
|
12280
|
+
visible.source_type_id,
|
|
12281
|
+
visible.source_id,
|
|
12282
|
+
visible.link_id,
|
|
12283
|
+
visible.target_type_id,
|
|
12284
|
+
visible.target_id,
|
|
11912
12285
|
CASE
|
|
11913
12286
|
WHEN stored.properties IS NULL THEN NULL
|
|
11914
12287
|
ELSE NULLIF(
|
|
@@ -12204,6 +12577,10 @@ function assertReconciliationPageLimit(limit) {
|
|
|
12204
12577
|
// src/ontology-storage/shared.ts
|
|
12205
12578
|
import { parseSixbFailure as parseSixbFailure2 } from "@sixb/core/internal/errors";
|
|
12206
12579
|
import { MaterializationConflictError } from "@sixb/core/internal/materialization";
|
|
12580
|
+
import {
|
|
12581
|
+
sourceAssertionFromColumns,
|
|
12582
|
+
sourceEntityFromKey
|
|
12583
|
+
} from "@sixb/core/internal/ontology-storage-provider";
|
|
12207
12584
|
import { ONTOLOGY_OUTBOX_FAILURE_CODES } from "@sixb/core/storage";
|
|
12208
12585
|
import {
|
|
12209
12586
|
assertNonblank,
|
|
@@ -12256,8 +12633,8 @@ function sourceRecord(row) {
|
|
|
12256
12633
|
}
|
|
12257
12634
|
function sourceAssertion(row) {
|
|
12258
12635
|
return {
|
|
12259
|
-
root:
|
|
12260
|
-
assertion: parseJson2(row.
|
|
12636
|
+
root: sourceEntityFromKey(row.root_key),
|
|
12637
|
+
assertion: sourceAssertionFromColumns(row, row.payload === null ? null : parseJson2(row.payload)),
|
|
12261
12638
|
stagingOrdinal: row.staging_ordinal
|
|
12262
12639
|
};
|
|
12263
12640
|
}
|
|
@@ -12475,29 +12852,68 @@ import {
|
|
|
12475
12852
|
MaterializationValidationError,
|
|
12476
12853
|
projectionEntityKey
|
|
12477
12854
|
} from "@sixb/core/internal/materialization";
|
|
12478
|
-
import {
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
|
|
12482
|
-
|
|
12483
|
-
|
|
12855
|
+
import {
|
|
12856
|
+
sourceEntityColumns,
|
|
12857
|
+
sourceStageRoots
|
|
12858
|
+
} from "@sixb/core/internal/ontology-storage-provider";
|
|
12859
|
+
var PUBLISHED = "('active', 'superseded')";
|
|
12860
|
+
var SOURCE_ASSERTION_COLUMNS = `versions.source_id, versions.materialization_id,
|
|
12861
|
+
roots.root_key, roots.staging_ordinal, rows.entity_kind, rows.object_type_id, rows.primary_id,
|
|
12862
|
+
rows.source_type_id, rows.source_primary_id, rows.link_id, rows.target_type_id,
|
|
12863
|
+
rows.target_primary_id, rows.payload`;
|
|
12864
|
+
var LIVE_SOURCE_ROWS_JOIN = `
|
|
12865
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.id = rows.root_id
|
|
12866
|
+
AND roots.project_id = ? AND roots.retired_at IS NULL AND roots.deleted = 0
|
|
12867
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12868
|
+
AND versions.status IN ${PUBLISHED}`;
|
|
12869
|
+
var ENTITY_MATCH = `rows.entity_kind = requested.entity_kind
|
|
12870
|
+
AND rows.object_type_id IS requested.object_type_id
|
|
12871
|
+
AND rows.primary_id IS requested.primary_id
|
|
12872
|
+
AND rows.source_type_id IS requested.source_type_id
|
|
12873
|
+
AND rows.source_primary_id IS requested.source_primary_id
|
|
12874
|
+
AND rows.link_id IS requested.link_id
|
|
12875
|
+
AND rows.target_type_id IS requested.target_type_id
|
|
12876
|
+
AND rows.target_primary_id IS requested.target_primary_id`;
|
|
12877
|
+
var REQUESTED_ENTITY = `json_extract(value, '$.entityKind') AS entity_kind,
|
|
12878
|
+
json_extract(value, '$.objectTypeId') AS object_type_id,
|
|
12879
|
+
json_extract(value, '$.primaryId') AS primary_id,
|
|
12880
|
+
json_extract(value, '$.sourceTypeId') AS source_type_id,
|
|
12881
|
+
json_extract(value, '$.sourcePrimaryId') AS source_primary_id,
|
|
12882
|
+
json_extract(value, '$.linkId') AS link_id,
|
|
12883
|
+
json_extract(value, '$.targetTypeId') AS target_type_id,
|
|
12884
|
+
json_extract(value, '$.targetPrimaryId') AS target_primary_id`;
|
|
12885
|
+
function requestedEntity(ref) {
|
|
12886
|
+
const key = projectionEntityKey(ref);
|
|
12484
12887
|
return {
|
|
12485
|
-
|
|
12888
|
+
entityKind: ref.kind,
|
|
12889
|
+
...sourceEntityColumns(ref),
|
|
12890
|
+
rootKeys: ref.kind === "object" ? [key] : [key, projectionEntityKey({ kind: "object", ref: ref.ref.source })]
|
|
12891
|
+
};
|
|
12892
|
+
}
|
|
12893
|
+
function sourceVersionId(db, input) {
|
|
12894
|
+
const version = db.query(`SELECT version_id FROM ontology_sources
|
|
12895
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?`).get(input.projectId, input.sourceId, input.materializationId);
|
|
12896
|
+
return version?.version_id ?? null;
|
|
12897
|
+
}
|
|
12898
|
+
function replacementSourceRows(db, input) {
|
|
12899
|
+
const version = sourceVersionId(db, input);
|
|
12900
|
+
const live = `live.retired_at IS NULL AND live.deleted = 0
|
|
12901
|
+
AND versions.source_id = ? AND versions.status IN ${PUBLISHED}`;
|
|
12902
|
+
const replaced = input.incremental ? `SELECT live.id FROM ontology_source_roots AS changed
|
|
12903
|
+
CROSS JOIN ontology_source_roots AS live
|
|
12904
|
+
ON live.project_id = ? AND live.root_key = changed.root_key
|
|
12905
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = live.version_id
|
|
12906
|
+
WHERE changed.version_id = ? AND ${live}` : `SELECT live.id FROM ontology_sources AS versions
|
|
12907
|
+
CROSS JOIN ontology_source_roots AS live ON live.version_id = versions.version_id
|
|
12908
|
+
WHERE versions.project_id = ? AND ${live}`;
|
|
12909
|
+
return {
|
|
12910
|
+
sql: `SELECT rows.* FROM ontology_source_roots AS roots
|
|
12911
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
12912
|
+
WHERE roots.version_id = ?
|
|
12486
12913
|
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
|
-
]
|
|
12914
|
+
SELECT rows.* FROM (${replaced}) AS roots
|
|
12915
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id`,
|
|
12916
|
+
values: [version, input.projectId, ...input.incremental ? [version] : [], input.sourceId]
|
|
12501
12917
|
};
|
|
12502
12918
|
}
|
|
12503
12919
|
function stageSourceRoots(db, manifest, input) {
|
|
@@ -12506,15 +12922,15 @@ function stageSourceRoots(db, manifest, input) {
|
|
|
12506
12922
|
throw new MaterializationValidationError("Root deletions require a source delta base.");
|
|
12507
12923
|
}
|
|
12508
12924
|
const existing = db.query(`SELECT staging_ordinal, deleted FROM ontology_source_roots
|
|
12509
|
-
WHERE
|
|
12925
|
+
WHERE version_id = ? AND root_key = ?`);
|
|
12510
12926
|
const insert = db.query(`INSERT INTO ontology_source_roots (
|
|
12511
|
-
|
|
12512
|
-
) VALUES (?, ?, ?, ?,
|
|
12927
|
+
version_id, project_id, root_key, staging_ordinal, deleted
|
|
12928
|
+
) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING id`);
|
|
12513
12929
|
for (const root of roots) {
|
|
12514
|
-
const inserted = insert.get(
|
|
12930
|
+
const inserted = insert.get(manifest.version_id, input.projectId, root.rootKey, root.stagingOrdinal, Number(root.deleted));
|
|
12515
12931
|
if (inserted)
|
|
12516
12932
|
continue;
|
|
12517
|
-
const previous = existing.get(
|
|
12933
|
+
const previous = existing.get(manifest.version_id, root.rootKey);
|
|
12518
12934
|
if (!previous || previous.staging_ordinal !== root.stagingOrdinal || Boolean(previous.deleted) !== root.deleted) {
|
|
12519
12935
|
throw new MaterializationValidationError("Source materialization repeats root or stream ordinal with different content.");
|
|
12520
12936
|
}
|
|
@@ -12525,72 +12941,61 @@ function activateSourceRoots(db, projectId, candidate, activation) {
|
|
|
12525
12941
|
throw new MaterializationConflictError2("projection-fence", "Source delta base changed before activation.");
|
|
12526
12942
|
if (candidate.base_materialization_id !== null && candidate.root_count === 0)
|
|
12527
12943
|
return;
|
|
12528
|
-
const
|
|
12529
|
-
db.query(`UPDATE ontology_source_roots
|
|
12530
|
-
WHERE
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
WHERE
|
|
12944
|
+
const delta = candidate.base_materialization_id !== null;
|
|
12945
|
+
db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
12946
|
+
WHERE retired_at IS NULL AND deleted = 0
|
|
12947
|
+
AND version_id IN (SELECT version_id FROM ontology_sources
|
|
12948
|
+
WHERE project_id = ? AND source_id = ? AND status IN ${PUBLISHED})
|
|
12949
|
+
${delta ? `AND project_id = ? AND root_key IN (
|
|
12950
|
+
SELECT root_key FROM ontology_source_roots WHERE version_id = ?)` : ""}`).run(activation.updatedAt, projectId, activation.source.projectionId, ...delta ? [projectId, candidate.version_id] : []);
|
|
12951
|
+
db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
12952
|
+
WHERE version_id = ? AND deleted = 1`).run(activation.updatedAt, candidate.version_id);
|
|
12537
12953
|
}
|
|
12538
12954
|
function assertSourceRootCoverage(db, manifest) {
|
|
12539
12955
|
const invalid = db.query(`SELECT 1 FROM ontology_source_roots AS roots
|
|
12540
|
-
WHERE
|
|
12956
|
+
WHERE roots.version_id = ?
|
|
12541
12957
|
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);
|
|
12958
|
+
SELECT 1 FROM ontology_source_rows AS rows WHERE rows.root_id = roots.id
|
|
12959
|
+
) LIMIT 1`).get(manifest.version_id);
|
|
12546
12960
|
if (invalid)
|
|
12547
12961
|
throw new MaterializationValidationError("Source roots must have complete assertions or an explicit deletion, never both.");
|
|
12548
12962
|
}
|
|
12549
12963
|
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 }));
|
|
12964
|
+
const requested = refs.flatMap((ref) => {
|
|
12965
|
+
const { rootKeys, ...entity } = requestedEntity(ref);
|
|
12966
|
+
return rootKeys.map((rootKey) => ({ ...entity, rootKey }));
|
|
12557
12967
|
});
|
|
12558
12968
|
return db.query(`WITH requested AS (
|
|
12559
|
-
SELECT DISTINCT json_extract(value, '$.
|
|
12560
|
-
|
|
12561
|
-
) SELECT
|
|
12969
|
+
SELECT DISTINCT json_extract(value, '$.rootKey') AS root_key, ${REQUESTED_ENTITY}
|
|
12970
|
+
FROM json_each(?)
|
|
12971
|
+
) SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested
|
|
12562
12972
|
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
12563
|
-
AND roots.
|
|
12564
|
-
CROSS JOIN
|
|
12565
|
-
AND
|
|
12566
|
-
|
|
12973
|
+
AND roots.root_key = requested.root_key AND roots.retired_at IS NULL AND roots.deleted = 0
|
|
12974
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12975
|
+
AND versions.status IN ${PUBLISHED}
|
|
12976
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${ENTITY_MATCH}`).all(canonicalJson(requested), projectId);
|
|
12567
12977
|
}
|
|
12568
12978
|
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
|
-
}));
|
|
12979
|
+
const version = sourceVersionId(db, input);
|
|
12577
12980
|
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
|
|
12981
|
+
SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested_entities AS requested
|
|
12982
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
12983
|
+
AND roots.root_key IN (SELECT value FROM json_each(requested.root_keys))
|
|
12984
|
+
AND roots.retired_at IS NULL AND roots.deleted = 0
|
|
12985
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12986
|
+
AND versions.source_id = ? AND versions.status IN ${PUBLISHED}
|
|
12987
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${ENTITY_MATCH}
|
|
12584
12988
|
${!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)`}`;
|
|
12989
|
+
WHERE changed.version_id = ? AND changed.root_key = roots.root_key)`}`;
|
|
12587
12990
|
return db.query(`WITH requested_entities AS (
|
|
12588
|
-
SELECT DISTINCT json_extract(value, '$.
|
|
12589
|
-
|
|
12590
|
-
) SELECT
|
|
12591
|
-
CROSS JOIN
|
|
12592
|
-
AND
|
|
12593
|
-
|
|
12991
|
+
SELECT DISTINCT ${REQUESTED_ENTITY}, json_extract(value, '$.rootKeys') AS root_keys
|
|
12992
|
+
FROM json_each(?)
|
|
12993
|
+
) SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested_entities AS requested
|
|
12994
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.version_id = ?
|
|
12995
|
+
AND roots.root_key IN (SELECT value FROM json_each(requested.root_keys))
|
|
12996
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
12997
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id AND ${ENTITY_MATCH}
|
|
12998
|
+
${previous}`).all(canonicalJson(input.refs.map(requestedEntity)), version, ...input.includePrevious ? [input.projectId, input.sourceId, ...input.incremental ? [version] : []] : []);
|
|
12594
12999
|
}
|
|
12595
13000
|
|
|
12596
13001
|
// src/ontology-storage/materialization-state.ts
|
|
@@ -12803,19 +13208,13 @@ class SqliteMaterializationStateReader {
|
|
|
12803
13208
|
json_extract(value, '$.linkId') AS link_id
|
|
12804
13209
|
FROM json_each(?)
|
|
12805
13210
|
)
|
|
12806
|
-
SELECT
|
|
13211
|
+
SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested
|
|
12807
13212
|
CROSS JOIN ontology_source_rows AS rows
|
|
12808
|
-
ON rows.
|
|
12809
|
-
AND rows.entity_kind = 'link'
|
|
13213
|
+
ON rows.entity_kind = 'link'
|
|
12810
13214
|
AND rows.source_type_id = requested.source_type_id
|
|
12811
13215
|
AND rows.source_primary_id = requested.source_primary_id
|
|
12812
13216
|
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);
|
|
13217
|
+
${LIVE_SOURCE_ROWS_JOIN}`).all(requestedJson, this.projectId);
|
|
12819
13218
|
const slotOverrideRows = this.db.query(`WITH requested AS (
|
|
12820
13219
|
SELECT DISTINCT
|
|
12821
13220
|
json_extract(value, '$.sourceTypeId') AS source_type_id,
|
|
@@ -12904,12 +13303,8 @@ class SqliteMaterializationStateReader {
|
|
|
12904
13303
|
SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
|
|
12905
13304
|
rows.target_type_id, rows.target_primary_id
|
|
12906
13305
|
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
|
|
13306
|
+
${LIVE_SOURCE_ROWS_JOIN}
|
|
13307
|
+
WHERE rows.entity_kind = 'link'
|
|
12913
13308
|
), selected AS (
|
|
12914
13309
|
SELECT DISTINCT links.*,
|
|
12915
13310
|
${linkSortExpression("links")} AS sort_key
|
|
@@ -13020,7 +13415,7 @@ class SqliteMaterializationStateReader {
|
|
|
13020
13415
|
this.prepareReplacementLinks(input);
|
|
13021
13416
|
}
|
|
13022
13417
|
prepareReplacementObjects(input) {
|
|
13023
|
-
const rows = replacementSourceRows({
|
|
13418
|
+
const rows = replacementSourceRows(this.db, {
|
|
13024
13419
|
projectId: this.projectId,
|
|
13025
13420
|
sourceId: input.sourceId,
|
|
13026
13421
|
materializationId: input.candidateMaterializationId,
|
|
@@ -13031,14 +13426,14 @@ class SqliteMaterializationStateReader {
|
|
|
13031
13426
|
session_id, entity_kind, identity_key, sort_key, diff_required
|
|
13032
13427
|
)
|
|
13033
13428
|
SELECT ?, 'object', json_array(object_type_id, primary_id),
|
|
13034
|
-
|
|
13429
|
+
${objectSortExpression()}, 1
|
|
13035
13430
|
FROM (${rows.sql}) AS rows
|
|
13036
13431
|
WHERE entity_kind = 'object'
|
|
13037
13432
|
GROUP BY object_type_id, primary_id
|
|
13038
13433
|
`).run(input.sessionId, ...rows.values);
|
|
13039
13434
|
}
|
|
13040
13435
|
prepareReplacementLinks(input) {
|
|
13041
|
-
const rows = replacementSourceRows({
|
|
13436
|
+
const rows = replacementSourceRows(this.db, {
|
|
13042
13437
|
projectId: this.projectId,
|
|
13043
13438
|
sourceId: input.sourceId,
|
|
13044
13439
|
materializationId: input.candidateMaterializationId,
|
|
@@ -13095,31 +13490,21 @@ class SqliteMaterializationStateReader {
|
|
|
13095
13490
|
UNION
|
|
13096
13491
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
13097
13492
|
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
|
|
13493
|
+
FROM incident_objects
|
|
13494
|
+
CROSS JOIN ontology_source_rows AS rows
|
|
13495
|
+
ON rows.entity_kind = 'link'
|
|
13496
|
+
AND rows.source_type_id = incident_objects.object_type_id
|
|
13497
|
+
AND rows.source_primary_id = incident_objects.primary_id
|
|
13498
|
+
${LIVE_SOURCE_ROWS_JOIN}
|
|
13109
13499
|
UNION
|
|
13110
13500
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
13111
13501
|
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
|
|
13502
|
+
FROM incident_objects
|
|
13503
|
+
CROSS JOIN ontology_source_rows AS rows
|
|
13504
|
+
ON rows.entity_kind = 'link'
|
|
13505
|
+
AND rows.target_type_id = incident_objects.object_type_id
|
|
13506
|
+
AND rows.target_primary_id = incident_objects.primary_id
|
|
13507
|
+
${LIVE_SOURCE_ROWS_JOIN}
|
|
13123
13508
|
), diff_links AS (
|
|
13124
13509
|
SELECT * FROM replacement_links
|
|
13125
13510
|
UNION SELECT * FROM incident_links
|
|
@@ -13269,9 +13654,7 @@ function storedSource(row) {
|
|
|
13269
13654
|
return {
|
|
13270
13655
|
source: { projectionId: row.source_id },
|
|
13271
13656
|
materializationId: row.materialization_id,
|
|
13272
|
-
|
|
13273
|
-
assertion: parseJson2(row.assertion),
|
|
13274
|
-
stagingOrdinal: row.staging_ordinal
|
|
13657
|
+
...sourceAssertion(row)
|
|
13275
13658
|
};
|
|
13276
13659
|
}
|
|
13277
13660
|
function effectiveObjectSnapshot(row) {
|
|
@@ -13319,6 +13702,10 @@ function storedPoint(row) {
|
|
|
13319
13702
|
lastCommitId: row.last_commit_id
|
|
13320
13703
|
};
|
|
13321
13704
|
}
|
|
13705
|
+
function objectSortExpression(alias) {
|
|
13706
|
+
const prefix = alias ? `${alias}.` : "";
|
|
13707
|
+
return `LOWER(HEX(CAST(json_array(${prefix}object_type_id, ${prefix}primary_id) AS BLOB)))`;
|
|
13708
|
+
}
|
|
13322
13709
|
function linkSortExpression(alias) {
|
|
13323
13710
|
const prefix = alias ? `${alias}.` : "";
|
|
13324
13711
|
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 +14886,80 @@ import {
|
|
|
14499
14886
|
sourceEntityColumns as entityColumns,
|
|
14500
14887
|
isExactStagingManifest,
|
|
14501
14888
|
reconcileSourceStageRows,
|
|
14889
|
+
sourceAssertionPayload,
|
|
14502
14890
|
sourceConflict,
|
|
14503
14891
|
sourceMaterializationIdentity,
|
|
14504
14892
|
sourceStageRow,
|
|
14505
|
-
sourceStageRows
|
|
14506
|
-
utf8SortKey as utf8SortKey2
|
|
14893
|
+
sourceStageRows
|
|
14507
14894
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
14508
14895
|
|
|
14509
14896
|
// src/ontology-storage/source-cleanup.ts
|
|
14897
|
+
function purgeAbandonedSourceVersions(db, input) {
|
|
14898
|
+
let remaining = input.limit;
|
|
14899
|
+
let rowsDeleted = 0;
|
|
14900
|
+
let materializationsDeleted = 0;
|
|
14901
|
+
const nextCandidate = db.query(`
|
|
14902
|
+
SELECT version_id FROM ontology_sources
|
|
14903
|
+
WHERE project_id = ? AND status = 'abandoned'
|
|
14904
|
+
ORDER BY terminal_at, source_id, materialization_id LIMIT 1
|
|
14905
|
+
`);
|
|
14906
|
+
const deleteRows = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
14907
|
+
SELECT rows.rowid FROM ontology_source_roots AS roots
|
|
14908
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
14909
|
+
WHERE roots.version_id = ? LIMIT ?)`);
|
|
14910
|
+
const deleteRoots = db.query(`DELETE FROM ontology_source_roots WHERE id IN (
|
|
14911
|
+
SELECT id FROM ontology_source_roots WHERE version_id = ? LIMIT ?)`);
|
|
14912
|
+
const deleteManifest = db.query("DELETE FROM ontology_sources WHERE version_id = ? AND status = 'abandoned'");
|
|
14913
|
+
while (remaining > 0) {
|
|
14914
|
+
const candidate = nextCandidate.get(input.projectId);
|
|
14915
|
+
if (!candidate)
|
|
14916
|
+
break;
|
|
14917
|
+
const rows = deleteRows.run(candidate.version_id, remaining).changes;
|
|
14918
|
+
rowsDeleted += rows;
|
|
14919
|
+
remaining -= rows;
|
|
14920
|
+
if (remaining === 0)
|
|
14921
|
+
break;
|
|
14922
|
+
const roots = deleteRoots.run(candidate.version_id, remaining).changes;
|
|
14923
|
+
rowsDeleted += roots;
|
|
14924
|
+
remaining -= roots;
|
|
14925
|
+
if (remaining === 0)
|
|
14926
|
+
break;
|
|
14927
|
+
const manifest = deleteManifest.run(candidate.version_id).changes;
|
|
14928
|
+
if (manifest === 0)
|
|
14929
|
+
break;
|
|
14930
|
+
materializationsDeleted += manifest;
|
|
14931
|
+
remaining -= manifest;
|
|
14932
|
+
}
|
|
14933
|
+
return { rowsDeleted, materializationsDeleted };
|
|
14934
|
+
}
|
|
14510
14935
|
function cleanupSourceVersions(db, input) {
|
|
14511
14936
|
let remaining = input.limit;
|
|
14937
|
+
const retired = `roots.project_id = ? AND roots.retired_at < ? AND versions.status = 'superseded'`;
|
|
14512
14938
|
let rowsDeleted = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
14513
14939
|
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
|
|
14940
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
14941
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
14942
|
+
WHERE ${retired}
|
|
14943
|
+
ORDER BY roots.retired_at, roots.id
|
|
14521
14944
|
LIMIT ?
|
|
14522
14945
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14523
14946
|
remaining -= rowsDeleted;
|
|
14524
14947
|
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 ?
|
|
14948
|
+
const removed = db.query(`DELETE FROM ontology_source_roots WHERE id IN (
|
|
14949
|
+
SELECT roots.id FROM ontology_source_roots AS roots
|
|
14950
|
+
JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
14951
|
+
WHERE ${retired}
|
|
14952
|
+
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows WHERE rows.root_id = roots.id)
|
|
14953
|
+
ORDER BY roots.retired_at, roots.id LIMIT ?
|
|
14534
14954
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14535
14955
|
rowsDeleted += removed;
|
|
14536
14956
|
remaining -= removed;
|
|
14537
14957
|
}
|
|
14538
|
-
const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE
|
|
14539
|
-
SELECT
|
|
14540
|
-
WHERE project_id = ? AND status
|
|
14958
|
+
const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE version_id IN (
|
|
14959
|
+
SELECT versions.version_id FROM ontology_sources AS versions
|
|
14960
|
+
WHERE project_id = ? AND status = 'superseded' AND terminal_at < ?
|
|
14541
14961
|
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)
|
|
14962
|
+
WHERE roots.version_id = versions.version_id)
|
|
14547
14963
|
ORDER BY terminal_at, source_id, materialization_id LIMIT ?
|
|
14548
14964
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14549
14965
|
return { rowsDeleted, materializationsDeleted };
|
|
@@ -14575,7 +14991,7 @@ class SqliteOntologySourceStorage {
|
|
|
14575
14991
|
LIMIT 1
|
|
14576
14992
|
`).get(input.projectId, input.execution.projectionRunId);
|
|
14577
14993
|
if (candidate) {
|
|
14578
|
-
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' already has a nonterminal source materialization;
|
|
14994
|
+
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' already has a nonterminal source materialization; adopt it before beginning another.`);
|
|
14579
14995
|
}
|
|
14580
14996
|
this.db.query(`
|
|
14581
14997
|
INSERT INTO ontology_sources (
|
|
@@ -14601,10 +15017,10 @@ class SqliteOntologySourceStorage {
|
|
|
14601
15017
|
throw sourceConflict(`Source materialization '${input.materializationId}' is '${manifest.status}' and cannot accept rows.`);
|
|
14602
15018
|
}
|
|
14603
15019
|
const rows = sourceStageRows(manifest.projection_kind, input.rows);
|
|
14604
|
-
const existing = this.findStageRows(
|
|
15020
|
+
const existing = this.findStageRows(manifest, rows);
|
|
14605
15021
|
const { pending, unchanged } = reconcileSourceStageRows(rows, existing);
|
|
14606
15022
|
stageSourceRoots(this.db, manifest, input);
|
|
14607
|
-
this.insertStageRows(
|
|
15023
|
+
this.insertStageRows(manifest, pending);
|
|
14608
15024
|
return { inserted: pending.length, unchanged };
|
|
14609
15025
|
});
|
|
14610
15026
|
await yieldSqliteEventLoop();
|
|
@@ -14654,13 +15070,40 @@ class SqliteOntologySourceStorage {
|
|
|
14654
15070
|
return row ? sourceRecord(row) : null;
|
|
14655
15071
|
});
|
|
14656
15072
|
}
|
|
15073
|
+
async adopt(input) {
|
|
15074
|
+
return this.runRootOperation(() => {
|
|
15075
|
+
assertProjectAndSource(input);
|
|
15076
|
+
assertExecutionIdentity(input.execution.projectionRunId, input.execution.executionToken);
|
|
15077
|
+
assertTimestamp(input.adoptedAt, "Source adoptedAt", true);
|
|
15078
|
+
this.assertExecution(input);
|
|
15079
|
+
const candidate = this.findRunCandidate(input);
|
|
15080
|
+
if (!candidate)
|
|
15081
|
+
return null;
|
|
15082
|
+
const changed = this.db.query(`
|
|
15083
|
+
UPDATE ontology_sources SET execution_token = ?, updated_at = MAX(updated_at, ?)
|
|
15084
|
+
WHERE project_id = ? AND source_id = ? AND materialization_id = ?
|
|
15085
|
+
AND status IN ('staging', 'ready')
|
|
15086
|
+
`).run(input.execution.executionToken, input.adoptedAt, candidate.project_id, candidate.source_id, candidate.materialization_id).changes;
|
|
15087
|
+
if (changed !== 1) {
|
|
15088
|
+
throw sourceConflict(`Source materialization '${candidate.materialization_id}' changed.`);
|
|
15089
|
+
}
|
|
15090
|
+
const adopted = this.requireManifest(candidate.project_id, candidate.source_id, candidate.materialization_id);
|
|
15091
|
+
return {
|
|
15092
|
+
record: sourceRecord(adopted),
|
|
15093
|
+
resumeStagingOrdinal: adopted.status === "ready" ? Number(adopted.root_count) : this.lastStagedOrdinal(adopted)
|
|
15094
|
+
};
|
|
15095
|
+
});
|
|
15096
|
+
}
|
|
14657
15097
|
async abandon(input) {
|
|
14658
15098
|
return this.runRootOperation(() => {
|
|
14659
15099
|
assertProjectAndSource(input);
|
|
14660
15100
|
assertExecutionIdentity(input.execution.projectionRunId, input.execution.executionToken);
|
|
14661
15101
|
assertTimestamp(input.abandonedAt, "Source abandonedAt", true);
|
|
14662
15102
|
this.assertExecution(input);
|
|
14663
|
-
|
|
15103
|
+
if (input.kind === "candidate")
|
|
15104
|
+
return this.abandonCandidate(input);
|
|
15105
|
+
const candidate = this.findRunCandidate(input);
|
|
15106
|
+
return candidate ? this.transitionToAbandoned(candidate, input.abandonedAt) : null;
|
|
14664
15107
|
});
|
|
14665
15108
|
}
|
|
14666
15109
|
async cleanupTerminal(input) {
|
|
@@ -14671,6 +15114,13 @@ class SqliteOntologySourceStorage {
|
|
|
14671
15114
|
return cleanupSourceVersions(this.db, input);
|
|
14672
15115
|
});
|
|
14673
15116
|
}
|
|
15117
|
+
async purgeAbandoned(input) {
|
|
15118
|
+
return this.runRootOperation(() => {
|
|
15119
|
+
assertNonblank(input.projectId, "Abandoned source purge project id");
|
|
15120
|
+
assertPositiveInteger(input.limit, "Abandoned source purge limit");
|
|
15121
|
+
return purgeAbandonedSourceVersions(this.db, input);
|
|
15122
|
+
});
|
|
15123
|
+
}
|
|
14674
15124
|
async summarizeTerminal(input) {
|
|
14675
15125
|
return this.runRootOperation(() => {
|
|
14676
15126
|
assertNonblank(input.projectId, "Terminal source summary project id");
|
|
@@ -14696,19 +15146,20 @@ class SqliteOntologySourceStorage {
|
|
|
14696
15146
|
assertSourceCandidateOwner(sourceRecord(manifest), input.execution);
|
|
14697
15147
|
return this.transitionToAbandoned(manifest, input.abandonedAt);
|
|
14698
15148
|
}
|
|
14699
|
-
|
|
15149
|
+
findRunCandidate(input) {
|
|
14700
15150
|
const candidates = this.db.query(`
|
|
14701
15151
|
SELECT * FROM ontology_sources
|
|
14702
15152
|
WHERE project_id = ? AND source_id = ? AND projection_run_id = ?
|
|
14703
15153
|
AND status IN ('staging', 'ready')
|
|
14704
15154
|
`).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
15155
|
if (candidates.length > 1) {
|
|
14709
15156
|
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' has multiple nonterminal source materializations.`);
|
|
14710
15157
|
}
|
|
14711
|
-
return candidates[0]
|
|
15158
|
+
return candidates[0] ?? null;
|
|
15159
|
+
}
|
|
15160
|
+
lastStagedOrdinal(manifest) {
|
|
15161
|
+
const last = this.db.query("SELECT MAX(staging_ordinal) AS ordinal FROM ontology_source_roots WHERE version_id = ?").get(manifest.version_id);
|
|
15162
|
+
return last?.ordinal ?? 0;
|
|
14712
15163
|
}
|
|
14713
15164
|
transitionToAbandoned(manifest, abandonedAt) {
|
|
14714
15165
|
if (abandonedAt < manifest.created_at || manifest.ready_at !== null && abandonedAt < manifest.ready_at) {
|
|
@@ -14723,84 +15174,95 @@ class SqliteOntologySourceStorage {
|
|
|
14723
15174
|
if (changed !== 1) {
|
|
14724
15175
|
throw sourceConflict(`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`);
|
|
14725
15176
|
}
|
|
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
15177
|
return sourceRecord(this.requireManifest(manifest.project_id, manifest.source_id, manifest.materialization_id));
|
|
14729
15178
|
}
|
|
14730
15179
|
assertReadyState(manifest, rootCount, assertionCount) {
|
|
14731
15180
|
const counts = this.db.query(`
|
|
14732
|
-
SELECT (SELECT COUNT(*) FROM
|
|
14733
|
-
|
|
14734
|
-
|
|
15181
|
+
SELECT (SELECT COUNT(*) FROM ontology_source_roots AS roots
|
|
15182
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15183
|
+
WHERE roots.version_id = ?1) AS assertions,
|
|
15184
|
+
COUNT(*) AS roots, COUNT(DISTINCT staging_ordinal) AS ordinals,
|
|
14735
15185
|
MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
|
|
14736
15186
|
FROM ontology_source_roots
|
|
14737
|
-
WHERE
|
|
14738
|
-
`).get(manifest.
|
|
15187
|
+
WHERE version_id = ?1
|
|
15188
|
+
`).get(manifest.version_id);
|
|
14739
15189
|
if (counts.assertions !== assertionCount || counts.roots !== rootCount || counts.ordinals !== rootCount || rootCount > 0 && (counts.min_ordinal !== 0 || counts.max_ordinal !== rootCount - 1)) {
|
|
14740
15190
|
throw new MaterializationValidationError4("Source ready counts do not match the staged roots and assertions.");
|
|
14741
15191
|
}
|
|
14742
15192
|
assertSourceRootCoverage(this.db, manifest);
|
|
14743
15193
|
const invalid = this.db.query(manifest.projection_kind === "link" ? `
|
|
14744
|
-
SELECT root_key
|
|
14745
|
-
FROM
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
15194
|
+
SELECT roots.root_key
|
|
15195
|
+
FROM ontology_source_roots AS roots
|
|
15196
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15197
|
+
WHERE roots.version_id = ?
|
|
15198
|
+
GROUP BY roots.id
|
|
15199
|
+
HAVING COUNT(*) <> 1
|
|
15200
|
+
OR MAX(json_extract(roots.root_key, '$[0]') <> 'link')
|
|
15201
|
+
OR MAX(rows.entity_kind <> 'link')
|
|
15202
|
+
OR MAX(rows.source_type_id <> json_extract(roots.root_key, '$[1]')
|
|
15203
|
+
OR rows.source_primary_id <> json_extract(roots.root_key, '$[2]')
|
|
15204
|
+
OR rows.link_id <> json_extract(roots.root_key, '$[3]')
|
|
15205
|
+
OR rows.target_type_id <> json_extract(roots.root_key, '$[4]')
|
|
15206
|
+
OR rows.target_primary_id <> json_extract(roots.root_key, '$[5]'))
|
|
14750
15207
|
LIMIT 1
|
|
14751
15208
|
` : `
|
|
14752
|
-
SELECT root_key
|
|
14753
|
-
FROM
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
OR SUM(
|
|
14759
|
-
|
|
14760
|
-
|
|
14761
|
-
|
|
14762
|
-
|
|
14763
|
-
|
|
14764
|
-
|
|
15209
|
+
SELECT roots.root_key
|
|
15210
|
+
FROM ontology_source_roots AS roots
|
|
15211
|
+
JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15212
|
+
WHERE roots.version_id = ?
|
|
15213
|
+
GROUP BY roots.id
|
|
15214
|
+
HAVING MAX(json_extract(roots.root_key, '$[0]') <> 'object')
|
|
15215
|
+
OR SUM(rows.entity_kind = 'object'
|
|
15216
|
+
AND rows.object_type_id = json_extract(roots.root_key, '$[1]')
|
|
15217
|
+
AND rows.primary_id = json_extract(roots.root_key, '$[2]')) <> 1
|
|
15218
|
+
OR SUM(
|
|
15219
|
+
(rows.entity_kind = 'object' AND (
|
|
15220
|
+
rows.object_type_id <> json_extract(roots.root_key, '$[1]')
|
|
15221
|
+
OR rows.primary_id <> json_extract(roots.root_key, '$[2]')))
|
|
15222
|
+
OR (rows.entity_kind = 'link' AND (
|
|
15223
|
+
rows.source_type_id <> json_extract(roots.root_key, '$[1]')
|
|
15224
|
+
OR rows.source_primary_id <> json_extract(roots.root_key, '$[2]')))
|
|
15225
|
+
) <> 0
|
|
15226
|
+
OR SUM(rows.entity_kind = 'link') <> COUNT(DISTINCT CASE
|
|
15227
|
+
WHEN rows.entity_kind = 'link'
|
|
15228
|
+
THEN json_array(rows.link_id, rows.target_type_id, rows.target_primary_id)
|
|
15229
|
+
END)
|
|
14765
15230
|
LIMIT 1
|
|
14766
|
-
`).get(manifest.
|
|
15231
|
+
`).get(manifest.version_id);
|
|
14767
15232
|
if (invalid) {
|
|
14768
15233
|
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
15234
|
}
|
|
14770
15235
|
}
|
|
14771
|
-
findStageRows(
|
|
15236
|
+
findStageRows(manifest, rows) {
|
|
14772
15237
|
if (rows.length === 0)
|
|
14773
15238
|
return [];
|
|
14774
|
-
const
|
|
15239
|
+
const rootKeys = [...new Set(rows.map((row) => row.rootKey))];
|
|
14775
15240
|
const ordinals = [...new Set(rows.map((row) => row.row.stagingOrdinal))];
|
|
14776
|
-
const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.entityKey)))];
|
|
14777
15241
|
const existing = this.db.query(`
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
|
|
14784
|
-
|
|
14785
|
-
|
|
15242
|
+
WITH selected AS (
|
|
15243
|
+
SELECT roots.id FROM json_each(?2) AS requested
|
|
15244
|
+
CROSS JOIN ontology_source_roots AS roots
|
|
15245
|
+
ON roots.version_id = ?1 AND roots.root_key = CAST(requested.value AS TEXT)
|
|
15246
|
+
UNION
|
|
15247
|
+
SELECT roots.id FROM json_each(?3) AS requested
|
|
15248
|
+
CROSS JOIN ontology_source_roots AS roots
|
|
15249
|
+
ON roots.version_id = ?1 AND roots.staging_ordinal = CAST(requested.value AS INTEGER)
|
|
15250
|
+
)
|
|
15251
|
+
SELECT ${SOURCE_ASSERTION_COLUMNS} FROM selected
|
|
15252
|
+
CROSS JOIN ontology_source_roots AS roots ON roots.id = selected.id
|
|
15253
|
+
CROSS JOIN ontology_sources AS versions ON versions.version_id = roots.version_id
|
|
15254
|
+
CROSS JOIN ontology_source_rows AS rows ON rows.root_id = roots.id
|
|
15255
|
+
`).all(manifest.version_id, JSON.stringify(rootKeys), JSON.stringify(ordinals));
|
|
14786
15256
|
return existing.map((row) => sourceStageRow(sourceAssertion(row)));
|
|
14787
15257
|
}
|
|
14788
|
-
insertStageRows(
|
|
15258
|
+
insertStageRows(manifest, rows) {
|
|
14789
15259
|
if (rows.length === 0)
|
|
14790
15260
|
return;
|
|
14791
|
-
const payload = rows.map(({
|
|
15261
|
+
const payload = rows.map(({ rootKey, row }) => {
|
|
14792
15262
|
const entity = entityColumns(row.assertion);
|
|
14793
|
-
const root = entityColumns(row.root);
|
|
14794
15263
|
return {
|
|
14795
|
-
entityKind: row.assertion.kind,
|
|
14796
|
-
entityKey,
|
|
14797
|
-
entitySortKey: utf8SortKey2(entityKey),
|
|
14798
|
-
rootKind: row.root.kind,
|
|
14799
15264
|
rootKey,
|
|
14800
|
-
|
|
14801
|
-
stagingOrdinal: row.stagingOrdinal,
|
|
14802
|
-
root: row.root,
|
|
14803
|
-
assertion: row.assertion,
|
|
15265
|
+
entityKind: row.assertion.kind,
|
|
14804
15266
|
objectTypeId: entity.objectTypeId,
|
|
14805
15267
|
primaryId: entity.primaryId,
|
|
14806
15268
|
sourceTypeId: entity.sourceTypeId,
|
|
@@ -14808,45 +15270,27 @@ class SqliteOntologySourceStorage {
|
|
|
14808
15270
|
linkId: entity.linkId,
|
|
14809
15271
|
targetTypeId: entity.targetTypeId,
|
|
14810
15272
|
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
|
|
15273
|
+
payload: sourceAssertionPayload(row.assertion)
|
|
14818
15274
|
};
|
|
14819
15275
|
});
|
|
14820
|
-
this.db.query(`
|
|
15276
|
+
const inserted = this.db.query(`
|
|
14821
15277
|
WITH staged(value) AS (SELECT value FROM json_each(?))
|
|
14822
15278
|
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
|
|
15279
|
+
root_id, entity_kind, object_type_id, primary_id,
|
|
15280
|
+
source_type_id, source_primary_id, link_id, target_type_id, target_primary_id, payload
|
|
14832
15281
|
)
|
|
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')),
|
|
15282
|
+
SELECT roots.id, json_extract(value, '$.entityKind'),
|
|
14840
15283
|
json_extract(value, '$.objectTypeId'), json_extract(value, '$.primaryId'),
|
|
14841
15284
|
json_extract(value, '$.sourceTypeId'), json_extract(value, '$.sourcePrimaryId'),
|
|
14842
15285
|
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')
|
|
15286
|
+
json_extract(value, '$.targetPrimaryId'), json_extract(value, '$.payload')
|
|
14848
15287
|
FROM staged
|
|
14849
|
-
|
|
15288
|
+
CROSS JOIN ontology_source_roots AS roots
|
|
15289
|
+
ON roots.version_id = ? AND roots.root_key = json_extract(value, '$.rootKey')
|
|
15290
|
+
`).run(canonicalJson(payload), manifest.version_id).changes;
|
|
15291
|
+
if (inserted !== rows.length) {
|
|
15292
|
+
throw sourceConflict("Staged source rows do not all belong to a staged root.");
|
|
15293
|
+
}
|
|
14850
15294
|
}
|
|
14851
15295
|
assertExecution(input, identity) {
|
|
14852
15296
|
assertProjectionExecution(this.db, {
|
|
@@ -15113,6 +15557,8 @@ function queryLatestRunsByOwnerId(input) {
|
|
|
15113
15557
|
return [];
|
|
15114
15558
|
}
|
|
15115
15559
|
const placeholders = ownerIds.map(() => "?").join(", ");
|
|
15560
|
+
const statusFilter = input.excludedStatus === undefined ? "" : " AND status <> ?";
|
|
15561
|
+
const statusArgs = input.excludedStatus === undefined ? [] : [input.excludedStatus];
|
|
15116
15562
|
const orderColumn = input.tableName === "sync_runs" || input.tableName === "pipeline_runs" || input.tableName === "projection_runs" ? "COALESCE(started_at, queued_at)" : "started_at";
|
|
15117
15563
|
const rows = input.db.query(`
|
|
15118
15564
|
SELECT *
|
|
@@ -15124,10 +15570,10 @@ function queryLatestRunsByOwnerId(input) {
|
|
|
15124
15570
|
ORDER BY ${orderColumn} DESC, id DESC
|
|
15125
15571
|
) AS sixb_latest_rank
|
|
15126
15572
|
FROM ${input.tableName}
|
|
15127
|
-
WHERE project_id = ? AND ${input.ownerColumn} IN (${placeholders})
|
|
15573
|
+
WHERE project_id = ? AND ${input.ownerColumn} IN (${placeholders})${statusFilter}
|
|
15128
15574
|
)
|
|
15129
15575
|
WHERE sixb_latest_rank = 1
|
|
15130
|
-
`).all(input.projectId, ...ownerIds);
|
|
15576
|
+
`).all(input.projectId, ...ownerIds, ...statusArgs);
|
|
15131
15577
|
const latestByOwnerId = new Map(rows.map((row) => [input.ownerIdFor(row), row]));
|
|
15132
15578
|
return ownerIds.flatMap((ownerId) => {
|
|
15133
15579
|
const row = latestByOwnerId.get(ownerId);
|
|
@@ -15473,12 +15919,14 @@ import {
|
|
|
15473
15919
|
failProjectionRunEnqueue,
|
|
15474
15920
|
immutableDatasetVersionConflict,
|
|
15475
15921
|
mergeProjectionRunProgress,
|
|
15922
|
+
parseProjectionAttemptFailure,
|
|
15476
15923
|
planProjectionRunFinish,
|
|
15477
15924
|
planProjectionRunReclaim,
|
|
15478
15925
|
projectionRunNotFound,
|
|
15479
15926
|
publicProjectionRunRecord,
|
|
15480
15927
|
requireTelemetryProjectionRun,
|
|
15481
15928
|
restoreProjectionRun,
|
|
15929
|
+
SUPERSEDING_PROJECTION_RUN_STATUSES,
|
|
15482
15930
|
staleProjectionRunExecution
|
|
15483
15931
|
} from "@sixb/core/internal/projection-run-storage-provider";
|
|
15484
15932
|
import { PROJECTION_RUN_FAILURE_CODES, ProjectionRunError } from "@sixb/core/storage";
|
|
@@ -15560,7 +16008,7 @@ class SqliteProjectionRunStorage {
|
|
|
15560
16008
|
const result = this.db.query(`
|
|
15561
16009
|
UPDATE projection_runs
|
|
15562
16010
|
SET status = 'running', started_at = COALESCE(started_at, ?), finished_at = NULL,
|
|
15563
|
-
attempt = ?, execution_token =
|
|
16011
|
+
attempt = ?, execution_token = ?
|
|
15564
16012
|
WHERE project_id = ? AND id = ?
|
|
15565
16013
|
AND (
|
|
15566
16014
|
(status = 'queued' AND execution_token IS NULL)
|
|
@@ -15604,6 +16052,20 @@ class SqliteProjectionRunStorage {
|
|
|
15604
16052
|
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
|
|
15605
16053
|
});
|
|
15606
16054
|
}
|
|
16055
|
+
async recordAttemptFailure(input) {
|
|
16056
|
+
return runImmediateTransaction(this.db, () => {
|
|
16057
|
+
const existingRow = this.requireMaterializationExecution(input);
|
|
16058
|
+
const error = parseProjectionAttemptFailure(restoreProjectionRunRow(existingRow), input.error);
|
|
16059
|
+
const result = this.db.query(`
|
|
16060
|
+
UPDATE projection_runs
|
|
16061
|
+
SET error = ?
|
|
16062
|
+
WHERE project_id = ? AND id = ? AND status = 'running' AND execution_token = ?
|
|
16063
|
+
`).run(serializeSixbFailure4(error, PROJECTION_RUN_FAILURE_CODES), input.projectId, input.id, input.executionToken);
|
|
16064
|
+
if (result.changes !== 1)
|
|
16065
|
+
throw staleProjectionRunExecution(input.id);
|
|
16066
|
+
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
|
|
16067
|
+
});
|
|
16068
|
+
}
|
|
15607
16069
|
async finish(input) {
|
|
15608
16070
|
return runImmediateTransaction(this.db, () => {
|
|
15609
16071
|
const existingRow = this.requireMaterializationExecution(input);
|
|
@@ -15741,6 +16203,21 @@ class SqliteProjectionRunStorage {
|
|
|
15741
16203
|
const runs = rows.map(rowToProjectionRunRecord);
|
|
15742
16204
|
return { runs, hasMore: offset + runs.length < totalRow.count, total: totalRow.count };
|
|
15743
16205
|
}
|
|
16206
|
+
async findSupersedingRun(input) {
|
|
16207
|
+
const run = this.requireRow(input.projectId, input.id);
|
|
16208
|
+
if (run.materialization_protocol !== "replacement")
|
|
16209
|
+
return null;
|
|
16210
|
+
const statuses = SUPERSEDING_PROJECTION_RUN_STATUSES;
|
|
16211
|
+
const newer = this.db.query(`
|
|
16212
|
+
SELECT * FROM projection_runs
|
|
16213
|
+
WHERE project_id = ? AND projection_id = ? AND materialization_protocol = 'replacement'
|
|
16214
|
+
AND status IN (${statuses.map(() => "?").join(", ")})
|
|
16215
|
+
AND dataset_version_created_at > ?
|
|
16216
|
+
ORDER BY dataset_version_created_at DESC, id DESC
|
|
16217
|
+
LIMIT 1
|
|
16218
|
+
`).get(input.projectId, run.projection_id, ...statuses, run.dataset_version_created_at);
|
|
16219
|
+
return newer ? rowToProjectionRunRecord(newer) : null;
|
|
16220
|
+
}
|
|
15744
16221
|
async listLatestByProjectionIds(input) {
|
|
15745
16222
|
const rows = queryLatestRunsByOwnerId({
|
|
15746
16223
|
db: this.db,
|
|
@@ -15748,7 +16225,8 @@ class SqliteProjectionRunStorage {
|
|
|
15748
16225
|
ownerColumn: "projection_id",
|
|
15749
16226
|
ownerIds: input.projectionIds,
|
|
15750
16227
|
projectId: input.projectId,
|
|
15751
|
-
ownerIdFor: (row) => row.projection_id
|
|
16228
|
+
ownerIdFor: (row) => row.projection_id,
|
|
16229
|
+
excludedStatus: "superseded"
|
|
15752
16230
|
});
|
|
15753
16231
|
return { runs: rows.map(rowToProjectionRunRecord) };
|
|
15754
16232
|
}
|
|
@@ -17945,4 +18423,4 @@ export {
|
|
|
17945
18423
|
migrateSqliteStorage
|
|
17946
18424
|
};
|
|
17947
18425
|
|
|
17948
|
-
//# debugId=
|
|
18426
|
+
//# debugId=A2CEAFAB428734DF64756E2164756E21
|