@lix-js/sdk 0.6.0-preview.4 → 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 +65 -64
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
- package/dist/engine-wasm/wasm/lix_engine.js +130 -118
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +33 -26
- package/dist/open-lix.js +10 -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 +803 -148
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +223 -83
- 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 +15 -12
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +495 -14
- package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
- 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} +71 -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 +1 -1
- 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 +28 -23
- 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 -110
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3533
- 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 -172
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
- 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
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
use crate::binary_cas::BinaryCasContext;
|
|
2
|
-
use crate::
|
|
2
|
+
use crate::branch::{BranchContext, BranchRefReader};
|
|
3
|
+
use crate::changelog::{
|
|
4
|
+
ChangeRecord, ChangelogAppend, ChangelogContext, ChangelogWriter, CommitChangeRef,
|
|
5
|
+
CommitChangeRefSet, CommitRecord,
|
|
6
|
+
};
|
|
7
|
+
use crate::entity_pk::EntityPk;
|
|
3
8
|
use crate::functions::FunctionContext;
|
|
4
|
-
use crate::json_store::{JsonStoreContext, JsonWritePlacementRef, NormalizedJsonRef};
|
|
5
|
-
use crate::storage::{
|
|
9
|
+
use crate::json_store::{JsonRef, JsonStoreContext, JsonWritePlacementRef, NormalizedJsonRef};
|
|
10
|
+
use crate::storage::{StorageRead, StorageWriteSet};
|
|
6
11
|
use crate::tracked_state::{TrackedStateContext, TrackedStateDeltaRef};
|
|
7
|
-
use crate::transaction::
|
|
12
|
+
use crate::transaction::prepare_branch_ref_row;
|
|
8
13
|
use crate::transaction::staging::PreparedWriteSet;
|
|
9
|
-
use crate::transaction::types::{
|
|
14
|
+
use crate::transaction::types::{PreparedStateRow, StagedCommitChangeRef, StagedCommitChangeRefs};
|
|
10
15
|
use crate::untracked_state::{
|
|
11
16
|
UntrackedStateContext, UntrackedStateIdentity, UntrackedStateIdentityRef, UntrackedStateRowRef,
|
|
12
17
|
};
|
|
13
|
-
use crate::version::{VersionContext, VersionRefReader};
|
|
14
18
|
use crate::LixError;
|
|
15
|
-
use std::collections::BTreeMap;
|
|
19
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
16
20
|
|
|
17
21
|
type RowIndex = usize;
|
|
18
|
-
type AdoptedRowIndex = usize;
|
|
19
22
|
|
|
20
23
|
/// Commits prepared transaction rows into durable tracked and untracked stores.
|
|
21
24
|
///
|
|
22
25
|
/// Providers decode DataFusion DML into hydrated `PreparedStateRow`s. Untracked
|
|
23
|
-
/// rows are durable local overlay state and bypass
|
|
24
|
-
/// rows stage canonical
|
|
25
|
-
///
|
|
26
|
+
/// rows are durable local overlay state and bypass changelog change refs. Tracked
|
|
27
|
+
/// rows stage canonical changelog facts, then update the live-state serving
|
|
28
|
+
/// commit root. The tracked side of that commit root is a prolly root keyed by
|
|
26
29
|
/// the new commit id.
|
|
27
30
|
pub(crate) async fn commit_prepared_writes(
|
|
28
31
|
binary_cas: &BinaryCasContext,
|
|
29
|
-
|
|
30
|
-
version_ctx: &VersionContext,
|
|
32
|
+
branch_ctx: &BranchContext,
|
|
31
33
|
runtime_functions: Option<&FunctionContext>,
|
|
32
|
-
|
|
34
|
+
read: &mut (impl StorageRead + Send + Sync),
|
|
33
35
|
prepared_writes: PreparedWriteSet,
|
|
34
|
-
) -> Result<
|
|
36
|
+
) -> Result<StorageWriteSet, LixError> {
|
|
35
37
|
let mut writes = StorageWriteSet::new();
|
|
36
38
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
37
39
|
|
|
@@ -43,19 +45,17 @@ pub(crate) async fn commit_prepared_writes(
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
let state_rows = prepared_writes.state_rows;
|
|
46
|
-
let adopted_rows = prepared_writes.adopted_rows;
|
|
47
48
|
let finalized = finalize_commit_rows(
|
|
48
|
-
prepared_writes.
|
|
49
|
-
prepared_writes.
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
prepared_writes.commit_change_refs_by_branch,
|
|
50
|
+
prepared_writes.extra_commit_parents_by_branch,
|
|
51
|
+
branch_ctx,
|
|
52
|
+
&*read,
|
|
52
53
|
)
|
|
53
54
|
.await?;
|
|
54
55
|
let commit_rows = finalized.commit_rows;
|
|
55
|
-
let
|
|
56
|
+
let branch_heads = finalized.branch_heads;
|
|
56
57
|
let tracked_roots = finalized.tracked_roots;
|
|
57
58
|
let row_index = index_prepared_rows(&state_rows)?;
|
|
58
|
-
let adopted_index = index_adopted_rows(&adopted_rows);
|
|
59
59
|
|
|
60
60
|
if let Some(runtime_functions) = runtime_functions {
|
|
61
61
|
runtime_functions
|
|
@@ -64,50 +64,39 @@ pub(crate) async fn commit_prepared_writes(
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
if state_rows.is_empty()
|
|
67
|
-
&& adopted_rows.is_empty()
|
|
68
67
|
&& commit_rows.is_empty()
|
|
69
|
-
&&
|
|
68
|
+
&& branch_heads.is_empty()
|
|
70
69
|
&& writes.is_empty()
|
|
71
70
|
{
|
|
72
|
-
return Ok(
|
|
71
|
+
return Ok(writes);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
let staged_commits =
|
|
76
|
-
|
|
77
|
-
transaction,
|
|
74
|
+
let staged_commits = stage_changelog_commits(
|
|
75
|
+
read,
|
|
78
76
|
&mut writes,
|
|
79
77
|
&state_rows,
|
|
80
78
|
&row_index.tracked_row_indices_by_commit,
|
|
81
|
-
&adopted_rows,
|
|
82
|
-
&adopted_index.tracked_row_indices_by_commit,
|
|
83
79
|
&commit_rows,
|
|
84
80
|
)
|
|
85
81
|
.await?;
|
|
86
82
|
|
|
87
|
-
|
|
83
|
+
stage_state_json_payloads(
|
|
88
84
|
&mut json_writer,
|
|
89
85
|
&mut writes,
|
|
90
86
|
&state_rows,
|
|
91
|
-
&row_index.
|
|
92
|
-
&staged_commits,
|
|
93
|
-
&row_index.untracked_row_indices,
|
|
87
|
+
&row_index.canonical_row_indices,
|
|
94
88
|
)?;
|
|
95
89
|
|
|
96
|
-
// The serving
|
|
97
|
-
//
|
|
90
|
+
// The serving commit root is updated in the same backend transaction as the
|
|
91
|
+
// changelog append. Tracked rows become prolly mutations under their owning
|
|
98
92
|
// commit root; untracked rows remain in the separate local overlay store.
|
|
99
93
|
{
|
|
100
94
|
let untracked_overlay_delete_identities = existing_untracked_overlay_delete_identities(
|
|
101
|
-
|
|
95
|
+
&*read,
|
|
102
96
|
row_index
|
|
103
97
|
.canonical_row_indices
|
|
104
98
|
.iter()
|
|
105
|
-
.map(|&row_index| untracked_identity_ref_from_state_row(&state_rows[row_index]))
|
|
106
|
-
.chain(
|
|
107
|
-
adopted_rows
|
|
108
|
-
.iter()
|
|
109
|
-
.map(untracked_identity_ref_from_adopted_row),
|
|
110
|
-
),
|
|
99
|
+
.map(|&row_index| untracked_identity_ref_from_state_row(&state_rows[row_index])),
|
|
111
100
|
)
|
|
112
101
|
.await?;
|
|
113
102
|
UntrackedStateContext::new()
|
|
@@ -124,89 +113,44 @@ pub(crate) async fn commit_prepared_writes(
|
|
|
124
113
|
untracked_overlay_delete_identities
|
|
125
114
|
.iter()
|
|
126
115
|
.map(UntrackedStateIdentity::as_ref),
|
|
127
|
-
)
|
|
116
|
+
)?;
|
|
128
117
|
stage_tracked_roots(
|
|
129
|
-
|
|
118
|
+
read,
|
|
130
119
|
&mut writes,
|
|
131
120
|
&state_rows,
|
|
132
121
|
row_index.tracked_row_indices_by_commit,
|
|
133
|
-
&adopted_rows,
|
|
134
|
-
adopted_index.tracked_row_indices_by_commit,
|
|
135
122
|
tracked_roots,
|
|
136
123
|
staged_commits,
|
|
137
|
-
json_pack_indexes_by_commit,
|
|
138
124
|
)
|
|
139
125
|
.await?;
|
|
140
126
|
}
|
|
141
127
|
|
|
142
|
-
for
|
|
143
|
-
let canonical_row =
|
|
144
|
-
&
|
|
145
|
-
&
|
|
146
|
-
&
|
|
128
|
+
for branch_head in branch_heads {
|
|
129
|
+
let canonical_row = prepare_branch_ref_row(
|
|
130
|
+
&branch_head.branch_id,
|
|
131
|
+
&branch_head.commit_id,
|
|
132
|
+
&branch_head.timestamp,
|
|
147
133
|
)?;
|
|
148
|
-
|
|
134
|
+
branch_ctx.stage_canonical_ref_rows(&mut writes, &[canonical_row.row])?;
|
|
149
135
|
}
|
|
150
136
|
|
|
151
|
-
writes
|
|
152
|
-
Ok(())
|
|
137
|
+
Ok(writes)
|
|
153
138
|
}
|
|
154
139
|
|
|
155
|
-
fn
|
|
140
|
+
fn stage_state_json_payloads(
|
|
156
141
|
json_writer: &mut crate::json_store::JsonStoreWriter,
|
|
157
142
|
writes: &mut StorageWriteSet,
|
|
158
143
|
state_rows: &[PreparedStateRow],
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
untracked_row_indices: &[RowIndex],
|
|
162
|
-
) -> Result<BTreeMap<String, BTreeMap<u32, std::collections::HashMap<[u8; 32], usize>>>, LixError> {
|
|
163
|
-
let mut pack_indexes_by_commit = BTreeMap::new();
|
|
164
|
-
for (commit_id, row_indices) in tracked_row_indices_by_commit {
|
|
165
|
-
let staged_commit = staged_commits.get(commit_id).ok_or_else(|| {
|
|
166
|
-
LixError::new(
|
|
167
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
168
|
-
format!("commit '{commit_id}' has tracked JSON rows but no staged commit-store locators"),
|
|
169
|
-
)
|
|
170
|
-
})?;
|
|
171
|
-
if row_indices.len() != staged_commit.authored_locators.len() {
|
|
172
|
-
return Err(LixError::new(
|
|
173
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
174
|
-
format!(
|
|
175
|
-
"commit '{commit_id}' has {} tracked JSON rows but {} authored locators",
|
|
176
|
-
row_indices.len(),
|
|
177
|
-
staged_commit.authored_locators.len()
|
|
178
|
-
),
|
|
179
|
-
));
|
|
180
|
-
}
|
|
181
|
-
let mut row_indices_by_pack = BTreeMap::<u32, Vec<RowIndex>>::new();
|
|
182
|
-
for (&row_index, locator) in row_indices.iter().zip(&staged_commit.authored_locators) {
|
|
183
|
-
row_indices_by_pack
|
|
184
|
-
.entry(locator.source_pack_id)
|
|
185
|
-
.or_default()
|
|
186
|
-
.push(row_index);
|
|
187
|
-
}
|
|
188
|
-
for (pack_id, pack_row_indices) in row_indices_by_pack {
|
|
189
|
-
let report = json_writer.stage_batch_report(
|
|
190
|
-
writes,
|
|
191
|
-
JsonWritePlacementRef::CommitPack { commit_id, pack_id },
|
|
192
|
-
pack_row_indices
|
|
193
|
-
.iter()
|
|
194
|
-
.flat_map(|&row_index| json_payloads_from_state_row(&state_rows[row_index])),
|
|
195
|
-
)?;
|
|
196
|
-
pack_indexes_by_commit
|
|
197
|
-
.entry(commit_id.clone())
|
|
198
|
-
.or_insert_with(BTreeMap::new)
|
|
199
|
-
.insert(pack_id, report.pack_indexes);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
144
|
+
row_indices: &[RowIndex],
|
|
145
|
+
) -> Result<(), LixError> {
|
|
202
146
|
json_writer.stage_batch(
|
|
203
147
|
writes,
|
|
204
148
|
JsonWritePlacementRef::OutOfBand,
|
|
205
|
-
|
|
149
|
+
row_indices
|
|
206
150
|
.iter()
|
|
207
151
|
.flat_map(|&row_index| json_payloads_from_state_row(&state_rows[row_index])),
|
|
208
152
|
)?;
|
|
209
|
-
Ok(
|
|
153
|
+
Ok(())
|
|
210
154
|
}
|
|
211
155
|
|
|
212
156
|
fn json_payloads_from_state_row(
|
|
@@ -219,11 +163,11 @@ fn json_payloads_from_state_row(
|
|
|
219
163
|
}
|
|
220
164
|
|
|
221
165
|
async fn existing_untracked_overlay_delete_identities<'a>(
|
|
222
|
-
|
|
166
|
+
read: &(impl StorageRead + Send + Sync + ?Sized),
|
|
223
167
|
identities: impl IntoIterator<Item = UntrackedStateIdentityRef<'a>>,
|
|
224
168
|
) -> Result<Vec<UntrackedStateIdentity>, LixError> {
|
|
225
169
|
UntrackedStateContext::new()
|
|
226
|
-
.reader(
|
|
170
|
+
.reader(read)
|
|
227
171
|
.existing_identities(identities)
|
|
228
172
|
.await
|
|
229
173
|
}
|
|
@@ -234,10 +178,6 @@ struct PreparedRowIndex {
|
|
|
234
178
|
tracked_row_indices_by_commit: BTreeMap<String, Vec<RowIndex>>,
|
|
235
179
|
}
|
|
236
180
|
|
|
237
|
-
struct PreparedAdoptedRowIndex {
|
|
238
|
-
tracked_row_indices_by_commit: BTreeMap<String, Vec<AdoptedRowIndex>>,
|
|
239
|
-
}
|
|
240
|
-
|
|
241
181
|
fn index_prepared_rows(rows: &[PreparedStateRow]) -> Result<PreparedRowIndex, LixError> {
|
|
242
182
|
let mut canonical_row_indices = Vec::new();
|
|
243
183
|
let mut untracked_row_indices = Vec::new();
|
|
@@ -268,242 +208,277 @@ fn index_prepared_rows(rows: &[PreparedStateRow]) -> Result<PreparedRowIndex, Li
|
|
|
268
208
|
})
|
|
269
209
|
}
|
|
270
210
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
.push(row_index);
|
|
278
|
-
}
|
|
279
|
-
PreparedAdoptedRowIndex {
|
|
280
|
-
tracked_row_indices_by_commit,
|
|
281
|
-
}
|
|
211
|
+
#[derive(Clone, Debug)]
|
|
212
|
+
struct StagedChangelogCommit {
|
|
213
|
+
change_ids: Vec<String>,
|
|
214
|
+
selected_change_refs: Vec<StagedCommitChangeRef>,
|
|
215
|
+
commit_change_id: String,
|
|
216
|
+
commit_created_at: String,
|
|
282
217
|
}
|
|
283
218
|
|
|
284
|
-
async fn
|
|
285
|
-
|
|
286
|
-
transaction: &mut (impl StorageReader + ?Sized),
|
|
219
|
+
async fn stage_changelog_commits(
|
|
220
|
+
read: &mut (impl StorageRead + Send + Sync),
|
|
287
221
|
writes: &mut StorageWriteSet,
|
|
288
222
|
state_rows: &[PreparedStateRow],
|
|
289
223
|
tracked_row_indices_by_commit: &BTreeMap<String, Vec<RowIndex>>,
|
|
290
|
-
adopted_rows: &[PreparedAdoptedStateRow],
|
|
291
|
-
adopted_row_indices_by_commit: &BTreeMap<String, Vec<AdoptedRowIndex>>,
|
|
292
224
|
commit_rows: &[FinalizedCommitRow],
|
|
293
|
-
) -> Result<BTreeMap<String,
|
|
225
|
+
) -> Result<BTreeMap<String, StagedChangelogCommit>, LixError> {
|
|
226
|
+
if commit_rows.is_empty() {
|
|
227
|
+
return Ok(BTreeMap::new());
|
|
228
|
+
}
|
|
229
|
+
|
|
294
230
|
let mut commits = Vec::with_capacity(commit_rows.len());
|
|
295
|
-
let mut
|
|
231
|
+
let mut changes = Vec::new();
|
|
232
|
+
let mut commit_change_refs = Vec::with_capacity(commit_rows.len());
|
|
233
|
+
let mut staged = BTreeMap::<String, StagedChangelogCommit>::new();
|
|
296
234
|
for commit_row in commit_rows {
|
|
297
235
|
let state_row_indices = tracked_row_indices_by_commit
|
|
298
236
|
.get(&commit_row.commit_id)
|
|
299
237
|
.map(Vec::as_slice)
|
|
300
238
|
.unwrap_or_default();
|
|
301
|
-
let
|
|
302
|
-
|
|
303
|
-
.
|
|
304
|
-
.unwrap_or_default();
|
|
305
|
-
let mut authored_changes = Vec::with_capacity(state_row_indices.len());
|
|
239
|
+
let mut refs = Vec::with_capacity(state_row_indices.len());
|
|
240
|
+
let mut change_ids =
|
|
241
|
+
Vec::with_capacity(state_row_indices.len() + commit_row.selected_change_refs.len());
|
|
306
242
|
for &row_index in state_row_indices {
|
|
307
|
-
|
|
243
|
+
let row = &state_rows[row_index];
|
|
244
|
+
let change_id = row.change_id.as_ref().ok_or_else(|| {
|
|
245
|
+
LixError::new(
|
|
246
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
247
|
+
"tracked staged row is missing change_id before changelog append",
|
|
248
|
+
)
|
|
249
|
+
})?;
|
|
250
|
+
refs.push(commit_change_ref_from_state_row(row, change_id));
|
|
251
|
+
change_ids.push(change_id.clone());
|
|
252
|
+
changes.push(change_record_from_state_row(row)?);
|
|
308
253
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
254
|
+
for change_ref in &commit_row.selected_change_refs {
|
|
255
|
+
refs.push(commit_change_ref_from_selected_change_ref(change_ref));
|
|
256
|
+
change_ids.push(change_ref.change_id.clone());
|
|
312
257
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
author_account_ids:
|
|
319
|
-
created_at:
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
|
|
258
|
+
commits.push(CommitRecord {
|
|
259
|
+
format_version: 1,
|
|
260
|
+
commit_id: commit_row.commit_id.clone(),
|
|
261
|
+
parent_commit_ids: commit_row.parent_commit_ids.clone(),
|
|
262
|
+
change_id: commit_row.change_id.clone(),
|
|
263
|
+
author_account_ids: Vec::new(),
|
|
264
|
+
created_at: commit_row.created_at.clone(),
|
|
265
|
+
});
|
|
266
|
+
commit_change_refs.push(CommitChangeRefSet {
|
|
267
|
+
commit_id: commit_row.commit_id.clone(),
|
|
268
|
+
entries: refs,
|
|
269
|
+
});
|
|
270
|
+
staged.insert(
|
|
271
|
+
commit_row.commit_id.clone(),
|
|
272
|
+
StagedChangelogCommit {
|
|
273
|
+
change_ids,
|
|
274
|
+
selected_change_refs: commit_row.selected_change_refs.clone(),
|
|
275
|
+
commit_change_id: commit_row.change_id.clone(),
|
|
276
|
+
commit_created_at: commit_row.created_at.clone(),
|
|
277
|
+
},
|
|
278
|
+
);
|
|
323
279
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
280
|
+
|
|
281
|
+
let append = ChangelogAppend {
|
|
282
|
+
commits,
|
|
283
|
+
changes,
|
|
284
|
+
commit_change_refs,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
let mut writer = ChangelogContext::new().writer(read, writes);
|
|
288
|
+
writer.stage_append(append).await?;
|
|
289
|
+
stage_commit_row_json_payloads(writes, commit_rows)?;
|
|
290
|
+
Ok(staged)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
fn change_record_from_state_row(row: &PreparedStateRow) -> Result<ChangeRecord, LixError> {
|
|
294
|
+
let Some(change_id) = row.change_id.as_ref() else {
|
|
329
295
|
return Err(LixError::new(
|
|
330
296
|
LixError::CODE_INTERNAL_ERROR,
|
|
331
|
-
|
|
332
|
-
"commit-store staged {} commits for {} finalized commit rows",
|
|
333
|
-
staged.len(),
|
|
334
|
-
commit_ids.len()
|
|
335
|
-
),
|
|
297
|
+
"tracked staged row is missing change_id before changelog change construction",
|
|
336
298
|
));
|
|
299
|
+
};
|
|
300
|
+
Ok(ChangeRecord {
|
|
301
|
+
format_version: 1,
|
|
302
|
+
change_id: change_id.clone(),
|
|
303
|
+
entity_pk: row.entity_pk.clone(),
|
|
304
|
+
schema_key: row.schema_key.clone(),
|
|
305
|
+
file_id: row.file_id.clone(),
|
|
306
|
+
snapshot_ref: row.snapshot.as_ref().map(|snapshot| snapshot.json_ref),
|
|
307
|
+
metadata_ref: row.metadata.as_ref().map(|metadata| metadata.json_ref),
|
|
308
|
+
created_at: row.updated_at.clone(),
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
fn stage_commit_row_json_payloads(
|
|
313
|
+
writes: &mut StorageWriteSet,
|
|
314
|
+
commit_rows: &[FinalizedCommitRow],
|
|
315
|
+
) -> Result<(), LixError> {
|
|
316
|
+
let snapshots = commit_rows
|
|
317
|
+
.iter()
|
|
318
|
+
.map(|row| commit_row_snapshot_content(&row.commit_id))
|
|
319
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
320
|
+
JsonStoreContext::new().writer().stage_batch(
|
|
321
|
+
writes,
|
|
322
|
+
JsonWritePlacementRef::OutOfBand,
|
|
323
|
+
snapshots
|
|
324
|
+
.iter()
|
|
325
|
+
.map(|snapshot| NormalizedJsonRef::new(snapshot.as_str())),
|
|
326
|
+
)?;
|
|
327
|
+
Ok(())
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
fn commit_row_snapshot_content(commit_id: &str) -> Result<String, LixError> {
|
|
331
|
+
serde_json::to_string(&serde_json::json!({
|
|
332
|
+
"id": commit_id,
|
|
333
|
+
}))
|
|
334
|
+
.map_err(|error| {
|
|
335
|
+
LixError::new(
|
|
336
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
337
|
+
format!("failed to encode lix_commit snapshot: {error}"),
|
|
338
|
+
)
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fn commit_change_ref_from_state_row(row: &PreparedStateRow, change_id: &str) -> CommitChangeRef {
|
|
343
|
+
CommitChangeRef {
|
|
344
|
+
schema_key: row.schema_key.clone(),
|
|
345
|
+
file_id: row.file_id.clone(),
|
|
346
|
+
entity_pk: row.entity_pk.clone(),
|
|
347
|
+
change_id: change_id.to_string(),
|
|
337
348
|
}
|
|
338
|
-
Ok(commit_ids.into_iter().zip(staged).collect())
|
|
339
349
|
}
|
|
340
350
|
|
|
341
|
-
fn
|
|
351
|
+
fn commit_change_ref_from_selected_change_ref(
|
|
352
|
+
change_ref: &StagedCommitChangeRef,
|
|
353
|
+
) -> CommitChangeRef {
|
|
354
|
+
CommitChangeRef {
|
|
355
|
+
schema_key: change_ref.schema_key.clone(),
|
|
356
|
+
file_id: change_ref.file_id.clone(),
|
|
357
|
+
entity_pk: change_ref.entity_pk.clone(),
|
|
358
|
+
change_id: change_ref.change_id.clone(),
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
fn tracked_delta_from_state_row(
|
|
363
|
+
row: &PreparedStateRow,
|
|
364
|
+
) -> Result<TrackedStateDeltaRef<'_>, LixError> {
|
|
342
365
|
let Some(change_id) = row.change_id.as_deref() else {
|
|
343
366
|
return Err(LixError::new(
|
|
344
|
-
|
|
345
|
-
"tracked staged row is missing change_id before
|
|
367
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
368
|
+
"tracked staged row is missing change_id before tracked root staging",
|
|
346
369
|
));
|
|
347
370
|
};
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
371
|
+
let Some(commit_id) = row.commit_id.as_deref() else {
|
|
372
|
+
return Err(LixError::new(
|
|
373
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
374
|
+
"tracked staged row is missing commit_id before tracked root staging",
|
|
375
|
+
));
|
|
376
|
+
};
|
|
377
|
+
Ok(TrackedStateDeltaRef {
|
|
352
378
|
schema_key: &row.schema_key,
|
|
353
379
|
file_id: row.file_id.as_deref(),
|
|
380
|
+
entity_pk: &row.entity_pk,
|
|
381
|
+
change_id,
|
|
382
|
+
commit_id,
|
|
354
383
|
snapshot_ref: row.snapshot.as_ref().map(|snapshot| &snapshot.json_ref),
|
|
355
384
|
metadata_ref: row.metadata.as_ref().map(|metadata| &metadata.json_ref),
|
|
356
|
-
|
|
385
|
+
deleted: row.snapshot.is_none(),
|
|
386
|
+
created_at: &row.created_at,
|
|
387
|
+
updated_at: &row.updated_at,
|
|
357
388
|
})
|
|
358
389
|
}
|
|
359
390
|
|
|
360
|
-
fn
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
391
|
+
fn tracked_delta_from_selected_change_ref<'a>(
|
|
392
|
+
change_ref: &'a StagedCommitChangeRef,
|
|
393
|
+
commit_id: &'a str,
|
|
394
|
+
) -> Result<TrackedStateDeltaRef<'a>, LixError> {
|
|
395
|
+
Ok(TrackedStateDeltaRef {
|
|
396
|
+
schema_key: &change_ref.schema_key,
|
|
397
|
+
file_id: change_ref.file_id.as_deref(),
|
|
398
|
+
entity_pk: &change_ref.entity_pk,
|
|
399
|
+
change_id: &change_ref.change_id,
|
|
400
|
+
commit_id,
|
|
401
|
+
snapshot_ref: change_ref.snapshot_ref.as_ref(),
|
|
402
|
+
metadata_ref: change_ref.metadata_ref.as_ref(),
|
|
403
|
+
deleted: change_ref.deleted,
|
|
404
|
+
created_at: &change_ref.created_at,
|
|
405
|
+
updated_at: &change_ref.updated_at,
|
|
406
|
+
})
|
|
370
407
|
}
|
|
371
408
|
|
|
372
409
|
async fn stage_tracked_roots(
|
|
373
|
-
|
|
410
|
+
read: &(impl StorageRead + Send + Sync + ?Sized),
|
|
374
411
|
writes: &mut StorageWriteSet,
|
|
375
412
|
state_rows: &[PreparedStateRow],
|
|
376
|
-
|
|
377
|
-
adopted_rows: &[PreparedAdoptedStateRow],
|
|
378
|
-
mut adopted_row_indices_by_commit: BTreeMap<String, Vec<AdoptedRowIndex>>,
|
|
413
|
+
tracked_row_indices_by_commit: BTreeMap<String, Vec<RowIndex>>,
|
|
379
414
|
tracked_roots: Vec<PendingTrackedRoot>,
|
|
380
|
-
|
|
381
|
-
json_pack_indexes_by_commit: BTreeMap<
|
|
382
|
-
String,
|
|
383
|
-
BTreeMap<u32, std::collections::HashMap<[u8; 32], usize>>,
|
|
384
|
-
>,
|
|
415
|
+
staged_commits: BTreeMap<String, StagedChangelogCommit>,
|
|
385
416
|
) -> Result<(), LixError> {
|
|
386
417
|
let tracked_state = TrackedStateContext::new();
|
|
387
|
-
let mut
|
|
388
|
-
for root in tracked_roots {
|
|
389
|
-
let staged = staged_commits.
|
|
418
|
+
let mut tracked_writer = tracked_state.writer(read, writes);
|
|
419
|
+
for root in tracked_roots_parent_first(&tracked_roots)? {
|
|
420
|
+
let staged = staged_commits.get(&root.commit_id).ok_or_else(|| {
|
|
390
421
|
LixError::new(
|
|
391
422
|
LixError::CODE_INTERNAL_ERROR,
|
|
392
423
|
format!(
|
|
393
|
-
"tracked-state root for commit '{}' has no staged
|
|
424
|
+
"tracked-state root for commit '{}' has no staged changelog facts",
|
|
394
425
|
root.commit_id
|
|
395
426
|
),
|
|
396
427
|
)
|
|
397
428
|
})?;
|
|
398
429
|
let state_row_indices = tracked_row_indices_by_commit
|
|
399
|
-
.
|
|
400
|
-
.
|
|
401
|
-
let adopted_row_indices = adopted_row_indices_by_commit
|
|
402
|
-
.remove(&root.commit_id)
|
|
430
|
+
.get(&root.commit_id)
|
|
431
|
+
.map(Vec::as_slice)
|
|
403
432
|
.unwrap_or_default();
|
|
404
|
-
if state_row_indices.len()
|
|
433
|
+
if state_row_indices.len() > staged.change_ids.len() {
|
|
405
434
|
return Err(LixError::new(
|
|
406
435
|
LixError::CODE_INTERNAL_ERROR,
|
|
407
436
|
format!(
|
|
408
|
-
"commit '{}' has {} tracked
|
|
437
|
+
"commit '{}' has {} tracked rows but only {} changelog changes",
|
|
409
438
|
root.commit_id,
|
|
410
439
|
state_row_indices.len(),
|
|
411
|
-
staged.
|
|
412
|
-
),
|
|
413
|
-
));
|
|
414
|
-
}
|
|
415
|
-
if adopted_row_indices.len() != staged.adopted_locators.len() {
|
|
416
|
-
return Err(LixError::new(
|
|
417
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
418
|
-
format!(
|
|
419
|
-
"commit '{}' has {} tracked adopted rows but {} commit-store adopted locators",
|
|
420
|
-
root.commit_id,
|
|
421
|
-
adopted_row_indices.len(),
|
|
422
|
-
staged.adopted_locators.len()
|
|
440
|
+
staged.change_ids.len()
|
|
423
441
|
),
|
|
424
442
|
));
|
|
425
443
|
}
|
|
426
|
-
let
|
|
444
|
+
let commit_snapshot = commit_row_snapshot_content(&root.commit_id)?;
|
|
445
|
+
let commit_snapshot_ref = JsonRef::for_content(commit_snapshot.as_bytes());
|
|
446
|
+
let commit_entity_pk = EntityPk::single(root.commit_id.clone());
|
|
447
|
+
let mut deltas = state_row_indices
|
|
427
448
|
.iter()
|
|
428
|
-
.map(|&row_index|
|
|
449
|
+
.map(|&row_index| tracked_delta_from_state_row(&state_rows[row_index]))
|
|
450
|
+
.chain(staged.selected_change_refs.iter().map(|change_ref| {
|
|
451
|
+
tracked_delta_from_selected_change_ref(change_ref, &root.commit_id)
|
|
452
|
+
}))
|
|
429
453
|
.collect::<Result<Vec<_>, _>>()?;
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
.
|
|
444
|
-
.
|
|
445
|
-
.collect::<Vec<_>>();
|
|
446
|
-
let adopted_created_at = adopted_row_indices
|
|
447
|
-
.iter()
|
|
448
|
-
.map(|&row_index| adopted_rows[row_index].created_at.as_str())
|
|
449
|
-
.collect::<Vec<_>>();
|
|
450
|
-
let mut deltas = Vec::with_capacity(authored_changes.len() + adopted_changes.len());
|
|
451
|
-
deltas.extend(
|
|
452
|
-
authored_changes
|
|
453
|
-
.iter()
|
|
454
|
-
.zip(&staged.authored_locators)
|
|
455
|
-
.zip(authored_created_at)
|
|
456
|
-
.zip(authored_updated_at)
|
|
457
|
-
.map(
|
|
458
|
-
|(((change, locator), created_at), updated_at)| TrackedStateDeltaRef {
|
|
459
|
-
change: *change,
|
|
460
|
-
locator: locator.as_ref(),
|
|
461
|
-
created_at,
|
|
462
|
-
updated_at,
|
|
463
|
-
},
|
|
464
|
-
),
|
|
465
|
-
);
|
|
466
|
-
deltas.extend(
|
|
467
|
-
adopted_changes
|
|
468
|
-
.iter()
|
|
469
|
-
.zip(&staged.adopted_locators)
|
|
470
|
-
.zip(adopted_created_at)
|
|
471
|
-
.zip(adopted_updated_at)
|
|
472
|
-
.map(
|
|
473
|
-
|(((change, locator), created_at), updated_at)| TrackedStateDeltaRef {
|
|
474
|
-
change: *change,
|
|
475
|
-
locator: locator.as_ref(),
|
|
476
|
-
created_at,
|
|
477
|
-
updated_at,
|
|
478
|
-
},
|
|
479
|
-
),
|
|
480
|
-
);
|
|
481
|
-
if let Some(indexes) = json_pack_indexes_by_commit
|
|
482
|
-
.get(&root.commit_id)
|
|
483
|
-
.and_then(|packs| packs.get(&0))
|
|
484
|
-
{
|
|
485
|
-
writer
|
|
486
|
-
.stage_delta_with_json_pack_indexes(
|
|
487
|
-
&root.commit_id,
|
|
488
|
-
root.parent_commit_id.as_deref(),
|
|
489
|
-
&deltas,
|
|
490
|
-
crate::tracked_state::DeltaJsonPackIndexesRef {
|
|
491
|
-
commit_id: &root.commit_id,
|
|
492
|
-
pack_id: 0,
|
|
493
|
-
indexes,
|
|
494
|
-
},
|
|
495
|
-
)
|
|
496
|
-
.await?;
|
|
497
|
-
} else {
|
|
498
|
-
writer
|
|
499
|
-
.stage_delta(&root.commit_id, root.parent_commit_id.as_deref(), &deltas)
|
|
500
|
-
.await?;
|
|
501
|
-
}
|
|
454
|
+
deltas.push(TrackedStateDeltaRef {
|
|
455
|
+
schema_key: "lix_commit",
|
|
456
|
+
file_id: None,
|
|
457
|
+
entity_pk: &commit_entity_pk,
|
|
458
|
+
change_id: &staged.commit_change_id,
|
|
459
|
+
commit_id: &root.commit_id,
|
|
460
|
+
snapshot_ref: Some(&commit_snapshot_ref),
|
|
461
|
+
metadata_ref: None,
|
|
462
|
+
deleted: false,
|
|
463
|
+
created_at: &staged.commit_created_at,
|
|
464
|
+
updated_at: &staged.commit_created_at,
|
|
465
|
+
});
|
|
466
|
+
tracked_writer
|
|
467
|
+
.stage_commit_root(&root.commit_id, root.parent_commit_id.as_deref(), deltas)
|
|
468
|
+
.await?;
|
|
502
469
|
}
|
|
503
|
-
|
|
470
|
+
let rooted_commit_ids = tracked_roots
|
|
471
|
+
.iter()
|
|
472
|
+
.map(|root| root.commit_id.as_str())
|
|
473
|
+
.collect::<BTreeSet<_>>();
|
|
474
|
+
let extra_tracked = tracked_row_indices_by_commit
|
|
475
|
+
.keys()
|
|
476
|
+
.filter(|commit_id| !rooted_commit_ids.contains(commit_id.as_str()))
|
|
477
|
+
.cloned()
|
|
478
|
+
.collect::<BTreeSet<_>>();
|
|
479
|
+
if !extra_tracked.is_empty() {
|
|
504
480
|
let mut commit_ids = tracked_row_indices_by_commit
|
|
505
481
|
.keys()
|
|
506
|
-
.chain(adopted_row_indices_by_commit.keys())
|
|
507
482
|
.cloned()
|
|
508
483
|
.collect::<Vec<_>>();
|
|
509
484
|
commit_ids.sort();
|
|
@@ -517,11 +492,18 @@ async fn stage_tracked_roots(
|
|
|
517
492
|
));
|
|
518
493
|
}
|
|
519
494
|
if !staged_commits.is_empty() {
|
|
520
|
-
let commit_ids = staged_commits
|
|
495
|
+
let commit_ids = staged_commits
|
|
496
|
+
.keys()
|
|
497
|
+
.filter(|commit_id| !rooted_commit_ids.contains(commit_id.as_str()))
|
|
498
|
+
.cloned()
|
|
499
|
+
.collect::<Vec<_>>();
|
|
500
|
+
if commit_ids.is_empty() {
|
|
501
|
+
return Ok(());
|
|
502
|
+
}
|
|
521
503
|
return Err(LixError::new(
|
|
522
504
|
LixError::CODE_INTERNAL_ERROR,
|
|
523
505
|
format!(
|
|
524
|
-
"
|
|
506
|
+
"changelog staged commits without tracked root metadata: {}",
|
|
525
507
|
commit_ids.join(", ")
|
|
526
508
|
),
|
|
527
509
|
));
|
|
@@ -529,9 +511,67 @@ async fn stage_tracked_roots(
|
|
|
529
511
|
Ok(())
|
|
530
512
|
}
|
|
531
513
|
|
|
514
|
+
fn tracked_roots_parent_first(
|
|
515
|
+
tracked_roots: &[PendingTrackedRoot],
|
|
516
|
+
) -> Result<Vec<&PendingTrackedRoot>, LixError> {
|
|
517
|
+
let mut roots_by_id = BTreeMap::new();
|
|
518
|
+
for root in tracked_roots {
|
|
519
|
+
if roots_by_id.insert(root.commit_id.as_str(), root).is_some() {
|
|
520
|
+
return Err(LixError::unknown(format!(
|
|
521
|
+
"cannot stage duplicate tracked_state root '{}'",
|
|
522
|
+
root.commit_id
|
|
523
|
+
)));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
let mut ordered = Vec::with_capacity(tracked_roots.len());
|
|
528
|
+
let mut visiting = BTreeSet::new();
|
|
529
|
+
let mut visited = BTreeSet::new();
|
|
530
|
+
for root in tracked_roots {
|
|
531
|
+
visit_tracked_root_parent_first(
|
|
532
|
+
root.commit_id.as_str(),
|
|
533
|
+
&roots_by_id,
|
|
534
|
+
&mut visiting,
|
|
535
|
+
&mut visited,
|
|
536
|
+
&mut ordered,
|
|
537
|
+
)?;
|
|
538
|
+
}
|
|
539
|
+
Ok(ordered)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
fn visit_tracked_root_parent_first<'a>(
|
|
543
|
+
commit_id: &str,
|
|
544
|
+
roots_by_id: &BTreeMap<&'a str, &'a PendingTrackedRoot>,
|
|
545
|
+
visiting: &mut BTreeSet<&'a str>,
|
|
546
|
+
visited: &mut BTreeSet<&'a str>,
|
|
547
|
+
ordered: &mut Vec<&'a PendingTrackedRoot>,
|
|
548
|
+
) -> Result<(), LixError> {
|
|
549
|
+
if visited.contains(commit_id) {
|
|
550
|
+
return Ok(());
|
|
551
|
+
}
|
|
552
|
+
let Some(root) = roots_by_id.get(commit_id).copied() else {
|
|
553
|
+
return Ok(());
|
|
554
|
+
};
|
|
555
|
+
if !visiting.insert(root.commit_id.as_str()) {
|
|
556
|
+
return Err(LixError::unknown(format!(
|
|
557
|
+
"cannot stage tracked_state root '{}' because staged root parents contain a cycle",
|
|
558
|
+
root.commit_id
|
|
559
|
+
)));
|
|
560
|
+
}
|
|
561
|
+
if let Some(parent_id) = root.parent_commit_id.as_deref() {
|
|
562
|
+
if roots_by_id.contains_key(parent_id) {
|
|
563
|
+
visit_tracked_root_parent_first(parent_id, roots_by_id, visiting, visited, ordered)?;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
visiting.remove(root.commit_id.as_str());
|
|
567
|
+
visited.insert(root.commit_id.as_str());
|
|
568
|
+
ordered.push(root);
|
|
569
|
+
Ok(())
|
|
570
|
+
}
|
|
571
|
+
|
|
532
572
|
fn untracked_row_ref_from_state_row(row: &PreparedStateRow) -> UntrackedStateRowRef<'_> {
|
|
533
573
|
UntrackedStateRowRef {
|
|
534
|
-
|
|
574
|
+
entity_pk: &row.entity_pk,
|
|
535
575
|
schema_key: &row.schema_key,
|
|
536
576
|
file_id: row.file_id.as_deref(),
|
|
537
577
|
snapshot_content: row
|
|
@@ -545,51 +585,37 @@ fn untracked_row_ref_from_state_row(row: &PreparedStateRow) -> UntrackedStateRow
|
|
|
545
585
|
created_at: &row.created_at,
|
|
546
586
|
updated_at: &row.updated_at,
|
|
547
587
|
global: row.global,
|
|
548
|
-
|
|
588
|
+
branch_id: &row.branch_id,
|
|
549
589
|
}
|
|
550
590
|
}
|
|
551
591
|
|
|
552
592
|
fn untracked_identity_ref_from_state_row(row: &PreparedStateRow) -> UntrackedStateIdentityRef<'_> {
|
|
553
593
|
UntrackedStateIdentityRef {
|
|
554
|
-
|
|
594
|
+
branch_id: &row.branch_id,
|
|
555
595
|
schema_key: &row.schema_key,
|
|
556
|
-
|
|
596
|
+
entity_pk: &row.entity_pk,
|
|
557
597
|
file_id: row.file_id.as_deref(),
|
|
558
598
|
}
|
|
559
599
|
}
|
|
560
600
|
|
|
561
|
-
|
|
562
|
-
row: &PreparedAdoptedStateRow,
|
|
563
|
-
) -> UntrackedStateIdentityRef<'_> {
|
|
564
|
-
UntrackedStateIdentityRef {
|
|
565
|
-
version_id: &row.version_id,
|
|
566
|
-
schema_key: &row.schema_key,
|
|
567
|
-
entity_id: &row.entity_id,
|
|
568
|
-
file_id: row.file_id.as_deref(),
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
/// Materializes tracked staged membership into commit-store commits.
|
|
601
|
+
/// Materializes tracked staged change refs into changelog commits.
|
|
573
602
|
///
|
|
574
|
-
/// Staging only accumulates `
|
|
603
|
+
/// Staging only accumulates `branch_id -> change_ids` because commit ids,
|
|
575
604
|
/// parent heads, and commit-row timestamps belong to transaction finalization.
|
|
576
605
|
/// The `change_ids` list is the ordered set of canonical changes whose effects
|
|
577
|
-
/// the commit introduces relative to its first parent
|
|
578
|
-
///
|
|
579
|
-
/// change payloads.
|
|
580
|
-
/// This function turns those membership sets into finalized commit facts.
|
|
606
|
+
/// the commit introduces relative to its first parent.
|
|
607
|
+
/// This function turns those change-ref sets into finalized commit facts.
|
|
581
608
|
///
|
|
582
609
|
/// Commit finalization output split by durability target.
|
|
583
610
|
///
|
|
584
|
-
/// `commit_rows` are canonical commit
|
|
585
|
-
/// commit
|
|
586
|
-
/// commit graph facts.
|
|
611
|
+
/// `commit_rows` are canonical changelog commit facts. tracked_state roots store
|
|
612
|
+
/// serving commit roots keyed by the corresponding commit id.
|
|
587
613
|
///
|
|
588
|
-
/// `
|
|
589
|
-
/// not the canonical
|
|
614
|
+
/// `branch_heads` are moving refs. They are written through `BranchContext`,
|
|
615
|
+
/// not the canonical changelog.
|
|
590
616
|
struct FinalizedCommitRows {
|
|
591
617
|
commit_rows: Vec<FinalizedCommitRow>,
|
|
592
|
-
|
|
618
|
+
branch_heads: Vec<PendingBranchHead>,
|
|
593
619
|
tracked_roots: Vec<PendingTrackedRoot>,
|
|
594
620
|
}
|
|
595
621
|
|
|
@@ -598,10 +624,11 @@ struct FinalizedCommitRow {
|
|
|
598
624
|
parent_commit_ids: Vec<String>,
|
|
599
625
|
created_at: String,
|
|
600
626
|
change_id: String,
|
|
627
|
+
selected_change_refs: Vec<StagedCommitChangeRef>,
|
|
601
628
|
}
|
|
602
629
|
|
|
603
|
-
struct
|
|
604
|
-
|
|
630
|
+
struct PendingBranchHead {
|
|
631
|
+
branch_id: String,
|
|
605
632
|
commit_id: String,
|
|
606
633
|
timestamp: String,
|
|
607
634
|
}
|
|
@@ -612,34 +639,34 @@ struct PendingTrackedRoot {
|
|
|
612
639
|
}
|
|
613
640
|
|
|
614
641
|
async fn finalize_commit_rows(
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
642
|
+
commit_change_refs_by_branch: BTreeMap<String, StagedCommitChangeRefs>,
|
|
643
|
+
extra_commit_parents_by_branch: BTreeMap<String, Vec<String>>,
|
|
644
|
+
branch_ctx: &BranchContext,
|
|
645
|
+
read: &(impl StorageRead + Send + Sync + ?Sized),
|
|
619
646
|
) -> Result<FinalizedCommitRows, LixError> {
|
|
620
647
|
let mut commit_rows = Vec::new();
|
|
621
|
-
let mut
|
|
648
|
+
let mut branch_heads = Vec::new();
|
|
622
649
|
let mut tracked_roots = Vec::new();
|
|
623
650
|
|
|
624
|
-
for (
|
|
625
|
-
if
|
|
651
|
+
for (branch_id, change_refs) in commit_change_refs_by_branch {
|
|
652
|
+
if change_refs.is_empty() && !change_refs.allow_empty {
|
|
626
653
|
continue;
|
|
627
654
|
}
|
|
628
655
|
|
|
629
|
-
let commit_id =
|
|
630
|
-
let commit_change_id =
|
|
631
|
-
let timestamp =
|
|
632
|
-
let
|
|
633
|
-
let parent_commit_ids =
|
|
634
|
-
.ref_reader(
|
|
635
|
-
.load_head_commit_id(&
|
|
656
|
+
let commit_id = change_refs.commit_id;
|
|
657
|
+
let commit_change_id = change_refs.commit_change_id;
|
|
658
|
+
let timestamp = change_refs.created_at;
|
|
659
|
+
let selected_change_refs = change_refs.selected_change_refs;
|
|
660
|
+
let parent_commit_ids = branch_ctx
|
|
661
|
+
.ref_reader(read)
|
|
662
|
+
.load_head_commit_id(&branch_id)
|
|
636
663
|
.await?
|
|
637
664
|
.into_iter()
|
|
638
665
|
.collect::<Vec<_>>();
|
|
639
666
|
let parent_commit_ids = merge_parent_commit_ids(
|
|
640
667
|
parent_commit_ids,
|
|
641
|
-
|
|
642
|
-
.get(&
|
|
668
|
+
extra_commit_parents_by_branch
|
|
669
|
+
.get(&branch_id)
|
|
643
670
|
.cloned()
|
|
644
671
|
.unwrap_or_default(),
|
|
645
672
|
);
|
|
@@ -650,9 +677,10 @@ async fn finalize_commit_rows(
|
|
|
650
677
|
parent_commit_ids: parent_commit_ids.clone(),
|
|
651
678
|
created_at: timestamp.clone(),
|
|
652
679
|
change_id: commit_change_id,
|
|
680
|
+
selected_change_refs,
|
|
653
681
|
});
|
|
654
|
-
|
|
655
|
-
|
|
682
|
+
branch_heads.push(PendingBranchHead {
|
|
683
|
+
branch_id: branch_id.clone(),
|
|
656
684
|
commit_id: commit_id.clone(),
|
|
657
685
|
timestamp,
|
|
658
686
|
});
|
|
@@ -664,7 +692,7 @@ async fn finalize_commit_rows(
|
|
|
664
692
|
|
|
665
693
|
Ok(FinalizedCommitRows {
|
|
666
694
|
commit_rows,
|
|
667
|
-
|
|
695
|
+
branch_heads,
|
|
668
696
|
tracked_roots,
|
|
669
697
|
})
|
|
670
698
|
}
|
|
@@ -688,23 +716,23 @@ mod tests {
|
|
|
688
716
|
|
|
689
717
|
use super::*;
|
|
690
718
|
use crate::backend::{
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
BackendKvValuePage, BackendKvWriteBatch, BackendKvWriteStats, BackendReadTransaction,
|
|
694
|
-
BackendWriteTransaction,
|
|
719
|
+
Backend, BackendCapabilities, BackendError, BackendWrite, CommitResult, DurableWriteLock,
|
|
720
|
+
KeyRange, PutBatch,
|
|
695
721
|
};
|
|
722
|
+
use crate::branch::BranchContext;
|
|
696
723
|
use crate::catalog::SchemaPlanId;
|
|
697
|
-
use crate::
|
|
724
|
+
use crate::changelog::ChangelogReader;
|
|
698
725
|
use crate::live_state::{LiveStateContext, LiveStateRowRequest};
|
|
699
|
-
use crate::storage::
|
|
726
|
+
use crate::storage::{
|
|
727
|
+
InMemoryStorageBackend, InMemoryStorageRead, InMemoryStorageWrite, StorageContext,
|
|
728
|
+
StorageKey, StorageReadOptions, StorageWriteOptions,
|
|
729
|
+
};
|
|
700
730
|
use crate::transaction::types::PreparedRowFacts;
|
|
701
731
|
use crate::untracked_state::{
|
|
702
732
|
MaterializedUntrackedStateRow, UntrackedStateContext, UntrackedStateRowRequest,
|
|
703
733
|
};
|
|
704
|
-
use crate::version::VersionContext;
|
|
705
734
|
use crate::NullableKeyFilter;
|
|
706
|
-
use crate::
|
|
707
|
-
use async_trait::async_trait;
|
|
735
|
+
use crate::GLOBAL_BRANCH_ID;
|
|
708
736
|
|
|
709
737
|
const DETERMINISTIC_MODE_KEY: &str = "lix_deterministic_mode";
|
|
710
738
|
const DETERMINISTIC_SEQUENCE_KEY: &str = "lix_deterministic_sequence_number";
|
|
@@ -718,138 +746,215 @@ mod tests {
|
|
|
718
746
|
}
|
|
719
747
|
|
|
720
748
|
#[tokio::test]
|
|
721
|
-
async fn
|
|
722
|
-
let
|
|
723
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
749
|
+
async fn commit_staged_writes_appends_changelog_and_updates_commit_root() {
|
|
750
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
724
751
|
let binary_cas = BinaryCasContext::new();
|
|
725
|
-
let
|
|
726
|
-
let mut
|
|
727
|
-
.
|
|
728
|
-
.
|
|
729
|
-
.expect("transaction should open");
|
|
752
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
753
|
+
let mut read = storage
|
|
754
|
+
.begin_read(StorageReadOptions::default())
|
|
755
|
+
.expect("read should open");
|
|
730
756
|
|
|
731
757
|
let state_rows = vec![tracked_global_row("change-1")];
|
|
732
|
-
commit_prepared_writes(
|
|
758
|
+
let writes = commit_prepared_writes(
|
|
733
759
|
&binary_cas,
|
|
734
|
-
&
|
|
735
|
-
&version_ctx,
|
|
760
|
+
&branch_ctx,
|
|
736
761
|
None,
|
|
737
|
-
|
|
762
|
+
&mut read,
|
|
738
763
|
PreparedWriteSet {
|
|
739
764
|
insert_identities: BTreeMap::new(),
|
|
740
765
|
state_rows,
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
members(["change-1"]),
|
|
766
|
+
commit_change_refs_by_branch: BTreeMap::from([(
|
|
767
|
+
GLOBAL_BRANCH_ID.to_string(),
|
|
768
|
+
change_refs(["change-1"]),
|
|
745
769
|
)]),
|
|
746
|
-
|
|
770
|
+
extra_commit_parents_by_branch: BTreeMap::new(),
|
|
747
771
|
file_data_writes: Vec::new(),
|
|
748
772
|
},
|
|
749
773
|
)
|
|
750
774
|
.await
|
|
751
775
|
.expect("commit should flush staged rows");
|
|
752
|
-
|
|
753
|
-
.
|
|
754
|
-
.
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
776
|
+
storage
|
|
777
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
778
|
+
.expect("writes should commit");
|
|
779
|
+
|
|
780
|
+
let mut changelog_reader = crate::changelog::ChangelogContext::new().reader(
|
|
781
|
+
storage
|
|
782
|
+
.begin_read(StorageReadOptions::default())
|
|
783
|
+
.expect("read should open"),
|
|
784
|
+
);
|
|
785
|
+
let commits = changelog_reader
|
|
786
|
+
.load_commits(crate::changelog::CommitLoadRequest {
|
|
787
|
+
commit_ids: &["test-uuid-1".to_string()],
|
|
788
|
+
projection: crate::changelog::CommitProjection::Full,
|
|
789
|
+
})
|
|
760
790
|
.await
|
|
761
|
-
.expect("
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
791
|
+
.expect("changelog commit should load");
|
|
792
|
+
let Some(crate::changelog::CommitLoadEntry::Full {
|
|
793
|
+
record,
|
|
794
|
+
change_ref_chunks,
|
|
795
|
+
}) = commits.entries.into_iter().next().flatten()
|
|
796
|
+
else {
|
|
797
|
+
panic!("changelog commit should exist");
|
|
798
|
+
};
|
|
799
|
+
assert_eq!(record.change_id, "test-uuid-2");
|
|
800
|
+
assert!(change_ref_chunks
|
|
801
|
+
.iter()
|
|
802
|
+
.flat_map(|chunk| chunk.entries.iter())
|
|
803
|
+
.any(|entry| entry.change_id == "change-1"));
|
|
804
|
+
let changes = changelog_reader
|
|
805
|
+
.load_changes(crate::changelog::ChangeLoadRequest {
|
|
806
|
+
change_ids: &["change-1".to_string(), record.change_id.clone()],
|
|
807
|
+
})
|
|
768
808
|
.await
|
|
769
|
-
.expect("
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
]
|
|
809
|
+
.expect("changelog change should load");
|
|
810
|
+
let mut loaded_changes = changes.entries.into_iter();
|
|
811
|
+
let Some(change) = loaded_changes.next().flatten() else {
|
|
812
|
+
panic!("changelog change should exist");
|
|
813
|
+
};
|
|
814
|
+
assert_eq!(change.change_id, "change-1");
|
|
815
|
+
assert_eq!(change.schema_key, "test_schema");
|
|
816
|
+
assert!(
|
|
817
|
+
loaded_changes.next().flatten().is_none(),
|
|
818
|
+
"commit row change is derived from changelog.commit, not stored as changelog.change"
|
|
819
|
+
);
|
|
820
|
+
|
|
821
|
+
let mut tracked_reader = crate::tracked_state::TrackedStateContext::new().reader(
|
|
822
|
+
storage
|
|
823
|
+
.begin_read(StorageReadOptions::default())
|
|
824
|
+
.expect("read should open"),
|
|
786
825
|
);
|
|
787
|
-
let
|
|
788
|
-
.
|
|
826
|
+
let commit_rows = tracked_reader
|
|
827
|
+
.scan_rows_at_commit(
|
|
828
|
+
"test-uuid-1",
|
|
829
|
+
&crate::tracked_state::TrackedStateScanRequest {
|
|
830
|
+
filter: crate::tracked_state::TrackedStateFilter {
|
|
831
|
+
schema_keys: vec!["lix_commit".to_string()],
|
|
832
|
+
..Default::default()
|
|
833
|
+
},
|
|
834
|
+
..Default::default()
|
|
835
|
+
},
|
|
836
|
+
)
|
|
789
837
|
.await
|
|
790
|
-
.expect("commit
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
838
|
+
.expect("commit root should scan");
|
|
839
|
+
assert!(
|
|
840
|
+
commit_rows
|
|
841
|
+
.iter()
|
|
842
|
+
.any(|row| row.change_id == record.change_id && row.snapshot_content.is_some()),
|
|
843
|
+
"commit root should surface the derived lix_commit row"
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
let loaded_head = branch_ctx
|
|
847
|
+
.ref_reader(
|
|
848
|
+
storage
|
|
849
|
+
.begin_read(StorageReadOptions::default())
|
|
850
|
+
.expect("read should open"),
|
|
851
|
+
)
|
|
852
|
+
.load_head_commit_id(GLOBAL_BRANCH_ID)
|
|
799
853
|
.await
|
|
800
|
-
.expect("
|
|
854
|
+
.expect("branch ref load should succeed");
|
|
801
855
|
assert_eq!(loaded_head.as_deref(), Some("test-uuid-1"));
|
|
802
856
|
}
|
|
803
857
|
|
|
858
|
+
#[tokio::test]
|
|
859
|
+
async fn stage_changelog_commits_orders_staged_parents_before_children() {
|
|
860
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
861
|
+
let mut writes = StorageWriteSet::new();
|
|
862
|
+
let mut read = storage
|
|
863
|
+
.begin_read(StorageReadOptions::default())
|
|
864
|
+
.expect("read should open");
|
|
865
|
+
let mut parent_row = tracked_global_row("parent-change");
|
|
866
|
+
parent_row.commit_id = Some("parent-commit".to_string());
|
|
867
|
+
let mut child_row = tracked_global_row("child-change");
|
|
868
|
+
child_row.commit_id = Some("child-commit".to_string());
|
|
869
|
+
|
|
870
|
+
let commits = vec![
|
|
871
|
+
FinalizedCommitRow {
|
|
872
|
+
commit_id: "child-commit".to_string(),
|
|
873
|
+
parent_commit_ids: vec!["parent-commit".to_string()],
|
|
874
|
+
created_at: "2026-01-01T00:00:01Z".to_string(),
|
|
875
|
+
change_id: "child-commit-change".to_string(),
|
|
876
|
+
selected_change_refs: Vec::new(),
|
|
877
|
+
},
|
|
878
|
+
FinalizedCommitRow {
|
|
879
|
+
commit_id: "parent-commit".to_string(),
|
|
880
|
+
parent_commit_ids: Vec::new(),
|
|
881
|
+
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
882
|
+
change_id: "parent-commit-change".to_string(),
|
|
883
|
+
selected_change_refs: Vec::new(),
|
|
884
|
+
},
|
|
885
|
+
];
|
|
886
|
+
stage_changelog_commits(
|
|
887
|
+
&mut read,
|
|
888
|
+
&mut writes,
|
|
889
|
+
&[parent_row, child_row],
|
|
890
|
+
&BTreeMap::from([
|
|
891
|
+
("parent-commit".to_string(), vec![0]),
|
|
892
|
+
("child-commit".to_string(), vec![1]),
|
|
893
|
+
]),
|
|
894
|
+
&commits,
|
|
895
|
+
)
|
|
896
|
+
.await
|
|
897
|
+
.expect("child-before-parent input should still stage parent first");
|
|
898
|
+
storage
|
|
899
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
900
|
+
.expect("writes should persist");
|
|
901
|
+
|
|
902
|
+
let mut changelog_reader = crate::changelog::ChangelogContext::new().reader(
|
|
903
|
+
storage
|
|
904
|
+
.begin_read(StorageReadOptions::default())
|
|
905
|
+
.expect("read should open"),
|
|
906
|
+
);
|
|
907
|
+
let commits = changelog_reader
|
|
908
|
+
.load_commits(crate::changelog::CommitLoadRequest {
|
|
909
|
+
commit_ids: &["parent-commit".to_string(), "child-commit".to_string()],
|
|
910
|
+
projection: crate::changelog::CommitProjection::Record,
|
|
911
|
+
})
|
|
912
|
+
.await
|
|
913
|
+
.expect("commits should load");
|
|
914
|
+
assert!(commits.entries.iter().all(Option::is_some));
|
|
915
|
+
}
|
|
916
|
+
|
|
804
917
|
#[tokio::test]
|
|
805
918
|
async fn commit_with_only_untracked_writes_does_not_create_lix_commit() {
|
|
806
|
-
let
|
|
807
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
919
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
808
920
|
let binary_cas = BinaryCasContext::new();
|
|
809
|
-
let
|
|
921
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
810
922
|
let untracked_state = UntrackedStateContext::new();
|
|
811
|
-
let mut
|
|
812
|
-
.
|
|
813
|
-
.
|
|
814
|
-
.expect("transaction should open");
|
|
923
|
+
let mut read = storage
|
|
924
|
+
.begin_read(StorageReadOptions::default())
|
|
925
|
+
.expect("read should open");
|
|
815
926
|
|
|
816
927
|
let state_rows = vec![untracked_global_row("change-untracked")];
|
|
817
|
-
commit_prepared_writes(
|
|
928
|
+
let writes = commit_prepared_writes(
|
|
818
929
|
&binary_cas,
|
|
819
|
-
&
|
|
820
|
-
&version_ctx,
|
|
930
|
+
&branch_ctx,
|
|
821
931
|
None,
|
|
822
|
-
|
|
932
|
+
&mut read,
|
|
823
933
|
PreparedWriteSet {
|
|
824
934
|
insert_identities: BTreeMap::new(),
|
|
825
935
|
state_rows,
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
extra_commit_parents_by_version: BTreeMap::new(),
|
|
936
|
+
commit_change_refs_by_branch: BTreeMap::new(),
|
|
937
|
+
extra_commit_parents_by_branch: BTreeMap::new(),
|
|
829
938
|
file_data_writes: Vec::new(),
|
|
830
939
|
},
|
|
831
940
|
)
|
|
832
941
|
.await
|
|
833
942
|
.expect("commit should flush untracked row");
|
|
834
|
-
|
|
835
|
-
.
|
|
836
|
-
.
|
|
837
|
-
.expect("commit should persist kv");
|
|
838
|
-
|
|
839
|
-
let commit_reader = crate::commit_store::CommitStoreContext::new().reader(storage.clone());
|
|
840
|
-
let index_entries = commit_reader
|
|
841
|
-
.load_change_index_entries(&["change-untracked".to_string()])
|
|
842
|
-
.await
|
|
843
|
-
.expect("commit-store change index should load");
|
|
844
|
-
assert_eq!(index_entries, vec![None]);
|
|
943
|
+
storage
|
|
944
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
945
|
+
.expect("writes should commit");
|
|
845
946
|
|
|
846
947
|
let loaded = {
|
|
847
|
-
let mut untracked_reader = untracked_state.reader(
|
|
948
|
+
let mut untracked_reader = untracked_state.reader(
|
|
949
|
+
storage
|
|
950
|
+
.begin_read(StorageReadOptions::default())
|
|
951
|
+
.expect("read should open"),
|
|
952
|
+
);
|
|
848
953
|
untracked_reader
|
|
849
954
|
.load_row(&UntrackedStateRowRequest {
|
|
850
955
|
schema_key: "test_schema".to_string(),
|
|
851
|
-
|
|
852
|
-
|
|
956
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
957
|
+
entity_pk: crate::entity_pk::EntityPk::single("entity-1"),
|
|
853
958
|
file_id: NullableKeyFilter::Null,
|
|
854
959
|
})
|
|
855
960
|
.await
|
|
@@ -864,17 +969,12 @@ mod tests {
|
|
|
864
969
|
|
|
865
970
|
#[tokio::test]
|
|
866
971
|
async fn tracked_write_deletes_matching_untracked_overlay() {
|
|
867
|
-
let
|
|
868
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
972
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
869
973
|
let binary_cas = BinaryCasContext::new();
|
|
870
974
|
let untracked_state = UntrackedStateContext::new();
|
|
871
975
|
let live_state = Arc::new(live_state_context());
|
|
872
|
-
let
|
|
976
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
873
977
|
|
|
874
|
-
let mut seed_transaction = storage
|
|
875
|
-
.begin_write_transaction()
|
|
876
|
-
.await
|
|
877
|
-
.expect("seed transaction should open");
|
|
878
978
|
let mut writes = StorageWriteSet::new();
|
|
879
979
|
let staged_row = untracked_global_row("change-untracked");
|
|
880
980
|
let canonical_row = crate::test_support::untracked_state_row_from_materialized(
|
|
@@ -886,54 +986,53 @@ mod tests {
|
|
|
886
986
|
.writer(&mut writes)
|
|
887
987
|
.stage_rows(std::iter::once(canonical_row.as_ref()))
|
|
888
988
|
.expect("untracked seed should write");
|
|
889
|
-
|
|
890
|
-
.
|
|
891
|
-
.
|
|
892
|
-
.expect("untracked seed should apply");
|
|
893
|
-
seed_transaction
|
|
894
|
-
.commit()
|
|
895
|
-
.await
|
|
896
|
-
.expect("seed transaction should persist");
|
|
989
|
+
storage
|
|
990
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
991
|
+
.expect("untracked seed should commit");
|
|
897
992
|
|
|
898
|
-
let mut
|
|
899
|
-
.
|
|
900
|
-
.
|
|
901
|
-
.expect("transaction should open");
|
|
993
|
+
let mut read = storage
|
|
994
|
+
.begin_read(StorageReadOptions::default())
|
|
995
|
+
.expect("read should open");
|
|
902
996
|
let state_rows = vec![tracked_global_row("change-tracked")];
|
|
903
|
-
commit_prepared_writes(
|
|
997
|
+
let writes = commit_prepared_writes(
|
|
904
998
|
&binary_cas,
|
|
905
|
-
&
|
|
906
|
-
&version_ctx,
|
|
999
|
+
&branch_ctx,
|
|
907
1000
|
None,
|
|
908
|
-
|
|
1001
|
+
&mut read,
|
|
909
1002
|
PreparedWriteSet {
|
|
910
1003
|
insert_identities: BTreeMap::new(),
|
|
911
1004
|
state_rows,
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
members(["change-tracked"]),
|
|
1005
|
+
commit_change_refs_by_branch: BTreeMap::from([(
|
|
1006
|
+
GLOBAL_BRANCH_ID.to_string(),
|
|
1007
|
+
change_refs(["change-tracked"]),
|
|
916
1008
|
)]),
|
|
917
|
-
|
|
1009
|
+
extra_commit_parents_by_branch: BTreeMap::new(),
|
|
918
1010
|
file_data_writes: Vec::new(),
|
|
919
1011
|
},
|
|
920
1012
|
)
|
|
921
1013
|
.await
|
|
922
1014
|
.expect("tracked commit should flush");
|
|
923
|
-
|
|
924
|
-
.
|
|
925
|
-
.
|
|
926
|
-
.expect("commit should persist kv");
|
|
1015
|
+
storage
|
|
1016
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1017
|
+
.expect("writes should commit");
|
|
927
1018
|
|
|
928
1019
|
let untracked = {
|
|
929
|
-
let mut untracked_reader = untracked_state.reader(
|
|
1020
|
+
let mut untracked_reader = untracked_state.reader(
|
|
1021
|
+
storage
|
|
1022
|
+
.begin_read(StorageReadOptions::default())
|
|
1023
|
+
.expect("read should open"),
|
|
1024
|
+
);
|
|
930
1025
|
untracked_reader.load_row(&untracked_request()).await
|
|
931
1026
|
}
|
|
932
1027
|
.expect("untracked load should succeed");
|
|
933
1028
|
assert_eq!(untracked, None);
|
|
934
1029
|
|
|
935
1030
|
let visible = live_state
|
|
936
|
-
.reader(
|
|
1031
|
+
.reader(
|
|
1032
|
+
storage
|
|
1033
|
+
.begin_read(StorageReadOptions::default())
|
|
1034
|
+
.expect("read should open"),
|
|
1035
|
+
)
|
|
937
1036
|
.load_row(&live_state_request())
|
|
938
1037
|
.await
|
|
939
1038
|
.expect("live-state load should succeed")
|
|
@@ -945,20 +1044,43 @@ mod tests {
|
|
|
945
1044
|
|
|
946
1045
|
#[tokio::test]
|
|
947
1046
|
async fn commit_staged_writes_applies_cross_subsystem_rows_as_one_backend_batch() {
|
|
948
|
-
let counting_backend =
|
|
1047
|
+
let counting_backend = CountingBackend::new();
|
|
949
1048
|
let write_batches = counting_backend.write_batches();
|
|
950
|
-
let
|
|
951
|
-
let storage = StorageContext::new(backend);
|
|
1049
|
+
let storage = StorageContext::new(counting_backend);
|
|
952
1050
|
let binary_cas = BinaryCasContext::new();
|
|
953
1051
|
let live_state = Arc::new(live_state_context());
|
|
954
1052
|
let untracked_state = UntrackedStateContext::new();
|
|
955
|
-
let
|
|
956
|
-
|
|
1053
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
1054
|
+
{
|
|
1055
|
+
let mut read = storage
|
|
1056
|
+
.begin_read(StorageReadOptions::default())
|
|
1057
|
+
.expect("seed read should open");
|
|
1058
|
+
let mut writes = storage.new_write_set();
|
|
1059
|
+
crate::test_support::stage_tracked_root_from_materialized(
|
|
1060
|
+
&mut read,
|
|
1061
|
+
&mut writes,
|
|
1062
|
+
&crate::tracked_state::TrackedStateContext::new(),
|
|
1063
|
+
crate::test_support::TEST_EMPTY_ROOT_COMMIT_ID,
|
|
1064
|
+
None,
|
|
1065
|
+
&[],
|
|
1066
|
+
)
|
|
1067
|
+
.await
|
|
1068
|
+
.expect("empty tracked root should stage");
|
|
1069
|
+
let branch_ref_row = crate::transaction::prepare_branch_ref_row(
|
|
1070
|
+
GLOBAL_BRANCH_ID,
|
|
1071
|
+
crate::test_support::TEST_EMPTY_ROOT_COMMIT_ID,
|
|
1072
|
+
"1970-01-01T00:00:00.000Z",
|
|
1073
|
+
)
|
|
1074
|
+
.expect("global branch ref should stage");
|
|
1075
|
+
UntrackedStateContext::new()
|
|
1076
|
+
.writer(&mut writes)
|
|
1077
|
+
.stage_rows([branch_ref_row.row.as_ref()])
|
|
1078
|
+
.expect("global branch ref should stage");
|
|
1079
|
+
storage
|
|
1080
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1081
|
+
.expect("global branch ref should commit");
|
|
1082
|
+
}
|
|
957
1083
|
{
|
|
958
|
-
let mut seed_transaction = storage
|
|
959
|
-
.begin_write_transaction()
|
|
960
|
-
.await
|
|
961
|
-
.expect("seed transaction should open");
|
|
962
1084
|
let mut writes = StorageWriteSet::new();
|
|
963
1085
|
let mode_snapshot = serde_json::to_string(&serde_json::json!({
|
|
964
1086
|
"key": DETERMINISTIC_MODE_KEY,
|
|
@@ -974,7 +1096,7 @@ mod tests {
|
|
|
974
1096
|
)
|
|
975
1097
|
.expect("deterministic mode snapshot should stage");
|
|
976
1098
|
let row = crate::untracked_state::UntrackedStateRow {
|
|
977
|
-
|
|
1099
|
+
entity_pk: crate::entity_pk::EntityPk::single(DETERMINISTIC_MODE_KEY),
|
|
978
1100
|
schema_key: "lix_key_value".to_string(),
|
|
979
1101
|
file_id: None,
|
|
980
1102
|
snapshot_content: Some(mode_snapshot.to_string()),
|
|
@@ -982,53 +1104,49 @@ mod tests {
|
|
|
982
1104
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
983
1105
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
984
1106
|
global: true,
|
|
985
|
-
|
|
1107
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
986
1108
|
};
|
|
987
1109
|
UntrackedStateContext::new()
|
|
988
1110
|
.writer(&mut writes)
|
|
989
1111
|
.stage_rows(std::iter::once(row.as_ref()))
|
|
990
1112
|
.expect("deterministic mode should stage");
|
|
991
|
-
|
|
992
|
-
.
|
|
993
|
-
.
|
|
994
|
-
.expect("deterministic mode should apply");
|
|
995
|
-
seed_transaction
|
|
996
|
-
.commit()
|
|
997
|
-
.await
|
|
998
|
-
.expect("seed transaction should persist");
|
|
1113
|
+
storage
|
|
1114
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1115
|
+
.expect("deterministic mode should commit");
|
|
999
1116
|
}
|
|
1000
1117
|
write_batches.store(0, Ordering::SeqCst);
|
|
1001
1118
|
let runtime_functions = {
|
|
1002
|
-
let reader = live_state.reader(
|
|
1119
|
+
let reader = live_state.reader(
|
|
1120
|
+
storage
|
|
1121
|
+
.begin_read(StorageReadOptions::default())
|
|
1122
|
+
.expect("read should open"),
|
|
1123
|
+
);
|
|
1003
1124
|
FunctionContext::prepare(&reader)
|
|
1004
1125
|
.await
|
|
1005
1126
|
.expect("runtime context should prepare")
|
|
1006
1127
|
};
|
|
1007
1128
|
runtime_functions.provider().call_uuid_v7();
|
|
1008
|
-
let mut
|
|
1009
|
-
.
|
|
1010
|
-
.
|
|
1011
|
-
.expect("transaction should open");
|
|
1129
|
+
let mut read = storage
|
|
1130
|
+
.begin_read(StorageReadOptions::default())
|
|
1131
|
+
.expect("read should open");
|
|
1012
1132
|
|
|
1013
1133
|
let tracked_row = tracked_global_row("change-tracked");
|
|
1014
1134
|
let mut untracked_row = untracked_global_row("change-untracked");
|
|
1015
|
-
untracked_row.
|
|
1135
|
+
untracked_row.entity_pk = crate::entity_pk::EntityPk::single("entity-2");
|
|
1016
1136
|
|
|
1017
|
-
commit_prepared_writes(
|
|
1137
|
+
let writes = commit_prepared_writes(
|
|
1018
1138
|
&binary_cas,
|
|
1019
|
-
&
|
|
1020
|
-
&version_ctx,
|
|
1139
|
+
&branch_ctx,
|
|
1021
1140
|
Some(&runtime_functions),
|
|
1022
|
-
|
|
1141
|
+
&mut read,
|
|
1023
1142
|
PreparedWriteSet {
|
|
1024
1143
|
insert_identities: BTreeMap::new(),
|
|
1025
1144
|
state_rows: vec![tracked_row, untracked_row],
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
members(["change-tracked"]),
|
|
1145
|
+
commit_change_refs_by_branch: BTreeMap::from([(
|
|
1146
|
+
GLOBAL_BRANCH_ID.to_string(),
|
|
1147
|
+
change_refs(["change-tracked"]),
|
|
1030
1148
|
)]),
|
|
1031
|
-
|
|
1149
|
+
extra_commit_parents_by_branch: BTreeMap::new(),
|
|
1032
1150
|
file_data_writes: Vec::new(),
|
|
1033
1151
|
},
|
|
1034
1152
|
)
|
|
@@ -1037,46 +1155,65 @@ mod tests {
|
|
|
1037
1155
|
|
|
1038
1156
|
assert_eq!(
|
|
1039
1157
|
write_batches.load(Ordering::SeqCst),
|
|
1040
|
-
|
|
1041
|
-
"
|
|
1158
|
+
0,
|
|
1159
|
+
"prepared writes should not touch the backend before the write set is committed"
|
|
1042
1160
|
);
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
.commit
|
|
1046
|
-
.await
|
|
1047
|
-
.expect("commit should persist kv");
|
|
1161
|
+
storage
|
|
1162
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1163
|
+
.expect("writes should commit");
|
|
1048
1164
|
assert_eq!(write_batches.load(Ordering::SeqCst), 1);
|
|
1049
1165
|
|
|
1050
|
-
let
|
|
1051
|
-
|
|
1052
|
-
|
|
1166
|
+
let mut changelog_reader = crate::changelog::ChangelogContext::new().reader(
|
|
1167
|
+
storage
|
|
1168
|
+
.begin_read(StorageReadOptions::default())
|
|
1169
|
+
.expect("read should open"),
|
|
1170
|
+
);
|
|
1171
|
+
let commits = changelog_reader
|
|
1172
|
+
.load_commits(crate::changelog::CommitLoadRequest {
|
|
1173
|
+
commit_ids: &["test-uuid-1".to_string()],
|
|
1174
|
+
projection: crate::changelog::CommitProjection::Record,
|
|
1175
|
+
})
|
|
1053
1176
|
.await
|
|
1054
|
-
.expect("
|
|
1055
|
-
|
|
1177
|
+
.expect("changelog commit should load");
|
|
1178
|
+
let Some(crate::changelog::CommitLoadEntry::Record(commit)) =
|
|
1179
|
+
commits.entries.into_iter().next().flatten()
|
|
1180
|
+
else {
|
|
1181
|
+
panic!("changelog commit should exist");
|
|
1182
|
+
};
|
|
1056
1183
|
assert_eq!(commit.change_id, "test-uuid-2");
|
|
1057
|
-
let
|
|
1058
|
-
.
|
|
1184
|
+
let changes = changelog_reader
|
|
1185
|
+
.load_changes(crate::changelog::ChangeLoadRequest {
|
|
1186
|
+
change_ids: &["change-tracked".to_string()],
|
|
1187
|
+
})
|
|
1059
1188
|
.await
|
|
1060
|
-
.expect("
|
|
1189
|
+
.expect("changelog change should load");
|
|
1061
1190
|
assert!(matches!(
|
|
1062
|
-
|
|
1063
|
-
[Some(
|
|
1191
|
+
changes.entries.as_slice(),
|
|
1192
|
+
[Some(change)] if change.change_id == "change-tracked"
|
|
1064
1193
|
));
|
|
1065
1194
|
|
|
1066
|
-
let loaded_head =
|
|
1067
|
-
.ref_reader(
|
|
1068
|
-
|
|
1195
|
+
let loaded_head = branch_ctx
|
|
1196
|
+
.ref_reader(
|
|
1197
|
+
storage
|
|
1198
|
+
.begin_read(StorageReadOptions::default())
|
|
1199
|
+
.expect("read should open"),
|
|
1200
|
+
)
|
|
1201
|
+
.load_head_commit_id(GLOBAL_BRANCH_ID)
|
|
1069
1202
|
.await
|
|
1070
|
-
.expect("
|
|
1203
|
+
.expect("branch ref load should succeed");
|
|
1071
1204
|
assert_eq!(loaded_head.as_deref(), Some("test-uuid-1"));
|
|
1072
1205
|
|
|
1073
1206
|
let untracked = {
|
|
1074
|
-
let mut untracked_reader = untracked_state.reader(
|
|
1207
|
+
let mut untracked_reader = untracked_state.reader(
|
|
1208
|
+
storage
|
|
1209
|
+
.begin_read(StorageReadOptions::default())
|
|
1210
|
+
.expect("read should open"),
|
|
1211
|
+
);
|
|
1075
1212
|
untracked_reader
|
|
1076
1213
|
.load_row(&UntrackedStateRowRequest {
|
|
1077
1214
|
schema_key: "test_schema".to_string(),
|
|
1078
|
-
|
|
1079
|
-
|
|
1215
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1216
|
+
entity_pk: crate::entity_pk::EntityPk::single("entity-2"),
|
|
1080
1217
|
file_id: NullableKeyFilter::Null,
|
|
1081
1218
|
})
|
|
1082
1219
|
.await
|
|
@@ -1089,13 +1226,15 @@ mod tests {
|
|
|
1089
1226
|
);
|
|
1090
1227
|
|
|
1091
1228
|
let sequence_row = live_state
|
|
1092
|
-
.reader(
|
|
1229
|
+
.reader(
|
|
1230
|
+
storage
|
|
1231
|
+
.begin_read(StorageReadOptions::default())
|
|
1232
|
+
.expect("read should open"),
|
|
1233
|
+
)
|
|
1093
1234
|
.load_row(&LiveStateRowRequest {
|
|
1094
1235
|
schema_key: "lix_key_value".to_string(),
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
DETERMINISTIC_SEQUENCE_KEY,
|
|
1098
|
-
),
|
|
1236
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1237
|
+
entity_pk: crate::entity_pk::EntityPk::single(DETERMINISTIC_SEQUENCE_KEY),
|
|
1099
1238
|
file_id: NullableKeyFilter::Null,
|
|
1100
1239
|
})
|
|
1101
1240
|
.await
|
|
@@ -1108,189 +1247,181 @@ mod tests {
|
|
|
1108
1247
|
}
|
|
1109
1248
|
|
|
1110
1249
|
#[tokio::test]
|
|
1111
|
-
async fn
|
|
1112
|
-
let
|
|
1113
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1250
|
+
async fn non_global_tracked_write_creates_one_commit_and_advances_only_touched_branch() {
|
|
1251
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1114
1252
|
let binary_cas = BinaryCasContext::new();
|
|
1115
|
-
let
|
|
1116
|
-
crate::test_support::
|
|
1117
|
-
.await;
|
|
1118
|
-
crate::test_support::seed_version_head(storage.clone(), "version-a", "version-a-before")
|
|
1253
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
1254
|
+
crate::test_support::seed_branch_head(storage.clone(), GLOBAL_BRANCH_ID, "global-before")
|
|
1119
1255
|
.await;
|
|
1256
|
+
crate::test_support::seed_branch_head(storage.clone(), "branch-a", "branch-a-before").await;
|
|
1120
1257
|
|
|
1121
|
-
let mut
|
|
1122
|
-
.
|
|
1123
|
-
.
|
|
1124
|
-
|
|
1125
|
-
let
|
|
1126
|
-
commit_prepared_writes(
|
|
1258
|
+
let mut read = storage
|
|
1259
|
+
.begin_read(StorageReadOptions::default())
|
|
1260
|
+
.expect("read should open");
|
|
1261
|
+
let state_rows = vec![tracked_branch_row("branch-a", "change-branch-a")];
|
|
1262
|
+
let writes = commit_prepared_writes(
|
|
1127
1263
|
&binary_cas,
|
|
1128
|
-
&
|
|
1129
|
-
&version_ctx,
|
|
1264
|
+
&branch_ctx,
|
|
1130
1265
|
None,
|
|
1131
|
-
|
|
1266
|
+
&mut read,
|
|
1132
1267
|
PreparedWriteSet {
|
|
1133
1268
|
insert_identities: BTreeMap::new(),
|
|
1134
1269
|
state_rows,
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
"
|
|
1138
|
-
members(["change-version-a"]),
|
|
1270
|
+
commit_change_refs_by_branch: BTreeMap::from([(
|
|
1271
|
+
"branch-a".to_string(),
|
|
1272
|
+
change_refs(["change-branch-a"]),
|
|
1139
1273
|
)]),
|
|
1140
|
-
|
|
1274
|
+
extra_commit_parents_by_branch: BTreeMap::new(),
|
|
1141
1275
|
file_data_writes: Vec::new(),
|
|
1142
1276
|
},
|
|
1143
1277
|
)
|
|
1144
1278
|
.await
|
|
1145
|
-
.expect("
|
|
1146
|
-
|
|
1147
|
-
.
|
|
1148
|
-
.
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1279
|
+
.expect("branch commit should flush");
|
|
1280
|
+
storage
|
|
1281
|
+
.commit_write_set(writes, StorageWriteOptions::default())
|
|
1282
|
+
.expect("writes should commit");
|
|
1283
|
+
|
|
1284
|
+
let mut changelog_reader = crate::changelog::ChangelogContext::new().reader(
|
|
1285
|
+
storage
|
|
1286
|
+
.begin_read(StorageReadOptions::default())
|
|
1287
|
+
.expect("read should open"),
|
|
1288
|
+
);
|
|
1289
|
+
let commits = changelog_reader
|
|
1290
|
+
.load_commits(crate::changelog::CommitLoadRequest {
|
|
1291
|
+
commit_ids: &["test-uuid-1".to_string()],
|
|
1292
|
+
projection: crate::changelog::CommitProjection::Record,
|
|
1293
|
+
})
|
|
1154
1294
|
.await
|
|
1155
|
-
.expect("
|
|
1156
|
-
|
|
1295
|
+
.expect("changelog commit should load");
|
|
1296
|
+
let Some(crate::changelog::CommitLoadEntry::Record(commit)) =
|
|
1297
|
+
commits.entries.into_iter().next().flatten()
|
|
1298
|
+
else {
|
|
1299
|
+
panic!("changelog commit should exist");
|
|
1300
|
+
};
|
|
1157
1301
|
assert_eq!(commit.change_id, "test-uuid-2");
|
|
1158
|
-
assert_eq!(commit.
|
|
1159
|
-
let index_entries = commit_reader
|
|
1160
|
-
.load_change_index_entries(&["change-version-a".to_string()])
|
|
1161
|
-
.await
|
|
1162
|
-
.expect("commit-store change index should load");
|
|
1163
|
-
assert!(matches!(
|
|
1164
|
-
index_entries.as_slice(),
|
|
1165
|
-
[Some(ChangeIndexEntry::PackedChange { .. })]
|
|
1166
|
-
));
|
|
1302
|
+
assert_eq!(commit.parent_commit_ids, vec!["branch-a-before"]);
|
|
1167
1303
|
|
|
1168
|
-
let global_head =
|
|
1169
|
-
.ref_reader(
|
|
1170
|
-
|
|
1304
|
+
let global_head = branch_ctx
|
|
1305
|
+
.ref_reader(
|
|
1306
|
+
storage
|
|
1307
|
+
.begin_read(StorageReadOptions::default())
|
|
1308
|
+
.expect("read should open"),
|
|
1309
|
+
)
|
|
1310
|
+
.load_head_commit_id(GLOBAL_BRANCH_ID)
|
|
1171
1311
|
.await
|
|
1172
1312
|
.expect("global head should load");
|
|
1173
|
-
let
|
|
1174
|
-
.ref_reader(
|
|
1175
|
-
|
|
1313
|
+
let branch_head = branch_ctx
|
|
1314
|
+
.ref_reader(
|
|
1315
|
+
storage
|
|
1316
|
+
.begin_read(StorageReadOptions::default())
|
|
1317
|
+
.expect("read should open"),
|
|
1318
|
+
)
|
|
1319
|
+
.load_head_commit_id("branch-a")
|
|
1176
1320
|
.await
|
|
1177
|
-
.expect("
|
|
1321
|
+
.expect("branch head should load");
|
|
1178
1322
|
assert_eq!(global_head.as_deref(), Some("global-before"));
|
|
1179
|
-
assert_eq!(
|
|
1323
|
+
assert_eq!(branch_head.as_deref(), Some("test-uuid-1"));
|
|
1180
1324
|
}
|
|
1181
1325
|
|
|
1182
1326
|
#[tokio::test]
|
|
1183
|
-
async fn
|
|
1184
|
-
let
|
|
1185
|
-
let
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
storage.clone(),
|
|
1189
|
-
GLOBAL_VERSION_ID,
|
|
1190
|
-
"initial-commit",
|
|
1191
|
-
)
|
|
1192
|
-
.await;
|
|
1327
|
+
async fn finalize_commit_rows_parents_global_commit_to_existing_branch_ref() {
|
|
1328
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1329
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
1330
|
+
crate::test_support::seed_branch_head(storage.clone(), GLOBAL_BRANCH_ID, "initial-commit")
|
|
1331
|
+
.await;
|
|
1193
1332
|
|
|
1194
|
-
let mut
|
|
1195
|
-
.
|
|
1196
|
-
.
|
|
1197
|
-
.expect("transaction should open");
|
|
1333
|
+
let mut read = storage
|
|
1334
|
+
.begin_read(StorageReadOptions::default())
|
|
1335
|
+
.expect("read should open");
|
|
1198
1336
|
let rows = finalize_commit_rows(
|
|
1199
1337
|
BTreeMap::from([(
|
|
1200
|
-
|
|
1201
|
-
|
|
1338
|
+
GLOBAL_BRANCH_ID.to_string(),
|
|
1339
|
+
change_refs(["change-a", "change-b"]),
|
|
1202
1340
|
)]),
|
|
1203
1341
|
BTreeMap::new(),
|
|
1204
|
-
&
|
|
1205
|
-
|
|
1342
|
+
&branch_ctx,
|
|
1343
|
+
&mut read,
|
|
1206
1344
|
)
|
|
1207
1345
|
.await
|
|
1208
1346
|
.expect("global commit row should finalize");
|
|
1209
1347
|
|
|
1210
1348
|
assert_eq!(rows.commit_rows.len(), 1);
|
|
1211
|
-
assert_eq!(rows.
|
|
1349
|
+
assert_eq!(rows.branch_heads.len(), 1);
|
|
1212
1350
|
let row = &rows.commit_rows[0];
|
|
1213
1351
|
assert_eq!(row.commit_id, "test-uuid-1");
|
|
1214
1352
|
assert_eq!(row.change_id, "test-uuid-2");
|
|
1215
1353
|
assert_eq!(row.created_at, "test-timestamp-1");
|
|
1216
1354
|
assert_eq!(row.parent_commit_ids, vec!["initial-commit"]);
|
|
1217
1355
|
|
|
1218
|
-
let
|
|
1219
|
-
assert_eq!(
|
|
1220
|
-
assert_eq!(
|
|
1356
|
+
let branch_head = &rows.branch_heads[0];
|
|
1357
|
+
assert_eq!(branch_head.branch_id, GLOBAL_BRANCH_ID);
|
|
1358
|
+
assert_eq!(branch_head.commit_id, "test-uuid-1");
|
|
1221
1359
|
}
|
|
1222
1360
|
|
|
1223
1361
|
#[tokio::test]
|
|
1224
1362
|
async fn finalize_commit_rows_skips_empty_members() {
|
|
1225
|
-
let
|
|
1226
|
-
let
|
|
1227
|
-
let
|
|
1228
|
-
|
|
1229
|
-
.
|
|
1230
|
-
.await
|
|
1231
|
-
.expect("transaction should open");
|
|
1363
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1364
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
1365
|
+
let mut read = storage
|
|
1366
|
+
.begin_read(StorageReadOptions::default())
|
|
1367
|
+
.expect("read should open");
|
|
1232
1368
|
let rows = finalize_commit_rows(
|
|
1233
1369
|
BTreeMap::from([(
|
|
1234
|
-
|
|
1235
|
-
|
|
1370
|
+
GLOBAL_BRANCH_ID.to_string(),
|
|
1371
|
+
StagedCommitChangeRefs::default(),
|
|
1236
1372
|
)]),
|
|
1237
1373
|
BTreeMap::new(),
|
|
1238
|
-
&
|
|
1239
|
-
|
|
1374
|
+
&branch_ctx,
|
|
1375
|
+
&mut read,
|
|
1240
1376
|
)
|
|
1241
1377
|
.await
|
|
1242
|
-
.expect("empty
|
|
1378
|
+
.expect("empty change_refs should be ignored");
|
|
1243
1379
|
|
|
1244
1380
|
assert!(rows.commit_rows.is_empty());
|
|
1245
|
-
assert!(rows.
|
|
1381
|
+
assert!(rows.branch_heads.is_empty());
|
|
1246
1382
|
}
|
|
1247
1383
|
|
|
1248
1384
|
#[tokio::test]
|
|
1249
|
-
async fn
|
|
1250
|
-
let
|
|
1251
|
-
let
|
|
1252
|
-
|
|
1253
|
-
crate::test_support::seed_version_head(storage.clone(), GLOBAL_VERSION_ID, "global-before")
|
|
1254
|
-
.await;
|
|
1255
|
-
crate::test_support::seed_version_head(storage.clone(), "version-a", "previous-commit")
|
|
1385
|
+
async fn finalize_commit_rows_uses_existing_branch_ref_as_parent() {
|
|
1386
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1387
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
1388
|
+
crate::test_support::seed_branch_head(storage.clone(), GLOBAL_BRANCH_ID, "global-before")
|
|
1256
1389
|
.await;
|
|
1390
|
+
crate::test_support::seed_branch_head(storage.clone(), "branch-a", "previous-commit").await;
|
|
1257
1391
|
|
|
1258
|
-
let mut
|
|
1259
|
-
.
|
|
1260
|
-
.
|
|
1261
|
-
.expect("transaction should open");
|
|
1392
|
+
let mut read = storage
|
|
1393
|
+
.begin_read(StorageReadOptions::default())
|
|
1394
|
+
.expect("read should open");
|
|
1262
1395
|
let rows = finalize_commit_rows(
|
|
1263
|
-
BTreeMap::from([("
|
|
1396
|
+
BTreeMap::from([("branch-a".to_string(), change_refs(["change-a"]))]),
|
|
1264
1397
|
BTreeMap::new(),
|
|
1265
|
-
&
|
|
1266
|
-
|
|
1398
|
+
&branch_ctx,
|
|
1399
|
+
&mut read,
|
|
1267
1400
|
)
|
|
1268
1401
|
.await
|
|
1269
|
-
.expect("active-
|
|
1402
|
+
.expect("active-branch commit finalization should resolve parent");
|
|
1270
1403
|
|
|
1271
1404
|
assert_eq!(
|
|
1272
1405
|
rows.commit_rows[0].parent_commit_ids,
|
|
1273
1406
|
vec!["previous-commit"]
|
|
1274
1407
|
);
|
|
1275
|
-
assert_eq!(rows.
|
|
1408
|
+
assert_eq!(rows.branch_heads[0].branch_id, "branch-a");
|
|
1276
1409
|
}
|
|
1277
1410
|
|
|
1278
1411
|
#[tokio::test]
|
|
1279
1412
|
async fn finalize_commit_rows_appends_extra_merge_parent_after_target_head() {
|
|
1280
|
-
let
|
|
1281
|
-
let
|
|
1282
|
-
|
|
1283
|
-
crate::test_support::seed_version_head(storage.clone(), "version-a", "target-head").await;
|
|
1413
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
1414
|
+
let branch_ctx = BranchContext::new(Arc::new(UntrackedStateContext::new()));
|
|
1415
|
+
crate::test_support::seed_branch_head(storage.clone(), "branch-a", "target-head").await;
|
|
1284
1416
|
|
|
1285
|
-
let mut
|
|
1286
|
-
.
|
|
1287
|
-
.
|
|
1288
|
-
.expect("transaction should open");
|
|
1417
|
+
let mut read = storage
|
|
1418
|
+
.begin_read(StorageReadOptions::default())
|
|
1419
|
+
.expect("read should open");
|
|
1289
1420
|
let rows = finalize_commit_rows(
|
|
1290
|
-
BTreeMap::from([("
|
|
1291
|
-
BTreeMap::from([("
|
|
1292
|
-
&
|
|
1293
|
-
|
|
1421
|
+
BTreeMap::from([("branch-a".to_string(), change_refs(["change-a"]))]),
|
|
1422
|
+
BTreeMap::from([("branch-a".to_string(), vec!["source-head".to_string()])]),
|
|
1423
|
+
&branch_ctx,
|
|
1424
|
+
&mut read,
|
|
1294
1425
|
)
|
|
1295
1426
|
.await
|
|
1296
1427
|
.expect("merge commit finalization should resolve parents");
|
|
@@ -1301,27 +1432,27 @@ mod tests {
|
|
|
1301
1432
|
);
|
|
1302
1433
|
}
|
|
1303
1434
|
|
|
1304
|
-
fn
|
|
1305
|
-
let mut
|
|
1435
|
+
fn change_refs<const N: usize>(change_ids: [&str; N]) -> StagedCommitChangeRefs {
|
|
1436
|
+
let mut change_refs = StagedCommitChangeRefs::new(
|
|
1306
1437
|
"test-uuid-1".to_string(),
|
|
1307
1438
|
"test-uuid-2".to_string(),
|
|
1308
1439
|
"test-timestamp-1".to_string(),
|
|
1309
1440
|
);
|
|
1310
1441
|
for change_id in change_ids {
|
|
1311
|
-
|
|
1442
|
+
change_refs.add_change_id(change_id.to_string());
|
|
1312
1443
|
}
|
|
1313
|
-
|
|
1444
|
+
change_refs
|
|
1314
1445
|
}
|
|
1315
1446
|
|
|
1316
1447
|
fn tracked_global_row(change_id: &str) -> PreparedStateRow {
|
|
1317
|
-
|
|
1448
|
+
tracked_branch_row(GLOBAL_BRANCH_ID, change_id)
|
|
1318
1449
|
}
|
|
1319
1450
|
|
|
1320
|
-
fn
|
|
1451
|
+
fn tracked_branch_row(branch_id: &str, change_id: &str) -> PreparedStateRow {
|
|
1321
1452
|
PreparedStateRow {
|
|
1322
1453
|
schema_plan_id: SchemaPlanId::for_test(0),
|
|
1323
1454
|
facts: PreparedRowFacts::default(),
|
|
1324
|
-
|
|
1455
|
+
entity_pk: crate::entity_pk::EntityPk::single("entity-1"),
|
|
1325
1456
|
schema_key: "test_schema".to_string(),
|
|
1326
1457
|
file_id: None,
|
|
1327
1458
|
snapshot: Some(
|
|
@@ -1337,11 +1468,11 @@ mod tests {
|
|
|
1337
1468
|
origin: None,
|
|
1338
1469
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1339
1470
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1340
|
-
global:
|
|
1471
|
+
global: branch_id == GLOBAL_BRANCH_ID,
|
|
1341
1472
|
change_id: Some(change_id.to_string()),
|
|
1342
1473
|
commit_id: Some("test-uuid-1".to_string()),
|
|
1343
1474
|
untracked: false,
|
|
1344
|
-
|
|
1475
|
+
branch_id: branch_id.to_string(),
|
|
1345
1476
|
}
|
|
1346
1477
|
}
|
|
1347
1478
|
|
|
@@ -1367,8 +1498,8 @@ mod tests {
|
|
|
1367
1498
|
fn untracked_request() -> UntrackedStateRowRequest {
|
|
1368
1499
|
UntrackedStateRowRequest {
|
|
1369
1500
|
schema_key: "test_schema".to_string(),
|
|
1370
|
-
|
|
1371
|
-
|
|
1501
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1502
|
+
entity_pk: crate::entity_pk::EntityPk::single("entity-1"),
|
|
1372
1503
|
file_id: NullableKeyFilter::Null,
|
|
1373
1504
|
}
|
|
1374
1505
|
}
|
|
@@ -1376,21 +1507,21 @@ mod tests {
|
|
|
1376
1507
|
fn live_state_request() -> LiveStateRowRequest {
|
|
1377
1508
|
LiveStateRowRequest {
|
|
1378
1509
|
schema_key: "test_schema".to_string(),
|
|
1379
|
-
|
|
1380
|
-
|
|
1510
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1511
|
+
entity_pk: crate::entity_pk::EntityPk::single("entity-1"),
|
|
1381
1512
|
file_id: NullableKeyFilter::Null,
|
|
1382
1513
|
}
|
|
1383
1514
|
}
|
|
1384
1515
|
|
|
1385
1516
|
struct CountingBackend {
|
|
1386
|
-
inner:
|
|
1517
|
+
inner: InMemoryStorageBackend,
|
|
1387
1518
|
write_batches: Arc<AtomicUsize>,
|
|
1388
1519
|
}
|
|
1389
1520
|
|
|
1390
1521
|
impl CountingBackend {
|
|
1391
1522
|
fn new() -> Self {
|
|
1392
1523
|
Self {
|
|
1393
|
-
inner:
|
|
1524
|
+
inner: InMemoryStorageBackend::new(),
|
|
1394
1525
|
write_batches: Arc::new(AtomicUsize::new(0)),
|
|
1395
1526
|
}
|
|
1396
1527
|
}
|
|
@@ -1400,85 +1531,62 @@ mod tests {
|
|
|
1400
1531
|
}
|
|
1401
1532
|
}
|
|
1402
1533
|
|
|
1403
|
-
#[async_trait]
|
|
1404
1534
|
impl Backend for CountingBackend {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1535
|
+
type Read<'a>
|
|
1536
|
+
= InMemoryStorageRead
|
|
1537
|
+
where
|
|
1538
|
+
Self: 'a;
|
|
1539
|
+
|
|
1540
|
+
type Write<'a>
|
|
1541
|
+
= CountingWrite
|
|
1542
|
+
where
|
|
1543
|
+
Self: 'a;
|
|
1544
|
+
|
|
1545
|
+
fn capabilities(&self) -> BackendCapabilities {
|
|
1546
|
+
self.inner.capabilities()
|
|
1409
1547
|
}
|
|
1410
1548
|
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
) -> Result<Box<dyn BackendWriteTransaction + Send + Sync + 'static>, LixError> {
|
|
1414
|
-
Ok(Box::new(CountingWriteTransaction {
|
|
1415
|
-
inner: self.inner.begin_write_transaction().await?,
|
|
1416
|
-
write_batches: Arc::clone(&self.write_batches),
|
|
1417
|
-
}))
|
|
1549
|
+
fn begin_read(&self, opts: StorageReadOptions) -> Result<Self::Read<'_>, BackendError> {
|
|
1550
|
+
self.inner.begin_read(opts)
|
|
1418
1551
|
}
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
struct CountingWriteTransaction {
|
|
1422
|
-
inner: Box<dyn BackendWriteTransaction + Send + Sync + 'static>,
|
|
1423
|
-
write_batches: Arc<AtomicUsize>,
|
|
1424
|
-
}
|
|
1425
1552
|
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
) -> Result<BackendKvValueBatch, LixError> {
|
|
1432
|
-
self.inner.get_values(request).await
|
|
1553
|
+
fn begin_write(&self, opts: StorageWriteOptions) -> Result<Self::Write<'_>, BackendError> {
|
|
1554
|
+
Ok(CountingWrite {
|
|
1555
|
+
inner: self.inner.begin_write(opts)?,
|
|
1556
|
+
write_batches: Arc::clone(&self.write_batches),
|
|
1557
|
+
})
|
|
1433
1558
|
}
|
|
1434
1559
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
request: BackendKvGetRequest,
|
|
1438
|
-
) -> Result<BackendKvExistsBatch, LixError> {
|
|
1439
|
-
self.inner.exists_many(request).await
|
|
1560
|
+
fn durable_write_lock(&self) -> DurableWriteLock {
|
|
1561
|
+
self.inner.durable_write_lock()
|
|
1440
1562
|
}
|
|
1563
|
+
}
|
|
1441
1564
|
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
self.inner.scan_keys(request).await
|
|
1447
|
-
}
|
|
1565
|
+
struct CountingWrite {
|
|
1566
|
+
inner: InMemoryStorageWrite,
|
|
1567
|
+
write_batches: Arc<AtomicUsize>,
|
|
1568
|
+
}
|
|
1448
1569
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
) -> Result<BackendKvValuePage, LixError> {
|
|
1453
|
-
self.inner.scan_values(request).await
|
|
1570
|
+
impl BackendWrite for CountingWrite {
|
|
1571
|
+
fn put_many(&mut self, entries: PutBatch) -> Result<(), BackendError> {
|
|
1572
|
+
self.inner.put_many(entries)
|
|
1454
1573
|
}
|
|
1455
1574
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
request: BackendKvScanRequest,
|
|
1459
|
-
) -> Result<BackendKvEntryPage, LixError> {
|
|
1460
|
-
self.inner.scan_entries(request).await
|
|
1575
|
+
fn delete_many(&mut self, keys: &[StorageKey]) -> Result<(), BackendError> {
|
|
1576
|
+
self.inner.delete_many(keys)
|
|
1461
1577
|
}
|
|
1462
1578
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
inner.rollback().await
|
|
1579
|
+
fn delete_range(&mut self, range: KeyRange) -> Result<(), BackendError> {
|
|
1580
|
+
self.inner.delete_range(range)
|
|
1466
1581
|
}
|
|
1467
|
-
}
|
|
1468
1582
|
|
|
1469
|
-
|
|
1470
|
-
impl BackendWriteTransaction for CountingWriteTransaction {
|
|
1471
|
-
async fn write_kv_batch(
|
|
1472
|
-
&mut self,
|
|
1473
|
-
batch: BackendKvWriteBatch,
|
|
1474
|
-
) -> Result<BackendKvWriteStats, LixError> {
|
|
1583
|
+
fn commit(self) -> Result<CommitResult, BackendError> {
|
|
1475
1584
|
self.write_batches.fetch_add(1, Ordering::SeqCst);
|
|
1476
|
-
self.inner.
|
|
1585
|
+
self.inner.commit()
|
|
1477
1586
|
}
|
|
1478
1587
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
inner.commit().await
|
|
1588
|
+
fn rollback(self) -> Result<(), BackendError> {
|
|
1589
|
+
self.inner.rollback()
|
|
1482
1590
|
}
|
|
1483
1591
|
}
|
|
1484
1592
|
}
|