@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
@@ -1,658 +0,0 @@
1
- use std::collections::BTreeMap;
2
- use std::sync::{Arc, Mutex};
3
-
4
- use async_trait::async_trait;
5
-
6
- use crate::backend::{
7
- Backend, BackendKvEntryPage, BackendKvExistsBatch, BackendKvExistsGroup, BackendKvGetRequest,
8
- BackendKvKeyPage, BackendKvScanRange, BackendKvScanRequest, BackendKvValueBatch,
9
- BackendKvValueGroup, BackendKvValuePage, BackendKvWriteBatch, BackendKvWriteStats,
10
- BackendReadTransaction, BackendWriteTransaction, BytePageBuilder,
11
- };
12
- use crate::LixError;
13
-
14
- type KvMap = BTreeMap<(String, Vec<u8>), Vec<u8>>;
15
-
16
- /// In-memory backend for unit tests that need backend KV semantics without SQL.
17
- ///
18
- /// SQL execution intentionally returns an error so new tests do not accidentally
19
- /// couple to raw SQL while exercising storage-facing APIs.
20
- #[derive(Debug, Clone, Default)]
21
- pub(crate) struct UnitTestBackend {
22
- kv: Arc<Mutex<KvMap>>,
23
- }
24
-
25
- impl UnitTestBackend {
26
- pub(crate) fn new() -> Self {
27
- Self::default()
28
- }
29
- }
30
-
31
- #[async_trait]
32
- impl Backend for UnitTestBackend {
33
- async fn begin_read_transaction(
34
- &self,
35
- ) -> Result<Box<dyn BackendReadTransaction + Send + Sync + 'static>, LixError> {
36
- let snapshot = self
37
- .kv
38
- .lock()
39
- .map_err(|_| lock_error("unit test backend kv"))?
40
- .clone();
41
- Ok(Box::new(UnitTestTransaction {
42
- parent: Arc::clone(&self.kv),
43
- kv: snapshot,
44
- }))
45
- }
46
-
47
- async fn begin_write_transaction(
48
- &self,
49
- ) -> Result<Box<dyn BackendWriteTransaction + Send + Sync + 'static>, LixError> {
50
- let snapshot = self
51
- .kv
52
- .lock()
53
- .map_err(|_| lock_error("unit test backend kv"))?
54
- .clone();
55
- Ok(Box::new(UnitTestTransaction {
56
- parent: Arc::clone(&self.kv),
57
- kv: snapshot,
58
- }))
59
- }
60
- }
61
-
62
- struct UnitTestTransaction {
63
- parent: Arc<Mutex<KvMap>>,
64
- kv: KvMap,
65
- }
66
-
67
- #[async_trait]
68
- impl BackendReadTransaction for UnitTestTransaction {
69
- async fn get_values(
70
- &mut self,
71
- request: BackendKvGetRequest,
72
- ) -> Result<BackendKvValueBatch, LixError> {
73
- let mut groups = Vec::with_capacity(request.groups.len());
74
- for group in request.groups {
75
- let namespace = group.namespace.clone();
76
- let mut values = BytePageBuilder::with_capacity(group.keys.len(), 0);
77
- let mut present = Vec::with_capacity(group.keys.len());
78
- for key in group.keys {
79
- if let Some(value) = self.kv.get(&(namespace.clone(), key)) {
80
- values.push(value);
81
- present.push(true);
82
- } else {
83
- values.push([]);
84
- present.push(false);
85
- }
86
- }
87
- groups.push(BackendKvValueGroup::new(
88
- namespace,
89
- values.finish(),
90
- present,
91
- ));
92
- }
93
- Ok(BackendKvValueBatch { groups })
94
- }
95
-
96
- async fn exists_many(
97
- &mut self,
98
- request: BackendKvGetRequest,
99
- ) -> Result<BackendKvExistsBatch, LixError> {
100
- let mut groups = Vec::with_capacity(request.groups.len());
101
- for group in request.groups {
102
- let namespace = group.namespace.clone();
103
- let exists = group
104
- .keys
105
- .into_iter()
106
- .map(|key| self.kv.contains_key(&(namespace.clone(), key)))
107
- .collect();
108
- groups.push(BackendKvExistsGroup { namespace, exists });
109
- }
110
- Ok(BackendKvExistsBatch { groups })
111
- }
112
-
113
- async fn scan_keys(
114
- &mut self,
115
- request: BackendKvScanRequest,
116
- ) -> Result<BackendKvKeyPage, LixError> {
117
- Ok(scan_map_keys(&self.kv, request))
118
- }
119
-
120
- async fn scan_values(
121
- &mut self,
122
- request: BackendKvScanRequest,
123
- ) -> Result<BackendKvValuePage, LixError> {
124
- Ok(scan_map_values(&self.kv, request))
125
- }
126
-
127
- async fn scan_entries(
128
- &mut self,
129
- request: BackendKvScanRequest,
130
- ) -> Result<BackendKvEntryPage, LixError> {
131
- Ok(scan_map_entries(&self.kv, request))
132
- }
133
-
134
- async fn rollback(self: Box<Self>) -> Result<(), LixError> {
135
- Ok(())
136
- }
137
- }
138
-
139
- #[async_trait]
140
- impl BackendWriteTransaction for UnitTestTransaction {
141
- async fn write_kv_batch(
142
- &mut self,
143
- batch: BackendKvWriteBatch,
144
- ) -> Result<BackendKvWriteStats, LixError> {
145
- let mut stats = BackendKvWriteStats::default();
146
- for group in batch.groups {
147
- let namespace = group.namespace().to_string();
148
- for index in 0..group.put_count() {
149
- let key = group.put_key(index).ok_or_else(|| {
150
- LixError::new("LIX_ERROR_UNKNOWN", "backend write batch missing put key")
151
- })?;
152
- let value = group.put_value(index).ok_or_else(|| {
153
- LixError::new("LIX_ERROR_UNKNOWN", "backend write batch missing put value")
154
- })?;
155
- stats.puts += 1;
156
- stats.bytes_written += key.len() + value.len();
157
- self.kv
158
- .insert((namespace.clone(), key.to_vec()), value.to_vec());
159
- }
160
- for index in 0..group.delete_count() {
161
- let key = group.delete_key(index).ok_or_else(|| {
162
- LixError::new(
163
- "LIX_ERROR_UNKNOWN",
164
- "backend write batch missing delete key",
165
- )
166
- })?;
167
- stats.deletes += 1;
168
- stats.bytes_written += key.len();
169
- self.kv.remove(&(namespace.clone(), key.to_vec()));
170
- }
171
- }
172
- Ok(stats)
173
- }
174
-
175
- async fn commit(self: Box<Self>) -> Result<(), LixError> {
176
- *self
177
- .parent
178
- .lock()
179
- .map_err(|_| lock_error("unit test backend kv"))? = self.kv;
180
- Ok(())
181
- }
182
- }
183
-
184
- #[async_trait]
185
- impl Backend for Arc<UnitTestBackend> {
186
- async fn begin_read_transaction(
187
- &self,
188
- ) -> Result<Box<dyn BackendReadTransaction + Send + Sync + 'static>, LixError> {
189
- self.as_ref().begin_read_transaction().await
190
- }
191
-
192
- async fn begin_write_transaction(
193
- &self,
194
- ) -> Result<Box<dyn BackendWriteTransaction + Send + Sync + 'static>, LixError> {
195
- self.as_ref().begin_write_transaction().await
196
- }
197
- }
198
-
199
- fn scan_pairs<'a>(
200
- kv: &'a KvMap,
201
- namespace: &str,
202
- range: &BackendKvScanRange,
203
- limit: Option<usize>,
204
- ) -> Vec<(&'a Vec<u8>, &'a Vec<u8>)> {
205
- let pairs = kv
206
- .iter()
207
- .filter(|((candidate_namespace, key), _)| {
208
- candidate_namespace == namespace && key_matches_range(key, range)
209
- })
210
- .collect::<Vec<_>>();
211
- let mut pairs = pairs;
212
- pairs.sort_by(|left, right| left.0 .1.cmp(&right.0 .1));
213
- if let Some(limit) = limit {
214
- pairs.truncate(limit);
215
- }
216
- pairs
217
- .into_iter()
218
- .map(|((_, key), value)| (key, value))
219
- .collect()
220
- }
221
-
222
- pub(crate) fn scan_map_keys(kv: &KvMap, request: BackendKvScanRequest) -> BackendKvKeyPage {
223
- let pairs = scan_filtered_pairs(kv, &request);
224
- let has_more = pairs.len() > request.limit;
225
- let mut keys = BytePageBuilder::with_capacity(request.limit.min(pairs.len()), 0);
226
- let mut resume_after = None;
227
- for (index, (key, _)) in pairs.into_iter().enumerate() {
228
- if index >= request.limit {
229
- break;
230
- }
231
- resume_after = Some(key.clone());
232
- keys.push(key);
233
- }
234
- let resume_after = has_more.then_some(resume_after).flatten();
235
- BackendKvKeyPage {
236
- keys: keys.finish(),
237
- resume_after,
238
- }
239
- }
240
-
241
- pub(crate) fn scan_map_values(kv: &KvMap, request: BackendKvScanRequest) -> BackendKvValuePage {
242
- let pairs = scan_filtered_pairs(kv, &request);
243
- let has_more = pairs.len() > request.limit;
244
- let mut values = BytePageBuilder::with_capacity(request.limit.min(pairs.len()), 0);
245
- let mut resume_after = None;
246
- for (index, (key, value)) in pairs.into_iter().enumerate() {
247
- if index >= request.limit {
248
- break;
249
- }
250
- resume_after = Some(key.clone());
251
- values.push(value);
252
- }
253
- let resume_after = has_more.then_some(resume_after).flatten();
254
- BackendKvValuePage {
255
- values: values.finish(),
256
- resume_after,
257
- }
258
- }
259
-
260
- pub(crate) fn scan_map_entries(kv: &KvMap, request: BackendKvScanRequest) -> BackendKvEntryPage {
261
- let pairs = scan_filtered_pairs(kv, &request);
262
- let has_more = pairs.len() > request.limit;
263
- let mut keys = BytePageBuilder::with_capacity(request.limit.min(pairs.len()), 0);
264
- let mut values = BytePageBuilder::with_capacity(request.limit.min(pairs.len()), 0);
265
- let mut resume_after = None;
266
- for (index, (key, value)) in pairs.into_iter().enumerate() {
267
- if index >= request.limit {
268
- break;
269
- }
270
- resume_after = Some(key.clone());
271
- keys.push(key);
272
- values.push(value);
273
- }
274
- let resume_after = has_more.then_some(resume_after).flatten();
275
- BackendKvEntryPage {
276
- keys: keys.finish(),
277
- values: values.finish(),
278
- resume_after,
279
- }
280
- }
281
-
282
- fn scan_filtered_pairs<'a>(
283
- kv: &'a KvMap,
284
- request: &BackendKvScanRequest,
285
- ) -> Vec<(&'a Vec<u8>, &'a Vec<u8>)> {
286
- let scan_limit = request
287
- .limit
288
- .checked_add(1 + usize::from(request.after.is_some()))
289
- .unwrap_or(request.limit);
290
- scan_pairs(kv, &request.namespace, &request.range, Some(scan_limit))
291
- .into_iter()
292
- .filter(|(key, _)| {
293
- request
294
- .after
295
- .as_deref()
296
- .is_none_or(|after| key.as_slice() > after)
297
- })
298
- .collect()
299
- }
300
-
301
- fn key_matches_range(key: &[u8], range: &BackendKvScanRange) -> bool {
302
- match range {
303
- BackendKvScanRange::Prefix(prefix) => key.starts_with(prefix),
304
- BackendKvScanRange::Range { start, end } => start.as_slice() <= key && key < end.as_slice(),
305
- }
306
- }
307
-
308
- fn lock_error(name: &str) -> LixError {
309
- LixError::new("LIX_ERROR_UNKNOWN", format!("{name} lock poisoned"))
310
- }
311
-
312
- #[cfg(test)]
313
- mod tests {
314
- use super::*;
315
- use crate::backend::{
316
- BackendKvGetGroup, BackendKvGetRequest, BackendKvScanRequest, BackendKvWriteBatch,
317
- BackendKvWriteGroup,
318
- };
319
-
320
- async fn put(
321
- transaction: &mut (dyn BackendWriteTransaction + Send + Sync),
322
- namespace: &str,
323
- key: &[u8],
324
- value: &[u8],
325
- ) {
326
- transaction
327
- .write_kv_batch(BackendKvWriteBatch {
328
- groups: {
329
- let mut group = BackendKvWriteGroup::new(namespace);
330
- group.put(key, value);
331
- vec![group]
332
- },
333
- })
334
- .await
335
- .expect("put should succeed");
336
- }
337
-
338
- async fn delete(
339
- transaction: &mut (dyn BackendWriteTransaction + Send + Sync),
340
- namespace: &str,
341
- key: &[u8],
342
- ) {
343
- transaction
344
- .write_kv_batch(BackendKvWriteBatch {
345
- groups: {
346
- let mut group = BackendKvWriteGroup::new(namespace);
347
- group.delete(key);
348
- vec![group]
349
- },
350
- })
351
- .await
352
- .expect("delete should succeed");
353
- }
354
-
355
- async fn get(backend: &UnitTestBackend, namespace: &str, key: &[u8]) -> Option<Vec<u8>> {
356
- let mut transaction = backend
357
- .begin_read_transaction()
358
- .await
359
- .expect("read transaction should open");
360
- let result = transaction
361
- .get_values(BackendKvGetRequest {
362
- groups: vec![BackendKvGetGroup {
363
- namespace: namespace.to_string(),
364
- keys: vec![key.to_vec()],
365
- }],
366
- })
367
- .await
368
- .expect("get should succeed");
369
- transaction
370
- .rollback()
371
- .await
372
- .expect("rollback should succeed");
373
- result
374
- .groups
375
- .into_iter()
376
- .next()
377
- .and_then(|group| group.value(0).flatten().map(<[u8]>::to_vec))
378
- }
379
-
380
- async fn scan(
381
- backend: &UnitTestBackend,
382
- namespace: &str,
383
- range: BackendKvScanRange,
384
- limit: usize,
385
- ) -> BackendKvEntryPage {
386
- let mut transaction = backend
387
- .begin_read_transaction()
388
- .await
389
- .expect("read transaction should open");
390
- let result = transaction
391
- .scan_entries(BackendKvScanRequest {
392
- namespace: namespace.to_string(),
393
- range,
394
- after: None,
395
- limit,
396
- })
397
- .await
398
- .expect("scan should succeed");
399
- transaction
400
- .rollback()
401
- .await
402
- .expect("rollback should succeed");
403
- result
404
- }
405
-
406
- fn assert_entries(page: &BackendKvEntryPage, expected: &[(&[u8], &[u8])]) {
407
- assert_eq!(page.len(), expected.len());
408
- for (index, (key, value)) in expected.iter().enumerate() {
409
- assert_eq!(page.key(index).expect("key exists"), *key);
410
- assert_eq!(page.value(index).expect("value exists"), *value);
411
- }
412
- }
413
-
414
- async fn scan_entries_request(
415
- backend: &UnitTestBackend,
416
- after: Option<&[u8]>,
417
- limit: usize,
418
- ) -> BackendKvEntryPage {
419
- let mut transaction = backend
420
- .begin_read_transaction()
421
- .await
422
- .expect("read transaction should open");
423
- let result = transaction
424
- .scan_entries(BackendKvScanRequest {
425
- namespace: "ns".to_string(),
426
- range: BackendKvScanRange::prefix(Vec::new()),
427
- after: after.map(Vec::from),
428
- limit,
429
- })
430
- .await
431
- .expect("scan should succeed");
432
- transaction
433
- .rollback()
434
- .await
435
- .expect("rollback should succeed");
436
- result
437
- }
438
-
439
- async fn scan_keys_request(
440
- backend: &UnitTestBackend,
441
- after: Option<&[u8]>,
442
- limit: usize,
443
- ) -> BackendKvKeyPage {
444
- let mut transaction = backend
445
- .begin_read_transaction()
446
- .await
447
- .expect("read transaction should open");
448
- let result = transaction
449
- .scan_keys(BackendKvScanRequest {
450
- namespace: "ns".to_string(),
451
- range: BackendKvScanRange::prefix(Vec::new()),
452
- after: after.map(Vec::from),
453
- limit,
454
- })
455
- .await
456
- .expect("scan should succeed");
457
- transaction
458
- .rollback()
459
- .await
460
- .expect("rollback should succeed");
461
- result
462
- }
463
-
464
- #[tokio::test]
465
- async fn committed_put_is_visible_to_backend_reads() {
466
- let backend = UnitTestBackend::new();
467
- let mut transaction = backend
468
- .begin_write_transaction()
469
- .await
470
- .expect("transaction should open");
471
- put(transaction.as_mut(), "live_state", b"key", b"value").await;
472
- transaction.commit().await.expect("commit should succeed");
473
-
474
- assert_eq!(
475
- get(&backend, "live_state", b"key").await,
476
- Some(b"value".to_vec())
477
- );
478
- }
479
-
480
- #[tokio::test]
481
- async fn rollback_discards_puts() {
482
- let backend = UnitTestBackend::new();
483
- let mut transaction = backend
484
- .begin_write_transaction()
485
- .await
486
- .expect("transaction should open");
487
- put(transaction.as_mut(), "live_state", b"key", b"value").await;
488
- transaction
489
- .rollback()
490
- .await
491
- .expect("rollback should succeed");
492
-
493
- assert_eq!(get(&backend, "live_state", b"key").await, None);
494
- }
495
-
496
- #[tokio::test]
497
- async fn close_is_idempotent_and_does_not_destroy_data() {
498
- let backend = UnitTestBackend::new();
499
- let mut transaction = backend
500
- .begin_write_transaction()
501
- .await
502
- .expect("transaction should open");
503
- put(transaction.as_mut(), "live_state", b"key", b"value").await;
504
- transaction.commit().await.expect("commit should succeed");
505
-
506
- backend.close().await.expect("first close should succeed");
507
- backend.close().await.expect("second close should succeed");
508
-
509
- assert_eq!(
510
- get(&backend, "live_state", b"key").await,
511
- Some(b"value".to_vec())
512
- );
513
- }
514
-
515
- #[tokio::test]
516
- async fn delete_removes_key_on_commit() {
517
- let backend = UnitTestBackend::new();
518
- let mut seed = backend
519
- .begin_write_transaction()
520
- .await
521
- .expect("seed transaction should open");
522
- put(seed.as_mut(), "live_state", b"key", b"value").await;
523
- seed.commit().await.expect("seed commit should succeed");
524
-
525
- let mut transaction = backend
526
- .begin_write_transaction()
527
- .await
528
- .expect("delete transaction should open");
529
- delete(transaction.as_mut(), "live_state", b"key").await;
530
- transaction.commit().await.expect("commit should succeed");
531
-
532
- assert_eq!(get(&backend, "live_state", b"key").await, None);
533
- }
534
-
535
- #[tokio::test]
536
- async fn prefix_scan_returns_lexicographic_order_with_limit() {
537
- let backend = UnitTestBackend::new();
538
- let mut transaction = backend
539
- .begin_write_transaction()
540
- .await
541
- .expect("transaction should open");
542
- put(transaction.as_mut(), "ns", b"b/2", b"2").await;
543
- put(transaction.as_mut(), "ns", b"a/2", b"2").await;
544
- put(transaction.as_mut(), "ns", b"a/1", b"1").await;
545
- put(transaction.as_mut(), "other", b"a/0", b"0").await;
546
- transaction.commit().await.unwrap();
547
-
548
- let pairs = scan(&backend, "ns", BackendKvScanRange::prefix(b"a/"), 1).await;
549
- assert_entries(&pairs, &[(b"a/1", b"1")]);
550
- }
551
-
552
- #[tokio::test]
553
- async fn scan_sets_resume_after_only_when_more_rows_exist() {
554
- let backend = UnitTestBackend::new();
555
- let mut transaction = backend
556
- .begin_write_transaction()
557
- .await
558
- .expect("transaction should open");
559
- put(transaction.as_mut(), "ns", b"a", b"1").await;
560
- put(transaction.as_mut(), "ns", b"b", b"2").await;
561
- put(transaction.as_mut(), "ns", b"c", b"3").await;
562
- transaction.commit().await.unwrap();
563
-
564
- let first_page = scan_entries_request(&backend, None, 2).await;
565
- assert_entries(&first_page, &[(b"a", b"1"), (b"b", b"2")]);
566
- assert_eq!(first_page.resume_after, Some(b"b".to_vec()));
567
-
568
- let second_page =
569
- scan_entries_request(&backend, first_page.resume_after.as_deref(), 2).await;
570
- assert_entries(&second_page, &[(b"c", b"3")]);
571
- assert_eq!(second_page.resume_after, None);
572
- }
573
-
574
- #[tokio::test]
575
- async fn scan_exact_page_size_has_no_resume_after() {
576
- let backend = UnitTestBackend::new();
577
- let mut transaction = backend
578
- .begin_write_transaction()
579
- .await
580
- .expect("transaction should open");
581
- put(transaction.as_mut(), "ns", b"a", b"1").await;
582
- put(transaction.as_mut(), "ns", b"b", b"2").await;
583
- transaction.commit().await.unwrap();
584
-
585
- let page = scan_entries_request(&backend, None, 2).await;
586
- assert_entries(&page, &[(b"a", b"1"), (b"b", b"2")]);
587
- assert_eq!(page.resume_after, None);
588
- }
589
-
590
- #[tokio::test]
591
- async fn key_only_scan_omits_values() {
592
- let backend = UnitTestBackend::new();
593
- let mut transaction = backend
594
- .begin_write_transaction()
595
- .await
596
- .expect("transaction should open");
597
- put(transaction.as_mut(), "ns", b"a", b"1").await;
598
- put(transaction.as_mut(), "ns", b"b", b"2").await;
599
- transaction.commit().await.unwrap();
600
-
601
- let page = scan_keys_request(&backend, None, 2).await;
602
- assert_eq!(page.keys.iter().collect::<Vec<_>>(), vec![b"a", b"b"]);
603
- assert_eq!(page.resume_after, None);
604
- }
605
-
606
- #[tokio::test]
607
- async fn existence_get_omits_values() {
608
- let backend = UnitTestBackend::new();
609
- let mut transaction = backend
610
- .begin_write_transaction()
611
- .await
612
- .expect("transaction should open");
613
- put(transaction.as_mut(), "ns", b"a", b"1").await;
614
- transaction.commit().await.unwrap();
615
-
616
- let mut transaction = backend
617
- .begin_read_transaction()
618
- .await
619
- .expect("read transaction should open");
620
- let result = transaction
621
- .exists_many(BackendKvGetRequest {
622
- groups: vec![BackendKvGetGroup {
623
- namespace: "ns".to_string(),
624
- keys: vec![b"a".to_vec(), b"missing".to_vec()],
625
- }],
626
- })
627
- .await
628
- .expect("existence get should succeed");
629
- transaction
630
- .rollback()
631
- .await
632
- .expect("rollback should succeed");
633
-
634
- assert_eq!(result.groups[0].exists, vec![true, false]);
635
- }
636
-
637
- #[tokio::test]
638
- async fn range_scan_is_half_open() {
639
- let backend = UnitTestBackend::new();
640
- let mut transaction = backend
641
- .begin_write_transaction()
642
- .await
643
- .expect("transaction should open");
644
- put(transaction.as_mut(), "ns", b"a", b"a").await;
645
- put(transaction.as_mut(), "ns", b"b", b"b").await;
646
- put(transaction.as_mut(), "ns", b"c", b"c").await;
647
- transaction.commit().await.unwrap();
648
-
649
- let pairs = scan(
650
- &backend,
651
- "ns",
652
- BackendKvScanRange::range(b"a", b"c"),
653
- usize::MAX,
654
- )
655
- .await;
656
- assert_entries(&pairs, &[(b"a", b"a"), (b"b", b"b")]);
657
- }
658
- }