@lix-js/sdk 0.6.0-preview.2 → 0.6.0-preview.4

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 (169) hide show
  1. package/SKILL.md +46 -8
  2. package/dist/engine-wasm/wasm/lix_engine.d.ts +25 -1
  3. package/dist/engine-wasm/wasm/lix_engine.js +60 -2
  4. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  5. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +5 -0
  6. package/dist/generated/builtin-schemas.d.ts +87 -162
  7. package/dist/generated/builtin-schemas.js +139 -236
  8. package/dist/open-lix.d.ts +10 -3
  9. package/dist/open-lix.js +39 -0
  10. package/dist-engine-src/src/binary_cas/types.rs +0 -6
  11. package/dist-engine-src/src/catalog/context.rs +412 -0
  12. package/dist-engine-src/src/catalog/mod.rs +10 -0
  13. package/dist-engine-src/src/catalog/schema.rs +4 -0
  14. package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
  15. package/dist-engine-src/src/cel/mod.rs +1 -1
  16. package/dist-engine-src/src/cel/provider.rs +1 -1
  17. package/dist-engine-src/src/commit_graph/context.rs +328 -1015
  18. package/dist-engine-src/src/commit_graph/mod.rs +2 -3
  19. package/dist-engine-src/src/commit_graph/types.rs +7 -43
  20. package/dist-engine-src/src/commit_graph/walker.rs +57 -81
  21. package/dist-engine-src/src/commit_store/codec.rs +887 -0
  22. package/dist-engine-src/src/commit_store/context.rs +944 -0
  23. package/dist-engine-src/src/commit_store/materialization.rs +84 -0
  24. package/dist-engine-src/src/commit_store/mod.rs +16 -0
  25. package/dist-engine-src/src/commit_store/storage.rs +600 -0
  26. package/dist-engine-src/src/commit_store/types.rs +215 -0
  27. package/dist-engine-src/src/common/identity.rs +15 -5
  28. package/dist-engine-src/src/common/json_pointer.rs +67 -0
  29. package/dist-engine-src/src/common/metadata.rs +17 -12
  30. package/dist-engine-src/src/common/mod.rs +5 -5
  31. package/dist-engine-src/src/domain.rs +324 -0
  32. package/dist-engine-src/src/engine.rs +29 -43
  33. package/dist-engine-src/src/entity_identity.rs +238 -118
  34. package/dist-engine-src/src/functions/context.rs +17 -52
  35. package/dist-engine-src/src/functions/deterministic.rs +1 -1
  36. package/dist-engine-src/src/functions/mod.rs +1 -1
  37. package/dist-engine-src/src/functions/provider.rs +4 -4
  38. package/dist-engine-src/src/functions/state.rs +39 -66
  39. package/dist-engine-src/src/functions/types.rs +1 -1
  40. package/dist-engine-src/src/init.rs +204 -151
  41. package/dist-engine-src/src/json_store/context.rs +354 -60
  42. package/dist-engine-src/src/json_store/encoded.rs +6 -6
  43. package/dist-engine-src/src/json_store/mod.rs +4 -1
  44. package/dist-engine-src/src/json_store/store.rs +884 -11
  45. package/dist-engine-src/src/json_store/types.rs +166 -1
  46. package/dist-engine-src/src/lib.rs +11 -10
  47. package/dist-engine-src/src/live_state/context.rs +608 -830
  48. package/dist-engine-src/src/live_state/mod.rs +3 -3
  49. package/dist-engine-src/src/live_state/overlay.rs +7 -7
  50. package/dist-engine-src/src/live_state/reader.rs +5 -5
  51. package/dist-engine-src/src/live_state/types.rs +19 -36
  52. package/dist-engine-src/src/live_state/visibility.rs +19 -14
  53. package/dist-engine-src/src/plugin/archive.rs +3 -6
  54. package/dist-engine-src/src/plugin/install.rs +0 -18
  55. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
  56. package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
  57. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
  58. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
  59. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
  60. package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
  61. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
  62. package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
  63. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
  64. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
  65. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
  66. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
  67. package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
  68. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
  69. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
  70. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
  71. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
  72. package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
  73. package/dist-engine-src/src/schema/compatibility.rs +787 -0
  74. package/dist-engine-src/src/schema/definition.json +47 -17
  75. package/dist-engine-src/src/schema/definition.rs +202 -96
  76. package/dist-engine-src/src/schema/key.rs +9 -77
  77. package/dist-engine-src/src/schema/mod.rs +4 -4
  78. package/dist-engine-src/src/schema/tests.rs +133 -92
  79. package/dist-engine-src/src/session/context.rs +86 -48
  80. package/dist-engine-src/src/session/create_version.rs +22 -14
  81. package/dist-engine-src/src/session/execute.rs +117 -23
  82. package/dist-engine-src/src/session/merge/apply.rs +4 -4
  83. package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
  84. package/dist-engine-src/src/session/merge/stats.rs +1 -1
  85. package/dist-engine-src/src/session/merge/version.rs +35 -45
  86. package/dist-engine-src/src/session/mod.rs +9 -7
  87. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
  88. package/dist-engine-src/src/session/switch_version.rs +17 -28
  89. package/dist-engine-src/src/session/transaction.rs +76 -0
  90. package/dist-engine-src/src/sql2/change_provider.rs +14 -20
  91. package/dist-engine-src/src/sql2/classify.rs +75 -48
  92. package/dist-engine-src/src/sql2/context.rs +22 -18
  93. package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
  94. package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
  95. package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
  96. package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
  97. package/dist-engine-src/src/sql2/error.rs +24 -5
  98. package/dist-engine-src/src/sql2/execute.rs +426 -272
  99. package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
  100. package/dist-engine-src/src/sql2/file_provider.rs +533 -108
  101. package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
  102. package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
  103. package/dist-engine-src/src/sql2/history_projection.rs +3 -27
  104. package/dist-engine-src/src/sql2/history_provider.rs +11 -17
  105. package/dist-engine-src/src/sql2/history_route.rs +22 -8
  106. package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
  107. package/dist-engine-src/src/sql2/mod.rs +8 -4
  108. package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
  109. package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
  110. package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
  111. package/dist-engine-src/src/sql2/public_bind/dml.rs +172 -0
  112. package/dist-engine-src/src/sql2/public_bind/mod.rs +26 -0
  113. package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
  114. package/dist-engine-src/src/sql2/read_only.rs +10 -12
  115. package/dist-engine-src/src/sql2/session.rs +7 -10
  116. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
  117. package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
  118. package/dist-engine-src/src/sql2/udfs/public_call.rs +238 -0
  119. package/dist-engine-src/src/sql2/version_provider.rs +46 -31
  120. package/dist-engine-src/src/sql2/version_scope.rs +4 -4
  121. package/dist-engine-src/src/storage_bench.rs +1782 -325
  122. package/dist-engine-src/src/test_support.rs +183 -36
  123. package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
  124. package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
  125. package/dist-engine-src/src/tracked_state/context.rs +1155 -271
  126. package/dist-engine-src/src/tracked_state/diff.rs +249 -57
  127. package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
  128. package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
  129. package/dist-engine-src/src/tracked_state/merge.rs +37 -19
  130. package/dist-engine-src/src/tracked_state/mod.rs +8 -7
  131. package/dist-engine-src/src/tracked_state/storage.rs +138 -6
  132. package/dist-engine-src/src/tracked_state/tree.rs +695 -252
  133. package/dist-engine-src/src/tracked_state/types.rs +176 -6
  134. package/dist-engine-src/src/transaction/commit.rs +695 -435
  135. package/dist-engine-src/src/transaction/context.rs +551 -310
  136. package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
  137. package/dist-engine-src/src/transaction/mod.rs +2 -0
  138. package/dist-engine-src/src/transaction/normalization.rs +311 -447
  139. package/dist-engine-src/src/transaction/prep.rs +37 -0
  140. package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
  141. package/dist-engine-src/src/transaction/staging.rs +701 -406
  142. package/dist-engine-src/src/transaction/types.rs +231 -122
  143. package/dist-engine-src/src/transaction/validation.rs +2717 -1698
  144. package/dist-engine-src/src/untracked_state/codec.rs +40 -96
  145. package/dist-engine-src/src/untracked_state/context.rs +21 -5
  146. package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
  147. package/dist-engine-src/src/untracked_state/mod.rs +3 -5
  148. package/dist-engine-src/src/untracked_state/storage.rs +105 -57
  149. package/dist-engine-src/src/untracked_state/types.rs +63 -13
  150. package/dist-engine-src/src/version/context.rs +1 -13
  151. package/dist-engine-src/src/version/lifecycle.rs +221 -0
  152. package/dist-engine-src/src/version/mod.rs +3 -2
  153. package/dist-engine-src/src/version/refs.rs +12 -103
  154. package/dist-engine-src/src/version/stage_rows.rs +15 -19
  155. package/package.json +1 -1
  156. package/dist-engine-src/src/changelog/codec.rs +0 -321
  157. package/dist-engine-src/src/changelog/context.rs +0 -92
  158. package/dist-engine-src/src/changelog/materialization.rs +0 -121
  159. package/dist-engine-src/src/changelog/mod.rs +0 -13
  160. package/dist-engine-src/src/changelog/reader.rs +0 -20
  161. package/dist-engine-src/src/changelog/storage.rs +0 -220
  162. package/dist-engine-src/src/changelog/types.rs +0 -38
  163. package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
  164. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
  165. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
  166. package/dist-engine-src/src/schema_registry.rs +0 -294
  167. package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
  168. package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
  169. package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
