@lix-js/sdk 0.6.0-preview.3 → 0.6.0-preview.5
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/README.md +1 -1
- package/SKILL.md +105 -65
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +30 -6
- package/dist/engine-wasm/wasm/lix_engine.js +187 -117
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +14 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +42 -28
- package/dist/open-lix.js +49 -10
- package/dist/sqlite/index.js +86 -30
- package/dist-engine-src/README.md +3 -3
- package/dist-engine-src/src/backend/capabilities.rs +67 -0
- package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
- package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
- package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
- package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
- package/dist-engine-src/src/backend/conformance/model.rs +28 -0
- package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
- package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
- package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
- package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
- package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
- package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
- package/dist-engine-src/src/backend/conformance/write.rs +16 -0
- package/dist-engine-src/src/backend/error.rs +94 -0
- package/dist-engine-src/src/backend/in_memory.rs +670 -0
- package/dist-engine-src/src/backend/mod.rs +36 -9
- package/dist-engine-src/src/backend/predicate.rs +80 -0
- package/dist-engine-src/src/backend/traits.rs +260 -0
- package/dist-engine-src/src/backend/types.rs +224 -81
- package/dist-engine-src/src/binary_cas/context.rs +8 -8
- package/dist-engine-src/src/binary_cas/kv.rs +234 -259
- package/dist-engine-src/src/{version → branch}/context.rs +12 -12
- package/dist-engine-src/src/branch/lifecycle.rs +221 -0
- package/dist-engine-src/src/branch/mod.rs +13 -0
- package/dist-engine-src/src/branch/refs.rs +321 -0
- package/dist-engine-src/src/branch/stage_rows.rs +67 -0
- package/dist-engine-src/src/branch/types.rs +21 -0
- package/dist-engine-src/src/catalog/context.rs +18 -18
- package/dist-engine-src/src/catalog/snapshot.rs +8 -8
- package/dist-engine-src/src/changelog/bench_support.rs +785 -0
- package/dist-engine-src/src/changelog/change.rs +1 -0
- package/dist-engine-src/src/changelog/codec.rs +497 -0
- package/dist-engine-src/src/changelog/commit.rs +1 -0
- package/dist-engine-src/src/changelog/context.rs +1614 -0
- package/dist-engine-src/src/changelog/mod.rs +29 -0
- package/dist-engine-src/src/changelog/store.rs +163 -0
- package/dist-engine-src/src/changelog/test_support.rs +54 -0
- package/dist-engine-src/src/changelog/types.rs +213 -0
- package/dist-engine-src/src/commit_graph/context.rs +317 -274
- package/dist-engine-src/src/commit_graph/mod.rs +2 -4
- package/dist-engine-src/src/commit_graph/types.rs +22 -42
- package/dist-engine-src/src/commit_graph/walker.rs +133 -103
- package/dist-engine-src/src/common/error.rs +52 -18
- package/dist-engine-src/src/common/identity.rs +2 -2
- package/dist-engine-src/src/common/mod.rs +1 -1
- package/dist-engine-src/src/domain.rs +42 -46
- package/dist-engine-src/src/engine.rs +74 -96
- package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
- package/dist-engine-src/src/functions/context.rs +56 -52
- package/dist-engine-src/src/functions/state.rs +51 -52
- package/dist-engine-src/src/init.rs +288 -154
- package/dist-engine-src/src/json_store/context.rs +15 -266
- package/dist-engine-src/src/json_store/mod.rs +26 -0
- package/dist-engine-src/src/json_store/store.rs +103 -718
- package/dist-engine-src/src/json_store/types.rs +4 -9
- package/dist-engine-src/src/lib.rs +49 -19
- package/dist-engine-src/src/live_state/context.rs +654 -790
- package/dist-engine-src/src/live_state/mod.rs +9 -3
- package/dist-engine-src/src/live_state/overlay.rs +4 -4
- package/dist-engine-src/src/live_state/types.rs +30 -21
- package/dist-engine-src/src/live_state/visibility.rs +514 -71
- package/dist-engine-src/src/plugin/install.rs +48 -48
- package/dist-engine-src/src/plugin/manifest.rs +7 -7
- package/dist-engine-src/src/plugin/materializer.rs +0 -275
- package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
- package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
- package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
- package/dist-engine-src/src/schema/compatibility.rs +11 -11
- package/dist-engine-src/src/schema/definition.json +2 -2
- package/dist-engine-src/src/schema/definition.rs +5 -5
- package/dist-engine-src/src/schema/key.rs +3 -3
- package/dist-engine-src/src/schema/mod.rs +1 -1
- package/dist-engine-src/src/schema/tests.rs +18 -18
- package/dist-engine-src/src/session/context.rs +819 -124
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +260 -57
- package/dist-engine-src/src/session/merge/analysis.rs +9 -3
- package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
- package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
- package/dist-engine-src/src/session/merge/mod.rs +5 -6
- package/dist-engine-src/src/session/merge/stats.rs +7 -11
- package/dist-engine-src/src/session/mod.rs +19 -16
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +557 -0
- package/dist-engine-src/src/sql2/bind/classify.rs +102 -0
- package/dist-engine-src/src/sql2/bind/error.rs +5 -0
- package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
- package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
- package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +98 -3
- package/dist-engine-src/src/sql2/bind/read.rs +65 -0
- package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
- package/dist-engine-src/src/sql2/bind/table.rs +273 -0
- package/dist-engine-src/src/sql2/bind/write.rs +86 -0
- package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
- package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
- package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
- package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
- package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
- package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
- package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
- package/dist-engine-src/src/sql2/context.rs +36 -30
- package/dist-engine-src/src/sql2/error.rs +4 -5
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
- package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
- package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
- package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
- package/dist-engine-src/src/sql2/exec/write.rs +661 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
- package/dist-engine-src/src/sql2/history_projection.rs +8 -8
- package/dist-engine-src/src/sql2/history_route.rs +35 -31
- package/dist-engine-src/src/sql2/mod.rs +30 -24
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
- package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
- package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
- package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
- package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
- package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
- package/dist-engine-src/src/sql2/plan/write.rs +147 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
- package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
- package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
- package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
- package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
- package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
- package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
- package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
- package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
- package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
- package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
- package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
- package/dist-engine-src/src/sql2/read_only.rs +2 -2
- package/dist-engine-src/src/sql2/session.rs +47 -96
- package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
- package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
- package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
- package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
- package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
- package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
- package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
- package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
- package/dist-engine-src/src/storage/conformance.rs +399 -0
- package/dist-engine-src/src/storage/context.rs +552 -288
- package/dist-engine-src/src/storage/mod.rs +48 -10
- package/dist-engine-src/src/storage/point.rs +440 -0
- package/dist-engine-src/src/storage/read_scope.rs +43 -64
- package/dist-engine-src/src/storage/reader.rs +867 -0
- package/dist-engine-src/src/storage/scan.rs +784 -0
- package/dist-engine-src/src/storage/spaces.rs +236 -0
- package/dist-engine-src/src/storage/stats.rs +80 -0
- package/dist-engine-src/src/storage/write_set.rs +962 -0
- package/dist-engine-src/src/storage_bench.rs +136 -4828
- package/dist-engine-src/src/test_support.rs +360 -138
- package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
- package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
- package/dist-engine-src/src/tracked_state/context.rs +1927 -993
- package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
- package/dist-engine-src/src/tracked_state/merge.rs +74 -88
- package/dist-engine-src/src/tracked_state/mod.rs +19 -16
- package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
- package/dist-engine-src/src/tracked_state/storage.rs +243 -191
- package/dist-engine-src/src/tracked_state/tree.rs +247 -371
- package/dist-engine-src/src/tracked_state/types.rs +49 -42
- package/dist-engine-src/src/transaction/bench_support.rs +407 -0
- package/dist-engine-src/src/transaction/commit.rs +821 -713
- package/dist-engine-src/src/transaction/context.rs +705 -600
- package/dist-engine-src/src/transaction/mod.rs +13 -2
- package/dist-engine-src/src/transaction/normalization.rs +63 -76
- package/dist-engine-src/src/transaction/prep.rs +13 -13
- package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
- package/dist-engine-src/src/transaction/staging.rs +228 -434
- package/dist-engine-src/src/transaction/types.rs +41 -98
- package/dist-engine-src/src/transaction/validation.rs +382 -446
- package/dist-engine-src/src/untracked_state/codec.rs +337 -29
- package/dist-engine-src/src/untracked_state/context.rs +7 -7
- package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
- package/dist-engine-src/src/untracked_state/mod.rs +1 -1
- package/dist-engine-src/src/untracked_state/storage.rs +659 -157
- package/dist-engine-src/src/untracked_state/types.rs +21 -21
- package/package.json +71 -68
- package/dist-engine-src/src/backend/kv.rs +0 -358
- package/dist-engine-src/src/backend/testing.rs +0 -658
- package/dist-engine-src/src/commit_store/codec.rs +0 -887
- package/dist-engine-src/src/commit_store/context.rs +0 -944
- package/dist-engine-src/src/commit_store/materialization.rs +0 -84
- package/dist-engine-src/src/commit_store/mod.rs +0 -16
- package/dist-engine-src/src/commit_store/storage.rs +0 -600
- package/dist-engine-src/src/commit_store/types.rs +0 -215
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
- package/dist-engine-src/src/session/create_version.rs +0 -88
- package/dist-engine-src/src/session/merge/apply.rs +0 -23
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
- package/dist-engine-src/src/session/switch_version.rs +0 -109
- package/dist-engine-src/src/sql2/classify.rs +0 -182
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3440
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
- package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
- package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -166
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -25
- package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
- package/dist-engine-src/src/sql2/version_scope.rs +0 -394
- package/dist-engine-src/src/storage/types.rs +0 -501
- package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
- package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
- package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
- package/dist-engine-src/src/version/lifecycle.rs +0 -221
- package/dist-engine-src/src/version/mod.rs +0 -13
- package/dist-engine-src/src/version/refs.rs +0 -330
- package/dist-engine-src/src/version/stage_rows.rs +0 -67
- package/dist-engine-src/src/version/types.rs +0 -21
|
@@ -2,6 +2,7 @@ use std::collections::{BTreeMap, BTreeSet};
|
|
|
2
2
|
|
|
3
3
|
use serde_json::Value as JsonValue;
|
|
4
4
|
|
|
5
|
+
use crate::branch::{BRANCH_DESCRIPTOR_SCHEMA_KEY, BRANCH_REF_SCHEMA_KEY};
|
|
5
6
|
use crate::catalog::{
|
|
6
7
|
CatalogSnapshot, ForeignKeyPlan, SchemaCatalogKey, SchemaPlan, StateDeleteReferencePlan,
|
|
7
8
|
StateForeignKeyPlan,
|
|
@@ -11,7 +12,7 @@ use crate::common::format_json_pointer;
|
|
|
11
12
|
use crate::common::parse_json_pointer;
|
|
12
13
|
use crate::common::{json_pointer_get, validate_row_metadata};
|
|
13
14
|
use crate::domain::{Domain, DomainFileScope, DomainRowIdentity};
|
|
14
|
-
use crate::
|
|
15
|
+
use crate::entity_pk::{canonical_json_text, EntityPk, EntityPkError};
|
|
15
16
|
#[cfg(test)]
|
|
16
17
|
use crate::live_state::LiveStateRowIdentity;
|
|
17
18
|
use crate::live_state::{
|
|
@@ -31,7 +32,6 @@ use crate::transaction::staging::{PreparedValidationRow, PreparedWriteValidation
|
|
|
31
32
|
#[cfg(test)]
|
|
32
33
|
use crate::transaction::types::PreparedStateRow;
|
|
33
34
|
use crate::transaction::types::TransactionWriteOrigin;
|
|
34
|
-
use crate::version::{VERSION_DESCRIPTOR_SCHEMA_KEY, VERSION_REF_SCHEMA_KEY};
|
|
35
35
|
use crate::LixError;
|
|
36
36
|
|
|
37
37
|
const REGISTERED_SCHEMA_KEY: &str = "lix_registered_schema";
|
|
@@ -83,15 +83,15 @@ async fn scan_committed_constraint_rows(
|
|
|
83
83
|
live_state: &dyn LiveStateReader,
|
|
84
84
|
domain: &Domain,
|
|
85
85
|
schema_keys: Vec<String>,
|
|
86
|
-
|
|
86
|
+
entity_pks: Vec<EntityPk>,
|
|
87
87
|
include_tombstones: bool,
|
|
88
88
|
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
89
89
|
let rows = live_state
|
|
90
90
|
.scan_rows(&LiveStateScanRequest {
|
|
91
91
|
filter: LiveStateFilter {
|
|
92
92
|
schema_keys: schema_keys.clone(),
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
entity_pks: entity_pks.clone(),
|
|
94
|
+
branch_ids: vec![domain.branch_id().to_string()],
|
|
95
95
|
file_ids: domain.file_filters(),
|
|
96
96
|
untracked: Some(domain.untracked()),
|
|
97
97
|
include_tombstones,
|
|
@@ -105,7 +105,7 @@ async fn scan_committed_constraint_rows(
|
|
|
105
105
|
.filter(|row| {
|
|
106
106
|
domain.contains(row)
|
|
107
107
|
&& (schema_keys.is_empty() || schema_keys.contains(&row.schema_key))
|
|
108
|
-
&& (
|
|
108
|
+
&& (entity_pks.is_empty() || entity_pks.contains(&row.entity_pk))
|
|
109
109
|
})
|
|
110
110
|
.collect())
|
|
111
111
|
}
|
|
@@ -114,14 +114,14 @@ async fn load_committed_constraint_row(
|
|
|
114
114
|
live_state: &dyn LiveStateReader,
|
|
115
115
|
domain: &Domain,
|
|
116
116
|
schema_key: &str,
|
|
117
|
-
|
|
117
|
+
entity_pk: EntityPk,
|
|
118
118
|
include_tombstones: bool,
|
|
119
119
|
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
120
120
|
Ok(scan_committed_constraint_rows(
|
|
121
121
|
live_state,
|
|
122
122
|
domain,
|
|
123
123
|
vec![schema_key.to_string()],
|
|
124
|
-
vec![
|
|
124
|
+
vec![entity_pk],
|
|
125
125
|
include_tombstones,
|
|
126
126
|
)
|
|
127
127
|
.await?
|
|
@@ -188,7 +188,7 @@ pub(crate) async fn validate_prepared_writes(
|
|
|
188
188
|
validate_committed_delete_restrictions(&input, input.schema_catalog, &pending_constraints)
|
|
189
189
|
.await?;
|
|
190
190
|
validate_file_descriptor_delete_restrictions(&input, &pending_constraints).await?;
|
|
191
|
-
|
|
191
|
+
validate_branch_ref_delete_restrictions(&input, &pending_constraints).await?;
|
|
192
192
|
validate_committed_insert_identities(&input, &pending_constraints).await?;
|
|
193
193
|
validate_committed_unique_constraints(&input, &pending_constraints).await?;
|
|
194
194
|
validate_directory_descriptor_parent_graph(&input, &staged_rows, &constraint_rows).await?;
|
|
@@ -245,7 +245,7 @@ async fn validate_registered_schema_identity_is_canonical(
|
|
|
245
245
|
input.live_state,
|
|
246
246
|
&pending_row.domain().with_exact_file_scope(None),
|
|
247
247
|
REGISTERED_SCHEMA_KEY,
|
|
248
|
-
pending_row.
|
|
248
|
+
pending_row.entity_pk().clone(),
|
|
249
249
|
false,
|
|
250
250
|
)
|
|
251
251
|
.await?
|
|
@@ -347,7 +347,7 @@ fn apply_staged_directory_parent_rows(
|
|
|
347
347
|
{
|
|
348
348
|
continue;
|
|
349
349
|
}
|
|
350
|
-
let id = row.
|
|
350
|
+
let id = row.entity_pk().as_single_string_owned()?;
|
|
351
351
|
let Some(snapshot) = row.snapshot_json() else {
|
|
352
352
|
parents.remove(&id);
|
|
353
353
|
continue;
|
|
@@ -432,27 +432,27 @@ fn optional_snapshot_string(
|
|
|
432
432
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
433
433
|
struct FilesystemNamespaceIdentity {
|
|
434
434
|
schema_key: String,
|
|
435
|
-
|
|
435
|
+
entity_pk: EntityPk,
|
|
436
436
|
}
|
|
437
437
|
|
|
438
438
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
439
439
|
enum FilesystemNamespaceOccupant {
|
|
440
440
|
Directory {
|
|
441
|
-
|
|
441
|
+
entity_pk: EntityPk,
|
|
442
442
|
parent_id: Option<String>,
|
|
443
443
|
name: String,
|
|
444
444
|
},
|
|
445
445
|
File {
|
|
446
|
-
|
|
446
|
+
entity_pk: EntityPk,
|
|
447
447
|
directory_id: Option<String>,
|
|
448
448
|
entry_name: String,
|
|
449
449
|
},
|
|
450
450
|
}
|
|
451
451
|
|
|
452
452
|
impl FilesystemNamespaceOccupant {
|
|
453
|
-
fn
|
|
453
|
+
fn entity_pk(&self) -> &EntityPk {
|
|
454
454
|
match self {
|
|
455
|
-
Self::Directory {
|
|
455
|
+
Self::Directory { entity_pk, .. } | Self::File { entity_pk, .. } => entity_pk,
|
|
456
456
|
}
|
|
457
457
|
}
|
|
458
458
|
|
|
@@ -483,8 +483,8 @@ async fn validate_filesystem_namespace(
|
|
|
483
483
|
staged_rows: &[PreparedValidationRow<'_>],
|
|
484
484
|
) -> Result<(), LixError> {
|
|
485
485
|
// Filesystem namespace constraints are storage-scope local. Global rows are
|
|
486
|
-
// validated in the global scope and may be projected into
|
|
487
|
-
// projected globals do not participate in
|
|
486
|
+
// validated in the global scope and may be projected into branch reads, but
|
|
487
|
+
// projected globals do not participate in branch-local constraint checks.
|
|
488
488
|
let domains = staged_filesystem_namespace_domains(staged_rows);
|
|
489
489
|
for domain in domains {
|
|
490
490
|
let mut occupants =
|
|
@@ -555,7 +555,7 @@ fn apply_staged_filesystem_namespace_rows(
|
|
|
555
555
|
}
|
|
556
556
|
let identity = FilesystemNamespaceIdentity {
|
|
557
557
|
schema_key: row.schema_key().to_string(),
|
|
558
|
-
|
|
558
|
+
entity_pk: row.entity_pk().clone(),
|
|
559
559
|
};
|
|
560
560
|
let Some(snapshot) = row.snapshot_json() else {
|
|
561
561
|
occupants.remove(&identity);
|
|
@@ -577,13 +577,13 @@ fn filesystem_namespace_occupant_from_live_row(
|
|
|
577
577
|
};
|
|
578
578
|
let identity = FilesystemNamespaceIdentity {
|
|
579
579
|
schema_key: row.schema_key.clone(),
|
|
580
|
-
|
|
580
|
+
entity_pk: row.entity_pk.clone(),
|
|
581
581
|
};
|
|
582
582
|
let occupant = match row.schema_key.as_str() {
|
|
583
583
|
DIRECTORY_DESCRIPTOR_SCHEMA_KEY => {
|
|
584
|
-
directory_namespace_occupant(&row.
|
|
584
|
+
directory_namespace_occupant(&row.entity_pk, snapshot_content)?
|
|
585
585
|
}
|
|
586
|
-
FILE_DESCRIPTOR_SCHEMA_KEY => file_namespace_occupant(&row.
|
|
586
|
+
FILE_DESCRIPTOR_SCHEMA_KEY => file_namespace_occupant(&row.entity_pk, snapshot_content)?,
|
|
587
587
|
_ => return Ok(None),
|
|
588
588
|
};
|
|
589
589
|
Ok(Some((identity, occupant)))
|
|
@@ -595,9 +595,9 @@ fn filesystem_namespace_occupant_from_staged_row(
|
|
|
595
595
|
) -> Result<FilesystemNamespaceOccupant, LixError> {
|
|
596
596
|
match row.schema_key() {
|
|
597
597
|
DIRECTORY_DESCRIPTOR_SCHEMA_KEY => {
|
|
598
|
-
directory_namespace_occupant_from_value(row.
|
|
598
|
+
directory_namespace_occupant_from_value(row.entity_pk(), snapshot)
|
|
599
599
|
}
|
|
600
|
-
FILE_DESCRIPTOR_SCHEMA_KEY => file_namespace_occupant_from_value(row.
|
|
600
|
+
FILE_DESCRIPTOR_SCHEMA_KEY => file_namespace_occupant_from_value(row.entity_pk(), snapshot),
|
|
601
601
|
_ => Err(LixError::new(
|
|
602
602
|
LixError::CODE_SCHEMA_VALIDATION,
|
|
603
603
|
format!(
|
|
@@ -609,31 +609,31 @@ fn filesystem_namespace_occupant_from_staged_row(
|
|
|
609
609
|
}
|
|
610
610
|
|
|
611
611
|
fn directory_namespace_occupant(
|
|
612
|
-
|
|
612
|
+
entity_pk: &EntityPk,
|
|
613
613
|
snapshot_content: &str,
|
|
614
614
|
) -> Result<FilesystemNamespaceOccupant, LixError> {
|
|
615
615
|
let snapshot = parse_directory_descriptor_snapshot(snapshot_content)?;
|
|
616
616
|
Ok(FilesystemNamespaceOccupant::Directory {
|
|
617
|
-
|
|
617
|
+
entity_pk: entity_pk.clone(),
|
|
618
618
|
parent_id: snapshot.parent_id,
|
|
619
619
|
name: snapshot.name,
|
|
620
620
|
})
|
|
621
621
|
}
|
|
622
622
|
|
|
623
623
|
fn directory_namespace_occupant_from_value(
|
|
624
|
-
|
|
624
|
+
entity_pk: &EntityPk,
|
|
625
625
|
snapshot: &JsonValue,
|
|
626
626
|
) -> Result<FilesystemNamespaceOccupant, LixError> {
|
|
627
627
|
let snapshot = directory_descriptor_snapshot_from_value(snapshot)?;
|
|
628
628
|
Ok(FilesystemNamespaceOccupant::Directory {
|
|
629
|
-
|
|
629
|
+
entity_pk: entity_pk.clone(),
|
|
630
630
|
parent_id: snapshot.parent_id,
|
|
631
631
|
name: snapshot.name,
|
|
632
632
|
})
|
|
633
633
|
}
|
|
634
634
|
|
|
635
635
|
fn file_namespace_occupant(
|
|
636
|
-
|
|
636
|
+
entity_pk: &EntityPk,
|
|
637
637
|
snapshot_content: &str,
|
|
638
638
|
) -> Result<FilesystemNamespaceOccupant, LixError> {
|
|
639
639
|
let snapshot =
|
|
@@ -644,19 +644,19 @@ fn file_namespace_occupant(
|
|
|
644
644
|
)
|
|
645
645
|
})?;
|
|
646
646
|
Ok(FilesystemNamespaceOccupant::File {
|
|
647
|
-
|
|
647
|
+
entity_pk: entity_pk.clone(),
|
|
648
648
|
directory_id: snapshot.directory_id,
|
|
649
649
|
entry_name: snapshot.name,
|
|
650
650
|
})
|
|
651
651
|
}
|
|
652
652
|
|
|
653
653
|
fn file_namespace_occupant_from_value(
|
|
654
|
-
|
|
654
|
+
entity_pk: &EntityPk,
|
|
655
655
|
snapshot: &JsonValue,
|
|
656
656
|
) -> Result<FilesystemNamespaceOccupant, LixError> {
|
|
657
657
|
let snapshot = file_descriptor_snapshot_from_value(snapshot)?;
|
|
658
658
|
Ok(FilesystemNamespaceOccupant::File {
|
|
659
|
-
|
|
659
|
+
entity_pk: entity_pk.clone(),
|
|
660
660
|
directory_id: snapshot.directory_id,
|
|
661
661
|
entry_name: snapshot.name,
|
|
662
662
|
})
|
|
@@ -693,18 +693,18 @@ fn filesystem_namespace_conflict_error(
|
|
|
693
693
|
) -> LixError {
|
|
694
694
|
let parent = parent_id.as_deref().unwrap_or("<root>");
|
|
695
695
|
let existing_id = existing
|
|
696
|
-
.
|
|
696
|
+
.entity_pk()
|
|
697
697
|
.as_single_string_owned()
|
|
698
|
-
.unwrap_or_else(|_| "<non-string-entity-
|
|
698
|
+
.unwrap_or_else(|_| "<non-string-entity-pk>".to_string());
|
|
699
699
|
let conflicting_id = conflicting
|
|
700
|
-
.
|
|
700
|
+
.entity_pk()
|
|
701
701
|
.as_single_string_owned()
|
|
702
|
-
.unwrap_or_else(|_| "<non-string-entity-
|
|
702
|
+
.unwrap_or_else(|_| "<non-string-entity-pk>".to_string());
|
|
703
703
|
LixError::new(
|
|
704
704
|
LixError::CODE_UNIQUE,
|
|
705
705
|
format!(
|
|
706
|
-
"filesystem namespace conflict in
|
|
707
|
-
domain.
|
|
706
|
+
"filesystem namespace conflict in branch '{}' for parent {parent:?} entry {entry_name:?}: {} '{}' conflicts with {} '{}'",
|
|
707
|
+
domain.branch_id(),
|
|
708
708
|
existing.kind(),
|
|
709
709
|
existing_id,
|
|
710
710
|
conflicting.kind(),
|
|
@@ -756,8 +756,8 @@ fn directory_parent_cycle_error(
|
|
|
756
756
|
LixError::new(
|
|
757
757
|
LixError::CODE_CONSTRAINT_VIOLATION,
|
|
758
758
|
format!(
|
|
759
|
-
"lix_directory_descriptor parent_id cycle in
|
|
760
|
-
scope.domain.
|
|
759
|
+
"lix_directory_descriptor parent_id cycle in branch '{}': directory '{}' reaches ancestor '{}' twice",
|
|
760
|
+
scope.domain.branch_id(), start_id, repeated_id
|
|
761
761
|
),
|
|
762
762
|
)
|
|
763
763
|
.with_hint("Set parent_id to null or to an existing directory outside the directory's descendants.")
|
|
@@ -771,8 +771,8 @@ fn directory_parent_missing_error(
|
|
|
771
771
|
LixError::new(
|
|
772
772
|
LixError::CODE_FOREIGN_KEY,
|
|
773
773
|
format!(
|
|
774
|
-
"lix_directory_descriptor parent_id chain in
|
|
775
|
-
scope.domain.
|
|
774
|
+
"lix_directory_descriptor parent_id chain in branch '{}' for directory '{}' references missing directory '{}'",
|
|
775
|
+
scope.domain.branch_id(), start_id, missing_id
|
|
776
776
|
),
|
|
777
777
|
)
|
|
778
778
|
}
|
|
@@ -781,8 +781,8 @@ fn directory_parent_depth_error(scope: &DirectoryDescriptorScope, start_id: &str
|
|
|
781
781
|
LixError::new(
|
|
782
782
|
LixError::CODE_CONSTRAINT_VIOLATION,
|
|
783
783
|
format!(
|
|
784
|
-
"lix_directory_descriptor parent_id chain in
|
|
785
|
-
scope.domain.
|
|
784
|
+
"lix_directory_descriptor parent_id chain in branch '{}' for directory '{}' exceeds maximum depth {}",
|
|
785
|
+
scope.domain.branch_id(), start_id, MAX_DIRECTORY_PARENT_DEPTH
|
|
786
786
|
),
|
|
787
787
|
)
|
|
788
788
|
}
|
|
@@ -797,12 +797,12 @@ async fn validate_committed_insert_identities(
|
|
|
797
797
|
.map(|target| target.identity.clone())
|
|
798
798
|
.collect::<BTreeSet<_>>();
|
|
799
799
|
let mut checks_by_domain_schema =
|
|
800
|
-
BTreeMap::<(Domain, String), Vec<(
|
|
800
|
+
BTreeMap::<(Domain, String), Vec<(EntityPk, Option<TransactionWriteOrigin>)>>::new();
|
|
801
801
|
for (identity, origin) in input.staged_writes.insert_identities() {
|
|
802
802
|
let pending_identity = DomainRowIdentity::in_domain(
|
|
803
803
|
identity.domain(),
|
|
804
804
|
identity.schema_key().to_string(),
|
|
805
|
-
identity.
|
|
805
|
+
identity.entity_pk().clone(),
|
|
806
806
|
);
|
|
807
807
|
if !pending_identity_targets.contains(&pending_identity) {
|
|
808
808
|
continue;
|
|
@@ -813,66 +813,66 @@ async fn validate_committed_insert_identities(
|
|
|
813
813
|
pending_identity.schema_key_owned(),
|
|
814
814
|
))
|
|
815
815
|
.or_default()
|
|
816
|
-
.push((pending_identity.
|
|
816
|
+
.push((pending_identity.entity_pk_owned(), origin.cloned()));
|
|
817
817
|
}
|
|
818
818
|
|
|
819
819
|
for ((domain, schema_key), checks) in checks_by_domain_schema {
|
|
820
|
-
let
|
|
820
|
+
let entity_pks = checks
|
|
821
821
|
.iter()
|
|
822
|
-
.map(|(
|
|
822
|
+
.map(|(entity_pk, _)| entity_pk.clone())
|
|
823
823
|
.collect::<Vec<_>>();
|
|
824
824
|
let committed_rows = scan_committed_constraint_rows(
|
|
825
825
|
input.live_state,
|
|
826
826
|
&domain,
|
|
827
827
|
vec![schema_key.clone()],
|
|
828
|
-
|
|
828
|
+
entity_pks,
|
|
829
829
|
false,
|
|
830
830
|
)
|
|
831
831
|
.await?;
|
|
832
|
-
let
|
|
832
|
+
let committed_rows_by_entity_pk = committed_rows
|
|
833
833
|
.into_iter()
|
|
834
834
|
.filter(|row| {
|
|
835
835
|
row.snapshot_content.is_some() && !pending_constraints.tombstones_identity(row)
|
|
836
836
|
})
|
|
837
|
-
.map(|row| (row.
|
|
837
|
+
.map(|row| (row.entity_pk.clone(), row))
|
|
838
838
|
.collect::<BTreeMap<_, _>>();
|
|
839
|
-
for (
|
|
840
|
-
if !
|
|
839
|
+
for (entity_pk, origin) in checks {
|
|
840
|
+
if !committed_rows_by_entity_pk.contains_key(&entity_pk) {
|
|
841
841
|
continue;
|
|
842
842
|
}
|
|
843
843
|
return Err(LixError::new(
|
|
844
844
|
LixError::CODE_UNIQUE,
|
|
845
|
-
duplicate_insert_identity_message(&schema_key, &
|
|
845
|
+
duplicate_insert_identity_message(&schema_key, &entity_pk, None, origin.as_ref()),
|
|
846
846
|
));
|
|
847
847
|
}
|
|
848
848
|
}
|
|
849
849
|
Ok(())
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
-
async fn
|
|
852
|
+
async fn validate_branch_ref_delete_restrictions(
|
|
853
853
|
input: &TransactionValidationInput<'_>,
|
|
854
854
|
pending_constraints: &PendingConstraintIndexes,
|
|
855
855
|
) -> Result<(), LixError> {
|
|
856
856
|
for tombstone in &pending_constraints.tombstones {
|
|
857
|
-
if tombstone.identity.schema_key() !=
|
|
857
|
+
if tombstone.identity.schema_key() != BRANCH_REF_SCHEMA_KEY {
|
|
858
858
|
continue;
|
|
859
859
|
}
|
|
860
860
|
|
|
861
861
|
for source_domain in tombstone
|
|
862
862
|
.identity
|
|
863
863
|
.domain()
|
|
864
|
-
.
|
|
864
|
+
.branch_descriptor_domains_for_ref_delete()
|
|
865
865
|
{
|
|
866
866
|
let descriptor_identity = DomainRowIdentity::in_domain(
|
|
867
867
|
source_domain,
|
|
868
|
-
|
|
869
|
-
tombstone.identity.
|
|
868
|
+
BRANCH_DESCRIPTOR_SCHEMA_KEY,
|
|
869
|
+
tombstone.identity.entity_pk_owned(),
|
|
870
870
|
);
|
|
871
871
|
if pending_constraints.tombstones_target_identity(&descriptor_identity) {
|
|
872
872
|
continue;
|
|
873
873
|
}
|
|
874
874
|
if pending_constraints.has_identity_target(&descriptor_identity) {
|
|
875
|
-
return Err(
|
|
875
|
+
return Err(branch_ref_delete_restriction_error(
|
|
876
876
|
&tombstone.identity,
|
|
877
877
|
&descriptor_identity,
|
|
878
878
|
)?);
|
|
@@ -882,7 +882,7 @@ async fn validate_version_ref_delete_restrictions(
|
|
|
882
882
|
input.live_state,
|
|
883
883
|
descriptor_identity.domain(),
|
|
884
884
|
descriptor_identity.schema_key(),
|
|
885
|
-
descriptor_identity.
|
|
885
|
+
descriptor_identity.entity_pk_owned(),
|
|
886
886
|
false,
|
|
887
887
|
)
|
|
888
888
|
.await?
|
|
@@ -892,7 +892,7 @@ async fn validate_version_ref_delete_restrictions(
|
|
|
892
892
|
if descriptor_row.snapshot_content.is_some()
|
|
893
893
|
&& !pending_constraints.tombstones_identity(&descriptor_row)
|
|
894
894
|
{
|
|
895
|
-
return Err(
|
|
895
|
+
return Err(branch_ref_delete_restriction_error(
|
|
896
896
|
&tombstone.identity,
|
|
897
897
|
&descriptor_identity,
|
|
898
898
|
)?);
|
|
@@ -902,19 +902,19 @@ async fn validate_version_ref_delete_restrictions(
|
|
|
902
902
|
Ok(())
|
|
903
903
|
}
|
|
904
904
|
|
|
905
|
-
fn
|
|
905
|
+
fn branch_ref_delete_restriction_error(
|
|
906
906
|
ref_identity: &DomainRowIdentity,
|
|
907
907
|
descriptor_identity: &DomainRowIdentity,
|
|
908
908
|
) -> Result<LixError, LixError> {
|
|
909
909
|
Ok(LixError::new(
|
|
910
910
|
LixError::CODE_FOREIGN_KEY,
|
|
911
911
|
format!(
|
|
912
|
-
"cannot delete '{}' row '{}' in
|
|
912
|
+
"cannot delete '{}' row '{}' in branch '{}' because matching '{}' row '{}' would remain without a branch ref",
|
|
913
913
|
ref_identity.schema_key(),
|
|
914
|
-
ref_identity.
|
|
915
|
-
ref_identity.domain().
|
|
914
|
+
ref_identity.entity_pk().as_single_string_owned()?,
|
|
915
|
+
ref_identity.domain().branch_id(),
|
|
916
916
|
descriptor_identity.schema_key(),
|
|
917
|
-
descriptor_identity.
|
|
917
|
+
descriptor_identity.entity_pk().as_single_string_owned()?,
|
|
918
918
|
),
|
|
919
919
|
))
|
|
920
920
|
}
|
|
@@ -937,7 +937,7 @@ impl PendingFileDescriptorIndex {
|
|
|
937
937
|
if row.schema_key() != FILE_DESCRIPTOR_SCHEMA_KEY || row.file_id().is_some() {
|
|
938
938
|
continue;
|
|
939
939
|
}
|
|
940
|
-
if row.
|
|
940
|
+
if row.entity_pk().as_single_string_owned().is_ok() {
|
|
941
941
|
let state = if (*row).snapshot_json().is_some() {
|
|
942
942
|
PendingFileDescriptorState::Present
|
|
943
943
|
} else {
|
|
@@ -958,7 +958,7 @@ impl PendingFileDescriptorIndex {
|
|
|
958
958
|
.get(&DomainRowIdentity::in_domain(
|
|
959
959
|
domain.with_exact_file_scope(None),
|
|
960
960
|
FILE_DESCRIPTOR_SCHEMA_KEY,
|
|
961
|
-
|
|
961
|
+
EntityPk::single(file_id),
|
|
962
962
|
))
|
|
963
963
|
.copied()
|
|
964
964
|
}
|
|
@@ -1009,7 +1009,7 @@ async fn committed_file_descriptor_exists_in_domain(
|
|
|
1009
1009
|
live_state,
|
|
1010
1010
|
&domain.with_exact_file_scope(None),
|
|
1011
1011
|
FILE_DESCRIPTOR_SCHEMA_KEY,
|
|
1012
|
-
|
|
1012
|
+
EntityPk::single(file_id),
|
|
1013
1013
|
false,
|
|
1014
1014
|
)
|
|
1015
1015
|
.await?
|
|
@@ -1018,7 +1018,7 @@ async fn committed_file_descriptor_exists_in_domain(
|
|
|
1018
1018
|
};
|
|
1019
1019
|
Ok(row.snapshot_content.is_some()
|
|
1020
1020
|
&& row.schema_key == FILE_DESCRIPTOR_SCHEMA_KEY
|
|
1021
|
-
&& row.
|
|
1021
|
+
&& row.entity_pk == EntityPk::single(file_id)
|
|
1022
1022
|
&& row.file_id.is_none())
|
|
1023
1023
|
}
|
|
1024
1024
|
|
|
@@ -1029,11 +1029,11 @@ fn missing_file_owner_reference_error(
|
|
|
1029
1029
|
Ok(LixError::new(
|
|
1030
1030
|
LixError::CODE_FILE_NOT_FOUND,
|
|
1031
1031
|
format!(
|
|
1032
|
-
"file ownership validation failed for schema '{}': entity '{}' references missing file_id '{}' in effective file scope for
|
|
1032
|
+
"file ownership validation failed for schema '{}': entity '{}' references missing file_id '{}' in effective file scope for branch '{}'",
|
|
1033
1033
|
row.schema_key(),
|
|
1034
|
-
row.
|
|
1034
|
+
row.entity_pk().as_json_array_text()?,
|
|
1035
1035
|
file_id,
|
|
1036
|
-
row.
|
|
1036
|
+
row.branch_id()
|
|
1037
1037
|
),
|
|
1038
1038
|
)
|
|
1039
1039
|
.with_hint("Insert a row into lix_file with this id first, or use null for a global entity."))
|
|
@@ -1051,7 +1051,7 @@ fn validate_staged_row_shape(row: PreparedValidationRow<'_>) -> Result<(), LixEr
|
|
|
1051
1051
|
LixError::CODE_SCHEMA_DEFINITION,
|
|
1052
1052
|
"lix_registered_schema rows must not be scoped to a file",
|
|
1053
1053
|
)
|
|
1054
|
-
.with_hint("Schema definitions are scoped by
|
|
1054
|
+
.with_hint("Schema definitions are scoped by branch and durability only; write them with null file_id."));
|
|
1055
1055
|
}
|
|
1056
1056
|
Ok(())
|
|
1057
1057
|
}
|
|
@@ -1181,15 +1181,15 @@ fn validate_primary_key_identity(
|
|
|
1181
1181
|
let Some(primary_key_paths) = schema_plan.primary_key.as_ref() else {
|
|
1182
1182
|
return Ok(());
|
|
1183
1183
|
};
|
|
1184
|
-
let derived =
|
|
1184
|
+
let derived = EntityPk::from_primary_key_paths(snapshot, &primary_key_paths)
|
|
1185
1185
|
.map_err(|error| primary_key_identity_error(row, &primary_key_paths, error))?;
|
|
1186
|
-
if row.
|
|
1186
|
+
if row.entity_pk() != &derived {
|
|
1187
1187
|
return Err(LixError::new(
|
|
1188
1188
|
LixError::CODE_UNIQUE,
|
|
1189
1189
|
format!(
|
|
1190
|
-
"primary-key constraint violation on schema '{}':
|
|
1190
|
+
"primary-key constraint violation on schema '{}': entity_pk '{}' does not match derived primary key '{}'",
|
|
1191
1191
|
row.schema_key(),
|
|
1192
|
-
row.
|
|
1192
|
+
row.entity_pk().as_json_array_text()?,
|
|
1193
1193
|
derived.as_json_array_text()?
|
|
1194
1194
|
),
|
|
1195
1195
|
));
|
|
@@ -1199,7 +1199,7 @@ fn validate_primary_key_identity(
|
|
|
1199
1199
|
|
|
1200
1200
|
#[derive(Default)]
|
|
1201
1201
|
struct PendingConstraintIndexes {
|
|
1202
|
-
unique_values: BTreeMap<PendingUniqueKey,
|
|
1202
|
+
unique_values: BTreeMap<PendingUniqueKey, EntityPk>,
|
|
1203
1203
|
identity_targets: Vec<PendingIdentityTarget>,
|
|
1204
1204
|
fk_targets: BTreeMap<PendingForeignKeyTargetKey, Vec<PendingForeignKeyTarget>>,
|
|
1205
1205
|
fk_references: BTreeMap<PendingForeignKeyReferenceTarget, Vec<PendingForeignKeyReference>>,
|
|
@@ -1259,11 +1259,11 @@ impl PendingConstraintIndexes {
|
|
|
1259
1259
|
pointer_group: unique_paths.clone(),
|
|
1260
1260
|
value,
|
|
1261
1261
|
};
|
|
1262
|
-
if let Some(
|
|
1262
|
+
if let Some(existing_entity_pk) = self
|
|
1263
1263
|
.unique_values
|
|
1264
|
-
.insert(key.clone(), row.
|
|
1264
|
+
.insert(key.clone(), row.entity_pk().clone())
|
|
1265
1265
|
{
|
|
1266
|
-
if
|
|
1266
|
+
if existing_entity_pk != *row.entity_pk() {
|
|
1267
1267
|
return Err(LixError::new(
|
|
1268
1268
|
LixError::CODE_UNIQUE,
|
|
1269
1269
|
format!(
|
|
@@ -1271,8 +1271,8 @@ impl PendingConstraintIndexes {
|
|
|
1271
1271
|
row.schema_key(),
|
|
1272
1272
|
format_pointer_group(&key.pointer_group),
|
|
1273
1273
|
key.value.display(),
|
|
1274
|
-
|
|
1275
|
-
row.
|
|
1274
|
+
existing_entity_pk.as_json_array_text()?,
|
|
1275
|
+
row.entity_pk().as_json_array_text()?
|
|
1276
1276
|
),
|
|
1277
1277
|
));
|
|
1278
1278
|
}
|
|
@@ -1299,7 +1299,7 @@ impl PendingConstraintIndexes {
|
|
|
1299
1299
|
})
|
|
1300
1300
|
.or_default()
|
|
1301
1301
|
.push(PendingForeignKeyTarget {
|
|
1302
|
-
|
|
1302
|
+
entity_pk: row.entity_pk().clone(),
|
|
1303
1303
|
});
|
|
1304
1304
|
}
|
|
1305
1305
|
|
|
@@ -1412,7 +1412,7 @@ impl PendingConstraintIndexes {
|
|
|
1412
1412
|
fn has_fk_reference_to_key(
|
|
1413
1413
|
&self,
|
|
1414
1414
|
schema_key: &str,
|
|
1415
|
-
|
|
1415
|
+
branch_id: &str,
|
|
1416
1416
|
file_id: Option<&str>,
|
|
1417
1417
|
pointer_group: &[&str],
|
|
1418
1418
|
value: UniqueConstraintValue,
|
|
@@ -1423,7 +1423,7 @@ impl PendingConstraintIndexes {
|
|
|
1423
1423
|
.collect::<Result<Vec<_>, _>>()?;
|
|
1424
1424
|
let key = PendingForeignKeyReferenceTarget::Key(PendingForeignKeyTargetKey {
|
|
1425
1425
|
schema_key: schema_key.to_string(),
|
|
1426
|
-
domain: Domain::exact_file(
|
|
1426
|
+
domain: Domain::exact_file(branch_id.to_string(), false, file_id.map(str::to_string)),
|
|
1427
1427
|
pointer_group,
|
|
1428
1428
|
value,
|
|
1429
1429
|
});
|
|
@@ -1442,7 +1442,7 @@ impl PendingConstraintIndexes {
|
|
|
1442
1442
|
fn has_fk_target(
|
|
1443
1443
|
&self,
|
|
1444
1444
|
schema_key: &str,
|
|
1445
|
-
|
|
1445
|
+
branch_id: &str,
|
|
1446
1446
|
file_id: Option<&str>,
|
|
1447
1447
|
pointer_group: &[&str],
|
|
1448
1448
|
value: UniqueConstraintValue,
|
|
@@ -1453,7 +1453,7 @@ impl PendingConstraintIndexes {
|
|
|
1453
1453
|
.collect::<Result<Vec<_>, _>>()?;
|
|
1454
1454
|
let key = PendingForeignKeyTargetKey {
|
|
1455
1455
|
schema_key: schema_key.to_string(),
|
|
1456
|
-
domain: Domain::exact_file(
|
|
1456
|
+
domain: Domain::exact_file(branch_id.to_string(), false, file_id.map(str::to_string)),
|
|
1457
1457
|
pointer_group,
|
|
1458
1458
|
value,
|
|
1459
1459
|
};
|
|
@@ -1473,7 +1473,7 @@ struct PendingIdentityTarget {
|
|
|
1473
1473
|
|
|
1474
1474
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
1475
1475
|
struct PendingForeignKeyTarget {
|
|
1476
|
-
|
|
1476
|
+
entity_pk: EntityPk,
|
|
1477
1477
|
}
|
|
1478
1478
|
|
|
1479
1479
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
@@ -1552,8 +1552,8 @@ fn validate_pending_delete_restrictions(
|
|
|
1552
1552
|
schema_key: tombstone.identity.schema_key_owned(),
|
|
1553
1553
|
domain,
|
|
1554
1554
|
pointer_group: primary_key_paths.clone(),
|
|
1555
|
-
value: UniqueConstraintValue::
|
|
1556
|
-
tombstone.identity.
|
|
1555
|
+
value: UniqueConstraintValue::from_entity_pk(
|
|
1556
|
+
tombstone.identity.entity_pk(),
|
|
1557
1557
|
),
|
|
1558
1558
|
})
|
|
1559
1559
|
})
|
|
@@ -1582,11 +1582,11 @@ fn reject_pending_delete_references(
|
|
|
1582
1582
|
Err(LixError::new(
|
|
1583
1583
|
LixError::CODE_FOREIGN_KEY,
|
|
1584
1584
|
format!(
|
|
1585
|
-
"cannot delete '{}' row '{}' in
|
|
1585
|
+
"cannot delete '{}' row '{}' in branch '{}' because pending row '{}' references it{}",
|
|
1586
1586
|
deleted_identity.schema_key(),
|
|
1587
|
-
deleted_identity.
|
|
1588
|
-
deleted_identity.domain().
|
|
1589
|
-
reference.identity.
|
|
1587
|
+
deleted_identity.entity_pk().as_json_array_text()?,
|
|
1588
|
+
deleted_identity.domain().branch_id(),
|
|
1589
|
+
reference.identity.entity_pk().as_json_array_text()?,
|
|
1590
1590
|
pending_foreign_key_reference_target_description(target)?
|
|
1591
1591
|
),
|
|
1592
1592
|
))
|
|
@@ -1605,7 +1605,7 @@ fn pending_foreign_key_reference_target_description(
|
|
|
1605
1605
|
PendingForeignKeyReferenceTarget::StateSurfaceIdentity(target) => Ok(format!(
|
|
1606
1606
|
" through '{}:{}'",
|
|
1607
1607
|
target.schema_key(),
|
|
1608
|
-
target.
|
|
1608
|
+
target.entity_pk().as_json_array_text()?
|
|
1609
1609
|
)),
|
|
1610
1610
|
}
|
|
1611
1611
|
}
|
|
@@ -1672,7 +1672,7 @@ async fn validate_file_descriptor_delete_restrictions(
|
|
|
1672
1672
|
if !tombstone.identity.domain().is_exact_file(&None) {
|
|
1673
1673
|
continue;
|
|
1674
1674
|
}
|
|
1675
|
-
let file_id = tombstone.identity.
|
|
1675
|
+
let file_id = tombstone.identity.entity_pk().as_single_string_owned()?;
|
|
1676
1676
|
for source_domain in tombstone
|
|
1677
1677
|
.identity
|
|
1678
1678
|
.domain()
|
|
@@ -1694,10 +1694,10 @@ async fn validate_file_descriptor_delete_restrictions(
|
|
|
1694
1694
|
return Err(LixError::new(
|
|
1695
1695
|
LixError::CODE_FOREIGN_KEY,
|
|
1696
1696
|
format!(
|
|
1697
|
-
"cannot delete file descriptor '{}' in
|
|
1697
|
+
"cannot delete file descriptor '{}' in branch '{}' because committed row '{}' in schema '{}' is still scoped to that file",
|
|
1698
1698
|
file_id,
|
|
1699
|
-
tombstone.identity.domain().
|
|
1700
|
-
row.
|
|
1699
|
+
tombstone.identity.domain().branch_id(),
|
|
1700
|
+
row.entity_pk.as_json_array_text()?,
|
|
1701
1701
|
row.schema_key,
|
|
1702
1702
|
),
|
|
1703
1703
|
));
|
|
@@ -1810,7 +1810,7 @@ async fn committed_deleted_row_value(
|
|
|
1810
1810
|
live_state,
|
|
1811
1811
|
tombstone.identity.domain(),
|
|
1812
1812
|
tombstone.identity.schema_key(),
|
|
1813
|
-
tombstone.identity.
|
|
1813
|
+
tombstone.identity.entity_pk_owned(),
|
|
1814
1814
|
true,
|
|
1815
1815
|
)
|
|
1816
1816
|
.await?
|
|
@@ -1835,11 +1835,11 @@ fn committed_delete_restriction_error(
|
|
|
1835
1835
|
Ok(LixError::new(
|
|
1836
1836
|
LixError::CODE_FOREIGN_KEY,
|
|
1837
1837
|
format!(
|
|
1838
|
-
"cannot delete '{}' row '{}' in
|
|
1838
|
+
"cannot delete '{}' row '{}' in branch '{}' because committed row '{}' references it through {}",
|
|
1839
1839
|
deleted_identity.schema_key(),
|
|
1840
|
-
deleted_identity.
|
|
1841
|
-
deleted_identity.domain().
|
|
1842
|
-
referencing_row.
|
|
1840
|
+
deleted_identity.entity_pk().as_json_array_text()?,
|
|
1841
|
+
deleted_identity.domain().branch_id(),
|
|
1842
|
+
referencing_row.entity_pk.as_json_array_text()?,
|
|
1843
1843
|
format_pointer_group(local_properties)
|
|
1844
1844
|
),
|
|
1845
1845
|
))
|
|
@@ -1993,11 +1993,11 @@ fn reject_unresolved_foreign_keys(
|
|
|
1993
1993
|
Err(LixError::new(
|
|
1994
1994
|
LixError::CODE_FOREIGN_KEY,
|
|
1995
1995
|
format!(
|
|
1996
|
-
"foreign key on schema '{}' row '{}' via {} has no matching target in
|
|
1996
|
+
"foreign key on schema '{}' row '{}' via {} has no matching target in branch '{}'{}",
|
|
1997
1997
|
check.source_schema_key,
|
|
1998
|
-
check.source_identity.
|
|
1998
|
+
check.source_identity.entity_pk().as_json_array_text()?,
|
|
1999
1999
|
format_pointer_group(&check.source_pointer_group),
|
|
2000
|
-
check.source_identity.domain().
|
|
2000
|
+
check.source_identity.domain().branch_id(),
|
|
2001
2001
|
unresolved_foreign_key_target_description(&check.target)?
|
|
2002
2002
|
),
|
|
2003
2003
|
))
|
|
@@ -2016,7 +2016,7 @@ fn unresolved_foreign_key_target_description(
|
|
|
2016
2016
|
UnresolvedForeignKeyTarget::StateSurfaceIdentity(target) => Ok(format!(
|
|
2017
2017
|
" for target '{}:{}'",
|
|
2018
2018
|
target.schema_key(),
|
|
2019
|
-
target.
|
|
2019
|
+
target.entity_pk().as_json_array_text()?
|
|
2020
2020
|
)),
|
|
2021
2021
|
}
|
|
2022
2022
|
}
|
|
@@ -2076,7 +2076,7 @@ async fn committed_state_surface_foreign_key_target_exists(
|
|
|
2076
2076
|
live_state,
|
|
2077
2077
|
candidate.domain(),
|
|
2078
2078
|
vec![candidate.schema_key_owned()],
|
|
2079
|
-
vec![candidate.
|
|
2079
|
+
vec![candidate.entity_pk_owned()],
|
|
2080
2080
|
false,
|
|
2081
2081
|
)
|
|
2082
2082
|
.await?;
|
|
@@ -2084,7 +2084,7 @@ async fn committed_state_surface_foreign_key_target_exists(
|
|
|
2084
2084
|
if pending_constraints.tombstones_identity(&row) {
|
|
2085
2085
|
continue;
|
|
2086
2086
|
}
|
|
2087
|
-
if candidate.matches_parts(&Domain::for_live_row(&row), &row.schema_key, &row.
|
|
2087
|
+
if candidate.matches_parts(&Domain::for_live_row(&row), &row.schema_key, &row.entity_pk)
|
|
2088
2088
|
{
|
|
2089
2089
|
return Ok(true);
|
|
2090
2090
|
}
|
|
@@ -2098,8 +2098,8 @@ fn state_surface_target_identity(
|
|
|
2098
2098
|
foreign_key: &StateForeignKeyPlan,
|
|
2099
2099
|
snapshot: &JsonValue,
|
|
2100
2100
|
) -> Result<DomainRowIdentity, LixError> {
|
|
2101
|
-
let
|
|
2102
|
-
state_surface_local_json_value(snapshot, &foreign_key.
|
|
2101
|
+
let entity_pk =
|
|
2102
|
+
state_surface_local_json_value(snapshot, &foreign_key.entity_pk_property, "entity_pk")?;
|
|
2103
2103
|
let schema_key =
|
|
2104
2104
|
state_surface_local_value(snapshot, &foreign_key.schema_key_property, "schema_key")?;
|
|
2105
2105
|
let file_id =
|
|
@@ -2107,10 +2107,10 @@ fn state_surface_target_identity(
|
|
|
2107
2107
|
Ok(DomainRowIdentity::in_domain(
|
|
2108
2108
|
source_domain.with_exact_file_scope(file_id),
|
|
2109
2109
|
schema_key,
|
|
2110
|
-
|
|
2110
|
+
EntityPk::from_json_array_value(entity_pk).map_err(|error| {
|
|
2111
2111
|
LixError::new(
|
|
2112
2112
|
LixError::CODE_FOREIGN_KEY,
|
|
2113
|
-
format!("state-surface foreign key
|
|
2113
|
+
format!("state-surface foreign key entity_pk is invalid: {error}"),
|
|
2114
2114
|
)
|
|
2115
2115
|
})?,
|
|
2116
2116
|
))
|
|
@@ -2200,15 +2200,15 @@ async fn validate_committed_unique_constraints(
|
|
|
2200
2200
|
) -> Result<(), LixError> {
|
|
2201
2201
|
let mut pending_by_scope = BTreeMap::<
|
|
2202
2202
|
PendingUniqueConstraintScope,
|
|
2203
|
-
BTreeMap<UniqueConstraintValue, Vec<&
|
|
2203
|
+
BTreeMap<UniqueConstraintValue, Vec<&EntityPk>>,
|
|
2204
2204
|
>::new();
|
|
2205
|
-
for (key,
|
|
2205
|
+
for (key, pending_entity_pk) in &pending_constraints.unique_values {
|
|
2206
2206
|
pending_by_scope
|
|
2207
2207
|
.entry(PendingUniqueConstraintScope::from(key))
|
|
2208
2208
|
.or_default()
|
|
2209
2209
|
.entry(key.value.clone())
|
|
2210
2210
|
.or_default()
|
|
2211
|
-
.push(
|
|
2211
|
+
.push(pending_entity_pk);
|
|
2212
2212
|
}
|
|
2213
2213
|
|
|
2214
2214
|
for (scope, pending_values) in pending_by_scope {
|
|
@@ -2246,11 +2246,11 @@ async fn validate_committed_unique_constraints(
|
|
|
2246
2246
|
else {
|
|
2247
2247
|
continue;
|
|
2248
2248
|
};
|
|
2249
|
-
let Some(
|
|
2249
|
+
let Some(pending_entity_pks) = pending_values.get(&committed_value) else {
|
|
2250
2250
|
continue;
|
|
2251
2251
|
};
|
|
2252
|
-
for
|
|
2253
|
-
if committed_row.
|
|
2252
|
+
for pending_entity_pk in pending_entity_pks {
|
|
2253
|
+
if committed_row.entity_pk == **pending_entity_pk {
|
|
2254
2254
|
continue;
|
|
2255
2255
|
}
|
|
2256
2256
|
return Err(LixError::new(
|
|
@@ -2260,8 +2260,8 @@ async fn validate_committed_unique_constraints(
|
|
|
2260
2260
|
scope.schema_key,
|
|
2261
2261
|
format_pointer_group(&scope.pointer_group),
|
|
2262
2262
|
committed_value.display(),
|
|
2263
|
-
committed_row.
|
|
2264
|
-
|
|
2263
|
+
committed_row.entity_pk.as_json_array_text()?,
|
|
2264
|
+
pending_entity_pk.as_json_array_text()?
|
|
2265
2265
|
),
|
|
2266
2266
|
));
|
|
2267
2267
|
}
|
|
@@ -2275,8 +2275,8 @@ fn committed_row_is_in_exact_unique_scope(
|
|
|
2275
2275
|
scope: &PendingUniqueConstraintScope,
|
|
2276
2276
|
) -> bool {
|
|
2277
2277
|
// LiveStateReader may return serving projections such as global rows
|
|
2278
|
-
// projected into a requested
|
|
2279
|
-
// only rows authored in the exact
|
|
2278
|
+
// projected into a requested branch. Constraint validation is root-local:
|
|
2279
|
+
// only rows authored in the exact branch participate.
|
|
2280
2280
|
scope.domain.contains(row) && row.schema_key == scope.schema_key
|
|
2281
2281
|
}
|
|
2282
2282
|
|
|
@@ -2294,7 +2294,7 @@ impl UniqueConstraintValue {
|
|
|
2294
2294
|
)
|
|
2295
2295
|
}
|
|
2296
2296
|
|
|
2297
|
-
fn
|
|
2297
|
+
fn from_entity_pk(identity: &EntityPk) -> Self {
|
|
2298
2298
|
Self(
|
|
2299
2299
|
identity
|
|
2300
2300
|
.parts
|
|
@@ -2360,31 +2360,22 @@ fn format_pointer_group(group: &[Vec<String>]) -> String {
|
|
|
2360
2360
|
fn primary_key_identity_error(
|
|
2361
2361
|
row: PreparedValidationRow<'_>,
|
|
2362
2362
|
primary_key_paths: &[Vec<String>],
|
|
2363
|
-
error:
|
|
2363
|
+
error: EntityPkError,
|
|
2364
2364
|
) -> LixError {
|
|
2365
2365
|
let reason = match error {
|
|
2366
|
-
|
|
2367
|
-
|
|
2366
|
+
EntityPkError::EmptyPrimaryKey => "empty x-lix-primary-key".to_string(),
|
|
2367
|
+
EntityPkError::EmptyPrimaryKeyPath { index } => {
|
|
2368
2368
|
format!("empty x-lix-primary-key pointer at index {index}")
|
|
2369
2369
|
}
|
|
2370
|
-
|
|
2371
|
-
let pointer = primary_key_paths
|
|
2372
|
-
.get(index)
|
|
2373
|
-
.map(|path| format_json_pointer(path))
|
|
2374
|
-
.unwrap_or_else(|| format!("index {index}"));
|
|
2375
|
-
format!("empty value at primary-key pointer '{pointer}'")
|
|
2376
|
-
}
|
|
2377
|
-
EntityIdentityError::MissingPrimaryKeyValue { index } => {
|
|
2370
|
+
EntityPkError::MissingPrimaryKeyValue { index } => {
|
|
2378
2371
|
let pointer = format_json_pointer(&primary_key_paths[index]);
|
|
2379
2372
|
format!("missing value at primary-key pointer '{pointer}'")
|
|
2380
2373
|
}
|
|
2381
|
-
|
|
2374
|
+
EntityPkError::UnsupportedPrimaryKeyValue { index } => {
|
|
2382
2375
|
let pointer = format_json_pointer(&primary_key_paths[index]);
|
|
2383
2376
|
format!("non-string value at primary-key pointer '{pointer}'")
|
|
2384
2377
|
}
|
|
2385
|
-
|
|
2386
|
-
"invalid encoded entity identity".to_string()
|
|
2387
|
-
}
|
|
2378
|
+
EntityPkError::InvalidEncodedEntityPk => "invalid encoded entity primary key".to_string(),
|
|
2388
2379
|
};
|
|
2389
2380
|
LixError::new(
|
|
2390
2381
|
LixError::CODE_UNIQUE,
|
|
@@ -2418,7 +2409,7 @@ fn validate_foreign_key_definition(
|
|
|
2418
2409
|
return Err(LixError::new(
|
|
2419
2410
|
LixError::CODE_SCHEMA_DEFINITION,
|
|
2420
2411
|
format!(
|
|
2421
|
-
"foreign key on schema '{}' must not reference schemaKey 'lix_state'; use x-lix-state-foreign-keys with pointers ordered as [
|
|
2412
|
+
"foreign key on schema '{}' must not reference schemaKey 'lix_state'; use x-lix-state-foreign-keys with pointers ordered as [entity_pk, schema_key, file_id]",
|
|
2422
2413
|
source_key.schema_key
|
|
2423
2414
|
),
|
|
2424
2415
|
));
|
|
@@ -2688,8 +2679,7 @@ mod tests {
|
|
|
2688
2679
|
visible_schemas: &[JsonValue],
|
|
2689
2680
|
) -> Result<CatalogSnapshot, LixError> {
|
|
2690
2681
|
let catalog = catalog_from_transaction_parts_unchecked(staged_writes, visible_schemas)?;
|
|
2691
|
-
let mut pending_keys =
|
|
2692
|
-
BTreeMap::<SchemaCatalogKey, crate::entity_identity::EntityIdentity>::new();
|
|
2682
|
+
let mut pending_keys = BTreeMap::<SchemaCatalogKey, crate::entity_pk::EntityPk>::new();
|
|
2693
2683
|
for row in staged_writes
|
|
2694
2684
|
.validation_rows()
|
|
2695
2685
|
.filter(|row| row.schema_key() == REGISTERED_SCHEMA_KEY)
|
|
@@ -2711,16 +2701,16 @@ mod tests {
|
|
|
2711
2701
|
})?;
|
|
2712
2702
|
let (key, _) = schema_from_registered_snapshot(&snapshot)?;
|
|
2713
2703
|
let catalog_key = SchemaCatalogKey::from_schema_key(key);
|
|
2714
|
-
if let Some(
|
|
2715
|
-
pending_keys.insert(catalog_key.clone(), row.
|
|
2704
|
+
if let Some(existing_entity_pk) =
|
|
2705
|
+
pending_keys.insert(catalog_key.clone(), row.entity_pk().clone())
|
|
2716
2706
|
{
|
|
2717
2707
|
return Err(LixError::new(
|
|
2718
2708
|
LixError::CODE_SCHEMA_DEFINITION,
|
|
2719
2709
|
format!(
|
|
2720
2710
|
"duplicate pending registered schema '{}' in transaction: rows '{}' and '{}'",
|
|
2721
2711
|
catalog_key.schema_key,
|
|
2722
|
-
|
|
2723
|
-
row.
|
|
2712
|
+
existing_entity_pk.as_json_array_text()?,
|
|
2713
|
+
row.entity_pk().as_json_array_text()?
|
|
2724
2714
|
),
|
|
2725
2715
|
));
|
|
2726
2716
|
}
|
|
@@ -2784,8 +2774,8 @@ mod tests {
|
|
|
2784
2774
|
.chain(test_file_descriptor_rows())
|
|
2785
2775
|
.find(|row| {
|
|
2786
2776
|
row.schema_key == request.schema_key
|
|
2787
|
-
&& row.
|
|
2788
|
-
&& row.
|
|
2777
|
+
&& row.branch_id == request.branch_id
|
|
2778
|
+
&& row.entity_pk == request.entity_pk
|
|
2789
2779
|
&& request.file_id.matches(row.file_id.as_ref())
|
|
2790
2780
|
}))
|
|
2791
2781
|
}
|
|
@@ -2834,8 +2824,8 @@ mod tests {
|
|
|
2834
2824
|
.scan_rows(&LiveStateScanRequest {
|
|
2835
2825
|
filter: LiveStateFilter {
|
|
2836
2826
|
schema_keys: vec![request.schema_key.clone()],
|
|
2837
|
-
|
|
2838
|
-
|
|
2827
|
+
entity_pks: vec![request.entity_pk.clone()],
|
|
2828
|
+
branch_ids: vec![request.branch_id.clone()],
|
|
2839
2829
|
file_ids: vec![request.file_id.clone()],
|
|
2840
2830
|
..Default::default()
|
|
2841
2831
|
},
|
|
@@ -2945,7 +2935,7 @@ mod tests {
|
|
|
2945
2935
|
}
|
|
2946
2936
|
|
|
2947
2937
|
#[test]
|
|
2948
|
-
fn
|
|
2938
|
+
fn schema_catalog_indexes_visible_schemas_by_key_and_branch() {
|
|
2949
2939
|
let visible_schemas = vec![json!({
|
|
2950
2940
|
"x-lix-key": "visible_schema",
|
|
2951
2941
|
"type": "object",
|
|
@@ -2970,7 +2960,6 @@ mod tests {
|
|
|
2970
2960
|
];
|
|
2971
2961
|
let staged_writes = PreparedWriteSet {
|
|
2972
2962
|
state_rows: vec![pending_registered_schema_row("pending_schema")],
|
|
2973
|
-
adopted_rows: Vec::new(),
|
|
2974
2963
|
..empty_staged_write_set()
|
|
2975
2964
|
};
|
|
2976
2965
|
let input = validation_input(&staged_writes, &visible_schemas);
|
|
@@ -2996,7 +2985,6 @@ mod tests {
|
|
|
2996
2985
|
];
|
|
2997
2986
|
let staged_writes = PreparedWriteSet {
|
|
2998
2987
|
state_rows: vec![pending_registered_schema_row("same_schema")],
|
|
2999
|
-
adopted_rows: Vec::new(),
|
|
3000
2988
|
..empty_staged_write_set()
|
|
3001
2989
|
};
|
|
3002
2990
|
|
|
@@ -3074,7 +3062,7 @@ mod tests {
|
|
|
3074
3062
|
#[test]
|
|
3075
3063
|
fn schema_catalog_rejects_duplicate_pending_registered_schema_identity() {
|
|
3076
3064
|
let mut duplicate = pending_registered_schema_row("duplicate_schema");
|
|
3077
|
-
duplicate.
|
|
3065
|
+
duplicate.entity_pk = registered_schema_entity_pk("duplicate_schema_duplicate");
|
|
3078
3066
|
let staged_writes = PreparedWriteSet {
|
|
3079
3067
|
state_rows: vec![pending_registered_schema_row("duplicate_schema"), duplicate],
|
|
3080
3068
|
..empty_staged_write_set()
|
|
@@ -3110,7 +3098,6 @@ mod tests {
|
|
|
3110
3098
|
fn schema_catalog_rejects_foreign_key_missing_target_schema() {
|
|
3111
3099
|
let staged_writes = PreparedWriteSet {
|
|
3112
3100
|
state_rows: vec![pending_registered_schema_from_definition(fk_child_schema())],
|
|
3113
|
-
adopted_rows: Vec::new(),
|
|
3114
3101
|
..empty_staged_write_set()
|
|
3115
3102
|
};
|
|
3116
3103
|
let visible_schemas = vec![registered_schema()];
|
|
@@ -3203,11 +3190,10 @@ mod tests {
|
|
|
3203
3190
|
schema["x-lix-foreign-keys"][0]["properties"] = json!(["/parent_id"]);
|
|
3204
3191
|
schema["x-lix-foreign-keys"][0]["references"] = json!({
|
|
3205
3192
|
"schemaKey": "lix_state",
|
|
3206
|
-
"properties": ["/
|
|
3193
|
+
"properties": ["/entity_pk"]
|
|
3207
3194
|
});
|
|
3208
3195
|
let staged_writes = PreparedWriteSet {
|
|
3209
3196
|
state_rows: vec![pending_registered_schema_from_definition(schema)],
|
|
3210
|
-
adopted_rows: Vec::new(),
|
|
3211
3197
|
..empty_staged_write_set()
|
|
3212
3198
|
};
|
|
3213
3199
|
let visible_schemas = vec![registered_schema()];
|
|
@@ -3225,20 +3211,19 @@ mod tests {
|
|
|
3225
3211
|
#[test]
|
|
3226
3212
|
fn schema_catalog_rejects_state_surface_foreign_key_without_full_address_tuple() {
|
|
3227
3213
|
let mut schema = state_surface_ref_schema();
|
|
3228
|
-
schema["x-lix-state-foreign-keys"][0] = json!(["/
|
|
3214
|
+
schema["x-lix-state-foreign-keys"][0] = json!(["/target_entity_pk"]);
|
|
3229
3215
|
let staged_writes = PreparedWriteSet {
|
|
3230
3216
|
state_rows: vec![pending_registered_schema_from_definition(schema)],
|
|
3231
|
-
adopted_rows: Vec::new(),
|
|
3232
3217
|
..empty_staged_write_set()
|
|
3233
3218
|
};
|
|
3234
3219
|
let visible_schemas = vec![registered_schema()];
|
|
3235
3220
|
|
|
3236
3221
|
let error = catalog_from_transaction_parts_unchecked(&staged_writes, &visible_schemas)
|
|
3237
|
-
.expect_err("state FK target must include
|
|
3222
|
+
.expect_err("state FK target must include entity_pk, schema_key, and file_id");
|
|
3238
3223
|
|
|
3239
3224
|
assert_eq!(error.code, LixError::CODE_SCHEMA_DEFINITION);
|
|
3240
3225
|
assert!(
|
|
3241
|
-
error.message.contains("[
|
|
3226
|
+
error.message.contains("[entity_pk, schema_key, file_id]"),
|
|
3242
3227
|
"unexpected error: {error:?}"
|
|
3243
3228
|
);
|
|
3244
3229
|
}
|
|
@@ -3263,7 +3248,6 @@ mod tests {
|
|
|
3263
3248
|
let visible_schemas = vec![key_value_schema()];
|
|
3264
3249
|
let staged_writes = PreparedWriteSet {
|
|
3265
3250
|
state_rows: vec![staged_row("unknown_schema", None)],
|
|
3266
|
-
adopted_rows: Vec::new(),
|
|
3267
3251
|
..empty_staged_write_set()
|
|
3268
3252
|
};
|
|
3269
3253
|
|
|
@@ -3302,10 +3286,9 @@ mod tests {
|
|
|
3302
3286
|
"untracked_only_schema",
|
|
3303
3287
|
Some(json!({ "id": "row-1" }).to_string()),
|
|
3304
3288
|
);
|
|
3305
|
-
tracked_row.
|
|
3289
|
+
tracked_row.entity_pk = EntityPk::single("row-1");
|
|
3306
3290
|
let staged_writes = PreparedWriteSet {
|
|
3307
3291
|
state_rows: vec![untracked_schema, tracked_row],
|
|
3308
|
-
adopted_rows: Vec::new(),
|
|
3309
3292
|
..empty_staged_write_set()
|
|
3310
3293
|
};
|
|
3311
3294
|
|
|
@@ -3347,7 +3330,6 @@ mod tests {
|
|
|
3347
3330
|
let visible_schemas = vec![key_value_schema()];
|
|
3348
3331
|
let staged_writes = PreparedWriteSet {
|
|
3349
3332
|
state_rows: vec![staged_row("lix_key_value", None)],
|
|
3350
|
-
adopted_rows: Vec::new(),
|
|
3351
3333
|
..empty_staged_write_set()
|
|
3352
3334
|
};
|
|
3353
3335
|
|
|
@@ -3361,7 +3343,6 @@ mod tests {
|
|
|
3361
3343
|
let visible_schemas = vec![unique_schema()];
|
|
3362
3344
|
let staged_writes = PreparedWriteSet {
|
|
3363
3345
|
state_rows: vec![unique_row("post-1", "hello-world", "first")],
|
|
3364
|
-
adopted_rows: Vec::new(),
|
|
3365
3346
|
..empty_staged_write_set()
|
|
3366
3347
|
};
|
|
3367
3348
|
|
|
@@ -3386,7 +3367,7 @@ mod tests {
|
|
|
3386
3367
|
];
|
|
3387
3368
|
let staged_writes = PreparedWriteSet {
|
|
3388
3369
|
state_rows: vec![
|
|
3389
|
-
staged_file_descriptor_row("file-a", "
|
|
3370
|
+
staged_file_descriptor_row("file-a", "branch-a"),
|
|
3390
3371
|
unique_row("post-1", "hello-world", "first"),
|
|
3391
3372
|
],
|
|
3392
3373
|
..empty_staged_write_set()
|
|
@@ -3408,7 +3389,7 @@ mod tests {
|
|
|
3408
3389
|
file_descriptor_schema(),
|
|
3409
3390
|
directory_descriptor_schema(),
|
|
3410
3391
|
];
|
|
3411
|
-
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "
|
|
3392
|
+
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "branch-a");
|
|
3412
3393
|
mark_prepared_row_untracked(&mut untracked_file_descriptor);
|
|
3413
3394
|
let staged_writes = PreparedWriteSet {
|
|
3414
3395
|
state_rows: vec![
|
|
@@ -3441,7 +3422,7 @@ mod tests {
|
|
|
3441
3422
|
mark_prepared_row_untracked(&mut untracked_row);
|
|
3442
3423
|
let staged_writes = PreparedWriteSet {
|
|
3443
3424
|
state_rows: vec![
|
|
3444
|
-
staged_file_descriptor_row("file-a", "
|
|
3425
|
+
staged_file_descriptor_row("file-a", "branch-a"),
|
|
3445
3426
|
untracked_row,
|
|
3446
3427
|
],
|
|
3447
3428
|
..empty_staged_write_set()
|
|
@@ -3463,14 +3444,13 @@ mod tests {
|
|
|
3463
3444
|
file_descriptor_schema(),
|
|
3464
3445
|
directory_descriptor_schema(),
|
|
3465
3446
|
];
|
|
3466
|
-
let mut file_descriptor_delete = staged_file_descriptor_row("file-a", "
|
|
3447
|
+
let mut file_descriptor_delete = staged_file_descriptor_row("file-a", "branch-a");
|
|
3467
3448
|
file_descriptor_delete.snapshot = None;
|
|
3468
3449
|
let staged_writes = PreparedWriteSet {
|
|
3469
3450
|
state_rows: vec![
|
|
3470
3451
|
file_descriptor_delete,
|
|
3471
3452
|
unique_row("post-1", "hello-world", "first"),
|
|
3472
3453
|
],
|
|
3473
|
-
adopted_rows: Vec::new(),
|
|
3474
3454
|
..empty_staged_write_set()
|
|
3475
3455
|
};
|
|
3476
3456
|
|
|
@@ -3492,11 +3472,10 @@ mod tests {
|
|
|
3492
3472
|
let visible_schemas = vec![unique_schema()];
|
|
3493
3473
|
let staged_writes = PreparedWriteSet {
|
|
3494
3474
|
state_rows: vec![unique_row("post-1", "hello-world", "first")],
|
|
3495
|
-
adopted_rows: Vec::new(),
|
|
3496
3475
|
..empty_staged_write_set()
|
|
3497
3476
|
};
|
|
3498
3477
|
let live_state = StaticLiveStateReader {
|
|
3499
|
-
rows: vec![committed_file_descriptor_row("file-a", "
|
|
3478
|
+
rows: vec![committed_file_descriptor_row("file-a", "branch-a")],
|
|
3500
3479
|
};
|
|
3501
3480
|
|
|
3502
3481
|
validate_prepared_writes(TransactionValidationInput::from_visible_schemas_for_tests(
|
|
@@ -3513,10 +3492,9 @@ mod tests {
|
|
|
3513
3492
|
let visible_schemas = vec![unique_schema()];
|
|
3514
3493
|
let staged_writes = PreparedWriteSet {
|
|
3515
3494
|
state_rows: vec![unique_row("post-1", "hello-world", "first")],
|
|
3516
|
-
adopted_rows: Vec::new(),
|
|
3517
3495
|
..empty_staged_write_set()
|
|
3518
3496
|
};
|
|
3519
|
-
let mut untracked_file_descriptor = committed_file_descriptor_row("file-a", "
|
|
3497
|
+
let mut untracked_file_descriptor = committed_file_descriptor_row("file-a", "branch-a");
|
|
3520
3498
|
mark_live_row_untracked(&mut untracked_file_descriptor);
|
|
3521
3499
|
let live_state = StrictStaticLiveStateReader {
|
|
3522
3500
|
rows: vec![untracked_file_descriptor],
|
|
@@ -3542,11 +3520,10 @@ mod tests {
|
|
|
3542
3520
|
mark_prepared_row_untracked(&mut untracked_row);
|
|
3543
3521
|
let staged_writes = PreparedWriteSet {
|
|
3544
3522
|
state_rows: vec![untracked_row],
|
|
3545
|
-
adopted_rows: Vec::new(),
|
|
3546
3523
|
..empty_staged_write_set()
|
|
3547
3524
|
};
|
|
3548
3525
|
let live_state = StrictStaticLiveStateReader {
|
|
3549
|
-
rows: vec![committed_file_descriptor_row("file-a", "
|
|
3526
|
+
rows: vec![committed_file_descriptor_row("file-a", "branch-a")],
|
|
3550
3527
|
};
|
|
3551
3528
|
|
|
3552
3529
|
validate_prepared_writes(TransactionValidationInput::from_visible_schemas_for_tests(
|
|
@@ -3563,11 +3540,10 @@ mod tests {
|
|
|
3563
3540
|
let visible_schemas = vec![unique_schema()];
|
|
3564
3541
|
let staged_writes = PreparedWriteSet {
|
|
3565
3542
|
state_rows: vec![unique_row("post-1", "hello-world", "first")],
|
|
3566
|
-
adopted_rows: Vec::new(),
|
|
3567
3543
|
..empty_staged_write_set()
|
|
3568
3544
|
};
|
|
3569
|
-
let tracked_file_descriptor = committed_file_descriptor_row("file-a", "
|
|
3570
|
-
let mut untracked_tombstone = committed_file_descriptor_row("file-a", "
|
|
3545
|
+
let tracked_file_descriptor = committed_file_descriptor_row("file-a", "branch-a");
|
|
3546
|
+
let mut untracked_tombstone = committed_file_descriptor_row("file-a", "branch-a");
|
|
3571
3547
|
untracked_tombstone.snapshot_content = None;
|
|
3572
3548
|
mark_live_row_untracked(&mut untracked_tombstone);
|
|
3573
3549
|
let live_state = OverlayingStaticLiveStateReader {
|
|
@@ -3590,11 +3566,10 @@ mod tests {
|
|
|
3590
3566
|
file_descriptor_schema(),
|
|
3591
3567
|
directory_descriptor_schema(),
|
|
3592
3568
|
];
|
|
3593
|
-
let mut file_descriptor_delete = staged_file_descriptor_row("file-a", "
|
|
3569
|
+
let mut file_descriptor_delete = staged_file_descriptor_row("file-a", "branch-a");
|
|
3594
3570
|
file_descriptor_delete.snapshot = None;
|
|
3595
3571
|
let staged_writes = PreparedWriteSet {
|
|
3596
3572
|
state_rows: vec![file_descriptor_delete],
|
|
3597
|
-
adopted_rows: Vec::new(),
|
|
3598
3573
|
..empty_staged_write_set()
|
|
3599
3574
|
};
|
|
3600
3575
|
let live_state = StaticLiveStateReader {
|
|
@@ -3621,11 +3596,10 @@ mod tests {
|
|
|
3621
3596
|
file_descriptor_schema(),
|
|
3622
3597
|
directory_descriptor_schema(),
|
|
3623
3598
|
];
|
|
3624
|
-
let mut file_descriptor_delete = staged_file_descriptor_row("file-a", "
|
|
3599
|
+
let mut file_descriptor_delete = staged_file_descriptor_row("file-a", "branch-a");
|
|
3625
3600
|
file_descriptor_delete.snapshot = None;
|
|
3626
3601
|
let staged_writes = PreparedWriteSet {
|
|
3627
3602
|
state_rows: vec![file_descriptor_delete],
|
|
3628
|
-
adopted_rows: Vec::new(),
|
|
3629
3603
|
..empty_staged_write_set()
|
|
3630
3604
|
};
|
|
3631
3605
|
let mut untracked_row =
|
|
@@ -3633,7 +3607,7 @@ mod tests {
|
|
|
3633
3607
|
mark_live_row_untracked(&mut untracked_row);
|
|
3634
3608
|
let live_state = StrictStaticLiveStateReader {
|
|
3635
3609
|
rows: vec![
|
|
3636
|
-
committed_file_descriptor_row("file-a", "
|
|
3610
|
+
committed_file_descriptor_row("file-a", "branch-a"),
|
|
3637
3611
|
untracked_row,
|
|
3638
3612
|
],
|
|
3639
3613
|
};
|
|
@@ -3653,9 +3627,9 @@ mod tests {
|
|
|
3653
3627
|
#[tokio::test]
|
|
3654
3628
|
async fn validation_allows_untracked_directory_parent_to_tracked_directory() {
|
|
3655
3629
|
let visible_schemas = vec![directory_descriptor_schema()];
|
|
3656
|
-
let tracked_parent = directory_descriptor_row("dir-parent", None, "parent", "
|
|
3630
|
+
let tracked_parent = directory_descriptor_row("dir-parent", None, "parent", "branch-a");
|
|
3657
3631
|
let mut untracked_child =
|
|
3658
|
-
directory_descriptor_row("dir-child", Some("dir-parent"), "child", "
|
|
3632
|
+
directory_descriptor_row("dir-child", Some("dir-parent"), "child", "branch-a");
|
|
3659
3633
|
mark_prepared_row_untracked(&mut untracked_child);
|
|
3660
3634
|
let staged_writes = PreparedWriteSet {
|
|
3661
3635
|
state_rows: vec![tracked_parent, untracked_child],
|
|
@@ -3672,13 +3646,12 @@ mod tests {
|
|
|
3672
3646
|
let visible_schemas = vec![unique_schema()];
|
|
3673
3647
|
let staged_writes = PreparedWriteSet {
|
|
3674
3648
|
state_rows: vec![unique_row("post-1", "hello-world", "first")],
|
|
3675
|
-
adopted_rows: Vec::new(),
|
|
3676
3649
|
..empty_staged_write_set()
|
|
3677
3650
|
};
|
|
3678
3651
|
let live_state = StrictStaticLiveStateReader {
|
|
3679
3652
|
rows: vec![committed_file_descriptor_row(
|
|
3680
3653
|
"file-a",
|
|
3681
|
-
crate::
|
|
3654
|
+
crate::GLOBAL_BRANCH_ID,
|
|
3682
3655
|
)],
|
|
3683
3656
|
};
|
|
3684
3657
|
|
|
@@ -3689,7 +3662,7 @@ mod tests {
|
|
|
3689
3662
|
&live_state,
|
|
3690
3663
|
))
|
|
3691
3664
|
.await
|
|
3692
|
-
.expect_err("global file descriptor should not satisfy a
|
|
3665
|
+
.expect_err("global file descriptor should not satisfy a branch-local row");
|
|
3693
3666
|
|
|
3694
3667
|
assert_eq!(error.code, LixError::CODE_FILE_NOT_FOUND);
|
|
3695
3668
|
}
|
|
@@ -3698,10 +3671,9 @@ mod tests {
|
|
|
3698
3671
|
async fn validation_rejects_primary_key_duplicate_with_different_identity() {
|
|
3699
3672
|
let visible_schemas = vec![unique_schema()];
|
|
3700
3673
|
let mut conflicting = unique_row("post-1", "hello-world", "first");
|
|
3701
|
-
conflicting.
|
|
3674
|
+
conflicting.entity_pk = crate::entity_pk::EntityPk::single("post-2");
|
|
3702
3675
|
let staged_writes = PreparedWriteSet {
|
|
3703
3676
|
state_rows: vec![unique_row("post-1", "hello-world", "first"), conflicting],
|
|
3704
|
-
adopted_rows: Vec::new(),
|
|
3705
3677
|
..empty_staged_write_set()
|
|
3706
3678
|
};
|
|
3707
3679
|
|
|
@@ -3749,37 +3721,35 @@ mod tests {
|
|
|
3749
3721
|
}
|
|
3750
3722
|
|
|
3751
3723
|
#[tokio::test]
|
|
3752
|
-
async fn
|
|
3724
|
+
async fn validation_rejects_pending_unique_same_value_in_same_branch() {
|
|
3753
3725
|
let visible_schemas = vec![unique_schema()];
|
|
3754
3726
|
let mut duplicate = unique_row("post-2", "hello-world", "second");
|
|
3755
|
-
duplicate.
|
|
3727
|
+
duplicate.branch_id = "branch-a".to_string();
|
|
3756
3728
|
let staged_writes = PreparedWriteSet {
|
|
3757
3729
|
state_rows: vec![unique_row("post-1", "hello-world", "first"), duplicate],
|
|
3758
|
-
adopted_rows: Vec::new(),
|
|
3759
3730
|
..empty_staged_write_set()
|
|
3760
3731
|
};
|
|
3761
3732
|
|
|
3762
3733
|
let error = validate_prepared_writes(validation_input(&staged_writes, &visible_schemas))
|
|
3763
3734
|
.await
|
|
3764
|
-
.expect_err("same unique value in the same
|
|
3735
|
+
.expect_err("same unique value in the same branch should fail");
|
|
3765
3736
|
|
|
3766
3737
|
assert_eq!(error.code, LixError::CODE_UNIQUE);
|
|
3767
3738
|
}
|
|
3768
3739
|
|
|
3769
3740
|
#[tokio::test]
|
|
3770
|
-
async fn
|
|
3741
|
+
async fn validation_allows_pending_unique_same_value_in_different_branches() {
|
|
3771
3742
|
let visible_schemas = vec![unique_schema()];
|
|
3772
|
-
let mut
|
|
3773
|
-
|
|
3743
|
+
let mut branch_b = unique_row("post-2", "hello-world", "second");
|
|
3744
|
+
branch_b.branch_id = "branch-b".to_string();
|
|
3774
3745
|
let staged_writes = PreparedWriteSet {
|
|
3775
|
-
state_rows: vec![unique_row("post-1", "hello-world", "first"),
|
|
3776
|
-
adopted_rows: Vec::new(),
|
|
3746
|
+
state_rows: vec![unique_row("post-1", "hello-world", "first"), branch_b],
|
|
3777
3747
|
..empty_staged_write_set()
|
|
3778
3748
|
};
|
|
3779
3749
|
|
|
3780
3750
|
validate_prepared_writes(validation_input(&staged_writes, &visible_schemas))
|
|
3781
3751
|
.await
|
|
3782
|
-
.expect("unique values should be scoped to the exact
|
|
3752
|
+
.expect("unique values should be scoped to the exact branch_id");
|
|
3783
3753
|
}
|
|
3784
3754
|
|
|
3785
3755
|
#[tokio::test]
|
|
@@ -3805,7 +3775,6 @@ mod tests {
|
|
|
3805
3775
|
tombstone.snapshot = None;
|
|
3806
3776
|
let staged_writes = PreparedWriteSet {
|
|
3807
3777
|
state_rows: vec![tombstone, unique_row("post-2", "hello-world", "second")],
|
|
3808
|
-
adopted_rows: Vec::new(),
|
|
3809
3778
|
..empty_staged_write_set()
|
|
3810
3779
|
};
|
|
3811
3780
|
|
|
@@ -3815,24 +3784,24 @@ mod tests {
|
|
|
3815
3784
|
}
|
|
3816
3785
|
|
|
3817
3786
|
#[tokio::test]
|
|
3818
|
-
async fn
|
|
3787
|
+
async fn validation_scopes_pending_unique_values_by_file_and_branch() {
|
|
3819
3788
|
let visible_schemas = vec![unique_schema()];
|
|
3820
3789
|
let mut different_file = unique_row("post-2", "hello-world", "second");
|
|
3821
3790
|
different_file.file_id = Some("file-b".to_string());
|
|
3822
|
-
let mut
|
|
3823
|
-
|
|
3791
|
+
let mut different_branch = unique_row("post-3", "hello-world", "third");
|
|
3792
|
+
different_branch.branch_id = "branch-b".to_string();
|
|
3824
3793
|
let staged_writes = PreparedWriteSet {
|
|
3825
3794
|
state_rows: vec![
|
|
3826
3795
|
unique_row("post-1", "hello-world", "first"),
|
|
3827
3796
|
different_file,
|
|
3828
|
-
|
|
3797
|
+
different_branch,
|
|
3829
3798
|
],
|
|
3830
3799
|
..empty_staged_write_set()
|
|
3831
3800
|
};
|
|
3832
3801
|
|
|
3833
3802
|
validate_prepared_writes(validation_input(&staged_writes, &visible_schemas))
|
|
3834
3803
|
.await
|
|
3835
|
-
.expect("unique values are scoped by file and
|
|
3804
|
+
.expect("unique values are scoped by file and branch");
|
|
3836
3805
|
}
|
|
3837
3806
|
|
|
3838
3807
|
#[tokio::test]
|
|
@@ -3840,7 +3809,6 @@ mod tests {
|
|
|
3840
3809
|
let visible_schemas = vec![unique_schema()];
|
|
3841
3810
|
let staged_writes = PreparedWriteSet {
|
|
3842
3811
|
state_rows: vec![unique_row("post-2", "hello-world", "second")],
|
|
3843
|
-
adopted_rows: Vec::new(),
|
|
3844
3812
|
..empty_staged_write_set()
|
|
3845
3813
|
};
|
|
3846
3814
|
let live_state = StaticLiveStateReader {
|
|
@@ -3864,7 +3832,6 @@ mod tests {
|
|
|
3864
3832
|
let visible_schemas = vec![unique_schema()];
|
|
3865
3833
|
let staged_writes = PreparedWriteSet {
|
|
3866
3834
|
state_rows: vec![unique_row("post-2", "hello-world", "second")],
|
|
3867
|
-
adopted_rows: Vec::new(),
|
|
3868
3835
|
..empty_staged_write_set()
|
|
3869
3836
|
};
|
|
3870
3837
|
let tracked_duplicate = committed_unique_row("post-1", "hello-world", "first");
|
|
@@ -3898,7 +3865,6 @@ mod tests {
|
|
|
3898
3865
|
untracked_tombstone,
|
|
3899
3866
|
unique_row("post-2", "hello-world", "second"),
|
|
3900
3867
|
],
|
|
3901
|
-
adopted_rows: Vec::new(),
|
|
3902
3868
|
..empty_staged_write_set()
|
|
3903
3869
|
};
|
|
3904
3870
|
let live_state = StaticLiveStateReader {
|
|
@@ -3922,7 +3888,6 @@ mod tests {
|
|
|
3922
3888
|
let visible_schemas = vec![nullable_unique_schema()];
|
|
3923
3889
|
let staged_writes = PreparedWriteSet {
|
|
3924
3890
|
state_rows: vec![nullable_unique_row("row-2", None, "root-name")],
|
|
3925
|
-
adopted_rows: Vec::new(),
|
|
3926
3891
|
..empty_staged_write_set()
|
|
3927
3892
|
};
|
|
3928
3893
|
let live_state = StaticLiveStateReader {
|
|
@@ -3942,11 +3907,10 @@ mod tests {
|
|
|
3942
3907
|
}
|
|
3943
3908
|
|
|
3944
3909
|
#[tokio::test]
|
|
3945
|
-
async fn
|
|
3910
|
+
async fn validation_rejects_committed_unique_same_value_in_same_branch() {
|
|
3946
3911
|
let visible_schemas = vec![unique_schema()];
|
|
3947
3912
|
let staged_writes = PreparedWriteSet {
|
|
3948
3913
|
state_rows: vec![unique_row("post-2", "hello-world", "second")],
|
|
3949
|
-
adopted_rows: Vec::new(),
|
|
3950
3914
|
..empty_staged_write_set()
|
|
3951
3915
|
};
|
|
3952
3916
|
let live_state = StaticLiveStateReader {
|
|
@@ -3960,19 +3924,18 @@ mod tests {
|
|
|
3960
3924
|
&live_state,
|
|
3961
3925
|
))
|
|
3962
3926
|
.await
|
|
3963
|
-
.expect_err("same unique value in the same
|
|
3927
|
+
.expect_err("same unique value in the same branch should conflict");
|
|
3964
3928
|
|
|
3965
3929
|
assert_eq!(error.code, LixError::CODE_UNIQUE);
|
|
3966
3930
|
}
|
|
3967
3931
|
|
|
3968
3932
|
#[tokio::test]
|
|
3969
|
-
async fn
|
|
3933
|
+
async fn validation_allows_committed_unique_same_value_in_different_branches() {
|
|
3970
3934
|
let visible_schemas = vec![unique_schema()];
|
|
3971
|
-
let mut
|
|
3972
|
-
|
|
3935
|
+
let mut branch_b = unique_row("post-2", "hello-world", "second");
|
|
3936
|
+
branch_b.branch_id = "branch-b".to_string();
|
|
3973
3937
|
let staged_writes = PreparedWriteSet {
|
|
3974
|
-
state_rows: vec![
|
|
3975
|
-
adopted_rows: Vec::new(),
|
|
3938
|
+
state_rows: vec![branch_b],
|
|
3976
3939
|
..empty_staged_write_set()
|
|
3977
3940
|
};
|
|
3978
3941
|
let live_state = StaticLiveStateReader {
|
|
@@ -3985,7 +3948,7 @@ mod tests {
|
|
|
3985
3948
|
&live_state,
|
|
3986
3949
|
))
|
|
3987
3950
|
.await
|
|
3988
|
-
.expect("committed unique values should be scoped to the exact
|
|
3951
|
+
.expect("committed unique values should be scoped to the exact branch_id");
|
|
3989
3952
|
}
|
|
3990
3953
|
|
|
3991
3954
|
#[tokio::test]
|
|
@@ -3993,11 +3956,10 @@ mod tests {
|
|
|
3993
3956
|
let visible_schemas = vec![unique_schema()];
|
|
3994
3957
|
let staged_writes = PreparedWriteSet {
|
|
3995
3958
|
state_rows: vec![unique_row("post-2", "hello-world", "second")],
|
|
3996
|
-
adopted_rows: Vec::new(),
|
|
3997
3959
|
..empty_staged_write_set()
|
|
3998
3960
|
};
|
|
3999
3961
|
let mut projected_overlay_row = committed_unique_row("post-1", "hello-world", "first");
|
|
4000
|
-
projected_overlay_row.
|
|
3962
|
+
projected_overlay_row.branch_id = "branch-a".to_string();
|
|
4001
3963
|
projected_overlay_row.global = true;
|
|
4002
3964
|
let live_state = StaticLiveStateReader {
|
|
4003
3965
|
rows: vec![projected_overlay_row],
|
|
@@ -4017,7 +3979,6 @@ mod tests {
|
|
|
4017
3979
|
let visible_schemas = vec![unique_schema()];
|
|
4018
3980
|
let staged_writes = PreparedWriteSet {
|
|
4019
3981
|
state_rows: vec![unique_row("post-1", "hello-world", "updated")],
|
|
4020
|
-
adopted_rows: Vec::new(),
|
|
4021
3982
|
..empty_staged_write_set()
|
|
4022
3983
|
};
|
|
4023
3984
|
let live_state = StaticLiveStateReader {
|
|
@@ -4046,7 +4007,6 @@ mod tests {
|
|
|
4046
4007
|
committed_two.file_id = None;
|
|
4047
4008
|
let staged_writes = PreparedWriteSet {
|
|
4048
4009
|
state_rows: vec![staged_one, staged_two],
|
|
4049
|
-
adopted_rows: Vec::new(),
|
|
4050
4010
|
..empty_staged_write_set()
|
|
4051
4011
|
};
|
|
4052
4012
|
let live_state = CountingStaticLiveStateReader {
|
|
@@ -4072,7 +4032,6 @@ mod tests {
|
|
|
4072
4032
|
tombstone.snapshot = None;
|
|
4073
4033
|
let staged_writes = PreparedWriteSet {
|
|
4074
4034
|
state_rows: vec![tombstone, unique_row("post-2", "hello-world", "second")],
|
|
4075
|
-
adopted_rows: Vec::new(),
|
|
4076
4035
|
..empty_staged_write_set()
|
|
4077
4036
|
};
|
|
4078
4037
|
let live_state = StaticLiveStateReader {
|
|
@@ -4089,15 +4048,14 @@ mod tests {
|
|
|
4089
4048
|
}
|
|
4090
4049
|
|
|
4091
4050
|
#[tokio::test]
|
|
4092
|
-
async fn
|
|
4051
|
+
async fn validation_allows_committed_unique_same_value_in_different_file_or_branch() {
|
|
4093
4052
|
let visible_schemas = vec![unique_schema()];
|
|
4094
4053
|
let mut different_file = unique_row("post-2", "hello-world", "second");
|
|
4095
4054
|
different_file.file_id = Some("file-b".to_string());
|
|
4096
|
-
let mut
|
|
4097
|
-
|
|
4055
|
+
let mut different_branch = unique_row("post-3", "hello-world", "third");
|
|
4056
|
+
different_branch.branch_id = "branch-b".to_string();
|
|
4098
4057
|
let staged_writes = PreparedWriteSet {
|
|
4099
|
-
state_rows: vec![different_file,
|
|
4100
|
-
adopted_rows: Vec::new(),
|
|
4058
|
+
state_rows: vec![different_file, different_branch],
|
|
4101
4059
|
..empty_staged_write_set()
|
|
4102
4060
|
};
|
|
4103
4061
|
let live_state = StaticLiveStateReader {
|
|
@@ -4110,39 +4068,38 @@ mod tests {
|
|
|
4110
4068
|
&live_state,
|
|
4111
4069
|
))
|
|
4112
4070
|
.await
|
|
4113
|
-
.expect("committed uniqueness is scoped by file and
|
|
4071
|
+
.expect("committed uniqueness is scoped by file and branch");
|
|
4114
4072
|
}
|
|
4115
4073
|
|
|
4116
4074
|
#[tokio::test]
|
|
4117
|
-
async fn
|
|
4075
|
+
async fn validation_rejects_foreign_key_target_missing_in_same_branch() {
|
|
4118
4076
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4119
4077
|
let staged_writes = PreparedWriteSet {
|
|
4120
|
-
state_rows: vec![fk_child_row("child-1", "parent-1", "
|
|
4121
|
-
adopted_rows: Vec::new(),
|
|
4078
|
+
state_rows: vec![fk_child_row("child-1", "parent-1", "branch-a")],
|
|
4122
4079
|
..empty_staged_write_set()
|
|
4123
4080
|
};
|
|
4124
4081
|
|
|
4125
4082
|
let error = validate_prepared_writes(validation_input(&staged_writes, &visible_schemas))
|
|
4126
4083
|
.await
|
|
4127
|
-
.expect_err("foreign key must resolve in the same
|
|
4084
|
+
.expect_err("foreign key must resolve in the same branch");
|
|
4128
4085
|
|
|
4129
4086
|
assert_eq!(error.code, LixError::CODE_FOREIGN_KEY);
|
|
4130
4087
|
}
|
|
4131
4088
|
|
|
4132
4089
|
#[tokio::test]
|
|
4133
|
-
async fn
|
|
4090
|
+
async fn validation_allows_foreign_key_target_in_same_branch() {
|
|
4134
4091
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4135
4092
|
let staged_writes = PreparedWriteSet {
|
|
4136
4093
|
state_rows: vec![
|
|
4137
|
-
fk_parent_row("parent-1", "
|
|
4138
|
-
fk_child_row("child-1", "parent-1", "
|
|
4094
|
+
fk_parent_row("parent-1", "branch-a"),
|
|
4095
|
+
fk_child_row("child-1", "parent-1", "branch-a"),
|
|
4139
4096
|
],
|
|
4140
4097
|
..empty_staged_write_set()
|
|
4141
4098
|
};
|
|
4142
4099
|
|
|
4143
4100
|
validate_prepared_writes(validation_input(&staged_writes, &visible_schemas))
|
|
4144
4101
|
.await
|
|
4145
|
-
.expect("foreign key should resolve against pending rows in the same
|
|
4102
|
+
.expect("foreign key should resolve against pending rows in the same branch");
|
|
4146
4103
|
}
|
|
4147
4104
|
|
|
4148
4105
|
#[tokio::test]
|
|
@@ -4153,15 +4110,15 @@ mod tests {
|
|
|
4153
4110
|
file_descriptor_schema(),
|
|
4154
4111
|
directory_descriptor_schema(),
|
|
4155
4112
|
];
|
|
4156
|
-
let mut untracked_parent = fk_parent_row("parent-1", "
|
|
4113
|
+
let mut untracked_parent = fk_parent_row("parent-1", "branch-a");
|
|
4157
4114
|
mark_prepared_row_untracked(&mut untracked_parent);
|
|
4158
|
-
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "
|
|
4115
|
+
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "branch-a");
|
|
4159
4116
|
mark_prepared_row_untracked(&mut untracked_file_descriptor);
|
|
4160
4117
|
let staged_writes = PreparedWriteSet {
|
|
4161
4118
|
state_rows: vec![
|
|
4162
4119
|
untracked_file_descriptor,
|
|
4163
4120
|
untracked_parent,
|
|
4164
|
-
fk_child_row("child-1", "parent-1", "
|
|
4121
|
+
fk_child_row("child-1", "parent-1", "branch-a"),
|
|
4165
4122
|
],
|
|
4166
4123
|
..empty_staged_write_set()
|
|
4167
4124
|
};
|
|
@@ -4181,11 +4138,11 @@ mod tests {
|
|
|
4181
4138
|
file_descriptor_schema(),
|
|
4182
4139
|
directory_descriptor_schema(),
|
|
4183
4140
|
];
|
|
4184
|
-
let tracked_file_descriptor = staged_file_descriptor_row("file-a", "
|
|
4185
|
-
let tracked_parent = fk_parent_row("parent-1", "
|
|
4186
|
-
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "
|
|
4141
|
+
let tracked_file_descriptor = staged_file_descriptor_row("file-a", "branch-a");
|
|
4142
|
+
let tracked_parent = fk_parent_row("parent-1", "branch-a");
|
|
4143
|
+
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "branch-a");
|
|
4187
4144
|
mark_prepared_row_untracked(&mut untracked_file_descriptor);
|
|
4188
|
-
let mut untracked_child = fk_child_row("child-1", "parent-1", "
|
|
4145
|
+
let mut untracked_child = fk_child_row("child-1", "parent-1", "branch-a");
|
|
4189
4146
|
mark_prepared_row_untracked(&mut untracked_child);
|
|
4190
4147
|
let staged_writes = PreparedWriteSet {
|
|
4191
4148
|
state_rows: vec![
|
|
@@ -4203,35 +4160,33 @@ mod tests {
|
|
|
4203
4160
|
}
|
|
4204
4161
|
|
|
4205
4162
|
#[tokio::test]
|
|
4206
|
-
async fn
|
|
4163
|
+
async fn validation_rejects_foreign_key_target_that_exists_only_in_different_branch() {
|
|
4207
4164
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4208
4165
|
let staged_writes = PreparedWriteSet {
|
|
4209
4166
|
state_rows: vec![
|
|
4210
|
-
fk_parent_row("parent-1", "
|
|
4211
|
-
fk_child_row("child-1", "parent-1", "
|
|
4167
|
+
fk_parent_row("parent-1", "branch-b"),
|
|
4168
|
+
fk_child_row("child-1", "parent-1", "branch-a"),
|
|
4212
4169
|
],
|
|
4213
4170
|
..empty_staged_write_set()
|
|
4214
4171
|
};
|
|
4215
4172
|
|
|
4216
4173
|
let error = validate_prepared_writes(validation_input(&staged_writes, &visible_schemas))
|
|
4217
4174
|
.await
|
|
4218
|
-
.expect_err("foreign key target in another
|
|
4175
|
+
.expect_err("foreign key target in another branch should not satisfy this branch");
|
|
4219
4176
|
|
|
4220
4177
|
assert_eq!(error.code, LixError::CODE_FOREIGN_KEY);
|
|
4221
4178
|
}
|
|
4222
4179
|
|
|
4223
4180
|
#[tokio::test]
|
|
4224
|
-
async fn
|
|
4181
|
+
async fn validation_allows_foreign_key_target_committed_in_same_branch() {
|
|
4225
4182
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4226
4183
|
let staged_writes = PreparedWriteSet {
|
|
4227
|
-
state_rows: vec![fk_child_row("child-1", "parent-1", "
|
|
4228
|
-
adopted_rows: Vec::new(),
|
|
4184
|
+
state_rows: vec![fk_child_row("child-1", "parent-1", "branch-a")],
|
|
4229
4185
|
..empty_staged_write_set()
|
|
4230
4186
|
};
|
|
4231
4187
|
let live_state = StaticLiveStateReader {
|
|
4232
4188
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
4233
|
-
"parent-1",
|
|
4234
|
-
"version-a",
|
|
4189
|
+
"parent-1", "branch-a",
|
|
4235
4190
|
))],
|
|
4236
4191
|
};
|
|
4237
4192
|
|
|
@@ -4241,19 +4196,18 @@ mod tests {
|
|
|
4241
4196
|
&live_state,
|
|
4242
4197
|
))
|
|
4243
4198
|
.await
|
|
4244
|
-
.expect("foreign key should resolve against committed rows in the same
|
|
4199
|
+
.expect("foreign key should resolve against committed rows in the same branch");
|
|
4245
4200
|
}
|
|
4246
4201
|
|
|
4247
4202
|
#[tokio::test]
|
|
4248
4203
|
async fn validation_rejects_tracked_foreign_key_target_committed_only_as_untracked() {
|
|
4249
4204
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4250
4205
|
let staged_writes = PreparedWriteSet {
|
|
4251
|
-
state_rows: vec![fk_child_row("child-1", "parent-1", "
|
|
4252
|
-
adopted_rows: Vec::new(),
|
|
4206
|
+
state_rows: vec![fk_child_row("child-1", "parent-1", "branch-a")],
|
|
4253
4207
|
..empty_staged_write_set()
|
|
4254
4208
|
};
|
|
4255
4209
|
let mut untracked_parent =
|
|
4256
|
-
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4210
|
+
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a"));
|
|
4257
4211
|
mark_live_row_untracked(&mut untracked_parent);
|
|
4258
4212
|
let live_state = StaticLiveStateReader {
|
|
4259
4213
|
rows: vec![untracked_parent],
|
|
@@ -4279,19 +4233,18 @@ mod tests {
|
|
|
4279
4233
|
file_descriptor_schema(),
|
|
4280
4234
|
directory_descriptor_schema(),
|
|
4281
4235
|
];
|
|
4282
|
-
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "
|
|
4236
|
+
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "branch-a");
|
|
4283
4237
|
mark_prepared_row_untracked(&mut untracked_file_descriptor);
|
|
4284
|
-
let mut untracked_child = fk_child_row("child-1", "parent-1", "
|
|
4238
|
+
let mut untracked_child = fk_child_row("child-1", "parent-1", "branch-a");
|
|
4285
4239
|
mark_prepared_row_untracked(&mut untracked_child);
|
|
4286
4240
|
let staged_writes = PreparedWriteSet {
|
|
4287
4241
|
state_rows: vec![untracked_file_descriptor, untracked_child],
|
|
4288
|
-
adopted_rows: Vec::new(),
|
|
4289
4242
|
..empty_staged_write_set()
|
|
4290
4243
|
};
|
|
4291
4244
|
let live_state = StaticLiveStateReader {
|
|
4292
4245
|
rows: vec![
|
|
4293
|
-
committed_file_descriptor_row("file-a", "
|
|
4294
|
-
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4246
|
+
committed_file_descriptor_row("file-a", "branch-a"),
|
|
4247
|
+
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a")),
|
|
4295
4248
|
],
|
|
4296
4249
|
};
|
|
4297
4250
|
|
|
@@ -4308,13 +4261,12 @@ mod tests {
|
|
|
4308
4261
|
async fn validation_allows_tracked_foreign_key_target_committed_behind_untracked_overlay() {
|
|
4309
4262
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4310
4263
|
let staged_writes = PreparedWriteSet {
|
|
4311
|
-
state_rows: vec![fk_child_row("child-1", "parent-1", "
|
|
4312
|
-
adopted_rows: Vec::new(),
|
|
4264
|
+
state_rows: vec![fk_child_row("child-1", "parent-1", "branch-a")],
|
|
4313
4265
|
..empty_staged_write_set()
|
|
4314
4266
|
};
|
|
4315
|
-
let tracked_parent = MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4267
|
+
let tracked_parent = MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a"));
|
|
4316
4268
|
let mut untracked_overlay =
|
|
4317
|
-
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4269
|
+
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a"));
|
|
4318
4270
|
mark_live_row_untracked(&mut untracked_overlay);
|
|
4319
4271
|
let live_state = OverlayingStaticLiveStateReader {
|
|
4320
4272
|
rows: vec![tracked_parent, untracked_overlay],
|
|
@@ -4334,18 +4286,17 @@ mod tests {
|
|
|
4334
4286
|
#[tokio::test]
|
|
4335
4287
|
async fn validation_rejects_deleting_tracked_fk_target_referenced_behind_untracked_overlay() {
|
|
4336
4288
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4337
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4289
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4338
4290
|
parent_delete.snapshot = None;
|
|
4339
4291
|
let staged_writes = PreparedWriteSet {
|
|
4340
4292
|
state_rows: vec![parent_delete],
|
|
4341
|
-
adopted_rows: Vec::new(),
|
|
4342
4293
|
..empty_staged_write_set()
|
|
4343
4294
|
};
|
|
4344
|
-
let tracked_parent = MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4295
|
+
let tracked_parent = MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a"));
|
|
4345
4296
|
let tracked_child =
|
|
4346
|
-
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "
|
|
4297
|
+
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "branch-a"));
|
|
4347
4298
|
let mut untracked_child_overlay =
|
|
4348
|
-
MaterializedLiveStateRow::from(fk_child_row("child-1", "other-parent", "
|
|
4299
|
+
MaterializedLiveStateRow::from(fk_child_row("child-1", "other-parent", "branch-a"));
|
|
4349
4300
|
mark_live_row_untracked(&mut untracked_child_overlay);
|
|
4350
4301
|
let live_state = OverlayingStaticLiveStateReader {
|
|
4351
4302
|
rows: vec![tracked_parent, tracked_child, untracked_child_overlay],
|
|
@@ -4366,16 +4317,15 @@ mod tests {
|
|
|
4366
4317
|
#[tokio::test]
|
|
4367
4318
|
async fn validation_rejects_deleting_tracked_fk_target_referenced_by_committed_untracked_row() {
|
|
4368
4319
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4369
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4320
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4370
4321
|
parent_delete.snapshot = None;
|
|
4371
4322
|
let staged_writes = PreparedWriteSet {
|
|
4372
4323
|
state_rows: vec![parent_delete],
|
|
4373
|
-
adopted_rows: Vec::new(),
|
|
4374
4324
|
..empty_staged_write_set()
|
|
4375
4325
|
};
|
|
4376
|
-
let tracked_parent = MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4326
|
+
let tracked_parent = MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a"));
|
|
4377
4327
|
let mut untracked_child =
|
|
4378
|
-
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "
|
|
4328
|
+
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "branch-a"));
|
|
4379
4329
|
mark_live_row_untracked(&mut untracked_child);
|
|
4380
4330
|
let live_state = StaticLiveStateReader {
|
|
4381
4331
|
rows: vec![tracked_parent, untracked_child],
|
|
@@ -4394,17 +4344,15 @@ mod tests {
|
|
|
4394
4344
|
}
|
|
4395
4345
|
|
|
4396
4346
|
#[tokio::test]
|
|
4397
|
-
async fn
|
|
4347
|
+
async fn validation_rejects_foreign_key_target_committed_only_in_different_branch() {
|
|
4398
4348
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4399
4349
|
let staged_writes = PreparedWriteSet {
|
|
4400
|
-
state_rows: vec![fk_child_row("child-1", "parent-1", "
|
|
4401
|
-
adopted_rows: Vec::new(),
|
|
4350
|
+
state_rows: vec![fk_child_row("child-1", "parent-1", "branch-a")],
|
|
4402
4351
|
..empty_staged_write_set()
|
|
4403
4352
|
};
|
|
4404
4353
|
let live_state = StaticLiveStateReader {
|
|
4405
4354
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
4406
|
-
"parent-1",
|
|
4407
|
-
"version-b",
|
|
4355
|
+
"parent-1", "branch-b",
|
|
4408
4356
|
))],
|
|
4409
4357
|
};
|
|
4410
4358
|
|
|
@@ -4416,7 +4364,7 @@ mod tests {
|
|
|
4416
4364
|
))
|
|
4417
4365
|
.await
|
|
4418
4366
|
.expect_err(
|
|
4419
|
-
"foreign key target in another committed
|
|
4367
|
+
"foreign key target in another committed branch should not satisfy this branch",
|
|
4420
4368
|
);
|
|
4421
4369
|
|
|
4422
4370
|
assert_eq!(error.code, LixError::CODE_FOREIGN_KEY);
|
|
@@ -4425,19 +4373,18 @@ mod tests {
|
|
|
4425
4373
|
#[tokio::test]
|
|
4426
4374
|
async fn validation_rejects_foreign_key_target_tombstoned_by_transaction() {
|
|
4427
4375
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4428
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4376
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4429
4377
|
parent_delete.snapshot = None;
|
|
4430
4378
|
let staged_writes = PreparedWriteSet {
|
|
4431
4379
|
state_rows: vec![
|
|
4432
4380
|
parent_delete,
|
|
4433
|
-
fk_child_row("child-1", "parent-1", "
|
|
4381
|
+
fk_child_row("child-1", "parent-1", "branch-a"),
|
|
4434
4382
|
],
|
|
4435
4383
|
..empty_staged_write_set()
|
|
4436
4384
|
};
|
|
4437
4385
|
let live_state = StaticLiveStateReader {
|
|
4438
4386
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
4439
|
-
"parent-1",
|
|
4440
|
-
"version-a",
|
|
4387
|
+
"parent-1", "branch-a",
|
|
4441
4388
|
))],
|
|
4442
4389
|
};
|
|
4443
4390
|
|
|
@@ -4456,21 +4403,19 @@ mod tests {
|
|
|
4456
4403
|
#[tokio::test]
|
|
4457
4404
|
async fn validation_allows_tracked_fk_target_when_untracked_tombstone_shadows_same_identity() {
|
|
4458
4405
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4459
|
-
let mut untracked_parent_delete = fk_parent_row("parent-1", "
|
|
4406
|
+
let mut untracked_parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4460
4407
|
untracked_parent_delete.snapshot = None;
|
|
4461
4408
|
mark_prepared_row_untracked(&mut untracked_parent_delete);
|
|
4462
4409
|
let staged_writes = PreparedWriteSet {
|
|
4463
4410
|
state_rows: vec![
|
|
4464
4411
|
untracked_parent_delete,
|
|
4465
|
-
fk_child_row("child-1", "parent-1", "
|
|
4412
|
+
fk_child_row("child-1", "parent-1", "branch-a"),
|
|
4466
4413
|
],
|
|
4467
|
-
adopted_rows: Vec::new(),
|
|
4468
4414
|
..empty_staged_write_set()
|
|
4469
4415
|
};
|
|
4470
4416
|
let live_state = StaticLiveStateReader {
|
|
4471
4417
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
4472
|
-
"parent-1",
|
|
4473
|
-
"version-a",
|
|
4418
|
+
"parent-1", "branch-a",
|
|
4474
4419
|
))],
|
|
4475
4420
|
};
|
|
4476
4421
|
|
|
@@ -4486,19 +4431,18 @@ mod tests {
|
|
|
4486
4431
|
#[tokio::test]
|
|
4487
4432
|
async fn validation_rejects_pending_reference_to_deleted_identity() {
|
|
4488
4433
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4489
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4434
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4490
4435
|
parent_delete.snapshot = None;
|
|
4491
4436
|
let staged_writes = PreparedWriteSet {
|
|
4492
4437
|
state_rows: vec![
|
|
4493
4438
|
parent_delete,
|
|
4494
|
-
fk_child_row("child-1", "parent-1", "
|
|
4439
|
+
fk_child_row("child-1", "parent-1", "branch-a"),
|
|
4495
4440
|
],
|
|
4496
4441
|
..empty_staged_write_set()
|
|
4497
4442
|
};
|
|
4498
4443
|
let live_state = StaticLiveStateReader {
|
|
4499
4444
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
4500
|
-
"parent-1",
|
|
4501
|
-
"version-a",
|
|
4445
|
+
"parent-1", "branch-a",
|
|
4502
4446
|
))],
|
|
4503
4447
|
};
|
|
4504
4448
|
|
|
@@ -4515,22 +4459,22 @@ mod tests {
|
|
|
4515
4459
|
}
|
|
4516
4460
|
|
|
4517
4461
|
#[tokio::test]
|
|
4518
|
-
async fn
|
|
4462
|
+
async fn validation_allows_delete_with_pending_reference_in_different_branch() {
|
|
4519
4463
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4520
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4464
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4521
4465
|
parent_delete.snapshot = None;
|
|
4522
4466
|
let staged_writes = PreparedWriteSet {
|
|
4523
4467
|
state_rows: vec![
|
|
4524
4468
|
parent_delete,
|
|
4525
|
-
fk_parent_row("parent-1", "
|
|
4526
|
-
fk_child_row("child-1", "parent-1", "
|
|
4469
|
+
fk_parent_row("parent-1", "branch-b"),
|
|
4470
|
+
fk_child_row("child-1", "parent-1", "branch-b"),
|
|
4527
4471
|
],
|
|
4528
4472
|
..empty_staged_write_set()
|
|
4529
4473
|
};
|
|
4530
4474
|
|
|
4531
4475
|
validate_prepared_writes(validation_input(&staged_writes, &visible_schemas))
|
|
4532
4476
|
.await
|
|
4533
|
-
.expect("pending references in another
|
|
4477
|
+
.expect("pending references in another branch should not block this delete");
|
|
4534
4478
|
}
|
|
4535
4479
|
|
|
4536
4480
|
#[tokio::test]
|
|
@@ -4547,8 +4491,7 @@ mod tests {
|
|
|
4547
4491
|
};
|
|
4548
4492
|
let live_state = StaticLiveStateReader {
|
|
4549
4493
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
4550
|
-
"target-1",
|
|
4551
|
-
"version-a",
|
|
4494
|
+
"target-1", "branch-a",
|
|
4552
4495
|
))],
|
|
4553
4496
|
};
|
|
4554
4497
|
|
|
@@ -4569,9 +4512,9 @@ mod tests {
|
|
|
4569
4512
|
file_descriptor_schema(),
|
|
4570
4513
|
directory_descriptor_schema(),
|
|
4571
4514
|
];
|
|
4572
|
-
let mut untracked_target = fk_parent_row("target-1", "
|
|
4515
|
+
let mut untracked_target = fk_parent_row("target-1", "branch-a");
|
|
4573
4516
|
mark_prepared_row_untracked(&mut untracked_target);
|
|
4574
|
-
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "
|
|
4517
|
+
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "branch-a");
|
|
4575
4518
|
mark_prepared_row_untracked(&mut untracked_file_descriptor);
|
|
4576
4519
|
let staged_writes = PreparedWriteSet {
|
|
4577
4520
|
state_rows: vec![
|
|
@@ -4604,7 +4547,7 @@ mod tests {
|
|
|
4604
4547
|
..empty_staged_write_set()
|
|
4605
4548
|
};
|
|
4606
4549
|
let mut untracked_target =
|
|
4607
|
-
MaterializedLiveStateRow::from(fk_parent_row("target-1", "
|
|
4550
|
+
MaterializedLiveStateRow::from(fk_parent_row("target-1", "branch-a"));
|
|
4608
4551
|
mark_live_row_untracked(&mut untracked_target);
|
|
4609
4552
|
let live_state = StaticLiveStateReader {
|
|
4610
4553
|
rows: vec![untracked_target],
|
|
@@ -4632,7 +4575,7 @@ mod tests {
|
|
|
4632
4575
|
file_descriptor_schema(),
|
|
4633
4576
|
directory_descriptor_schema(),
|
|
4634
4577
|
];
|
|
4635
|
-
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "
|
|
4578
|
+
let mut untracked_file_descriptor = staged_file_descriptor_row("file-a", "branch-a");
|
|
4636
4579
|
mark_prepared_row_untracked(&mut untracked_file_descriptor);
|
|
4637
4580
|
let mut untracked_ref =
|
|
4638
4581
|
state_surface_ref_row("ref-1", "target-1", "fk_parent_schema", "file-a");
|
|
@@ -4643,8 +4586,8 @@ mod tests {
|
|
|
4643
4586
|
};
|
|
4644
4587
|
let live_state = StaticLiveStateReader {
|
|
4645
4588
|
rows: vec![
|
|
4646
|
-
committed_file_descriptor_row("file-a", "
|
|
4647
|
-
MaterializedLiveStateRow::from(fk_parent_row("target-1", "
|
|
4589
|
+
committed_file_descriptor_row("file-a", "branch-a"),
|
|
4590
|
+
MaterializedLiveStateRow::from(fk_parent_row("target-1", "branch-a")),
|
|
4648
4591
|
],
|
|
4649
4592
|
};
|
|
4650
4593
|
|
|
@@ -4670,9 +4613,9 @@ mod tests {
|
|
|
4670
4613
|
)],
|
|
4671
4614
|
..empty_staged_write_set()
|
|
4672
4615
|
};
|
|
4673
|
-
let tracked_target = MaterializedLiveStateRow::from(fk_parent_row("target-1", "
|
|
4616
|
+
let tracked_target = MaterializedLiveStateRow::from(fk_parent_row("target-1", "branch-a"));
|
|
4674
4617
|
let mut untracked_overlay =
|
|
4675
|
-
MaterializedLiveStateRow::from(fk_parent_row("target-1", "
|
|
4618
|
+
MaterializedLiveStateRow::from(fk_parent_row("target-1", "branch-a"));
|
|
4676
4619
|
mark_live_row_untracked(&mut untracked_overlay);
|
|
4677
4620
|
let live_state = OverlayingStaticLiveStateReader {
|
|
4678
4621
|
rows: vec![tracked_target, untracked_overlay],
|
|
@@ -4690,10 +4633,10 @@ mod tests {
|
|
|
4690
4633
|
}
|
|
4691
4634
|
|
|
4692
4635
|
#[tokio::test]
|
|
4693
|
-
async fn
|
|
4636
|
+
async fn validation_allows_state_surface_fk_target_with_composite_entity_pk() {
|
|
4694
4637
|
let visible_schemas = vec![composite_message_schema(), state_surface_ref_schema()];
|
|
4695
4638
|
let staged_writes = PreparedWriteSet {
|
|
4696
|
-
state_rows: vec![
|
|
4639
|
+
state_rows: vec![state_surface_ref_row_with_target_entity_pk(
|
|
4697
4640
|
"ref-1",
|
|
4698
4641
|
json!(["welcome.title", "en"]),
|
|
4699
4642
|
"composite_message_schema",
|
|
@@ -4705,7 +4648,7 @@ mod tests {
|
|
|
4705
4648
|
rows: vec![MaterializedLiveStateRow::from(composite_message_row(
|
|
4706
4649
|
"welcome.title",
|
|
4707
4650
|
"en",
|
|
4708
|
-
"
|
|
4651
|
+
"branch-a",
|
|
4709
4652
|
))],
|
|
4710
4653
|
};
|
|
4711
4654
|
|
|
@@ -4715,23 +4658,22 @@ mod tests {
|
|
|
4715
4658
|
&live_state,
|
|
4716
4659
|
))
|
|
4717
4660
|
.await
|
|
4718
|
-
.expect("state FK should resolve composite JSON-array entity
|
|
4661
|
+
.expect("state FK should resolve composite JSON-array entity pks");
|
|
4719
4662
|
}
|
|
4720
4663
|
|
|
4721
4664
|
#[tokio::test]
|
|
4722
|
-
async fn
|
|
4665
|
+
async fn validation_rejects_delete_when_same_branch_reference_exists() {
|
|
4723
4666
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4724
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4667
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4725
4668
|
parent_delete.snapshot = None;
|
|
4726
4669
|
let live_state = StaticLiveStateReader {
|
|
4727
4670
|
rows: vec![
|
|
4728
|
-
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4729
|
-
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "
|
|
4671
|
+
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a")),
|
|
4672
|
+
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "branch-a")),
|
|
4730
4673
|
],
|
|
4731
4674
|
};
|
|
4732
4675
|
let staged_writes = PreparedWriteSet {
|
|
4733
4676
|
state_rows: vec![parent_delete],
|
|
4734
|
-
adopted_rows: Vec::new(),
|
|
4735
4677
|
..empty_staged_write_set()
|
|
4736
4678
|
};
|
|
4737
4679
|
|
|
@@ -4742,25 +4684,24 @@ mod tests {
|
|
|
4742
4684
|
&live_state,
|
|
4743
4685
|
))
|
|
4744
4686
|
.await
|
|
4745
|
-
.expect_err("delete should be restricted by same-
|
|
4687
|
+
.expect_err("delete should be restricted by same-branch references");
|
|
4746
4688
|
|
|
4747
4689
|
assert_eq!(error.code, LixError::CODE_FOREIGN_KEY);
|
|
4748
4690
|
}
|
|
4749
4691
|
|
|
4750
4692
|
#[tokio::test]
|
|
4751
|
-
async fn
|
|
4693
|
+
async fn validation_allows_delete_when_only_different_branch_reference_exists() {
|
|
4752
4694
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4753
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4695
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4754
4696
|
parent_delete.snapshot = None;
|
|
4755
4697
|
let live_state = StaticLiveStateReader {
|
|
4756
4698
|
rows: vec![
|
|
4757
|
-
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4758
|
-
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "
|
|
4699
|
+
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a")),
|
|
4700
|
+
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "branch-b")),
|
|
4759
4701
|
],
|
|
4760
4702
|
};
|
|
4761
4703
|
let staged_writes = PreparedWriteSet {
|
|
4762
4704
|
state_rows: vec![parent_delete],
|
|
4763
|
-
adopted_rows: Vec::new(),
|
|
4764
4705
|
..empty_staged_write_set()
|
|
4765
4706
|
};
|
|
4766
4707
|
|
|
@@ -4770,25 +4711,24 @@ mod tests {
|
|
|
4770
4711
|
&live_state,
|
|
4771
4712
|
))
|
|
4772
4713
|
.await
|
|
4773
|
-
.expect("references in another
|
|
4714
|
+
.expect("references in another branch should not restrict this branch");
|
|
4774
4715
|
}
|
|
4775
4716
|
|
|
4776
4717
|
#[tokio::test]
|
|
4777
4718
|
async fn validation_allows_delete_when_committed_reference_is_also_deleted() {
|
|
4778
4719
|
let visible_schemas = vec![fk_parent_schema(), fk_child_schema()];
|
|
4779
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4720
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4780
4721
|
parent_delete.snapshot = None;
|
|
4781
|
-
let mut child_delete = fk_child_row("child-1", "parent-1", "
|
|
4722
|
+
let mut child_delete = fk_child_row("child-1", "parent-1", "branch-a");
|
|
4782
4723
|
child_delete.snapshot = None;
|
|
4783
4724
|
let live_state = StaticLiveStateReader {
|
|
4784
4725
|
rows: vec![
|
|
4785
|
-
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "
|
|
4786
|
-
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "
|
|
4726
|
+
MaterializedLiveStateRow::from(fk_parent_row("parent-1", "branch-a")),
|
|
4727
|
+
MaterializedLiveStateRow::from(fk_child_row("child-1", "parent-1", "branch-a")),
|
|
4787
4728
|
],
|
|
4788
4729
|
};
|
|
4789
4730
|
let staged_writes = PreparedWriteSet {
|
|
4790
4731
|
state_rows: vec![parent_delete, child_delete],
|
|
4791
|
-
adopted_rows: Vec::new(),
|
|
4792
4732
|
..empty_staged_write_set()
|
|
4793
4733
|
};
|
|
4794
4734
|
|
|
@@ -4821,7 +4761,7 @@ mod tests {
|
|
|
4821
4761
|
#[test]
|
|
4822
4762
|
fn pending_indexes_record_primary_key_fk_targets_by_exact_scope() {
|
|
4823
4763
|
let mut indexes = PendingConstraintIndexes::default();
|
|
4824
|
-
let row = fk_parent_row("parent-1", "
|
|
4764
|
+
let row = fk_parent_row("parent-1", "branch-a");
|
|
4825
4765
|
let snapshot = serde_json::from_str::<JsonValue>(
|
|
4826
4766
|
row.snapshot
|
|
4827
4767
|
.as_ref()
|
|
@@ -4841,7 +4781,7 @@ mod tests {
|
|
|
4841
4781
|
assert!(indexes
|
|
4842
4782
|
.has_fk_target(
|
|
4843
4783
|
"fk_parent_schema",
|
|
4844
|
-
"
|
|
4784
|
+
"branch-a",
|
|
4845
4785
|
Some("file-a"),
|
|
4846
4786
|
&["/id"],
|
|
4847
4787
|
UniqueConstraintValue::string_values(["parent-1"]),
|
|
@@ -4850,7 +4790,7 @@ mod tests {
|
|
|
4850
4790
|
assert!(!indexes
|
|
4851
4791
|
.has_fk_target(
|
|
4852
4792
|
"fk_parent_schema",
|
|
4853
|
-
"
|
|
4793
|
+
"branch-b",
|
|
4854
4794
|
Some("file-a"),
|
|
4855
4795
|
&["/id"],
|
|
4856
4796
|
UniqueConstraintValue::string_values(["parent-1"]),
|
|
@@ -4881,7 +4821,7 @@ mod tests {
|
|
|
4881
4821
|
assert!(indexes
|
|
4882
4822
|
.has_fk_target(
|
|
4883
4823
|
"unique_schema",
|
|
4884
|
-
"
|
|
4824
|
+
"branch-a",
|
|
4885
4825
|
Some("file-a"),
|
|
4886
4826
|
&["/slug"],
|
|
4887
4827
|
UniqueConstraintValue::string_values(["hello-world"]),
|
|
@@ -4892,7 +4832,7 @@ mod tests {
|
|
|
4892
4832
|
#[test]
|
|
4893
4833
|
fn pending_indexes_record_normal_fk_references_by_exact_scope() {
|
|
4894
4834
|
let mut indexes = PendingConstraintIndexes::default();
|
|
4895
|
-
let row = fk_child_row("child-1", "parent-1", "
|
|
4835
|
+
let row = fk_child_row("child-1", "parent-1", "branch-a");
|
|
4896
4836
|
let snapshot = serde_json::from_str::<JsonValue>(
|
|
4897
4837
|
row.snapshot
|
|
4898
4838
|
.as_ref()
|
|
@@ -4916,7 +4856,7 @@ mod tests {
|
|
|
4916
4856
|
assert!(indexes
|
|
4917
4857
|
.has_fk_reference_to_key(
|
|
4918
4858
|
"fk_parent_schema",
|
|
4919
|
-
"
|
|
4859
|
+
"branch-a",
|
|
4920
4860
|
Some("file-a"),
|
|
4921
4861
|
&["/id"],
|
|
4922
4862
|
UniqueConstraintValue::string_values(["parent-1"]),
|
|
@@ -4925,7 +4865,7 @@ mod tests {
|
|
|
4925
4865
|
assert!(!indexes
|
|
4926
4866
|
.has_fk_reference_to_key(
|
|
4927
4867
|
"fk_parent_schema",
|
|
4928
|
-
"
|
|
4868
|
+
"branch-b",
|
|
4929
4869
|
Some("file-a"),
|
|
4930
4870
|
&["/id"],
|
|
4931
4871
|
UniqueConstraintValue::string_values(["parent-1"]),
|
|
@@ -4959,11 +4899,11 @@ mod tests {
|
|
|
4959
4899
|
|
|
4960
4900
|
assert!(
|
|
4961
4901
|
indexes.has_fk_reference_to_identity(DomainRowIdentity::exact(
|
|
4962
|
-
"
|
|
4902
|
+
"branch-a",
|
|
4963
4903
|
false,
|
|
4964
4904
|
Some("file-a".to_string()),
|
|
4965
4905
|
"fk_parent_schema",
|
|
4966
|
-
|
|
4906
|
+
EntityPk::single("target-1"),
|
|
4967
4907
|
))
|
|
4968
4908
|
);
|
|
4969
4909
|
}
|
|
@@ -4971,11 +4911,11 @@ mod tests {
|
|
|
4971
4911
|
#[test]
|
|
4972
4912
|
fn pending_delete_restrictions_ignore_tombstoned_referencing_rows() {
|
|
4973
4913
|
let mut indexes = PendingConstraintIndexes::default();
|
|
4974
|
-
let mut parent_delete = fk_parent_row("parent-1", "
|
|
4914
|
+
let mut parent_delete = fk_parent_row("parent-1", "branch-a");
|
|
4975
4915
|
parent_delete.snapshot = None;
|
|
4976
4916
|
indexes.remember_tombstone(PreparedValidationRow::State(&parent_delete));
|
|
4977
4917
|
|
|
4978
|
-
let child = fk_child_row("child-1", "parent-1", "
|
|
4918
|
+
let child = fk_child_row("child-1", "parent-1", "branch-a");
|
|
4979
4919
|
let child_snapshot = serde_json::from_str::<JsonValue>(
|
|
4980
4920
|
child
|
|
4981
4921
|
.snapshot
|
|
@@ -4996,7 +4936,7 @@ mod tests {
|
|
|
4996
4936
|
)
|
|
4997
4937
|
.expect("child row should index FK reference");
|
|
4998
4938
|
|
|
4999
|
-
let mut child_delete = fk_child_row("child-1", "parent-1", "
|
|
4939
|
+
let mut child_delete = fk_child_row("child-1", "parent-1", "branch-a");
|
|
5000
4940
|
child_delete.snapshot = None;
|
|
5001
4941
|
indexes.remember_tombstone(PreparedValidationRow::State(&child_delete));
|
|
5002
4942
|
|
|
@@ -5007,7 +4947,7 @@ mod tests {
|
|
|
5007
4947
|
#[test]
|
|
5008
4948
|
fn pending_fk_validation_collects_unresolved_normal_fk_check() {
|
|
5009
4949
|
let indexes = PendingConstraintIndexes::default();
|
|
5010
|
-
let row = fk_child_row("child-1", "parent-1", "
|
|
4950
|
+
let row = fk_child_row("child-1", "parent-1", "branch-a");
|
|
5011
4951
|
let snapshot = serde_json::from_str::<JsonValue>(
|
|
5012
4952
|
row.snapshot
|
|
5013
4953
|
.as_ref()
|
|
@@ -5034,11 +4974,11 @@ mod tests {
|
|
|
5034
4974
|
assert_eq!(
|
|
5035
4975
|
unresolved[0].source_identity,
|
|
5036
4976
|
DomainRowIdentity::exact(
|
|
5037
|
-
"
|
|
4977
|
+
"branch-a",
|
|
5038
4978
|
false,
|
|
5039
4979
|
Some("file-a".to_string()),
|
|
5040
4980
|
"fk_child_schema",
|
|
5041
|
-
|
|
4981
|
+
EntityPk::single("child-1"),
|
|
5042
4982
|
)
|
|
5043
4983
|
);
|
|
5044
4984
|
assert_eq!(unresolved[0].source_schema_key, "fk_child_schema");
|
|
@@ -5050,7 +4990,7 @@ mod tests {
|
|
|
5050
4990
|
panic!("normal FK should produce key target");
|
|
5051
4991
|
};
|
|
5052
4992
|
assert_eq!(target.schema_key, "fk_parent_schema");
|
|
5053
|
-
assert_eq!(target.domain.
|
|
4993
|
+
assert_eq!(target.domain.branch_id(), "branch-a");
|
|
5054
4994
|
assert_eq!(
|
|
5055
4995
|
target.domain.file_scope(),
|
|
5056
4996
|
&DomainFileScope::Exact(Some("file-a".to_string()))
|
|
@@ -5065,7 +5005,7 @@ mod tests {
|
|
|
5065
5005
|
#[test]
|
|
5066
5006
|
fn pending_fk_validation_resolves_normal_fk_against_pending_target() {
|
|
5067
5007
|
let mut indexes = PendingConstraintIndexes::default();
|
|
5068
|
-
let parent = fk_parent_row("parent-1", "
|
|
5008
|
+
let parent = fk_parent_row("parent-1", "branch-a");
|
|
5069
5009
|
let parent_snapshot = serde_json::from_str::<JsonValue>(
|
|
5070
5010
|
parent
|
|
5071
5011
|
.snapshot
|
|
@@ -5082,7 +5022,7 @@ mod tests {
|
|
|
5082
5022
|
)
|
|
5083
5023
|
.expect("parent should index as pending FK target");
|
|
5084
5024
|
|
|
5085
|
-
let child = fk_child_row("child-1", "parent-1", "
|
|
5025
|
+
let child = fk_child_row("child-1", "parent-1", "branch-a");
|
|
5086
5026
|
let child_snapshot = serde_json::from_str::<JsonValue>(
|
|
5087
5027
|
child
|
|
5088
5028
|
.snapshot
|
|
@@ -5108,14 +5048,14 @@ mod tests {
|
|
|
5108
5048
|
|
|
5109
5049
|
assert!(
|
|
5110
5050
|
unresolved.is_empty(),
|
|
5111
|
-
"same-
|
|
5051
|
+
"same-branch pending parent should satisfy the child FK"
|
|
5112
5052
|
);
|
|
5113
5053
|
}
|
|
5114
5054
|
|
|
5115
5055
|
#[test]
|
|
5116
|
-
fn
|
|
5056
|
+
fn pending_fk_validation_keeps_normal_fk_unresolved_across_branches() {
|
|
5117
5057
|
let mut indexes = PendingConstraintIndexes::default();
|
|
5118
|
-
let parent = fk_parent_row("parent-1", "
|
|
5058
|
+
let parent = fk_parent_row("parent-1", "branch-b");
|
|
5119
5059
|
let parent_snapshot = serde_json::from_str::<JsonValue>(
|
|
5120
5060
|
parent
|
|
5121
5061
|
.snapshot
|
|
@@ -5132,7 +5072,7 @@ mod tests {
|
|
|
5132
5072
|
)
|
|
5133
5073
|
.expect("parent should index as pending FK target");
|
|
5134
5074
|
|
|
5135
|
-
let child = fk_child_row("child-1", "parent-1", "
|
|
5075
|
+
let child = fk_child_row("child-1", "parent-1", "branch-a");
|
|
5136
5076
|
let child_snapshot = serde_json::from_str::<JsonValue>(
|
|
5137
5077
|
child
|
|
5138
5078
|
.snapshot
|
|
@@ -5161,9 +5101,9 @@ mod tests {
|
|
|
5161
5101
|
panic!("normal FK should produce key target");
|
|
5162
5102
|
};
|
|
5163
5103
|
assert_eq!(
|
|
5164
|
-
target.domain.
|
|
5165
|
-
"
|
|
5166
|
-
"FK checks are exact-
|
|
5104
|
+
target.domain.branch_id(),
|
|
5105
|
+
"branch-a",
|
|
5106
|
+
"FK checks are exact-branch scoped, not overlay scoped"
|
|
5167
5107
|
);
|
|
5168
5108
|
}
|
|
5169
5109
|
|
|
@@ -5197,18 +5137,18 @@ mod tests {
|
|
|
5197
5137
|
assert_eq!(
|
|
5198
5138
|
unresolved[0].source_identity,
|
|
5199
5139
|
DomainRowIdentity::exact(
|
|
5200
|
-
"
|
|
5140
|
+
"branch-a",
|
|
5201
5141
|
false,
|
|
5202
5142
|
Some("file-a".to_string()),
|
|
5203
5143
|
"state_surface_ref_schema",
|
|
5204
|
-
|
|
5144
|
+
EntityPk::single("ref-1"),
|
|
5205
5145
|
)
|
|
5206
5146
|
);
|
|
5207
5147
|
assert_eq!(unresolved[0].source_schema_key, "state_surface_ref_schema");
|
|
5208
5148
|
assert_eq!(
|
|
5209
5149
|
unresolved[0].source_pointer_group,
|
|
5210
5150
|
vec![
|
|
5211
|
-
vec!["
|
|
5151
|
+
vec!["target_entity_pk".to_string()],
|
|
5212
5152
|
vec!["target_schema_key".to_string()],
|
|
5213
5153
|
vec!["target_file_id".to_string()],
|
|
5214
5154
|
]
|
|
@@ -5216,9 +5156,9 @@ mod tests {
|
|
|
5216
5156
|
let UnresolvedForeignKeyTarget::StateSurfaceIdentity(target) = &unresolved[0].target else {
|
|
5217
5157
|
panic!("state FK should produce state-surface identity target");
|
|
5218
5158
|
};
|
|
5219
|
-
assert_eq!(target.domain().
|
|
5159
|
+
assert_eq!(target.domain().branch_id(), "branch-a");
|
|
5220
5160
|
assert_eq!(target.schema_key(), "fk_parent_schema");
|
|
5221
|
-
assert_eq!(target.
|
|
5161
|
+
assert_eq!(target.entity_pk(), &EntityPk::single("target-1"));
|
|
5222
5162
|
assert_eq!(
|
|
5223
5163
|
target.domain().file_scope(),
|
|
5224
5164
|
&DomainFileScope::Exact(Some("file-a".to_string()))
|
|
@@ -5228,7 +5168,7 @@ mod tests {
|
|
|
5228
5168
|
#[tokio::test]
|
|
5229
5169
|
async fn committed_fk_lookup_resolves_normal_fk_in_exact_scope() {
|
|
5230
5170
|
let indexes = PendingConstraintIndexes::default();
|
|
5231
|
-
let child = fk_child_row("child-1", "parent-1", "
|
|
5171
|
+
let child = fk_child_row("child-1", "parent-1", "branch-a");
|
|
5232
5172
|
let child_snapshot = serde_json::from_str::<JsonValue>(
|
|
5233
5173
|
child
|
|
5234
5174
|
.snapshot
|
|
@@ -5252,8 +5192,7 @@ mod tests {
|
|
|
5252
5192
|
.expect("pending FK validation should collect unresolved check");
|
|
5253
5193
|
let live_state = StaticLiveStateReader {
|
|
5254
5194
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
5255
|
-
"parent-1",
|
|
5256
|
-
"version-a",
|
|
5195
|
+
"parent-1", "branch-a",
|
|
5257
5196
|
))],
|
|
5258
5197
|
};
|
|
5259
5198
|
|
|
@@ -5271,14 +5210,14 @@ mod tests {
|
|
|
5271
5210
|
|
|
5272
5211
|
assert!(
|
|
5273
5212
|
still_unresolved.is_empty(),
|
|
5274
|
-
"same-
|
|
5213
|
+
"same-branch committed parent should satisfy unresolved FK"
|
|
5275
5214
|
);
|
|
5276
5215
|
}
|
|
5277
5216
|
|
|
5278
5217
|
#[tokio::test]
|
|
5279
|
-
async fn
|
|
5218
|
+
async fn committed_fk_lookup_keeps_normal_fk_unresolved_across_branches() {
|
|
5280
5219
|
let indexes = PendingConstraintIndexes::default();
|
|
5281
|
-
let child = fk_child_row("child-1", "parent-1", "
|
|
5220
|
+
let child = fk_child_row("child-1", "parent-1", "branch-a");
|
|
5282
5221
|
let child_snapshot = serde_json::from_str::<JsonValue>(
|
|
5283
5222
|
child
|
|
5284
5223
|
.snapshot
|
|
@@ -5302,8 +5241,7 @@ mod tests {
|
|
|
5302
5241
|
.expect("pending FK validation should collect unresolved check");
|
|
5303
5242
|
let live_state = StaticLiveStateReader {
|
|
5304
5243
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
5305
|
-
"parent-1",
|
|
5306
|
-
"version-b",
|
|
5244
|
+
"parent-1", "branch-b",
|
|
5307
5245
|
))],
|
|
5308
5246
|
};
|
|
5309
5247
|
|
|
@@ -5322,7 +5260,7 @@ mod tests {
|
|
|
5322
5260
|
assert_eq!(
|
|
5323
5261
|
still_unresolved.len(),
|
|
5324
5262
|
1,
|
|
5325
|
-
"committed FK lookup is exact-
|
|
5263
|
+
"committed FK lookup is exact-branch scoped"
|
|
5326
5264
|
);
|
|
5327
5265
|
}
|
|
5328
5266
|
|
|
@@ -5352,8 +5290,7 @@ mod tests {
|
|
|
5352
5290
|
.expect("pending FK validation should collect unresolved check");
|
|
5353
5291
|
let live_state = StaticLiveStateReader {
|
|
5354
5292
|
rows: vec![MaterializedLiveStateRow::from(fk_parent_row(
|
|
5355
|
-
"target-1",
|
|
5356
|
-
"version-a",
|
|
5293
|
+
"target-1", "branch-a",
|
|
5357
5294
|
))],
|
|
5358
5295
|
};
|
|
5359
5296
|
|
|
@@ -5378,10 +5315,9 @@ mod tests {
|
|
|
5378
5315
|
fn empty_staged_write_set() -> PreparedWriteSet {
|
|
5379
5316
|
PreparedWriteSet {
|
|
5380
5317
|
state_rows: Vec::new(),
|
|
5381
|
-
adopted_rows: Vec::new(),
|
|
5382
5318
|
insert_identities: BTreeMap::new(),
|
|
5383
|
-
|
|
5384
|
-
|
|
5319
|
+
commit_change_refs_by_branch: BTreeMap::new(),
|
|
5320
|
+
extra_commit_parents_by_branch: BTreeMap::new(),
|
|
5385
5321
|
file_data_writes: Vec::new(),
|
|
5386
5322
|
}
|
|
5387
5323
|
}
|
|
@@ -5399,8 +5335,8 @@ mod tests {
|
|
|
5399
5335
|
}
|
|
5400
5336
|
(request.filter.schema_keys.is_empty()
|
|
5401
5337
|
|| request.filter.schema_keys.contains(&row.schema_key))
|
|
5402
|
-
&& (request.filter.
|
|
5403
|
-
|| request.filter.
|
|
5338
|
+
&& (request.filter.branch_ids.is_empty()
|
|
5339
|
+
|| request.filter.branch_ids.contains(&row.branch_id))
|
|
5404
5340
|
&& (request.filter.file_ids.is_empty()
|
|
5405
5341
|
|| request
|
|
5406
5342
|
.filter
|
|
@@ -5414,17 +5350,17 @@ mod tests {
|
|
|
5414
5350
|
request: &LiveStateRowRequest,
|
|
5415
5351
|
) -> bool {
|
|
5416
5352
|
row.schema_key == request.schema_key
|
|
5417
|
-
&& row.
|
|
5418
|
-
&& row.
|
|
5353
|
+
&& row.branch_id == request.branch_id
|
|
5354
|
+
&& row.entity_pk == request.entity_pk
|
|
5419
5355
|
&& request.file_id.matches(row.file_id.as_ref())
|
|
5420
5356
|
}
|
|
5421
5357
|
|
|
5422
5358
|
fn test_file_descriptor_rows() -> Vec<MaterializedLiveStateRow> {
|
|
5423
5359
|
vec![
|
|
5424
|
-
committed_file_descriptor_row("file-a", "
|
|
5425
|
-
committed_file_descriptor_row("file-a", "
|
|
5426
|
-
committed_file_descriptor_row("file-b", "
|
|
5427
|
-
committed_file_descriptor_row("file-b", "
|
|
5360
|
+
committed_file_descriptor_row("file-a", "branch-a"),
|
|
5361
|
+
committed_file_descriptor_row("file-a", "branch-b"),
|
|
5362
|
+
committed_file_descriptor_row("file-b", "branch-a"),
|
|
5363
|
+
committed_file_descriptor_row("file-b", "branch-b"),
|
|
5428
5364
|
]
|
|
5429
5365
|
}
|
|
5430
5366
|
|
|
@@ -5445,7 +5381,7 @@ mod tests {
|
|
|
5445
5381
|
PreparedStateRow {
|
|
5446
5382
|
schema_plan_id: crate::catalog::SchemaPlanId::for_test(0),
|
|
5447
5383
|
facts: crate::transaction::types::PreparedRowFacts::default(),
|
|
5448
|
-
|
|
5384
|
+
entity_pk: registered_schema_entity_pk(&key.schema_key),
|
|
5449
5385
|
schema_key: REGISTERED_SCHEMA_KEY.to_string(),
|
|
5450
5386
|
file_id: None,
|
|
5451
5387
|
snapshot: Some(test_stage_json(&json!({ "value": schema }).to_string())),
|
|
@@ -5457,12 +5393,12 @@ mod tests {
|
|
|
5457
5393
|
change_id: Some("change-registered-schema".to_string()),
|
|
5458
5394
|
commit_id: Some("commit-registered-schema".to_string()),
|
|
5459
5395
|
untracked: false,
|
|
5460
|
-
|
|
5396
|
+
branch_id: crate::GLOBAL_BRANCH_ID.to_string(),
|
|
5461
5397
|
}
|
|
5462
5398
|
}
|
|
5463
5399
|
|
|
5464
|
-
fn
|
|
5465
|
-
crate::
|
|
5400
|
+
fn registered_schema_entity_pk(schema_key: &str) -> crate::entity_pk::EntityPk {
|
|
5401
|
+
crate::entity_pk::EntityPk::from_primary_key_paths(
|
|
5466
5402
|
&serde_json::json!({
|
|
5467
5403
|
"value": {
|
|
5468
5404
|
"x-lix-key": schema_key,
|
|
@@ -5583,12 +5519,12 @@ mod tests {
|
|
|
5583
5519
|
"x-lix-key": "state_surface_ref_schema",
|
|
5584
5520
|
"x-lix-primary-key": ["/id"],
|
|
5585
5521
|
"x-lix-state-foreign-keys": [
|
|
5586
|
-
["/
|
|
5522
|
+
["/target_entity_pk", "/target_schema_key", "/target_file_id"]
|
|
5587
5523
|
],
|
|
5588
5524
|
"type": "object",
|
|
5589
5525
|
"properties": {
|
|
5590
5526
|
"id": { "type": "string" },
|
|
5591
|
-
"
|
|
5527
|
+
"target_entity_pk": {
|
|
5592
5528
|
"type": "array",
|
|
5593
5529
|
"items": { "type": "string" },
|
|
5594
5530
|
"minItems": 1
|
|
@@ -5596,108 +5532,108 @@ mod tests {
|
|
|
5596
5532
|
"target_schema_key": { "type": "string" },
|
|
5597
5533
|
"target_file_id": { "type": ["string", "null"] }
|
|
5598
5534
|
},
|
|
5599
|
-
"required": ["id", "
|
|
5535
|
+
"required": ["id", "target_entity_pk", "target_schema_key", "target_file_id"],
|
|
5600
5536
|
"additionalProperties": false
|
|
5601
5537
|
})
|
|
5602
5538
|
}
|
|
5603
5539
|
|
|
5604
|
-
fn unique_row(
|
|
5540
|
+
fn unique_row(entity_pk: &str, slug: &str, title: &str) -> PreparedStateRow {
|
|
5605
5541
|
let mut row = staged_row(
|
|
5606
5542
|
"unique_schema",
|
|
5607
5543
|
Some(
|
|
5608
5544
|
json!({
|
|
5609
|
-
"id":
|
|
5545
|
+
"id": entity_pk,
|
|
5610
5546
|
"slug": slug,
|
|
5611
5547
|
"title": title,
|
|
5612
5548
|
})
|
|
5613
5549
|
.to_string(),
|
|
5614
5550
|
),
|
|
5615
5551
|
);
|
|
5616
|
-
row.
|
|
5552
|
+
row.entity_pk = crate::entity_pk::EntityPk::single(entity_pk);
|
|
5617
5553
|
row.file_id = Some("file-a".to_string());
|
|
5618
|
-
row.
|
|
5554
|
+
row.branch_id = "branch-a".to_string();
|
|
5619
5555
|
row.global = false;
|
|
5620
5556
|
row
|
|
5621
5557
|
}
|
|
5622
5558
|
|
|
5623
|
-
fn nullable_unique_row(
|
|
5559
|
+
fn nullable_unique_row(entity_pk: &str, scope: Option<&str>, name: &str) -> PreparedStateRow {
|
|
5624
5560
|
let mut row = staged_row(
|
|
5625
5561
|
"nullable_unique_schema",
|
|
5626
5562
|
Some(
|
|
5627
5563
|
json!({
|
|
5628
|
-
"id":
|
|
5564
|
+
"id": entity_pk,
|
|
5629
5565
|
"scope": scope,
|
|
5630
5566
|
"name": name,
|
|
5631
5567
|
})
|
|
5632
5568
|
.to_string(),
|
|
5633
5569
|
),
|
|
5634
5570
|
);
|
|
5635
|
-
row.
|
|
5571
|
+
row.entity_pk = crate::entity_pk::EntityPk::single(entity_pk);
|
|
5636
5572
|
row.file_id = Some("file-a".to_string());
|
|
5637
|
-
row.
|
|
5573
|
+
row.branch_id = "branch-a".to_string();
|
|
5638
5574
|
row.global = false;
|
|
5639
5575
|
row
|
|
5640
5576
|
}
|
|
5641
5577
|
|
|
5642
|
-
fn fk_parent_row(
|
|
5578
|
+
fn fk_parent_row(entity_pk: &str, branch_id: &str) -> PreparedStateRow {
|
|
5643
5579
|
let mut row = staged_row(
|
|
5644
5580
|
"fk_parent_schema",
|
|
5645
|
-
Some(json!({ "id":
|
|
5581
|
+
Some(json!({ "id": entity_pk }).to_string()),
|
|
5646
5582
|
);
|
|
5647
|
-
row.
|
|
5583
|
+
row.entity_pk = crate::entity_pk::EntityPk::single(entity_pk);
|
|
5648
5584
|
row.file_id = Some("file-a".to_string());
|
|
5649
|
-
row.
|
|
5585
|
+
row.branch_id = branch_id.to_string();
|
|
5650
5586
|
row.global = false;
|
|
5651
5587
|
row
|
|
5652
5588
|
}
|
|
5653
5589
|
|
|
5654
|
-
fn fk_child_row(
|
|
5590
|
+
fn fk_child_row(entity_pk: &str, parent_id: &str, branch_id: &str) -> PreparedStateRow {
|
|
5655
5591
|
let mut row = staged_row(
|
|
5656
5592
|
"fk_child_schema",
|
|
5657
|
-
Some(json!({ "id":
|
|
5593
|
+
Some(json!({ "id": entity_pk, "parent_id": parent_id }).to_string()),
|
|
5658
5594
|
);
|
|
5659
|
-
row.
|
|
5595
|
+
row.entity_pk = crate::entity_pk::EntityPk::single(entity_pk);
|
|
5660
5596
|
row.file_id = Some("file-a".to_string());
|
|
5661
|
-
row.
|
|
5597
|
+
row.branch_id = branch_id.to_string();
|
|
5662
5598
|
row.global = false;
|
|
5663
5599
|
row
|
|
5664
5600
|
}
|
|
5665
5601
|
|
|
5666
|
-
fn composite_message_row(key: &str, locale: &str,
|
|
5602
|
+
fn composite_message_row(key: &str, locale: &str, branch_id: &str) -> PreparedStateRow {
|
|
5667
5603
|
let snapshot = json!({
|
|
5668
5604
|
"key": key,
|
|
5669
5605
|
"locale": locale,
|
|
5670
5606
|
"text": "Welcome",
|
|
5671
5607
|
});
|
|
5672
5608
|
let mut row = staged_row("composite_message_schema", Some(snapshot.to_string()));
|
|
5673
|
-
row.
|
|
5609
|
+
row.entity_pk = EntityPk::from_primary_key_paths(
|
|
5674
5610
|
&snapshot,
|
|
5675
5611
|
&[vec!["key".to_string()], vec!["locale".to_string()]],
|
|
5676
5612
|
)
|
|
5677
5613
|
.expect("composite message identity should derive");
|
|
5678
5614
|
row.file_id = Some("file-a".to_string());
|
|
5679
|
-
row.
|
|
5615
|
+
row.branch_id = branch_id.to_string();
|
|
5680
5616
|
row.global = false;
|
|
5681
5617
|
row
|
|
5682
5618
|
}
|
|
5683
5619
|
|
|
5684
5620
|
fn state_surface_ref_row(
|
|
5685
|
-
|
|
5686
|
-
|
|
5621
|
+
entity_pk: &str,
|
|
5622
|
+
target_entity_pk: &str,
|
|
5687
5623
|
target_schema_key: &str,
|
|
5688
5624
|
target_file_id: &str,
|
|
5689
5625
|
) -> PreparedStateRow {
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
json!([
|
|
5626
|
+
state_surface_ref_row_with_target_entity_pk(
|
|
5627
|
+
entity_pk,
|
|
5628
|
+
json!([target_entity_pk]),
|
|
5693
5629
|
target_schema_key,
|
|
5694
5630
|
target_file_id,
|
|
5695
5631
|
)
|
|
5696
5632
|
}
|
|
5697
5633
|
|
|
5698
|
-
fn
|
|
5699
|
-
|
|
5700
|
-
|
|
5634
|
+
fn state_surface_ref_row_with_target_entity_pk(
|
|
5635
|
+
entity_pk: &str,
|
|
5636
|
+
target_entity_pk: JsonValue,
|
|
5701
5637
|
target_schema_key: &str,
|
|
5702
5638
|
target_file_id: &str,
|
|
5703
5639
|
) -> PreparedStateRow {
|
|
@@ -5705,17 +5641,17 @@ mod tests {
|
|
|
5705
5641
|
"state_surface_ref_schema",
|
|
5706
5642
|
Some(
|
|
5707
5643
|
json!({
|
|
5708
|
-
"id":
|
|
5709
|
-
"
|
|
5644
|
+
"id": entity_pk,
|
|
5645
|
+
"target_entity_pk": target_entity_pk,
|
|
5710
5646
|
"target_schema_key": target_schema_key,
|
|
5711
5647
|
"target_file_id": target_file_id,
|
|
5712
5648
|
})
|
|
5713
5649
|
.to_string(),
|
|
5714
5650
|
),
|
|
5715
5651
|
);
|
|
5716
|
-
row.
|
|
5652
|
+
row.entity_pk = crate::entity_pk::EntityPk::single(entity_pk);
|
|
5717
5653
|
row.file_id = Some("file-a".to_string());
|
|
5718
|
-
row.
|
|
5654
|
+
row.branch_id = "branch-a".to_string();
|
|
5719
5655
|
row.global = false;
|
|
5720
5656
|
row
|
|
5721
5657
|
}
|
|
@@ -5732,7 +5668,7 @@ mod tests {
|
|
|
5732
5668
|
row.commit_id = None;
|
|
5733
5669
|
}
|
|
5734
5670
|
|
|
5735
|
-
fn staged_file_descriptor_row(file_id: &str,
|
|
5671
|
+
fn staged_file_descriptor_row(file_id: &str, branch_id: &str) -> PreparedStateRow {
|
|
5736
5672
|
let mut row = staged_row(
|
|
5737
5673
|
FILE_DESCRIPTOR_SCHEMA_KEY,
|
|
5738
5674
|
Some(
|
|
@@ -5745,22 +5681,22 @@ mod tests {
|
|
|
5745
5681
|
.to_string(),
|
|
5746
5682
|
),
|
|
5747
5683
|
);
|
|
5748
|
-
row.
|
|
5684
|
+
row.entity_pk = crate::entity_pk::EntityPk::single(file_id);
|
|
5749
5685
|
row.file_id = None;
|
|
5750
|
-
row.
|
|
5751
|
-
row.global =
|
|
5686
|
+
row.branch_id = branch_id.to_string();
|
|
5687
|
+
row.global = branch_id == crate::GLOBAL_BRANCH_ID;
|
|
5752
5688
|
row
|
|
5753
5689
|
}
|
|
5754
5690
|
|
|
5755
|
-
fn committed_file_descriptor_row(file_id: &str,
|
|
5756
|
-
MaterializedLiveStateRow::from(staged_file_descriptor_row(file_id,
|
|
5691
|
+
fn committed_file_descriptor_row(file_id: &str, branch_id: &str) -> MaterializedLiveStateRow {
|
|
5692
|
+
MaterializedLiveStateRow::from(staged_file_descriptor_row(file_id, branch_id))
|
|
5757
5693
|
}
|
|
5758
5694
|
|
|
5759
5695
|
fn directory_descriptor_row(
|
|
5760
5696
|
directory_id: &str,
|
|
5761
5697
|
parent_id: Option<&str>,
|
|
5762
5698
|
name: &str,
|
|
5763
|
-
|
|
5699
|
+
branch_id: &str,
|
|
5764
5700
|
) -> PreparedStateRow {
|
|
5765
5701
|
let mut row = staged_row(
|
|
5766
5702
|
DIRECTORY_DESCRIPTOR_SCHEMA_KEY,
|
|
@@ -5774,17 +5710,17 @@ mod tests {
|
|
|
5774
5710
|
.to_string(),
|
|
5775
5711
|
),
|
|
5776
5712
|
);
|
|
5777
|
-
row.
|
|
5713
|
+
row.entity_pk = crate::entity_pk::EntityPk::single(directory_id);
|
|
5778
5714
|
row.file_id = None;
|
|
5779
|
-
row.
|
|
5780
|
-
row.global =
|
|
5715
|
+
row.branch_id = branch_id.to_string();
|
|
5716
|
+
row.global = branch_id == crate::GLOBAL_BRANCH_ID;
|
|
5781
5717
|
row
|
|
5782
5718
|
}
|
|
5783
5719
|
|
|
5784
|
-
fn committed_unique_row(
|
|
5785
|
-
let row = unique_row(
|
|
5720
|
+
fn committed_unique_row(entity_pk: &str, slug: &str, title: &str) -> MaterializedLiveStateRow {
|
|
5721
|
+
let row = unique_row(entity_pk, slug, title);
|
|
5786
5722
|
MaterializedLiveStateRow {
|
|
5787
|
-
|
|
5723
|
+
entity_pk: row.entity_pk,
|
|
5788
5724
|
schema_key: row.schema_key,
|
|
5789
5725
|
file_id: row.file_id,
|
|
5790
5726
|
snapshot_content: row.snapshot.as_ref().map(|snapshot| snapshot.materialize()),
|
|
@@ -5796,23 +5732,23 @@ mod tests {
|
|
|
5796
5732
|
change_id: row.change_id,
|
|
5797
5733
|
commit_id: row.commit_id,
|
|
5798
5734
|
untracked: row.untracked,
|
|
5799
|
-
|
|
5735
|
+
branch_id: row.branch_id,
|
|
5800
5736
|
}
|
|
5801
5737
|
}
|
|
5802
5738
|
|
|
5803
5739
|
fn committed_nullable_unique_row(
|
|
5804
|
-
|
|
5740
|
+
entity_pk: &str,
|
|
5805
5741
|
scope: Option<&str>,
|
|
5806
5742
|
name: &str,
|
|
5807
5743
|
) -> MaterializedLiveStateRow {
|
|
5808
|
-
MaterializedLiveStateRow::from(nullable_unique_row(
|
|
5744
|
+
MaterializedLiveStateRow::from(nullable_unique_row(entity_pk, scope, name))
|
|
5809
5745
|
}
|
|
5810
5746
|
|
|
5811
5747
|
fn staged_row(schema_key: &str, snapshot_content: Option<String>) -> PreparedStateRow {
|
|
5812
5748
|
PreparedStateRow {
|
|
5813
5749
|
schema_plan_id: crate::catalog::SchemaPlanId::for_test(0),
|
|
5814
5750
|
facts: crate::transaction::types::PreparedRowFacts::default(),
|
|
5815
|
-
|
|
5751
|
+
entity_pk: crate::entity_pk::EntityPk::single("entity-1"),
|
|
5816
5752
|
schema_key: schema_key.to_string(),
|
|
5817
5753
|
file_id: None,
|
|
5818
5754
|
snapshot: snapshot_content.as_deref().map(test_stage_json),
|
|
@@ -5824,7 +5760,7 @@ mod tests {
|
|
|
5824
5760
|
change_id: Some("change-1".to_string()),
|
|
5825
5761
|
commit_id: Some("commit-1".to_string()),
|
|
5826
5762
|
untracked: false,
|
|
5827
|
-
|
|
5763
|
+
branch_id: crate::GLOBAL_BRANCH_ID.to_string(),
|
|
5828
5764
|
}
|
|
5829
5765
|
}
|
|
5830
5766
|
}
|