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

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 (165) hide show
  1. package/SKILL.md +4 -5
  2. package/dist/engine-wasm/wasm/lix_engine.js +1 -1
  3. package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
  4. package/dist/generated/builtin-schemas.d.ts +87 -162
  5. package/dist/generated/builtin-schemas.js +139 -236
  6. package/dist/open-lix.d.ts +1 -1
  7. package/dist-engine-src/src/binary_cas/types.rs +0 -6
  8. package/dist-engine-src/src/catalog/context.rs +412 -0
  9. package/dist-engine-src/src/catalog/mod.rs +10 -0
  10. package/dist-engine-src/src/catalog/schema.rs +4 -0
  11. package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
  12. package/dist-engine-src/src/cel/mod.rs +1 -1
  13. package/dist-engine-src/src/cel/provider.rs +1 -1
  14. package/dist-engine-src/src/commit_graph/context.rs +328 -1015
  15. package/dist-engine-src/src/commit_graph/mod.rs +2 -3
  16. package/dist-engine-src/src/commit_graph/types.rs +7 -43
  17. package/dist-engine-src/src/commit_graph/walker.rs +57 -81
  18. package/dist-engine-src/src/commit_store/codec.rs +887 -0
  19. package/dist-engine-src/src/commit_store/context.rs +944 -0
  20. package/dist-engine-src/src/commit_store/materialization.rs +84 -0
  21. package/dist-engine-src/src/commit_store/mod.rs +16 -0
  22. package/dist-engine-src/src/commit_store/storage.rs +600 -0
  23. package/dist-engine-src/src/commit_store/types.rs +215 -0
  24. package/dist-engine-src/src/common/identity.rs +15 -5
  25. package/dist-engine-src/src/common/json_pointer.rs +67 -0
  26. package/dist-engine-src/src/common/metadata.rs +17 -12
  27. package/dist-engine-src/src/common/mod.rs +5 -5
  28. package/dist-engine-src/src/domain.rs +324 -0
  29. package/dist-engine-src/src/engine.rs +29 -43
  30. package/dist-engine-src/src/entity_identity.rs +238 -118
  31. package/dist-engine-src/src/functions/context.rs +17 -52
  32. package/dist-engine-src/src/functions/deterministic.rs +1 -1
  33. package/dist-engine-src/src/functions/mod.rs +1 -1
  34. package/dist-engine-src/src/functions/provider.rs +4 -4
  35. package/dist-engine-src/src/functions/state.rs +39 -66
  36. package/dist-engine-src/src/functions/types.rs +1 -1
  37. package/dist-engine-src/src/init.rs +204 -151
  38. package/dist-engine-src/src/json_store/context.rs +354 -60
  39. package/dist-engine-src/src/json_store/encoded.rs +6 -6
  40. package/dist-engine-src/src/json_store/mod.rs +4 -1
  41. package/dist-engine-src/src/json_store/store.rs +884 -11
  42. package/dist-engine-src/src/json_store/types.rs +166 -1
  43. package/dist-engine-src/src/lib.rs +10 -9
  44. package/dist-engine-src/src/live_state/context.rs +608 -830
  45. package/dist-engine-src/src/live_state/mod.rs +3 -3
  46. package/dist-engine-src/src/live_state/overlay.rs +7 -7
  47. package/dist-engine-src/src/live_state/reader.rs +5 -5
  48. package/dist-engine-src/src/live_state/types.rs +19 -36
  49. package/dist-engine-src/src/live_state/visibility.rs +19 -14
  50. package/dist-engine-src/src/plugin/archive.rs +3 -6
  51. package/dist-engine-src/src/plugin/install.rs +0 -18
  52. package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
  53. package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
  54. package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
  55. package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
  56. package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
  57. package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
  58. package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
  59. package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
  60. package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
  61. package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
  62. package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
  63. package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
  64. package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
  65. package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
  66. package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
  67. package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
  68. package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
  69. package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
  70. package/dist-engine-src/src/schema/compatibility.rs +787 -0
  71. package/dist-engine-src/src/schema/definition.json +47 -17
  72. package/dist-engine-src/src/schema/definition.rs +202 -96
  73. package/dist-engine-src/src/schema/key.rs +9 -77
  74. package/dist-engine-src/src/schema/mod.rs +4 -4
  75. package/dist-engine-src/src/schema/tests.rs +133 -92
  76. package/dist-engine-src/src/session/context.rs +40 -42
  77. package/dist-engine-src/src/session/create_version.rs +22 -14
  78. package/dist-engine-src/src/session/execute.rs +45 -14
  79. package/dist-engine-src/src/session/merge/apply.rs +4 -4
  80. package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
  81. package/dist-engine-src/src/session/merge/stats.rs +1 -1
  82. package/dist-engine-src/src/session/merge/version.rs +35 -45
  83. package/dist-engine-src/src/session/mod.rs +4 -2
  84. package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
  85. package/dist-engine-src/src/session/switch_version.rs +16 -28
  86. package/dist-engine-src/src/sql2/change_provider.rs +14 -20
  87. package/dist-engine-src/src/sql2/classify.rs +61 -26
  88. package/dist-engine-src/src/sql2/context.rs +22 -18
  89. package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
  90. package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
  91. package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
  92. package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
  93. package/dist-engine-src/src/sql2/error.rs +21 -1
  94. package/dist-engine-src/src/sql2/execute.rs +325 -264
  95. package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
  96. package/dist-engine-src/src/sql2/file_provider.rs +533 -108
  97. package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
  98. package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
  99. package/dist-engine-src/src/sql2/history_projection.rs +3 -27
  100. package/dist-engine-src/src/sql2/history_provider.rs +11 -17
  101. package/dist-engine-src/src/sql2/history_route.rs +22 -8
  102. package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
  103. package/dist-engine-src/src/sql2/mod.rs +6 -3
  104. package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
  105. package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
  106. package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
  107. package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
  108. package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
  109. package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
  110. package/dist-engine-src/src/sql2/read_only.rs +10 -12
  111. package/dist-engine-src/src/sql2/session.rs +7 -10
  112. package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
  113. package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
  114. package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
  115. package/dist-engine-src/src/sql2/version_provider.rs +46 -31
  116. package/dist-engine-src/src/sql2/version_scope.rs +4 -4
  117. package/dist-engine-src/src/storage_bench.rs +1782 -325
  118. package/dist-engine-src/src/test_support.rs +183 -36
  119. package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
  120. package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
  121. package/dist-engine-src/src/tracked_state/context.rs +1155 -271
  122. package/dist-engine-src/src/tracked_state/diff.rs +249 -57
  123. package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
  124. package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
  125. package/dist-engine-src/src/tracked_state/merge.rs +37 -19
  126. package/dist-engine-src/src/tracked_state/mod.rs +8 -7
  127. package/dist-engine-src/src/tracked_state/storage.rs +138 -6
  128. package/dist-engine-src/src/tracked_state/tree.rs +695 -252
  129. package/dist-engine-src/src/tracked_state/types.rs +176 -6
  130. package/dist-engine-src/src/transaction/commit.rs +695 -435
  131. package/dist-engine-src/src/transaction/context.rs +551 -310
  132. package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
  133. package/dist-engine-src/src/transaction/mod.rs +2 -0
  134. package/dist-engine-src/src/transaction/normalization.rs +311 -447
  135. package/dist-engine-src/src/transaction/prep.rs +37 -0
  136. package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
  137. package/dist-engine-src/src/transaction/staging.rs +701 -406
  138. package/dist-engine-src/src/transaction/types.rs +231 -122
  139. package/dist-engine-src/src/transaction/validation.rs +2717 -1698
  140. package/dist-engine-src/src/untracked_state/codec.rs +40 -96
  141. package/dist-engine-src/src/untracked_state/context.rs +21 -5
  142. package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
  143. package/dist-engine-src/src/untracked_state/mod.rs +3 -5
  144. package/dist-engine-src/src/untracked_state/storage.rs +105 -57
  145. package/dist-engine-src/src/untracked_state/types.rs +63 -13
  146. package/dist-engine-src/src/version/context.rs +1 -13
  147. package/dist-engine-src/src/version/lifecycle.rs +221 -0
  148. package/dist-engine-src/src/version/mod.rs +3 -2
  149. package/dist-engine-src/src/version/refs.rs +12 -103
  150. package/dist-engine-src/src/version/stage_rows.rs +15 -19
  151. package/package.json +1 -1
  152. package/dist-engine-src/src/changelog/codec.rs +0 -321
  153. package/dist-engine-src/src/changelog/context.rs +0 -92
  154. package/dist-engine-src/src/changelog/materialization.rs +0 -121
  155. package/dist-engine-src/src/changelog/mod.rs +0 -13
  156. package/dist-engine-src/src/changelog/reader.rs +0 -20
  157. package/dist-engine-src/src/changelog/storage.rs +0 -220
  158. package/dist-engine-src/src/changelog/types.rs +0 -38
  159. package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
  160. package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
  161. package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
  162. package/dist-engine-src/src/schema_registry.rs +0 -294
  163. package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
  164. package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
  165. package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
@@ -1,16 +1,19 @@
1
1
  use datafusion::arrow::datatypes::Field;
2
2
  use datafusion::arrow::record_batch::RecordBatch;
3
- use datafusion::common::ScalarValue;
3
+ use datafusion::common::metadata::{FieldMetadata, ScalarAndMetadata};
4
+ use datafusion::common::{ParamValues, ScalarValue};
4
5
  use datafusion::logical_expr::{Expr, LogicalPlan, WriteOp};
5
6
  use datafusion::prelude::SessionContext;
7
+ use datafusion::sql::parser::Statement as DataFusionStatement;
6
8
  use serde_json::{json, Value as JsonValue};