@@ -6,23 +6,27 @@ use std::sync::Arc;
6
6
  use serde_json::Value as JsonValue;
7
7
 
8
8
  use crate::binary_cas::{BinaryCasContext, BlobDataReader};
9
- use crate::changelog::ChangelogContext;
9
+ use crate::catalog::CatalogContext;
10
10
  use crate::commit_graph::{CommitGraphContext, CommitGraphReader};
11
+ use crate::commit_store::CommitStoreContext;
11
12
  use crate::entity_identity::EntityIdentity;
12
13
  use crate::functions::FunctionProviderHandle;
13
14
  use crate::json_store::JsonStoreContext;
14
15
  use crate::live_state::{LiveStateContext, LiveStateReader, LiveStateRowRequest};
15
- use crate::schema_registry::SchemaRegistry;
16
- use crate::sql2::{ChangelogQuerySource, SqlChangelogQuerySource, SqlExecutionContext};
16
+ use crate::sql2::{CommitStoreQuerySource, SqlCommitStoreQuerySource, SqlExecutionContext};
17
17
  use crate::storage::{
18
18
  ScopedStorageReader, StorageContext, StorageReadScope, StorageReadTransaction, StorageReader,
19
19
  };
20
20
  use crate::tracked_state::TrackedStateContext;
