@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
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
use crate::changelog::{CanonicalChange, MaterializedCanonicalChange};
|
|
2
|
-
use crate::json_store::{JsonRef, JsonStoreReader, JsonStoreWriter};
|
|
3
|
-
use crate::storage::{StorageReader, StorageWriteSet};
|
|
4
|
-
use crate::{serialize_row_metadata, validate_row_metadata, LixError, RowMetadata};
|
|
5
|
-
|
|
6
|
-
pub(crate) fn canonicalize_materialized_change(
|
|
7
|
-
writes: &mut StorageWriteSet,
|
|
8
|
-
json_writer: &mut JsonStoreWriter,
|
|
9
|
-
change: &MaterializedCanonicalChange,
|
|
10
|
-
) -> Result<CanonicalChange, LixError> {
|
|
11
|
-
let snapshot_ref =
|
|
12
|
-
stage_optional_json(writes, json_writer, change.snapshot_content.as_deref())?;
|
|
13
|
-
let metadata_ref = stage_optional_metadata(writes, json_writer, change.metadata.as_ref())?;
|
|
14
|
-
Ok(CanonicalChange {
|
|
15
|
-
id: change.id.clone(),
|
|
16
|
-
entity_id: change.entity_id.clone(),
|
|
17
|
-
schema_key: change.schema_key.clone(),
|
|
18
|
-
schema_version: change.schema_version.clone(),
|
|
19
|
-
file_id: change.file_id.clone(),
|
|
20
|
-
snapshot_ref,
|
|
21
|
-
metadata_ref,
|
|
22
|
-
created_at: change.created_at.clone(),
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
pub(crate) async fn materialize_change<S>(
|
|
27
|
-
json_reader: &mut JsonStoreReader<S>,
|
|
28
|
-
change: CanonicalChange,
|
|
29
|
-
) -> Result<MaterializedCanonicalChange, LixError>
|
|
30
|
-
where
|
|
31
|
-
S: StorageReader,
|
|
32
|
-
{
|
|
33
|
-
let snapshot_content =
|
|
34
|
-
load_optional_json(json_reader, change.snapshot_ref.as_ref(), "snapshot_ref").await?;
|
|
35
|
-
let metadata = load_optional_metadata(json_reader, change.metadata_ref.as_ref()).await?;
|
|
36
|
-
Ok(MaterializedCanonicalChange {
|
|
37
|
-
id: change.id,
|
|
38
|
-
entity_id: change.entity_id,
|
|
39
|
-
schema_key: change.schema_key,
|
|
40
|
-
schema_version: change.schema_version,
|
|
41
|
-
file_id: change.file_id,
|
|
42
|
-
snapshot_content,
|
|
43
|
-
metadata,
|
|
44
|
-
created_at: change.created_at,
|
|
45
|
-
})
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
fn stage_optional_json(
|
|
49
|
-
writes: &mut StorageWriteSet,
|
|
50
|
-
json_writer: &mut JsonStoreWriter,
|
|
51
|
-
value: Option<&str>,
|
|
52
|
-
) -> Result<Option<JsonRef>, LixError> {
|
|
53
|
-
let Some(value) = value else {
|
|
54
|
-
return Ok(None);
|
|
55
|
-
};
|
|
56
|
-
json_writer.stage_bytes(writes, value.as_bytes()).map(Some)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
fn stage_optional_metadata(
|
|
60
|
-
writes: &mut StorageWriteSet,
|
|
61
|
-
json_writer: &mut JsonStoreWriter,
|
|
62
|
-
value: Option<&RowMetadata>,
|
|
63
|
-
) -> Result<Option<JsonRef>, LixError> {
|
|
64
|
-
let Some(value) = value else {
|
|
65
|
-
return Ok(None);
|
|
66
|
-
};
|
|
67
|
-
let serialized = serialize_row_metadata(value);
|
|
68
|
-
json_writer
|
|
69
|
-
.stage_bytes(writes, serialized.as_bytes())
|
|
70
|
-
.map(Some)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async fn load_optional_metadata<S>(
|
|
74
|
-
json_reader: &mut JsonStoreReader<S>,
|
|
75
|
-
json_ref: Option<&JsonRef>,
|
|
76
|
-
) -> Result<Option<RowMetadata>, LixError>
|
|
77
|
-
where
|
|
78
|
-
S: StorageReader,
|
|
79
|
-
{
|
|
80
|
-
let Some(json) = load_optional_json(json_reader, json_ref, "metadata_ref").await? else {
|
|
81
|
-
return Ok(None);
|
|
82
|
-
};
|
|
83
|
-
let metadata = serde_json::from_str::<RowMetadata>(&json).map_err(|error| {
|
|
84
|
-
LixError::new(
|
|
85
|
-
"LIX_ERROR_INVALID_JSON",
|
|
86
|
-
format!("metadata_ref is invalid JSON: {error}"),
|
|
87
|
-
)
|
|
88
|
-
})?;
|
|
89
|
-
validate_row_metadata(metadata, "metadata_ref").map(Some)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async fn load_optional_json<S>(
|
|
93
|
-
json_reader: &mut JsonStoreReader<S>,
|
|
94
|
-
json_ref: Option<&JsonRef>,
|
|
95
|
-
field: &str,
|
|
96
|
-
) -> Result<Option<String>, LixError>
|
|
97
|
-
where
|
|
98
|
-
S: StorageReader,
|
|
99
|
-
{
|
|
100
|
-
let Some(json_ref) = json_ref else {
|
|
101
|
-
return Ok(None);
|
|
102
|
-
};
|
|
103
|
-
let bytes = json_reader.load_bytes(json_ref).await?.ok_or_else(|| {
|
|
104
|
-
LixError::new(
|
|
105
|
-
"LIX_ERROR_UNKNOWN",
|
|
106
|
-
format!(
|
|
107
|
-
"changelog {field} '{}' is missing from json_store",
|
|
108
|
-
json_ref.to_hex()
|
|
109
|
-
),
|
|
110
|
-
)
|
|
111
|
-
})?;
|
|
112
|
-
String::from_utf8(bytes).map(Some).map_err(|error| {
|
|
113
|
-
LixError::new(
|
|
114
|
-
"LIX_ERROR_UNKNOWN",
|
|
115
|
-
format!(
|
|
116
|
-
"changelog {field} '{}' is not valid UTF-8 JSON bytes: {error}",
|
|
117
|
-
json_ref.to_hex()
|
|
118
|
-
),
|
|
119
|
-
)
|
|
120
|
-
})
|
|
121
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
pub(crate) mod codec;
|
|
2
|
-
mod context;
|
|
3
|
-
mod materialization;
|
|
4
|
-
mod reader;
|
|
5
|
-
mod storage;
|
|
6
|
-
mod types;
|
|
7
|
-
|
|
8
|
-
#[allow(unused_imports)]
|
|
9
|
-
pub(crate) use context::{ChangelogContext, ChangelogStoreReader, ChangelogWriter};
|
|
10
|
-
pub(crate) use materialization::{canonicalize_materialized_change, materialize_change};
|
|
11
|
-
pub(crate) use reader::ChangelogReader;
|
|
12
|
-
#[allow(unused_imports)]
|
|
13
|
-
pub(crate) use types::{CanonicalChange, ChangelogScanRequest, MaterializedCanonicalChange};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
use async_trait::async_trait;
|
|
2
|
-
|
|
3
|
-
use crate::changelog::{CanonicalChange, ChangelogScanRequest};
|
|
4
|
-
use crate::LixError;
|
|
5
|
-
|
|
6
|
-
/// Read side for immutable changelog facts.
|
|
7
|
-
///
|
|
8
|
-
/// SQL providers and commit-graph readers depend on this role instead of
|
|
9
|
-
/// knowing which KV store backs the changelog for the current execution.
|
|
10
|
-
#[async_trait]
|
|
11
|
-
pub(crate) trait ChangelogReader: Send + Sync {
|
|
12
|
-
#[allow(dead_code)]
|
|
13
|
-
async fn load_change(&self, change_id: &str) -> Result<Option<CanonicalChange>, LixError>;
|
|
14
|
-
|
|
15
|
-
#[allow(dead_code)]
|
|
16
|
-
async fn scan_changes(
|
|
17
|
-
&self,
|
|
18
|
-
request: &ChangelogScanRequest,
|
|
19
|
-
) -> Result<Vec<CanonicalChange>, LixError>;
|
|
20
|
-
}
|
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
use crate::changelog::codec::{decode_change, encode_change};
|
|
2
|
-
use crate::changelog::{CanonicalChange, ChangelogScanRequest};
|
|
3
|
-
use crate::storage::KvScanRange;
|
|
4
|
-
use crate::storage::{KvGetGroup, KvGetRequest, KvScanRequest, StorageReader, StorageWriteSet};
|
|
5
|
-
use crate::LixError;
|
|
6
|
-
|
|
7
|
-
const CHANGELOG_CHANGE_NAMESPACE: &str = "changelog.change";
|
|
8
|
-
|
|
9
|
-
pub(crate) async fn load_change(
|
|
10
|
-
store: &mut impl StorageReader,
|
|
11
|
-
change_id: &str,
|
|
12
|
-
) -> Result<Option<CanonicalChange>, LixError> {
|
|
13
|
-
let bytes = store
|
|
14
|
-
.get_values(KvGetRequest {
|
|
15
|
-
groups: vec![KvGetGroup {
|
|
16
|
-
namespace: CHANGELOG_CHANGE_NAMESPACE.to_string(),
|
|
17
|
-
keys: vec![encode_change_key(change_id)],
|
|
18
|
-
}],
|
|
19
|
-
})
|
|
20
|
-
.await?
|
|
21
|
-
.groups
|
|
22
|
-
.into_iter()
|
|
23
|
-
.next()
|
|
24
|
-
.and_then(|group| group.single_value_owned());
|
|
25
|
-
let Some(bytes) = bytes else {
|
|
26
|
-
return Ok(None);
|
|
27
|
-
};
|
|
28
|
-
decode_change(&bytes).map(Some)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
pub(crate) async fn scan_changes(
|
|
32
|
-
store: &mut impl StorageReader,
|
|
33
|
-
request: &ChangelogScanRequest,
|
|
34
|
-
) -> Result<Vec<CanonicalChange>, LixError> {
|
|
35
|
-
// TODO(engine2): scan by a durable append sequence instead of change id.
|
|
36
|
-
// This first index is enough for exact lookup and deterministic debug scans.
|
|
37
|
-
let page = store
|
|
38
|
-
.scan_values(KvScanRequest {
|
|
39
|
-
namespace: CHANGELOG_CHANGE_NAMESPACE.to_string(),
|
|
40
|
-
range: KvScanRange::prefix(Vec::new()),
|
|
41
|
-
after: None,
|
|
42
|
-
limit: request.limit.unwrap_or(usize::MAX),
|
|
43
|
-
})
|
|
44
|
-
.await?;
|
|
45
|
-
page.values.iter().map(decode_change).collect()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
pub(crate) fn stage_changes(
|
|
49
|
-
writes: &mut StorageWriteSet,
|
|
50
|
-
changes: &[CanonicalChange],
|
|
51
|
-
) -> Result<(), LixError> {
|
|
52
|
-
for change in changes {
|
|
53
|
-
writes.put(
|
|
54
|
-
CHANGELOG_CHANGE_NAMESPACE,
|
|
55
|
-
encode_change_key(&change.id),
|
|
56
|
-
encode_change(change)?,
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
Ok(())
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fn encode_change_key(change_id: &str) -> Vec<u8> {
|
|
63
|
-
change_id.as_bytes().to_vec()
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
#[cfg(test)]
|
|
67
|
-
mod tests {
|
|
68
|
-
use std::sync::Arc;
|
|
69
|
-
|
|
70
|
-
use crate::backend::testing::UnitTestBackend;
|
|
71
|
-
use crate::changelog::{
|
|
72
|
-
canonicalize_materialized_change, materialize_change, ChangelogContext,
|
|
73
|
-
ChangelogScanRequest, MaterializedCanonicalChange,
|
|
74
|
-
};
|
|
75
|
-
use crate::json_store::JsonStoreContext;
|
|
76
|
-
use crate::storage::{StorageContext, StorageWriteSet, StorageWriteTransaction};
|
|
77
|
-
|
|
78
|
-
use super::*;
|
|
79
|
-
|
|
80
|
-
#[tokio::test]
|
|
81
|
-
async fn append_and_load_change_roundtrips() {
|
|
82
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
83
|
-
let changelog = ChangelogContext::new();
|
|
84
|
-
let change = test_change("change-1");
|
|
85
|
-
|
|
86
|
-
let mut tx = storage
|
|
87
|
-
.begin_write_transaction()
|
|
88
|
-
.await
|
|
89
|
-
.expect("transaction should open");
|
|
90
|
-
append_test_changes(&changelog, &mut tx, std::slice::from_ref(&change)).await;
|
|
91
|
-
tx.commit().await.expect("commit should succeed");
|
|
92
|
-
|
|
93
|
-
let loaded = load_test_change(&changelog, storage, "change-1").await;
|
|
94
|
-
assert_eq!(loaded, Some(change));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#[tokio::test]
|
|
98
|
-
async fn append_and_load_composite_entity_identity_roundtrips() {
|
|
99
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
100
|
-
let changelog = ChangelogContext::new();
|
|
101
|
-
let mut change = test_change("change-composite");
|
|
102
|
-
change.entity_id = crate::entity_identity::EntityIdentity::tuple(vec![
|
|
103
|
-
crate::entity_identity::EntityIdentityPart::String("entity".to_string()),
|
|
104
|
-
crate::entity_identity::EntityIdentityPart::Number("7".to_string()),
|
|
105
|
-
crate::entity_identity::EntityIdentityPart::Bool(true),
|
|
106
|
-
])
|
|
107
|
-
.expect("composite identity should be valid");
|
|
108
|
-
|
|
109
|
-
let mut tx = storage
|
|
110
|
-
.begin_write_transaction()
|
|
111
|
-
.await
|
|
112
|
-
.expect("transaction should open");
|
|
113
|
-
append_test_changes(&changelog, &mut tx, std::slice::from_ref(&change)).await;
|
|
114
|
-
tx.commit().await.expect("commit should succeed");
|
|
115
|
-
|
|
116
|
-
let loaded = load_test_change(&changelog, storage, "change-composite").await;
|
|
117
|
-
assert_eq!(loaded, Some(change));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
#[test]
|
|
121
|
-
fn decode_rejects_non_flatbuffer_bytes() {
|
|
122
|
-
let error = decode_change(br#"{"id":"change-json"}"#)
|
|
123
|
-
.expect_err("json changelog payloads are not accepted after the hard cut");
|
|
124
|
-
assert!(
|
|
125
|
-
error
|
|
126
|
-
.message
|
|
127
|
-
.contains("invalid FlatBuffers file identifier"),
|
|
128
|
-
"unexpected error: {error:?}"
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
#[tokio::test]
|
|
133
|
-
async fn scan_changes_respects_limit() {
|
|
134
|
-
let storage = StorageContext::new(Arc::new(UnitTestBackend::new()));
|
|
135
|
-
let changelog = ChangelogContext::new();
|
|
136
|
-
let mut tx = storage
|
|
137
|
-
.begin_write_transaction()
|
|
138
|
-
.await
|
|
139
|
-
.expect("transaction should open");
|
|
140
|
-
append_test_changes(
|
|
141
|
-
&changelog,
|
|
142
|
-
&mut tx,
|
|
143
|
-
&[test_change("change-1"), test_change("change-2")],
|
|
144
|
-
)
|
|
145
|
-
.await;
|
|
146
|
-
tx.commit().await.expect("commit should succeed");
|
|
147
|
-
|
|
148
|
-
let canonical_changes = {
|
|
149
|
-
let reader = changelog.reader(storage.clone());
|
|
150
|
-
reader
|
|
151
|
-
.scan_changes(&ChangelogScanRequest { limit: Some(1) })
|
|
152
|
-
.await
|
|
153
|
-
}
|
|
154
|
-
.expect("scan should succeed");
|
|
155
|
-
let mut json_reader = JsonStoreContext::new().reader(storage);
|
|
156
|
-
let mut changes = Vec::new();
|
|
157
|
-
for change in canonical_changes {
|
|
158
|
-
changes.push(
|
|
159
|
-
materialize_change(&mut json_reader, change)
|
|
160
|
-
.await
|
|
161
|
-
.expect("change should materialize"),
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
assert_eq!(changes, vec![test_change("change-1")]);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
fn test_change(id: &str) -> MaterializedCanonicalChange {
|
|
168
|
-
MaterializedCanonicalChange {
|
|
169
|
-
id: id.to_string(),
|
|
170
|
-
entity_id: crate::entity_identity::EntityIdentity::single("entity-1"),
|
|
171
|
-
schema_key: "test_schema".to_string(),
|
|
172
|
-
schema_version: "1".to_string(),
|
|
173
|
-
file_id: None,
|
|
174
|
-
snapshot_content: Some("{\"value\":1}".to_string()),
|
|
175
|
-
metadata: None,
|
|
176
|
-
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async fn append_test_changes(
|
|
181
|
-
changelog: &ChangelogContext,
|
|
182
|
-
tx: &mut Box<dyn StorageWriteTransaction + Send + Sync + 'static>,
|
|
183
|
-
changes: &[MaterializedCanonicalChange],
|
|
184
|
-
) {
|
|
185
|
-
let mut writes = StorageWriteSet::new();
|
|
186
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
187
|
-
let canonical_changes = changes
|
|
188
|
-
.iter()
|
|
189
|
-
.map(|change| canonicalize_materialized_change(&mut writes, &mut json_writer, change))
|
|
190
|
-
.collect::<Result<Vec<_>, _>>()
|
|
191
|
-
.expect("changes should canonicalize");
|
|
192
|
-
let mut writer = changelog.writer(&mut writes);
|
|
193
|
-
writer
|
|
194
|
-
.stage_changes(&canonical_changes)
|
|
195
|
-
.expect("append should succeed");
|
|
196
|
-
writes
|
|
197
|
-
.apply(&mut tx.as_mut())
|
|
198
|
-
.await
|
|
199
|
-
.expect("writes should apply");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async fn load_test_change(
|
|
203
|
-
changelog: &ChangelogContext,
|
|
204
|
-
storage: StorageContext,
|
|
205
|
-
change_id: &str,
|
|
206
|
-
) -> Option<MaterializedCanonicalChange> {
|
|
207
|
-
let canonical = {
|
|
208
|
-
let reader = changelog.reader(storage.clone());
|
|
209
|
-
reader
|
|
210
|
-
.load_change(change_id)
|
|
211
|
-
.await
|
|
212
|
-
.expect("load should succeed")
|
|
213
|
-
}?;
|
|
214
|
-
let mut json_reader = JsonStoreContext::new().reader(storage);
|
|
215
|
-
materialize_change(&mut json_reader, canonical)
|
|
216
|
-
.await
|
|
217
|
-
.map(Some)
|
|
218
|
-
.expect("change should materialize")
|
|
219
|
-
}
|
|
220
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
use crate::entity_identity::EntityIdentity;
|
|
2
|
-
use crate::json_store::JsonRef;
|
|
3
|
-
use crate::RowMetadata;
|
|
4
|
-
|
|
5
|
-
/// Immutable canonical change fact stored in the changelog.
|
|
6
|
-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
7
|
-
pub(crate) struct CanonicalChange {
|
|
8
|
-
pub(crate) id: String,
|
|
9
|
-
pub(crate) entity_id: EntityIdentity,
|
|
10
|
-
pub(crate) schema_key: String,
|
|
11
|
-
pub(crate) schema_version: String,
|
|
12
|
-
pub(crate) file_id: Option<String>,
|
|
13
|
-
pub(crate) snapshot_ref: Option<JsonRef>,
|
|
14
|
-
pub(crate) metadata_ref: Option<JsonRef>,
|
|
15
|
-
pub(crate) created_at: String,
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/// Boundary shape for callers that still work with materialized JSON payloads.
|
|
19
|
-
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
|
20
|
-
pub(crate) struct MaterializedCanonicalChange {
|
|
21
|
-
pub(crate) id: String,
|
|
22
|
-
pub(crate) entity_id: EntityIdentity,
|
|
23
|
-
pub(crate) schema_key: String,
|
|
24
|
-
pub(crate) schema_version: String,
|
|
25
|
-
pub(crate) file_id: Option<String>,
|
|
26
|
-
pub(crate) snapshot_content: Option<String>,
|
|
27
|
-
pub(crate) metadata: Option<RowMetadata>,
|
|
28
|
-
pub(crate) created_at: String,
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/// Minimal changelog scan request.
|
|
32
|
-
///
|
|
33
|
-
/// TODO(engine2): add filters and append-order cursors once changelog storage
|
|
34
|
-
/// has real append sequence keys.
|
|
35
|
-
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
36
|
-
pub(crate) struct ChangelogScanRequest {
|
|
37
|
-
pub(crate) limit: Option<usize>,
|
|
38
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"x-lix-key": "lix_change_set",
|
|
3
|
-
"x-lix-version": "1",
|
|
4
|
-
"x-lix-primary-key": [
|
|
5
|
-
"/id"
|
|
6
|
-
],
|
|
7
|
-
"type": "object",
|
|
8
|
-
"properties": {
|
|
9
|
-
"id": {
|
|
10
|
-
"type": "string",
|
|
11
|
-
"x-lix-default": "lix_uuid_v7()"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"required": [
|
|
15
|
-
"id"
|
|
16
|
-
],
|
|
17
|
-
"additionalProperties": false
|
|
18
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"x-lix-key": "lix_change_set_element",
|
|
3
|
-
"x-lix-version": "1",
|
|
4
|
-
"description": "Derived relational index mapping a change_set to the canonical changes introduced by its commit relative to the commit's first parent. Rebuildable from `lix_commit.change_ids` plus the referenced `lix_change` rows; the unique constraint enforces at most one change per (entity, schema, file) tuple within a single change_set.",
|
|
5
|
-
"x-lix-primary-key": [
|
|
6
|
-
"/change_set_id",
|
|
7
|
-
"/change_id"
|
|
8
|
-
],
|
|
9
|
-
"x-lix-unique": [
|
|
10
|
-
[
|
|
11
|
-
"/change_set_id",
|
|
12
|
-
"/entity_id",
|
|
13
|
-
"/schema_key",
|
|
14
|
-
"/file_id"
|
|
15
|
-
]
|
|
16
|
-
],
|
|
17
|
-
"x-lix-foreign-keys": [
|
|
18
|
-
{
|
|
19
|
-
"properties": [
|
|
20
|
-
"/change_set_id"
|
|
21
|
-
],
|
|
22
|
-
"references": {
|
|
23
|
-
"schemaKey": "lix_change_set",
|
|
24
|
-
"properties": [
|
|
25
|
-
"/id"
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
{
|
|
30
|
-
"properties": [
|
|
31
|
-
"/change_id"
|
|
32
|
-
],
|
|
33
|
-
"references": {
|
|
34
|
-
"schemaKey": "lix_change",
|
|
35
|
-
"properties": [
|
|
36
|
-
"/id"
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"type": "object",
|
|
42
|
-
"properties": {
|
|
43
|
-
"change_set_id": {
|
|
44
|
-
"type": "string",
|
|
45
|
-
"description": "The change_set this element belongs to (references `lix_change_set.id`)."
|
|
46
|
-
},
|
|
47
|
-
"change_id": {
|
|
48
|
-
"type": "string",
|
|
49
|
-
"description": "The canonical change introduced/adopted by the change_set's commit (references `lix_change.id`)."
|
|
50
|
-
},
|
|
51
|
-
"entity_id": {
|
|
52
|
-
"type": "string",
|
|
53
|
-
"description": "Denormalized from the referenced `lix_change` for fast entity-scoped queries and to anchor the unique constraint."
|
|
54
|
-
},
|
|
55
|
-
"schema_key": {
|
|
56
|
-
"type": "string",
|
|
57
|
-
"description": "Denormalized schema identifier from the referenced `lix_change`; part of the per-change_set uniqueness tuple."
|
|
58
|
-
},
|
|
59
|
-
"file_id": {
|
|
60
|
-
"type": [
|
|
61
|
-
"string",
|
|
62
|
-
"null"
|
|
63
|
-
],
|
|
64
|
-
"description": "Denormalized file scope from the referenced `lix_change`; NULL for engine-internal entities. Part of the per-change_set uniqueness tuple."
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"required": [
|
|
68
|
-
"change_set_id",
|
|
69
|
-
"change_id",
|
|
70
|
-
"entity_id",
|
|
71
|
-
"schema_key",
|
|
72
|
-
"file_id"
|
|
73
|
-
],
|
|
74
|
-
"additionalProperties": false
|
|
75
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"x-lix-key": "lix_entity_label",
|
|
3
|
-
"x-lix-version": "1",
|
|
4
|
-
"x-lix-primary-key": [
|
|
5
|
-
"/entity_id",
|
|
6
|
-
"/schema_key",
|
|
7
|
-
"/file_id",
|
|
8
|
-
"/label_id"
|
|
9
|
-
],
|
|
10
|
-
"x-lix-foreign-keys": [
|
|
11
|
-
{
|
|
12
|
-
"properties": [
|
|
13
|
-
"/entity_id",
|
|
14
|
-
"/schema_key",
|
|
15
|
-
"/file_id"
|
|
16
|
-
],
|
|
17
|
-
"references": {
|
|
18
|
-
"schemaKey": "lix_state",
|
|
19
|
-
"properties": [
|
|
20
|
-
"/entity_id",
|
|
21
|
-
"/schema_key",
|
|
22
|
-
"/file_id"
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"properties": [
|
|
28
|
-
"/label_id"
|
|
29
|
-
],
|
|
30
|
-
"references": {
|
|
31
|
-
"schemaKey": "lix_label",
|
|
32
|
-
"properties": [
|
|
33
|
-
"/id"
|
|
34
|
-
]
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
],
|
|
38
|
-
"type": "object",
|
|
39
|
-
"properties": {
|
|
40
|
-
"entity_id": {
|
|
41
|
-
"type": "string"
|
|
42
|
-
},
|
|
43
|
-
"schema_key": {
|
|
44
|
-
"type": "string"
|
|
45
|
-
},
|
|
46
|
-
"file_id": {
|
|
47
|
-
"type": [
|
|
48
|
-
"string",
|
|
49
|
-
"null"
|
|
50
|
-
]
|
|
51
|
-
},
|
|
52
|
-
"label_id": {
|
|
53
|
-
"type": "string"
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
"required": [
|
|
57
|
-
"entity_id",
|
|
58
|
-
"schema_key",
|
|
59
|
-
"file_id",
|
|
60
|
-
"label_id"
|
|
61
|
-
],
|
|
62
|
-
"additionalProperties": false
|
|
63
|
-
}
|