@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.
Files changed (235) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +105 -65
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +30 -6
  5. package/dist/engine-wasm/wasm/lix_engine.js +187 -117
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +14 -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 +42 -28
  11. package/dist/open-lix.js +49 -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 +819 -124
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +260 -57
  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 +19 -16
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +557 -0
  104. package/dist-engine-src/src/sql2/bind/classify.rs +102 -0
  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} +98 -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 +4 -5
  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 +30 -24
  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 -109
  218. package/dist-engine-src/src/sql2/classify.rs +0 -182
  219. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  220. package/dist-engine-src/src/sql2/execute.rs +0 -3440
  221. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  222. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  223. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -166
  224. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -25
  225. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  226. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  227. package/dist-engine-src/src/storage/types.rs +0 -501
  228. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  229. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  230. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  231. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  232. package/dist-engine-src/src/version/mod.rs +0 -13
  233. package/dist-engine-src/src/version/refs.rs +0 -330
  234. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  235. package/dist-engine-src/src/version/types.rs +0 -21
@@ -1,356 +1,620 @@
1
- use std::sync::Arc;
1
+ use std::ops::Bound;
2
2
 
3
- use async_trait::async_trait;
4
-
5
- use crate::backend::{Backend, BackendReadTransaction, BackendWriteTransaction};
6
- use crate::storage::types::{KvWriteBatch, StorageWriter};
3
+ use crate::backend::{
4
+ Backend, BackendError, BackendWrite, CommitResult, InMemoryBackend, KeyRange, Prefix,
5
+ ReadOptions, WriteOptions,
6
+ };
7
7
  use crate::storage::{
8
- KvEntryPage, KvExistsBatch, KvGetRequest, KvKeyPage, KvScanRequest, KvValueBatch, KvValuePage,
9
- KvWriteStats, StorageReadTransaction, StorageReader, StorageWriteTransaction,
8
+ StorageRead, StorageReadScope, StorageSpace, StorageWriteSet, StorageWriteSetError,
9
+ StorageWriteSetStats,
10
10
  };
11
- use crate::LixError;
12
11
 