21
21
  use crate::transaction::{open_transaction, Transaction};
22
- use crate::version::{VersionContext, VersionRefReader};
22
+ use crate::version::{
23
+ VersionContext, VersionLifecycle, VersionOperation, VersionRefReader, VersionReferenceRole,
24
+ };
23
25
  use crate::GLOBAL_VERSION_ID;
24
26
  use crate::{LixError, NullableKeyFilter};
25
27
 
28
+ use super::transaction::transaction_state_error;
29
+
26
30
  pub(crate) const WORKSPACE_VERSION_KEY: &str = "lix_workspace_version_id";
27
31
 
28
32
  #[derive(Clone)]
@@ -31,16 +35,13 @@ pub(crate) enum SessionMode {
31
35
  Workspace,
32
36
  }
33
37
 
34
- /// Session-context state for engine2 execution.
38
+ /// Session-context state for engine execution.
35
39
  ///
36
40
  /// A session context pins the active version selector and shared execution
37
- /// services. Each call to `execute(...)` projects this state into a read-only
38
- /// SQL context or a transaction-owned write context.
39
- ///
40
- /// Write transaction invariant: any engine2 operation that may write must enter
41
- /// through `SessionContext::with_write_transaction`. Reads that influence writes
42
- /// are only available from that transaction capability, not from session-level
43
- /// helpers.
41
+ /// services. Parent-handle `execute(...)` runs as an implicit single-statement
42
+ /// transaction. Explicit transactions hold the session execution lease until
43
+ /// commit or rollback, so all SQL during that window must run through the
44
+ /// transaction handle.
44
45
  #[derive(Clone)]
