@lix-js/sdk 0.6.0-preview.5 → 0.6.0
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 +76 -4
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +19 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +3 -3
- package/dist/native.d.ts +1 -0
- package/dist/native.js +47 -0
- package/dist/open-lix.d.ts +38 -207
- package/dist/open-lix.js +59 -284
- package/dist/result.d.ts +18 -0
- package/dist/result.js +48 -0
- package/dist/types.d.ts +114 -1
- package/dist/value.d.ts +28 -0
- package/dist/value.js +245 -0
- package/package.json +38 -71
- package/SKILL.md +0 -507
- package/dist/builtin-schemas.d.ts +0 -1
- package/dist/builtin-schemas.js +0 -1
- package/dist/engine-wasm/index.d.ts +0 -87
- package/dist/engine-wasm/index.js +0 -339
- package/dist/engine-wasm/wasm/lix_engine.d.ts +0 -79
- package/dist/engine-wasm/wasm/lix_engine.js +0 -833
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +0 -27
- package/dist/generated/builtin-schemas.d.ts +0 -427
- package/dist/generated/builtin-schemas.js +0 -643
- package/dist/sqlite/index.d.ts +0 -12
- package/dist/sqlite/index.js +0 -359
- package/dist-engine-src/README.md +0 -18
- package/dist-engine-src/src/backend/capabilities.rs +0 -67
- package/dist-engine-src/src/backend/conformance/baseline.rs +0 -1127
- package/dist-engine-src/src/backend/conformance/factory.rs +0 -93
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +0 -608
- package/dist-engine-src/src/backend/conformance/fixtures.rs +0 -26
- package/dist-engine-src/src/backend/conformance/mod.rs +0 -75
- package/dist-engine-src/src/backend/conformance/model.rs +0 -28
- package/dist-engine-src/src/backend/conformance/model_based.rs +0 -257
- package/dist-engine-src/src/backend/conformance/persistence.rs +0 -204
- package/dist-engine-src/src/backend/conformance/projection.rs +0 -21
- package/dist-engine-src/src/backend/conformance/pushdown.rs +0 -24
- package/dist-engine-src/src/backend/conformance/runner.rs +0 -90
- package/dist-engine-src/src/backend/conformance/scan.rs +0 -24
- package/dist-engine-src/src/backend/conformance/write.rs +0 -16
- package/dist-engine-src/src/backend/error.rs +0 -94
- package/dist-engine-src/src/backend/in_memory.rs +0 -670
- package/dist-engine-src/src/backend/mod.rs +0 -39
- package/dist-engine-src/src/backend/predicate.rs +0 -80
- package/dist-engine-src/src/backend/traits.rs +0 -260
- package/dist-engine-src/src/backend/types.rs +0 -239
- package/dist-engine-src/src/binary_cas/chunking.rs +0 -31
- package/dist-engine-src/src/binary_cas/codec.rs +0 -346
- package/dist-engine-src/src/binary_cas/context.rs +0 -139
- package/dist-engine-src/src/binary_cas/kv.rs +0 -1038
- package/dist-engine-src/src/binary_cas/mod.rs +0 -11
- package/dist-engine-src/src/binary_cas/types.rs +0 -121
- package/dist-engine-src/src/branch/context.rs +0 -40
- package/dist-engine-src/src/branch/lifecycle.rs +0 -221
- package/dist-engine-src/src/branch/mod.rs +0 -13
- package/dist-engine-src/src/branch/refs.rs +0 -321
- package/dist-engine-src/src/branch/stage_rows.rs +0 -67
- package/dist-engine-src/src/branch/types.rs +0 -21
- package/dist-engine-src/src/catalog/context.rs +0 -412
- package/dist-engine-src/src/catalog/mod.rs +0 -10
- package/dist-engine-src/src/catalog/schema.rs +0 -4
- package/dist-engine-src/src/catalog/snapshot.rs +0 -1114
- package/dist-engine-src/src/cel/context.rs +0 -86
- package/dist-engine-src/src/cel/error.rs +0 -19
- package/dist-engine-src/src/cel/mod.rs +0 -8
- package/dist-engine-src/src/cel/provider.rs +0 -9
- package/dist-engine-src/src/cel/runtime.rs +0 -167
- package/dist-engine-src/src/cel/value.rs +0 -50
- package/dist-engine-src/src/changelog/bench_support.rs +0 -785
- package/dist-engine-src/src/changelog/change.rs +0 -1
- package/dist-engine-src/src/changelog/codec.rs +0 -497
- package/dist-engine-src/src/changelog/commit.rs +0 -1
- package/dist-engine-src/src/changelog/context.rs +0 -1614
- package/dist-engine-src/src/changelog/mod.rs +0 -29
- package/dist-engine-src/src/changelog/store.rs +0 -163
- package/dist-engine-src/src/changelog/test_support.rs +0 -54
- package/dist-engine-src/src/changelog/types.rs +0 -213
- package/dist-engine-src/src/commit_graph/context.rs +0 -944
- package/dist-engine-src/src/commit_graph/mod.rs +0 -9
- package/dist-engine-src/src/commit_graph/types.rs +0 -89
- package/dist-engine-src/src/commit_graph/walker.rs +0 -786
- package/dist-engine-src/src/common/error.rs +0 -347
- package/dist-engine-src/src/common/fingerprint.rs +0 -3
- package/dist-engine-src/src/common/fs_path.rs +0 -1336
- package/dist-engine-src/src/common/identity.rs +0 -145
- package/dist-engine-src/src/common/json_pointer.rs +0 -67
- package/dist-engine-src/src/common/metadata.rs +0 -40
- package/dist-engine-src/src/common/mod.rs +0 -23
- package/dist-engine-src/src/common/types.rs +0 -105
- package/dist-engine-src/src/common/wire.rs +0 -222
- package/dist-engine-src/src/domain.rs +0 -320
- package/dist-engine-src/src/engine.rs +0 -203
- package/dist-engine-src/src/entity_pk.rs +0 -402
- package/dist-engine-src/src/functions/context.rs +0 -296
- package/dist-engine-src/src/functions/deterministic.rs +0 -113
- package/dist-engine-src/src/functions/mod.rs +0 -18
- package/dist-engine-src/src/functions/provider.rs +0 -130
- package/dist-engine-src/src/functions/state.rs +0 -335
- package/dist-engine-src/src/functions/types.rs +0 -37
- package/dist-engine-src/src/init.rs +0 -692
- package/dist-engine-src/src/json_store/compression.rs +0 -77
- package/dist-engine-src/src/json_store/context.rs +0 -172
- package/dist-engine-src/src/json_store/encoded.rs +0 -15
- package/dist-engine-src/src/json_store/mod.rs +0 -38
- package/dist-engine-src/src/json_store/store.rs +0 -494
- package/dist-engine-src/src/json_store/types.rs +0 -212
- package/dist-engine-src/src/lib.rs +0 -92
- package/dist-engine-src/src/live_state/context.rs +0 -1883
- package/dist-engine-src/src/live_state/mod.rs +0 -21
- package/dist-engine-src/src/live_state/overlay.rs +0 -75
- package/dist-engine-src/src/live_state/reader.rs +0 -23
- package/dist-engine-src/src/live_state/types.rs +0 -231
- package/dist-engine-src/src/live_state/visibility.rs +0 -666
- package/dist-engine-src/src/plugin/archive.rs +0 -438
- package/dist-engine-src/src/plugin/component.rs +0 -183
- package/dist-engine-src/src/plugin/install.rs +0 -619
- package/dist-engine-src/src/plugin/manifest.rs +0 -516
- package/dist-engine-src/src/plugin/materializer.rs +0 -202
- package/dist-engine-src/src/plugin/mod.rs +0 -33
- package/dist-engine-src/src/plugin/plugin_manifest.json +0 -119
- package/dist-engine-src/src/plugin/storage.rs +0 -74
- package/dist-engine-src/src/schema/annotations/defaults.rs +0 -275
- package/dist-engine-src/src/schema/annotations/mod.rs +0 -1
- package/dist-engine-src/src/schema/builtin/lix_account.json +0 -21
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +0 -48
- package/dist-engine-src/src/schema/builtin/lix_change.json +0 -63
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -45
- package/dist-engine-src/src/schema/builtin/lix_commit.json +0 -24
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +0 -53
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -52
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -52
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -40
- package/dist-engine-src/src/schema/builtin/lix_label.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +0 -74
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +0 -25
- package/dist-engine-src/src/schema/builtin/mod.rs +0 -220
- package/dist-engine-src/src/schema/compatibility.rs +0 -787
- package/dist-engine-src/src/schema/definition.json +0 -187
- package/dist-engine-src/src/schema/definition.rs +0 -742
- package/dist-engine-src/src/schema/key.rs +0 -138
- package/dist-engine-src/src/schema/mod.rs +0 -20
- package/dist-engine-src/src/schema/seed.rs +0 -14
- package/dist-engine-src/src/schema/tests.rs +0 -780
- package/dist-engine-src/src/session/context.rs +0 -1059
- package/dist-engine-src/src/session/create_branch.rs +0 -94
- package/dist-engine-src/src/session/execute.rs +0 -681
- package/dist-engine-src/src/session/merge/analysis.rs +0 -108
- package/dist-engine-src/src/session/merge/branch.rs +0 -417
- package/dist-engine-src/src/session/merge/conflicts.rs +0 -63
- package/dist-engine-src/src/session/merge/mod.rs +0 -10
- package/dist-engine-src/src/session/merge/stats.rs +0 -61
- package/dist-engine-src/src/session/mod.rs +0 -30
- package/dist-engine-src/src/session/switch_branch.rs +0 -113
- package/dist-engine-src/src/session/transaction.rs +0 -557
- package/dist-engine-src/src/sql2/bind/classify.rs +0 -102
- package/dist-engine-src/src/sql2/bind/error.rs +0 -5
- package/dist-engine-src/src/sql2/bind/expr.rs +0 -29
- package/dist-engine-src/src/sql2/bind/mod.rs +0 -12
- package/dist-engine-src/src/sql2/bind/public_udf.rs +0 -306
- package/dist-engine-src/src/sql2/bind/read.rs +0 -65
- package/dist-engine-src/src/sql2/bind/statement.rs +0 -2236
- package/dist-engine-src/src/sql2/bind/table.rs +0 -273
- package/dist-engine-src/src/sql2/bind/write.rs +0 -86
- package/dist-engine-src/src/sql2/branch_scope.rs +0 -436
- package/dist-engine-src/src/sql2/catalog/capability.rs +0 -20
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +0 -296
- package/dist-engine-src/src/sql2/catalog/mod.rs +0 -15
- package/dist-engine-src/src/sql2/catalog/registry.rs +0 -556
- package/dist-engine-src/src/sql2/catalog/schema.rs +0 -88
- package/dist-engine-src/src/sql2/catalog/surface.rs +0 -41
- package/dist-engine-src/src/sql2/change_materialization.rs +0 -122
- package/dist-engine-src/src/sql2/context.rs +0 -317
- package/dist-engine-src/src/sql2/dml.rs +0 -148
- package/dist-engine-src/src/sql2/error.rs +0 -215
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +0 -1593
- package/dist-engine-src/src/sql2/exec/datafusion.rs +0 -5266
- package/dist-engine-src/src/sql2/exec/fast_write.rs +0 -82
- package/dist-engine-src/src/sql2/exec/mod.rs +0 -24
- package/dist-engine-src/src/sql2/exec/write.rs +0 -661
- package/dist-engine-src/src/sql2/filesystem_planner.rs +0 -1485
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +0 -159
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +0 -383
- package/dist-engine-src/src/sql2/history_projection.rs +0 -56
- package/dist-engine-src/src/sql2/history_route.rs +0 -661
- package/dist-engine-src/src/sql2/mod.rs +0 -52
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +0 -1
- package/dist-engine-src/src/sql2/optimize/mod.rs +0 -2
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +0 -116
- package/dist-engine-src/src/sql2/parse/mod.rs +0 -69
- package/dist-engine-src/src/sql2/parse/normalize.rs +0 -1
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +0 -24
- package/dist-engine-src/src/sql2/plan/mod.rs +0 -5
- package/dist-engine-src/src/sql2/plan/predicate.rs +0 -22
- package/dist-engine-src/src/sql2/plan/write.rs +0 -147
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +0 -504
- package/dist-engine-src/src/sql2/providers/branch.rs +0 -1206
- package/dist-engine-src/src/sql2/providers/change.rs +0 -445
- package/dist-engine-src/src/sql2/providers/directory.rs +0 -2422
- package/dist-engine-src/src/sql2/providers/directory_history.rs +0 -645
- package/dist-engine-src/src/sql2/providers/entity.rs +0 -1484
- package/dist-engine-src/src/sql2/providers/entity_history.rs +0 -452
- package/dist-engine-src/src/sql2/providers/file.rs +0 -3686
- package/dist-engine-src/src/sql2/providers/file_history.rs +0 -924
- package/dist-engine-src/src/sql2/providers/history.rs +0 -426
- package/dist-engine-src/src/sql2/providers/lix_state.rs +0 -2542
- package/dist-engine-src/src/sql2/providers/mod.rs +0 -508
- package/dist-engine-src/src/sql2/read_only.rs +0 -63
- package/dist-engine-src/src/sql2/record_batch.rs +0 -17
- package/dist-engine-src/src/sql2/result_metadata.rs +0 -29
- package/dist-engine-src/src/sql2/runtime.rs +0 -60
- package/dist-engine-src/src/sql2/session.rs +0 -83
- package/dist-engine-src/src/sql2/storage/constraints.rs +0 -1
- package/dist-engine-src/src/sql2/storage/mod.rs +0 -1
- package/dist-engine-src/src/sql2/test_support/differential.rs +0 -712
- package/dist-engine-src/src/sql2/test_support/generators.rs +0 -354
- package/dist-engine-src/src/sql2/test_support/mod.rs +0 -2
- package/dist-engine-src/src/sql2/udfs/common.rs +0 -295
- package/dist-engine-src/src/sql2/udfs/lix_active_branch_commit_id.rs +0 -53
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +0 -47
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +0 -100
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +0 -99
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +0 -99
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +0 -82
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +0 -85
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +0 -76
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +0 -76
- package/dist-engine-src/src/sql2/udfs/mod.rs +0 -86
- package/dist-engine-src/src/sql2/write_normalization.rs +0 -368
- package/dist-engine-src/src/storage/conformance.rs +0 -399
- package/dist-engine-src/src/storage/context.rs +0 -620
- package/dist-engine-src/src/storage/mod.rs +0 -52
- package/dist-engine-src/src/storage/point.rs +0 -440
- package/dist-engine-src/src/storage/read_scope.rs +0 -67
- package/dist-engine-src/src/storage/reader.rs +0 -867
- package/dist-engine-src/src/storage/scan.rs +0 -784
- package/dist-engine-src/src/storage/spaces.rs +0 -236
- package/dist-engine-src/src/storage/stats.rs +0 -80
- package/dist-engine-src/src/storage/write_set.rs +0 -962
- package/dist-engine-src/src/storage_bench.rs +0 -171
- package/dist-engine-src/src/test_support.rs +0 -450
- package/dist-engine-src/src/tracked_state/bench_support.rs +0 -394
- package/dist-engine-src/src/tracked_state/codec.rs +0 -1183
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +0 -358
- package/dist-engine-src/src/tracked_state/context.rs +0 -2801
- package/dist-engine-src/src/tracked_state/diff.rs +0 -2140
- package/dist-engine-src/src/tracked_state/merge.rs +0 -478
- package/dist-engine-src/src/tracked_state/mod.rs +0 -35
- package/dist-engine-src/src/tracked_state/row_materialization.rs +0 -275
- package/dist-engine-src/src/tracked_state/storage.rs +0 -427
- package/dist-engine-src/src/tracked_state/tree.rs +0 -3063
- package/dist-engine-src/src/tracked_state/types.rs +0 -238
- package/dist-engine-src/src/transaction/bench_support.rs +0 -407
- package/dist-engine-src/src/transaction/commit.rs +0 -1592
- package/dist-engine-src/src/transaction/context.rs +0 -1653
- package/dist-engine-src/src/transaction/mod.rs +0 -24
- package/dist-engine-src/src/transaction/normalization.rs +0 -877
- package/dist-engine-src/src/transaction/prep.rs +0 -37
- package/dist-engine-src/src/transaction/schema_resolver.rs +0 -163
- package/dist-engine-src/src/transaction/staging.rs +0 -1525
- package/dist-engine-src/src/transaction/types.rs +0 -403
- package/dist-engine-src/src/transaction/validation.rs +0 -5766
- package/dist-engine-src/src/untracked_state/codec.rs +0 -615
- package/dist-engine-src/src/untracked_state/context.rs +0 -98
- package/dist-engine-src/src/untracked_state/materialization.rs +0 -63
- package/dist-engine-src/src/untracked_state/mod.rs +0 -15
- package/dist-engine-src/src/untracked_state/storage.rs +0 -898
- package/dist-engine-src/src/untracked_state/types.rs +0 -146
- package/dist-engine-src/src/wasm/mod.rs +0 -60
|
@@ -1,1883 +0,0 @@
|
|
|
1
|
-
use async_trait::async_trait;
|
|
2
|
-
use tokio::sync::Mutex;
|
|
3
|
-
|
|
4
|
-
use crate::branch::BRANCH_REF_SCHEMA_KEY;
|
|
5
|
-
use crate::commit_graph::CommitGraphContext;
|
|
6
|
-
use crate::entity_pk::EntityPk;
|
|
7
|
-
use crate::live_state::{
|
|
8
|
-
expanded_branch_ids, resolve_visible_rows, LiveStateReader, LiveStateRowRequest,
|
|
9
|
-
LiveStateScanRequest, MaterializedLiveStateRow, VisibilityBranchScope, VisibilityRequest,
|
|
10
|
-
};
|
|
11
|
-
use crate::storage::StorageRead;
|
|
12
|
-
use crate::tracked_state::{
|
|
13
|
-
MaterializedTrackedStateRow, TrackedStateContext, TrackedStateFilter, TrackedStateReadColumns,
|
|
14
|
-
TrackedStateScanRequest,
|
|
15
|
-
};
|
|
16
|
-
use crate::untracked_state::{
|
|
17
|
-
UntrackedStateContext, UntrackedStateRowRequest, UntrackedStateScanRequest,
|
|
18
|
-
};
|
|
19
|
-
use crate::LixError;
|
|
20
|
-
use crate::NullableKeyFilter;
|
|
21
|
-
use crate::GLOBAL_BRANCH_ID;
|
|
22
|
-
|
|
23
|
-
const COMMIT_SCHEMA_KEY: &str = "lix_commit";
|
|
24
|
-
const COMMIT_EDGE_SCHEMA_KEY: &str = "lix_commit_edge";
|
|
25
|
-
|
|
26
|
-
/// Serving facade for visible live-state reads.
|
|
27
|
-
///
|
|
28
|
-
/// Live state composes the rebuildable tracked projection with the durable
|
|
29
|
-
/// untracked local overlay. Lower stores own persistence; this facade owns the
|
|
30
|
-
/// visibility rule.
|
|
31
|
-
pub(crate) struct LiveStateContext {
|
|
32
|
-
tracked_state: TrackedStateContext,
|
|
33
|
-
untracked_state: UntrackedStateContext,
|
|
34
|
-
commit_graph: CommitGraphContext,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
impl LiveStateContext {
|
|
38
|
-
pub(crate) fn new(
|
|
39
|
-
tracked_state: TrackedStateContext,
|
|
40
|
-
untracked_state: UntrackedStateContext,
|
|
41
|
-
commit_graph: CommitGraphContext,
|
|
42
|
-
) -> Self {
|
|
43
|
-
Self {
|
|
44
|
-
tracked_state,
|
|
45
|
-
untracked_state,
|
|
46
|
-
commit_graph,
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/// Creates a visible live-state reader over a caller-provided KV store.
|
|
51
|
-
pub(crate) fn reader<S>(&self, store: S) -> LiveStateStoreReader<S>
|
|
52
|
-
where
|
|
53
|
-
S: StorageRead + Send + Sync,
|
|
54
|
-
{
|
|
55
|
-
LiveStateStoreReader {
|
|
56
|
-
store: Mutex::new(store),
|
|
57
|
-
tracked_state: self.tracked_state.clone(),
|
|
58
|
-
untracked_state: self.untracked_state,
|
|
59
|
-
commit_graph: self.commit_graph.clone(),
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/// Visible live-state reader backed by a caller-provided KV store.
|
|
65
|
-
pub(crate) struct LiveStateStoreReader<S> {
|
|
66
|
-
store: Mutex<S>,
|
|
67
|
-
tracked_state: TrackedStateContext,
|
|
68
|
-
untracked_state: UntrackedStateContext,
|
|
69
|
-
commit_graph: CommitGraphContext,
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
impl<S> LiveStateStoreReader<S>
|
|
73
|
-
where
|
|
74
|
-
S: StorageRead + Send + Sync,
|
|
75
|
-
{
|
|
76
|
-
pub(crate) async fn scan_rows(
|
|
77
|
-
&self,
|
|
78
|
-
request: &LiveStateScanRequest,
|
|
79
|
-
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
80
|
-
let store = self.store.lock().await;
|
|
81
|
-
let scope = scan_scope(&*store, &self.untracked_state, request).await?;
|
|
82
|
-
let derived_rows =
|
|
83
|
-
scan_commit_derived_rows(&*store, &self.commit_graph, request, &scope).await?;
|
|
84
|
-
let mut tracked_rows = Vec::new();
|
|
85
|
-
if request.filter.untracked != Some(true) && !is_commit_derived_only_request(request) {
|
|
86
|
-
for branch_id in &scope.storage_branch_ids {
|
|
87
|
-
let Some(commit_id) =
|
|
88
|
-
load_branch_ref_commit_id(&*store, &self.untracked_state, branch_id).await?
|
|
89
|
-
else {
|
|
90
|
-
continue;
|
|
91
|
-
};
|
|
92
|
-
let tracked_request = tracked_scan_request_from_live(request);
|
|
93
|
-
let source = tracked_source_from_branch_id(branch_id);
|
|
94
|
-
let store = &*store;
|
|
95
|
-
tracked_rows.extend(
|
|
96
|
-
self.tracked_state
|
|
97
|
-
.reader(store)
|
|
98
|
-
.scan_rows_at_commit(&commit_id, &tracked_request)
|
|
99
|
-
.await?
|
|
100
|
-
.into_iter()
|
|
101
|
-
.map(|row| project_tracked_row(row, branch_id, source)),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
let untracked_rows = if request.filter.untracked != Some(false) {
|
|
107
|
-
let store = &*store;
|
|
108
|
-
self.untracked_state
|
|
109
|
-
.reader(store)
|
|
110
|
-
.scan_rows(&untracked_scan_request_from_live(
|
|
111
|
-
request,
|
|
112
|
-
&scope.storage_branch_ids,
|
|
113
|
-
))
|
|
114
|
-
.await?
|
|
115
|
-
.into_iter()
|
|
116
|
-
.map(MaterializedLiveStateRow::from)
|
|
117
|
-
.collect::<Vec<_>>()
|
|
118
|
-
} else {
|
|
119
|
-
Vec::new()
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
let mut rows = if request.filter.untracked.is_some() {
|
|
123
|
-
tracked_rows
|
|
124
|
-
.into_iter()
|
|
125
|
-
.chain(untracked_rows)
|
|
126
|
-
.chain(derived_rows)
|
|
127
|
-
.collect()
|
|
128
|
-
} else {
|
|
129
|
-
crate::live_state::overlay::overlay_untracked_rows(tracked_rows, untracked_rows)
|
|
130
|
-
.into_iter()
|
|
131
|
-
.chain(derived_rows)
|
|
132
|
-
.collect()
|
|
133
|
-
};
|
|
134
|
-
rows = resolve_visible_rows(
|
|
135
|
-
rows,
|
|
136
|
-
Vec::new(),
|
|
137
|
-
&VisibilityRequest {
|
|
138
|
-
branch_scope: VisibilityBranchScope::BranchIds {
|
|
139
|
-
branch_ids: scope.projection_branch_ids.clone(),
|
|
140
|
-
},
|
|
141
|
-
include_tombstones: request.filter.include_tombstones,
|
|
142
|
-
limit: request.limit,
|
|
143
|
-
},
|
|
144
|
-
);
|
|
145
|
-
Ok(rows)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
pub(crate) async fn load_row(
|
|
149
|
-
&self,
|
|
150
|
-
request: &LiveStateRowRequest,
|
|
151
|
-
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
152
|
-
{
|
|
153
|
-
let store = self.store.lock().await;
|
|
154
|
-
if !branch_ref_exists(&*store, &self.untracked_state, &request.branch_id).await? {
|
|
155
|
-
return Ok(None);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
let rows = self
|
|
159
|
-
.scan_rows(&LiveStateScanRequest {
|
|
160
|
-
filter: crate::live_state::LiveStateFilter {
|
|
161
|
-
schema_keys: vec![request.schema_key.clone()],
|
|
162
|
-
entity_pks: vec![request.entity_pk.clone()],
|
|
163
|
-
branch_ids: vec![request.branch_id.clone()],
|
|
164
|
-
file_ids: vec![request.file_id.clone()],
|
|
165
|
-
include_tombstones: false,
|
|
166
|
-
..Default::default()
|
|
167
|
-
},
|
|
168
|
-
limit: Some(1),
|
|
169
|
-
..Default::default()
|
|
170
|
-
})
|
|
171
|
-
.await?;
|
|
172
|
-
Ok(rows.into_iter().next())
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
#[async_trait]
|
|
177
|
-
impl<S> LiveStateReader for LiveStateStoreReader<S>
|
|
178
|
-
where
|
|
179
|
-
S: StorageRead + Send + Sync,
|
|
180
|
-
{
|
|
181
|
-
async fn scan_rows(
|
|
182
|
-
&self,
|
|
183
|
-
request: &LiveStateScanRequest,
|
|
184
|
-
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
185
|
-
LiveStateStoreReader::scan_rows(self, request).await
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async fn load_row(
|
|
189
|
-
&self,
|
|
190
|
-
request: &LiveStateRowRequest,
|
|
191
|
-
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
192
|
-
LiveStateStoreReader::load_row(self, request).await
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
async fn scan_commit_derived_rows(
|
|
197
|
-
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
198
|
-
commit_graph: &CommitGraphContext,
|
|
199
|
-
request: &LiveStateScanRequest,
|
|
200
|
-
scope: &LiveStateScanScope,
|
|
201
|
-
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
202
|
-
if request.filter.untracked == Some(true) || !request_may_include_commit_derived(request) {
|
|
203
|
-
return Ok(Vec::new());
|
|
204
|
-
}
|
|
205
|
-
if !file_filter_allows_null(&request.filter.file_ids) {
|
|
206
|
-
return Ok(Vec::new());
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
let branch_ids = if scope.projection_branch_ids.is_empty() {
|
|
210
|
-
vec![GLOBAL_BRANCH_ID.to_string()]
|
|
211
|
-
} else {
|
|
212
|
-
scope.projection_branch_ids.clone()
|
|
213
|
-
};
|
|
214
|
-
let mut graph = commit_graph.reader(store);
|
|
215
|
-
let commits = graph.all_commits().await?;
|
|
216
|
-
let include_commit = schema_filter_allows(&request.filter.schema_keys, COMMIT_SCHEMA_KEY);
|
|
217
|
-
let include_commit_edge =
|
|
218
|
-
schema_filter_allows(&request.filter.schema_keys, COMMIT_EDGE_SCHEMA_KEY);
|
|
219
|
-
|
|
220
|
-
let mut rows = Vec::new();
|
|
221
|
-
for branch_id in &branch_ids {
|
|
222
|
-
if include_commit {
|
|
223
|
-
for commit in &commits {
|
|
224
|
-
rows.push(commit_row(commit, branch_id)?);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
if include_commit_edge {
|
|
228
|
-
for edge in graph.commit_edges(&commits) {
|
|
229
|
-
rows.push(commit_edge_row(&edge, branch_id)?);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
rows.retain(|row| {
|
|
235
|
-
(request.filter.entity_pks.is_empty() || request.filter.entity_pks.contains(&row.entity_pk))
|
|
236
|
-
&& (request.filter.branch_ids.is_empty()
|
|
237
|
-
|| request.filter.branch_ids.contains(&row.branch_id))
|
|
238
|
-
});
|
|
239
|
-
Ok(rows)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
fn request_may_include_commit_derived(request: &LiveStateScanRequest) -> bool {
|
|
243
|
-
request.filter.schema_keys.is_empty()
|
|
244
|
-
|| request
|
|
245
|
-
.filter
|
|
246
|
-
.schema_keys
|
|
247
|
-
.iter()
|
|
248
|
-
.any(|schema_key| is_commit_derived_schema(schema_key))
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
fn is_commit_derived_only_request(request: &LiveStateScanRequest) -> bool {
|
|
252
|
-
!request.filter.schema_keys.is_empty()
|
|
253
|
-
&& request
|
|
254
|
-
.filter
|
|
255
|
-
.schema_keys
|
|
256
|
-
.iter()
|
|
257
|
-
.all(|schema_key| is_commit_derived_schema(schema_key))
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
fn is_commit_derived_schema(schema_key: &str) -> bool {
|
|
261
|
-
matches!(schema_key, COMMIT_SCHEMA_KEY | COMMIT_EDGE_SCHEMA_KEY)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
fn schema_filter_allows(schema_keys: &[String], schema_key: &str) -> bool {
|
|
265
|
-
schema_keys.is_empty() || schema_keys.iter().any(|candidate| candidate == schema_key)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
fn file_filter_allows_null(file_ids: &[NullableKeyFilter<String>]) -> bool {
|
|
269
|
-
file_ids.is_empty()
|
|
270
|
-
|| file_ids
|
|
271
|
-
.iter()
|
|
272
|
-
.any(|file_id| matches!(file_id, NullableKeyFilter::Any | NullableKeyFilter::Null))
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
fn commit_row(
|
|
276
|
-
commit: &crate::commit_graph::CommitGraphCommit,
|
|
277
|
-
branch_id: &str,
|
|
278
|
-
) -> Result<MaterializedLiveStateRow, LixError> {
|
|
279
|
-
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
280
|
-
"id": commit.commit_id,
|
|
281
|
-
}))
|
|
282
|
-
.map_err(|error| {
|
|
283
|
-
LixError::new(
|
|
284
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
285
|
-
format!("failed to encode derived lix_commit snapshot: {error}"),
|
|
286
|
-
)
|
|
287
|
-
})?;
|
|
288
|
-
Ok(MaterializedLiveStateRow {
|
|
289
|
-
entity_pk: EntityPk::single(commit.commit_id.clone()),
|
|
290
|
-
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
291
|
-
file_id: None,
|
|
292
|
-
snapshot_content: Some(snapshot_content),
|
|
293
|
-
metadata: None,
|
|
294
|
-
deleted: false,
|
|
295
|
-
created_at: commit.change.created_at.clone(),
|
|
296
|
-
updated_at: commit.change.created_at.clone(),
|
|
297
|
-
global: true,
|
|
298
|
-
change_id: Some(commit.change.id.clone()),
|
|
299
|
-
commit_id: Some(commit.commit_id.clone()),
|
|
300
|
-
untracked: false,
|
|
301
|
-
branch_id: branch_id.to_string(),
|
|
302
|
-
})
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
fn commit_edge_row(
|
|
306
|
-
edge: &crate::commit_graph::CommitGraphEdge,
|
|
307
|
-
branch_id: &str,
|
|
308
|
-
) -> Result<MaterializedLiveStateRow, LixError> {
|
|
309
|
-
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
310
|
-
"parent_id": edge.parent_commit_id,
|
|
311
|
-
"child_id": edge.child_commit_id,
|
|
312
|
-
"parent_order": edge.parent_order,
|
|
313
|
-
}))
|
|
314
|
-
.map_err(|error| {
|
|
315
|
-
LixError::new(
|
|
316
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
317
|
-
format!("failed to encode derived lix_commit_edge snapshot: {error}"),
|
|
318
|
-
)
|
|
319
|
-
})?;
|
|
320
|
-
Ok(MaterializedLiveStateRow {
|
|
321
|
-
entity_pk: EntityPk {
|
|
322
|
-
parts: vec![edge.parent_commit_id.clone(), edge.child_commit_id.clone()],
|
|
323
|
-
},
|
|
324
|
-
schema_key: COMMIT_EDGE_SCHEMA_KEY.to_string(),
|
|
325
|
-
file_id: None,
|
|
326
|
-
snapshot_content: Some(snapshot_content),
|
|
327
|
-
metadata: None,
|
|
328
|
-
deleted: false,
|
|
329
|
-
created_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
330
|
-
updated_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
331
|
-
global: true,
|
|
332
|
-
change_id: None,
|
|
333
|
-
commit_id: Some(edge.child_commit_id.clone()),
|
|
334
|
-
untracked: false,
|
|
335
|
-
branch_id: branch_id.to_string(),
|
|
336
|
-
})
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
fn tracked_scan_request_from_live(request: &LiveStateScanRequest) -> TrackedStateScanRequest {
|
|
340
|
-
TrackedStateScanRequest {
|
|
341
|
-
filter: TrackedStateFilter {
|
|
342
|
-
schema_keys: request.filter.schema_keys.clone(),
|
|
343
|
-
entity_pks: request.filter.entity_pks.clone(),
|
|
344
|
-
file_ids: request.filter.file_ids.clone(),
|
|
345
|
-
// Scan tombstones internally so branch-local tombstones can hide
|
|
346
|
-
// global fallback rows before the serving facade filters them.
|
|
347
|
-
include_tombstones: true,
|
|
348
|
-
},
|
|
349
|
-
read_columns: TrackedStateReadColumns {
|
|
350
|
-
columns: request.projection.columns.clone(),
|
|
351
|
-
},
|
|
352
|
-
limit: None,
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
fn untracked_scan_request_from_live(
|
|
357
|
-
request: &LiveStateScanRequest,
|
|
358
|
-
branch_ids: &[String],
|
|
359
|
-
) -> UntrackedStateScanRequest {
|
|
360
|
-
let mut filter: crate::untracked_state::UntrackedStateFilter = request.filter.clone().into();
|
|
361
|
-
filter.branch_ids = branch_ids.to_vec();
|
|
362
|
-
UntrackedStateScanRequest {
|
|
363
|
-
filter,
|
|
364
|
-
projection: crate::untracked_state::UntrackedStateProjection {
|
|
365
|
-
columns: request.projection.columns.clone(),
|
|
366
|
-
},
|
|
367
|
-
limit: None,
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
372
|
-
struct LiveStateScanScope {
|
|
373
|
-
storage_branch_ids: Vec<String>,
|
|
374
|
-
projection_branch_ids: Vec<String>,
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
async fn scan_scope(
|
|
378
|
-
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
379
|
-
untracked_state: &UntrackedStateContext,
|
|
380
|
-
request: &LiveStateScanRequest,
|
|
381
|
-
) -> Result<LiveStateScanScope, LixError> {
|
|
382
|
-
if request.filter.branch_ids.is_empty() {
|
|
383
|
-
return Ok(LiveStateScanScope {
|
|
384
|
-
storage_branch_ids: all_branch_ref_ids(store, untracked_state).await?,
|
|
385
|
-
projection_branch_ids: Vec::new(),
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
let mut projection_branch_ids = Vec::new();
|
|
390
|
-
for branch_id in &request.filter.branch_ids {
|
|
391
|
-
if branch_ref_exists(store, untracked_state, branch_id).await? {
|
|
392
|
-
projection_branch_ids.push(branch_id.clone());
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
let storage_branch_ids = expanded_branch_ids(&projection_branch_ids);
|
|
397
|
-
Ok(LiveStateScanScope {
|
|
398
|
-
storage_branch_ids,
|
|
399
|
-
projection_branch_ids,
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async fn all_branch_ref_ids(
|
|
404
|
-
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
405
|
-
untracked_state: &UntrackedStateContext,
|
|
406
|
-
) -> Result<Vec<String>, LixError> {
|
|
407
|
-
let rows = untracked_state
|
|
408
|
-
.reader(store)
|
|
409
|
-
.scan_rows(&UntrackedStateScanRequest {
|
|
410
|
-
filter: crate::untracked_state::UntrackedStateFilter {
|
|
411
|
-
schema_keys: vec![BRANCH_REF_SCHEMA_KEY.to_string()],
|
|
412
|
-
branch_ids: vec![GLOBAL_BRANCH_ID.to_string()],
|
|
413
|
-
..Default::default()
|
|
414
|
-
},
|
|
415
|
-
..Default::default()
|
|
416
|
-
})
|
|
417
|
-
.await?;
|
|
418
|
-
rows.into_iter()
|
|
419
|
-
.map(|row| row.entity_pk.as_single_string_owned())
|
|
420
|
-
.collect()
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
async fn load_branch_ref_commit_id(
|
|
424
|
-
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
425
|
-
untracked_state: &UntrackedStateContext,
|
|
426
|
-
branch_id: &str,
|
|
427
|
-
) -> Result<Option<String>, LixError> {
|
|
428
|
-
let Some(row) = untracked_state
|
|
429
|
-
.reader(store)
|
|
430
|
-
.load_row(&UntrackedStateRowRequest {
|
|
431
|
-
schema_key: BRANCH_REF_SCHEMA_KEY.to_string(),
|
|
432
|
-
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
433
|
-
entity_pk: crate::entity_pk::EntityPk::single(branch_id),
|
|
434
|
-
file_id: crate::NullableKeyFilter::Null,
|
|
435
|
-
})
|
|
436
|
-
.await?
|
|
437
|
-
else {
|
|
438
|
-
return Ok(None);
|
|
439
|
-
};
|
|
440
|
-
let Some(snapshot_content) = row.snapshot_content.as_deref() else {
|
|
441
|
-
return Ok(None);
|
|
442
|
-
};
|
|
443
|
-
let snapshot =
|
|
444
|
-
serde_json::from_str::<serde_json::Value>(snapshot_content).map_err(|error| {
|
|
445
|
-
LixError::new(
|
|
446
|
-
"LIX_ERROR_UNKNOWN",
|
|
447
|
-
format!("live_state branch-ref snapshot parse failed: {error}"),
|
|
448
|
-
)
|
|
449
|
-
})?;
|
|
450
|
-
Ok(snapshot
|
|
451
|
-
.get("commit_id")
|
|
452
|
-
.and_then(serde_json::Value::as_str)
|
|
453
|
-
.map(str::to_string))
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
async fn branch_ref_exists(
|
|
457
|
-
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
458
|
-
untracked_state: &UntrackedStateContext,
|
|
459
|
-
branch_id: &str,
|
|
460
|
-
) -> Result<bool, LixError> {
|
|
461
|
-
Ok(load_branch_ref_commit_id(store, untracked_state, branch_id)
|
|
462
|
-
.await?
|
|
463
|
-
.is_some())
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
467
|
-
enum TrackedRowSource {
|
|
468
|
-
Global,
|
|
469
|
-
Branch,
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
fn tracked_source_from_branch_id(branch_id: &str) -> TrackedRowSource {
|
|
473
|
-
if branch_id == GLOBAL_BRANCH_ID {
|
|
474
|
-
TrackedRowSource::Global
|
|
475
|
-
} else {
|
|
476
|
-
TrackedRowSource::Branch
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
fn project_tracked_row(
|
|
481
|
-
row: MaterializedTrackedStateRow,
|
|
482
|
-
view_branch_id: &str,
|
|
483
|
-
source: TrackedRowSource,
|
|
484
|
-
) -> MaterializedLiveStateRow {
|
|
485
|
-
MaterializedLiveStateRow {
|
|
486
|
-
entity_pk: row.entity_pk,
|
|
487
|
-
schema_key: row.schema_key,
|
|
488
|
-
file_id: row.file_id,
|
|
489
|
-
snapshot_content: row.snapshot_content,
|
|
490
|
-
metadata: row.metadata,
|
|
491
|
-
deleted: row.deleted,
|
|
492
|
-
created_at: row.created_at,
|
|
493
|
-
updated_at: row.updated_at,
|
|
494
|
-
global: source == TrackedRowSource::Global,
|
|
495
|
-
change_id: Some(row.change_id),
|
|
496
|
-
commit_id: Some(row.commit_id),
|
|
497
|
-
untracked: false,
|
|
498
|
-
branch_id: view_branch_id.to_string(),
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
#[cfg(test)]
|
|
503
|
-
mod tests {
|
|
504
|
-
use super::*;
|
|
505
|
-
use crate::entity_pk::EntityPk;
|
|
506
|
-
use crate::json_store::{JsonRef, JsonStoreContext, JsonWritePlacementRef, NormalizedJsonRef};
|
|
507
|
-
use crate::live_state::LiveStateFilter;
|
|
508
|
-
use crate::storage::{InMemoryStorageBackend, StorageReadOptions, StorageWriteOptions};
|
|
509
|
-
use crate::storage::{StorageContext, StorageWriteSet};
|
|
510
|
-
use crate::tracked_state::{TrackedStateDeltaRef, TrackedStateScanRequest};
|
|
511
|
-
use crate::untracked_state::{MaterializedUntrackedStateRow, UntrackedStateContext};
|
|
512
|
-
use crate::NullableKeyFilter;
|
|
513
|
-
use serde_json::json;
|
|
514
|
-
|
|
515
|
-
const COMMIT_SCHEMA_KEY: &str = "lix_commit";
|
|
516
|
-
|
|
517
|
-
fn live_state_context() -> LiveStateContext {
|
|
518
|
-
LiveStateContext::new(
|
|
519
|
-
crate::tracked_state::TrackedStateContext::new(),
|
|
520
|
-
crate::untracked_state::UntrackedStateContext::new(),
|
|
521
|
-
crate::commit_graph::CommitGraphContext::new(),
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
async fn write_untracked_rows_to_store(
|
|
526
|
-
storage: &StorageContext,
|
|
527
|
-
_read: &(impl crate::storage::StorageRead + Send + Sync + ?Sized),
|
|
528
|
-
rows: &[MaterializedUntrackedStateRow],
|
|
529
|
-
) {
|
|
530
|
-
let mut writes = storage.new_write_set();
|
|
531
|
-
let canonical_rows = rows
|
|
532
|
-
.iter()
|
|
533
|
-
.map(|row| crate::test_support::untracked_state_row_from_materialized(&mut writes, row))
|
|
534
|
-
.collect::<Result<Vec<_>, _>>()
|
|
535
|
-
.expect("untracked rows should canonicalize");
|
|
536
|
-
UntrackedStateContext::new()
|
|
537
|
-
.writer(&mut writes)
|
|
538
|
-
.stage_rows(canonical_rows.iter().map(|row| row.as_ref()))
|
|
539
|
-
.expect("untracked rows should write");
|
|
540
|
-
storage
|
|
541
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
542
|
-
.expect("untracked rows should commit");
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
async fn write_empty_commits_to_store(
|
|
546
|
-
storage: &StorageContext,
|
|
547
|
-
read: &(impl crate::storage::StorageRead + Send + Sync),
|
|
548
|
-
commit_ids: &[&str],
|
|
549
|
-
) {
|
|
550
|
-
let mut writes = storage.new_write_set();
|
|
551
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
552
|
-
let mut append = crate::changelog::ChangelogAppend::default();
|
|
553
|
-
for commit_id in commit_ids {
|
|
554
|
-
let commit_change_id = format!("{commit_id}:commit");
|
|
555
|
-
append.commits.push(crate::changelog::CommitRecord {
|
|
556
|
-
format_version: 1,
|
|
557
|
-
commit_id: (*commit_id).to_string(),
|
|
558
|
-
parent_commit_ids: Vec::new(),
|
|
559
|
-
change_id: commit_change_id.clone(),
|
|
560
|
-
author_account_ids: Vec::new(),
|
|
561
|
-
created_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
562
|
-
});
|
|
563
|
-
append
|
|
564
|
-
.commit_change_refs
|
|
565
|
-
.push(crate::changelog::CommitChangeRefSet {
|
|
566
|
-
commit_id: (*commit_id).to_string(),
|
|
567
|
-
entries: Vec::new(),
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
let mut changelog_read = read;
|
|
571
|
-
let mut writer =
|
|
572
|
-
crate::changelog::ChangelogContext::new().writer(&mut changelog_read, &mut writes);
|
|
573
|
-
crate::changelog::ChangelogWriter::stage_append(&mut writer, append)
|
|
574
|
-
.await
|
|
575
|
-
.expect("empty changelog commits should stage");
|
|
576
|
-
drop(writer);
|
|
577
|
-
for commit_id in commit_ids {
|
|
578
|
-
let snapshot_content =
|
|
579
|
-
commit_row_snapshot_content(commit_id).expect("commit snapshot should encode");
|
|
580
|
-
let snapshot_ref = JsonRef::for_content(snapshot_content.as_bytes());
|
|
581
|
-
json_writer
|
|
582
|
-
.stage_batch(
|
|
583
|
-
&mut writes,
|
|
584
|
-
JsonWritePlacementRef::OutOfBand,
|
|
585
|
-
[NormalizedJsonRef::trusted_prehashed(
|
|
586
|
-
&snapshot_content,
|
|
587
|
-
snapshot_ref.clone(),
|
|
588
|
-
)],
|
|
589
|
-
)
|
|
590
|
-
.expect("commit snapshot should stage");
|
|
591
|
-
let change_id = format!("{commit_id}:commit");
|
|
592
|
-
let entity_pk = EntityPk::single(*commit_id);
|
|
593
|
-
let deltas = [TrackedStateDeltaRef {
|
|
594
|
-
schema_key: COMMIT_SCHEMA_KEY,
|
|
595
|
-
file_id: None,
|
|
596
|
-
entity_pk: &entity_pk,
|
|
597
|
-
change_id: &change_id,
|
|
598
|
-
commit_id,
|
|
599
|
-
snapshot_ref: Some(&snapshot_ref),
|
|
600
|
-
metadata_ref: None,
|
|
601
|
-
deleted: false,
|
|
602
|
-
created_at: "1970-01-01T00:00:00.000Z",
|
|
603
|
-
updated_at: "1970-01-01T00:00:00.000Z",
|
|
604
|
-
}];
|
|
605
|
-
TrackedStateContext::new()
|
|
606
|
-
.writer(read, &mut writes)
|
|
607
|
-
.stage_commit_root(commit_id, None, deltas)
|
|
608
|
-
.await
|
|
609
|
-
.expect("empty tracked roots should stage");
|
|
610
|
-
}
|
|
611
|
-
storage
|
|
612
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
613
|
-
.expect("empty commits should commit");
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
async fn stage_materialized_live_rows(
|
|
617
|
-
store: &(impl StorageRead + Send + Sync),
|
|
618
|
-
writes: &mut StorageWriteSet,
|
|
619
|
-
json_writer: &mut crate::json_store::JsonStoreWriter,
|
|
620
|
-
rows: &[MaterializedLiveStateRow],
|
|
621
|
-
) -> Result<(), LixError> {
|
|
622
|
-
let mut untracked_rows = Vec::new();
|
|
623
|
-
let mut tracked_rows_by_commit = std::collections::BTreeMap::<
|
|
624
|
-
String,
|
|
625
|
-
Vec<(crate::changelog::ChangeRecord, String, String)>,
|
|
626
|
-
>::new();
|
|
627
|
-
let mut parent_by_commit = std::collections::BTreeMap::<String, Option<String>>::new();
|
|
628
|
-
|
|
629
|
-
for row in rows {
|
|
630
|
-
if row.untracked {
|
|
631
|
-
let materialized = crate::untracked_state::MaterializedUntrackedStateRow::from(row);
|
|
632
|
-
let canonical = crate::test_support::untracked_state_row_from_materialized(
|
|
633
|
-
writes,
|
|
634
|
-
&materialized,
|
|
635
|
-
)?;
|
|
636
|
-
untracked_rows.push(canonical);
|
|
637
|
-
continue;
|
|
638
|
-
}
|
|
639
|
-
let materialized = MaterializedTrackedStateRow::try_from(row)?;
|
|
640
|
-
let commit_id = row.commit_id.clone().ok_or_else(|| {
|
|
641
|
-
LixError::new("LIX_ERROR_UNKNOWN", "test tracked row missing commit_id")
|
|
642
|
-
})?;
|
|
643
|
-
if row.schema_key == COMMIT_SCHEMA_KEY {
|
|
644
|
-
parent_by_commit.insert(
|
|
645
|
-
commit_id.clone(),
|
|
646
|
-
parent_commit_id_from_test_commit_row(row)?,
|
|
647
|
-
);
|
|
648
|
-
}
|
|
649
|
-
if row.schema_key != COMMIT_SCHEMA_KEY {
|
|
650
|
-
let change = crate::test_support::tracked_change_from_materialized(&materialized)?;
|
|
651
|
-
stage_json_payloads_from_materialized(writes, json_writer, &materialized)?;
|
|
652
|
-
tracked_rows_by_commit.entry(commit_id).or_default().push((
|
|
653
|
-
change,
|
|
654
|
-
materialized.created_at,
|
|
655
|
-
materialized.updated_at,
|
|
656
|
-
));
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
UntrackedStateContext::new()
|
|
661
|
-
.writer(writes)
|
|
662
|
-
.stage_rows(untracked_rows.iter().map(|row| row.as_ref()))?;
|
|
663
|
-
for (commit_id, rows) in tracked_rows_by_commit {
|
|
664
|
-
let parent_commit_id = parent_by_commit.remove(&commit_id).flatten();
|
|
665
|
-
let parent_ids = parent_commit_id
|
|
666
|
-
.as_ref()
|
|
667
|
-
.map(|parent| vec![parent.clone()])
|
|
668
|
-
.unwrap_or_default();
|
|
669
|
-
let commit_created_at = rows
|
|
670
|
-
.first()
|
|
671
|
-
.map(|(change, _, _)| change.created_at.as_str())
|
|
672
|
-
.unwrap_or("1970-01-01T00:00:00.000Z")
|
|
673
|
-
.to_string();
|
|
674
|
-
let change_refs = rows
|
|
675
|
-
.iter()
|
|
676
|
-
.map(|(change, _, _)| crate::changelog::CommitChangeRef {
|
|
677
|
-
schema_key: change.schema_key.clone(),
|
|
678
|
-
file_id: change.file_id.clone(),
|
|
679
|
-
entity_pk: change.entity_pk.clone(),
|
|
680
|
-
change_id: change.change_id.clone(),
|
|
681
|
-
})
|
|
682
|
-
.collect::<Vec<_>>();
|
|
683
|
-
let commit_change_id = format!("{commit_id}:commit");
|
|
684
|
-
let mut append = crate::changelog::ChangelogAppend::default();
|
|
685
|
-
append
|
|
686
|
-
.changes
|
|
687
|
-
.extend(rows.iter().map(|(change, _, _)| change.clone()));
|
|
688
|
-
append.commits.push(crate::changelog::CommitRecord {
|
|
689
|
-
format_version: 1,
|
|
690
|
-
commit_id: commit_id.clone(),
|
|
691
|
-
parent_commit_ids: parent_ids,
|
|
692
|
-
change_id: commit_change_id.clone(),
|
|
693
|
-
author_account_ids: Vec::new(),
|
|
694
|
-
created_at: commit_created_at.clone(),
|
|
695
|
-
});
|
|
696
|
-
append
|
|
697
|
-
.commit_change_refs
|
|
698
|
-
.push(crate::changelog::CommitChangeRefSet {
|
|
699
|
-
commit_id: commit_id.clone(),
|
|
700
|
-
entries: change_refs,
|
|
701
|
-
});
|
|
702
|
-
let mut changelog_read = store;
|
|
703
|
-
let mut writer =
|
|
704
|
-
crate::changelog::ChangelogContext::new().writer(&mut changelog_read, writes);
|
|
705
|
-
crate::changelog::ChangelogWriter::stage_append(&mut writer, append).await?;
|
|
706
|
-
drop(writer);
|
|
707
|
-
let snapshot_content = commit_row_snapshot_content(&commit_id)?;
|
|
708
|
-
let snapshot_ref = JsonRef::for_content(snapshot_content.as_bytes());
|
|
709
|
-
json_writer.stage_batch(
|
|
710
|
-
writes,
|
|
711
|
-
JsonWritePlacementRef::OutOfBand,
|
|
712
|
-
[NormalizedJsonRef::trusted_prehashed(
|
|
713
|
-
&snapshot_content,
|
|
714
|
-
snapshot_ref.clone(),
|
|
715
|
-
)],
|
|
716
|
-
)?;
|
|
717
|
-
let commit_entity_pk = EntityPk::single(&commit_id);
|
|
718
|
-
let mut deltas = rows
|
|
719
|
-
.iter()
|
|
720
|
-
.map(|(change, created_at, updated_at)| TrackedStateDeltaRef {
|
|
721
|
-
schema_key: &change.schema_key,
|
|
722
|
-
file_id: change.file_id.as_deref(),
|
|
723
|
-
entity_pk: &change.entity_pk,
|
|
724
|
-
change_id: &change.change_id,
|
|
725
|
-
commit_id: &commit_id,
|
|
726
|
-
snapshot_ref: change.snapshot_ref.as_ref(),
|
|
727
|
-
metadata_ref: change.metadata_ref.as_ref(),
|
|
728
|
-
deleted: change.snapshot_ref.is_none(),
|
|
729
|
-
created_at,
|
|
730
|
-
updated_at,
|
|
731
|
-
})
|
|
732
|
-
.collect::<Vec<_>>();
|
|
733
|
-
deltas.push(TrackedStateDeltaRef {
|
|
734
|
-
schema_key: COMMIT_SCHEMA_KEY,
|
|
735
|
-
file_id: None,
|
|
736
|
-
entity_pk: &commit_entity_pk,
|
|
737
|
-
change_id: &commit_change_id,
|
|
738
|
-
commit_id: &commit_id,
|
|
739
|
-
snapshot_ref: Some(&snapshot_ref),
|
|
740
|
-
metadata_ref: None,
|
|
741
|
-
deleted: false,
|
|
742
|
-
created_at: &commit_created_at,
|
|
743
|
-
updated_at: &commit_created_at,
|
|
744
|
-
});
|
|
745
|
-
TrackedStateContext::new()
|
|
746
|
-
.writer(&*store, writes)
|
|
747
|
-
.stage_commit_root(&commit_id, parent_commit_id.as_deref(), deltas)
|
|
748
|
-
.await?;
|
|
749
|
-
}
|
|
750
|
-
Ok(())
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
fn commit_row_snapshot_content(commit_id: &str) -> Result<String, LixError> {
|
|
754
|
-
serde_json::to_string(&json!({ "id": commit_id })).map_err(|error| {
|
|
755
|
-
LixError::new(
|
|
756
|
-
"LIX_ERROR_UNKNOWN",
|
|
757
|
-
format!("failed to encode test commit snapshot: {error}"),
|
|
758
|
-
)
|
|
759
|
-
})
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
fn stage_json_payloads_from_materialized(
|
|
763
|
-
writes: &mut StorageWriteSet,
|
|
764
|
-
json_writer: &mut crate::json_store::JsonStoreWriter,
|
|
765
|
-
row: &MaterializedTrackedStateRow,
|
|
766
|
-
) -> Result<(), LixError> {
|
|
767
|
-
if let Some(snapshot) = row.snapshot_content.as_deref() {
|
|
768
|
-
json_writer.stage_batch(
|
|
769
|
-
writes,
|
|
770
|
-
JsonWritePlacementRef::OutOfBand,
|
|
771
|
-
[NormalizedJsonRef::trusted_prehashed(
|
|
772
|
-
snapshot,
|
|
773
|
-
JsonRef::for_content(snapshot.as_bytes()),
|
|
774
|
-
)],
|
|
775
|
-
)?;
|
|
776
|
-
}
|
|
777
|
-
if let Some(metadata) = row.metadata.as_ref() {
|
|
778
|
-
let serialized = crate::serialize_row_metadata(metadata);
|
|
779
|
-
json_writer.stage_batch(
|
|
780
|
-
writes,
|
|
781
|
-
JsonWritePlacementRef::OutOfBand,
|
|
782
|
-
[NormalizedJsonRef::trusted_prehashed(
|
|
783
|
-
&serialized,
|
|
784
|
-
JsonRef::for_content(serialized.as_bytes()),
|
|
785
|
-
)],
|
|
786
|
-
)?;
|
|
787
|
-
}
|
|
788
|
-
Ok(())
|
|
789
|
-
}
|
|
790
|
-
|
|
791
|
-
fn parent_commit_id_from_test_commit_row(
|
|
792
|
-
row: &MaterializedLiveStateRow,
|
|
793
|
-
) -> Result<Option<String>, LixError> {
|
|
794
|
-
let Some(metadata) = row.metadata.as_deref() else {
|
|
795
|
-
return Ok(None);
|
|
796
|
-
};
|
|
797
|
-
let metadata = serde_json::from_str::<serde_json::Value>(metadata).map_err(|error| {
|
|
798
|
-
LixError::new(
|
|
799
|
-
"LIX_ERROR_UNKNOWN",
|
|
800
|
-
format!("test commit row has invalid metadata: {error}"),
|
|
801
|
-
)
|
|
802
|
-
})?;
|
|
803
|
-
Ok(metadata
|
|
804
|
-
.get("test_parents")
|
|
805
|
-
.and_then(serde_json::Value::as_array)
|
|
806
|
-
.and_then(|parents| parents.first())
|
|
807
|
-
.and_then(serde_json::Value::as_str)
|
|
808
|
-
.map(str::to_string))
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
#[tokio::test]
|
|
812
|
-
async fn live_state_overlays_untracked_rows() {
|
|
813
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
814
|
-
let live_state = live_state_context();
|
|
815
|
-
|
|
816
|
-
let read = storage
|
|
817
|
-
.begin_read(StorageReadOptions::default())
|
|
818
|
-
.expect("read should open");
|
|
819
|
-
{
|
|
820
|
-
let mut writes = StorageWriteSet::new();
|
|
821
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
822
|
-
{
|
|
823
|
-
stage_materialized_live_rows(
|
|
824
|
-
&read,
|
|
825
|
-
&mut writes,
|
|
826
|
-
&mut json_writer,
|
|
827
|
-
&[tracked_row_with_commit(
|
|
828
|
-
"tracked-value",
|
|
829
|
-
Some("change-tracked"),
|
|
830
|
-
"commit-tracked",
|
|
831
|
-
)],
|
|
832
|
-
)
|
|
833
|
-
.await
|
|
834
|
-
.expect("tracked row should stage");
|
|
835
|
-
}
|
|
836
|
-
storage
|
|
837
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
838
|
-
.expect("writes should commit");
|
|
839
|
-
}
|
|
840
|
-
write_untracked_rows_to_store(
|
|
841
|
-
&storage,
|
|
842
|
-
&read,
|
|
843
|
-
&[
|
|
844
|
-
branch_ref_row("global", "commit-tracked"),
|
|
845
|
-
untracked_row("untracked-value"),
|
|
846
|
-
],
|
|
847
|
-
)
|
|
848
|
-
.await;
|
|
849
|
-
|
|
850
|
-
let rows = scan_selected_tab_at(&live_state, &storage, "global", false)
|
|
851
|
-
.await
|
|
852
|
-
.expect("scan should succeed");
|
|
853
|
-
assert_eq!(rows.len(), 1);
|
|
854
|
-
assert_eq!(
|
|
855
|
-
rows[0].snapshot_content.as_deref(),
|
|
856
|
-
Some("{\"value\":\"untracked-value\"}")
|
|
857
|
-
);
|
|
858
|
-
assert!(rows[0].untracked);
|
|
859
|
-
assert_eq!(rows[0].change_id, None);
|
|
860
|
-
|
|
861
|
-
let loaded = live_state
|
|
862
|
-
.reader(
|
|
863
|
-
storage
|
|
864
|
-
.begin_read(StorageReadOptions::default())
|
|
865
|
-
.expect("read should open"),
|
|
866
|
-
)
|
|
867
|
-
.load_row(&LiveStateRowRequest {
|
|
868
|
-
schema_key: "lix_key_value".to_string(),
|
|
869
|
-
branch_id: "global".to_string(),
|
|
870
|
-
entity_pk: crate::entity_pk::EntityPk::single("selected-tab"),
|
|
871
|
-
file_id: NullableKeyFilter::Null,
|
|
872
|
-
})
|
|
873
|
-
.await
|
|
874
|
-
.expect("load should succeed")
|
|
875
|
-
.expect("overlay row should be visible");
|
|
876
|
-
assert!(loaded.untracked);
|
|
877
|
-
assert_eq!(
|
|
878
|
-
loaded.snapshot_content.as_deref(),
|
|
879
|
-
Some("{\"value\":\"untracked-value\"}")
|
|
880
|
-
);
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
#[tokio::test]
|
|
884
|
-
async fn tracked_row_is_visible_without_untracked_overlay() {
|
|
885
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
886
|
-
let live_state = live_state_context();
|
|
887
|
-
|
|
888
|
-
let read = storage
|
|
889
|
-
.begin_read(StorageReadOptions::default())
|
|
890
|
-
.expect("read should open");
|
|
891
|
-
{
|
|
892
|
-
let mut writes = StorageWriteSet::new();
|
|
893
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
894
|
-
{
|
|
895
|
-
stage_materialized_live_rows(
|
|
896
|
-
&read,
|
|
897
|
-
&mut writes,
|
|
898
|
-
&mut json_writer,
|
|
899
|
-
&[tracked_row_with_commit(
|
|
900
|
-
"tracked-value",
|
|
901
|
-
Some("change-tracked"),
|
|
902
|
-
"commit-tracked",
|
|
903
|
-
)],
|
|
904
|
-
)
|
|
905
|
-
.await
|
|
906
|
-
.expect("tracked row should stage");
|
|
907
|
-
}
|
|
908
|
-
storage
|
|
909
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
910
|
-
.expect("writes should commit");
|
|
911
|
-
}
|
|
912
|
-
write_untracked_rows_to_store(
|
|
913
|
-
&storage,
|
|
914
|
-
&read,
|
|
915
|
-
&[branch_ref_row("global", "commit-tracked")],
|
|
916
|
-
)
|
|
917
|
-
.await;
|
|
918
|
-
|
|
919
|
-
let loaded = load_selected_tab(&live_state, &storage)
|
|
920
|
-
.await
|
|
921
|
-
.expect("load should succeed")
|
|
922
|
-
.expect("tracked row should be visible");
|
|
923
|
-
assert!(!loaded.untracked);
|
|
924
|
-
assert_eq!(loaded.change_id.as_deref(), Some("change-tracked"));
|
|
925
|
-
assert_eq!(
|
|
926
|
-
loaded.snapshot_content.as_deref(),
|
|
927
|
-
Some("{\"value\":\"tracked-value\"}")
|
|
928
|
-
);
|
|
929
|
-
}
|
|
930
|
-
|
|
931
|
-
#[tokio::test]
|
|
932
|
-
async fn deleting_untracked_row_reveals_tracked_row() {
|
|
933
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
934
|
-
let live_state = live_state_context();
|
|
935
|
-
|
|
936
|
-
let read = storage
|
|
937
|
-
.begin_read(StorageReadOptions::default())
|
|
938
|
-
.expect("read should open");
|
|
939
|
-
{
|
|
940
|
-
let mut writes = StorageWriteSet::new();
|
|
941
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
942
|
-
{
|
|
943
|
-
stage_materialized_live_rows(
|
|
944
|
-
&read,
|
|
945
|
-
&mut writes,
|
|
946
|
-
&mut json_writer,
|
|
947
|
-
&[tracked_row_with_commit(
|
|
948
|
-
"tracked-value",
|
|
949
|
-
Some("change-tracked"),
|
|
950
|
-
"commit-tracked",
|
|
951
|
-
)],
|
|
952
|
-
)
|
|
953
|
-
.await
|
|
954
|
-
.expect("tracked row should stage");
|
|
955
|
-
}
|
|
956
|
-
storage
|
|
957
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
958
|
-
.expect("writes should commit");
|
|
959
|
-
}
|
|
960
|
-
write_untracked_rows_to_store(
|
|
961
|
-
&storage,
|
|
962
|
-
&read,
|
|
963
|
-
&[
|
|
964
|
-
branch_ref_row("global", "commit-tracked"),
|
|
965
|
-
untracked_row("untracked-value"),
|
|
966
|
-
],
|
|
967
|
-
)
|
|
968
|
-
.await;
|
|
969
|
-
{
|
|
970
|
-
let mut writes = StorageWriteSet::new();
|
|
971
|
-
let identity = crate::untracked_state::UntrackedStateIdentity {
|
|
972
|
-
branch_id: "global".to_string(),
|
|
973
|
-
schema_key: "lix_key_value".to_string(),
|
|
974
|
-
entity_pk: EntityPk::single("selected-tab"),
|
|
975
|
-
file_id: None,
|
|
976
|
-
};
|
|
977
|
-
UntrackedStateContext::new()
|
|
978
|
-
.writer(&mut writes)
|
|
979
|
-
.stage_delete_rows(std::iter::once(identity.as_ref()))
|
|
980
|
-
.expect("delete identity should stage");
|
|
981
|
-
storage
|
|
982
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
983
|
-
.expect("writes should commit");
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
let loaded = load_selected_tab(&live_state, &storage)
|
|
987
|
-
.await
|
|
988
|
-
.expect("load should succeed")
|
|
989
|
-
.expect("tracked row should be visible again");
|
|
990
|
-
assert!(!loaded.untracked);
|
|
991
|
-
assert_eq!(loaded.change_id.as_deref(), Some("change-tracked"));
|
|
992
|
-
assert_eq!(
|
|
993
|
-
loaded.snapshot_content.as_deref(),
|
|
994
|
-
Some("{\"value\":\"tracked-value\"}")
|
|
995
|
-
);
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
#[tokio::test]
|
|
999
|
-
async fn load_row_falls_back_to_global_tracked_row_for_requested_branch() {
|
|
1000
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1001
|
-
let live_state = live_state_context();
|
|
1002
|
-
|
|
1003
|
-
let read = storage
|
|
1004
|
-
.begin_read(StorageReadOptions::default())
|
|
1005
|
-
.expect("read should open");
|
|
1006
|
-
{
|
|
1007
|
-
let rows = [tracked_row_with_commit(
|
|
1008
|
-
"global-tracked",
|
|
1009
|
-
Some("change-global"),
|
|
1010
|
-
"commit-global",
|
|
1011
|
-
)];
|
|
1012
|
-
let mut writes = StorageWriteSet::new();
|
|
1013
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1014
|
-
{
|
|
1015
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1016
|
-
.await
|
|
1017
|
-
.expect("tracked row should stage");
|
|
1018
|
-
}
|
|
1019
|
-
storage
|
|
1020
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1021
|
-
.expect("writes should commit");
|
|
1022
|
-
}
|
|
1023
|
-
write_untracked_rows_to_store(
|
|
1024
|
-
&storage,
|
|
1025
|
-
&read,
|
|
1026
|
-
&[
|
|
1027
|
-
branch_ref_row("global", "commit-global"),
|
|
1028
|
-
branch_ref_row("branch-a", "commit-branch-a"),
|
|
1029
|
-
],
|
|
1030
|
-
)
|
|
1031
|
-
.await;
|
|
1032
|
-
write_empty_commits_to_store(&storage, &read, &["commit-branch-a"]).await;
|
|
1033
|
-
|
|
1034
|
-
let loaded = load_selected_tab_at(&live_state, &storage, "branch-a")
|
|
1035
|
-
.await
|
|
1036
|
-
.expect("load should succeed")
|
|
1037
|
-
.expect("global row should be visible for requested branch");
|
|
1038
|
-
|
|
1039
|
-
assert_eq!(loaded.branch_id, "branch-a");
|
|
1040
|
-
assert!(loaded.global);
|
|
1041
|
-
assert!(!loaded.untracked);
|
|
1042
|
-
assert_eq!(
|
|
1043
|
-
loaded.snapshot_content.as_deref(),
|
|
1044
|
-
Some("{\"value\":\"global-tracked\"}")
|
|
1045
|
-
);
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
#[tokio::test]
|
|
1049
|
-
async fn main_sees_global_row_by_reading_global_root_separately() {
|
|
1050
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1051
|
-
let tracked_state = TrackedStateContext::new();
|
|
1052
|
-
let live_state = LiveStateContext::new(
|
|
1053
|
-
tracked_state.clone(),
|
|
1054
|
-
UntrackedStateContext::new(),
|
|
1055
|
-
crate::commit_graph::CommitGraphContext::new(),
|
|
1056
|
-
);
|
|
1057
|
-
|
|
1058
|
-
let read = storage
|
|
1059
|
-
.begin_read(StorageReadOptions::default())
|
|
1060
|
-
.expect("read should open");
|
|
1061
|
-
{
|
|
1062
|
-
let rows = [tracked_row_with_commit(
|
|
1063
|
-
"global-tracked",
|
|
1064
|
-
Some("change-global"),
|
|
1065
|
-
"commit-global",
|
|
1066
|
-
)];
|
|
1067
|
-
let mut writes = StorageWriteSet::new();
|
|
1068
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1069
|
-
{
|
|
1070
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1071
|
-
.await
|
|
1072
|
-
.expect("global tracked row should stage");
|
|
1073
|
-
}
|
|
1074
|
-
storage
|
|
1075
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1076
|
-
.expect("writes should commit");
|
|
1077
|
-
}
|
|
1078
|
-
write_untracked_rows_to_store(
|
|
1079
|
-
&storage,
|
|
1080
|
-
&read,
|
|
1081
|
-
&[
|
|
1082
|
-
branch_ref_row("global", "commit-global"),
|
|
1083
|
-
branch_ref_row("main", "commit-main"),
|
|
1084
|
-
],
|
|
1085
|
-
)
|
|
1086
|
-
.await;
|
|
1087
|
-
write_empty_commits_to_store(&storage, &read, &["commit-main"]).await;
|
|
1088
|
-
|
|
1089
|
-
let loaded = load_selected_tab_at(&live_state, &storage, "main")
|
|
1090
|
-
.await
|
|
1091
|
-
.expect("load should succeed")
|
|
1092
|
-
.expect("global row should be projected into main");
|
|
1093
|
-
assert_eq!(loaded.branch_id, "main");
|
|
1094
|
-
assert!(loaded.global);
|
|
1095
|
-
assert_eq!(
|
|
1096
|
-
loaded.snapshot_content.as_deref(),
|
|
1097
|
-
Some("{\"value\":\"global-tracked\"}")
|
|
1098
|
-
);
|
|
1099
|
-
|
|
1100
|
-
let main_root_rows = scan_tracked_root(&tracked_state, &storage, "commit-main").await;
|
|
1101
|
-
assert_eq!(
|
|
1102
|
-
main_root_rows.len(),
|
|
1103
|
-
1,
|
|
1104
|
-
"empty commit root should contain only its derived lix_commit row"
|
|
1105
|
-
);
|
|
1106
|
-
assert_eq!(main_root_rows[0].schema_key, "lix_commit");
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
#[tokio::test]
|
|
1110
|
-
async fn load_row_prefers_requested_branch_over_global() {
|
|
1111
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1112
|
-
let live_state = live_state_context();
|
|
1113
|
-
|
|
1114
|
-
let read = storage
|
|
1115
|
-
.begin_read(StorageReadOptions::default())
|
|
1116
|
-
.expect("read should open");
|
|
1117
|
-
{
|
|
1118
|
-
let rows = [
|
|
1119
|
-
tracked_row_with_commit("global-tracked", Some("change-global"), "commit-global"),
|
|
1120
|
-
tracked_row_at_with_commit(
|
|
1121
|
-
"branch-a",
|
|
1122
|
-
"branch-tracked",
|
|
1123
|
-
Some("change-branch"),
|
|
1124
|
-
"commit-branch",
|
|
1125
|
-
),
|
|
1126
|
-
];
|
|
1127
|
-
let mut writes = StorageWriteSet::new();
|
|
1128
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1129
|
-
{
|
|
1130
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1131
|
-
.await
|
|
1132
|
-
.expect("tracked rows should stage");
|
|
1133
|
-
}
|
|
1134
|
-
storage
|
|
1135
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1136
|
-
.expect("writes should commit");
|
|
1137
|
-
}
|
|
1138
|
-
write_untracked_rows_to_store(
|
|
1139
|
-
&storage,
|
|
1140
|
-
&read,
|
|
1141
|
-
&[
|
|
1142
|
-
branch_ref_row("global", "commit-global"),
|
|
1143
|
-
branch_ref_row("branch-a", "commit-branch"),
|
|
1144
|
-
],
|
|
1145
|
-
)
|
|
1146
|
-
.await;
|
|
1147
|
-
|
|
1148
|
-
let loaded = load_selected_tab_at(&live_state, &storage, "branch-a")
|
|
1149
|
-
.await
|
|
1150
|
-
.expect("load should succeed")
|
|
1151
|
-
.expect("branch row should be visible");
|
|
1152
|
-
|
|
1153
|
-
assert_eq!(loaded.branch_id, "branch-a");
|
|
1154
|
-
assert!(!loaded.untracked);
|
|
1155
|
-
assert_eq!(
|
|
1156
|
-
loaded.snapshot_content.as_deref(),
|
|
1157
|
-
Some("{\"value\":\"branch-tracked\"}")
|
|
1158
|
-
);
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
#[tokio::test]
|
|
1162
|
-
async fn main_override_hides_global_row() {
|
|
1163
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1164
|
-
let live_state = live_state_context();
|
|
1165
|
-
|
|
1166
|
-
let read = storage
|
|
1167
|
-
.begin_read(StorageReadOptions::default())
|
|
1168
|
-
.expect("read should open");
|
|
1169
|
-
{
|
|
1170
|
-
let rows = [
|
|
1171
|
-
tracked_row_with_commit("global-tracked", Some("change-global"), "commit-global"),
|
|
1172
|
-
tracked_row_at_with_commit(
|
|
1173
|
-
"main",
|
|
1174
|
-
"main-tracked",
|
|
1175
|
-
Some("change-main"),
|
|
1176
|
-
"commit-main",
|
|
1177
|
-
),
|
|
1178
|
-
];
|
|
1179
|
-
let mut writes = StorageWriteSet::new();
|
|
1180
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1181
|
-
{
|
|
1182
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1183
|
-
.await
|
|
1184
|
-
.expect("tracked rows should stage");
|
|
1185
|
-
}
|
|
1186
|
-
storage
|
|
1187
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1188
|
-
.expect("writes should commit");
|
|
1189
|
-
}
|
|
1190
|
-
write_untracked_rows_to_store(
|
|
1191
|
-
&storage,
|
|
1192
|
-
&read,
|
|
1193
|
-
&[
|
|
1194
|
-
branch_ref_row("global", "commit-global"),
|
|
1195
|
-
branch_ref_row("main", "commit-main"),
|
|
1196
|
-
],
|
|
1197
|
-
)
|
|
1198
|
-
.await;
|
|
1199
|
-
|
|
1200
|
-
let loaded = load_selected_tab_at(&live_state, &storage, "main")
|
|
1201
|
-
.await
|
|
1202
|
-
.expect("load should succeed")
|
|
1203
|
-
.expect("main row should be visible");
|
|
1204
|
-
|
|
1205
|
-
assert_eq!(loaded.branch_id, "main");
|
|
1206
|
-
assert!(!loaded.global);
|
|
1207
|
-
assert_eq!(
|
|
1208
|
-
loaded.snapshot_content.as_deref(),
|
|
1209
|
-
Some("{\"value\":\"main-tracked\"}")
|
|
1210
|
-
);
|
|
1211
|
-
}
|
|
1212
|
-
|
|
1213
|
-
#[tokio::test]
|
|
1214
|
-
async fn load_row_prefers_requested_untracked_over_requested_tracked_and_global_rows() {
|
|
1215
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1216
|
-
let live_state = live_state_context();
|
|
1217
|
-
|
|
1218
|
-
let read = storage
|
|
1219
|
-
.begin_read(StorageReadOptions::default())
|
|
1220
|
-
.expect("read should open");
|
|
1221
|
-
{
|
|
1222
|
-
let rows = [
|
|
1223
|
-
tracked_row_with_commit("global-tracked", Some("change-global"), "commit-global"),
|
|
1224
|
-
tracked_row_at_with_commit(
|
|
1225
|
-
"branch-a",
|
|
1226
|
-
"branch-tracked",
|
|
1227
|
-
Some("change-branch"),
|
|
1228
|
-
"commit-branch",
|
|
1229
|
-
),
|
|
1230
|
-
];
|
|
1231
|
-
let mut writes = StorageWriteSet::new();
|
|
1232
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1233
|
-
{
|
|
1234
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1235
|
-
.await
|
|
1236
|
-
.expect("tracked rows should stage");
|
|
1237
|
-
}
|
|
1238
|
-
storage
|
|
1239
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1240
|
-
.expect("writes should commit");
|
|
1241
|
-
}
|
|
1242
|
-
write_untracked_rows_to_store(
|
|
1243
|
-
&storage,
|
|
1244
|
-
&read,
|
|
1245
|
-
&[
|
|
1246
|
-
branch_ref_row("global", "commit-global"),
|
|
1247
|
-
branch_ref_row("branch-a", "commit-branch"),
|
|
1248
|
-
untracked_row_at("global", "global-untracked"),
|
|
1249
|
-
untracked_row_at("branch-a", "branch-untracked"),
|
|
1250
|
-
],
|
|
1251
|
-
)
|
|
1252
|
-
.await;
|
|
1253
|
-
|
|
1254
|
-
let loaded = load_selected_tab_at(&live_state, &storage, "branch-a")
|
|
1255
|
-
.await
|
|
1256
|
-
.expect("load should succeed")
|
|
1257
|
-
.expect("branch untracked row should be visible");
|
|
1258
|
-
|
|
1259
|
-
assert_eq!(loaded.branch_id, "branch-a");
|
|
1260
|
-
assert!(loaded.untracked);
|
|
1261
|
-
assert_eq!(
|
|
1262
|
-
loaded.snapshot_content.as_deref(),
|
|
1263
|
-
Some("{\"value\":\"branch-untracked\"}")
|
|
1264
|
-
);
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
#[tokio::test]
|
|
1268
|
-
async fn scan_rows_overlays_requested_branch_over_global() {
|
|
1269
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1270
|
-
let live_state = live_state_context();
|
|
1271
|
-
|
|
1272
|
-
let read = storage
|
|
1273
|
-
.begin_read(StorageReadOptions::default())
|
|
1274
|
-
.expect("read should open");
|
|
1275
|
-
{
|
|
1276
|
-
let rows = [
|
|
1277
|
-
tracked_row_with_commit("global-tracked", Some("change-global"), "commit-global"),
|
|
1278
|
-
tracked_row_at_with_commit(
|
|
1279
|
-
"branch-a",
|
|
1280
|
-
"branch-tracked",
|
|
1281
|
-
Some("change-branch"),
|
|
1282
|
-
"commit-branch",
|
|
1283
|
-
),
|
|
1284
|
-
];
|
|
1285
|
-
let mut writes = StorageWriteSet::new();
|
|
1286
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1287
|
-
{
|
|
1288
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1289
|
-
.await
|
|
1290
|
-
.expect("rows should stage");
|
|
1291
|
-
}
|
|
1292
|
-
storage
|
|
1293
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1294
|
-
.expect("writes should commit");
|
|
1295
|
-
}
|
|
1296
|
-
write_untracked_rows_to_store(
|
|
1297
|
-
&storage,
|
|
1298
|
-
&read,
|
|
1299
|
-
&[
|
|
1300
|
-
branch_ref_row("global", "commit-global"),
|
|
1301
|
-
branch_ref_row("branch-a", "commit-branch"),
|
|
1302
|
-
],
|
|
1303
|
-
)
|
|
1304
|
-
.await;
|
|
1305
|
-
|
|
1306
|
-
let rows = scan_selected_tab_at(&live_state, &storage, "branch-a", false)
|
|
1307
|
-
.await
|
|
1308
|
-
.expect("scan should succeed");
|
|
1309
|
-
|
|
1310
|
-
assert_eq!(rows.len(), 1);
|
|
1311
|
-
assert_eq!(rows[0].branch_id, "branch-a");
|
|
1312
|
-
assert_eq!(
|
|
1313
|
-
rows[0].snapshot_content.as_deref(),
|
|
1314
|
-
Some("{\"value\":\"branch-tracked\"}")
|
|
1315
|
-
);
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
#[tokio::test]
|
|
1319
|
-
async fn scan_rows_projects_global_row_into_requested_branch() {
|
|
1320
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1321
|
-
let live_state = live_state_context();
|
|
1322
|
-
|
|
1323
|
-
let read = storage
|
|
1324
|
-
.begin_read(StorageReadOptions::default())
|
|
1325
|
-
.expect("read should open");
|
|
1326
|
-
{
|
|
1327
|
-
let rows = [tracked_row_with_commit(
|
|
1328
|
-
"global-tracked",
|
|
1329
|
-
Some("change-global"),
|
|
1330
|
-
"commit-global",
|
|
1331
|
-
)];
|
|
1332
|
-
let mut writes = StorageWriteSet::new();
|
|
1333
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1334
|
-
{
|
|
1335
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1336
|
-
.await
|
|
1337
|
-
.expect("rows should stage");
|
|
1338
|
-
}
|
|
1339
|
-
storage
|
|
1340
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1341
|
-
.expect("writes should commit");
|
|
1342
|
-
}
|
|
1343
|
-
write_untracked_rows_to_store(
|
|
1344
|
-
&storage,
|
|
1345
|
-
&read,
|
|
1346
|
-
&[
|
|
1347
|
-
branch_ref_row("global", "commit-global"),
|
|
1348
|
-
branch_ref_row("branch-a", "commit-branch-a"),
|
|
1349
|
-
],
|
|
1350
|
-
)
|
|
1351
|
-
.await;
|
|
1352
|
-
write_empty_commits_to_store(&storage, &read, &["commit-branch-a"]).await;
|
|
1353
|
-
|
|
1354
|
-
let rows = scan_selected_tab_at(&live_state, &storage, "branch-a", false)
|
|
1355
|
-
.await
|
|
1356
|
-
.expect("scan should succeed");
|
|
1357
|
-
|
|
1358
|
-
assert_eq!(rows.len(), 1);
|
|
1359
|
-
assert_eq!(rows[0].branch_id, "branch-a");
|
|
1360
|
-
assert!(rows[0].global);
|
|
1361
|
-
assert_eq!(
|
|
1362
|
-
rows[0].snapshot_content.as_deref(),
|
|
1363
|
-
Some("{\"value\":\"global-tracked\"}")
|
|
1364
|
-
);
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
#[tokio::test]
|
|
1368
|
-
async fn scan_rows_does_not_project_global_rows_into_missing_branch() {
|
|
1369
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1370
|
-
let live_state = live_state_context();
|
|
1371
|
-
|
|
1372
|
-
let read = storage
|
|
1373
|
-
.begin_read(StorageReadOptions::default())
|
|
1374
|
-
.expect("read should open");
|
|
1375
|
-
{
|
|
1376
|
-
let rows = [tracked_row_with_commit(
|
|
1377
|
-
"global-tracked",
|
|
1378
|
-
Some("change-global"),
|
|
1379
|
-
"commit-global",
|
|
1380
|
-
)];
|
|
1381
|
-
let mut writes = StorageWriteSet::new();
|
|
1382
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1383
|
-
{
|
|
1384
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1385
|
-
.await
|
|
1386
|
-
.expect("tracked row should stage");
|
|
1387
|
-
}
|
|
1388
|
-
storage
|
|
1389
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1390
|
-
.expect("writes should commit");
|
|
1391
|
-
}
|
|
1392
|
-
write_untracked_rows_to_store(
|
|
1393
|
-
&storage,
|
|
1394
|
-
&read,
|
|
1395
|
-
&[branch_ref_row("global", "commit-global")],
|
|
1396
|
-
)
|
|
1397
|
-
.await;
|
|
1398
|
-
|
|
1399
|
-
let rows = scan_selected_tab_at(&live_state, &storage, "missing-branch", false)
|
|
1400
|
-
.await
|
|
1401
|
-
.expect("scan should succeed");
|
|
1402
|
-
|
|
1403
|
-
assert_eq!(
|
|
1404
|
-
rows.len(),
|
|
1405
|
-
0,
|
|
1406
|
-
"global rows must not be projected into a missing branch scope"
|
|
1407
|
-
);
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
#[tokio::test]
|
|
1411
|
-
async fn winning_tombstone_hides_row_unless_tombstones_are_included() {
|
|
1412
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1413
|
-
let live_state = live_state_context();
|
|
1414
|
-
|
|
1415
|
-
let read = storage
|
|
1416
|
-
.begin_read(StorageReadOptions::default())
|
|
1417
|
-
.expect("read should open");
|
|
1418
|
-
{
|
|
1419
|
-
let rows = [
|
|
1420
|
-
tracked_row_with_commit("global-tracked", Some("change-global"), "commit-global"),
|
|
1421
|
-
tombstone_tracked_row_at_with_commit(
|
|
1422
|
-
"branch-a",
|
|
1423
|
-
Some("change-tombstone"),
|
|
1424
|
-
"commit-branch",
|
|
1425
|
-
),
|
|
1426
|
-
];
|
|
1427
|
-
let mut writes = StorageWriteSet::new();
|
|
1428
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1429
|
-
{
|
|
1430
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1431
|
-
.await
|
|
1432
|
-
.expect("rows should stage");
|
|
1433
|
-
}
|
|
1434
|
-
storage
|
|
1435
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1436
|
-
.expect("writes should commit");
|
|
1437
|
-
}
|
|
1438
|
-
write_untracked_rows_to_store(
|
|
1439
|
-
&storage,
|
|
1440
|
-
&read,
|
|
1441
|
-
&[
|
|
1442
|
-
branch_ref_row("global", "commit-global"),
|
|
1443
|
-
branch_ref_row("branch-a", "commit-branch"),
|
|
1444
|
-
],
|
|
1445
|
-
)
|
|
1446
|
-
.await;
|
|
1447
|
-
|
|
1448
|
-
let hidden = scan_selected_tab_at(&live_state, &storage, "branch-a", false)
|
|
1449
|
-
.await
|
|
1450
|
-
.expect("scan should succeed");
|
|
1451
|
-
assert_eq!(hidden.len(), 0);
|
|
1452
|
-
|
|
1453
|
-
let with_tombstone = scan_selected_tab_at(&live_state, &storage, "branch-a", true)
|
|
1454
|
-
.await
|
|
1455
|
-
.expect("scan should succeed");
|
|
1456
|
-
assert_eq!(with_tombstone.len(), 1);
|
|
1457
|
-
assert_eq!(with_tombstone[0].branch_id, "branch-a");
|
|
1458
|
-
assert_eq!(with_tombstone[0].snapshot_content, None);
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
#[tokio::test]
|
|
1462
|
-
async fn main_tombstone_hides_global_row() {
|
|
1463
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1464
|
-
let live_state = live_state_context();
|
|
1465
|
-
|
|
1466
|
-
let read = storage
|
|
1467
|
-
.begin_read(StorageReadOptions::default())
|
|
1468
|
-
.expect("read should open");
|
|
1469
|
-
{
|
|
1470
|
-
let rows = [
|
|
1471
|
-
tracked_row_with_commit("global-tracked", Some("change-global"), "commit-global"),
|
|
1472
|
-
tombstone_tracked_row_at_with_commit(
|
|
1473
|
-
"main",
|
|
1474
|
-
Some("change-main-tombstone"),
|
|
1475
|
-
"commit-main",
|
|
1476
|
-
),
|
|
1477
|
-
];
|
|
1478
|
-
let mut writes = StorageWriteSet::new();
|
|
1479
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1480
|
-
{
|
|
1481
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1482
|
-
.await
|
|
1483
|
-
.expect("tracked rows should stage");
|
|
1484
|
-
}
|
|
1485
|
-
storage
|
|
1486
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1487
|
-
.expect("writes should commit");
|
|
1488
|
-
}
|
|
1489
|
-
write_untracked_rows_to_store(
|
|
1490
|
-
&storage,
|
|
1491
|
-
&read,
|
|
1492
|
-
&[
|
|
1493
|
-
branch_ref_row("global", "commit-global"),
|
|
1494
|
-
branch_ref_row("main", "commit-main"),
|
|
1495
|
-
],
|
|
1496
|
-
)
|
|
1497
|
-
.await;
|
|
1498
|
-
|
|
1499
|
-
let hidden = scan_selected_tab_at(&live_state, &storage, "main", false)
|
|
1500
|
-
.await
|
|
1501
|
-
.expect("scan should succeed");
|
|
1502
|
-
assert_eq!(hidden.len(), 0);
|
|
1503
|
-
|
|
1504
|
-
let tombstones = scan_selected_tab_at(&live_state, &storage, "main", true)
|
|
1505
|
-
.await
|
|
1506
|
-
.expect("scan should succeed");
|
|
1507
|
-
assert_eq!(tombstones.len(), 1);
|
|
1508
|
-
assert_eq!(tombstones[0].branch_id, "main");
|
|
1509
|
-
assert!(!tombstones[0].global);
|
|
1510
|
-
assert_eq!(tombstones[0].snapshot_content, None);
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
#[tokio::test]
|
|
1514
|
-
async fn writer_allows_commit_fact_to_share_the_touched_branch_commit_id() {
|
|
1515
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1516
|
-
let live_state = live_state_context();
|
|
1517
|
-
let read = storage
|
|
1518
|
-
.begin_read(StorageReadOptions::default())
|
|
1519
|
-
.expect("read should open");
|
|
1520
|
-
|
|
1521
|
-
{
|
|
1522
|
-
let rows = [
|
|
1523
|
-
tracked_row_at_with_commit(
|
|
1524
|
-
"branch-a",
|
|
1525
|
-
"branch-row",
|
|
1526
|
-
Some("change-branch"),
|
|
1527
|
-
"commit-branch",
|
|
1528
|
-
),
|
|
1529
|
-
commit_live_state_row("commit-branch"),
|
|
1530
|
-
];
|
|
1531
|
-
let mut writes = StorageWriteSet::new();
|
|
1532
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1533
|
-
{
|
|
1534
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1535
|
-
.await
|
|
1536
|
-
.expect("commit facts are changelog projections, not root-local rows");
|
|
1537
|
-
}
|
|
1538
|
-
storage
|
|
1539
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1540
|
-
.expect("writes should commit");
|
|
1541
|
-
}
|
|
1542
|
-
write_untracked_rows_to_store(
|
|
1543
|
-
&storage,
|
|
1544
|
-
&read,
|
|
1545
|
-
&[branch_ref_row("branch-a", "commit-branch")],
|
|
1546
|
-
)
|
|
1547
|
-
.await;
|
|
1548
|
-
|
|
1549
|
-
let loaded = load_selected_tab_at(&live_state, &storage, "branch-a")
|
|
1550
|
-
.await
|
|
1551
|
-
.expect("load should succeed")
|
|
1552
|
-
.expect("branch row should be visible");
|
|
1553
|
-
assert_eq!(
|
|
1554
|
-
loaded.snapshot_content.as_deref(),
|
|
1555
|
-
Some("{\"value\":\"branch-row\"}")
|
|
1556
|
-
);
|
|
1557
|
-
}
|
|
1558
|
-
|
|
1559
|
-
#[tokio::test]
|
|
1560
|
-
async fn writer_uses_first_parent_as_merge_root_base() {
|
|
1561
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1562
|
-
let read = storage
|
|
1563
|
-
.begin_read(StorageReadOptions::default())
|
|
1564
|
-
.expect("read should open");
|
|
1565
|
-
write_empty_commits_to_store(&storage, &read, &["parent-left"]).await;
|
|
1566
|
-
let mut writes = StorageWriteSet::new();
|
|
1567
|
-
TrackedStateContext::new()
|
|
1568
|
-
.writer(&read, &mut writes)
|
|
1569
|
-
.stage_commit_root("parent-left", None, [])
|
|
1570
|
-
.await
|
|
1571
|
-
.expect("first parent tracked root should stage");
|
|
1572
|
-
storage
|
|
1573
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1574
|
-
.expect("first parent tracked root should commit");
|
|
1575
|
-
|
|
1576
|
-
let read = storage
|
|
1577
|
-
.begin_read(StorageReadOptions::default())
|
|
1578
|
-
.expect("read should open");
|
|
1579
|
-
|
|
1580
|
-
{
|
|
1581
|
-
let rows = [
|
|
1582
|
-
tracked_row_at_with_commit(
|
|
1583
|
-
"branch-a",
|
|
1584
|
-
"branch-row",
|
|
1585
|
-
Some("change-branch"),
|
|
1586
|
-
"commit-merge",
|
|
1587
|
-
),
|
|
1588
|
-
commit_live_state_row_with_parents(
|
|
1589
|
-
"commit-merge",
|
|
1590
|
-
&["parent-left", "parent-right"],
|
|
1591
|
-
),
|
|
1592
|
-
];
|
|
1593
|
-
let mut writes = StorageWriteSet::new();
|
|
1594
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1595
|
-
{
|
|
1596
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1597
|
-
.await
|
|
1598
|
-
.expect("merge commit should use first parent as tracked-root base");
|
|
1599
|
-
}
|
|
1600
|
-
storage
|
|
1601
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1602
|
-
.expect("writes should commit");
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
#[tokio::test]
|
|
1607
|
-
async fn non_global_root_does_not_store_global_rows() {
|
|
1608
|
-
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1609
|
-
let tracked_state = TrackedStateContext::new();
|
|
1610
|
-
let read = storage
|
|
1611
|
-
.begin_read(StorageReadOptions::default())
|
|
1612
|
-
.expect("read should open");
|
|
1613
|
-
|
|
1614
|
-
{
|
|
1615
|
-
let rows = [
|
|
1616
|
-
tracked_row_with_commit("global-tracked", Some("change-global"), "commit-global"),
|
|
1617
|
-
tracked_row_at_with_commit(
|
|
1618
|
-
"main",
|
|
1619
|
-
"main-tracked",
|
|
1620
|
-
Some("change-main"),
|
|
1621
|
-
"commit-main",
|
|
1622
|
-
),
|
|
1623
|
-
];
|
|
1624
|
-
let mut writes = StorageWriteSet::new();
|
|
1625
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1626
|
-
{
|
|
1627
|
-
stage_materialized_live_rows(&read, &mut writes, &mut json_writer, &rows)
|
|
1628
|
-
.await
|
|
1629
|
-
.expect("tracked rows should stage");
|
|
1630
|
-
}
|
|
1631
|
-
storage
|
|
1632
|
-
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1633
|
-
.expect("writes should commit");
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
let global_root_rows = scan_tracked_root(&tracked_state, &storage, "commit-global").await;
|
|
1637
|
-
assert_eq!(global_root_rows.len(), 2);
|
|
1638
|
-
let Some(global_row) = global_root_rows
|
|
1639
|
-
.iter()
|
|
1640
|
-
.find(|row| row.schema_key == "lix_key_value")
|
|
1641
|
-
else {
|
|
1642
|
-
panic!("global root should contain the explicit global tracked row");
|
|
1643
|
-
};
|
|
1644
|
-
assert_eq!(
|
|
1645
|
-
global_row.snapshot_content.as_deref(),
|
|
1646
|
-
Some("{\"value\":\"global-tracked\"}")
|
|
1647
|
-
);
|
|
1648
|
-
|
|
1649
|
-
let main_root_rows = scan_tracked_root(&tracked_state, &storage, "commit-main").await;
|
|
1650
|
-
assert_eq!(main_root_rows.len(), 2);
|
|
1651
|
-
let Some(main_row) = main_root_rows
|
|
1652
|
-
.iter()
|
|
1653
|
-
.find(|row| row.schema_key == "lix_key_value")
|
|
1654
|
-
else {
|
|
1655
|
-
panic!("main root should contain the explicit main tracked row");
|
|
1656
|
-
};
|
|
1657
|
-
assert_eq!(
|
|
1658
|
-
main_row.snapshot_content.as_deref(),
|
|
1659
|
-
Some("{\"value\":\"main-tracked\"}")
|
|
1660
|
-
);
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
async fn load_selected_tab(
|
|
1664
|
-
live_state: &LiveStateContext,
|
|
1665
|
-
storage: &StorageContext,
|
|
1666
|
-
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
1667
|
-
live_state
|
|
1668
|
-
.reader(
|
|
1669
|
-
storage
|
|
1670
|
-
.begin_read(StorageReadOptions::default())
|
|
1671
|
-
.expect("read should open"),
|
|
1672
|
-
)
|
|
1673
|
-
.load_row(&LiveStateRowRequest {
|
|
1674
|
-
schema_key: "lix_key_value".to_string(),
|
|
1675
|
-
branch_id: "global".to_string(),
|
|
1676
|
-
entity_pk: crate::entity_pk::EntityPk::single("selected-tab"),
|
|
1677
|
-
file_id: NullableKeyFilter::Null,
|
|
1678
|
-
})
|
|
1679
|
-
.await
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
|
-
async fn load_selected_tab_at(
|
|
1683
|
-
live_state: &LiveStateContext,
|
|
1684
|
-
storage: &StorageContext,
|
|
1685
|
-
branch_id: &str,
|
|
1686
|
-
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
1687
|
-
live_state
|
|
1688
|
-
.reader(
|
|
1689
|
-
storage
|
|
1690
|
-
.begin_read(StorageReadOptions::default())
|
|
1691
|
-
.expect("read should open"),
|
|
1692
|
-
)
|
|
1693
|
-
.load_row(&LiveStateRowRequest {
|
|
1694
|
-
schema_key: "lix_key_value".to_string(),
|
|
1695
|
-
branch_id: branch_id.to_string(),
|
|
1696
|
-
entity_pk: crate::entity_pk::EntityPk::single("selected-tab"),
|
|
1697
|
-
file_id: NullableKeyFilter::Null,
|
|
1698
|
-
})
|
|
1699
|
-
.await
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
async fn scan_selected_tab_at(
|
|
1703
|
-
live_state: &LiveStateContext,
|
|
1704
|
-
storage: &StorageContext,
|
|
1705
|
-
branch_id: &str,
|
|
1706
|
-
include_tombstones: bool,
|
|
1707
|
-
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
1708
|
-
live_state
|
|
1709
|
-
.reader(
|
|
1710
|
-
storage
|
|
1711
|
-
.begin_read(StorageReadOptions::default())
|
|
1712
|
-
.expect("read should open"),
|
|
1713
|
-
)
|
|
1714
|
-
.scan_rows(&LiveStateScanRequest {
|
|
1715
|
-
filter: LiveStateFilter {
|
|
1716
|
-
schema_keys: vec!["lix_key_value".to_string()],
|
|
1717
|
-
entity_pks: vec![crate::entity_pk::EntityPk::single("selected-tab")],
|
|
1718
|
-
branch_ids: vec![branch_id.to_string()],
|
|
1719
|
-
file_ids: vec![NullableKeyFilter::Null],
|
|
1720
|
-
include_tombstones,
|
|
1721
|
-
..LiveStateFilter::default()
|
|
1722
|
-
},
|
|
1723
|
-
..LiveStateScanRequest::default()
|
|
1724
|
-
})
|
|
1725
|
-
.await
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
async fn scan_tracked_root(
|
|
1729
|
-
tracked_state: &TrackedStateContext,
|
|
1730
|
-
storage: &StorageContext,
|
|
1731
|
-
commit_id: &str,
|
|
1732
|
-
) -> Vec<MaterializedTrackedStateRow> {
|
|
1733
|
-
tracked_state
|
|
1734
|
-
.reader(
|
|
1735
|
-
storage
|
|
1736
|
-
.begin_read(StorageReadOptions::default())
|
|
1737
|
-
.expect("read should open"),
|
|
1738
|
-
)
|
|
1739
|
-
.scan_rows_at_commit(
|
|
1740
|
-
commit_id,
|
|
1741
|
-
&TrackedStateScanRequest {
|
|
1742
|
-
filter: TrackedStateFilter {
|
|
1743
|
-
include_tombstones: true,
|
|
1744
|
-
..Default::default()
|
|
1745
|
-
},
|
|
1746
|
-
..Default::default()
|
|
1747
|
-
},
|
|
1748
|
-
)
|
|
1749
|
-
.await
|
|
1750
|
-
.expect("tracked root should scan")
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
fn tracked_row_with_commit(
|
|
1754
|
-
value: &str,
|
|
1755
|
-
change_id: Option<&str>,
|
|
1756
|
-
commit_id: &str,
|
|
1757
|
-
) -> MaterializedLiveStateRow {
|
|
1758
|
-
tracked_row_at_with_commit("global", value, change_id, commit_id)
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
fn tracked_row_at_with_commit(
|
|
1762
|
-
branch_id: &str,
|
|
1763
|
-
value: &str,
|
|
1764
|
-
change_id: Option<&str>,
|
|
1765
|
-
commit_id: &str,
|
|
1766
|
-
) -> MaterializedLiveStateRow {
|
|
1767
|
-
MaterializedLiveStateRow {
|
|
1768
|
-
entity_pk: identity("selected-tab"),
|
|
1769
|
-
schema_key: "lix_key_value".to_string(),
|
|
1770
|
-
file_id: None,
|
|
1771
|
-
snapshot_content: Some(format!("{{\"value\":\"{value}\"}}")),
|
|
1772
|
-
metadata: None,
|
|
1773
|
-
deleted: false,
|
|
1774
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1775
|
-
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1776
|
-
global: branch_id == "global",
|
|
1777
|
-
change_id: change_id.map(str::to_string),
|
|
1778
|
-
commit_id: Some(commit_id.to_string()),
|
|
1779
|
-
untracked: false,
|
|
1780
|
-
branch_id: branch_id.to_string(),
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
fn tombstone_tracked_row_at_with_commit(
|
|
1785
|
-
branch_id: &str,
|
|
1786
|
-
change_id: Option<&str>,
|
|
1787
|
-
commit_id: &str,
|
|
1788
|
-
) -> MaterializedLiveStateRow {
|
|
1789
|
-
MaterializedLiveStateRow {
|
|
1790
|
-
snapshot_content: None,
|
|
1791
|
-
deleted: true,
|
|
1792
|
-
..tracked_row_at_with_commit(branch_id, "ignored", change_id, commit_id)
|
|
1793
|
-
}
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
fn untracked_row(value: &str) -> MaterializedUntrackedStateRow {
|
|
1797
|
-
untracked_row_at("global", value)
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
fn untracked_row_at(branch_id: &str, value: &str) -> MaterializedUntrackedStateRow {
|
|
1801
|
-
MaterializedUntrackedStateRow {
|
|
1802
|
-
entity_pk: identity("selected-tab"),
|
|
1803
|
-
schema_key: "lix_key_value".to_string(),
|
|
1804
|
-
file_id: None,
|
|
1805
|
-
snapshot_content: Some(format!("{{\"value\":\"{value}\"}}")),
|
|
1806
|
-
metadata: None,
|
|
1807
|
-
deleted: false,
|
|
1808
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1809
|
-
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1810
|
-
global: branch_id == "global",
|
|
1811
|
-
branch_id: branch_id.to_string(),
|
|
1812
|
-
}
|
|
1813
|
-
}
|
|
1814
|
-
|
|
1815
|
-
fn branch_ref_row(branch_id: &str, commit_id: &str) -> MaterializedUntrackedStateRow {
|
|
1816
|
-
MaterializedUntrackedStateRow {
|
|
1817
|
-
entity_pk: identity(branch_id),
|
|
1818
|
-
schema_key: "lix_branch_ref".to_string(),
|
|
1819
|
-
file_id: None,
|
|
1820
|
-
snapshot_content: Some(
|
|
1821
|
-
serde_json::to_string(&json!({
|
|
1822
|
-
"id": branch_id,
|
|
1823
|
-
"commit_id": commit_id,
|
|
1824
|
-
}))
|
|
1825
|
-
.expect("branch ref should serialize"),
|
|
1826
|
-
),
|
|
1827
|
-
metadata: None,
|
|
1828
|
-
deleted: false,
|
|
1829
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1830
|
-
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1831
|
-
global: true,
|
|
1832
|
-
branch_id: "global".to_string(),
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
fn commit_live_state_row(commit_id: &str) -> MaterializedLiveStateRow {
|
|
1837
|
-
commit_live_state_row_with_parents(commit_id, &[])
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
fn commit_live_state_row_with_parents(
|
|
1841
|
-
commit_id: &str,
|
|
1842
|
-
parent_ids: &[&str],
|
|
1843
|
-
) -> MaterializedLiveStateRow {
|
|
1844
|
-
let mut row = commit_live_state_row_with_snapshot(
|
|
1845
|
-
commit_id,
|
|
1846
|
-
json!({
|
|
1847
|
-
"id": commit_id,
|
|
1848
|
-
}),
|
|
1849
|
-
);
|
|
1850
|
-
row.metadata = Some(
|
|
1851
|
-
serde_json::to_string(&json!({ "test_parents": parent_ids }))
|
|
1852
|
-
.expect("test metadata should serialize"),
|
|
1853
|
-
);
|
|
1854
|
-
row
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
fn commit_live_state_row_with_snapshot(
|
|
1858
|
-
commit_id: &str,
|
|
1859
|
-
snapshot: serde_json::Value,
|
|
1860
|
-
) -> MaterializedLiveStateRow {
|
|
1861
|
-
MaterializedLiveStateRow {
|
|
1862
|
-
entity_pk: identity(commit_id),
|
|
1863
|
-
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
1864
|
-
file_id: None,
|
|
1865
|
-
snapshot_content: Some(
|
|
1866
|
-
serde_json::to_string(&snapshot).expect("commit snapshot should serialize"),
|
|
1867
|
-
),
|
|
1868
|
-
metadata: None,
|
|
1869
|
-
deleted: false,
|
|
1870
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1871
|
-
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1872
|
-
global: true,
|
|
1873
|
-
change_id: Some(format!("change-{commit_id}")),
|
|
1874
|
-
commit_id: Some(commit_id.to_string()),
|
|
1875
|
-
untracked: false,
|
|
1876
|
-
branch_id: "global".to_string(),
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
fn identity(entity_pk: &str) -> EntityPk {
|
|
1881
|
-
EntityPk::single(entity_pk)
|
|
1882
|
-
}
|
|
1883
|
-
}
|