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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +65 -64
  3. package/dist/engine-wasm/index.js +4 -4
  4. package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
  5. package/dist/engine-wasm/wasm/lix_engine.js +130 -118
  6. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  7. package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
  8. package/dist/generated/builtin-schemas.d.ts +69 -69
  9. package/dist/generated/builtin-schemas.js +94 -94
  10. package/dist/open-lix.d.ts +33 -26
  11. package/dist/open-lix.js +10 -10
  12. package/dist/sqlite/index.js +86 -30
  13. package/dist-engine-src/README.md +3 -3
  14. package/dist-engine-src/src/backend/capabilities.rs +67 -0
  15. package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
  16. package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
  17. package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
  18. package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
  19. package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
  20. package/dist-engine-src/src/backend/conformance/model.rs +28 -0
  21. package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
  22. package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
  23. package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
  24. package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
  25. package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
  26. package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
  27. package/dist-engine-src/src/backend/conformance/write.rs +16 -0
  28. package/dist-engine-src/src/backend/error.rs +94 -0
  29. package/dist-engine-src/src/backend/in_memory.rs +670 -0
  30. package/dist-engine-src/src/backend/mod.rs +36 -9
  31. package/dist-engine-src/src/backend/predicate.rs +80 -0
  32. package/dist-engine-src/src/backend/traits.rs +260 -0
  33. package/dist-engine-src/src/backend/types.rs +224 -81
  34. package/dist-engine-src/src/binary_cas/context.rs +8 -8
  35. package/dist-engine-src/src/binary_cas/kv.rs +234 -259
  36. package/dist-engine-src/src/{version → branch}/context.rs +12 -12
  37. package/dist-engine-src/src/branch/lifecycle.rs +221 -0
  38. package/dist-engine-src/src/branch/mod.rs +13 -0
  39. package/dist-engine-src/src/branch/refs.rs +321 -0
  40. package/dist-engine-src/src/branch/stage_rows.rs +67 -0
  41. package/dist-engine-src/src/branch/types.rs +21 -0
  42. package/dist-engine-src/src/catalog/context.rs +18 -18
  43. package/dist-engine-src/src/catalog/snapshot.rs +8 -8
  44. package/dist-engine-src/src/changelog/bench_support.rs +785 -0
  45. package/dist-engine-src/src/changelog/change.rs +1 -0
  46. package/dist-engine-src/src/changelog/codec.rs +497 -0
  47. package/dist-engine-src/src/changelog/commit.rs +1 -0
  48. package/dist-engine-src/src/changelog/context.rs +1614 -0
  49. package/dist-engine-src/src/changelog/mod.rs +29 -0
  50. package/dist-engine-src/src/changelog/store.rs +163 -0
  51. package/dist-engine-src/src/changelog/test_support.rs +54 -0
  52. package/dist-engine-src/src/changelog/types.rs +213 -0
  53. package/dist-engine-src/src/commit_graph/context.rs +317 -274
  54. package/dist-engine-src/src/commit_graph/mod.rs +2 -4
  55. package/dist-engine-src/src/commit_graph/types.rs +22 -42
  56. package/dist-engine-src/src/commit_graph/walker.rs +133 -103
  57. package/dist-engine-src/src/common/error.rs +52 -18
  58. package/dist-engine-src/src/common/identity.rs +2 -2
  59. package/dist-engine-src/src/common/mod.rs +1 -1
  60. package/dist-engine-src/src/domain.rs +42 -46
  61. package/dist-engine-src/src/engine.rs +74 -96
  62. package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
  63. package/dist-engine-src/src/functions/context.rs +56 -52
  64. package/dist-engine-src/src/functions/state.rs +51 -52
  65. package/dist-engine-src/src/init.rs +288 -154
  66. package/dist-engine-src/src/json_store/context.rs +15 -266
  67. package/dist-engine-src/src/json_store/mod.rs +26 -0
  68. package/dist-engine-src/src/json_store/store.rs +103 -718
  69. package/dist-engine-src/src/json_store/types.rs +4 -9
  70. package/dist-engine-src/src/lib.rs +49 -19
  71. package/dist-engine-src/src/live_state/context.rs +654 -790
  72. package/dist-engine-src/src/live_state/mod.rs +9 -3
  73. package/dist-engine-src/src/live_state/overlay.rs +4 -4
  74. package/dist-engine-src/src/live_state/types.rs +30 -21
  75. package/dist-engine-src/src/live_state/visibility.rs +514 -71
  76. package/dist-engine-src/src/plugin/install.rs +48 -48
  77. package/dist-engine-src/src/plugin/manifest.rs +7 -7
  78. package/dist-engine-src/src/plugin/materializer.rs +0 -275
  79. package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
  80. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
  81. package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
  82. package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
  83. package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
  84. package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
  85. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
  86. package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
  87. package/dist-engine-src/src/schema/compatibility.rs +11 -11
  88. package/dist-engine-src/src/schema/definition.json +2 -2
  89. package/dist-engine-src/src/schema/definition.rs +5 -5
  90. package/dist-engine-src/src/schema/key.rs +3 -3
  91. package/dist-engine-src/src/schema/mod.rs +1 -1
  92. package/dist-engine-src/src/schema/tests.rs +18 -18
  93. package/dist-engine-src/src/session/context.rs +803 -148
  94. package/dist-engine-src/src/session/create_branch.rs +94 -0
  95. package/dist-engine-src/src/session/execute.rs +223 -83
  96. package/dist-engine-src/src/session/merge/analysis.rs +9 -3
  97. package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
  98. package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
  99. package/dist-engine-src/src/session/merge/mod.rs +5 -6
  100. package/dist-engine-src/src/session/merge/stats.rs +7 -11
  101. package/dist-engine-src/src/session/mod.rs +15 -12
  102. package/dist-engine-src/src/session/switch_branch.rs +113 -0
  103. package/dist-engine-src/src/session/transaction.rs +495 -14
  104. package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
  105. package/dist-engine-src/src/sql2/bind/error.rs +5 -0
  106. package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
  107. package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
  108. package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
  109. package/dist-engine-src/src/sql2/bind/read.rs +65 -0
  110. package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
  111. package/dist-engine-src/src/sql2/bind/table.rs +273 -0
  112. package/dist-engine-src/src/sql2/bind/write.rs +86 -0
  113. package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
  114. package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
  115. package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
  116. package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
  117. package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
  118. package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
  119. package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
  120. package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
  121. package/dist-engine-src/src/sql2/context.rs +36 -30
  122. package/dist-engine-src/src/sql2/error.rs +1 -1
  123. package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
  124. package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
  125. package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
  126. package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
  127. package/dist-engine-src/src/sql2/exec/write.rs +661 -0
  128. package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
  129. package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
  130. package/dist-engine-src/src/sql2/history_projection.rs +8 -8
  131. package/dist-engine-src/src/sql2/history_route.rs +35 -31
  132. package/dist-engine-src/src/sql2/mod.rs +28 -23
  133. package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
  134. package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
  135. package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
  136. package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
  137. package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
  138. package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
  139. package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
  140. package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
  141. package/dist-engine-src/src/sql2/plan/write.rs +147 -0
  142. package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
  143. package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
  144. package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
  145. package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
  146. package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
  147. package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
  148. package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
  149. package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
  150. package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
  151. package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
  152. package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
  153. package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
  154. package/dist-engine-src/src/sql2/read_only.rs +2 -2
  155. package/dist-engine-src/src/sql2/session.rs +47 -96
  156. package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
  157. package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
  158. package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
  159. package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
  160. package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
  161. package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
  162. package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
  163. package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
  164. package/dist-engine-src/src/storage/conformance.rs +399 -0
  165. package/dist-engine-src/src/storage/context.rs +552 -288
  166. package/dist-engine-src/src/storage/mod.rs +48 -10
  167. package/dist-engine-src/src/storage/point.rs +440 -0
  168. package/dist-engine-src/src/storage/read_scope.rs +43 -64
  169. package/dist-engine-src/src/storage/reader.rs +867 -0
  170. package/dist-engine-src/src/storage/scan.rs +784 -0
  171. package/dist-engine-src/src/storage/spaces.rs +236 -0
  172. package/dist-engine-src/src/storage/stats.rs +80 -0
  173. package/dist-engine-src/src/storage/write_set.rs +962 -0
  174. package/dist-engine-src/src/storage_bench.rs +136 -4828
  175. package/dist-engine-src/src/test_support.rs +360 -138
  176. package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
  177. package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
  178. package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
  179. package/dist-engine-src/src/tracked_state/context.rs +1927 -993
  180. package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
  181. package/dist-engine-src/src/tracked_state/merge.rs +74 -88
  182. package/dist-engine-src/src/tracked_state/mod.rs +19 -16
  183. package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
  184. package/dist-engine-src/src/tracked_state/storage.rs +243 -191
  185. package/dist-engine-src/src/tracked_state/tree.rs +247 -371
  186. package/dist-engine-src/src/tracked_state/types.rs +49 -42
  187. package/dist-engine-src/src/transaction/bench_support.rs +407 -0
  188. package/dist-engine-src/src/transaction/commit.rs +821 -713
  189. package/dist-engine-src/src/transaction/context.rs +705 -600
  190. package/dist-engine-src/src/transaction/mod.rs +13 -2
  191. package/dist-engine-src/src/transaction/normalization.rs +63 -76
  192. package/dist-engine-src/src/transaction/prep.rs +13 -13
  193. package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
  194. package/dist-engine-src/src/transaction/staging.rs +228 -434
  195. package/dist-engine-src/src/transaction/types.rs +41 -98
  196. package/dist-engine-src/src/transaction/validation.rs +382 -446
  197. package/dist-engine-src/src/untracked_state/codec.rs +337 -29
  198. package/dist-engine-src/src/untracked_state/context.rs +7 -7
  199. package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
  200. package/dist-engine-src/src/untracked_state/mod.rs +1 -1
  201. package/dist-engine-src/src/untracked_state/storage.rs +659 -157
  202. package/dist-engine-src/src/untracked_state/types.rs +21 -21
  203. package/package.json +71 -68
  204. package/dist-engine-src/src/backend/kv.rs +0 -358
  205. package/dist-engine-src/src/backend/testing.rs +0 -658
  206. package/dist-engine-src/src/commit_store/codec.rs +0 -887
  207. package/dist-engine-src/src/commit_store/context.rs +0 -944
  208. package/dist-engine-src/src/commit_store/materialization.rs +0 -84
  209. package/dist-engine-src/src/commit_store/mod.rs +0 -16
  210. package/dist-engine-src/src/commit_store/storage.rs +0 -600
  211. package/dist-engine-src/src/commit_store/types.rs +0 -215
  212. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
  213. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
  214. package/dist-engine-src/src/session/create_version.rs +0 -88
  215. package/dist-engine-src/src/session/merge/apply.rs +0 -23
  216. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
  217. package/dist-engine-src/src/session/switch_version.rs +0 -110
  218. package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
  219. package/dist-engine-src/src/sql2/execute.rs +0 -3533
  220. package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
  221. package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
  222. package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
  223. package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
  224. package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
  225. package/dist-engine-src/src/sql2/version_scope.rs +0 -394
  226. package/dist-engine-src/src/storage/types.rs +0 -501
  227. package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
  228. package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
  229. package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
  230. package/dist-engine-src/src/version/lifecycle.rs +0 -221
  231. package/dist-engine-src/src/version/mod.rs +0 -13
  232. package/dist-engine-src/src/version/refs.rs +0 -330
  233. package/dist-engine-src/src/version/stage_rows.rs +0 -67
  234. package/dist-engine-src/src/version/types.rs +0 -21
