@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,557 @@
1
+ use std::sync::Arc;
2
+
3
+ use crate::functions::FunctionContext;
4
+ use crate::storage::{InMemoryStorageBackend, StorageBackend};
5
+ use tokio::sync::Notify;
6
+
7
+ use crate::transaction::{
8
+ open_transaction, CommitBoundaryGuard, CommitBoundaryState, Transaction,
9
+ TransactionCommitBoundary,
10
+ };
11
+ use crate::LixError;
12
+
13
+ use super::context::{closed_error, SessionWriteAccess};
14
+ use super::SessionContext;
15
+
16
+ pub struct SessionTransaction<B: StorageBackend = InMemoryStorageBackend> {
17
+ pub(super) transaction: Option<Transaction<B>>,
18
+ pub(super) runtime_functions: FunctionContext,
19
+ transaction_manager: SessionTransactionManager,
20
+ _write_access: SessionWriteAccess,
21
+ }
22
+
23
+ impl<B> SessionContext<B>
24
+ where
25
+ B: StorageBackend + Clone + Send + Sync + 'static,
26
+ for<'backend> B::Read<'backend>: Clone + Send + Sync + 'static,
27
+ for<'backend> B::Write<'backend>: Send,
28
+ {
29
+ pub async fn begin_transaction(&self) -> Result<SessionTransaction<B>, LixError> {
30
+ self.ensure_open()?;
31
+ let write_access = self.begin_explicit_session_write_access().await?;
32
+ let mut opened = match open_transaction(
33
+ &self.mode,
34
+ self.storage.clone(),
35
+ Arc::clone(&self.live_state),
36
+ Arc::clone(&self.tracked_state),
37
+ Arc::clone(&self.binary_cas),
38
+ Arc::clone(&self.branch_ctx),
39
+ Arc::clone(&self.catalog_context),
40
+ )
41
+ .await
42
+ {
43
+ Ok(opened) => opened,
44
+ Err(error) => {
45
+ return Err(error);
46
+ }
47
+ };
48
+ self.ensure_open()?;
49
+ opened
50
+ .transaction
51
+ .attach_commit_boundary(self.transaction_commit_boundary());
52
+ self.transaction_manager()
53
+ .mark_explicit_transaction_open()?;
54
+ Ok(SessionTransaction {
55
+ transaction: Some(opened.transaction),
56
+ runtime_functions: opened.runtime_functions,
57
+ transaction_manager: self.transaction_manager(),
58
+ _write_access: write_access,
59
+ })
60
+ }
61
+ }
62
+
63
+ impl<B> SessionTransaction<B>
64
+ where
65
+ B: StorageBackend + Clone + Send + Sync + 'static,
66
+ for<'backend> B::Read<'backend>: Clone + Send + Sync + 'static,
67
+ for<'backend> B::Write<'backend>: Send,
68
+ {
69
+ pub(super) fn transaction_mut(&mut self) -> Result<&mut Transaction<B>, LixError> {
70
+ self.ensure_session_open()?;
71
+ self.transaction
72
+ .as_mut()
73
+ .ok_or_else(|| transaction_state_error("Lix transaction is closed"))
74
+ }
75
+
76
+ pub fn active_branch_id(&self) -> Result<&str, LixError> {
77
+ self.ensure_session_open()?;
78
+ self.transaction
79
+ .as_ref()
80
+ .map(|transaction| transaction.active_branch_id())
81
+ .ok_or_else(|| transaction_state_error("Lix transaction is closed"))
82
+ }
83
+
84
+ pub async fn commit(mut self) -> Result<(), LixError> {
85
+ let operation_guard = self.begin_session_commit_operation()?;
86
+ let transaction = self
87
+ .transaction
88
+ .take()
89
+ .ok_or_else(|| transaction_state_error("Lix transaction is closed"))?;
90
+ let queued_commit_guard = self.transaction_manager.begin_commit();
91
+ let result = transaction
92
+ .commit(&self.runtime_functions)
93
+ .await
94
+ .map(|_| ());
95
+ drop(queued_commit_guard);
96
+ drop(operation_guard);
97
+ result
98
+ }
99
+
100
+ pub async fn rollback(mut self) -> Result<(), LixError> {
101
+ let transaction = self
102
+ .transaction
103
+ .take()
104
+ .ok_or_else(|| transaction_state_error("Lix transaction is closed"))?;
105
+ let result = transaction.rollback().await;
106
+ result
107
+ }
108
+
109
+ pub(super) fn ensure_session_open(&self) -> Result<(), LixError> {
110
+ self.transaction_manager.ensure_open()
111
+ }
112
+
113
+ pub(super) fn begin_session_operation(&self) -> Result<SessionOperationGuard, LixError> {
114
+ self.transaction_manager.begin_transaction_operation()
115
+ }
116
+
117
+ fn begin_session_commit_operation(&self) -> Result<SessionOperationGuard, LixError> {
118
+ self.transaction_manager
119
+ .begin_transaction_commit_operation()
120
+ }
121
+ }
122
+
123
+ pub(crate) fn transaction_state_error(message: impl Into<String>) -> LixError {
124
+ LixError::new("LIX_INVALID_TRANSACTION_STATE", message)
125
+ }
126
+
127
+ #[derive(Clone)]
128
+ pub(super) struct SessionTransactionManager {
129
+ inner: Arc<SessionTransactionManagerInner>,
130
+ }
131
+
132
+ struct SessionTransactionManagerInner {
133
+ state: std::sync::Mutex<SessionTransactionState>,
134
+ state_changed: Notify,
135
+ commit_boundary: CommitBoundaryState,
136
+ }
137
+
138
+ #[derive(Debug, Default)]
139
+ enum SessionTransactionState {
140
+ #[default]
141
+ OpenIdle,
142
+ OpenOperation {
143
+ active_operations: usize,
144
+ },
145
+ OpenTransaction {
146
+ active_operations: usize,
147
+ owner: TransactionOwner,
148
+ },
149
+ Closing {
150
+ active_operations: usize,
151
+ },
152
+ Closed,
153
+ }
154
+
155
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
156
+ enum TransactionOwner {
157
+ Automatic,
158
+ ExplicitOpening,
159
+ Explicit,
160
+ ExplicitCommitting,
161
+ }
162
+
163
+ impl SessionTransactionManager {
164
+ pub(super) fn new() -> Self {
165
+ Self {
166
+ inner: Arc::new(SessionTransactionManagerInner {
167
+ state: std::sync::Mutex::new(SessionTransactionState::default()),
168
+ state_changed: Notify::new(),
169
+ commit_boundary: CommitBoundaryState::new(),
170
+ }),
171
+ }
172
+ }
173
+
174
+ pub(super) async fn close(&self) -> Result<(), LixError> {
175
+ let mut commit_rx = self.inner.commit_boundary.subscribe();
176
+ loop {
177
+ if let Some(_commit_gate) = self.inner.commit_boundary.try_lock_durable_commit() {
178
+ {
179
+ let mut state = self.lock_state();
180
+ if state.has_explicit_transaction() {
181
+ return Err(active_transaction_error());
182
+ }
183
+ let active_operations = state.active_operations();
184
+ *state = if active_operations == 0 {
185
+ SessionTransactionState::Closed
186
+ } else {
187
+ SessionTransactionState::Closing { active_operations }
188
+ };
189
+ }
190
+ self.inner.state_changed.notify_waiters();
191
+ break;
192
+ }
193
+
194
+ let notified = self.inner.state_changed.notified();
195
+ tokio::select! {
196
+ _ = notified => {}
197
+ result = commit_rx.changed() => {
198
+ if result.is_err() {
199
+ self.inner.state_changed.notify_waiters();
200
+ }
201
+ }
202
+ }
203
+ }
204
+
205
+ loop {
206
+ let notified = self.inner.state_changed.notified();
207
+ {
208
+ let mut state = self.lock_state();
209
+ let commit_count = *commit_rx.borrow_and_update();
210
+ if state.active_operations() == 0
211
+ && commit_count == 0
212
+ && !self.inner.commit_boundary.is_active()
213
+ {
214
+ *state = SessionTransactionState::Closed;
215
+ break;
216
+ }
217
+ }
218
+ tokio::select! {
219
+ _ = notified => {}
220
+ result = commit_rx.changed() => {
221
+ if result.is_err() {
222
+ self.inner.state_changed.notify_waiters();
223
+ }
224
+ }
225
+ }
226
+ }
227
+ Ok(())
228
+ }
229
+
230
+ pub(super) fn is_closed(&self) -> bool {
231
+ self.lock_state().is_closed()
232
+ }
233
+
234
+ pub(super) fn ensure_open(&self) -> Result<(), LixError> {
235
+ if self.is_closed() {
236
+ return Err(closed_error());
237
+ }
238
+ Ok(())
239
+ }
240
+
241
+ pub(super) fn begin_session_operation(&self) -> Result<SessionOperationGuard, LixError> {
242
+ self.begin_operation(SessionOperationScope::Session)
243
+ }
244
+
245
+ pub(super) fn begin_transaction_operation(&self) -> Result<SessionOperationGuard, LixError> {
246
+ self.begin_operation(SessionOperationScope::Transaction)
247
+ }
248
+
249
+ pub(super) fn begin_transaction_commit_operation(
250
+ &self,
251
+ ) -> Result<SessionOperationGuard, LixError> {
252
+ self.begin_operation(SessionOperationScope::TransactionCommit)
253
+ }
254
+
255
+ fn begin_operation(
256
+ &self,
257
+ scope: SessionOperationScope,
258
+ ) -> Result<SessionOperationGuard, LixError> {
259
+ {
260
+ let mut state = self.lock_state();
261
+ state.begin_operation(scope)?;
262
+ }
263
+ self.inner.state_changed.notify_waiters();
264
+
265
+ if let Err(error) = self.ensure_open() {
266
+ self.finish_operation();
267
+ return Err(error);
268
+ }
269
+
270
+ Ok(SessionOperationGuard {
271
+ manager: self.clone(),
272
+ })
273
+ }
274
+
275
+ pub(super) fn begin_write_lease(&self) -> Result<SessionWriteLease, LixError> {
276
+ self.begin_write_lease_for(TransactionOwner::Automatic)
277
+ }
278
+
279
+ pub(super) fn begin_explicit_write_lease(&self) -> Result<SessionWriteLease, LixError> {
280
+ self.begin_write_lease_for(TransactionOwner::ExplicitOpening)
281
+ }
282
+
283
+ fn begin_write_lease_for(
284
+ &self,
285
+ owner: TransactionOwner,
286
+ ) -> Result<SessionWriteLease, LixError> {
287
+ {
288
+ let mut state = self.lock_state();
289
+ state.begin_write_lease(owner)?;
290
+ }
291
+ self.inner.state_changed.notify_waiters();
292
+
293
+ let operation_guard = SessionOperationGuard {
294
+ manager: self.clone(),
295
+ };
296
+ if let Err(error) = self.ensure_open() {
297
+ drop(operation_guard);
298
+ self.finish_transaction();
299
+ return Err(error);
300
+ }
301
+ let transaction_guard = SessionTransactionGuard {
302
+ manager: self.clone(),
303
+ };
304
+ Ok(SessionWriteLease {
305
+ _transaction_guard: transaction_guard,
306
+ _operation_guard: operation_guard,
307
+ })
308
+ }
309
+
310
+ pub(super) fn begin_commit(&self) -> CommitBoundaryGuard {
311
+ self.inner.commit_boundary.begin()
312
+ }
313
+
314
+ pub(super) fn mark_explicit_transaction_open(&self) -> Result<(), LixError> {
315
+ {
316
+ let mut state = self.lock_state();
317
+ state.mark_explicit_transaction_open()?;
318
+ }
319
+ self.inner.state_changed.notify_waiters();
320
+ Ok(())
321
+ }
322
+
323
+ pub(super) fn transaction_commit_boundary(&self) -> TransactionCommitBoundary {
324
+ let manager = self.clone();
325
+ TransactionCommitBoundary::new(
326
+ self.inner.commit_boundary.clone(),
327
+ Arc::new(move || manager.ensure_open()),
328
+ )
329
+ }
330
+
331
+ fn finish_operation(&self) {
332
+ {
333
+ let mut state = self.lock_state();
334
+ state.finish_operation();
335
+ }
336
+ self.inner.state_changed.notify_waiters();
337
+ }
338
+
339
+ fn finish_transaction(&self) {
340
+ {
341
+ let mut state = self.lock_state();
342
+ state.finish_transaction();
343
+ }
344
+ self.inner.state_changed.notify_waiters();
345
+ }
346
+
347
+ fn lock_state(&self) -> std::sync::MutexGuard<'_, SessionTransactionState> {
348
+ self.inner
349
+ .state
350
+ .lock()
351
+ .expect("session transaction manager lock should not poison")
352
+ }
353
+
354
+ #[cfg(test)]
355
+ pub(super) fn operation_count_for_test(&self) -> usize {
356
+ self.lock_state().active_operations()
357
+ }
358
+
359
+ #[cfg(test)]
360
+ pub(super) fn commit_in_progress_for_test(&self) -> bool {
361
+ self.inner.commit_boundary.is_active()
362
+ }
363
+
364
+ #[cfg(test)]
365
+ pub(super) fn active_transaction_for_test(&self) -> bool {
366
+ matches!(
367
+ *self.lock_state(),
368
+ SessionTransactionState::OpenTransaction { .. }
369
+ )
370
+ }
371
+ }
372
+
373
+ impl SessionTransactionState {
374
+ fn is_closed(&self) -> bool {
375
+ matches!(self, Self::Closing { .. } | Self::Closed)
376
+ }
377
+
378
+ fn active_operations(&self) -> usize {
379
+ match self {
380
+ Self::OpenIdle | Self::Closed => 0,
381
+ Self::OpenOperation { active_operations }
382
+ | Self::OpenTransaction {
383
+ active_operations, ..
384
+ }
385
+ | Self::Closing { active_operations } => *active_operations,
386
+ }
387
+ }
388
+
389
+ fn has_explicit_transaction(&self) -> bool {
390
+ matches!(
391
+ self,
392
+ Self::OpenTransaction {
393
+ owner: TransactionOwner::Explicit,
394
+ ..
395
+ }
396
+ )
397
+ }
398
+
399
+ fn begin_operation(&mut self, scope: SessionOperationScope) -> Result<(), LixError> {
400
+ match self {
401
+ Self::OpenIdle => {
402
+ if matches!(scope, SessionOperationScope::Session) {
403
+ *self = Self::OpenOperation {
404
+ active_operations: 1,
405
+ };
406
+ Ok(())
407
+ } else {
408
+ Err(active_transaction_error())
409
+ }
410
+ }
411
+ Self::OpenOperation { active_operations } => {
412
+ if matches!(scope, SessionOperationScope::Session) {
413
+ *active_operations += 1;
414
+ Ok(())
415
+ } else {
416
+ Err(active_transaction_error())
417
+ }
418
+ }
419
+ Self::OpenTransaction {
420
+ active_operations,
421
+ owner,
422
+ } => match scope {
423
+ SessionOperationScope::Session => Err(active_transaction_error()),
424
+ SessionOperationScope::Transaction => {
425
+ *active_operations += 1;
426
+ Ok(())
427
+ }
428
+ SessionOperationScope::TransactionCommit => {
429
+ if *owner != TransactionOwner::Explicit {
430
+ return Err(active_transaction_error());
431
+ }
432
+ *owner = TransactionOwner::ExplicitCommitting;
433
+ *active_operations += 1;
434
+ Ok(())
435
+ }
436
+ },
437
+ Self::Closing { .. } | Self::Closed => Err(closed_error()),
438
+ }
439
+ }
440
+
441
+ fn begin_write_lease(&mut self, owner: TransactionOwner) -> Result<(), LixError> {
442
+ match self {
443
+ Self::OpenIdle => {
444
+ *self = Self::OpenTransaction {
445
+ active_operations: 1,
446
+ owner,
447
+ };
448
+ Ok(())
449
+ }
450
+ Self::OpenOperation { .. } | Self::OpenTransaction { .. } => {
451
+ Err(active_transaction_error())
452
+ }
453
+ Self::Closing { .. } | Self::Closed => Err(closed_error()),
454
+ }
455
+ }
456
+
457
+ fn mark_explicit_transaction_open(&mut self) -> Result<(), LixError> {
458
+ match self {
459
+ Self::OpenTransaction {
460
+ active_operations: 1,
461
+ owner,
462
+ } if *owner == TransactionOwner::ExplicitOpening => {
463
+ *owner = TransactionOwner::Explicit;
464
+ Ok(())
465
+ }
466
+ Self::Closing { .. } | Self::Closed => Err(closed_error()),
467
+ _ => {
468
+ panic!("explicit transaction should be opening before it is marked open");
469
+ }
470
+ }
471
+ }
472
+
473
+ fn finish_operation(&mut self) {
474
+ match self {
475
+ Self::OpenOperation { active_operations } => {
476
+ *active_operations = active_operations
477
+ .checked_sub(1)
478
+ .expect("session operation count should not underflow");
479
+ if *active_operations == 0 {
480
+ *self = Self::OpenIdle;
481
+ }
482
+ }
483
+ Self::OpenTransaction {
484
+ active_operations, ..
485
+ } => {
486
+ *active_operations = active_operations
487
+ .checked_sub(1)
488
+ .expect("session operation count should not underflow");
489
+ }
490
+ Self::Closing { active_operations } => {
491
+ *active_operations = active_operations
492
+ .checked_sub(1)
493
+ .expect("session operation count should not underflow");
494
+ if *active_operations == 0 {
495
+ *self = Self::Closed;
496
+ }
497
+ }
498
+ Self::OpenIdle | Self::Closed => {
499
+ panic!("session operation count should not underflow");
500
+ }
501
+ }
502
+ }
503
+
504
+ fn finish_transaction(&mut self) {
505
+ match self {
506
+ Self::OpenTransaction {
507
+ active_operations: 0,
508
+ ..
509
+ } => {
510
+ *self = Self::OpenIdle;
511
+ }
512
+ Self::OpenTransaction { .. } => {}
513
+ Self::Closing { .. } | Self::Closed => {}
514
+ Self::OpenIdle | Self::OpenOperation { .. } => {
515
+ panic!("session transaction should be active before it is finished");
516
+ }
517
+ }
518
+ }
519
+ }
520
+
521
+ #[derive(Clone, Copy)]
522
+ enum SessionOperationScope {
523
+ Session,
524
+ Transaction,
525
+ TransactionCommit,
526
+ }
527
+
528
+ fn active_transaction_error() -> LixError {
529
+ transaction_state_error(
530
+ "Lix handle has an active transaction; use the transaction handle for reads and writes until it is committed or rolled back",
531
+ )
532
+ }
533
+
534
+ pub(super) struct SessionWriteLease {
535
+ _operation_guard: SessionOperationGuard,
536
+ _transaction_guard: SessionTransactionGuard,
537
+ }
538
+
539
+ pub(super) struct SessionTransactionGuard {
540
+ manager: SessionTransactionManager,
541
+ }
542
+
543
+ impl Drop for SessionTransactionGuard {
544
+ fn drop(&mut self) {
545
+ self.manager.finish_transaction();
546
+ }
547
+ }
548
+
549
+ pub(super) struct SessionOperationGuard {
550
+ manager: SessionTransactionManager,
551
+ }
552
+
553
+ impl Drop for SessionOperationGuard {
554
+ fn drop(&mut self) {
555
+ self.manager.finish_operation();
556
+ }
557
+ }
@@ -0,0 +1,102 @@
1
+ use datafusion::sql::parser::Statement as DataFusionStatement;
2
+ use datafusion::sql::sqlparser::ast::{Query, SetExpr, Statement as SqlStatement};
3
+
4
+ use crate::LixError;
5
+
6
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
7
+ pub(crate) enum SqlStatementKind {
8
+ Read,
9
+ Write,
10
+ Other,
11
+ }
12
+
13
+ pub(crate) fn validate_supported_datafusion_statement_ast(
14
+ statement: &DataFusionStatement,
15
+ ) -> Result<(), LixError> {
16
+ match statement {
17
+ DataFusionStatement::Statement(statement) => validate_supported_ast_statement(statement),
18
+ DataFusionStatement::Explain(explain) => {
19
+ if classify_datafusion_statement(explain.statement.as_ref()) == SqlStatementKind::Write
20
+ {
21
+ return Err(unsupported_sql_error(
22
+ "EXPLAIN of write statements is not supported by Lix SQL",
23
+ ));
24
+ }
25
+ validate_supported_datafusion_statement_ast(explain.statement.as_ref())
26
+ }
27
+ _ => Err(unsupported_sql_error(format!(
28
+ "SQL statement is not supported by Lix SQL: {statement}"
29
+ ))),
30
+ }
31
+ }
32
+
33
+ pub(crate) fn classify_datafusion_statement(statement: &DataFusionStatement) -> SqlStatementKind {
34
+ match statement {
35
+ DataFusionStatement::Statement(statement) => classify_ast_statement(statement),
36
+ DataFusionStatement::Explain(explain) => {
37
+ classify_datafusion_statement(explain.statement.as_ref())
38
+ }
39
+ _ => SqlStatementKind::Other,
40
+ }
41
+ }
42
+
43
+ fn classify_ast_statement(statement: &SqlStatement) -> SqlStatementKind {
44
+ match statement {
45
+ SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => {
46
+ SqlStatementKind::Write
47
+ }
48
+ SqlStatement::Query(_) => SqlStatementKind::Read,
49
+ SqlStatement::Explain { .. } => SqlStatementKind::Read,
50
+ _ => SqlStatementKind::Other,
51
+ }
52
+ }
53
+
54
+ fn validate_supported_ast_statement(statement: &SqlStatement) -> Result<(), LixError> {
55
+ match statement {
56
+ SqlStatement::Query(query) => validate_supported_query(query),
57
+ SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
58
+ SqlStatement::Explain { statement, .. } => {
59
+ if classify_ast_statement(statement.as_ref()) == SqlStatementKind::Write {
60
+ return Err(unsupported_sql_error(
61
+ "EXPLAIN of write statements is not supported by Lix SQL",
62
+ ));
63
+ }
64
+ validate_supported_ast_statement(statement)
65
+ }
66
+ _ => Err(unsupported_sql_error(format!(
67
+ "SQL statement is not supported by Lix SQL: {statement}"
68
+ ))),
69
+ }
70
+ }
71
+
72
+ fn validate_supported_query(query: &Query) -> Result<(), LixError> {
73
+ if query.with.as_ref().is_some_and(|with| with.recursive) {
74
+ return Err(
75
+ unsupported_sql_error("recursive CTEs are not supported by Lix SQL").with_hint(
76
+ "Use explicit commit graph surfaces such as lix_commit, lix_commit_edge, and lix_state_history instead of WITH RECURSIVE.",
77
+ ),
78
+ );
79
+ }
80
+
81
+ if let Some(with) = &query.with {
82
+ for cte in &with.cte_tables {
83
+ validate_supported_query(&cte.query)?;
84
+ }
85
+ }
86
+ validate_supported_set_expr(&query.body)
87
+ }
88
+
89
+ fn validate_supported_set_expr(expr: &SetExpr) -> Result<(), LixError> {
90
+ match expr {
91
+ SetExpr::Query(query) => validate_supported_query(query),
92
+ SetExpr::SetOperation { left, right, .. } => {
93
+ validate_supported_set_expr(left)?;
94
+ validate_supported_set_expr(right)
95
+ }
96
+ _ => Ok(()),
97
+ }
98
+ }
99
+
100
+ fn unsupported_sql_error(message: impl Into<String>) -> LixError {
101
+ LixError::new(LixError::CODE_UNSUPPORTED_SQL, message)
102
+ }
@@ -0,0 +1,5 @@
1
+ use crate::LixError;
2
+
3
+ pub(crate) fn unsupported(message: impl Into<String>) -> LixError {
4
+ LixError::new(LixError::CODE_UNSUPPORTED_SQL, message.into())
5
+ }
@@ -0,0 +1,29 @@
1
+ #[derive(Clone, Debug, Eq, PartialEq)]
2
+ pub(crate) enum BoundExpr {
3
+ Column(BoundColumnRef),
4
+ Param(BoundParamRef),
5
+ Literal(BoundLiteral),
6
+ Function { name: String, args: Vec<BoundExpr> },
7
+ }
8
+
9
+ #[derive(Clone, Debug, Eq, PartialEq)]
10
+ pub(crate) enum BoundLiteral {
11
+ Null,
12
+ Bool(bool),
13
+ Integer(i64),
14
+ Text(String),
15
+ Json(serde_json::Value),
16
+ Blob(Vec<u8>),
17
+ }
18
+
19
+ #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
20
+ pub(crate) struct BoundColumnRef {
21
+ pub(crate) table: String,
22
+ pub(crate) column_id: usize,
23
+ pub(crate) name: String,
24
+ }
25
+
26
+ #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
27
+ pub(crate) struct BoundParamRef {
28
+ pub(crate) index: usize,
29
+ }
@@ -0,0 +1,12 @@
1
+ pub(crate) mod classify;
2
+ pub(crate) mod error;
3
+ pub(crate) mod expr;
4
+ mod public_udf;
5
+ pub(crate) mod read;
6
+ pub(crate) mod statement;
7
+ pub(crate) mod table;
8
+ pub(crate) mod write;
9
+
10
+ pub(crate) use public_udf::statement_has_durable_runtime_function;
11
+ pub(crate) use read::{bind_read_statement, bind_statement_route, BoundStatementRoute};
12
+ pub(crate) use statement::bind_statement;