@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,284 +1,603 @@
|
|
|
1
|
-
use std::collections::BTreeMap;
|
|
2
|
-
|
|
3
1
|
use crate::binary_cas::BinaryCasContext;
|
|
4
|
-
use crate::
|
|
2
|
+
use crate::commit_store::{ChangeRef, CommitDraftRef, CommitStoreContext, StagedCommitStoreCommit};
|
|
5
3
|
use crate::functions::FunctionContext;
|
|
6
|
-
use crate::json_store::{
|
|
7
|
-
use crate::live_state::{LiveStateContext, LiveStateRow};
|
|
4
|
+
use crate::json_store::{JsonStoreContext, JsonWritePlacementRef, NormalizedJsonRef};
|
|
8
5
|
use crate::storage::{StorageReader, StorageWriteSet, StorageWriteTransaction};
|
|
9
|
-
use crate::
|
|
10
|
-
use crate::transaction::
|
|
6
|
+
use crate::tracked_state::{TrackedStateContext, TrackedStateDeltaRef};
|
|
7
|
+
use crate::transaction::prepare_version_ref_row;
|
|
8
|
+
use crate::transaction::staging::PreparedWriteSet;
|
|
9
|
+
use crate::transaction::types::{PreparedAdoptedStateRow, PreparedStateRow, StagedCommitMembers};
|
|
10
|
+
use crate::untracked_state::{
|
|
11
|
+
UntrackedStateContext, UntrackedStateIdentity, UntrackedStateIdentityRef, UntrackedStateRowRef,
|
|
12
|
+
};
|
|
11
13
|
use crate::version::{VersionContext, VersionRefReader};
|
|
12
|
-
use crate::
|
|
13
|
-
use
|
|
14
|
+
use crate::LixError;
|
|
15
|
+
use std::collections::BTreeMap;
|
|
16
|
+
|
|
17
|
+
type RowIndex = usize;
|
|
18
|
+
type AdoptedRowIndex = usize;
|
|
14
19
|
|
|
15
|
-
/// Commits transaction
|
|
20
|
+
/// Commits prepared transaction rows into durable tracked and untracked stores.
|
|
16
21
|
///
|
|
17
|
-
/// Providers decode DataFusion DML into hydrated `
|
|
18
|
-
/// rows are durable local overlay state and bypass
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
///
|
|
22
|
-
pub(crate) async fn
|
|
22
|
+
/// Providers decode DataFusion DML into hydrated `PreparedStateRow`s. Untracked
|
|
23
|
+
/// rows are durable local overlay state and bypass commit-store rows. Tracked
|
|
24
|
+
/// rows stage canonical commit-store facts, then update the live-state serving
|
|
25
|
+
/// projection. The tracked side of that projection is a prolly root keyed by
|
|
26
|
+
/// the new commit id.
|
|
27
|
+
pub(crate) async fn commit_prepared_writes(
|
|
23
28
|
binary_cas: &BinaryCasContext,
|
|
24
|
-
|
|
25
|
-
live_state: &LiveStateContext,
|
|
29
|
+
commit_store: &CommitStoreContext,
|
|
26
30
|
version_ctx: &VersionContext,
|
|
27
31
|
runtime_functions: Option<&FunctionContext>,
|
|
28
32
|
transaction: &mut (impl StorageWriteTransaction + ?Sized),
|
|
29
|
-
|
|
33
|
+
prepared_writes: PreparedWriteSet,
|
|
30
34
|
) -> Result<(), LixError> {
|
|
31
35
|
let mut writes = StorageWriteSet::new();
|
|
32
36
|
let mut json_writer = JsonStoreContext::new().writer();
|
|
33
37
|
|
|
34
|
-
if !
|
|
38
|
+
if !prepared_writes.file_data_writes.is_empty() {
|
|
35
39
|
let mut blob_writer = binary_cas.writer(&mut writes);
|
|
36
|
-
for write in &
|
|
40
|
+
for write in &prepared_writes.file_data_writes {
|
|
37
41
|
blob_writer.stage_bytes(&write.data)?;
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
let
|
|
42
|
-
|
|
43
|
-
.into_iter()
|
|
44
|
-
.partition(|row| !row.untracked);
|
|
45
|
-
let adopted_rows = staged_writes.adopted_rows;
|
|
45
|
+
let state_rows = prepared_writes.state_rows;
|
|
46
|
+
let adopted_rows = prepared_writes.adopted_rows;
|
|
46
47
|
let finalized = finalize_commit_rows(
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
prepared_writes.commit_members_by_version,
|
|
49
|
+
prepared_writes.extra_commit_parents_by_version,
|
|
49
50
|
version_ctx,
|
|
50
51
|
transaction,
|
|
51
52
|
)
|
|
52
53
|
.await?;
|
|
53
|
-
|
|
54
|
+
let commit_rows = finalized.commit_rows;
|
|
54
55
|
let version_heads = finalized.version_heads;
|
|
56
|
+
let tracked_roots = finalized.tracked_roots;
|
|
57
|
+
let row_index = index_prepared_rows(&state_rows)?;
|
|
58
|
+
let adopted_index = index_adopted_rows(&adopted_rows);
|
|
55
59
|
|
|
56
60
|
if let Some(runtime_functions) = runtime_functions {
|
|
57
|
-
let mut writer = live_state.writer(&mut *transaction);
|
|
58
61
|
runtime_functions
|
|
59
|
-
.stage_persist_if_needed(&mut
|
|
62
|
+
.stage_persist_if_needed(&mut writes)
|
|
60
63
|
.await?;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
if
|
|
66
|
+
if state_rows.is_empty()
|
|
64
67
|
&& adopted_rows.is_empty()
|
|
65
|
-
&&
|
|
68
|
+
&& commit_rows.is_empty()
|
|
66
69
|
&& version_heads.is_empty()
|
|
67
70
|
&& writes.is_empty()
|
|
68
71
|
{
|
|
69
72
|
return Ok(());
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
75
|
+
let staged_commits = stage_commit_store_commits(
|
|
76
|
+
commit_store,
|
|
77
|
+
transaction,
|
|
78
|
+
&mut writes,
|
|
79
|
+
&state_rows,
|
|
80
|
+
&row_index.tracked_row_indices_by_commit,
|
|
81
|
+
&adopted_rows,
|
|
82
|
+
&adopted_index.tracked_row_indices_by_commit,
|
|
83
|
+
&commit_rows,
|
|
84
|
+
)
|
|
85
|
+
.await?;
|
|
86
|
+
|
|
87
|
+
let json_pack_indexes_by_commit = stage_prepared_json_payloads(
|
|
88
|
+
&mut json_writer,
|
|
89
|
+
&mut writes,
|
|
90
|
+
&state_rows,
|
|
91
|
+
&row_index.tracked_row_indices_by_commit,
|
|
92
|
+
&staged_commits,
|
|
93
|
+
&row_index.untracked_row_indices,
|
|
94
|
+
)?;
|
|
89
95
|
|
|
90
96
|
// The serving projection is updated in the same backend transaction as the
|
|
91
|
-
//
|
|
97
|
+
// commit-store append. Tracked rows become prolly mutations under their owning
|
|
92
98
|
// commit root; untracked rows remain in the separate local overlay store.
|
|
93
|
-
let live_state_rows = changelog_rows
|
|
94
|
-
.into_iter()
|
|
95
|
-
.map(LiveStateRow::from)
|
|
96
|
-
.chain(adopted_rows.into_iter().map(LiveStateRow::from))
|
|
97
|
-
.chain(untracked_rows.into_iter().map(LiveStateRow::from))
|
|
98
|
-
.collect::<Vec<_>>();
|
|
99
|
-
|
|
100
99
|
{
|
|
101
|
-
let
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
100
|
+
let untracked_overlay_delete_identities = existing_untracked_overlay_delete_identities(
|
|
101
|
+
transaction,
|
|
102
|
+
row_index
|
|
103
|
+
.canonical_row_indices
|
|
104
|
+
.iter()
|
|
105
|
+
.map(|&row_index| untracked_identity_ref_from_state_row(&state_rows[row_index]))
|
|
106
|
+
.chain(
|
|
107
|
+
adopted_rows
|
|
108
|
+
.iter()
|
|
109
|
+
.map(untracked_identity_ref_from_adopted_row),
|
|
110
|
+
),
|
|
111
|
+
)
|
|
112
|
+
.await?;
|
|
113
|
+
UntrackedStateContext::new()
|
|
114
|
+
.writer(&mut writes)
|
|
115
|
+
.stage_rows(
|
|
116
|
+
row_index
|
|
117
|
+
.untracked_row_indices
|
|
118
|
+
.iter()
|
|
119
|
+
.map(|&row_index| untracked_row_ref_from_state_row(&state_rows[row_index])),
|
|
120
|
+
)?;
|
|
121
|
+
UntrackedStateContext::new()
|
|
122
|
+
.writer(&mut writes)
|
|
123
|
+
.stage_delete_rows(
|
|
124
|
+
untracked_overlay_delete_identities
|
|
125
|
+
.iter()
|
|
126
|
+
.map(UntrackedStateIdentity::as_ref),
|
|
127
|
+
);
|
|
128
|
+
stage_tracked_roots(
|
|
129
|
+
transaction,
|
|
130
|
+
&mut writes,
|
|
131
|
+
&state_rows,
|
|
132
|
+
row_index.tracked_row_indices_by_commit,
|
|
133
|
+
&adopted_rows,
|
|
134
|
+
adopted_index.tracked_row_indices_by_commit,
|
|
135
|
+
tracked_roots,
|
|
136
|
+
staged_commits,
|
|
137
|
+
json_pack_indexes_by_commit,
|
|
138
|
+
)
|
|
139
|
+
.await?;
|
|
105
140
|
}
|
|
106
141
|
|
|
107
142
|
for version_head in version_heads {
|
|
108
|
-
let canonical_row =
|
|
109
|
-
&mut writes,
|
|
110
|
-
&mut json_writer,
|
|
143
|
+
let canonical_row = prepare_version_ref_row(
|
|
111
144
|
&version_head.version_id,
|
|
112
145
|
&version_head.commit_id,
|
|
113
146
|
&version_head.timestamp,
|
|
114
147
|
)?;
|
|
115
|
-
version_ctx.stage_canonical_ref_rows(&mut writes, &[canonical_row])?;
|
|
148
|
+
version_ctx.stage_canonical_ref_rows(&mut writes, &[canonical_row.row])?;
|
|
116
149
|
}
|
|
117
150
|
|
|
118
151
|
writes.apply(transaction).await?;
|
|
119
152
|
Ok(())
|
|
120
153
|
}
|
|
121
154
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
transaction: &mut (impl StorageReader + ?Sized),
|
|
155
|
+
fn stage_prepared_json_payloads(
|
|
156
|
+
json_writer: &mut crate::json_store::JsonStoreWriter,
|
|
125
157
|
writes: &mut StorageWriteSet,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
158
|
+
state_rows: &[PreparedStateRow],
|
|
159
|
+
tracked_row_indices_by_commit: &BTreeMap<String, Vec<RowIndex>>,
|
|
160
|
+
staged_commits: &BTreeMap<String, StagedCommitStoreCommit>,
|
|
161
|
+
untracked_row_indices: &[RowIndex],
|
|
162
|
+
) -> Result<BTreeMap<String, BTreeMap<u32, std::collections::HashMap<[u8; 32], usize>>>, LixError> {
|
|
163
|
+
let mut pack_indexes_by_commit = BTreeMap::new();
|
|
164
|
+
for (commit_id, row_indices) in tracked_row_indices_by_commit {
|
|
165
|
+
let staged_commit = staged_commits.get(commit_id).ok_or_else(|| {
|
|
166
|
+
LixError::new(
|
|
167
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
168
|
+
format!("commit '{commit_id}' has tracked JSON rows but no staged commit-store locators"),
|
|
169
|
+
)
|
|
170
|
+
})?;
|
|
171
|
+
if row_indices.len() != staged_commit.authored_locators.len() {
|
|
172
|
+
return Err(LixError::new(
|
|
173
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
174
|
+
format!(
|
|
175
|
+
"commit '{commit_id}' has {} tracked JSON rows but {} authored locators",
|
|
176
|
+
row_indices.len(),
|
|
177
|
+
staged_commit.authored_locators.len()
|
|
178
|
+
),
|
|
179
|
+
));
|
|
180
|
+
}
|
|
181
|
+
let mut row_indices_by_pack = BTreeMap::<u32, Vec<RowIndex>>::new();
|
|
182
|
+
for (&row_index, locator) in row_indices.iter().zip(&staged_commit.authored_locators) {
|
|
183
|
+
row_indices_by_pack
|
|
184
|
+
.entry(locator.source_pack_id)
|
|
185
|
+
.or_default()
|
|
186
|
+
.push(row_index);
|
|
187
|
+
}
|
|
188
|
+
for (pack_id, pack_row_indices) in row_indices_by_pack {
|
|
189
|
+
let report = json_writer.stage_batch_report(
|
|
190
|
+
writes,
|
|
191
|
+
JsonWritePlacementRef::CommitPack { commit_id, pack_id },
|
|
192
|
+
pack_row_indices
|
|
193
|
+
.iter()
|
|
194
|
+
.flat_map(|&row_index| json_payloads_from_state_row(&state_rows[row_index])),
|
|
195
|
+
)?;
|
|
196
|
+
pack_indexes_by_commit
|
|
197
|
+
.entry(commit_id.clone())
|
|
198
|
+
.or_insert_with(BTreeMap::new)
|
|
199
|
+
.insert(pack_id, report.pack_indexes);
|
|
150
200
|
}
|
|
151
201
|
}
|
|
152
|
-
|
|
202
|
+
json_writer.stage_batch(
|
|
203
|
+
writes,
|
|
204
|
+
JsonWritePlacementRef::OutOfBand,
|
|
205
|
+
untracked_row_indices
|
|
206
|
+
.iter()
|
|
207
|
+
.flat_map(|&row_index| json_payloads_from_state_row(&state_rows[row_index])),
|
|
208
|
+
)?;
|
|
209
|
+
Ok(pack_indexes_by_commit)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fn json_payloads_from_state_row(
|
|
213
|
+
row: &PreparedStateRow,
|
|
214
|
+
) -> impl Iterator<Item = NormalizedJsonRef<'_>> {
|
|
215
|
+
row.snapshot
|
|
216
|
+
.iter()
|
|
217
|
+
.chain(row.metadata.iter())
|
|
218
|
+
.map(|json| NormalizedJsonRef::trusted_prehashed(json.normalized.as_ref(), json.json_ref))
|
|
153
219
|
}
|
|
154
220
|
|
|
155
|
-
async fn
|
|
156
|
-
changelog: &ChangelogContext,
|
|
221
|
+
async fn existing_untracked_overlay_delete_identities<'a>(
|
|
157
222
|
transaction: &mut (impl StorageReader + ?Sized),
|
|
158
|
-
|
|
159
|
-
) -> Result<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
expected.id
|
|
186
|
-
),
|
|
187
|
-
));
|
|
188
|
-
}
|
|
223
|
+
identities: impl IntoIterator<Item = UntrackedStateIdentityRef<'a>>,
|
|
224
|
+
) -> Result<Vec<UntrackedStateIdentity>, LixError> {
|
|
225
|
+
UntrackedStateContext::new()
|
|
226
|
+
.reader(transaction)
|
|
227
|
+
.existing_identities(identities)
|
|
228
|
+
.await
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
struct PreparedRowIndex {
|
|
232
|
+
canonical_row_indices: Vec<RowIndex>,
|
|
233
|
+
untracked_row_indices: Vec<RowIndex>,
|
|
234
|
+
tracked_row_indices_by_commit: BTreeMap<String, Vec<RowIndex>>,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
struct PreparedAdoptedRowIndex {
|
|
238
|
+
tracked_row_indices_by_commit: BTreeMap<String, Vec<AdoptedRowIndex>>,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
fn index_prepared_rows(rows: &[PreparedStateRow]) -> Result<PreparedRowIndex, LixError> {
|
|
242
|
+
let mut canonical_row_indices = Vec::new();
|
|
243
|
+
let mut untracked_row_indices = Vec::new();
|
|
244
|
+
let mut tracked_row_indices_by_commit = BTreeMap::<String, Vec<RowIndex>>::new();
|
|
245
|
+
|
|
246
|
+
for (row_index, row) in rows.iter().enumerate() {
|
|
247
|
+
if row.untracked {
|
|
248
|
+
untracked_row_indices.push(row_index);
|
|
249
|
+
continue;
|
|
189
250
|
}
|
|
251
|
+
let Some(commit_id) = row.commit_id.as_ref() else {
|
|
252
|
+
return Err(LixError::new(
|
|
253
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
254
|
+
"tracked prepared row is missing commit_id before commit indexing",
|
|
255
|
+
));
|
|
256
|
+
};
|
|
257
|
+
canonical_row_indices.push(row_index);
|
|
258
|
+
tracked_row_indices_by_commit
|
|
259
|
+
.entry(commit_id.clone())
|
|
260
|
+
.or_default()
|
|
261
|
+
.push(row_index);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
Ok(PreparedRowIndex {
|
|
265
|
+
canonical_row_indices,
|
|
266
|
+
untracked_row_indices,
|
|
267
|
+
tracked_row_indices_by_commit,
|
|
268
|
+
})
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
fn index_adopted_rows(rows: &[PreparedAdoptedStateRow]) -> PreparedAdoptedRowIndex {
|
|
272
|
+
let mut tracked_row_indices_by_commit = BTreeMap::<String, Vec<AdoptedRowIndex>>::new();
|
|
273
|
+
for (row_index, row) in rows.iter().enumerate() {
|
|
274
|
+
tracked_row_indices_by_commit
|
|
275
|
+
.entry(row.commit_id.clone())
|
|
276
|
+
.or_default()
|
|
277
|
+
.push(row_index);
|
|
278
|
+
}
|
|
279
|
+
PreparedAdoptedRowIndex {
|
|
280
|
+
tracked_row_indices_by_commit,
|
|
190
281
|
}
|
|
191
|
-
Ok(())
|
|
192
282
|
}
|
|
193
283
|
|
|
194
|
-
fn
|
|
284
|
+
async fn stage_commit_store_commits(
|
|
285
|
+
commit_store: &CommitStoreContext,
|
|
286
|
+
transaction: &mut (impl StorageReader + ?Sized),
|
|
195
287
|
writes: &mut StorageWriteSet,
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
288
|
+
state_rows: &[PreparedStateRow],
|
|
289
|
+
tracked_row_indices_by_commit: &BTreeMap<String, Vec<RowIndex>>,
|
|
290
|
+
adopted_rows: &[PreparedAdoptedStateRow],
|
|
291
|
+
adopted_row_indices_by_commit: &BTreeMap<String, Vec<AdoptedRowIndex>>,
|
|
292
|
+
commit_rows: &[FinalizedCommitRow],
|
|
293
|
+
) -> Result<BTreeMap<String, StagedCommitStoreCommit>, LixError> {
|
|
294
|
+
let mut commits = Vec::with_capacity(commit_rows.len());
|
|
295
|
+
let mut commit_ids = Vec::with_capacity(commit_rows.len());
|
|
296
|
+
for commit_row in commit_rows {
|
|
297
|
+
let state_row_indices = tracked_row_indices_by_commit
|
|
298
|
+
.get(&commit_row.commit_id)
|
|
299
|
+
.map(Vec::as_slice)
|
|
300
|
+
.unwrap_or_default();
|
|
301
|
+
let adopted_row_indices = adopted_row_indices_by_commit
|
|
302
|
+
.get(&commit_row.commit_id)
|
|
303
|
+
.map(Vec::as_slice)
|
|
304
|
+
.unwrap_or_default();
|
|
305
|
+
let mut authored_changes = Vec::with_capacity(state_row_indices.len());
|
|
306
|
+
for &row_index in state_row_indices {
|
|
307
|
+
authored_changes.push(change_ref_from_state_row(&state_rows[row_index])?);
|
|
308
|
+
}
|
|
309
|
+
let mut adopted_changes = Vec::with_capacity(adopted_row_indices.len());
|
|
310
|
+
for &row_index in adopted_row_indices {
|
|
311
|
+
adopted_changes.push(change_ref_from_adopted_row(&adopted_rows[row_index]));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
let commit = CommitDraftRef {
|
|
315
|
+
id: &commit_row.commit_id,
|
|
316
|
+
change_id: &commit_row.change_id,
|
|
317
|
+
parent_ids: &commit_row.parent_commit_ids,
|
|
318
|
+
author_account_ids: &[],
|
|
319
|
+
created_at: &commit_row.created_at,
|
|
320
|
+
};
|
|
321
|
+
commit_ids.push(commit_row.commit_id.clone());
|
|
322
|
+
commits.push((commit, authored_changes, adopted_changes));
|
|
323
|
+
}
|
|
324
|
+
let staged = commit_store
|
|
325
|
+
.writer(transaction, writes)
|
|
326
|
+
.stage_tracked_commit_drafts(commits)
|
|
327
|
+
.await?;
|
|
328
|
+
if staged.len() != commit_ids.len() {
|
|
329
|
+
return Err(LixError::new(
|
|
330
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
331
|
+
format!(
|
|
332
|
+
"commit-store staged {} commits for {} finalized commit rows",
|
|
333
|
+
staged.len(),
|
|
334
|
+
commit_ids.len()
|
|
335
|
+
),
|
|
336
|
+
));
|
|
337
|
+
}
|
|
338
|
+
Ok(commit_ids.into_iter().zip(staged).collect())
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
fn change_ref_from_state_row(row: &PreparedStateRow) -> Result<ChangeRef<'_>, LixError> {
|
|
342
|
+
let Some(change_id) = row.change_id.as_deref() else {
|
|
200
343
|
return Err(LixError::new(
|
|
201
344
|
"LIX_ERROR_UNKNOWN",
|
|
202
|
-
"tracked staged row is missing change_id before
|
|
345
|
+
"tracked staged row is missing change_id before commit-store append",
|
|
203
346
|
));
|
|
204
347
|
};
|
|
205
348
|
|
|
206
|
-
Ok(
|
|
207
|
-
id: change_id
|
|
208
|
-
entity_id: row.entity_id
|
|
209
|
-
schema_key: row.schema_key
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
created_at: row.created_at.clone(),
|
|
349
|
+
Ok(ChangeRef {
|
|
350
|
+
id: change_id,
|
|
351
|
+
entity_id: &row.entity_id,
|
|
352
|
+
schema_key: &row.schema_key,
|
|
353
|
+
file_id: row.file_id.as_deref(),
|
|
354
|
+
snapshot_ref: row.snapshot.as_ref().map(|snapshot| &snapshot.json_ref),
|
|
355
|
+
metadata_ref: row.metadata.as_ref().map(|metadata| &metadata.json_ref),
|
|
356
|
+
created_at: &row.updated_at,
|
|
215
357
|
})
|
|
216
358
|
}
|
|
217
359
|
|
|
218
|
-
fn
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
360
|
+
fn change_ref_from_adopted_row(row: &PreparedAdoptedStateRow) -> ChangeRef<'_> {
|
|
361
|
+
ChangeRef {
|
|
362
|
+
id: &row.change_id,
|
|
363
|
+
entity_id: &row.entity_id,
|
|
364
|
+
schema_key: &row.schema_key,
|
|
365
|
+
file_id: row.file_id.as_deref(),
|
|
366
|
+
snapshot_ref: row.snapshot.as_ref().map(|snapshot| &snapshot.json_ref),
|
|
367
|
+
metadata_ref: row.metadata.as_ref().map(|metadata| &metadata.json_ref),
|
|
368
|
+
created_at: &row.updated_at,
|
|
369
|
+
}
|
|
227
370
|
}
|
|
228
371
|
|
|
229
|
-
fn
|
|
372
|
+
async fn stage_tracked_roots(
|
|
373
|
+
transaction: &mut (impl StorageReader + ?Sized),
|
|
230
374
|
writes: &mut StorageWriteSet,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
375
|
+
state_rows: &[PreparedStateRow],
|
|
376
|
+
mut tracked_row_indices_by_commit: BTreeMap<String, Vec<RowIndex>>,
|
|
377
|
+
adopted_rows: &[PreparedAdoptedStateRow],
|
|
378
|
+
mut adopted_row_indices_by_commit: BTreeMap<String, Vec<AdoptedRowIndex>>,
|
|
379
|
+
tracked_roots: Vec<PendingTrackedRoot>,
|
|
380
|
+
mut staged_commits: BTreeMap<String, StagedCommitStoreCommit>,
|
|
381
|
+
json_pack_indexes_by_commit: BTreeMap<
|
|
382
|
+
String,
|
|
383
|
+
BTreeMap<u32, std::collections::HashMap<[u8; 32], usize>>,
|
|
384
|
+
>,
|
|
385
|
+
) -> Result<(), LixError> {
|
|
386
|
+
let tracked_state = TrackedStateContext::new();
|
|
387
|
+
let mut writer = tracked_state.writer(transaction, writes);
|
|
388
|
+
for root in tracked_roots {
|
|
389
|
+
let staged = staged_commits.remove(&root.commit_id).ok_or_else(|| {
|
|
390
|
+
LixError::new(
|
|
391
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
392
|
+
format!(
|
|
393
|
+
"tracked-state root for commit '{}' has no staged commit-store locators",
|
|
394
|
+
root.commit_id
|
|
395
|
+
),
|
|
396
|
+
)
|
|
397
|
+
})?;
|
|
398
|
+
let state_row_indices = tracked_row_indices_by_commit
|
|
399
|
+
.remove(&root.commit_id)
|
|
400
|
+
.unwrap_or_default();
|
|
401
|
+
let adopted_row_indices = adopted_row_indices_by_commit
|
|
402
|
+
.remove(&root.commit_id)
|
|
403
|
+
.unwrap_or_default();
|
|
404
|
+
if state_row_indices.len() != staged.authored_locators.len() {
|
|
405
|
+
return Err(LixError::new(
|
|
406
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
407
|
+
format!(
|
|
408
|
+
"commit '{}' has {} tracked authored rows but {} commit-store authored locators",
|
|
409
|
+
root.commit_id,
|
|
410
|
+
state_row_indices.len(),
|
|
411
|
+
staged.authored_locators.len()
|
|
412
|
+
),
|
|
413
|
+
));
|
|
414
|
+
}
|
|
415
|
+
if adopted_row_indices.len() != staged.adopted_locators.len() {
|
|
416
|
+
return Err(LixError::new(
|
|
417
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
418
|
+
format!(
|
|
419
|
+
"commit '{}' has {} tracked adopted rows but {} commit-store adopted locators",
|
|
420
|
+
root.commit_id,
|
|
421
|
+
adopted_row_indices.len(),
|
|
422
|
+
staged.adopted_locators.len()
|
|
423
|
+
),
|
|
424
|
+
));
|
|
425
|
+
}
|
|
426
|
+
let authored_changes = state_row_indices
|
|
427
|
+
.iter()
|
|
428
|
+
.map(|&row_index| change_ref_from_state_row(&state_rows[row_index]))
|
|
429
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
430
|
+
let adopted_changes = adopted_row_indices
|
|
431
|
+
.iter()
|
|
432
|
+
.map(|&row_index| change_ref_from_adopted_row(&adopted_rows[row_index]))
|
|
433
|
+
.collect::<Vec<_>>();
|
|
434
|
+
let authored_updated_at = state_row_indices
|
|
435
|
+
.iter()
|
|
436
|
+
.map(|&row_index| state_rows[row_index].updated_at.as_str())
|
|
437
|
+
.collect::<Vec<_>>();
|
|
438
|
+
let authored_created_at = state_row_indices
|
|
439
|
+
.iter()
|
|
440
|
+
.map(|&row_index| state_rows[row_index].created_at.as_str())
|
|
441
|
+
.collect::<Vec<_>>();
|
|
442
|
+
let adopted_updated_at = adopted_row_indices
|
|
443
|
+
.iter()
|
|
444
|
+
.map(|&row_index| adopted_rows[row_index].updated_at.as_str())
|
|
445
|
+
.collect::<Vec<_>>();
|
|
446
|
+
let adopted_created_at = adopted_row_indices
|
|
447
|
+
.iter()
|
|
448
|
+
.map(|&row_index| adopted_rows[row_index].created_at.as_str())
|
|
449
|
+
.collect::<Vec<_>>();
|
|
450
|
+
let mut deltas = Vec::with_capacity(authored_changes.len() + adopted_changes.len());
|
|
451
|
+
deltas.extend(
|
|
452
|
+
authored_changes
|
|
453
|
+
.iter()
|
|
454
|
+
.zip(&staged.authored_locators)
|
|
455
|
+
.zip(authored_created_at)
|
|
456
|
+
.zip(authored_updated_at)
|
|
457
|
+
.map(
|
|
458
|
+
|(((change, locator), created_at), updated_at)| TrackedStateDeltaRef {
|
|
459
|
+
change: *change,
|
|
460
|
+
locator: locator.as_ref(),
|
|
461
|
+
created_at,
|
|
462
|
+
updated_at,
|
|
463
|
+
},
|
|
464
|
+
),
|
|
465
|
+
);
|
|
466
|
+
deltas.extend(
|
|
467
|
+
adopted_changes
|
|
468
|
+
.iter()
|
|
469
|
+
.zip(&staged.adopted_locators)
|
|
470
|
+
.zip(adopted_created_at)
|
|
471
|
+
.zip(adopted_updated_at)
|
|
472
|
+
.map(
|
|
473
|
+
|(((change, locator), created_at), updated_at)| TrackedStateDeltaRef {
|
|
474
|
+
change: *change,
|
|
475
|
+
locator: locator.as_ref(),
|
|
476
|
+
created_at,
|
|
477
|
+
updated_at,
|
|
478
|
+
},
|
|
479
|
+
),
|
|
480
|
+
);
|
|
481
|
+
if let Some(indexes) = json_pack_indexes_by_commit
|
|
482
|
+
.get(&root.commit_id)
|
|
483
|
+
.and_then(|packs| packs.get(&0))
|
|
484
|
+
{
|
|
485
|
+
writer
|
|
486
|
+
.stage_delta_with_json_pack_indexes(
|
|
487
|
+
&root.commit_id,
|
|
488
|
+
root.parent_commit_id.as_deref(),
|
|
489
|
+
&deltas,
|
|
490
|
+
crate::tracked_state::DeltaJsonPackIndexesRef {
|
|
491
|
+
commit_id: &root.commit_id,
|
|
492
|
+
pack_id: 0,
|
|
493
|
+
indexes,
|
|
494
|
+
},
|
|
495
|
+
)
|
|
496
|
+
.await?;
|
|
497
|
+
} else {
|
|
498
|
+
writer
|
|
499
|
+
.stage_delta(&root.commit_id, root.parent_commit_id.as_deref(), &deltas)
|
|
500
|
+
.await?;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if !tracked_row_indices_by_commit.is_empty() || !adopted_row_indices_by_commit.is_empty() {
|
|
504
|
+
let mut commit_ids = tracked_row_indices_by_commit
|
|
505
|
+
.keys()
|
|
506
|
+
.chain(adopted_row_indices_by_commit.keys())
|
|
507
|
+
.cloned()
|
|
508
|
+
.collect::<Vec<_>>();
|
|
509
|
+
commit_ids.sort();
|
|
510
|
+
commit_ids.dedup();
|
|
511
|
+
return Err(LixError::new(
|
|
512
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
513
|
+
format!(
|
|
514
|
+
"tracked live_state rows have no finalized root metadata for commit ids: {}",
|
|
515
|
+
commit_ids.join(", ")
|
|
516
|
+
),
|
|
517
|
+
));
|
|
518
|
+
}
|
|
519
|
+
if !staged_commits.is_empty() {
|
|
520
|
+
let commit_ids = staged_commits.keys().cloned().collect::<Vec<_>>();
|
|
521
|
+
return Err(LixError::new(
|
|
522
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
523
|
+
format!(
|
|
524
|
+
"commit-store staged commits without tracked root metadata: {}",
|
|
525
|
+
commit_ids.join(", ")
|
|
526
|
+
),
|
|
527
|
+
));
|
|
528
|
+
}
|
|
529
|
+
Ok(())
|
|
241
530
|
}
|
|
242
531
|
|
|
243
|
-
fn
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
created_at: row.created_at
|
|
257
|
-
|
|
532
|
+
fn untracked_row_ref_from_state_row(row: &PreparedStateRow) -> UntrackedStateRowRef<'_> {
|
|
533
|
+
UntrackedStateRowRef {
|
|
534
|
+
entity_id: &row.entity_id,
|
|
535
|
+
schema_key: &row.schema_key,
|
|
536
|
+
file_id: row.file_id.as_deref(),
|
|
537
|
+
snapshot_content: row
|
|
538
|
+
.snapshot
|
|
539
|
+
.as_ref()
|
|
540
|
+
.map(|snapshot| snapshot.normalized.as_ref()),
|
|
541
|
+
metadata: row
|
|
542
|
+
.metadata
|
|
543
|
+
.as_ref()
|
|
544
|
+
.map(|metadata| metadata.normalized.as_ref()),
|
|
545
|
+
created_at: &row.created_at,
|
|
546
|
+
updated_at: &row.updated_at,
|
|
547
|
+
global: row.global,
|
|
548
|
+
version_id: &row.version_id,
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
fn untracked_identity_ref_from_state_row(row: &PreparedStateRow) -> UntrackedStateIdentityRef<'_> {
|
|
553
|
+
UntrackedStateIdentityRef {
|
|
554
|
+
version_id: &row.version_id,
|
|
555
|
+
schema_key: &row.schema_key,
|
|
556
|
+
entity_id: &row.entity_id,
|
|
557
|
+
file_id: row.file_id.as_deref(),
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
fn untracked_identity_ref_from_adopted_row(
|
|
562
|
+
row: &PreparedAdoptedStateRow,
|
|
563
|
+
) -> UntrackedStateIdentityRef<'_> {
|
|
564
|
+
UntrackedStateIdentityRef {
|
|
565
|
+
version_id: &row.version_id,
|
|
566
|
+
schema_key: &row.schema_key,
|
|
567
|
+
entity_id: &row.entity_id,
|
|
568
|
+
file_id: row.file_id.as_deref(),
|
|
569
|
+
}
|
|
258
570
|
}
|
|
259
571
|
|
|
260
|
-
/// Materializes tracked staged membership into
|
|
572
|
+
/// Materializes tracked staged membership into commit-store commits.
|
|
261
573
|
///
|
|
262
574
|
/// Staging only accumulates `version_id -> change_ids` because commit ids,
|
|
263
575
|
/// parent heads, and commit-row timestamps belong to transaction finalization.
|
|
264
576
|
/// The `change_ids` list is the ordered set of canonical changes whose effects
|
|
265
577
|
/// the commit introduces relative to its first parent; merge commits may later
|
|
266
578
|
/// populate this list with existing source-parent changes instead of copied
|
|
267
|
-
///
|
|
268
|
-
/// This function turns those membership sets into
|
|
269
|
-
/// `schema_key = "lix_commit"`, so the changelog/live_state flush can treat
|
|
270
|
-
/// commit rows exactly like any other staged state row.
|
|
579
|
+
/// change payloads.
|
|
580
|
+
/// This function turns those membership sets into finalized commit facts.
|
|
271
581
|
///
|
|
272
582
|
/// Commit finalization output split by durability target.
|
|
273
583
|
///
|
|
274
|
-
/// `commit_rows` are
|
|
275
|
-
/// from
|
|
584
|
+
/// `commit_rows` are canonical commit-store facts. live_state later projects
|
|
585
|
+
/// commit SQL surfaces from commit_store; tracked_state roots do not store
|
|
586
|
+
/// commit graph facts.
|
|
276
587
|
///
|
|
277
|
-
/// `version_heads` are moving refs. They are written through `VersionContext
|
|
278
|
-
///
|
|
588
|
+
/// `version_heads` are moving refs. They are written through `VersionContext`,
|
|
589
|
+
/// not the canonical commit store.
|
|
279
590
|
struct FinalizedCommitRows {
|
|
280
|
-
commit_rows: Vec<
|
|
591
|
+
commit_rows: Vec<FinalizedCommitRow>,
|
|
281
592
|
version_heads: Vec<PendingVersionHead>,
|
|
593
|
+
tracked_roots: Vec<PendingTrackedRoot>,
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
struct FinalizedCommitRow {
|
|
597
|
+
commit_id: String,
|
|
598
|
+
parent_commit_ids: Vec<String>,
|
|
599
|
+
created_at: String,
|
|
600
|
+
change_id: String,
|
|
282
601
|
}
|
|
283
602
|
|
|
284
603
|
struct PendingVersionHead {
|
|
@@ -287,6 +606,11 @@ struct PendingVersionHead {
|
|
|
287
606
|
timestamp: String,
|
|
288
607
|
}
|
|
289
608
|
|
|
609
|
+
struct PendingTrackedRoot {
|
|
610
|
+
commit_id: String,
|
|
611
|
+
parent_commit_id: Option<String>,
|
|
612
|
+
}
|
|
613
|
+
|
|
290
614
|
async fn finalize_commit_rows(
|
|
291
615
|
commit_members_by_version: BTreeMap<String, StagedCommitMembers>,
|
|
292
616
|
extra_commit_parents_by_version: BTreeMap<String, Vec<String>>,
|
|
@@ -295,6 +619,7 @@ async fn finalize_commit_rows(
|
|
|
295
619
|
) -> Result<FinalizedCommitRows, LixError> {
|
|
296
620
|
let mut commit_rows = Vec::new();
|
|
297
621
|
let mut version_heads = Vec::new();
|
|
622
|
+
let mut tracked_roots = Vec::new();
|
|
298
623
|
|
|
299
624
|
for (version_id, members) in commit_members_by_version {
|
|
300
625
|
if members.is_empty() && !members.allow_empty {
|
|
@@ -303,9 +628,8 @@ async fn finalize_commit_rows(
|
|
|
303
628
|
|
|
304
629
|
let commit_id = members.commit_id;
|
|
305
630
|
let commit_change_id = members.commit_change_id;
|
|
306
|
-
let change_set_id = members.change_set_id;
|
|
307
631
|
let timestamp = members.created_at;
|
|
308
|
-
let
|
|
632
|
+
let _change_ids = members.change_ids;
|
|
309
633
|
let parent_commit_ids = version_ctx
|
|
310
634
|
.ref_reader(&mut *transaction)
|
|
311
635
|
.load_head_commit_id(&version_id)
|
|
@@ -319,46 +643,29 @@ async fn finalize_commit_rows(
|
|
|
319
643
|
.cloned()
|
|
320
644
|
.unwrap_or_default(),
|
|
321
645
|
);
|
|
322
|
-
let
|
|
323
|
-
"id": commit_id,
|
|
324
|
-
"change_set_id": change_set_id,
|
|
325
|
-
"change_ids": change_ids,
|
|
326
|
-
"author_account_ids": [],
|
|
327
|
-
"parent_commit_ids": parent_commit_ids,
|
|
328
|
-
}))
|
|
329
|
-
.map_err(|error| {
|
|
330
|
-
LixError::new(
|
|
331
|
-
"LIX_ERROR_UNKNOWN",
|
|
332
|
-
format!("engine2 commit row snapshot serialization failed: {error}"),
|
|
333
|
-
)
|
|
334
|
-
})?;
|
|
646
|
+
let parent_commit_id = parent_commit_ids.first().cloned();
|
|
335
647
|
|
|
336
|
-
commit_rows.push(
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
file_id: None,
|
|
340
|
-
snapshot_content: Some(snapshot_content),
|
|
341
|
-
metadata: None,
|
|
342
|
-
origin: None,
|
|
343
|
-
schema_version: "1".to_string(),
|
|
648
|
+
commit_rows.push(FinalizedCommitRow {
|
|
649
|
+
commit_id: commit_id.clone(),
|
|
650
|
+
parent_commit_ids: parent_commit_ids.clone(),
|
|
344
651
|
created_at: timestamp.clone(),
|
|
345
|
-
|
|
346
|
-
global: true,
|
|
347
|
-
change_id: Some(commit_change_id),
|
|
348
|
-
commit_id: Some(commit_id.clone()),
|
|
349
|
-
untracked: false,
|
|
350
|
-
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
652
|
+
change_id: commit_change_id,
|
|
351
653
|
});
|
|
352
654
|
version_heads.push(PendingVersionHead {
|
|
353
|
-
version_id,
|
|
354
|
-
commit_id,
|
|
655
|
+
version_id: version_id.clone(),
|
|
656
|
+
commit_id: commit_id.clone(),
|
|
355
657
|
timestamp,
|
|
356
658
|
});
|
|
659
|
+
tracked_roots.push(PendingTrackedRoot {
|
|
660
|
+
commit_id,
|
|
661
|
+
parent_commit_id,
|
|
662
|
+
});
|
|
357
663
|
}
|
|
358
664
|
|
|
359
665
|
Ok(FinalizedCommitRows {
|
|
360
666
|
commit_rows,
|
|
361
667
|
version_heads,
|
|
668
|
+
tracked_roots,
|
|
362
669
|
})
|
|
363
670
|
}
|
|
364
671
|
|
|
@@ -379,9 +686,6 @@ mod tests {
|
|
|
379
686
|
Arc,
|
|
380
687
|
};
|
|
381
688
|
|
|
382
|
-
use async_trait::async_trait;
|
|
383
|
-
use serde_json::Value as JsonValue;
|
|
384
|
-
|
|
385
689
|
use super::*;
|
|
386
690
|
use crate::backend::{
|
|
387
691
|
testing::UnitTestBackend, Backend, BackendKvEntryPage, BackendKvExistsBatch,
|
|
@@ -389,15 +693,18 @@ mod tests {
|
|
|
389
693
|
BackendKvValuePage, BackendKvWriteBatch, BackendKvWriteStats, BackendReadTransaction,
|
|
390
694
|
BackendWriteTransaction,
|
|
391
695
|
};
|
|
392
|
-
use crate::
|
|
696
|
+
use crate::catalog::SchemaPlanId;
|
|
697
|
+
use crate::commit_store::{ChangeIndexEntry, ChangeLocator};
|
|
393
698
|
use crate::live_state::{LiveStateContext, LiveStateRowRequest};
|
|
394
699
|
use crate::storage::StorageContext;
|
|
700
|
+
use crate::transaction::types::PreparedRowFacts;
|
|
395
701
|
use crate::untracked_state::{
|
|
396
|
-
|
|
397
|
-
UntrackedStateRowRequest,
|
|
702
|
+
MaterializedUntrackedStateRow, UntrackedStateContext, UntrackedStateRowRequest,
|
|
398
703
|
};
|
|
399
704
|
use crate::version::VersionContext;
|
|
400
705
|
use crate::NullableKeyFilter;
|
|
706
|
+
use crate::GLOBAL_VERSION_ID;
|
|
707
|
+
use async_trait::async_trait;
|
|
401
708
|
|
|
402
709
|
const DETERMINISTIC_MODE_KEY: &str = "lix_deterministic_mode";
|
|
403
710
|
const DETERMINISTIC_SEQUENCE_KEY: &str = "lix_deterministic_sequence_number";
|
|
@@ -406,33 +713,31 @@ mod tests {
|
|
|
406
713
|
LiveStateContext::new(
|
|
407
714
|
crate::tracked_state::TrackedStateContext::new(),
|
|
408
715
|
crate::untracked_state::UntrackedStateContext::new(),
|
|
409
|
-
crate::commit_graph::CommitGraphContext::new(
|
|
716
|
+
crate::commit_graph::CommitGraphContext::new(),
|
|
410
717
|
)
|
|
411
718
|
}
|
|
412
719
|
|
|
413
720
|
#[tokio::test]
|
|
414
|
-
async fn
|
|
721
|
+
async fn commit_staged_writes_appends_commit_store_and_updates_serving_projection() {
|
|
415
722
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
416
723
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
417
724
|
let binary_cas = BinaryCasContext::new();
|
|
418
|
-
let changelog = ChangelogContext::new();
|
|
419
|
-
let live_state = Arc::new(live_state_context());
|
|
420
725
|
let version_ctx = VersionContext::new(Arc::new(UntrackedStateContext::new()));
|
|
421
726
|
let mut transaction = storage
|
|
422
727
|
.begin_write_transaction()
|
|
423
728
|
.await
|
|
424
729
|
.expect("transaction should open");
|
|
425
730
|
|
|
426
|
-
|
|
731
|
+
let state_rows = vec![tracked_global_row("change-1")];
|
|
732
|
+
commit_prepared_writes(
|
|
427
733
|
&binary_cas,
|
|
428
|
-
&
|
|
429
|
-
live_state.as_ref(),
|
|
734
|
+
&crate::commit_store::CommitStoreContext::new(),
|
|
430
735
|
&version_ctx,
|
|
431
736
|
None,
|
|
432
737
|
transaction.as_mut(),
|
|
433
|
-
|
|
738
|
+
PreparedWriteSet {
|
|
434
739
|
insert_identities: BTreeMap::new(),
|
|
435
|
-
state_rows
|
|
740
|
+
state_rows,
|
|
436
741
|
adopted_rows: Vec::new(),
|
|
437
742
|
commit_members_by_version: BTreeMap::from([(
|
|
438
743
|
GLOBAL_VERSION_ID.to_string(),
|
|
@@ -449,24 +754,44 @@ mod tests {
|
|
|
449
754
|
.await
|
|
450
755
|
.expect("commit should persist kv");
|
|
451
756
|
|
|
452
|
-
let
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
.
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
757
|
+
let commit_reader = crate::commit_store::CommitStoreContext::new().reader(storage.clone());
|
|
758
|
+
let commit = commit_reader
|
|
759
|
+
.load_commit("test-uuid-1")
|
|
760
|
+
.await
|
|
761
|
+
.expect("commit-store commit should load")
|
|
762
|
+
.expect("commit-store commit should exist");
|
|
763
|
+
assert_eq!(commit.change_id, "test-uuid-2");
|
|
764
|
+
assert_eq!(commit.change_pack_count, 1);
|
|
765
|
+
assert_eq!(commit.membership_pack_count, 0);
|
|
766
|
+
let index_entries = commit_reader
|
|
767
|
+
.load_change_index_entries(&["change-1".to_string(), "test-uuid-2".to_string()])
|
|
768
|
+
.await
|
|
769
|
+
.expect("commit-store change index should load");
|
|
770
|
+
assert_eq!(
|
|
771
|
+
index_entries,
|
|
772
|
+
vec![
|
|
773
|
+
Some(ChangeIndexEntry::PackedChange {
|
|
774
|
+
locator: ChangeLocator {
|
|
775
|
+
source_commit_id: "test-uuid-1".to_string(),
|
|
776
|
+
source_pack_id: 0,
|
|
777
|
+
source_ordinal: 0,
|
|
778
|
+
change_id: "change-1".to_string(),
|
|
779
|
+
},
|
|
780
|
+
}),
|
|
781
|
+
Some(ChangeIndexEntry::CommitHeader {
|
|
782
|
+
commit_id: "test-uuid-1".to_string(),
|
|
783
|
+
change_id: "test-uuid-2".to_string(),
|
|
784
|
+
}),
|
|
785
|
+
]
|
|
786
|
+
);
|
|
787
|
+
let change_pack = commit_reader
|
|
788
|
+
.load_change_pack("test-uuid-1", 0)
|
|
789
|
+
.await
|
|
790
|
+
.expect("commit-store change pack should load")
|
|
791
|
+
.expect("commit-store change pack should exist");
|
|
792
|
+
assert_eq!(change_pack.len(), 1);
|
|
793
|
+
assert_eq!(change_pack[0].id, "change-1");
|
|
794
|
+
assert_eq!(change_pack[0].schema_key, "test_schema");
|
|
470
795
|
|
|
471
796
|
let loaded_head = version_ctx
|
|
472
797
|
.ref_reader(storage.clone())
|
|
@@ -481,8 +806,6 @@ mod tests {
|
|
|
481
806
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
482
807
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
483
808
|
let binary_cas = BinaryCasContext::new();
|
|
484
|
-
let changelog = ChangelogContext::new();
|
|
485
|
-
let live_state = Arc::new(live_state_context());
|
|
486
809
|
let version_ctx = VersionContext::new(Arc::new(UntrackedStateContext::new()));
|
|
487
810
|
let untracked_state = UntrackedStateContext::new();
|
|
488
811
|
let mut transaction = storage
|
|
@@ -490,16 +813,16 @@ mod tests {
|
|
|
490
813
|
.await
|
|
491
814
|
.expect("transaction should open");
|
|
492
815
|
|
|
493
|
-
|
|
816
|
+
let state_rows = vec![untracked_global_row("change-untracked")];
|
|
817
|
+
commit_prepared_writes(
|
|
494
818
|
&binary_cas,
|
|
495
|
-
&
|
|
496
|
-
live_state.as_ref(),
|
|
819
|
+
&crate::commit_store::CommitStoreContext::new(),
|
|
497
820
|
&version_ctx,
|
|
498
821
|
None,
|
|
499
822
|
transaction.as_mut(),
|
|
500
|
-
|
|
823
|
+
PreparedWriteSet {
|
|
501
824
|
insert_identities: BTreeMap::new(),
|
|
502
|
-
state_rows
|
|
825
|
+
state_rows,
|
|
503
826
|
adopted_rows: Vec::new(),
|
|
504
827
|
commit_members_by_version: BTreeMap::new(),
|
|
505
828
|
extra_commit_parents_by_version: BTreeMap::new(),
|
|
@@ -513,14 +836,12 @@ mod tests {
|
|
|
513
836
|
.await
|
|
514
837
|
.expect("commit should persist kv");
|
|
515
838
|
|
|
516
|
-
let
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
.expect("changelog scan should succeed");
|
|
523
|
-
assert!(changes.is_empty());
|
|
839
|
+
let commit_reader = crate::commit_store::CommitStoreContext::new().reader(storage.clone());
|
|
840
|
+
let index_entries = commit_reader
|
|
841
|
+
.load_change_index_entries(&["change-untracked".to_string()])
|
|
842
|
+
.await
|
|
843
|
+
.expect("commit-store change index should load");
|
|
844
|
+
assert_eq!(index_entries, vec![None]);
|
|
524
845
|
|
|
525
846
|
let loaded = {
|
|
526
847
|
let mut untracked_reader = untracked_state.reader(storage.clone());
|
|
@@ -546,7 +867,6 @@ mod tests {
|
|
|
546
867
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
547
868
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
548
869
|
let binary_cas = BinaryCasContext::new();
|
|
549
|
-
let changelog = ChangelogContext::new();
|
|
550
870
|
let untracked_state = UntrackedStateContext::new();
|
|
551
871
|
let live_state = Arc::new(live_state_context());
|
|
552
872
|
let version_ctx = VersionContext::new(Arc::new(UntrackedStateContext::new()));
|
|
@@ -556,18 +876,15 @@ mod tests {
|
|
|
556
876
|
.await
|
|
557
877
|
.expect("seed transaction should open");
|
|
558
878
|
let mut writes = StorageWriteSet::new();
|
|
559
|
-
let
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
)
|
|
566
|
-
.expect("untracked seed should canonicalize")
|
|
567
|
-
};
|
|
879
|
+
let staged_row = untracked_global_row("change-untracked");
|
|
880
|
+
let canonical_row = crate::test_support::untracked_state_row_from_materialized(
|
|
881
|
+
&mut writes,
|
|
882
|
+
&MaterializedUntrackedStateRow::from(staged_row),
|
|
883
|
+
)
|
|
884
|
+
.expect("untracked seed should canonicalize");
|
|
568
885
|
untracked_state
|
|
569
886
|
.writer(&mut writes)
|
|
570
|
-
.stage_rows(
|
|
887
|
+
.stage_rows(std::iter::once(canonical_row.as_ref()))
|
|
571
888
|
.expect("untracked seed should write");
|
|
572
889
|
writes
|
|
573
890
|
.apply(&mut seed_transaction.as_mut())
|
|
@@ -582,16 +899,16 @@ mod tests {
|
|
|
582
899
|
.begin_write_transaction()
|
|
583
900
|
.await
|
|
584
901
|
.expect("transaction should open");
|
|
585
|
-
|
|
902
|
+
let state_rows = vec![tracked_global_row("change-tracked")];
|
|
903
|
+
commit_prepared_writes(
|
|
586
904
|
&binary_cas,
|
|
587
|
-
&
|
|
588
|
-
live_state.as_ref(),
|
|
905
|
+
&crate::commit_store::CommitStoreContext::new(),
|
|
589
906
|
&version_ctx,
|
|
590
907
|
None,
|
|
591
908
|
transaction.as_mut(),
|
|
592
|
-
|
|
909
|
+
PreparedWriteSet {
|
|
593
910
|
insert_identities: BTreeMap::new(),
|
|
594
|
-
state_rows
|
|
911
|
+
state_rows,
|
|
595
912
|
adopted_rows: Vec::new(),
|
|
596
913
|
commit_members_by_version: BTreeMap::from([(
|
|
597
914
|
GLOBAL_VERSION_ID.to_string(),
|
|
@@ -633,7 +950,6 @@ mod tests {
|
|
|
633
950
|
let backend: Arc<dyn Backend + Send + Sync> = counting_backend;
|
|
634
951
|
let storage = StorageContext::new(backend);
|
|
635
952
|
let binary_cas = BinaryCasContext::new();
|
|
636
|
-
let changelog = ChangelogContext::new();
|
|
637
953
|
let live_state = Arc::new(live_state_context());
|
|
638
954
|
let untracked_state = UntrackedStateContext::new();
|
|
639
955
|
let version_ctx = VersionContext::new(Arc::new(UntrackedStateContext::new()));
|
|
@@ -644,39 +960,34 @@ mod tests {
|
|
|
644
960
|
.await
|
|
645
961
|
.expect("seed transaction should open");
|
|
646
962
|
let mut writes = StorageWriteSet::new();
|
|
647
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
648
963
|
let mode_snapshot = serde_json::to_string(&serde_json::json!({
|
|
649
964
|
"key": DETERMINISTIC_MODE_KEY,
|
|
650
965
|
"value": { "enabled": true },
|
|
651
966
|
}))
|
|
652
967
|
.expect("mode snapshot should serialize");
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
)
|
|
677
|
-
.await
|
|
678
|
-
.expect("deterministic mode should stage");
|
|
679
|
-
}
|
|
968
|
+
JsonStoreContext::new()
|
|
969
|
+
.writer()
|
|
970
|
+
.stage_batch(
|
|
971
|
+
&mut writes,
|
|
972
|
+
JsonWritePlacementRef::OutOfBand,
|
|
973
|
+
[NormalizedJsonRef::new(mode_snapshot.as_str())],
|
|
974
|
+
)
|
|
975
|
+
.expect("deterministic mode snapshot should stage");
|
|
976
|
+
let row = crate::untracked_state::UntrackedStateRow {
|
|
977
|
+
entity_id: crate::entity_identity::EntityIdentity::single(DETERMINISTIC_MODE_KEY),
|
|
978
|
+
schema_key: "lix_key_value".to_string(),
|
|
979
|
+
file_id: None,
|
|
980
|
+
snapshot_content: Some(mode_snapshot.to_string()),
|
|
981
|
+
metadata: None,
|
|
982
|
+
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
983
|
+
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
984
|
+
global: true,
|
|
985
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
986
|
+
};
|
|
987
|
+
UntrackedStateContext::new()
|
|
988
|
+
.writer(&mut writes)
|
|
989
|
+
.stage_rows(std::iter::once(row.as_ref()))
|
|
990
|
+
.expect("deterministic mode should stage");
|
|
680
991
|
writes
|
|
681
992
|
.apply(&mut seed_transaction.as_mut())
|
|
682
993
|
.await
|
|
@@ -699,19 +1010,19 @@ mod tests {
|
|
|
699
1010
|
.await
|
|
700
1011
|
.expect("transaction should open");
|
|
701
1012
|
|
|
1013
|
+
let tracked_row = tracked_global_row("change-tracked");
|
|
702
1014
|
let mut untracked_row = untracked_global_row("change-untracked");
|
|
703
1015
|
untracked_row.entity_id = crate::entity_identity::EntityIdentity::single("entity-2");
|
|
704
1016
|
|
|
705
|
-
|
|
1017
|
+
commit_prepared_writes(
|
|
706
1018
|
&binary_cas,
|
|
707
|
-
&
|
|
708
|
-
live_state.as_ref(),
|
|
1019
|
+
&crate::commit_store::CommitStoreContext::new(),
|
|
709
1020
|
&version_ctx,
|
|
710
1021
|
Some(&runtime_functions),
|
|
711
1022
|
transaction.as_mut(),
|
|
712
|
-
|
|
1023
|
+
PreparedWriteSet {
|
|
713
1024
|
insert_identities: BTreeMap::new(),
|
|
714
|
-
state_rows: vec![
|
|
1025
|
+
state_rows: vec![tracked_row, untracked_row],
|
|
715
1026
|
adopted_rows: Vec::new(),
|
|
716
1027
|
commit_members_by_version: BTreeMap::from([(
|
|
717
1028
|
GLOBAL_VERSION_ID.to_string(),
|
|
@@ -727,7 +1038,7 @@ mod tests {
|
|
|
727
1038
|
assert_eq!(
|
|
728
1039
|
write_batches.load(Ordering::SeqCst),
|
|
729
1040
|
1,
|
|
730
|
-
"tracked, json, untracked,
|
|
1041
|
+
"tracked, json, untracked, commit-store, and version refs must apply as one backend write batch"
|
|
731
1042
|
);
|
|
732
1043
|
|
|
733
1044
|
transaction
|
|
@@ -736,15 +1047,21 @@ mod tests {
|
|
|
736
1047
|
.expect("commit should persist kv");
|
|
737
1048
|
assert_eq!(write_batches.load(Ordering::SeqCst), 1);
|
|
738
1049
|
|
|
739
|
-
let
|
|
740
|
-
|
|
741
|
-
.
|
|
1050
|
+
let commit_reader = crate::commit_store::CommitStoreContext::new().reader(storage.clone());
|
|
1051
|
+
let commit = commit_reader
|
|
1052
|
+
.load_commit("test-uuid-1")
|
|
742
1053
|
.await
|
|
743
|
-
.expect("
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
.
|
|
1054
|
+
.expect("commit-store commit should load")
|
|
1055
|
+
.expect("commit-store commit should exist");
|
|
1056
|
+
assert_eq!(commit.change_id, "test-uuid-2");
|
|
1057
|
+
let index_entries = commit_reader
|
|
1058
|
+
.load_change_index_entries(&["change-tracked".to_string()])
|
|
1059
|
+
.await
|
|
1060
|
+
.expect("commit-store change index should load");
|
|
1061
|
+
assert!(matches!(
|
|
1062
|
+
index_entries.as_slice(),
|
|
1063
|
+
[Some(ChangeIndexEntry::PackedChange { .. })]
|
|
1064
|
+
));
|
|
748
1065
|
|
|
749
1066
|
let loaded_head = version_ctx
|
|
750
1067
|
.ref_reader(storage.clone())
|
|
@@ -795,8 +1112,6 @@ mod tests {
|
|
|
795
1112
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
796
1113
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
797
1114
|
let binary_cas = BinaryCasContext::new();
|
|
798
|
-
let changelog = ChangelogContext::new();
|
|
799
|
-
let live_state = Arc::new(live_state_context());
|
|
800
1115
|
let version_ctx = VersionContext::new(Arc::new(UntrackedStateContext::new()));
|
|
801
1116
|
crate::test_support::seed_version_head(storage.clone(), GLOBAL_VERSION_ID, "global-before")
|
|
802
1117
|
.await;
|
|
@@ -807,16 +1122,16 @@ mod tests {
|
|
|
807
1122
|
.begin_write_transaction()
|
|
808
1123
|
.await
|
|
809
1124
|
.expect("transaction should open");
|
|
810
|
-
|
|
1125
|
+
let state_rows = vec![tracked_version_row("version-a", "change-version-a")];
|
|
1126
|
+
commit_prepared_writes(
|
|
811
1127
|
&binary_cas,
|
|
812
|
-
&
|
|
813
|
-
live_state.as_ref(),
|
|
1128
|
+
&crate::commit_store::CommitStoreContext::new(),
|
|
814
1129
|
&version_ctx,
|
|
815
1130
|
None,
|
|
816
1131
|
transaction.as_mut(),
|
|
817
|
-
|
|
1132
|
+
PreparedWriteSet {
|
|
818
1133
|
insert_identities: BTreeMap::new(),
|
|
819
|
-
state_rows
|
|
1134
|
+
state_rows,
|
|
820
1135
|
adopted_rows: Vec::new(),
|
|
821
1136
|
commit_members_by_version: BTreeMap::from([(
|
|
822
1137
|
"version-a".to_string(),
|
|
@@ -833,31 +1148,22 @@ mod tests {
|
|
|
833
1148
|
.await
|
|
834
1149
|
.expect("commit should persist kv");
|
|
835
1150
|
|
|
836
|
-
let
|
|
837
|
-
|
|
838
|
-
.
|
|
1151
|
+
let commit_reader = crate::commit_store::CommitStoreContext::new().reader(storage.clone());
|
|
1152
|
+
let commit = commit_reader
|
|
1153
|
+
.load_commit("test-uuid-1")
|
|
839
1154
|
.await
|
|
840
|
-
.expect("
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
.entity_id
|
|
853
|
-
.as_string()
|
|
854
|
-
.expect("commit entity id should project"),
|
|
855
|
-
"test-uuid-1"
|
|
856
|
-
);
|
|
857
|
-
assert!(changes.iter().any(|change| change.id == "change-version-a"));
|
|
858
|
-
assert!(!changes
|
|
859
|
-
.iter()
|
|
860
|
-
.any(|change| change.schema_key == "lix_version_ref"));
|
|
1155
|
+
.expect("commit-store commit should load")
|
|
1156
|
+
.expect("commit-store commit should exist");
|
|
1157
|
+
assert_eq!(commit.change_id, "test-uuid-2");
|
|
1158
|
+
assert_eq!(commit.parent_ids, vec!["version-a-before"]);
|
|
1159
|
+
let index_entries = commit_reader
|
|
1160
|
+
.load_change_index_entries(&["change-version-a".to_string()])
|
|
1161
|
+
.await
|
|
1162
|
+
.expect("commit-store change index should load");
|
|
1163
|
+
assert!(matches!(
|
|
1164
|
+
index_entries.as_slice(),
|
|
1165
|
+
[Some(ChangeIndexEntry::PackedChange { .. })]
|
|
1166
|
+
));
|
|
861
1167
|
|
|
862
1168
|
let global_head = version_ctx
|
|
863
1169
|
.ref_reader(storage.clone())
|
|
@@ -904,47 +1210,10 @@ mod tests {
|
|
|
904
1210
|
assert_eq!(rows.commit_rows.len(), 1);
|
|
905
1211
|
assert_eq!(rows.version_heads.len(), 1);
|
|
906
1212
|
let row = &rows.commit_rows[0];
|
|
907
|
-
assert_eq!(row.
|
|
908
|
-
assert_eq!(row.
|
|
909
|
-
assert_eq!(row.schema_version, "1");
|
|
910
|
-
assert_eq!(row.change_id.as_deref(), Some("test-uuid-2"));
|
|
911
|
-
assert_eq!(row.commit_id.as_deref(), Some("test-uuid-1"));
|
|
912
|
-
assert!(row.global);
|
|
913
|
-
assert!(!row.untracked);
|
|
914
|
-
assert_eq!(row.version_id, GLOBAL_VERSION_ID);
|
|
1213
|
+
assert_eq!(row.commit_id, "test-uuid-1");
|
|
1214
|
+
assert_eq!(row.change_id, "test-uuid-2");
|
|
915
1215
|
assert_eq!(row.created_at, "test-timestamp-1");
|
|
916
|
-
assert_eq!(row.
|
|
917
|
-
|
|
918
|
-
let snapshot = serde_json::from_str::<JsonValue>(
|
|
919
|
-
row.snapshot_content
|
|
920
|
-
.as_deref()
|
|
921
|
-
.expect("commit row should have snapshot"),
|
|
922
|
-
)
|
|
923
|
-
.expect("commit snapshot should be JSON");
|
|
924
|
-
assert_eq!(
|
|
925
|
-
snapshot.get("id").and_then(JsonValue::as_str),
|
|
926
|
-
Some("test-uuid-1")
|
|
927
|
-
);
|
|
928
|
-
assert_eq!(
|
|
929
|
-
snapshot
|
|
930
|
-
.get("change_ids")
|
|
931
|
-
.and_then(JsonValue::as_array)
|
|
932
|
-
.expect("change_ids should be array")
|
|
933
|
-
.iter()
|
|
934
|
-
.map(|value| value.as_str().expect("change id should be string"))
|
|
935
|
-
.collect::<Vec<_>>(),
|
|
936
|
-
vec!["change-a", "change-b"]
|
|
937
|
-
);
|
|
938
|
-
assert_eq!(
|
|
939
|
-
snapshot
|
|
940
|
-
.get("parent_commit_ids")
|
|
941
|
-
.and_then(JsonValue::as_array)
|
|
942
|
-
.expect("parent_commit_ids should be array")
|
|
943
|
-
.iter()
|
|
944
|
-
.map(|value| value.as_str().expect("parent id should be string"))
|
|
945
|
-
.collect::<Vec<_>>(),
|
|
946
|
-
vec!["initial-commit"]
|
|
947
|
-
);
|
|
1216
|
+
assert_eq!(row.parent_commit_ids, vec!["initial-commit"]);
|
|
948
1217
|
|
|
949
1218
|
let version_head = &rows.version_heads[0];
|
|
950
1219
|
assert_eq!(version_head.version_id, GLOBAL_VERSION_ID);
|
|
@@ -999,21 +1268,8 @@ mod tests {
|
|
|
999
1268
|
.await
|
|
1000
1269
|
.expect("active-version commit finalization should resolve parent");
|
|
1001
1270
|
|
|
1002
|
-
let snapshot = serde_json::from_str::<JsonValue>(
|
|
1003
|
-
rows.commit_rows[0]
|
|
1004
|
-
.snapshot_content
|
|
1005
|
-
.as_deref()
|
|
1006
|
-
.expect("commit row should have snapshot"),
|
|
1007
|
-
)
|
|
1008
|
-
.expect("commit snapshot should be JSON");
|
|
1009
1271
|
assert_eq!(
|
|
1010
|
-
|
|
1011
|
-
.get("parent_commit_ids")
|
|
1012
|
-
.and_then(JsonValue::as_array)
|
|
1013
|
-
.expect("parent_commit_ids should be array")
|
|
1014
|
-
.iter()
|
|
1015
|
-
.map(|value| value.as_str().expect("parent id should be text"))
|
|
1016
|
-
.collect::<Vec<_>>(),
|
|
1272
|
+
rows.commit_rows[0].parent_commit_ids,
|
|
1017
1273
|
vec!["previous-commit"]
|
|
1018
1274
|
);
|
|
1019
1275
|
assert_eq!(rows.version_heads[0].version_id, "version-a");
|
|
@@ -1039,21 +1295,8 @@ mod tests {
|
|
|
1039
1295
|
.await
|
|
1040
1296
|
.expect("merge commit finalization should resolve parents");
|
|
1041
1297
|
|
|
1042
|
-
let snapshot = serde_json::from_str::<JsonValue>(
|
|
1043
|
-
rows.commit_rows[0]
|
|
1044
|
-
.snapshot_content
|
|
1045
|
-
.as_deref()
|
|
1046
|
-
.expect("commit row should have snapshot"),
|
|
1047
|
-
)
|
|
1048
|
-
.expect("commit snapshot should be JSON");
|
|
1049
1298
|
assert_eq!(
|
|
1050
|
-
|
|
1051
|
-
.get("parent_commit_ids")
|
|
1052
|
-
.and_then(JsonValue::as_array)
|
|
1053
|
-
.expect("parent_commit_ids should be array")
|
|
1054
|
-
.iter()
|
|
1055
|
-
.map(|value| value.as_str().expect("parent id should be text"))
|
|
1056
|
-
.collect::<Vec<_>>(),
|
|
1299
|
+
rows.commit_rows[0].parent_commit_ids,
|
|
1057
1300
|
vec!["target-head", "source-head"]
|
|
1058
1301
|
);
|
|
1059
1302
|
}
|
|
@@ -1062,7 +1305,6 @@ mod tests {
|
|
|
1062
1305
|
let mut members = StagedCommitMembers::new(
|
|
1063
1306
|
"test-uuid-1".to_string(),
|
|
1064
1307
|
"test-uuid-2".to_string(),
|
|
1065
|
-
"test-uuid-3".to_string(),
|
|
1066
1308
|
"test-timestamp-1".to_string(),
|
|
1067
1309
|
);
|
|
1068
1310
|
for change_id in change_ids {
|
|
@@ -1071,19 +1313,28 @@ mod tests {
|
|
|
1071
1313
|
members
|
|
1072
1314
|
}
|
|
1073
1315
|
|
|
1074
|
-
fn tracked_global_row(change_id: &str) ->
|
|
1316
|
+
fn tracked_global_row(change_id: &str) -> PreparedStateRow {
|
|
1075
1317
|
tracked_version_row(GLOBAL_VERSION_ID, change_id)
|
|
1076
1318
|
}
|
|
1077
1319
|
|
|
1078
|
-
fn tracked_version_row(version_id: &str, change_id: &str) ->
|
|
1079
|
-
|
|
1320
|
+
fn tracked_version_row(version_id: &str, change_id: &str) -> PreparedStateRow {
|
|
1321
|
+
PreparedStateRow {
|
|
1322
|
+
schema_plan_id: SchemaPlanId::for_test(0),
|
|
1323
|
+
facts: PreparedRowFacts::default(),
|
|
1080
1324
|
entity_id: crate::entity_identity::EntityIdentity::single("entity-1"),
|
|
1081
1325
|
schema_key: "test_schema".to_string(),
|
|
1082
1326
|
file_id: None,
|
|
1083
|
-
|
|
1327
|
+
snapshot: Some(
|
|
1328
|
+
crate::transaction::types::stage_json_from_value(
|
|
1329
|
+
crate::transaction::types::TransactionJson::from_value_for_test(
|
|
1330
|
+
serde_json::json!({ "value": 1 }),
|
|
1331
|
+
),
|
|
1332
|
+
"test tracked row snapshot",
|
|
1333
|
+
)
|
|
1334
|
+
.expect("test snapshot should stage"),
|
|
1335
|
+
),
|
|
1084
1336
|
metadata: None,
|
|
1085
1337
|
origin: None,
|
|
1086
|
-
schema_version: "1".to_string(),
|
|
1087
1338
|
created_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1088
1339
|
updated_at: "2026-01-01T00:00:00Z".to_string(),
|
|
1089
1340
|
global: version_id == GLOBAL_VERSION_ID,
|
|
@@ -1094,13 +1345,22 @@ mod tests {
|
|
|
1094
1345
|
}
|
|
1095
1346
|
}
|
|
1096
1347
|
|
|
1097
|
-
fn untracked_global_row(change_id: &str) ->
|
|
1098
|
-
|
|
1099
|
-
|
|
1348
|
+
fn untracked_global_row(change_id: &str) -> PreparedStateRow {
|
|
1349
|
+
let mut row = tracked_global_row(change_id);
|
|
1350
|
+
row.snapshot = Some(
|
|
1351
|
+
crate::transaction::types::stage_json_from_value(
|
|
1352
|
+
crate::transaction::types::TransactionJson::from_value_for_test(
|
|
1353
|
+
serde_json::json!({ "value": "untracked" }),
|
|
1354
|
+
),
|
|
1355
|
+
"test untracked row snapshot",
|
|
1356
|
+
)
|
|
1357
|
+
.expect("test snapshot should stage"),
|
|
1358
|
+
);
|
|
1359
|
+
PreparedStateRow {
|
|
1100
1360
|
change_id: None,
|
|
1101
1361
|
commit_id: None,
|
|
1102
1362
|
untracked: true,
|
|
1103
|
-
..
|
|
1363
|
+
..row
|
|
1104
1364
|
}
|
|
1105
1365
|
}
|
|
1106
1366
|
|