@lix-js/sdk 0.6.0-preview.4 → 0.6.0-preview.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +65 -64
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
  5. package/dist/engine-wasm/wasm/lix_engine.js +130 -118
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
  8. package/dist/generated/builtin-schemas.d.ts +69 -69
  9. package/dist/generated/builtin-schemas.js +94 -94
  10. package/dist/open-lix.d.ts +33 -26
  11. package/dist/open-lix.js +10 -10
  12. package/dist/sqlite/index.js +86 -30
  13. package/dist-engine-src/README.md +3 -3
  14. package/dist-engine-src/src/backend/capabilities.rs +67 -0
  15. package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
  16. package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
  17. package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
  18. package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
  19. package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
  20. package/dist-engine-src/src/backend/conformance/model.rs +28 -0
  21. package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
  22. package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
  23. package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
  24. package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
  25. package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
  26. package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
  27. package/dist-engine-src/src/backend/conformance/write.rs +16 -0
  28. package/dist-engine-src/src/backend/error.rs +94 -0
  29. package/dist-engine-src/src/backend/in_memory.rs +670 -0
  30. package/dist-engine-src/src/backend/mod.rs +36 -9
  31. package/dist-engine-src/src/backend/predicate.rs +80 -0
  32. package/dist-engine-src/src/backend/traits.rs +260 -0
  33. package/dist-engine-src/src/backend/types.rs +224 -81
  34. package/dist-engine-src/src/binary_cas/context.rs +8 -8
  35. package/dist-engine-src/src/binary_cas/kv.rs +234 -259
  36. package/dist-engine-src/src/{version → branch}/context.rs +12 -12
  37. package/dist-engine-src/src/branch/lifecycle.rs +221 -0
  38. package/dist-engine-src/src/branch/mod.rs +13 -0
  39. package/dist-engine-src/src/branch/refs.rs +321 -0
  40. package/dist-engine-src/src/branch/stage_rows.rs +67 -0
  41. package/dist-engine-src/src/branch/types.rs +21 -0
  42. package/dist-engine-src/src/catalog/context.rs +18 -18
  43. package/dist-engine-src/src/catalog/snapshot.rs +8 -8
  44. package/dist-engine-src/src/changelog/bench_support.rs +785 -0
  45. package/dist-engine-src/src/changelog/change.rs +1 -0
  46. package/dist-engine-src/src/changelog/codec.rs +497 -0
  47. package/dist-engine-src/src/changelog/commit.rs +1 -0
  48. package/dist-engine-src/src/changelog/context.rs +1614 -0
  49. package/dist-engine-src/src/changelog/mod.rs +29 -0
  50. package/dist-engine-src/src/changelog/store.rs +163 -0
  51. package/dist-engine-src/src/changelog/test_support.rs +54 -0
  52. package/dist-engine-src/src/changelog/types.rs +213 -0
  53. package/dist-engine-src/src/commit_graph/context.rs +317 -274
  54. package/dist-engine-src/src/commit_graph/mod.rs +2 -4
  55. package/dist-engine-src/src/commit_graph/types.rs +22 -42
  56. package/dist-engine-src/src/commit_graph/walker.rs +133 -103
  57. package/dist-engine-src/src/common/error.rs +52 -18
  58. package/dist-engine-src/src/common/identity.rs +2 -2
  59. package/dist-engine-src/src/common/mod.rs +1 -1
  60. package/dist-engine-src/src/domain.rs +42 -46
  61. package/dist-engine-src/src/engine.rs +74 -96
  62. package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
  63. package/dist-engine-src/src/functions/context.rs +56 -52
  64. package/dist-engine-src/src/functions/state.rs +51 -52
  65. package/dist-engine-src/src/init.rs +288 -154
  66. package/dist-engine-src/src/json_store/context.rs +15 -266
  67. package/dist-engine-src/src/json_store/mod.rs +26 -0
  68. package/dist-engine-src/src/json_store/store.rs +103 -718
  69. package/dist-engine-src/src/json_store/types.rs +4 -9
  70. package/dist-engine-src/src/lib.rs +49 -19
  71. package/dist-engine-src/src/live_state/context.rs +654 -790
  72. package/dist-engine-src/src/live_state/mod.rs +9 -3
  73. package/dist-engine-src/src/live_state/overlay.rs +4 -4
  74. package/dist-engine-src/src/live_state/types.rs +30 -21
  75. package/dist-engine-src/src/live_state/visibility.rs +514 -71
  76. package/dist-engine-src/src/plugin/install.rs +48 -48
  77. package/dist-engine-src/src/plugin/manifest.rs +7 -7
  78. package/dist-engine-src/src/plugin/materializer.rs +0 -275
  79. package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
  80. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
  81. package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
  82. package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
  85. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
  86. package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
  87. package/dist-engine-src/src/schema/compatibility.rs +11 -11
  88. package/dist-engine-src/src/schema/definition.json +2 -2
  89. package/dist-engine-src/src/schema/definition.rs +5 -5
  90. package/dist-engine-src/src/schema/key.rs +3 -3
  91. package/dist-engine-src/src/schema/mod.rs +1 -1
  92. package/dist-engine-src/src/schema/tests.rs +18 -18
  93. package/dist-engine-src/src/session/context.rs +803 -148
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +223 -83
  96. package/dist-engine-src/src/session/merge/analysis.rs +9 -3
  97. package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
  98. package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
  99. package/dist-engine-src/src/session/merge/mod.rs +5 -6
  100. package/dist-engine-src/src/session/merge/stats.rs +7 -11
  101. package/dist-engine-src/src/session/mod.rs +15 -12
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +495 -14
  104. package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
  105. package/dist-engine-src/src/sql2/bind/error.rs +5 -0
  106. package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
  107. package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
  108. package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
  109. package/dist-engine-src/src/sql2/bind/read.rs +65 -0
  110. package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
  111. package/dist-engine-src/src/sql2/bind/table.rs +273 -0
  112. package/dist-engine-src/src/sql2/bind/write.rs +86 -0
  113. package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
  114. package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
  115. package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
  116. package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
  117. package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
  118. package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
  119. package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
  120. package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
  121. package/dist-engine-src/src/sql2/context.rs +36 -30
  122. package/dist-engine-src/src/sql2/error.rs +1 -1
  123. package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
  124. package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
  125. package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
  126. package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
  127. package/dist-engine-src/src/sql2/exec/write.rs +661 -0
  128. package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
  129. package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
  130. package/dist-engine-src/src/sql2/history_projection.rs +8 -8
  131. package/dist-engine-src/src/sql2/history_route.rs +35 -31
  132. package/dist-engine-src/src/sql2/mod.rs +28 -23
  133. package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
  134. package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
  135. package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
  136. package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
  137. package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
  138. package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
  139. package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
  140. package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
  141. package/dist-engine-src/src/sql2/plan/write.rs +147 -0
  142. package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
  143. package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
  144. package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
  145. package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
  146. package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
  147. package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
  148. package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
  149. package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
  150. package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
  151. package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
  152. package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
  153. package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
  154. package/dist-engine-src/src/sql2/read_only.rs +2 -2
  155. package/dist-engine-src/src/sql2/session.rs +47 -96
  156. package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
  157. package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
  158. package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
  159. package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
  160. package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
  161. package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
  162. package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
  163. package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
  164. package/dist-engine-src/src/storage/conformance.rs +399 -0
  165. package/dist-engine-src/src/storage/context.rs +552 -288
  166. package/dist-engine-src/src/storage/mod.rs +48 -10
  167. package/dist-engine-src/src/storage/point.rs +440 -0
  168. package/dist-engine-src/src/storage/read_scope.rs +43 -64
  169. package/dist-engine-src/src/storage/reader.rs +867 -0
  170. package/dist-engine-src/src/storage/scan.rs +784 -0
  171. package/dist-engine-src/src/storage/spaces.rs +236 -0
  172. package/dist-engine-src/src/storage/stats.rs +80 -0
  173. package/dist-engine-src/src/storage/write_set.rs +962 -0
  174. package/dist-engine-src/src/storage_bench.rs +136 -4828
  175. package/dist-engine-src/src/test_support.rs +360 -138
  176. package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
  177. package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
  178. package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
  179. package/dist-engine-src/src/tracked_state/context.rs +1927 -993
  180. package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
  181. package/dist-engine-src/src/tracked_state/merge.rs +74 -88
  182. package/dist-engine-src/src/tracked_state/mod.rs +19 -16
  183. package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
  184. package/dist-engine-src/src/tracked_state/storage.rs +243 -191
  185. package/dist-engine-src/src/tracked_state/tree.rs +247 -371
  186. package/dist-engine-src/src/tracked_state/types.rs +49 -42
  187. package/dist-engine-src/src/transaction/bench_support.rs +407 -0
  188. package/dist-engine-src/src/transaction/commit.rs +821 -713
  189. package/dist-engine-src/src/transaction/context.rs +705 -600
  190. package/dist-engine-src/src/transaction/mod.rs +13 -2
  191. package/dist-engine-src/src/transaction/normalization.rs +63 -76
  192. package/dist-engine-src/src/transaction/prep.rs +13 -13
  193. package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
  194. package/dist-engine-src/src/transaction/staging.rs +228 -434
  195. package/dist-engine-src/src/transaction/types.rs +41 -98
  196. package/dist-engine-src/src/transaction/validation.rs +382 -446
  197. package/dist-engine-src/src/untracked_state/codec.rs +337 -29
  198. package/dist-engine-src/src/untracked_state/context.rs +7 -7
  199. package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
  200. package/dist-engine-src/src/untracked_state/mod.rs +1 -1
  201. package/dist-engine-src/src/untracked_state/storage.rs +659 -157
  202. package/dist-engine-src/src/untracked_state/types.rs +21 -21
  203. package/package.json +71 -68
  204. package/dist-engine-src/src/backend/kv.rs +0 -358
  205. package/dist-engine-src/src/backend/testing.rs +0 -658
  206. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  207. package/dist-engine-src/src/commit_store/context.rs +0 -944
  208. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  209. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  210. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  211. package/dist-engine-src/src/commit_store/types.rs +0 -215
  212. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  213. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  214. package/dist-engine-src/src/session/create_version.rs +0 -88
  215. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  216. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  217. package/dist-engine-src/src/session/switch_version.rs +0 -110
  218. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  219. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  220. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  221. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  222. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  223. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  224. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  225. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  226. package/dist-engine-src/src/storage/types.rs +0 -501
  227. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  228. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  229. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  230. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  231. package/dist-engine-src/src/version/mod.rs +0 -13
  232. package/dist-engine-src/src/version/refs.rs +0 -330
  233. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  234. package/dist-engine-src/src/version/types.rs +0 -21
