@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
@@ -0,0 +1,93 @@
1
+ use crate::backend::{
2
+ Backend, BackendCapabilities, BackendError, DurableWriteLock, ReadOptions, WriteOptions,
3
+ };
4
+
5
+ pub trait BackendFactory {
6
+ type Backend: Backend;
7
+ type Fixture: BackendFixture<Backend = Self::Backend>;
8
+
9
+ fn create_fixture(&self) -> Self::Fixture;
10
+
11
+ fn capabilities(&self) -> BackendCapabilities {
12
+ self.create_fixture().open().capabilities()
13
+ }
14
+
15
+ fn config(&self) -> BackendTestConfig {
16
+ BackendTestConfig::default()
17
+ }
18
+ }
19
+
20
+ pub trait BackendFixture {
21
+ type Backend: Backend;
22
+
23
+ fn open(&self) -> Self::Backend;
24
+ }
25
+
26
+ pub(crate) struct OpenBackend<F>
27
+ where
28
+ F: BackendFactory,
29
+ {
30
+ _fixture: F::Fixture,
31
+ backend: F::Backend,
32
+ }
33
+
34
+ pub(crate) fn open_backend<F>(factory: &F) -> OpenBackend<F>
35
+ where
36
+ F: BackendFactory,
37
+ {
38
+ let fixture = factory.create_fixture();
39
+ let backend = fixture.open();
40
+ OpenBackend {
41
+ _fixture: fixture,
42
+ backend,
43
+ }
44
+ }
45
+
46
+ impl<F> Backend for OpenBackend<F>
47
+ where
48
+ F: BackendFactory,
49
+ {
50
+ type Read<'a>
51
+ = <F::Backend as Backend>::Read<'a>
52
+ where
53
+ Self: 'a;
54
+ type Write<'a>
55
+ = <F::Backend as Backend>::Write<'a>
56
+ where
57
+ Self: 'a;
58
+
59
+ fn capabilities(&self) -> BackendCapabilities {
60
+ self.backend.capabilities()
61
+ }
62
+
63
+ fn begin_read(&self, opts: ReadOptions) -> Result<Self::Read<'_>, BackendError> {
64
+ self.backend.begin_read(opts)
65
+ }
66
+
67
+ fn begin_write(&self, opts: WriteOptions) -> Result<Self::Write<'_>, BackendError> {
68
+ self.backend.begin_write(opts)
69
+ }
70
+
71
+ fn durable_write_lock(&self) -> DurableWriteLock {
72
+ self.backend.durable_write_lock()
73
+ }
74
+ }
75
+
76
+ #[derive(Clone, Debug, PartialEq, Eq)]
77
+ pub struct BackendTestConfig {
78
+ pub max_key_len: usize,
79
+ pub max_value_len: usize,
80
+ pub ephemeral: bool,
81
+ pub supports_concurrent_writers: bool,
82
+ }
83
+
84
+ impl Default for BackendTestConfig {
85
+ fn default() -> Self {
86
+ Self {
87
+ max_key_len: 256,
88
+ max_value_len: 4096,
89
+ ephemeral: false,
90
+ supports_concurrent_writers: false,
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,608 @@
1
+ use std::collections::{BTreeMap, BTreeSet};
2
+ use std::ops::Bound;
3
+ use std::sync::{Arc, Mutex};
4
+
5
+ use bytes::Bytes;
6
+
7
+ use super::{
8
+ run_backend_conformance, BackendFactory, BackendFixture, BackendTestConfig, ConformanceStatus,
9
+ };
10
+ use crate::backend::{
11
+ Backend, BackendCapabilities, BackendError, BackendRead, BackendWrite, BufferedRangeScan,
12
+ CommitResult, CoreProjection, DurableWriteLock, GetOptions, Key, KeyRange, KeyRef,
13
+ PointVisitor, ProjectedValueRef, ProjectionCapabilities, PutBatch, ReadEntry, ReadOptions,
14
+ ScanOptions, ScanResult, ScanVisitor, StoredValue, WriteConcurrency, WriteOptions, WriteStats,
15
+ };
16
+
17
+ type BrokenMap = BTreeMap<Key, Bytes>;
18
+
19
+ #[derive(Clone, Copy, Debug)]
20
+ enum BrokenMode {
21
+ GetManyMissesExistingKey,
22
+ ReadSeesLaterCommits,
23
+ ReadSeesSecondLaterCommit,
24
+ ScanReadSeesLaterCommits,
25
+ DeleteManyIgnoresExistingKeys,
26
+ DeleteRangeIgnoresUpperBound,
27
+ KeyOnlyScanReturnsFullValues,
28
+ AdvertisesPendingCapability,
29
+ RollbackCommits,
30
+ BadByteOrdering,
31
+ KeyResumeRepeatsLastKey,
32
+ LoseCommittedDataOnReopen,
33
+ ReopenReturnsFreshDurableWriteLock,
34
+ CorruptOpaqueBytes,
35
+ }
36
+
37
+ #[derive(Clone, Debug)]
38
+ struct BrokenBackendFactory {
39
+ mode: BrokenMode,
40
+ }
41
+
42
+ #[derive(Clone, Debug)]
43
+ struct BrokenBackendFixture {
44
+ mode: BrokenMode,
45
+ entries: Arc<Mutex<BrokenMap>>,
46
+ commit_count: Arc<Mutex<u64>>,
47
+ open_count: Arc<Mutex<u64>>,
48
+ durable_write_lock: DurableWriteLock,
49
+ }
50
+
51
+ #[derive(Clone, Debug)]
52
+ struct BrokenBackend {
53
+ mode: BrokenMode,
54
+ entries: Arc<Mutex<BrokenMap>>,
55
+ commit_count: Arc<Mutex<u64>>,
56
+ durable_write_lock: DurableWriteLock,
57
+ }
58
+
59
+ #[derive(Clone)]
60
+ struct BrokenRead {
61
+ mode: BrokenMode,
62
+ parent: Arc<Mutex<BrokenMap>>,
63
+ commit_count: Arc<Mutex<u64>>,
64
+ snapshot_commit_count: u64,
65
+ snapshot: BrokenMap,
66
+ }
67
+
68
+ struct BrokenWrite {
69
+ mode: BrokenMode,
70
+ parent: Arc<Mutex<BrokenMap>>,
71
+ commit_count: Arc<Mutex<u64>>,
72
+ staged: BrokenMap,
73
+ }
74
+
75
+ #[test]
76
+ fn detects_get_many_missing_existing_key_violation() {
77
+ assert_failed(
78
+ BrokenMode::GetManyMissesExistingKey,
79
+ "baseline::get_many_returns_requested_slots",
80
+ );
81
+ }
82
+
83
+ #[test]
84
+ fn detects_read_snapshot_violation() {
85
+ assert_failed(
86
+ BrokenMode::ReadSeesLaterCommits,
87
+ "baseline::begin_read_pins_coherent_view",
88
+ );
89
+ }
90
+
91
+ #[test]
92
+ fn detects_read_snapshot_second_commit_violation() {
93
+ assert_failed(
94
+ BrokenMode::ReadSeesSecondLaterCommit,
95
+ "baseline::begin_read_pins_coherent_view",
96
+ );
97
+ }
98
+
99
+ #[test]
100
+ fn detects_scan_read_snapshot_violation() {
101
+ assert_failed(
102
+ BrokenMode::ScanReadSeesLaterCommits,
103
+ "baseline::begin_read_pins_coherent_view",
104
+ );
105
+ }
106
+
107
+ #[test]
108
+ fn detects_delete_many_ignores_existing_keys() {
109
+ assert_failed(
110
+ BrokenMode::DeleteManyIgnoresExistingKeys,
111
+ "baseline::delete_many_removes_existing_keys",
112
+ );
113
+ }
114
+
115
+ #[test]
116
+ fn detects_delete_range_ignores_upper_bound() {
117
+ assert_failed(
118
+ BrokenMode::DeleteRangeIgnoresUpperBound,
119
+ "baseline::delete_range_removes_exact_range",
120
+ );
121
+ }
122
+
123
+ #[test]
124
+ fn detects_key_only_scan_projection_violation() {
125
+ assert_failed(
126
+ BrokenMode::KeyOnlyScanReturnsFullValues,
127
+ "baseline::full_value_and_key_only_are_core",
128
+ );
129
+ }
130
+
131
+ #[test]
132
+ fn detects_advertised_pending_capability() {
133
+ let report = run_backend_conformance(&BrokenBackendFactory {
134
+ mode: BrokenMode::AdvertisesPendingCapability,
135
+ });
136
+ let pending = report.tests.iter().any(|test| {
137
+ test.name == "projection::header_returns_header_without_payload"
138
+ && matches!(test.status, ConformanceStatus::Pending)
139
+ });
140
+ assert!(
141
+ pending,
142
+ "expected advertised projection capability to create pending test, got {report:#?}"
143
+ );
144
+
145
+ let panic = std::panic::catch_unwind(|| report.assert_no_failures());
146
+ assert!(
147
+ panic.is_err(),
148
+ "assert_no_failures should fail when advertised capabilities are pending"
149
+ );
150
+ }
151
+
152
+ #[test]
153
+ fn detects_rollback_commits_violation() {
154
+ assert_failed(
155
+ BrokenMode::RollbackCommits,
156
+ "baseline::rollback_discards_staged_mutations",
157
+ );
158
+ }
159
+
160
+ #[test]
161
+ fn detects_rollback_overwrite_delete_violation() {
162
+ assert_failed(
163
+ BrokenMode::RollbackCommits,
164
+ "baseline::rollback_discards_overwrite_and_delete",
165
+ );
166
+ }
167
+
168
+ #[test]
169
+ fn detects_bad_byte_ordering_violation() {
170
+ assert_failed(
171
+ BrokenMode::BadByteOrdering,
172
+ "baseline::scan_range_orders_raw_byte_keys",
173
+ );
174
+ }
175
+
176
+ #[test]
177
+ fn detects_multi_chunk_drain_repeat_violation() {
178
+ assert_failed(
179
+ BrokenMode::KeyResumeRepeatsLastKey,
180
+ "baseline::scan_range_drains_multi_chunk_limits",
181
+ );
182
+ }
183
+
184
+ #[test]
185
+ fn detects_opaque_byte_corruption_violation() {
186
+ assert_failed(
187
+ BrokenMode::CorruptOpaqueBytes,
188
+ "baseline::full_value_preserves_opaque_bytes",
189
+ );
190
+ }
191
+
192
+ #[test]
193
+ fn detects_persistent_commit_lost_on_reopen_violation() {
194
+ assert_failed(
195
+ BrokenMode::LoseCommittedDataOnReopen,
196
+ "persistence::committed_data_survives_reopen",
197
+ );
198
+ }
199
+
200
+ #[test]
201
+ fn detects_fresh_durable_write_lock_on_reopen_violation() {
202
+ assert_failed(
203
+ BrokenMode::ReopenReturnsFreshDurableWriteLock,
204
+ "persistence::durable_write_lock_is_shared_across_reopen",
205
+ );
206
+ }
207
+
208
+ #[test]
209
+ fn detects_persistent_rollback_on_reopen_violation() {
210
+ assert_failed(
211
+ BrokenMode::RollbackCommits,
212
+ "persistence::rolled_back_data_does_not_survive_reopen",
213
+ );
214
+ }
215
+
216
+ fn assert_failed(mode: BrokenMode, test_name: &'static str) {
217
+ let report = run_backend_conformance(&BrokenBackendFactory { mode });
218
+ let failed = report
219
+ .tests
220
+ .iter()
221
+ .any(|test| test.name == test_name && matches!(test.status, ConformanceStatus::Failed(_)));
222
+ assert!(
223
+ failed,
224
+ "expected {test_name} to fail for {mode:?}, got {:#?}",
225
+ report
226
+ );
227
+ }
228
+
229
+ impl BackendFactory for BrokenBackendFactory {
230
+ type Backend = BrokenBackend;
231
+ type Fixture = BrokenBackendFixture;
232
+
233
+ fn create_fixture(&self) -> Self::Fixture {
234
+ BrokenBackendFixture {
235
+ mode: self.mode,
236
+ entries: Arc::new(Mutex::new(BrokenMap::new())),
237
+ commit_count: Arc::new(Mutex::new(0)),
238
+ open_count: Arc::new(Mutex::new(0)),
239
+ durable_write_lock: DurableWriteLock::new(),
240
+ }
241
+ }
242
+
243
+ fn config(&self) -> BackendTestConfig {
244
+ BackendTestConfig::default()
245
+ }
246
+ }
247
+
248
+ impl BackendFixture for BrokenBackendFixture {
249
+ type Backend = BrokenBackend;
250
+
251
+ fn open(&self) -> Self::Backend {
252
+ let mut open_count = self
253
+ .open_count
254
+ .lock()
255
+ .expect("broken backend open count lock poisoned");
256
+ if matches!(self.mode, BrokenMode::LoseCommittedDataOnReopen) && *open_count > 0 {
257
+ self.entries
258
+ .lock()
259
+ .expect("broken backend entries lock poisoned")
260
+ .clear();
261
+ }
262
+ *open_count += 1;
263
+ let durable_write_lock =
264
+ if matches!(self.mode, BrokenMode::ReopenReturnsFreshDurableWriteLock) {
265
+ DurableWriteLock::new()
266
+ } else {
267
+ self.durable_write_lock.clone()
268
+ };
269
+ BrokenBackend {
270
+ mode: self.mode,
271
+ entries: Arc::clone(&self.entries),
272
+ commit_count: Arc::clone(&self.commit_count),
273
+ durable_write_lock,
274
+ }
275
+ }
276
+ }
277
+
278
+ impl Backend for BrokenBackend {
279
+ type Read<'a>
280
+ = BrokenRead
281
+ where
282
+ Self: 'a;
283
+
284
+ type Write<'a>
285
+ = BrokenWrite
286
+ where
287
+ Self: 'a;
288
+
289
+ fn capabilities(&self) -> BackendCapabilities {
290
+ let mut capabilities = BackendCapabilities::v0(WriteConcurrency::SingleWriter);
291
+ if matches!(self.mode, BrokenMode::AdvertisesPendingCapability) {
292
+ capabilities.projection = ProjectionCapabilities {
293
+ header: true,
294
+ ..ProjectionCapabilities::default()
295
+ };
296
+ }
297
+ capabilities
298
+ }
299
+
300
+ fn begin_read(&self, _opts: ReadOptions) -> Result<Self::Read<'_>, BackendError> {
301
+ Ok(BrokenRead {
302
+ mode: self.mode,
303
+ parent: Arc::clone(&self.entries),
304
+ commit_count: Arc::clone(&self.commit_count),
305
+ snapshot_commit_count: *self
306
+ .commit_count
307
+ .lock()
308
+ .map_err(|_| BackendError::Io("broken backend commit lock poisoned".to_string()))?,
309
+ snapshot: self.snapshot()?,
310
+ })
311
+ }
312
+
313
+ fn begin_write(&self, _opts: WriteOptions) -> Result<Self::Write<'_>, BackendError> {
314
+ Ok(BrokenWrite {
315
+ mode: self.mode,
316
+ parent: Arc::clone(&self.entries),
317
+ commit_count: Arc::clone(&self.commit_count),
318
+ staged: self.snapshot()?,
319
+ })
320
+ }
321
+
322
+ fn durable_write_lock(&self) -> DurableWriteLock {
323
+ self.durable_write_lock.clone()
324
+ }
325
+ }
326
+
327
+ impl BackendRead for BrokenRead {
328
+ type RangeScan<'a> = BufferedRangeScan;
329
+
330
+ fn visit_keys<V>(
331
+ &self,
332
+ keys: &[Key],
333
+ opts: GetOptions<'_>,
334
+ visitor: &mut V,
335
+ ) -> Result<(), BackendError>
336
+ where
337
+ V: PointVisitor + ?Sized,
338
+ {
339
+ let live_entries;
340
+ let current_commit_count = *self
341
+ .commit_count
342
+ .lock()
343
+ .map_err(|_| BackendError::Io("broken backend commit lock poisoned".to_string()))?;
344
+ let entries = if matches!(self.mode, BrokenMode::ReadSeesLaterCommits)
345
+ || (matches!(self.mode, BrokenMode::ReadSeesSecondLaterCommit)
346
+ && current_commit_count >= self.snapshot_commit_count + 2)
347
+ {
348
+ live_entries = self
349
+ .parent
350
+ .lock()
351
+ .map_err(|_| BackendError::Io("broken backend lock poisoned".to_string()))?
352
+ .clone();
353
+ &live_entries
354
+ } else {
355
+ &self.snapshot
356
+ };
357
+ visit_keys_from_map(entries, self.mode, keys, opts, visitor)
358
+ }
359
+
360
+ fn with_range_scan<T, F>(
361
+ &self,
362
+ range: KeyRange,
363
+ opts: ScanOptions<'_>,
364
+ f: F,
365
+ ) -> Result<T, BackendError>
366
+ where
367
+ F: FnOnce(&mut Self::RangeScan<'_>) -> Result<T, BackendError>,
368
+ {
369
+ let live_entries;
370
+ let entries = if matches!(self.mode, BrokenMode::ScanReadSeesLaterCommits) {
371
+ live_entries = self
372
+ .parent
373
+ .lock()
374
+ .map_err(|_| BackendError::Io("broken backend lock poisoned".to_string()))?
375
+ .clone();
376
+ &live_entries
377
+ } else {
378
+ &self.snapshot
379
+ };
380
+ let mut cursor =
381
+ BufferedRangeScan::new(scan_rows_from_map(entries, self.mode, range, opts));
382
+ f(&mut cursor)
383
+ }
384
+ }
385
+
386
+ impl BackendWrite for BrokenWrite {
387
+ fn put_many(&mut self, entries: PutBatch) -> Result<(), BackendError> {
388
+ for entry in entries.entries {
389
+ let mut bytes = stored_value_bytes(entry.value);
390
+ if matches!(self.mode, BrokenMode::CorruptOpaqueBytes) {
391
+ bytes = Bytes::from(
392
+ bytes
393
+ .iter()
394
+ .copied()
395
+ .filter(|byte| *byte != 0)
396
+ .collect::<Vec<_>>(),
397
+ );
398
+ }
399
+ self.staged.insert(entry.key, bytes);
400
+ }
401
+ Ok(())
402
+ }
403
+
404
+ fn delete_many(&mut self, keys: &[Key]) -> Result<(), BackendError> {
405
+ for key in keys {
406
+ if matches!(self.mode, BrokenMode::DeleteManyIgnoresExistingKeys)
407
+ && self.staged.contains_key(key)
408
+ {
409
+ continue;
410
+ }
411
+ self.staged.remove(key);
412
+ }
413
+ Ok(())
414
+ }
415
+
416
+ fn delete_range(&mut self, range: KeyRange) -> Result<(), BackendError> {
417
+ if matches!(self.mode, BrokenMode::DeleteRangeIgnoresUpperBound) {
418
+ self.staged.retain(|key, _value| match &range.lower {
419
+ Bound::Included(lower) => key < lower,
420
+ Bound::Excluded(lower) => key <= lower,
421
+ Bound::Unbounded => false,
422
+ });
423
+ } else {
424
+ self.staged
425
+ .retain(|key, _value| !range_contains(&range, key));
426
+ }
427
+ Ok(())
428
+ }
429
+
430
+ fn commit(self) -> Result<CommitResult, BackendError> {
431
+ *self
432
+ .parent
433
+ .lock()
434
+ .map_err(|_| BackendError::Io("broken backend lock poisoned".to_string()))? =
435
+ self.staged;
436
+ *self
437
+ .commit_count
438
+ .lock()
439
+ .map_err(|_| BackendError::Io("broken backend commit lock poisoned".to_string()))? += 1;
440
+ Ok(CommitResult {
441
+ commit_id: None,
442
+ stats: WriteStats::default(),
443
+ })
444
+ }
445
+
446
+ fn rollback(self) -> Result<(), BackendError> {
447
+ if matches!(self.mode, BrokenMode::RollbackCommits) {
448
+ *self
449
+ .parent
450
+ .lock()
451
+ .map_err(|_| BackendError::Io("broken backend lock poisoned".to_string()))? =
452
+ self.staged;
453
+ *self.commit_count.lock().map_err(|_| {
454
+ BackendError::Io("broken backend commit lock poisoned".to_string())
455
+ })? += 1;
456
+ }
457
+ Ok(())
458
+ }
459
+ }
460
+
461
+ impl BrokenBackend {
462
+ fn snapshot(&self) -> Result<BrokenMap, BackendError> {
463
+ self.entries
464
+ .lock()
465
+ .map_err(|_| BackendError::Io("broken backend lock poisoned".to_string()))
466
+ .map(|entries| entries.clone())
467
+ }
468
+ }
469
+
470
+ fn visit_keys_from_map<V>(
471
+ entries: &BrokenMap,
472
+ mode: BrokenMode,
473
+ keys: &[Key],
474
+ opts: GetOptions<'_>,
475
+ visitor: &mut V,
476
+ ) -> Result<(), BackendError>
477
+ where
478
+ V: PointVisitor + ?Sized,
479
+ {
480
+ let mut seen = BTreeSet::new();
481
+ for (index, key) in keys.iter().enumerate() {
482
+ if matches!(mode, BrokenMode::GetManyMissesExistingKey)
483
+ && key == &Key(Bytes::from_static(b"a"))
484
+ {
485
+ visitor.visit(index, key, None)?;
486
+ continue;
487
+ }
488
+ let value = if !seen.insert(key.clone()) {
489
+ entries
490
+ .get(key)
491
+ .map(|value| project_value_ref(value, mode, opts.projection, false))
492
+ } else {
493
+ entries
494
+ .get(key)
495
+ .map(|value| project_value_ref(value, mode, opts.projection, false))
496
+ };
497
+ visitor.visit(index, key, value)?;
498
+ }
499
+ Ok(())
500
+ }
501
+
502
+ fn visit_range_from_map<V>(
503
+ entries: &BrokenMap,
504
+ mode: BrokenMode,
505
+ range: KeyRange,
506
+ opts: ScanOptions<'_>,
507
+ visitor: &mut V,
508
+ ) -> Result<ScanResult, BackendError>
509
+ where
510
+ V: ScanVisitor + ?Sized,
511
+ {
512
+ let mut emitted = 0;
513
+ let mut has_more = false;
514
+ let mut candidates = entries
515
+ .iter()
516
+ .filter(|(key, _)| range_contains(&range, key))
517
+ .collect::<Vec<_>>();
518
+ if matches!(mode, BrokenMode::BadByteOrdering) {
519
+ candidates.sort_by(|left, right| {
520
+ left.0
521
+ .0
522
+ .len()
523
+ .cmp(&right.0 .0.len())
524
+ .then(left.0.cmp(right.0))
525
+ });
526
+ }
527
+
528
+ for (key, value) in candidates {
529
+ if opts.resume_after.is_some_and(|resume_after| {
530
+ if matches!(mode, BrokenMode::KeyResumeRepeatsLastKey) {
531
+ key < resume_after
532
+ } else {
533
+ key <= resume_after
534
+ }
535
+ }) {
536
+ continue;
537
+ }
538
+ if emitted == opts.limit_rows {
539
+ has_more = true;
540
+ break;
541
+ }
542
+ visitor.visit(
543
+ key.as_ref(),
544
+ project_value_ref(value, mode, opts.projection, true),
545
+ )?;
546
+ emitted += 1;
547
+ }
548
+
549
+ Ok(ScanResult { emitted, has_more })
550
+ }
551
+
552
+ fn scan_rows_from_map(
553
+ entries: &BrokenMap,
554
+ mode: BrokenMode,
555
+ range: KeyRange,
556
+ opts: ScanOptions<'_>,
557
+ ) -> Vec<ReadEntry> {
558
+ let mut rows = Vec::new();
559
+ let _ = visit_range_from_map(
560
+ entries,
561
+ mode,
562
+ range,
563
+ opts,
564
+ &mut |key: KeyRef<'_>, value: ProjectedValueRef<'_>| {
565
+ rows.push(ReadEntry {
566
+ key: key.to_owned_key(),
567
+ value: value.to_owned(),
568
+ });
569
+ Ok(())
570
+ },
571
+ );
572
+ rows
573
+ }
574
+
575
+ fn range_contains(range: &KeyRange, key: &Key) -> bool {
576
+ let lower_matches = match &range.lower {
577
+ Bound::Included(lower) => key >= lower,
578
+ Bound::Excluded(lower) => key > lower,
579
+ Bound::Unbounded => true,
580
+ };
581
+ let upper_matches = match &range.upper {
582
+ Bound::Included(upper) => key <= upper,
583
+ Bound::Excluded(upper) => key < upper,
584
+ Bound::Unbounded => true,
585
+ };
586
+ lower_matches && upper_matches
587
+ }
588
+
589
+ fn project_value_ref(
590
+ value: &Bytes,
591
+ mode: BrokenMode,
592
+ projection: CoreProjection,
593
+ break_key_only: bool,
594
+ ) -> ProjectedValueRef<'_> {
595
+ match projection {
596
+ CoreProjection::KeyOnly
597
+ if break_key_only && matches!(mode, BrokenMode::KeyOnlyScanReturnsFullValues) =>
598
+ {
599
+ ProjectedValueRef::FullValue(value.as_ref())
600
+ }
601
+ CoreProjection::KeyOnly => ProjectedValueRef::KeyOnly,
602
+ CoreProjection::FullValue => ProjectedValueRef::FullValue(value.as_ref()),
603
+ }
604
+ }
605
+
606
+ fn stored_value_bytes(value: StoredValue) -> Bytes {
607
+ value.bytes
608
+ }