@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,26 @@
1
+ use bytes::Bytes;
2
+
3
+ use crate::backend::{Key, PutBatch, PutEntry, SpaceId, StoredValue};
4
+
5
+ pub fn space(id: u32) -> SpaceId {
6
+ SpaceId(id)
7
+ }
8
+
9
+ pub fn key(bytes: impl AsRef<[u8]>) -> Key {
10
+ Key(Bytes::copy_from_slice(bytes.as_ref()))
11
+ }
12
+
13
+ pub fn full_put(key: Key, value: impl Into<Bytes>) -> PutEntry {
14
+ PutEntry {
15
+ key,
16
+ value: StoredValue {
17
+ bytes: value.into(),
18
+ },
19
+ }
20
+ }
21
+
22
+ pub fn put_batch(entries: impl IntoIterator<Item = PutEntry>) -> PutBatch {
23
+ PutBatch {
24
+ entries: entries.into_iter().collect(),
25
+ }
26
+ }
@@ -0,0 +1,75 @@
1
+ //! Conformance harness for backend implementations.
2
+ //!
3
+ //! The harness is colocated with the experimental API for now. Once backend
4
+ //! is stable, rs-sdk can re-export this as the public backend author test kit.
5
+
6
+ mod baseline;
7
+ mod factory;
8
+ #[cfg(test)]
9
+ mod failure_tests;
10
+ #[allow(dead_code)]
11
+ mod fixtures;
12
+ #[allow(dead_code)]
13
+ mod model;
14
+ mod model_based;
15
+ mod persistence;
16
+ mod projection;
17
+ mod pushdown;
18
+ mod runner;
19
+ mod scan;
20
+ mod write;
21
+
22
+ pub(crate) use factory::open_backend;
23
+ pub use factory::{BackendFactory, BackendFixture, BackendTestConfig};
24
+ pub use runner::{
25
+ run_backend_conformance, ConformanceReport, ConformanceResult, ConformanceStatus,
26
+ ConformanceTest,
27
+ };
28
+
29
+ #[cfg(test)]
30
+ mod tests {
31
+ use super::{run_backend_conformance, ConformanceStatus};
32
+ use crate::backend::InMemoryBackendFactory;
33
+
34
+ #[test]
35
+ fn in_memory_backend_passes_baseline_conformance() {
36
+ let report = run_backend_conformance(&InMemoryBackendFactory);
37
+
38
+ report.assert_no_failures();
39
+
40
+ let passed = report
41
+ .tests
42
+ .iter()
43
+ .filter(|test| matches!(test.status, ConformanceStatus::Passed))
44
+ .map(|test| test.name)
45
+ .collect::<Vec<_>>();
46
+ assert_eq!(
47
+ passed,
48
+ vec![
49
+ "baseline::get_many_returns_requested_slots",
50
+ "baseline::get_many_empty_key_list",
51
+ "baseline::delete_many_missing_keys_is_idempotent",
52
+ "baseline::delete_many_removes_existing_keys",
53
+ "baseline::delete_range_removes_exact_range",
54
+ "baseline::delete_range_applies_after_staged_puts",
55
+ "baseline::put_many_applies_after_delete_range",
56
+ "baseline::put_many_overwrites_existing_value",
57
+ "baseline::scan_range_sees_overwritten_existing_value",
58
+ "baseline::scan_range_returns_forward_row_bounded_chunks",
59
+ "baseline::scan_range_honors_bound_variants",
60
+ "baseline::scan_range_resume_before_lower_does_not_widen_range",
61
+ "baseline::scan_range_orders_raw_byte_keys",
62
+ "baseline::scan_range_drains_multi_chunk_limits",
63
+ "baseline::scan_cursor_drains_multi_chunk_limits",
64
+ "baseline::scan_range_empty_range_returns_empty_chunk",
65
+ "baseline::commit_is_atomic",
66
+ "baseline::rollback_discards_staged_mutations",
67
+ "baseline::rollback_discards_overwrite_and_delete",
68
+ "baseline::begin_read_pins_coherent_view",
69
+ "baseline::full_value_and_key_only_are_core",
70
+ "baseline::full_value_preserves_opaque_bytes",
71
+ "model::deterministic_history_matches_reference_model",
72
+ ]
73
+ );
74
+ }
75
+ }
@@ -0,0 +1,28 @@
1
+ use std::collections::BTreeMap;
2
+
3
+ use bytes::Bytes;
4
+
5
+ use crate::backend::Key;
6
+
7
+ #[derive(Clone, Debug, Default, PartialEq, Eq)]
8
+ pub struct ReferenceModel {
9
+ entries: BTreeMap<Key, Bytes>,
10
+ }
11
+
12
+ impl ReferenceModel {
13
+ pub fn get(&self, key: &Key) -> Option<&Bytes> {
14
+ self.entries.get(key)
15
+ }
16
+
17
+ pub fn put(&mut self, key: Key, value: Bytes) {
18
+ self.entries.insert(key, value);
19
+ }
20
+
21
+ pub fn delete(&mut self, key: &Key) {
22
+ self.entries.remove(key);
23
+ }
24
+
25
+ pub fn iter(&self) -> impl Iterator<Item = (&Key, &Bytes)> {
26
+ self.entries.iter()
27
+ }
28
+ }
@@ -0,0 +1,257 @@
1
+ use std::collections::BTreeMap;
2
+ use std::ops::Bound;
3
+
4
+ use bytes::Bytes;
5
+
6
+ use crate::backend::conformance::{
7
+ fixtures::{full_put, key, put_batch},
8
+ model::ReferenceModel,
9
+ open_backend, BackendFactory, ConformanceReport, ConformanceResult,
10
+ };
11
+ use crate::backend::{
12
+ get_many as backend_get_many, visit_range as backend_visit_range, Backend, BackendRead,
13
+ BackendWrite, GetOptions, Key, KeyRange, KeyRef, ProjectedValue, ProjectedValueRef, ReadEntry,
14
+ ReadOptions, ScanChunk, ScanOptions, WriteOptions,
15
+ };
16
+
17
+ pub(crate) fn register<F>(report: &mut ConformanceReport, factory: &F)
18
+ where
19
+ F: BackendFactory,
20
+ {
21
+ report.run(
22
+ "model::deterministic_history_matches_reference_model",
23
+ || deterministic_history_matches_reference_model(factory),
24
+ );
25
+ }
26
+
27
+ fn deterministic_history_matches_reference_model<F>(factory: &F) -> ConformanceResult
28
+ where
29
+ F: BackendFactory,
30
+ {
31
+ let backend = open_backend(factory);
32
+ let mut model = ReferenceModel::default();
33
+ let mut rng = TinyRng::new(0x51cedeed);
34
+ let keys = [
35
+ key("a"),
36
+ key("b"),
37
+ key("c"),
38
+ key("d"),
39
+ key("aa"),
40
+ key("ab"),
41
+ key("ba"),
42
+ ];
43
+
44
+ for step in 0..48 {
45
+ let old_read = backend
46
+ .begin_read(ReadOptions::default())
47
+ .map_err(|error| format!("step {step}: begin old read failed: {error}"))?;
48
+ let old_model = model.clone();
49
+ let mut write = backend
50
+ .begin_write(WriteOptions::default())
51
+ .map_err(|error| format!("step {step}: begin write failed: {error}"))?;
52
+ let mut staged = model.clone();
53
+
54
+ let mutation_count = 1 + rng.usize(4);
55
+ for mutation_index in 0..mutation_count {
56
+ let target_key = keys[rng.usize(keys.len())].clone();
57
+ if rng.bool() {
58
+ let value = Bytes::from(format!("v{step}-{mutation_index}"));
59
+ write
60
+ .put_many(put_batch([full_put(target_key.clone(), value.clone())]))
61
+ .map_err(|error| format!("step {step}: put_many failed: {error}"))?;
62
+ staged.put(target_key, value);
63
+ } else {
64
+ write
65
+ .delete_many(&[target_key.clone()])
66
+ .map_err(|error| format!("step {step}: delete_many failed: {error}"))?;
67
+ staged.delete(&target_key);
68
+ }
69
+ }
70
+
71
+ if rng.bool() {
72
+ write
73
+ .commit()
74
+ .map_err(|error| format!("step {step}: commit failed: {error}"))?;
75
+ model = staged;
76
+ } else {
77
+ write
78
+ .rollback()
79
+ .map_err(|error| format!("step {step}: rollback failed: {error}"))?;
80
+ }
81
+
82
+ compare_read_to_model(
83
+ &old_read,
84
+ &old_model,
85
+ &keys,
86
+ &mut rng,
87
+ &format!("step {step} old snapshot"),
88
+ )?;
89
+
90
+ let new_read = backend
91
+ .begin_read(ReadOptions::default())
92
+ .map_err(|error| format!("step {step}: begin new read failed: {error}"))?;
93
+ compare_read_to_model(
94
+ &new_read,
95
+ &model,
96
+ &keys,
97
+ &mut rng,
98
+ &format!("step {step} new snapshot"),
99
+ )?;
100
+ }
101
+
102
+ Ok(())
103
+ }
104
+
105
+ fn compare_read_to_model<R>(
106
+ read: &R,
107
+ model: &ReferenceModel,
108
+ keys: &[Key],
109
+ rng: &mut TinyRng,
110
+ label: &str,
111
+ ) -> ConformanceResult
112
+ where
113
+ R: BackendRead,
114
+ {
115
+ let point_keys = [
116
+ keys[rng.usize(keys.len())].clone(),
117
+ keys[rng.usize(keys.len())].clone(),
118
+ key("missing"),
119
+ keys[rng.usize(keys.len())].clone(),
120
+ ];
121
+ let result = backend_get_many(read, &point_keys, GetOptions::default())
122
+ .map_err(|error| format!("{label}: get_many failed: {error}"))?;
123
+ let actual = entries_to_map(&result.entries_for_requested_keys(&point_keys));
124
+ let expected = point_keys
125
+ .iter()
126
+ .filter_map(|key| model.get(key).map(|value| (key.clone(), value.clone())))
127
+ .collect::<BTreeMap<_, _>>();
128
+ if actual != expected {
129
+ return Err(format!(
130
+ "{label}: get_many mismatch: expected {expected:?}, got {actual:?}"
131
+ ));
132
+ }
133
+
134
+ let lower_key = keys[rng.usize(keys.len())].clone();
135
+ let upper_key = keys[rng.usize(keys.len())].clone();
136
+ let (lower, upper) = if lower_key <= upper_key {
137
+ (lower_key, upper_key)
138
+ } else {
139
+ (upper_key, lower_key)
140
+ };
141
+ let range = KeyRange {
142
+ lower: Bound::Included(lower),
143
+ upper: Bound::Included(upper),
144
+ };
145
+ let chunk = scan_range(
146
+ read,
147
+ range.clone(),
148
+ ScanOptions {
149
+ limit_rows: 3,
150
+ ..Default::default()
151
+ },
152
+ )
153
+ .map_err(|error| format!("{label}: scan_range failed: {error}"))?;
154
+ let actual_scan = chunk_entries(&chunk.entries);
155
+ let expected_scan = model_scan(model, &range, Some(3));
156
+ if actual_scan != expected_scan {
157
+ return Err(format!(
158
+ "{label}: scan_range mismatch: expected {expected_scan:?}, got {actual_scan:?}"
159
+ ));
160
+ }
161
+
162
+ Ok(())
163
+ }
164
+
165
+ fn scan_range<R>(
166
+ read: &R,
167
+ range: KeyRange,
168
+ opts: ScanOptions<'_>,
169
+ ) -> Result<ScanChunk, crate::backend::BackendError>
170
+ where
171
+ R: BackendRead,
172
+ {
173
+ let mut entries = Vec::with_capacity(opts.limit_rows);
174
+ let result = backend_visit_range(
175
+ read,
176
+ range,
177
+ opts,
178
+ &mut |key: KeyRef<'_>, value: ProjectedValueRef<'_>| {
179
+ entries.push(ReadEntry {
180
+ key: key.to_owned_key(),
181
+ value: value.to_owned(),
182
+ });
183
+ Ok(())
184
+ },
185
+ )?;
186
+ Ok(ScanChunk {
187
+ entries,
188
+ has_more: result.has_more,
189
+ })
190
+ }
191
+
192
+ fn model_scan(model: &ReferenceModel, range: &KeyRange, limit: Option<usize>) -> Vec<(Key, Bytes)> {
193
+ model
194
+ .iter()
195
+ .filter(|(key, _)| range_contains(range, key))
196
+ .take(limit.unwrap_or(usize::MAX))
197
+ .map(|(key, value)| (key.clone(), value.clone()))
198
+ .collect()
199
+ }
200
+
201
+ fn range_contains(range: &KeyRange, key: &Key) -> bool {
202
+ let lower_matches = match &range.lower {
203
+ Bound::Included(lower) => key >= lower,
204
+ Bound::Excluded(lower) => key > lower,
205
+ Bound::Unbounded => true,
206
+ };
207
+ let upper_matches = match &range.upper {
208
+ Bound::Included(upper) => key <= upper,
209
+ Bound::Excluded(upper) => key < upper,
210
+ Bound::Unbounded => true,
211
+ };
212
+ lower_matches && upper_matches
213
+ }
214
+
215
+ fn chunk_entries(entries: &[crate::backend::ReadEntry]) -> Vec<(Key, Bytes)> {
216
+ entries
217
+ .iter()
218
+ .map(|entry| (entry.key.clone(), projected_value_bytes(&entry.value)))
219
+ .collect()
220
+ }
221
+
222
+ fn entries_to_map(entries: &[crate::backend::ReadEntry]) -> BTreeMap<Key, Bytes> {
223
+ chunk_entries(entries).into_iter().collect()
224
+ }
225
+
226
+ fn projected_value_bytes(value: &ProjectedValue) -> Bytes {
227
+ match value {
228
+ ProjectedValue::FullValue(bytes) => bytes.clone(),
229
+ ProjectedValue::KeyOnly => Bytes::new(),
230
+ }
231
+ }
232
+
233
+ struct TinyRng {
234
+ state: u64,
235
+ }
236
+
237
+ impl TinyRng {
238
+ fn new(seed: u64) -> Self {
239
+ Self { state: seed }
240
+ }
241
+
242
+ fn next(&mut self) -> u64 {
243
+ self.state = self
244
+ .state
245
+ .wrapping_mul(6364136223846793005)
246
+ .wrapping_add(1442695040888963407);
247
+ self.state
248
+ }
249
+
250
+ fn usize(&mut self, upper: usize) -> usize {
251
+ (self.next() as usize) % upper
252
+ }
253
+
254
+ fn bool(&mut self) -> bool {
255
+ self.next() & 1 == 0
256
+ }
257
+ }
@@ -0,0 +1,204 @@
1
+ use crate::backend::conformance::{
2
+ fixtures::{full_put, key, put_batch, space},
3
+ BackendFactory, BackendFixture, ConformanceReport, ConformanceResult,
4
+ };
5
+ use crate::backend::{
6
+ get_many as backend_get_many, Backend, BackendWrite, GetOptions, ProjectedValue, ReadOptions,
7
+ WriteOptions,
8
+ };
9
+
10
+ pub(crate) fn register<F>(report: &mut ConformanceReport, factory: &F)
11
+ where
12
+ F: BackendFactory,
13
+ {
14
+ report.run(
15
+ "persistence::durable_write_lock_is_shared_across_reopen",
16
+ || durable_write_lock_is_shared_across_reopen(factory),
17
+ );
18
+ report.run("persistence::committed_data_survives_reopen", || {
19
+ committed_data_survives_reopen(factory)
20
+ });
21
+ report.run(
22
+ "persistence::rolled_back_data_does_not_survive_reopen",
23
+ || rolled_back_data_does_not_survive_reopen(factory),
24
+ );
25
+ report.run(
26
+ "persistence::overwrite_and_delete_final_state_survives_reopen",
27
+ || overwrite_and_delete_final_state_survives_reopen(factory),
28
+ );
29
+ }
30
+
31
+ fn durable_write_lock_is_shared_across_reopen<F>(factory: &F) -> ConformanceResult
32
+ where
33
+ F: BackendFactory,
34
+ {
35
+ let fixture = factory.create_fixture();
36
+ let first_lock = {
37
+ let first = fixture.open();
38
+ first.durable_write_lock()
39
+ };
40
+ let second_lock = {
41
+ let second = fixture.open();
42
+ second.durable_write_lock()
43
+ };
44
+ if !first_lock.ptr_eq(&second_lock) {
45
+ return Err(
46
+ "durable_write_lock must return the same lock for reopened handles to one durable target"
47
+ .to_string(),
48
+ );
49
+ }
50
+ Ok(())
51
+ }
52
+
53
+ fn committed_data_survives_reopen<F>(factory: &F) -> ConformanceResult
54
+ where
55
+ F: BackendFactory,
56
+ {
57
+ let fixture = factory.create_fixture();
58
+ let test_space = space(71);
59
+ let alpha = key("alpha");
60
+ let beta = key("beta");
61
+
62
+ {
63
+ let backend = fixture.open();
64
+ let mut write = backend
65
+ .begin_write(WriteOptions::default())
66
+ .map_err(|error| format!("begin_write failed: {error}"))?;
67
+ write
68
+ .put_many(put_batch([
69
+ full_put(alpha.clone(), "persisted-alpha"),
70
+ full_put(beta.clone(), "persisted-beta"),
71
+ ]))
72
+ .map_err(|error| format!("put_many failed: {error}"))?;
73
+ write
74
+ .commit()
75
+ .map_err(|error| format!("commit failed: {error}"))?;
76
+ }
77
+
78
+ let reopened = fixture.open();
79
+ assert_full_values(
80
+ &reopened,
81
+ test_space,
82
+ &[
83
+ (alpha, Some("persisted-alpha")),
84
+ (beta, Some("persisted-beta")),
85
+ ],
86
+ )
87
+ }
88
+
89
+ fn rolled_back_data_does_not_survive_reopen<F>(factory: &F) -> ConformanceResult
90
+ where
91
+ F: BackendFactory,
92
+ {
93
+ let fixture = factory.create_fixture();
94
+ let test_space = space(72);
95
+ let rolled_back = key("rolled-back");
96
+
97
+ {
98
+ let backend = fixture.open();
99
+ let mut write = backend
100
+ .begin_write(WriteOptions::default())
101
+ .map_err(|error| format!("begin_write failed: {error}"))?;
102
+ write
103
+ .put_many(put_batch([full_put(
104
+ rolled_back.clone(),
105
+ "should-not-persist",
106
+ )]))
107
+ .map_err(|error| format!("put_many failed: {error}"))?;
108
+ write
109
+ .rollback()
110
+ .map_err(|error| format!("rollback failed: {error}"))?;
111
+ }
112
+
113
+ let reopened = fixture.open();
114
+ assert_full_values(&reopened, test_space, &[(rolled_back, None)])
115
+ }
116
+
117
+ fn overwrite_and_delete_final_state_survives_reopen<F>(factory: &F) -> ConformanceResult
118
+ where
119
+ F: BackendFactory,
120
+ {
121
+ let fixture = factory.create_fixture();
122
+ let test_space = space(73);
123
+ let overwritten = key("overwritten");
124
+ let deleted = key("deleted");
125
+
126
+ {
127
+ let backend = fixture.open();
128
+ let mut write = backend
129
+ .begin_write(WriteOptions::default())
130
+ .map_err(|error| format!("begin_write failed: {error}"))?;
131
+ write
132
+ .put_many(put_batch([
133
+ full_put(overwritten.clone(), "old"),
134
+ full_put(deleted.clone(), "delete-me"),
135
+ ]))
136
+ .map_err(|error| format!("initial put_many failed: {error}"))?;
137
+ write
138
+ .commit()
139
+ .map_err(|error| format!("initial commit failed: {error}"))?;
140
+ }
141
+
142
+ {
143
+ let backend = fixture.open();
144
+ let mut write = backend
145
+ .begin_write(WriteOptions::default())
146
+ .map_err(|error| format!("begin_write failed: {error}"))?;
147
+ write
148
+ .put_many(put_batch([full_put(overwritten.clone(), "new")]))
149
+ .map_err(|error| format!("overwrite put_many failed: {error}"))?;
150
+ write
151
+ .delete_many(&[deleted.clone()])
152
+ .map_err(|error| format!("delete_many failed: {error}"))?;
153
+ write
154
+ .commit()
155
+ .map_err(|error| format!("final commit failed: {error}"))?;
156
+ }
157
+
158
+ let reopened = fixture.open();
159
+ assert_full_values(
160
+ &reopened,
161
+ test_space,
162
+ &[(overwritten, Some("new")), (deleted, None)],
163
+ )
164
+ }
165
+
166
+ fn assert_full_values<B>(
167
+ backend: &B,
168
+ _test_space: crate::backend::SpaceId,
169
+ expected: &[(crate::backend::Key, Option<&str>)],
170
+ ) -> ConformanceResult
171
+ where
172
+ B: Backend,
173
+ {
174
+ let keys = expected
175
+ .iter()
176
+ .map(|(key, _)| key.clone())
177
+ .collect::<Vec<_>>();
178
+ let read = backend
179
+ .begin_read(ReadOptions::default())
180
+ .map_err(|error| format!("begin_read failed: {error}"))?;
181
+ let result = backend_get_many(&read, &keys, GetOptions::default())
182
+ .map_err(|error| format!("get_many failed: {error}"))?;
183
+
184
+ for (index, (key, expected_value)) in expected.iter().enumerate() {
185
+ let actual = match result.values.get(index).and_then(|value| value.as_ref()) {
186
+ Some(ProjectedValue::FullValue(bytes)) => Some(
187
+ std::str::from_utf8(bytes.as_ref())
188
+ .map_err(|error| format!("slot {index} contained non-utf8 bytes: {error}"))?,
189
+ ),
190
+ Some(other) => {
191
+ return Err(format!("slot {index} returned unexpected value: {other:?}"));
192
+ }
193
+ None => None,
194
+ };
195
+ if actual != *expected_value {
196
+ return Err(format!(
197
+ "key {:?} value mismatch: expected {:?}, got {:?}",
198
+ key, expected_value, actual
199
+ ));
200
+ }
201
+ }
202
+
203
+ Ok(())
204
+ }
@@ -0,0 +1,21 @@
1
+ use crate::backend::conformance::{BackendFactory, ConformanceReport};
2
+
3
+ pub(crate) fn register<F>(report: &mut ConformanceReport, factory: &F)
4
+ where
5
+ F: BackendFactory,
6
+ {
7
+ let capabilities = factory.capabilities();
8
+
9
+ if capabilities.projection.header {
10
+ report.add_pending("projection::header_returns_header_without_payload");
11
+ }
12
+ if capabilities.projection.refs {
13
+ report.add_pending("projection::refs_returns_refs_without_payload");
14
+ }
15
+ if capabilities.projection.header_and_refs {
16
+ report.add_pending("projection::header_and_refs_returns_both_without_payload");
17
+ }
18
+ if capabilities.projection.payload {
19
+ report.add_pending("projection::payload_returns_payload_only");
20
+ }
21
+ }
@@ -0,0 +1,24 @@
1
+ use crate::backend::{
2
+ conformance::{BackendFactory, ConformanceReport},
3
+ PredicateSupportLevel,
4
+ };
5
+
6
+ pub(crate) fn register<F>(report: &mut ConformanceReport, factory: &F)
7
+ where
8
+ F: BackendFactory,
9
+ {
10
+ let capabilities = factory.capabilities();
11
+
12
+ if capabilities.pushdown.key != PredicateSupportLevel::None {
13
+ report.add_pending("pushdown::key_support_metadata_is_truthful");
14
+ }
15
+ if capabilities.pushdown.header != PredicateSupportLevel::None {
16
+ report.add_pending("pushdown::header_support_metadata_is_truthful");
17
+ }
18
+ if capabilities.pushdown.refs != PredicateSupportLevel::None {
19
+ report.add_pending("pushdown::refs_support_metadata_is_truthful");
20
+ }
21
+ if capabilities.pushdown.object_pruning != PredicateSupportLevel::None {
22
+ report.add_pending("pushdown::object_pruning_requires_residual_filtering_when_inexact");
23
+ }
24
+ }