@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,670 @@
1
+ use std::collections::btree_map;
2
+ use std::collections::{BTreeMap, BTreeSet};
3
+ use std::iter::Peekable;
4
+ use std::ops::Bound;
5
+ use std::sync::{Arc, Mutex};
6
+
7
+ use bytes::Bytes;
8
+
9
+ use crate::backend::conformance::{BackendFactory, BackendFixture, BackendTestConfig};
10
+ use crate::backend::{
11
+ Backend, BackendCapabilities, BackendError, BackendRangeScan, BackendRead, BackendWrite,
12
+ BufferedRangeScan, CommitResult, CoreProjection, DurableWriteLock, GetOptions, Key, KeyRange,
13
+ KeyRef, PointVisitor, ProjectedValueRef, PutBatch, ReadOptions, ScanOptions, ScanResult,
14
+ ScanVisitor, StoredValue, WriteConcurrency, WriteOptions, WriteStats,
15
+ };
16
+
17
+ type InMemoryMap = BTreeMap<Key, Bytes>;
18
+
19
+ #[derive(Clone, Debug, Default)]
20
+ enum EntriesState {
21
+ #[default]
22
+ Empty,
23
+ Flat(InMemoryMap),
24
+ Layered {
25
+ base: Arc<EntriesState>,
26
+ puts: InMemoryMap,
27
+ deletes: BTreeSet<Key>,
28
+ },
29
+ }
30
+
31
+ #[derive(Clone, Debug, Default)]
32
+ pub struct InMemoryBackend {
33
+ entries: Arc<Mutex<Arc<EntriesState>>>,
34
+ durable_write_lock: DurableWriteLock,
35
+ }
36
+
37
+ #[derive(Clone, Debug, Default)]
38
+ pub struct InMemoryBackendFactory;
39
+
40
+ #[derive(Clone, Debug, Default)]
41
+ pub struct InMemoryBackendFixture {
42
+ entries: Arc<Mutex<Arc<EntriesState>>>,
43
+ durable_write_lock: DurableWriteLock,
44
+ }
45
+
46
+ #[derive(Clone)]
47
+ pub struct InMemoryRead {
48
+ entries: Arc<EntriesState>,
49
+ }
50
+
51
+ pub enum InMemoryRangeScan<'a> {
52
+ Flat {
53
+ iter: Peekable<btree_map::Range<'a, Key, Bytes>>,
54
+ projection: CoreProjection,
55
+ },
56
+ Buffered(BufferedRangeScan),
57
+ }
58
+
59
+ pub type InMemoryScanVisitResult = ScanResult;
60
+
61
+ pub struct InMemoryWrite {
62
+ parent: Arc<Mutex<Arc<EntriesState>>>,
63
+ base: Arc<EntriesState>,
64
+ overlay: EntriesOverlay,
65
+ stats: WriteStats,
66
+ }
67
+
68
+ #[derive(Debug, Default)]
69
+ struct EntriesOverlay {
70
+ puts: InMemoryMap,
71
+ deletes: BTreeSet<Key>,
72
+ }
73
+
74
+ impl InMemoryBackend {
75
+ pub fn new() -> Self {
76
+ Self::default()
77
+ }
78
+
79
+ #[cfg(feature = "storage-benches")]
80
+ pub fn fork_snapshot(&self) -> Result<Self, BackendError> {
81
+ Ok(Self {
82
+ entries: Arc::new(Mutex::new(self.snapshot()?)),
83
+ durable_write_lock: DurableWriteLock::new(),
84
+ })
85
+ }
86
+
87
+ fn snapshot(&self) -> Result<Arc<EntriesState>, BackendError> {
88
+ self.entries
89
+ .lock()
90
+ .map_err(|_| BackendError::Io("in-memory backend lock poisoned".to_string()))
91
+ .map(|entries| Arc::clone(&entries))
92
+ }
93
+ }
94
+
95
+ impl BackendFactory for InMemoryBackendFactory {
96
+ type Backend = InMemoryBackend;
97
+ type Fixture = InMemoryBackendFixture;
98
+
99
+ fn create_fixture(&self) -> Self::Fixture {
100
+ InMemoryBackendFixture::default()
101
+ }
102
+
103
+ fn config(&self) -> BackendTestConfig {
104
+ BackendTestConfig {
105
+ ephemeral: true,
106
+ supports_concurrent_writers: false,
107
+ ..BackendTestConfig::default()
108
+ }
109
+ }
110
+ }
111
+
112
+ impl BackendFixture for InMemoryBackendFixture {
113
+ type Backend = InMemoryBackend;
114
+
115
+ fn open(&self) -> Self::Backend {
116
+ InMemoryBackend {
117
+ entries: Arc::clone(&self.entries),
118
+ durable_write_lock: self.durable_write_lock.clone(),
119
+ }
120
+ }
121
+ }
122
+
123
+ impl Backend for InMemoryBackend {
124
+ type Read<'a>
125
+ = InMemoryRead
126
+ where
127
+ Self: 'a;
128
+
129
+ type Write<'a>
130
+ = InMemoryWrite
131
+ where
132
+ Self: 'a;
133
+
134
+ fn capabilities(&self) -> BackendCapabilities {
135
+ BackendCapabilities::v0(WriteConcurrency::SingleWriter)
136
+ }
137
+
138
+ fn begin_read(&self, _opts: ReadOptions) -> Result<Self::Read<'_>, BackendError> {
139
+ Ok(InMemoryRead {
140
+ entries: self.snapshot()?,
141
+ })
142
+ }
143
+
144
+ fn begin_write(&self, _opts: WriteOptions) -> Result<Self::Write<'_>, BackendError> {
145
+ Ok(InMemoryWrite {
146
+ parent: Arc::clone(&self.entries),
147
+ base: self.snapshot()?,
148
+ overlay: EntriesOverlay::default(),
149
+ stats: WriteStats::default(),
150
+ })
151
+ }
152
+
153
+ fn durable_write_lock(&self) -> DurableWriteLock {
154
+ self.durable_write_lock.clone()
155
+ }
156
+ }
157
+
158
+ impl BackendRead for InMemoryRead {
159
+ type RangeScan<'a> = InMemoryRangeScan<'a>;
160
+
161
+ fn visit_keys<V>(
162
+ &self,
163
+ keys: &[Key],
164
+ opts: GetOptions<'_>,
165
+ visitor: &mut V,
166
+ ) -> Result<(), BackendError>
167
+ where
168
+ V: PointVisitor + ?Sized,
169
+ {
170
+ match self.entries.as_ref() {
171
+ EntriesState::Flat(entries) => {
172
+ for (index, key) in keys.iter().enumerate() {
173
+ let value = entries
174
+ .get(key)
175
+ .map(|value| project_value_ref(value, opts.projection));
176
+ visitor.visit(index, key, value)?;
177
+ }
178
+ }
179
+ entries => {
180
+ for (index, key) in keys.iter().enumerate() {
181
+ let value = entries
182
+ .get(key)
183
+ .map(|value| project_value_ref(value, opts.projection));
184
+ visitor.visit(index, key, value)?;
185
+ }
186
+ }
187
+ }
188
+ Ok(())
189
+ }
190
+
191
+ fn with_range_scan<T, F>(
192
+ &self,
193
+ range: KeyRange,
194
+ opts: ScanOptions<'_>,
195
+ f: F,
196
+ ) -> Result<T, BackendError>
197
+ where
198
+ F: FnOnce(&mut Self::RangeScan<'_>) -> Result<T, BackendError>,
199
+ {
200
+ if opts.limit_rows == 0 {
201
+ let mut cursor = InMemoryRangeScan::Buffered(BufferedRangeScan::default());
202
+ return f(&mut cursor);
203
+ }
204
+
205
+ let lower = lower_bound(&range, opts.resume_after);
206
+ let upper = upper_bound(&range);
207
+ if bounds_are_empty(&lower, &upper) {
208
+ let mut cursor = InMemoryRangeScan::Buffered(BufferedRangeScan::default());
209
+ return f(&mut cursor);
210
+ }
211
+
212
+ match self.entries.as_ref() {
213
+ EntriesState::Flat(entries) => {
214
+ let mut cursor = InMemoryRangeScan::Flat {
215
+ iter: entries.range((lower, upper)).peekable(),
216
+ projection: opts.projection,
217
+ };
218
+ f(&mut cursor)
219
+ }
220
+ entries => {
221
+ let mut rows = Vec::new();
222
+ visit_range(
223
+ entries,
224
+ range,
225
+ ScanOptions {
226
+ limit_rows: usize::MAX,
227
+ ..opts
228
+ },
229
+ &mut |key: KeyRef<'_>, value: ProjectedValueRef<'_>| {
230
+ rows.push(crate::backend::ReadEntry {
231
+ key: key.to_owned_key(),
232
+ value: value.to_owned(),
233
+ });
234
+ Ok(())
235
+ },
236
+ )?;
237
+ let mut cursor = InMemoryRangeScan::Buffered(BufferedRangeScan::new(rows));
238
+ f(&mut cursor)
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ impl BackendRangeScan for InMemoryRangeScan<'_> {
245
+ fn visit_next<V>(
246
+ &mut self,
247
+ limit_rows: usize,
248
+ visitor: &mut V,
249
+ ) -> Result<ScanResult, BackendError>
250
+ where
251
+ V: ScanVisitor + ?Sized,
252
+ {
253
+ match self {
254
+ InMemoryRangeScan::Buffered(cursor) => cursor.visit_next(limit_rows, visitor),
255
+ InMemoryRangeScan::Flat { iter, projection } => {
256
+ if limit_rows == 0 {
257
+ return Ok(ScanResult {
258
+ emitted: 0,
259
+ has_more: iter.peek().is_some(),
260
+ });
261
+ }
262
+
263
+ let mut emitted = 0;
264
+ while emitted < limit_rows {
265
+ let Some((key, value)) = iter.next() else {
266
+ return Ok(ScanResult {
267
+ emitted,
268
+ has_more: false,
269
+ });
270
+ };
271
+ visitor.visit(key.as_ref(), project_value_ref(value, *projection))?;
272
+ emitted += 1;
273
+ }
274
+
275
+ Ok(ScanResult {
276
+ emitted,
277
+ has_more: iter.peek().is_some(),
278
+ })
279
+ }
280
+ }
281
+ }
282
+ }
283
+
284
+ impl InMemoryRead {
285
+ pub fn visit_scan_range<F>(
286
+ &self,
287
+ range: KeyRange,
288
+ opts: ScanOptions<'_>,
289
+ mut visitor: F,
290
+ ) -> Result<InMemoryScanVisitResult, BackendError>
291
+ where
292
+ F: FnMut(KeyRef<'_>, Option<&[u8]>),
293
+ {
294
+ let mut visitor = |key: KeyRef<'_>, value: ProjectedValueRef<'_>| {
295
+ let value = match value {
296
+ ProjectedValueRef::KeyOnly => None,
297
+ ProjectedValueRef::FullValue(value) => Some(value),
298
+ };
299
+ visitor(key, value);
300
+ Ok(())
301
+ };
302
+ visit_range(&self.entries, range, opts, &mut visitor)
303
+ }
304
+ }
305
+
306
+ impl BackendWrite for InMemoryWrite {
307
+ fn put_many(&mut self, entries: PutBatch) -> Result<(), BackendError> {
308
+ for entry in entries.entries {
309
+ let value = stored_value_bytes(entry.value);
310
+ self.stats.put_entries += 1;
311
+ self.stats.written_bytes += value.len() as u64;
312
+ if !self.overlay.deletes.is_empty() {
313
+ self.overlay.deletes.remove(&entry.key);
314
+ }
315
+ self.overlay.puts.insert(entry.key, value);
316
+ }
317
+ self.stats.backend_calls += 1;
318
+ Ok(())
319
+ }
320
+
321
+ fn delete_many(&mut self, keys: &[Key]) -> Result<(), BackendError> {
322
+ for key in keys {
323
+ if !self.overlay.puts.is_empty() {
324
+ self.overlay.puts.remove(key);
325
+ }
326
+ self.overlay.deletes.insert(key.clone());
327
+ }
328
+ self.stats.deleted_entries += keys.len() as u64;
329
+ self.stats.backend_calls += 1;
330
+ Ok(())
331
+ }
332
+
333
+ fn delete_range(&mut self, range: KeyRange) -> Result<(), BackendError> {
334
+ let mut base_keys = Vec::new();
335
+ visit_range(
336
+ &self.base,
337
+ range.clone(),
338
+ ScanOptions {
339
+ limit_rows: usize::MAX,
340
+ projection: CoreProjection::KeyOnly,
341
+ resume_after: None,
342
+ },
343
+ &mut |key: KeyRef<'_>, _value: ProjectedValueRef<'_>| {
344
+ base_keys.push(key.to_owned_key());
345
+ Ok(())
346
+ },
347
+ )?;
348
+
349
+ let overlay_puts_before = self.overlay.puts.len();
350
+ self.overlay
351
+ .puts
352
+ .retain(|key, _value| !range_contains(&range, key));
353
+ let removed_overlay_puts = overlay_puts_before - self.overlay.puts.len();
354
+
355
+ for key in &base_keys {
356
+ self.overlay.deletes.insert(key.clone());
357
+ }
358
+
359
+ self.stats.deleted_entries += (base_keys.len() + removed_overlay_puts) as u64;
360
+ self.stats.deleted_ranges += 1;
361
+ self.stats.backend_calls += 1;
362
+ Ok(())
363
+ }
364
+
365
+ fn commit(self) -> Result<CommitResult, BackendError> {
366
+ let mut parent = self
367
+ .parent
368
+ .lock()
369
+ .map_err(|_| BackendError::Io("in-memory backend lock poisoned".to_string()))?;
370
+ let base = if Arc::ptr_eq(&parent, &self.base) {
371
+ self.base
372
+ } else {
373
+ Arc::clone(&parent)
374
+ };
375
+ let entries = if self.overlay.puts.is_empty() && self.overlay.deletes.is_empty() {
376
+ base
377
+ } else if matches!(base.as_ref(), EntriesState::Empty) && self.overlay.deletes.is_empty() {
378
+ Arc::new(EntriesState::Flat(self.overlay.puts))
379
+ } else {
380
+ Arc::new(EntriesState::Layered {
381
+ base,
382
+ puts: self.overlay.puts,
383
+ deletes: self.overlay.deletes,
384
+ })
385
+ };
386
+
387
+ *parent = entries;
388
+ Ok(CommitResult {
389
+ commit_id: None,
390
+ stats: self.stats,
391
+ })
392
+ }
393
+
394
+ fn rollback(self) -> Result<(), BackendError> {
395
+ Ok(())
396
+ }
397
+ }
398
+
399
+ impl EntriesState {
400
+ fn get(&self, key: &Key) -> Option<&Bytes> {
401
+ match self {
402
+ EntriesState::Empty => None,
403
+ EntriesState::Flat(entries) => entries.get(key),
404
+ EntriesState::Layered {
405
+ base,
406
+ puts,
407
+ deletes,
408
+ } => {
409
+ if let Some(value) = puts.get(key) {
410
+ Some(value)
411
+ } else if deletes.contains(key) {
412
+ None
413
+ } else {
414
+ base.get(key)
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+
421
+ fn visit_range<V>(
422
+ entries: &EntriesState,
423
+ range: KeyRange,
424
+ opts: ScanOptions<'_>,
425
+ visitor: &mut V,
426
+ ) -> Result<ScanResult, BackendError>
427
+ where
428
+ V: ScanVisitor + ?Sized,
429
+ {
430
+ if opts.limit_rows == 0 {
431
+ return Ok(ScanResult::default());
432
+ }
433
+
434
+ let lower = lower_bound(&range, opts.resume_after);
435
+ let upper = upper_bound(&range);
436
+ if bounds_are_empty(&lower, &upper) {
437
+ return Ok(ScanResult::default());
438
+ }
439
+
440
+ visit_entries_range(entries, lower, upper, opts, visitor)
441
+ }
442
+
443
+ fn visit_entries_range<V>(
444
+ state: &EntriesState,
445
+ lower: Bound<&Key>,
446
+ upper: Bound<&Key>,
447
+ opts: ScanOptions<'_>,
448
+ visitor: &mut V,
449
+ ) -> Result<ScanResult, BackendError>
450
+ where
451
+ V: ScanVisitor + ?Sized,
452
+ {
453
+ match state {
454
+ EntriesState::Empty => Ok(ScanResult::default()),
455
+ EntriesState::Flat(entries) => visit_flat_range(entries, lower, upper, opts, visitor),
456
+ EntriesState::Layered {
457
+ base,
458
+ puts,
459
+ deletes,
460
+ } if !range_has_entries(puts, &lower, &upper)
461
+ && !range_has_keys(deletes, &lower, &upper) =>
462
+ {
463
+ visit_entries_range(base, lower, upper, opts, visitor)
464
+ }
465
+ EntriesState::Layered { .. } => {
466
+ let mut rows = BTreeMap::<&Key, Option<&Bytes>>::new();
467
+ collect_range(state, &lower, &upper, &mut rows);
468
+
469
+ match opts.projection {
470
+ CoreProjection::KeyOnly => visit_rows(rows, opts.limit_rows, visitor, |_, _| {
471
+ ProjectedValueRef::KeyOnly
472
+ }),
473
+ CoreProjection::FullValue => {
474
+ visit_rows(rows, opts.limit_rows, visitor, |_, value| {
475
+ ProjectedValueRef::FullValue(value.as_ref())
476
+ })
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ fn range_has_entries(entries: &InMemoryMap, lower: &Bound<&Key>, upper: &Bound<&Key>) -> bool {
484
+ entries.range((*lower, *upper)).next().is_some()
485
+ }
486
+
487
+ fn range_has_keys(keys: &BTreeSet<Key>, lower: &Bound<&Key>, upper: &Bound<&Key>) -> bool {
488
+ keys.range((*lower, *upper)).next().is_some()
489
+ }
490
+
491
+ fn visit_flat_range<V>(
492
+ entries: &InMemoryMap,
493
+ lower: Bound<&Key>,
494
+ upper: Bound<&Key>,
495
+ opts: ScanOptions<'_>,
496
+ visitor: &mut V,
497
+ ) -> Result<ScanResult, BackendError>
498
+ where
499
+ V: ScanVisitor + ?Sized,
500
+ {
501
+ let mut emitted = 0;
502
+ let mut has_more = false;
503
+
504
+ match opts.projection {
505
+ CoreProjection::KeyOnly => {
506
+ for (key, _) in entries.range((lower, upper)) {
507
+ if emitted == opts.limit_rows {
508
+ has_more = true;
509
+ break;
510
+ }
511
+ visitor.visit(key.as_ref(), ProjectedValueRef::KeyOnly)?;
512
+ emitted += 1;
513
+ }
514
+ }
515
+ CoreProjection::FullValue => {
516
+ for (key, value) in entries.range((lower, upper)) {
517
+ if emitted == opts.limit_rows {
518
+ has_more = true;
519
+ break;
520
+ }
521
+ visitor.visit(key.as_ref(), ProjectedValueRef::FullValue(value.as_ref()))?;
522
+ emitted += 1;
523
+ }
524
+ }
525
+ }
526
+
527
+ Ok(ScanResult { emitted, has_more })
528
+ }
529
+
530
+ fn collect_range<'a>(
531
+ state: &'a EntriesState,
532
+ lower: &Bound<&'a Key>,
533
+ upper: &Bound<&'a Key>,
534
+ rows: &mut BTreeMap<&'a Key, Option<&'a Bytes>>,
535
+ ) {
536
+ match state {
537
+ EntriesState::Empty => {}
538
+ EntriesState::Flat(entries) => {
539
+ for (key, value) in entries.range((*lower, *upper)) {
540
+ rows.entry(key).or_insert(Some(value));
541
+ }
542
+ }
543
+ EntriesState::Layered {
544
+ base,
545
+ puts,
546
+ deletes,
547
+ } => {
548
+ collect_range(base, lower, upper, rows);
549
+ for delete in deletes.range((*lower, *upper)) {
550
+ rows.insert(delete, None);
551
+ }
552
+ for (key, value) in puts.range((*lower, *upper)) {
553
+ rows.insert(key, Some(value));
554
+ }
555
+ }
556
+ }
557
+ }
558
+
559
+ fn visit_rows<'a, V, F>(
560
+ rows: BTreeMap<&'a Key, Option<&'a Bytes>>,
561
+ limit_rows: usize,
562
+ visitor: &mut V,
563
+ project: F,
564
+ ) -> Result<ScanResult, BackendError>
565
+ where
566
+ V: ScanVisitor + ?Sized,
567
+ F: Fn(&'a Key, &'a Bytes) -> ProjectedValueRef<'a>,
568
+ {
569
+ let mut emitted = 0;
570
+ let mut has_more = false;
571
+ for (key, value) in rows {
572
+ let Some(value) = value else {
573
+ continue;
574
+ };
575
+ if emitted == limit_rows {
576
+ has_more = true;
577
+ break;
578
+ }
579
+ visitor.visit(key.as_ref(), project(key, value))?;
580
+ emitted += 1;
581
+ }
582
+ Ok(ScanResult { emitted, has_more })
583
+ }
584
+
585
+ fn lower_bound<'a>(range: &'a KeyRange, resume_after: Option<&'a Key>) -> Bound<&'a Key> {
586
+ let range_lower = match &range.lower {
587
+ Bound::Included(key) => Some((key, true)),
588
+ Bound::Excluded(key) => Some((key, false)),
589
+ Bound::Unbounded => None,
590
+ };
591
+
592
+ match (range_lower, resume_after) {
593
+ (Some((lower, lower_inclusive)), Some(resume_after)) => {
594
+ if resume_after >= lower {
595
+ Bound::Excluded(resume_after)
596
+ } else if lower_inclusive {
597
+ Bound::Included(lower)
598
+ } else {
599
+ Bound::Excluded(lower)
600
+ }
601
+ }
602
+ (Some((lower, true)), None) => Bound::Included(lower),
603
+ (Some((lower, false)), None) => Bound::Excluded(lower),
604
+ (None, Some(resume_after)) => Bound::Excluded(resume_after),
605
+ (None, None) => Bound::Unbounded,
606
+ }
607
+ }
608
+
609
+ fn upper_bound(range: &KeyRange) -> Bound<&Key> {
610
+ match &range.upper {
611
+ Bound::Included(key) => Bound::Included(key),
612
+ Bound::Excluded(key) => Bound::Excluded(key),
613
+ Bound::Unbounded => Bound::Unbounded,
614
+ }
615
+ }
616
+
617
+ fn bounds_are_empty(lower: &Bound<&Key>, upper: &Bound<&Key>) -> bool {
618
+ match (lower, upper) {
619
+ (_, Bound::Unbounded) | (Bound::Unbounded, _) => false,
620
+ (Bound::Included(lower), Bound::Included(upper)) => lower > upper,
621
+ (Bound::Included(lower), Bound::Excluded(upper))
622
+ | (Bound::Excluded(lower), Bound::Included(upper))
623
+ | (Bound::Excluded(lower), Bound::Excluded(upper)) => lower >= upper,
624
+ }
625
+ }
626
+
627
+ fn range_contains(range: &KeyRange, key: &Key) -> bool {
628
+ let lower_matches = match &range.lower {
629
+ Bound::Included(lower) => key >= lower,
630
+ Bound::Excluded(lower) => key > lower,
631
+ Bound::Unbounded => true,
632
+ };
633
+ let upper_matches = match &range.upper {
634
+ Bound::Included(upper) => key <= upper,
635
+ Bound::Excluded(upper) => key < upper,
636
+ Bound::Unbounded => true,
637
+ };
638
+ lower_matches && upper_matches
639
+ }
640
+
641
+ fn project_value_ref(value: &Bytes, projection: CoreProjection) -> ProjectedValueRef<'_> {
642
+ match projection {
643
+ CoreProjection::KeyOnly => ProjectedValueRef::KeyOnly,
644
+ CoreProjection::FullValue => ProjectedValueRef::FullValue(value.as_ref()),
645
+ }
646
+ }
647
+
648
+ fn stored_value_bytes(value: StoredValue) -> Bytes {
649
+ value.bytes
650
+ }
651
+
652
+ #[cfg(test)]
653
+ mod tests {
654
+ use crate::backend::conformance::{run_backend_conformance, ConformanceStatus};
655
+
656
+ #[test]
657
+ fn in_memory_backend_passes_backend_conformance() {
658
+ let report = run_backend_conformance(&crate::backend::InMemoryBackendFactory);
659
+
660
+ report.assert_no_failures();
661
+
662
+ assert!(
663
+ report
664
+ .tests
665
+ .iter()
666
+ .any(|test| matches!(test.status, ConformanceStatus::Passed)),
667
+ "expected at least one conformance test to run"
668
+ );
669
+ }
670
+ }
@@ -1,12 +1,39 @@
1
- mod kv;
2
- #[cfg(test)]
3
- pub(crate) mod testing;
1
+ //! Primary backend API.
2
+ //!
3
+ //! This module is intentionally isolated from the current `backend` module
4
+ //! while the ordered byte-key API and its conformance suite settle.
5
+
6
+ mod capabilities;
7
+ pub mod conformance;
8
+ mod error;
9
+ mod in_memory;
10
+ mod predicate;
11
+ mod traits;
4
12
  mod types;
5
13
 
6
- pub use kv::{
7
- BackendKvEntryPage, BackendKvExistsBatch, BackendKvExistsGroup, BackendKvGetGroup,
8
- BackendKvGetRequest, BackendKvKeyPage, BackendKvScanRange, BackendKvScanRequest,
9
- BackendKvValueBatch, BackendKvValueGroup, BackendKvValuePage, BackendKvWriteBatch,
10
- BackendKvWriteGroup, BackendKvWriteStats, BytePage, BytePageBuilder,
14
+ pub use capabilities::{
15
+ BackendCapabilities, BackendProfile, ProjectionCapabilities, PushdownCapabilities,
16
+ ScanCapabilities, WriteCapabilities, WriteConcurrency,
17
+ };
18
+ pub use error::{
19
+ BackendError, Capability, Precondition, PreconditionFailure, PreconditionItemSupport,
20
+ PreconditionSupportReport,
21
+ };
22
+ pub use in_memory::{
23
+ InMemoryBackend, InMemoryBackendFactory, InMemoryBackendFixture, InMemoryRangeScan,
24
+ InMemoryRead, InMemoryScanVisitResult, InMemoryWrite,
25
+ };
26
+ pub use predicate::{
27
+ BackendPredicate, HeaderFieldId, HeaderPredicate, KeyPredicate, PredicateExpr, PredicateId,
28
+ PredicateSupportLevel, RefKind, RefsPredicate, ScalarValue, Support,
29
+ };
30
+ pub use traits::{
31
+ get_many, visit_range, Backend, BackendRangeScan, BackendRead, BackendWrite, BufferedRangeScan,
32
+ DurableWriteGuard, DurableWriteLock, PointVisitor, ScanVisitor,
33
+ };
34
+ pub use types::{
35
+ CommitResult, CoreProjection, Durability, GetManyResult, GetOptions, Key, KeyRange, KeyRef,
36
+ Prefix, ProjectedValue, ProjectedValueRef, PutBatch, PutEntry, ReadConsistency, ReadEntry,
37
+ ReadOptions, ScanChunk, ScanOptions, ScanResult, SnapshotRef, SpaceId, StoredValue, Value,
38
+ WriteOptions, WriteStats,
11
39
  };
12
- pub use types::{Backend, BackendReadTransaction, BackendWriteTransaction};