@sixb/sqlite 0.1.13 → 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/auth-storage/auth-storage.d.ts +2 -1
- package/dist/auth-storage/auth-storage.d.ts.map +1 -1
- package/dist/auth-storage/rows.d.ts +1 -1
- package/dist/auth-storage/rows.d.ts.map +1 -1
- package/dist/index.js +726 -286
- package/dist/index.js.map +12 -12
- 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/auth-storage/auth-storage.ts +44 -79
- package/src/auth-storage/rows.ts +1 -1
- package/src/latest-run-query.ts +8 -2
- package/src/migrations/050-directory-group-memberships.sql +21 -0
- package/src/migrations/051-projection-run-supersession.sql +126 -0
- package/src/migrations/052-compact-source-storage.sql +181 -0
- package/src/migrations.ts +42 -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
|
@@ -4333,6 +4333,343 @@ ALTER TABLE auth_device_authorizations DROP COLUMN token_name;
|
|
|
4333
4333
|
ALTER TABLE auth_device_authorizations DROP COLUMN token_expires_at;
|
|
4334
4334
|
`;
|
|
4335
4335
|
|
|
4336
|
+
// src/migrations/050-directory-group-memberships.sql
|
|
4337
|
+
var _050_directory_group_memberships_default = `-- A membership the identity provider owns is recorded with source 'directory' and synced to the
|
|
4338
|
+
-- provider's groups on every sign-in. Service accounts never sign in, so their table is unchanged.
|
|
4339
|
+
-- SQLite cannot change a CHECK constraint in place, so the table is rebuilt.
|
|
4340
|
+
CREATE TABLE auth_group_memberships_next (
|
|
4341
|
+
project_id TEXT NOT NULL,
|
|
4342
|
+
user_id TEXT NOT NULL,
|
|
4343
|
+
group_id TEXT NOT NULL,
|
|
4344
|
+
source TEXT NOT NULL CHECK (source IN ('invitation', 'manual', 'agent', 'directory')),
|
|
4345
|
+
created_at TEXT NOT NULL,
|
|
4346
|
+
PRIMARY KEY (project_id, user_id, group_id)
|
|
4347
|
+
);
|
|
4348
|
+
|
|
4349
|
+
INSERT INTO auth_group_memberships_next (project_id, user_id, group_id, source, created_at)
|
|
4350
|
+
SELECT project_id, user_id, group_id, source, created_at
|
|
4351
|
+
FROM auth_group_memberships;
|
|
4352
|
+
|
|
4353
|
+
DROP TABLE auth_group_memberships;
|
|
4354
|
+
ALTER TABLE auth_group_memberships_next RENAME TO auth_group_memberships;
|
|
4355
|
+
|
|
4356
|
+
CREATE INDEX idx_auth_group_memberships_group
|
|
4357
|
+
ON auth_group_memberships(project_id, group_id);
|
|
4358
|
+
`;
|
|
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
|
+
|
|
4336
4673
|
// src/migrations/037-execution-requester-groups.sql
|
|
4337
4674
|
var _037_execution_requester_groups_default = `ALTER TABLE executions ADD COLUMN requester_group_ids TEXT NOT NULL DEFAULT '[]';
|
|
4338
4675
|
|
|
@@ -4536,7 +4873,17 @@ var sqliteStorageMigrations = defineMigrations({
|
|
|
4536
4873
|
sqliteSql("046-workflow-intervention-principals", _046_workflow_intervention_principals_default),
|
|
4537
4874
|
sqliteSql("047-ontology-commit-attribution", _047_ontology_commit_attribution_default),
|
|
4538
4875
|
sqliteSql("048-file-upload-sessions", _048_file_upload_sessions_default),
|
|
4539
|
-
sqliteSql("049-native-sessions", _049_native_sessions_default)
|
|
4876
|
+
sqliteSql("049-native-sessions", _049_native_sessions_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) })
|
|
4540
4887
|
]
|
|
4541
4888
|
});
|
|
4542
4889
|
function sqliteStoragePath(basePath) {
|
|
@@ -4651,6 +4998,11 @@ function rowToMigrationRecord(row) {
|
|
|
4651
4998
|
finishedAt: migration.finished_at ?? undefined
|
|
4652
4999
|
};
|
|
4653
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
|
+
}
|
|
4654
5006
|
function checksum(value) {
|
|
4655
5007
|
return createHash("sha256").update(value).digest("hex");
|
|
4656
5008
|
}
|
|
@@ -9135,17 +9487,6 @@ class SqliteAuthStorage {
|
|
|
9135
9487
|
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
9136
9488
|
};
|
|
9137
9489
|
}
|
|
9138
|
-
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && hasActiveUsers(this.db, projectId)) {
|
|
9139
|
-
consumeMagicLink(this.db, {
|
|
9140
|
-
projectId,
|
|
9141
|
-
id: input.magicLinkId,
|
|
9142
|
-
tokenHash: input.tokenHash,
|
|
9143
|
-
consumedAt: completedAt
|
|
9144
|
-
});
|
|
9145
|
-
return {
|
|
9146
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] Magic link '${input.magicLinkId}' cannot create a user for project '${projectId}'.`)
|
|
9147
|
-
};
|
|
9148
|
-
}
|
|
9149
9490
|
validateCompleteSessionInput(this.db, projectId, input.session);
|
|
9150
9491
|
assertSignInSessionAudience(projectId, input.session.audience, magicLink.audience);
|
|
9151
9492
|
let newUserId;
|
|
@@ -9175,8 +9516,9 @@ class SqliteAuthStorage {
|
|
|
9175
9516
|
projectId,
|
|
9176
9517
|
user
|
|
9177
9518
|
});
|
|
9178
|
-
const groupMemberships = this.
|
|
9519
|
+
const groupMemberships = this.applyGroups({
|
|
9179
9520
|
completedAt,
|
|
9521
|
+
source: "manual",
|
|
9180
9522
|
existing: invitation.groupMemberships,
|
|
9181
9523
|
groupIds: manualGroupIds,
|
|
9182
9524
|
projectId,
|
|
@@ -9230,36 +9572,18 @@ class SqliteAuthStorage {
|
|
|
9230
9572
|
error: new AuthStorageError11("missing_user", `[Sixb] User '${identity.user_id}' not found for linked OIDC identity.`)
|
|
9231
9573
|
};
|
|
9232
9574
|
}
|
|
9233
|
-
if (!identity && userRow && (!input.autoLinkByVerifiedEmail || !input.emailVerified)) {
|
|
9234
|
-
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9235
|
-
return {
|
|
9236
|
-
error: new AuthStorageError11("email_link_not_allowed", `[Sixb] OIDC identity cannot auto-link to user '${userRow.id}' for project '${projectId}'.`)
|
|
9237
|
-
};
|
|
9238
|
-
}
|
|
9239
9575
|
if (userRow?.status === "suspended") {
|
|
9240
9576
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9241
9577
|
return {
|
|
9242
9578
|
error: new AuthStorageError11("suspended_user", `[Sixb] User '${userRow.id}' is suspended for project '${projectId}'.`)
|
|
9243
9579
|
};
|
|
9244
9580
|
}
|
|
9245
|
-
if (shouldCreateUser && !input.emailVerified) {
|
|
9246
|
-
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9247
|
-
return {
|
|
9248
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
9249
|
-
};
|
|
9250
|
-
}
|
|
9251
9581
|
if (shouldCreateUser && !activeInvitation && !input.allowUserCreationWithoutInvitation) {
|
|
9252
9582
|
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9253
9583
|
return {
|
|
9254
9584
|
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
9255
9585
|
};
|
|
9256
9586
|
}
|
|
9257
|
-
if (shouldCreateUser && !activeInvitation && input.allowUserCreationWithoutInvitation && input.requireNoActiveUsersForUserCreation && hasActiveUsers(this.db, projectId)) {
|
|
9258
|
-
this.consumeOidcAttempt(input, completedAt, projectId);
|
|
9259
|
-
return {
|
|
9260
|
-
error: new AuthStorageError11("user_creation_not_allowed", `[Sixb] OIDC authorization attempt '${input.oidcAuthorizationAttemptId}' cannot create a user for project '${projectId}'.`)
|
|
9261
|
-
};
|
|
9262
|
-
}
|
|
9263
9587
|
validateCompleteSessionInput(this.db, projectId, input.session);
|
|
9264
9588
|
assertSignInSessionAudience(projectId, input.session.audience, attempt.audience);
|
|
9265
9589
|
let newUserId;
|
|
@@ -9297,10 +9621,17 @@ class SqliteAuthStorage {
|
|
|
9297
9621
|
projectId,
|
|
9298
9622
|
user
|
|
9299
9623
|
});
|
|
9300
|
-
const groupMemberships = this.
|
|
9624
|
+
const groupMemberships = this.syncDirectoryGroups({
|
|
9301
9625
|
completedAt,
|
|
9302
|
-
existing:
|
|
9303
|
-
|
|
9626
|
+
existing: this.applyGroups({
|
|
9627
|
+
completedAt,
|
|
9628
|
+
source: "manual",
|
|
9629
|
+
existing: invitation.groupMemberships,
|
|
9630
|
+
groupIds: manualGroupIds,
|
|
9631
|
+
projectId,
|
|
9632
|
+
userId: user.id
|
|
9633
|
+
}),
|
|
9634
|
+
groupIds: input.directoryGroupIds,
|
|
9304
9635
|
projectId,
|
|
9305
9636
|
userId: user.id
|
|
9306
9637
|
});
|
|
@@ -9440,19 +9771,33 @@ class SqliteAuthStorage {
|
|
|
9440
9771
|
}));
|
|
9441
9772
|
return { invitation, groupMemberships };
|
|
9442
9773
|
}
|
|
9443
|
-
|
|
9774
|
+
applyGroups(input) {
|
|
9444
9775
|
const groupMemberships = [...input.existing];
|
|
9445
9776
|
for (const groupId of input.groupIds) {
|
|
9446
9777
|
groupMemberships.push(upsertGroupMembership(this.db, {
|
|
9447
9778
|
projectId: input.projectId,
|
|
9448
9779
|
userId: input.userId,
|
|
9449
9780
|
groupId,
|
|
9450
|
-
source:
|
|
9781
|
+
source: input.source,
|
|
9451
9782
|
createdAt: input.completedAt
|
|
9452
9783
|
}));
|
|
9453
9784
|
}
|
|
9454
9785
|
return groupMemberships;
|
|
9455
9786
|
}
|
|
9787
|
+
syncDirectoryGroups(input) {
|
|
9788
|
+
if (!input.groupIds) {
|
|
9789
|
+
return input.existing;
|
|
9790
|
+
}
|
|
9791
|
+
const groupIds = normalizeGroupIds(input.groupIds);
|
|
9792
|
+
this.db.query(`
|
|
9793
|
+
DELETE FROM auth_group_memberships
|
|
9794
|
+
WHERE project_id = ?
|
|
9795
|
+
AND user_id = ?
|
|
9796
|
+
AND source = 'directory'
|
|
9797
|
+
AND group_id NOT IN (${groupIds.map(() => "?").join(", ")})
|
|
9798
|
+
`).run(input.projectId, input.userId, ...groupIds);
|
|
9799
|
+
return this.applyGroups({ ...input, groupIds, source: "directory" });
|
|
9800
|
+
}
|
|
9456
9801
|
createSignInSession(input) {
|
|
9457
9802
|
return createSession(this.db, {
|
|
9458
9803
|
id: input.session.id,
|
|
@@ -9504,16 +9849,6 @@ class SqliteAuthStorage {
|
|
|
9504
9849
|
});
|
|
9505
9850
|
}
|
|
9506
9851
|
}
|
|
9507
|
-
function hasActiveUsers(db, projectId) {
|
|
9508
|
-
const row = db.query(`
|
|
9509
|
-
SELECT 1 AS active
|
|
9510
|
-
FROM auth_users
|
|
9511
|
-
WHERE project_id = ?
|
|
9512
|
-
AND status = 'active'
|
|
9513
|
-
LIMIT 1
|
|
9514
|
-
`).get(projectId);
|
|
9515
|
-
return row !== null;
|
|
9516
|
-
}
|
|
9517
9852
|
function assertSignInSessionAudience(projectId, sessionAudience, storedAudience) {
|
|
9518
9853
|
if (sessionAudience === storedAudience) {
|
|
9519
9854
|
return;
|
|
@@ -12196,6 +12531,10 @@ function assertReconciliationPageLimit(limit) {
|
|
|
12196
12531
|
// src/ontology-storage/shared.ts
|
|
12197
12532
|
import { parseSixbFailure as parseSixbFailure2 } from "@sixb/core/internal/errors";
|
|
12198
12533
|
import { MaterializationConflictError } from "@sixb/core/internal/materialization";
|
|
12534
|
+
import {
|
|
12535
|
+
sourceAssertionFromColumns,
|
|
12536
|
+
sourceEntityFromKey
|
|
12537
|
+
} from "@sixb/core/internal/ontology-storage-provider";
|
|
12199
12538
|
import { ONTOLOGY_OUTBOX_FAILURE_CODES } from "@sixb/core/storage";
|
|
12200
12539
|
import {
|
|
12201
12540
|
assertNonblank,
|
|
@@ -12248,8 +12587,8 @@ function sourceRecord(row) {
|
|
|
12248
12587
|
}
|
|
12249
12588
|
function sourceAssertion(row) {
|
|
12250
12589
|
return {
|
|
12251
|
-
root:
|
|
12252
|
-
assertion: parseJson2(row.
|
|
12590
|
+
root: sourceEntityFromKey(row.root_key),
|
|
12591
|
+
assertion: sourceAssertionFromColumns(row, row.payload === null ? null : parseJson2(row.payload)),
|
|
12253
12592
|
stagingOrdinal: row.staging_ordinal
|
|
12254
12593
|
};
|
|
12255
12594
|
}
|
|
@@ -12467,29 +12806,68 @@ import {
|
|
|
12467
12806
|
MaterializationValidationError,
|
|
12468
12807
|
projectionEntityKey
|
|
12469
12808
|
} from "@sixb/core/internal/materialization";
|
|
12470
|
-
import {
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
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);
|
|
12476
12841
|
return {
|
|
12477
|
-
|
|
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 = ?
|
|
12478
12867
|
UNION ALL
|
|
12479
|
-
SELECT rows.* FROM ${roots
|
|
12480
|
-
CROSS JOIN ontology_source_rows AS rows ON rows.
|
|
12481
|
-
|
|
12482
|
-
AND rows.root_sort_key = roots.root_sort_key
|
|
12483
|
-
WHERE ${selected}.project_id = ? AND ${selected}.source_id = ?
|
|
12484
|
-
AND ${input.incremental ? "changed.materialization_id = ?" : "roots.active = 1"}`,
|
|
12485
|
-
values: [
|
|
12486
|
-
input.projectId,
|
|
12487
|
-
input.sourceId,
|
|
12488
|
-
input.materializationId,
|
|
12489
|
-
input.projectId,
|
|
12490
|
-
input.sourceId,
|
|
12491
|
-
...input.incremental ? [input.materializationId] : []
|
|
12492
|
-
]
|
|
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]
|
|
12493
12871
|
};
|
|
12494
12872
|
}
|
|
12495
12873
|
function stageSourceRoots(db, manifest, input) {
|
|
@@ -12498,15 +12876,15 @@ function stageSourceRoots(db, manifest, input) {
|
|
|
12498
12876
|
throw new MaterializationValidationError("Root deletions require a source delta base.");
|
|
12499
12877
|
}
|
|
12500
12878
|
const existing = db.query(`SELECT staging_ordinal, deleted FROM ontology_source_roots
|
|
12501
|
-
WHERE
|
|
12879
|
+
WHERE version_id = ? AND root_key = ?`);
|
|
12502
12880
|
const insert = db.query(`INSERT INTO ontology_source_roots (
|
|
12503
|
-
|
|
12504
|
-
) VALUES (?, ?, ?, ?,
|
|
12881
|
+
version_id, project_id, root_key, staging_ordinal, deleted
|
|
12882
|
+
) VALUES (?, ?, ?, ?, ?) ON CONFLICT DO NOTHING RETURNING id`);
|
|
12505
12883
|
for (const root of roots) {
|
|
12506
|
-
const inserted = insert.get(
|
|
12884
|
+
const inserted = insert.get(manifest.version_id, input.projectId, root.rootKey, root.stagingOrdinal, Number(root.deleted));
|
|
12507
12885
|
if (inserted)
|
|
12508
12886
|
continue;
|
|
12509
|
-
const previous = existing.get(
|
|
12887
|
+
const previous = existing.get(manifest.version_id, root.rootKey);
|
|
12510
12888
|
if (!previous || previous.staging_ordinal !== root.stagingOrdinal || Boolean(previous.deleted) !== root.deleted) {
|
|
12511
12889
|
throw new MaterializationValidationError("Source materialization repeats root or stream ordinal with different content.");
|
|
12512
12890
|
}
|
|
@@ -12517,72 +12895,61 @@ function activateSourceRoots(db, projectId, candidate, activation) {
|
|
|
12517
12895
|
throw new MaterializationConflictError2("projection-fence", "Source delta base changed before activation.");
|
|
12518
12896
|
if (candidate.base_materialization_id !== null && candidate.root_count === 0)
|
|
12519
12897
|
return;
|
|
12520
|
-
const
|
|
12521
|
-
db.query(`UPDATE ontology_source_roots
|
|
12522
|
-
WHERE
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
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);
|
|
12529
12907
|
}
|
|
12530
12908
|
function assertSourceRootCoverage(db, manifest) {
|
|
12531
12909
|
const invalid = db.query(`SELECT 1 FROM ontology_source_roots AS roots
|
|
12532
|
-
WHERE
|
|
12910
|
+
WHERE roots.version_id = ?
|
|
12533
12911
|
AND roots.deleted = EXISTS (
|
|
12534
|
-
SELECT 1 FROM ontology_source_rows AS rows
|
|
12535
|
-
|
|
12536
|
-
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key
|
|
12537
|
-
) 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);
|
|
12538
12914
|
if (invalid)
|
|
12539
12915
|
throw new MaterializationValidationError("Source roots must have complete assertions or an explicit deletion, never both.");
|
|
12540
12916
|
}
|
|
12541
12917
|
function activeSourceRows(db, projectId, refs) {
|
|
12542
|
-
const
|
|
12543
|
-
const
|
|
12544
|
-
|
|
12545
|
-
entitySortKey,
|
|
12546
|
-
utf8SortKey(projectionEntityKey({ kind: "object", ref: entity.ref.source }))
|
|
12547
|
-
];
|
|
12548
|
-
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 }));
|
|
12549
12921
|
});
|
|
12550
12922
|
return db.query(`WITH requested AS (
|
|
12551
|
-
SELECT DISTINCT json_extract(value, '$.
|
|
12552
|
-
|
|
12553
|
-
) SELECT
|
|
12923
|
+
SELECT DISTINCT json_extract(value, '$.rootKey') AS root_key, ${REQUESTED_ENTITY}
|
|
12924
|
+
FROM json_each(?)
|
|
12925
|
+
) SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested
|
|
12554
12926
|
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
12555
|
-
AND roots.
|
|
12556
|
-
CROSS JOIN
|
|
12557
|
-
AND
|
|
12558
|
-
|
|
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);
|
|
12559
12931
|
}
|
|
12560
12932
|
function replacementAssertionRows(db, input) {
|
|
12561
|
-
const
|
|
12562
|
-
entityKind: ref.kind,
|
|
12563
|
-
entityKey: projectionEntityKey(ref),
|
|
12564
|
-
rootKeys: ref.kind === "object" ? [utf8SortKey(projectionEntityKey(ref))] : [
|
|
12565
|
-
utf8SortKey(projectionEntityKey(ref)),
|
|
12566
|
-
utf8SortKey(projectionEntityKey({ kind: "object", ref: ref.ref.source }))
|
|
12567
|
-
]
|
|
12568
|
-
}));
|
|
12933
|
+
const version = sourceVersionId(db, input);
|
|
12569
12934
|
const previous = !input.includePrevious ? "" : `UNION ALL
|
|
12570
|
-
SELECT
|
|
12571
|
-
CROSS JOIN ontology_source_roots AS roots ON roots.project_id = ?
|
|
12572
|
-
AND roots.
|
|
12573
|
-
|
|
12574
|
-
|
|
12575
|
-
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}
|
|
12576
12942
|
${!input.incremental ? "" : `WHERE EXISTS (SELECT 1 FROM ontology_source_roots AS changed
|
|
12577
|
-
WHERE changed.
|
|
12578
|
-
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)`}`;
|
|
12579
12944
|
return db.query(`WITH requested_entities AS (
|
|
12580
|
-
SELECT DISTINCT json_extract(value, '$.
|
|
12581
|
-
|
|
12582
|
-
) SELECT
|
|
12583
|
-
CROSS JOIN
|
|
12584
|
-
AND
|
|
12585
|
-
|
|
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] : []] : []);
|
|
12586
12953
|
}
|
|
12587
12954
|
|
|
12588
12955
|
// src/ontology-storage/materialization-state.ts
|
|
@@ -12795,19 +13162,13 @@ class SqliteMaterializationStateReader {
|
|
|
12795
13162
|
json_extract(value, '$.linkId') AS link_id
|
|
12796
13163
|
FROM json_each(?)
|
|
12797
13164
|
)
|
|
12798
|
-
SELECT
|
|
13165
|
+
SELECT ${SOURCE_ASSERTION_COLUMNS} FROM requested
|
|
12799
13166
|
CROSS JOIN ontology_source_rows AS rows
|
|
12800
|
-
ON rows.
|
|
12801
|
-
AND rows.entity_kind = 'link'
|
|
13167
|
+
ON rows.entity_kind = 'link'
|
|
12802
13168
|
AND rows.source_type_id = requested.source_type_id
|
|
12803
13169
|
AND rows.source_primary_id = requested.source_primary_id
|
|
12804
13170
|
AND rows.link_id = requested.link_id
|
|
12805
|
-
|
|
12806
|
-
ON sources.project_id = rows.project_id
|
|
12807
|
-
AND sources.source_id = rows.source_id
|
|
12808
|
-
AND sources.materialization_id = rows.materialization_id
|
|
12809
|
-
AND sources.root_sort_key = rows.root_sort_key
|
|
12810
|
-
WHERE sources.active = 1`).all(requestedJson, this.projectId);
|
|
13171
|
+
${LIVE_SOURCE_ROWS_JOIN}`).all(requestedJson, this.projectId);
|
|
12811
13172
|
const slotOverrideRows = this.db.query(`WITH requested AS (
|
|
12812
13173
|
SELECT DISTINCT
|
|
12813
13174
|
json_extract(value, '$.sourceTypeId') AS source_type_id,
|
|
@@ -12896,12 +13257,8 @@ class SqliteMaterializationStateReader {
|
|
|
12896
13257
|
SELECT rows.source_type_id, rows.source_primary_id, rows.link_id,
|
|
12897
13258
|
rows.target_type_id, rows.target_primary_id
|
|
12898
13259
|
FROM ontology_source_rows AS rows
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
AND sources.source_id = rows.source_id
|
|
12902
|
-
AND sources.materialization_id = rows.materialization_id
|
|
12903
|
-
AND sources.root_sort_key = rows.root_sort_key
|
|
12904
|
-
WHERE rows.project_id = ? AND rows.entity_kind = 'link' AND sources.active = 1
|
|
13260
|
+
${LIVE_SOURCE_ROWS_JOIN}
|
|
13261
|
+
WHERE rows.entity_kind = 'link'
|
|
12905
13262
|
), selected AS (
|
|
12906
13263
|
SELECT DISTINCT links.*,
|
|
12907
13264
|
${linkSortExpression("links")} AS sort_key
|
|
@@ -13012,7 +13369,7 @@ class SqliteMaterializationStateReader {
|
|
|
13012
13369
|
this.prepareReplacementLinks(input);
|
|
13013
13370
|
}
|
|
13014
13371
|
prepareReplacementObjects(input) {
|
|
13015
|
-
const rows = replacementSourceRows({
|
|
13372
|
+
const rows = replacementSourceRows(this.db, {
|
|
13016
13373
|
projectId: this.projectId,
|
|
13017
13374
|
sourceId: input.sourceId,
|
|
13018
13375
|
materializationId: input.candidateMaterializationId,
|
|
@@ -13023,14 +13380,14 @@ class SqliteMaterializationStateReader {
|
|
|
13023
13380
|
session_id, entity_kind, identity_key, sort_key, diff_required
|
|
13024
13381
|
)
|
|
13025
13382
|
SELECT ?, 'object', json_array(object_type_id, primary_id),
|
|
13026
|
-
|
|
13383
|
+
${objectSortExpression()}, 1
|
|
13027
13384
|
FROM (${rows.sql}) AS rows
|
|
13028
13385
|
WHERE entity_kind = 'object'
|
|
13029
13386
|
GROUP BY object_type_id, primary_id
|
|
13030
13387
|
`).run(input.sessionId, ...rows.values);
|
|
13031
13388
|
}
|
|
13032
13389
|
prepareReplacementLinks(input) {
|
|
13033
|
-
const rows = replacementSourceRows({
|
|
13390
|
+
const rows = replacementSourceRows(this.db, {
|
|
13034
13391
|
projectId: this.projectId,
|
|
13035
13392
|
sourceId: input.sourceId,
|
|
13036
13393
|
materializationId: input.candidateMaterializationId,
|
|
@@ -13087,31 +13444,21 @@ class SqliteMaterializationStateReader {
|
|
|
13087
13444
|
UNION
|
|
13088
13445
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
13089
13446
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
13090
|
-
FROM
|
|
13091
|
-
JOIN
|
|
13092
|
-
ON
|
|
13093
|
-
AND
|
|
13094
|
-
AND
|
|
13095
|
-
|
|
13096
|
-
JOIN incident_objects
|
|
13097
|
-
ON incident_objects.object_type_id = rows.source_type_id
|
|
13098
|
-
AND incident_objects.primary_id = rows.source_primary_id
|
|
13099
|
-
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
13100
|
-
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}
|
|
13101
13453
|
UNION
|
|
13102
13454
|
SELECT rows.source_type_id, rows.source_primary_id AS source_id,
|
|
13103
13455
|
rows.link_id, rows.target_type_id, rows.target_primary_id AS target_id
|
|
13104
|
-
FROM
|
|
13105
|
-
JOIN
|
|
13106
|
-
ON
|
|
13107
|
-
AND
|
|
13108
|
-
AND
|
|
13109
|
-
|
|
13110
|
-
JOIN incident_objects
|
|
13111
|
-
ON incident_objects.object_type_id = rows.target_type_id
|
|
13112
|
-
AND incident_objects.primary_id = rows.target_primary_id
|
|
13113
|
-
WHERE rows.project_id = ? AND rows.entity_kind = 'link'
|
|
13114
|
-
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}
|
|
13115
13462
|
), diff_links AS (
|
|
13116
13463
|
SELECT * FROM replacement_links
|
|
13117
13464
|
UNION SELECT * FROM incident_links
|
|
@@ -13261,9 +13608,7 @@ function storedSource(row) {
|
|
|
13261
13608
|
return {
|
|
13262
13609
|
source: { projectionId: row.source_id },
|
|
13263
13610
|
materializationId: row.materialization_id,
|
|
13264
|
-
|
|
13265
|
-
assertion: parseJson2(row.assertion),
|
|
13266
|
-
stagingOrdinal: row.staging_ordinal
|
|
13611
|
+
...sourceAssertion(row)
|
|
13267
13612
|
};
|
|
13268
13613
|
}
|
|
13269
13614
|
function effectiveObjectSnapshot(row) {
|
|
@@ -13311,6 +13656,10 @@ function storedPoint(row) {
|
|
|
13311
13656
|
lastCommitId: row.last_commit_id
|
|
13312
13657
|
};
|
|
13313
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
|
+
}
|
|
13314
13663
|
function linkSortExpression(alias) {
|
|
13315
13664
|
const prefix = alias ? `${alias}.` : "";
|
|
13316
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)))`;
|
|
@@ -14491,51 +14840,80 @@ import {
|
|
|
14491
14840
|
sourceEntityColumns as entityColumns,
|
|
14492
14841
|
isExactStagingManifest,
|
|
14493
14842
|
reconcileSourceStageRows,
|
|
14843
|
+
sourceAssertionPayload,
|
|
14494
14844
|
sourceConflict,
|
|
14495
14845
|
sourceMaterializationIdentity,
|
|
14496
14846
|
sourceStageRow,
|
|
14497
|
-
sourceStageRows
|
|
14498
|
-
utf8SortKey as utf8SortKey2
|
|
14847
|
+
sourceStageRows
|
|
14499
14848
|
} from "@sixb/core/internal/ontology-storage-provider";
|
|
14500
14849
|
|
|
14501
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
|
+
}
|
|
14502
14889
|
function cleanupSourceVersions(db, input) {
|
|
14503
14890
|
let remaining = input.limit;
|
|
14891
|
+
const retired = `roots.project_id = ? AND roots.retired_at < ? AND versions.status = 'superseded'`;
|
|
14504
14892
|
let rowsDeleted = db.query(`DELETE FROM ontology_source_rows WHERE rowid IN (
|
|
14505
14893
|
SELECT rows.rowid FROM ontology_source_roots AS roots
|
|
14506
|
-
JOIN ontology_sources AS
|
|
14507
|
-
CROSS JOIN ontology_source_rows AS rows
|
|
14508
|
-
|
|
14509
|
-
|
|
14510
|
-
WHERE roots.project_id = ? AND roots.active = 0 AND roots.retired_at < ?
|
|
14511
|
-
AND sources.status IN ('superseded', 'abandoned')
|
|
14512
|
-
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
|
|
14513
14898
|
LIMIT ?
|
|
14514
14899
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14515
14900
|
remaining -= rowsDeleted;
|
|
14516
14901
|
if (remaining > 0) {
|
|
14517
|
-
const removed = db.query(`DELETE FROM ontology_source_roots WHERE
|
|
14518
|
-
SELECT roots.
|
|
14519
|
-
JOIN ontology_sources AS
|
|
14520
|
-
WHERE
|
|
14521
|
-
AND
|
|
14522
|
-
|
|
14523
|
-
WHERE rows.project_id = roots.project_id AND rows.source_id = roots.source_id
|
|
14524
|
-
AND rows.materialization_id = roots.materialization_id AND rows.root_sort_key = roots.root_sort_key)
|
|
14525
|
-
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 ?
|
|
14526
14908
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14527
14909
|
rowsDeleted += removed;
|
|
14528
14910
|
remaining -= removed;
|
|
14529
14911
|
}
|
|
14530
|
-
const materializationsDeleted = remaining === 0 ? 0 : db.query(`DELETE FROM ontology_sources WHERE
|
|
14531
|
-
SELECT
|
|
14532
|
-
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 < ?
|
|
14533
14915
|
AND NOT EXISTS (SELECT 1 FROM ontology_source_roots AS roots
|
|
14534
|
-
WHERE roots.
|
|
14535
|
-
AND roots.materialization_id = sources.materialization_id)
|
|
14536
|
-
AND NOT EXISTS (SELECT 1 FROM ontology_source_rows AS rows
|
|
14537
|
-
WHERE rows.project_id = sources.project_id AND rows.source_id = sources.source_id
|
|
14538
|
-
AND rows.materialization_id = sources.materialization_id)
|
|
14916
|
+
WHERE roots.version_id = versions.version_id)
|
|
14539
14917
|
ORDER BY terminal_at, source_id, materialization_id LIMIT ?
|
|
14540
14918
|
)`).run(input.projectId, input.terminalBefore, remaining).changes;
|
|
14541
14919
|
return { rowsDeleted, materializationsDeleted };
|
|
@@ -14567,7 +14945,7 @@ class SqliteOntologySourceStorage {
|
|
|
14567
14945
|
LIMIT 1
|
|
14568
14946
|
`).get(input.projectId, input.execution.projectionRunId);
|
|
14569
14947
|
if (candidate) {
|
|
14570
|
-
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.`);
|
|
14571
14949
|
}
|
|
14572
14950
|
this.db.query(`
|
|
14573
14951
|
INSERT INTO ontology_sources (
|
|
@@ -14593,10 +14971,10 @@ class SqliteOntologySourceStorage {
|
|
|
14593
14971
|
throw sourceConflict(`Source materialization '${input.materializationId}' is '${manifest.status}' and cannot accept rows.`);
|
|
14594
14972
|
}
|
|
14595
14973
|
const rows = sourceStageRows(manifest.projection_kind, input.rows);
|
|
14596
|
-
const existing = this.findStageRows(
|
|
14974
|
+
const existing = this.findStageRows(manifest, rows);
|
|
14597
14975
|
const { pending, unchanged } = reconcileSourceStageRows(rows, existing);
|
|
14598
14976
|
stageSourceRoots(this.db, manifest, input);
|
|
14599
|
-
this.insertStageRows(
|
|
14977
|
+
this.insertStageRows(manifest, pending);
|
|
14600
14978
|
return { inserted: pending.length, unchanged };
|
|
14601
14979
|
});
|
|
14602
14980
|
await yieldSqliteEventLoop();
|
|
@@ -14646,13 +15024,40 @@ class SqliteOntologySourceStorage {
|
|
|
14646
15024
|
return row ? sourceRecord(row) : null;
|
|
14647
15025
|
});
|
|
14648
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
|
+
}
|
|
14649
15051
|
async abandon(input) {
|
|
14650
15052
|
return this.runRootOperation(() => {
|
|
14651
15053
|
assertProjectAndSource(input);
|
|
14652
15054
|
assertExecutionIdentity(input.execution.projectionRunId, input.execution.executionToken);
|
|
14653
15055
|
assertTimestamp(input.abandonedAt, "Source abandonedAt", true);
|
|
14654
15056
|
this.assertExecution(input);
|
|
14655
|
-
|
|
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;
|
|
14656
15061
|
});
|
|
14657
15062
|
}
|
|
14658
15063
|
async cleanupTerminal(input) {
|
|
@@ -14663,6 +15068,13 @@ class SqliteOntologySourceStorage {
|
|
|
14663
15068
|
return cleanupSourceVersions(this.db, input);
|
|
14664
15069
|
});
|
|
14665
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
|
+
}
|
|
14666
15078
|
async summarizeTerminal(input) {
|
|
14667
15079
|
return this.runRootOperation(() => {
|
|
14668
15080
|
assertNonblank(input.projectId, "Terminal source summary project id");
|
|
@@ -14688,19 +15100,20 @@ class SqliteOntologySourceStorage {
|
|
|
14688
15100
|
assertSourceCandidateOwner(sourceRecord(manifest), input.execution);
|
|
14689
15101
|
return this.transitionToAbandoned(manifest, input.abandonedAt);
|
|
14690
15102
|
}
|
|
14691
|
-
|
|
15103
|
+
findRunCandidate(input) {
|
|
14692
15104
|
const candidates = this.db.query(`
|
|
14693
15105
|
SELECT * FROM ontology_sources
|
|
14694
15106
|
WHERE project_id = ? AND source_id = ? AND projection_run_id = ?
|
|
14695
15107
|
AND status IN ('staging', 'ready')
|
|
14696
15108
|
`).all(input.projectId, input.source.projectionId, input.execution.projectionRunId);
|
|
14697
|
-
if (candidates.some((candidate) => candidate.execution_token === input.execution.executionToken)) {
|
|
14698
|
-
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' already has a source materialization owned by the current execution.`);
|
|
14699
|
-
}
|
|
14700
15109
|
if (candidates.length > 1) {
|
|
14701
15110
|
throw sourceConflict(`Projection run '${input.execution.projectionRunId}' has multiple nonterminal source materializations.`);
|
|
14702
15111
|
}
|
|
14703
|
-
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;
|
|
14704
15117
|
}
|
|
14705
15118
|
transitionToAbandoned(manifest, abandonedAt) {
|
|
14706
15119
|
if (abandonedAt < manifest.created_at || manifest.ready_at !== null && abandonedAt < manifest.ready_at) {
|
|
@@ -14715,84 +15128,95 @@ class SqliteOntologySourceStorage {
|
|
|
14715
15128
|
if (changed !== 1) {
|
|
14716
15129
|
throw sourceConflict(`Source materialization '${manifest.materialization_id}' cannot transition to 'abandoned'.`);
|
|
14717
15130
|
}
|
|
14718
|
-
this.db.query(`UPDATE ontology_source_roots SET retired_at = ?
|
|
14719
|
-
WHERE project_id = ? AND source_id = ? AND materialization_id = ? AND active = 0`).run(abandonedAt, manifest.project_id, manifest.source_id, manifest.materialization_id);
|
|
14720
15131
|
return sourceRecord(this.requireManifest(manifest.project_id, manifest.source_id, manifest.materialization_id));
|
|
14721
15132
|
}
|
|
14722
15133
|
assertReadyState(manifest, rootCount, assertionCount) {
|
|
14723
15134
|
const counts = this.db.query(`
|
|
14724
|
-
SELECT (SELECT COUNT(*) FROM
|
|
14725
|
-
|
|
14726
|
-
|
|
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,
|
|
14727
15139
|
MIN(staging_ordinal) AS min_ordinal, MAX(staging_ordinal) AS max_ordinal
|
|
14728
15140
|
FROM ontology_source_roots
|
|
14729
|
-
WHERE
|
|
14730
|
-
`).get(manifest.
|
|
15141
|
+
WHERE version_id = ?1
|
|
15142
|
+
`).get(manifest.version_id);
|
|
14731
15143
|
if (counts.assertions !== assertionCount || counts.roots !== rootCount || counts.ordinals !== rootCount || rootCount > 0 && (counts.min_ordinal !== 0 || counts.max_ordinal !== rootCount - 1)) {
|
|
14732
15144
|
throw new MaterializationValidationError4("Source ready counts do not match the staged roots and assertions.");
|
|
14733
15145
|
}
|
|
14734
15146
|
assertSourceRootCoverage(this.db, manifest);
|
|
14735
15147
|
const invalid = this.db.query(manifest.projection_kind === "link" ? `
|
|
14736
|
-
SELECT root_key
|
|
14737
|
-
FROM
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
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]'))
|
|
14742
15161
|
LIMIT 1
|
|
14743
15162
|
` : `
|
|
14744
|
-
SELECT root_key
|
|
14745
|
-
FROM
|
|
14746
|
-
|
|
14747
|
-
|
|
14748
|
-
|
|
14749
|
-
|
|
14750
|
-
OR SUM(
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
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)
|
|
14757
15184
|
LIMIT 1
|
|
14758
|
-
`).get(manifest.
|
|
15185
|
+
`).get(manifest.version_id);
|
|
14759
15186
|
if (invalid) {
|
|
14760
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.");
|
|
14761
15188
|
}
|
|
14762
15189
|
}
|
|
14763
|
-
findStageRows(
|
|
15190
|
+
findStageRows(manifest, rows) {
|
|
14764
15191
|
if (rows.length === 0)
|
|
14765
15192
|
return [];
|
|
14766
|
-
const
|
|
15193
|
+
const rootKeys = [...new Set(rows.map((row) => row.rootKey))];
|
|
14767
15194
|
const ordinals = [...new Set(rows.map((row) => row.row.stagingOrdinal))];
|
|
14768
|
-
const entitySortKeys = [...new Set(rows.map((row) => utf8SortKey2(row.entityKey)))];
|
|
14769
15195
|
const existing = this.db.query(`
|
|
14770
|
-
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
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));
|
|
14778
15210
|
return existing.map((row) => sourceStageRow(sourceAssertion(row)));
|
|
14779
15211
|
}
|
|
14780
|
-
insertStageRows(
|
|
15212
|
+
insertStageRows(manifest, rows) {
|
|
14781
15213
|
if (rows.length === 0)
|
|
14782
15214
|
return;
|
|
14783
|
-
const payload = rows.map(({
|
|
15215
|
+
const payload = rows.map(({ rootKey, row }) => {
|
|
14784
15216
|
const entity = entityColumns(row.assertion);
|
|
14785
|
-
const root = entityColumns(row.root);
|
|
14786
15217
|
return {
|
|
14787
|
-
entityKind: row.assertion.kind,
|
|
14788
|
-
entityKey,
|
|
14789
|
-
entitySortKey: utf8SortKey2(entityKey),
|
|
14790
|
-
rootKind: row.root.kind,
|
|
14791
15218
|
rootKey,
|
|
14792
|
-
|
|
14793
|
-
stagingOrdinal: row.stagingOrdinal,
|
|
14794
|
-
root: row.root,
|
|
14795
|
-
assertion: row.assertion,
|
|
15219
|
+
entityKind: row.assertion.kind,
|
|
14796
15220
|
objectTypeId: entity.objectTypeId,
|
|
14797
15221
|
primaryId: entity.primaryId,
|
|
14798
15222
|
sourceTypeId: entity.sourceTypeId,
|
|
@@ -14800,45 +15224,27 @@ class SqliteOntologySourceStorage {
|
|
|
14800
15224
|
linkId: entity.linkId,
|
|
14801
15225
|
targetTypeId: entity.targetTypeId,
|
|
14802
15226
|
targetPrimaryId: entity.targetPrimaryId,
|
|
14803
|
-
|
|
14804
|
-
rootPrimaryId: root.primaryId,
|
|
14805
|
-
rootSourceTypeId: root.sourceTypeId,
|
|
14806
|
-
rootSourcePrimaryId: root.sourcePrimaryId,
|
|
14807
|
-
rootLinkId: root.linkId,
|
|
14808
|
-
rootTargetTypeId: root.targetTypeId,
|
|
14809
|
-
rootTargetPrimaryId: root.targetPrimaryId
|
|
15227
|
+
payload: sourceAssertionPayload(row.assertion)
|
|
14810
15228
|
};
|
|
14811
15229
|
});
|
|
14812
|
-
this.db.query(`
|
|
15230
|
+
const inserted = this.db.query(`
|
|
14813
15231
|
WITH staged(value) AS (SELECT value FROM json_each(?))
|
|
14814
15232
|
INSERT INTO ontology_source_rows (
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
root_kind, root_key, root_sort_key, staging_ordinal,
|
|
14818
|
-
root, assertion,
|
|
14819
|
-
object_type_id, primary_id,
|
|
14820
|
-
source_type_id, source_primary_id, link_id, target_type_id, target_primary_id,
|
|
14821
|
-
root_object_type_id, root_primary_id,
|
|
14822
|
-
root_source_type_id, root_source_primary_id, root_link_id,
|
|
14823
|
-
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
|
|
14824
15235
|
)
|
|
14825
|
-
SELECT
|
|
14826
|
-
?, ?, ?,
|
|
14827
|
-
json_extract(value, '$.entityKind'), json_extract(value, '$.entityKey'),
|
|
14828
|
-
json_extract(value, '$.entitySortKey'),
|
|
14829
|
-
json_extract(value, '$.rootKind'), json_extract(value, '$.rootKey'),
|
|
14830
|
-
json_extract(value, '$.rootSortKey'), json_extract(value, '$.stagingOrdinal'),
|
|
14831
|
-
json(json_extract(value, '$.root')), json(json_extract(value, '$.assertion')),
|
|
15236
|
+
SELECT roots.id, json_extract(value, '$.entityKind'),
|
|
14832
15237
|
json_extract(value, '$.objectTypeId'), json_extract(value, '$.primaryId'),
|
|
14833
15238
|
json_extract(value, '$.sourceTypeId'), json_extract(value, '$.sourcePrimaryId'),
|
|
14834
15239
|
json_extract(value, '$.linkId'), json_extract(value, '$.targetTypeId'),
|
|
14835
|
-
json_extract(value, '$.targetPrimaryId'),
|
|
14836
|
-
json_extract(value, '$.rootObjectTypeId'), json_extract(value, '$.rootPrimaryId'),
|
|
14837
|
-
json_extract(value, '$.rootSourceTypeId'),
|
|
14838
|
-
json_extract(value, '$.rootSourcePrimaryId'), json_extract(value, '$.rootLinkId'),
|
|
14839
|
-
json_extract(value, '$.rootTargetTypeId'), json_extract(value, '$.rootTargetPrimaryId')
|
|
15240
|
+
json_extract(value, '$.targetPrimaryId'), json_extract(value, '$.payload')
|
|
14840
15241
|
FROM staged
|
|
14841
|
-
|
|
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
|
+
}
|
|
14842
15248
|
}
|
|
14843
15249
|
assertExecution(input, identity) {
|
|
14844
15250
|
assertProjectionExecution(this.db, {
|
|
@@ -15105,6 +15511,8 @@ function queryLatestRunsByOwnerId(input) {
|
|
|
15105
15511
|
return [];
|
|
15106
15512
|
}
|
|
15107
15513
|
const placeholders = ownerIds.map(() => "?").join(", ");
|
|
15514
|
+
const statusFilter = input.excludedStatus === undefined ? "" : " AND status <> ?";
|
|
15515
|
+
const statusArgs = input.excludedStatus === undefined ? [] : [input.excludedStatus];
|
|
15108
15516
|
const orderColumn = input.tableName === "sync_runs" || input.tableName === "pipeline_runs" || input.tableName === "projection_runs" ? "COALESCE(started_at, queued_at)" : "started_at";
|
|
15109
15517
|
const rows = input.db.query(`
|
|
15110
15518
|
SELECT *
|
|
@@ -15116,10 +15524,10 @@ function queryLatestRunsByOwnerId(input) {
|
|
|
15116
15524
|
ORDER BY ${orderColumn} DESC, id DESC
|
|
15117
15525
|
) AS sixb_latest_rank
|
|
15118
15526
|
FROM ${input.tableName}
|
|
15119
|
-
WHERE project_id = ? AND ${input.ownerColumn} IN (${placeholders})
|
|
15527
|
+
WHERE project_id = ? AND ${input.ownerColumn} IN (${placeholders})${statusFilter}
|
|
15120
15528
|
)
|
|
15121
15529
|
WHERE sixb_latest_rank = 1
|
|
15122
|
-
`).all(input.projectId, ...ownerIds);
|
|
15530
|
+
`).all(input.projectId, ...ownerIds, ...statusArgs);
|
|
15123
15531
|
const latestByOwnerId = new Map(rows.map((row) => [input.ownerIdFor(row), row]));
|
|
15124
15532
|
return ownerIds.flatMap((ownerId) => {
|
|
15125
15533
|
const row = latestByOwnerId.get(ownerId);
|
|
@@ -15465,12 +15873,14 @@ import {
|
|
|
15465
15873
|
failProjectionRunEnqueue,
|
|
15466
15874
|
immutableDatasetVersionConflict,
|
|
15467
15875
|
mergeProjectionRunProgress,
|
|
15876
|
+
parseProjectionAttemptFailure,
|
|
15468
15877
|
planProjectionRunFinish,
|
|
15469
15878
|
planProjectionRunReclaim,
|
|
15470
15879
|
projectionRunNotFound,
|
|
15471
15880
|
publicProjectionRunRecord,
|
|
15472
15881
|
requireTelemetryProjectionRun,
|
|
15473
15882
|
restoreProjectionRun,
|
|
15883
|
+
SUPERSEDING_PROJECTION_RUN_STATUSES,
|
|
15474
15884
|
staleProjectionRunExecution
|
|
15475
15885
|
} from "@sixb/core/internal/projection-run-storage-provider";
|
|
15476
15886
|
import { PROJECTION_RUN_FAILURE_CODES, ProjectionRunError } from "@sixb/core/storage";
|
|
@@ -15552,7 +15962,7 @@ class SqliteProjectionRunStorage {
|
|
|
15552
15962
|
const result = this.db.query(`
|
|
15553
15963
|
UPDATE projection_runs
|
|
15554
15964
|
SET status = 'running', started_at = COALESCE(started_at, ?), finished_at = NULL,
|
|
15555
|
-
attempt = ?, execution_token =
|
|
15965
|
+
attempt = ?, execution_token = ?
|
|
15556
15966
|
WHERE project_id = ? AND id = ?
|
|
15557
15967
|
AND (
|
|
15558
15968
|
(status = 'queued' AND execution_token IS NULL)
|
|
@@ -15596,6 +16006,20 @@ class SqliteProjectionRunStorage {
|
|
|
15596
16006
|
return rowToProjectionRunRecord(this.requireRow(input.projectId, input.id));
|
|
15597
16007
|
});
|
|
15598
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
|
+
}
|
|
15599
16023
|
async finish(input) {
|
|
15600
16024
|
return runImmediateTransaction(this.db, () => {
|
|
15601
16025
|
const existingRow = this.requireMaterializationExecution(input);
|
|
@@ -15733,6 +16157,21 @@ class SqliteProjectionRunStorage {
|
|
|
15733
16157
|
const runs = rows.map(rowToProjectionRunRecord);
|
|
15734
16158
|
return { runs, hasMore: offset + runs.length < totalRow.count, total: totalRow.count };
|
|
15735
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
|
+
}
|
|
15736
16175
|
async listLatestByProjectionIds(input) {
|
|
15737
16176
|
const rows = queryLatestRunsByOwnerId({
|
|
15738
16177
|
db: this.db,
|
|
@@ -15740,7 +16179,8 @@ class SqliteProjectionRunStorage {
|
|
|
15740
16179
|
ownerColumn: "projection_id",
|
|
15741
16180
|
ownerIds: input.projectionIds,
|
|
15742
16181
|
projectId: input.projectId,
|
|
15743
|
-
ownerIdFor: (row) => row.projection_id
|
|
16182
|
+
ownerIdFor: (row) => row.projection_id,
|
|
16183
|
+
excludedStatus: "superseded"
|
|
15744
16184
|
});
|
|
15745
16185
|
return { runs: rows.map(rowToProjectionRunRecord) };
|
|
15746
16186
|
}
|
|
@@ -17937,4 +18377,4 @@ export {
|
|
|
17937
18377
|
migrateSqliteStorage
|
|
17938
18378
|
};
|
|
17939
18379
|
|
|
17940
|
-
//# debugId=
|
|
18380
|
+
//# debugId=1D3AEE45CA656B6464756E2164756E21
|