@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,60 +1,303 @@
|
|
|
1
|
-
use std::collections::{BTreeMap, BTreeSet};
|
|
2
|
-
use std::sync::Mutex;
|
|
1
|
+
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
|
2
|
+
use std::sync::{Arc, Mutex};
|
|
3
3
|
|
|
4
|
+
use crate::catalog::SchemaPlanId;
|
|
5
|
+
use crate::domain::{Domain, DomainRowIdentity};
|
|
6
|
+
use crate::entity_identity::EntityIdentity;
|
|
4
7
|
use crate::functions::{FunctionProvider, FunctionProviderHandle};
|
|
5
8
|
#[cfg(test)]
|
|
6
9
|
use crate::live_state::LiveStateRowRequest;
|
|
7
|
-
use crate::live_state::{
|
|
10
|
+
use crate::live_state::{LiveStateScanRequest, MaterializedLiveStateRow};
|
|
11
|
+
#[cfg(test)]
|
|
12
|
+
use crate::transaction::types::{stage_json_from_value, TransactionJson};
|
|
8
13
|
use crate::transaction::types::{
|
|
9
|
-
LogicalPrimaryKey,
|
|
10
|
-
|
|
14
|
+
LogicalPrimaryKey, PreparedTransactionWrite, TransactionFileData, TransactionWriteMode,
|
|
15
|
+
TransactionWriteOperation, TransactionWriteOrigin, TransactionWriteOutcome,
|
|
11
16
|
};
|
|
12
|
-
use crate::transaction::types::{
|
|
17
|
+
use crate::transaction::types::{PreparedAdoptedStateRow, PreparedStateRow, StagedCommitMembers};
|
|
13
18
|
use crate::GLOBAL_VERSION_ID;
|
|
14
19
|
use crate::{LixError, NullableKeyFilter};
|
|
15
20
|
|
|
16
|
-
/// Transaction-local
|
|
21
|
+
/// Transaction-local write buffer after transaction-boundary preparation.
|
|
17
22
|
///
|
|
18
|
-
/// This is the
|
|
19
|
-
/// write frontends
|
|
20
|
-
/// stable `
|
|
21
|
-
/// and commit
|
|
22
|
-
pub(crate) struct
|
|
23
|
+
/// This is the engine seam between SQL execution and transaction ownership:
|
|
24
|
+
/// write frontends pass decoded `TransactionWriteRow`s to `Transaction`, the
|
|
25
|
+
/// transaction prepares them into stable `PreparedStateRow`s, reads build a
|
|
26
|
+
/// `PreparedStateRowOverlay` from those rows, and commit drains the same rows.
|
|
27
|
+
pub(crate) struct TransactionWriteBuffer {
|
|
23
28
|
functions: FunctionProviderHandle,
|
|
24
|
-
rows: Mutex<
|
|
25
|
-
adopted_rows: Mutex<
|
|
26
|
-
|
|
29
|
+
rows: Mutex<Vec<Option<PreparedStateRow>>>,
|
|
30
|
+
adopted_rows: Mutex<Vec<Option<PreparedAdoptedStateRow>>>,
|
|
31
|
+
by_identity: Mutex<HashMap<PreparedStateRowIdentity, RowSlot>>,
|
|
32
|
+
insert_identities: Mutex<BTreeMap<PreparedStateRowIdentity, Option<TransactionWriteOrigin>>>,
|
|
27
33
|
commit_members_by_version: Mutex<BTreeMap<String, StagedCommitMembers>>,
|
|
28
34
|
extra_commit_parents_by_version: Mutex<BTreeMap<String, Vec<String>>>,
|
|
29
|
-
file_data_writes: Mutex<Vec<
|
|
35
|
+
file_data_writes: Mutex<Vec<TransactionFileData>>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
39
|
+
pub(crate) enum RowSlot {
|
|
40
|
+
State(usize),
|
|
41
|
+
Adopted(usize),
|
|
30
42
|
}
|
|
31
43
|
|
|
32
|
-
/// Drained transaction
|
|
33
|
-
pub(crate) struct
|
|
34
|
-
pub(crate) state_rows: Vec<
|
|
35
|
-
pub(crate) adopted_rows: Vec<
|
|
36
|
-
pub(crate) insert_identities:
|
|
44
|
+
/// Drained prepared transaction writes ready for commit.
|
|
45
|
+
pub(crate) struct PreparedWriteSet {
|
|
46
|
+
pub(crate) state_rows: Vec<PreparedStateRow>,
|
|
47
|
+
pub(crate) adopted_rows: Vec<PreparedAdoptedStateRow>,
|
|
48
|
+
pub(crate) insert_identities:
|
|
49
|
+
BTreeMap<PreparedStateRowIdentity, Option<TransactionWriteOrigin>>,
|
|
37
50
|
pub(crate) commit_members_by_version: BTreeMap<String, StagedCommitMembers>,
|
|
38
51
|
pub(crate) extra_commit_parents_by_version: BTreeMap<String, Vec<String>>,
|
|
39
|
-
pub(crate) file_data_writes: Vec<
|
|
52
|
+
pub(crate) file_data_writes: Vec<TransactionFileData>,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
pub(crate) struct PreparedWriteValidationSet<'a> {
|
|
56
|
+
rows: Vec<PreparedValidationRow<'a>>,
|
|
57
|
+
constraint_rows: Vec<PreparedValidationRow<'a>>,
|
|
58
|
+
insert_identities: Vec<(
|
|
59
|
+
&'a PreparedStateRowIdentity,
|
|
60
|
+
Option<&'a TransactionWriteOrigin>,
|
|
61
|
+
)>,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
pub(crate) struct PreparedWriteValidationIndex<'a> {
|
|
65
|
+
rows_by_schema_scope: BTreeMap<Domain, Vec<PreparedValidationRow<'a>>>,
|
|
66
|
+
insert_identities_by_schema_scope: BTreeMap<
|
|
67
|
+
Domain,
|
|
68
|
+
Vec<(
|
|
69
|
+
&'a PreparedStateRowIdentity,
|
|
70
|
+
Option<&'a TransactionWriteOrigin>,
|
|
71
|
+
)>,
|
|
72
|
+
>,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[derive(Clone, Copy)]
|
|
76
|
+
pub(crate) enum PreparedValidationRow<'a> {
|
|
77
|
+
State(&'a PreparedStateRow),
|
|
78
|
+
Adopted(&'a PreparedAdoptedStateRow),
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl<'a> PreparedValidationRow<'a> {
|
|
82
|
+
pub(crate) fn entity_id(&self) -> &EntityIdentity {
|
|
83
|
+
match self {
|
|
84
|
+
Self::State(row) => &row.entity_id,
|
|
85
|
+
Self::Adopted(row) => &row.entity_id,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
pub(crate) fn schema_plan_id(&self) -> SchemaPlanId {
|
|
90
|
+
match self {
|
|
91
|
+
Self::State(row) => row.schema_plan_id,
|
|
92
|
+
Self::Adopted(row) => row.schema_plan_id,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
pub(crate) fn schema_key(&self) -> &str {
|
|
97
|
+
match self {
|
|
98
|
+
Self::State(row) => &row.schema_key,
|
|
99
|
+
Self::Adopted(row) => &row.schema_key,
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
pub(crate) fn file_id(&self) -> &Option<String> {
|
|
104
|
+
match self {
|
|
105
|
+
Self::State(row) => &row.file_id,
|
|
106
|
+
Self::Adopted(row) => &row.file_id,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#[cfg(test)]
|
|
111
|
+
pub(crate) fn snapshot_content(&self) -> Option<&str> {
|
|
112
|
+
match self {
|
|
113
|
+
Self::State(row) => row
|
|
114
|
+
.snapshot
|
|
115
|
+
.as_ref()
|
|
116
|
+
.map(|snapshot| snapshot.normalized.as_ref()),
|
|
117
|
+
Self::Adopted(row) => row
|
|
118
|
+
.snapshot
|
|
119
|
+
.as_ref()
|
|
120
|
+
.map(|snapshot| snapshot.normalized.as_ref()),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
pub(crate) fn snapshot_json(self) -> Option<&'a serde_json::Value> {
|
|
125
|
+
match self {
|
|
126
|
+
Self::State(row) => row
|
|
127
|
+
.snapshot
|
|
128
|
+
.as_ref()
|
|
129
|
+
.map(|snapshot| snapshot.value.as_ref()),
|
|
130
|
+
Self::Adopted(row) => row
|
|
131
|
+
.snapshot
|
|
132
|
+
.as_ref()
|
|
133
|
+
.map(|snapshot| snapshot.value.as_ref()),
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pub(crate) fn metadata_json(self) -> Option<&'a serde_json::Value> {
|
|
138
|
+
match self {
|
|
139
|
+
Self::State(row) => row
|
|
140
|
+
.metadata
|
|
141
|
+
.as_ref()
|
|
142
|
+
.map(|metadata| metadata.value.as_ref()),
|
|
143
|
+
Self::Adopted(row) => row
|
|
144
|
+
.metadata
|
|
145
|
+
.as_ref()
|
|
146
|
+
.map(|metadata| metadata.value.as_ref()),
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
pub(crate) fn untracked(&self) -> bool {
|
|
151
|
+
match self {
|
|
152
|
+
Self::State(row) => row.untracked,
|
|
153
|
+
Self::Adopted(_) => false,
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pub(crate) fn version_id(&self) -> &str {
|
|
158
|
+
match self {
|
|
159
|
+
Self::State(row) => &row.version_id,
|
|
160
|
+
Self::Adopted(row) => &row.version_id,
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pub(crate) fn domain(&self) -> Domain {
|
|
165
|
+
Domain::exact_file(
|
|
166
|
+
self.version_id().to_string(),
|
|
167
|
+
self.untracked(),
|
|
168
|
+
self.file_id().clone(),
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
pub(crate) fn domain_row_identity(&self) -> DomainRowIdentity {
|
|
173
|
+
DomainRowIdentity::in_domain(
|
|
174
|
+
self.domain(),
|
|
175
|
+
self.schema_key().to_string(),
|
|
176
|
+
self.entity_id().clone(),
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
impl<'a> PreparedWriteValidationIndex<'a> {
|
|
182
|
+
pub(crate) fn schema_scopes(&self) -> impl Iterator<Item = &Domain> {
|
|
183
|
+
self.rows_by_schema_scope.keys()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
pub(crate) fn validation_set_for_schema_scope(
|
|
187
|
+
&self,
|
|
188
|
+
schema_scope: &Domain,
|
|
189
|
+
) -> PreparedWriteValidationSet<'a> {
|
|
190
|
+
let constraint_rows = self
|
|
191
|
+
.rows_by_schema_scope
|
|
192
|
+
.iter()
|
|
193
|
+
.flat_map(|(target_scope, rows)| {
|
|
194
|
+
rows.iter().copied().filter(move |row| {
|
|
195
|
+
schema_scope.validation_scope_contains_constraint_domain(target_scope)
|
|
196
|
+
|| (row.snapshot_json().is_none()
|
|
197
|
+
&& target_scope.tombstone_domain_affects_validation_scope(schema_scope))
|
|
198
|
+
})
|
|
199
|
+
})
|
|
200
|
+
.collect();
|
|
201
|
+
PreparedWriteValidationSet {
|
|
202
|
+
rows: self
|
|
203
|
+
.rows_by_schema_scope
|
|
204
|
+
.get(schema_scope)
|
|
205
|
+
.cloned()
|
|
206
|
+
.unwrap_or_default(),
|
|
207
|
+
constraint_rows,
|
|
208
|
+
insert_identities: self
|
|
209
|
+
.insert_identities_by_schema_scope
|
|
210
|
+
.get(schema_scope)
|
|
211
|
+
.cloned()
|
|
212
|
+
.unwrap_or_default(),
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
impl<'a> PreparedWriteValidationSet<'a> {
|
|
218
|
+
pub(crate) fn rows(&self) -> impl Iterator<Item = PreparedValidationRow<'a>> + '_ {
|
|
219
|
+
self.rows.iter().copied()
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
pub(crate) fn constraint_rows(&self) -> impl Iterator<Item = PreparedValidationRow<'a>> + '_ {
|
|
223
|
+
self.constraint_rows.iter().copied()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
pub(crate) fn insert_identities(
|
|
227
|
+
&self,
|
|
228
|
+
) -> impl Iterator<Item = (&PreparedStateRowIdentity, Option<&TransactionWriteOrigin>)> {
|
|
229
|
+
self.insert_identities
|
|
230
|
+
.iter()
|
|
231
|
+
.map(|(identity, origin)| (*identity, *origin))
|
|
232
|
+
}
|
|
40
233
|
}
|
|
41
234
|
|
|
42
|
-
impl
|
|
43
|
-
|
|
235
|
+
impl PreparedWriteSet {
|
|
236
|
+
#[cfg(test)]
|
|
237
|
+
pub(crate) fn validation_rows(&self) -> impl Iterator<Item = PreparedValidationRow<'_>> + '_ {
|
|
44
238
|
self.state_rows
|
|
45
239
|
.iter()
|
|
46
|
-
.
|
|
47
|
-
.chain(self.adopted_rows.iter().map(
|
|
48
|
-
|
|
240
|
+
.map(PreparedValidationRow::State)
|
|
241
|
+
.chain(self.adopted_rows.iter().map(PreparedValidationRow::Adopted))
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
pub(crate) fn validation_index(&self) -> PreparedWriteValidationIndex<'_> {
|
|
245
|
+
let mut rows_by_schema_scope = BTreeMap::<Domain, Vec<PreparedValidationRow<'_>>>::new();
|
|
246
|
+
for row in &self.state_rows {
|
|
247
|
+
let row = PreparedValidationRow::State(row);
|
|
248
|
+
rows_by_schema_scope
|
|
249
|
+
.entry(row.domain().schema_catalog_domain())
|
|
250
|
+
.or_default()
|
|
251
|
+
.push(row);
|
|
252
|
+
}
|
|
253
|
+
for row in &self.adopted_rows {
|
|
254
|
+
let row = PreparedValidationRow::Adopted(row);
|
|
255
|
+
rows_by_schema_scope
|
|
256
|
+
.entry(row.domain().schema_catalog_domain())
|
|
257
|
+
.or_default()
|
|
258
|
+
.push(row);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let mut insert_identities_by_schema_scope = BTreeMap::<
|
|
262
|
+
Domain,
|
|
263
|
+
Vec<(&PreparedStateRowIdentity, Option<&TransactionWriteOrigin>)>,
|
|
264
|
+
>::new();
|
|
265
|
+
for (identity, origin) in &self.insert_identities {
|
|
266
|
+
insert_identities_by_schema_scope
|
|
267
|
+
.entry(identity.domain().schema_catalog_domain())
|
|
268
|
+
.or_default()
|
|
269
|
+
.push((identity, origin.as_ref()));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
PreparedWriteValidationIndex {
|
|
273
|
+
rows_by_schema_scope,
|
|
274
|
+
insert_identities_by_schema_scope,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
#[cfg(test)]
|
|
279
|
+
pub(crate) fn validation_set_for_tests(&self) -> PreparedWriteValidationSet<'_> {
|
|
280
|
+
let rows: Vec<_> = self.validation_rows().collect();
|
|
281
|
+
let insert_identities = self
|
|
282
|
+
.insert_identities
|
|
283
|
+
.iter()
|
|
284
|
+
.map(|(identity, origin)| (identity, origin.as_ref()))
|
|
285
|
+
.collect();
|
|
286
|
+
PreparedWriteValidationSet {
|
|
287
|
+
constraint_rows: rows.clone(),
|
|
288
|
+
rows,
|
|
289
|
+
insert_identities,
|
|
290
|
+
}
|
|
49
291
|
}
|
|
50
292
|
}
|
|
51
293
|
|
|
52
|
-
impl
|
|
294
|
+
impl TransactionWriteBuffer {
|
|
53
295
|
pub(crate) fn new(functions: FunctionProviderHandle) -> Self {
|
|
54
296
|
Self {
|
|
55
297
|
functions,
|
|
56
|
-
rows: Mutex::new(
|
|
57
|
-
adopted_rows: Mutex::new(
|
|
298
|
+
rows: Mutex::new(Vec::new()),
|
|
299
|
+
adopted_rows: Mutex::new(Vec::new()),
|
|
300
|
+
by_identity: Mutex::new(HashMap::new()),
|
|
58
301
|
insert_identities: Mutex::new(BTreeMap::new()),
|
|
59
302
|
commit_members_by_version: Mutex::new(BTreeMap::new()),
|
|
60
303
|
extra_commit_parents_by_version: Mutex::new(BTreeMap::new()),
|
|
@@ -63,7 +306,7 @@ impl TransactionStagedWrites {
|
|
|
63
306
|
}
|
|
64
307
|
|
|
65
308
|
/// Drains staged writes for commit.
|
|
66
|
-
pub(crate) fn drain(&self) -> Result<
|
|
309
|
+
pub(crate) fn drain(&self) -> Result<PreparedWriteSet, LixError> {
|
|
67
310
|
let mut rows_guard = self.rows.lock().map_err(|_| {
|
|
68
311
|
LixError::new(
|
|
69
312
|
"LIX_ERROR_UNKNOWN",
|
|
@@ -76,6 +319,12 @@ impl TransactionStagedWrites {
|
|
|
76
319
|
"failed to acquire transaction staged adopted writes lock",
|
|
77
320
|
)
|
|
78
321
|
})?;
|
|
322
|
+
let mut by_identity_guard = self.by_identity.lock().map_err(|_| {
|
|
323
|
+
LixError::new(
|
|
324
|
+
"LIX_ERROR_UNKNOWN",
|
|
325
|
+
"failed to acquire transaction staged identity index lock",
|
|
326
|
+
)
|
|
327
|
+
})?;
|
|
79
328
|
let mut file_data_guard = self.file_data_writes.lock().map_err(|_| {
|
|
80
329
|
LixError::new(
|
|
81
330
|
"LIX_ERROR_UNKNOWN",
|
|
@@ -101,16 +350,22 @@ impl TransactionStagedWrites {
|
|
|
101
350
|
"failed to acquire transaction staged extra commit parents lock",
|
|
102
351
|
)
|
|
103
352
|
})?;
|
|
104
|
-
Ok(
|
|
105
|
-
state_rows: std::mem::take(&mut *rows_guard)
|
|
353
|
+
let result = Ok(PreparedWriteSet {
|
|
354
|
+
state_rows: std::mem::take(&mut *rows_guard)
|
|
355
|
+
.into_iter()
|
|
356
|
+
.flatten()
|
|
357
|
+
.collect(),
|
|
106
358
|
adopted_rows: std::mem::take(&mut *adopted_rows_guard)
|
|
107
|
-
.
|
|
359
|
+
.into_iter()
|
|
360
|
+
.flatten()
|
|
108
361
|
.collect(),
|
|
109
362
|
insert_identities: std::mem::take(&mut *insert_identities_guard),
|
|
110
363
|
commit_members_by_version: std::mem::take(&mut *commit_members_guard),
|
|
111
364
|
extra_commit_parents_by_version: std::mem::take(&mut *extra_parents_guard),
|
|
112
365
|
file_data_writes: std::mem::take(&mut *file_data_guard),
|
|
113
|
-
})
|
|
366
|
+
});
|
|
367
|
+
by_identity_guard.clear();
|
|
368
|
+
result
|
|
114
369
|
}
|
|
115
370
|
|
|
116
371
|
/// Records an additional parent for the commit generated for `version_id`.
|
|
@@ -164,7 +419,6 @@ impl TransactionStagedWrites {
|
|
|
164
419
|
})?;
|
|
165
420
|
let members = guard.entry(version_id).or_insert_with(|| {
|
|
166
421
|
StagedCommitMembers::new(
|
|
167
|
-
functions.uuid_v7(),
|
|
168
422
|
functions.uuid_v7(),
|
|
169
423
|
functions.uuid_v7(),
|
|
170
424
|
functions.timestamp(),
|
|
@@ -175,40 +429,39 @@ impl TransactionStagedWrites {
|
|
|
175
429
|
}
|
|
176
430
|
|
|
177
431
|
/// Builds the transaction-local read overlay from currently staged writes.
|
|
178
|
-
pub(crate) fn staging_overlay(&
|
|
179
|
-
let
|
|
180
|
-
LixError::new(
|
|
181
|
-
"LIX_ERROR_UNKNOWN",
|
|
182
|
-
"failed to acquire transaction staged writes lock",
|
|
183
|
-
)
|
|
184
|
-
})?;
|
|
185
|
-
let adopted_guard = self.adopted_rows.lock().map_err(|_| {
|
|
432
|
+
pub(crate) fn staging_overlay(self: &Arc<Self>) -> Result<PreparedStateRowOverlay, LixError> {
|
|
433
|
+
let by_identity_guard = self.by_identity.lock().map_err(|_| {
|
|
186
434
|
LixError::new(
|
|
187
435
|
"LIX_ERROR_UNKNOWN",
|
|
188
|
-
"failed to acquire transaction staged
|
|
436
|
+
"failed to acquire transaction staged identity index lock",
|
|
189
437
|
)
|
|
190
438
|
})?;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
439
|
+
let slots = by_identity_guard
|
|
440
|
+
.iter()
|
|
441
|
+
.map(|(identity, slot)| (identity.clone(), *slot))
|
|
442
|
+
.collect();
|
|
443
|
+
Ok(PreparedStateRowOverlay {
|
|
444
|
+
staged_writes: Arc::clone(self),
|
|
445
|
+
slots,
|
|
446
|
+
})
|
|
195
447
|
}
|
|
196
448
|
|
|
197
|
-
/// Stages one
|
|
449
|
+
/// Stages one prepared write batch into this transaction.
|
|
198
450
|
///
|
|
199
|
-
///
|
|
200
|
-
///
|
|
201
|
-
///
|
|
202
|
-
|
|
203
|
-
|
|
451
|
+
/// Frontends hand raw `TransactionWriteRow`s to `Transaction`; normalization prepares
|
|
452
|
+
/// stable `PreparedStateRow`s before this method indexes them for transaction-
|
|
453
|
+
/// local reads and commit routing.
|
|
454
|
+
pub(crate) fn stage_write(
|
|
455
|
+
&self,
|
|
456
|
+
write: PreparedTransactionWrite,
|
|
457
|
+
) -> Result<TransactionWriteOutcome, LixError> {
|
|
204
458
|
let (mode, count) = match &write {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
459
|
+
PreparedTransactionWrite::Rows { mode, rows } => (Some(*mode), rows.len() as u64),
|
|
460
|
+
PreparedTransactionWrite::RowsWithFileData { mode, count, .. } => (Some(*mode), *count),
|
|
461
|
+
PreparedTransactionWrite::AdoptedChanges { rows } => (None, rows.len() as u64),
|
|
208
462
|
};
|
|
209
463
|
let mut functions = self.functions.clone();
|
|
210
|
-
let (rows, adopted_rows, file_data_writes) =
|
|
211
|
-
self.state_rows_from_stage_write(write, &mut functions)?;
|
|
464
|
+
let (rows, adopted_rows, file_data_writes) = self.state_rows_from_stage_write(write)?;
|
|
212
465
|
for row in &rows {
|
|
213
466
|
validate_commit_membership_support(row)?;
|
|
214
467
|
}
|
|
@@ -228,6 +481,12 @@ impl TransactionStagedWrites {
|
|
|
228
481
|
"failed to acquire transaction staged adopted writes lock",
|
|
229
482
|
)
|
|
230
483
|
})?;
|
|
484
|
+
let mut by_identity_guard = self.by_identity.lock().map_err(|_| {
|
|
485
|
+
LixError::new(
|
|
486
|
+
"LIX_ERROR_UNKNOWN",
|
|
487
|
+
"failed to acquire transaction staged identity index lock",
|
|
488
|
+
)
|
|
489
|
+
})?;
|
|
231
490
|
let mut commit_members_guard = self.commit_members_by_version.lock().map_err(|_| {
|
|
232
491
|
LixError::new(
|
|
233
492
|
"LIX_ERROR_UNKNOWN",
|
|
@@ -241,41 +500,49 @@ impl TransactionStagedWrites {
|
|
|
241
500
|
)
|
|
242
501
|
})?;
|
|
243
502
|
for mut row in rows {
|
|
244
|
-
let identity =
|
|
245
|
-
if mode == Some(
|
|
246
|
-
&&
|
|
247
|
-
|| guard.contains_key(&identity.opposite_untracked())
|
|
248
|
-
|| adopted_guard.contains_key(&identity))
|
|
503
|
+
let identity = PreparedStateRowIdentity::from(&row);
|
|
504
|
+
if mode == Some(TransactionWriteMode::Insert)
|
|
505
|
+
&& by_identity_guard.contains_key(&identity)
|
|
249
506
|
{
|
|
250
507
|
return Err(duplicate_insert_identity_error(&row));
|
|
251
508
|
}
|
|
252
|
-
if
|
|
509
|
+
if matches!(by_identity_guard.get(&identity), Some(RowSlot::Adopted(_))) {
|
|
253
510
|
return Err(conflicting_adopted_identity_error(&row));
|
|
254
511
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
512
|
+
let existing_slot = by_identity_guard.remove(&identity);
|
|
513
|
+
if let Some(RowSlot::State(index)) = existing_slot {
|
|
514
|
+
if let Some(previous) = guard.get_mut(index).and_then(Option::take) {
|
|
515
|
+
remove_row_from_commit_members(&mut commit_members_guard, &previous);
|
|
516
|
+
}
|
|
260
517
|
}
|
|
261
518
|
add_row_to_commit_members(&mut commit_members_guard, &mut row, &mut functions);
|
|
262
|
-
let identity =
|
|
263
|
-
if mode == Some(
|
|
264
|
-
insert_identities_guard.insert(
|
|
265
|
-
live_state_identity_from_staged_row(&row),
|
|
266
|
-
row.origin.clone(),
|
|
267
|
-
);
|
|
519
|
+
let identity = PreparedStateRowIdentity::from(&row);
|
|
520
|
+
if mode == Some(TransactionWriteMode::Insert) {
|
|
521
|
+
insert_identities_guard.insert(identity.clone(), row.origin.clone());
|
|
268
522
|
}
|
|
269
|
-
|
|
523
|
+
let slot = match existing_slot {
|
|
524
|
+
Some(RowSlot::State(index)) => {
|
|
525
|
+
guard[index] = Some(row);
|
|
526
|
+
RowSlot::State(index)
|
|
527
|
+
}
|
|
528
|
+
_ => {
|
|
529
|
+
let index = guard.len();
|
|
530
|
+
guard.push(Some(row));
|
|
531
|
+
RowSlot::State(index)
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
by_identity_guard.insert(identity, slot);
|
|
270
535
|
}
|
|
271
536
|
for mut row in adopted_rows {
|
|
272
|
-
let identity =
|
|
273
|
-
if
|
|
537
|
+
let identity = PreparedStateRowIdentity::from(&row);
|
|
538
|
+
if by_identity_guard.contains_key(&identity) {
|
|
274
539
|
return Err(conflicting_adopted_projection_error(&row));
|
|
275
540
|
}
|
|
276
541
|
add_adopted_row_to_commit_members(&mut commit_members_guard, &mut row, &mut functions);
|
|
277
|
-
let identity =
|
|
278
|
-
adopted_guard.
|
|
542
|
+
let identity = PreparedStateRowIdentity::from(&row);
|
|
543
|
+
let index = adopted_guard.len();
|
|
544
|
+
adopted_guard.push(Some(row));
|
|
545
|
+
by_identity_guard.insert(identity, RowSlot::Adopted(index));
|
|
279
546
|
}
|
|
280
547
|
if !file_data_writes.is_empty() {
|
|
281
548
|
self.file_data_writes
|
|
@@ -288,18 +555,17 @@ impl TransactionStagedWrites {
|
|
|
288
555
|
})?
|
|
289
556
|
.extend(file_data_writes);
|
|
290
557
|
}
|
|
291
|
-
Ok(
|
|
558
|
+
Ok(TransactionWriteOutcome { count })
|
|
292
559
|
}
|
|
293
560
|
|
|
294
561
|
fn state_rows_from_stage_write(
|
|
295
562
|
&self,
|
|
296
|
-
write:
|
|
297
|
-
functions: &mut dyn FunctionProvider,
|
|
563
|
+
write: PreparedTransactionWrite,
|
|
298
564
|
) -> Result<
|
|
299
565
|
(
|
|
300
|
-
Vec<
|
|
301
|
-
Vec<
|
|
302
|
-
Vec<
|
|
566
|
+
Vec<PreparedStateRow>,
|
|
567
|
+
Vec<PreparedAdoptedStateRow>,
|
|
568
|
+
Vec<TransactionFileData>,
|
|
303
569
|
),
|
|
304
570
|
LixError,
|
|
305
571
|
> {
|
|
@@ -307,136 +573,170 @@ impl TransactionStagedWrites {
|
|
|
307
573
|
let mut adopted_rows = Vec::new();
|
|
308
574
|
let mut file_data_writes = Vec::new();
|
|
309
575
|
match write {
|
|
310
|
-
|
|
311
|
-
|
|
576
|
+
PreparedTransactionWrite::Rows { rows, .. } => {
|
|
577
|
+
state_rows.extend(rows);
|
|
312
578
|
}
|
|
313
|
-
|
|
579
|
+
PreparedTransactionWrite::RowsWithFileData {
|
|
314
580
|
rows, file_data, ..
|
|
315
581
|
} => {
|
|
316
|
-
|
|
582
|
+
state_rows.extend(rows);
|
|
317
583
|
file_data_writes.extend(file_data);
|
|
318
584
|
}
|
|
319
|
-
|
|
320
|
-
|
|
585
|
+
PreparedTransactionWrite::AdoptedChanges { rows } => {
|
|
586
|
+
adopted_rows.extend(rows);
|
|
321
587
|
}
|
|
322
588
|
}
|
|
323
589
|
Ok((state_rows, adopted_rows, file_data_writes))
|
|
324
590
|
}
|
|
325
|
-
|
|
326
|
-
fn push_state_rows(
|
|
327
|
-
&self,
|
|
328
|
-
state_rows: &mut Vec<StagedStateRow>,
|
|
329
|
-
rows: Vec<StageRow>,
|
|
330
|
-
functions: &mut dyn FunctionProvider,
|
|
331
|
-
) -> Result<(), LixError> {
|
|
332
|
-
state_rows.reserve(rows.len());
|
|
333
|
-
for row in rows {
|
|
334
|
-
state_rows.push(hydrate_state_write_row(row, functions)?);
|
|
335
|
-
}
|
|
336
|
-
Ok(())
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
fn push_adopted_rows(
|
|
340
|
-
&self,
|
|
341
|
-
adopted_rows: &mut Vec<StagedAdoptedStateRow>,
|
|
342
|
-
changes: Vec<StageAdoptedChange>,
|
|
343
|
-
) -> Result<(), LixError> {
|
|
344
|
-
adopted_rows.reserve(changes.len());
|
|
345
|
-
for change in changes {
|
|
346
|
-
adopted_rows.push(hydrate_adopted_state_row(change)?);
|
|
347
|
-
}
|
|
348
|
-
Ok(())
|
|
349
|
-
}
|
|
350
591
|
}
|
|
351
592
|
|
|
352
593
|
/// Read overlay derived from staged transaction writes.
|
|
353
|
-
pub(crate) struct
|
|
354
|
-
|
|
355
|
-
|
|
594
|
+
pub(crate) struct PreparedStateRowOverlay {
|
|
595
|
+
staged_writes: Arc<TransactionWriteBuffer>,
|
|
596
|
+
slots: BTreeMap<PreparedStateRowIdentity, RowSlot>,
|
|
356
597
|
}
|
|
357
598
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
) -> Self {
|
|
363
|
-
Self { rows, adopted_rows }
|
|
364
|
-
}
|
|
599
|
+
pub(crate) struct StagedScanParts {
|
|
600
|
+
pub(crate) rows: Vec<MaterializedLiveStateRow>,
|
|
601
|
+
pub(crate) hidden_identities: BTreeSet<PreparedStateRowIdentity>,
|
|
602
|
+
}
|
|
365
603
|
|
|
604
|
+
impl PreparedStateRowOverlay {
|
|
366
605
|
/// Returns staged rows visible for a scan request.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
self.adopted_rows
|
|
374
|
-
.values()
|
|
375
|
-
.filter(|row| adopted_row_matches_scan(row, request))
|
|
376
|
-
.map(LiveStateRow::from),
|
|
377
|
-
)
|
|
378
|
-
.collect()
|
|
606
|
+
#[cfg(test)]
|
|
607
|
+
pub(crate) fn scan(
|
|
608
|
+
&self,
|
|
609
|
+
request: &LiveStateScanRequest,
|
|
610
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
611
|
+
Ok(self.scan_parts(request)?.rows)
|
|
379
612
|
}
|
|
380
613
|
|
|
381
|
-
/// Returns staged identities
|
|
614
|
+
/// Returns staged rows and base-row identities hidden by staged rows in one pass.
|
|
382
615
|
///
|
|
383
|
-
/// Tombstones
|
|
384
|
-
///
|
|
385
|
-
pub(crate) fn
|
|
616
|
+
/// Tombstones hide base rows even when the request does not include
|
|
617
|
+
/// tombstone rows in the visible result set.
|
|
618
|
+
pub(crate) fn scan_parts(
|
|
386
619
|
&self,
|
|
387
620
|
request: &LiveStateScanRequest,
|
|
388
|
-
) ->
|
|
389
|
-
self.rows
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
621
|
+
) -> Result<StagedScanParts, LixError> {
|
|
622
|
+
let rows_guard = self.staged_writes.rows.lock().map_err(|_| {
|
|
623
|
+
LixError::new(
|
|
624
|
+
"LIX_ERROR_UNKNOWN",
|
|
625
|
+
"failed to acquire transaction staged writes lock",
|
|
626
|
+
)
|
|
627
|
+
})?;
|
|
628
|
+
let adopted_guard = self.staged_writes.adopted_rows.lock().map_err(|_| {
|
|
629
|
+
LixError::new(
|
|
630
|
+
"LIX_ERROR_UNKNOWN",
|
|
631
|
+
"failed to acquire transaction staged adopted writes lock",
|
|
398
632
|
)
|
|
399
|
-
|
|
633
|
+
})?;
|
|
634
|
+
|
|
635
|
+
let mut rows = Vec::new();
|
|
636
|
+
let mut hidden_identities = BTreeSet::new();
|
|
637
|
+
for (identity, slot) in &self.slots {
|
|
638
|
+
match *slot {
|
|
639
|
+
RowSlot::State(index) => {
|
|
640
|
+
let Some(row) = rows_guard.get(index).and_then(Option::as_ref) else {
|
|
641
|
+
continue;
|
|
642
|
+
};
|
|
643
|
+
if !staged_row_identity_matches_scan(row, request) {
|
|
644
|
+
continue;
|
|
645
|
+
}
|
|
646
|
+
hidden_identities.insert(identity.clone());
|
|
647
|
+
if row.snapshot.is_some() || request.filter.include_tombstones {
|
|
648
|
+
rows.push(MaterializedLiveStateRow::from(row));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
RowSlot::Adopted(index) => {
|
|
652
|
+
let Some(row) = adopted_guard.get(index).and_then(Option::as_ref) else {
|
|
653
|
+
continue;
|
|
654
|
+
};
|
|
655
|
+
if !adopted_row_identity_matches_scan(row, request) {
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
hidden_identities.insert(identity.clone());
|
|
659
|
+
if row.snapshot.is_some() || request.filter.include_tombstones {
|
|
660
|
+
rows.push(MaterializedLiveStateRow::from(row));
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
Ok(StagedScanParts {
|
|
666
|
+
rows,
|
|
667
|
+
hidden_identities,
|
|
668
|
+
})
|
|
400
669
|
}
|
|
401
670
|
|
|
402
671
|
/// Returns a staged exact-row answer, if this transaction has one.
|
|
403
672
|
#[cfg(test)]
|
|
404
673
|
pub(crate) fn load_exact(&self, request: &LiveStateRowRequest) -> Option<StagedExactRow> {
|
|
405
|
-
let untracked_identity =
|
|
406
|
-
if let Some(row) = self.
|
|
407
|
-
return Some(if row.
|
|
674
|
+
let untracked_identity = PreparedStateRowIdentity::from_exact_request(request, true)?;
|
|
675
|
+
if let Some(row) = self.load_state_slot(&untracked_identity) {
|
|
676
|
+
return Some(if row.snapshot.is_none() {
|
|
408
677
|
StagedExactRow::Tombstone
|
|
409
678
|
} else {
|
|
410
|
-
StagedExactRow::Row(
|
|
679
|
+
StagedExactRow::Row(MaterializedLiveStateRow::from(&row))
|
|
411
680
|
});
|
|
412
681
|
}
|
|
413
682
|
|
|
414
|
-
let identity =
|
|
415
|
-
if let Some(row) = self.
|
|
416
|
-
return Some(if row.
|
|
683
|
+
let identity = PreparedStateRowIdentity::from_exact_request(request, false)?;
|
|
684
|
+
if let Some(row) = self.load_state_slot(&identity) {
|
|
685
|
+
return Some(if row.snapshot.is_none() {
|
|
417
686
|
StagedExactRow::Tombstone
|
|
418
687
|
} else {
|
|
419
|
-
StagedExactRow::Row(
|
|
688
|
+
StagedExactRow::Row(MaterializedLiveStateRow::from(&row))
|
|
420
689
|
});
|
|
421
690
|
}
|
|
422
|
-
self.
|
|
423
|
-
if row.
|
|
691
|
+
self.load_adopted_slot(&identity).map(|row| {
|
|
692
|
+
if row.snapshot.is_none() {
|
|
424
693
|
StagedExactRow::Tombstone
|
|
425
694
|
} else {
|
|
426
|
-
StagedExactRow::Row(
|
|
695
|
+
StagedExactRow::Row(MaterializedLiveStateRow::from(&row))
|
|
427
696
|
}
|
|
428
697
|
})
|
|
429
698
|
}
|
|
699
|
+
|
|
700
|
+
#[cfg(test)]
|
|
701
|
+
fn load_state_slot(&self, identity: &PreparedStateRowIdentity) -> Option<PreparedStateRow> {
|
|
702
|
+
let Some(RowSlot::State(index)) = self.slots.get(identity).copied() else {
|
|
703
|
+
return None;
|
|
704
|
+
};
|
|
705
|
+
self.staged_writes
|
|
706
|
+
.rows
|
|
707
|
+
.lock()
|
|
708
|
+
.ok()?
|
|
709
|
+
.get(index)?
|
|
710
|
+
.as_ref()
|
|
711
|
+
.cloned()
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
#[cfg(test)]
|
|
715
|
+
fn load_adopted_slot(
|
|
716
|
+
&self,
|
|
717
|
+
identity: &PreparedStateRowIdentity,
|
|
718
|
+
) -> Option<PreparedAdoptedStateRow> {
|
|
719
|
+
let Some(RowSlot::Adopted(index)) = self.slots.get(identity).copied() else {
|
|
720
|
+
return None;
|
|
721
|
+
};
|
|
722
|
+
self.staged_writes
|
|
723
|
+
.adopted_rows
|
|
724
|
+
.lock()
|
|
725
|
+
.ok()?
|
|
726
|
+
.get(index)?
|
|
727
|
+
.as_ref()
|
|
728
|
+
.cloned()
|
|
729
|
+
}
|
|
430
730
|
}
|
|
431
731
|
|
|
432
732
|
#[cfg(test)]
|
|
433
733
|
pub(crate) enum StagedExactRow {
|
|
434
|
-
Row(
|
|
734
|
+
Row(MaterializedLiveStateRow),
|
|
435
735
|
Tombstone,
|
|
436
736
|
}
|
|
437
737
|
|
|
438
|
-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
439
|
-
pub(crate) struct
|
|
738
|
+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
739
|
+
pub(crate) struct PreparedStateRowIdentity {
|
|
440
740
|
untracked: bool,
|
|
441
741
|
schema_key: String,
|
|
442
742
|
entity_id: crate::entity_identity::EntityIdentity,
|
|
@@ -444,8 +744,8 @@ pub(crate) struct StagedStateRowIdentity {
|
|
|
444
744
|
version_id: String,
|
|
445
745
|
}
|
|
446
746
|
|
|
447
|
-
impl
|
|
448
|
-
fn from_staged_row(row: &
|
|
747
|
+
impl PreparedStateRowIdentity {
|
|
748
|
+
fn from_staged_row(row: &PreparedStateRow) -> Self {
|
|
449
749
|
Self {
|
|
450
750
|
untracked: row.untracked,
|
|
451
751
|
schema_key: row.schema_key.clone(),
|
|
@@ -472,25 +772,31 @@ impl StagedStateRowIdentity {
|
|
|
472
772
|
})
|
|
473
773
|
}
|
|
474
774
|
|
|
475
|
-
fn
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
775
|
+
pub(crate) fn schema_key(&self) -> &str {
|
|
776
|
+
&self.schema_key
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
pub(crate) fn entity_id(&self) -> &crate::entity_identity::EntityIdentity {
|
|
780
|
+
&self.entity_id
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
pub(crate) fn domain(&self) -> Domain {
|
|
784
|
+
Domain::exact_file(
|
|
785
|
+
self.version_id.clone(),
|
|
786
|
+
self.untracked,
|
|
787
|
+
self.file_id.clone(),
|
|
788
|
+
)
|
|
483
789
|
}
|
|
484
790
|
}
|
|
485
791
|
|
|
486
|
-
impl From<&
|
|
487
|
-
fn from(row: &
|
|
792
|
+
impl From<&PreparedStateRow> for PreparedStateRowIdentity {
|
|
793
|
+
fn from(row: &PreparedStateRow) -> Self {
|
|
488
794
|
Self::from_staged_row(row)
|
|
489
795
|
}
|
|
490
796
|
}
|
|
491
797
|
|
|
492
|
-
impl From<&
|
|
493
|
-
fn from(row: &
|
|
798
|
+
impl From<&PreparedAdoptedStateRow> for PreparedStateRowIdentity {
|
|
799
|
+
fn from(row: &PreparedAdoptedStateRow) -> Self {
|
|
494
800
|
Self {
|
|
495
801
|
untracked: false,
|
|
496
802
|
schema_key: row.schema_key.clone(),
|
|
@@ -501,8 +807,8 @@ impl From<&StagedAdoptedStateRow> for StagedStateRowIdentity {
|
|
|
501
807
|
}
|
|
502
808
|
}
|
|
503
809
|
|
|
504
|
-
impl From<&
|
|
505
|
-
fn from(row: &
|
|
810
|
+
impl From<&MaterializedLiveStateRow> for PreparedStateRowIdentity {
|
|
811
|
+
fn from(row: &MaterializedLiveStateRow) -> Self {
|
|
506
812
|
Self {
|
|
507
813
|
untracked: row.untracked,
|
|
508
814
|
schema_key: row.schema_key.clone(),
|
|
@@ -513,92 +819,33 @@ impl From<&LiveStateRow> for StagedStateRowIdentity {
|
|
|
513
819
|
}
|
|
514
820
|
}
|
|
515
821
|
|
|
516
|
-
fn
|
|
517
|
-
row: StageRow,
|
|
518
|
-
functions: &mut dyn FunctionProvider,
|
|
519
|
-
) -> Result<StagedStateRow, LixError> {
|
|
520
|
-
let updated_at = row.updated_at.unwrap_or_else(|| functions.timestamp());
|
|
521
|
-
Ok(StagedStateRow {
|
|
522
|
-
entity_id: row.entity_id.ok_or_else(|| {
|
|
523
|
-
LixError::new(
|
|
524
|
-
"LIX_ERROR_UNKNOWN",
|
|
525
|
-
"normalized staged row is missing entity_id",
|
|
526
|
-
)
|
|
527
|
-
})?,
|
|
528
|
-
schema_key: row.schema_key,
|
|
529
|
-
file_id: row.file_id,
|
|
530
|
-
snapshot_content: row.snapshot_content,
|
|
531
|
-
metadata: row.metadata,
|
|
532
|
-
origin: row.origin,
|
|
533
|
-
schema_version: row.schema_version,
|
|
534
|
-
created_at: row.created_at.unwrap_or_else(|| updated_at.clone()),
|
|
535
|
-
updated_at,
|
|
536
|
-
global: row.global,
|
|
537
|
-
change_id: if row.untracked {
|
|
538
|
-
row.change_id
|
|
539
|
-
} else {
|
|
540
|
-
Some(row.change_id.unwrap_or_else(|| functions.uuid_v7()))
|
|
541
|
-
},
|
|
542
|
-
commit_id: row.commit_id,
|
|
543
|
-
untracked: row.untracked,
|
|
544
|
-
version_id: row.version_id,
|
|
545
|
-
})
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
fn hydrate_adopted_state_row(
|
|
549
|
-
change: StageAdoptedChange,
|
|
550
|
-
) -> Result<StagedAdoptedStateRow, LixError> {
|
|
551
|
-
if change.change_id != change.projected_row.change_id {
|
|
552
|
-
return Err(LixError::new(
|
|
553
|
-
"LIX_ERROR_UNKNOWN",
|
|
554
|
-
format!(
|
|
555
|
-
"adopted change '{}' does not match projected row change_id '{}'",
|
|
556
|
-
change.change_id, change.projected_row.change_id
|
|
557
|
-
),
|
|
558
|
-
));
|
|
559
|
-
}
|
|
560
|
-
let row = change.projected_row;
|
|
561
|
-
Ok(StagedAdoptedStateRow {
|
|
562
|
-
entity_id: row.entity_id,
|
|
563
|
-
schema_key: row.schema_key,
|
|
564
|
-
file_id: row.file_id,
|
|
565
|
-
snapshot_content: row.snapshot_content,
|
|
566
|
-
metadata: row.metadata,
|
|
567
|
-
schema_version: row.schema_version,
|
|
568
|
-
created_at: row.created_at,
|
|
569
|
-
updated_at: row.updated_at,
|
|
570
|
-
global: change.version_id == GLOBAL_VERSION_ID,
|
|
571
|
-
change_id: change.change_id,
|
|
572
|
-
commit_id: String::new(),
|
|
573
|
-
version_id: change.version_id,
|
|
574
|
-
})
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
fn validate_commit_membership_support(row: &StagedStateRow) -> Result<(), LixError> {
|
|
822
|
+
fn validate_commit_membership_support(row: &PreparedStateRow) -> Result<(), LixError> {
|
|
578
823
|
if row.global && row.version_id != GLOBAL_VERSION_ID {
|
|
579
824
|
return Err(LixError::new(
|
|
580
825
|
"LIX_ERROR_UNKNOWN",
|
|
581
|
-
"
|
|
826
|
+
"engine global staged rows must use the global version id",
|
|
582
827
|
));
|
|
583
828
|
}
|
|
584
829
|
Ok(())
|
|
585
830
|
}
|
|
586
831
|
|
|
587
|
-
fn validate_adopted_commit_membership_support(
|
|
832
|
+
fn validate_adopted_commit_membership_support(
|
|
833
|
+
row: &PreparedAdoptedStateRow,
|
|
834
|
+
) -> Result<(), LixError> {
|
|
588
835
|
if row.global && row.version_id != GLOBAL_VERSION_ID {
|
|
589
836
|
return Err(LixError::new(
|
|
590
837
|
"LIX_ERROR_UNKNOWN",
|
|
591
|
-
"
|
|
838
|
+
"engine global adopted rows must use the global version id",
|
|
592
839
|
));
|
|
593
840
|
}
|
|
594
841
|
Ok(())
|
|
595
842
|
}
|
|
596
843
|
|
|
597
|
-
fn reject_duplicate_present_rows_in_batch(rows: &[
|
|
598
|
-
let mut pending_present_rows = BTreeMap::<
|
|
844
|
+
fn reject_duplicate_present_rows_in_batch(rows: &[PreparedStateRow]) -> Result<(), LixError> {
|
|
845
|
+
let mut pending_present_rows = BTreeMap::<PreparedStateRowIdentity, &PreparedStateRow>::new();
|
|
599
846
|
for row in rows {
|
|
600
|
-
let identity =
|
|
601
|
-
if row.
|
|
847
|
+
let identity = PreparedStateRowIdentity::from(row);
|
|
848
|
+
if row.snapshot.is_none() {
|
|
602
849
|
pending_present_rows.remove(&identity);
|
|
603
850
|
continue;
|
|
604
851
|
}
|
|
@@ -609,16 +856,18 @@ fn reject_duplicate_present_rows_in_batch(rows: &[StagedStateRow]) -> Result<(),
|
|
|
609
856
|
Ok(())
|
|
610
857
|
}
|
|
611
858
|
|
|
612
|
-
fn duplicate_staged_present_row_error(
|
|
859
|
+
fn duplicate_staged_present_row_error(
|
|
860
|
+
row: &PreparedStateRow,
|
|
861
|
+
previous: &PreparedStateRow,
|
|
862
|
+
) -> LixError {
|
|
613
863
|
let message = logical_primary_key_violation_message(row.origin.as_ref())
|
|
614
864
|
.unwrap_or_else(|| {
|
|
615
865
|
format!(
|
|
616
|
-
"primary-key constraint violation on schema '{}'
|
|
866
|
+
"primary-key constraint violation on schema '{}': duplicate staged rows for entity_id '{}' in version '{}'",
|
|
617
867
|
row.schema_key,
|
|
618
|
-
row.schema_version,
|
|
619
868
|
previous
|
|
620
869
|
.entity_id
|
|
621
|
-
.
|
|
870
|
+
.as_json_array_text()
|
|
622
871
|
.unwrap_or_else(|_| "<invalid entity_id>".to_string()),
|
|
623
872
|
row.version_id
|
|
624
873
|
)
|
|
@@ -628,31 +877,29 @@ fn duplicate_staged_present_row_error(row: &StagedStateRow, previous: &StagedSta
|
|
|
628
877
|
|
|
629
878
|
pub(crate) fn duplicate_insert_identity_message(
|
|
630
879
|
schema_key: &str,
|
|
631
|
-
schema_version: &str,
|
|
632
880
|
entity_id: &crate::entity_identity::EntityIdentity,
|
|
633
881
|
version_id: Option<&str>,
|
|
634
|
-
origin: Option<&
|
|
882
|
+
origin: Option<&TransactionWriteOrigin>,
|
|
635
883
|
) -> String {
|
|
636
884
|
if let Some(message) = logical_primary_key_violation_message(origin) {
|
|
637
885
|
return message;
|
|
638
886
|
}
|
|
639
887
|
let entity_id = entity_id
|
|
640
|
-
.
|
|
888
|
+
.as_json_array_text()
|
|
641
889
|
.unwrap_or_else(|_| "<invalid entity_id>".to_string());
|
|
642
890
|
match version_id {
|
|
643
891
|
Some(version_id) => format!(
|
|
644
|
-
"primary-key constraint violation on schema '{schema_key}'
|
|
892
|
+
"primary-key constraint violation on schema '{schema_key}': INSERT would duplicate entity_id '{entity_id}' in version '{version_id}'"
|
|
645
893
|
),
|
|
646
894
|
None => format!(
|
|
647
|
-
"primary-key constraint violation on schema '{schema_key}'
|
|
895
|
+
"primary-key constraint violation on schema '{schema_key}': INSERT would duplicate entity_id '{entity_id}'"
|
|
648
896
|
),
|
|
649
897
|
}
|
|
650
898
|
}
|
|
651
899
|
|
|
652
|
-
fn duplicate_insert_identity_error(row: &
|
|
900
|
+
fn duplicate_insert_identity_error(row: &PreparedStateRow) -> LixError {
|
|
653
901
|
let message = duplicate_insert_identity_message(
|
|
654
902
|
&row.schema_key,
|
|
655
|
-
&row.schema_version,
|
|
656
903
|
&row.entity_id,
|
|
657
904
|
Some(&row.version_id),
|
|
658
905
|
row.origin.as_ref(),
|
|
@@ -660,9 +907,11 @@ fn duplicate_insert_identity_error(row: &StagedStateRow) -> LixError {
|
|
|
660
907
|
LixError::new(LixError::CODE_UNIQUE, message)
|
|
661
908
|
}
|
|
662
909
|
|
|
663
|
-
fn logical_primary_key_violation_message(
|
|
910
|
+
fn logical_primary_key_violation_message(
|
|
911
|
+
origin: Option<&TransactionWriteOrigin>,
|
|
912
|
+
) -> Option<String> {
|
|
664
913
|
let origin = origin?;
|
|
665
|
-
if origin.operation !=
|
|
914
|
+
if origin.operation != TransactionWriteOperation::Insert {
|
|
666
915
|
return None;
|
|
667
916
|
}
|
|
668
917
|
let primary_key = origin.primary_key.as_ref()?;
|
|
@@ -690,46 +939,37 @@ fn format_logical_primary_key(primary_key: &LogicalPrimaryKey) -> String {
|
|
|
690
939
|
.join(", ")
|
|
691
940
|
}
|
|
692
941
|
|
|
693
|
-
fn conflicting_adopted_identity_error(row: &
|
|
942
|
+
fn conflicting_adopted_identity_error(row: &PreparedStateRow) -> LixError {
|
|
694
943
|
LixError::new(
|
|
695
944
|
LixError::CODE_UNIQUE,
|
|
696
945
|
format!(
|
|
697
946
|
"transaction cannot stage a new row and an adopted projection for schema '{}' entity_id '{}' in version '{}'",
|
|
698
947
|
row.schema_key,
|
|
699
948
|
row.entity_id
|
|
700
|
-
.
|
|
949
|
+
.as_json_array_text()
|
|
701
950
|
.unwrap_or_else(|_| "<invalid entity_id>".to_string()),
|
|
702
951
|
row.version_id
|
|
703
952
|
),
|
|
704
953
|
)
|
|
705
954
|
}
|
|
706
955
|
|
|
707
|
-
fn conflicting_adopted_projection_error(row: &
|
|
956
|
+
fn conflicting_adopted_projection_error(row: &PreparedAdoptedStateRow) -> LixError {
|
|
708
957
|
LixError::new(
|
|
709
958
|
LixError::CODE_UNIQUE,
|
|
710
959
|
format!(
|
|
711
960
|
"transaction cannot stage duplicate adopted projections for schema '{}' entity_id '{}' in version '{}'",
|
|
712
961
|
row.schema_key,
|
|
713
962
|
row.entity_id
|
|
714
|
-
.
|
|
963
|
+
.as_json_array_text()
|
|
715
964
|
.unwrap_or_else(|_| "<invalid entity_id>".to_string()),
|
|
716
965
|
row.version_id
|
|
717
966
|
),
|
|
718
967
|
)
|
|
719
968
|
}
|
|
720
969
|
|
|
721
|
-
fn live_state_identity_from_staged_row(row: &StagedStateRow) -> LiveStateRowIdentity {
|
|
722
|
-
LiveStateRowIdentity {
|
|
723
|
-
version_id: row.version_id.clone(),
|
|
724
|
-
schema_key: row.schema_key.clone(),
|
|
725
|
-
entity_id: row.entity_id.clone(),
|
|
726
|
-
file_id: row.file_id.clone(),
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
|
|
730
970
|
fn add_row_to_commit_members(
|
|
731
971
|
members_by_version: &mut BTreeMap<String, StagedCommitMembers>,
|
|
732
|
-
row: &mut
|
|
972
|
+
row: &mut PreparedStateRow,
|
|
733
973
|
functions: &mut dyn FunctionProvider,
|
|
734
974
|
) {
|
|
735
975
|
if row.untracked {
|
|
@@ -743,7 +983,6 @@ fn add_row_to_commit_members(
|
|
|
743
983
|
.entry(row.version_id.clone())
|
|
744
984
|
.or_insert_with(|| {
|
|
745
985
|
StagedCommitMembers::new(
|
|
746
|
-
functions.uuid_v7(),
|
|
747
986
|
functions.uuid_v7(),
|
|
748
987
|
functions.uuid_v7(),
|
|
749
988
|
functions.timestamp(),
|
|
@@ -755,14 +994,13 @@ fn add_row_to_commit_members(
|
|
|
755
994
|
|
|
756
995
|
fn add_adopted_row_to_commit_members(
|
|
757
996
|
members_by_version: &mut BTreeMap<String, StagedCommitMembers>,
|
|
758
|
-
row: &mut
|
|
997
|
+
row: &mut PreparedAdoptedStateRow,
|
|
759
998
|
functions: &mut dyn FunctionProvider,
|
|
760
999
|
) {
|
|
761
1000
|
let members = members_by_version
|
|
762
1001
|
.entry(row.version_id.clone())
|
|
763
1002
|
.or_insert_with(|| {
|
|
764
1003
|
StagedCommitMembers::new(
|
|
765
|
-
functions.uuid_v7(),
|
|
766
1004
|
functions.uuid_v7(),
|
|
767
1005
|
functions.uuid_v7(),
|
|
768
1006
|
functions.timestamp(),
|
|
@@ -774,7 +1012,7 @@ fn add_adopted_row_to_commit_members(
|
|
|
774
1012
|
|
|
775
1013
|
fn remove_row_from_commit_members(
|
|
776
1014
|
members_by_version: &mut BTreeMap<String, StagedCommitMembers>,
|
|
777
|
-
row: &
|
|
1015
|
+
row: &PreparedStateRow,
|
|
778
1016
|
) {
|
|
779
1017
|
if row.untracked {
|
|
780
1018
|
return;
|
|
@@ -791,18 +1029,8 @@ fn remove_row_from_commit_members(
|
|
|
791
1029
|
}
|
|
792
1030
|
}
|
|
793
1031
|
|
|
794
|
-
fn staged_row_matches_scan(row: &StagedStateRow, request: &LiveStateScanRequest) -> bool {
|
|
795
|
-
staged_row_identity_matches_scan(row, request)
|
|
796
|
-
&& (row.snapshot_content.is_some() || request.filter.include_tombstones)
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
fn adopted_row_matches_scan(row: &StagedAdoptedStateRow, request: &LiveStateScanRequest) -> bool {
|
|
800
|
-
adopted_row_identity_matches_scan(row, request)
|
|
801
|
-
&& (row.snapshot_content.is_some() || request.filter.include_tombstones)
|
|
802
|
-
}
|
|
803
|
-
|
|
804
1032
|
fn adopted_row_identity_matches_scan(
|
|
805
|
-
row: &
|
|
1033
|
+
row: &PreparedAdoptedStateRow,
|
|
806
1034
|
request: &LiveStateScanRequest,
|
|
807
1035
|
) -> bool {
|
|
808
1036
|
if !request.filter.schema_keys.is_empty()
|
|
@@ -819,10 +1047,16 @@ fn adopted_row_identity_matches_scan(
|
|
|
819
1047
|
{
|
|
820
1048
|
return false;
|
|
821
1049
|
}
|
|
1050
|
+
if request.filter.untracked == Some(true) {
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
822
1053
|
nullable_key_matches_filters(&row.file_id, &request.filter.file_ids)
|
|
823
1054
|
}
|
|
824
1055
|
|
|
825
|
-
fn staged_row_identity_matches_scan(
|
|
1056
|
+
fn staged_row_identity_matches_scan(
|
|
1057
|
+
row: &PreparedStateRow,
|
|
1058
|
+
request: &LiveStateScanRequest,
|
|
1059
|
+
) -> bool {
|
|
826
1060
|
if !request.filter.schema_keys.is_empty()
|
|
827
1061
|
&& !request.filter.schema_keys.contains(&row.schema_key)
|
|
828
1062
|
{
|
|
@@ -837,6 +1071,13 @@ fn staged_row_identity_matches_scan(row: &StagedStateRow, request: &LiveStateSca
|
|
|
837
1071
|
{
|
|
838
1072
|
return false;
|
|
839
1073
|
}
|
|
1074
|
+
if request
|
|
1075
|
+
.filter
|
|
1076
|
+
.untracked
|
|
1077
|
+
.is_some_and(|untracked| row.untracked != untracked)
|
|
1078
|
+
{
|
|
1079
|
+
return false;
|
|
1080
|
+
}
|
|
840
1081
|
nullable_key_matches_filters(&row.file_id, &request.filter.file_ids)
|
|
841
1082
|
}
|
|
842
1083
|
|
|
@@ -869,14 +1110,14 @@ mod tests {
|
|
|
869
1110
|
let staged_writes = test_staged_writes();
|
|
870
1111
|
|
|
871
1112
|
staged_writes
|
|
872
|
-
.stage_write(
|
|
873
|
-
mode:
|
|
1113
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1114
|
+
mode: TransactionWriteMode::Replace,
|
|
874
1115
|
rows: vec![state_row("sql2-duplicate-key", "first")],
|
|
875
1116
|
})
|
|
876
1117
|
.expect("initial row should stage");
|
|
877
1118
|
staged_writes
|
|
878
|
-
.stage_write(
|
|
879
|
-
mode:
|
|
1119
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1120
|
+
mode: TransactionWriteMode::Replace,
|
|
880
1121
|
rows: vec![state_row("sql2-duplicate-key", "second")],
|
|
881
1122
|
})
|
|
882
1123
|
.expect("staging rows should succeed");
|
|
@@ -907,14 +1148,14 @@ mod tests {
|
|
|
907
1148
|
let staged_writes = test_staged_writes();
|
|
908
1149
|
|
|
909
1150
|
staged_writes
|
|
910
|
-
.stage_write(
|
|
911
|
-
mode:
|
|
1151
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1152
|
+
mode: TransactionWriteMode::Replace,
|
|
912
1153
|
rows: vec![state_row("sql2-duplicate-key", "first")],
|
|
913
1154
|
})
|
|
914
1155
|
.expect("initial row should stage");
|
|
915
1156
|
staged_writes
|
|
916
|
-
.stage_write(
|
|
917
|
-
mode:
|
|
1157
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1158
|
+
mode: TransactionWriteMode::Replace,
|
|
918
1159
|
rows: vec![state_row("sql2-duplicate-key", "second")],
|
|
919
1160
|
})
|
|
920
1161
|
.expect("staging rows should succeed");
|
|
@@ -922,7 +1163,9 @@ mod tests {
|
|
|
922
1163
|
let overlay = staged_writes
|
|
923
1164
|
.staging_overlay()
|
|
924
1165
|
.expect("overlay should build from staged rows");
|
|
925
|
-
let rows = overlay
|
|
1166
|
+
let rows = overlay
|
|
1167
|
+
.scan(&scan_request_for_key("sql2-duplicate-key", false))
|
|
1168
|
+
.expect("overlay scan should succeed");
|
|
926
1169
|
|
|
927
1170
|
assert_eq!(rows.len(), 1);
|
|
928
1171
|
assert_eq!(
|
|
@@ -936,8 +1179,8 @@ mod tests {
|
|
|
936
1179
|
let staged_writes = test_staged_writes();
|
|
937
1180
|
|
|
938
1181
|
staged_writes
|
|
939
|
-
.stage_write(
|
|
940
|
-
mode:
|
|
1182
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1183
|
+
mode: TransactionWriteMode::Replace,
|
|
941
1184
|
rows: vec![
|
|
942
1185
|
state_row("sql2-delete-key", "visible"),
|
|
943
1186
|
tombstone_row("sql2-delete-key"),
|
|
@@ -954,9 +1197,12 @@ mod tests {
|
|
|
954
1197
|
assert!(matches!(exact, StagedExactRow::Tombstone));
|
|
955
1198
|
assert!(overlay
|
|
956
1199
|
.scan(&scan_request_for_key("sql2-delete-key", false))
|
|
1200
|
+
.expect("overlay scan should succeed")
|
|
957
1201
|
.is_empty());
|
|
958
1202
|
|
|
959
|
-
let tombstones = overlay
|
|
1203
|
+
let tombstones = overlay
|
|
1204
|
+
.scan(&scan_request_for_key("sql2-delete-key", true))
|
|
1205
|
+
.expect("overlay scan should succeed");
|
|
960
1206
|
assert_eq!(tombstones.len(), 1);
|
|
961
1207
|
assert_eq!(tombstones[0].snapshot_content, None);
|
|
962
1208
|
}
|
|
@@ -966,8 +1212,8 @@ mod tests {
|
|
|
966
1212
|
let staged_writes = test_staged_writes();
|
|
967
1213
|
|
|
968
1214
|
staged_writes
|
|
969
|
-
.stage_write(
|
|
970
|
-
mode:
|
|
1215
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1216
|
+
mode: TransactionWriteMode::Replace,
|
|
971
1217
|
rows: vec![
|
|
972
1218
|
tombstone_row("sql2-resurrect-key"),
|
|
973
1219
|
state_row("sql2-resurrect-key", "visible-again"),
|
|
@@ -992,6 +1238,7 @@ mod tests {
|
|
|
992
1238
|
assert_eq!(
|
|
993
1239
|
overlay
|
|
994
1240
|
.scan(&scan_request_for_key("sql2-resurrect-key", false))
|
|
1241
|
+
.expect("overlay scan should succeed")
|
|
995
1242
|
.len(),
|
|
996
1243
|
1
|
|
997
1244
|
);
|
|
@@ -1002,8 +1249,8 @@ mod tests {
|
|
|
1002
1249
|
let staged_writes = test_staged_writes();
|
|
1003
1250
|
|
|
1004
1251
|
staged_writes
|
|
1005
|
-
.stage_write(
|
|
1006
|
-
mode:
|
|
1252
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1253
|
+
mode: TransactionWriteMode::Replace,
|
|
1007
1254
|
rows: vec![
|
|
1008
1255
|
state_row("sql2-key-a", "first"),
|
|
1009
1256
|
state_row("sql2-key-b", "only"),
|
|
@@ -1011,8 +1258,8 @@ mod tests {
|
|
|
1011
1258
|
})
|
|
1012
1259
|
.expect("initial rows should stage");
|
|
1013
1260
|
staged_writes
|
|
1014
|
-
.stage_write(
|
|
1015
|
-
mode:
|
|
1261
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1262
|
+
mode: TransactionWriteMode::Replace,
|
|
1016
1263
|
rows: vec![state_row("sql2-key-a", "second")],
|
|
1017
1264
|
})
|
|
1018
1265
|
.expect("staging rows should succeed");
|
|
@@ -1022,12 +1269,18 @@ mod tests {
|
|
|
1022
1269
|
assert_eq!(drained.state_rows.len(), 2);
|
|
1023
1270
|
assert!(drained.state_rows.iter().any(|row| {
|
|
1024
1271
|
row.entity_id == crate::entity_identity::EntityIdentity::single("sql2-key-a")
|
|
1025
|
-
&& row
|
|
1272
|
+
&& row
|
|
1273
|
+
.snapshot
|
|
1274
|
+
.as_ref()
|
|
1275
|
+
.map(|snapshot| snapshot.normalized.as_ref())
|
|
1026
1276
|
== Some("{\"key\":\"sql2-key-a\",\"value\":\"second\"}")
|
|
1027
1277
|
}));
|
|
1028
1278
|
assert!(drained.state_rows.iter().any(|row| {
|
|
1029
1279
|
row.entity_id == crate::entity_identity::EntityIdentity::single("sql2-key-b")
|
|
1030
|
-
&& row
|
|
1280
|
+
&& row
|
|
1281
|
+
.snapshot
|
|
1282
|
+
.as_ref()
|
|
1283
|
+
.map(|snapshot| snapshot.normalized.as_ref())
|
|
1031
1284
|
== Some("{\"key\":\"sql2-key-b\",\"value\":\"only\"}")
|
|
1032
1285
|
}));
|
|
1033
1286
|
}
|
|
@@ -1037,10 +1290,10 @@ mod tests {
|
|
|
1037
1290
|
let staged_writes = test_staged_writes();
|
|
1038
1291
|
|
|
1039
1292
|
staged_writes
|
|
1040
|
-
.stage_write(
|
|
1041
|
-
mode:
|
|
1293
|
+
.stage_write(PreparedTransactionWrite::RowsWithFileData {
|
|
1294
|
+
mode: TransactionWriteMode::Replace,
|
|
1042
1295
|
rows: vec![state_row("file-readme", "descriptor")],
|
|
1043
|
-
file_data: vec![
|
|
1296
|
+
file_data: vec![TransactionFileData {
|
|
1044
1297
|
file_id: "file-readme".to_string(),
|
|
1045
1298
|
version_id: "global".to_string(),
|
|
1046
1299
|
untracked: true,
|
|
@@ -1063,8 +1316,8 @@ mod tests {
|
|
|
1063
1316
|
let staged_writes = test_staged_writes();
|
|
1064
1317
|
|
|
1065
1318
|
staged_writes
|
|
1066
|
-
.stage_write(
|
|
1067
|
-
mode:
|
|
1319
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1320
|
+
mode: TransactionWriteMode::Replace,
|
|
1068
1321
|
rows: vec![state_row("tracked-key", "value").with_tracked()],
|
|
1069
1322
|
})
|
|
1070
1323
|
.expect("tracked global row should stage");
|
|
@@ -1076,7 +1329,7 @@ mod tests {
|
|
|
1076
1329
|
.expect("global commit members should exist");
|
|
1077
1330
|
assert_eq!(
|
|
1078
1331
|
members.change_ids.iter().cloned().collect::<Vec<_>>(),
|
|
1079
|
-
vec!["test-
|
|
1332
|
+
vec!["test-change-id".to_string()]
|
|
1080
1333
|
);
|
|
1081
1334
|
}
|
|
1082
1335
|
|
|
@@ -1085,8 +1338,8 @@ mod tests {
|
|
|
1085
1338
|
let staged_writes = test_staged_writes();
|
|
1086
1339
|
|
|
1087
1340
|
staged_writes
|
|
1088
|
-
.stage_write(
|
|
1089
|
-
mode:
|
|
1341
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1342
|
+
mode: TransactionWriteMode::Replace,
|
|
1090
1343
|
rows: vec![state_row("untracked-key", "value")],
|
|
1091
1344
|
})
|
|
1092
1345
|
.expect("untracked row should stage");
|
|
@@ -1100,16 +1353,16 @@ mod tests {
|
|
|
1100
1353
|
let staged_writes = test_staged_writes();
|
|
1101
1354
|
|
|
1102
1355
|
staged_writes
|
|
1103
|
-
.stage_write(
|
|
1104
|
-
mode:
|
|
1356
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1357
|
+
mode: TransactionWriteMode::Replace,
|
|
1105
1358
|
rows: vec![state_row("overwrite-key", "first")
|
|
1106
1359
|
.with_tracked()
|
|
1107
1360
|
.with_change_id("change-first")],
|
|
1108
1361
|
})
|
|
1109
1362
|
.expect("initial tracked row should stage");
|
|
1110
1363
|
staged_writes
|
|
1111
|
-
.stage_write(
|
|
1112
|
-
mode:
|
|
1364
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1365
|
+
mode: TransactionWriteMode::Replace,
|
|
1113
1366
|
rows: vec![state_row("overwrite-key", "second")
|
|
1114
1367
|
.with_tracked()
|
|
1115
1368
|
.with_change_id("change-second")],
|
|
@@ -1128,12 +1381,12 @@ mod tests {
|
|
|
1128
1381
|
}
|
|
1129
1382
|
|
|
1130
1383
|
#[tokio::test]
|
|
1131
|
-
async fn
|
|
1384
|
+
async fn staged_writes_keep_tracked_and_untracked_domains_separate() {
|
|
1132
1385
|
let staged_writes = test_staged_writes();
|
|
1133
1386
|
|
|
1134
1387
|
staged_writes
|
|
1135
|
-
.stage_write(
|
|
1136
|
-
mode:
|
|
1388
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1389
|
+
mode: TransactionWriteMode::Replace,
|
|
1137
1390
|
rows: vec![
|
|
1138
1391
|
state_row("tracked-to-untracked-key", "tracked")
|
|
1139
1392
|
.with_tracked()
|
|
@@ -1145,12 +1398,23 @@ mod tests {
|
|
|
1145
1398
|
.expect("untracked overwrite should stage");
|
|
1146
1399
|
|
|
1147
1400
|
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1148
|
-
assert_eq!(drained.state_rows.len(),
|
|
1401
|
+
assert_eq!(drained.state_rows.len(), 2);
|
|
1402
|
+
assert!(drained
|
|
1403
|
+
.state_rows
|
|
1404
|
+
.iter()
|
|
1405
|
+
.any(|row| { row.change_id.as_deref() == Some("change-tracked") && !row.untracked }));
|
|
1406
|
+
assert!(drained
|
|
1407
|
+
.state_rows
|
|
1408
|
+
.iter()
|
|
1409
|
+
.any(|row| { row.change_id.as_deref() == Some("change-untracked") && row.untracked }));
|
|
1410
|
+
let members = drained
|
|
1411
|
+
.commit_members_by_version
|
|
1412
|
+
.get("global")
|
|
1413
|
+
.expect("tracked commit member should remain in tracked domain");
|
|
1149
1414
|
assert_eq!(
|
|
1150
|
-
|
|
1151
|
-
|
|
1415
|
+
members.change_ids.iter().cloned().collect::<Vec<_>>(),
|
|
1416
|
+
vec!["change-tracked".to_string()]
|
|
1152
1417
|
);
|
|
1153
|
-
assert!(drained.commit_members_by_version.is_empty());
|
|
1154
1418
|
}
|
|
1155
1419
|
|
|
1156
1420
|
#[tokio::test]
|
|
@@ -1158,8 +1422,8 @@ mod tests {
|
|
|
1158
1422
|
let staged_writes = test_staged_writes();
|
|
1159
1423
|
|
|
1160
1424
|
let error = staged_writes
|
|
1161
|
-
.stage_write(
|
|
1162
|
-
mode:
|
|
1425
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1426
|
+
mode: TransactionWriteMode::Replace,
|
|
1163
1427
|
rows: vec![
|
|
1164
1428
|
state_row("duplicate-present-key", "first"),
|
|
1165
1429
|
state_row("duplicate-present-key", "second"),
|
|
@@ -1174,13 +1438,39 @@ mod tests {
|
|
|
1174
1438
|
);
|
|
1175
1439
|
}
|
|
1176
1440
|
|
|
1441
|
+
#[tokio::test]
|
|
1442
|
+
async fn staged_writes_insert_keeps_tracked_and_untracked_rows_as_distinct_identities() {
|
|
1443
|
+
let staged_writes = test_staged_writes();
|
|
1444
|
+
|
|
1445
|
+
staged_writes
|
|
1446
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1447
|
+
mode: TransactionWriteMode::Insert,
|
|
1448
|
+
rows: vec![
|
|
1449
|
+
state_row("shared-domain-key", "tracked").with_tracked(),
|
|
1450
|
+
state_row("shared-domain-key", "untracked"),
|
|
1451
|
+
],
|
|
1452
|
+
})
|
|
1453
|
+
.expect("tracked and untracked rows are distinct domain identities");
|
|
1454
|
+
|
|
1455
|
+
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1456
|
+
assert_eq!(drained.state_rows.len(), 2);
|
|
1457
|
+
assert!(drained.state_rows.iter().any(|row| {
|
|
1458
|
+
row.entity_id == crate::entity_identity::EntityIdentity::single("shared-domain-key")
|
|
1459
|
+
&& !row.untracked
|
|
1460
|
+
}));
|
|
1461
|
+
assert!(drained.state_rows.iter().any(|row| {
|
|
1462
|
+
row.entity_id == crate::entity_identity::EntityIdentity::single("shared-domain-key")
|
|
1463
|
+
&& row.untracked
|
|
1464
|
+
}));
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1177
1467
|
#[tokio::test]
|
|
1178
1468
|
async fn staged_writes_track_active_version_members_separately() {
|
|
1179
1469
|
let staged_writes = test_staged_writes();
|
|
1180
1470
|
|
|
1181
1471
|
staged_writes
|
|
1182
|
-
.stage_write(
|
|
1183
|
-
mode:
|
|
1472
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1473
|
+
mode: TransactionWriteMode::Replace,
|
|
1184
1474
|
rows: vec![state_row("active-version-key", "value")
|
|
1185
1475
|
.with_tracked()
|
|
1186
1476
|
.with_version("version-a")],
|
|
@@ -1194,7 +1484,7 @@ mod tests {
|
|
|
1194
1484
|
.expect("active-version commit members should exist");
|
|
1195
1485
|
assert_eq!(
|
|
1196
1486
|
members.change_ids.iter().cloned().collect::<Vec<_>>(),
|
|
1197
|
-
vec!["test-
|
|
1487
|
+
vec!["test-change-id".to_string()]
|
|
1198
1488
|
);
|
|
1199
1489
|
}
|
|
1200
1490
|
|
|
@@ -1203,8 +1493,8 @@ mod tests {
|
|
|
1203
1493
|
let staged_writes = test_staged_writes();
|
|
1204
1494
|
|
|
1205
1495
|
let error = staged_writes
|
|
1206
|
-
.stage_write(
|
|
1207
|
-
mode:
|
|
1496
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1497
|
+
mode: TransactionWriteMode::Replace,
|
|
1208
1498
|
rows: vec![{
|
|
1209
1499
|
let mut row = state_row("invalid-global-key", "value");
|
|
1210
1500
|
row.version_id = "version-a".to_string();
|
|
@@ -1223,16 +1513,14 @@ mod tests {
|
|
|
1223
1513
|
let staged_writes = test_staged_writes();
|
|
1224
1514
|
|
|
1225
1515
|
staged_writes
|
|
1226
|
-
.stage_write(
|
|
1227
|
-
mode:
|
|
1228
|
-
rows: vec![
|
|
1229
|
-
state_row("shared-entity", "other-schema-version").with_schema_version("2")
|
|
1230
|
-
],
|
|
1516
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1517
|
+
mode: TransactionWriteMode::Replace,
|
|
1518
|
+
rows: vec![state_row("shared-entity", "base")],
|
|
1231
1519
|
})
|
|
1232
1520
|
.expect("initial same-identity row should stage");
|
|
1233
1521
|
staged_writes
|
|
1234
|
-
.stage_write(
|
|
1235
|
-
mode:
|
|
1522
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1523
|
+
mode: TransactionWriteMode::Replace,
|
|
1236
1524
|
rows: vec![
|
|
1237
1525
|
state_row("shared-entity", "base"),
|
|
1238
1526
|
state_row("shared-entity", "other-version").with_version("version-b"),
|
|
@@ -1246,18 +1534,30 @@ mod tests {
|
|
|
1246
1534
|
let overlay = staged_writes
|
|
1247
1535
|
.staging_overlay()
|
|
1248
1536
|
.expect("overlay should build from staged rows");
|
|
1249
|
-
let rows = overlay
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1537
|
+
let rows = overlay
|
|
1538
|
+
.scan(&LiveStateScanRequest {
|
|
1539
|
+
filter: LiveStateFilter {
|
|
1540
|
+
entity_ids: vec![crate::entity_identity::EntityIdentity::single(
|
|
1541
|
+
"shared-entity",
|
|
1542
|
+
)],
|
|
1543
|
+
include_tombstones: true,
|
|
1544
|
+
..LiveStateFilter::default()
|
|
1545
|
+
},
|
|
1546
|
+
..LiveStateScanRequest::default()
|
|
1547
|
+
})
|
|
1548
|
+
.expect("overlay scan should succeed");
|
|
1259
1549
|
|
|
1260
|
-
assert_eq!(rows.len(),
|
|
1550
|
+
assert_eq!(rows.len(), 5);
|
|
1551
|
+
assert_eq!(
|
|
1552
|
+
rows.iter()
|
|
1553
|
+
.filter(|row| row.entity_id
|
|
1554
|
+
== crate::entity_identity::EntityIdentity::single("shared-entity")
|
|
1555
|
+
&& row.version_id == "global"
|
|
1556
|
+
&& row.schema_key == "lix_key_value"
|
|
1557
|
+
&& row.file_id.is_none())
|
|
1558
|
+
.count(),
|
|
1559
|
+
2
|
|
1560
|
+
);
|
|
1261
1561
|
assert!(rows.iter().any(|row| {
|
|
1262
1562
|
row.snapshot_content.as_deref()
|
|
1263
1563
|
== Some("{\"key\":\"shared-entity\",\"value\":\"tracked\"}")
|
|
@@ -1265,30 +1565,24 @@ mod tests {
|
|
|
1265
1565
|
}
|
|
1266
1566
|
|
|
1267
1567
|
#[tokio::test]
|
|
1268
|
-
async fn
|
|
1568
|
+
async fn staged_writes_use_injected_function_provider_for_commit_metadata() {
|
|
1269
1569
|
let staged_writes = test_staged_writes();
|
|
1270
1570
|
|
|
1271
1571
|
staged_writes
|
|
1272
|
-
.stage_write(
|
|
1273
|
-
mode:
|
|
1572
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1573
|
+
mode: TransactionWriteMode::Replace,
|
|
1274
1574
|
rows: vec![state_row("sql2-functions-key", "value").with_tracked()],
|
|
1275
1575
|
})
|
|
1276
1576
|
.expect("staging rows should succeed");
|
|
1277
1577
|
|
|
1278
1578
|
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
);
|
|
1284
|
-
assert_eq!(
|
|
1285
|
-
|
|
1286
|
-
"test-timestamp-1"
|
|
1287
|
-
);
|
|
1288
|
-
assert_eq!(
|
|
1289
|
-
drained.state_rows[0].updated_at.as_str(),
|
|
1290
|
-
"test-timestamp-1"
|
|
1291
|
-
);
|
|
1579
|
+
let members = drained
|
|
1580
|
+
.commit_members_by_version
|
|
1581
|
+
.get("global")
|
|
1582
|
+
.expect("global commit members should exist");
|
|
1583
|
+
assert_eq!(members.commit_id, "test-uuid-1");
|
|
1584
|
+
assert_eq!(members.commit_change_id, "test-uuid-2");
|
|
1585
|
+
assert_eq!(members.created_at, "test-timestamp-1");
|
|
1292
1586
|
}
|
|
1293
1587
|
|
|
1294
1588
|
#[tokio::test]
|
|
@@ -1296,8 +1590,8 @@ mod tests {
|
|
|
1296
1590
|
let staged_writes = test_staged_writes();
|
|
1297
1591
|
|
|
1298
1592
|
staged_writes
|
|
1299
|
-
.stage_write(
|
|
1300
|
-
mode:
|
|
1593
|
+
.stage_write(PreparedTransactionWrite::Rows {
|
|
1594
|
+
mode: TransactionWriteMode::Replace,
|
|
1301
1595
|
rows: vec![state_row("tracked-commit-key", "value").with_tracked()],
|
|
1302
1596
|
})
|
|
1303
1597
|
.expect("tracked row should stage");
|
|
@@ -1306,7 +1600,7 @@ mod tests {
|
|
|
1306
1600
|
assert_eq!(drained.state_rows.len(), 1);
|
|
1307
1601
|
assert_eq!(
|
|
1308
1602
|
drained.state_rows[0].commit_id.as_deref(),
|
|
1309
|
-
Some("test-uuid-
|
|
1603
|
+
Some("test-uuid-1")
|
|
1310
1604
|
);
|
|
1311
1605
|
assert_eq!(
|
|
1312
1606
|
drained
|
|
@@ -1314,15 +1608,14 @@ mod tests {
|
|
|
1314
1608
|
.get("global")
|
|
1315
1609
|
.expect("global commit members should exist")
|
|
1316
1610
|
.commit_id,
|
|
1317
|
-
"test-uuid-
|
|
1611
|
+
"test-uuid-1"
|
|
1318
1612
|
);
|
|
1319
1613
|
}
|
|
1320
1614
|
|
|
1321
|
-
fn test_staged_writes() ->
|
|
1322
|
-
|
|
1323
|
-
TestFunctionProvider::default()
|
|
1324
|
-
)
|
|
1325
|
-
as Box<dyn FunctionProvider + Send>))
|
|
1615
|
+
fn test_staged_writes() -> Arc<TransactionWriteBuffer> {
|
|
1616
|
+
Arc::new(TransactionWriteBuffer::new(SharedFunctionProvider::new(
|
|
1617
|
+
Box::new(TestFunctionProvider::default()) as Box<dyn FunctionProvider + Send>,
|
|
1618
|
+
)))
|
|
1326
1619
|
}
|
|
1327
1620
|
|
|
1328
1621
|
#[derive(Default)]
|
|
@@ -1343,17 +1636,23 @@ mod tests {
|
|
|
1343
1636
|
}
|
|
1344
1637
|
}
|
|
1345
1638
|
|
|
1346
|
-
fn state_row(key: &str, value: &str) ->
|
|
1347
|
-
|
|
1348
|
-
|
|
1639
|
+
fn state_row(key: &str, value: &str) -> PreparedStateRow {
|
|
1640
|
+
let snapshot = stage_json_from_value(
|
|
1641
|
+
TransactionJson::from_value_for_test(serde_json::json!({ "key": key, "value": value })),
|
|
1642
|
+
"test staged row snapshot_content",
|
|
1643
|
+
)
|
|
1644
|
+
.expect("test snapshot should prepare");
|
|
1645
|
+
PreparedStateRow {
|
|
1646
|
+
schema_plan_id: SchemaPlanId::for_test(0),
|
|
1647
|
+
facts: crate::transaction::types::PreparedRowFacts::default(),
|
|
1648
|
+
entity_id: crate::entity_identity::EntityIdentity::single(key),
|
|
1349
1649
|
schema_key: "lix_key_value".to_string(),
|
|
1350
1650
|
file_id: None,
|
|
1351
|
-
|
|
1651
|
+
snapshot: Some(snapshot),
|
|
1352
1652
|
metadata: None,
|
|
1353
1653
|
origin: None,
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
updated_at: None,
|
|
1654
|
+
created_at: "test-created-at".to_string(),
|
|
1655
|
+
updated_at: "test-updated-at".to_string(),
|
|
1357
1656
|
global: true,
|
|
1358
1657
|
change_id: None,
|
|
1359
1658
|
commit_id: None,
|
|
@@ -1362,11 +1661,10 @@ mod tests {
|
|
|
1362
1661
|
}
|
|
1363
1662
|
}
|
|
1364
1663
|
|
|
1365
|
-
fn tombstone_row(key: &str) ->
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
}
|
|
1664
|
+
fn tombstone_row(key: &str) -> PreparedStateRow {
|
|
1665
|
+
let mut row = state_row(key, "deleted");
|
|
1666
|
+
row.snapshot = None;
|
|
1667
|
+
row
|
|
1370
1668
|
}
|
|
1371
1669
|
|
|
1372
1670
|
fn exact_request_for_key(key: &str) -> LiveStateRowRequest {
|
|
@@ -1394,24 +1692,18 @@ mod tests {
|
|
|
1394
1692
|
|
|
1395
1693
|
trait StateRowTestExt {
|
|
1396
1694
|
fn with_schema(self, schema_key: &str) -> Self;
|
|
1397
|
-
fn with_schema_version(self, schema_version: &str) -> Self;
|
|
1398
1695
|
fn with_file_id(self, file_id: &str) -> Self;
|
|
1399
1696
|
fn with_tracked(self) -> Self;
|
|
1400
1697
|
fn with_version(self, version_id: &str) -> Self;
|
|
1401
1698
|
fn with_change_id(self, change_id: &str) -> Self;
|
|
1402
1699
|
}
|
|
1403
1700
|
|
|
1404
|
-
impl StateRowTestExt for
|
|
1701
|
+
impl StateRowTestExt for PreparedStateRow {
|
|
1405
1702
|
fn with_schema(mut self, schema_key: &str) -> Self {
|
|
1406
1703
|
self.schema_key = schema_key.to_string();
|
|
1407
1704
|
self
|
|
1408
1705
|
}
|
|
1409
1706
|
|
|
1410
|
-
fn with_schema_version(mut self, schema_version: &str) -> Self {
|
|
1411
|
-
self.schema_version = schema_version.to_string();
|
|
1412
|
-
self
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
1707
|
fn with_file_id(mut self, file_id: &str) -> Self {
|
|
1416
1708
|
self.file_id = Some(file_id.to_string());
|
|
1417
1709
|
self
|
|
@@ -1419,6 +1711,9 @@ mod tests {
|
|
|
1419
1711
|
|
|
1420
1712
|
fn with_tracked(mut self) -> Self {
|
|
1421
1713
|
self.untracked = false;
|
|
1714
|
+
if self.change_id.is_none() {
|
|
1715
|
+
self.change_id = Some("test-change-id".to_string());
|
|
1716
|
+
}
|
|
1422
1717
|
self
|
|
1423
1718
|
}
|
|
1424
1719
|
|