@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.
- package/SKILL.md +4 -5
- package/dist/engine-wasm/wasm/lix_engine.js +1 -1
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/generated/builtin-schemas.d.ts +87 -162
- package/dist/generated/builtin-schemas.js +139 -236
- package/dist/open-lix.d.ts +1 -1
- package/dist-engine-src/src/binary_cas/types.rs +0 -6
- package/dist-engine-src/src/catalog/context.rs +412 -0
- package/dist-engine-src/src/catalog/mod.rs +10 -0
- package/dist-engine-src/src/catalog/schema.rs +4 -0
- package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
- package/dist-engine-src/src/cel/mod.rs +1 -1
- package/dist-engine-src/src/cel/provider.rs +1 -1
- package/dist-engine-src/src/commit_graph/context.rs +328 -1015
- package/dist-engine-src/src/commit_graph/mod.rs +2 -3
- package/dist-engine-src/src/commit_graph/types.rs +7 -43
- package/dist-engine-src/src/commit_graph/walker.rs +57 -81
- package/dist-engine-src/src/commit_store/codec.rs +887 -0
- package/dist-engine-src/src/commit_store/context.rs +944 -0
- package/dist-engine-src/src/commit_store/materialization.rs +84 -0
- package/dist-engine-src/src/commit_store/mod.rs +16 -0
- package/dist-engine-src/src/commit_store/storage.rs +600 -0
- package/dist-engine-src/src/commit_store/types.rs +215 -0
- package/dist-engine-src/src/common/identity.rs +15 -5
- package/dist-engine-src/src/common/json_pointer.rs +67 -0
- package/dist-engine-src/src/common/metadata.rs +17 -12
- package/dist-engine-src/src/common/mod.rs +5 -5
- package/dist-engine-src/src/domain.rs +324 -0
- package/dist-engine-src/src/engine.rs +29 -43
- package/dist-engine-src/src/entity_identity.rs +238 -118
- package/dist-engine-src/src/functions/context.rs +17 -52
- package/dist-engine-src/src/functions/deterministic.rs +1 -1
- package/dist-engine-src/src/functions/mod.rs +1 -1
- package/dist-engine-src/src/functions/provider.rs +4 -4
- package/dist-engine-src/src/functions/state.rs +39 -66
- package/dist-engine-src/src/functions/types.rs +1 -1
- package/dist-engine-src/src/init.rs +204 -151
- package/dist-engine-src/src/json_store/context.rs +354 -60
- package/dist-engine-src/src/json_store/encoded.rs +6 -6
- package/dist-engine-src/src/json_store/mod.rs +4 -1
- package/dist-engine-src/src/json_store/store.rs +884 -11
- package/dist-engine-src/src/json_store/types.rs +166 -1
- package/dist-engine-src/src/lib.rs +10 -9
- package/dist-engine-src/src/live_state/context.rs +608 -830
- package/dist-engine-src/src/live_state/mod.rs +3 -3
- package/dist-engine-src/src/live_state/overlay.rs +7 -7
- package/dist-engine-src/src/live_state/reader.rs +5 -5
- package/dist-engine-src/src/live_state/types.rs +19 -36
- package/dist-engine-src/src/live_state/visibility.rs +19 -14
- package/dist-engine-src/src/plugin/archive.rs +3 -6
- package/dist-engine-src/src/plugin/install.rs +0 -18
- package/dist-engine-src/src/plugin/plugin_manifest.json +0 -1
- package/dist-engine-src/src/schema/annotations/defaults.rs +2 -7
- package/dist-engine-src/src/schema/builtin/lix_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_change.json +11 -10
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_commit.json +8 -46
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +29 -22
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_label.json +10 -3
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +2 -8
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -1
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -1
- package/dist-engine-src/src/schema/builtin/mod.rs +10 -59
- package/dist-engine-src/src/schema/compatibility.rs +787 -0
- package/dist-engine-src/src/schema/definition.json +47 -17
- package/dist-engine-src/src/schema/definition.rs +202 -96
- package/dist-engine-src/src/schema/key.rs +9 -77
- package/dist-engine-src/src/schema/mod.rs +4 -4
- package/dist-engine-src/src/schema/tests.rs +133 -92
- package/dist-engine-src/src/session/context.rs +40 -42
- package/dist-engine-src/src/session/create_version.rs +22 -14
- package/dist-engine-src/src/session/execute.rs +45 -14
- package/dist-engine-src/src/session/merge/apply.rs +4 -4
- package/dist-engine-src/src/session/merge/conflicts.rs +3 -2
- package/dist-engine-src/src/session/merge/stats.rs +1 -1
- package/dist-engine-src/src/session/merge/version.rs +35 -45
- package/dist-engine-src/src/session/mod.rs +4 -2
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
- package/dist-engine-src/src/session/switch_version.rs +16 -28
- package/dist-engine-src/src/sql2/change_provider.rs +14 -20
- package/dist-engine-src/src/sql2/classify.rs +61 -26
- package/dist-engine-src/src/sql2/context.rs +22 -18
- package/dist-engine-src/src/sql2/directory_history_provider.rs +28 -20
- package/dist-engine-src/src/sql2/directory_provider.rs +131 -83
- package/dist-engine-src/src/sql2/entity_history_provider.rs +10 -14
- package/dist-engine-src/src/sql2/entity_provider.rs +680 -169
- package/dist-engine-src/src/sql2/error.rs +21 -1
- package/dist-engine-src/src/sql2/execute.rs +325 -264
- package/dist-engine-src/src/sql2/file_history_provider.rs +29 -21
- package/dist-engine-src/src/sql2/file_provider.rs +533 -108
- package/dist-engine-src/src/sql2/filesystem_planner.rs +58 -94
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +37 -23
- package/dist-engine-src/src/sql2/history_projection.rs +3 -27
- package/dist-engine-src/src/sql2/history_provider.rs +11 -17
- package/dist-engine-src/src/sql2/history_route.rs +22 -8
- package/dist-engine-src/src/sql2/lix_state_provider.rs +178 -96
- package/dist-engine-src/src/sql2/mod.rs +6 -3
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
- package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
- package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
- package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
- package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
- package/dist-engine-src/src/sql2/read_only.rs +10 -12
- package/dist-engine-src/src/sql2/session.rs +7 -10
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +8 -1
- package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
- package/dist-engine-src/src/sql2/version_provider.rs +46 -31
- package/dist-engine-src/src/sql2/version_scope.rs +4 -4
- package/dist-engine-src/src/storage_bench.rs +1782 -325
- package/dist-engine-src/src/test_support.rs +183 -36
- package/dist-engine-src/src/tracked_state/by_file_index.rs +20 -24
- package/dist-engine-src/src/tracked_state/codec.rs +1519 -181
- package/dist-engine-src/src/tracked_state/context.rs +1155 -271
- package/dist-engine-src/src/tracked_state/diff.rs +249 -57
- package/dist-engine-src/src/tracked_state/materialization.rs +365 -103
- package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
- package/dist-engine-src/src/tracked_state/merge.rs +37 -19
- package/dist-engine-src/src/tracked_state/mod.rs +8 -7
- package/dist-engine-src/src/tracked_state/storage.rs +138 -6
- package/dist-engine-src/src/tracked_state/tree.rs +695 -252
- package/dist-engine-src/src/tracked_state/types.rs +176 -6
- package/dist-engine-src/src/transaction/commit.rs +695 -435
- package/dist-engine-src/src/transaction/context.rs +551 -310
- package/dist-engine-src/src/transaction/live_state_overlay.rs +9 -8
- package/dist-engine-src/src/transaction/mod.rs +2 -0
- package/dist-engine-src/src/transaction/normalization.rs +311 -447
- package/dist-engine-src/src/transaction/prep.rs +37 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +93 -71
- package/dist-engine-src/src/transaction/staging.rs +701 -406
- package/dist-engine-src/src/transaction/types.rs +231 -122
- package/dist-engine-src/src/transaction/validation.rs +2717 -1698
- package/dist-engine-src/src/untracked_state/codec.rs +40 -96
- package/dist-engine-src/src/untracked_state/context.rs +21 -5
- package/dist-engine-src/src/untracked_state/materialization.rs +10 -104
- package/dist-engine-src/src/untracked_state/mod.rs +3 -5
- package/dist-engine-src/src/untracked_state/storage.rs +105 -57
- package/dist-engine-src/src/untracked_state/types.rs +63 -13
- package/dist-engine-src/src/version/context.rs +1 -13
- package/dist-engine-src/src/version/lifecycle.rs +221 -0
- package/dist-engine-src/src/version/mod.rs +3 -2
- package/dist-engine-src/src/version/refs.rs +12 -103
- package/dist-engine-src/src/version/stage_rows.rs +15 -19
- package/package.json +1 -1
- package/dist-engine-src/src/changelog/codec.rs +0 -321
- package/dist-engine-src/src/changelog/context.rs +0 -92
- package/dist-engine-src/src/changelog/materialization.rs +0 -121
- package/dist-engine-src/src/changelog/mod.rs +0 -13
- package/dist-engine-src/src/changelog/reader.rs +0 -20
- package/dist-engine-src/src/changelog/storage.rs +0 -220
- package/dist-engine-src/src/changelog/types.rs +0 -38
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +0 -18
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +0 -75
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +0 -63
- package/dist-engine-src/src/schema_registry.rs +0 -294
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +0 -591
- package/dist-engine-src/src/tracked_state/rebuild.rs +0 -771
- package/dist-engine-src/src/tracked_state/tree_types.rs +0 -176
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
use std::collections::BTreeMap;
|
|
2
|
+
|
|
3
|
+
use serde_json::Value as JsonValue;
|
|
4
|
+
|
|
5
|
+
use crate::catalog::SchemaCatalogFact;
|
|
6
|
+
use crate::domain::{committed_row_is_exact_version_scoped, Domain};
|
|
7
|
+
use crate::live_state::MaterializedLiveStateRow;
|
|
8
|
+
use crate::live_state::{LiveStateFilter, LiveStateReader, LiveStateScanRequest};
|
|
9
|
+
use crate::schema::schema_key_from_definition;
|
|
10
|
+
use crate::{LixError, NullableKeyFilter};
|
|
11
|
+
|
|
12
|
+
const REGISTERED_SCHEMA_KEY: &str = "lix_registered_schema";
|
|
13
|
+
|
|
14
|
+
/// Engine schema visibility boundary.
|
|
15
|
+
///
|
|
16
|
+
/// SQL planning receives a schema snapshot from live state. System schemas are
|
|
17
|
+
/// seeded as ordinary `lix_registered_schema` rows during initialization, so
|
|
18
|
+
/// runtime schema visibility has one source of truth.
|
|
19
|
+
pub(crate) struct CatalogContext;
|
|
20
|
+
|
|
21
|
+
impl CatalogContext {
|
|
22
|
+
pub(crate) fn new() -> Self {
|
|
23
|
+
Self
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Loads schema definitions for SQL surface planning at `version_id`.
|
|
27
|
+
///
|
|
28
|
+
/// SQL surfaces are a read-planning projection over the active untracked
|
|
29
|
+
/// schema catalog. Validation must use `schema_facts_for_domain` instead so
|
|
30
|
+
/// schema durability remains explicit.
|
|
31
|
+
pub(crate) async fn schema_jsons_for_sql_read_planning<R>(
|
|
32
|
+
&self,
|
|
33
|
+
live_state: &R,
|
|
34
|
+
version_id: &str,
|
|
35
|
+
) -> Result<Vec<JsonValue>, LixError>
|
|
36
|
+
where
|
|
37
|
+
R: LiveStateReader + ?Sized,
|
|
38
|
+
{
|
|
39
|
+
let facts = self
|
|
40
|
+
.schema_facts_for_domain(live_state, &Domain::schema_catalog(version_id, true))
|
|
41
|
+
.await?;
|
|
42
|
+
let mut schemas = BTreeMap::<String, JsonValue>::new();
|
|
43
|
+
for fact in facts {
|
|
44
|
+
let schema_key = fact.catalog_key().schema_key.clone();
|
|
45
|
+
if schemas
|
|
46
|
+
.insert(schema_key.clone(), fact.schema().clone())
|
|
47
|
+
.is_some()
|
|
48
|
+
{
|
|
49
|
+
return Err(LixError::new(
|
|
50
|
+
LixError::CODE_SCHEMA_DEFINITION,
|
|
51
|
+
format!(
|
|
52
|
+
"SQL surface schema '{}' is visible from more than one schema catalog fact",
|
|
53
|
+
schema_key
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
.with_hint("SQL entity surfaces are named by schema_key. Keep exactly one visible schema per schema_key for SQL planning."));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
Ok(schemas.into_values().collect())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Loads schema facts reachable from a row domain.
|
|
63
|
+
pub(crate) async fn schema_facts_for_domain<R>(
|
|
64
|
+
&self,
|
|
65
|
+
live_state: &R,
|
|
66
|
+
domain: &Domain,
|
|
67
|
+
) -> Result<Vec<SchemaCatalogFact>, LixError>
|
|
68
|
+
where
|
|
69
|
+
R: LiveStateReader + ?Sized,
|
|
70
|
+
{
|
|
71
|
+
let mut facts = Vec::new();
|
|
72
|
+
for schema_domain in domain.schema_catalog_domains() {
|
|
73
|
+
let rows = live_state
|
|
74
|
+
.scan_rows(&LiveStateScanRequest {
|
|
75
|
+
filter: LiveStateFilter {
|
|
76
|
+
schema_keys: vec![REGISTERED_SCHEMA_KEY.to_string()],
|
|
77
|
+
version_ids: vec![schema_domain.version_id().to_string()],
|
|
78
|
+
file_ids: vec![NullableKeyFilter::Null],
|
|
79
|
+
untracked: Some(schema_domain.untracked()),
|
|
80
|
+
include_tombstones: false,
|
|
81
|
+
..LiveStateFilter::default()
|
|
82
|
+
},
|
|
83
|
+
..LiveStateScanRequest::default()
|
|
84
|
+
})
|
|
85
|
+
.await?;
|
|
86
|
+
for row in rows
|
|
87
|
+
.into_iter()
|
|
88
|
+
.filter(|row| row_belongs_to_schema_catalog_domain(row, &schema_domain))
|
|
89
|
+
{
|
|
90
|
+
let Some((key, schema)) = decode_registered_schema_row(&row)? else {
|
|
91
|
+
continue;
|
|
92
|
+
};
|
|
93
|
+
facts.push(SchemaCatalogFact::new(schema_domain.clone(), key, schema));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
Ok(facts)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fn row_belongs_to_schema_catalog_domain(row: &MaterializedLiveStateRow, domain: &Domain) -> bool {
|
|
101
|
+
row.schema_key == REGISTERED_SCHEMA_KEY
|
|
102
|
+
&& row.file_id.is_none()
|
|
103
|
+
&& row.snapshot_content.is_some()
|
|
104
|
+
&& row.version_id == domain.version_id()
|
|
105
|
+
&& row.untracked == domain.untracked()
|
|
106
|
+
&& committed_row_is_exact_version_scoped(row, domain.version_id())
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn decode_registered_schema_row(
|
|
110
|
+
row: &MaterializedLiveStateRow,
|
|
111
|
+
) -> Result<Option<(crate::schema::SchemaKey, JsonValue)>, LixError> {
|
|
112
|
+
if row.schema_key != REGISTERED_SCHEMA_KEY {
|
|
113
|
+
return Err(LixError::new(
|
|
114
|
+
"LIX_ERROR_UNKNOWN",
|
|
115
|
+
format!(
|
|
116
|
+
"expected lix_registered_schema row, got schema_key={}",
|
|
117
|
+
row.schema_key
|
|
118
|
+
),
|
|
119
|
+
));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let Some(snapshot_content) = row.snapshot_content.as_deref() else {
|
|
123
|
+
return Ok(None);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
let snapshot: JsonValue = serde_json::from_str(snapshot_content).map_err(|err| {
|
|
127
|
+
LixError::new(
|
|
128
|
+
"LIX_ERROR_UNKNOWN",
|
|
129
|
+
format!("invalid registered schema snapshot JSON: {err}"),
|
|
130
|
+
)
|
|
131
|
+
})?;
|
|
132
|
+
let schema = snapshot.get("value").cloned().ok_or_else(|| {
|
|
133
|
+
LixError::new(
|
|
134
|
+
"LIX_ERROR_UNKNOWN",
|
|
135
|
+
"registered schema snapshot missing value",
|
|
136
|
+
)
|
|
137
|
+
})?;
|
|
138
|
+
let key = schema_key_from_definition(&schema)?;
|
|
139
|
+
Ok(Some((key, schema)))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[cfg(test)]
|
|
143
|
+
mod tests {
|
|
144
|
+
use async_trait::async_trait;
|
|
145
|
+
use serde_json::json;
|
|
146
|
+
|
|
147
|
+
use super::*;
|
|
148
|
+
use crate::live_state::LiveStateRowRequest;
|
|
149
|
+
use crate::GLOBAL_VERSION_ID;
|
|
150
|
+
|
|
151
|
+
#[tokio::test]
|
|
152
|
+
async fn visible_schemas_are_loaded_from_registered_schema_rows() {
|
|
153
|
+
let context = CatalogContext::new();
|
|
154
|
+
|
|
155
|
+
let schemas = context
|
|
156
|
+
.schema_jsons_for_sql_read_planning(
|
|
157
|
+
&RowsLiveStateReader::new(vec![
|
|
158
|
+
registered_schema_row("lix_registered_schema"),
|
|
159
|
+
registered_schema_row("lix_key_value"),
|
|
160
|
+
]),
|
|
161
|
+
"global",
|
|
162
|
+
)
|
|
163
|
+
.await
|
|
164
|
+
.expect("schema visibility should load");
|
|
165
|
+
|
|
166
|
+
assert!(schemas.iter().any(|schema| {
|
|
167
|
+
schema.get("x-lix-key").and_then(JsonValue::as_str) == Some("lix_registered_schema")
|
|
168
|
+
}));
|
|
169
|
+
assert!(schemas.iter().any(|schema| {
|
|
170
|
+
schema.get("x-lix-key").and_then(JsonValue::as_str) == Some("lix_key_value")
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[tokio::test]
|
|
175
|
+
async fn visible_schemas_include_registered_schema_rows() {
|
|
176
|
+
let context = CatalogContext::new();
|
|
177
|
+
|
|
178
|
+
let schemas = context
|
|
179
|
+
.schema_jsons_for_sql_read_planning(
|
|
180
|
+
&RowsLiveStateReader::new(vec![registered_schema_row("engine_dynamic_schema")]),
|
|
181
|
+
"global",
|
|
182
|
+
)
|
|
183
|
+
.await
|
|
184
|
+
.expect("schema visibility should load");
|
|
185
|
+
|
|
186
|
+
assert!(schemas.iter().any(|schema| {
|
|
187
|
+
schema.get("x-lix-key").and_then(JsonValue::as_str) == Some("engine_dynamic_schema")
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[tokio::test]
|
|
192
|
+
async fn sql_read_planning_rejects_multiple_visible_schemas_for_same_surface() {
|
|
193
|
+
let context = CatalogContext::new();
|
|
194
|
+
let error = context
|
|
195
|
+
.schema_jsons_for_sql_read_planning(
|
|
196
|
+
&RowsLiveStateReader::new(vec![
|
|
197
|
+
registered_schema_row("engine_dynamic_schema"),
|
|
198
|
+
registered_schema_row("engine_dynamic_schema"),
|
|
199
|
+
]),
|
|
200
|
+
"global",
|
|
201
|
+
)
|
|
202
|
+
.await
|
|
203
|
+
.expect_err("SQL surfaces must not choose a schema identity implicitly");
|
|
204
|
+
|
|
205
|
+
assert_eq!(error.code, LixError::CODE_SCHEMA_DEFINITION);
|
|
206
|
+
assert!(error.message.contains("SQL surface schema"));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#[tokio::test]
|
|
210
|
+
async fn tracked_domain_sees_tracked_seed_schemas_but_not_user_untracked_schemas() {
|
|
211
|
+
let context = CatalogContext::new();
|
|
212
|
+
let mut seed_schema = registered_schema_row("lix_key_value");
|
|
213
|
+
seed_schema.untracked = false;
|
|
214
|
+
|
|
215
|
+
let facts = context
|
|
216
|
+
.schema_facts_for_domain(
|
|
217
|
+
&RowsLiveStateReader::new(vec![
|
|
218
|
+
seed_schema,
|
|
219
|
+
registered_schema_row("engine_dynamic_schema"),
|
|
220
|
+
]),
|
|
221
|
+
&Domain::schema_catalog("global", false),
|
|
222
|
+
)
|
|
223
|
+
.await
|
|
224
|
+
.expect("schema visibility should load");
|
|
225
|
+
let schemas = facts
|
|
226
|
+
.iter()
|
|
227
|
+
.map(SchemaCatalogFact::schema)
|
|
228
|
+
.collect::<Vec<_>>();
|
|
229
|
+
|
|
230
|
+
assert!(schemas.iter().any(|schema| {
|
|
231
|
+
schema.get("x-lix-key").and_then(JsonValue::as_str) == Some("lix_key_value")
|
|
232
|
+
}));
|
|
233
|
+
assert!(!schemas.iter().any(|schema| {
|
|
234
|
+
schema.get("x-lix-key").and_then(JsonValue::as_str) == Some("engine_dynamic_schema")
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
#[tokio::test]
|
|
239
|
+
async fn tracked_domain_does_not_see_untracked_seed_schemas() {
|
|
240
|
+
let context = CatalogContext::new();
|
|
241
|
+
|
|
242
|
+
let facts = context
|
|
243
|
+
.schema_facts_for_domain(
|
|
244
|
+
&RowsLiveStateReader::new(vec![registered_schema_row("lix_key_value")]),
|
|
245
|
+
&Domain::schema_catalog("global", false),
|
|
246
|
+
)
|
|
247
|
+
.await
|
|
248
|
+
.expect("schema visibility should load");
|
|
249
|
+
let schemas = facts
|
|
250
|
+
.iter()
|
|
251
|
+
.map(SchemaCatalogFact::schema)
|
|
252
|
+
.collect::<Vec<_>>();
|
|
253
|
+
|
|
254
|
+
assert!(!schemas.iter().any(|schema| {
|
|
255
|
+
schema.get("x-lix-key").and_then(JsonValue::as_str) == Some("lix_key_value")
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[tokio::test]
|
|
260
|
+
async fn visible_schemas_ignore_projected_global_schema_rows_for_version_scope() {
|
|
261
|
+
let context = CatalogContext::new();
|
|
262
|
+
let mut global_only = registered_schema_row("global_only_schema");
|
|
263
|
+
global_only.global = true;
|
|
264
|
+
global_only.version_id = "main".to_string();
|
|
265
|
+
|
|
266
|
+
let schemas = context
|
|
267
|
+
.schema_jsons_for_sql_read_planning(
|
|
268
|
+
&RowsLiveStateReader::new(vec![global_only]),
|
|
269
|
+
"main",
|
|
270
|
+
)
|
|
271
|
+
.await
|
|
272
|
+
.expect("schema visibility should load");
|
|
273
|
+
|
|
274
|
+
assert!(schemas.is_empty());
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
#[tokio::test]
|
|
278
|
+
async fn schema_facts_post_filter_non_catalog_rows_even_if_reader_returns_them() {
|
|
279
|
+
let context = CatalogContext::new();
|
|
280
|
+
let valid_schema = registered_schema_row("valid_schema");
|
|
281
|
+
let mut file_scoped_schema = registered_schema_row("file_scoped_schema");
|
|
282
|
+
file_scoped_schema.file_id = Some("file-a".to_string());
|
|
283
|
+
let mut tombstoned_schema = registered_schema_row("tombstoned_schema");
|
|
284
|
+
tombstoned_schema.snapshot_content = None;
|
|
285
|
+
|
|
286
|
+
let facts = context
|
|
287
|
+
.schema_facts_for_domain(
|
|
288
|
+
&RowsLiveStateReader::new(vec![
|
|
289
|
+
valid_schema,
|
|
290
|
+
file_scoped_schema,
|
|
291
|
+
tombstoned_schema,
|
|
292
|
+
]),
|
|
293
|
+
&Domain::schema_catalog("global", true),
|
|
294
|
+
)
|
|
295
|
+
.await
|
|
296
|
+
.expect("schema facts should load");
|
|
297
|
+
let schema_keys = facts
|
|
298
|
+
.iter()
|
|
299
|
+
.filter_map(|fact| fact.schema().get("x-lix-key").and_then(JsonValue::as_str))
|
|
300
|
+
.collect::<Vec<_>>();
|
|
301
|
+
|
|
302
|
+
assert_eq!(schema_keys, vec!["valid_schema"]);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
#[tokio::test]
|
|
306
|
+
async fn visible_schemas_are_empty_when_no_schema_rows_are_visible() {
|
|
307
|
+
let context = CatalogContext::new();
|
|
308
|
+
|
|
309
|
+
let schemas = context
|
|
310
|
+
.schema_jsons_for_sql_read_planning(&RowsLiveStateReader::new(Vec::new()), "global")
|
|
311
|
+
.await
|
|
312
|
+
.expect("schema visibility should load");
|
|
313
|
+
|
|
314
|
+
assert!(schemas.is_empty());
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
struct RowsLiveStateReader {
|
|
318
|
+
rows: Vec<MaterializedLiveStateRow>,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
impl RowsLiveStateReader {
|
|
322
|
+
fn new(rows: Vec<MaterializedLiveStateRow>) -> Self {
|
|
323
|
+
Self { rows }
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[async_trait]
|
|
328
|
+
impl LiveStateReader for RowsLiveStateReader {
|
|
329
|
+
async fn scan_rows(
|
|
330
|
+
&self,
|
|
331
|
+
request: &LiveStateScanRequest,
|
|
332
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
333
|
+
Ok(self
|
|
334
|
+
.rows
|
|
335
|
+
.iter()
|
|
336
|
+
.filter(|row| {
|
|
337
|
+
request.filter.schema_keys.is_empty()
|
|
338
|
+
|| request.filter.schema_keys.contains(&row.schema_key)
|
|
339
|
+
})
|
|
340
|
+
.filter(|row| {
|
|
341
|
+
request.filter.version_ids.is_empty()
|
|
342
|
+
|| request.filter.version_ids.contains(&row.version_id)
|
|
343
|
+
})
|
|
344
|
+
.filter(|row| {
|
|
345
|
+
request
|
|
346
|
+
.filter
|
|
347
|
+
.untracked
|
|
348
|
+
.is_none_or(|untracked| row.untracked == untracked)
|
|
349
|
+
})
|
|
350
|
+
.cloned()
|
|
351
|
+
.collect())
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async fn load_row(
|
|
355
|
+
&self,
|
|
356
|
+
request: &LiveStateRowRequest,
|
|
357
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
358
|
+
Ok(self
|
|
359
|
+
.rows
|
|
360
|
+
.iter()
|
|
361
|
+
.find(|row| {
|
|
362
|
+
row.schema_key == request.schema_key
|
|
363
|
+
&& row.version_id == request.version_id
|
|
364
|
+
&& row.entity_id == request.entity_id
|
|
365
|
+
})
|
|
366
|
+
.cloned())
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
fn registered_schema_row(schema_key: &str) -> MaterializedLiveStateRow {
|
|
371
|
+
MaterializedLiveStateRow {
|
|
372
|
+
entity_id: registered_schema_entity_id(schema_key),
|
|
373
|
+
file_id: None,
|
|
374
|
+
schema_key: REGISTERED_SCHEMA_KEY.to_string(),
|
|
375
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
376
|
+
metadata: None,
|
|
377
|
+
deleted: false,
|
|
378
|
+
change_id: Some("change-registered-schema".to_string()),
|
|
379
|
+
commit_id: None,
|
|
380
|
+
global: true,
|
|
381
|
+
untracked: true,
|
|
382
|
+
created_at: "2026-04-23T00:00:00Z".to_string(),
|
|
383
|
+
updated_at: "2026-04-23T01:00:00Z".to_string(),
|
|
384
|
+
snapshot_content: Some(
|
|
385
|
+
json!({
|
|
386
|
+
"value": {
|
|
387
|
+
"x-lix-key": schema_key,
|
|
388
|
+
"type": "object",
|
|
389
|
+
"properties": {
|
|
390
|
+
"id": { "type": "string" }
|
|
391
|
+
},
|
|
392
|
+
"required": ["id"],
|
|
393
|
+
"additionalProperties": false
|
|
394
|
+
}
|
|
395
|
+
})
|
|
396
|
+
.to_string(),
|
|
397
|
+
),
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
fn registered_schema_entity_id(schema_key: &str) -> crate::entity_identity::EntityIdentity {
|
|
402
|
+
crate::entity_identity::EntityIdentity::from_primary_key_paths(
|
|
403
|
+
&json!({
|
|
404
|
+
"value": {
|
|
405
|
+
"x-lix-key": schema_key,
|
|
406
|
+
}
|
|
407
|
+
}),
|
|
408
|
+
&[vec!["value".to_string(), "x-lix-key".to_string()]],
|
|
409
|
+
)
|
|
410
|
+
.expect("registered schema identity should derive")
|
|
411
|
+
}
|
|
412
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
mod context;
|
|
2
|
+
mod schema;
|
|
3
|
+
mod snapshot;
|
|
4
|
+
|
|
5
|
+
pub(crate) use context::CatalogContext;
|
|
6
|
+
pub(crate) use schema::{
|
|
7
|
+
ForeignKeyPlan, SchemaCatalogFact, SchemaCatalogKey, SchemaPlan, SchemaPlanId,
|
|
8
|
+
StateForeignKeyPlan,
|
|
9
|
+
};
|
|
10
|
+
pub(crate) use snapshot::{CatalogSnapshot, StateDeleteReferencePlan};
|