@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,25 +1,29 @@
|
|
|
1
1
|
use async_trait::async_trait;
|
|
2
2
|
use tokio::sync::Mutex;
|
|
3
3
|
|
|
4
|
-
use crate::commit_graph::
|
|
4
|
+
use crate::commit_graph::CommitGraphContext;
|
|
5
|
+
use crate::entity_identity::EntityIdentity;
|
|
5
6
|
use crate::live_state::visibility;
|
|
6
7
|
use crate::live_state::{
|
|
7
|
-
|
|
8
|
+
LiveStateReader, LiveStateRowRequest, LiveStateScanRequest, MaterializedLiveStateRow,
|
|
8
9
|
};
|
|
9
|
-
use crate::storage::
|
|
10
|
+
use crate::storage::StorageReader;
|
|
10
11
|
use crate::tracked_state::{
|
|
11
|
-
TrackedStateContext, TrackedStateFilter, TrackedStateProjection,
|
|
12
|
+
MaterializedTrackedStateRow, TrackedStateContext, TrackedStateFilter, TrackedStateProjection,
|
|
12
13
|
TrackedStateRowRequest, TrackedStateScanRequest,
|
|
13
14
|
};
|
|
14
15
|
use crate::untracked_state::{
|
|
15
|
-
|
|
16
|
-
UntrackedStateIdentity, UntrackedStateRowRequest, UntrackedStateScanRequest,
|
|
16
|
+
UntrackedStateContext, UntrackedStateRowRequest, UntrackedStateScanRequest,
|
|
17
17
|
};
|
|
18
18
|
use crate::version::VERSION_REF_SCHEMA_KEY;
|
|
19
19
|
use crate::LixError;
|
|
20
|
+
use crate::NullableKeyFilter;
|
|
20
21
|
use crate::GLOBAL_VERSION_ID;
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
const COMMIT_SCHEMA_KEY: &str = "lix_commit";
|
|
24
|
+
const COMMIT_EDGE_SCHEMA_KEY: &str = "lix_commit_edge";
|
|
25
|
+
|
|
26
|
+
/// Serving facade for visible live-state reads.
|
|
23
27
|
///
|
|
24
28
|
/// Live state composes the rebuildable tracked projection with the durable
|
|
25
29
|
/// untracked local overlay. Lower stores own persistence; this facade owns the
|
|
@@ -55,22 +59,6 @@ impl LiveStateContext {
|
|
|
55
59
|
commit_graph: self.commit_graph.clone(),
|
|
56
60
|
}
|
|
57
61
|
}
|
|
58
|
-
|
|
59
|
-
/// Creates a visible live-state writer over a caller-provided KV reader.
|
|
60
|
-
///
|
|
61
|
-
/// The writer owns the tracked/untracked routing rule: tracked rows update
|
|
62
|
-
/// the tracked projection and clear matching untracked overlay rows, while
|
|
63
|
-
/// untracked rows update only the local untracked overlay.
|
|
64
|
-
pub(crate) fn writer<S>(&self, store: S) -> LiveStateWriter<S>
|
|
65
|
-
where
|
|
66
|
-
S: StorageReader,
|
|
67
|
-
{
|
|
68
|
-
LiveStateWriter {
|
|
69
|
-
store,
|
|
70
|
-
tracked_state: self.tracked_state.clone(),
|
|
71
|
-
untracked_state: self.untracked_state,
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
62
|
}
|
|
75
63
|
|
|
76
64
|
/// Visible live-state reader backed by a caller-provided KV store.
|
|
@@ -88,30 +76,35 @@ where
|
|
|
88
76
|
pub(crate) async fn scan_rows(
|
|
89
77
|
&self,
|
|
90
78
|
request: &LiveStateScanRequest,
|
|
91
|
-
) -> Result<Vec<
|
|
79
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
92
80
|
let mut store = self.store.lock().await;
|
|
93
81
|
let scope = scan_scope(&mut *store, &self.untracked_state, request).await?;
|
|
82
|
+
let derived_rows =
|
|
83
|
+
scan_commit_derived_rows(&mut *store, &self.commit_graph, request, &scope).await?;
|
|
94
84
|
let mut tracked_rows = Vec::new();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
85
|
+
if request.filter.untracked != Some(true) && !is_commit_derived_only_request(request) {
|
|
86
|
+
for version_id in &scope.storage_version_ids {
|
|
87
|
+
let Some(commit_id) =
|
|
88
|
+
load_version_ref_commit_id(&mut *store, &self.untracked_state, version_id)
|
|
89
|
+
.await?
|
|
90
|
+
else {
|
|
91
|
+
continue;
|
|
92
|
+
};
|
|
93
|
+
let tracked_request = tracked_scan_request_from_live(request);
|
|
94
|
+
let source = tracked_source_from_version_id(version_id);
|
|
95
|
+
let store: &mut dyn StorageReader = &mut *store;
|
|
96
|
+
tracked_rows.extend(
|
|
97
|
+
self.tracked_state
|
|
98
|
+
.reader(store)
|
|
99
|
+
.scan_rows_at_commit(&commit_id, &tracked_request)
|
|
100
|
+
.await?
|
|
101
|
+
.into_iter()
|
|
102
|
+
.map(|row| project_tracked_row(row, version_id, source)),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
112
105
|
}
|
|
113
106
|
|
|
114
|
-
let untracked_rows = {
|
|
107
|
+
let untracked_rows = if request.filter.untracked != Some(false) {
|
|
115
108
|
let store: &mut dyn StorageReader = &mut *store;
|
|
116
109
|
self.untracked_state
|
|
117
110
|
.reader(store)
|
|
@@ -120,28 +113,25 @@ where
|
|
|
120
113
|
&scope.storage_version_ids,
|
|
121
114
|
))
|
|
122
115
|
.await?
|
|
123
|
-
}
|
|
124
|
-
.into_iter()
|
|
125
|
-
.map(LiveStateRow::from)
|
|
126
|
-
.collect::<Vec<_>>();
|
|
127
|
-
|
|
128
|
-
let mut commit_rows = if scope.includes_commit_graph_projection {
|
|
129
|
-
let store: &mut dyn StorageReader = &mut *store;
|
|
130
|
-
self.commit_graph
|
|
131
|
-
.reader(store)
|
|
132
|
-
.all_commits()
|
|
133
|
-
.await?
|
|
134
116
|
.into_iter()
|
|
135
|
-
.map(
|
|
117
|
+
.map(MaterializedLiveStateRow::from)
|
|
136
118
|
.collect::<Vec<_>>()
|
|
137
119
|
} else {
|
|
138
120
|
Vec::new()
|
|
139
121
|
};
|
|
140
|
-
commit_rows.retain(|row| live_state_row_matches_filter(row, &request.filter));
|
|
141
122
|
|
|
142
|
-
let mut rows =
|
|
143
|
-
|
|
144
|
-
|
|
123
|
+
let mut rows = if request.filter.untracked.is_some() {
|
|
124
|
+
tracked_rows
|
|
125
|
+
.into_iter()
|
|
126
|
+
.chain(untracked_rows)
|
|
127
|
+
.chain(derived_rows)
|
|
128
|
+
.collect()
|
|
129
|
+
} else {
|
|
130
|
+
crate::live_state::overlay::overlay_untracked_rows(tracked_rows, untracked_rows)
|
|
131
|
+
.into_iter()
|
|
132
|
+
.chain(derived_rows)
|
|
133
|
+
.collect()
|
|
134
|
+
};
|
|
145
135
|
rows = visibility::resolve_scan_rows(
|
|
146
136
|
rows,
|
|
147
137
|
&scope.projection_version_ids,
|
|
@@ -156,14 +146,40 @@ where
|
|
|
156
146
|
pub(crate) async fn load_row(
|
|
157
147
|
&self,
|
|
158
148
|
request: &LiveStateRowRequest,
|
|
159
|
-
) -> Result<Option<
|
|
149
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
160
150
|
let mut store = self.store.lock().await;
|
|
161
151
|
if !version_ref_exists(&mut *store, &self.untracked_state, &request.version_id).await? {
|
|
162
152
|
return Ok(None);
|
|
163
153
|
}
|
|
164
|
-
if request.schema_key
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
if is_commit_derived_schema(&request.schema_key)
|
|
155
|
+
&& request.file_id == NullableKeyFilter::Null
|
|
156
|
+
{
|
|
157
|
+
let scope = LiveStateScanScope {
|
|
158
|
+
storage_version_ids: vec![request.version_id.clone()],
|
|
159
|
+
projection_version_ids: vec![request.version_id.clone()],
|
|
160
|
+
};
|
|
161
|
+
let rows = scan_commit_derived_rows(
|
|
162
|
+
&mut *store,
|
|
163
|
+
&self.commit_graph,
|
|
164
|
+
&LiveStateScanRequest {
|
|
165
|
+
filter: crate::live_state::LiveStateFilter {
|
|
166
|
+
schema_keys: vec![request.schema_key.clone()],
|
|
167
|
+
entity_ids: vec![request.entity_id.clone()],
|
|
168
|
+
version_ids: vec![request.version_id.clone()],
|
|
169
|
+
file_ids: vec![NullableKeyFilter::Null],
|
|
170
|
+
untracked: Some(false),
|
|
171
|
+
include_tombstones: false,
|
|
172
|
+
..Default::default()
|
|
173
|
+
},
|
|
174
|
+
limit: Some(1),
|
|
175
|
+
..Default::default()
|
|
176
|
+
},
|
|
177
|
+
&scope,
|
|
178
|
+
)
|
|
179
|
+
.await?;
|
|
180
|
+
if let Some(row) = rows.into_iter().next() {
|
|
181
|
+
return Ok(Some(row));
|
|
182
|
+
}
|
|
167
183
|
}
|
|
168
184
|
for candidate in load_row_candidates(request) {
|
|
169
185
|
match candidate.source {
|
|
@@ -179,7 +195,7 @@ where
|
|
|
179
195
|
.await?
|
|
180
196
|
{
|
|
181
197
|
return Ok(Some(visibility::project_loaded_row(
|
|
182
|
-
|
|
198
|
+
MaterializedLiveStateRow::from(row),
|
|
183
199
|
&request.version_id,
|
|
184
200
|
&candidate.version_id,
|
|
185
201
|
)));
|
|
@@ -196,12 +212,13 @@ where
|
|
|
196
212
|
continue;
|
|
197
213
|
};
|
|
198
214
|
let store: &mut dyn StorageReader = &mut *store;
|
|
199
|
-
|
|
215
|
+
let tracked_request = tracked_row_request_from_live(request);
|
|
216
|
+
let mut rows = self
|
|
200
217
|
.tracked_state
|
|
201
218
|
.reader(store)
|
|
202
|
-
.
|
|
203
|
-
.await
|
|
204
|
-
{
|
|
219
|
+
.load_rows_at_commit(&commit_id, &[tracked_request])
|
|
220
|
+
.await?;
|
|
221
|
+
if let Some(row) = rows.pop().flatten() {
|
|
205
222
|
return Ok(Some(project_tracked_row(
|
|
206
223
|
row,
|
|
207
224
|
&request.version_id,
|
|
@@ -213,30 +230,6 @@ where
|
|
|
213
230
|
}
|
|
214
231
|
Ok(None)
|
|
215
232
|
}
|
|
216
|
-
|
|
217
|
-
async fn load_commit_row(
|
|
218
|
-
&self,
|
|
219
|
-
store: &mut dyn StorageReader,
|
|
220
|
-
request: &LiveStateRowRequest,
|
|
221
|
-
) -> Result<Option<LiveStateRow>, LixError> {
|
|
222
|
-
if !nullable_filter_matches(&request.file_id, &None) {
|
|
223
|
-
return Ok(None);
|
|
224
|
-
}
|
|
225
|
-
let Some(commit) = self
|
|
226
|
-
.commit_graph
|
|
227
|
-
.reader(store)
|
|
228
|
-
.load_commit(&request.entity_id.as_string()?)
|
|
229
|
-
.await?
|
|
230
|
-
else {
|
|
231
|
-
return Ok(None);
|
|
232
|
-
};
|
|
233
|
-
let row = live_state_row_from_commit(commit);
|
|
234
|
-
Ok(Some(visibility::project_loaded_row(
|
|
235
|
-
row,
|
|
236
|
-
&request.version_id,
|
|
237
|
-
GLOBAL_VERSION_ID,
|
|
238
|
-
)))
|
|
239
|
-
}
|
|
240
233
|
}
|
|
241
234
|
|
|
242
235
|
#[async_trait]
|
|
@@ -247,105 +240,162 @@ where
|
|
|
247
240
|
async fn scan_rows(
|
|
248
241
|
&self,
|
|
249
242
|
request: &LiveStateScanRequest,
|
|
250
|
-
) -> Result<Vec<
|
|
243
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
251
244
|
LiveStateStoreReader::scan_rows(self, request).await
|
|
252
245
|
}
|
|
253
246
|
|
|
254
247
|
async fn load_row(
|
|
255
248
|
&self,
|
|
256
249
|
request: &LiveStateRowRequest,
|
|
257
|
-
) -> Result<Option<
|
|
250
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
258
251
|
LiveStateStoreReader::load_row(self, request).await
|
|
259
252
|
}
|
|
260
253
|
}
|
|
261
254
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
255
|
+
async fn scan_commit_derived_rows(
|
|
256
|
+
store: &mut dyn StorageReader,
|
|
257
|
+
commit_graph: &CommitGraphContext,
|
|
258
|
+
request: &LiveStateScanRequest,
|
|
259
|
+
scope: &LiveStateScanScope,
|
|
260
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
261
|
+
if request.filter.untracked == Some(true) || !request_may_include_commit_derived(request) {
|
|
262
|
+
return Ok(Vec::new());
|
|
263
|
+
}
|
|
264
|
+
if !file_filter_allows_null(&request.filter.file_ids) {
|
|
265
|
+
return Ok(Vec::new());
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let version_ids = if scope.projection_version_ids.is_empty() {
|
|
269
|
+
vec![GLOBAL_VERSION_ID.to_string()]
|
|
270
|
+
} else {
|
|
271
|
+
scope.projection_version_ids.clone()
|
|
272
|
+
};
|
|
273
|
+
let mut graph = commit_graph.reader(store);
|
|
274
|
+
let commits = graph.all_commits().await?;
|
|
275
|
+
let include_commit = schema_filter_allows(&request.filter.schema_keys, COMMIT_SCHEMA_KEY);
|
|
276
|
+
let include_commit_edge =
|
|
277
|
+
schema_filter_allows(&request.filter.schema_keys, COMMIT_EDGE_SCHEMA_KEY);
|
|
278
|
+
|
|
279
|
+
let mut rows = Vec::new();
|
|
280
|
+
for version_id in &version_ids {
|
|
281
|
+
if include_commit {
|
|
282
|
+
for commit in &commits {
|
|
283
|
+
rows.push(commit_row(commit, version_id)?);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if include_commit_edge {
|
|
287
|
+
for edge in graph.commit_edges(&commits) {
|
|
288
|
+
rows.push(commit_edge_row(&edge, version_id)?);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
rows.retain(|row| {
|
|
294
|
+
(request.filter.entity_ids.is_empty() || request.filter.entity_ids.contains(&row.entity_id))
|
|
295
|
+
&& (request.filter.version_ids.is_empty()
|
|
296
|
+
|| request.filter.version_ids.contains(&row.version_id))
|
|
297
|
+
});
|
|
298
|
+
Ok(rows)
|
|
267
299
|
}
|
|
268
300
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
rows: &[LiveStateRow],
|
|
278
|
-
) -> Result<(), LixError> {
|
|
279
|
-
let (tracked_rows, untracked_rows): (Vec<_>, Vec<_>) =
|
|
280
|
-
rows.iter().partition(|row| !row.untracked);
|
|
301
|
+
fn request_may_include_commit_derived(request: &LiveStateScanRequest) -> bool {
|
|
302
|
+
request.filter.schema_keys.is_empty()
|
|
303
|
+
|| request
|
|
304
|
+
.filter
|
|
305
|
+
.schema_keys
|
|
306
|
+
.iter()
|
|
307
|
+
.any(|schema_key| is_commit_derived_schema(schema_key))
|
|
308
|
+
}
|
|
281
309
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.collect::<Result<Vec<_>, _>>()?;
|
|
291
|
-
self.untracked_state
|
|
292
|
-
.writer(writes)
|
|
293
|
-
.stage_rows(&canonical_rows)?;
|
|
294
|
-
}
|
|
310
|
+
fn is_commit_derived_only_request(request: &LiveStateScanRequest) -> bool {
|
|
311
|
+
!request.filter.schema_keys.is_empty()
|
|
312
|
+
&& request
|
|
313
|
+
.filter
|
|
314
|
+
.schema_keys
|
|
315
|
+
.iter()
|
|
316
|
+
.all(|schema_key| is_commit_derived_schema(schema_key))
|
|
317
|
+
}
|
|
295
318
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
319
|
+
fn is_commit_derived_schema(schema_key: &str) -> bool {
|
|
320
|
+
matches!(schema_key, COMMIT_SCHEMA_KEY | COMMIT_EDGE_SCHEMA_KEY)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
fn schema_filter_allows(schema_keys: &[String], schema_key: &str) -> bool {
|
|
324
|
+
schema_keys.is_empty() || schema_keys.iter().any(|candidate| candidate == schema_key)
|
|
325
|
+
}
|
|
299
326
|
|
|
300
|
-
|
|
327
|
+
fn file_filter_allows_null(file_ids: &[NullableKeyFilter<String>]) -> bool {
|
|
328
|
+
file_ids.is_empty()
|
|
329
|
+
|| file_ids
|
|
301
330
|
.iter()
|
|
302
|
-
.
|
|
303
|
-
|
|
304
|
-
version_id: row.version_id.clone(),
|
|
305
|
-
schema_key: row.schema_key.clone(),
|
|
306
|
-
entity_id: row.entity_id.clone(),
|
|
307
|
-
file_id: row.file_id.clone(),
|
|
308
|
-
})
|
|
309
|
-
})
|
|
310
|
-
.collect::<Result<Vec<_>, LixError>>()?;
|
|
311
|
-
self.untracked_state
|
|
312
|
-
.writer(writes)
|
|
313
|
-
.stage_delete_rows(&identities);
|
|
314
|
-
|
|
315
|
-
for (commit_id, rows) in grouped_live_rows_by_commit(&tracked_rows)? {
|
|
316
|
-
let parent_commit_id = parent_commit_id_for_commit_rows(commit_id, &rows)?;
|
|
317
|
-
validate_root_local_write_batch(commit_id, &rows)?;
|
|
318
|
-
// Commit graph facts live in the changelog/commit_graph projection.
|
|
319
|
-
// They are present in the write batch so the tracked root can inherit
|
|
320
|
-
// parent metadata, but they are not stored as version entities.
|
|
321
|
-
let root_rows = rows
|
|
322
|
-
.iter()
|
|
323
|
-
.filter(|row| row.schema_key != COMMIT_SCHEMA_KEY)
|
|
324
|
-
.map(|row| TrackedStateRow::try_from(*row))
|
|
325
|
-
.collect::<Result<Vec<_>, _>>()?;
|
|
326
|
-
self.tracked_state
|
|
327
|
-
.writer()
|
|
328
|
-
.stage_root(
|
|
329
|
-
&mut self.store,
|
|
330
|
-
writes,
|
|
331
|
-
json_writer,
|
|
332
|
-
commit_id,
|
|
333
|
-
parent_commit_id.as_deref(),
|
|
334
|
-
&root_rows,
|
|
335
|
-
)
|
|
336
|
-
.await?;
|
|
337
|
-
}
|
|
331
|
+
.any(|file_id| matches!(file_id, NullableKeyFilter::Any | NullableKeyFilter::Null))
|
|
332
|
+
}
|
|
338
333
|
|
|
339
|
-
|
|
340
|
-
|
|
334
|
+
fn commit_row(
|
|
335
|
+
commit: &crate::commit_graph::CommitGraphCommit,
|
|
336
|
+
version_id: &str,
|
|
337
|
+
) -> Result<MaterializedLiveStateRow, LixError> {
|
|
338
|
+
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
339
|
+
"id": commit.commit_id,
|
|
340
|
+
}))
|
|
341
|
+
.map_err(|error| {
|
|
342
|
+
LixError::new(
|
|
343
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
344
|
+
format!("failed to encode derived lix_commit snapshot: {error}"),
|
|
345
|
+
)
|
|
346
|
+
})?;
|
|
347
|
+
Ok(MaterializedLiveStateRow {
|
|
348
|
+
entity_id: EntityIdentity::single(commit.commit_id.clone()),
|
|
349
|
+
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
350
|
+
file_id: None,
|
|
351
|
+
snapshot_content: Some(snapshot_content),
|
|
352
|
+
metadata: None,
|
|
353
|
+
deleted: false,
|
|
354
|
+
created_at: commit.change.created_at.clone(),
|
|
355
|
+
updated_at: commit.change.created_at.clone(),
|
|
356
|
+
global: true,
|
|
357
|
+
change_id: Some(commit.change.id.clone()),
|
|
358
|
+
commit_id: Some(commit.commit_id.clone()),
|
|
359
|
+
untracked: false,
|
|
360
|
+
version_id: version_id.to_string(),
|
|
361
|
+
})
|
|
341
362
|
}
|
|
342
363
|
|
|
343
|
-
fn
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
364
|
+
fn commit_edge_row(
|
|
365
|
+
edge: &crate::commit_graph::CommitGraphEdge,
|
|
366
|
+
version_id: &str,
|
|
367
|
+
) -> Result<MaterializedLiveStateRow, LixError> {
|
|
368
|
+
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
369
|
+
"parent_id": edge.parent_commit_id,
|
|
370
|
+
"child_id": edge.child_commit_id,
|
|
371
|
+
"parent_order": edge.parent_order,
|
|
372
|
+
}))
|
|
373
|
+
.map_err(|error| {
|
|
374
|
+
LixError::new(
|
|
375
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
376
|
+
format!("failed to encode derived lix_commit_edge snapshot: {error}"),
|
|
377
|
+
)
|
|
378
|
+
})?;
|
|
379
|
+
Ok(MaterializedLiveStateRow {
|
|
380
|
+
entity_id: EntityIdentity {
|
|
381
|
+
parts: vec![edge.parent_commit_id.clone(), edge.child_commit_id.clone()],
|
|
382
|
+
},
|
|
383
|
+
schema_key: COMMIT_EDGE_SCHEMA_KEY.to_string(),
|
|
384
|
+
file_id: None,
|
|
385
|
+
snapshot_content: Some(snapshot_content),
|
|
386
|
+
metadata: None,
|
|
387
|
+
deleted: false,
|
|
388
|
+
created_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
389
|
+
updated_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
390
|
+
global: true,
|
|
391
|
+
change_id: None,
|
|
392
|
+
commit_id: Some(edge.child_commit_id.clone()),
|
|
393
|
+
untracked: false,
|
|
394
|
+
version_id: version_id.to_string(),
|
|
395
|
+
})
|
|
396
|
+
}
|
|
348
397
|
|
|
398
|
+
fn tracked_scan_request_from_live(request: &LiveStateScanRequest) -> TrackedStateScanRequest {
|
|
349
399
|
TrackedStateScanRequest {
|
|
350
400
|
filter: TrackedStateFilter {
|
|
351
401
|
schema_keys: request.filter.schema_keys.clone(),
|
|
@@ -355,7 +405,9 @@ fn tracked_scan_request_from_live(request: &LiveStateScanRequest) -> TrackedStat
|
|
|
355
405
|
// global fallback rows before the serving facade filters them.
|
|
356
406
|
include_tombstones: true,
|
|
357
407
|
},
|
|
358
|
-
projection: TrackedStateProjection {
|
|
408
|
+
projection: TrackedStateProjection {
|
|
409
|
+
columns: request.projection.columns.clone(),
|
|
410
|
+
},
|
|
359
411
|
limit: None,
|
|
360
412
|
}
|
|
361
413
|
}
|
|
@@ -368,7 +420,9 @@ fn untracked_scan_request_from_live(
|
|
|
368
420
|
filter.version_ids = version_ids.to_vec();
|
|
369
421
|
UntrackedStateScanRequest {
|
|
370
422
|
filter,
|
|
371
|
-
projection:
|
|
423
|
+
projection: crate::untracked_state::UntrackedStateProjection {
|
|
424
|
+
columns: request.projection.columns.clone(),
|
|
425
|
+
},
|
|
372
426
|
limit: None,
|
|
373
427
|
}
|
|
374
428
|
}
|
|
@@ -377,7 +431,6 @@ fn untracked_scan_request_from_live(
|
|
|
377
431
|
struct LiveStateScanScope {
|
|
378
432
|
storage_version_ids: Vec<String>,
|
|
379
433
|
projection_version_ids: Vec<String>,
|
|
380
|
-
includes_commit_graph_projection: bool,
|
|
381
434
|
}
|
|
382
435
|
|
|
383
436
|
async fn scan_scope(
|
|
@@ -389,7 +442,6 @@ async fn scan_scope(
|
|
|
389
442
|
return Ok(LiveStateScanScope {
|
|
390
443
|
storage_version_ids: all_version_ref_ids(store, untracked_state).await?,
|
|
391
444
|
projection_version_ids: Vec::new(),
|
|
392
|
-
includes_commit_graph_projection: true,
|
|
393
445
|
});
|
|
394
446
|
}
|
|
395
447
|
|
|
@@ -403,7 +455,6 @@ async fn scan_scope(
|
|
|
403
455
|
let storage_version_ids = visibility::expanded_version_ids(&projection_version_ids);
|
|
404
456
|
Ok(LiveStateScanScope {
|
|
405
457
|
storage_version_ids,
|
|
406
|
-
includes_commit_graph_projection: !projection_version_ids.is_empty(),
|
|
407
458
|
projection_version_ids,
|
|
408
459
|
})
|
|
409
460
|
}
|
|
@@ -424,7 +475,7 @@ async fn all_version_ref_ids(
|
|
|
424
475
|
})
|
|
425
476
|
.await?;
|
|
426
477
|
rows.into_iter()
|
|
427
|
-
.map(|row| row.entity_id.
|
|
478
|
+
.map(|row| row.entity_id.as_single_string_owned())
|
|
428
479
|
.collect()
|
|
429
480
|
}
|
|
430
481
|
|
|
@@ -473,27 +524,6 @@ async fn version_ref_exists(
|
|
|
473
524
|
)
|
|
474
525
|
}
|
|
475
526
|
|
|
476
|
-
const COMMIT_SCHEMA_KEY: &str = "lix_commit";
|
|
477
|
-
|
|
478
|
-
fn live_state_row_from_commit(commit: CommitGraphCommit) -> LiveStateRow {
|
|
479
|
-
let change = commit.change;
|
|
480
|
-
LiveStateRow {
|
|
481
|
-
entity_id: change.entity_id,
|
|
482
|
-
schema_key: change.schema_key,
|
|
483
|
-
file_id: change.file_id,
|
|
484
|
-
snapshot_content: change.snapshot_content,
|
|
485
|
-
metadata: change.metadata,
|
|
486
|
-
schema_version: change.schema_version,
|
|
487
|
-
created_at: change.created_at.clone(),
|
|
488
|
-
updated_at: change.created_at,
|
|
489
|
-
global: true,
|
|
490
|
-
change_id: Some(change.id),
|
|
491
|
-
commit_id: Some(commit.commit_id),
|
|
492
|
-
untracked: false,
|
|
493
|
-
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
527
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
498
528
|
enum TrackedRowSource {
|
|
499
529
|
Global,
|
|
@@ -509,17 +539,17 @@ fn tracked_source_from_version_id(version_id: &str) -> TrackedRowSource {
|
|
|
509
539
|
}
|
|
510
540
|
|
|
511
541
|
fn project_tracked_row(
|
|
512
|
-
row:
|
|
542
|
+
row: MaterializedTrackedStateRow,
|
|
513
543
|
view_version_id: &str,
|
|
514
544
|
source: TrackedRowSource,
|
|
515
|
-
) ->
|
|
516
|
-
|
|
545
|
+
) -> MaterializedLiveStateRow {
|
|
546
|
+
MaterializedLiveStateRow {
|
|
517
547
|
entity_id: row.entity_id,
|
|
518
548
|
schema_key: row.schema_key,
|
|
519
549
|
file_id: row.file_id,
|
|
520
550
|
snapshot_content: row.snapshot_content,
|
|
521
551
|
metadata: row.metadata,
|
|
522
|
-
|
|
552
|
+
deleted: row.deleted,
|
|
523
553
|
created_at: row.created_at,
|
|
524
554
|
updated_at: row.updated_at,
|
|
525
555
|
global: source == TrackedRowSource::Global,
|
|
@@ -530,164 +560,6 @@ fn project_tracked_row(
|
|
|
530
560
|
}
|
|
531
561
|
}
|
|
532
562
|
|
|
533
|
-
fn live_state_row_matches_filter(row: &LiveStateRow, filter: &LiveStateFilter) -> bool {
|
|
534
|
-
if !filter.schema_keys.is_empty() && !filter.schema_keys.contains(&row.schema_key) {
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
if !filter.entity_ids.is_empty() && !filter.entity_ids.contains(&row.entity_id) {
|
|
538
|
-
return false;
|
|
539
|
-
}
|
|
540
|
-
if !filter.file_ids.is_empty()
|
|
541
|
-
&& !filter
|
|
542
|
-
.file_ids
|
|
543
|
-
.iter()
|
|
544
|
-
.any(|filter| nullable_filter_matches(filter, &row.file_id))
|
|
545
|
-
{
|
|
546
|
-
return false;
|
|
547
|
-
}
|
|
548
|
-
true
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
fn nullable_filter_matches(
|
|
552
|
-
filter: &crate::NullableKeyFilter<String>,
|
|
553
|
-
value: &Option<String>,
|
|
554
|
-
) -> bool {
|
|
555
|
-
match filter {
|
|
556
|
-
crate::NullableKeyFilter::Any => true,
|
|
557
|
-
crate::NullableKeyFilter::Null => value.is_none(),
|
|
558
|
-
crate::NullableKeyFilter::Value(expected) => value.as_ref() == Some(expected),
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
fn grouped_live_rows_by_commit<'a>(
|
|
563
|
-
rows: &[&'a LiveStateRow],
|
|
564
|
-
) -> Result<Vec<(&'a str, Vec<&'a LiveStateRow>)>, LixError> {
|
|
565
|
-
let mut grouped = Vec::<(&str, Vec<&LiveStateRow>)>::new();
|
|
566
|
-
for row in rows {
|
|
567
|
-
let commit_id = row.commit_id.as_deref().ok_or_else(|| {
|
|
568
|
-
LixError::new(
|
|
569
|
-
"LIX_ERROR_UNKNOWN",
|
|
570
|
-
"tracked live-state row is missing commit_id before tracked root write",
|
|
571
|
-
)
|
|
572
|
-
})?;
|
|
573
|
-
if let Some((_, bucket)) = grouped
|
|
574
|
-
.iter_mut()
|
|
575
|
-
.find(|(existing_commit_id, _)| *existing_commit_id == commit_id)
|
|
576
|
-
{
|
|
577
|
-
bucket.push(*row);
|
|
578
|
-
} else {
|
|
579
|
-
grouped.push((commit_id, vec![*row]));
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
Ok(grouped)
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
586
|
-
struct RootWriteScope {
|
|
587
|
-
version_id: String,
|
|
588
|
-
global: bool,
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
fn validate_root_local_write_batch(
|
|
592
|
-
commit_id: &str,
|
|
593
|
-
rows: &[&LiveStateRow],
|
|
594
|
-
) -> Result<(), LixError> {
|
|
595
|
-
let mut root_scope = None::<RootWriteScope>;
|
|
596
|
-
for row in rows
|
|
597
|
-
.iter()
|
|
598
|
-
.copied()
|
|
599
|
-
.filter(|row| row.schema_key != COMMIT_SCHEMA_KEY)
|
|
600
|
-
{
|
|
601
|
-
let scope = RootWriteScope {
|
|
602
|
-
version_id: row.version_id.clone(),
|
|
603
|
-
global: row.global,
|
|
604
|
-
};
|
|
605
|
-
if row.global != (row.version_id == GLOBAL_VERSION_ID) {
|
|
606
|
-
return Err(LixError::new(
|
|
607
|
-
"LIX_ERROR_UNKNOWN",
|
|
608
|
-
format!(
|
|
609
|
-
"tracked root write for commit '{commit_id}' has invalid storage scope: version_id='{}', global={}",
|
|
610
|
-
row.version_id, row.global
|
|
611
|
-
),
|
|
612
|
-
));
|
|
613
|
-
}
|
|
614
|
-
if let Some(existing) = &root_scope {
|
|
615
|
-
if existing != &scope {
|
|
616
|
-
return Err(LixError::new(
|
|
617
|
-
"LIX_ERROR_UNKNOWN",
|
|
618
|
-
format!(
|
|
619
|
-
"tracked root write for commit '{commit_id}' mixes multiple storage scopes"
|
|
620
|
-
),
|
|
621
|
-
));
|
|
622
|
-
}
|
|
623
|
-
} else {
|
|
624
|
-
root_scope = Some(scope);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
Ok(())
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
fn parent_commit_id_for_commit_rows(
|
|
631
|
-
commit_id: &str,
|
|
632
|
-
rows: &[&LiveStateRow],
|
|
633
|
-
) -> Result<Option<String>, LixError> {
|
|
634
|
-
let Some(row) = rows.iter().find(|row| {
|
|
635
|
-
row.schema_key == COMMIT_SCHEMA_KEY
|
|
636
|
-
&& row
|
|
637
|
-
.entity_id
|
|
638
|
-
.as_string()
|
|
639
|
-
.is_ok_and(|entity_id| entity_id == commit_id)
|
|
640
|
-
}) else {
|
|
641
|
-
return Ok(None);
|
|
642
|
-
};
|
|
643
|
-
parent_commit_id_from_commit_row(row)
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
fn parent_commit_id_from_commit_row(row: &&LiveStateRow) -> Result<Option<String>, LixError> {
|
|
647
|
-
let snapshot = serde_json::from_str::<serde_json::Value>(
|
|
648
|
-
row.snapshot_content.as_deref().ok_or_else(|| {
|
|
649
|
-
LixError::new(
|
|
650
|
-
"LIX_ERROR_UNKNOWN",
|
|
651
|
-
"tracked root commit row is missing snapshot_content",
|
|
652
|
-
)
|
|
653
|
-
})?,
|
|
654
|
-
)
|
|
655
|
-
.map_err(|error| {
|
|
656
|
-
LixError::new(
|
|
657
|
-
"LIX_ERROR_UNKNOWN",
|
|
658
|
-
format!("tracked root commit snapshot parse failed: {error}"),
|
|
659
|
-
)
|
|
660
|
-
})?;
|
|
661
|
-
let Some(parent_commit_ids_value) = snapshot.get("parent_commit_ids") else {
|
|
662
|
-
return Err(LixError::new(
|
|
663
|
-
"LIX_ERROR_UNKNOWN",
|
|
664
|
-
"tracked root commit row is missing parent_commit_ids",
|
|
665
|
-
));
|
|
666
|
-
};
|
|
667
|
-
let Some(parent_commit_ids_array) = parent_commit_ids_value.as_array() else {
|
|
668
|
-
return Err(LixError::new(
|
|
669
|
-
"LIX_ERROR_UNKNOWN",
|
|
670
|
-
"tracked root commit parent_commit_ids must be an array",
|
|
671
|
-
));
|
|
672
|
-
};
|
|
673
|
-
let parent_commit_ids = parent_commit_ids_array
|
|
674
|
-
.iter()
|
|
675
|
-
.map(|value| {
|
|
676
|
-
value.as_str().map(str::to_string).ok_or_else(|| {
|
|
677
|
-
LixError::new(
|
|
678
|
-
"LIX_ERROR_UNKNOWN",
|
|
679
|
-
"tracked root commit parent_commit_ids must contain strings",
|
|
680
|
-
)
|
|
681
|
-
})
|
|
682
|
-
})
|
|
683
|
-
.collect::<Result<Vec<_>, _>>()?;
|
|
684
|
-
|
|
685
|
-
// Tracked roots inherit from the first parent. Merge commits record
|
|
686
|
-
// additional parents for graph ancestry, but the merge operation has
|
|
687
|
-
// already materialized the source-side rows as target-version writes.
|
|
688
|
-
Ok(parent_commit_ids.into_iter().next())
|
|
689
|
-
}
|
|
690
|
-
|
|
691
563
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
692
564
|
enum LiveStateLookupSource {
|
|
693
565
|
Untracked,
|
|
@@ -754,21 +626,25 @@ mod tests {
|
|
|
754
626
|
|
|
755
627
|
use super::*;
|
|
756
628
|
use crate::backend::{testing::UnitTestBackend, Backend};
|
|
757
|
-
use crate::
|
|
629
|
+
use crate::commit_store::{CommitDraftRef, CommitStoreContext};
|
|
758
630
|
use crate::entity_identity::EntityIdentity;
|
|
759
|
-
use crate::json_store::
|
|
631
|
+
use crate::json_store::{
|
|
632
|
+
JsonStoreContext, JsonWritePlacementRef, NormalizedJson, NormalizedJsonRef,
|
|
633
|
+
};
|
|
760
634
|
use crate::live_state::LiveStateFilter;
|
|
761
|
-
use crate::storage::{StorageContext, StorageWriteTransaction};
|
|
762
|
-
use crate::tracked_state::TrackedStateScanRequest;
|
|
635
|
+
use crate::storage::{StorageContext, StorageWriteSet, StorageWriteTransaction};
|
|
636
|
+
use crate::tracked_state::{TrackedStateDeltaRef, TrackedStateScanRequest};
|
|
763
637
|
use crate::untracked_state::{MaterializedUntrackedStateRow, UntrackedStateContext};
|
|
764
638
|
use crate::NullableKeyFilter;
|
|
765
639
|
use serde_json::json;
|
|
766
640
|
|
|
641
|
+
const COMMIT_SCHEMA_KEY: &str = "lix_commit";
|
|
642
|
+
|
|
767
643
|
fn live_state_context() -> LiveStateContext {
|
|
768
644
|
LiveStateContext::new(
|
|
769
645
|
crate::tracked_state::TrackedStateContext::new(),
|
|
770
646
|
crate::untracked_state::UntrackedStateContext::new(),
|
|
771
|
-
crate::commit_graph::CommitGraphContext::new(
|
|
647
|
+
crate::commit_graph::CommitGraphContext::new(),
|
|
772
648
|
)
|
|
773
649
|
}
|
|
774
650
|
|
|
@@ -777,16 +653,14 @@ mod tests {
|
|
|
777
653
|
rows: &[MaterializedUntrackedStateRow],
|
|
778
654
|
) {
|
|
779
655
|
let mut writes = StorageWriteSet::new();
|
|
780
|
-
let canonical_rows =
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
.expect("untracked rows should canonicalize")
|
|
786
|
-
};
|
|
656
|
+
let canonical_rows = rows
|
|
657
|
+
.iter()
|
|
658
|
+
.map(|row| crate::test_support::untracked_state_row_from_materialized(&mut writes, row))
|
|
659
|
+
.collect::<Result<Vec<_>, _>>()
|
|
660
|
+
.expect("untracked rows should canonicalize");
|
|
787
661
|
UntrackedStateContext::new()
|
|
788
662
|
.writer(&mut writes)
|
|
789
|
-
.stage_rows(
|
|
663
|
+
.stage_rows(canonical_rows.iter().map(|row| row.as_ref()))
|
|
790
664
|
.expect("untracked rows should write");
|
|
791
665
|
writes
|
|
792
666
|
.apply(store)
|
|
@@ -794,6 +668,174 @@ mod tests {
|
|
|
794
668
|
.expect("untracked rows should apply");
|
|
795
669
|
}
|
|
796
670
|
|
|
671
|
+
async fn write_empty_commits_to_store(
|
|
672
|
+
store: &mut (impl StorageWriteTransaction + ?Sized),
|
|
673
|
+
commit_ids: &[&str],
|
|
674
|
+
) {
|
|
675
|
+
let mut writes = StorageWriteSet::new();
|
|
676
|
+
for commit_id in commit_ids {
|
|
677
|
+
let commit_change_id = format!("{commit_id}:commit");
|
|
678
|
+
CommitStoreContext::new()
|
|
679
|
+
.writer(&mut *store, &mut writes)
|
|
680
|
+
.stage_commit_draft(
|
|
681
|
+
CommitDraftRef {
|
|
682
|
+
id: commit_id,
|
|
683
|
+
change_id: &commit_change_id,
|
|
684
|
+
parent_ids: &[],
|
|
685
|
+
author_account_ids: &[],
|
|
686
|
+
created_at: "1970-01-01T00:00:00.000Z",
|
|
687
|
+
},
|
|
688
|
+
Vec::new(),
|
|
689
|
+
Vec::new(),
|
|
690
|
+
)
|
|
691
|
+
.await
|
|
692
|
+
.expect("empty commit should stage");
|
|
693
|
+
}
|
|
694
|
+
writes
|
|
695
|
+
.apply(store)
|
|
696
|
+
.await
|
|
697
|
+
.expect("empty commits should apply");
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
async fn stage_materialized_live_rows(
|
|
701
|
+
store: &mut (impl StorageReader + ?Sized),
|
|
702
|
+
writes: &mut StorageWriteSet,
|
|
703
|
+
_json_writer: &mut crate::json_store::JsonStoreWriter,
|
|
704
|
+
rows: &[MaterializedLiveStateRow],
|
|
705
|
+
) -> Result<(), LixError> {
|
|
706
|
+
let mut untracked_rows = Vec::new();
|
|
707
|
+
let mut tracked_rows_by_commit = std::collections::BTreeMap::<
|
|
708
|
+
String,
|
|
709
|
+
Vec<(crate::commit_store::Change, String, String)>,
|
|
710
|
+
>::new();
|
|
711
|
+
let mut parent_by_commit = std::collections::BTreeMap::<String, Option<String>>::new();
|
|
712
|
+
|
|
713
|
+
for row in rows {
|
|
714
|
+
if row.untracked {
|
|
715
|
+
let materialized = crate::untracked_state::MaterializedUntrackedStateRow::from(row);
|
|
716
|
+
let canonical = crate::test_support::untracked_state_row_from_materialized(
|
|
717
|
+
writes,
|
|
718
|
+
&materialized,
|
|
719
|
+
)?;
|
|
720
|
+
untracked_rows.push(canonical);
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
let materialized = MaterializedTrackedStateRow::try_from(row)?;
|
|
724
|
+
let commit_id = row.commit_id.clone().ok_or_else(|| {
|
|
725
|
+
LixError::new("LIX_ERROR_UNKNOWN", "test tracked row missing commit_id")
|
|
726
|
+
})?;
|
|
727
|
+
if row.schema_key == COMMIT_SCHEMA_KEY {
|
|
728
|
+
parent_by_commit.insert(
|
|
729
|
+
commit_id.clone(),
|
|
730
|
+
parent_commit_id_from_test_commit_row(row)?,
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
if row.schema_key != COMMIT_SCHEMA_KEY {
|
|
734
|
+
let change = crate::test_support::tracked_change_from_materialized(&materialized)?;
|
|
735
|
+
stage_tracked_materialized_json(writes, &commit_id, &materialized)?;
|
|
736
|
+
tracked_rows_by_commit.entry(commit_id).or_default().push((
|
|
737
|
+
change,
|
|
738
|
+
materialized.created_at,
|
|
739
|
+
materialized.updated_at,
|
|
740
|
+
));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
UntrackedStateContext::new()
|
|
745
|
+
.writer(writes)
|
|
746
|
+
.stage_rows(untracked_rows.iter().map(|row| row.as_ref()))?;
|
|
747
|
+
for (commit_id, rows) in tracked_rows_by_commit {
|
|
748
|
+
let parent_commit_id = parent_by_commit.remove(&commit_id).flatten();
|
|
749
|
+
let parent_ids = parent_commit_id
|
|
750
|
+
.as_ref()
|
|
751
|
+
.map(|parent| vec![parent.clone()])
|
|
752
|
+
.unwrap_or_default();
|
|
753
|
+
let commit_change_id = format!("{commit_id}:commit");
|
|
754
|
+
let commit = CommitDraftRef {
|
|
755
|
+
id: &commit_id,
|
|
756
|
+
change_id: &commit_change_id,
|
|
757
|
+
parent_ids: &parent_ids,
|
|
758
|
+
author_account_ids: &[],
|
|
759
|
+
created_at: rows
|
|
760
|
+
.first()
|
|
761
|
+
.map(|(change, _, _)| change.created_at.as_str())
|
|
762
|
+
.unwrap_or("1970-01-01T00:00:00.000Z"),
|
|
763
|
+
};
|
|
764
|
+
let staged = CommitStoreContext::new()
|
|
765
|
+
.writer(&mut *store, writes)
|
|
766
|
+
.stage_tracked_commit_draft(
|
|
767
|
+
commit,
|
|
768
|
+
rows.iter().map(|(change, _, _)| change.as_ref()).collect(),
|
|
769
|
+
Vec::new(),
|
|
770
|
+
)
|
|
771
|
+
.await?;
|
|
772
|
+
let deltas = rows
|
|
773
|
+
.iter()
|
|
774
|
+
.zip(&staged.authored_locators)
|
|
775
|
+
.map(
|
|
776
|
+
|((change, created_at, updated_at), locator)| TrackedStateDeltaRef {
|
|
777
|
+
change: change.as_ref(),
|
|
778
|
+
locator: locator.as_ref(),
|
|
779
|
+
created_at,
|
|
780
|
+
updated_at,
|
|
781
|
+
},
|
|
782
|
+
)
|
|
783
|
+
.collect::<Vec<_>>();
|
|
784
|
+
TrackedStateContext::new()
|
|
785
|
+
.writer(&mut *store, writes)
|
|
786
|
+
.stage_delta(&commit_id, parent_commit_id.as_deref(), &deltas)
|
|
787
|
+
.await?;
|
|
788
|
+
}
|
|
789
|
+
Ok(())
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
fn stage_tracked_materialized_json(
|
|
793
|
+
writes: &mut StorageWriteSet,
|
|
794
|
+
commit_id: &str,
|
|
795
|
+
row: &MaterializedTrackedStateRow,
|
|
796
|
+
) -> Result<(), LixError> {
|
|
797
|
+
let mut payloads = Vec::new();
|
|
798
|
+
if let Some(snapshot) = row.snapshot_content.as_deref() {
|
|
799
|
+
payloads.push(NormalizedJson::from_arc_unchecked(Arc::from(snapshot)));
|
|
800
|
+
}
|
|
801
|
+
if let Some(metadata) = row.metadata.as_ref() {
|
|
802
|
+
payloads.push(NormalizedJson::from_arc_unchecked(Arc::from(
|
|
803
|
+
crate::serialize_row_metadata(metadata),
|
|
804
|
+
)));
|
|
805
|
+
}
|
|
806
|
+
JsonStoreContext::new().writer().stage_batch(
|
|
807
|
+
writes,
|
|
808
|
+
JsonWritePlacementRef::CommitPack {
|
|
809
|
+
commit_id,
|
|
810
|
+
pack_id: 0,
|
|
811
|
+
},
|
|
812
|
+
payloads
|
|
813
|
+
.iter()
|
|
814
|
+
.map(|payload| NormalizedJsonRef::from(payload)),
|
|
815
|
+
)?;
|
|
816
|
+
Ok(())
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
fn parent_commit_id_from_test_commit_row(
|
|
820
|
+
row: &MaterializedLiveStateRow,
|
|
821
|
+
) -> Result<Option<String>, LixError> {
|
|
822
|
+
let Some(metadata) = row.metadata.as_deref() else {
|
|
823
|
+
return Ok(None);
|
|
824
|
+
};
|
|
825
|
+
let metadata = serde_json::from_str::<serde_json::Value>(metadata).map_err(|error| {
|
|
826
|
+
LixError::new(
|
|
827
|
+
"LIX_ERROR_UNKNOWN",
|
|
828
|
+
format!("test commit row has invalid metadata: {error}"),
|
|
829
|
+
)
|
|
830
|
+
})?;
|
|
831
|
+
Ok(metadata
|
|
832
|
+
.get("test_parents")
|
|
833
|
+
.and_then(serde_json::Value::as_array)
|
|
834
|
+
.and_then(|parents| parents.first())
|
|
835
|
+
.and_then(serde_json::Value::as_str)
|
|
836
|
+
.map(str::to_string))
|
|
837
|
+
}
|
|
838
|
+
|
|
797
839
|
#[tokio::test]
|
|
798
840
|
async fn live_state_overlays_untracked_rows() {
|
|
799
841
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
@@ -808,19 +850,18 @@ mod tests {
|
|
|
808
850
|
let mut writes = StorageWriteSet::new();
|
|
809
851
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
810
852
|
{
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
.expect("tracked row should stage");
|
|
853
|
+
stage_materialized_live_rows(
|
|
854
|
+
transaction.as_mut(),
|
|
855
|
+
&mut writes,
|
|
856
|
+
&mut json_writer,
|
|
857
|
+
&[tracked_row_with_commit(
|
|
858
|
+
"tracked-value",
|
|
859
|
+
Some("change-tracked"),
|
|
860
|
+
"commit-tracked",
|
|
861
|
+
)],
|
|
862
|
+
)
|
|
863
|
+
.await
|
|
864
|
+
.expect("tracked row should stage");
|
|
824
865
|
}
|
|
825
866
|
writes
|
|
826
867
|
.apply(&mut transaction.as_mut())
|
|
@@ -880,19 +921,18 @@ mod tests {
|
|
|
880
921
|
let mut writes = StorageWriteSet::new();
|
|
881
922
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
882
923
|
{
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
.expect("tracked row should stage");
|
|
924
|
+
stage_materialized_live_rows(
|
|
925
|
+
transaction.as_mut(),
|
|
926
|
+
&mut writes,
|
|
927
|
+
&mut json_writer,
|
|
928
|
+
&[tracked_row_with_commit(
|
|
929
|
+
"tracked-value",
|
|
930
|
+
Some("change-tracked"),
|
|
931
|
+
"commit-tracked",
|
|
932
|
+
)],
|
|
933
|
+
)
|
|
934
|
+
.await
|
|
935
|
+
.expect("tracked row should stage");
|
|
896
936
|
}
|
|
897
937
|
writes
|
|
898
938
|
.apply(&mut transaction.as_mut())
|
|
@@ -932,19 +972,18 @@ mod tests {
|
|
|
932
972
|
let mut writes = StorageWriteSet::new();
|
|
933
973
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
934
974
|
{
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
.expect("tracked row should stage");
|
|
975
|
+
stage_materialized_live_rows(
|
|
976
|
+
transaction.as_mut(),
|
|
977
|
+
&mut writes,
|
|
978
|
+
&mut json_writer,
|
|
979
|
+
&[tracked_row_with_commit(
|
|
980
|
+
"tracked-value",
|
|
981
|
+
Some("change-tracked"),
|
|
982
|
+
"commit-tracked",
|
|
983
|
+
)],
|
|
984
|
+
)
|
|
985
|
+
.await
|
|
986
|
+
.expect("tracked row should stage");
|
|
948
987
|
}
|
|
949
988
|
writes
|
|
950
989
|
.apply(&mut transaction.as_mut())
|
|
@@ -961,14 +1000,15 @@ mod tests {
|
|
|
961
1000
|
.await;
|
|
962
1001
|
{
|
|
963
1002
|
let mut writes = StorageWriteSet::new();
|
|
1003
|
+
let identity = crate::untracked_state::UntrackedStateIdentity {
|
|
1004
|
+
version_id: "global".to_string(),
|
|
1005
|
+
schema_key: "lix_key_value".to_string(),
|
|
1006
|
+
entity_id: EntityIdentity::single("selected-tab"),
|
|
1007
|
+
file_id: None,
|
|
1008
|
+
};
|
|
964
1009
|
UntrackedStateContext::new()
|
|
965
1010
|
.writer(&mut writes)
|
|
966
|
-
.stage_delete_rows(
|
|
967
|
-
version_id: "global".to_string(),
|
|
968
|
-
schema_key: "lix_key_value".to_string(),
|
|
969
|
-
entity_id: EntityIdentity::single("selected-tab"),
|
|
970
|
-
file_id: None,
|
|
971
|
-
}]);
|
|
1011
|
+
.stage_delete_rows(std::iter::once(identity.as_ref()));
|
|
972
1012
|
writes
|
|
973
1013
|
.apply(&mut transaction.as_mut())
|
|
974
1014
|
.await
|
|
@@ -1007,11 +1047,14 @@ mod tests {
|
|
|
1007
1047
|
let mut writes = StorageWriteSet::new();
|
|
1008
1048
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1009
1049
|
{
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1050
|
+
stage_materialized_live_rows(
|
|
1051
|
+
transaction.as_mut(),
|
|
1052
|
+
&mut writes,
|
|
1053
|
+
&mut json_writer,
|
|
1054
|
+
&rows,
|
|
1055
|
+
)
|
|
1056
|
+
.await
|
|
1057
|
+
.expect("tracked row should stage");
|
|
1015
1058
|
}
|
|
1016
1059
|
writes
|
|
1017
1060
|
.apply(&mut transaction.as_mut())
|
|
@@ -1026,6 +1069,7 @@ mod tests {
|
|
|
1026
1069
|
],
|
|
1027
1070
|
)
|
|
1028
1071
|
.await;
|
|
1072
|
+
write_empty_commits_to_store(transaction.as_mut(), &["commit-version-a"]).await;
|
|
1029
1073
|
transaction.commit().await.expect("commit should persist");
|
|
1030
1074
|
|
|
1031
1075
|
let loaded = load_selected_tab_at(&live_state, storage.clone(), "version-a")
|
|
@@ -1050,7 +1094,7 @@ mod tests {
|
|
|
1050
1094
|
let live_state = LiveStateContext::new(
|
|
1051
1095
|
tracked_state.clone(),
|
|
1052
1096
|
UntrackedStateContext::new(),
|
|
1053
|
-
crate::commit_graph::CommitGraphContext::new(
|
|
1097
|
+
crate::commit_graph::CommitGraphContext::new(),
|
|
1054
1098
|
);
|
|
1055
1099
|
|
|
1056
1100
|
let mut transaction = storage
|
|
@@ -1066,11 +1110,14 @@ mod tests {
|
|
|
1066
1110
|
let mut writes = StorageWriteSet::new();
|
|
1067
1111
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1068
1112
|
{
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1113
|
+
stage_materialized_live_rows(
|
|
1114
|
+
transaction.as_mut(),
|
|
1115
|
+
&mut writes,
|
|
1116
|
+
&mut json_writer,
|
|
1117
|
+
&rows,
|
|
1118
|
+
)
|
|
1119
|
+
.await
|
|
1120
|
+
.expect("global tracked row should stage");
|
|
1074
1121
|
}
|
|
1075
1122
|
writes
|
|
1076
1123
|
.apply(&mut transaction.as_mut())
|
|
@@ -1085,6 +1132,7 @@ mod tests {
|
|
|
1085
1132
|
],
|
|
1086
1133
|
)
|
|
1087
1134
|
.await;
|
|
1135
|
+
write_empty_commits_to_store(transaction.as_mut(), &["commit-main"]).await;
|
|
1088
1136
|
transaction.commit().await.expect("commit should persist");
|
|
1089
1137
|
|
|
1090
1138
|
let loaded = load_selected_tab_at(&live_state, storage.clone(), "main")
|
|
@@ -1130,11 +1178,14 @@ mod tests {
|
|
|
1130
1178
|
let mut writes = StorageWriteSet::new();
|
|
1131
1179
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1132
1180
|
{
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1181
|
+
stage_materialized_live_rows(
|
|
1182
|
+
transaction.as_mut(),
|
|
1183
|
+
&mut writes,
|
|
1184
|
+
&mut json_writer,
|
|
1185
|
+
&rows,
|
|
1186
|
+
)
|
|
1187
|
+
.await
|
|
1188
|
+
.expect("tracked rows should stage");
|
|
1138
1189
|
}
|
|
1139
1190
|
writes
|
|
1140
1191
|
.apply(&mut transaction.as_mut())
|
|
@@ -1187,11 +1238,14 @@ mod tests {
|
|
|
1187
1238
|
let mut writes = StorageWriteSet::new();
|
|
1188
1239
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1189
1240
|
{
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1241
|
+
stage_materialized_live_rows(
|
|
1242
|
+
transaction.as_mut(),
|
|
1243
|
+
&mut writes,
|
|
1244
|
+
&mut json_writer,
|
|
1245
|
+
&rows,
|
|
1246
|
+
)
|
|
1247
|
+
.await
|
|
1248
|
+
.expect("tracked rows should stage");
|
|
1195
1249
|
}
|
|
1196
1250
|
writes
|
|
1197
1251
|
.apply(&mut transaction.as_mut())
|
|
@@ -1244,11 +1298,14 @@ mod tests {
|
|
|
1244
1298
|
let mut writes = StorageWriteSet::new();
|
|
1245
1299
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1246
1300
|
{
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1301
|
+
stage_materialized_live_rows(
|
|
1302
|
+
transaction.as_mut(),
|
|
1303
|
+
&mut writes,
|
|
1304
|
+
&mut json_writer,
|
|
1305
|
+
&rows,
|
|
1306
|
+
)
|
|
1307
|
+
.await
|
|
1308
|
+
.expect("tracked rows should stage");
|
|
1252
1309
|
}
|
|
1253
1310
|
writes
|
|
1254
1311
|
.apply(&mut transaction.as_mut())
|
|
@@ -1303,11 +1360,14 @@ mod tests {
|
|
|
1303
1360
|
let mut writes = StorageWriteSet::new();
|
|
1304
1361
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1305
1362
|
{
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1363
|
+
stage_materialized_live_rows(
|
|
1364
|
+
transaction.as_mut(),
|
|
1365
|
+
&mut writes,
|
|
1366
|
+
&mut json_writer,
|
|
1367
|
+
&rows,
|
|
1368
|
+
)
|
|
1369
|
+
.await
|
|
1370
|
+
.expect("rows should stage");
|
|
1311
1371
|
}
|
|
1312
1372
|
writes
|
|
1313
1373
|
.apply(&mut transaction.as_mut())
|
|
@@ -1355,11 +1415,14 @@ mod tests {
|
|
|
1355
1415
|
let mut writes = StorageWriteSet::new();
|
|
1356
1416
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1357
1417
|
{
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1418
|
+
stage_materialized_live_rows(
|
|
1419
|
+
transaction.as_mut(),
|
|
1420
|
+
&mut writes,
|
|
1421
|
+
&mut json_writer,
|
|
1422
|
+
&rows,
|
|
1423
|
+
)
|
|
1424
|
+
.await
|
|
1425
|
+
.expect("rows should stage");
|
|
1363
1426
|
}
|
|
1364
1427
|
writes
|
|
1365
1428
|
.apply(&mut transaction.as_mut())
|
|
@@ -1374,6 +1437,7 @@ mod tests {
|
|
|
1374
1437
|
],
|
|
1375
1438
|
)
|
|
1376
1439
|
.await;
|
|
1440
|
+
write_empty_commits_to_store(transaction.as_mut(), &["commit-version-a"]).await;
|
|
1377
1441
|
transaction.commit().await.expect("commit should persist");
|
|
1378
1442
|
|
|
1379
1443
|
let rows = scan_selected_tab_at(&live_state, storage.clone(), "version-a", false)
|
|
@@ -1408,11 +1472,14 @@ mod tests {
|
|
|
1408
1472
|
let mut writes = StorageWriteSet::new();
|
|
1409
1473
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1410
1474
|
{
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1475
|
+
stage_materialized_live_rows(
|
|
1476
|
+
transaction.as_mut(),
|
|
1477
|
+
&mut writes,
|
|
1478
|
+
&mut json_writer,
|
|
1479
|
+
&rows,
|
|
1480
|
+
)
|
|
1481
|
+
.await
|
|
1482
|
+
.expect("tracked row should stage");
|
|
1416
1483
|
}
|
|
1417
1484
|
writes
|
|
1418
1485
|
.apply(&mut transaction.as_mut())
|
|
@@ -1459,11 +1526,14 @@ mod tests {
|
|
|
1459
1526
|
let mut writes = StorageWriteSet::new();
|
|
1460
1527
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1461
1528
|
{
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1529
|
+
stage_materialized_live_rows(
|
|
1530
|
+
transaction.as_mut(),
|
|
1531
|
+
&mut writes,
|
|
1532
|
+
&mut json_writer,
|
|
1533
|
+
&rows,
|
|
1534
|
+
)
|
|
1535
|
+
.await
|
|
1536
|
+
.expect("rows should stage");
|
|
1467
1537
|
}
|
|
1468
1538
|
writes
|
|
1469
1539
|
.apply(&mut transaction.as_mut())
|
|
@@ -1515,11 +1585,14 @@ mod tests {
|
|
|
1515
1585
|
let mut writes = StorageWriteSet::new();
|
|
1516
1586
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1517
1587
|
{
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1588
|
+
stage_materialized_live_rows(
|
|
1589
|
+
transaction.as_mut(),
|
|
1590
|
+
&mut writes,
|
|
1591
|
+
&mut json_writer,
|
|
1592
|
+
&rows,
|
|
1593
|
+
)
|
|
1594
|
+
.await
|
|
1595
|
+
.expect("tracked rows should stage");
|
|
1523
1596
|
}
|
|
1524
1597
|
writes
|
|
1525
1598
|
.apply(&mut transaction.as_mut())
|
|
@@ -1550,158 +1623,6 @@ mod tests {
|
|
|
1550
1623
|
assert_eq!(tombstones[0].snapshot_content, None);
|
|
1551
1624
|
}
|
|
1552
1625
|
|
|
1553
|
-
#[tokio::test]
|
|
1554
|
-
async fn scan_rows_projects_commit_graph_facts_as_global_rows() {
|
|
1555
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1556
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1557
|
-
let live_state = live_state_context();
|
|
1558
|
-
append_commit_change(storage.clone(), "commit-a").await;
|
|
1559
|
-
write_version_refs(storage.clone(), &[version_ref_row("version-a", "commit-a")]).await;
|
|
1560
|
-
|
|
1561
|
-
let rows = live_state
|
|
1562
|
-
.reader(storage.clone())
|
|
1563
|
-
.scan_rows(&LiveStateScanRequest {
|
|
1564
|
-
filter: LiveStateFilter {
|
|
1565
|
-
schema_keys: vec![COMMIT_SCHEMA_KEY.to_string()],
|
|
1566
|
-
version_ids: vec!["version-a".to_string()],
|
|
1567
|
-
..LiveStateFilter::default()
|
|
1568
|
-
},
|
|
1569
|
-
..LiveStateScanRequest::default()
|
|
1570
|
-
})
|
|
1571
|
-
.await
|
|
1572
|
-
.expect("commit rows should scan");
|
|
1573
|
-
|
|
1574
|
-
assert_eq!(rows.len(), 1);
|
|
1575
|
-
assert_eq!(rows[0].entity_id.as_string().as_deref(), Ok("commit-a"));
|
|
1576
|
-
assert_eq!(rows[0].schema_key, COMMIT_SCHEMA_KEY);
|
|
1577
|
-
assert_eq!(rows[0].version_id, "version-a");
|
|
1578
|
-
assert!(rows[0].global);
|
|
1579
|
-
assert!(!rows[0].untracked);
|
|
1580
|
-
assert_eq!(rows[0].change_id.as_deref(), Some("change-commit-a"));
|
|
1581
|
-
assert_eq!(rows[0].commit_id.as_deref(), Some("commit-a"));
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
#[tokio::test]
|
|
1585
|
-
async fn load_row_reads_commit_graph_fact() {
|
|
1586
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1587
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1588
|
-
let live_state = live_state_context();
|
|
1589
|
-
append_commit_change(storage.clone(), "commit-a").await;
|
|
1590
|
-
write_version_refs(storage.clone(), &[version_ref_row("version-a", "commit-a")]).await;
|
|
1591
|
-
|
|
1592
|
-
let row = live_state
|
|
1593
|
-
.reader(storage.clone())
|
|
1594
|
-
.load_row(&LiveStateRowRequest {
|
|
1595
|
-
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
1596
|
-
version_id: "version-a".to_string(),
|
|
1597
|
-
entity_id: crate::entity_identity::EntityIdentity::single("commit-a"),
|
|
1598
|
-
file_id: NullableKeyFilter::Null,
|
|
1599
|
-
})
|
|
1600
|
-
.await
|
|
1601
|
-
.expect("commit row should load")
|
|
1602
|
-
.expect("commit row should exist");
|
|
1603
|
-
|
|
1604
|
-
assert_eq!(row.entity_id.as_string().as_deref(), Ok("commit-a"));
|
|
1605
|
-
assert_eq!(row.version_id, "version-a");
|
|
1606
|
-
assert!(row.global);
|
|
1607
|
-
assert_eq!(row.change_id.as_deref(), Some("change-commit-a"));
|
|
1608
|
-
assert_eq!(row.commit_id.as_deref(), Some("commit-a"));
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
#[tokio::test]
|
|
1612
|
-
async fn load_commit_row_does_not_project_into_missing_version() {
|
|
1613
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1614
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1615
|
-
let live_state = live_state_context();
|
|
1616
|
-
append_commit_change(storage.clone(), "commit-a").await;
|
|
1617
|
-
|
|
1618
|
-
let row = live_state
|
|
1619
|
-
.reader(storage.clone())
|
|
1620
|
-
.load_row(&LiveStateRowRequest {
|
|
1621
|
-
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
1622
|
-
version_id: "missing-version".to_string(),
|
|
1623
|
-
entity_id: crate::entity_identity::EntityIdentity::single("commit-a"),
|
|
1624
|
-
file_id: NullableKeyFilter::Null,
|
|
1625
|
-
})
|
|
1626
|
-
.await
|
|
1627
|
-
.expect("commit row load should succeed");
|
|
1628
|
-
|
|
1629
|
-
assert_eq!(
|
|
1630
|
-
row, None,
|
|
1631
|
-
"commit rows must not be projected into a missing version scope"
|
|
1632
|
-
);
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
#[tokio::test]
|
|
1636
|
-
async fn writer_rejects_tracked_root_batches_that_mix_global_and_version_rows() {
|
|
1637
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1638
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1639
|
-
let live_state = live_state_context();
|
|
1640
|
-
let mut transaction = storage
|
|
1641
|
-
.begin_write_transaction()
|
|
1642
|
-
.await
|
|
1643
|
-
.expect("transaction should open");
|
|
1644
|
-
|
|
1645
|
-
let error = {
|
|
1646
|
-
let rows = [
|
|
1647
|
-
tracked_row_at_with_commit(
|
|
1648
|
-
"global",
|
|
1649
|
-
"global-row",
|
|
1650
|
-
Some("change-global"),
|
|
1651
|
-
"commit-shared",
|
|
1652
|
-
),
|
|
1653
|
-
tracked_row_at_with_commit(
|
|
1654
|
-
"version-a",
|
|
1655
|
-
"version-row",
|
|
1656
|
-
Some("change-version"),
|
|
1657
|
-
"commit-shared",
|
|
1658
|
-
),
|
|
1659
|
-
];
|
|
1660
|
-
let mut writes = StorageWriteSet::new();
|
|
1661
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1662
|
-
let mut writer = live_state.writer(transaction.as_mut());
|
|
1663
|
-
writer
|
|
1664
|
-
.stage_rows(&mut writes, &mut json_writer, &rows)
|
|
1665
|
-
.await
|
|
1666
|
-
}
|
|
1667
|
-
.expect_err("one tracked root must not mix global and version rows");
|
|
1668
|
-
|
|
1669
|
-
assert!(
|
|
1670
|
-
error.message.contains("mixes multiple storage scopes"),
|
|
1671
|
-
"unexpected error: {error:?}"
|
|
1672
|
-
);
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
#[tokio::test]
|
|
1676
|
-
async fn writer_rejects_tracked_rows_with_invalid_storage_scope() {
|
|
1677
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1678
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1679
|
-
let live_state = live_state_context();
|
|
1680
|
-
let mut invalid_row =
|
|
1681
|
-
tracked_row_at_with_commit("version-a", "bad-row", Some("change-bad"), "commit-bad");
|
|
1682
|
-
invalid_row.global = true;
|
|
1683
|
-
let mut transaction = storage
|
|
1684
|
-
.begin_write_transaction()
|
|
1685
|
-
.await
|
|
1686
|
-
.expect("transaction should open");
|
|
1687
|
-
|
|
1688
|
-
let error = {
|
|
1689
|
-
let rows = [invalid_row];
|
|
1690
|
-
let mut writes = StorageWriteSet::new();
|
|
1691
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1692
|
-
let mut writer = live_state.writer(transaction.as_mut());
|
|
1693
|
-
writer
|
|
1694
|
-
.stage_rows(&mut writes, &mut json_writer, &rows)
|
|
1695
|
-
.await
|
|
1696
|
-
}
|
|
1697
|
-
.expect_err("global rows must be stored in the global root only");
|
|
1698
|
-
|
|
1699
|
-
assert!(
|
|
1700
|
-
error.message.contains("invalid storage scope"),
|
|
1701
|
-
"unexpected error: {error:?}"
|
|
1702
|
-
);
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
1626
|
#[tokio::test]
|
|
1706
1627
|
async fn writer_allows_commit_fact_to_share_the_touched_version_commit_id() {
|
|
1707
1628
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
@@ -1725,11 +1646,14 @@ mod tests {
|
|
|
1725
1646
|
let mut writes = StorageWriteSet::new();
|
|
1726
1647
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1727
1648
|
{
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1649
|
+
stage_materialized_live_rows(
|
|
1650
|
+
transaction.as_mut(),
|
|
1651
|
+
&mut writes,
|
|
1652
|
+
&mut json_writer,
|
|
1653
|
+
&rows,
|
|
1654
|
+
)
|
|
1655
|
+
.await
|
|
1656
|
+
.expect("commit facts are changelog projections, not root-local rows");
|
|
1733
1657
|
}
|
|
1734
1658
|
writes
|
|
1735
1659
|
.apply(&mut transaction.as_mut())
|
|
@@ -1757,25 +1681,31 @@ mod tests {
|
|
|
1757
1681
|
async fn writer_uses_first_parent_as_merge_root_base() {
|
|
1758
1682
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1759
1683
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1760
|
-
let live_state = live_state_context();
|
|
1761
1684
|
let mut seed_transaction = storage
|
|
1762
1685
|
.begin_write_transaction()
|
|
1763
1686
|
.await
|
|
1764
1687
|
.expect("seed transaction should open");
|
|
1765
1688
|
let mut writes = StorageWriteSet::new();
|
|
1766
1689
|
{
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
.
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1690
|
+
CommitStoreContext::new()
|
|
1691
|
+
.writer(&mut seed_transaction.as_mut(), &mut writes)
|
|
1692
|
+
.stage_commit_draft(
|
|
1693
|
+
CommitDraftRef {
|
|
1694
|
+
id: "parent-left",
|
|
1695
|
+
change_id: "parent-left:commit",
|
|
1696
|
+
parent_ids: &[],
|
|
1697
|
+
author_account_ids: &[],
|
|
1698
|
+
created_at: "1970-01-01T00:00:00.000Z",
|
|
1699
|
+
},
|
|
1700
|
+
Vec::new(),
|
|
1701
|
+
Vec::new(),
|
|
1777
1702
|
)
|
|
1778
1703
|
.await
|
|
1704
|
+
.expect("first parent commit should stage");
|
|
1705
|
+
TrackedStateContext::new()
|
|
1706
|
+
.writer(&mut seed_transaction.as_mut(), &mut writes)
|
|
1707
|
+
.stage_delta("parent-left", None, &[])
|
|
1708
|
+
.await
|
|
1779
1709
|
.expect("first parent root should exist");
|
|
1780
1710
|
}
|
|
1781
1711
|
writes
|
|
@@ -1808,11 +1738,14 @@ mod tests {
|
|
|
1808
1738
|
let mut writes = StorageWriteSet::new();
|
|
1809
1739
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1810
1740
|
{
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1741
|
+
stage_materialized_live_rows(
|
|
1742
|
+
transaction.as_mut(),
|
|
1743
|
+
&mut writes,
|
|
1744
|
+
&mut json_writer,
|
|
1745
|
+
&rows,
|
|
1746
|
+
)
|
|
1747
|
+
.await
|
|
1748
|
+
.expect("merge commit should use first parent as tracked-root base");
|
|
1816
1749
|
}
|
|
1817
1750
|
writes
|
|
1818
1751
|
.apply(&mut transaction.as_mut())
|
|
@@ -1821,101 +1754,11 @@ mod tests {
|
|
|
1821
1754
|
}
|
|
1822
1755
|
}
|
|
1823
1756
|
|
|
1824
|
-
#[tokio::test]
|
|
1825
|
-
async fn writer_rejects_commit_root_with_missing_parent_commit_ids() {
|
|
1826
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1827
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1828
|
-
let live_state = live_state_context();
|
|
1829
|
-
let mut transaction = storage
|
|
1830
|
-
.begin_write_transaction()
|
|
1831
|
-
.await
|
|
1832
|
-
.expect("transaction should open");
|
|
1833
|
-
|
|
1834
|
-
let error = {
|
|
1835
|
-
let rows = [
|
|
1836
|
-
tracked_row_at_with_commit(
|
|
1837
|
-
"version-a",
|
|
1838
|
-
"version-row",
|
|
1839
|
-
Some("change-version"),
|
|
1840
|
-
"commit-malformed",
|
|
1841
|
-
),
|
|
1842
|
-
commit_live_state_row_with_snapshot(
|
|
1843
|
-
"commit-malformed",
|
|
1844
|
-
json!({
|
|
1845
|
-
"id": "commit-malformed",
|
|
1846
|
-
"change_set_id": "change-set-commit-malformed",
|
|
1847
|
-
"change_ids": ["change-version"],
|
|
1848
|
-
}),
|
|
1849
|
-
),
|
|
1850
|
-
];
|
|
1851
|
-
let mut writes = StorageWriteSet::new();
|
|
1852
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1853
|
-
let mut writer = live_state.writer(transaction.as_mut());
|
|
1854
|
-
writer
|
|
1855
|
-
.stage_rows(&mut writes, &mut json_writer, &rows)
|
|
1856
|
-
.await
|
|
1857
|
-
}
|
|
1858
|
-
.expect_err("commit roots must declare parent_commit_ids");
|
|
1859
|
-
|
|
1860
|
-
assert!(
|
|
1861
|
-
error.message.contains("missing parent_commit_ids"),
|
|
1862
|
-
"unexpected error: {error:?}"
|
|
1863
|
-
);
|
|
1864
|
-
}
|
|
1865
|
-
|
|
1866
|
-
#[tokio::test]
|
|
1867
|
-
async fn writer_rejects_commit_root_with_non_array_parent_commit_ids() {
|
|
1868
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1869
|
-
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1870
|
-
let live_state = live_state_context();
|
|
1871
|
-
let mut transaction = storage
|
|
1872
|
-
.begin_write_transaction()
|
|
1873
|
-
.await
|
|
1874
|
-
.expect("transaction should open");
|
|
1875
|
-
|
|
1876
|
-
let error = {
|
|
1877
|
-
let rows = [
|
|
1878
|
-
tracked_row_at_with_commit(
|
|
1879
|
-
"version-a",
|
|
1880
|
-
"version-row",
|
|
1881
|
-
Some("change-version"),
|
|
1882
|
-
"commit-malformed",
|
|
1883
|
-
),
|
|
1884
|
-
commit_live_state_row_with_snapshot(
|
|
1885
|
-
"commit-malformed",
|
|
1886
|
-
json!({
|
|
1887
|
-
"id": "commit-malformed",
|
|
1888
|
-
"change_set_id": "change-set-commit-malformed",
|
|
1889
|
-
"change_ids": ["change-version"],
|
|
1890
|
-
"parent_commit_ids": "parent-1",
|
|
1891
|
-
}),
|
|
1892
|
-
),
|
|
1893
|
-
];
|
|
1894
|
-
let mut writes = StorageWriteSet::new();
|
|
1895
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
1896
|
-
let mut writer = live_state.writer(transaction.as_mut());
|
|
1897
|
-
writer
|
|
1898
|
-
.stage_rows(&mut writes, &mut json_writer, &rows)
|
|
1899
|
-
.await
|
|
1900
|
-
}
|
|
1901
|
-
.expect_err("commit root parent_commit_ids must be an array");
|
|
1902
|
-
|
|
1903
|
-
assert!(
|
|
1904
|
-
error.message.contains("parent_commit_ids must be an array"),
|
|
1905
|
-
"unexpected error: {error:?}"
|
|
1906
|
-
);
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
1757
|
#[tokio::test]
|
|
1910
1758
|
async fn non_global_root_does_not_store_global_rows() {
|
|
1911
1759
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
1912
1760
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
1913
1761
|
let tracked_state = TrackedStateContext::new();
|
|
1914
|
-
let live_state = LiveStateContext::new(
|
|
1915
|
-
tracked_state.clone(),
|
|
1916
|
-
UntrackedStateContext::new(),
|
|
1917
|
-
crate::commit_graph::CommitGraphContext::new(crate::changelog::ChangelogContext::new()),
|
|
1918
|
-
);
|
|
1919
1762
|
let mut transaction = storage
|
|
1920
1763
|
.begin_write_transaction()
|
|
1921
1764
|
.await
|
|
@@ -1934,11 +1777,14 @@ mod tests {
|
|
|
1934
1777
|
let mut writes = StorageWriteSet::new();
|
|
1935
1778
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
1936
1779
|
{
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1780
|
+
stage_materialized_live_rows(
|
|
1781
|
+
transaction.as_mut(),
|
|
1782
|
+
&mut writes,
|
|
1783
|
+
&mut json_writer,
|
|
1784
|
+
&rows,
|
|
1785
|
+
)
|
|
1786
|
+
.await
|
|
1787
|
+
.expect("tracked rows should stage");
|
|
1942
1788
|
}
|
|
1943
1789
|
writes
|
|
1944
1790
|
.apply(&mut transaction.as_mut())
|
|
@@ -1967,7 +1813,7 @@ mod tests {
|
|
|
1967
1813
|
async fn load_selected_tab(
|
|
1968
1814
|
live_state: &LiveStateContext,
|
|
1969
1815
|
storage: StorageContext,
|
|
1970
|
-
) -> Result<Option<
|
|
1816
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
1971
1817
|
live_state
|
|
1972
1818
|
.reader(storage)
|
|
1973
1819
|
.load_row(&LiveStateRowRequest {
|
|
@@ -1983,7 +1829,7 @@ mod tests {
|
|
|
1983
1829
|
live_state: &LiveStateContext,
|
|
1984
1830
|
storage: StorageContext,
|
|
1985
1831
|
version_id: &str,
|
|
1986
|
-
) -> Result<Option<
|
|
1832
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
1987
1833
|
live_state
|
|
1988
1834
|
.reader(storage)
|
|
1989
1835
|
.load_row(&LiveStateRowRequest {
|
|
@@ -2000,7 +1846,7 @@ mod tests {
|
|
|
2000
1846
|
storage: StorageContext,
|
|
2001
1847
|
version_id: &str,
|
|
2002
1848
|
include_tombstones: bool,
|
|
2003
|
-
) -> Result<Vec<
|
|
1849
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
2004
1850
|
live_state
|
|
2005
1851
|
.reader(storage)
|
|
2006
1852
|
.scan_rows(&LiveStateScanRequest {
|
|
@@ -2023,7 +1869,7 @@ mod tests {
|
|
|
2023
1869
|
tracked_state: &TrackedStateContext,
|
|
2024
1870
|
storage: StorageContext,
|
|
2025
1871
|
commit_id: &str,
|
|
2026
|
-
) -> Vec<
|
|
1872
|
+
) -> Vec<MaterializedTrackedStateRow> {
|
|
2027
1873
|
tracked_state
|
|
2028
1874
|
.reader(storage)
|
|
2029
1875
|
.scan_rows_at_commit(
|
|
@@ -2044,7 +1890,7 @@ mod tests {
|
|
|
2044
1890
|
value: &str,
|
|
2045
1891
|
change_id: Option<&str>,
|
|
2046
1892
|
commit_id: &str,
|
|
2047
|
-
) ->
|
|
1893
|
+
) -> MaterializedLiveStateRow {
|
|
2048
1894
|
tracked_row_at_with_commit("global", value, change_id, commit_id)
|
|
2049
1895
|
}
|
|
2050
1896
|
|
|
@@ -2053,14 +1899,14 @@ mod tests {
|
|
|
2053
1899
|
value: &str,
|
|
2054
1900
|
change_id: Option<&str>,
|
|
2055
1901
|
commit_id: &str,
|
|
2056
|
-
) ->
|
|
2057
|
-
|
|
1902
|
+
) -> MaterializedLiveStateRow {
|
|
1903
|
+
MaterializedLiveStateRow {
|
|
2058
1904
|
entity_id: identity("selected-tab"),
|
|
2059
1905
|
schema_key: "lix_key_value".to_string(),
|
|
2060
1906
|
file_id: None,
|
|
2061
1907
|
snapshot_content: Some(format!("{{\"value\":\"{value}\"}}")),
|
|
2062
1908
|
metadata: None,
|
|
2063
|
-
|
|
1909
|
+
deleted: false,
|
|
2064
1910
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2065
1911
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2066
1912
|
global: version_id == "global",
|
|
@@ -2075,9 +1921,10 @@ mod tests {
|
|
|
2075
1921
|
version_id: &str,
|
|
2076
1922
|
change_id: Option<&str>,
|
|
2077
1923
|
commit_id: &str,
|
|
2078
|
-
) ->
|
|
2079
|
-
|
|
1924
|
+
) -> MaterializedLiveStateRow {
|
|
1925
|
+
MaterializedLiveStateRow {
|
|
2080
1926
|
snapshot_content: None,
|
|
1927
|
+
deleted: true,
|
|
2081
1928
|
..tracked_row_at_with_commit(version_id, "ignored", change_id, commit_id)
|
|
2082
1929
|
}
|
|
2083
1930
|
}
|
|
@@ -2093,7 +1940,7 @@ mod tests {
|
|
|
2093
1940
|
file_id: None,
|
|
2094
1941
|
snapshot_content: Some(format!("{{\"value\":\"{value}\"}}")),
|
|
2095
1942
|
metadata: None,
|
|
2096
|
-
|
|
1943
|
+
deleted: false,
|
|
2097
1944
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2098
1945
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2099
1946
|
global: version_id == "global",
|
|
@@ -2114,7 +1961,7 @@ mod tests {
|
|
|
2114
1961
|
.expect("version ref should serialize"),
|
|
2115
1962
|
),
|
|
2116
1963
|
metadata: None,
|
|
2117
|
-
|
|
1964
|
+
deleted: false,
|
|
2118
1965
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2119
1966
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2120
1967
|
global: true,
|
|
@@ -2122,57 +1969,32 @@ mod tests {
|
|
|
2122
1969
|
}
|
|
2123
1970
|
}
|
|
2124
1971
|
|
|
2125
|
-
|
|
2126
|
-
let mut transaction = storage
|
|
2127
|
-
.begin_write_transaction()
|
|
2128
|
-
.await
|
|
2129
|
-
.expect("version-ref transaction should open");
|
|
2130
|
-
let mut writes = StorageWriteSet::new();
|
|
2131
|
-
let canonical_refs = {
|
|
2132
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
2133
|
-
refs.iter()
|
|
2134
|
-
.map(|row| canonicalize_materialized_row(&mut writes, &mut json_writer, row))
|
|
2135
|
-
.collect::<Result<Vec<_>, _>>()
|
|
2136
|
-
.expect("version refs should canonicalize")
|
|
2137
|
-
};
|
|
2138
|
-
UntrackedStateContext::new()
|
|
2139
|
-
.writer(&mut writes)
|
|
2140
|
-
.stage_rows(&canonical_refs)
|
|
2141
|
-
.expect("version refs should write");
|
|
2142
|
-
writes
|
|
2143
|
-
.apply(&mut transaction.as_mut())
|
|
2144
|
-
.await
|
|
2145
|
-
.expect("version refs should apply");
|
|
2146
|
-
transaction
|
|
2147
|
-
.commit()
|
|
2148
|
-
.await
|
|
2149
|
-
.expect("version-ref transaction should commit");
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
fn commit_live_state_row(commit_id: &str) -> LiveStateRow {
|
|
1972
|
+
fn commit_live_state_row(commit_id: &str) -> MaterializedLiveStateRow {
|
|
2153
1973
|
commit_live_state_row_with_parents(commit_id, &[])
|
|
2154
1974
|
}
|
|
2155
1975
|
|
|
2156
1976
|
fn commit_live_state_row_with_parents(
|
|
2157
1977
|
commit_id: &str,
|
|
2158
|
-
|
|
2159
|
-
) ->
|
|
2160
|
-
commit_live_state_row_with_snapshot(
|
|
1978
|
+
parent_ids: &[&str],
|
|
1979
|
+
) -> MaterializedLiveStateRow {
|
|
1980
|
+
let mut row = commit_live_state_row_with_snapshot(
|
|
2161
1981
|
commit_id,
|
|
2162
1982
|
json!({
|
|
2163
1983
|
"id": commit_id,
|
|
2164
|
-
"change_set_id": format!("change-set-{commit_id}"),
|
|
2165
|
-
"change_ids": ["change-version"],
|
|
2166
|
-
"parent_commit_ids": parent_commit_ids,
|
|
2167
1984
|
}),
|
|
2168
|
-
)
|
|
1985
|
+
);
|
|
1986
|
+
row.metadata = Some(
|
|
1987
|
+
serde_json::to_string(&json!({ "test_parents": parent_ids }))
|
|
1988
|
+
.expect("test metadata should serialize"),
|
|
1989
|
+
);
|
|
1990
|
+
row
|
|
2169
1991
|
}
|
|
2170
1992
|
|
|
2171
1993
|
fn commit_live_state_row_with_snapshot(
|
|
2172
1994
|
commit_id: &str,
|
|
2173
1995
|
snapshot: serde_json::Value,
|
|
2174
|
-
) ->
|
|
2175
|
-
|
|
1996
|
+
) -> MaterializedLiveStateRow {
|
|
1997
|
+
MaterializedLiveStateRow {
|
|
2176
1998
|
entity_id: identity(commit_id),
|
|
2177
1999
|
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
2178
2000
|
file_id: None,
|
|
@@ -2180,7 +2002,7 @@ mod tests {
|
|
|
2180
2002
|
serde_json::to_string(&snapshot).expect("commit snapshot should serialize"),
|
|
2181
2003
|
),
|
|
2182
2004
|
metadata: None,
|
|
2183
|
-
|
|
2005
|
+
deleted: false,
|
|
2184
2006
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2185
2007
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2186
2008
|
global: true,
|
|
@@ -2191,50 +2013,6 @@ mod tests {
|
|
|
2191
2013
|
}
|
|
2192
2014
|
}
|
|
2193
2015
|
|
|
2194
|
-
async fn append_commit_change(storage: StorageContext, commit_id: &str) {
|
|
2195
|
-
let changelog = crate::changelog::ChangelogContext::new();
|
|
2196
|
-
let mut transaction = storage
|
|
2197
|
-
.begin_write_transaction()
|
|
2198
|
-
.await
|
|
2199
|
-
.expect("transaction should open");
|
|
2200
|
-
let change = MaterializedCanonicalChange {
|
|
2201
|
-
id: format!("change-{commit_id}"),
|
|
2202
|
-
entity_id: crate::entity_identity::EntityIdentity::single(commit_id),
|
|
2203
|
-
schema_key: COMMIT_SCHEMA_KEY.to_string(),
|
|
2204
|
-
schema_version: "1".to_string(),
|
|
2205
|
-
file_id: None,
|
|
2206
|
-
snapshot_content: Some(
|
|
2207
|
-
serde_json::to_string(&json!({
|
|
2208
|
-
"id": commit_id,
|
|
2209
|
-
"change_set_id": format!("change-set-{commit_id}"),
|
|
2210
|
-
"change_ids": [],
|
|
2211
|
-
"parent_commit_ids": [],
|
|
2212
|
-
}))
|
|
2213
|
-
.expect("commit snapshot should serialize"),
|
|
2214
|
-
),
|
|
2215
|
-
metadata: None,
|
|
2216
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
2217
|
-
};
|
|
2218
|
-
let mut writes = StorageWriteSet::new();
|
|
2219
|
-
let canonical_change = {
|
|
2220
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
2221
|
-
canonicalize_materialized_change(&mut writes, &mut json_writer, &change)
|
|
2222
|
-
.expect("commit change should canonicalize")
|
|
2223
|
-
};
|
|
2224
|
-
changelog
|
|
2225
|
-
.writer(&mut writes)
|
|
2226
|
-
.stage_changes(&[canonical_change])
|
|
2227
|
-
.expect("commit change should append");
|
|
2228
|
-
writes
|
|
2229
|
-
.apply(&mut transaction.as_mut())
|
|
2230
|
-
.await
|
|
2231
|
-
.expect("commit change should apply");
|
|
2232
|
-
transaction
|
|
2233
|
-
.commit()
|
|
2234
|
-
.await
|
|
2235
|
-
.expect("transaction should commit");
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
2016
|
fn identity(entity_id: &str) -> EntityIdentity {
|
|
2239
2017
|
EntityIdentity::single(entity_id)
|
|
2240
2018
|
}
|