@@ -0,0 +1,784 @@
1
+ use crate::backend::{
2
+ visit_range as backend_visit_range, BackendError, BackendRead, CoreProjection, Key, KeyRange,
3
+ KeyRef, Prefix, ProjectedValueRef, ReadEntry, ScanChunk, ScanOptions, ScanResult, ScanVisitor,
4
+ SpaceId,
5
+ };
6
+ use crate::storage::{
7
+ decode_logical_key_ref, StorageRead, StorageReadResult, StorageReadStats, StorageSpace,
8
+ };
9
+
10
+ #[derive(Clone, Debug, Default, PartialEq, Eq)]
11
+ pub struct ScanBuffer {
12
+ entries: Vec<ReadEntry>,
13
+ }
14
+
15
+ impl ScanBuffer {
16
+ pub fn new() -> Self {
17
+ Self::default()
18
+ }
19
+
20
+ pub fn with_capacity(capacity: usize) -> Self {
21
+ Self {
22
+ entries: Vec::with_capacity(capacity),
23
+ }
24
+ }
25
+
26
+ pub fn clear(&mut self) {
27
+ self.entries.clear();
28
+ }
29
+
30
+ pub fn entries(&self) -> &[ReadEntry] {
31
+ &self.entries
32
+ }
33
+
34
+ pub fn capacity(&self) -> usize {
35
+ self.entries.capacity()
36
+ }
37
+ }
38
+
39
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
40
+ pub struct ScanChunkRef<'a> {
41
+ pub entries: &'a [ReadEntry],
42
+ pub has_more: bool,
43
+ }
44
+
45
+ pub struct ScanCursor<'a, C> {
46
+ inner: &'a mut C,
47
+ kind: ScanKind,
48
+ projection: CoreProjection,
49
+ chunks_seen: u64,
50
+ }
51
+
52
+ #[derive(Clone, Debug, PartialEq, Eq)]
53
+ pub struct ScanPlan {
54
+ space: StorageSpace,
55
+ kind: ScanPlanKind,
56
+ }
57
+
58
+ #[derive(Clone, Debug, PartialEq, Eq)]
59
+ enum ScanPlanKind {
60
+ Range(KeyRange),
61
+ Prefix(Prefix),
62
+ }
63
+
64
+ impl ScanPlan {
65
+ pub fn range(space: StorageSpace, range: KeyRange) -> Self {
66
+ Self {
67
+ space,
68
+ kind: ScanPlanKind::Range(range),
69
+ }
70
+ }
71
+
72
+ pub fn prefix(space: StorageSpace, prefix: Prefix) -> Self {
73
+ Self {
74
+ space,
75
+ kind: ScanPlanKind::Prefix(prefix),
76
+ }
77
+ }
78
+
79
+ pub fn collect<R>(
80
+ &self,
81
+ read: &R,
82
+ opts: ScanOptions<'_>,
83
+ ) -> Result<StorageReadResult<ScanChunk>, BackendError>
84
+ where
85
+ R: StorageRead + ?Sized,
86
+ {
87
+ match &self.kind {
88
+ ScanPlanKind::Range(range) => {
89
+ scan_range_with_stats(read.backend_read(), self.space.id, range.clone(), opts)
90
+ }
91
+ ScanPlanKind::Prefix(prefix) => {
92
+ scan_prefix_with_stats(read.backend_read(), self.space.id, prefix.clone(), opts)
93
+ }
94
+ }
95
+ }
96
+
97
+ pub fn collect_into<'a, R>(
98
+ &self,
99
+ read: &R,
100
+ opts: ScanOptions<'_>,
101
+ buffer: &'a mut ScanBuffer,
102
+ ) -> Result<StorageReadResult<ScanChunkRef<'a>>, BackendError>
103
+ where
104
+ R: StorageRead + ?Sized,
105
+ {
106
+ let chunk = match &self.kind {
107
+ ScanPlanKind::Range(range) => scan_range_into(
108
+ read.backend_read(),
109
+ self.space.id,
110
+ range.clone(),
111
+ opts,
112
+ buffer,
113
+ )?,
114
+ ScanPlanKind::Prefix(prefix) => scan_prefix_into(
115
+ read.backend_read(),
116
+ self.space.id,
117
+ prefix.clone(),
118
+ opts,
119
+ buffer,
120
+ )?,
121
+ };
122
+ let backend_calls = u64::from(opts.limit_rows != 0);
123
+ let kind = match self.kind {
124
+ ScanPlanKind::Range(_) => ScanKind::Range,
125
+ ScanPlanKind::Prefix(_) => ScanKind::Prefix,
126
+ };
127
+ let mut stats = scan_trace_stats(
128
+ kind,
129
+ opts,
130
+ chunk.entries.len() as u64,
131
+ chunk.has_more,
132
+ backend_calls,
133
+ );
134
+ if matches!(kind, ScanKind::Prefix) {
135
+ stats.prefix_lowered = 1;
136
+ }
137
+ Ok(StorageReadResult::new(chunk, stats))
138
+ }
139
+
140
+ pub fn visit<R, V>(
141
+ &self,
142
+ read: &R,
143
+ opts: ScanOptions<'_>,
144
+ visitor: &mut V,
145
+ ) -> Result<StorageReadResult<ScanResult>, BackendError>
146
+ where
147
+ R: StorageRead + ?Sized,
148
+ V: ScanVisitor + ?Sized,
149
+ {
150
+ match &self.kind {
151
+ ScanPlanKind::Range(range) => visit_scan_range_with_stats(
152
+ read.backend_read(),
153
+ self.space.id,
154
+ range.clone(),
155
+ opts,
156
+ visitor,
157
+ ),
158
+ ScanPlanKind::Prefix(prefix) => visit_scan_prefix_with_stats(
159
+ read.backend_read(),
160
+ self.space.id,
161
+ prefix.clone(),
162
+ opts,
163
+ visitor,
164
+ ),
165
+ }
166
+ }
167
+
168
+ pub fn cursor<R, T, F>(&self, read: &R, opts: ScanOptions<'_>, f: F) -> Result<T, BackendError>
169
+ where
170
+ R: StorageRead + ?Sized,
171
+ F: FnOnce(
172
+ &mut ScanCursor<'_, <R::BackendRead as BackendRead>::RangeScan<'_>>,
173
+ ) -> Result<T, BackendError>,
174
+ {
175
+ match &self.kind {
176
+ ScanPlanKind::Range(range) => {
177
+ with_range_scan(read.backend_read(), self.space.id, range.clone(), opts, f)
178
+ }
179
+ ScanPlanKind::Prefix(prefix) => {
180
+ with_prefix_scan(read.backend_read(), self.space.id, prefix.clone(), opts, f)
181
+ }
182
+ }
183
+ }
184
+ }
185
+
186
+ impl<C> ScanCursor<'_, C>
187
+ where
188
+ C: crate::backend::BackendRangeScan,
189
+ {
190
+ pub fn visit_next(
191
+ &mut self,
192
+ limit_rows: usize,
193
+ visitor: &mut dyn ScanVisitor,
194
+ ) -> Result<ScanResult, BackendError> {
195
+ Ok(self.visit_next_with_stats(limit_rows, visitor)?.value)
196
+ }
197
+
198
+ pub fn visit_next_with_stats<V>(
199
+ &mut self,
200
+ limit_rows: usize,
201
+ visitor: &mut V,
202
+ ) -> Result<StorageReadResult<ScanResult>, BackendError>
203
+ where
204
+ V: ScanVisitor + ?Sized,
205
+ {
206
+ struct LogicalScanVisitor<'a, V: ?Sized> {
207
+ inner: &'a mut V,
208
+ }
209
+
210
+ impl<V> ScanVisitor for LogicalScanVisitor<'_, V>
211
+ where
212
+ V: ScanVisitor + ?Sized,
213
+ {
214
+ fn visit(
215
+ &mut self,
216
+ key: KeyRef<'_>,
217
+ value: ProjectedValueRef<'_>,
218
+ ) -> Result<(), BackendError> {
219
+ self.inner.visit(decode_logical_key_ref(key)?, value)
220
+ }
221
+ }
222
+
223
+ let result = self
224
+ .inner
225
+ .visit_next(limit_rows, &mut LogicalScanVisitor { inner: visitor })?;
226
+ let mut stats = scan_trace_stats(
227
+ self.kind,
228
+ ScanOptions {
229
+ projection: self.projection,
230
+ limit_rows,
231
+ resume_after: None,
232
+ },
233
+ result.emitted as u64,
234
+ result.has_more,
235
+ u64::from(limit_rows != 0),
236
+ );
237
+ stats.scan_resume_after = u64::from(self.chunks_seen > 0);
238
+ if matches!(self.kind, ScanKind::Prefix) {
239
+ stats.prefix_lowered = u64::from(self.chunks_seen == 0);
240
+ }
241
+ self.chunks_seen += u64::from(result.emitted > 0 || result.has_more);
242
+ Ok(StorageReadResult::new(result, stats))
243
+ }
244
+ }
245
+
246
+ #[derive(Clone, Debug, PartialEq, Eq)]
247
+ pub(crate) struct ScanResumeKey {
248
+ pub last_key: Option<Key>,
249
+ }
250
+
251
+ impl ScanResumeKey {
252
+ pub fn start() -> Self {
253
+ Self { last_key: None }
254
+ }
255
+
256
+ pub fn from_last_key(last_key: Key) -> Self {
257
+ Self {
258
+ last_key: Some(last_key),
259
+ }
260
+ }
261
+ }
262
+
263
+ pub(crate) fn scan_prefix<R>(
264
+ read: &R,
265
+ space: SpaceId,
266
+ prefix: Prefix,
267
+ opts: ScanOptions<'_>,
268
+ ) -> Result<ScanChunk, BackendError>
269
+ where
270
+ R: BackendRead,
271
+ {
272
+ Ok(scan_prefix_with_stats(read, space, prefix, opts)?.value)
273
+ }
274
+
275
+ pub(crate) fn with_range_scan<R, T, F>(
276
+ read: &R,
277
+ space: SpaceId,
278
+ range: KeyRange,
279
+ opts: ScanOptions<'_>,
280
+ f: F,
281
+ ) -> Result<T, BackendError>
282
+ where
283
+ R: BackendRead,
284
+ F: FnOnce(&mut ScanCursor<'_, R::RangeScan<'_>>) -> Result<T, BackendError>,
285
+ {
286
+ let storage_space = StorageSpace::new(space, "storage.scan");
287
+ let resume_after = opts.resume_after;
288
+ let physical_range = storage_space.encode_range(range, resume_after);
289
+ let physical_opts = ScanOptions {
290
+ resume_after: None,
291
+ ..opts
292
+ };
293
+ read.with_range_scan(physical_range, physical_opts, |backend_cursor| {
294
+ let mut cursor = ScanCursor {
295
+ inner: backend_cursor,
296
+ kind: ScanKind::Range,
297
+ projection: opts.projection,
298
+ chunks_seen: 0,
299
+ };
300
+ f(&mut cursor)
301
+ })
302
+ }
303
+
304
+ pub(crate) fn with_prefix_scan<R, T, F>(
305
+ read: &R,
306
+ space: SpaceId,
307
+ prefix: Prefix,
308
+ opts: ScanOptions<'_>,
309
+ f: F,
310
+ ) -> Result<T, BackendError>
311
+ where
312
+ R: BackendRead,
313
+ F: FnOnce(&mut ScanCursor<'_, R::RangeScan<'_>>) -> Result<T, BackendError>,
314
+ {
315
+ with_range_scan(read, space, prefix.to_range()?, opts, |cursor| {
316
+ cursor.kind = ScanKind::Prefix;
317
+ f(cursor)
318
+ })
319
+ }
320
+
321
+ pub(crate) fn scan_range<R>(
322
+ read: &R,
323
+ space: SpaceId,
324
+ range: KeyRange,
325
+ opts: ScanOptions<'_>,
326
+ ) -> Result<ScanChunk, BackendError>
327
+ where
328
+ R: BackendRead,
329
+ {
330
+ let mut buffer = ScanBuffer::with_capacity(opts.limit_rows);
331
+ let has_more = {
332
+ let chunk = scan_range_into(read, space, range, opts, &mut buffer)?;
333
+ chunk.has_more
334
+ };
335
+
336
+ Ok(ScanChunk {
337
+ entries: buffer.entries,
338
+ has_more,
339
+ })
340
+ }
341
+
342
+ pub(crate) fn scan_range_into<'a, R>(
343
+ read: &R,
344
+ space: SpaceId,
345
+ range: KeyRange,
346
+ opts: ScanOptions<'_>,
347
+ buffer: &'a mut ScanBuffer,
348
+ ) -> Result<ScanChunkRef<'a>, BackendError>
349
+ where
350
+ R: BackendRead,
351
+ {
352
+ buffer.clear();
353
+
354
+ if opts.limit_rows == 0 {
355
+ return Ok(ScanChunkRef {
356
+ entries: buffer.entries(),
357
+ has_more: false,
358
+ });
359
+ }
360
+
361
+ if buffer.entries.capacity() < opts.limit_rows {
362
+ buffer
363
+ .entries
364
+ .reserve(opts.limit_rows - buffer.entries.capacity());
365
+ }
366
+
367
+ let storage_space = StorageSpace::new(space, "storage.scan");
368
+ let resume_after = opts.resume_after;
369
+ let physical_range = storage_space.encode_range(range, resume_after);
370
+ let physical_opts = ScanOptions {
371
+ resume_after: None,
372
+ ..opts
373
+ };
374
+
375
+ let result = backend_visit_range(
376
+ read,
377
+ physical_range,
378
+ physical_opts,
379
+ &mut |key: KeyRef<'_>, value: ProjectedValueRef<'_>| {
380
+ let key = decode_logical_key_ref(key)?;
381
+ buffer.entries.push(ReadEntry {
382
+ key: key.to_owned_key(),
383
+ value: value.to_owned(),
384
+ });
385
+ Ok(())
386
+ },
387
+ )?;
388
+
389
+ Ok(ScanChunkRef {
390
+ entries: buffer.entries(),
391
+ has_more: result.has_more,
392
+ })
393
+ }
394
+
395
+ pub(crate) fn visit_scan_range<R, V>(
396
+ read: &R,
397
+ space: SpaceId,
398
+ range: KeyRange,
399
+ opts: ScanOptions<'_>,
400
+ visitor: &mut V,
401
+ ) -> Result<ScanResult, BackendError>
402
+ where
403
+ R: BackendRead,
404
+ V: ScanVisitor + ?Sized,
405
+ {
406
+ Ok(visit_scan_range_with_stats(read, space, range, opts, visitor)?.value)
407
+ }
408
+
409
+ pub(crate) fn visit_scan_range_with_stats<R, V>(
410
+ read: &R,
411
+ space: SpaceId,
412
+ range: KeyRange,
413
+ opts: ScanOptions<'_>,
414
+ visitor: &mut V,
415
+ ) -> Result<StorageReadResult<ScanResult>, BackendError>
416
+ where
417
+ R: BackendRead,
418
+ V: ScanVisitor + ?Sized,
419
+ {
420
+ if opts.limit_rows == 0 {
421
+ return Ok(StorageReadResult::new(
422
+ ScanResult::default(),
423
+ scan_trace_stats(ScanKind::Range, opts, 0, false, 0),
424
+ ));
425
+ }
426
+
427
+ let storage_space = StorageSpace::new(space, "storage.scan");
428
+ let resume_after = opts.resume_after;
429
+ let physical_range = storage_space.encode_range(range, resume_after);
430
+ let physical_opts = ScanOptions {
431
+ resume_after: None,
432
+ ..opts
433
+ };
434
+
435
+ struct LogicalScanVisitor<'a, V: ?Sized> {
436
+ inner: &'a mut V,
437
+ }
438
+
439
+ impl<V> ScanVisitor for LogicalScanVisitor<'_, V>
440
+ where
441
+ V: ScanVisitor + ?Sized,
442
+ {
443
+ fn visit(
444
+ &mut self,
445
+ key: KeyRef<'_>,
446
+ value: ProjectedValueRef<'_>,
447
+ ) -> Result<(), BackendError> {
448
+ self.inner.visit(decode_logical_key_ref(key)?, value)
449
+ }
450
+ }
451
+
452
+ let result = backend_visit_range(
453
+ read,
454
+ physical_range,
455
+ physical_opts,
456
+ &mut LogicalScanVisitor { inner: visitor },
457
+ )?;
458
+ let stats = scan_trace_stats(
459
+ ScanKind::Range,
460
+ opts,
461
+ result.emitted as u64,
462
+ result.has_more,
463
+ 1,
464
+ );
465
+ Ok(StorageReadResult::new(result, stats))
466
+ }
467
+
468
+ pub(crate) fn scan_range_with_stats<R>(
469
+ read: &R,
470
+ space: SpaceId,
471
+ range: KeyRange,
472
+ opts: ScanOptions<'_>,
473
+ ) -> Result<StorageReadResult<ScanChunk>, BackendError>
474
+ where
475
+ R: BackendRead,
476
+ {
477
+ let backend_calls = u64::from(opts.limit_rows != 0);
478
+ let chunk = scan_range(read, space, range, opts)?;
479
+ let mut stats = scan_trace_stats(
480
+ ScanKind::Range,
481
+ opts,
482
+ chunk.entries.len() as u64,
483
+ chunk.has_more,
484
+ backend_calls,
485
+ );
486
+ stats.prefix_lowered = 0;
487
+ Ok(StorageReadResult::new(chunk, stats))
488
+ }
489
+
490
+ pub(crate) fn scan_prefix_into<'a, R>(
491
+ read: &R,
492
+ space: SpaceId,
493
+ prefix: Prefix,
494
+ opts: ScanOptions<'_>,
495
+ buffer: &'a mut ScanBuffer,
496
+ ) -> Result<ScanChunkRef<'a>, BackendError>
497
+ where
498
+ R: BackendRead,
499
+ {
500
+ scan_range_into(read, space, prefix.to_range()?, opts, buffer)
501
+ }
502
+
503
+ pub(crate) fn visit_scan_prefix<R, V>(
504
+ read: &R,
505
+ space: SpaceId,
506
+ prefix: Prefix,
507
+ opts: ScanOptions<'_>,
508
+ visitor: &mut V,
509
+ ) -> Result<ScanResult, BackendError>
510
+ where
511
+ R: BackendRead,
512
+ V: ScanVisitor + ?Sized,
513
+ {
514
+ Ok(visit_scan_prefix_with_stats(read, space, prefix, opts, visitor)?.value)
515
+ }
516
+
517
+ pub(crate) fn visit_scan_prefix_with_stats<R, V>(
518
+ read: &R,
519
+ space: SpaceId,
520
+ prefix: Prefix,
521
+ opts: ScanOptions<'_>,
522
+ visitor: &mut V,
523
+ ) -> Result<StorageReadResult<ScanResult>, BackendError>
524
+ where
525
+ R: BackendRead,
526
+ V: ScanVisitor + ?Sized,
527
+ {
528
+ let mut result = visit_scan_range_with_stats(read, space, prefix.to_range()?, opts, visitor)?;
529
+ result.stats.range_scan_chunks = 0;
530
+ result.stats.prefix_scan_chunks = 1;
531
+ result.stats.prefix_lowered = 1;
532
+ Ok(result)
533
+ }
534
+
535
+ pub(crate) fn scan_prefix_with_stats<R>(
536
+ read: &R,
537
+ space: SpaceId,
538
+ prefix: Prefix,
539
+ opts: ScanOptions<'_>,
540
+ ) -> Result<StorageReadResult<ScanChunk>, BackendError>
541
+ where
542
+ R: BackendRead,
543
+ {
544
+ if opts.limit_rows == 0 {
545
+ let mut stats = scan_trace_stats(ScanKind::Prefix, opts, 0, false, 0);
546
+ stats.prefix_lowered = 1;
547
+ return Ok(StorageReadResult::new(
548
+ ScanChunk {
549
+ entries: Vec::new(),
550
+ has_more: false,
551
+ },
552
+ stats,
553
+ ));
554
+ }
555
+ let chunk = scan_range(read, space, prefix.to_range()?, opts)?;
556
+ let mut stats = scan_trace_stats(
557
+ ScanKind::Prefix,
558
+ opts,
559
+ chunk.entries.len() as u64,
560
+ chunk.has_more,
561
+ 1,
562
+ );
563
+ stats.prefix_lowered = 1;
564
+ Ok(StorageReadResult::new(chunk, stats))
565
+ }
566
+
567
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
568
+ enum ScanKind {
569
+ Range,
570
+ Prefix,
571
+ }
572
+
573
+ fn scan_trace_stats(
574
+ kind: ScanKind,
575
+ opts: ScanOptions<'_>,
576
+ emitted_rows: u64,
577
+ has_more: bool,
578
+ backend_calls: u64,
579
+ ) -> StorageReadStats {
580
+ let (range_scan_chunks, prefix_scan_chunks) = match kind {
581
+ ScanKind::Range => (1, 0),
582
+ ScanKind::Prefix => (0, 1),
583
+ };
584
+ let (scan_key_only_chunks, scan_full_value_chunks) = match opts.projection {
585
+ CoreProjection::KeyOnly => (1, 0),
586
+ CoreProjection::FullValue => (0, 1),
587
+ };
588
+ StorageReadStats {
589
+ requested_keys: 0,
590
+ unique_backend_keys: 0,
591
+ backend_calls,
592
+ prefix_lowered: 0,
593
+ range_scan_chunks,
594
+ prefix_scan_chunks,
595
+ scan_key_only_chunks,
596
+ scan_full_value_chunks,
597
+ scan_rows: emitted_rows,
598
+ scan_has_more: u64::from(has_more),
599
+ scan_resume_after: u64::from(opts.resume_after.is_some()),
600
+ scan_limit_rows_total: opts.limit_rows as u64,
601
+ scan_limit_rows_max: opts.limit_rows as u64,
602
+ }
603
+ }
604
+
605
+ #[cfg(test)]
606
+ mod tests {
607
+ use std::cell::RefCell;
608
+ use std::ops::Bound;
609
+
610
+ use bytes::Bytes;
611
+
612
+ use super::scan_prefix;
613
+ use crate::backend::{
614
+ BackendError, BackendRangeScan, BackendRead, BufferedRangeScan, GetOptions,
615
+ InMemoryBackend, Key, KeyRange, PointVisitor, Prefix, ProjectedValueRef, ReadOptions,
616
+ ScanOptions, ScanResult, ScanVisitor, SpaceId, StoredValue, WriteOptions,
617
+ };
618
+ use crate::storage::{ScanPlan, StorageContext, StorageSpace};
619
+
620
+ fn key(bytes: &'static str) -> Key {
621
+ Key(Bytes::from_static(bytes.as_bytes()))
622
+ }
623
+
624
+ fn key_bytes(bytes: &'static [u8]) -> Key {
625
+ Key(Bytes::from_static(bytes))
626
+ }
627
+
628
+ fn value(bytes: &'static str) -> StoredValue {
629
+ StoredValue {
630
+ bytes: Bytes::from_static(bytes.as_bytes()),
631
+ }
632
+ }
633
+
634
+ fn space(id: u32) -> StorageSpace {
635
+ match id {
636
+ 1 => StorageSpace::new(SpaceId(1), "test.space.one"),
637
+ _ => StorageSpace::new(SpaceId(id), "test.space.other"),
638
+ }
639
+ }
640
+
641
+ #[test]
642
+ fn prefix_scan_limit_zero_returns_empty_page() {
643
+ let storage = StorageContext::new(InMemoryBackend::new());
644
+ let mut writes = storage.new_write_set();
645
+ writes.put(space(1), key("aa"), value("AA"));
646
+ storage
647
+ .commit_write_set(writes, WriteOptions::default())
648
+ .expect("seed");
649
+
650
+ let read = storage
651
+ .begin_read(ReadOptions::default())
652
+ .expect("begin read");
653
+ let chunk = ScanPlan::prefix(
654
+ space(1),
655
+ Prefix {
656
+ bytes: Bytes::from_static(b"a"),
657
+ },
658
+ )
659
+ .collect(
660
+ &read,
661
+ ScanOptions {
662
+ limit_rows: 0,
663
+ ..ScanOptions::default()
664
+ },
665
+ )
666
+ .expect("prefix scan");
667
+
668
+ assert!(chunk.value.entries.is_empty());
669
+ assert!(!chunk.value.has_more);
670
+ }
671
+
672
+ #[test]
673
+ fn prefix_scan_lowers_empty_prefix_to_unbounded_upper_range() {
674
+ let read = CapturingRead::default();
675
+
676
+ scan_prefix(
677
+ &read,
678
+ SpaceId(1),
679
+ Prefix {
680
+ bytes: Bytes::new(),
681
+ },
682
+ ScanOptions::default(),
683
+ )
684
+ .expect("scan prefix");
685
+
686
+ assert_eq!(
687
+ read.take_range(),
688
+ KeyRange {
689
+ lower: Bound::Included(space(1).encode_key(&Key(Bytes::new()))),
690
+ upper: Bound::Excluded(space(2).encode_key(&Key(Bytes::new()))),
691
+ }
692
+ );
693
+ }
694
+
695
+ #[test]
696
+ fn prefix_scan_lowers_ff_prefix_to_unbounded_upper_range() {
697
+ let read = CapturingRead::default();
698
+
699
+ scan_prefix(
700
+ &read,
701
+ SpaceId(1),
702
+ Prefix {
703
+ bytes: Bytes::from_static(&[0xff]),
704
+ },
705
+ ScanOptions::default(),
706
+ )
707
+ .expect("scan prefix");
708
+
709
+ assert_eq!(
710
+ read.take_range(),
711
+ KeyRange {
712
+ lower: Bound::Included(space(1).encode_key(&key_bytes(&[0xff]))),
713
+ upper: Bound::Excluded(space(2).encode_key(&Key(Bytes::new()))),
714
+ }
715
+ );
716
+ }
717
+
718
+ #[test]
719
+ fn prefix_scan_lowers_trailing_ff_prefix_to_next_lexicographic_bound() {
720
+ let read = CapturingRead::default();
721
+
722
+ scan_prefix(
723
+ &read,
724
+ SpaceId(1),
725
+ Prefix {
726
+ bytes: Bytes::from_static(&[0x00, 0xff]),
727
+ },
728
+ ScanOptions::default(),
729
+ )
730
+ .expect("scan prefix");
731
+
732
+ assert_eq!(
733
+ read.take_range(),
734
+ KeyRange {
735
+ lower: Bound::Included(space(1).encode_key(&key_bytes(&[0x00, 0xff]))),
736
+ upper: Bound::Excluded(space(1).encode_key(&key_bytes(&[0x01]))),
737
+ }
738
+ );
739
+ }
740
+
741
+ #[derive(Default)]
742
+ struct CapturingRead {
743
+ range: RefCell<Option<KeyRange>>,
744
+ }
745
+
746
+ impl CapturingRead {
747
+ fn take_range(&self) -> KeyRange {
748
+ self.range
749
+ .borrow_mut()
750
+ .take()
751
+ .expect("scan_range should have been called")
752
+ }
753
+ }
754
+
755
+ impl BackendRead for CapturingRead {
756
+ type RangeScan<'a> = BufferedRangeScan;
757
+
758
+ fn visit_keys<V>(
759
+ &self,
760
+ _keys: &[Key],
761
+ _opts: GetOptions<'_>,
762
+ _visitor: &mut V,
763
+ ) -> Result<(), BackendError>
764
+ where
765
+ V: PointVisitor + ?Sized,
766
+ {
767
+ unimplemented!("not used by prefix lowering tests")
768
+ }
769
+
770
+ fn with_range_scan<T, F>(
771
+ &self,
772
+ range: KeyRange,
773
+ _opts: ScanOptions<'_>,
774
+ f: F,
775
+ ) -> Result<T, BackendError>
776
+ where
777
+ F: FnOnce(&mut Self::RangeScan<'_>) -> Result<T, BackendError>,
778
+ {
779
+ self.range.replace(Some(range));
780
+ let mut cursor = BufferedRangeScan::default();
781
+ f(&mut cursor)
782
+ }
783
+ }
784
+ }