7
- use std::collections::{BTreeSet, HashSet};
9
+ use std::collections::{BTreeMap, BTreeSet, HashSet};
8
10
 
9
11
  use crate::schema::schema_key_from_definition;
10
12
  use crate::{LixError, LixNotice, SqlQueryResult, Value};
11
13
 
12
- use super::result_metadata::field_is_json;
13
- use super::session::{build_read_session, build_write_session};
14
+ use super::predicate_typecheck::validate_json_predicate_expr_with_dfschema;
15
+ use super::result_metadata::{field_is_json, LIX_VALUE_TYPE_JSON, LIX_VALUE_TYPE_METADATA_KEY};
16
+ use super::session::{build_read_session, build_write_session, new_sql_session_context};
14
17
  use super::write_normalization::{
15
18
  is_binary_type, lix_file_data_type_lix_error, logical_expr_is_binary_or_null,
16
19
  };
@@ -58,6 +61,8 @@ pub(crate) async fn create_logical_plan(
58
61
  sql: &str,
59
62
  ) -> Result<SqlLogicalPlan, LixError> {
60
63
  super::validate_supported_statement_ast(sql)?;
64
+ super::udfs::validate_public_udf_calls(sql)?;
65
+ validate_public_read_sql_surface(sql)?;
61
66
  let session = build_read_session(ctx).await?;
62
67
  let plan = session
63
68
  .state()
@@ -65,6 +70,7 @@ pub(crate) async fn create_logical_plan(
65
70
  .await
66
71
  .map_err(datafusion_error_to_lix_error)?;
67
72
  validate_supported_logical_plan(&plan)?;
73
+ validate_json_predicates_in_logical_plan(&plan)?;
68
74
  let kind = classify_logical_plan(&plan);
69
75
  let notices = history_filter_notices(&plan);
70
76
 
@@ -82,15 +88,17 @@ pub(crate) async fn create_write_logical_plan(
82
88
  ctx: &mut dyn SqlWriteExecutionContext,
83
89
  sql: &str,
84
90
  ) -> Result<SqlLogicalPlan, LixError> {
85
- super::validate_supported_statement_ast(sql)?;
86
- reject_read_only_history_view_dml(sql, &ctx.list_visible_schemas()?)?;
91
+ super::udfs::validate_public_udf_calls(sql)?;
92
+ let visible_schemas = ctx.list_visible_schemas()?;
93
+ super::public_bind::validate_public_dml_sql(sql, &visible_schemas)?;
94
+ let statement = parse_datafusion_statement(sql)?;
95
+ super::validate_supported_datafusion_statement_ast(&statement)?;
96
+ reject_read_only_history_view_dml_from_statement(&statement, &visible_schemas)?;
87
97
  let session = build_write_session(ctx).await?;
88
- let plan = session
89
- .state()
90
- .create_logical_plan(sql)
91
- .await
92
- .map_err(datafusion_error_to_lix_error)?;
98
+ let plan = create_logical_plan_from_statement(&session, statement).await?;
93
99
  validate_supported_logical_plan(&plan)?;
100
+ super::public_bind::validate_public_dml_plan(&plan, &visible_schemas)?;
101
+ validate_json_predicates_in_logical_plan(&plan)?;
94
102
  let strict_binary_params = validate_strict_lix_file_data_writes(&plan)?;
95
103
  let kind = classify_logical_plan(&plan);
96
104
 
@@ -103,6 +111,65 @@ pub(crate) async fn create_write_logical_plan(
103
111
  })
104
112
  }
105
113
 
114
+ fn validate_public_read_sql_surface(sql: &str) -> Result<(), LixError> {
115
+ let normalized = sql.to_ascii_lowercase();
116
+ if normalized.contains("lower(path)") {
117
+ return Err(LixError::new(
118
+ LixError::CODE_UNSUPPORTED_SQL,
119
+ "public column 'path' must be compared directly to a literal or parameter",
120
+ ));
121
+ }
122
+ if normalized.contains("lixcol_version_id")
123
+ && (normalized.contains("= lower(") || normalized.contains(" in (lower("))
124
+ {
125
+ return Err(LixError::new(
126
+ LixError::CODE_UNSUPPORTED_SQL,
127
+ "public column 'lixcol_version_id' must be compared directly to a literal or parameter",
128
+ ));
129
+ }
130
+ Ok(())
131
+ }
132
+
133
+ fn parse_datafusion_statement(sql: &str) -> Result<DataFusionStatement, LixError> {
134
+ let session = new_sql_session_context();
135
+ let dialect = session.state().config_options().sql_parser.dialect;
136
+ session
137
+ .state()
138
+ .sql_to_statement(sql, &dialect)
139
+ .map_err(datafusion_error_to_lix_error)
140
+ }
141
+
142
+ async fn create_logical_plan_from_statement(
143
+ session: &SessionContext,
144
+ statement: DataFusionStatement,
145
+ ) -> Result<LogicalPlan, LixError> {
146
+ session
147
+ .state()
148
+ .statement_to_plan(statement)
149
+ .await
150
+ .map_err(datafusion_error_to_lix_error)
151
+ }
152
+
153
+ fn validate_json_predicates_in_logical_plan(plan: &LogicalPlan) -> Result<(), LixError> {
154
+ match plan {
155
+ LogicalPlan::Filter(filter) => {
156
+ validate_json_predicate_expr_with_dfschema(filter.input.schema(), &filter.predicate)?;
157
+ }
158
+ LogicalPlan::TableScan(scan) => {
159
+ for filter in &scan.filters {
160
+ validate_json_predicate_expr_with_dfschema(scan.projected_schema.as_ref(), filter)?;
161
+ }
162
+ }
163
+ _ => {}
164
+ }
165
+
166
+ for input in plan.inputs() {
167
+ validate_json_predicates_in_logical_plan(input)?;
168
+ }
169
+
170
+ Ok(())
171
+ }
172
+
106
173
  fn validate_strict_lix_file_data_writes(plan: &LogicalPlan) -> Result<BTreeSet<usize>, LixError> {
107
174
  let mut strict_binary_params = BTreeSet::new();
108
175
  let LogicalPlan::Dml(dml) = plan else {
@@ -224,12 +291,9 @@ pub(crate) async fn execute_logical_plan(
224
291
  .map_err(datafusion_error_to_lix_error)?;
225
292
  if !params.is_empty() {
226
293
  dataframe = dataframe
227
- .with_param_values(
228
- params
229
- .iter()
230
- .map(scalar_value_from_lix_value)
231
- .collect::<Vec<_>>(),
232
- )
294
+ .with_param_values(ParamValues::List(
295
+ params.iter().map(scalar_value_from_lix_value).collect(),
296
+ ))
233
297
  .map_err(datafusion_error_to_lix_error)?;
234
298
  }
235
299
 
@@ -326,11 +390,11 @@ fn sorted_parameter_names(parameter_names: &HashSet<String>) -> Vec<String> {
326
390
  names
327
391
  }
328
392
 
329
- fn reject_read_only_history_view_dml(
330
- sql: &str,
393
+ fn reject_read_only_history_view_dml_from_statement(
394
+ statement: &DataFusionStatement,
331
395
  visible_schemas: &[JsonValue],
332
396
  ) -> Result<(), LixError> {
333
- let target_names = super::dml_target_table_names(sql)?;
397
+ let target_names = super::datafusion_statement_dml_target_table_names(statement);
334
398
  for target_name in target_names {
335
399
  if is_history_view_name(&target_name, visible_schemas)? {
336
400
  return Err(read_only_history_view_error(&target_name));
@@ -419,18 +483,28 @@ fn validate_supported_logical_plan(plan: &LogicalPlan) -> Result<(), LixError> {
419
483
  Ok(())
420
484
  }
421
485
 
422
- fn scalar_value_from_lix_value(value: &Value) -> ScalarValue {
486
+ fn scalar_value_from_lix_value(value: &Value) -> ScalarAndMetadata {
423
487
  match value {
424
- Value::Null => ScalarValue::Null,
425
- Value::Boolean(value) => ScalarValue::Boolean(Some(*value)),
426
- Value::Integer(value) => ScalarValue::Int64(Some(*value)),
427
- Value::Real(value) => ScalarValue::Float64(Some(*value)),
428
- Value::Text(value) => ScalarValue::Utf8(Some(value.clone())),
429
- Value::Json(value) => ScalarValue::Utf8(Some(value.to_string())),
430
- Value::Blob(value) => ScalarValue::Binary(Some(value.clone())),
488
+ Value::Null => ScalarValue::Null.into(),
489
+ Value::Boolean(value) => ScalarValue::Boolean(Some(*value)).into(),
490
+ Value::Integer(value) => ScalarValue::Int64(Some(*value)).into(),
491
+ Value::Real(value) => ScalarValue::Float64(Some(*value)).into(),
492
+ Value::Text(value) => ScalarValue::Utf8(Some(value.clone())).into(),
493
+ Value::Json(value) => ScalarAndMetadata::new(
494
+ ScalarValue::Utf8(Some(value.to_string())),
495
+ Some(json_field_metadata()),
496
+ ),
497
+ Value::Blob(value) => ScalarValue::Binary(Some(value.clone())).into(),
431
498
  }
432
499
  }
433
500
 
501
+ fn json_field_metadata() -> FieldMetadata {
502
+ FieldMetadata::new(BTreeMap::from([(
503
+ LIX_VALUE_TYPE_METADATA_KEY.to_string(),
504
+ LIX_VALUE_TYPE_JSON.to_string(),
505
+ )]))
506
+ }
507
+
434
508
  fn datafusion_error_to_lix_error(error: datafusion::error::DataFusionError) -> LixError {
435
509
  super::error::datafusion_error_to_lix_error(error)
436
510
  }
@@ -660,27 +734,30 @@ mod tests {
660
734
  SqlWriteExecutionContext,
661
735
  };
662
736
  use crate::binary_cas::BlobDataReader;
663
- use crate::changelog::{CanonicalChange, ChangelogReader, ChangelogScanRequest};
664
737
  use crate::commit_graph::{
665
- CommitGraphChangeHistoryEntry, CommitGraphChangeHistoryRequest, CommitGraphChangeSet,
666
- CommitGraphChangeSetElement, CommitGraphCommit, CommitGraphEdge, CommitGraphReader,
667
- ReachableCommitGraphCommit,
738
+ CommitGraphChangeHistoryEntry, CommitGraphChangeHistoryRequest, CommitGraphCommit,
739
+ CommitGraphEdge, CommitGraphReader, ReachableCommitGraphCommit,
668
740
  };
741
+ use crate::commit_store::CommitStoreContext;
669
742
  use crate::functions::{
670
743
  FunctionProvider, FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
671
744
  };
672
745
  use crate::json_store::JsonStoreContext;
673
746
  use crate::live_state::{
674
- LiveStateContext, LiveStateReader, LiveStateRow, LiveStateRowRequest, LiveStateScanRequest,
747
+ LiveStateContext, LiveStateReader, LiveStateRowRequest, LiveStateScanRequest,
748
+ MaterializedLiveStateRow,
675
749
  };
676
- use crate::sql2::{ChangelogQuerySource, SqlChangelogQuerySource};
750
+ use crate::sql2::{CommitStoreQuerySource, SqlCommitStoreQuerySource};
677
751
  use crate::storage::{
678
752
  KvEntryPage, KvExistsBatch, KvGetRequest, KvKeyPage, KvScanRequest, KvValueBatch,
679
753
  KvValuePage, StorageContext, StorageReadScope, StorageReadTransaction, StorageReader,
680
754
  StorageWriteSet,
681
755
  };
682
756
  use crate::tracked_state::TrackedStateContext;
683
- use crate::transaction::types::{StageRow, StageWrite, StageWriteOutcome};
757
+ use crate::transaction::prepare_version_ref_row;
758
+ use crate::transaction::types::{
759
+ TransactionWrite, TransactionWriteOutcome, TransactionWriteRow,
760
+ };
684
761
  use crate::untracked_state::UntrackedStateContext;
685
762
  use crate::version::VersionRefReader;
686
763
  use crate::{Engine, ExecuteResult, SessionContext};
@@ -689,10 +766,9 @@ mod tests {
689
766
  struct DummyBlobReader;
690
767
  struct DummyLiveStateReader;
691
768
  struct RowsLiveStateReader {
692
- rows: Vec<LiveStateRow>,
769
+ rows: Vec<MaterializedLiveStateRow>,
693
770
  }
694
771
  struct BackendBlobReader(StorageContext);
695
- struct DummyChangelogReader;
696
772
  struct DummyCommitGraphReader;
697
773
  struct DummyVersionRefReader;
698
774
  struct TestReadTransaction(StorageContext);
@@ -747,7 +823,7 @@ mod tests {
747
823
 
748
824
  #[derive(Clone)]
749
825
  struct CapturedStageWrite {
750
- rows: Vec<StageRow>,
826
+ rows: Vec<TransactionWriteRow>,
751
827
  }
752
828
 
753
829
  impl CapturedStageWrite {
@@ -759,7 +835,7 @@ mod tests {
759
835
  }
760
836
 
761
837
  struct CapturedStageOverlay {
762
- rows: Vec<StageRow>,
838
+ rows: Vec<TransactionWriteRow>,
763
839
  }
764
840
 
765
841
  impl CapturedStageOverlay {
@@ -787,33 +863,31 @@ mod tests {
787
863
  struct CapturedStageRow {
788
864
  entity_id: String,
789
865
  schema_key: String,
790
- schema_version: String,
791
866
  version_id: String,
792
867
  file_id: Option<String>,
793
868
  snapshot_content: Option<String>,
794
- metadata: Option<JsonValue>,
869
+ metadata: Option<String>,
795
870
  global: bool,
796
871
  untracked: bool,
797
872
  tombstone: bool,
798
873
  }
799
874
 
800
- impl From<StageRow> for CapturedStageRow {
801
- fn from(row: StageRow) -> Self {
875
+ impl From<TransactionWriteRow> for CapturedStageRow {
876
+ fn from(row: TransactionWriteRow) -> Self {
802
877
  Self {
803
878
  entity_id: row
804
879
  .entity_id
805
880
  .expect("captured staged row should carry entity_id")
806
- .as_string()
881
+ .as_json_array_text()
807
882
  .expect("captured staged row should project entity_id"),
808
883
  schema_key: row.schema_key,
809
- schema_version: row.schema_version,
810
884
  version_id: row.version_id,
811
885
  file_id: row.file_id,
812
886
  global: row.global,
813
887
  untracked: row.untracked,
814
- tombstone: row.snapshot_content.is_none(),
815
- snapshot_content: row.snapshot_content,
816
- metadata: row.metadata,
888
+ tombstone: row.snapshot.is_none(),
889
+ snapshot_content: row.snapshot.map(|snapshot| snapshot.to_string()),
890
+ metadata: row.metadata.map(|metadata| metadata.to_string()),
817
891
  }
818
892
  }
819
893
  }
@@ -842,13 +916,13 @@ mod tests {
842
916
  Arc::clone(&self.blob_reader)
843
917
  }
844
918
 
845
- fn changelog_query_source(&self) -> SqlChangelogQuerySource {
919
+ fn commit_store_query_source(&self) -> SqlCommitStoreQuerySource {
846
920
  let base_scope = test_read_scope(StorageContext::new(Arc::new(
847
921
  crate::backend::testing::UnitTestBackend::new(),
848
922
  )));
849
923
  let read_scope = StorageReadScope::new(base_scope.store());
850
- ChangelogQuerySource {
851
- changelog_reader: Arc::new(DummyChangelogReader),
924
+ CommitStoreQuerySource {
925
+ commit_store_reader: Arc::new(CommitStoreContext::new().reader(read_scope.store())),
852
926
  json_reader: JsonStoreContext::new().reader(read_scope.store()),
853
927
  }
854
928
  }
@@ -898,7 +972,7 @@ mod tests {
898
972
  async fn scan_live_state(
899
973
  &mut self,
900
974
  request: &LiveStateScanRequest,
901
- ) -> Result<Vec<LiveStateRow>, LixError> {
975
+ ) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
902
976
  self.live_state.scan_rows(request).await
903
977
  }
904
978
 
@@ -909,23 +983,26 @@ mod tests {
909
983
  Ok(Some(format!("commit-{version_id}")))
910
984
  }
911
985
 
912
- async fn stage_write(&mut self, write: StageWrite) -> Result<StageWriteOutcome, LixError> {
986
+ async fn stage_write(
987
+ &mut self,
988
+ write: TransactionWrite,
989
+ ) -> Result<TransactionWriteOutcome, LixError> {
913
990
  let count = match &write {
914
- StageWrite::Rows { rows, .. } => rows.len() as u64,
915
- StageWrite::RowsWithFileData { count, .. } => *count,
916
- StageWrite::AdoptedChanges { changes } => changes.len() as u64,
991
+ TransactionWrite::Rows { rows, .. } => rows.len() as u64,
992
+ TransactionWrite::RowsWithFileData { count, .. } => *count,
993
+ TransactionWrite::AdoptedChanges { changes } => changes.len() as u64,
917
994
  };
918
995
  let rows = match write {
919
- StageWrite::Rows { rows, .. } => rows,
920
- StageWrite::RowsWithFileData { rows, .. } => rows,
921
- StageWrite::AdoptedChanges { .. } => Vec::new(),
996
+ TransactionWrite::Rows { rows, .. } => rows,
997
+ TransactionWrite::RowsWithFileData { rows, .. } => rows,
998
+ TransactionWrite::AdoptedChanges { .. } => Vec::new(),
922
999
  };
923
1000
  self.staged_writes
924
1001
  .lock()
925
1002
  .expect("staged writes lock")
926
1003
  .deltas
927
1004
  .push(CapturedStageWrite { rows });
928
- Ok(StageWriteOutcome { count })
1005
+ Ok(TransactionWriteOutcome { count })
929
1006
  }
930
1007
  }
931
1008
 
@@ -938,20 +1015,6 @@ mod tests {
938
1015
  execute_logical_plan(plan, params).await
939
1016
  }
940
1017
 
941
- #[async_trait]
942
- impl ChangelogReader for DummyChangelogReader {
943
- async fn load_change(&self, _change_id: &str) -> Result<Option<CanonicalChange>, LixError> {
944
- Ok(None)
945
- }
946
-
947
- async fn scan_changes(
948
- &self,
949
- _request: &ChangelogScanRequest,
950
- ) -> Result<Vec<CanonicalChange>, LixError> {
951
- Ok(Vec::new())
952
- }
953
- }
954
-
955
1018
  #[async_trait]
956
1019
  impl VersionRefReader for DummyVersionRefReader {
957
1020
  async fn load_head(
@@ -1009,17 +1072,6 @@ mod tests {
1009
1072
  Vec::new()
1010
1073
  }
1011
1074
 
1012
- fn change_sets(&self, _commits: &[CommitGraphCommit]) -> Vec<CommitGraphChangeSet> {
1013
- Vec::new()
1014
- }
1015
-
1016
- async fn change_set_elements(
1017
- &mut self,
1018
- _commits: &[CommitGraphCommit],
1019
- ) -> Result<Vec<CommitGraphChangeSetElement>, LixError> {
1020
- Ok(Vec::new())
1021
- }
1022
-
1023
1075
  async fn change_history_from_commit(
1024
1076
  &mut self,
1025
1077
  _start_commit_id: &str,
@@ -1034,14 +1086,14 @@ mod tests {
1034
1086
  async fn scan_rows(
1035
1087
  &self,
1036
1088
  _request: &LiveStateScanRequest,
1037
- ) -> Result<Vec<LiveStateRow>, LixError> {
1089
+ ) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
1038
1090
  Ok(vec![])
1039
1091
  }
1040
1092
 
1041
1093
  async fn load_row(
1042
1094
  &self,
1043
1095
  _request: &LiveStateRowRequest,
1044
- ) -> Result<Option<LiveStateRow>, LixError> {
1096
+ ) -> Result<Option<MaterializedLiveStateRow>, LixError> {
1045
1097
  Ok(None)
1046
1098
  }
1047
1099
  }
@@ -1051,14 +1103,14 @@ mod tests {
1051
1103
  async fn scan_rows(
1052
1104
  &self,
1053
1105
  _request: &LiveStateScanRequest,
1054
- ) -> Result<Vec<LiveStateRow>, LixError> {
1106
+ ) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
1055
1107
  Ok(self.rows.clone())
1056
1108
  }
1057
1109
 
1058
1110
  async fn load_row(
1059
1111
  &self,
1060
1112
  _request: &LiveStateRowRequest,
1061
- ) -> Result<Option<LiveStateRow>, LixError> {
1113
+ ) -> Result<Option<MaterializedLiveStateRow>, LixError> {
1062
1114
  Ok(None)
1063
1115
  }
1064
1116
  }
@@ -1069,7 +1121,10 @@ mod tests {
1069
1121
  &self,
1070
1122
  hashes: &[crate::binary_cas::BlobHash],
1071
1123
  ) -> Result<crate::binary_cas::BlobBytesBatch, LixError> {
1072
- Ok(crate::binary_cas::BlobBytesBatch::missing(hashes.len()))
1124
+ Ok(crate::binary_cas::BlobBytesBatch::new(vec![
1125
+ None;
1126
+ hashes.len()
1127
+ ]))
1073
1128
  }
1074
1129
  }
1075
1130
 
@@ -1085,17 +1140,14 @@ mod tests {
1085
1140
  }
1086
1141
  }
1087
1142
 
1088
- fn live_lix_state_row(entity_id: &str, metadata: Option<&str>) -> LiveStateRow {
1089
- LiveStateRow {
1090
- entity_id: crate::entity_identity::EntityIdentity::from_string(entity_id)
1091
- .expect("entity id should decode"),
1143
+ fn live_lix_state_row(entity_id: &str, metadata: Option<&str>) -> MaterializedLiveStateRow {
1144
+ MaterializedLiveStateRow {
1145
+ entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
1092
1146
  schema_key: "lix_key_value".to_string(),
1093
1147
  file_id: None,
1094
1148
  snapshot_content: Some("{\"key\":\"hello\",\"value\":\"world\"}".to_string()),
1095
- metadata: metadata.map(|value| {
1096
- serde_json::from_str(value).expect("test metadata should be valid JSON")
1097
- }),
1098
- schema_version: "1".to_string(),
1149
+ metadata: metadata.map(str::to_string),
1150
+ deleted: false,
1099
1151
  version_id: "version-a".to_string(),
1100
1152
  change_id: Some(format!("change-{entity_id}")),
1101
1153
  commit_id: Some(format!("commit-{entity_id}")),
@@ -1106,15 +1158,14 @@ mod tests {
1106
1158
  }
1107
1159
  }
1108
1160
 
1109
- fn live_entity_row(entity_id: &str, version_id: &str, value: &str) -> LiveStateRow {
1110
- LiveStateRow {
1111
- entity_id: crate::entity_identity::EntityIdentity::from_string(entity_id)
1112
- .expect("entity id should decode"),
1161
+ fn live_entity_row(entity_id: &str, version_id: &str, value: &str) -> MaterializedLiveStateRow {
1162
+ MaterializedLiveStateRow {
1163
+ entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
1113
1164
  schema_key: "test_state_schema".to_string(),
1114
1165
  file_id: None,
1115
1166
  snapshot_content: Some(format!("{{\"value\":\"{value}\"}}")),
1116
- metadata: Some(json!({ "source": entity_id })),
1117
- schema_version: "1".to_string(),
1167
+ metadata: Some(json!({ "source": entity_id }).to_string()),
1168
+ deleted: false,
1118
1169
  version_id: version_id.to_string(),
1119
1170
  change_id: Some(format!("change-{entity_id}")),
1120
1171
  commit_id: Some(format!("commit-{entity_id}")),
@@ -1131,10 +1182,9 @@ mod tests {
1131
1182
  parent_id: Option<&str>,
1132
1183
  name: &str,
1133
1184
  hidden: bool,
1134
- ) -> LiveStateRow {
1135
- LiveStateRow {
1136
- entity_id: crate::entity_identity::EntityIdentity::from_string(entity_id)
1137
- .expect("entity id should decode"),
1185
+ ) -> MaterializedLiveStateRow {
1186
+ MaterializedLiveStateRow {
1187
+ entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
1138
1188
  schema_key: "lix_directory_descriptor".to_string(),
1139
1189
  file_id: None,
1140
1190
  snapshot_content: Some(
@@ -1146,8 +1196,8 @@ mod tests {
1146
1196
  })
1147
1197
  .to_string(),
1148
1198
  ),
1149
- metadata: Some(json!({ "source": entity_id })),
1150
- schema_version: "1".to_string(),
1199
+ metadata: Some(json!({ "source": entity_id }).to_string()),
1200
+ deleted: false,
1151
1201
  version_id: version_id.to_string(),
1152
1202
  change_id: Some(format!("change-{entity_id}")),
1153
1203
  commit_id: Some(format!("commit-{entity_id}")),
@@ -1164,10 +1214,9 @@ mod tests {
1164
1214
  directory_id: Option<&str>,
1165
1215
  name: &str,
1166
1216
  hidden: bool,
1167
- ) -> LiveStateRow {
1168
- LiveStateRow {
1169
- entity_id: crate::entity_identity::EntityIdentity::from_string(entity_id)
1170
- .expect("entity id should decode"),
1217
+ ) -> MaterializedLiveStateRow {
1218
+ MaterializedLiveStateRow {
1219
+ entity_id: crate::entity_identity::EntityIdentity::single(entity_id),
1171
1220
  schema_key: "lix_file_descriptor".to_string(),
1172
1221
  file_id: None,
1173
1222
  snapshot_content: Some(
@@ -1179,8 +1228,8 @@ mod tests {
1179
1228
  })
1180
1229
  .to_string(),
1181
1230
  ),
1182
- metadata: Some(json!({ "source": entity_id })),
1183
- schema_version: "1".to_string(),
1231
+ metadata: Some(json!({ "source": entity_id }).to_string()),
1232
+ deleted: false,
1184
1233
  version_id: version_id.to_string(),
1185
1234
  change_id: Some(format!("change-{entity_id}")),
1186
1235
  commit_id: Some(format!("commit-{entity_id}")),
@@ -1317,7 +1366,7 @@ mod tests {
1317
1366
  }));
1318
1367
  }
1319
1368
 
1320
- async fn setup_engine2_history_fixture() -> Result<(SessionContext, String), LixError> {
1369
+ async fn setup_engine_history_fixture() -> Result<(SessionContext, String), LixError> {
1321
1370
  let backend = crate::backend::testing::UnitTestBackend::new();
1322
1371
  let init_receipt = Engine::initialize(Box::new(backend.clone())).await?;
1323
1372
  let engine = Engine::new(Box::new(backend)).await?;
@@ -1327,9 +1376,9 @@ mod tests {
1327
1376
  .execute(
1328
1377
  "INSERT INTO lix_registered_schema (value, lixcol_global, lixcol_untracked) \
1329
1378
  VALUES (\
1330
- lix_json('{\"x-lix-key\":\"test_state_schema\",\"x-lix-version\":\"1\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"},\"count\":{\"type\":\"integer\"}},\"required\":[\"value\",\"count\"],\"additionalProperties\":false}'),\
1379
+ lix_json('{\"x-lix-key\":\"test_state_schema\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"},\"count\":{\"type\":\"integer\"}},\"required\":[\"value\",\"count\"],\"additionalProperties\":false}'),\
1331
1380
  false,\
1332
- true\
1381
+ false\
1333
1382
  )",
1334
1383
  &[],
1335
1384
  )
@@ -1337,8 +1386,8 @@ mod tests {
1337
1386
  session
1338
1387
  .execute(
1339
1388
  "INSERT INTO test_state_schema \
1340
- (lixcol_entity_id, value, count, lixcol_metadata, lixcol_untracked) \
1341
- VALUES ('entity-history', 'A', 7, '{\"source\":\"history\"}', false)",
1389
+ (lixcol_entity_id, value, count, lixcol_metadata, lixcol_untracked) \
1390
+ VALUES (lix_json('[\"entity-history\"]'), 'A', 7, '{\"source\":\"history\"}', false)",
1342
1391
  &[],
1343
1392
  )
1344
1393
  .await?;
@@ -1616,23 +1665,23 @@ mod tests {
1616
1665
 
1617
1666
  #[tokio::test]
1618
1667
  async fn execute_sql_reads_lix_state_history_from_history_context() {
1619
- let (session, head_commit_id) = setup_engine2_history_fixture()
1668
+ let (session, head_commit_id) = setup_engine_history_fixture()
1620
1669
  .await
1621
1670
  .expect("history fixture should initialize");
1622
1671
  let result = session
1623
1672
  .execute(
1624
1673
  &format!(
1625
1674
  "SELECT entity_id, snapshot_content, metadata, depth, start_commit_id \
1626
- FROM lix_state_history \
1627
- WHERE schema_key = 'test_state_schema' \
1628
- AND entity_id = 'entity-history' \
1629
- AND start_commit_id = '{head_commit_id}' \
1630
- AND depth >= 0"
1675
+ FROM lix_state_history \
1676
+ WHERE schema_key = 'test_state_schema' \
1677
+ AND entity_id = lix_json('[\"entity-history\"]') \
1678
+ AND start_commit_id = '{head_commit_id}' \
1679
+ AND depth >= 0"
1631
1680
  ),
1632
1681
  &[],
1633
1682
  )
1634
1683
  .await
1635
- .expect("sql2 execute should read lix_state_history through real engine2 context");
1684
+ .expect("sql2 execute should read lix_state_history through real engine context");
1636
1685
  let (columns, rows) = rows_from_execute_result(result);
1637
1686
 
1638
1687
  assert_eq!(
@@ -1646,7 +1695,7 @@ mod tests {
1646
1695
  ]
1647
1696
  );
1648
1697
  assert_eq!(rows.len(), 1);
1649
- assert_eq!(rows[0][0], Value::Text("entity-history".to_string()));
1698
+ assert_eq!(rows[0][0], Value::Json(json!(["entity-history"])));
1650
1699
  assert_eq!(rows[0][1], Value::Json(json!({"count": 7, "value": "A"})));
1651
1700
  assert_eq!(rows[0][2], Value::Json(json!({"source": "history"})));
1652
1701
  assert!(matches!(rows[0][3], Value::Integer(_)));
@@ -1655,21 +1704,21 @@ mod tests {
1655
1704
 
1656
1705
  #[tokio::test]
1657
1706
  async fn execute_sql_reads_entity_history_view_from_history_context() {
1658
- let (session, head_commit_id) = setup_engine2_history_fixture()
1707
+ let (session, head_commit_id) = setup_engine_history_fixture()
1659
1708
  .await
1660
1709
  .expect("history fixture should initialize");
1661
1710
  let result = session
1662
1711
  .execute(
1663
1712
  &format!(
1664
1713
  "SELECT value, count, lixcol_entity_id, lixcol_start_commit_id, lixcol_depth \
1665
- FROM test_state_schema_history \
1666
- WHERE lixcol_start_commit_id = '{head_commit_id}' \
1667
- AND lixcol_entity_id = 'entity-history'"
1714
+ FROM test_state_schema_history \
1715
+ WHERE lixcol_start_commit_id = '{head_commit_id}' \
1716
+ AND lixcol_entity_id = lix_json('[\"entity-history\"]')"
1668
1717
  ),
1669
1718
  &[],
1670
1719
  )
1671
1720
  .await
1672
- .expect("sql2 execute should read entity history through real engine2 context");
1721
+ .expect("sql2 execute should read entity history through real engine context");
1673
1722
  let (columns, rows) = rows_from_execute_result(result);
1674
1723
 
1675
1724
  assert_eq!(
@@ -1685,14 +1734,14 @@ mod tests {
1685
1734
  assert_eq!(rows.len(), 1);
1686
1735
  assert_eq!(rows[0][0], Value::Text("A".to_string()));
1687
1736
  assert_eq!(rows[0][1], Value::Integer(7));
1688
- assert_eq!(rows[0][2], Value::Text("entity-history".to_string()));
1737
+ assert_eq!(rows[0][2], Value::Json(json!(["entity-history"])));
1689
1738
  assert_eq!(rows[0][3], Value::Text(head_commit_id));
1690
1739
  assert!(matches!(rows[0][4], Value::Integer(_)));
1691
1740
  }
1692
1741
 
1693
1742
  #[tokio::test]
1694
1743
  async fn execute_sql_reads_directory_history_view_from_history_context() {
1695
- let (session, head_commit_id) = setup_engine2_history_fixture()
1744
+ let (session, head_commit_id) = setup_engine_history_fixture()
1696
1745
  .await
1697
1746
  .expect("history fixture should initialize");
1698
1747
  let result = session
@@ -1705,7 +1754,7 @@ mod tests {
1705
1754
  &[],
1706
1755
  )
1707
1756
  .await
1708
- .expect("sql2 execute should read directory history through real engine2 context");
1757
+ .expect("sql2 execute should read directory history through real engine context");
1709
1758
  assert!(
1710
1759
  result.notices().is_empty(),
1711
1760
  "identity-filtered directory history should not emit soft notices"
@@ -1754,7 +1803,7 @@ mod tests {
1754
1803
 
1755
1804
  #[tokio::test]
1756
1805
  async fn execute_sql_reads_file_history_view_from_history_context() {
1757
- let (session, head_commit_id) = setup_engine2_history_fixture()
1806
+ let (session, head_commit_id) = setup_engine_history_fixture()
1758
1807
  .await
1759
1808
  .expect("history fixture should initialize");
1760
1809
  let result = session
@@ -1770,7 +1819,7 @@ mod tests {
1770
1819
  &[],
1771
1820
  )
1772
1821
  .await
1773
- .expect("sql2 execute should read file history through real engine2 context");
1822
+ .expect("sql2 execute should read file history through real engine context");
1774
1823
  assert!(
1775
1824
  result.notices().is_empty(),
1776
1825
  "identity-filtered file history should not emit soft notices"
@@ -1815,6 +1864,37 @@ mod tests {
1815
1864
  );
1816
1865
  }
1817
1866
 
1867
+ #[tokio::test]
1868
+ async fn execute_sql_rejects_writes_to_history_views_before_planning() {
1869
+ for sql in [
1870
+ "DELETE FROM lix_state_history",
1871
+ "DELETE FROM LIX_STATE_HISTORY",
1872
+ "DELETE FROM main.LIX_STATE_HISTORY",
1873
+ "EXPLAIN DELETE FROM lix_state_history",
1874
+ ] {
1875
+ let blob_reader: Arc<dyn BlobDataReader> = Arc::new(DummyBlobReader);
1876
+ let live_state = Arc::new(DummyLiveStateReader);
1877
+ let staged_writes = Arc::new(Mutex::new(CapturingStagedWrites::default()));
1878
+ let mut ctx = DummySqlWriteExecutionContext {
1879
+ active_version_id: "version-a",
1880
+ blob_reader,
1881
+ live_state,
1882
+ staged_writes,
1883
+ schema_definitions: vec![],
1884
+ };
1885
+
1886
+ let error = execute_write_sql(&mut ctx, sql, &[])
1887
+ .await
1888
+ .expect_err("history views are read-only");
1889
+
1890
+ assert_eq!(error.code, LixError::CODE_READ_ONLY, "{sql}");
1891
+ assert_eq!(
1892
+ error.message, "DML cannot write read-only history view 'lix_state_history'",
1893
+ "{sql}"
1894
+ );
1895
+ }
1896
+ }
1897
+
1818
1898
  #[tokio::test]
1819
1899
  async fn execute_sql_insert_into_lix_state_values_stages_write() {
1820
1900
  let blob_reader: Arc<dyn BlobDataReader> = Arc::new(DummyBlobReader);
@@ -1829,14 +1909,14 @@ mod tests {
1829
1909
  };
1830
1910
 
1831
1911
  let result = execute_write_sql(
1832
- &mut ctx,
1833
- "INSERT INTO lix_state (\
1834
- entity_id, schema_key, file_id, snapshot_content, metadata, schema_version, global, untracked\
1835
- ) VALUES (\
1836
- 'entity-1', 'lix_key_value', NULL, '{\"key\":\"hello\",\"value\":\"world\"}', '{\"source\":\"sql\"}', '1', false, false\
1837
- )",
1838
- &[],
1839
- )
1912
+ &mut ctx,
1913
+ "INSERT INTO lix_state (\
1914
+ entity_id, schema_key, file_id, snapshot_content, metadata, global, untracked\
1915
+ ) VALUES (\
1916
+ lix_json('[\"entity-1\"]'), 'lix_key_value', NULL, '{\"key\":\"hello\",\"value\":\"world\"}', '{\"source\":\"sql\"}', false, false\
1917
+ )",
1918
+ &[],
1919
+ )
1840
1920
  .await
1841
1921
  .expect("INSERT INTO lix_state VALUES should stage write");
1842
1922
 
@@ -1850,8 +1930,7 @@ mod tests {
1850
1930
  .expect("staged delta should expose pending overlay");
1851
1931
  let rows = overlay.visible_semantic_rows(false, "lix_key_value");
1852
1932
  assert_eq!(rows.len(), 1);
1853
- assert_eq!(rows[0].entity_id, "entity-1");
1854
- assert_eq!(rows[0].schema_version, "1");
1933
+ assert_eq!(rows[0].entity_id, "[\"entity-1\"]");
1855
1934
  assert_eq!(rows[0].version_id, "version-a");
1856
1935
  assert!(!rows[0].global);
1857
1936
  assert!(!rows[0].untracked);
@@ -1859,7 +1938,7 @@ mod tests {
1859
1938
  rows[0].snapshot_content.as_deref(),
1860
1939
  Some("{\"key\":\"hello\",\"value\":\"world\"}")
1861
1940
  );
1862
- assert_eq!(rows[0].metadata.as_ref(), Some(&json!({"source": "sql"})));
1941
+ assert_eq!(rows[0].metadata.as_deref(), Some("{\"source\":\"sql\"}"));
1863
1942
  }
1864
1943
 
1865
1944
  #[tokio::test]
@@ -1876,14 +1955,14 @@ mod tests {
1876
1955
  };
1877
1956
 
1878
1957
  let result = execute_write_sql(
1879
- &mut ctx,
1880
- "INSERT INTO lix_state (\
1881
- entity_id, schema_key, file_id, snapshot_content, metadata, schema_version\
1882
- ) VALUES (\
1883
- 'entity-defaults', 'lix_key_value', NULL, '{\"key\":\"hello\",\"value\":\"defaults\"}', NULL, '1'\
1884
- )",
1885
- &[],
1886
- )
1958
+ &mut ctx,
1959
+ "INSERT INTO lix_state (\
1960
+ entity_id, schema_key, file_id, snapshot_content, metadata\
1961
+ ) VALUES (\
1962
+ lix_json('[\"entity-defaults\"]'), 'lix_key_value', NULL, '{\"key\":\"hello\",\"value\":\"defaults\"}', NULL\
1963
+ )",
1964
+ &[],
1965
+ )
1887
1966
  .await
1888
1967
  .expect("INSERT INTO lix_state should default bookkeeping flags");
1889
1968
 
@@ -1897,7 +1976,7 @@ mod tests {
1897
1976
  .expect("staged delta should expose pending overlay");
1898
1977
  let rows = overlay.visible_semantic_rows(false, "lix_key_value");
1899
1978
  assert_eq!(rows.len(), 1);
1900
- assert_eq!(rows[0].entity_id, "entity-defaults");
1979
+ assert_eq!(rows[0].entity_id, "[\"entity-defaults\"]");
1901
1980
  assert_eq!(rows[0].version_id, "version-a");
1902
1981
  assert!(!rows[0].global);
1903
1982
  assert!(!rows[0].untracked);
@@ -1919,15 +1998,14 @@ mod tests {
1919
1998
  let result = execute_write_sql(
1920
1999
  &mut ctx,
1921
2000
  "INSERT INTO lix_state (\
1922
- entity_id, schema_key, file_id, snapshot_content, metadata, schema_version, global, untracked\
1923
- ) \
1924
- SELECT \
1925
- 'entity-from-select' AS entity_id, \
1926
- 'lix_key_value' AS schema_key, \
1927
- NULL AS file_id, \
2001
+ entity_id, schema_key, file_id, snapshot_content, metadata, global, untracked\
2002
+ ) \
2003
+ SELECT \
2004
+ lix_json('[\"entity-from-select\"]') AS entity_id, \
2005
+ 'lix_key_value' AS schema_key, \
2006
+ NULL AS file_id, \
1928
2007
  '{\"key\":\"hello\",\"value\":\"from-select\"}' AS snapshot_content, \
1929
2008
  '{\"source\":\"select\"}' AS metadata, \
1930
- '1' AS schema_version, \
1931
2009
  false AS global, \
1932
2010
  false AS untracked",
1933
2011
  &[],
@@ -1945,17 +2023,13 @@ mod tests {
1945
2023
  .expect("staged delta should expose pending overlay");
1946
2024
  let rows = overlay.visible_semantic_rows(false, "lix_key_value");
1947
2025
  assert_eq!(rows.len(), 1);
1948
- assert_eq!(rows[0].entity_id, "entity-from-select");
1949
- assert_eq!(rows[0].schema_version, "1");
2026
+ assert_eq!(rows[0].entity_id, "[\"entity-from-select\"]");
1950
2027
  assert_eq!(rows[0].version_id, "version-a");
1951
2028
  assert_eq!(
1952
2029
  rows[0].snapshot_content.as_deref(),
1953
2030
  Some("{\"key\":\"hello\",\"value\":\"from-select\"}")
1954
2031
  );
1955
- assert_eq!(
1956
- rows[0].metadata.as_ref(),
1957
- Some(&json!({"source": "select"}))
1958
- );
2032
+ assert_eq!(rows[0].metadata.as_deref(), Some("{\"source\":\"select\"}"));
1959
2033
  }
1960
2034
 
1961
2035
  #[tokio::test]
@@ -1970,7 +2044,6 @@ mod tests {
1970
2044
  staged_writes: Arc::clone(&staged_writes),
1971
2045
  schema_definitions: vec![json!({
1972
2046
  "x-lix-key": "test_state_schema",
1973
- "x-lix-version": "1",
1974
2047
  "type": "object",
1975
2048
  "properties": {
1976
2049
  "value": { "type": "string" }
@@ -1981,8 +2054,8 @@ mod tests {
1981
2054
  let result = execute_write_sql(
1982
2055
  &mut ctx,
1983
2056
  "INSERT INTO test_state_schema_by_version (\
1984
- lixcol_entity_id, lixcol_version_id, value\
1985
- ) VALUES ('entity-c', 'version-b', 'C')",
2057
+ lixcol_entity_id, lixcol_version_id, value\
2058
+ ) VALUES (lix_json('[\"entity-c\"]'), 'version-b', 'C')",
1986
2059
  &[],
1987
2060
  )
1988
2061
  .await
@@ -1998,8 +2071,7 @@ mod tests {
1998
2071
  .expect("staged delta should expose pending overlay");
1999
2072
  let rows = overlay.visible_semantic_rows(false, "test_state_schema");
2000
2073
  assert_eq!(rows.len(), 1);
2001
- assert_eq!(rows[0].entity_id, "entity-c");
2002
- assert_eq!(rows[0].schema_version, "1");
2074
+ assert_eq!(rows[0].entity_id, "[\"entity-c\"]");
2003
2075
  assert_eq!(rows[0].version_id, "version-b");
2004
2076
  assert!(!rows[0].global);
2005
2077
  assert!(!rows[0].untracked);
@@ -2021,7 +2093,6 @@ mod tests {
2021
2093
  staged_writes: Arc::clone(&staged_writes),
2022
2094
  schema_definitions: vec![json!({
2023
2095
  "x-lix-key": "test_state_schema",
2024
- "x-lix-version": "1",
2025
2096
  "type": "object",
2026
2097
  "properties": {
2027
2098
  "value": { "type": "string" }
@@ -2032,7 +2103,7 @@ mod tests {
2032
2103
  let result = execute_write_sql(
2033
2104
  &mut ctx,
2034
2105
  "INSERT INTO test_state_schema (lixcol_entity_id, value) \
2035
- VALUES ('entity-c', 'C')",
2106
+ VALUES (lix_json('[\"entity-c\"]'), 'C')",
2036
2107
  &[],
2037
2108
  )
2038
2109
  .await
@@ -2048,7 +2119,7 @@ mod tests {
2048
2119
  .expect("staged delta should expose pending overlay");
2049
2120
  let rows = overlay.visible_semantic_rows(false, "test_state_schema");
2050
2121
  assert_eq!(rows.len(), 1);
2051
- assert_eq!(rows[0].entity_id, "entity-c");
2122
+ assert_eq!(rows[0].entity_id, "[\"entity-c\"]");
2052
2123
  assert_eq!(rows[0].version_id, "version-a");
2053
2124
  assert!(!rows[0].global);
2054
2125
  assert!(!rows[0].untracked);
@@ -2091,8 +2162,7 @@ mod tests {
2091
2162
  .expect("staged delta should expose pending overlay");
2092
2163
  let rows = overlay.visible_semantic_rows(false, "lix_directory_descriptor");
2093
2164
  assert_eq!(rows.len(), 1);
2094
- assert_eq!(rows[0].entity_id, "dir-docs");
2095
- assert_eq!(rows[0].schema_version, "1");
2165
+ assert_eq!(rows[0].entity_id, "[\"dir-docs\"]");
2096
2166
  assert_eq!(rows[0].version_id, "version-b");
2097
2167
  assert!(!rows[0].global);
2098
2168
  assert!(!rows[0].untracked);
@@ -2134,7 +2204,7 @@ mod tests {
2134
2204
  .expect("staged delta should expose pending overlay");
2135
2205
  let rows = overlay.visible_semantic_rows(false, "lix_directory_descriptor");
2136
2206
  assert_eq!(rows.len(), 1);
2137
- assert_eq!(rows[0].entity_id, "dir-docs");
2207
+ assert_eq!(rows[0].entity_id, "[\"dir-docs\"]");
2138
2208
  assert_eq!(rows[0].version_id, "version-a");
2139
2209
  assert!(!rows[0].global);
2140
2210
  assert!(!rows[0].untracked);
@@ -2178,15 +2248,15 @@ mod tests {
2178
2248
  .expect("staged delta should expose pending overlay");
2179
2249
  let rows = overlay.visible_semantic_rows(false, "lix_directory_descriptor");
2180
2250
  assert_eq!(rows.len(), 1);
2181
- assert_eq!(rows[0].entity_id, "dir-docs");
2251
+ assert_eq!(rows[0].entity_id, "[\"dir-docs\"]");
2182
2252
  assert_eq!(rows[0].version_id, "version-a");
2183
2253
  assert_eq!(
2184
2254
  rows[0].snapshot_content.as_deref(),
2185
2255
  Some("{\"hidden\":true,\"id\":\"dir-docs\",\"name\":\"docs\",\"parent_id\":null}")
2186
2256
  );
2187
2257
  assert_eq!(
2188
- rows[0].metadata.as_ref(),
2189
- Some(&json!({"source": "directory-update"}))
2258
+ rows[0].metadata.as_deref(),
2259
+ Some("{\"source\":\"directory-update\"}")
2190
2260
  );
2191
2261
  }
2192
2262
 
@@ -2267,7 +2337,7 @@ mod tests {
2267
2337
  .expect("staged delta should expose pending overlay");
2268
2338
  let rows = overlay.visible_all_semantic_rows();
2269
2339
  assert_eq!(rows.len(), 1);
2270
- assert_eq!(rows[0].entity_id, "dir-guides");
2340
+ assert_eq!(rows[0].entity_id, "[\"dir-guides\"]");
2271
2341
  assert_eq!(rows[0].version_id, "version-b");
2272
2342
  assert!(rows[0].tombstone);
2273
2343
  assert_eq!(rows[0].snapshot_content, None);
@@ -2306,8 +2376,7 @@ mod tests {
2306
2376
  .expect("staged delta should expose pending overlay");
2307
2377
  let rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
2308
2378
  assert_eq!(rows.len(), 1);
2309
- assert_eq!(rows[0].entity_id, "file-readme");
2310
- assert_eq!(rows[0].schema_version, "1");
2379
+ assert_eq!(rows[0].entity_id, "[\"file-readme\"]");
2311
2380
  assert_eq!(rows[0].version_id, "version-b");
2312
2381
  assert!(!rows[0].global);
2313
2382
  assert!(!rows[0].untracked);
@@ -2352,7 +2421,7 @@ mod tests {
2352
2421
  .expect("staged delta should expose pending overlay");
2353
2422
  let rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
2354
2423
  assert_eq!(rows.len(), 1);
2355
- assert_eq!(rows[0].entity_id, "file-readme");
2424
+ assert_eq!(rows[0].entity_id, "[\"file-readme\"]");
2356
2425
  assert_eq!(rows[0].version_id, "version-a");
2357
2426
  assert!(!rows[0].global);
2358
2427
  assert!(!rows[0].untracked);
@@ -2391,10 +2460,10 @@ mod tests {
2391
2460
  .expect("staged delta should expose pending overlay");
2392
2461
  let descriptor_rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
2393
2462
  assert_eq!(descriptor_rows.len(), 1);
2394
- assert_eq!(descriptor_rows[0].entity_id, "file-readme");
2463
+ assert_eq!(descriptor_rows[0].entity_id, "[\"file-readme\"]");
2395
2464
  let blob_ref_rows = overlay.visible_semantic_rows(false, "lix_binary_blob_ref");
2396
2465
  assert_eq!(blob_ref_rows.len(), 1);
2397
- assert_eq!(blob_ref_rows[0].entity_id, "file-readme");
2466
+ assert_eq!(blob_ref_rows[0].entity_id, "[\"file-readme\"]");
2398
2467
  assert_eq!(blob_ref_rows[0].file_id.as_deref(), Some("file-readme"));
2399
2468
  assert_eq!(blob_ref_rows[0].version_id, "version-b");
2400
2469
  let snapshot: JsonValue =
@@ -2458,7 +2527,7 @@ mod tests {
2458
2527
  .expect("staged delta should expose pending overlay");
2459
2528
  let rows = overlay.visible_semantic_rows(false, "lix_file_descriptor");
2460
2529
  assert_eq!(rows.len(), 1);
2461
- assert_eq!(rows[0].entity_id, "file-readme");
2530
+ assert_eq!(rows[0].entity_id, "[\"file-readme\"]");
2462
2531
  assert_eq!(rows[0].version_id, "version-a");
2463
2532
  let snapshot: JsonValue =
2464
2533
  serde_json::from_str(rows[0].snapshot_content.as_deref().unwrap())
@@ -2468,8 +2537,8 @@ mod tests {
2468
2537
  assert_eq!(snapshot["name"], "readme-updated.txt");
2469
2538
  assert_eq!(snapshot["hidden"], true);
2470
2539
  assert_eq!(
2471
- rows[0].metadata.as_ref(),
2472
- Some(&json!({"source": "file-update"}))
2540
+ rows[0].metadata.as_deref(),
2541
+ Some("{\"source\":\"file-update\"}")
2473
2542
  );
2474
2543
  }
2475
2544
 
@@ -2518,7 +2587,7 @@ mod tests {
2518
2587
  .is_empty());
2519
2588
  let blob_ref_rows = overlay.visible_semantic_rows(false, "lix_binary_blob_ref");
2520
2589
  assert_eq!(blob_ref_rows.len(), 1);
2521
- assert_eq!(blob_ref_rows[0].entity_id, "file-readme");
2590
+ assert_eq!(blob_ref_rows[0].entity_id, "[\"file-readme\"]");
2522
2591
  let snapshot: JsonValue =
2523
2592
  serde_json::from_str(blob_ref_rows[0].snapshot_content.as_deref().unwrap())
2524
2593
  .expect("blob ref snapshot JSON");
@@ -2626,7 +2695,7 @@ mod tests {
2626
2695
  .expect("staged delta should expose pending overlay");
2627
2696
  let rows = overlay.visible_all_semantic_rows();
2628
2697
  assert_eq!(rows.len(), 1);
2629
- assert_eq!(rows[0].entity_id, "file-guide");
2698
+ assert_eq!(rows[0].entity_id, "[\"file-guide\"]");
2630
2699
  assert_eq!(rows[0].version_id, "version-b");
2631
2700
  assert!(rows[0].tombstone);
2632
2701
  assert_eq!(rows[0].snapshot_content, None);
@@ -2649,7 +2718,6 @@ mod tests {
2649
2718
  staged_writes: Arc::clone(&staged_writes),
2650
2719
  schema_definitions: vec![json!({
2651
2720
  "x-lix-key": "test_state_schema",
2652
- "x-lix-version": "1",
2653
2721
  "type": "object",
2654
2722
  "properties": {
2655
2723
  "value": { "type": "string" }
@@ -2677,15 +2745,15 @@ mod tests {
2677
2745
  .expect("staged delta should expose pending overlay");
2678
2746
  let rows = overlay.visible_semantic_rows(false, "test_state_schema");
2679
2747
  assert_eq!(rows.len(), 1);
2680
- assert_eq!(rows[0].entity_id, "entity-a");
2748
+ assert_eq!(rows[0].entity_id, "[\"entity-a\"]");
2681
2749
  assert_eq!(rows[0].version_id, "version-a");
2682
2750
  assert_eq!(
2683
2751
  rows[0].snapshot_content.as_deref(),
2684
2752
  Some("{\"value\":\"updated\"}")
2685
2753
  );
2686
2754
  assert_eq!(
2687
- rows[0].metadata.as_ref(),
2688
- Some(&json!({"source": "entity-update"}))
2755
+ rows[0].metadata.as_deref(),
2756
+ Some("{\"source\":\"entity-update\"}")
2689
2757
  );
2690
2758
  }
2691
2759
 
@@ -2706,7 +2774,6 @@ mod tests {
2706
2774
  staged_writes: Arc::clone(&staged_writes),
2707
2775
  schema_definitions: vec![json!({
2708
2776
  "x-lix-key": "test_state_schema",
2709
- "x-lix-version": "1",
2710
2777
  "type": "object",
2711
2778
  "properties": {
2712
2779
  "value": { "type": "string" }
@@ -2733,7 +2800,7 @@ mod tests {
2733
2800
  .expect("staged delta should expose pending overlay");
2734
2801
  let rows = overlay.visible_all_semantic_rows();
2735
2802
  assert_eq!(rows.len(), 1);
2736
- assert_eq!(rows[0].entity_id, "entity-b");
2803
+ assert_eq!(rows[0].entity_id, "[\"entity-b\"]");
2737
2804
  assert_eq!(rows[0].version_id, "version-b");
2738
2805
  assert!(rows[0].tombstone);
2739
2806
  assert_eq!(rows[0].snapshot_content, None);
@@ -2762,7 +2829,7 @@ mod tests {
2762
2829
  "UPDATE lix_state \
2763
2830
  SET snapshot_content = '{\"key\":\"hello\",\"value\":\"updated\"}', \
2764
2831
  metadata = '{\"schema_key\":\"lix_key_value\"}' \
2765
- WHERE metadata = '{\"source\":\"match\"}'",
2832
+ WHERE metadata = lix_json('{\"source\":\"match\"}')",
2766
2833
  &[],
2767
2834
  )
2768
2835
  .await
@@ -2778,15 +2845,15 @@ mod tests {
2778
2845
  .expect("staged delta should expose pending overlay");
2779
2846
  let rows = overlay.visible_semantic_rows(false, "lix_key_value");
2780
2847
  assert_eq!(rows.len(), 1);
2781
- assert_eq!(rows[0].entity_id, "entity-1");
2848
+ assert_eq!(rows[0].entity_id, "[\"entity-1\"]");
2782
2849
  assert_eq!(rows[0].version_id, "version-a");
2783
2850
  assert_eq!(
2784
2851
  rows[0].snapshot_content.as_deref(),
2785
2852
  Some("{\"key\":\"hello\",\"value\":\"updated\"}")
2786
2853
  );
2787
2854
  assert_eq!(
2788
- rows[0].metadata.as_ref(),
2789
- Some(&json!({"schema_key": "lix_key_value"}))
2855
+ rows[0].metadata.as_deref(),
2856
+ Some("{\"schema_key\":\"lix_key_value\"}")
2790
2857
  );
2791
2858
  }
2792
2859
 
@@ -2824,8 +2891,8 @@ mod tests {
2824
2891
  assert_eq!(rows.len(), 2);
2825
2892
  assert!(rows.iter().all(|row| row.tombstone));
2826
2893
  assert!(rows.iter().all(|row| row.snapshot_content.is_none()));
2827
- assert!(rows.iter().any(|row| row.entity_id == "entity-1"));
2828
- assert!(rows.iter().any(|row| row.entity_id == "entity-2"));
2894
+ assert!(rows.iter().any(|row| row.entity_id == "[\"entity-1\"]"));
2895
+ assert!(rows.iter().any(|row| row.entity_id == "[\"entity-2\"]"));
2829
2896
  }
2830
2897
 
2831
2898
  struct BackendSqlExecutionContext<'a> {
@@ -2853,13 +2920,11 @@ mod tests {
2853
2920
  Arc::clone(&self.blob_reader)
2854
2921
  }
2855
2922
 
2856
- fn changelog_query_source(&self) -> SqlChangelogQuerySource {
2923
+ fn commit_store_query_source(&self) -> SqlCommitStoreQuerySource {
2857
2924
  let base_scope = test_read_scope(self.storage.clone());
2858
2925
  let read_scope = StorageReadScope::new(base_scope.store());
2859
- ChangelogQuerySource {
2860
- changelog_reader: Arc::new(
2861
- crate::changelog::ChangelogContext::new().reader(read_scope.store()),
2862
- ),
2926
+ CommitStoreQuerySource {
2927
+ commit_store_reader: Arc::new(CommitStoreContext::new().reader(read_scope.store())),
2863
2928
  json_reader: JsonStoreContext::new().reader(read_scope.store()),
2864
2929
  }
2865
2930
  }
@@ -2891,26 +2956,23 @@ mod tests {
2891
2956
  crate::untracked_state::UntrackedStateContext::new(),
2892
2957
  ));
2893
2958
  let mut writes = StorageWriteSet::new();
2894
- let canonical_rows = {
2895
- let mut json_writer = JsonStoreContext::new().writer();
2896
- vec![
2897
- version_ctx.canonical_ref_row(
2898
- &mut writes,
2899
- &mut json_writer,
2900
- "version-a",
2901
- &init_receipt.initial_commit_id,
2902
- "1970-01-01T00:00:00.000Z",
2903
- )?,
2904
- version_ctx.canonical_ref_row(
2905
- &mut writes,
2906
- &mut json_writer,
2907
- "version-b",
2908
- &init_receipt.initial_commit_id,
2909
- "1970-01-01T00:00:00.000Z",
2910
- )?,
2911
- ]
2912
- };
2913
- version_ctx.stage_canonical_ref_rows(&mut writes, &canonical_rows)?;
2959
+ let canonical_rows = vec![
2960
+ prepare_version_ref_row(
2961
+ "version-a",
2962
+ &init_receipt.initial_commit_id,
2963
+ "1970-01-01T00:00:00.000Z",
2964
+ )?,
2965
+ prepare_version_ref_row(
2966
+ "version-b",
2967
+ &init_receipt.initial_commit_id,
2968
+ "1970-01-01T00:00:00.000Z",
2969
+ )?,
2970
+ ];
2971
+ let rows = canonical_rows
2972
+ .into_iter()
2973
+ .map(|prepared| prepared.row)
2974
+ .collect::<Vec<_>>();
2975
+ version_ctx.stage_canonical_ref_rows(&mut writes, &rows)?;
2914
2976
  writes.apply(&mut transaction.as_mut()).await?;
2915
2977
  transaction.commit().await?;
2916
2978
  }
@@ -2919,7 +2981,6 @@ mod tests {
2919
2981
  let session_b = engine.open_session("version-b").await?;
2920
2982
  let schema_definition = json!({
2921
2983
  "x-lix-key": "test_state_schema",
2922
- "x-lix-version": "1",
2923
2984
  "type": "object",
2924
2985
  "properties": {
2925
2986
  "value": { "type": "string" }
@@ -2931,9 +2992,9 @@ mod tests {
2931
2992
  .execute(
2932
2993
  "INSERT INTO lix_registered_schema (value, lixcol_global, lixcol_untracked) \
2933
2994
  VALUES (\
2934
- lix_json('{\"x-lix-key\":\"test_state_schema\",\"x-lix-version\":\"1\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"}},\"required\":[\"value\"],\"additionalProperties\":false}'),\
2995
+ lix_json('{\"x-lix-key\":\"test_state_schema\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"}},\"required\":[\"value\"],\"additionalProperties\":false}'),\
2935
2996
  false,\
2936
- true\
2997
+ false\
2937
2998
  )",
2938
2999
  &[],
2939
3000
  )
@@ -2942,9 +3003,9 @@ mod tests {
2942
3003
  .execute(
2943
3004
  "INSERT INTO lix_registered_schema (value, lixcol_global, lixcol_untracked) \
2944
3005
  VALUES (\
2945
- lix_json('{\"x-lix-key\":\"test_state_schema\",\"x-lix-version\":\"1\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"}},\"required\":[\"value\"],\"additionalProperties\":false}'),\
3006
+ lix_json('{\"x-lix-key\":\"test_state_schema\",\"type\":\"object\",\"properties\":{\"value\":{\"type\":\"string\"}},\"required\":[\"value\"],\"additionalProperties\":false}'),\
2946
3007
  false,\
2947
- true\
3008
+ false\
2948
3009
  )",
2949
3010
  &[],
2950
3011
  )
@@ -2952,32 +3013,32 @@ mod tests {
2952
3013
  session_a
2953
3014
  .execute(
2954
3015
  "INSERT INTO lix_state (\
2955
- entity_id, schema_key, file_id, snapshot_content, schema_version, global, untracked\
2956
- ) VALUES (\
2957
- 'entity-a', 'test_state_schema', NULL, '{\"value\":\"A\"}', '1', false, false\
2958
- )",
3016
+ entity_id, schema_key, file_id, snapshot_content, global, untracked\
3017
+ ) VALUES (\
3018
+ lix_json('[\"entity-a\"]'), 'test_state_schema', NULL, '{\"value\":\"A\"}', false, false\
3019
+ )",
2959
3020
  &[],
2960
3021
  )
2961
3022
  .await?;
2962
3023
  session_b
2963
3024
  .execute(
2964
3025
  "INSERT INTO lix_state (\
2965
- entity_id, schema_key, file_id, snapshot_content, schema_version, global, untracked\
2966
- ) VALUES (\
2967
- 'entity-b', 'test_state_schema', NULL, '{\"value\":\"B\"}', '1', false, false\
2968
- )",
3026
+ entity_id, schema_key, file_id, snapshot_content, global, untracked\
3027
+ ) VALUES (\
3028
+ lix_json('[\"entity-b\"]'), 'test_state_schema', NULL, '{\"value\":\"B\"}', false, false\
3029
+ )",
2969
3030
  &[],
2970
3031
  )
2971
3032
  .await?;
2972
3033
  session_a
2973
- .execute(
2974
- "INSERT INTO lix_state (\
2975
- entity_id, schema_key, file_id, snapshot_content, schema_version, global, untracked\
2976
- ) VALUES (\
2977
- 'dir-docs', 'lix_directory_descriptor', NULL, '{\"id\":\"dir-docs\",\"parent_id\":null,\"name\":\"docs\",\"hidden\":false}', '1', false, false\
2978
- )",
2979
- &[],
2980
- )
3034
+ .execute(
3035
+ "INSERT INTO lix_state (\
3036
+ entity_id, schema_key, file_id, snapshot_content, global, untracked\
3037
+ ) VALUES (\
3038
+ lix_json('[\"dir-docs\"]'), 'lix_directory_descriptor', NULL, '{\"id\":\"dir-docs\",\"parent_id\":null,\"name\":\"docs\",\"hidden\":false}', false, false\
3039
+ )",
3040
+ &[],
3041
+ )
2981
3042
  .await?;
2982
3043
  session_a
2983
3044
  .execute(
@@ -2993,7 +3054,7 @@ mod tests {
2993
3054
  LiveStateContext::new(
2994
3055
  TrackedStateContext::new(),
2995
3056
  UntrackedStateContext::new(),
2996
- crate::commit_graph::CommitGraphContext::new(crate::changelog::ChangelogContext::new()),
3057
+ crate::commit_graph::CommitGraphContext::new(),
2997
3058
  )
2998
3059
  }
2999
3060
 
@@ -3050,7 +3111,7 @@ mod tests {
3050
3111
  vec!["entity_id", "version_id", "snapshot_content", "commit_id"]
3051
3112
  );
3052
3113
  assert_eq!(result.rows.len(), 1);
3053
- assert_eq!(result.rows[0][0], Value::Text("entity-b".to_string()));
3114
+ assert_eq!(result.rows[0][0], Value::Json(json!(["entity-b"])));
3054
3115
  assert_eq!(result.rows[0][1], Value::Text("version-b".to_string()));
3055
3116
  assert_eq!(result.rows[0][2], Value::Json(json!({"value": "B"})));
3056
3117
  match &result.rows[0][3] {
@@ -3090,11 +3151,11 @@ mod tests {
3090
3151
  .expect("broad by-version read should succeed");
3091
3152
 
3092
3153
  assert!(
3093
- result.rows.iter().any(|row| row[0] == Value::Text("entity-a".to_string()))
3094
- && result.rows.iter().any(|row| row[0] == Value::Text("entity-b".to_string())),
3095
- "expected broad by-version read to include rows from multiple visible versions: {:?}",
3096
- result.rows
3097
- );
3154
+ result.rows.iter().any(|row| row[0] == Value::Json(json!(["entity-a"])))
3155
+ && result.rows.iter().any(|row| row[0] == Value::Json(json!(["entity-b"]))),
3156
+ "expected broad by-version read to include rows from multiple visible versions: {:?}",
3157
+ result.rows
3158
+ );
3098
3159
  })
3099
3160
  });
3100
3161
  }
@@ -3131,7 +3192,7 @@ mod tests {
3131
3192
 
3132
3193
  assert_eq!(result.columns, vec!["entity_id", "snapshot_content"]);
3133
3194
  assert_eq!(result.rows.len(), 1);
3134
- assert_eq!(result.rows[0][0], Value::Text("entity-a".to_string()));
3195
+ assert_eq!(result.rows[0][0], Value::Json(json!(["entity-a"])));
3135
3196
  assert_eq!(result.rows[0][1], Value::Json(json!({"value": "A"})));
3136
3197
  })
3137
3198
  });
@@ -3169,7 +3230,7 @@ mod tests {
3169
3230
  assert_eq!(result.columns, vec!["value", "lixcol_entity_id"]);
3170
3231
  assert_eq!(result.rows.len(), 1);
3171
3232
  assert_eq!(result.rows[0][0], Value::Text("A".to_string()));
3172
- assert_eq!(result.rows[0][1], Value::Text("entity-a".to_string()));
3233
+ assert_eq!(result.rows[0][1], Value::Json(json!(["entity-a"])));
3173
3234
  })
3174
3235
  });
3175
3236
  }