45
46
  pub struct SessionContext {
46
47
  pub(super) mode: SessionMode,
@@ -48,10 +49,11 @@ pub struct SessionContext {
48
49
  pub(super) live_state: Arc<LiveStateContext>,
49
50
  pub(super) tracked_state: Arc<TrackedStateContext>,
50
51
  pub(super) binary_cas: Arc<BinaryCasContext>,
51
- pub(super) changelog: Arc<ChangelogContext>,
52
+ pub(super) commit_store: Arc<CommitStoreContext>,
52
53
  pub(super) version_ctx: Arc<VersionContext>,
53
- pub(super) schema_registry: Arc<SchemaRegistry>,
54
+ pub(super) catalog_context: Arc<CatalogContext>,
54
55
  closed: Arc<AtomicBool>,
56
+ active_transaction: Arc<AtomicBool>,
55
57
  }
56
58
 
57
59
  impl SessionContext {
@@ -60,9 +62,9 @@ impl SessionContext {
60
62
  live_state: Arc<LiveStateContext>,
61
63
  tracked_state: Arc<TrackedStateContext>,
62
64
  binary_cas: Arc<BinaryCasContext>,
63
- changelog: Arc<ChangelogContext>,
65
+ commit_store: Arc<CommitStoreContext>,
64
66
  version_ctx: Arc<VersionContext>,
65
- schema_registry: Arc<SchemaRegistry>,
67
+ catalog_context: Arc<CatalogContext>,
66
68
  ) -> Result<Self, LixError> {
67
69
  let session = Self::new(
68
70
  SessionMode::Workspace,
@@ -70,9 +72,9 @@ impl SessionContext {
70
72
  live_state,
71
73
  tracked_state,
72
74
  binary_cas,
73
- changelog,
75
+ commit_store,
74
76
  version_ctx,
75
- schema_registry,
77
+ catalog_context,
76
78
  );
77
79
  session.active_version_id().await?;
78
80
  Ok(session)
@@ -84,9 +86,9 @@ impl SessionContext {
84
86
  live_state: Arc<LiveStateContext>,
85
87
  tracked_state: Arc<TrackedStateContext>,
86
88
  binary_cas: Arc<BinaryCasContext>,
87
- changelog: Arc<ChangelogContext>,
89
+ commit_store: Arc<CommitStoreContext>,
88
90
  version_ctx: Arc<VersionContext>,
89
- schema_registry: Arc<SchemaRegistry>,
91
+ catalog_context: Arc<CatalogContext>,
90
92
  ) -> Result<Self, LixError> {
91
93
  Ok(Self::new(
92
94
  SessionMode::Pinned {
@@ -96,9 +98,9 @@ impl SessionContext {
96
98
  live_state,
97
99
  tracked_state,
98
100
  binary_cas,
99
- changelog,
101
+ commit_store,
100
102
  version_ctx,
101
- schema_registry,
103
+ catalog_context,
102
104
  ))
103
105
  }
104
106
 
@@ -108,9 +110,9 @@ impl SessionContext {
108
110
  live_state: Arc<LiveStateContext>,
109
111
  tracked_state: Arc<TrackedStateContext>,
110
112
  binary_cas: Arc<BinaryCasContext>,
111
- changelog: Arc<ChangelogContext>,
113
+ commit_store: Arc<CommitStoreContext>,
112
114
  version_ctx: Arc<VersionContext>,
113
- schema_registry: Arc<SchemaRegistry>,
115
+ catalog_context: Arc<CatalogContext>,
114
116
  ) -> Self {
115
117
  Self::new_with_closed(
116
118
  mode,
@@ -118,9 +120,10 @@ impl SessionContext {
118
120
  live_state,
119
121
  tracked_state,
120
122
  binary_cas,
121
- changelog,
123
+ commit_store,
122
124
  version_ctx,
123
- schema_registry,
125
+ catalog_context,
126
+ Arc::new(AtomicBool::new(false)),
124
127
  Arc::new(AtomicBool::new(false)),
125
128
  )
126
129
  }
@@ -131,10 +134,11 @@ impl SessionContext {
131
134
  live_state: Arc<LiveStateContext>,
132
135
  tracked_state: Arc<TrackedStateContext>,
133
136
  binary_cas: Arc<BinaryCasContext>,
134
- changelog: Arc<ChangelogContext>,
137
+ commit_store: Arc<CommitStoreContext>,
135
138
  version_ctx: Arc<VersionContext>,
136
- schema_registry: Arc<SchemaRegistry>,
139
+ catalog_context: Arc<CatalogContext>,
137
140
  closed: Arc<AtomicBool>,
141
+ active_transaction: Arc<AtomicBool>,
138
142
  ) -> Self {
139
143
  Self {
140
144
  mode,
@@ -142,10 +146,11 @@ impl SessionContext {
142
146
  live_state,
143
147
  tracked_state,
144
148
  binary_cas,
145
- changelog,
149
+ commit_store,
146
150
  version_ctx,
147
- schema_registry,
151
+ catalog_context,
148
152
  closed,
153
+ active_transaction,
149
154
  }
150
155
  }
151
156
 
@@ -164,6 +169,10 @@ impl SessionContext {
164
169
  Arc::clone(&self.closed)
165
170
  }
166
171
 
172
+ pub(crate) fn active_transaction_flag(&self) -> Arc<AtomicBool> {
173
+ Arc::clone(&self.active_transaction)
174
+ }
175
+
167
176
  pub(crate) fn ensure_open(&self) -> Result<(), LixError> {
168
177
  if self.is_closed() {
169
178
  return Err(closed_error());
@@ -256,18 +265,14 @@ impl SessionContext {
256
265
  })?
257
266
  .to_string();
258
267
 
259
- let head = self
260
- .version_ctx
261
- .ref_reader(&mut *reader)
262
- .load_head_commit_id(&version_id)
268
+ let version_ref = self.version_ctx.ref_reader(&mut *reader);
269
+ VersionLifecycle::new(&version_ref)
270
+ .require_existing_ref(
271
+ &version_id,
272
+ VersionOperation::LoadWorkspaceSelector,
273
+ VersionReferenceRole::WorkspaceSelector,
274
+ )
263
275
  .await?;
264
- if head.is_none() {
265
- return Err(LixError::version_not_found(
266
- version_id,
267
- "load_workspace_version_id",
268
- "workspace_selector",
269
- ));
270
- }
271
276
 
272
277
  Ok(version_id)
273
278
  }
@@ -279,15 +284,25 @@ impl SessionContext {
279
284
  ) -> Pin<Box<dyn Future<Output = Result<T, LixError>> + 'tx>>,
280
285
  {
281
286
  self.ensure_open()?;
287
+ let _transaction_guard = self.reserve_session_transaction()?;
288
+ self.with_write_transaction_reserved(f).await
289
+ }
290
+
291
+ pub(crate) async fn with_write_transaction_reserved<T, F>(&self, f: F) -> Result<T, LixError>
292
+ where
293
+ F: for<'tx> FnOnce(
294
+ &'tx mut Transaction,
295
+ ) -> Pin<Box<dyn Future<Output = Result<T, LixError>> + 'tx>>,
296
+ {
282
297
  let opened = open_transaction(
283
298
  &self.mode,
284
299
  self.storage.clone(),
285
300
  Arc::clone(&self.live_state),
286
301
  Arc::clone(&self.tracked_state),
287
302
  Arc::clone(&self.binary_cas),
288
- Arc::clone(&self.changelog),
303
+ Arc::clone(&self.commit_store),
289
304
  Arc::clone(&self.version_ctx),
290
- Arc::clone(&self.schema_registry),
305
+ Arc::clone(&self.catalog_context),
291
306
  )
292
307
  .await?;
293
308
  let mut transaction = opened.transaction;
@@ -304,6 +319,19 @@ impl SessionContext {
304
319
  }
305
320
  }
306
321
  }
322
+
323
+ pub(super) fn reserve_session_transaction(&self) -> Result<SessionTransactionGuard, LixError> {
324
+ let active_transaction = self.active_transaction_flag();
325
+ if active_transaction
326
+ .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
327
+ .is_err()
328
+ {
329
+ return Err(transaction_state_error(
330
+ "Lix handle has an active transaction; use the transaction handle for reads and writes until it is committed or rolled back",
331
+ ));
332
+ }
333
+ Ok(SessionTransactionGuard { active_transaction })
334
+ }
307
335
  }
308
336
 
309
337
  fn closed_error() -> LixError {
@@ -311,6 +339,16 @@ fn closed_error() -> LixError {
311
339
  .with_hint("Open a new Lix handle before calling this method.")
312
340
  }
313
341
 
342
+ pub(super) struct SessionTransactionGuard {
343
+ active_transaction: Arc<AtomicBool>,
344
+ }
345
+
346
+ impl Drop for SessionTransactionGuard {
347
+ fn drop(&mut self) {
348
+ self.active_transaction.store(false, Ordering::SeqCst);
349
+ }
350
+ }
351
+
314
352
  /// Read-only SQL execution context derived from a session.
315
353
  ///
316
354
  /// Write statements re-plan against `Transaction`; this context intentionally
@@ -321,7 +359,7 @@ pub(super) struct SessionSqlExecutionContext<'a> {
321
359
  ScopedStorageReader<Box<dyn StorageReadTransaction + Send + Sync + 'static>>,
322
360
  pub(super) live_state: Arc<LiveStateContext>,
323
361
  pub(super) binary_cas: Arc<BinaryCasContext>,
324
- pub(super) changelog: Arc<ChangelogContext>,
362
+ pub(super) commit_store: Arc<CommitStoreContext>,
325
363
  pub(super) version_ctx: Arc<VersionContext>,
326
364
  pub(super) visible_schemas: Vec<JsonValue>,
327
365
  pub(super) functions: FunctionProviderHandle,
@@ -336,16 +374,16 @@ impl SqlExecutionContext for SessionSqlExecutionContext<'_> {
336
374
  Arc::new(self.live_state.reader(self.read_store.clone())) as Arc<dyn LiveStateReader>
337
375
  }
338
376
 
339
- fn changelog_query_source(&self) -> SqlChangelogQuerySource {
377
+ fn commit_store_query_source(&self) -> SqlCommitStoreQuerySource {
340
378
  let read_scope = StorageReadScope::new(self.read_store.clone());
341
- ChangelogQuerySource {
342
- changelog_reader: Arc::new(self.changelog.reader(read_scope.store())),
379
+ CommitStoreQuerySource {
380
+ commit_store_reader: Arc::new(self.commit_store.reader(read_scope.store())),
343
381
  json_reader: JsonStoreContext::new().reader(read_scope.store()),
344
382
  }
345
383
  }
346
384
 
347
385
  fn commit_graph(&self) -> Box<dyn CommitGraphReader> {
348
- Box::new(CommitGraphContext::new(ChangelogContext::new()).reader(self.read_store.clone()))
386
+ Box::new(CommitGraphContext::new().reader(self.read_store.clone()))
349
387
  }
350
388
 
351
389
  fn version_ref(&self) -> Arc<dyn VersionRefReader> {
@@ -1,5 +1,8 @@
1
- use crate::transaction::types::{StageWrite, StageWriteMode};
2
- use crate::version::{version_descriptor_stage_row, version_ref_stage_row, VersionRefReader};
1
+ use crate::transaction::types::{TransactionWrite, TransactionWriteMode};
2
+ use crate::version::{
3
+ version_descriptor_stage_row, version_ref_stage_row, VersionLifecycle, VersionOperation,
4
+ VersionReferenceRole,
5
+ };
3
6
  use crate::LixError;
4
7
 
5
8
  use super::context::SessionContext;
@@ -7,7 +10,7 @@ use super::context::SessionContext;
7
10
  /// Options for creating a new version from the session's active version.
8
11
  #[derive(Debug, Clone, PartialEq, Eq)]
9
12
  pub struct CreateVersionOptions {
10
- /// Optional caller-provided version id. If omitted, engine2 generates one.
13
+ /// Optional caller-provided version id. If omitted, engine generates one.
11
14
  pub id: Option<String>,
12
15
  /// User-facing version name.
13
16
  pub name: String,
@@ -41,25 +44,30 @@ impl SessionContext {
41
44
  .id
42
45
  .unwrap_or_else(|| transaction.functions().call_uuid_v7());
43
46
  let source_head = if let Some(from_commit_id) = options.from_commit_id {
47
+ let mut commit_graph = transaction.commit_graph_reader();
48
+ VersionLifecycle::require_existing_commit(
49
+ &mut commit_graph,
50
+ &from_commit_id,
51
+ VersionOperation::CreateVersion,
52
+ VersionReferenceRole::CommitSource,
53
+ )
54
+ .await?;
44
55
  from_commit_id
45
56
  } else {
46
57
  let active_version_id = transaction.active_version_id().to_string();
47
58
  let reader = transaction.version_ref_reader();
48
- reader
49
- .load_head_commit_id(&active_version_id)
59
+ VersionLifecycle::new(&reader)
60
+ .require_existing_commit_id(
61
+ &active_version_id,
62
+ VersionOperation::CreateVersion,
63
+ VersionReferenceRole::Source,
64
+ )
50
65
  .await?
51
- .ok_or_else(|| {
52
- LixError::version_not_found(
53
- active_version_id.clone(),
54
- "create_version",
55
- "source",
56
- )
57
- })?
58
66
  };
59
67
 
60
68
  transaction
61
- .stage_write(StageWrite::Rows {
62
- mode: StageWriteMode::Insert,
69
+ .stage_write(TransactionWrite::Rows {
70
+ mode: TransactionWriteMode::Insert,
63
71
  rows: vec![
64
72
  version_descriptor_stage_row(&version_id, &options.name, false),
65
73
  version_ref_stage_row(&version_id, &source_head),
@@ -1,14 +1,14 @@
1
1
  use std::sync::Arc;
2
2
 
3
3
  use crate::functions::FunctionContext;
4
- use crate::json_store::JsonStoreContext;
5
4
  use crate::sql2;
6
5
  use crate::storage::{StorageReadScope, StorageWriteSet};
7
6
  use crate::{LixError, LixNotice, SqlQueryResult, Value};
8
7
 
9
8
  use super::context::{SessionContext, SessionSqlExecutionContext};
9
+ use super::transaction::SessionTransaction;
10
10
 
11
- /// Result of executing one SQL statement through engine2.
11
+ /// Result of executing one SQL statement through engine.
12
12
  ///
13
13
  /// Column names live once at the result-set level. Individual rows only own
14
14
  /// values, which keeps the public API row-oriented without copying schema
@@ -251,7 +251,7 @@ fn value_type_error(expected: &str, actual: &Value) -> LixError {
251
251
  )
252
252
  }
253
253
 
254
- /// Borrowed row view with access to the result-set column names.
254
+ /// Zero-copy row view with access to the result-set column names.
255
255
  ///
256
256
  /// This is the ergonomic path for callers that want `row.get("column")`
257
257
  /// without storing column metadata on every owned row.
@@ -291,29 +291,40 @@ impl SessionContext {
291
291
  /// Executes one DataFusion SQL statement against this Lix session.
292
292
  ///
293
293
  /// The SQL dialect is DataFusion SQL, not SQLite SQL. Positional
294
- /// placeholders use `$1`, `$2`, and so on. SQLite-specific catalog tables
294
+ /// placeholders use `?` or `$1`, `$2`, and so on. SQLite-specific catalog tables
295
295
  /// and transaction statements such as `sqlite_master`, `BEGIN`, and
296
296
  /// `COMMIT` are not part of this contract; use `information_schema` for
297
297
  /// catalog inspection. Lix owns transaction boundaries for each statement.
298
298
  pub async fn execute(&self, sql: &str, params: &[Value]) -> Result<ExecuteResult, LixError> {
299
299
  self.ensure_open()?;
300
- let kind = sql2::classify_statement(sql)?;
300
+ let _transaction_guard = self.reserve_session_transaction()?;
301
+ let statement = sql2::parse_statement(sql)?;
302
+ let kind = sql2::classify_datafusion_statement(&statement);
301
303
  if kind == sql2::SqlStatementKind::Write {
302
- let sql = sql.to_string();
304
+ let sql_for_error = sql.to_string();
303
305
  let params = params.to_vec();
304
306
  return self
305
- .with_write_transaction(|transaction| {
307
+ .with_write_transaction_reserved(|transaction| {
306
308
  Box::pin(async move {
307
309
  // Re-plan against the transaction-backed write
308
310
  // session so provider hooks read and stage through the
309
311
  // transaction-owned SQL write context.
310
- let tx_plan = sql2::create_write_logical_plan(transaction, &sql).await?;
312
+ let tx_plan =
313
+ sql2::create_write_logical_plan_from_parsed(transaction, statement)
314
+ .await?;
311
315
  let result = sql2::execute_logical_plan(tx_plan, &params).await?;
312
316
  let affected_rows = affected_rows_from_query_result(result)?;
313
317
  Ok(ExecuteResult::from_rows_affected(affected_rows))
314
318
  })
315
319
  })
316
- .await;
320
+ .await
321
+ .map_err(|error| normalize_sql_surface_error(error, &sql_for_error));
322
+ }
323
+ if kind == sql2::SqlStatementKind::Other {
324
+ return Err(LixError::new(
325
+ LixError::CODE_UNSUPPORTED_SQL,
326
+ "SQL statement is not supported by Lix SQL",
327
+ ));
317
328
  }
318
329
 
319
330
  let read_scope = StorageReadScope::new(self.storage.begin_read_transaction().await?);
@@ -325,21 +336,21 @@ impl SessionContext {
325
336
  let functions = runtime_functions.provider();
326
337
  let active_version_id = self.active_version_id_from_reader(&mut read_store).await?;
327
338
  let visible_schemas = self
328
- .schema_registry
329
- .visible_schemas(live_state.as_ref(), &active_version_id)
339
+ .catalog_context
340
+ .schema_jsons_for_sql_read_planning(live_state.as_ref(), &active_version_id)
330
341
  .await?;
331
342
  let ctx = SessionSqlExecutionContext {
332
343
  active_version_id: &active_version_id,
333
344
  read_store,
334
345
  live_state: Arc::clone(&self.live_state),
335
346
  binary_cas: Arc::clone(&self.binary_cas),
336
- changelog: Arc::clone(&self.changelog),
347
+ commit_store: Arc::clone(&self.commit_store),
337
348
  version_ctx: Arc::clone(&self.version_ctx),
338
349
  visible_schemas,
339
350
  functions: functions.clone(),
340
351
  };
341
352
 
342
- let plan = sql2::create_logical_plan(&ctx, sql).await?;
353
+ let plan = sql2::create_logical_plan_from_parsed(&ctx, sql, statement).await?;
343
354
  let result = sql2::execute_logical_plan(plan, params).await?;
344
355
  drop(ctx);
345
356
  drop(live_state);
@@ -352,7 +363,7 @@ impl SessionContext {
352
363
  }
353
364
  Err(error) => {
354
365
  let _ = read_scope.rollback().await;
355
- return Err(error);
366
+ return Err(normalize_sql_surface_error(error, sql));
356
367
  }
357
368
  };
358
369
  self.persist_runtime_functions_if_needed(&runtime_functions)
@@ -370,23 +381,106 @@ impl SessionContext {
370
381
  &self,
371
382
  runtime_functions: &FunctionContext,
372
383
  ) -> Result<(), LixError> {
373
- let mut transaction = self.storage.begin_write_transaction().await?;
374
384
  let mut writes = StorageWriteSet::new();
375
- let mut json_writer = JsonStoreContext::new().writer();
376
385
  runtime_functions
377
- .stage_persist_if_needed(
378
- &mut self.live_state.writer(transaction.as_mut()),
379
- &mut writes,
380
- &mut json_writer,
381
- )
386
+ .stage_persist_if_needed(&mut writes)
382
387
  .await?;
383
- if !writes.is_empty() {
384
- writes.apply(&mut transaction.as_mut()).await?;
388
+ if writes.is_empty() {
389
+ return Ok(());
385
390
  }
391
+ let mut transaction = self.storage.begin_write_transaction().await?;
392
+ writes.apply(&mut transaction.as_mut()).await?;
386
393
  transaction.commit().await
387
394
  }
388
395
  }
389
396
 
397
+ impl SessionTransaction {
398
+ /// Executes one SQL statement inside this transaction.
399
+ ///
400
+ /// Write statements are staged until `commit()`. Read statements use the
401
+ /// transaction overlay, so they can observe writes staged by earlier calls
402
+ /// on this transaction handle.
403
+ pub async fn execute(
404
+ &mut self,
405
+ sql: &str,
406
+ params: &[Value],
407
+ ) -> Result<ExecuteResult, LixError> {
408
+ let statement = sql2::parse_statement(sql)?;
409
+ let kind = sql2::classify_datafusion_statement(&statement);
410
+ let transaction = self.transaction_mut()?;
411
+ match kind {
412
+ sql2::SqlStatementKind::Write => {
413
+ execute_transaction_write(transaction, statement, params)
414
+ .await
415
+ .map_err(|error| normalize_sql_surface_error(error, sql))
416
+ }
417
+ sql2::SqlStatementKind::Read => {
418
+ let plan = sql2::create_transaction_read_logical_plan_from_parsed(
419
+ transaction,
420
+ sql,
421
+ statement,
422
+ )
423
+ .await
424
+ .map_err(|error| normalize_sql_surface_error(error, sql))?;
425
+ let result = sql2::execute_logical_plan(plan, params)
426
+ .await
427
+ .map_err(|error| normalize_sql_surface_error(error, sql))?;
428
+ Ok(ExecuteResult::from_sql_query_result(result))
429
+ }
430
+ sql2::SqlStatementKind::Other => Err(LixError::new(
431
+ LixError::CODE_UNSUPPORTED_SQL,
432
+ "SQL statement is not supported by Lix SQL",
433
+ )),
434
+ }
435
+ }
436
+ }
437
+
438
+ async fn execute_transaction_write(
439
+ transaction: &mut crate::transaction::Transaction,
440
+ statement: datafusion::sql::parser::Statement,
441
+ params: &[Value],
442
+ ) -> Result<ExecuteResult, LixError> {
443
+ let tx_plan = sql2::create_write_logical_plan_from_parsed(transaction, statement).await?;
444
+ let result = sql2::execute_logical_plan(tx_plan, params).await?;
445
+ let affected_rows = affected_rows_from_query_result(result)?;
446
+ Ok(ExecuteResult::from_rows_affected(affected_rows))
447
+ }
448
+
449
+ fn normalize_sql_surface_error(error: LixError, sql: &str) -> LixError {
450
+ if error.code.starts_with("LIX_ERROR_PATH_") && sql_uses_public_filesystem_path_surface(sql) {
451
+ return LixError {
452
+ code: LixError::CODE_INVALID_PARAM.to_string(),
453
+ ..error
454
+ };
455
+ }
456
+ if error.code == LixError::CODE_INVALID_JSON_PATH
457
+ && error
458
+ .message
459
+ .to_ascii_lowercase()
460
+ .contains("uses variadic path segments")
461
+ {
462
+ return LixError {
463
+ code: LixError::CODE_INVALID_PARAM.to_string(),
464
+ ..error
465
+ };
466
+ }
467
+ if error.code == LixError::CODE_FOREIGN_KEY {
468
+ let lower = error.message.to_ascii_lowercase();
469
+ if lower.contains("schema 'lix_version_ref'") && lower.contains("target 'lix_commit.") {
470
+ return LixError {
471
+ code: LixError::CODE_VERSION_NOT_FOUND.to_string(),
472
+ ..error
473
+ };
474
+ }
475
+ }
476
+ error
477
+ }
478
+
479
+ fn sql_uses_public_filesystem_path_surface(sql: &str) -> bool {
480
+ let lower = sql.to_ascii_lowercase();
481
+ (lower.contains("lix_file") || lower.contains("lix_directory")) && lower.contains("path")
482
+ }
483
+
390
484
  fn affected_rows_from_query_result(result: SqlQueryResult) -> Result<u64, LixError> {
391
485
  let Some(first_row) = result.rows.first() else {
392
486
  return Ok(0);
@@ -1,10 +1,10 @@
1
1
  use crate::tracked_state::TrackedStateMergePlan;
2
- use crate::transaction::types::StageAdoptedChange;
2
+ use crate::transaction::types::TransactionAdoptedChange;
3
3
 
4
4
  pub(crate) fn adopted_changes_from_merge_plan(
5
5
  plan: &TrackedStateMergePlan,
6
6
  target_version_id: &str,
7
- ) -> Vec<StageAdoptedChange> {
7
+ ) -> Vec<TransactionAdoptedChange> {
8
8
  plan.patches
9
9
  .iter()
10
10
  .map(|patch| stage_adopted_change_from_patch(patch, target_version_id))
@@ -14,8 +14,8 @@ pub(crate) fn adopted_changes_from_merge_plan(
14
14
  fn stage_adopted_change_from_patch(
15
15
  patch: &crate::tracked_state::TrackedStateMergePatch,
16
16
  target_version_id: &str,
17
- ) -> StageAdoptedChange {
18
- StageAdoptedChange {
17
+ ) -> TransactionAdoptedChange {
18
+ TransactionAdoptedChange {
19
19
  version_id: target_version_id.to_string(),
20
20
  change_id: patch.change_id().to_string(),
21
21
  projected_row: patch.projected_row().clone(),
@@ -2,12 +2,13 @@ use crate::tracked_state::{
2
2
  TrackedStateDiffEntry, TrackedStateDiffKind, TrackedStateMergeConflict, TrackedStateMergePlan,
3
3
  };
4
4
  use crate::LixError;
5
+ use serde_json::Value as JsonValue;
5
6
 
6
7
  #[derive(Debug, Clone, PartialEq, Eq)]
7
8
  pub(crate) struct MergeConflict {
8
9
  pub(crate) kind: MergeConflictKind,
9
10
  pub(crate) schema_key: String,
10
- pub(crate) entity_id: String,
11
+ pub(crate) entity_id: JsonValue,
11
12
  pub(crate) file_id: Option<String>,
12
13
  pub(crate) target: MergeConflictSide,
13
14
  pub(crate) source: MergeConflictSide,
@@ -42,7 +43,7 @@ fn conflict_from_tracked(conflict: &TrackedStateMergeConflict) -> Result<MergeCo
42
43
  Ok(MergeConflict {
43
44
  kind: MergeConflictKind::SameEntityChanged,
44
45
  schema_key: conflict.identity.schema_key.clone(),
45
- entity_id: conflict.identity.entity_id.as_string()?,
46
+ entity_id: conflict.identity.entity_id.as_json_array_value()?,
46
47
  file_id: conflict.identity.file_id.clone(),
47
48
  target: conflict_side_from_diff_entry(&conflict.target),
48
49
  source: conflict_side_from_diff_entry(&conflict.source),
@@ -36,7 +36,7 @@ pub(crate) fn stats_from_plan(
36
36
  format!(
37
37
  "merge analysis could not find source diff entry for adopted schema '{}' entity '{}'",
38
38
  identity.schema_key,
39
- identity.entity_id.as_string()?
39
+ identity.entity_id.as_json_array_text()?
40
40
  ),
41
41
  ));
42
42
  };