@@ -25,70 +25,88 @@ use datafusion::scalar::ScalarValue;
25
25
  use futures_util::{stream, TryStreamExt};
26
26
  use serde_json::Value as JsonValue;
27
27
 
28
- use crate::entity_identity::EntityIdentity;
28
+ use crate::branch::BranchRefReader;
29
+ use crate::entity_pk::EntityPk;
29
30
  use crate::live_state::MaterializedLiveStateRow;
30
31
  use crate::live_state::{
31
- LiveStateFilter, LiveStateProjection, LiveStateReader, LiveStateScanRequest,
32
+ LiveStateFilter, LiveStateProjection, LiveStateReader, LiveStateRowFilter, LiveStateScanRequest,
32
33
  };
34
+ use crate::sql2::branch_scope::{resolve_provider_branch_ids, BranchBinding};
33
35
  use crate::sql2::dml::{InsertExec, InsertSink};
34
36
  use crate::sql2::read_only::reject_read_only_stage_rows;
35
- use crate::sql2::version_scope::{resolve_provider_version_ids, VersionBinding};
36
37
  use crate::sql2::write_normalization::{InsertCell, SqlCell, UpdateAssignmentValues};
37
38
  use crate::transaction::types::{TransactionJson, TransactionWriteRow};
38
- use crate::version::VersionRefReader;
39
- use crate::GLOBAL_VERSION_ID;
39
+ use crate::GLOBAL_BRANCH_ID;
40
40
  use crate::{parse_row_metadata_value, serialize_row_metadata, LixError, NullableKeyFilter};
41
41
 
42
42
  use crate::sql2::{
43
- SqlWriteContext, WriteAccess, WriteContextLiveStateReader, WriteContextVersionRefReader,
43
+ SqlWriteContext, WriteAccess, WriteContextBranchRefReader, WriteContextLiveStateReader,
44
44
  };
45
45
  use crate::transaction::types::{TransactionWrite, TransactionWriteMode};
46
46
 
47
- use super::predicate_typecheck::validate_json_predicate_filters;
48
- use super::result_metadata::json_field;
47
+ use crate::sql2::predicate_typecheck::{
48
+ canonicalize_json_identity_text_filters, validate_json_predicate_filters,
49
+ };
50
+ use crate::sql2::result_metadata::json_field;
49
51
 
