@lix-js/sdk 0.6.0-preview.2 → 0.6.0-preview.3
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/SKILL.md +4 -5
- package/dist/engine-wasm/wasm/lix_engine.js +1 -1
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/generated/builtin-schemas.d.ts +87 -162
- package/dist/generated/builtin-schemas.js +139 -236
- package/dist/open-lix.d.ts +1 -1
- package/dist-engine-src/src/binary_cas/types.rs +0 -6
- package/dist-engine-src/src/catalog/context.rs +412 -0
- package/dist-engine-src/src/catalog/mod.rs +10 -0
- package/dist-engine-src/src/catalog/schema.rs +4 -0
- package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
- package/dist-engine-src/src/cel/mod.rs +1 -1
- package/dist-engine-src/src/cel/provider.rs +1 -1
- package/dist-engine-src/src/commit_graph/context.rs +328 -1015
- package/dist-engine-src/src/commit_graph/mod.rs +2 -3
- package/dist-engine-src/src/commit_graph/types.rs +7 -43
- package/dist-engine-src/src/commit_graph/walker.rs +57 -81
- package/dist-engine-src/src/commit_store/codec.rs +887 -0
- package/dist-engine-src/src/commit_store/context.rs +944 -0
- package/dist-engine-src/src/commit_store/materialization.rs +84 -0
- package/dist-engine-src/src/commit_store/mod.rs +16 -0
- package/dist-engine-src/src/commit_store/storage.rs +600 -0
- package/dist-engine-src/src/commit_store/types.rs +215 -0
- package/dist-engine-src/src/common/identity.rs +15 -5
- package/dist-engine-src/src/common/json_pointer.rs +67 -0
- package/dist-engine-src/src/common/metadata.rs +17 -12
- package/dist-engine-src/src/common/mod.rs +5 -5
- package/dist-engine-src/src/domain.rs +324 -0
- package/dist-engine-src/src/engine.rs +29 -43
- package/dist-engine-src/src/entity_identity.rs +238 -118
- package/dist-engine-src/src/functions/context.rs +17 -52
- package/dist-engine-src/src/functions/deterministic.rs +1 -1
- package/dist-engine-src/src/functions/mod.rs +1 -1
- package/dist-engine-src/src/functions/provider.rs +4 -4
- package/dist-engine-src/src/functions/state.rs +39 -66
- package/dist-engine-src/src/functions/types.rs +1 -1
- package/dist-engine-src/src/init.rs +204 -151
- package/dist-engine-src/src/json_store/context.rs +354 -60
- package/dist-engine-src/src/json_store/encoded.rs +6 -6
- package/dist-engine-src/src/json_store/mod.rs +4 -1
- package/dist-engine-src/src/json_store/store.rs +884 -11
- package/dist-engine-src/src/json_store/types.rs +166 -1
- package/dist-engine-src/src/lib.rs +10 -9
- package/dist-engine-src/src/live_state/context.rs +608 -830
- package/dist-engine-src/src/live_state/mod.rs +3 -3
- package/dist-engine-src/src/live_state/overlay.rs +7 -7
- package/dist-engine-src/src/live_state/reader.rs +5 -5
- package/dist-engine-src/src/live_state/types.rs +19 -36
- package/dist-engine-src/src/live_state/visibility.rs +19 -14
- package/dist-engine-src/src/plugin/archive.rs +3 -6
- package/dist-engine-src/src/plugin/install.rs +0 -18
- package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
- package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
- package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
- package/dist-engine-src/src/schema/compatibility.rs +787 -0
- package/dist-engine-src/src/schema/definition.json +47 -17
- package/dist-engine-src/src/schema/definition.rs +202 -96
- package/dist-engine-src/src/schema/key.rs +9 -77
- package/dist-engine-src/src/schema/mod.rs +4 -4
- package/dist-engine-src/src/schema/tests.rs +133 -92
- package/dist-engine-src/src/session/context.rs +40 -42
- package/dist-engine-src/src/session/create_version.rs +22 -14
- package/dist-engine-src/src/session/execute.rs +45 -14
- package/dist-engine-src/src/session/merge/apply.rs +4 -4
- package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
- package/dist-engine-src/src/session/merge/stats.rs +1 -1
- package/dist-engine-src/src/session/merge/version.rs +35 -45
- package/dist-engine-src/src/session/mod.rs +4 -2
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
- package/dist-engine-src/src/session/switch_version.rs +16 -28
- package/dist-engine-src/src/sql2/change_provider.rs +14 -20
- package/dist-engine-src/src/sql2/classify.rs +61 -26
- package/dist-engine-src/src/sql2/context.rs +22 -18
- package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
- package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
- package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
- package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
- package/dist-engine-src/src/sql2/error.rs +21 -1
- package/dist-engine-src/src/sql2/execute.rs +325 -264
- package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
- package/dist-engine-src/src/sql2/file_provider.rs +533 -108
- package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
- package/dist-engine-src/src/sql2/history_projection.rs +3 -27
- package/dist-engine-src/src/sql2/history_provider.rs +11 -17
- package/dist-engine-src/src/sql2/history_route.rs +22 -8
- package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
- package/dist-engine-src/src/sql2/mod.rs +6 -3
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
- package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
- package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
- package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
- package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
- package/dist-engine-src/src/sql2/read_only.rs +10 -12
- package/dist-engine-src/src/sql2/session.rs +7 -10
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
- package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
- package/dist-engine-src/src/sql2/version_provider.rs +46 -31
- package/dist-engine-src/src/sql2/version_scope.rs +4 -4
- package/dist-engine-src/src/storage_bench.rs +1782 -325
- package/dist-engine-src/src/test_support.rs +183 -36
- package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
- package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
- package/dist-engine-src/src/tracked_state/context.rs +1155 -271
- package/dist-engine-src/src/tracked_state/diff.rs +249 -57
- package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
- package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
- package/dist-engine-src/src/tracked_state/merge.rs +37 -19
- package/dist-engine-src/src/tracked_state/mod.rs +8 -7
- package/dist-engine-src/src/tracked_state/storage.rs +138 -6
- package/dist-engine-src/src/tracked_state/tree.rs +695 -252
- package/dist-engine-src/src/tracked_state/types.rs +176 -6
- package/dist-engine-src/src/transaction/commit.rs +695 -435
- package/dist-engine-src/src/transaction/context.rs +551 -310
- package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
- package/dist-engine-src/src/transaction/mod.rs +2 -0
- package/dist-engine-src/src/transaction/normalization.rs +311 -447
- package/dist-engine-src/src/transaction/prep.rs +37 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
- package/dist-engine-src/src/transaction/staging.rs +701 -406
- package/dist-engine-src/src/transaction/types.rs +231 -122
- package/dist-engine-src/src/transaction/validation.rs +2717 -1698
- package/dist-engine-src/src/untracked_state/codec.rs +40 -96
- package/dist-engine-src/src/untracked_state/context.rs +21 -5
- package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
- package/dist-engine-src/src/untracked_state/mod.rs +3 -5
- package/dist-engine-src/src/untracked_state/storage.rs +105 -57
- package/dist-engine-src/src/untracked_state/types.rs +63 -13
- package/dist-engine-src/src/version/context.rs +1 -13
- package/dist-engine-src/src/version/lifecycle.rs +221 -0
- package/dist-engine-src/src/version/mod.rs +3 -2
- package/dist-engine-src/src/version/refs.rs +12 -103
- package/dist-engine-src/src/version/stage_rows.rs +15 -19
- package/package.json +1 -1
- package/dist-engine-src/src/changelog/codec.rs +0 -321
- package/dist-engine-src/src/changelog/context.rs +0 -92
- package/dist-engine-src/src/changelog/materialization.rs +0 -121
- package/dist-engine-src/src/changelog/mod.rs +0 -13
- package/dist-engine-src/src/changelog/reader.rs +0 -20
- package/dist-engine-src/src/changelog/storage.rs +0 -220
- package/dist-engine-src/src/changelog/types.rs +0 -38
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
- package/dist-engine-src/src/schema_registry.rs +0 -294
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
- package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
- package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
|
@@ -1,35 +1,33 @@
|
|
|
1
|
-
use std::collections::
|
|
1
|
+
use std::collections::BTreeSet;
|
|
2
2
|
|
|
3
|
-
use crate::changelog::{
|
|
4
|
-
materialize_change, CanonicalChange, ChangelogContext, ChangelogStoreReader,
|
|
5
|
-
MaterializedCanonicalChange,
|
|
6
|
-
};
|
|
7
3
|
use crate::commit_graph::walker::{best_common_ancestors, walk_reachable_commits};
|
|
8
4
|
use crate::commit_graph::{
|
|
9
|
-
CommitGraphChangeHistoryEntry, CommitGraphChangeHistoryRequest,
|
|
10
|
-
|
|
11
|
-
CommitGraphReader, ReachableCommitGraphCommit,
|
|
5
|
+
CommitGraphChangeHistoryEntry, CommitGraphChangeHistoryRequest, CommitGraphCommit,
|
|
6
|
+
CommitGraphEdge, CommitGraphReader, ReachableCommitGraphCommit,
|
|
12
7
|
};
|
|
8
|
+
use crate::commit_store::{Change, Commit, CommitStoreContext, CommitStoreReader, LocatedChange};
|
|
13
9
|
use crate::entity_identity::EntityIdentity;
|
|
14
|
-
use crate::json_store::{JsonStoreContext, JsonStoreReader};
|
|
15
10
|
use crate::storage::StorageReader;
|
|
16
11
|
use crate::storage::{ScopedStorageReader, StorageReadScope};
|
|
17
12
|
use crate::LixError;
|
|
18
13
|
|
|
19
14
|
const COMMIT_SCHEMA_KEY: &str = "lix_commit";
|
|
20
15
|
|
|
21
|
-
/// Read model for resolving
|
|
16
|
+
/// Read model for resolving commit-store commits into entity state at a head.
|
|
22
17
|
///
|
|
23
|
-
/// This module does not own durable storage. It reads immutable
|
|
24
|
-
/// through a caller-provided KV store and applies commit graph rules on
|
|
18
|
+
/// This module does not own durable storage. It reads immutable commit-store
|
|
19
|
+
/// facts through a caller-provided KV store and applies commit graph rules on
|
|
20
|
+
/// top.
|
|
25
21
|
#[derive(Clone)]
|
|
26
22
|
pub(crate) struct CommitGraphContext {
|
|
27
|
-
|
|
23
|
+
commit_store: CommitStoreContext,
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
impl CommitGraphContext {
|
|
31
|
-
pub(crate) fn new(
|
|
32
|
-
Self {
|
|
27
|
+
pub(crate) fn new() -> Self {
|
|
28
|
+
Self {
|
|
29
|
+
commit_store: CommitStoreContext::new(),
|
|
30
|
+
}
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
/// Creates a graph reader over a caller-provided KV store.
|
|
@@ -39,64 +37,43 @@ impl CommitGraphContext {
|
|
|
39
37
|
{
|
|
40
38
|
let read_scope = StorageReadScope::new(store);
|
|
41
39
|
CommitGraphStoreReader {
|
|
42
|
-
|
|
43
|
-
json_reader: JsonStoreContext::new().reader(read_scope.store()),
|
|
40
|
+
commit_store_reader: self.commit_store.reader(read_scope.store()),
|
|
44
41
|
}
|
|
45
42
|
}
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
/// Commit-graph reader that resolves
|
|
45
|
+
/// Commit-graph reader that resolves commit-store entities at a commit head.
|
|
49
46
|
pub(crate) struct CommitGraphStoreReader<S>
|
|
50
47
|
where
|
|
51
48
|
S: StorageReader,
|
|
52
49
|
{
|
|
53
|
-
|
|
54
|
-
json_reader: JsonStoreReader<ScopedStorageReader<S>>,
|
|
50
|
+
commit_store_reader: CommitStoreReader<ScopedStorageReader<S>>,
|
|
55
51
|
}
|
|
56
52
|
|
|
57
53
|
impl<S> CommitGraphStoreReader<S>
|
|
58
54
|
where
|
|
59
55
|
S: StorageReader,
|
|
60
56
|
{
|
|
61
|
-
/// Returns the canonical entities that are effective at `head_commit_id`.
|
|
62
|
-
///
|
|
63
|
-
/// Reachable commits are visited nearest-first. For each commit, the commit
|
|
64
|
-
/// row itself is visible, then introduced/adopted `change_ids` are visited
|
|
65
|
-
/// in reverse order so later writes in the same commit win.
|
|
66
|
-
pub(crate) async fn entities_at(
|
|
67
|
-
&mut self,
|
|
68
|
-
head_commit_id: &str,
|
|
69
|
-
) -> Result<Vec<CommitGraphEntity>, LixError> {
|
|
70
|
-
let commits = self.reachable_commits(head_commit_id).await?;
|
|
71
|
-
self.select_entities(commits).await
|
|
72
|
-
}
|
|
73
|
-
|
|
74
57
|
/// Loads and parses a `lix_commit` canonical change by commit id.
|
|
75
58
|
pub(crate) async fn load_commit(
|
|
76
59
|
&mut self,
|
|
77
60
|
commit_id: &str,
|
|
78
61
|
) -> Result<Option<CommitGraphCommit>, LixError> {
|
|
79
|
-
let Some(
|
|
62
|
+
let Some(commit) = self.commit_store_reader.load_commit(commit_id).await? else {
|
|
80
63
|
return Ok(None);
|
|
81
64
|
};
|
|
82
|
-
|
|
65
|
+
self.graph_commit_from_store_commit(commit).await.map(Some)
|
|
83
66
|
}
|
|
84
67
|
|
|
85
|
-
/// Loads every commit fact from the
|
|
68
|
+
/// Loads every commit fact from the commit store.
|
|
86
69
|
///
|
|
87
70
|
/// This is used by global commit surfaces where the caller wants the durable
|
|
88
71
|
/// graph facts themselves, not reachability from a particular version head.
|
|
89
72
|
pub(crate) async fn all_commits(&mut self) -> Result<Vec<CommitGraphCommit>, LixError> {
|
|
90
|
-
let
|
|
91
|
-
.changelog_reader
|
|
92
|
-
.scan_changes(&crate::changelog::ChangelogScanRequest { limit: None })
|
|
93
|
-
.await?;
|
|
73
|
+
let stored_commits = self.commit_store_reader.scan_commits().await?;
|
|
94
74
|
let mut commits = Vec::new();
|
|
95
|
-
for
|
|
96
|
-
.
|
|
97
|
-
.filter(|change| change.schema_key == COMMIT_SCHEMA_KEY)
|
|
98
|
-
{
|
|
99
|
-
commits.push(parse_commit_change(self.materialize_change(change).await?)?);
|
|
75
|
+
for commit in stored_commits {
|
|
76
|
+
commits.push(self.graph_commit_from_store_commit(commit).await?);
|
|
100
77
|
}
|
|
101
78
|
commits.sort_by(|left, right| left.commit_id.cmp(&right.commit_id));
|
|
102
79
|
Ok(commits)
|
|
@@ -160,48 +137,17 @@ where
|
|
|
160
137
|
commits
|
|
161
138
|
.iter()
|
|
162
139
|
.flat_map(|commit| {
|
|
163
|
-
commit
|
|
164
|
-
|
|
165
|
-
.iter()
|
|
166
|
-
.map(|parent_commit_id| CommitGraphEdge {
|
|
140
|
+
commit.parent_commit_ids.iter().enumerate().map(
|
|
141
|
+
|(parent_order, parent_commit_id)| CommitGraphEdge {
|
|
167
142
|
parent_commit_id: parent_commit_id.clone(),
|
|
168
143
|
child_commit_id: commit.commit_id.clone(),
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/// Derives one change-set row for each parsed commit.
|
|
175
|
-
pub(crate) fn change_sets(&self, commits: &[CommitGraphCommit]) -> Vec<CommitGraphChangeSet> {
|
|
176
|
-
commits
|
|
177
|
-
.iter()
|
|
178
|
-
.map(|commit| CommitGraphChangeSet {
|
|
179
|
-
id: commit.change_set_id.clone(),
|
|
180
|
-
commit_id: commit.commit_id.clone(),
|
|
144
|
+
parent_order: parent_order as u32,
|
|
145
|
+
},
|
|
146
|
+
)
|
|
181
147
|
})
|
|
182
148
|
.collect()
|
|
183
149
|
}
|
|
184
150
|
|
|
185
|
-
/// Loads the canonical changes introduced/adopted by each commit's change set.
|
|
186
|
-
pub(crate) async fn change_set_elements(
|
|
187
|
-
&mut self,
|
|
188
|
-
commits: &[CommitGraphCommit],
|
|
189
|
-
) -> Result<Vec<CommitGraphChangeSetElement>, LixError> {
|
|
190
|
-
let mut elements = Vec::new();
|
|
191
|
-
for commit in commits {
|
|
192
|
-
for change_id in &commit.change_ids {
|
|
193
|
-
let change = self
|
|
194
|
-
.load_member_change(change_id, &commit.commit_id)
|
|
195
|
-
.await?;
|
|
196
|
-
elements.push(CommitGraphChangeSetElement {
|
|
197
|
-
change_set_id: commit.change_set_id.clone(),
|
|
198
|
-
change,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
Ok(elements)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
151
|
/// Returns canonical changes reachable from `start_commit_id`.
|
|
206
152
|
///
|
|
207
153
|
/// This is the primitive history API. It reports the commit/depth where
|
|
@@ -229,9 +175,9 @@ where
|
|
|
229
175
|
let change = self
|
|
230
176
|
.load_member_canonical_change(&change_id, &commit_id)
|
|
231
177
|
.await?;
|
|
232
|
-
if change_matches_history_request(&change, request) {
|
|
178
|
+
if change_matches_history_request(&change.record, request) {
|
|
233
179
|
entries.push(CommitGraphChangeHistoryEntry {
|
|
234
|
-
change,
|
|
180
|
+
located_change: change,
|
|
235
181
|
observed_commit_id: commit_id.clone(),
|
|
236
182
|
start_commit_id: start_commit_id.to_string(),
|
|
237
183
|
depth: reachable.depth,
|
|
@@ -247,10 +193,13 @@ where
|
|
|
247
193
|
&mut self,
|
|
248
194
|
change_id: &str,
|
|
249
195
|
source_commit_id: &str,
|
|
250
|
-
) -> Result<
|
|
251
|
-
|
|
252
|
-
|
|
196
|
+
) -> Result<LocatedChange, LixError> {
|
|
197
|
+
let change_ids = vec![change_id.to_string()];
|
|
198
|
+
self.load_canonical_changes(&change_ids)
|
|
253
199
|
.await?
|
|
200
|
+
.into_iter()
|
|
201
|
+
.next()
|
|
202
|
+
.flatten()
|
|
254
203
|
.ok_or_else(|| {
|
|
255
204
|
LixError::new(
|
|
256
205
|
"LIX_ERROR_UNKNOWN",
|
|
@@ -261,95 +210,58 @@ where
|
|
|
261
210
|
})
|
|
262
211
|
}
|
|
263
212
|
|
|
264
|
-
|
|
265
|
-
async fn select_entities(
|
|
213
|
+
async fn graph_commit_from_store_commit(
|
|
266
214
|
&mut self,
|
|
267
|
-
|
|
268
|
-
) -> Result<
|
|
269
|
-
let
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
&
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
for change_id in reachable.commit.change_ids.iter().rev() {
|
|
285
|
-
let change = self
|
|
286
|
-
.load_member_change(change_id, &source_commit_id)
|
|
287
|
-
.await?;
|
|
288
|
-
observe_change(
|
|
289
|
-
&mut order,
|
|
290
|
-
&mut entities,
|
|
291
|
-
change,
|
|
292
|
-
source_commit_id.clone(),
|
|
293
|
-
depth,
|
|
294
|
-
);
|
|
295
|
-
}
|
|
215
|
+
commit: Commit,
|
|
216
|
+
) -> Result<CommitGraphCommit, LixError> {
|
|
217
|
+
let change_ids = self.load_commit_change_ids(&commit).await?;
|
|
218
|
+
Ok(commit_graph_commit_from_store_commit(commit, change_ids)?)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async fn load_commit_change_ids(&self, commit: &Commit) -> Result<Vec<String>, LixError> {
|
|
222
|
+
let mut change_ids = Vec::new();
|
|
223
|
+
for pack_id in 0..commit.change_pack_count {
|
|
224
|
+
let Some(changes) = self
|
|
225
|
+
.commit_store_reader
|
|
226
|
+
.load_change_pack(&commit.id, pack_id)
|
|
227
|
+
.await?
|
|
228
|
+
else {
|
|
229
|
+
return Err(missing_pack_error("change", &commit.id, pack_id));
|
|
230
|
+
};
|
|
231
|
+
change_ids.extend(changes.into_iter().map(|change| change.id));
|
|
296
232
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
async fn load_member_change(
|
|
309
|
-
&mut self,
|
|
310
|
-
change_id: &str,
|
|
311
|
-
source_commit_id: &str,
|
|
312
|
-
) -> Result<MaterializedCanonicalChange, LixError> {
|
|
313
|
-
let change = self
|
|
314
|
-
.changelog_reader
|
|
315
|
-
.load_change(change_id)
|
|
316
|
-
.await?
|
|
317
|
-
.ok_or_else(|| {
|
|
318
|
-
LixError::new(
|
|
319
|
-
"LIX_ERROR_UNKNOWN",
|
|
320
|
-
format!(
|
|
321
|
-
"commit_graph commit '{source_commit_id}' references missing change '{change_id}'"
|
|
322
|
-
),
|
|
323
|
-
)
|
|
324
|
-
})?;
|
|
325
|
-
self.materialize_change(change).await
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async fn materialize_change(
|
|
329
|
-
&mut self,
|
|
330
|
-
change: CanonicalChange,
|
|
331
|
-
) -> Result<MaterializedCanonicalChange, LixError> {
|
|
332
|
-
materialize_change(&mut self.json_reader, change).await
|
|
233
|
+
for pack_id in 0..commit.membership_pack_count {
|
|
234
|
+
let Some(members) = self
|
|
235
|
+
.commit_store_reader
|
|
236
|
+
.load_membership_pack(&commit.id, pack_id)
|
|
237
|
+
.await?
|
|
238
|
+
else {
|
|
239
|
+
return Err(missing_pack_error("membership", &commit.id, pack_id));
|
|
240
|
+
};
|
|
241
|
+
change_ids.extend(members.into_iter().map(|locator| locator.change_id));
|
|
242
|
+
}
|
|
243
|
+
Ok(change_ids)
|
|
333
244
|
}
|
|
334
245
|
|
|
335
|
-
async fn
|
|
336
|
-
&
|
|
337
|
-
|
|
338
|
-
) -> Result<Option<
|
|
339
|
-
|
|
340
|
-
.
|
|
341
|
-
.
|
|
342
|
-
.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
246
|
+
async fn load_canonical_changes(
|
|
247
|
+
&self,
|
|
248
|
+
change_ids: &[String],
|
|
249
|
+
) -> Result<Vec<Option<LocatedChange>>, LixError> {
|
|
250
|
+
self.commit_store_reader
|
|
251
|
+
.load_located_changes(change_ids)
|
|
252
|
+
.await
|
|
253
|
+
.map(|changes| {
|
|
254
|
+
changes
|
|
255
|
+
.into_iter()
|
|
256
|
+
.map(|located| {
|
|
257
|
+
located.map(|located| LocatedChange {
|
|
258
|
+
record: canonical_change_from_store_change(located.record),
|
|
259
|
+
source_commit_id: located.source_commit_id,
|
|
260
|
+
source_pack_id: located.source_pack_id,
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
.collect()
|
|
264
|
+
})
|
|
353
265
|
}
|
|
354
266
|
}
|
|
355
267
|
|
|
@@ -396,17 +308,6 @@ where
|
|
|
396
308
|
CommitGraphStoreReader::commit_edges(self, commits)
|
|
397
309
|
}
|
|
398
310
|
|
|
399
|
-
fn change_sets(&self, commits: &[CommitGraphCommit]) -> Vec<CommitGraphChangeSet> {
|
|
400
|
-
CommitGraphStoreReader::change_sets(self, commits)
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
async fn change_set_elements(
|
|
404
|
-
&mut self,
|
|
405
|
-
commits: &[CommitGraphCommit],
|
|
406
|
-
) -> Result<Vec<CommitGraphChangeSetElement>, LixError> {
|
|
407
|
-
CommitGraphStoreReader::change_set_elements(self, commits).await
|
|
408
|
-
}
|
|
409
|
-
|
|
410
311
|
async fn change_history_from_commit(
|
|
411
312
|
&mut self,
|
|
412
313
|
start_commit_id: &str,
|
|
@@ -422,7 +323,7 @@ fn depth_matches(depth: u32, request: &CommitGraphChangeHistoryRequest) -> bool
|
|
|
422
323
|
}
|
|
423
324
|
|
|
424
325
|
fn change_matches_history_request(
|
|
425
|
-
change: &
|
|
326
|
+
change: &Change,
|
|
426
327
|
request: &CommitGraphChangeHistoryRequest,
|
|
427
328
|
) -> bool {
|
|
428
329
|
(request.include_tombstones || change.snapshot_ref.is_some())
|
|
@@ -435,202 +336,70 @@ fn change_matches_history_request(
|
|
|
435
336
|
.is_some_and(|file_id| request.file_ids.contains(file_id)))
|
|
436
337
|
}
|
|
437
338
|
|
|
438
|
-
fn
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
change: MaterializedCanonicalChange,
|
|
442
|
-
source_commit_id: String,
|
|
443
|
-
depth: u32,
|
|
444
|
-
) {
|
|
445
|
-
let identity = CanonicalEntityIdentity::from_change(&change);
|
|
446
|
-
if let Some(accumulator) = entities.get_mut(&identity) {
|
|
447
|
-
// TODO: represent unresolved parent-parent merge conflicts instead of
|
|
448
|
-
// collapsing them through deterministic traversal order. A head commit
|
|
449
|
-
// change for the same identity should remain the explicit resolution.
|
|
450
|
-
accumulator.entity.created_at = change.created_at.clone();
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
order.push(identity.clone());
|
|
455
|
-
entities.insert(
|
|
456
|
-
identity,
|
|
457
|
-
EntityAccumulator {
|
|
458
|
-
entity: CommitGraphEntity {
|
|
459
|
-
created_at: change.created_at.clone(),
|
|
460
|
-
updated_at: change.created_at.clone(),
|
|
461
|
-
change,
|
|
462
|
-
source_commit_id,
|
|
463
|
-
depth,
|
|
464
|
-
},
|
|
465
|
-
},
|
|
466
|
-
);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
#[derive(Debug)]
|
|
470
|
-
struct EntityAccumulator {
|
|
471
|
-
entity: CommitGraphEntity,
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
475
|
-
struct CanonicalEntityIdentity {
|
|
476
|
-
entity_id: EntityIdentity,
|
|
477
|
-
schema_key: String,
|
|
478
|
-
file_id: Option<String>,
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
impl CanonicalEntityIdentity {
|
|
482
|
-
fn from_change(change: &MaterializedCanonicalChange) -> Self {
|
|
483
|
-
Self {
|
|
484
|
-
entity_id: change.entity_id.clone(),
|
|
485
|
-
schema_key: change.schema_key.clone(),
|
|
486
|
-
file_id: change.file_id.clone(),
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
fn parse_commit_change(
|
|
492
|
-
change: crate::changelog::MaterializedCanonicalChange,
|
|
339
|
+
fn commit_graph_commit_from_store_commit(
|
|
340
|
+
commit: Commit,
|
|
341
|
+
change_ids: Vec<String>,
|
|
493
342
|
) -> Result<CommitGraphCommit, LixError> {
|
|
494
|
-
let
|
|
495
|
-
if change.schema_key != COMMIT_SCHEMA_KEY {
|
|
496
|
-
return Err(LixError::new(
|
|
497
|
-
"LIX_ERROR_UNKNOWN",
|
|
498
|
-
format!(
|
|
499
|
-
"commit_graph expected schema_key '{COMMIT_SCHEMA_KEY}' but got '{}'",
|
|
500
|
-
change.schema_key
|
|
501
|
-
),
|
|
502
|
-
));
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
let snapshot_content = change.snapshot_content.as_deref().ok_or_else(|| {
|
|
506
|
-
LixError::new(
|
|
507
|
-
"LIX_ERROR_UNKNOWN",
|
|
508
|
-
format!(
|
|
509
|
-
"commit_graph commit '{}' is missing snapshot_content",
|
|
510
|
-
change_entity_id
|
|
511
|
-
),
|
|
512
|
-
)
|
|
513
|
-
})?;
|
|
514
|
-
let snapshot =
|
|
515
|
-
serde_json::from_str::<serde_json::Value>(snapshot_content).map_err(|error| {
|
|
516
|
-
LixError::new(
|
|
517
|
-
"LIX_ERROR_UNKNOWN",
|
|
518
|
-
format!(
|
|
519
|
-
"commit_graph commit '{}' snapshot_content is invalid JSON: {error}",
|
|
520
|
-
change_entity_id
|
|
521
|
-
),
|
|
522
|
-
)
|
|
523
|
-
})?;
|
|
524
|
-
|
|
525
|
-
let commit_id = required_string(&snapshot, "id", &change_entity_id)?;
|
|
526
|
-
if commit_id != change_entity_id {
|
|
527
|
-
return Err(LixError::new(
|
|
528
|
-
"LIX_ERROR_UNKNOWN",
|
|
529
|
-
format!(
|
|
530
|
-
"commit_graph commit change entity_id '{}' does not match snapshot id '{}'",
|
|
531
|
-
change_entity_id, commit_id
|
|
532
|
-
),
|
|
533
|
-
));
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
let change_ids = required_string_array(&snapshot, "change_ids", &change_entity_id)?;
|
|
537
|
-
let author_account_ids =
|
|
538
|
-
optional_string_array(&snapshot, "author_account_ids", &change_entity_id)?;
|
|
539
|
-
let parent_commit_ids =
|
|
540
|
-
required_string_array(&snapshot, "parent_commit_ids", &change_entity_id)?;
|
|
541
|
-
let change_set_id = required_string(&snapshot, "change_set_id", &change_entity_id)?;
|
|
542
|
-
|
|
343
|
+
let change = commit_header_canonical_change(commit.clone());
|
|
543
344
|
Ok(CommitGraphCommit {
|
|
345
|
+
canonical_change: change.clone(),
|
|
544
346
|
change,
|
|
545
|
-
commit_id,
|
|
546
|
-
change_set_id,
|
|
347
|
+
commit_id: commit.id,
|
|
547
348
|
change_ids,
|
|
548
|
-
author_account_ids,
|
|
549
|
-
parent_commit_ids,
|
|
349
|
+
author_account_ids: commit.author_account_ids,
|
|
350
|
+
parent_commit_ids: commit.parent_ids,
|
|
550
351
|
})
|
|
551
352
|
}
|
|
552
353
|
|
|
553
|
-
fn
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
)
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
.ok_or_else(|| {
|
|
564
|
-
LixError::new(
|
|
565
|
-
"LIX_ERROR_UNKNOWN",
|
|
566
|
-
format!("commit_graph commit '{commit_id}' requires string field '{field}'"),
|
|
567
|
-
)
|
|
568
|
-
})
|
|
354
|
+
fn commit_header_canonical_change(commit: Commit) -> Change {
|
|
355
|
+
Change {
|
|
356
|
+
id: commit.change_id,
|
|
357
|
+
entity_id: EntityIdentity::single(&commit.id),
|
|
358
|
+
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
359
|
+
file_id: None,
|
|
360
|
+
snapshot_ref: None,
|
|
361
|
+
metadata_ref: None,
|
|
362
|
+
created_at: commit.created_at,
|
|
363
|
+
}
|
|
569
364
|
}
|
|
570
365
|
|
|
571
|
-
fn
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
.
|
|
578
|
-
.
|
|
579
|
-
.
|
|
580
|
-
|
|
581
|
-
"LIX_ERROR_UNKNOWN",
|
|
582
|
-
format!("commit_graph commit '{commit_id}' requires array field '{field}'"),
|
|
583
|
-
)
|
|
584
|
-
})?;
|
|
585
|
-
|
|
586
|
-
values
|
|
587
|
-
.iter()
|
|
588
|
-
.map(|value| {
|
|
589
|
-
value.as_str().filter(|value| !value.is_empty()).map(str::to_string).ok_or_else(|| {
|
|
590
|
-
LixError::new(
|
|
591
|
-
"LIX_ERROR_UNKNOWN",
|
|
592
|
-
format!(
|
|
593
|
-
"commit_graph commit '{commit_id}' field '{field}' must contain only non-empty strings"
|
|
594
|
-
),
|
|
595
|
-
)
|
|
596
|
-
})
|
|
597
|
-
})
|
|
598
|
-
.collect()
|
|
366
|
+
fn canonical_change_from_store_change(change: Change) -> Change {
|
|
367
|
+
Change {
|
|
368
|
+
id: change.id,
|
|
369
|
+
entity_id: change.entity_id,
|
|
370
|
+
schema_key: change.schema_key,
|
|
371
|
+
file_id: change.file_id,
|
|
372
|
+
snapshot_ref: change.snapshot_ref,
|
|
373
|
+
metadata_ref: change.metadata_ref,
|
|
374
|
+
created_at: change.created_at,
|
|
375
|
+
}
|
|
599
376
|
}
|
|
600
377
|
|
|
601
|
-
fn
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
)
|
|
606
|
-
match snapshot.get(field) {
|
|
607
|
-
Some(_) => required_string_array(snapshot, field, commit_id),
|
|
608
|
-
None => Ok(Vec::new()),
|
|
609
|
-
}
|
|
378
|
+
fn missing_pack_error(label: &str, commit_id: &str, pack_id: u32) -> LixError {
|
|
379
|
+
LixError::new(
|
|
380
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
381
|
+
format!("commit_graph missing {label} pack ({commit_id}, {pack_id})"),
|
|
382
|
+
)
|
|
610
383
|
}
|
|
611
384
|
|
|
612
385
|
#[cfg(test)]
|
|
613
386
|
mod tests {
|
|
387
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
614
388
|
use std::sync::Arc;
|
|
615
389
|
|
|
616
|
-
use serde_json::json;
|
|
617
|
-
|
|
618
390
|
use crate::backend::testing::UnitTestBackend;
|
|
619
|
-
use crate::changelog::{
|
|
620
|
-
canonicalize_materialized_change, ChangelogContext, MaterializedCanonicalChange,
|
|
621
|
-
};
|
|
622
391
|
use crate::commit_graph::{CommitGraphChangeHistoryRequest, CommitGraphContext};
|
|
623
|
-
use crate::
|
|
392
|
+
use crate::commit_store::{
|
|
393
|
+
Change, ChangeLocator, ChangeRef, CommitDraftRef, CommitStoreContext,
|
|
394
|
+
};
|
|
624
395
|
use crate::storage::{StorageContext, StorageWriteSet};
|
|
625
396
|
|
|
626
397
|
#[tokio::test]
|
|
627
398
|
async fn load_commit_parses_commit_snapshot() {
|
|
628
399
|
let backend = Arc::new(UnitTestBackend::new());
|
|
629
400
|
let storage = StorageContext::new(backend.clone());
|
|
630
|
-
let changelog = ChangelogContext::new();
|
|
631
401
|
append_changes(
|
|
632
402
|
storage.clone(),
|
|
633
|
-
&changelog,
|
|
634
403
|
&[commit_change(
|
|
635
404
|
"commit-1-change",
|
|
636
405
|
"commit-1",
|
|
@@ -640,7 +409,7 @@ mod tests {
|
|
|
640
409
|
)
|
|
641
410
|
.await;
|
|
642
411
|
|
|
643
|
-
let graph = CommitGraphContext::new(
|
|
412
|
+
let graph = CommitGraphContext::new();
|
|
644
413
|
let mut reader = graph.reader(storage);
|
|
645
414
|
let commit = reader
|
|
646
415
|
.load_commit("commit-1")
|
|
@@ -649,7 +418,6 @@ mod tests {
|
|
|
649
418
|
.expect("commit should exist");
|
|
650
419
|
|
|
651
420
|
assert_eq!(commit.commit_id, "commit-1");
|
|
652
|
-
assert_eq!(commit.change_set_id, "change-set-1");
|
|
653
421
|
assert_eq!(commit.change_ids, vec!["change-1", "change-2"]);
|
|
654
422
|
assert_eq!(commit.parent_commit_ids, vec!["parent-1"]);
|
|
655
423
|
assert_eq!(commit.change.id, "commit-1-change");
|
|
@@ -659,7 +427,7 @@ mod tests {
|
|
|
659
427
|
async fn load_commit_returns_none_for_missing_commit() {
|
|
660
428
|
let backend = Arc::new(UnitTestBackend::new());
|
|
661
429
|
let storage = StorageContext::new(backend.clone());
|
|
662
|
-
let graph = CommitGraphContext::new(
|
|
430
|
+
let graph = CommitGraphContext::new();
|
|
663
431
|
let mut reader = graph.reader(storage);
|
|
664
432
|
|
|
665
433
|
let commit = reader
|
|
@@ -670,45 +438,12 @@ mod tests {
|
|
|
670
438
|
assert_eq!(commit, None);
|
|
671
439
|
}
|
|
672
440
|
|
|
673
|
-
#[tokio::test]
|
|
674
|
-
async fn load_commit_rejects_malformed_snapshot() {
|
|
675
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
676
|
-
let storage = StorageContext::new(backend.clone());
|
|
677
|
-
let changelog = ChangelogContext::new();
|
|
678
|
-
append_changes(
|
|
679
|
-
storage.clone(),
|
|
680
|
-
&changelog,
|
|
681
|
-
&[MaterializedCanonicalChange {
|
|
682
|
-
id: "commit-1-change".to_string(),
|
|
683
|
-
entity_id: crate::entity_identity::EntityIdentity::single("commit-1"),
|
|
684
|
-
schema_key: super::COMMIT_SCHEMA_KEY.to_string(),
|
|
685
|
-
schema_version: "1".to_string(),
|
|
686
|
-
file_id: None,
|
|
687
|
-
snapshot_content: Some("{\"id\":\"commit-1\"}".to_string()),
|
|
688
|
-
metadata: None,
|
|
689
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
690
|
-
}],
|
|
691
|
-
)
|
|
692
|
-
.await;
|
|
693
|
-
|
|
694
|
-
let graph = CommitGraphContext::new(changelog);
|
|
695
|
-
let mut reader = graph.reader(storage);
|
|
696
|
-
let error = reader
|
|
697
|
-
.load_commit("commit-1")
|
|
698
|
-
.await
|
|
699
|
-
.expect_err("malformed commit should fail");
|
|
700
|
-
|
|
701
|
-
assert!(error.message.contains("change_ids"));
|
|
702
|
-
}
|
|
703
|
-
|
|
704
441
|
#[tokio::test]
|
|
705
442
|
async fn all_commits_returns_parsed_commits_sorted_by_id() {
|
|
706
443
|
let backend = Arc::new(UnitTestBackend::new());
|
|
707
444
|
let storage = StorageContext::new(backend.clone());
|
|
708
|
-
let changelog = ChangelogContext::new();
|
|
709
445
|
append_changes(
|
|
710
446
|
storage.clone(),
|
|
711
|
-
&changelog,
|
|
712
447
|
&[
|
|
713
448
|
commit_change("commit-b-change", "commit-b", &[], &[]),
|
|
714
449
|
entity_change("change-1", "entity-1", "example", "{}"),
|
|
@@ -717,7 +452,7 @@ mod tests {
|
|
|
717
452
|
)
|
|
718
453
|
.await;
|
|
719
454
|
|
|
720
|
-
let graph = CommitGraphContext::new(
|
|
455
|
+
let graph = CommitGraphContext::new();
|
|
721
456
|
let mut reader = graph.reader(storage);
|
|
722
457
|
let commits = reader
|
|
723
458
|
.all_commits()
|
|
@@ -733,53 +468,9 @@ mod tests {
|
|
|
733
468
|
);
|
|
734
469
|
}
|
|
735
470
|
|
|
736
|
-
#[tokio::test]
|
|
737
|
-
async fn entities_at_walks_ancestors_and_computes_nearest_depth() {
|
|
738
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
739
|
-
let storage = StorageContext::new(backend.clone());
|
|
740
|
-
let changelog = ChangelogContext::new();
|
|
741
|
-
append_changes(
|
|
742
|
-
storage.clone(),
|
|
743
|
-
&changelog,
|
|
744
|
-
&[
|
|
745
|
-
commit_change("commit-root-change", "commit-root", &[], &[]),
|
|
746
|
-
commit_change("commit-left-change", "commit-left", &[], &["commit-root"]),
|
|
747
|
-
commit_change("commit-right-change", "commit-right", &[], &["commit-root"]),
|
|
748
|
-
commit_change(
|
|
749
|
-
"commit-head-change",
|
|
750
|
-
"commit-head",
|
|
751
|
-
&[],
|
|
752
|
-
&["commit-left", "commit-right"],
|
|
753
|
-
),
|
|
754
|
-
],
|
|
755
|
-
)
|
|
756
|
-
.await;
|
|
757
|
-
|
|
758
|
-
let graph = CommitGraphContext::new(changelog);
|
|
759
|
-
let mut reader = graph.reader(storage);
|
|
760
|
-
let entities = reader
|
|
761
|
-
.entities_at("commit-head")
|
|
762
|
-
.await
|
|
763
|
-
.expect("ancestor traversal should succeed");
|
|
764
|
-
|
|
765
|
-
let depths = entities
|
|
766
|
-
.into_iter()
|
|
767
|
-
.map(|entity| (entity.source_commit_id, entity.depth))
|
|
768
|
-
.collect::<Vec<_>>();
|
|
769
|
-
assert_eq!(
|
|
770
|
-
depths,
|
|
771
|
-
vec![
|
|
772
|
-
("commit-head".to_string(), 0),
|
|
773
|
-
("commit-left".to_string(), 1),
|
|
774
|
-
("commit-right".to_string(), 1),
|
|
775
|
-
("commit-root".to_string(), 2),
|
|
776
|
-
]
|
|
777
|
-
);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
471
|
#[tokio::test]
|
|
781
472
|
async fn commit_edges_are_derived_from_parent_commit_ids() {
|
|
782
|
-
let graph = CommitGraphContext::new(
|
|
473
|
+
let graph = CommitGraphContext::new();
|
|
783
474
|
let reader = graph.reader(StorageContext::new(Arc::new(UnitTestBackend::new())));
|
|
784
475
|
let commits = vec![parsed_commit(
|
|
785
476
|
"commit-head",
|
|
@@ -794,76 +485,23 @@ mod tests {
|
|
|
794
485
|
.iter()
|
|
795
486
|
.map(|edge| (
|
|
796
487
|
edge.parent_commit_id.as_str(),
|
|
797
|
-
edge.child_commit_id.as_str()
|
|
488
|
+
edge.child_commit_id.as_str(),
|
|
489
|
+
edge.parent_order,
|
|
798
490
|
))
|
|
799
491
|
.collect::<Vec<_>>(),
|
|
800
492
|
vec![
|
|
801
|
-
("commit-left", "commit-head"),
|
|
802
|
-
("commit-right", "commit-head")
|
|
493
|
+
("commit-left", "commit-head", 0),
|
|
494
|
+
("commit-right", "commit-head", 1)
|
|
803
495
|
]
|
|
804
496
|
);
|
|
805
497
|
}
|
|
806
498
|
|
|
807
|
-
#[tokio::test]
|
|
808
|
-
async fn change_sets_are_derived_from_commits() {
|
|
809
|
-
let graph = CommitGraphContext::new(ChangelogContext::new());
|
|
810
|
-
let reader = graph.reader(StorageContext::new(Arc::new(UnitTestBackend::new())));
|
|
811
|
-
let commits = vec![parsed_commit("commit-1", &[], &[])];
|
|
812
|
-
|
|
813
|
-
let change_sets = reader.change_sets(&commits);
|
|
814
|
-
|
|
815
|
-
assert_eq!(change_sets.len(), 1);
|
|
816
|
-
assert_eq!(change_sets[0].id, "change-set-1");
|
|
817
|
-
assert_eq!(change_sets[0].commit_id, "commit-1");
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
#[tokio::test]
|
|
821
|
-
async fn change_set_elements_load_member_changes() {
|
|
822
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
823
|
-
let storage = StorageContext::new(backend.clone());
|
|
824
|
-
let changelog = ChangelogContext::new();
|
|
825
|
-
append_changes(
|
|
826
|
-
storage.clone(),
|
|
827
|
-
&changelog,
|
|
828
|
-
&[
|
|
829
|
-
entity_change("change-1", "entity-1", "example", "{}"),
|
|
830
|
-
commit_change("commit-1-change", "commit-1", &["change-1"], &[]),
|
|
831
|
-
],
|
|
832
|
-
)
|
|
833
|
-
.await;
|
|
834
|
-
|
|
835
|
-
let graph = CommitGraphContext::new(changelog);
|
|
836
|
-
let mut reader = graph.reader(storage);
|
|
837
|
-
let commits = reader
|
|
838
|
-
.all_commits()
|
|
839
|
-
.await
|
|
840
|
-
.expect("commit scan should succeed");
|
|
841
|
-
let elements = reader
|
|
842
|
-
.change_set_elements(&commits)
|
|
843
|
-
.await
|
|
844
|
-
.expect("change-set elements should load");
|
|
845
|
-
|
|
846
|
-
assert_eq!(elements.len(), 1);
|
|
847
|
-
assert_eq!(elements[0].change_set_id, "change-set-1");
|
|
848
|
-
assert_eq!(elements[0].change.id, "change-1");
|
|
849
|
-
assert_eq!(
|
|
850
|
-
elements[0]
|
|
851
|
-
.change
|
|
852
|
-
.entity_id
|
|
853
|
-
.as_string()
|
|
854
|
-
.expect("entity id should project"),
|
|
855
|
-
"entity-1"
|
|
856
|
-
);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
499
|
#[tokio::test]
|
|
860
500
|
async fn change_history_from_commit_reports_matching_canonical_changes_with_depth() {
|
|
861
501
|
let backend = Arc::new(UnitTestBackend::new());
|
|
862
502
|
let storage = StorageContext::new(backend.clone());
|
|
863
|
-
let changelog = ChangelogContext::new();
|
|
864
503
|
append_changes(
|
|
865
504
|
storage.clone(),
|
|
866
|
-
&changelog,
|
|
867
505
|
&[
|
|
868
506
|
entity_change("change-root", "entity-root", "test_schema", "{}"),
|
|
869
507
|
entity_change("change-head", "entity-head", "test_schema", "{}"),
|
|
@@ -878,7 +516,7 @@ mod tests {
|
|
|
878
516
|
)
|
|
879
517
|
.await;
|
|
880
518
|
|
|
881
|
-
let graph = CommitGraphContext::new(
|
|
519
|
+
let graph = CommitGraphContext::new();
|
|
882
520
|
let mut reader = graph.reader(storage);
|
|
883
521
|
let history = reader
|
|
884
522
|
.change_history_from_commit(
|
|
@@ -896,7 +534,7 @@ mod tests {
|
|
|
896
534
|
history
|
|
897
535
|
.iter()
|
|
898
536
|
.map(|entry| (
|
|
899
|
-
entry.
|
|
537
|
+
entry.located_change.record.id.as_str(),
|
|
900
538
|
entry.observed_commit_id.as_str(),
|
|
901
539
|
entry.start_commit_id.as_str(),
|
|
902
540
|
entry.depth
|
|
@@ -913,10 +551,8 @@ mod tests {
|
|
|
913
551
|
async fn change_history_from_commit_filters_depth_entity_file_and_tombstones() {
|
|
914
552
|
let backend = Arc::new(UnitTestBackend::new());
|
|
915
553
|
let storage = StorageContext::new(backend.clone());
|
|
916
|
-
let changelog = ChangelogContext::new();
|
|
917
554
|
append_changes(
|
|
918
555
|
storage.clone(),
|
|
919
|
-
&changelog,
|
|
920
556
|
&[
|
|
921
557
|
entity_change_with_file(
|
|
922
558
|
"change-file-a",
|
|
@@ -944,7 +580,7 @@ mod tests {
|
|
|
944
580
|
)
|
|
945
581
|
.await;
|
|
946
582
|
|
|
947
|
-
let graph = CommitGraphContext::new(
|
|
583
|
+
let graph = CommitGraphContext::new();
|
|
948
584
|
let mut reader = graph.reader(storage);
|
|
949
585
|
let history = reader
|
|
950
586
|
.change_history_from_commit(
|
|
@@ -962,7 +598,7 @@ mod tests {
|
|
|
962
598
|
.expect("history should resolve");
|
|
963
599
|
|
|
964
600
|
assert_eq!(history.len(), 1);
|
|
965
|
-
assert_eq!(history[0].
|
|
601
|
+
assert_eq!(history[0].located_change.record.id, "change-file-a");
|
|
966
602
|
assert_eq!(history[0].depth, 1);
|
|
967
603
|
}
|
|
968
604
|
|
|
@@ -970,10 +606,8 @@ mod tests {
|
|
|
970
606
|
async fn change_history_from_commit_includes_tombstones_when_requested() {
|
|
971
607
|
let backend = Arc::new(UnitTestBackend::new());
|
|
972
608
|
let storage = StorageContext::new(backend.clone());
|
|
973
|
-
let changelog = ChangelogContext::new();
|
|
974
609
|
append_changes(
|
|
975
610
|
storage.clone(),
|
|
976
|
-
&changelog,
|
|
977
611
|
&[
|
|
978
612
|
entity_tombstone("change-deleted", "entity-1", "test_schema"),
|
|
979
613
|
commit_change(
|
|
@@ -986,7 +620,7 @@ mod tests {
|
|
|
986
620
|
)
|
|
987
621
|
.await;
|
|
988
622
|
|
|
989
|
-
let graph = CommitGraphContext::new(
|
|
623
|
+
let graph = CommitGraphContext::new();
|
|
990
624
|
let mut reader = graph.reader(storage);
|
|
991
625
|
let hidden = reader
|
|
992
626
|
.change_history_from_commit("commit-head", &CommitGraphChangeHistoryRequest::default())
|
|
@@ -1005,453 +639,151 @@ mod tests {
|
|
|
1005
639
|
|
|
1006
640
|
assert!(hidden.is_empty());
|
|
1007
641
|
assert_eq!(visible.len(), 1);
|
|
1008
|
-
assert_eq!(visible[0].
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
#[
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
"
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
),
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
.await;
|
|
1042
|
-
|
|
1043
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1044
|
-
let mut reader = graph.reader(storage);
|
|
1045
|
-
let entities = reader
|
|
1046
|
-
.entities_at("commit-head")
|
|
1047
|
-
.await
|
|
1048
|
-
.expect("entities should resolve");
|
|
1049
|
-
|
|
1050
|
-
assert_eq!(
|
|
1051
|
-
entity_ids_for_schema(&entities, "test_schema"),
|
|
1052
|
-
vec![("change-new".to_string(), "commit-head".to_string(), 0)]
|
|
1053
|
-
);
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
#[tokio::test]
|
|
1057
|
-
async fn entities_at_reports_created_at_from_oldest_reachable_change() {
|
|
1058
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1059
|
-
let storage = StorageContext::new(backend.clone());
|
|
1060
|
-
let changelog = ChangelogContext::new();
|
|
1061
|
-
append_changes(
|
|
1062
|
-
storage.clone(),
|
|
1063
|
-
&changelog,
|
|
1064
|
-
&[
|
|
1065
|
-
entity_change_at(
|
|
1066
|
-
"change-created",
|
|
1067
|
-
"entity-1",
|
|
1068
|
-
"test_schema",
|
|
1069
|
-
"{\"value\":\"created\"}",
|
|
1070
|
-
"2026-01-01T00:00:00Z",
|
|
1071
|
-
),
|
|
1072
|
-
entity_change_at(
|
|
1073
|
-
"change-updated",
|
|
1074
|
-
"entity-1",
|
|
1075
|
-
"test_schema",
|
|
1076
|
-
"{\"value\":\"updated\"}",
|
|
1077
|
-
"2026-01-02T00:00:00Z",
|
|
1078
|
-
),
|
|
1079
|
-
commit_change(
|
|
1080
|
-
"commit-root-change",
|
|
1081
|
-
"commit-root",
|
|
1082
|
-
&["change-created"],
|
|
1083
|
-
&[],
|
|
1084
|
-
),
|
|
1085
|
-
commit_change(
|
|
1086
|
-
"commit-head-change",
|
|
1087
|
-
"commit-head",
|
|
1088
|
-
&["change-updated"],
|
|
1089
|
-
&["commit-root"],
|
|
1090
|
-
),
|
|
1091
|
-
],
|
|
1092
|
-
)
|
|
1093
|
-
.await;
|
|
1094
|
-
|
|
1095
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1096
|
-
let mut reader = graph.reader(storage);
|
|
1097
|
-
let entities = reader
|
|
1098
|
-
.entities_at("commit-head")
|
|
1099
|
-
.await
|
|
1100
|
-
.expect("entities should resolve");
|
|
1101
|
-
let entity = entities
|
|
1102
|
-
.iter()
|
|
1103
|
-
.find(|entity| entity.change.schema_key == "test_schema")
|
|
1104
|
-
.expect("test entity should resolve");
|
|
1105
|
-
|
|
1106
|
-
assert_eq!(entity.change.id, "change-updated");
|
|
1107
|
-
assert_eq!(entity.created_at, "2026-01-01T00:00:00Z");
|
|
1108
|
-
assert_eq!(entity.updated_at, "2026-01-02T00:00:00Z");
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
#[tokio::test]
|
|
1112
|
-
async fn entities_at_uses_reverse_change_order_within_commit() {
|
|
1113
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1114
|
-
let storage = StorageContext::new(backend.clone());
|
|
1115
|
-
let changelog = ChangelogContext::new();
|
|
1116
|
-
append_changes(
|
|
1117
|
-
storage.clone(),
|
|
1118
|
-
&changelog,
|
|
1119
|
-
&[
|
|
1120
|
-
entity_change(
|
|
1121
|
-
"change-first",
|
|
1122
|
-
"entity-1",
|
|
1123
|
-
"test_schema",
|
|
1124
|
-
"{\"value\":\"first\"}",
|
|
1125
|
-
),
|
|
1126
|
-
entity_change(
|
|
1127
|
-
"change-last",
|
|
1128
|
-
"entity-1",
|
|
1129
|
-
"test_schema",
|
|
1130
|
-
"{\"value\":\"last\"}",
|
|
1131
|
-
),
|
|
1132
|
-
commit_change(
|
|
1133
|
-
"commit-head-change",
|
|
1134
|
-
"commit-head",
|
|
1135
|
-
&["change-first", "change-last"],
|
|
1136
|
-
&[],
|
|
1137
|
-
),
|
|
1138
|
-
],
|
|
1139
|
-
)
|
|
1140
|
-
.await;
|
|
1141
|
-
|
|
1142
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1143
|
-
let mut reader = graph.reader(storage);
|
|
1144
|
-
let entities = reader
|
|
1145
|
-
.entities_at("commit-head")
|
|
1146
|
-
.await
|
|
1147
|
-
.expect("entities should resolve");
|
|
1148
|
-
|
|
1149
|
-
assert_eq!(
|
|
1150
|
-
entity_ids_for_schema(&entities, "test_schema"),
|
|
1151
|
-
vec![("change-last".to_string(), "commit-head".to_string(), 0)]
|
|
1152
|
-
);
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
#[tokio::test]
|
|
1156
|
-
async fn entities_at_head_change_overrides_both_merge_parents() {
|
|
1157
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1158
|
-
let storage = StorageContext::new(backend.clone());
|
|
1159
|
-
let changelog = ChangelogContext::new();
|
|
1160
|
-
append_changes(
|
|
1161
|
-
storage.clone(),
|
|
1162
|
-
&changelog,
|
|
1163
|
-
&[
|
|
1164
|
-
entity_change(
|
|
1165
|
-
"change-left",
|
|
1166
|
-
"entity-1",
|
|
1167
|
-
"test_schema",
|
|
1168
|
-
"{\"value\":\"left\"}",
|
|
1169
|
-
),
|
|
1170
|
-
entity_change(
|
|
1171
|
-
"change-right",
|
|
1172
|
-
"entity-1",
|
|
1173
|
-
"test_schema",
|
|
1174
|
-
"{\"value\":\"right\"}",
|
|
1175
|
-
),
|
|
1176
|
-
entity_change(
|
|
1177
|
-
"change-resolved",
|
|
1178
|
-
"entity-1",
|
|
1179
|
-
"test_schema",
|
|
1180
|
-
"{\"value\":\"resolved\"}",
|
|
1181
|
-
),
|
|
1182
|
-
commit_change("commit-left-change", "commit-left", &["change-left"], &[]),
|
|
1183
|
-
commit_change(
|
|
1184
|
-
"commit-right-change",
|
|
1185
|
-
"commit-right",
|
|
1186
|
-
&["change-right"],
|
|
1187
|
-
&[],
|
|
1188
|
-
),
|
|
1189
|
-
commit_change(
|
|
1190
|
-
"commit-head-change",
|
|
1191
|
-
"commit-head",
|
|
1192
|
-
&["change-resolved"],
|
|
1193
|
-
&["commit-left", "commit-right"],
|
|
1194
|
-
),
|
|
1195
|
-
],
|
|
1196
|
-
)
|
|
1197
|
-
.await;
|
|
1198
|
-
|
|
1199
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1200
|
-
let mut reader = graph.reader(storage);
|
|
1201
|
-
let entities = reader
|
|
1202
|
-
.entities_at("commit-head")
|
|
1203
|
-
.await
|
|
1204
|
-
.expect("entities should resolve");
|
|
1205
|
-
|
|
1206
|
-
assert_eq!(
|
|
1207
|
-
entity_ids_for_schema(&entities, "test_schema"),
|
|
1208
|
-
vec![("change-resolved".to_string(), "commit-head".to_string(), 0)]
|
|
1209
|
-
);
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
#[tokio::test]
|
|
1213
|
-
async fn entities_at_distinguishes_same_entity_with_different_file_id() {
|
|
1214
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1215
|
-
let storage = StorageContext::new(backend.clone());
|
|
1216
|
-
let changelog = ChangelogContext::new();
|
|
1217
|
-
append_changes(
|
|
1218
|
-
storage.clone(),
|
|
1219
|
-
&changelog,
|
|
1220
|
-
&[
|
|
1221
|
-
entity_change_with_file(
|
|
1222
|
-
"change-file-a",
|
|
1223
|
-
"entity-1",
|
|
1224
|
-
"test_schema",
|
|
1225
|
-
Some("file-a"),
|
|
1226
|
-
"{\"value\":\"file-a\"}",
|
|
1227
|
-
),
|
|
1228
|
-
entity_change_with_file(
|
|
1229
|
-
"change-file-b",
|
|
1230
|
-
"entity-1",
|
|
1231
|
-
"test_schema",
|
|
1232
|
-
Some("file-b"),
|
|
1233
|
-
"{\"value\":\"file-b\"}",
|
|
1234
|
-
),
|
|
1235
|
-
commit_change(
|
|
1236
|
-
"commit-head-change",
|
|
1237
|
-
"commit-head",
|
|
1238
|
-
&["change-file-a", "change-file-b"],
|
|
1239
|
-
&[],
|
|
1240
|
-
),
|
|
1241
|
-
],
|
|
1242
|
-
)
|
|
1243
|
-
.await;
|
|
1244
|
-
|
|
1245
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1246
|
-
let mut reader = graph.reader(storage);
|
|
1247
|
-
let entities = reader
|
|
1248
|
-
.entities_at("commit-head")
|
|
1249
|
-
.await
|
|
1250
|
-
.expect("entities should resolve");
|
|
1251
|
-
|
|
1252
|
-
assert_eq!(
|
|
1253
|
-
entity_ids_for_schema(&entities, "test_schema"),
|
|
1254
|
-
vec![
|
|
1255
|
-
("change-file-b".to_string(), "commit-head".to_string(), 0),
|
|
1256
|
-
("change-file-a".to_string(), "commit-head".to_string(), 0),
|
|
1257
|
-
]
|
|
1258
|
-
);
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
#[tokio::test]
|
|
1262
|
-
async fn entities_at_uses_latest_change_for_same_entity_identity() {
|
|
1263
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1264
|
-
let storage = StorageContext::new(backend.clone());
|
|
1265
|
-
let changelog = ChangelogContext::new();
|
|
1266
|
-
append_changes(
|
|
1267
|
-
storage.clone(),
|
|
1268
|
-
&changelog,
|
|
1269
|
-
&[
|
|
1270
|
-
entity_change_with_file(
|
|
1271
|
-
"change-entity-a",
|
|
1272
|
-
"entity-1",
|
|
1273
|
-
"test_schema",
|
|
1274
|
-
None,
|
|
1275
|
-
"{\"value\":\"a\"}",
|
|
1276
|
-
),
|
|
1277
|
-
entity_change_with_file(
|
|
1278
|
-
"change-entity-b",
|
|
1279
|
-
"entity-1",
|
|
1280
|
-
"test_schema",
|
|
1281
|
-
None,
|
|
1282
|
-
"{\"value\":\"b\"}",
|
|
1283
|
-
),
|
|
1284
|
-
commit_change(
|
|
1285
|
-
"commit-head-change",
|
|
1286
|
-
"commit-head",
|
|
1287
|
-
&["change-entity-a", "change-entity-b"],
|
|
1288
|
-
&[],
|
|
1289
|
-
),
|
|
1290
|
-
],
|
|
1291
|
-
)
|
|
1292
|
-
.await;
|
|
1293
|
-
|
|
1294
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1295
|
-
let mut reader = graph.reader(storage);
|
|
1296
|
-
let entities = reader
|
|
1297
|
-
.entities_at("commit-head")
|
|
1298
|
-
.await
|
|
1299
|
-
.expect("entities should resolve");
|
|
1300
|
-
let entity = entities
|
|
1301
|
-
.iter()
|
|
1302
|
-
.find(|entity| entity.change.schema_key == "test_schema")
|
|
1303
|
-
.expect("entity should resolve");
|
|
1304
|
-
|
|
1305
|
-
assert_eq!(
|
|
1306
|
-
entity_ids_for_schema(&entities, "test_schema"),
|
|
1307
|
-
vec![("change-entity-b".to_string(), "commit-head".to_string(), 0)]
|
|
1308
|
-
);
|
|
1309
|
-
assert_eq!(
|
|
1310
|
-
entity.change.snapshot_content.as_deref(),
|
|
1311
|
-
Some("{\"value\":\"b\"}")
|
|
1312
|
-
);
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
#[tokio::test]
|
|
1316
|
-
async fn entities_at_head_tombstone_hides_parent_entity() {
|
|
1317
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1318
|
-
let storage = StorageContext::new(backend.clone());
|
|
1319
|
-
let changelog = ChangelogContext::new();
|
|
1320
|
-
append_changes(
|
|
1321
|
-
storage.clone(),
|
|
1322
|
-
&changelog,
|
|
1323
|
-
&[
|
|
1324
|
-
entity_change(
|
|
1325
|
-
"change-created",
|
|
1326
|
-
"entity-1",
|
|
1327
|
-
"test_schema",
|
|
1328
|
-
"{\"value\":\"created\"}",
|
|
1329
|
-
),
|
|
1330
|
-
entity_tombstone("change-deleted", "entity-1", "test_schema"),
|
|
1331
|
-
commit_change(
|
|
1332
|
-
"commit-root-change",
|
|
1333
|
-
"commit-root",
|
|
1334
|
-
&["change-created"],
|
|
1335
|
-
&[],
|
|
1336
|
-
),
|
|
1337
|
-
commit_change(
|
|
1338
|
-
"commit-head-change",
|
|
1339
|
-
"commit-head",
|
|
1340
|
-
&["change-deleted"],
|
|
1341
|
-
&["commit-root"],
|
|
1342
|
-
),
|
|
1343
|
-
],
|
|
1344
|
-
)
|
|
1345
|
-
.await;
|
|
1346
|
-
|
|
1347
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1348
|
-
let mut reader = graph.reader(storage);
|
|
1349
|
-
let entities = reader
|
|
1350
|
-
.entities_at("commit-head")
|
|
1351
|
-
.await
|
|
1352
|
-
.expect("entities should resolve");
|
|
1353
|
-
let entity = entities
|
|
1354
|
-
.iter()
|
|
1355
|
-
.find(|entity| entity.change.schema_key == "test_schema")
|
|
1356
|
-
.expect("tombstone entity should resolve");
|
|
1357
|
-
|
|
1358
|
-
assert_eq!(
|
|
1359
|
-
entity_ids_for_schema(&entities, "test_schema"),
|
|
1360
|
-
vec![("change-deleted".to_string(), "commit-head".to_string(), 0)]
|
|
1361
|
-
);
|
|
1362
|
-
assert_eq!(entity.change.snapshot_content, None);
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
#[tokio::test]
|
|
1366
|
-
async fn entities_at_includes_reachable_commit_rows() {
|
|
1367
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1368
|
-
let storage = StorageContext::new(backend.clone());
|
|
1369
|
-
let changelog = ChangelogContext::new();
|
|
1370
|
-
append_changes(
|
|
1371
|
-
storage.clone(),
|
|
1372
|
-
&changelog,
|
|
1373
|
-
&[
|
|
1374
|
-
commit_change("commit-root-change", "commit-root", &[], &[]),
|
|
1375
|
-
commit_change("commit-head-change", "commit-head", &[], &["commit-root"]),
|
|
1376
|
-
],
|
|
1377
|
-
)
|
|
1378
|
-
.await;
|
|
1379
|
-
|
|
1380
|
-
let graph = CommitGraphContext::new(changelog);
|
|
1381
|
-
let mut reader = graph.reader(storage);
|
|
1382
|
-
let entities = reader
|
|
1383
|
-
.entities_at("commit-head")
|
|
1384
|
-
.await
|
|
1385
|
-
.expect("entities should resolve");
|
|
1386
|
-
|
|
1387
|
-
assert_eq!(
|
|
1388
|
-
entity_ids_for_schema(&entities, super::COMMIT_SCHEMA_KEY),
|
|
1389
|
-
vec![
|
|
1390
|
-
(
|
|
1391
|
-
"commit-head-change".to_string(),
|
|
1392
|
-
"commit-head".to_string(),
|
|
1393
|
-
0
|
|
1394
|
-
),
|
|
1395
|
-
(
|
|
1396
|
-
"commit-root-change".to_string(),
|
|
1397
|
-
"commit-root".to_string(),
|
|
1398
|
-
1
|
|
1399
|
-
),
|
|
1400
|
-
]
|
|
1401
|
-
);
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
#[tokio::test]
|
|
1405
|
-
async fn entities_at_errors_on_missing_member_change() {
|
|
1406
|
-
let backend = Arc::new(UnitTestBackend::new());
|
|
1407
|
-
let storage = StorageContext::new(backend.clone());
|
|
1408
|
-
let changelog = ChangelogContext::new();
|
|
1409
|
-
append_changes(
|
|
1410
|
-
storage.clone(),
|
|
1411
|
-
&changelog,
|
|
1412
|
-
&[commit_change(
|
|
1413
|
-
"commit-head-change",
|
|
1414
|
-
"commit-head",
|
|
1415
|
-
&["missing-change"],
|
|
1416
|
-
&[],
|
|
1417
|
-
)],
|
|
1418
|
-
)
|
|
1419
|
-
.await;
|
|
642
|
+
assert_eq!(visible[0].located_change.record.id, "change-deleted");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
#[derive(Clone)]
|
|
646
|
+
struct TestChange {
|
|
647
|
+
change: Change,
|
|
648
|
+
commit_change_ids: Vec<String>,
|
|
649
|
+
parent_commit_ids: Vec<String>,
|
|
650
|
+
author_account_ids: Vec<String>,
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
impl TestChange {
|
|
654
|
+
fn commit(
|
|
655
|
+
change_id: &str,
|
|
656
|
+
commit_id: &str,
|
|
657
|
+
change_ids: &[&str],
|
|
658
|
+
parent_commit_ids: &[&str],
|
|
659
|
+
) -> Self {
|
|
660
|
+
Self {
|
|
661
|
+
change: Change {
|
|
662
|
+
id: change_id.to_string(),
|
|
663
|
+
entity_id: crate::entity_identity::EntityIdentity::single(commit_id),
|
|
664
|
+
schema_key: super::COMMIT_SCHEMA_KEY.to_string(),
|
|
665
|
+
file_id: None,
|
|
666
|
+
snapshot_ref: None,
|
|
667
|
+
metadata_ref: None,
|
|
668
|
+
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
669
|
+
},
|
|
670
|
+
commit_change_ids: change_ids.iter().map(|id| id.to_string()).collect(),
|
|
671
|
+
parent_commit_ids: parent_commit_ids.iter().map(|id| id.to_string()).collect(),
|
|
672
|
+
author_account_ids: Vec::new(),
|
|
673
|
+
}
|
|
674
|
+
}
|
|
1420
675
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
676
|
+
fn entity(
|
|
677
|
+
change_id: &str,
|
|
678
|
+
entity_id: &str,
|
|
679
|
+
schema_key: &str,
|
|
680
|
+
file_id: Option<&str>,
|
|
681
|
+
snapshot_content: Option<&str>,
|
|
682
|
+
created_at: &str,
|
|
683
|
+
) -> Self {
|
|
684
|
+
Self {
|
|
685
|
+
change: Change {
|
|
686
|
+
id: change_id.to_string(),
|
|
687
|
+
entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
|
|
688
|
+
schema_key: schema_key.to_string(),
|
|
689
|
+
file_id: file_id.map(str::to_string),
|
|
690
|
+
snapshot_ref: snapshot_content.map(|content| {
|
|
691
|
+
crate::json_store::JsonRef::from_hash(blake3::hash(content.as_bytes()))
|
|
692
|
+
}),
|
|
693
|
+
metadata_ref: None,
|
|
694
|
+
created_at: created_at.to_string(),
|
|
695
|
+
},
|
|
696
|
+
commit_change_ids: Vec::new(),
|
|
697
|
+
parent_commit_ids: Vec::new(),
|
|
698
|
+
author_account_ids: Vec::new(),
|
|
699
|
+
}
|
|
700
|
+
}
|
|
1427
701
|
|
|
1428
|
-
|
|
702
|
+
fn is_commit(&self) -> bool {
|
|
703
|
+
self.change.schema_key == super::COMMIT_SCHEMA_KEY
|
|
704
|
+
}
|
|
1429
705
|
}
|
|
1430
706
|
|
|
1431
|
-
async fn append_changes(
|
|
1432
|
-
storage: StorageContext,
|
|
1433
|
-
changelog: &ChangelogContext,
|
|
1434
|
-
changes: &[MaterializedCanonicalChange],
|
|
1435
|
-
) {
|
|
707
|
+
async fn append_changes(storage: StorageContext, changes: &[TestChange]) {
|
|
1436
708
|
let mut tx = storage
|
|
1437
709
|
.begin_write_transaction()
|
|
1438
710
|
.await
|
|
1439
711
|
.expect("transaction should open");
|
|
1440
712
|
let mut writes = StorageWriteSet::new();
|
|
1441
|
-
let canonical_changes =
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
713
|
+
let canonical_changes = changes
|
|
714
|
+
.iter()
|
|
715
|
+
.filter(|change| !change.is_commit())
|
|
716
|
+
.map(|change| change.change.clone())
|
|
717
|
+
.collect::<Vec<_>>();
|
|
718
|
+
let changes_by_id: BTreeMap<&str, &Change> = canonical_changes
|
|
719
|
+
.iter()
|
|
720
|
+
.map(|change| (change.id.as_str(), change))
|
|
721
|
+
.collect::<BTreeMap<_, _>>();
|
|
722
|
+
let mut authored_change_ids = BTreeSet::new();
|
|
723
|
+
let commit_store = CommitStoreContext::new();
|
|
724
|
+
for change in changes.iter().filter(|change| change.is_commit()) {
|
|
725
|
+
let commit = crate::commit_graph::CommitGraphCommit {
|
|
726
|
+
canonical_change: change.change.clone(),
|
|
727
|
+
change: change.change.clone(),
|
|
728
|
+
commit_id: change
|
|
729
|
+
.change
|
|
730
|
+
.entity_id
|
|
731
|
+
.as_single_string()
|
|
732
|
+
.expect("commit fixture should use single entity id")
|
|
733
|
+
.to_string(),
|
|
734
|
+
change_ids: change.commit_change_ids.clone(),
|
|
735
|
+
author_account_ids: change.author_account_ids.clone(),
|
|
736
|
+
parent_commit_ids: change.parent_commit_ids.clone(),
|
|
737
|
+
};
|
|
738
|
+
let parent_commit_ids = commit.parent_commit_ids.clone();
|
|
739
|
+
let author_account_ids = commit.author_account_ids.clone();
|
|
740
|
+
let commit_draft = CommitDraftRef {
|
|
741
|
+
id: &commit.commit_id,
|
|
742
|
+
change_id: &commit.canonical_change.id,
|
|
743
|
+
parent_ids: &parent_commit_ids,
|
|
744
|
+
author_account_ids: &author_account_ids,
|
|
745
|
+
created_at: &commit.canonical_change.created_at,
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
let mut authored_changes = Vec::new();
|
|
749
|
+
let mut adopted_changes = Vec::new();
|
|
750
|
+
let mut corrupt_missing_members = Vec::new();
|
|
751
|
+
for change_id in &commit.change_ids {
|
|
752
|
+
if let Some(change) = changes_by_id.get(change_id.as_str()) {
|
|
753
|
+
if authored_change_ids.insert(change_id.clone()) {
|
|
754
|
+
authored_changes.push(change_ref_from_canonical(change.as_ref()));
|
|
755
|
+
} else {
|
|
756
|
+
adopted_changes.push(change_ref_from_canonical(change.as_ref()));
|
|
757
|
+
}
|
|
758
|
+
} else {
|
|
759
|
+
corrupt_missing_members.push(change_id.clone());
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if corrupt_missing_members.is_empty() {
|
|
764
|
+
commit_store
|
|
765
|
+
.writer(tx.as_mut(), &mut writes)
|
|
766
|
+
.stage_commit_draft(commit_draft, authored_changes, adopted_changes)
|
|
767
|
+
.await
|
|
768
|
+
.expect("commit-store append should succeed");
|
|
769
|
+
} else {
|
|
770
|
+
crate::commit_store::storage::stage_commit(
|
|
771
|
+
&mut writes,
|
|
772
|
+
commit_draft,
|
|
773
|
+
authored_changes,
|
|
774
|
+
corrupt_missing_members
|
|
775
|
+
.into_iter()
|
|
776
|
+
.map(|change_id| ChangeLocator {
|
|
777
|
+
source_commit_id: "missing-source-commit".to_string(),
|
|
778
|
+
source_pack_id: 0,
|
|
779
|
+
source_ordinal: 0,
|
|
780
|
+
change_id,
|
|
781
|
+
})
|
|
782
|
+
.collect(),
|
|
783
|
+
)
|
|
784
|
+
.expect("corrupt commit-store fixture should stage");
|
|
785
|
+
}
|
|
786
|
+
}
|
|
1455
787
|
writes
|
|
1456
788
|
.apply(&mut tx.as_mut())
|
|
1457
789
|
.await
|
|
@@ -1459,30 +791,25 @@ mod tests {
|
|
|
1459
791
|
tx.commit().await.expect("commit should succeed");
|
|
1460
792
|
}
|
|
1461
793
|
|
|
794
|
+
fn change_ref_from_canonical<'a>(change: crate::commit_store::ChangeRef<'a>) -> ChangeRef<'a> {
|
|
795
|
+
ChangeRef {
|
|
796
|
+
id: change.id,
|
|
797
|
+
entity_id: change.entity_id,
|
|
798
|
+
schema_key: change.schema_key,
|
|
799
|
+
file_id: change.file_id,
|
|
800
|
+
snapshot_ref: change.snapshot_ref,
|
|
801
|
+
metadata_ref: change.metadata_ref,
|
|
802
|
+
created_at: change.created_at,
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
1462
806
|
fn commit_change(
|
|
1463
807
|
change_id: &str,
|
|
1464
808
|
commit_id: &str,
|
|
1465
809
|
change_ids: &[&str],
|
|
1466
810
|
parent_commit_ids: &[&str],
|
|
1467
|
-
) ->
|
|
1468
|
-
|
|
1469
|
-
id: change_id.to_string(),
|
|
1470
|
-
entity_id: crate::entity_identity::EntityIdentity::single(commit_id),
|
|
1471
|
-
schema_key: super::COMMIT_SCHEMA_KEY.to_string(),
|
|
1472
|
-
schema_version: "1".to_string(),
|
|
1473
|
-
file_id: None,
|
|
1474
|
-
snapshot_content: Some(
|
|
1475
|
-
serde_json::to_string(&json!({
|
|
1476
|
-
"id": commit_id,
|
|
1477
|
-
"change_set_id": "change-set-1",
|
|
1478
|
-
"change_ids": change_ids,
|
|
1479
|
-
"parent_commit_ids": parent_commit_ids,
|
|
1480
|
-
}))
|
|
1481
|
-
.expect("snapshot should serialize"),
|
|
1482
|
-
),
|
|
1483
|
-
metadata: None,
|
|
1484
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1485
|
-
}
|
|
811
|
+
) -> TestChange {
|
|
812
|
+
TestChange::commit(change_id, commit_id, change_ids, parent_commit_ids)
|
|
1486
813
|
}
|
|
1487
814
|
|
|
1488
815
|
fn parsed_commit(
|
|
@@ -1490,13 +817,26 @@ mod tests {
|
|
|
1490
817
|
change_ids: &[&str],
|
|
1491
818
|
parent_commit_ids: &[&str],
|
|
1492
819
|
) -> crate::commit_graph::CommitGraphCommit {
|
|
1493
|
-
|
|
820
|
+
let fixture = commit_change(
|
|
1494
821
|
&format!("{commit_id}-change"),
|
|
1495
822
|
commit_id,
|
|
1496
823
|
change_ids,
|
|
1497
824
|
parent_commit_ids,
|
|
1498
|
-
)
|
|
1499
|
-
|
|
825
|
+
);
|
|
826
|
+
crate::commit_graph::CommitGraphCommit {
|
|
827
|
+
canonical_change: fixture.change.clone(),
|
|
828
|
+
change: fixture.change,
|
|
829
|
+
commit_id: commit_id.to_string(),
|
|
830
|
+
change_ids: change_ids
|
|
831
|
+
.iter()
|
|
832
|
+
.map(|change_id| change_id.to_string())
|
|
833
|
+
.collect(),
|
|
834
|
+
author_account_ids: Vec::new(),
|
|
835
|
+
parent_commit_ids: parent_commit_ids
|
|
836
|
+
.iter()
|
|
837
|
+
.map(|parent_id| parent_id.to_string())
|
|
838
|
+
.collect(),
|
|
839
|
+
}
|
|
1500
840
|
}
|
|
1501
841
|
|
|
1502
842
|
fn entity_change(
|
|
@@ -1504,7 +844,7 @@ mod tests {
|
|
|
1504
844
|
entity_id: &str,
|
|
1505
845
|
schema_key: &str,
|
|
1506
846
|
snapshot_content: &str,
|
|
1507
|
-
) ->
|
|
847
|
+
) -> TestChange {
|
|
1508
848
|
entity_change_at(
|
|
1509
849
|
change_id,
|
|
1510
850
|
entity_id,
|
|
@@ -1520,17 +860,15 @@ mod tests {
|
|
|
1520
860
|
schema_key: &str,
|
|
1521
861
|
snapshot_content: &str,
|
|
1522
862
|
created_at: &str,
|
|
1523
|
-
) ->
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
entity_id
|
|
1527
|
-
schema_key
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
created_at: created_at.to_string(),
|
|
1533
|
-
}
|
|
863
|
+
) -> TestChange {
|
|
864
|
+
TestChange::entity(
|
|
865
|
+
change_id,
|
|
866
|
+
entity_id,
|
|
867
|
+
schema_key,
|
|
868
|
+
None,
|
|
869
|
+
Some(snapshot_content),
|
|
870
|
+
created_at,
|
|
871
|
+
)
|
|
1534
872
|
}
|
|
1535
873
|
|
|
1536
874
|
fn entity_change_with_file(
|
|
@@ -1539,50 +877,25 @@ mod tests {
|
|
|
1539
877
|
schema_key: &str,
|
|
1540
878
|
file_id: Option<&str>,
|
|
1541
879
|
snapshot_content: &str,
|
|
1542
|
-
) ->
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
entity_id
|
|
1546
|
-
schema_key
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
|
|
1555
|
-
fn entity_tombstone(
|
|
1556
|
-
change_id: &str,
|
|
1557
|
-
entity_id: &str,
|
|
1558
|
-
schema_key: &str,
|
|
1559
|
-
) -> MaterializedCanonicalChange {
|
|
1560
|
-
MaterializedCanonicalChange {
|
|
1561
|
-
id: change_id.to_string(),
|
|
1562
|
-
entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
|
|
1563
|
-
schema_key: schema_key.to_string(),
|
|
1564
|
-
schema_version: "1".to_string(),
|
|
1565
|
-
file_id: None,
|
|
1566
|
-
snapshot_content: None,
|
|
1567
|
-
metadata: None,
|
|
1568
|
-
created_at: "2026-01-02T00:00:00Z".to_string(),
|
|
1569
|
-
}
|
|
880
|
+
) -> TestChange {
|
|
881
|
+
TestChange::entity(
|
|
882
|
+
change_id,
|
|
883
|
+
entity_id,
|
|
884
|
+
schema_key,
|
|
885
|
+
file_id,
|
|
886
|
+
Some(snapshot_content),
|
|
887
|
+
"2026-01-01T00:00:00Z",
|
|
888
|
+
)
|
|
1570
889
|
}
|
|
1571
890
|
|
|
1572
|
-
fn
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
entity.change.id.clone(),
|
|
1582
|
-
entity.source_commit_id.clone(),
|
|
1583
|
-
entity.depth,
|
|
1584
|
-
)
|
|
1585
|
-
})
|
|
1586
|
-
.collect()
|
|
891
|
+
fn entity_tombstone(change_id: &str, entity_id: &str, schema_key: &str) -> TestChange {
|
|
892
|
+
TestChange::entity(
|
|
893
|
+
change_id,
|
|
894
|
+
entity_id,
|
|
895
|
+
schema_key,
|
|
896
|
+
None,
|
|
897
|
+
None,
|
|
898
|
+
"2026-01-02T00:00:00Z",
|
|
899
|
+
)
|
|
1587
900
|
}
|
|
1588
901
|
}
|