@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,944 +0,0 @@
|
|
|
1
|
-
use crate::commit_store::{
|
|
2
|
-
Change, ChangeIndexEntry, ChangeLocator, ChangeRef, ChangeScanRequest, Commit, CommitDraftRef,
|
|
3
|
-
LocatedChange, StagedCommitStoreCommit,
|
|
4
|
-
};
|
|
5
|
-
use crate::storage::{StorageReader, StorageWriteSet};
|
|
6
|
-
use crate::LixError;
|
|
7
|
-
use std::collections::{BTreeMap, BTreeSet};
|
|
8
|
-
use tokio::sync::Mutex;
|
|
9
|
-
|
|
10
|
-
/// Canonical physical storage boundary for commits and their changes.
|
|
11
|
-
#[derive(Clone, Copy, Debug, Default)]
|
|
12
|
-
pub(crate) struct CommitStoreContext;
|
|
13
|
-
|
|
14
|
-
impl CommitStoreContext {
|
|
15
|
-
pub(crate) fn new() -> Self {
|
|
16
|
-
Self
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/// Creates a commit-store writer over read visibility and a pending write set.
|
|
20
|
-
pub(crate) fn writer<'a, S>(
|
|
21
|
-
&self,
|
|
22
|
-
store: &'a mut S,
|
|
23
|
-
writes: &'a mut StorageWriteSet,
|
|
24
|
-
) -> CommitStoreWriter<'a, S>
|
|
25
|
-
where
|
|
26
|
-
S: StorageReader + ?Sized,
|
|
27
|
-
{
|
|
28
|
-
CommitStoreWriter { store, writes }
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/// Creates a commit-store reader over a storage snapshot or transaction.
|
|
32
|
-
pub(crate) fn reader<S>(&self, store: S) -> CommitStoreReader<S>
|
|
33
|
-
where
|
|
34
|
-
S: StorageReader,
|
|
35
|
-
{
|
|
36
|
-
CommitStoreReader {
|
|
37
|
-
store: Mutex::new(store),
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
pub(crate) async fn load_commit_from(
|
|
42
|
-
&self,
|
|
43
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
44
|
-
commit_id: &str,
|
|
45
|
-
) -> Result<Option<Commit>, LixError> {
|
|
46
|
-
crate::commit_store::storage::load_commit(store, commit_id).await
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
pub(crate) async fn load_change_pack_from(
|
|
50
|
-
&self,
|
|
51
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
52
|
-
commit_id: &str,
|
|
53
|
-
pack_id: u32,
|
|
54
|
-
) -> Result<Option<Vec<Change>>, LixError> {
|
|
55
|
-
crate::commit_store::storage::load_change_pack(store, commit_id, pack_id).await
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
pub(crate) async fn load_membership_pack_from(
|
|
59
|
-
&self,
|
|
60
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
61
|
-
commit_id: &str,
|
|
62
|
-
pack_id: u32,
|
|
63
|
-
) -> Result<Option<Vec<ChangeLocator>>, LixError> {
|
|
64
|
-
crate::commit_store::storage::load_membership_pack(store, commit_id, pack_id).await
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/// Commit-store reader over a storage snapshot or transaction.
|
|
69
|
-
pub(crate) struct CommitStoreReader<S> {
|
|
70
|
-
store: Mutex<S>,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
impl<S> CommitStoreReader<S>
|
|
74
|
-
where
|
|
75
|
-
S: StorageReader,
|
|
76
|
-
{
|
|
77
|
-
pub(crate) async fn load_change_index_entries(
|
|
78
|
-
&self,
|
|
79
|
-
change_ids: &[String],
|
|
80
|
-
) -> Result<Vec<Option<crate::commit_store::ChangeIndexEntry>>, LixError> {
|
|
81
|
-
crate::commit_store::storage::load_change_index_entries(
|
|
82
|
-
&mut *self.store.lock().await,
|
|
83
|
-
change_ids,
|
|
84
|
-
)
|
|
85
|
-
.await
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
pub(crate) async fn load_commit(
|
|
89
|
-
&self,
|
|
90
|
-
commit_id: &str,
|
|
91
|
-
) -> Result<Option<crate::commit_store::Commit>, LixError> {
|
|
92
|
-
crate::commit_store::storage::load_commit(&mut *self.store.lock().await, commit_id).await
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
pub(crate) async fn scan_commits(&self) -> Result<Vec<crate::commit_store::Commit>, LixError> {
|
|
96
|
-
crate::commit_store::storage::scan_commits(&mut *self.store.lock().await).await
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
pub(crate) async fn load_change_pack(
|
|
100
|
-
&self,
|
|
101
|
-
commit_id: &str,
|
|
102
|
-
pack_id: u32,
|
|
103
|
-
) -> Result<Option<Vec<crate::commit_store::Change>>, LixError> {
|
|
104
|
-
crate::commit_store::storage::load_change_pack(
|
|
105
|
-
&mut *self.store.lock().await,
|
|
106
|
-
commit_id,
|
|
107
|
-
pack_id,
|
|
108
|
-
)
|
|
109
|
-
.await
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
pub(crate) async fn load_membership_pack(
|
|
113
|
-
&self,
|
|
114
|
-
commit_id: &str,
|
|
115
|
-
pack_id: u32,
|
|
116
|
-
) -> Result<Option<Vec<crate::commit_store::ChangeLocator>>, LixError> {
|
|
117
|
-
crate::commit_store::storage::load_membership_pack(
|
|
118
|
-
&mut *self.store.lock().await,
|
|
119
|
-
commit_id,
|
|
120
|
-
pack_id,
|
|
121
|
-
)
|
|
122
|
-
.await
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
pub(crate) async fn load_changes(
|
|
126
|
-
&self,
|
|
127
|
-
change_ids: &[String],
|
|
128
|
-
) -> Result<Vec<Option<crate::commit_store::Change>>, LixError> {
|
|
129
|
-
if change_ids.is_empty() {
|
|
130
|
-
return Ok(Vec::new());
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
let mut store = self.store.lock().await;
|
|
134
|
-
let entries =
|
|
135
|
-
crate::commit_store::storage::load_change_index_entries(&mut *store, change_ids)
|
|
136
|
-
.await?;
|
|
137
|
-
let mut changes = Vec::with_capacity(entries.len());
|
|
138
|
-
let mut commits_by_id = BTreeMap::new();
|
|
139
|
-
let mut packs_by_locator = BTreeMap::new();
|
|
140
|
-
for (change_id, entry) in change_ids.iter().zip(entries) {
|
|
141
|
-
changes.push(match entry {
|
|
142
|
-
Some(ChangeIndexEntry::CommitHeader { commit_id, .. }) => {
|
|
143
|
-
if !commits_by_id.contains_key(&commit_id) {
|
|
144
|
-
let commit =
|
|
145
|
-
crate::commit_store::storage::load_commit(&mut *store, &commit_id)
|
|
146
|
-
.await?;
|
|
147
|
-
commits_by_id.insert(commit_id.clone(), commit);
|
|
148
|
-
}
|
|
149
|
-
commits_by_id
|
|
150
|
-
.get(&commit_id)
|
|
151
|
-
.cloned()
|
|
152
|
-
.flatten()
|
|
153
|
-
.map(commit_header_change)
|
|
154
|
-
}
|
|
155
|
-
Some(ChangeIndexEntry::PackedChange { locator }) => Some(
|
|
156
|
-
load_change_by_locator_cached(
|
|
157
|
-
&mut *store,
|
|
158
|
-
&mut packs_by_locator,
|
|
159
|
-
&locator,
|
|
160
|
-
change_id,
|
|
161
|
-
)
|
|
162
|
-
.await?,
|
|
163
|
-
),
|
|
164
|
-
None => None,
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
Ok(changes)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
pub(crate) async fn load_located_changes(
|
|
171
|
-
&self,
|
|
172
|
-
change_ids: &[String],
|
|
173
|
-
) -> Result<Vec<Option<LocatedChange>>, LixError> {
|
|
174
|
-
if change_ids.is_empty() {
|
|
175
|
-
return Ok(Vec::new());
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
let mut store = self.store.lock().await;
|
|
179
|
-
let entries =
|
|
180
|
-
crate::commit_store::storage::load_change_index_entries(&mut *store, change_ids)
|
|
181
|
-
.await?;
|
|
182
|
-
let mut changes = Vec::with_capacity(entries.len());
|
|
183
|
-
let mut commits_by_id = BTreeMap::new();
|
|
184
|
-
let mut packs_by_locator = BTreeMap::new();
|
|
185
|
-
for (change_id, entry) in change_ids.iter().zip(entries) {
|
|
186
|
-
changes.push(match entry {
|
|
187
|
-
Some(ChangeIndexEntry::CommitHeader { commit_id, .. }) => {
|
|
188
|
-
if !commits_by_id.contains_key(&commit_id) {
|
|
189
|
-
let commit =
|
|
190
|
-
crate::commit_store::storage::load_commit(&mut *store, &commit_id)
|
|
191
|
-
.await?;
|
|
192
|
-
commits_by_id.insert(commit_id.clone(), commit);
|
|
193
|
-
}
|
|
194
|
-
commits_by_id
|
|
195
|
-
.get(&commit_id)
|
|
196
|
-
.cloned()
|
|
197
|
-
.flatten()
|
|
198
|
-
.map(|commit| located_commit_header_change(commit, 0))
|
|
199
|
-
}
|
|
200
|
-
Some(ChangeIndexEntry::PackedChange { locator }) => Some(LocatedChange {
|
|
201
|
-
record: load_change_by_locator_cached(
|
|
202
|
-
&mut *store,
|
|
203
|
-
&mut packs_by_locator,
|
|
204
|
-
&locator,
|
|
205
|
-
change_id,
|
|
206
|
-
)
|
|
207
|
-
.await?,
|
|
208
|
-
source_commit_id: locator.source_commit_id,
|
|
209
|
-
source_pack_id: locator.source_pack_id,
|
|
210
|
-
}),
|
|
211
|
-
None => None,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
Ok(changes)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
pub(crate) async fn load_commit_changes(
|
|
218
|
-
&self,
|
|
219
|
-
commit_id: &str,
|
|
220
|
-
) -> Result<Vec<crate::commit_store::Change>, LixError> {
|
|
221
|
-
let mut store = self.store.lock().await;
|
|
222
|
-
let Some(commit) =
|
|
223
|
-
crate::commit_store::storage::load_commit(&mut *store, commit_id).await?
|
|
224
|
-
else {
|
|
225
|
-
return Ok(Vec::new());
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
let mut changes = Vec::new();
|
|
229
|
-
for pack_id in 0..commit.change_pack_count {
|
|
230
|
-
let Some(mut pack_changes) =
|
|
231
|
-
crate::commit_store::storage::load_change_pack(&mut *store, commit_id, pack_id)
|
|
232
|
-
.await?
|
|
233
|
-
else {
|
|
234
|
-
return Err(missing_pack_error("change", commit_id, pack_id));
|
|
235
|
-
};
|
|
236
|
-
changes.append(&mut pack_changes);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
for pack_id in 0..commit.membership_pack_count {
|
|
240
|
-
let Some(locators) =
|
|
241
|
-
crate::commit_store::storage::load_membership_pack(&mut *store, commit_id, pack_id)
|
|
242
|
-
.await?
|
|
243
|
-
else {
|
|
244
|
-
return Err(missing_pack_error("membership", commit_id, pack_id));
|
|
245
|
-
};
|
|
246
|
-
for locator in locators {
|
|
247
|
-
let change =
|
|
248
|
-
load_change_by_locator(&mut *store, &locator, &locator.change_id).await?;
|
|
249
|
-
changes.push(change);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
Ok(changes)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
pub(crate) async fn scan_changes(
|
|
257
|
-
&self,
|
|
258
|
-
request: &ChangeScanRequest,
|
|
259
|
-
) -> Result<Vec<LocatedChange>, LixError> {
|
|
260
|
-
scan_changes_from_commit_store(&mut *self.store.lock().await, request).await
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/// Commit-store writer over read visibility and a transaction-local write set.
|
|
265
|
-
pub(crate) struct CommitStoreWriter<'a, S: ?Sized> {
|
|
266
|
-
store: &'a mut S,
|
|
267
|
-
writes: &'a mut StorageWriteSet,
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
struct PendingCommitDraft<'a> {
|
|
271
|
-
commit: CommitDraftRef<'a>,
|
|
272
|
-
authored_changes: Vec<ChangeRef<'a>>,
|
|
273
|
-
adopted_changes: Vec<ChangeRef<'a>>,
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
impl<S> CommitStoreWriter<'_, S>
|
|
277
|
-
where
|
|
278
|
-
S: StorageReader + ?Sized,
|
|
279
|
-
{
|
|
280
|
-
/// Validates and stages canonical commit-store writes for complete commits.
|
|
281
|
-
///
|
|
282
|
-
/// Callers provide logical commit facts and borrowed change facts. The
|
|
283
|
-
/// commit store owns change-id uniqueness, adoption resolution, pack
|
|
284
|
-
/// locators, and physical namespace writes.
|
|
285
|
-
pub(crate) async fn stage_commit_draft<'a>(
|
|
286
|
-
&mut self,
|
|
287
|
-
commit: CommitDraftRef<'a>,
|
|
288
|
-
authored_changes: Vec<ChangeRef<'a>>,
|
|
289
|
-
adopted_changes: Vec<ChangeRef<'a>>,
|
|
290
|
-
) -> Result<StagedCommitStoreCommit, LixError> {
|
|
291
|
-
let mut staged = self
|
|
292
|
-
.stage_commit_drafts([(commit, authored_changes, adopted_changes)])
|
|
293
|
-
.await?;
|
|
294
|
-
staged.pop().ok_or_else(|| {
|
|
295
|
-
LixError::new(
|
|
296
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
297
|
-
"commit-store staged no result for one commit draft",
|
|
298
|
-
)
|
|
299
|
-
})
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/// Validates and stages a tracked commit whose authored rows will be stored
|
|
303
|
-
/// in the tracked-state delta pack instead of a duplicate commit-store pack.
|
|
304
|
-
pub(crate) async fn stage_tracked_commit_draft<'a>(
|
|
305
|
-
&mut self,
|
|
306
|
-
commit: CommitDraftRef<'a>,
|
|
307
|
-
authored_changes: Vec<ChangeRef<'a>>,
|
|
308
|
-
adopted_changes: Vec<ChangeRef<'a>>,
|
|
309
|
-
) -> Result<StagedCommitStoreCommit, LixError> {
|
|
310
|
-
let mut staged = self
|
|
311
|
-
.stage_tracked_commit_drafts([(commit, authored_changes, adopted_changes)])
|
|
312
|
-
.await?;
|
|
313
|
-
staged.pop().ok_or_else(|| {
|
|
314
|
-
LixError::new(
|
|
315
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
316
|
-
"commit-store staged no result for one tracked commit draft",
|
|
317
|
-
)
|
|
318
|
-
})
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/// Validates and stages multiple commit drafts as one commit-store batch.
|
|
322
|
-
pub(crate) async fn stage_commit_drafts<'a>(
|
|
323
|
-
&mut self,
|
|
324
|
-
commits: impl IntoIterator<Item = (CommitDraftRef<'a>, Vec<ChangeRef<'a>>, Vec<ChangeRef<'a>>)>,
|
|
325
|
-
) -> Result<Vec<StagedCommitStoreCommit>, LixError> {
|
|
326
|
-
self.stage_commit_drafts_with_authored_pack(commits, true)
|
|
327
|
-
.await
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/// Validates and stages multiple tracked commit drafts whose authored rows
|
|
331
|
-
/// will be stored in tracked-state delta packs.
|
|
332
|
-
pub(crate) async fn stage_tracked_commit_drafts<'a>(
|
|
333
|
-
&mut self,
|
|
334
|
-
commits: impl IntoIterator<Item = (CommitDraftRef<'a>, Vec<ChangeRef<'a>>, Vec<ChangeRef<'a>>)>,
|
|
335
|
-
) -> Result<Vec<StagedCommitStoreCommit>, LixError> {
|
|
336
|
-
self.stage_commit_drafts_with_authored_pack(commits, false)
|
|
337
|
-
.await
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
async fn stage_commit_drafts_with_authored_pack<'a>(
|
|
341
|
-
&mut self,
|
|
342
|
-
commits: impl IntoIterator<Item = (CommitDraftRef<'a>, Vec<ChangeRef<'a>>, Vec<ChangeRef<'a>>)>,
|
|
343
|
-
write_authored_change_pack: bool,
|
|
344
|
-
) -> Result<Vec<StagedCommitStoreCommit>, LixError> {
|
|
345
|
-
let commits = commits
|
|
346
|
-
.into_iter()
|
|
347
|
-
.map(
|
|
348
|
-
|(commit, authored_changes, adopted_changes)| PendingCommitDraft {
|
|
349
|
-
commit,
|
|
350
|
-
authored_changes,
|
|
351
|
-
adopted_changes,
|
|
352
|
-
},
|
|
353
|
-
)
|
|
354
|
-
.collect::<Vec<_>>();
|
|
355
|
-
let adopted_locators = validate_stage_commits(self.store, &commits).await?;
|
|
356
|
-
let mut staged = Vec::with_capacity(commits.len());
|
|
357
|
-
for commit in commits {
|
|
358
|
-
let mut adopted_changes = Vec::with_capacity(commit.adopted_changes.len());
|
|
359
|
-
for change in &commit.adopted_changes {
|
|
360
|
-
let Some(locator) = adopted_locators.get(change.id) else {
|
|
361
|
-
return Err(LixError::new(
|
|
362
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
363
|
-
format!(
|
|
364
|
-
"validated adopted commit-store change id '{}' has no locator",
|
|
365
|
-
change.id
|
|
366
|
-
),
|
|
367
|
-
));
|
|
368
|
-
};
|
|
369
|
-
adopted_changes.push(locator.clone());
|
|
370
|
-
}
|
|
371
|
-
staged.push(if write_authored_change_pack {
|
|
372
|
-
crate::commit_store::storage::stage_commit(
|
|
373
|
-
self.writes,
|
|
374
|
-
commit.commit,
|
|
375
|
-
commit.authored_changes,
|
|
376
|
-
adopted_changes,
|
|
377
|
-
)?
|
|
378
|
-
} else {
|
|
379
|
-
crate::commit_store::storage::stage_commit_with_external_authored_pack(
|
|
380
|
-
self.writes,
|
|
381
|
-
commit.commit,
|
|
382
|
-
commit.authored_changes,
|
|
383
|
-
adopted_changes,
|
|
384
|
-
)?
|
|
385
|
-
});
|
|
386
|
-
}
|
|
387
|
-
Ok(staged)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
async fn validate_stage_commits<'a>(
|
|
392
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
393
|
-
commits: &[PendingCommitDraft<'a>],
|
|
394
|
-
) -> Result<BTreeMap<&'a str, ChangeLocator>, LixError> {
|
|
395
|
-
validate_new_changes_absent(store, commits).await?;
|
|
396
|
-
validate_adopted_changes_present(store, commits).await
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
async fn scan_changes_from_commit_store(
|
|
400
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
401
|
-
request: &ChangeScanRequest,
|
|
402
|
-
) -> Result<Vec<LocatedChange>, LixError> {
|
|
403
|
-
let limit = request.limit.unwrap_or(usize::MAX);
|
|
404
|
-
let commits = crate::commit_store::storage::scan_commits(store).await?;
|
|
405
|
-
let mut changes = Vec::new();
|
|
406
|
-
for commit in commits {
|
|
407
|
-
if changes.len() >= limit {
|
|
408
|
-
break;
|
|
409
|
-
}
|
|
410
|
-
for pack_id in 0..commit.change_pack_count {
|
|
411
|
-
if changes.len() >= limit {
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
let Some(mut pack_changes) =
|
|
415
|
-
crate::commit_store::storage::load_change_pack(store, &commit.id, pack_id).await?
|
|
416
|
-
else {
|
|
417
|
-
return Err(missing_pack_error("change", &commit.id, pack_id));
|
|
418
|
-
};
|
|
419
|
-
let remaining = limit - changes.len();
|
|
420
|
-
if pack_changes.len() > remaining {
|
|
421
|
-
pack_changes.truncate(remaining);
|
|
422
|
-
}
|
|
423
|
-
changes.extend(pack_changes.into_iter().map(|record| LocatedChange {
|
|
424
|
-
record,
|
|
425
|
-
source_commit_id: commit.id.clone(),
|
|
426
|
-
source_pack_id: pack_id,
|
|
427
|
-
}));
|
|
428
|
-
}
|
|
429
|
-
if changes.len() < limit {
|
|
430
|
-
changes.push(located_commit_header_change(commit, 0));
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
Ok(changes)
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
async fn load_change_by_locator(
|
|
437
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
438
|
-
locator: &ChangeLocator,
|
|
439
|
-
expected_change_id: &str,
|
|
440
|
-
) -> Result<Change, LixError> {
|
|
441
|
-
let Some(changes) = crate::commit_store::storage::load_change_pack(
|
|
442
|
-
store,
|
|
443
|
-
&locator.source_commit_id,
|
|
444
|
-
locator.source_pack_id,
|
|
445
|
-
)
|
|
446
|
-
.await?
|
|
447
|
-
else {
|
|
448
|
-
return Err(missing_pack_error(
|
|
449
|
-
"change",
|
|
450
|
-
&locator.source_commit_id,
|
|
451
|
-
locator.source_pack_id,
|
|
452
|
-
));
|
|
453
|
-
};
|
|
454
|
-
let change = changes
|
|
455
|
-
.get(usize::try_from(locator.source_ordinal).map_err(|_| {
|
|
456
|
-
LixError::new(
|
|
457
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
458
|
-
"commit-store change locator ordinal does not fit usize",
|
|
459
|
-
)
|
|
460
|
-
})?)
|
|
461
|
-
.ok_or_else(|| {
|
|
462
|
-
LixError::new(
|
|
463
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
464
|
-
format!(
|
|
465
|
-
"commit-store change locator for '{}' points past pack '{}' in commit '{}'",
|
|
466
|
-
expected_change_id, locator.source_pack_id, locator.source_commit_id
|
|
467
|
-
),
|
|
468
|
-
)
|
|
469
|
-
})?;
|
|
470
|
-
if change.id != expected_change_id || change.id != locator.change_id {
|
|
471
|
-
return Err(LixError::new(
|
|
472
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
473
|
-
format!(
|
|
474
|
-
"commit-store change locator expected '{}' but found '{}'",
|
|
475
|
-
expected_change_id, change.id
|
|
476
|
-
),
|
|
477
|
-
));
|
|
478
|
-
}
|
|
479
|
-
Ok(change.clone())
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
async fn load_change_by_locator_cached(
|
|
483
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
484
|
-
packs_by_locator: &mut BTreeMap<(String, u32), Vec<Change>>,
|
|
485
|
-
locator: &ChangeLocator,
|
|
486
|
-
expected_change_id: &str,
|
|
487
|
-
) -> Result<Change, LixError> {
|
|
488
|
-
let key = (locator.source_commit_id.clone(), locator.source_pack_id);
|
|
489
|
-
if !packs_by_locator.contains_key(&key) {
|
|
490
|
-
let Some(changes) = crate::commit_store::storage::load_change_pack(
|
|
491
|
-
store,
|
|
492
|
-
&locator.source_commit_id,
|
|
493
|
-
locator.source_pack_id,
|
|
494
|
-
)
|
|
495
|
-
.await?
|
|
496
|
-
else {
|
|
497
|
-
return Err(missing_pack_error(
|
|
498
|
-
"change",
|
|
499
|
-
&locator.source_commit_id,
|
|
500
|
-
locator.source_pack_id,
|
|
501
|
-
));
|
|
502
|
-
};
|
|
503
|
-
packs_by_locator.insert(key.clone(), changes);
|
|
504
|
-
}
|
|
505
|
-
let changes = packs_by_locator.get(&key).ok_or_else(|| {
|
|
506
|
-
LixError::new(
|
|
507
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
508
|
-
"commit-store change pack cache lost a loaded pack",
|
|
509
|
-
)
|
|
510
|
-
})?;
|
|
511
|
-
let change = changes
|
|
512
|
-
.get(usize::try_from(locator.source_ordinal).map_err(|_| {
|
|
513
|
-
LixError::new(
|
|
514
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
515
|
-
"commit-store change locator ordinal does not fit usize",
|
|
516
|
-
)
|
|
517
|
-
})?)
|
|
518
|
-
.ok_or_else(|| {
|
|
519
|
-
LixError::new(
|
|
520
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
521
|
-
format!(
|
|
522
|
-
"commit-store change locator for '{}' points past pack '{}' in commit '{}'",
|
|
523
|
-
expected_change_id, locator.source_pack_id, locator.source_commit_id
|
|
524
|
-
),
|
|
525
|
-
)
|
|
526
|
-
})?;
|
|
527
|
-
if change.id != expected_change_id || change.id != locator.change_id {
|
|
528
|
-
return Err(LixError::new(
|
|
529
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
530
|
-
format!(
|
|
531
|
-
"commit-store change locator expected '{}' but found '{}'",
|
|
532
|
-
expected_change_id, change.id
|
|
533
|
-
),
|
|
534
|
-
));
|
|
535
|
-
}
|
|
536
|
-
Ok(change.clone())
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
fn commit_header_change(commit: Commit) -> Change {
|
|
540
|
-
Change {
|
|
541
|
-
id: commit.change_id,
|
|
542
|
-
entity_id: crate::entity_identity::EntityIdentity::single(commit.id),
|
|
543
|
-
schema_key: "lix_commit".to_string(),
|
|
544
|
-
file_id: None,
|
|
545
|
-
snapshot_ref: None,
|
|
546
|
-
metadata_ref: None,
|
|
547
|
-
created_at: commit.created_at,
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
fn located_commit_header_change(commit: Commit, source_pack_id: u32) -> LocatedChange {
|
|
552
|
-
let source_commit_id = commit.id.clone();
|
|
553
|
-
LocatedChange {
|
|
554
|
-
record: commit_header_change(commit),
|
|
555
|
-
source_commit_id,
|
|
556
|
-
source_pack_id,
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
fn missing_pack_error(label: &str, commit_id: &str, pack_id: u32) -> LixError {
|
|
561
|
-
LixError::new(
|
|
562
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
563
|
-
format!("commit-store missing {label} pack ({commit_id}, {pack_id})"),
|
|
564
|
-
)
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
async fn validate_new_changes_absent<'a>(
|
|
568
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
569
|
-
commits: &[PendingCommitDraft<'a>],
|
|
570
|
-
) -> Result<(), LixError> {
|
|
571
|
-
let mut change_ids = Vec::new();
|
|
572
|
-
let mut seen_change_ids = BTreeSet::new();
|
|
573
|
-
for commit in commits {
|
|
574
|
-
if !seen_change_ids.insert(commit.commit.change_id) {
|
|
575
|
-
return Err(duplicate_change_id_error(commit.commit.change_id));
|
|
576
|
-
}
|
|
577
|
-
change_ids.push(commit.commit.change_id.to_string());
|
|
578
|
-
for change in &commit.authored_changes {
|
|
579
|
-
if !seen_change_ids.insert(change.id) {
|
|
580
|
-
return Err(duplicate_change_id_error(change.id));
|
|
581
|
-
}
|
|
582
|
-
change_ids.push(change.id.to_string());
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
let reader = CommitStoreContext::new().reader(&mut *store);
|
|
587
|
-
let existing_changes = reader.load_change_index_entries(&change_ids).await?;
|
|
588
|
-
for (change_id, existing) in change_ids.iter().zip(existing_changes) {
|
|
589
|
-
if existing.is_some() {
|
|
590
|
-
return Err(LixError::new(
|
|
591
|
-
LixError::CODE_UNIQUE,
|
|
592
|
-
format!("commit-store change id '{}' already exists", change_id),
|
|
593
|
-
));
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
Ok(())
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
async fn validate_adopted_changes_present<'a>(
|
|
600
|
-
store: &mut (impl StorageReader + ?Sized),
|
|
601
|
-
commits: &[PendingCommitDraft<'a>],
|
|
602
|
-
) -> Result<BTreeMap<&'a str, ChangeLocator>, LixError> {
|
|
603
|
-
let mut expected_changes = Vec::new();
|
|
604
|
-
let mut seen_change_ids = BTreeSet::new();
|
|
605
|
-
for commit in commits {
|
|
606
|
-
for change in &commit.adopted_changes {
|
|
607
|
-
if !seen_change_ids.insert(change.id) {
|
|
608
|
-
return Err(LixError::new(
|
|
609
|
-
LixError::CODE_UNIQUE,
|
|
610
|
-
format!(
|
|
611
|
-
"adopted commit-store change id '{}' appears more than once in the same transaction",
|
|
612
|
-
change.id
|
|
613
|
-
),
|
|
614
|
-
));
|
|
615
|
-
}
|
|
616
|
-
expected_changes.push(*change);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
if expected_changes.is_empty() {
|
|
620
|
-
return Ok(BTreeMap::new());
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
let change_ids = expected_changes
|
|
624
|
-
.iter()
|
|
625
|
-
.map(|change| change.id.to_string())
|
|
626
|
-
.collect::<Vec<_>>();
|
|
627
|
-
let reader = CommitStoreContext::new().reader(&mut *store);
|
|
628
|
-
let existing_entries = reader.load_change_index_entries(&change_ids).await?;
|
|
629
|
-
let mut locators_by_change_id = BTreeMap::new();
|
|
630
|
-
for (expected, existing) in expected_changes.into_iter().zip(existing_entries) {
|
|
631
|
-
match existing {
|
|
632
|
-
Some(ChangeIndexEntry::PackedChange { locator }) => {
|
|
633
|
-
let existing_change = load_packed_change(&reader, &locator, expected.id).await?;
|
|
634
|
-
if !change_matches_ref(&existing_change, expected) {
|
|
635
|
-
let entity_id = existing_change
|
|
636
|
-
.entity_id
|
|
637
|
-
.as_json_array_text()
|
|
638
|
-
.unwrap_or_else(|_| "<invalid entity_id>".to_string());
|
|
639
|
-
return Err(LixError::new(
|
|
640
|
-
LixError::CODE_UNIQUE,
|
|
641
|
-
format!(
|
|
642
|
-
"adopted commit-store change id '{}' exists with different content for schema '{}' entity '{}'",
|
|
643
|
-
expected.id, existing_change.schema_key, entity_id
|
|
644
|
-
),
|
|
645
|
-
));
|
|
646
|
-
}
|
|
647
|
-
locators_by_change_id.insert(expected.id, locator);
|
|
648
|
-
}
|
|
649
|
-
Some(ChangeIndexEntry::CommitHeader { .. }) => {
|
|
650
|
-
return Err(LixError::new(
|
|
651
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
652
|
-
format!(
|
|
653
|
-
"adopted commit-store change id '{}' resolves to a commit header, not a packed state change",
|
|
654
|
-
expected.id
|
|
655
|
-
),
|
|
656
|
-
));
|
|
657
|
-
}
|
|
658
|
-
None => {
|
|
659
|
-
return Err(LixError::new(
|
|
660
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
661
|
-
format!(
|
|
662
|
-
"adopted commit-store change id '{}' does not exist",
|
|
663
|
-
expected.id
|
|
664
|
-
),
|
|
665
|
-
));
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
Ok(locators_by_change_id)
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
async fn load_packed_change<S>(
|
|
673
|
-
reader: &CommitStoreReader<S>,
|
|
674
|
-
locator: &ChangeLocator,
|
|
675
|
-
expected_change_id: &str,
|
|
676
|
-
) -> Result<Change, LixError>
|
|
677
|
-
where
|
|
678
|
-
S: StorageReader,
|
|
679
|
-
{
|
|
680
|
-
let pack = reader
|
|
681
|
-
.load_change_pack(&locator.source_commit_id, locator.source_pack_id)
|
|
682
|
-
.await?
|
|
683
|
-
.ok_or_else(|| {
|
|
684
|
-
LixError::new(
|
|
685
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
686
|
-
format!(
|
|
687
|
-
"commit-store change pack '{}:{}' for change '{}' is missing",
|
|
688
|
-
locator.source_commit_id, locator.source_pack_id, expected_change_id
|
|
689
|
-
),
|
|
690
|
-
)
|
|
691
|
-
})?;
|
|
692
|
-
let change = pack
|
|
693
|
-
.get(usize::try_from(locator.source_ordinal).map_err(|_| {
|
|
694
|
-
LixError::new(
|
|
695
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
696
|
-
"commit-store change locator ordinal exceeds usize",
|
|
697
|
-
)
|
|
698
|
-
})?)
|
|
699
|
-
.ok_or_else(|| {
|
|
700
|
-
LixError::new(
|
|
701
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
702
|
-
format!(
|
|
703
|
-
"commit-store change locator '{}' points past pack length",
|
|
704
|
-
expected_change_id
|
|
705
|
-
),
|
|
706
|
-
)
|
|
707
|
-
})?
|
|
708
|
-
.clone();
|
|
709
|
-
if change.id != expected_change_id {
|
|
710
|
-
return Err(LixError::new(
|
|
711
|
-
LixError::CODE_INTERNAL_ERROR,
|
|
712
|
-
format!(
|
|
713
|
-
"commit-store change locator expected '{}' but loaded '{}'",
|
|
714
|
-
expected_change_id, change.id
|
|
715
|
-
),
|
|
716
|
-
));
|
|
717
|
-
}
|
|
718
|
-
Ok(change)
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
fn change_matches_ref(change: &Change, expected: ChangeRef<'_>) -> bool {
|
|
722
|
-
change.id == expected.id
|
|
723
|
-
&& &change.entity_id == expected.entity_id
|
|
724
|
-
&& change.schema_key == expected.schema_key
|
|
725
|
-
&& change.file_id.as_deref() == expected.file_id
|
|
726
|
-
&& change.snapshot_ref.as_ref() == expected.snapshot_ref
|
|
727
|
-
&& change.metadata_ref.as_ref() == expected.metadata_ref
|
|
728
|
-
&& change.created_at == expected.created_at
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
fn duplicate_change_id_error(change_id: &str) -> LixError {
|
|
732
|
-
LixError::new(
|
|
733
|
-
LixError::CODE_UNIQUE,
|
|
734
|
-
format!(
|
|
735
|
-
"commit-store change id '{}' appears more than once in the same transaction",
|
|
736
|
-
change_id
|
|
737
|
-
),
|
|
738
|
-
)
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
#[cfg(test)]
|
|
742
|
-
mod tests {
|
|
743
|
-
use std::sync::Arc;
|
|
744
|
-
|
|
745
|
-
use crate::backend::testing::UnitTestBackend;
|
|
746
|
-
use crate::commit_store::{
|
|
747
|
-
ChangeIndexEntry, ChangeLocator, CommitDraftRef, CommitStoreContext,
|
|
748
|
-
};
|
|
749
|
-
use crate::entity_identity::EntityIdentity;
|
|
750
|
-
use crate::json_store::JsonRef;
|
|
751
|
-
use crate::storage::{StorageContext, StorageWriteSet, StorageWriteTransaction};
|
|
752
|
-
|
|
753
|
-
use super::*;
|
|
754
|
-
|
|
755
|
-
#[tokio::test]
|
|
756
|
-
async fn load_changes_materializes_commit_header_and_packed_change() {
|
|
757
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
758
|
-
let mut transaction = storage
|
|
759
|
-
.begin_write_transaction()
|
|
760
|
-
.await
|
|
761
|
-
.expect("transaction should open");
|
|
762
|
-
let mut writes = StorageWriteSet::new();
|
|
763
|
-
let parent_ids = vec!["parent-1".to_string()];
|
|
764
|
-
let author_account_ids = vec!["author-1".to_string()];
|
|
765
|
-
let commit_id = "commit-1".to_string();
|
|
766
|
-
let commit_change_id = "commit-change-1".to_string();
|
|
767
|
-
let authored_change = test_change("change-1");
|
|
768
|
-
|
|
769
|
-
CommitStoreContext::new()
|
|
770
|
-
.writer(transaction.as_mut(), &mut writes)
|
|
771
|
-
.stage_commit_draft(
|
|
772
|
-
CommitDraftRef {
|
|
773
|
-
id: &commit_id,
|
|
774
|
-
change_id: &commit_change_id,
|
|
775
|
-
parent_ids: &parent_ids,
|
|
776
|
-
author_account_ids: &author_account_ids,
|
|
777
|
-
created_at: "2026-01-01T00:00:00Z",
|
|
778
|
-
},
|
|
779
|
-
vec![authored_change.as_ref()],
|
|
780
|
-
Vec::new(),
|
|
781
|
-
)
|
|
782
|
-
.await
|
|
783
|
-
.expect("commit should stage");
|
|
784
|
-
writes
|
|
785
|
-
.apply(&mut transaction.as_mut())
|
|
786
|
-
.await
|
|
787
|
-
.expect("writes should apply");
|
|
788
|
-
transaction.commit().await.expect("commit should persist");
|
|
789
|
-
|
|
790
|
-
let reader = CommitStoreContext::new().reader(storage.clone());
|
|
791
|
-
let index_entries = reader
|
|
792
|
-
.load_change_index_entries(&[
|
|
793
|
-
commit_change_id.clone(),
|
|
794
|
-
authored_change.id.clone(),
|
|
795
|
-
"missing-change".to_string(),
|
|
796
|
-
])
|
|
797
|
-
.await
|
|
798
|
-
.expect("index entries should load");
|
|
799
|
-
assert_eq!(
|
|
800
|
-
index_entries,
|
|
801
|
-
vec![
|
|
802
|
-
Some(ChangeIndexEntry::CommitHeader {
|
|
803
|
-
commit_id: commit_id.clone(),
|
|
804
|
-
change_id: commit_change_id.clone(),
|
|
805
|
-
}),
|
|
806
|
-
Some(ChangeIndexEntry::PackedChange {
|
|
807
|
-
locator: ChangeLocator {
|
|
808
|
-
source_commit_id: commit_id.clone(),
|
|
809
|
-
source_pack_id: 0,
|
|
810
|
-
source_ordinal: 0,
|
|
811
|
-
change_id: authored_change.id.clone(),
|
|
812
|
-
},
|
|
813
|
-
}),
|
|
814
|
-
None,
|
|
815
|
-
]
|
|
816
|
-
);
|
|
817
|
-
|
|
818
|
-
let changes = reader
|
|
819
|
-
.load_changes(&[
|
|
820
|
-
commit_change_id.clone(),
|
|
821
|
-
authored_change.id.clone(),
|
|
822
|
-
"missing-change".to_string(),
|
|
823
|
-
])
|
|
824
|
-
.await
|
|
825
|
-
.expect("changes should load");
|
|
826
|
-
assert_eq!(changes.len(), 3);
|
|
827
|
-
|
|
828
|
-
let header_change = changes[0]
|
|
829
|
-
.as_ref()
|
|
830
|
-
.expect("commit-header change should materialize");
|
|
831
|
-
assert_eq!(header_change.id, commit_change_id);
|
|
832
|
-
assert_eq!(header_change.entity_id, EntityIdentity::single(&commit_id));
|
|
833
|
-
assert_eq!(header_change.schema_key, "lix_commit");
|
|
834
|
-
assert_eq!(header_change.file_id, None);
|
|
835
|
-
assert_eq!(header_change.snapshot_ref, None);
|
|
836
|
-
assert_eq!(header_change.metadata_ref, None);
|
|
837
|
-
assert_eq!(header_change.created_at, "2026-01-01T00:00:00Z");
|
|
838
|
-
|
|
839
|
-
assert_eq!(
|
|
840
|
-
changes[1]
|
|
841
|
-
.as_ref()
|
|
842
|
-
.expect("packed change should decode from change pack"),
|
|
843
|
-
&authored_change
|
|
844
|
-
);
|
|
845
|
-
assert_eq!(changes[2], None);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
#[tokio::test]
|
|
849
|
-
async fn load_commit_changes_returns_equivalent_authored_and_adopted_changes() {
|
|
850
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
851
|
-
let authored_change = test_change("shared-change-1");
|
|
852
|
-
|
|
853
|
-
stage_test_commit(
|
|
854
|
-
storage.clone(),
|
|
855
|
-
"source-commit",
|
|
856
|
-
"source-commit-change",
|
|
857
|
-
vec![authored_change.as_ref()],
|
|
858
|
-
Vec::new(),
|
|
859
|
-
)
|
|
860
|
-
.await;
|
|
861
|
-
stage_test_commit(
|
|
862
|
-
storage.clone(),
|
|
863
|
-
"adopting-commit",
|
|
864
|
-
"adopting-commit-change",
|
|
865
|
-
Vec::new(),
|
|
866
|
-
vec![authored_change.as_ref()],
|
|
867
|
-
)
|
|
868
|
-
.await;
|
|
869
|
-
|
|
870
|
-
let reader = CommitStoreContext::new().reader(storage.clone());
|
|
871
|
-
let source_changes = reader
|
|
872
|
-
.load_commit_changes("source-commit")
|
|
873
|
-
.await
|
|
874
|
-
.expect("source commit changes should load");
|
|
875
|
-
let adopting_changes = reader
|
|
876
|
-
.load_commit_changes("adopting-commit")
|
|
877
|
-
.await
|
|
878
|
-
.expect("adopting commit changes should load");
|
|
879
|
-
|
|
880
|
-
assert_eq!(source_changes, vec![authored_change.clone()]);
|
|
881
|
-
assert_eq!(adopting_changes, source_changes);
|
|
882
|
-
assert_eq!(
|
|
883
|
-
reader
|
|
884
|
-
.load_membership_pack("adopting-commit", 0)
|
|
885
|
-
.await
|
|
886
|
-
.expect("membership pack should load"),
|
|
887
|
-
Some(vec![ChangeLocator {
|
|
888
|
-
source_commit_id: "source-commit".to_string(),
|
|
889
|
-
source_pack_id: 0,
|
|
890
|
-
source_ordinal: 0,
|
|
891
|
-
change_id: authored_change.id.clone(),
|
|
892
|
-
}])
|
|
893
|
-
);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
async fn stage_test_commit(
|
|
897
|
-
storage: StorageContext,
|
|
898
|
-
commit_id: &str,
|
|
899
|
-
commit_change_id: &str,
|
|
900
|
-
authored_changes: Vec<ChangeRef<'_>>,
|
|
901
|
-
adopted_changes: Vec<ChangeRef<'_>>,
|
|
902
|
-
) {
|
|
903
|
-
let mut transaction = storage
|
|
904
|
-
.begin_write_transaction()
|
|
905
|
-
.await
|
|
906
|
-
.expect("transaction should open");
|
|
907
|
-
let mut writes = StorageWriteSet::new();
|
|
908
|
-
let parent_ids = Vec::new();
|
|
909
|
-
let author_account_ids = Vec::new();
|
|
910
|
-
|
|
911
|
-
CommitStoreContext::new()
|
|
912
|
-
.writer(transaction.as_mut(), &mut writes)
|
|
913
|
-
.stage_commit_draft(
|
|
914
|
-
CommitDraftRef {
|
|
915
|
-
id: commit_id,
|
|
916
|
-
change_id: commit_change_id,
|
|
917
|
-
parent_ids: &parent_ids,
|
|
918
|
-
author_account_ids: &author_account_ids,
|
|
919
|
-
created_at: "2026-01-01T00:00:00Z",
|
|
920
|
-
},
|
|
921
|
-
authored_changes,
|
|
922
|
-
adopted_changes,
|
|
923
|
-
)
|
|
924
|
-
.await
|
|
925
|
-
.expect("commit should stage");
|
|
926
|
-
writes
|
|
927
|
-
.apply(&mut transaction.as_mut())
|
|
928
|
-
.await
|
|
929
|
-
.expect("writes should apply");
|
|
930
|
-
transaction.commit().await.expect("commit should persist");
|
|
931
|
-
}
|
|
932
|
-
|
|
933
|
-
fn test_change(id: &str) -> Change {
|
|
934
|
-
Change {
|
|
935
|
-
id: id.to_string(),
|
|
936
|
-
entity_id: EntityIdentity::single("entity-1"),
|
|
937
|
-
schema_key: "test_schema".to_string(),
|
|
938
|
-
file_id: Some("file-1".to_string()),
|
|
939
|
-
snapshot_ref: Some(JsonRef::from_hash_bytes([1; 32])),
|
|
940
|
-
metadata_ref: Some(JsonRef::from_hash_bytes([2; 32])),
|
|
941
|
-
created_at: "2026-01-02T00:00:00Z".to_string(),
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|