50
- pub(crate) async fn register_lix_state_providers(
52
+ pub(super) async fn register_lix_state_active_provider(
51
53
  session: &SessionContext,
52
- active_version_id: &str,
54
+ surface_name: &str,
55
+ active_branch_id: &str,
53
56
  live_state: Arc<dyn LiveStateReader>,
54
- version_ref: Arc<dyn VersionRefReader>,
57
+ branch_ref: Arc<dyn BranchRefReader>,
55
58
  ) -> Result<(), LixError> {
56
59
  session
57
60
  .register_table(
58
- "lix_state_by_version",
59
- Arc::new(LixStateProvider::by_version(
60
- Arc::clone(&live_state),
61
- Arc::clone(&version_ref),
61
+ surface_name,
62
+ Arc::new(LixStateProvider::active_branch(
63
+ active_branch_id,
64
+ live_state,
65
+ branch_ref,
62
66
  )),
63
67
  )
64
68
  .map_err(datafusion_error_to_lix_error)?;
69
+ Ok(())
70
+ }
71
+
72
+ pub(super) async fn register_lix_state_by_branch_provider(
73
+ session: &SessionContext,
74
+ surface_name: &str,
75
+ live_state: Arc<dyn LiveStateReader>,
76
+ branch_ref: Arc<dyn BranchRefReader>,
77
+ ) -> Result<(), LixError> {
65
78
  session
66
79
  .register_table(
67
- "lix_state",
68
- Arc::new(LixStateProvider::active_version(
69
- active_version_id,
70
- live_state,
71
- version_ref,
72
- )),
80
+ surface_name,
81
+ Arc::new(LixStateProvider::by_branch(live_state, branch_ref)),
73
82
  )
74
83
  .map_err(datafusion_error_to_lix_error)?;
75
84
  Ok(())
76
85
  }
77
86
 
78
- pub(crate) async fn register_lix_state_write_providers(
87
+ pub(super) async fn register_lix_state_by_branch_write_provider(
79
88
  session: &SessionContext,
89
+ surface_name: &str,
80
90
  write_ctx: SqlWriteContext,
81
91
  ) -> Result<(), LixError> {
82
92
  session
83
93
  .register_table(
84
- "lix_state_by_version",
85
- Arc::new(LixStateProvider::by_version_with_write(write_ctx.clone())),
94
+ surface_name,
95
+ Arc::new(LixStateProvider::by_branch_with_write(write_ctx)),
86
96
  )
87
97
  .map_err(datafusion_error_to_lix_error)?;
98
+ Ok(())
99
+ }
100
+
101
+ pub(super) async fn register_lix_state_active_write_provider(
102
+ session: &SessionContext,
103
+ surface_name: &str,
104
+ write_ctx: SqlWriteContext,
105
+ ) -> Result<(), LixError> {
88
106
  session
89
107
  .register_table(
90
- "lix_state",
91
- Arc::new(LixStateProvider::active_version_with_write(write_ctx)),
108
+ surface_name,
109
+ Arc::new(LixStateProvider::active_branch_with_write(write_ctx)),
92
110
  )
93
111
  .map_err(datafusion_error_to_lix_error)?;
94
112
  Ok(())
@@ -97,9 +115,9 @@ pub(crate) async fn register_lix_state_write_providers(
97
115
  pub(crate) struct LixStateProvider {
98
116
  schema: SchemaRef,
99
117
  live_state: Arc<dyn LiveStateReader>,
100
- version_ref: Arc<dyn VersionRefReader>,
118
+ branch_ref: Arc<dyn BranchRefReader>,
101
119
  write_access: WriteAccess,
102
- version_binding: VersionBinding,
120
+ branch_binding: BranchBinding,
103
121
  }
104
122
 
105
123
  impl std::fmt::Debug for LixStateProvider {
@@ -111,55 +129,55 @@ impl std::fmt::Debug for LixStateProvider {
111
129
  }
112
130
 
113
131
  impl LixStateProvider {
114
- pub(crate) fn active_version(
115
- active_version_id: impl Into<String>,
132
+ pub(crate) fn active_branch(
133
+ active_branch_id: impl Into<String>,
116
134
  live_state: Arc<dyn LiveStateReader>,
117
- version_ref: Arc<dyn VersionRefReader>,
135
+ branch_ref: Arc<dyn BranchRefReader>,
118
136
  ) -> Self {
119
137
  Self {
120
138
  schema: lix_state_schema(),
121
139
  live_state,
122
- version_ref,
140
+ branch_ref,
123
141
  write_access: WriteAccess::read_only(),
124
- version_binding: VersionBinding::active(active_version_id),
142
+ branch_binding: BranchBinding::active(active_branch_id),
125
143
  }
126
144
  }
127
145
 
128
- pub(crate) fn active_version_with_write(write_ctx: SqlWriteContext) -> Self {
129
- let active_version_id = write_ctx.active_version_id();
146
+ pub(crate) fn active_branch_with_write(write_ctx: SqlWriteContext) -> Self {
147
+ let active_branch_id = write_ctx.active_branch_id();
130
148
  let live_state = Arc::new(WriteContextLiveStateReader::new(write_ctx.clone()));
131
- let version_ref = Arc::new(WriteContextVersionRefReader::new(write_ctx.clone()));
149
+ let branch_ref = Arc::new(WriteContextBranchRefReader::new(write_ctx.clone()));
132
150
  Self {
133
151
  schema: lix_state_schema(),
134
152
  live_state,
135
- version_ref,
153
+ branch_ref,
136
154
  write_access: WriteAccess::write(write_ctx),
137
- version_binding: VersionBinding::active(active_version_id),
155
+ branch_binding: BranchBinding::active(active_branch_id),
138
156
  }
139
157
  }
140
158
 
141
- pub(crate) fn by_version(
159
+ pub(crate) fn by_branch(
142
160
  live_state: Arc<dyn LiveStateReader>,
143
- version_ref: Arc<dyn VersionRefReader>,
161
+ branch_ref: Arc<dyn BranchRefReader>,
144
162
  ) -> Self {
145
163
  Self {
146
- schema: lix_state_by_version_schema(),
164
+ schema: lix_state_by_branch_schema(),
147
165
  live_state,
148
- version_ref,
166
+ branch_ref,
149
167
  write_access: WriteAccess::read_only(),
150
- version_binding: VersionBinding::explicit(),
168
+ branch_binding: BranchBinding::explicit(),
151
169
  }
152
170
  }
153
171
 
154
- pub(crate) fn by_version_with_write(write_ctx: SqlWriteContext) -> Self {
172
+ pub(crate) fn by_branch_with_write(write_ctx: SqlWriteContext) -> Self {
155
173
  let live_state = Arc::new(WriteContextLiveStateReader::new(write_ctx.clone()));
156
- let version_ref = Arc::new(WriteContextVersionRefReader::new(write_ctx.clone()));
174
+ let branch_ref = Arc::new(WriteContextBranchRefReader::new(write_ctx.clone()));
157
175
  Self {
158
- schema: lix_state_by_version_schema(),
176
+ schema: lix_state_by_branch_schema(),
159
177
  live_state,
160
- version_ref,
178
+ branch_ref,
161
179
  write_access: WriteAccess::write(write_ctx),
162
- version_binding: VersionBinding::explicit(),
180
+ branch_binding: BranchBinding::explicit(),
163
181
  }
164
182
  }
165
183
  }
@@ -201,24 +219,22 @@ impl TableProvider for LixStateProvider {
201
219
  filters: &[Expr],
202
220
  limit: Option<usize>,
203
221
  ) -> Result<Arc<dyn datafusion::physical_plan::ExecutionPlan>> {
204
- let route = LixStateByVersionRoute::from_filters(filters);
222
+ let route = LixStateByBranchRoute::from_filters(filters);
205
223
  let projected_schema = projected_schema(&self.schema, projection)?;
206
224
  let mut request = lix_state_scan_request(
207
225
  &self.schema,
208
- self.version_binding.active_version_id(),
226
+ self.branch_binding.active_branch_id(),
209
227
  projection,
210
228
  &route,
211
229
  limit,
212
230
  );
213
- if !route.contradictory {
214
- request.filter.version_ids = resolve_provider_version_ids(
215
- self.version_ref.as_ref(),
216
- &self.version_binding,
217
- request.filter.version_ids,
218
- )
219
- .await
220
- .map_err(lix_error_to_datafusion_error)?;
221
- }
231
+ request.filter.branch_ids = resolve_provider_branch_ids(
232
+ self.branch_ref.as_ref(),
233
+ &self.branch_binding,
234
+ request.filter.branch_ids,
235
+ )
236
+ .await
237
+ .map_err(lix_error_to_datafusion_error)?;
222
238
  Ok(Arc::new(LixStateScanExec::new(
223
239
  Arc::clone(&self.live_state),
224
240
  projected_schema,
@@ -236,21 +252,13 @@ impl TableProvider for LixStateProvider {
236
252
  return not_impl_err!("{insert_op} not implemented for lix_state yet");
237
253
  }
238
254
 
239
- let active_version_id = self
240
- .version_binding
241
- .require_active_version_id("INSERT")
242
- .map_err(lix_error_to_datafusion_error)?;
243
-
244
255
  let write_ctx = self.write_access.require_write("INSERT into lix_state")?;
256
+ let branch_binding = self.branch_binding.active_branch_id().map(str::to_owned);
245
257
 
246
258
  self.schema
247
259
  .logically_equivalent_names_and_types(&input.schema())?;
248
260
 
249
- let sink = LixStateInsertSink::new(
250
- Arc::clone(&self.schema),
251
- write_ctx.clone(),
252
- active_version_id,
253
- );
261
+ let sink = LixStateInsertSink::new(write_ctx.clone(), branch_binding);
254
262
  Ok(Arc::new(InsertExec::new(input, Arc::new(sink))))
255
263
  }
256
264
 
@@ -259,28 +267,25 @@ impl TableProvider for LixStateProvider {
259
267
  state: &dyn Session,
260
268
  filters: Vec<Expr>,
261
269
  ) -> Result<Arc<dyn ExecutionPlan>> {
262
- let active_version_id = self
263
- .version_binding
264
- .require_active_version_id("DELETE")
265
- .map_err(lix_error_to_datafusion_error)?;
266
-
267
270
  let write_ctx = self.write_access.require_write("DELETE FROM lix_state")?;
268
271
 
269
272
  let df_schema = DFSchema::try_from(Arc::clone(&self.schema))?;
273
+ let filters = canonicalize_json_identity_text_filters(self.schema.as_ref(), &filters)?;
270
274
  validate_json_predicate_filters(self.schema.as_ref(), &filters)?;
271
275
  let physical_filters = filters
272
276
  .iter()
273
277
  .map(|expr| create_physical_expr(expr, &df_schema, state.execution_props()))
274
278
  .collect::<Result<Vec<_>>>()?;
275
279
 
276
- let route = LixStateByVersionRoute::from_filters(&filters);
280
+ let route = LixStateByBranchRoute::from_filters(&filters);
281
+ let branch_binding = self.branch_binding.active_branch_id().map(str::to_owned);
277
282
  let request =
278
- lix_state_scan_request(&self.schema, Some(&active_version_id), None, &route, None);
283
+ lix_state_scan_request(&self.schema, branch_binding.as_deref(), None, &route, None);
279
284
 
280
285
  Ok(Arc::new(LixStateDeleteExec::new(
281
286
  write_ctx.clone(),
282
287
  Arc::clone(&self.schema),
283
- active_version_id,
288
+ branch_binding,
284
289
  request,
285
290
  physical_filters,
286
291
  )))
@@ -292,16 +297,11 @@ impl TableProvider for LixStateProvider {
292
297
  assignments: Vec<(String, Expr)>,
293
298
  filters: Vec<Expr>,
294
299
  ) -> Result<Arc<dyn ExecutionPlan>> {
295
- let active_version_id = self
296
- .version_binding
297
- .require_active_version_id("UPDATE")
298
- .map_err(lix_error_to_datafusion_error)?;
299
-
300
300
  let write_ctx = self.write_access.require_write("UPDATE lix_state")?;
301
-
302
301
  validate_lix_state_update_assignments(&self.schema, &assignments)?;
303
302
 
304
303
  let df_schema = DFSchema::try_from(Arc::clone(&self.schema))?;
304
+ let filters = canonicalize_json_identity_text_filters(self.schema.as_ref(), &filters)?;
305
305
  validate_json_predicate_filters(self.schema.as_ref(), &filters)?;
306
306
  let physical_assignments = assignments
307
307
  .iter()
@@ -317,14 +317,15 @@ impl TableProvider for LixStateProvider {
317
317
  .map(|expr| create_physical_expr(expr, &df_schema, state.execution_props()))
318
318
  .collect::<Result<Vec<_>>>()?;
319
319
 
320
- let route = LixStateByVersionRoute::from_filters(&filters);
320
+ let route = LixStateByBranchRoute::from_filters(&filters);
321
+ let branch_binding = self.branch_binding.active_branch_id().map(str::to_owned);
321
322
  let request =
322
- lix_state_scan_request(&self.schema, Some(&active_version_id), None, &route, None);
323
+ lix_state_scan_request(&self.schema, branch_binding.as_deref(), None, &route, None);
323
324
 
324
325
  Ok(Arc::new(LixStateUpdateExec::new(
325
326
  write_ctx.clone(),
326
327
  Arc::clone(&self.schema),
327
- active_version_id,
328
+ branch_binding,
328
329
  request,
329
330
  physical_assignments,
330
331
  physical_filters,
@@ -334,7 +335,7 @@ impl TableProvider for LixStateProvider {
334
335
 
335
336
  struct LixStateInsertSink {
336
337
  write_ctx: SqlWriteContext,
337
- version_binding: String,
338
+ branch_binding: Option<String>,
338
339
  }
339
340
 
340
341
  impl std::fmt::Debug for LixStateInsertSink {
@@ -344,10 +345,10 @@ impl std::fmt::Debug for LixStateInsertSink {
344
345
  }
345
346
 
346
347
  impl LixStateInsertSink {
347
- fn new(_schema: SchemaRef, write_ctx: SqlWriteContext, version_binding: String) -> Self {
348
+ fn new(write_ctx: SqlWriteContext, branch_binding: Option<String>) -> Self {
348
349
  Self {
349
350
  write_ctx,
350
- version_binding,
351
+ branch_binding,
351
352
  }
352
353
  }
353
354
  }
@@ -374,7 +375,8 @@ impl InsertSink for LixStateInsertSink {
374
375
  for batch in batches {
375
376
  rows.extend(lix_state_write_rows_from_batch(
376
377
  &batch,
377
- &self.version_binding,
378
+ self.branch_binding.as_deref(),
379
+ "INSERT into lix_state",
378
380
  )?);
379
381
  }
380
382
  reject_read_only_stage_rows(&rows, "INSERT into lix_state")?;
@@ -397,7 +399,7 @@ impl InsertSink for LixStateInsertSink {
397
399
  struct LixStateDeleteExec {
398
400
  write_ctx: SqlWriteContext,
399
401
  table_schema: SchemaRef,
400
- version_binding: String,
402
+ branch_binding: Option<String>,
401
403
  request: LiveStateScanRequest,
402
404
  filters: Vec<Arc<dyn PhysicalExpr>>,
403
405
  result_schema: SchemaRef,
@@ -414,7 +416,7 @@ impl LixStateDeleteExec {
414
416
  fn new(
415
417
  write_ctx: SqlWriteContext,
416
418
  table_schema: SchemaRef,
417
- version_binding: String,
419
+ branch_binding: Option<String>,
418
420
  request: LiveStateScanRequest,
419
421
  filters: Vec<Arc<dyn PhysicalExpr>>,
420
422
  ) -> Self {
@@ -428,7 +430,7 @@ impl LixStateDeleteExec {
428
430
  Self {
429
431
  write_ctx,
430
432
  table_schema,
431
- version_binding,
433
+ branch_binding,
432
434
  request,
433
435
  filters,
434
436
  result_schema,
@@ -489,26 +491,24 @@ impl ExecutionPlan for LixStateDeleteExec {
489
491
  }
490
492
  let write_ctx = self.write_ctx.clone();
491
493
  let table_schema = Arc::clone(&self.table_schema);
492
- let version_binding = self.version_binding.clone();
494
+ let branch_binding = self.branch_binding.clone();
493
495
  let request = self.request.clone();
494
496
  let filters = self.filters.clone();
495
497
  let result_schema = Arc::clone(&self.result_schema);
496
498
  let stream_schema = Arc::clone(&result_schema);
497
499
 
498
500
  let stream = stream::once(async move {
499
- let rows = if request.limit == Some(0) {
500
- Vec::new()
501
- } else {
502
- write_ctx
503
- .scan_live_state(&request)
504
- .await
505
- .map_err(lix_error_to_datafusion_error)?
506
- };
501
+ let rows = write_ctx
502
+ .scan_live_state(&request)
503
+ .await
504
+ .map_err(lix_error_to_datafusion_error)?;
507
505
  let source_batch = lix_state_record_batch(Arc::clone(&table_schema), &rows)
508
506
  .map_err(lix_error_to_datafusion_error)?;
509
507
  let matched_batch = filter_lix_state_batch(source_batch, &filters)?;
510
- let write_rows =
511
- lix_state_deletable_write_rows_from_batch(&matched_batch, &version_binding)?;
508
+ let write_rows = lix_state_deletable_write_rows_from_batch(
509
+ &matched_batch,
510
+ branch_binding.as_deref(),
511
+ )?;
512
512
  reject_read_only_stage_rows(&write_rows, "DELETE FROM lix_state")?;
513
513
  let count = u64::try_from(write_rows.len())
514
514
  .map_err(|_| DataFusionError::Execution("DELETE row count overflow".to_string()))?;
@@ -539,7 +539,7 @@ impl ExecutionPlan for LixStateDeleteExec {
539
539
  struct LixStateUpdateExec {
540
540
  write_ctx: SqlWriteContext,
541
541
  table_schema: SchemaRef,
542
- version_binding: String,
542
+ branch_binding: Option<String>,
543
543
  request: LiveStateScanRequest,
544
544
  assignments: Vec<(String, Arc<dyn PhysicalExpr>)>,
545
545
  filters: Vec<Arc<dyn PhysicalExpr>>,
@@ -557,7 +557,7 @@ impl LixStateUpdateExec {
557
557
  fn new(
558
558
  write_ctx: SqlWriteContext,
559
559
  table_schema: SchemaRef,
560
- version_binding: String,
560
+ branch_binding: Option<String>,
561
561
  request: LiveStateScanRequest,
562
562
  assignments: Vec<(String, Arc<dyn PhysicalExpr>)>,
563
563
  filters: Vec<Arc<dyn PhysicalExpr>>,
@@ -572,7 +572,7 @@ impl LixStateUpdateExec {
572
572
  Self {
573
573
  write_ctx,
574
574
  table_schema,
575
- version_binding,
575
+ branch_binding,
576
576
  request,
577
577
  assignments,
578
578
  filters,
@@ -639,7 +639,7 @@ impl ExecutionPlan for LixStateUpdateExec {
639
639
  }
640
640
  let write_ctx = self.write_ctx.clone();
641
641
  let table_schema = Arc::clone(&self.table_schema);
642
- let version_binding = self.version_binding.clone();
642
+ let branch_binding = self.branch_binding.clone();
643
643
  let request = self.request.clone();
644
644
  let assignments = self.assignments.clone();
645
645
  let filters = self.filters.clone();
@@ -647,21 +647,17 @@ impl ExecutionPlan for LixStateUpdateExec {
647
647
  let stream_schema = Arc::clone(&result_schema);
648
648
 
649
649
  let stream = stream::once(async move {
650
- let rows = if request.limit == Some(0) {
651
- Vec::new()
652
- } else {
653
- write_ctx
654
- .scan_live_state(&request)
655
- .await
656
- .map_err(lix_error_to_datafusion_error)?
657
- };
650
+ let rows = write_ctx
651
+ .scan_live_state(&request)
652
+ .await
653
+ .map_err(lix_error_to_datafusion_error)?;
658
654
  let source_batch = lix_state_record_batch(Arc::clone(&table_schema), &rows)
659
655
  .map_err(lix_error_to_datafusion_error)?;
660
656
  let matched_batch = filter_lix_state_batch(source_batch, &filters)?;
661
657
  let write_rows = lix_state_update_write_rows_from_batch(
662
658
  &matched_batch,
663
659
  &assignments,
664
- &version_binding,
660
+ branch_binding.as_deref(),
665
661
  )?;
666
662
  reject_read_only_stage_rows(&write_rows, "UPDATE lix_state")?;
667
663
  let count = u64::try_from(write_rows.len())
@@ -689,25 +685,6 @@ impl ExecutionPlan for LixStateUpdateExec {
689
685
  }
690
686
  }
691
687
 
692
- fn validate_lix_state_update_assignments(
693
- schema: &SchemaRef,
694
- assignments: &[(String, Expr)],
695
- ) -> Result<()> {
696
- for (column_name, _) in assignments {
697
- schema.field_with_name(column_name).map_err(|_| {
698
- DataFusionError::Plan(format!(
699
- "UPDATE lix_state failed: column '{column_name}' does not exist"
700
- ))
701
- })?;
702
- if !matches!(column_name.as_str(), "snapshot_content" | "metadata") {
703
- return Err(DataFusionError::Execution(format!(
704
- "UPDATE lix_state cannot stage read-only column '{column_name}'"
705
- )));
706
- }
707
- }
708
- Ok(())
709
- }
710
-
711
688
  fn filter_lix_state_batch(
712
689
  batch: RecordBatch,
713
690
  filters: &[Arc<dyn PhysicalExpr>],
@@ -750,9 +727,10 @@ fn evaluate_lix_state_filters(
750
727
 
751
728
  fn lix_state_stageable_write_rows_from_batch(
752
729
  batch: &RecordBatch,
753
- version_binding: &str,
730
+ branch_binding: Option<&str>,
731
+ action: &str,
754
732
  ) -> Result<Vec<TransactionWriteRow>> {
755
- let mut rows = lix_state_write_rows_from_batch(batch, version_binding)?;
733
+ let mut rows = lix_state_write_rows_from_batch(batch, branch_binding, action)?;
756
734
  for row in &mut rows {
757
735
  row.created_at = None;
758
736
  row.updated_at = None;
@@ -765,31 +743,36 @@ fn lix_state_stageable_write_rows_from_batch(
765
743
  fn lix_state_update_write_rows_from_batch(
766
744
  batch: &RecordBatch,
767
745
  assignments: &[(String, Arc<dyn PhysicalExpr>)],
768
- version_binding: &str,
746
+ branch_binding: Option<&str>,
769
747
  ) -> Result<Vec<TransactionWriteRow>> {
770
748
  let assignment_values = UpdateAssignmentValues::evaluate(batch, assignments)?;
771
749
  (0..batch.num_rows())
772
750
  .map(|row_index| {
773
751
  let global = optional_bool_value(batch, row_index, "global")?.unwrap_or(false);
774
- let version_id =
775
- optional_string_value(batch, row_index, "version_id")?.unwrap_or_else(|| {
752
+ let branch_id =
753
+ optional_string_value(batch, row_index, "branch_id")?.unwrap_or_else(|| {
776
754
  if global {
777
- GLOBAL_VERSION_ID.to_string()
755
+ GLOBAL_BRANCH_ID.to_string()
778
756
  } else {
779
- version_binding.to_string()
757
+ branch_binding.unwrap_or_default().to_string()
780
758
  }
781
759
  });
760
+ if !global && branch_id.is_empty() {
761
+ return Err(DataFusionError::Execution(
762
+ "UPDATE lix_state_by_branch requires branch_id".to_string(),
763
+ ));
764
+ }
782
765
 
783
766
  Ok(TransactionWriteRow {
784
- entity_id: Some(
785
- EntityIdentity::from_json_array_text(&required_string_value(
767
+ entity_pk: Some(
768
+ EntityPk::from_json_array_text(&required_string_value(
786
769
  batch,
787
770
  row_index,
788
- "entity_id",
771
+ "entity_pk",
789
772
  )?)
790
773
  .map_err(|error| {
791
774
  DataFusionError::Execution(format!(
792
- "lix_state UPDATE has invalid entity_id: {error}"
775
+ "lix_state UPDATE has invalid entity_pk: {error}"
793
776
  ))
794
777
  })?,
795
778
  ),
@@ -815,7 +798,7 @@ fn lix_state_update_write_rows_from_batch(
815
798
  change_id: None,
816
799
  commit_id: None,
817
800
  untracked: optional_bool_value(batch, row_index, "untracked")?.unwrap_or(false),
818
- version_id,
801
+ branch_id,
819
802
  })
820
803
  })
821
804
  .collect()
@@ -823,9 +806,10 @@ fn lix_state_update_write_rows_from_batch(
823
806
 
824
807
  fn lix_state_deletable_write_rows_from_batch(
825
808
  batch: &RecordBatch,
826
- version_binding: &str,
809
+ branch_binding: Option<&str>,
827
810
  ) -> Result<Vec<TransactionWriteRow>> {
828
- let mut rows = lix_state_stageable_write_rows_from_batch(batch, version_binding)?;
811
+ let mut rows =
812
+ lix_state_stageable_write_rows_from_batch(batch, branch_binding, "DELETE FROM lix_state")?;
829
813
  for row in &mut rows {
830
814
  row.snapshot = None;
831
815
  }
@@ -861,9 +845,9 @@ fn update_optional_metadata_value(
861
845
  update_optional_string_value(batch, assignment_values, row_index, column_name)?
862
846
  .map(|value| {
863
847
  let metadata = parse_row_metadata_value(&value, context)
864
- .map_err(super::error::lix_error_to_datafusion_error)?;
848
+ .map_err(crate::sql2::error::lix_error_to_datafusion_error)?;
865
849
  TransactionJson::from_value(metadata, &format!("{context} metadata"))
866
- .map_err(super::error::lix_error_to_datafusion_error)
850
+ .map_err(crate::sql2::error::lix_error_to_datafusion_error)
867
851
  })
868
852
  .transpose()
869
853
  }
@@ -897,30 +881,36 @@ fn dml_count_batch(schema: SchemaRef, count: u64) -> Result<RecordBatch> {
897
881
 
898
882
  fn lix_state_write_rows_from_batch(
899
883
  batch: &RecordBatch,
900
- version_binding: &str,
884
+ branch_binding: Option<&str>,
885
+ action: &str,
901
886
  ) -> Result<Vec<TransactionWriteRow>> {
902
887
  (0..batch.num_rows())
903
888
  .map(|row_index| {
904
889
  let global = optional_bool_value(batch, row_index, "global")?.unwrap_or(false);
905
- let version_id =
906
- optional_string_value(batch, row_index, "version_id")?.unwrap_or_else(|| {
890
+ let branch_id =
891
+ optional_string_value(batch, row_index, "branch_id")?.unwrap_or_else(|| {
907
892
  if global {
908
- GLOBAL_VERSION_ID.to_string()
893
+ GLOBAL_BRANCH_ID.to_string()
909
894
  } else {
910
- version_binding.to_string()
895
+ branch_binding.unwrap_or_default().to_string()
911
896
  }
912
897
  });
898
+ if !global && branch_id.is_empty() {
899
+ return Err(DataFusionError::Execution(format!(
900
+ "{action} requires branch_id"
901
+ )));
902
+ }
913
903
 
914
904
  Ok(TransactionWriteRow {
915
- entity_id: Some(
916
- EntityIdentity::from_json_array_text(&required_string_value(
905
+ entity_pk: Some(
906
+ EntityPk::from_json_array_text(&required_string_value(
917
907
  batch,
918
908
  row_index,
919
- "entity_id",
909
+ "entity_pk",
920
910
  )?)
921
911
  .map_err(|error| {
922
912
  DataFusionError::Execution(format!(
923
- "lix_state INSERT has invalid entity_id: {error}"
913
+ "lix_state INSERT has invalid entity_pk: {error}"
924
914
  ))
925
915
  })?,
926
916
  ),
@@ -935,12 +925,34 @@ fn lix_state_write_rows_from_batch(
935
925
  change_id: optional_string_value(batch, row_index, "change_id")?,
936
926
  commit_id: optional_string_value(batch, row_index, "commit_id")?,
937
927
  untracked: optional_bool_value(batch, row_index, "untracked")?.unwrap_or(false),
938
- version_id,
928
+ branch_id,
939
929
  })
940
930
  })
941
931
  .collect()
942
932
  }
943
933
 
934
+ fn validate_lix_state_update_assignments(
935
+ schema: &SchemaRef,
936
+ assignments: &[(String, Expr)],
937
+ ) -> Result<()> {
938
+ for (column_name, _) in assignments {
939
+ schema.field_with_name(column_name).map_err(|_| {
940
+ DataFusionError::Plan(format!(
941
+ "UPDATE lix_state failed: column '{column_name}' does not exist"
942
+ ))
943
+ })?;
944
+ if !matches!(
945
+ column_name.as_str(),
946
+ "snapshot_content" | "metadata" | "global" | "untracked"
947
+ ) {
948
+ return Err(DataFusionError::Execution(format!(
949
+ "UPDATE lix_state cannot stage read-only column '{column_name}'"
950
+ )));
951
+ }
952
+ }
953
+ Ok(())
954
+ }
955
+
944
956
  fn required_string_value(
945
957
  batch: &RecordBatch,
946
958
  row_index: usize,
@@ -982,9 +994,9 @@ fn optional_metadata_value(
982
994
  optional_string_value(batch, row_index, column_name)?
983
995
  .map(|value| {
984
996
  let metadata = parse_row_metadata_value(&value, context)
985
- .map_err(super::error::lix_error_to_datafusion_error)?;
997
+ .map_err(crate::sql2::error::lix_error_to_datafusion_error)?;
986
998
  TransactionJson::from_value(metadata, &format!("{context} metadata"))
987
- .map_err(super::error::lix_error_to_datafusion_error)
999
+ .map_err(crate::sql2::error::lix_error_to_datafusion_error)
988
1000
  })
989
1001
  .transpose()
990
1002
  }
@@ -1006,7 +1018,7 @@ fn parse_snapshot_json(value: &str, column_name: &str) -> Result<TransactionJson
1006
1018
  ))
1007
1019
  })?;
1008
1020
  TransactionJson::from_value(parsed, &format!("lix_state {column_name}"))
1009
- .map_err(super::error::lix_error_to_datafusion_error)
1021
+ .map_err(crate::sql2::error::lix_error_to_datafusion_error)
1010
1022
  }
1011
1023
 
1012
1024
  fn optional_bool_value(
@@ -1140,14 +1152,10 @@ impl ExecutionPlan for LixStateScanExec {
1140
1152
  let request = self.request.clone();
1141
1153
  let stream_schema = Arc::clone(&schema);
1142
1154
  let stream = stream::once(async move {
1143
- let rows = if request.limit == Some(0) {
1144
- Vec::new()
1145
- } else {
1146
- live_state
1147
- .scan_rows(&request)
1148
- .await
1149
- .map_err(lix_error_to_datafusion_error)?
1150
- };
1155
+ let rows = live_state
1156
+ .scan_rows(&request)
1157
+ .await
1158
+ .map_err(lix_error_to_datafusion_error)?;
1151
1159
  let batch = lix_state_record_batch(Arc::clone(&stream_schema), &rows)
1152
1160
  .map_err(lix_error_to_datafusion_error)?;
1153
1161
  Ok::<_, DataFusionError>(stream::iter(vec![Ok::<RecordBatch, DataFusionError>(
@@ -1159,9 +1167,9 @@ impl ExecutionPlan for LixStateScanExec {
1159
1167
  }
1160
1168
  }
1161
1169
 
1162
- fn lix_state_schema() -> SchemaRef {
1170
+ pub(super) fn lix_state_schema() -> SchemaRef {
1163
1171
  Arc::new(Schema::new(vec![
1164
- json_field("entity_id", false),
1172
+ json_field("entity_pk", false),
1165
1173
  Field::new("schema_key", DataType::Utf8, false),
1166
1174
  Field::new("file_id", DataType::Utf8, true),
1167
1175
  json_field("snapshot_content", true),
@@ -1175,9 +1183,9 @@ fn lix_state_schema() -> SchemaRef {
1175
1183
  ]))
1176
1184
  }
1177
1185
 
1178
- fn lix_state_by_version_schema() -> SchemaRef {
1186
+ pub(super) fn lix_state_by_branch_schema() -> SchemaRef {
1179
1187
  Arc::new(Schema::new(vec![
1180
- json_field("entity_id", false),
1188
+ json_field("entity_pk", false),
1181
1189
  Field::new("schema_key", DataType::Utf8, false),
1182
1190
  Field::new("file_id", DataType::Utf8, true),
1183
1191
  json_field("snapshot_content", true),
@@ -1188,20 +1196,20 @@ fn lix_state_by_version_schema() -> SchemaRef {
1188
1196
  Field::new("change_id", DataType::Utf8, true),
1189
1197
  Field::new("commit_id", DataType::Utf8, true),
1190
1198
  Field::new("untracked", DataType::Boolean, true),
1191
- Field::new("version_id", DataType::Utf8, false),
1199
+ Field::new("branch_id", DataType::Utf8, false),
1192
1200
  ]))
1193
1201
  }
1194
1202
 
1195
1203
  #[derive(Debug, Clone, PartialEq, Eq, Default)]
1196
- struct LixStateByVersionRoute {
1204
+ struct LixStateByBranchRoute {
1197
1205
  schema_keys: Option<BTreeSet<String>>,
1198
- version_ids: Option<BTreeSet<String>>,
1199
- entity_ids: Option<BTreeSet<String>>,
1206
+ branch_ids: Option<BTreeSet<String>>,
1207
+ entity_pks: Option<BTreeSet<String>>,
1200
1208
  file_id: Option<NullableKeyFilter<String>>,
1201
1209
  contradictory: bool,
1202
1210
  }
1203
1211
 
1204
- impl LixStateByVersionRoute {
1212
+ impl LixStateByBranchRoute {
1205
1213
  fn from_filters(filters: &[Expr]) -> Self {
1206
1214
  let mut route = Self::default();
1207
1215
  for filter in filters {
@@ -1217,16 +1225,16 @@ impl LixStateByVersionRoute {
1217
1225
  &mut route.contradictory,
1218
1226
  );
1219
1227
  }
1220
- LixStateFilterPredicate::VersionIds(values) => {
1228
+ LixStateFilterPredicate::BranchIds(values) => {
1221
1229
  merge_string_route_slot(
1222
- &mut route.version_ids,
1230
+ &mut route.branch_ids,
1223
1231
  values,
1224
1232
  &mut route.contradictory,
1225
1233
  );
1226
1234
  }
1227
- LixStateFilterPredicate::EntityIds(values) => {
1235
+ LixStateFilterPredicate::EntityPks(values) => {
1228
1236
  merge_string_route_slot(
1229
- &mut route.entity_ids,
1237
+ &mut route.entity_pks,
1230
1238
  values,
1231
1239
  &mut route.contradictory,
1232
1240
  );
@@ -1248,16 +1256,16 @@ impl LixStateByVersionRoute {
1248
1256
  #[derive(Debug, Clone, PartialEq, Eq)]
1249
1257
  enum LixStateFilterPredicate {
1250
1258
  SchemaKeys(BTreeSet<String>),
1251
- VersionIds(BTreeSet<String>),
1252
- EntityIds(BTreeSet<String>),
1259
+ BranchIds(BTreeSet<String>),
1260
+ EntityPks(BTreeSet<String>),
1253
1261
  FileId(NullableKeyFilter<String>),
1254
1262
  }
1255
1263
 
1256
1264
  fn lix_state_scan_request(
1257
1265
  schema: &SchemaRef,
1258
- version_binding: Option<&str>,
1266
+ branch_binding: Option<&str>,
1259
1267
  projection: Option<&Vec<usize>>,
1260
- route: &LixStateByVersionRoute,
1268
+ route: &LixStateByBranchRoute,
1261
1269
  limit: Option<usize>,
1262
1270
  ) -> LiveStateScanRequest {
1263
1271
  let projection = LiveStateProjection {
@@ -1269,21 +1277,21 @@ fn lix_state_scan_request(
1269
1277
  .as_ref()
1270
1278
  .map(|values| values.iter().cloned().collect())
1271
1279
  .unwrap_or_default(),
1272
- entity_ids: route
1273
- .entity_ids
1280
+ entity_pks: route
1281
+ .entity_pks
1274
1282
  .as_ref()
1275
1283
  .map(|values| {
1276
1284
  values
1277
1285
  .iter()
1278
- .filter_map(|value| EntityIdentity::from_json_array_text(value).ok())
1286
+ .filter_map(|value| EntityPk::from_json_array_text(value).ok())
1279
1287
  .collect()
1280
1288
  })
1281
1289
  .unwrap_or_default(),
1282
- version_ids: version_binding
1290
+ branch_ids: branch_binding
1283
1291
  .map(|value| vec![value.to_string()])
1284
1292
  .or_else(|| {
1285
1293
  route
1286
- .version_ids
1294
+ .branch_ids
1287
1295
  .as_ref()
1288
1296
  .map(|values| values.iter().cloned().collect())
1289
1297
  })
@@ -1294,10 +1302,14 @@ fn lix_state_scan_request(
1294
1302
  filter.file_ids.push(file_id);
1295
1303
  }
1296
1304
 
1305
+ if route.contradictory {
1306
+ filter.rows = LiveStateRowFilter::None;
1307
+ }
1308
+
1297
1309
  LiveStateScanRequest {
1298
1310
  filter,
1299
1311
  projection,
1300
- limit: route.contradictory.then_some(0).or(limit),
1312
+ limit,
1301
1313
  }
1302
1314
  }
1303
1315
 
@@ -1396,8 +1408,8 @@ fn parse_lix_state_in_list_filter(in_list: &InList) -> Option<LixStateFilterPred
1396
1408
  let values = values.into_iter().collect::<BTreeSet<_>>();
1397
1409
  match column.name.as_str() {
1398
1410
  "schema_key" => Some(LixStateFilterPredicate::SchemaKeys(values)),
1399
- "version_id" => Some(LixStateFilterPredicate::VersionIds(values)),
1400
- "entity_id" => canonical_entity_id_values(values).map(LixStateFilterPredicate::EntityIds),
1411
+ "branch_id" => Some(LixStateFilterPredicate::BranchIds(values)),
1412
+ "entity_pk" => canonical_entity_pk_values(values).map(LixStateFilterPredicate::EntityPks),
1401
1413
  _ => None,
1402
1414
  }
1403
1415
  }
@@ -1424,25 +1436,25 @@ fn parse_lix_state_column_literal_filter(
1424
1436
  match column.name.as_str() {
1425
1437
  "schema_key" => string_expr_literal(literal_expr)
1426
1438
  .map(|value| LixStateFilterPredicate::SchemaKeys(BTreeSet::from([value]))),
1427
- "version_id" => string_expr_literal(literal_expr)
1428
- .map(|value| LixStateFilterPredicate::VersionIds(BTreeSet::from([value]))),
1429
- "entity_id" => string_expr_literal(literal_expr)
1430
- .and_then(|value| canonical_entity_id_value(&value))
1431
- .map(|value| LixStateFilterPredicate::EntityIds(BTreeSet::from([value]))),
1439
+ "branch_id" => string_expr_literal(literal_expr)
1440
+ .map(|value| LixStateFilterPredicate::BranchIds(BTreeSet::from([value]))),
1441
+ "entity_pk" => string_expr_literal(literal_expr)
1442
+ .and_then(|value| canonical_entity_pk_value(&value))
1443
+ .map(|value| LixStateFilterPredicate::EntityPks(BTreeSet::from([value]))),
1432
1444
  "file_id" => nullable_key_literal(literal_expr).map(LixStateFilterPredicate::FileId),
1433
1445
  _ => None,
1434
1446
  }
1435
1447
  }
1436
1448
 
1437
- fn canonical_entity_id_values(values: BTreeSet<String>) -> Option<BTreeSet<String>> {
1449
+ fn canonical_entity_pk_values(values: BTreeSet<String>) -> Option<BTreeSet<String>> {
1438
1450
  values
1439
1451
  .into_iter()
1440
- .map(|value| canonical_entity_id_value(&value))
1452
+ .map(|value| canonical_entity_pk_value(&value))
1441
1453
  .collect()
1442
1454
  }
1443
1455
 
1444
- fn canonical_entity_id_value(value: &str) -> Option<String> {
1445
- EntityIdentity::from_json_array_text(value)
1456
+ fn canonical_entity_pk_value(value: &str) -> Option<String> {
1457
+ EntityPk::from_json_array_text(value)
1446
1458
  .ok()?
1447
1459
  .as_json_array_text()
1448
1460
  .ok()
@@ -1490,9 +1502,9 @@ fn lix_state_record_batch(
1490
1502
  .iter()
1491
1503
  .map(|field| {
1492
1504
  Ok(match field.name().as_str() {
1493
- "entity_id" => Arc::new(StringArray::from(
1505
+ "entity_pk" => Arc::new(StringArray::from(
1494
1506
  rows.iter()
1495
- .map(|row| row.entity_id.as_json_array_text().map(Some))
1507
+ .map(|row| row.entity_pk.as_json_array_text().map(Some))
1496
1508
  .collect::<std::result::Result<Vec<_>, LixError>>()?,
1497
1509
  )) as ArrayRef,
1498
1510
  "schema_key" => string_array(rows.iter().map(|row| Some(row.schema_key.as_str()))),
@@ -1515,7 +1527,7 @@ fn lix_state_record_batch(
1515
1527
  "untracked" => Arc::new(BooleanArray::from(
1516
1528
  rows.iter().map(|row| row.untracked).collect::<Vec<_>>(),
1517
1529
  )) as ArrayRef,
1518
- "version_id" => string_array(rows.iter().map(|row| Some(row.version_id.as_str()))),
1530
+ "branch_id" => string_array(rows.iter().map(|row| Some(row.branch_id.as_str()))),
1519
1531
  other => {
1520
1532
  return Err(LixError::new(
1521
1533
  "LIX_ERROR_UNKNOWN",
@@ -1529,7 +1541,7 @@ fn lix_state_record_batch(
1529
1541
  RecordBatch::try_new(schema, columns).map_err(|error| {
1530
1542
  LixError::new(
1531
1543
  "LIX_ERROR_UNKNOWN",
1532
- format!("sql2 failed to build lix_state_by_version batch: {error}"),
1544
+ format!("sql2 failed to build lix_state_by_branch batch: {error}"),
1533
1545
  )
1534
1546
  })
1535
1547
  }
@@ -1553,22 +1565,23 @@ fn projected_schema(schema: &SchemaRef, projection: Option<&Vec<usize>>) -> Resu
1553
1565
  }
1554
1566
 
1555
1567
  fn datafusion_error_to_lix_error(error: DataFusionError) -> LixError {
1556
- super::error::datafusion_error_to_lix_error(error)
1568
+ crate::sql2::error::datafusion_error_to_lix_error(error)
1557
1569
  }
1558
1570
 
1559
1571
  fn lix_error_to_datafusion_error(error: LixError) -> DataFusionError {
1560
- super::error::lix_error_to_datafusion_error(error)
1572
+ crate::sql2::error::lix_error_to_datafusion_error(error)
1561
1573
  }
1562
1574
 
1563
1575
  #[cfg(test)]
1564
1576
  mod tests {
1565
1577
  use super::{
1566
1578
  lix_state_scan_request, lix_state_schema, lix_state_write_rows_from_batch,
1567
- parse_lix_state_filter, register_lix_state_write_providers, LixStateByVersionRoute,
1568
- LixStateDeleteExec, LixStateFilterPredicate, LixStateInsertSink, LixStateProvider,
1569
- LixStateUpdateExec,
1579
+ parse_lix_state_filter, register_lix_state_active_write_provider,
1580
+ register_lix_state_by_branch_write_provider, LixStateByBranchRoute, LixStateDeleteExec,
1581
+ LixStateFilterPredicate, LixStateInsertSink, LixStateProvider, LixStateUpdateExec,
1570
1582
  };
1571
1583
  use crate::binary_cas::BlobDataReader;
1584
+ use crate::branch::{BranchHead, BranchRefReader};
1572
1585
  use crate::functions::{
1573
1586
  FunctionProvider, FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
1574
1587
  };
@@ -1578,9 +1591,8 @@ mod tests {
1578
1591
  TransactionJson, TransactionWrite, TransactionWriteMode, TransactionWriteOutcome,
1579
1592
  TransactionWriteRow,
1580
1593
  };
1581
- use crate::version::{VersionHead, VersionRefReader};
1582
1594
  use crate::{
1583
- entity_identity::EntityIdentity,
1595
+ entity_pk::EntityPk,
1584
1596
  live_state::{
1585
1597
  LiveStateReader, LiveStateRowRequest, LiveStateScanRequest, MaterializedLiveStateRow,
1586
1598
  },
@@ -1611,11 +1623,7 @@ mod tests {
1611
1623
  use std::sync::Arc;
1612
1624
 
1613
1625
  struct EmptyLiveStateReader;
1614
- struct EmptyVersionRefReader;
1615
- #[allow(dead_code)]
1616
- struct RowsLiveStateReader {
1617
- rows: Vec<MaterializedLiveStateRow>,
1618
- }
1626
+ struct EmptyBranchRefReader;
1619
1627
  struct DummyBlobReader;
1620
1628
 
1621
1629
  #[derive(Default)]
@@ -1730,35 +1738,18 @@ mod tests {
1730
1738
  }
1731
1739
 
1732
1740
  #[async_trait]
1733
- impl VersionRefReader for EmptyVersionRefReader {
1734
- async fn load_head(&self, _version_id: &str) -> Result<Option<VersionHead>, LixError> {
1741
+ impl BranchRefReader for EmptyBranchRefReader {
1742
+ async fn load_head(&self, _branch_id: &str) -> Result<Option<BranchHead>, LixError> {
1735
1743
  Ok(None)
1736
1744
  }
1737
1745
 
1738
- async fn scan_heads(&self) -> Result<Vec<VersionHead>, LixError> {
1746
+ async fn scan_heads(&self) -> Result<Vec<BranchHead>, LixError> {
1739
1747
  Ok(Vec::new())
1740
1748
  }
1741
1749
  }
1742
1750
 
1743
- fn empty_version_ref() -> Arc<dyn VersionRefReader> {
1744
- Arc::new(EmptyVersionRefReader)
1745
- }
1746
-
1747
- #[async_trait]
1748
- impl LiveStateReader for RowsLiveStateReader {
1749
- async fn scan_rows(
1750
- &self,
1751
- _request: &LiveStateScanRequest,
1752
- ) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
1753
- Ok(self.rows.clone())
1754
- }
1755
-
1756
- async fn load_row(
1757
- &self,
1758
- _request: &LiveStateRowRequest,
1759
- ) -> Result<Option<MaterializedLiveStateRow>, LixError> {
1760
- Ok(None)
1761
- }
1751
+ fn empty_branch_ref() -> Arc<dyn BranchRefReader> {
1752
+ Arc::new(EmptyBranchRefReader)
1762
1753
  }
1763
1754
 
1764
1755
  fn test_functions() -> FunctionProviderHandle {
@@ -1782,8 +1773,8 @@ mod tests {
1782
1773
 
1783
1774
  #[async_trait]
1784
1775
  impl SqlWriteExecutionContext for DummyWriteContext {
1785
- fn active_version_id(&self) -> &str {
1786
- "version-a"
1776
+ fn active_branch_id(&self) -> &str {
1777
+ "branch-a"
1787
1778
  }
1788
1779
 
1789
1780
  fn functions(&self) -> FunctionProviderHandle {
@@ -1808,14 +1799,11 @@ mod tests {
1808
1799
  Ok(self.rows.clone())
1809
1800
  }
1810
1801
 
1811
- async fn load_version_head(
1812
- &mut self,
1813
- version_id: &str,
1814
- ) -> Result<Option<String>, LixError> {
1815
- if version_id == "ghost-version" {
1802
+ async fn load_branch_head(&mut self, branch_id: &str) -> Result<Option<String>, LixError> {
1803
+ if branch_id == "ghost-branch" {
1816
1804
  return Ok(None);
1817
1805
  }
1818
- Ok(Some(format!("commit-{version_id}")))
1806
+ Ok(Some(format!("commit-{branch_id}")))
1819
1807
  }
1820
1808
 
1821
1809
  async fn stage_write(
@@ -1828,8 +1816,8 @@ mod tests {
1828
1816
 
1829
1817
  #[async_trait]
1830
1818
  impl SqlWriteExecutionContext for CapturingWriteContext {
1831
- fn active_version_id(&self) -> &str {
1832
- "version-a"
1819
+ fn active_branch_id(&self) -> &str {
1820
+ "branch-a"
1833
1821
  }
1834
1822
 
1835
1823
  fn functions(&self) -> FunctionProviderHandle {
@@ -1854,14 +1842,11 @@ mod tests {
1854
1842
  Ok(self.rows.clone())
1855
1843
  }
1856
1844
 
1857
- async fn load_version_head(
1858
- &mut self,
1859
- version_id: &str,
1860
- ) -> Result<Option<String>, LixError> {
1861
- if version_id == "ghost-version" {
1845
+ async fn load_branch_head(&mut self, branch_id: &str) -> Result<Option<String>, LixError> {
1846
+ if branch_id == "ghost-branch" {
1862
1847
  return Ok(None);
1863
1848
  }
1864
- Ok(Some(format!("commit-{version_id}")))
1849
+ Ok(Some(format!("commit-{branch_id}")))
1865
1850
  }
1866
1851
 
1867
1852
  async fn stage_write(
@@ -1937,17 +1922,17 @@ mod tests {
1937
1922
  .expect("valid stageable lix_state batch")
1938
1923
  }
1939
1924
 
1940
- fn live_row(entity_id: &str, metadata: Option<&str>) -> MaterializedLiveStateRow {
1925
+ fn live_row(entity_pk: &str, metadata: Option<&str>) -> MaterializedLiveStateRow {
1941
1926
  MaterializedLiveStateRow {
1942
- entity_id: EntityIdentity::single(entity_id),
1927
+ entity_pk: EntityPk::single(entity_pk),
1943
1928
  schema_key: "lix_key_value".to_string(),
1944
1929
  file_id: None,
1945
1930
  snapshot_content: Some("{\"key\":\"hello\",\"value\":\"world\"}".to_string()),
1946
1931
  metadata: metadata.map(str::to_string),
1947
1932
  deleted: false,
1948
- version_id: "version-a".to_string(),
1949
- change_id: Some(format!("change-{entity_id}")),
1950
- commit_id: Some(format!("commit-{entity_id}")),
1933
+ branch_id: "branch-a".to_string(),
1934
+ change_id: Some(format!("change-{entity_pk}")),
1935
+ commit_id: Some(format!("commit-{entity_pk}")),
1951
1936
  global: false,
1952
1937
  untracked: false,
1953
1938
  created_at: "2026-04-23T00:00:00Z".to_string(),
@@ -1972,16 +1957,16 @@ mod tests {
1972
1957
  }
1973
1958
 
1974
1959
  #[test]
1975
- fn parses_in_list_filter_for_version_id() {
1960
+ fn parses_in_list_filter_for_branch_id() {
1976
1961
  let expr = Expr::InList(InList::new(
1977
- Box::new(col("version_id")),
1962
+ Box::new(col("branch_id")),
1978
1963
  vec![str_lit("a"), str_lit("b")],
1979
1964
  false,
1980
1965
  ));
1981
1966
 
1982
1967
  assert_eq!(
1983
1968
  parse_lix_state_filter(&expr),
1984
- Some(LixStateFilterPredicate::VersionIds(BTreeSet::from([
1969
+ Some(LixStateFilterPredicate::BranchIds(BTreeSet::from([
1985
1970
  "a".to_string(),
1986
1971
  "b".to_string(),
1987
1972
  ])))
@@ -1990,15 +1975,15 @@ mod tests {
1990
1975
 
1991
1976
  #[test]
1992
1977
  fn builds_scan_request_from_route_and_projection() {
1993
- let schema = super::lix_state_by_version_schema();
1994
- let route = LixStateByVersionRoute::from_filters(&[
1978
+ let schema = super::lix_state_by_branch_schema();
1979
+ let route = LixStateByBranchRoute::from_filters(&[
1995
1980
  Expr::BinaryExpr(BinaryExpr::new(
1996
1981
  Box::new(col("schema_key")),
1997
1982
  Operator::Eq,
1998
1983
  Box::new(str_lit("profile")),
1999
1984
  )),
2000
1985
  Expr::BinaryExpr(BinaryExpr::new(
2001
- Box::new(col("version_id")),
1986
+ Box::new(col("branch_id")),
2002
1987
  Operator::Eq,
2003
1988
  Box::new(str_lit("v1")),
2004
1989
  )),
@@ -2009,14 +1994,14 @@ mod tests {
2009
1994
  lix_state_scan_request(&schema, None, Some(&vec![0, 1, 11]), &route, Some(10));
2010
1995
 
2011
1996
  assert_eq!(request.filter.schema_keys, vec!["profile".to_string()]);
2012
- assert_eq!(request.filter.version_ids, vec!["v1".to_string()]);
1997
+ assert_eq!(request.filter.branch_ids, vec!["v1".to_string()]);
2013
1998
  assert_eq!(request.filter.file_ids, vec![NullableKeyFilter::Null]);
2014
1999
  assert_eq!(
2015
2000
  request.projection.columns,
2016
2001
  vec![
2017
- "entity_id".to_string(),
2002
+ "entity_pk".to_string(),
2018
2003
  "schema_key".to_string(),
2019
- "version_id".to_string()
2004
+ "branch_id".to_string()
2020
2005
  ]
2021
2006
  );
2022
2007
  assert_eq!(request.limit, Some(10));
@@ -2024,37 +2009,37 @@ mod tests {
2024
2009
 
2025
2010
  #[test]
2026
2011
  fn builds_route_from_and_filter_tree() {
2027
- let route = LixStateByVersionRoute::from_filters(&[Expr::BinaryExpr(BinaryExpr::new(
2012
+ let route = LixStateByBranchRoute::from_filters(&[Expr::BinaryExpr(BinaryExpr::new(
2028
2013
  Box::new(Expr::BinaryExpr(BinaryExpr::new(
2029
- Box::new(col("entity_id")),
2014
+ Box::new(col("entity_pk")),
2030
2015
  Operator::Eq,
2031
2016
  Box::new(str_lit("[\"entity-a\"]")),
2032
2017
  ))),
2033
2018
  Operator::And,
2034
2019
  Box::new(Expr::InList(InList::new(
2035
- Box::new(col("version_id")),
2036
- vec![str_lit("version-a"), str_lit("global")],
2020
+ Box::new(col("branch_id")),
2021
+ vec![str_lit("branch-a"), str_lit("global")],
2037
2022
  false,
2038
2023
  ))),
2039
2024
  ))]);
2040
2025
 
2041
2026
  assert_eq!(
2042
- route.entity_ids,
2027
+ route.entity_pks,
2043
2028
  Some(BTreeSet::from(["[\"entity-a\"]".to_string()]))
2044
2029
  );
2045
2030
  assert_eq!(
2046
- route.version_ids,
2031
+ route.branch_ids,
2047
2032
  Some(BTreeSet::from([
2048
2033
  "global".to_string(),
2049
- "version-a".to_string()
2034
+ "branch-a".to_string()
2050
2035
  ]))
2051
2036
  );
2052
2037
  }
2053
2038
 
2054
2039
  #[test]
2055
2040
  fn contradictory_filters_turn_into_zero_limit_request() {
2056
- let schema = super::lix_state_by_version_schema();
2057
- let route = LixStateByVersionRoute::from_filters(&[
2041
+ let schema = super::lix_state_by_branch_schema();
2042
+ let route = LixStateByBranchRoute::from_filters(&[
2058
2043
  Expr::BinaryExpr(BinaryExpr::new(
2059
2044
  Box::new(col("schema_key")),
2060
2045
  Operator::Eq,
@@ -2069,23 +2054,60 @@ mod tests {
2069
2054
 
2070
2055
  let request = lix_state_scan_request(&schema, None, None, &route, None);
2071
2056
 
2072
- assert_eq!(request.limit, Some(0));
2057
+ assert_eq!(
2058
+ request.filter.rows,
2059
+ crate::live_state::LiveStateRowFilter::None
2060
+ );
2061
+ assert_eq!(request.limit, None);
2073
2062
  assert!(request.filter.schema_keys.is_empty());
2074
2063
  }
2075
2064
 
2065
+ #[tokio::test]
2066
+ async fn active_provider_contradictory_filters_still_validate_active_head() {
2067
+ let provider = LixStateProvider::active_branch(
2068
+ "missing-branch",
2069
+ Arc::new(EmptyLiveStateReader),
2070
+ empty_branch_ref(),
2071
+ );
2072
+ let session = SessionContext::new();
2073
+ let filters = vec![
2074
+ Expr::BinaryExpr(BinaryExpr::new(
2075
+ Box::new(col("schema_key")),
2076
+ Operator::Eq,
2077
+ Box::new(str_lit("a")),
2078
+ )),
2079
+ Expr::BinaryExpr(BinaryExpr::new(
2080
+ Box::new(col("schema_key")),
2081
+ Operator::Eq,
2082
+ Box::new(str_lit("b")),
2083
+ )),
2084
+ ];
2085
+
2086
+ let error = provider
2087
+ .scan(&session.state(), None, &filters, None)
2088
+ .await
2089
+ .expect_err("missing active branch should be checked before zero-row scan");
2090
+ let error = super::datafusion_error_to_lix_error(error);
2091
+
2092
+ assert_eq!(error.code, LixError::CODE_BRANCH_NOT_FOUND);
2093
+ assert!(error
2094
+ .message
2095
+ .contains("branch 'missing-branch' was not found"));
2096
+ }
2097
+
2076
2098
  #[test]
2077
- fn active_version_view_pins_version_filter() {
2099
+ fn active_branch_view_pins_branch_filter() {
2078
2100
  let schema = super::lix_state_schema();
2079
- let route = LixStateByVersionRoute::from_filters(&[Expr::BinaryExpr(BinaryExpr::new(
2101
+ let route = LixStateByBranchRoute::from_filters(&[Expr::BinaryExpr(BinaryExpr::new(
2080
2102
  Box::new(col("schema_key")),
2081
2103
  Operator::Eq,
2082
2104
  Box::new(str_lit("profile")),
2083
2105
  ))]);
2084
2106
 
2085
- let request = lix_state_scan_request(&schema, Some("version-a"), None, &route, None);
2107
+ let request = lix_state_scan_request(&schema, Some("branch-a"), None, &route, None);
2086
2108
 
2087
2109
  assert_eq!(request.filter.schema_keys, vec!["profile".to_string()]);
2088
- assert_eq!(request.filter.version_ids, vec!["version-a".to_string()]);
2110
+ assert_eq!(request.filter.branch_ids, vec!["branch-a".to_string()]);
2089
2111
  }
2090
2112
 
2091
2113
  #[tokio::test]
@@ -2094,9 +2116,12 @@ mod tests {
2094
2116
  let mut write_context = DummyWriteContext::default();
2095
2117
  let write_ctx = SqlWriteContext::new(&mut write_context);
2096
2118
 
2097
- register_lix_state_write_providers(&session, write_ctx)
2119
+ register_lix_state_active_write_provider(&session, "lix_state", write_ctx.clone())
2120
+ .await
2121
+ .expect("lix_state provider should register");
2122
+ register_lix_state_by_branch_write_provider(&session, "lix_state_by_branch", write_ctx)
2098
2123
  .await
2099
- .expect("lix_state providers should register");
2124
+ .expect("lix_state_by_branch provider should register");
2100
2125
 
2101
2126
  let lix_state = session
2102
2127
  .table_provider("lix_state")
@@ -2108,23 +2133,22 @@ mod tests {
2108
2133
  .expect("lix_state should be a LixStateProvider");
2109
2134
  assert!(lix_state.write_access.is_write());
2110
2135
 
2111
- let by_version = session
2112
- .table_provider("lix_state_by_version")
2136
+ let by_branch = session
2137
+ .table_provider("lix_state_by_branch")
2113
2138
  .await
2114
- .expect("lix_state_by_version provider should exist");
2115
- let by_version = by_version
2139
+ .expect("lix_state_by_branch provider should exist");
2140
+ let by_branch = by_branch
2116
2141
  .as_any()
2117
2142
  .downcast_ref::<LixStateProvider>()
2118
- .expect("lix_state_by_version should be a LixStateProvider");
2119
- assert!(by_version.write_access.is_write());
2143
+ .expect("lix_state_by_branch should be a LixStateProvider");
2144
+ assert!(by_branch.write_access.is_write());
2120
2145
  }
2121
2146
 
2122
2147
  #[tokio::test]
2123
2148
  async fn insert_into_requires_write_transaction() {
2124
2149
  let session = SessionContext::new();
2125
2150
  let live_state = Arc::new(EmptyLiveStateReader) as Arc<dyn LiveStateReader>;
2126
- let provider =
2127
- LixStateProvider::active_version("version-a", live_state, empty_version_ref());
2151
+ let provider = LixStateProvider::active_branch("branch-a", live_state, empty_branch_ref());
2128
2152
  let input = Arc::new(EmptyExec::new(provider.schema())) as Arc<dyn ExecutionPlan>;
2129
2153
 
2130
2154
  let error = provider
@@ -2142,8 +2166,7 @@ mod tests {
2142
2166
  async fn update_requires_write_transaction() {
2143
2167
  let session = SessionContext::new();
2144
2168
  let live_state = Arc::new(EmptyLiveStateReader) as Arc<dyn LiveStateReader>;
2145
- let provider =
2146
- LixStateProvider::active_version("version-a", live_state, empty_version_ref());
2169
+ let provider = LixStateProvider::active_branch("branch-a", live_state, empty_branch_ref());
2147
2170
 
2148
2171
  let error = provider
2149
2172
  .update(
@@ -2164,8 +2187,7 @@ mod tests {
2164
2187
  async fn delete_requires_write_transaction() {
2165
2188
  let session = SessionContext::new();
2166
2189
  let live_state = Arc::new(EmptyLiveStateReader) as Arc<dyn LiveStateReader>;
2167
- let provider =
2168
- LixStateProvider::active_version("version-a", live_state, empty_version_ref());
2190
+ let provider = LixStateProvider::active_branch("branch-a", live_state, empty_branch_ref());
2169
2191
 
2170
2192
  let error = provider
2171
2193
  .delete_from(&session.state(), vec![])
@@ -2183,7 +2205,7 @@ mod tests {
2183
2205
  let session = SessionContext::new();
2184
2206
  let mut write_context = DummyWriteContext::default();
2185
2207
  let write_ctx = SqlWriteContext::new(&mut write_context);
2186
- let provider = LixStateProvider::active_version_with_write(write_ctx);
2208
+ let provider = LixStateProvider::active_branch_with_write(write_ctx);
2187
2209
 
2188
2210
  let plan = provider
2189
2211
  .delete_from(&session.state(), vec![])
@@ -2198,19 +2220,19 @@ mod tests {
2198
2220
  let session = SessionContext::new();
2199
2221
  let mut write_context = DummyWriteContext::default();
2200
2222
  let write_ctx = SqlWriteContext::new(&mut write_context);
2201
- let provider = LixStateProvider::active_version_with_write(write_ctx);
2223
+ let provider = LixStateProvider::active_branch_with_write(write_ctx);
2202
2224
 
2203
2225
  let error = provider
2204
2226
  .update(
2205
2227
  &session.state(),
2206
- vec![("entity_id".to_string(), str_lit("entity-2"))],
2228
+ vec![("entity_pk".to_string(), str_lit("entity-2"))],
2207
2229
  vec![],
2208
2230
  )
2209
2231
  .await
2210
2232
  .expect_err("updating a read-only field should fail");
2211
2233
 
2212
2234
  assert!(
2213
- error.to_string().contains("read-only column 'entity_id'"),
2235
+ error.to_string().contains("read-only column 'entity_pk'"),
2214
2236
  "unexpected error: {error}"
2215
2237
  );
2216
2238
  }
@@ -2220,7 +2242,7 @@ mod tests {
2220
2242
  let session = SessionContext::new();
2221
2243
  let mut write_context = DummyWriteContext::default();
2222
2244
  let write_ctx = SqlWriteContext::new(&mut write_context);
2223
- let provider = LixStateProvider::active_version_with_write(write_ctx);
2245
+ let provider = LixStateProvider::active_branch_with_write(write_ctx);
2224
2246
 
2225
2247
  let plan = provider
2226
2248
  .update(
@@ -2239,7 +2261,7 @@ mod tests {
2239
2261
  let session = SessionContext::new();
2240
2262
  let mut write_context = DummyWriteContext::default();
2241
2263
  let write_ctx = SqlWriteContext::new(&mut write_context);
2242
- let provider = LixStateProvider::active_version_with_write(write_ctx);
2264
+ let provider = LixStateProvider::active_branch_with_write(write_ctx);
2243
2265
  let input = Arc::new(EmptyExec::new(provider.schema())) as Arc<dyn ExecutionPlan>;
2244
2266
 
2245
2267
  let plan = provider
@@ -2252,13 +2274,17 @@ mod tests {
2252
2274
 
2253
2275
  #[test]
2254
2276
  fn decodes_lix_state_batch_into_write_rows() {
2255
- let rows = lix_state_write_rows_from_batch(&one_row_lix_state_batch(false), "version-a")
2256
- .expect("batch should decode");
2277
+ let rows = lix_state_write_rows_from_batch(
2278
+ &one_row_lix_state_batch(false),
2279
+ Some("branch-a"),
2280
+ "INSERT into lix_state",
2281
+ )
2282
+ .expect("batch should decode");
2257
2283
 
2258
2284
  assert_eq!(
2259
2285
  rows,
2260
2286
  vec![TransactionWriteRow {
2261
- entity_id: Some(crate::entity_identity::EntityIdentity::single("entity-1")),
2287
+ entity_pk: Some(crate::entity_pk::EntityPk::single("entity-1")),
2262
2288
  schema_key: "lix_key_value".to_string(),
2263
2289
  file_id: None,
2264
2290
  snapshot: Some(TransactionJson::from_value_for_test(
@@ -2274,17 +2300,21 @@ mod tests {
2274
2300
  change_id: Some("change-a".to_string()),
2275
2301
  commit_id: None,
2276
2302
  untracked: false,
2277
- version_id: "version-a".to_string(),
2303
+ branch_id: "branch-a".to_string(),
2278
2304
  }]
2279
2305
  );
2280
2306
  }
2281
2307
 
2282
2308
  #[test]
2283
- fn decodes_global_lix_state_batch_into_global_version() {
2284
- let rows = lix_state_write_rows_from_batch(&one_row_lix_state_batch(true), "version-a")
2285
- .expect("batch should decode");
2309
+ fn decodes_global_lix_state_batch_into_global_branch() {
2310
+ let rows = lix_state_write_rows_from_batch(
2311
+ &one_row_lix_state_batch(true),
2312
+ Some("branch-a"),
2313
+ "INSERT into lix_state",
2314
+ )
2315
+ .expect("batch should decode");
2286
2316
 
2287
- assert_eq!(rows[0].version_id, "global");
2317
+ assert_eq!(rows[0].branch_id, "global");
2288
2318
  assert!(rows[0].global);
2289
2319
  }
2290
2320
 
@@ -2292,7 +2322,7 @@ mod tests {
2292
2322
  async fn insert_sink_stages_decoded_lix_state_rows() {
2293
2323
  let mut write_context = CapturingWriteContext::default();
2294
2324
  let write_ctx = SqlWriteContext::new(&mut write_context);
2295
- let sink = LixStateInsertSink::new(lix_state_schema(), write_ctx, "version-a".to_string());
2325
+ let sink = LixStateInsertSink::new(write_ctx, Some("branch-a".to_string()));
2296
2326
  let batch = one_row_lix_state_batch(false);
2297
2327
  let count = sink
2298
2328
  .write_batches(vec![batch], &Arc::new(TaskContext::default()))
@@ -2305,7 +2335,7 @@ mod tests {
2305
2335
  &[TransactionWrite::Rows {
2306
2336
  mode: TransactionWriteMode::Insert,
2307
2337
  rows: vec![TransactionWriteRow {
2308
- entity_id: Some(crate::entity_identity::EntityIdentity::single("entity-1")),
2338
+ entity_pk: Some(crate::entity_pk::EntityPk::single("entity-1")),
2309
2339
  schema_key: "lix_key_value".to_string(),
2310
2340
  file_id: None,
2311
2341
  snapshot: Some(TransactionJson::from_value_for_test(
@@ -2321,7 +2351,7 @@ mod tests {
2321
2351
  change_id: Some("change-a".to_string()),
2322
2352
  commit_id: None,
2323
2353
  untracked: false,
2324
- version_id: "version-a".to_string(),
2354
+ branch_id: "branch-a".to_string(),
2325
2355
  }]
2326
2356
  }]
2327
2357
  );
@@ -2332,7 +2362,7 @@ mod tests {
2332
2362
  let session = SessionContext::new();
2333
2363
  let mut write_context = CapturingWriteContext::default();
2334
2364
  let write_ctx = SqlWriteContext::new(&mut write_context);
2335
- let provider = LixStateProvider::active_version_with_write(write_ctx);
2365
+ let provider = LixStateProvider::active_branch_with_write(write_ctx);
2336
2366
  let input = Arc::new(SingleBatchExec::new(one_row_stageable_lix_state_batch()))
2337
2367
  as Arc<dyn ExecutionPlan>;
2338
2368
 
@@ -2371,7 +2401,7 @@ mod tests {
2371
2401
  writes: Vec::new(),
2372
2402
  };
2373
2403
  let write_ctx = SqlWriteContext::new(&mut write_context);
2374
- let provider = LixStateProvider::active_version_with_write(write_ctx);
2404
+ let provider = LixStateProvider::active_branch_with_write(write_ctx);
2375
2405
 
2376
2406
  let plan = provider
2377
2407
  .update(
@@ -2413,7 +2443,7 @@ mod tests {
2413
2443
  &[TransactionWrite::Rows {
2414
2444
  mode: TransactionWriteMode::Replace,
2415
2445
  rows: vec![TransactionWriteRow {
2416
- entity_id: Some(crate::entity_identity::EntityIdentity::single("entity-1")),
2446
+ entity_pk: Some(crate::entity_pk::EntityPk::single("entity-1")),
2417
2447
  schema_key: "lix_key_value".to_string(),
2418
2448
  file_id: None,
2419
2449
  snapshot: Some(TransactionJson::from_value_for_test(
@@ -2429,7 +2459,7 @@ mod tests {
2429
2459
  change_id: None,
2430
2460
  commit_id: None,
2431
2461
  untracked: false,
2432
- version_id: "version-a".to_string(),
2462
+ branch_id: "branch-a".to_string(),
2433
2463
  }]
2434
2464
  }]
2435
2465
  );
@@ -2446,7 +2476,7 @@ mod tests {
2446
2476
  writes: Vec::new(),
2447
2477
  };
2448
2478
  let write_ctx = SqlWriteContext::new(&mut write_context);
2449
- let provider = LixStateProvider::active_version_with_write(write_ctx);
2479
+ let provider = LixStateProvider::active_branch_with_write(write_ctx);
2450
2480
 
2451
2481
  let plan = provider
2452
2482
  .delete_from(&session.state(), vec![])
@@ -2472,7 +2502,7 @@ mod tests {
2472
2502
  mode: TransactionWriteMode::Replace,
2473
2503
  rows: vec![
2474
2504
  TransactionWriteRow {
2475
- entity_id: Some(crate::entity_identity::EntityIdentity::single("entity-1")),
2505
+ entity_pk: Some(crate::entity_pk::EntityPk::single("entity-1")),
2476
2506
  schema_key: "lix_key_value".to_string(),
2477
2507
  file_id: None,
2478
2508
  snapshot: None,
@@ -2486,10 +2516,10 @@ mod tests {
2486
2516
  change_id: None,
2487
2517
  commit_id: None,
2488
2518
  untracked: false,
2489
- version_id: "version-a".to_string(),
2519
+ branch_id: "branch-a".to_string(),
2490
2520
  },
2491
2521
  TransactionWriteRow {
2492
- entity_id: Some(crate::entity_identity::EntityIdentity::single("entity-2")),
2522
+ entity_pk: Some(crate::entity_pk::EntityPk::single("entity-2")),
2493
2523
  schema_key: "lix_key_value".to_string(),
2494
2524
  file_id: None,
2495
2525
  snapshot: None,
@@ -2503,7 +2533,7 @@ mod tests {
2503
2533
  change_id: None,
2504
2534
  commit_id: None,
2505
2535
  untracked: false,
2506
- version_id: "version-a".to_string(),
2536
+ branch_id: "branch-a".to_string(),
2507
2537
  },
2508
2538
  ]
2509
2539
  }]