13
- #[derive(Clone)]
14
- pub(crate) struct StorageContext {
15
- backend: Arc<dyn Backend + Send + Sync>,
12
+ #[derive(Clone, Debug)]
13
+ pub struct StorageContext<B = InMemoryBackend> {
14
+ backend: B,
16
15
  }
17
16
 
18
- impl StorageContext {
19
- pub(crate) fn new(backend: Arc<dyn Backend + Send + Sync>) -> Self {
17
+ impl<B> StorageContext<B>
18
+ where
19
+ B: Backend,
20
+ {
21
+ pub fn new(backend: B) -> Self {
20
22
  Self { backend }
21
23
  }
22
24
 
23
- pub(crate) async fn begin_read_transaction(
25
+ pub fn begin_read(
24
26
  &self,
25
- ) -> Result<Box<dyn StorageReadTransaction + Send + Sync + 'static>, LixError> {
26
- let transaction = self.backend.begin_read_transaction().await?;
27
- Ok(Box::new(StorageContextReadTransaction { transaction }))
27
+ opts: ReadOptions,
28
+ ) -> Result<StorageReadScope<B::Read<'_>>, BackendError> {
29
+ self.backend.begin_read(opts).map(StorageReadScope::new)
28
30
  }
29
31
 
30
- pub(crate) async fn begin_write_transaction(
31
- &self,
32
- ) -> Result<Box<dyn StorageWriteTransaction + Send + Sync + 'static>, LixError> {
33
- let transaction = self.backend.begin_write_transaction().await?;
34
- Ok(Box::new(StorageContextWriteTransaction { transaction }))
32
+ pub fn new_write_set(&self) -> StorageWriteSet {
33
+ StorageWriteSet::new()
35
34
  }
36
35
 
37
- pub(crate) async fn close(&self) -> Result<(), LixError> {
38
- self.backend.close().await
36
+ pub async fn begin_read_transaction(
37
+ &self,
38
+ ) -> Result<Box<StorageReadTransaction<B::Read<'_>>>, crate::LixError> {
39
+ Ok(Box::new(StorageReadTransaction {
40
+ read: self.begin_read(ReadOptions::default())?,
41
+ }))
39
42
  }
40
43
 
41
- pub(crate) async fn destroy(&self) -> Result<(), LixError> {
42
- self.backend.destroy().await
44
+ pub async fn begin_write_transaction(
45
+ &self,
46
+ ) -> Result<Box<StorageWriteTransaction<'_, B>>, crate::LixError> {
47
+ Ok(Box::new(StorageWriteTransaction {
48
+ storage: self,
49
+ read: self.begin_read(ReadOptions::default())?,
50
+ }))
43
51
  }
44
- }
45
52
 
46
- #[cfg(any(test, feature = "storage-benches"))]
47
- #[async_trait]
48
- impl StorageReader for StorageContext {
49
- async fn get_values(&mut self, request: KvGetRequest) -> Result<KvValueBatch, LixError> {
50
- let mut transaction = self.begin_read_transaction().await?;
51
- let result = transaction.get_values(request).await;
52
- match result {
53
- Ok(result) => {
54
- transaction.rollback().await?;
55
- Ok(result)
56
- }
57
- Err(error) => {
58
- let _ = transaction.rollback().await;
59
- Err(error)
60
- }
61
- }
53
+ pub fn commit_write_set(
54
+ &self,
55
+ write_set: StorageWriteSet,
56
+ opts: WriteOptions,
57
+ ) -> Result<(CommitResult, StorageWriteSetStats), StorageWriteSetError> {
58
+ write_set.commit(&self.backend, opts)
62
59
  }
63
60
 
64
- async fn exists_many(&mut self, request: KvGetRequest) -> Result<KvExistsBatch, LixError> {
65
- let mut transaction = self.begin_read_transaction().await?;
66
- let result = transaction.exists_many(request).await;
67
- match result {
68
- Ok(result) => {
69
- transaction.rollback().await?;
70
- Ok(result)
71
- }
72
- Err(error) => {
73
- let _ = transaction.rollback().await;
74
- Err(error)
75
- }
76
- }
61
+ pub fn durable_write_lock(&self) -> crate::backend::DurableWriteLock {
62
+ self.backend.durable_write_lock()
77
63
  }
78
64
 
79
- async fn scan_keys(&mut self, request: KvScanRequest) -> Result<KvKeyPage, LixError> {
80
- let mut transaction = self.begin_read_transaction().await?;
81
- let result = transaction.scan_keys(request).await;
82
- match result {
83
- Ok(result) => {
84
- transaction.rollback().await?;
85
- Ok(result)
86
- }
87
- Err(error) => {
88
- let _ = transaction.rollback().await;
89
- Err(error)
90
- }
65
+ pub fn delete_range(
66
+ &self,
67
+ space: StorageSpace,
68
+ range: KeyRange,
69
+ opts: WriteOptions,
70
+ ) -> Result<CommitResult, BackendError> {
71
+ let mut write = self.backend.begin_write(opts)?;
72
+ let physical_range = space.encode_range(range, None);
73
+ if let Err(error) = write.delete_range(physical_range) {
74
+ let _ = write.rollback();
75
+ return Err(error);
91
76
  }
77
+ write.commit()
92
78
  }
93
79
 
94
- async fn scan_values(&mut self, request: KvScanRequest) -> Result<KvValuePage, LixError> {
95
- let mut transaction = self.begin_read_transaction().await?;
96
- let result = transaction.scan_values(request).await;
97
- match result {
98
- Ok(result) => {
99
- transaction.rollback().await?;
100
- Ok(result)
101
- }
102
- Err(error) => {
103
- let _ = transaction.rollback().await;
104
- Err(error)
105
- }
106
- }
80
+ pub fn delete_prefix(
81
+ &self,
82
+ space: StorageSpace,
83
+ prefix: Prefix,
84
+ opts: WriteOptions,
85
+ ) -> Result<CommitResult, BackendError> {
86
+ self.delete_range(space, prefix.to_range()?, opts)
107
87
  }
108
88
 
109
- async fn scan_entries(&mut self, request: KvScanRequest) -> Result<KvEntryPage, LixError> {
110
- let mut transaction = self.begin_read_transaction().await?;
111
- let result = transaction.scan_entries(request).await;
112
- match result {
113
- Ok(result) => {
114
- transaction.rollback().await?;
115
- Ok(result)
116
- }
117
- Err(error) => {
118
- let _ = transaction.rollback().await;
119
- Err(error)
120
- }
121
- }
89
+ pub fn clear_space(
90
+ &self,
91
+ space: StorageSpace,
92
+ opts: WriteOptions,
93
+ ) -> Result<CommitResult, BackendError> {
94
+ self.delete_range(
95
+ space,
96
+ KeyRange {
97
+ lower: Bound::Unbounded,
98
+ upper: Bound::Unbounded,
99
+ },
100
+ opts,
101
+ )
122
102
  }
123
103
  }
124
104
 
125
- struct StorageContextReadTransaction {
126
- transaction: Box<dyn BackendReadTransaction + Send + Sync + 'static>,
105
+ pub struct StorageReadTransaction<R>
106
+ where
107
+ R: crate::backend::BackendRead,
108
+ {
109
+ read: StorageReadScope<R>,
127
110
  }
128
111
 
129
- struct StorageContextWriteTransaction {
130
- transaction: Box<dyn BackendWriteTransaction + Send + Sync + 'static>,
112
+ impl<R> StorageReadTransaction<R>
113
+ where
114
+ R: crate::backend::BackendRead,
115
+ {
116
+ pub async fn rollback(self: Box<Self>) -> Result<(), crate::LixError> {
117
+ Ok(())
118
+ }
131
119
  }
132
120
 
133
- #[async_trait]
134
- impl StorageReader for StorageContextReadTransaction {
135
- async fn get_values(&mut self, request: KvGetRequest) -> Result<KvValueBatch, LixError> {
136
- self.transaction
137
- .get_values(request.into())
138
- .await
139
- .map(Into::into)
140
- }
121
+ impl<R> StorageRead for StorageReadTransaction<R>
122
+ where
123
+ R: crate::backend::BackendRead,
124
+ {
125
+ type BackendRead = R;
141
126
 
142
- async fn exists_many(&mut self, request: KvGetRequest) -> Result<KvExistsBatch, LixError> {
143
- self.transaction
144
- .exists_many(request.into())
145
- .await
146
- .map(Into::into)
127
+ fn backend_read(&self) -> &Self::BackendRead {
128
+ self.read.backend_read()
147
129
  }
130
+ }
131
+
132
+ pub struct StorageWriteTransaction<'a, B>
133
+ where
134
+ B: Backend,
135
+ {
136
+ storage: &'a StorageContext<B>,
137
+ read: StorageReadScope<B::Read<'a>>,
138
+ }
148
139
 
149
- async fn scan_keys(&mut self, request: KvScanRequest) -> Result<KvKeyPage, LixError> {
150
- self.transaction
151
- .scan_keys(request.into())
152
- .await
153
- .map(Into::into)
140
+ impl<B> StorageWriteTransaction<'_, B>
141
+ where
142
+ B: Backend,
143
+ {
144
+ pub async fn commit(self: Box<Self>) -> Result<(), crate::LixError> {
145
+ Ok(())
154
146
  }
155
147
 
156
- async fn scan_values(&mut self, request: KvScanRequest) -> Result<KvValuePage, LixError> {
157
- self.transaction
158
- .scan_values(request.into())
159
- .await
160
- .map(Into::into)
148
+ pub async fn rollback(self: Box<Self>) -> Result<(), crate::LixError> {
149
+ Ok(())
161
150
  }
162
151
 
163
- async fn scan_entries(&mut self, request: KvScanRequest) -> Result<KvEntryPage, LixError> {
164
- self.transaction
165
- .scan_entries(request.into())
166
- .await
167
- .map(Into::into)
152
+ pub fn write_set(
153
+ &mut self,
154
+ write_set: StorageWriteSet,
155
+ ) -> Result<StorageWriteSetStats, crate::LixError> {
156
+ let (_commit, stats) = self
157
+ .storage
158
+ .commit_write_set(write_set, WriteOptions::default())?;
159
+ Ok(stats)
168
160
  }
169
161
  }
170
162
 
171
- #[async_trait]
172
- impl StorageReadTransaction for StorageContextReadTransaction {
173
- async fn rollback(self: Box<Self>) -> Result<(), LixError> {
174
- self.transaction.rollback().await
163
+ impl<'a, B> StorageRead for StorageWriteTransaction<'a, B>
164
+ where
165
+ B: Backend,
166
+ {
167
+ type BackendRead = B::Read<'a>;
168
+
169
+ fn backend_read(&self) -> &Self::BackendRead {
170
+ self.read.backend_read()
175
171
  }
176
172
  }
177
173
 
178
- #[async_trait]
179
- impl StorageReader for StorageContextWriteTransaction {
180
- async fn get_values(&mut self, request: KvGetRequest) -> Result<KvValueBatch, LixError> {
181
- self.transaction
182
- .get_values(request.into())
183
- .await
184
- .map(Into::into)
185
- }
174
+ #[cfg(test)]
175
+ mod tests {
176
+ use bytes::Bytes;
177
+
178
+ use crate::backend::{
179
+ DurableWriteLock, GetOptions, InMemoryBackend, Key, ProjectedValue, ReadOptions, SpaceId,
180
+ StoredValue, WriteOptions,
181
+ };
182
+ use crate::storage::{PointReadPlan, StorageContext, StorageSpace};
186
183
 
187
- async fn exists_many(&mut self, request: KvGetRequest) -> Result<KvExistsBatch, LixError> {
188
- self.transaction
189
- .exists_many(request.into())
190
- .await
191
- .map(Into::into)
184
+ fn key(bytes: &'static str) -> Key {
185
+ Key(Bytes::from_static(bytes.as_bytes()))
192
186
  }
193
187
 
194
- async fn scan_keys(&mut self, request: KvScanRequest) -> Result<KvKeyPage, LixError> {
195
- self.transaction
196
- .scan_keys(request.into())
197
- .await
198
- .map(Into::into)
188
+ fn value(bytes: &'static str) -> StoredValue {
189
+ StoredValue {
190
+ bytes: Bytes::from_static(bytes.as_bytes()),
191
+ }
199
192
  }
200
193
 
201
- async fn scan_values(&mut self, request: KvScanRequest) -> Result<KvValuePage, LixError> {
202
- self.transaction
203
- .scan_values(request.into())
204
- .await
205
- .map(Into::into)
194
+ fn space(id: u32) -> StorageSpace {
195
+ match id {
196
+ 1 => StorageSpace::new(SpaceId(1), "test.space.one"),
197
+ 2 => StorageSpace::new(SpaceId(2), "test.space.two"),
198
+ _ => StorageSpace::new(SpaceId(id), "test.space.other"),
199
+ }
206
200
  }
207
201
 
208
- async fn scan_entries(&mut self, request: KvScanRequest) -> Result<KvEntryPage, LixError> {
209
- self.transaction
210
- .scan_entries(request.into())
211
- .await
212
- .map(Into::into)
202
+ #[test]
203
+ fn context_commits_write_set_and_reads_through_storage_contract() {
204
+ let storage = StorageContext::new(InMemoryBackend::new());
205
+ let mut writes = storage.new_write_set();
206
+ writes.put(space(1), key("a"), value("A"));
207
+ writes.put(space(1), key("b"), value("B"));
208
+ writes.put(space(2), key("a"), value("other"));
209
+ writes.delete(space(2), key("missing"));
210
+
211
+ let (_commit, stats) = storage
212
+ .commit_write_set(writes, WriteOptions::default())
213
+ .expect("commit write set");
214
+
215
+ assert_eq!(stats.staged_puts, 3);
216
+ assert_eq!(stats.staged_deletes, 1);
217
+ assert_eq!(stats.touched_spaces, 2);
218
+ assert_eq!(stats.put_batches, 2);
219
+ assert_eq!(stats.delete_batches, 1);
220
+ assert_eq!(stats.backend_calls, 3);
221
+
222
+ let read = storage
223
+ .begin_read(ReadOptions::default())
224
+ .expect("begin read");
225
+ let result = PointReadPlan::new(space(1), &[key("a"), key("b")])
226
+ .materialize(&read, GetOptions::default())
227
+ .expect("read back values");
228
+ assert_eq!(
229
+ result.value,
230
+ vec![
231
+ Some(ProjectedValue::FullValue(Bytes::from_static(b"A"))),
232
+ Some(ProjectedValue::FullValue(Bytes::from_static(b"B"))),
233
+ ]
234
+ );
213
235
  }
214
- }
215
236
 
216
- #[async_trait]
217
- impl StorageWriter for StorageContextWriteTransaction {
218
- async fn write_kv_batch(&mut self, batch: KvWriteBatch) -> Result<KvWriteStats, LixError> {
219
- self.transaction
220
- .write_kv_batch(batch.into())
221
- .await
222
- .map(Into::into)
237
+ #[test]
238
+ fn context_read_scope_pins_snapshot_across_later_commits() {
239
+ let storage = StorageContext::new(InMemoryBackend::new());
240
+ let mut writes = storage.new_write_set();
241
+ writes.put(space(1), key("a"), value("A"));
242
+ storage
243
+ .commit_write_set(writes, WriteOptions::default())
244
+ .expect("seed");
245
+
246
+ let read = storage
247
+ .begin_read(ReadOptions::default())
248
+ .expect("begin read");
249
+
250
+ let mut later = storage.new_write_set();
251
+ later.put(space(1), key("a"), value("B"));
252
+ storage
253
+ .commit_write_set(later, WriteOptions::default())
254
+ .expect("later commit");
255
+
256
+ let result = PointReadPlan::new(space(1), &[key("a")])
257
+ .materialize(&read, GetOptions::default())
258
+ .expect("read old scope");
259
+
260
+ assert_eq!(
261
+ result.value[0],
262
+ Some(ProjectedValue::FullValue(Bytes::from_static(b"A")))
263
+ );
223
264
  }
224
265
  }
225
266
 
226
- #[async_trait]
227
- impl StorageReadTransaction for StorageContextWriteTransaction {
228
- async fn rollback(self: Box<Self>) -> Result<(), LixError> {
229
- self.transaction.rollback().await
267
+ #[cfg(test)]
268
+ mod shape_tests {
269
+ use std::cell::{Cell, RefCell};
270
+ use std::ops::Bound;
271
+ use std::rc::Rc;
272
+
273
+ use bytes::Bytes;
274
+
275
+ use crate::backend::{
276
+ Backend, BackendCapabilities, BackendError, BackendRangeScan, BackendRead, BackendWrite,
277
+ BufferedRangeScan, CommitResult, DurableWriteLock, GetOptions, Key, KeyRange, PointVisitor,
278
+ ProjectedValue, ProjectedValueRef, PutBatch, ReadOptions, ScanOptions, ScanResult,
279
+ ScanVisitor, SpaceId, StoredValue, WriteConcurrency, WriteOptions, WriteStats,
280
+ };
281
+ use crate::storage::{PointReadPlan, ScanPlan, StorageContext, StorageReadScope, StorageSpace};
282
+
283
+ #[test]
284
+ fn write_set_across_g_spaces_lowers_to_g_put_many_calls_and_one_commit() {
285
+ let backend = CountingBackend::default();
286
+ let storage = StorageContext::new(backend.clone());
287
+ let mut writes = storage.new_write_set();
288
+ writes.put(space(1), key("a"), value("A"));
289
+ writes.put(space(2), key("a"), value("B"));
290
+ writes.put(space(3), key("a"), value("C"));
291
+
292
+ storage
293
+ .commit_write_set(writes, WriteOptions::default())
294
+ .expect("commit write set");
295
+
296
+ assert_eq!(backend.state.begin_write_calls.get(), 1);
297
+ assert_eq!(backend.state.commit_calls.get(), 1);
298
+ assert_eq!(backend.state.put_batches.borrow().len(), 3);
299
+ assert_eq!(
300
+ backend
301
+ .state
302
+ .put_batches
303
+ .borrow()
304
+ .iter()
305
+ .map(|(space, keys)| (*space, keys.clone()))
306
+ .collect::<Vec<_>>(),
307
+ vec![
308
+ (SpaceId(1), vec![key("a")]),
309
+ (SpaceId(2), vec![key("a")]),
310
+ (SpaceId(3), vec![key("a")]),
311
+ ]
312
+ );
230
313
  }
231
- }
232
314
 
233
- #[async_trait]
234
- impl StorageWriteTransaction for StorageContextWriteTransaction {
235
- async fn commit(self: Box<Self>) -> Result<(), LixError> {
236
- self.transaction.commit().await
315
+ #[test]
316
+ fn point_read_m_requested_u_unique_sends_u_backend_keys() {
317
+ let read = SpyRead::default();
318
+ let scope = StorageReadScope::new(read.clone());
319
+
320
+ let result = PointReadPlan::new(
321
+ space(1),
322
+ &[key("b"), key("a"), key("b"), key("missing"), key("a")],
323
+ )
324
+ .materialize(&scope, GetOptions::default())
325
+ .expect("point read");
326
+
327
+ assert_eq!(
328
+ read.get_many_keys.borrow().clone(),
329
+ vec![key("b"), key("a"), key("missing")]
330
+ .into_iter()
331
+ .map(|key| space(1).encode_key(&key))
332
+ .collect::<Vec<_>>()
333
+ );
334
+ assert_eq!(
335
+ result.value,
336
+ vec![
337
+ Some(ProjectedValue::FullValue(space(1).encode_key(&key("b")).0)),
338
+ Some(ProjectedValue::FullValue(space(1).encode_key(&key("a")).0)),
339
+ Some(ProjectedValue::FullValue(space(1).encode_key(&key("b")).0)),
340
+ Some(ProjectedValue::FullValue(
341
+ space(1).encode_key(&key("missing")).0
342
+ )),
343
+ Some(ProjectedValue::FullValue(space(1).encode_key(&key("a")).0)),
344
+ ]
345
+ );
237
346
  }
238
- }
239
347
 
240
- #[cfg(test)]
241
- mod tests {
242
- use std::sync::Arc;
243
-
244
- use crate::backend::testing::UnitTestBackend;
245
- use crate::storage::types::KvWriteBatch;
246
- use crate::storage::{KvGetGroup, KvScanRange, StorageWriteSet};
247
-
248
- use super::*;
249
-
250
- #[tokio::test]
251
- async fn storage_context_roundtrips_batched_writes_and_reads() {
252
- let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
253
- let storage = StorageContext::new(backend);
254
- let mut tx = storage
255
- .begin_write_transaction()
256
- .await
257
- .expect("transaction opens");
258
-
259
- let mut batch = KvWriteBatch::new();
260
- batch.put("ns", b"a".to_vec(), b"1".to_vec());
261
- batch.put("ns", b"b".to_vec(), b"2".to_vec());
262
- let stats = tx.write_kv_batch(batch).await.expect("batch writes");
263
- assert_eq!(stats.puts, 2);
264
- tx.commit().await.expect("commit succeeds");
265
-
266
- let mut tx = storage
267
- .begin_read_transaction()
268
- .await
269
- .expect("read transaction opens");
270
- let result = tx
271
- .get_values(KvGetRequest {
272
- groups: vec![KvGetGroup {
273
- namespace: "ns".to_string(),
274
- keys: vec![b"a".to_vec(), b"b".to_vec()],
275
- }],
276
- })
277
- .await
278
- .expect("batch reads");
279
- assert_eq!(result.groups[0].value(0), Some(Some(b"1".as_slice())));
280
- assert_eq!(result.groups[0].value(1), Some(Some(b"2".as_slice())));
281
-
282
- let exists = tx
283
- .exists_many(KvGetRequest {
284
- groups: vec![KvGetGroup {
285
- namespace: "ns".to_string(),
286
- keys: vec![b"a".to_vec(), b"missing".to_vec()],
287
- }],
348
+ #[test]
349
+ fn prefix_scan_calls_scan_range_once() {
350
+ let read = SpyRead::default();
351
+ let scope = StorageReadScope::new(read.clone());
352
+
353
+ ScanPlan::prefix(
354
+ space(1),
355
+ crate::backend::Prefix {
356
+ bytes: Bytes::from_static(b"a"),
357
+ },
358
+ )
359
+ .collect(&scope, ScanOptions::default())
360
+ .expect("prefix scan");
361
+
362
+ assert_eq!(read.scan_range_calls.get(), 1);
363
+ assert_eq!(
364
+ read.scan_range.borrow().clone(),
365
+ Some(KeyRange {
366
+ lower: Bound::Included(space(1).encode_key(&key("a"))),
367
+ upper: Bound::Excluded(space(1).encode_key(&key("b"))),
288
368
  })
289
- .await
290
- .expect("existence reads");
291
- assert_eq!(exists.groups[0].exists, vec![true, false]);
292
-
293
- let result = tx
294
- .scan_entries(KvScanRequest {
295
- namespace: "ns".to_string(),
296
- range: KvScanRange::prefix(Vec::new()),
297
- after: Some(b"a".to_vec()),
298
- limit: 1,
299
- })
300
- .await
301
- .expect("scan reads");
302
- assert_eq!(result.key(0).expect("key exists"), b"b");
303
- assert_eq!(result.value(0).expect("value exists"), b"2");
304
-
305
- let key_only = tx
306
- .scan_keys(KvScanRequest {
307
- namespace: "ns".to_string(),
308
- range: KvScanRange::prefix(Vec::new()),
309
- after: None,
310
- limit: 2,
369
+ );
370
+ }
371
+
372
+ #[test]
373
+ fn delete_range_lowers_to_one_backend_delete_range() {
374
+ let backend = CountingBackend::default();
375
+ let storage = StorageContext::new(backend.clone());
376
+
377
+ storage
378
+ .delete_range(
379
+ space(7),
380
+ KeyRange {
381
+ lower: Bound::Included(key("a")),
382
+ upper: Bound::Excluded(key("c")),
383
+ },
384
+ WriteOptions::default(),
385
+ )
386
+ .expect("delete range");
387
+
388
+ assert_eq!(backend.state.begin_write_calls.get(), 1);
389
+ assert_eq!(backend.state.commit_calls.get(), 1);
390
+ assert_eq!(backend.state.delete_many_calls.get(), 0);
391
+ assert_eq!(
392
+ backend.state.delete_ranges.borrow().as_slice(),
393
+ &[KeyRange {
394
+ lower: Bound::Included(space(7).encode_key(&key("a"))),
395
+ upper: Bound::Excluded(space(7).encode_key(&key("c"))),
396
+ }]
397
+ );
398
+ }
399
+
400
+ #[test]
401
+ fn delete_prefix_lowers_to_one_backend_delete_range() {
402
+ let backend = CountingBackend::default();
403
+ let storage = StorageContext::new(backend.clone());
404
+
405
+ storage
406
+ .delete_prefix(
407
+ space(7),
408
+ crate::backend::Prefix {
409
+ bytes: Bytes::from_static(b"ab"),
410
+ },
411
+ WriteOptions::default(),
412
+ )
413
+ .expect("delete prefix");
414
+
415
+ assert_eq!(backend.state.begin_write_calls.get(), 1);
416
+ assert_eq!(backend.state.commit_calls.get(), 1);
417
+ assert_eq!(backend.state.delete_many_calls.get(), 0);
418
+ assert_eq!(
419
+ backend.state.delete_ranges.borrow().as_slice(),
420
+ &[KeyRange {
421
+ lower: Bound::Included(space(7).encode_key(&key("ab"))),
422
+ upper: Bound::Excluded(space(7).encode_key(&key("ac"))),
423
+ }]
424
+ );
425
+ }
426
+
427
+ #[test]
428
+ fn clear_space_lowers_to_one_backend_delete_range() {
429
+ let backend = CountingBackend::default();
430
+ let storage = StorageContext::new(backend.clone());
431
+
432
+ storage
433
+ .clear_space(space(7), WriteOptions::default())
434
+ .expect("clear space");
435
+
436
+ assert_eq!(backend.state.begin_write_calls.get(), 1);
437
+ assert_eq!(backend.state.commit_calls.get(), 1);
438
+ assert_eq!(backend.state.delete_many_calls.get(), 0);
439
+ assert_eq!(
440
+ backend.state.delete_ranges.borrow().as_slice(),
441
+ &[KeyRange {
442
+ lower: Bound::Included(Key(Bytes::from_static(b"\0\0\0\x07"))),
443
+ upper: Bound::Excluded(Key(Bytes::from_static(b"\0\0\0\x08"))),
444
+ }]
445
+ );
446
+ }
447
+
448
+ #[derive(Clone, Default)]
449
+ struct CountingBackend {
450
+ state: Rc<CountingState>,
451
+ }
452
+
453
+ #[derive(Default)]
454
+ struct CountingState {
455
+ begin_write_calls: Cell<u64>,
456
+ commit_calls: Cell<u64>,
457
+ delete_many_calls: Cell<u64>,
458
+ put_batches: RefCell<Vec<(SpaceId, Vec<Key>)>>,
459
+ delete_ranges: RefCell<Vec<KeyRange>>,
460
+ }
461
+
462
+ struct CountingWrite {
463
+ state: Rc<CountingState>,
464
+ put_batches: Vec<(SpaceId, Vec<Key>)>,
465
+ delete_ranges: Vec<KeyRange>,
466
+ }
467
+
468
+ impl Backend for CountingBackend {
469
+ type Read<'a>
470
+ = SpyRead
471
+ where
472
+ Self: 'a;
473
+
474
+ type Write<'a>
475
+ = CountingWrite
476
+ where
477
+ Self: 'a;
478
+
479
+ fn capabilities(&self) -> BackendCapabilities {
480
+ BackendCapabilities::v0(WriteConcurrency::SingleWriter)
481
+ }
482
+
483
+ fn begin_read(&self, _opts: ReadOptions) -> Result<Self::Read<'_>, BackendError> {
484
+ Ok(SpyRead::default())
485
+ }
486
+
487
+ fn begin_write(&self, _opts: WriteOptions) -> Result<Self::Write<'_>, BackendError> {
488
+ self.state
489
+ .begin_write_calls
490
+ .set(self.state.begin_write_calls.get() + 1);
491
+ Ok(CountingWrite {
492
+ state: Rc::clone(&self.state),
493
+ put_batches: Vec::new(),
494
+ delete_ranges: Vec::new(),
311
495
  })
312
- .await
313
- .expect("key-only scan reads");
314
- assert_eq!(key_only.keys.iter().collect::<Vec<_>>(), vec![b"a", b"b"]);
315
- tx.rollback().await.expect("rollback succeeds");
316
- }
317
-
318
- #[tokio::test]
319
- async fn storage_write_set_applies_as_one_batch() {
320
- let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
321
- let storage = StorageContext::new(backend);
322
- let mut tx = storage
323
- .begin_write_transaction()
324
- .await
325
- .expect("transaction opens");
326
-
327
- let mut writes = StorageWriteSet::new();
328
- assert!(writes.is_empty());
329
- writes.put("ns", b"a".to_vec(), b"1".to_vec());
330
- writes.put("ns", b"b".to_vec(), b"2".to_vec());
331
- writes.delete("ns", b"missing".to_vec());
332
- assert!(!writes.is_empty());
333
-
334
- let stats = writes.apply(tx.as_mut()).await.expect("write set applies");
335
- assert_eq!(stats.puts, 2);
336
- assert_eq!(stats.deletes, 1);
337
- tx.commit().await.expect("commit succeeds");
338
-
339
- let mut tx = storage
340
- .begin_read_transaction()
341
- .await
342
- .expect("read transaction opens");
343
- let result = tx
344
- .get_values(KvGetRequest {
345
- groups: vec![KvGetGroup {
346
- namespace: "ns".to_string(),
347
- keys: vec![b"a".to_vec(), b"b".to_vec()],
348
- }],
496
+ }
497
+
498
+ fn durable_write_lock(&self) -> DurableWriteLock {
499
+ DurableWriteLock::new()
500
+ }
501
+ }
502
+
503
+ impl BackendWrite for CountingWrite {
504
+ fn put_many(&mut self, entries: PutBatch) -> Result<(), BackendError> {
505
+ let space = entries
506
+ .entries
507
+ .first()
508
+ .map(|entry| physical_space(&entry.key))
509
+ .unwrap_or(SpaceId(0));
510
+ self.put_batches.push((
511
+ space,
512
+ entries
513
+ .entries
514
+ .into_iter()
515
+ .map(|entry| logical_key(entry.key))
516
+ .collect(),
517
+ ));
518
+ Ok(())
519
+ }
520
+
521
+ fn delete_many(&mut self, _keys: &[Key]) -> Result<(), BackendError> {
522
+ self.state
523
+ .delete_many_calls
524
+ .set(self.state.delete_many_calls.get() + 1);
525
+ Ok(())
526
+ }
527
+
528
+ fn delete_range(&mut self, range: KeyRange) -> Result<(), BackendError> {
529
+ self.delete_ranges.push(range);
530
+ Ok(())
531
+ }
532
+
533
+ fn commit(self) -> Result<CommitResult, BackendError> {
534
+ self.state
535
+ .commit_calls
536
+ .set(self.state.commit_calls.get() + 1);
537
+ self.state.put_batches.borrow_mut().extend(self.put_batches);
538
+ self.state
539
+ .delete_ranges
540
+ .borrow_mut()
541
+ .extend(self.delete_ranges);
542
+ Ok(CommitResult {
543
+ commit_id: None,
544
+ stats: WriteStats::default(),
349
545
  })
350
- .await
351
- .expect("batch reads");
352
- assert_eq!(result.groups[0].value(0), Some(Some(&b"1"[..])));
353
- assert_eq!(result.groups[0].value(1), Some(Some(&b"2"[..])));
354
- tx.rollback().await.expect("rollback succeeds");
546
+ }
547
+
548
+ fn rollback(self) -> Result<(), BackendError> {
549
+ Ok(())
550
+ }
551
+ }
552
+
553
+ #[derive(Clone, Default)]
554
+ struct SpyRead {
555
+ get_many_keys: Rc<RefCell<Vec<Key>>>,
556
+ scan_range_calls: Rc<Cell<u64>>,
557
+ scan_range: Rc<RefCell<Option<KeyRange>>>,
558
+ }
559
+
560
+ impl BackendRead for SpyRead {
561
+ type RangeScan<'a> = BufferedRangeScan;
562
+
563
+ fn visit_keys<V>(
564
+ &self,
565
+ keys: &[Key],
566
+ _opts: GetOptions<'_>,
567
+ visitor: &mut V,
568
+ ) -> Result<(), BackendError>
569
+ where
570
+ V: PointVisitor + ?Sized,
571
+ {
572
+ self.get_many_keys.replace(keys.to_vec());
573
+ for (index, key) in keys.iter().enumerate() {
574
+ let value = (key.0.as_ref() != b"missing")
575
+ .then_some(ProjectedValueRef::FullValue(key.0.as_ref()));
576
+ visitor.visit(index, key, value)?;
577
+ }
578
+ Ok(())
579
+ }
580
+
581
+ fn with_range_scan<T, F>(
582
+ &self,
583
+ range: KeyRange,
584
+ _opts: ScanOptions<'_>,
585
+ f: F,
586
+ ) -> Result<T, BackendError>
587
+ where
588
+ F: FnOnce(&mut Self::RangeScan<'_>) -> Result<T, BackendError>,
589
+ {
590
+ self.scan_range_calls.set(self.scan_range_calls.get() + 1);
591
+ self.scan_range.replace(Some(range));
592
+ let mut cursor = BufferedRangeScan::default();
593
+ f(&mut cursor)
594
+ }
595
+ }
596
+
597
+ fn space(id: u32) -> StorageSpace {
598
+ StorageSpace::new(SpaceId(id), "shape.test.space")
599
+ }
600
+
601
+ fn physical_space(key: &Key) -> SpaceId {
602
+ SpaceId(u32::from_be_bytes(
603
+ key.0[..4].try_into().expect("space prefix"),
604
+ ))
605
+ }
606
+
607
+ fn logical_key(key: Key) -> Key {
608
+ Key(key.0.slice(4..))
609
+ }
610
+
611
+ fn key(bytes: &'static str) -> Key {
612
+ Key(Bytes::from_static(bytes.as_bytes()))
613
+ }
614
+
615
+ fn value(bytes: &'static str) -> StoredValue {
616
+ StoredValue {
617
+ bytes: Bytes::from_static(bytes.as_bytes()),
618
+ }
355
619
  }
356
620
  }