@lix-js/sdk 0.6.0-preview.3 → 0.6.0-preview.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +105 -65
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +30 -6
- package/dist/engine-wasm/wasm/lix_engine.js +187 -117
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +14 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +42 -28
- package/dist/open-lix.js +49 -10
- package/dist/sqlite/index.js +86 -30
- package/dist-engine-src/README.md +3 -3
- package/dist-engine-src/src/backend/capabilities.rs +67 -0
- package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
- package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
- package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
- package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
- package/dist-engine-src/src/backend/conformance/model.rs +28 -0
- package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
- package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
- package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
- package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
- package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
- package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
- package/dist-engine-src/src/backend/conformance/write.rs +16 -0
- package/dist-engine-src/src/backend/error.rs +94 -0
- package/dist-engine-src/src/backend/in_memory.rs +670 -0
- package/dist-engine-src/src/backend/mod.rs +36 -9
- package/dist-engine-src/src/backend/predicate.rs +80 -0
- package/dist-engine-src/src/backend/traits.rs +260 -0
- package/dist-engine-src/src/backend/types.rs +224 -81
- package/dist-engine-src/src/binary_cas/context.rs +8 -8
- package/dist-engine-src/src/binary_cas/kv.rs +234 -259
- package/dist-engine-src/src/{version → branch}/context.rs +12 -12
- package/dist-engine-src/src/branch/lifecycle.rs +221 -0
- package/dist-engine-src/src/branch/mod.rs +13 -0
- package/dist-engine-src/src/branch/refs.rs +321 -0
- package/dist-engine-src/src/branch/stage_rows.rs +67 -0
- package/dist-engine-src/src/branch/types.rs +21 -0
- package/dist-engine-src/src/catalog/context.rs +18 -18
- package/dist-engine-src/src/catalog/snapshot.rs +8 -8
- package/dist-engine-src/src/changelog/bench_support.rs +785 -0
- package/dist-engine-src/src/changelog/change.rs +1 -0
- package/dist-engine-src/src/changelog/codec.rs +497 -0
- package/dist-engine-src/src/changelog/commit.rs +1 -0
- package/dist-engine-src/src/changelog/context.rs +1614 -0
- package/dist-engine-src/src/changelog/mod.rs +29 -0
- package/dist-engine-src/src/changelog/store.rs +163 -0
- package/dist-engine-src/src/changelog/test_support.rs +54 -0
- package/dist-engine-src/src/changelog/types.rs +213 -0
- package/dist-engine-src/src/commit_graph/context.rs +317 -274
- package/dist-engine-src/src/commit_graph/mod.rs +2 -4
- package/dist-engine-src/src/commit_graph/types.rs +22 -42
- package/dist-engine-src/src/commit_graph/walker.rs +133 -103
- package/dist-engine-src/src/common/error.rs +52 -18
- package/dist-engine-src/src/common/identity.rs +2 -2
- package/dist-engine-src/src/common/mod.rs +1 -1
- package/dist-engine-src/src/domain.rs +42 -46
- package/dist-engine-src/src/engine.rs +74 -96
- package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
- package/dist-engine-src/src/functions/context.rs +56 -52
- package/dist-engine-src/src/functions/state.rs +51 -52
- package/dist-engine-src/src/init.rs +288 -154
- package/dist-engine-src/src/json_store/context.rs +15 -266
- package/dist-engine-src/src/json_store/mod.rs +26 -0
- package/dist-engine-src/src/json_store/store.rs +103 -718
- package/dist-engine-src/src/json_store/types.rs +4 -9
- package/dist-engine-src/src/lib.rs +49 -19
- package/dist-engine-src/src/live_state/context.rs +654 -790
- package/dist-engine-src/src/live_state/mod.rs +9 -3
- package/dist-engine-src/src/live_state/overlay.rs +4 -4
- package/dist-engine-src/src/live_state/types.rs +30 -21
- package/dist-engine-src/src/live_state/visibility.rs +514 -71
- package/dist-engine-src/src/plugin/install.rs +48 -48
- package/dist-engine-src/src/plugin/manifest.rs +7 -7
- package/dist-engine-src/src/plugin/materializer.rs +0 -275
- package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
- package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
- package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
- package/dist-engine-src/src/schema/compatibility.rs +11 -11
- package/dist-engine-src/src/schema/definition.json +2 -2
- package/dist-engine-src/src/schema/definition.rs +5 -5
- package/dist-engine-src/src/schema/key.rs +3 -3
- package/dist-engine-src/src/schema/mod.rs +1 -1
- package/dist-engine-src/src/schema/tests.rs +18 -18
- package/dist-engine-src/src/session/context.rs +819 -124
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +260 -57
- package/dist-engine-src/src/session/merge/analysis.rs +9 -3
- package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
- package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
- package/dist-engine-src/src/session/merge/mod.rs +5 -6
- package/dist-engine-src/src/session/merge/stats.rs +7 -11
- package/dist-engine-src/src/session/mod.rs +19 -16
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +557 -0
- package/dist-engine-src/src/sql2/bind/classify.rs +102 -0
- package/dist-engine-src/src/sql2/bind/error.rs +5 -0
- package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
- package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
- package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +98 -3
- package/dist-engine-src/src/sql2/bind/read.rs +65 -0
- package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
- package/dist-engine-src/src/sql2/bind/table.rs +273 -0
- package/dist-engine-src/src/sql2/bind/write.rs +86 -0
- package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
- package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
- package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
- package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
- package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
- package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
- package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
- package/dist-engine-src/src/sql2/context.rs +36 -30
- package/dist-engine-src/src/sql2/error.rs +4 -5
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
- package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
- package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
- package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
- package/dist-engine-src/src/sql2/exec/write.rs +661 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
- package/dist-engine-src/src/sql2/history_projection.rs +8 -8
- package/dist-engine-src/src/sql2/history_route.rs +35 -31
- package/dist-engine-src/src/sql2/mod.rs +30 -24
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
- package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
- package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
- package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
- package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
- package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
- package/dist-engine-src/src/sql2/plan/write.rs +147 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
- package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
- package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
- package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
- package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
- package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
- package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
- package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
- package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
- package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
- package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
- package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
- package/dist-engine-src/src/sql2/read_only.rs +2 -2
- package/dist-engine-src/src/sql2/session.rs +47 -96
- package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
- package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
- package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
- package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
- package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
- package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
- package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
- package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
- package/dist-engine-src/src/storage/conformance.rs +399 -0
- package/dist-engine-src/src/storage/context.rs +552 -288
- package/dist-engine-src/src/storage/mod.rs +48 -10
- package/dist-engine-src/src/storage/point.rs +440 -0
- package/dist-engine-src/src/storage/read_scope.rs +43 -64
- package/dist-engine-src/src/storage/reader.rs +867 -0
- package/dist-engine-src/src/storage/scan.rs +784 -0
- package/dist-engine-src/src/storage/spaces.rs +236 -0
- package/dist-engine-src/src/storage/stats.rs +80 -0
- package/dist-engine-src/src/storage/write_set.rs +962 -0
- package/dist-engine-src/src/storage_bench.rs +136 -4828
- package/dist-engine-src/src/test_support.rs +360 -138
- package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
- package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
- package/dist-engine-src/src/tracked_state/context.rs +1927 -993
- package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
- package/dist-engine-src/src/tracked_state/merge.rs +74 -88
- package/dist-engine-src/src/tracked_state/mod.rs +19 -16
- package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
- package/dist-engine-src/src/tracked_state/storage.rs +243 -191
- package/dist-engine-src/src/tracked_state/tree.rs +247 -371
- package/dist-engine-src/src/tracked_state/types.rs +49 -42
- package/dist-engine-src/src/transaction/bench_support.rs +407 -0
- package/dist-engine-src/src/transaction/commit.rs +821 -713
- package/dist-engine-src/src/transaction/context.rs +705 -600
- package/dist-engine-src/src/transaction/mod.rs +13 -2
- package/dist-engine-src/src/transaction/normalization.rs +63 -76
- package/dist-engine-src/src/transaction/prep.rs +13 -13
- package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
- package/dist-engine-src/src/transaction/staging.rs +228 -434
- package/dist-engine-src/src/transaction/types.rs +41 -98
- package/dist-engine-src/src/transaction/validation.rs +382 -446
- package/dist-engine-src/src/untracked_state/codec.rs +337 -29
- package/dist-engine-src/src/untracked_state/context.rs +7 -7
- package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
- package/dist-engine-src/src/untracked_state/mod.rs +1 -1
- package/dist-engine-src/src/untracked_state/storage.rs +659 -157
- package/dist-engine-src/src/untracked_state/types.rs +21 -21
- package/package.json +71 -68
- package/dist-engine-src/src/backend/kv.rs +0 -358
- package/dist-engine-src/src/backend/testing.rs +0 -658
- package/dist-engine-src/src/commit_store/codec.rs +0 -887
- package/dist-engine-src/src/commit_store/context.rs +0 -944
- package/dist-engine-src/src/commit_store/materialization.rs +0 -84
- package/dist-engine-src/src/commit_store/mod.rs +0 -16
- package/dist-engine-src/src/commit_store/storage.rs +0 -600
- package/dist-engine-src/src/commit_store/types.rs +0 -215
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
- package/dist-engine-src/src/session/create_version.rs +0 -88
- package/dist-engine-src/src/session/merge/apply.rs +0 -23
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
- package/dist-engine-src/src/session/switch_version.rs +0 -109
- package/dist-engine-src/src/sql2/classify.rs +0 -182
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3440
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
- package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
- package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -166
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -25
- package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
- package/dist-engine-src/src/sql2/version_scope.rs +0 -394
- package/dist-engine-src/src/storage/types.rs +0 -501
- package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
- package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
- package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
- package/dist-engine-src/src/version/lifecycle.rs +0 -221
- package/dist-engine-src/src/version/mod.rs +0 -13
- package/dist-engine-src/src/version/refs.rs +0 -330
- package/dist-engine-src/src/version/stage_rows.rs +0 -67
- package/dist-engine-src/src/version/types.rs +0 -21
|
@@ -5,7 +5,7 @@ use std::{
|
|
|
5
5
|
pin::Pin,
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
use crate::storage::{
|
|
8
|
+
use crate::storage::{StorageRead, StorageWriteSet};
|
|
9
9
|
use crate::tracked_state::codec::{
|
|
10
10
|
boundary_trigger, child_summary_from_node, decode_key, decode_key_with_trusted_prefix,
|
|
11
11
|
decode_node, decode_node_ref, decode_value, decode_visible_value, encode_internal_node,
|
|
@@ -46,7 +46,7 @@ impl Default for TrackedStateTreeOptions {
|
|
|
46
46
|
|
|
47
47
|
/// Content-addressed tracked-state tree operations.
|
|
48
48
|
///
|
|
49
|
-
/// This type owns tracked-state tree mechanics only.
|
|
49
|
+
/// This type owns tracked-state tree mechanics only. Branch refs, untracked overlay,
|
|
50
50
|
/// and SQL visibility remain outside the tree.
|
|
51
51
|
#[derive(Debug, Clone)]
|
|
52
52
|
pub(crate) struct TrackedStateTree {
|
|
@@ -60,14 +60,14 @@ impl TrackedStateTree {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
#[
|
|
63
|
+
#[cfg(test)]
|
|
64
64
|
pub(crate) fn with_options(options: TrackedStateTreeOptions) -> Self {
|
|
65
65
|
Self { options }
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
pub(crate) async fn load_root(
|
|
69
69
|
&self,
|
|
70
|
-
store: &
|
|
70
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
71
71
|
commit_id: &str,
|
|
72
72
|
) -> Result<Option<TrackedStateRootId>, LixError> {
|
|
73
73
|
storage::load_root(store, commit_id).await
|
|
@@ -76,7 +76,7 @@ impl TrackedStateTree {
|
|
|
76
76
|
#[cfg(test)]
|
|
77
77
|
pub(crate) async fn get(
|
|
78
78
|
&self,
|
|
79
|
-
store: &
|
|
79
|
+
store: &(impl StorageRead + Send + Sync),
|
|
80
80
|
root_id: &TrackedStateRootId,
|
|
81
81
|
key: &TrackedStateKey,
|
|
82
82
|
) -> Result<Option<TrackedStateIndexValue>, LixError> {
|
|
@@ -112,7 +112,7 @@ impl TrackedStateTree {
|
|
|
112
112
|
|
|
113
113
|
pub(crate) async fn get_many(
|
|
114
114
|
&self,
|
|
115
|
-
store: &
|
|
115
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
116
116
|
root_id: &TrackedStateRootId,
|
|
117
117
|
keys: &[TrackedStateKey],
|
|
118
118
|
) -> Result<Vec<Option<TrackedStateIndexValue>>, LixError> {
|
|
@@ -133,24 +133,9 @@ impl TrackedStateTree {
|
|
|
133
133
|
Ok(values)
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
pub(crate) async fn row_count(
|
|
137
|
-
&self,
|
|
138
|
-
store: &mut impl StorageReader,
|
|
139
|
-
root_id: &TrackedStateRootId,
|
|
140
|
-
) -> Result<usize, LixError> {
|
|
141
|
-
match self.load_node(store, root_id.as_bytes()).await? {
|
|
142
|
-
DecodedNode::Leaf(leaf) => Ok(leaf.entries().len()),
|
|
143
|
-
DecodedNode::Internal(internal) => Ok(internal
|
|
144
|
-
.children()
|
|
145
|
-
.iter()
|
|
146
|
-
.map(|child| child.subtree_count as usize)
|
|
147
|
-
.sum()),
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
136
|
pub(crate) async fn scan(
|
|
152
137
|
&self,
|
|
153
|
-
store: &
|
|
138
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
154
139
|
root_id: &TrackedStateRootId,
|
|
155
140
|
request: &TrackedStateTreeScanRequest,
|
|
156
141
|
) -> Result<Vec<(TrackedStateKey, TrackedStateIndexValue)>, LixError> {
|
|
@@ -173,24 +158,9 @@ impl TrackedStateTree {
|
|
|
173
158
|
Ok(rows)
|
|
174
159
|
}
|
|
175
160
|
|
|
176
|
-
pub(crate) async fn count_matching_keys(
|
|
177
|
-
&self,
|
|
178
|
-
store: &mut impl StorageReader,
|
|
179
|
-
root_id: &TrackedStateRootId,
|
|
180
|
-
request: &TrackedStateTreeScanRequest,
|
|
181
|
-
) -> Result<usize, LixError> {
|
|
182
|
-
if request.limit == Some(0) {
|
|
183
|
-
return Ok(0);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
let ranges = scan_ranges(request);
|
|
187
|
-
self.count_matching_keys_node(store, *root_id.as_bytes(), request, &ranges)
|
|
188
|
-
.await
|
|
189
|
-
}
|
|
190
|
-
|
|
191
161
|
pub(crate) async fn diff(
|
|
192
162
|
&self,
|
|
193
|
-
store: &
|
|
163
|
+
store: &(impl StorageRead + Send + Sync),
|
|
194
164
|
left_root: Option<&TrackedStateRootId>,
|
|
195
165
|
right_root: Option<&TrackedStateRootId>,
|
|
196
166
|
request: &TrackedStateTreeScanRequest,
|
|
@@ -231,27 +201,41 @@ impl TrackedStateTree {
|
|
|
231
201
|
}
|
|
232
202
|
}
|
|
233
203
|
|
|
204
|
+
#[cfg(test)]
|
|
234
205
|
pub(crate) async fn apply_mutations(
|
|
235
206
|
&self,
|
|
236
|
-
store: &
|
|
207
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
237
208
|
writes: &mut StorageWriteSet,
|
|
238
209
|
base_root: Option<&TrackedStateRootId>,
|
|
239
|
-
|
|
210
|
+
mutations: Vec<TrackedStateMutation>,
|
|
240
211
|
commit_id: Option<&str>,
|
|
241
212
|
) -> Result<TrackedStateApplyResult, LixError> {
|
|
242
213
|
let mut overlay = storage::TrackedStateChunkOverlay::new();
|
|
214
|
+
self.apply_mutations_with_overlay(
|
|
215
|
+
store,
|
|
216
|
+
writes,
|
|
217
|
+
&mut overlay,
|
|
218
|
+
base_root,
|
|
219
|
+
mutations,
|
|
220
|
+
commit_id,
|
|
221
|
+
)
|
|
222
|
+
.await
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
pub(crate) async fn apply_mutations_with_overlay(
|
|
226
|
+
&self,
|
|
227
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
228
|
+
writes: &mut StorageWriteSet,
|
|
229
|
+
overlay: &mut storage::TrackedStateChunkOverlay,
|
|
230
|
+
base_root: Option<&TrackedStateRootId>,
|
|
231
|
+
mut mutations: Vec<TrackedStateMutation>,
|
|
232
|
+
commit_id: Option<&str>,
|
|
233
|
+
) -> Result<TrackedStateApplyResult, LixError> {
|
|
243
234
|
if let Some(root_id) = base_root {
|
|
244
235
|
if mutations.len() == 1 {
|
|
245
236
|
let mutation = mutations.pop().expect("single mutation should exist");
|
|
246
237
|
match self
|
|
247
|
-
.apply_single_mutation(
|
|
248
|
-
store,
|
|
249
|
-
writes,
|
|
250
|
-
&mut overlay,
|
|
251
|
-
root_id,
|
|
252
|
-
mutation,
|
|
253
|
-
commit_id,
|
|
254
|
-
)
|
|
238
|
+
.apply_single_mutation(store, writes, overlay, root_id, mutation, commit_id)
|
|
255
239
|
.await?
|
|
256
240
|
{
|
|
257
241
|
MutationApply::Applied(result) => return Ok(result),
|
|
@@ -260,12 +244,7 @@ impl TrackedStateTree {
|
|
|
260
244
|
} else if mutations.len() > 1 {
|
|
261
245
|
match self
|
|
262
246
|
.apply_sorted_mutations_chunker(
|
|
263
|
-
store,
|
|
264
|
-
writes,
|
|
265
|
-
&mut overlay,
|
|
266
|
-
root_id,
|
|
267
|
-
mutations,
|
|
268
|
-
commit_id,
|
|
247
|
+
store, writes, overlay, root_id, mutations, commit_id,
|
|
269
248
|
)
|
|
270
249
|
.await?
|
|
271
250
|
{
|
|
@@ -277,7 +256,7 @@ impl TrackedStateTree {
|
|
|
277
256
|
|
|
278
257
|
let mut entries = match base_root {
|
|
279
258
|
Some(root_id) => self
|
|
280
|
-
.
|
|
259
|
+
.collect_leaf_entries_with_overlay(store, overlay, root_id)
|
|
281
260
|
.await?
|
|
282
261
|
.into_iter()
|
|
283
262
|
.map(|entry| (entry.key, entry.value))
|
|
@@ -298,12 +277,6 @@ impl TrackedStateTree {
|
|
|
298
277
|
.collect(),
|
|
299
278
|
)?;
|
|
300
279
|
overlay.stage_chunks(writes, &built.chunks);
|
|
301
|
-
let persisted_root = if let Some(commit_id) = commit_id {
|
|
302
|
-
storage::stage_root(writes, commit_id, &built.root_id);
|
|
303
|
-
true
|
|
304
|
-
} else {
|
|
305
|
-
false
|
|
306
|
-
};
|
|
307
280
|
|
|
308
281
|
Ok(TrackedStateApplyResult {
|
|
309
282
|
root_id: built.root_id,
|
|
@@ -311,13 +284,12 @@ impl TrackedStateTree {
|
|
|
311
284
|
tree_height: built.tree_height,
|
|
312
285
|
chunk_count: built.chunks.len(),
|
|
313
286
|
chunk_bytes: built.chunk_bytes,
|
|
314
|
-
persisted_root,
|
|
315
287
|
})
|
|
316
288
|
}
|
|
317
289
|
|
|
318
290
|
async fn apply_single_mutation(
|
|
319
291
|
&self,
|
|
320
|
-
store: &
|
|
292
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
321
293
|
writes: &mut StorageWriteSet,
|
|
322
294
|
overlay: &mut storage::TrackedStateChunkOverlay,
|
|
323
295
|
root_id: &TrackedStateRootId,
|
|
@@ -447,12 +419,6 @@ impl TrackedStateTree {
|
|
|
447
419
|
suffix_entries[mutation_entry_index].key.as_slice(),
|
|
448
420
|
)?;
|
|
449
421
|
overlay.stage_chunks(writes, &built.chunks);
|
|
450
|
-
let persisted_root = if let Some(commit_id) = commit_id {
|
|
451
|
-
storage::stage_root(writes, commit_id, &built.root_id);
|
|
452
|
-
true
|
|
453
|
-
} else {
|
|
454
|
-
false
|
|
455
|
-
};
|
|
456
422
|
|
|
457
423
|
Ok(MutationApply::Applied(TrackedStateApplyResult {
|
|
458
424
|
root_id: built.root_id,
|
|
@@ -460,20 +426,19 @@ impl TrackedStateTree {
|
|
|
460
426
|
tree_height: built.tree_height,
|
|
461
427
|
chunk_count: built.chunks.len(),
|
|
462
428
|
chunk_bytes: built.chunk_bytes,
|
|
463
|
-
persisted_root,
|
|
464
429
|
}))
|
|
465
430
|
}
|
|
466
431
|
|
|
467
432
|
fn diff_nodes<'a, S>(
|
|
468
433
|
&'a self,
|
|
469
|
-
store: &'a
|
|
434
|
+
store: &'a S,
|
|
470
435
|
left_hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
471
436
|
right_hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
472
437
|
request: &'a TrackedStateTreeScanRequest,
|
|
473
438
|
out: &'a mut Vec<TrackedStateTreeDiffEntry>,
|
|
474
439
|
) -> Pin<Box<dyn Future<Output = Result<(), LixError>> + 'a>>
|
|
475
440
|
where
|
|
476
|
-
S:
|
|
441
|
+
S: StorageRead + Send + Sync + 'a,
|
|
477
442
|
{
|
|
478
443
|
Box::pin(async move {
|
|
479
444
|
if left_hash == right_hash {
|
|
@@ -514,7 +479,7 @@ impl TrackedStateTree {
|
|
|
514
479
|
|
|
515
480
|
async fn diff_leaf_summary_cursors(
|
|
516
481
|
&self,
|
|
517
|
-
store: &
|
|
482
|
+
store: &(impl StorageRead + Send + Sync),
|
|
518
483
|
left_hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
519
484
|
right_hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
520
485
|
request: &TrackedStateTreeScanRequest,
|
|
@@ -572,7 +537,7 @@ impl TrackedStateTree {
|
|
|
572
537
|
|
|
573
538
|
async fn diff_leaf_summary_window(
|
|
574
539
|
&self,
|
|
575
|
-
store: &
|
|
540
|
+
store: &(impl StorageRead + Send + Sync),
|
|
576
541
|
left_leaves: &[ChildSummary],
|
|
577
542
|
right_leaves: &[ChildSummary],
|
|
578
543
|
request: &TrackedStateTreeScanRequest,
|
|
@@ -684,7 +649,7 @@ impl TrackedStateTree {
|
|
|
684
649
|
|
|
685
650
|
async fn apply_sorted_mutations_chunker(
|
|
686
651
|
&self,
|
|
687
|
-
store: &
|
|
652
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
688
653
|
writes: &mut StorageWriteSet,
|
|
689
654
|
overlay: &mut storage::TrackedStateChunkOverlay,
|
|
690
655
|
root_id: &TrackedStateRootId,
|
|
@@ -851,7 +816,7 @@ impl TrackedStateTree {
|
|
|
851
816
|
|
|
852
817
|
async fn apply_single_mutation_from_seek_path(
|
|
853
818
|
&self,
|
|
854
|
-
store: &
|
|
819
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
855
820
|
writes: &mut StorageWriteSet,
|
|
856
821
|
overlay: &mut storage::TrackedStateChunkOverlay,
|
|
857
822
|
root_id: &TrackedStateRootId,
|
|
@@ -1033,22 +998,15 @@ impl TrackedStateTree {
|
|
|
1033
998
|
writes: &mut StorageWriteSet,
|
|
1034
999
|
overlay: &mut storage::TrackedStateChunkOverlay,
|
|
1035
1000
|
built: BuiltTree,
|
|
1036
|
-
|
|
1001
|
+
_commit_id: Option<&str>,
|
|
1037
1002
|
) -> Result<TrackedStateApplyResult, LixError> {
|
|
1038
1003
|
overlay.stage_chunks(writes, &built.chunks);
|
|
1039
|
-
let persisted_root = if let Some(commit_id) = commit_id {
|
|
1040
|
-
storage::stage_root(writes, commit_id, &built.root_id);
|
|
1041
|
-
true
|
|
1042
|
-
} else {
|
|
1043
|
-
false
|
|
1044
|
-
};
|
|
1045
1004
|
Ok(TrackedStateApplyResult {
|
|
1046
1005
|
root_id: built.root_id,
|
|
1047
1006
|
row_count: built.row_count,
|
|
1048
1007
|
tree_height: built.tree_height,
|
|
1049
1008
|
chunk_count: built.chunks.len(),
|
|
1050
1009
|
chunk_bytes: built.chunk_bytes,
|
|
1051
|
-
persisted_root,
|
|
1052
1010
|
})
|
|
1053
1011
|
}
|
|
1054
1012
|
|
|
@@ -1379,9 +1337,21 @@ impl TrackedStateTree {
|
|
|
1379
1337
|
.collect()
|
|
1380
1338
|
}
|
|
1381
1339
|
|
|
1340
|
+
#[cfg(test)]
|
|
1382
1341
|
async fn collect_leaf_entries(
|
|
1383
1342
|
&self,
|
|
1384
|
-
store: &
|
|
1343
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1344
|
+
root_id: &TrackedStateRootId,
|
|
1345
|
+
) -> Result<Vec<EncodedLeafEntry>, LixError> {
|
|
1346
|
+
let overlay = storage::TrackedStateChunkOverlay::new();
|
|
1347
|
+
self.collect_leaf_entries_with_overlay(store, &overlay, root_id)
|
|
1348
|
+
.await
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
async fn collect_leaf_entries_with_overlay(
|
|
1352
|
+
&self,
|
|
1353
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1354
|
+
overlay: &storage::TrackedStateChunkOverlay,
|
|
1385
1355
|
root_id: &TrackedStateRootId,
|
|
1386
1356
|
) -> Result<Vec<EncodedLeafEntry>, LixError> {
|
|
1387
1357
|
let mut out = Vec::new();
|
|
@@ -1389,7 +1359,7 @@ impl TrackedStateTree {
|
|
|
1389
1359
|
while !current.is_empty() {
|
|
1390
1360
|
let mut next = Vec::new();
|
|
1391
1361
|
for hash in current {
|
|
1392
|
-
match self.
|
|
1362
|
+
match self.load_node_with_overlay(store, overlay, &hash).await? {
|
|
1393
1363
|
DecodedNode::Leaf(leaf) => out.extend(leaf.entries().iter().cloned()),
|
|
1394
1364
|
DecodedNode::Internal(internal) => {
|
|
1395
1365
|
next.extend(internal.children().iter().map(|child| child.child_hash));
|
|
@@ -1403,7 +1373,7 @@ impl TrackedStateTree {
|
|
|
1403
1373
|
|
|
1404
1374
|
async fn collect_filtered_entries(
|
|
1405
1375
|
&self,
|
|
1406
|
-
store: &
|
|
1376
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1407
1377
|
root_id: &TrackedStateRootId,
|
|
1408
1378
|
request: &TrackedStateTreeScanRequest,
|
|
1409
1379
|
) -> Result<Vec<(TrackedStateKey, TrackedStateIndexValue)>, LixError> {
|
|
@@ -1412,7 +1382,7 @@ impl TrackedStateTree {
|
|
|
1412
1382
|
|
|
1413
1383
|
fn scan_node<'a, S>(
|
|
1414
1384
|
&'a self,
|
|
1415
|
-
store: &'a
|
|
1385
|
+
store: &'a S,
|
|
1416
1386
|
hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
1417
1387
|
request: &'a TrackedStateTreeScanRequest,
|
|
1418
1388
|
ranges: &'a [EncodedScanRange],
|
|
@@ -1420,7 +1390,7 @@ impl TrackedStateTree {
|
|
|
1420
1390
|
rows: &'a mut Vec<(TrackedStateKey, TrackedStateIndexValue)>,
|
|
1421
1391
|
) -> Pin<Box<dyn Future<Output = Result<(), LixError>> + Send + 'a>>
|
|
1422
1392
|
where
|
|
1423
|
-
S:
|
|
1393
|
+
S: StorageRead + Send + Sync + ?Sized + 'a,
|
|
1424
1394
|
{
|
|
1425
1395
|
Box::pin(async move {
|
|
1426
1396
|
let bytes = self.load_node_bytes(store, &hash).await?;
|
|
@@ -1486,13 +1456,13 @@ impl TrackedStateTree {
|
|
|
1486
1456
|
|
|
1487
1457
|
fn get_many_node<'a, S>(
|
|
1488
1458
|
&'a self,
|
|
1489
|
-
store: &'a
|
|
1459
|
+
store: &'a S,
|
|
1490
1460
|
hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
1491
1461
|
encoded_keys: &'a [(usize, Vec<u8>)],
|
|
1492
1462
|
values: &'a mut [Option<TrackedStateIndexValue>],
|
|
1493
1463
|
) -> Pin<Box<dyn Future<Output = Result<(), LixError>> + Send + 'a>>
|
|
1494
1464
|
where
|
|
1495
|
-
S:
|
|
1465
|
+
S: StorageRead + Send + Sync + ?Sized + 'a,
|
|
1496
1466
|
{
|
|
1497
1467
|
Box::pin(async move {
|
|
1498
1468
|
if encoded_keys.is_empty() {
|
|
@@ -1550,51 +1520,9 @@ impl TrackedStateTree {
|
|
|
1550
1520
|
})
|
|
1551
1521
|
}
|
|
1552
1522
|
|
|
1553
|
-
fn count_matching_keys_node<'a, S>(
|
|
1554
|
-
&'a self,
|
|
1555
|
-
store: &'a mut S,
|
|
1556
|
-
hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
1557
|
-
request: &'a TrackedStateTreeScanRequest,
|
|
1558
|
-
ranges: &'a [EncodedScanRange],
|
|
1559
|
-
) -> Pin<Box<dyn Future<Output = Result<usize, LixError>> + Send + 'a>>
|
|
1560
|
-
where
|
|
1561
|
-
S: StorageReader + Send + 'a,
|
|
1562
|
-
{
|
|
1563
|
-
Box::pin(async move {
|
|
1564
|
-
let mut count = 0usize;
|
|
1565
|
-
match self.load_node(store, &hash).await? {
|
|
1566
|
-
DecodedNode::Leaf(leaf) => {
|
|
1567
|
-
for entry in leaf.entries() {
|
|
1568
|
-
if !encoded_key_in_scan_ranges(&entry.key, ranges) {
|
|
1569
|
-
continue;
|
|
1570
|
-
}
|
|
1571
|
-
let key = decode_key(&entry.key)?;
|
|
1572
|
-
if key_matches_scan_filters(request, &key) {
|
|
1573
|
-
count += 1;
|
|
1574
|
-
}
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
DecodedNode::Internal(internal) => {
|
|
1578
|
-
for child in internal.children() {
|
|
1579
|
-
if child_summary_contained_by_scan_ranges(child, ranges)
|
|
1580
|
-
&& request.entity_ids.is_empty()
|
|
1581
|
-
{
|
|
1582
|
-
count += child.subtree_count as usize;
|
|
1583
|
-
} else if child_summary_overlaps_scan_ranges(child, ranges) {
|
|
1584
|
-
count += self
|
|
1585
|
-
.count_matching_keys_node(store, child.child_hash, request, ranges)
|
|
1586
|
-
.await?;
|
|
1587
|
-
}
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
Ok(count)
|
|
1592
|
-
})
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
1523
|
async fn collect_entries_from_leaf_summaries(
|
|
1596
1524
|
&self,
|
|
1597
|
-
store: &
|
|
1525
|
+
store: &(impl StorageRead + Send + Sync),
|
|
1598
1526
|
leaves: &[ChildSummary],
|
|
1599
1527
|
) -> Result<Vec<EncodedLeafEntry>, LixError> {
|
|
1600
1528
|
let mut entries = Vec::new();
|
|
@@ -1606,7 +1534,7 @@ impl TrackedStateTree {
|
|
|
1606
1534
|
|
|
1607
1535
|
async fn collect_summary_levels_with_overlay(
|
|
1608
1536
|
&self,
|
|
1609
|
-
store: &
|
|
1537
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1610
1538
|
overlay: &storage::TrackedStateChunkOverlay,
|
|
1611
1539
|
root_id: &TrackedStateRootId,
|
|
1612
1540
|
) -> Result<Vec<Vec<ChildSummary>>, LixError> {
|
|
@@ -1623,13 +1551,13 @@ impl TrackedStateTree {
|
|
|
1623
1551
|
|
|
1624
1552
|
fn collect_summary_levels_for_node_with_overlay<'a, S>(
|
|
1625
1553
|
&'a self,
|
|
1626
|
-
store: &'a
|
|
1554
|
+
store: &'a S,
|
|
1627
1555
|
overlay: &'a storage::TrackedStateChunkOverlay,
|
|
1628
1556
|
hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
1629
1557
|
levels: &'a mut Vec<Vec<ChildSummary>>,
|
|
1630
1558
|
) -> Pin<Box<dyn Future<Output = Result<(ChildSummary, usize), LixError>> + 'a>>
|
|
1631
1559
|
where
|
|
1632
|
-
S:
|
|
1560
|
+
S: StorageRead + Send + Sync + ?Sized + 'a,
|
|
1633
1561
|
{
|
|
1634
1562
|
Box::pin(async move {
|
|
1635
1563
|
match self.load_node_with_overlay(store, overlay, &hash).await? {
|
|
@@ -1681,7 +1609,7 @@ impl TrackedStateTree {
|
|
|
1681
1609
|
|
|
1682
1610
|
async fn load_leaf_entries(
|
|
1683
1611
|
&self,
|
|
1684
|
-
store: &
|
|
1612
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1685
1613
|
hash: &[u8; TRACKED_STATE_HASH_BYTES],
|
|
1686
1614
|
) -> Result<Vec<EncodedLeafEntry>, LixError> {
|
|
1687
1615
|
match self.load_node(store, hash).await? {
|
|
@@ -1695,7 +1623,7 @@ impl TrackedStateTree {
|
|
|
1695
1623
|
|
|
1696
1624
|
async fn load_leaf_entries_with_overlay(
|
|
1697
1625
|
&self,
|
|
1698
|
-
store: &
|
|
1626
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1699
1627
|
overlay: &storage::TrackedStateChunkOverlay,
|
|
1700
1628
|
hash: &[u8; TRACKED_STATE_HASH_BYTES],
|
|
1701
1629
|
) -> Result<Vec<EncodedLeafEntry>, LixError> {
|
|
@@ -1710,7 +1638,7 @@ impl TrackedStateTree {
|
|
|
1710
1638
|
|
|
1711
1639
|
async fn load_node(
|
|
1712
1640
|
&self,
|
|
1713
|
-
store: &
|
|
1641
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1714
1642
|
hash: &[u8; TRACKED_STATE_HASH_BYTES],
|
|
1715
1643
|
) -> Result<DecodedNode, LixError> {
|
|
1716
1644
|
let bytes = self.load_node_bytes(store, hash).await?;
|
|
@@ -1719,7 +1647,7 @@ impl TrackedStateTree {
|
|
|
1719
1647
|
|
|
1720
1648
|
async fn load_node_bytes(
|
|
1721
1649
|
&self,
|
|
1722
|
-
store: &
|
|
1650
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1723
1651
|
hash: &[u8; TRACKED_STATE_HASH_BYTES],
|
|
1724
1652
|
) -> Result<Vec<u8>, LixError> {
|
|
1725
1653
|
let bytes = storage::read_chunk(store, hash).await?.ok_or_else(|| {
|
|
@@ -1731,7 +1659,7 @@ impl TrackedStateTree {
|
|
|
1731
1659
|
|
|
1732
1660
|
async fn load_node_with_overlay(
|
|
1733
1661
|
&self,
|
|
1734
|
-
store: &
|
|
1662
|
+
store: &(impl StorageRead + Send + Sync + ?Sized),
|
|
1735
1663
|
overlay: &storage::TrackedStateChunkOverlay,
|
|
1736
1664
|
hash: &[u8; TRACKED_STATE_HASH_BYTES],
|
|
1737
1665
|
) -> Result<DecodedNode, LixError> {
|
|
@@ -1813,7 +1741,7 @@ struct LeafSummaryCursorFrame {
|
|
|
1813
1741
|
impl LeafSummaryCursor {
|
|
1814
1742
|
async fn new(
|
|
1815
1743
|
tree: &TrackedStateTree,
|
|
1816
|
-
store: &
|
|
1744
|
+
store: &(impl StorageRead + Send + Sync),
|
|
1817
1745
|
root_hash: [u8; TRACKED_STATE_HASH_BYTES],
|
|
1818
1746
|
) -> Result<Self, LixError> {
|
|
1819
1747
|
let mut cursor = Self {
|
|
@@ -1846,7 +1774,7 @@ impl LeafSummaryCursor {
|
|
|
1846
1774
|
async fn advance(
|
|
1847
1775
|
&mut self,
|
|
1848
1776
|
tree: &TrackedStateTree,
|
|
1849
|
-
store: &
|
|
1777
|
+
store: &(impl StorageRead + Send + Sync),
|
|
1850
1778
|
) -> Result<(), LixError> {
|
|
1851
1779
|
self.current = None;
|
|
1852
1780
|
while let Some(frame) = self.stack.last_mut() {
|
|
@@ -1871,7 +1799,7 @@ impl LeafSummaryCursor {
|
|
|
1871
1799
|
async fn descend_to_leaf(
|
|
1872
1800
|
&mut self,
|
|
1873
1801
|
tree: &TrackedStateTree,
|
|
1874
|
-
store: &
|
|
1802
|
+
store: &(impl StorageRead + Send + Sync),
|
|
1875
1803
|
mut summary: ChildSummary,
|
|
1876
1804
|
) -> Result<(), LixError> {
|
|
1877
1805
|
loop {
|
|
@@ -1911,14 +1839,12 @@ impl LeafSummaryCursor {
|
|
|
1911
1839
|
struct LeafChunkAccumulator {
|
|
1912
1840
|
entries: Vec<EncodedLeafEntry>,
|
|
1913
1841
|
key_bytes: usize,
|
|
1914
|
-
value_bytes: usize,
|
|
1915
1842
|
}
|
|
1916
1843
|
|
|
1917
1844
|
#[derive(Debug, Default)]
|
|
1918
1845
|
struct LeafChunkRefAccumulator<'a> {
|
|
1919
1846
|
entries: Vec<EncodedLeafEntryRef<'a>>,
|
|
1920
1847
|
key_bytes: usize,
|
|
1921
|
-
value_bytes: usize,
|
|
1922
1848
|
}
|
|
1923
1849
|
|
|
1924
1850
|
#[derive(Debug, Default)]
|
|
@@ -1945,24 +1871,19 @@ fn chunk_leaf_entries(
|
|
|
1945
1871
|
let mut groups = Vec::new();
|
|
1946
1872
|
let mut current = LeafChunkAccumulator::default();
|
|
1947
1873
|
for entry in entries {
|
|
1948
|
-
let item_size =
|
|
1949
|
-
let projected_size =
|
|
1874
|
+
let item_size = estimate_leaf_boundary_entry_size(entry.key.len());
|
|
1875
|
+
let projected_size = estimate_leaf_boundary_chunk_size(
|
|
1950
1876
|
current.entries.len() + 1,
|
|
1951
1877
|
current.key_bytes + entry.key.len(),
|
|
1952
|
-
current.value_bytes + entry.value.len(),
|
|
1953
1878
|
);
|
|
1954
1879
|
if !current.entries.is_empty() && projected_size > options.max_chunk_bytes {
|
|
1955
1880
|
groups.push(std::mem::take(&mut current));
|
|
1956
1881
|
}
|
|
1957
1882
|
|
|
1958
1883
|
current.key_bytes += entry.key.len();
|
|
1959
|
-
current.value_bytes += entry.value.len();
|
|
1960
1884
|
current.entries.push(entry);
|
|
1961
|
-
let current_size =
|
|
1962
|
-
current.entries.len(),
|
|
1963
|
-
current.key_bytes,
|
|
1964
|
-
current.value_bytes,
|
|
1965
|
-
);
|
|
1885
|
+
let current_size =
|
|
1886
|
+
estimate_leaf_boundary_chunk_size(current.entries.len(), current.key_bytes);
|
|
1966
1887
|
if current_size >= options.min_chunk_bytes
|
|
1967
1888
|
&& (current_size >= options.max_chunk_bytes
|
|
1968
1889
|
|| current.entries.last().is_some_and(|entry| {
|
|
@@ -1995,24 +1916,19 @@ fn chunk_leaf_entry_refs<'a>(
|
|
|
1995
1916
|
let mut groups = Vec::new();
|
|
1996
1917
|
let mut current = LeafChunkRefAccumulator::default();
|
|
1997
1918
|
for entry in iter {
|
|
1998
|
-
let item_size =
|
|
1999
|
-
let projected_size =
|
|
1919
|
+
let item_size = estimate_leaf_boundary_entry_size(entry.key.len());
|
|
1920
|
+
let projected_size = estimate_leaf_boundary_chunk_size(
|
|
2000
1921
|
current.entries.len() + 1,
|
|
2001
1922
|
current.key_bytes + entry.key.len(),
|
|
2002
|
-
current.value_bytes + entry.value.len(),
|
|
2003
1923
|
);
|
|
2004
1924
|
if !current.entries.is_empty() && projected_size > options.max_chunk_bytes {
|
|
2005
1925
|
groups.push(std::mem::take(&mut current));
|
|
2006
1926
|
}
|
|
2007
1927
|
|
|
2008
1928
|
current.key_bytes += entry.key.len();
|
|
2009
|
-
current.value_bytes += entry.value.len();
|
|
2010
1929
|
current.entries.push(entry);
|
|
2011
|
-
let current_size =
|
|
2012
|
-
current.entries.len(),
|
|
2013
|
-
current.key_bytes,
|
|
2014
|
-
current.value_bytes,
|
|
2015
|
-
);
|
|
1930
|
+
let current_size =
|
|
1931
|
+
estimate_leaf_boundary_chunk_size(current.entries.len(), current.key_bytes);
|
|
2016
1932
|
if current_size >= options.min_chunk_bytes
|
|
2017
1933
|
&& (current_size >= options.max_chunk_bytes
|
|
2018
1934
|
|| current.entries.last().is_some_and(|entry| {
|
|
@@ -2138,8 +2054,12 @@ fn estimate_leaf_chunk_size(entry_count: usize, key_bytes: usize, value_bytes: u
|
|
|
2138
2054
|
10 + entry_count * 12 + key_bytes + value_bytes
|
|
2139
2055
|
}
|
|
2140
2056
|
|
|
2141
|
-
fn
|
|
2142
|
-
|
|
2057
|
+
fn estimate_leaf_boundary_chunk_size(entry_count: usize, key_bytes: usize) -> usize {
|
|
2058
|
+
estimate_leaf_chunk_size(entry_count, key_bytes, 0)
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
fn estimate_leaf_boundary_entry_size(key_bytes: usize) -> usize {
|
|
2062
|
+
12 + key_bytes
|
|
2143
2063
|
}
|
|
2144
2064
|
|
|
2145
2065
|
fn estimate_internal_chunk_size(
|
|
@@ -2179,7 +2099,7 @@ fn internal_boundaries_match(left: &[ChildSummary], right: &[ChildSummary]) -> b
|
|
|
2179
2099
|
|
|
2180
2100
|
async fn child_summaries_are_leaves(
|
|
2181
2101
|
tree: &TrackedStateTree,
|
|
2182
|
-
store: &
|
|
2102
|
+
store: &(impl StorageRead + Send + Sync),
|
|
2183
2103
|
children: &[ChildSummary],
|
|
2184
2104
|
) -> Result<bool, LixError> {
|
|
2185
2105
|
let Some(first_child) = children.first() else {
|
|
@@ -2302,7 +2222,7 @@ fn scan_ranges(request: &TrackedStateTreeScanRequest) -> Vec<EncodedScanRange> {
|
|
|
2302
2222
|
return Vec::new();
|
|
2303
2223
|
}
|
|
2304
2224
|
|
|
2305
|
-
let can_bind_entity = !request.
|
|
2225
|
+
let can_bind_entity = !request.entity_pks.is_empty()
|
|
2306
2226
|
&& !request.file_ids.is_empty()
|
|
2307
2227
|
&& request
|
|
2308
2228
|
.file_ids
|
|
@@ -2318,11 +2238,11 @@ fn scan_ranges(request: &TrackedStateTreeScanRequest) -> Vec<EncodedScanRange> {
|
|
|
2318
2238
|
NullableKeyFilter::Value(file_id) => Some(file_id.clone()),
|
|
2319
2239
|
NullableKeyFilter::Any => unreachable!("filtered above"),
|
|
2320
2240
|
};
|
|
2321
|
-
for
|
|
2241
|
+
for entity_pk in &request.entity_pks {
|
|
2322
2242
|
let key = TrackedStateKey {
|
|
2323
2243
|
schema_key: schema_key.clone(),
|
|
2324
2244
|
file_id: file_id.clone(),
|
|
2325
|
-
|
|
2245
|
+
entity_pk: entity_pk.clone(),
|
|
2326
2246
|
};
|
|
2327
2247
|
ranges.push(exact_scan_range(encode_key(&key)));
|
|
2328
2248
|
}
|
|
@@ -2361,7 +2281,7 @@ fn scan_key_decode_hint<'a>(
|
|
|
2361
2281
|
if ranges.len() != 1 || request.schema_keys.len() != 1 || request.file_ids.len() != 1 {
|
|
2362
2282
|
return None;
|
|
2363
2283
|
}
|
|
2364
|
-
if !request.
|
|
2284
|
+
if !request.entity_pks.is_empty() {
|
|
2365
2285
|
return None;
|
|
2366
2286
|
}
|
|
2367
2287
|
let file_id = match request.file_ids.first()? {
|
|
@@ -2413,20 +2333,6 @@ fn child_summary_overlaps_scan_ranges(child: &ChildSummary, ranges: &[EncodedSca
|
|
|
2413
2333
|
})
|
|
2414
2334
|
}
|
|
2415
2335
|
|
|
2416
|
-
fn child_summary_contained_by_scan_ranges(
|
|
2417
|
-
child: &ChildSummary,
|
|
2418
|
-
ranges: &[EncodedScanRange],
|
|
2419
|
-
) -> bool {
|
|
2420
|
-
ranges.is_empty()
|
|
2421
|
-
|| ranges.iter().any(|range| {
|
|
2422
|
-
child.first_key.as_slice() >= range.start.as_slice()
|
|
2423
|
-
&& range
|
|
2424
|
-
.end
|
|
2425
|
-
.as_ref()
|
|
2426
|
-
.is_none_or(|end| child.last_key.as_slice() < end.as_slice())
|
|
2427
|
-
})
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
2336
|
fn encoded_key_in_scan_ranges(key: &[u8], ranges: &[EncodedScanRange]) -> bool {
|
|
2431
2337
|
ranges.is_empty()
|
|
2432
2338
|
|| ranges.iter().any(|range| {
|
|
@@ -2439,7 +2345,7 @@ fn key_matches_scan_filters(request: &TrackedStateTreeScanRequest, key: &Tracked
|
|
|
2439
2345
|
if !request.schema_keys.is_empty() && !request.schema_keys.contains(&key.schema_key) {
|
|
2440
2346
|
return false;
|
|
2441
2347
|
}
|
|
2442
|
-
if !request.
|
|
2348
|
+
if !request.entity_pks.is_empty() && !request.entity_pks.contains(&key.entity_pk) {
|
|
2443
2349
|
return false;
|
|
2444
2350
|
}
|
|
2445
2351
|
if !request.file_ids.is_empty()
|
|
@@ -2459,48 +2365,28 @@ fn scan_limit_reached(request: &TrackedStateTreeScanRequest, row_count: usize) -
|
|
|
2459
2365
|
|
|
2460
2366
|
#[cfg(test)]
|
|
2461
2367
|
mod tests {
|
|
2462
|
-
use std::sync::Arc;
|
|
2463
|
-
|
|
2464
2368
|
use super::*;
|
|
2465
|
-
use crate::
|
|
2466
|
-
use crate::
|
|
2467
|
-
use crate::storage::{
|
|
2369
|
+
use crate::entity_pk::EntityPk;
|
|
2370
|
+
use crate::storage::StorageContext;
|
|
2371
|
+
use crate::storage::{InMemoryStorageBackend, StorageReadOptions, StorageWriteOptions};
|
|
2468
2372
|
use crate::tracked_state::codec::encode_value;
|
|
2469
2373
|
|
|
2470
2374
|
#[tokio::test]
|
|
2471
|
-
async fn
|
|
2472
|
-
let storage = StorageContext::new(
|
|
2375
|
+
async fn exact_read_roundtrips_from_applied_root() {
|
|
2376
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2473
2377
|
let tree = TrackedStateTree::new();
|
|
2474
2378
|
let key = key("schema", None, "entity");
|
|
2475
2379
|
let value = value("change-1", Some("{}"));
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
.begin_write_transaction()
|
|
2479
|
-
.await
|
|
2480
|
-
.expect("transaction should open");
|
|
2481
|
-
let result = apply_mutations_for_test(
|
|
2482
|
-
&tree,
|
|
2483
|
-
transaction.as_mut(),
|
|
2484
|
-
None,
|
|
2485
|
-
vec![mutation(&key, &value)],
|
|
2486
|
-
Some("commit-1"),
|
|
2487
|
-
)
|
|
2488
|
-
.await
|
|
2489
|
-
.expect("mutations should apply");
|
|
2490
|
-
transaction
|
|
2491
|
-
.commit()
|
|
2492
|
-
.await
|
|
2493
|
-
.expect("transaction should commit");
|
|
2494
|
-
|
|
2495
|
-
let mut store = storage.clone();
|
|
2496
|
-
assert_eq!(
|
|
2497
|
-
tree.load_root(&mut store, "commit-1")
|
|
2380
|
+
let result =
|
|
2381
|
+
apply_mutations_for_test(&tree, &storage, None, vec![mutation(&key, &value)], None)
|
|
2498
2382
|
.await
|
|
2499
|
-
.expect("
|
|
2500
|
-
|
|
2501
|
-
|
|
2383
|
+
.expect("mutations should apply");
|
|
2384
|
+
|
|
2385
|
+
let store = storage
|
|
2386
|
+
.begin_read(StorageReadOptions::default())
|
|
2387
|
+
.expect("read should open");
|
|
2502
2388
|
assert_eq!(
|
|
2503
|
-
tree.get(&
|
|
2389
|
+
tree.get(&store, &result.root_id, &key)
|
|
2504
2390
|
.await
|
|
2505
2391
|
.expect("row should load"),
|
|
2506
2392
|
Some(value)
|
|
@@ -2509,52 +2395,40 @@ mod tests {
|
|
|
2509
2395
|
|
|
2510
2396
|
#[tokio::test]
|
|
2511
2397
|
async fn latest_mutation_for_key_wins() {
|
|
2512
|
-
let storage = StorageContext::new(
|
|
2398
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2513
2399
|
let tree = TrackedStateTree::new();
|
|
2514
2400
|
let key = key("schema", None, "entity");
|
|
2515
2401
|
let old_value = value("change-old", Some("{\"v\":1}"));
|
|
2516
2402
|
let new_value = value("change-new", Some("{\"v\":2}"));
|
|
2517
|
-
|
|
2518
|
-
let mut transaction = storage
|
|
2519
|
-
.begin_write_transaction()
|
|
2520
|
-
.await
|
|
2521
|
-
.expect("transaction should open");
|
|
2522
2403
|
let result = apply_mutations_for_test(
|
|
2523
2404
|
&tree,
|
|
2524
|
-
|
|
2405
|
+
&storage,
|
|
2525
2406
|
None,
|
|
2526
2407
|
vec![mutation(&key, &old_value), mutation(&key, &new_value)],
|
|
2527
2408
|
None,
|
|
2528
2409
|
)
|
|
2529
2410
|
.await
|
|
2530
2411
|
.expect("mutations should apply");
|
|
2531
|
-
transaction
|
|
2532
|
-
.commit()
|
|
2533
|
-
.await
|
|
2534
|
-
.expect("transaction should commit");
|
|
2535
2412
|
|
|
2536
|
-
let
|
|
2413
|
+
let store = storage
|
|
2414
|
+
.begin_read(StorageReadOptions::default())
|
|
2415
|
+
.expect("read should open");
|
|
2537
2416
|
let loaded = tree
|
|
2538
|
-
.get(&
|
|
2417
|
+
.get(&store, &result.root_id, &key)
|
|
2539
2418
|
.await
|
|
2540
2419
|
.expect("row should load")
|
|
2541
2420
|
.expect("row should exist");
|
|
2542
|
-
assert_eq!(loaded.
|
|
2543
|
-
assert_eq!(loaded.
|
|
2421
|
+
assert_eq!(loaded.change_id, "change-new");
|
|
2422
|
+
assert_eq!(loaded.commit_id, "commit");
|
|
2544
2423
|
}
|
|
2545
2424
|
|
|
2546
2425
|
#[tokio::test]
|
|
2547
2426
|
async fn scan_filters_by_index_key_without_materializing_tombstones() {
|
|
2548
|
-
let storage = StorageContext::new(
|
|
2427
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2549
2428
|
let tree = TrackedStateTree::new();
|
|
2550
|
-
|
|
2551
|
-
let mut transaction = storage
|
|
2552
|
-
.begin_write_transaction()
|
|
2553
|
-
.await
|
|
2554
|
-
.expect("transaction should open");
|
|
2555
2429
|
let result = apply_mutations_for_test(
|
|
2556
2430
|
&tree,
|
|
2557
|
-
|
|
2431
|
+
&storage,
|
|
2558
2432
|
None,
|
|
2559
2433
|
vec![
|
|
2560
2434
|
mutation_owned(key("schema-a", None, "visible"), value("c1", Some("{}"))),
|
|
@@ -2565,15 +2439,13 @@ mod tests {
|
|
|
2565
2439
|
)
|
|
2566
2440
|
.await
|
|
2567
2441
|
.expect("mutations should apply");
|
|
2568
|
-
transaction
|
|
2569
|
-
.commit()
|
|
2570
|
-
.await
|
|
2571
|
-
.expect("transaction should commit");
|
|
2572
2442
|
|
|
2573
|
-
let
|
|
2443
|
+
let store = storage
|
|
2444
|
+
.begin_read(StorageReadOptions::default())
|
|
2445
|
+
.expect("read should open");
|
|
2574
2446
|
let rows = tree
|
|
2575
2447
|
.scan(
|
|
2576
|
-
&
|
|
2448
|
+
&store,
|
|
2577
2449
|
&result.root_id,
|
|
2578
2450
|
&TrackedStateTreeScanRequest {
|
|
2579
2451
|
schema_keys: vec!["schema-a".to_string()],
|
|
@@ -2585,13 +2457,13 @@ mod tests {
|
|
|
2585
2457
|
assert_eq!(rows.len(), 2);
|
|
2586
2458
|
let identities = rows
|
|
2587
2459
|
.iter()
|
|
2588
|
-
.map(|(key, _)| key.
|
|
2460
|
+
.map(|(key, _)| key.entity_pk.as_single_string_owned().expect("identity"))
|
|
2589
2461
|
.collect::<Vec<_>>();
|
|
2590
2462
|
assert_eq!(identities, vec!["deleted", "visible"]);
|
|
2591
2463
|
|
|
2592
2464
|
let live_rows = tree
|
|
2593
2465
|
.scan(
|
|
2594
|
-
&
|
|
2466
|
+
&store,
|
|
2595
2467
|
&result.root_id,
|
|
2596
2468
|
&TrackedStateTreeScanRequest {
|
|
2597
2469
|
schema_keys: vec!["schema-a".to_string()],
|
|
@@ -2603,23 +2475,18 @@ mod tests {
|
|
|
2603
2475
|
.expect("live scan should succeed");
|
|
2604
2476
|
let live_identities = live_rows
|
|
2605
2477
|
.iter()
|
|
2606
|
-
.map(|(key, _)| key.
|
|
2478
|
+
.map(|(key, _)| key.entity_pk.as_single_string_owned().expect("identity"))
|
|
2607
2479
|
.collect::<Vec<_>>();
|
|
2608
2480
|
assert_eq!(live_identities, vec!["visible"]);
|
|
2609
2481
|
}
|
|
2610
2482
|
|
|
2611
2483
|
#[tokio::test]
|
|
2612
2484
|
async fn scan_filters_by_schema_entity_and_file() {
|
|
2613
|
-
let storage = StorageContext::new(
|
|
2485
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2614
2486
|
let tree = TrackedStateTree::new();
|
|
2615
|
-
|
|
2616
|
-
let mut transaction = storage
|
|
2617
|
-
.begin_write_transaction()
|
|
2618
|
-
.await
|
|
2619
|
-
.expect("transaction should open");
|
|
2620
2487
|
let result = apply_mutations_for_test(
|
|
2621
2488
|
&tree,
|
|
2622
|
-
|
|
2489
|
+
&storage,
|
|
2623
2490
|
None,
|
|
2624
2491
|
vec![
|
|
2625
2492
|
mutation_owned(
|
|
@@ -2643,19 +2510,17 @@ mod tests {
|
|
|
2643
2510
|
)
|
|
2644
2511
|
.await
|
|
2645
2512
|
.expect("mutations should apply");
|
|
2646
|
-
transaction
|
|
2647
|
-
.commit()
|
|
2648
|
-
.await
|
|
2649
|
-
.expect("transaction should commit");
|
|
2650
2513
|
|
|
2651
|
-
let
|
|
2514
|
+
let store = storage
|
|
2515
|
+
.begin_read(StorageReadOptions::default())
|
|
2516
|
+
.expect("read should open");
|
|
2652
2517
|
let rows = tree
|
|
2653
2518
|
.scan(
|
|
2654
|
-
&
|
|
2519
|
+
&store,
|
|
2655
2520
|
&result.root_id,
|
|
2656
2521
|
&TrackedStateTreeScanRequest {
|
|
2657
2522
|
schema_keys: vec!["schema-a".to_string()],
|
|
2658
|
-
|
|
2523
|
+
entity_pks: vec![crate::entity_pk::EntityPk::single("entity-a")],
|
|
2659
2524
|
file_ids: vec![crate::NullableKeyFilter::Value("file-a".to_string())],
|
|
2660
2525
|
..Default::default()
|
|
2661
2526
|
},
|
|
@@ -2668,7 +2533,7 @@ mod tests {
|
|
|
2668
2533
|
assert_eq!(
|
|
2669
2534
|
rows[0]
|
|
2670
2535
|
.0
|
|
2671
|
-
.
|
|
2536
|
+
.entity_pk
|
|
2672
2537
|
.as_single_string_owned()
|
|
2673
2538
|
.expect("identity"),
|
|
2674
2539
|
"entity-a"
|
|
@@ -2678,16 +2543,11 @@ mod tests {
|
|
|
2678
2543
|
|
|
2679
2544
|
#[tokio::test]
|
|
2680
2545
|
async fn scan_schema_file_prefix_honors_tombstones_and_limit() {
|
|
2681
|
-
let storage = StorageContext::new(
|
|
2546
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2682
2547
|
let tree = TrackedStateTree::new();
|
|
2683
|
-
|
|
2684
|
-
let mut transaction = storage
|
|
2685
|
-
.begin_write_transaction()
|
|
2686
|
-
.await
|
|
2687
|
-
.expect("transaction should open");
|
|
2688
2548
|
let result = apply_mutations_for_test(
|
|
2689
2549
|
&tree,
|
|
2690
|
-
|
|
2550
|
+
&storage,
|
|
2691
2551
|
None,
|
|
2692
2552
|
vec![
|
|
2693
2553
|
mutation_owned(
|
|
@@ -2711,15 +2571,13 @@ mod tests {
|
|
|
2711
2571
|
)
|
|
2712
2572
|
.await
|
|
2713
2573
|
.expect("mutations should apply");
|
|
2714
|
-
transaction
|
|
2715
|
-
.commit()
|
|
2716
|
-
.await
|
|
2717
|
-
.expect("transaction should commit");
|
|
2718
2574
|
|
|
2719
|
-
let
|
|
2575
|
+
let store = storage
|
|
2576
|
+
.begin_read(StorageReadOptions::default())
|
|
2577
|
+
.expect("read should open");
|
|
2720
2578
|
let rows = tree
|
|
2721
2579
|
.scan(
|
|
2722
|
-
&
|
|
2580
|
+
&store,
|
|
2723
2581
|
&result.root_id,
|
|
2724
2582
|
&TrackedStateTreeScanRequest {
|
|
2725
2583
|
schema_keys: vec!["schema-a".to_string()],
|
|
@@ -2738,7 +2596,7 @@ mod tests {
|
|
|
2738
2596
|
));
|
|
2739
2597
|
assert_eq!(
|
|
2740
2598
|
rows.iter()
|
|
2741
|
-
.map(|(key, _)| key.
|
|
2599
|
+
.map(|(key, _)| key.entity_pk.as_single_string_owned().expect("identity"))
|
|
2742
2600
|
.collect::<Vec<_>>(),
|
|
2743
2601
|
vec!["entity-a", "entity-c"]
|
|
2744
2602
|
);
|
|
@@ -2746,21 +2604,16 @@ mod tests {
|
|
|
2746
2604
|
|
|
2747
2605
|
#[tokio::test]
|
|
2748
2606
|
async fn applying_to_base_root_reuses_existing_rows_and_overwrites_changed_rows() {
|
|
2749
|
-
let storage = StorageContext::new(
|
|
2607
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2750
2608
|
let tree = TrackedStateTree::new();
|
|
2751
2609
|
let unchanged_key = key("schema", None, "unchanged");
|
|
2752
2610
|
let changed_key = key("schema", None, "changed");
|
|
2753
2611
|
let unchanged_value = value("c1", Some("{}"));
|
|
2754
2612
|
let old_changed_value = value("c2", Some("{\"old\":true}"));
|
|
2755
2613
|
let new_changed_value = value("c3", Some("{\"new\":true}"));
|
|
2756
|
-
|
|
2757
|
-
let mut transaction = storage
|
|
2758
|
-
.begin_write_transaction()
|
|
2759
|
-
.await
|
|
2760
|
-
.expect("transaction should open");
|
|
2761
2614
|
let base = apply_mutations_for_test(
|
|
2762
2615
|
&tree,
|
|
2763
|
-
|
|
2616
|
+
&storage,
|
|
2764
2617
|
None,
|
|
2765
2618
|
vec![
|
|
2766
2619
|
mutation(&unchanged_key, &unchanged_value),
|
|
@@ -2772,34 +2625,30 @@ mod tests {
|
|
|
2772
2625
|
.expect("base should build");
|
|
2773
2626
|
let next = apply_mutations_for_test(
|
|
2774
2627
|
&tree,
|
|
2775
|
-
|
|
2628
|
+
&storage,
|
|
2776
2629
|
Some(&base.root_id),
|
|
2777
2630
|
vec![mutation(&changed_key, &new_changed_value)],
|
|
2778
2631
|
None,
|
|
2779
2632
|
)
|
|
2780
2633
|
.await
|
|
2781
2634
|
.expect("next should build");
|
|
2782
|
-
transaction
|
|
2783
|
-
.commit()
|
|
2784
|
-
.await
|
|
2785
|
-
.expect("transaction should commit");
|
|
2786
2635
|
|
|
2787
|
-
let
|
|
2636
|
+
let store = storage
|
|
2637
|
+
.begin_read(StorageReadOptions::default())
|
|
2638
|
+
.expect("read should open");
|
|
2788
2639
|
assert_eq!(
|
|
2789
|
-
tree.get(&
|
|
2640
|
+
tree.get(&store, &next.root_id, &unchanged_key)
|
|
2790
2641
|
.await
|
|
2791
2642
|
.expect("unchanged read")
|
|
2792
2643
|
.expect("unchanged exists")
|
|
2793
|
-
.change_locator
|
|
2794
2644
|
.change_id,
|
|
2795
2645
|
"c1"
|
|
2796
2646
|
);
|
|
2797
2647
|
assert_eq!(
|
|
2798
|
-
tree.get(&
|
|
2648
|
+
tree.get(&store, &next.root_id, &changed_key)
|
|
2799
2649
|
.await
|
|
2800
2650
|
.expect("changed read")
|
|
2801
2651
|
.expect("changed exists")
|
|
2802
|
-
.change_locator
|
|
2803
2652
|
.change_id,
|
|
2804
2653
|
"c3"
|
|
2805
2654
|
);
|
|
@@ -2807,7 +2656,7 @@ mod tests {
|
|
|
2807
2656
|
|
|
2808
2657
|
#[tokio::test]
|
|
2809
2658
|
async fn two_commit_roots_can_share_unchanged_rows() {
|
|
2810
|
-
let storage = StorageContext::new(
|
|
2659
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2811
2660
|
let tree = TrackedStateTree::new();
|
|
2812
2661
|
let shared_key = key("schema", None, "shared");
|
|
2813
2662
|
let branch_a_key = key("schema", None, "branch-a");
|
|
@@ -2815,14 +2664,9 @@ mod tests {
|
|
|
2815
2664
|
let shared_value = value("shared-change", Some("{\"shared\":true}"));
|
|
2816
2665
|
let branch_a_value = value("branch-a-change", Some("{\"branch\":\"a\"}"));
|
|
2817
2666
|
let branch_b_value = value("branch-b-change", Some("{\"branch\":\"b\"}"));
|
|
2818
|
-
|
|
2819
|
-
let mut transaction = storage
|
|
2820
|
-
.begin_write_transaction()
|
|
2821
|
-
.await
|
|
2822
|
-
.expect("transaction should open");
|
|
2823
2667
|
let base = apply_mutations_for_test(
|
|
2824
2668
|
&tree,
|
|
2825
|
-
|
|
2669
|
+
&storage,
|
|
2826
2670
|
None,
|
|
2827
2671
|
vec![mutation(&shared_key, &shared_value)],
|
|
2828
2672
|
Some("commit-base"),
|
|
@@ -2831,7 +2675,7 @@ mod tests {
|
|
|
2831
2675
|
.expect("base root should build");
|
|
2832
2676
|
let branch_a = apply_mutations_for_test(
|
|
2833
2677
|
&tree,
|
|
2834
|
-
|
|
2678
|
+
&storage,
|
|
2835
2679
|
Some(&base.root_id),
|
|
2836
2680
|
vec![mutation(&branch_a_key, &branch_a_value)],
|
|
2837
2681
|
Some("commit-a"),
|
|
@@ -2840,39 +2684,37 @@ mod tests {
|
|
|
2840
2684
|
.expect("branch a root should build");
|
|
2841
2685
|
let branch_b = apply_mutations_for_test(
|
|
2842
2686
|
&tree,
|
|
2843
|
-
|
|
2687
|
+
&storage,
|
|
2844
2688
|
Some(&base.root_id),
|
|
2845
2689
|
vec![mutation(&branch_b_key, &branch_b_value)],
|
|
2846
2690
|
Some("commit-b"),
|
|
2847
2691
|
)
|
|
2848
2692
|
.await
|
|
2849
2693
|
.expect("branch b root should build");
|
|
2850
|
-
transaction
|
|
2851
|
-
.commit()
|
|
2852
|
-
.await
|
|
2853
|
-
.expect("transaction should commit");
|
|
2854
2694
|
|
|
2855
2695
|
assert_ne!(branch_a.root_id, branch_b.root_id);
|
|
2856
|
-
let
|
|
2696
|
+
let store = storage
|
|
2697
|
+
.begin_read(StorageReadOptions::default())
|
|
2698
|
+
.expect("read should open");
|
|
2857
2699
|
assert_eq!(
|
|
2858
|
-
tree.get(&
|
|
2700
|
+
tree.get(&store, &branch_a.root_id, &shared_key)
|
|
2859
2701
|
.await
|
|
2860
2702
|
.expect("branch a shared row should load"),
|
|
2861
2703
|
Some(value("shared-change", Some("{\"shared\":true}")))
|
|
2862
2704
|
);
|
|
2863
2705
|
assert_eq!(
|
|
2864
|
-
tree.get(&
|
|
2706
|
+
tree.get(&store, &branch_b.root_id, &shared_key)
|
|
2865
2707
|
.await
|
|
2866
2708
|
.expect("branch b shared row should load"),
|
|
2867
2709
|
Some(value("shared-change", Some("{\"shared\":true}")))
|
|
2868
2710
|
);
|
|
2869
2711
|
assert!(tree
|
|
2870
|
-
.get(&
|
|
2712
|
+
.get(&store, &branch_a.root_id, &branch_b_key)
|
|
2871
2713
|
.await
|
|
2872
2714
|
.expect("branch a should read")
|
|
2873
2715
|
.is_none());
|
|
2874
2716
|
assert!(tree
|
|
2875
|
-
.get(&
|
|
2717
|
+
.get(&store, &branch_b.root_id, &branch_a_key)
|
|
2876
2718
|
.await
|
|
2877
2719
|
.expect("branch b should read")
|
|
2878
2720
|
.is_none());
|
|
@@ -2880,7 +2722,7 @@ mod tests {
|
|
|
2880
2722
|
|
|
2881
2723
|
#[tokio::test]
|
|
2882
2724
|
async fn single_update_matches_full_canonical_rebuild() {
|
|
2883
|
-
let storage = StorageContext::new(
|
|
2725
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2884
2726
|
let tree = TrackedStateTree::with_options(TrackedStateTreeOptions {
|
|
2885
2727
|
target_chunk_bytes: 128,
|
|
2886
2728
|
min_chunk_bytes: 64,
|
|
@@ -2896,25 +2738,23 @@ mod tests {
|
|
|
2896
2738
|
.collect::<Vec<_>>();
|
|
2897
2739
|
let changed_key = key("schema", None, "entity-000");
|
|
2898
2740
|
let changed_value = value("changed", Some("{\"v\":\"changed\"}"));
|
|
2899
|
-
|
|
2900
|
-
let mut transaction = storage
|
|
2901
|
-
.begin_write_transaction()
|
|
2902
|
-
.await
|
|
2903
|
-
.expect("transaction should open");
|
|
2904
|
-
let base = apply_mutations_for_test(&tree, transaction.as_mut(), None, rows, None)
|
|
2741
|
+
let base = apply_mutations_for_test(&tree, &storage, None, rows, None)
|
|
2905
2742
|
.await
|
|
2906
2743
|
.expect("base should build");
|
|
2907
2744
|
let fast = apply_mutations_for_test(
|
|
2908
2745
|
&tree,
|
|
2909
|
-
|
|
2746
|
+
&storage,
|
|
2910
2747
|
Some(&base.root_id),
|
|
2911
2748
|
vec![mutation(&changed_key, &changed_value)],
|
|
2912
2749
|
None,
|
|
2913
2750
|
)
|
|
2914
2751
|
.await
|
|
2915
2752
|
.expect("fast path should apply");
|
|
2753
|
+
let read = storage
|
|
2754
|
+
.begin_read(StorageReadOptions::default())
|
|
2755
|
+
.expect("read should open");
|
|
2916
2756
|
let mut canonical_entries = tree
|
|
2917
|
-
.collect_leaf_entries(&
|
|
2757
|
+
.collect_leaf_entries(&read, &base.root_id)
|
|
2918
2758
|
.await
|
|
2919
2759
|
.expect("base entries should collect");
|
|
2920
2760
|
assert!(canonical_entries
|
|
@@ -2935,7 +2775,7 @@ mod tests {
|
|
|
2935
2775
|
|
|
2936
2776
|
#[tokio::test]
|
|
2937
2777
|
async fn single_insert_matches_full_canonical_rebuild() {
|
|
2938
|
-
let storage = StorageContext::new(
|
|
2778
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2939
2779
|
let tree = TrackedStateTree::with_options(TrackedStateTreeOptions {
|
|
2940
2780
|
target_chunk_bytes: 128,
|
|
2941
2781
|
min_chunk_bytes: 64,
|
|
@@ -2951,25 +2791,23 @@ mod tests {
|
|
|
2951
2791
|
.collect::<Vec<_>>();
|
|
2952
2792
|
let inserted_key = key("schema", None, "entity-050a");
|
|
2953
2793
|
let inserted_value = value("inserted", Some("{\"v\":\"inserted\"}"));
|
|
2954
|
-
|
|
2955
|
-
let mut transaction = storage
|
|
2956
|
-
.begin_write_transaction()
|
|
2957
|
-
.await
|
|
2958
|
-
.expect("transaction should open");
|
|
2959
|
-
let base = apply_mutations_for_test(&tree, transaction.as_mut(), None, rows, None)
|
|
2794
|
+
let base = apply_mutations_for_test(&tree, &storage, None, rows, None)
|
|
2960
2795
|
.await
|
|
2961
2796
|
.expect("base should build");
|
|
2962
2797
|
let fast = apply_mutations_for_test(
|
|
2963
2798
|
&tree,
|
|
2964
|
-
|
|
2799
|
+
&storage,
|
|
2965
2800
|
Some(&base.root_id),
|
|
2966
2801
|
vec![mutation(&inserted_key, &inserted_value)],
|
|
2967
2802
|
None,
|
|
2968
2803
|
)
|
|
2969
2804
|
.await
|
|
2970
2805
|
.expect("fast path should apply");
|
|
2806
|
+
let read = storage
|
|
2807
|
+
.begin_read(StorageReadOptions::default())
|
|
2808
|
+
.expect("read should open");
|
|
2971
2809
|
let mut canonical_entries = tree
|
|
2972
|
-
.collect_leaf_entries(&
|
|
2810
|
+
.collect_leaf_entries(&read, &base.root_id)
|
|
2973
2811
|
.await
|
|
2974
2812
|
.expect("base entries should collect");
|
|
2975
2813
|
let encoded_inserted_key = encode_key(&inserted_key);
|
|
@@ -2993,7 +2831,7 @@ mod tests {
|
|
|
2993
2831
|
|
|
2994
2832
|
#[tokio::test]
|
|
2995
2833
|
async fn batch_update_matches_full_canonical_rebuild() {
|
|
2996
|
-
let storage = StorageContext::new(
|
|
2834
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
2997
2835
|
let tree = TrackedStateTree::with_options(TrackedStateTreeOptions {
|
|
2998
2836
|
target_chunk_bytes: 128,
|
|
2999
2837
|
min_chunk_bytes: 64,
|
|
@@ -3018,17 +2856,12 @@ mod tests {
|
|
|
3018
2856
|
)
|
|
3019
2857
|
})
|
|
3020
2858
|
.collect::<Vec<_>>();
|
|
3021
|
-
|
|
3022
|
-
let mut transaction = storage
|
|
3023
|
-
.begin_write_transaction()
|
|
3024
|
-
.await
|
|
3025
|
-
.expect("transaction should open");
|
|
3026
|
-
let base = apply_mutations_for_test(&tree, transaction.as_mut(), None, rows, None)
|
|
2859
|
+
let base = apply_mutations_for_test(&tree, &storage, None, rows, None)
|
|
3027
2860
|
.await
|
|
3028
2861
|
.expect("base should build");
|
|
3029
2862
|
let fast = apply_mutations_for_test(
|
|
3030
2863
|
&tree,
|
|
3031
|
-
|
|
2864
|
+
&storage,
|
|
3032
2865
|
Some(&base.root_id),
|
|
3033
2866
|
updates
|
|
3034
2867
|
.iter()
|
|
@@ -3038,8 +2871,11 @@ mod tests {
|
|
|
3038
2871
|
)
|
|
3039
2872
|
.await
|
|
3040
2873
|
.expect("batch path should apply");
|
|
2874
|
+
let read = storage
|
|
2875
|
+
.begin_read(StorageReadOptions::default())
|
|
2876
|
+
.expect("read should open");
|
|
3041
2877
|
let mut canonical_entries = tree
|
|
3042
|
-
.collect_leaf_entries(&
|
|
2878
|
+
.collect_leaf_entries(&read, &base.root_id)
|
|
3043
2879
|
.await
|
|
3044
2880
|
.expect("base entries should collect");
|
|
3045
2881
|
for (key, value) in updates {
|
|
@@ -3059,7 +2895,7 @@ mod tests {
|
|
|
3059
2895
|
|
|
3060
2896
|
#[tokio::test]
|
|
3061
2897
|
async fn batch_insert_matches_full_canonical_rebuild() {
|
|
3062
|
-
let storage = StorageContext::new(
|
|
2898
|
+
let storage = StorageContext::new(InMemoryStorageBackend::new());
|
|
3063
2899
|
let tree = TrackedStateTree::with_options(TrackedStateTreeOptions {
|
|
3064
2900
|
target_chunk_bytes: 128,
|
|
3065
2901
|
min_chunk_bytes: 64,
|
|
@@ -3076,9 +2912,9 @@ mod tests {
|
|
|
3076
2912
|
let inserts = ["entity-050a", "entity-050b", "entity-050c"]
|
|
3077
2913
|
.into_iter()
|
|
3078
2914
|
.enumerate()
|
|
3079
|
-
.map(|(index,
|
|
2915
|
+
.map(|(index, entity_pk)| {
|
|
3080
2916
|
(
|
|
3081
|
-
key("schema", None,
|
|
2917
|
+
key("schema", None, entity_pk),
|
|
3082
2918
|
value(
|
|
3083
2919
|
&format!("inserted-{index}"),
|
|
3084
2920
|
Some(&format!("{{\"inserted\":{index}}}")),
|
|
@@ -3086,17 +2922,12 @@ mod tests {
|
|
|
3086
2922
|
)
|
|
3087
2923
|
})
|
|
3088
2924
|
.collect::<Vec<_>>();
|
|
3089
|
-
|
|
3090
|
-
let mut transaction = storage
|
|
3091
|
-
.begin_write_transaction()
|
|
3092
|
-
.await
|
|
3093
|
-
.expect("transaction should open");
|
|
3094
|
-
let base = apply_mutations_for_test(&tree, transaction.as_mut(), None, rows, None)
|
|
2925
|
+
let base = apply_mutations_for_test(&tree, &storage, None, rows, None)
|
|
3095
2926
|
.await
|
|
3096
2927
|
.expect("base should build");
|
|
3097
2928
|
let fast = apply_mutations_for_test(
|
|
3098
2929
|
&tree,
|
|
3099
|
-
|
|
2930
|
+
&storage,
|
|
3100
2931
|
Some(&base.root_id),
|
|
3101
2932
|
inserts
|
|
3102
2933
|
.iter()
|
|
@@ -3106,8 +2937,11 @@ mod tests {
|
|
|
3106
2937
|
)
|
|
3107
2938
|
.await
|
|
3108
2939
|
.expect("batch path should apply");
|
|
2940
|
+
let read = storage
|
|
2941
|
+
.begin_read(StorageReadOptions::default())
|
|
2942
|
+
.expect("read should open");
|
|
3109
2943
|
let mut canonical_entries = tree
|
|
3110
|
-
.collect_leaf_entries(&
|
|
2944
|
+
.collect_leaf_entries(&read, &base.root_id)
|
|
3111
2945
|
.await
|
|
3112
2946
|
.expect("base entries should collect");
|
|
3113
2947
|
for (key, value) in inserts {
|
|
@@ -3131,18 +2965,37 @@ mod tests {
|
|
|
3131
2965
|
assert_eq!(fast.root_id, canonical.root_id);
|
|
3132
2966
|
}
|
|
3133
2967
|
|
|
2968
|
+
#[test]
|
|
2969
|
+
fn leaf_chunk_boundaries_ignore_value_bytes() {
|
|
2970
|
+
let options = TrackedStateTreeOptions {
|
|
2971
|
+
target_chunk_bytes: 64,
|
|
2972
|
+
min_chunk_bytes: 32,
|
|
2973
|
+
max_chunk_bytes: 96,
|
|
2974
|
+
};
|
|
2975
|
+
let short_entries = encoded_entries_with_change_id("c");
|
|
2976
|
+
let large_entries = encoded_entries_with_change_id(&"c".repeat(4096));
|
|
2977
|
+
|
|
2978
|
+
assert_eq!(
|
|
2979
|
+
leaf_chunk_boundary_keys(chunk_leaf_entries(short_entries, &options)),
|
|
2980
|
+
leaf_chunk_boundary_keys(chunk_leaf_entries(large_entries, &options))
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
|
|
3134
2984
|
async fn apply_mutations_for_test(
|
|
3135
2985
|
tree: &TrackedStateTree,
|
|
3136
|
-
|
|
2986
|
+
storage: &StorageContext,
|
|
3137
2987
|
base_root: Option<&TrackedStateRootId>,
|
|
3138
2988
|
mutations: Vec<TrackedStateMutation>,
|
|
3139
2989
|
commit_id: Option<&str>,
|
|
3140
2990
|
) -> Result<TrackedStateApplyResult, LixError> {
|
|
3141
|
-
let
|
|
2991
|
+
let read = storage
|
|
2992
|
+
.begin_read(StorageReadOptions::default())
|
|
2993
|
+
.expect("read should open");
|
|
2994
|
+
let mut writes = storage.new_write_set();
|
|
3142
2995
|
let result = tree
|
|
3143
|
-
.apply_mutations(
|
|
2996
|
+
.apply_mutations(&read, &mut writes, base_root, mutations, commit_id)
|
|
3144
2997
|
.await?;
|
|
3145
|
-
|
|
2998
|
+
storage.commit_write_set(writes, StorageWriteOptions::default())?;
|
|
3146
2999
|
Ok(result)
|
|
3147
3000
|
}
|
|
3148
3001
|
|
|
@@ -3154,28 +3007,51 @@ mod tests {
|
|
|
3154
3007
|
mutation(&key, &value)
|
|
3155
3008
|
}
|
|
3156
3009
|
|
|
3157
|
-
fn
|
|
3010
|
+
fn encoded_entries_with_change_id(change_id: &str) -> Vec<EncodedLeafEntry> {
|
|
3011
|
+
(0..64)
|
|
3012
|
+
.map(|index| {
|
|
3013
|
+
let key = key("schema", None, &format!("entity-{index:03}"));
|
|
3014
|
+
EncodedLeafEntry {
|
|
3015
|
+
key: encode_key(&key),
|
|
3016
|
+
value: encode_value(&value(change_id, Some("{}"))),
|
|
3017
|
+
}
|
|
3018
|
+
})
|
|
3019
|
+
.collect()
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
fn leaf_chunk_boundary_keys(
|
|
3023
|
+
groups: Vec<LeafChunkAccumulator>,
|
|
3024
|
+
) -> Vec<(Vec<u8>, Vec<u8>, usize)> {
|
|
3025
|
+
groups
|
|
3026
|
+
.into_iter()
|
|
3027
|
+
.map(|group| {
|
|
3028
|
+
let first_key = group
|
|
3029
|
+
.entries
|
|
3030
|
+
.first()
|
|
3031
|
+
.map(|entry| entry.key.clone())
|
|
3032
|
+
.unwrap_or_default();
|
|
3033
|
+
let last_key = group
|
|
3034
|
+
.entries
|
|
3035
|
+
.last()
|
|
3036
|
+
.map(|entry| entry.key.clone())
|
|
3037
|
+
.unwrap_or_default();
|
|
3038
|
+
(first_key, last_key, group.entries.len())
|
|
3039
|
+
})
|
|
3040
|
+
.collect()
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
fn key(schema_key: &str, file_id: Option<&str>, entity_pk: &str) -> TrackedStateKey {
|
|
3158
3044
|
TrackedStateKey {
|
|
3159
3045
|
schema_key: schema_key.to_string(),
|
|
3160
3046
|
file_id: file_id.map(str::to_string),
|
|
3161
|
-
|
|
3047
|
+
entity_pk: EntityPk::single(entity_pk),
|
|
3162
3048
|
}
|
|
3163
3049
|
}
|
|
3164
3050
|
|
|
3165
3051
|
fn value(change_id: &str, snapshot_content: Option<&str>) -> TrackedStateIndexValue {
|
|
3166
|
-
let source_ordinal = match snapshot_content {
|
|
3167
|
-
Some("{\"v\":1}") => 1,
|
|
3168
|
-
Some("{\"v\":2}") => 2,
|
|
3169
|
-
Some(_) => 3,
|
|
3170
|
-
None => 0,
|
|
3171
|
-
};
|
|
3172
3052
|
TrackedStateIndexValue {
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
source_pack_id: 0,
|
|
3176
|
-
source_ordinal,
|
|
3177
|
-
change_id: change_id.to_string(),
|
|
3178
|
-
},
|
|
3053
|
+
change_id: change_id.to_string(),
|
|
3054
|
+
commit_id: "commit".to_string(),
|
|
3179
3055
|
deleted: snapshot_content.is_none(),
|
|
3180
3056
|
snapshot_ref: snapshot_content
|
|
3181
3057
|
.map(|content| crate::json_store::JsonRef::for_content(content.as_bytes())),
|