@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,32 +1,38 @@
|
|
|
1
|
-
use std::collections::BTreeSet;
|
|
1
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
2
2
|
use std::sync::Arc;
|
|
3
3
|
|
|
4
4
|
use async_trait::async_trait;
|
|
5
5
|
use serde_json::Value as JsonValue;
|
|
6
6
|
|
|
7
7
|
use crate::binary_cas::{BinaryCasContext, BlobBytesBatch, BlobHash};
|
|
8
|
-
use crate::
|
|
8
|
+
use crate::catalog::CatalogContext;
|
|
9
9
|
use crate::commit_graph::{CommitGraphContext, CommitGraphStoreReader};
|
|
10
|
+
use crate::commit_store::CommitStoreContext;
|
|
11
|
+
use crate::domain::Domain;
|
|
10
12
|
use crate::entity_identity::EntityIdentity;
|
|
11
13
|
use crate::functions::{FunctionContext, FunctionProviderHandle};
|
|
12
|
-
use crate::json_store::JsonStoreContext;
|
|
13
14
|
use crate::live_state::{
|
|
14
|
-
LiveStateContext,
|
|
15
|
+
LiveStateContext, LiveStateRowRequest, LiveStateScanRequest, MaterializedLiveStateRow,
|
|
15
16
|
};
|
|
16
|
-
use crate::schema_registry::SchemaRegistry;
|
|
17
17
|
use crate::session::{SessionMode, WORKSPACE_VERSION_KEY};
|
|
18
18
|
use crate::sql2::SqlWriteExecutionContext;
|
|
19
19
|
use crate::storage::{StorageContext, StorageWriteSet, StorageWriteTransaction};
|
|
20
20
|
use crate::tracked_state::{TrackedStateContext, TrackedStateStoreReader};
|
|
21
21
|
use crate::transaction::commit;
|
|
22
22
|
use crate::transaction::live_state_overlay::overlay_scan_rows;
|
|
23
|
-
use crate::transaction::normalization::
|
|
23
|
+
use crate::transaction::normalization::{
|
|
24
|
+
normalize_transaction_write_row, remember_pending_registered_schema,
|
|
25
|
+
NormalizedTransactionWriteRow, REGISTERED_SCHEMA_KEY,
|
|
26
|
+
};
|
|
27
|
+
use crate::transaction::prepare_version_ref_row;
|
|
24
28
|
use crate::transaction::schema_resolver::TransactionSchemaResolver;
|
|
25
|
-
use crate::transaction::staging::{
|
|
29
|
+
use crate::transaction::staging::{PreparedWriteSet, TransactionWriteBuffer};
|
|
26
30
|
use crate::transaction::types::{
|
|
27
|
-
|
|
31
|
+
stage_json_from_value, PreparedAdoptedStateRow, PreparedRowFacts, PreparedStateRow,
|
|
32
|
+
PreparedTransactionWrite, TransactionAdoptedChange, TransactionFileData, TransactionJson,
|
|
33
|
+
TransactionWrite, TransactionWriteMode, TransactionWriteOutcome, TransactionWriteRow,
|
|
28
34
|
};
|
|
29
|
-
use crate::transaction::validation::{
|
|
35
|
+
use crate::transaction::validation::{validate_prepared_writes, TransactionValidationInput};
|
|
30
36
|
use crate::version::{VersionContext, VersionRefReader};
|
|
31
37
|
use crate::GLOBAL_VERSION_ID;
|
|
32
38
|
use crate::{LixError, NullableKeyFilter};
|
|
@@ -34,14 +40,14 @@ use crate::{LixError, NullableKeyFilter};
|
|
|
34
40
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
35
41
|
pub(crate) struct TransactionCommitOutcome;
|
|
36
42
|
|
|
37
|
-
/// One execution-scoped transaction capability for
|
|
43
|
+
/// One execution-scoped transaction capability for engine write paths.
|
|
38
44
|
///
|
|
39
45
|
/// This is intentionally not a session-wide kitchen sink. It owns the backend
|
|
40
46
|
/// write transaction for one `SessionContext::execute(...)` call and projects
|
|
41
|
-
///
|
|
47
|
+
/// accepted SQL/provider writes back into the SQL DAG through an engine-local live-state
|
|
42
48
|
/// overlay.
|
|
43
49
|
///
|
|
44
|
-
/// Transaction invariant: this is the capability for
|
|
50
|
+
/// Transaction invariant: this is the capability for engine operations
|
|
45
51
|
/// that may write. Write-relevant reads must be exposed from this transaction,
|
|
46
52
|
/// after the backend write transaction has begun, rather than from session-level
|
|
47
53
|
/// helpers.
|
|
@@ -50,10 +56,10 @@ pub(crate) struct Transaction {
|
|
|
50
56
|
live_state: Arc<LiveStateContext>,
|
|
51
57
|
tracked_state: Arc<TrackedStateContext>,
|
|
52
58
|
binary_cas: Arc<BinaryCasContext>,
|
|
53
|
-
|
|
59
|
+
commit_store: Arc<CommitStoreContext>,
|
|
54
60
|
version_ctx: Arc<VersionContext>,
|
|
55
61
|
schema_resolver: TransactionSchemaResolver,
|
|
56
|
-
staged_writes: Arc<
|
|
62
|
+
staged_writes: Arc<TransactionWriteBuffer>,
|
|
57
63
|
storage_transaction: Box<dyn StorageWriteTransaction + Send + Sync + 'static>,
|
|
58
64
|
visible_schemas: Vec<JsonValue>,
|
|
59
65
|
functions: FunctionProviderHandle,
|
|
@@ -68,9 +74,9 @@ impl Transaction {
|
|
|
68
74
|
live_state: Arc<LiveStateContext>,
|
|
69
75
|
tracked_state: Arc<TrackedStateContext>,
|
|
70
76
|
binary_cas: Arc<BinaryCasContext>,
|
|
71
|
-
|
|
77
|
+
commit_store: Arc<CommitStoreContext>,
|
|
72
78
|
version_ctx: Arc<VersionContext>,
|
|
73
|
-
|
|
79
|
+
catalog_context: Arc<CatalogContext>,
|
|
74
80
|
) -> Result<OpenTransaction, LixError> {
|
|
75
81
|
let mut storage_transaction = storage.begin_write_transaction().await?;
|
|
76
82
|
let setup_result = async {
|
|
@@ -88,8 +94,17 @@ impl Transaction {
|
|
|
88
94
|
let functions = runtime_functions.provider();
|
|
89
95
|
let visible_schemas = {
|
|
90
96
|
let visible_live_state = live_state.reader(storage_transaction.as_mut());
|
|
91
|
-
|
|
92
|
-
.
|
|
97
|
+
catalog_context
|
|
98
|
+
.schema_jsons_for_sql_read_planning(&visible_live_state, &active_version_id)
|
|
99
|
+
.await?
|
|
100
|
+
};
|
|
101
|
+
let schema_facts = {
|
|
102
|
+
let visible_live_state = live_state.reader(storage_transaction.as_mut());
|
|
103
|
+
catalog_context
|
|
104
|
+
.schema_facts_for_domain(
|
|
105
|
+
&visible_live_state,
|
|
106
|
+
&Domain::schema_catalog(active_version_id.clone(), true),
|
|
107
|
+
)
|
|
93
108
|
.await?
|
|
94
109
|
};
|
|
95
110
|
Ok::<_, LixError>((
|
|
@@ -97,28 +112,31 @@ impl Transaction {
|
|
|
97
112
|
runtime_functions,
|
|
98
113
|
functions,
|
|
99
114
|
visible_schemas,
|
|
115
|
+
schema_facts,
|
|
100
116
|
))
|
|
101
117
|
}
|
|
102
118
|
.await;
|
|
103
|
-
let (active_version_id, runtime_functions, functions, visible_schemas) =
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let mut schema_resolver = TransactionSchemaResolver::new(
|
|
112
|
-
schema_resolver
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
let (active_version_id, runtime_functions, functions, visible_schemas, schema_facts) =
|
|
120
|
+
match setup_result {
|
|
121
|
+
Ok(result) => result,
|
|
122
|
+
Err(error) => {
|
|
123
|
+
let _ = storage_transaction.rollback().await;
|
|
124
|
+
return Err(error);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
let mut schema_resolver = TransactionSchemaResolver::new(catalog_context);
|
|
128
|
+
schema_resolver.remember_schema_facts(
|
|
129
|
+
&Domain::schema_catalog(active_version_id.clone(), true),
|
|
130
|
+
schema_facts,
|
|
131
|
+
);
|
|
132
|
+
let staged_writes = Arc::new(TransactionWriteBuffer::new(functions.clone()));
|
|
115
133
|
Ok(OpenTransaction {
|
|
116
134
|
transaction: Self {
|
|
117
135
|
active_version_id,
|
|
118
136
|
live_state,
|
|
119
137
|
tracked_state,
|
|
120
138
|
binary_cas,
|
|
121
|
-
|
|
139
|
+
commit_store,
|
|
122
140
|
version_ctx,
|
|
123
141
|
schema_resolver,
|
|
124
142
|
staged_writes,
|
|
@@ -130,34 +148,36 @@ impl Transaction {
|
|
|
130
148
|
})
|
|
131
149
|
}
|
|
132
150
|
|
|
133
|
-
/// Commits
|
|
151
|
+
/// Commits prepared writes, runtime function state, and the backend transaction.
|
|
134
152
|
///
|
|
135
|
-
/// Commit owns the execution boundary:
|
|
136
|
-
///
|
|
137
|
-
///
|
|
153
|
+
/// Commit owns the execution boundary: prepared rows become commit-store
|
|
154
|
+
/// facts, version-ref updates, and visible live_state rows before the
|
|
155
|
+
/// backend transaction is committed.
|
|
138
156
|
pub(crate) async fn commit(
|
|
139
157
|
mut self,
|
|
140
158
|
runtime_functions: &FunctionContext,
|
|
141
159
|
) -> Result<TransactionCommitOutcome, LixError> {
|
|
142
|
-
let
|
|
143
|
-
Ok(
|
|
160
|
+
let prepared_writes = match self.staged_writes.drain() {
|
|
161
|
+
Ok(prepared_writes) => prepared_writes,
|
|
144
162
|
Err(error) => {
|
|
145
163
|
let _ = self.storage_transaction.rollback().await;
|
|
146
164
|
return Err(error);
|
|
147
165
|
}
|
|
148
166
|
};
|
|
149
|
-
if let Err(error) = self
|
|
167
|
+
if let Err(error) = self
|
|
168
|
+
.validate_prepared_writes_by_version(&prepared_writes)
|
|
169
|
+
.await
|
|
170
|
+
{
|
|
150
171
|
let _ = self.storage_transaction.rollback().await;
|
|
151
172
|
return Err(error);
|
|
152
173
|
}
|
|
153
|
-
if let Err(error) = commit::
|
|
174
|
+
if let Err(error) = commit::commit_prepared_writes(
|
|
154
175
|
&self.binary_cas,
|
|
155
|
-
&self.
|
|
156
|
-
&self.live_state,
|
|
176
|
+
&self.commit_store,
|
|
157
177
|
self.version_ctx.as_ref(),
|
|
158
178
|
Some(runtime_functions),
|
|
159
179
|
self.storage_transaction.as_mut(),
|
|
160
|
-
|
|
180
|
+
prepared_writes,
|
|
161
181
|
)
|
|
162
182
|
.await
|
|
163
183
|
{
|
|
@@ -181,75 +201,226 @@ impl Transaction {
|
|
|
181
201
|
/// Stages one decoded write batch into this transaction.
|
|
182
202
|
///
|
|
183
203
|
/// This is the programmatic write entrypoint used by non-SQL APIs. The
|
|
184
|
-
/// transaction still owns
|
|
185
|
-
/// so generated timestamps, change ids, commit ids, and
|
|
186
|
-
/// stay in one place.
|
|
204
|
+
/// transaction still owns preparation from `TransactionWriteRow` into
|
|
205
|
+
/// `PreparedStateRow`, so generated timestamps, change ids, commit ids, and
|
|
206
|
+
/// commit membership stay in one place.
|
|
187
207
|
#[allow(dead_code)]
|
|
188
208
|
pub(crate) async fn stage_write(
|
|
189
209
|
&mut self,
|
|
190
|
-
write:
|
|
191
|
-
) -> Result<
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
write: TransactionWrite,
|
|
211
|
+
) -> Result<TransactionWriteOutcome, LixError> {
|
|
212
|
+
require_valid_transaction_write_storage_scopes(&write)?;
|
|
213
|
+
#[cfg(feature = "storage-benches")]
|
|
214
|
+
{
|
|
215
|
+
crate::storage_bench::record_transaction_rows_staged(transaction_write_row_count(
|
|
216
|
+
&write,
|
|
217
|
+
));
|
|
218
|
+
crate::storage_bench::record_transaction_untracked_rows(
|
|
219
|
+
transaction_write_untracked_row_count(&write),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
self.require_existing_transaction_write_version_ids(&write)
|
|
194
223
|
.await?;
|
|
195
|
-
let write = self.
|
|
224
|
+
let write = self.prepare_transaction_write(write).await?;
|
|
196
225
|
self.staged_writes.stage_write(write)
|
|
197
226
|
}
|
|
198
227
|
|
|
199
|
-
async fn
|
|
228
|
+
async fn prepare_transaction_write(
|
|
229
|
+
&mut self,
|
|
230
|
+
write: TransactionWrite,
|
|
231
|
+
) -> Result<PreparedTransactionWrite, LixError> {
|
|
200
232
|
Ok(match write {
|
|
201
|
-
|
|
233
|
+
TransactionWrite::Rows { mode, rows } => PreparedTransactionWrite::Rows {
|
|
202
234
|
mode,
|
|
203
|
-
rows: self.
|
|
235
|
+
rows: self.prepare_transaction_rows(rows).await?,
|
|
204
236
|
},
|
|
205
|
-
|
|
237
|
+
TransactionWrite::RowsWithFileData {
|
|
206
238
|
mode,
|
|
207
239
|
rows,
|
|
208
240
|
file_data,
|
|
209
241
|
count,
|
|
210
|
-
} =>
|
|
242
|
+
} => PreparedTransactionWrite::RowsWithFileData {
|
|
211
243
|
mode,
|
|
212
|
-
rows: self.
|
|
244
|
+
rows: self.prepare_transaction_rows(rows).await?,
|
|
213
245
|
file_data,
|
|
214
246
|
count,
|
|
215
247
|
},
|
|
216
|
-
|
|
248
|
+
TransactionWrite::AdoptedChanges { changes } => {
|
|
249
|
+
PreparedTransactionWrite::AdoptedChanges {
|
|
250
|
+
rows: self.prepare_adopted_changes(changes).await?,
|
|
251
|
+
}
|
|
252
|
+
}
|
|
217
253
|
})
|
|
218
254
|
}
|
|
219
255
|
|
|
220
|
-
async fn
|
|
256
|
+
async fn prepare_transaction_rows(
|
|
221
257
|
&mut self,
|
|
222
|
-
rows: Vec<
|
|
223
|
-
) -> Result<Vec<
|
|
224
|
-
let
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
258
|
+
rows: Vec<TransactionWriteRow>,
|
|
259
|
+
) -> Result<Vec<PreparedStateRow>, LixError> {
|
|
260
|
+
let row_count = rows.len();
|
|
261
|
+
let staged = self.staged_writes.staging_overlay()?;
|
|
262
|
+
let live_state = self.live_state.reader(self.storage_transaction.as_mut());
|
|
263
|
+
let mut rows_by_scope = BTreeMap::<Domain, Vec<(usize, TransactionWriteRow)>>::new();
|
|
264
|
+
for (index, row) in rows.into_iter().enumerate() {
|
|
265
|
+
rows_by_scope
|
|
266
|
+
.entry(Domain::schema_catalog(
|
|
267
|
+
row.schema_scope_version_id().to_string(),
|
|
268
|
+
row.untracked,
|
|
269
|
+
))
|
|
270
|
+
.or_default()
|
|
271
|
+
.push((index, row));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let mut prepared_rows = Vec::with_capacity(row_count);
|
|
275
|
+
prepared_rows.resize_with(row_count, || None);
|
|
276
|
+
for (domain, rows) in rows_by_scope {
|
|
277
|
+
let functions = self.functions.clone();
|
|
229
278
|
let catalog = self
|
|
230
279
|
.schema_resolver
|
|
231
|
-
.catalog_for_row_normalization(&live_state, staged, &
|
|
280
|
+
.catalog_for_row_normalization(&live_state, &staged, &domain)
|
|
232
281
|
.await?;
|
|
233
|
-
|
|
234
|
-
|
|
282
|
+
for (_, row) in &rows {
|
|
283
|
+
if row.schema_key != REGISTERED_SCHEMA_KEY {
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
if row.file_id.is_some() {
|
|
287
|
+
return Err(LixError::new(
|
|
288
|
+
LixError::CODE_SCHEMA_DEFINITION,
|
|
289
|
+
"lix_registered_schema rows must not be scoped to a file",
|
|
290
|
+
)
|
|
291
|
+
.with_hint("Schema definitions are scoped by version and durability only; write them with null file_id."));
|
|
292
|
+
}
|
|
293
|
+
remember_pending_registered_schema(
|
|
294
|
+
row.snapshot.as_ref().map(TransactionJson::value),
|
|
295
|
+
Domain::schema_catalog(
|
|
296
|
+
row.schema_scope_version_id().to_string(),
|
|
297
|
+
row.untracked,
|
|
298
|
+
),
|
|
299
|
+
catalog,
|
|
300
|
+
)?;
|
|
301
|
+
}
|
|
302
|
+
let normalized_rows = rows
|
|
303
|
+
.into_iter()
|
|
304
|
+
.map(|(index, row)| {
|
|
305
|
+
normalize_transaction_write_row(row, catalog, functions.clone())
|
|
306
|
+
.map(|row| (index, row))
|
|
307
|
+
})
|
|
308
|
+
.collect::<Result<Vec<_>, _>>()?;
|
|
309
|
+
for (index, row) in normalized_rows {
|
|
310
|
+
prepared_rows[index] = Some(prepare_state_row(row, &functions)?);
|
|
311
|
+
}
|
|
235
312
|
}
|
|
236
|
-
Ok(
|
|
313
|
+
Ok(prepared_rows
|
|
314
|
+
.into_iter()
|
|
315
|
+
.map(|row| {
|
|
316
|
+
row.expect("every row should be prepared exactly once by schema scope grouping")
|
|
317
|
+
})
|
|
318
|
+
.collect())
|
|
237
319
|
}
|
|
238
320
|
|
|
239
|
-
async fn
|
|
321
|
+
async fn prepare_adopted_changes(
|
|
240
322
|
&mut self,
|
|
241
|
-
|
|
323
|
+
changes: Vec<TransactionAdoptedChange>,
|
|
324
|
+
) -> Result<Vec<PreparedAdoptedStateRow>, LixError> {
|
|
325
|
+
let change_count = changes.len();
|
|
326
|
+
let staged = self.staged_writes.staging_overlay()?;
|
|
327
|
+
let live_state = self.live_state.reader(self.storage_transaction.as_mut());
|
|
328
|
+
let mut changes_by_scope =
|
|
329
|
+
BTreeMap::<Domain, Vec<(usize, TransactionAdoptedChange)>>::new();
|
|
330
|
+
for (index, change) in changes.into_iter().enumerate() {
|
|
331
|
+
let schema_scope_version_id = if change.version_id == GLOBAL_VERSION_ID {
|
|
332
|
+
GLOBAL_VERSION_ID
|
|
333
|
+
} else {
|
|
334
|
+
change.version_id.as_str()
|
|
335
|
+
};
|
|
336
|
+
changes_by_scope
|
|
337
|
+
.entry(Domain::schema_catalog(
|
|
338
|
+
schema_scope_version_id.to_string(),
|
|
339
|
+
false,
|
|
340
|
+
))
|
|
341
|
+
.or_default()
|
|
342
|
+
.push((index, change));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
let mut prepared_rows = Vec::with_capacity(change_count);
|
|
346
|
+
prepared_rows.resize_with(change_count, || None);
|
|
347
|
+
for (domain, changes) in changes_by_scope {
|
|
348
|
+
let catalog = self
|
|
349
|
+
.schema_resolver
|
|
350
|
+
.catalog_for_row_normalization(&live_state, &staged, &domain)
|
|
351
|
+
.await?;
|
|
352
|
+
for (_, change) in &changes {
|
|
353
|
+
let row = &change.projected_row;
|
|
354
|
+
if row.schema_key != REGISTERED_SCHEMA_KEY {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
if row.file_id.is_some() {
|
|
358
|
+
return Err(LixError::new(
|
|
359
|
+
LixError::CODE_SCHEMA_DEFINITION,
|
|
360
|
+
"lix_registered_schema rows must not be scoped to a file",
|
|
361
|
+
)
|
|
362
|
+
.with_hint("Schema definitions are scoped by version and durability only; write them with null file_id."));
|
|
363
|
+
}
|
|
364
|
+
remember_adopted_registered_schema(
|
|
365
|
+
Domain::schema_catalog(change.version_id.clone(), false),
|
|
366
|
+
row.snapshot_content.as_deref(),
|
|
367
|
+
catalog,
|
|
368
|
+
)?;
|
|
369
|
+
}
|
|
370
|
+
let mut planned_changes = Vec::with_capacity(changes.len());
|
|
371
|
+
for (index, change) in changes {
|
|
372
|
+
let row = &change.projected_row;
|
|
373
|
+
let Some((schema_plan_id, _)) = catalog.plan_for_key(&row.schema_key) else {
|
|
374
|
+
return Err(LixError::new(
|
|
375
|
+
LixError::CODE_SCHEMA_DEFINITION,
|
|
376
|
+
format!(
|
|
377
|
+
"schema '{}' is not visible to this transaction",
|
|
378
|
+
row.schema_key
|
|
379
|
+
),
|
|
380
|
+
));
|
|
381
|
+
};
|
|
382
|
+
if row.schema_key == REGISTERED_SCHEMA_KEY {
|
|
383
|
+
if row.file_id.is_some() {
|
|
384
|
+
return Err(LixError::new(
|
|
385
|
+
LixError::CODE_SCHEMA_DEFINITION,
|
|
386
|
+
"lix_registered_schema rows must not be scoped to a file",
|
|
387
|
+
)
|
|
388
|
+
.with_hint("Schema definitions are scoped by version and durability only; write them with null file_id."));
|
|
389
|
+
}
|
|
390
|
+
remember_adopted_registered_schema(
|
|
391
|
+
Domain::schema_catalog(change.version_id.clone(), false),
|
|
392
|
+
row.snapshot_content.as_deref(),
|
|
393
|
+
catalog,
|
|
394
|
+
)?;
|
|
395
|
+
}
|
|
396
|
+
planned_changes.push((index, change, schema_plan_id));
|
|
397
|
+
}
|
|
398
|
+
for (index, change, schema_plan_id) in planned_changes {
|
|
399
|
+
prepared_rows[index] = Some(prepare_adopted_state_row(change, schema_plan_id)?);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
Ok(prepared_rows
|
|
403
|
+
.into_iter()
|
|
404
|
+
.map(|row| row.expect("every adopted row should be prepared exactly once"))
|
|
405
|
+
.collect())
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async fn validate_prepared_writes_by_version(
|
|
409
|
+
&mut self,
|
|
410
|
+
prepared_writes: &PreparedWriteSet,
|
|
242
411
|
) -> Result<(), LixError> {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
412
|
+
let validation_index = prepared_writes.validation_index();
|
|
413
|
+
for scope in validation_index.schema_scopes() {
|
|
414
|
+
#[cfg(feature = "storage-benches")]
|
|
415
|
+
crate::storage_bench::record_transaction_validation_version();
|
|
416
|
+
let version_prepared_writes = validation_index.validation_set_for_schema_scope(scope);
|
|
246
417
|
let live_state = self.live_state.reader(self.storage_transaction.as_mut());
|
|
247
418
|
let schema_catalog = self
|
|
248
419
|
.schema_resolver
|
|
249
|
-
.catalog_for_validation(&live_state,
|
|
420
|
+
.catalog_for_validation(&live_state, scope)
|
|
250
421
|
.await?;
|
|
251
|
-
|
|
252
|
-
&
|
|
422
|
+
validate_prepared_writes(TransactionValidationInput::new(
|
|
423
|
+
&version_prepared_writes,
|
|
253
424
|
&schema_catalog,
|
|
254
425
|
&live_state,
|
|
255
426
|
))
|
|
@@ -262,20 +433,20 @@ impl Transaction {
|
|
|
262
433
|
#[allow(dead_code)]
|
|
263
434
|
pub(crate) async fn stage_rows(
|
|
264
435
|
&mut self,
|
|
265
|
-
rows: Vec<
|
|
266
|
-
) -> Result<
|
|
267
|
-
self.stage_write(
|
|
268
|
-
mode:
|
|
436
|
+
rows: Vec<TransactionWriteRow>,
|
|
437
|
+
) -> Result<TransactionWriteOutcome, LixError> {
|
|
438
|
+
self.stage_write(TransactionWrite::Rows {
|
|
439
|
+
mode: TransactionWriteMode::Replace,
|
|
269
440
|
rows,
|
|
270
441
|
})
|
|
271
442
|
.await
|
|
272
443
|
}
|
|
273
444
|
|
|
274
|
-
async fn
|
|
445
|
+
async fn require_existing_transaction_write_version_ids(
|
|
275
446
|
&mut self,
|
|
276
|
-
write: &
|
|
447
|
+
write: &TransactionWrite,
|
|
277
448
|
) -> Result<(), LixError> {
|
|
278
|
-
let version_ids =
|
|
449
|
+
let version_ids = transaction_write_version_ids(write);
|
|
279
450
|
let reader = self
|
|
280
451
|
.version_ctx
|
|
281
452
|
.ref_reader(self.storage_transaction.as_mut());
|
|
@@ -329,18 +500,9 @@ impl Transaction {
|
|
|
329
500
|
) -> Result<(), LixError> {
|
|
330
501
|
let timestamp = self.functions.call_timestamp();
|
|
331
502
|
let mut writes = StorageWriteSet::new();
|
|
332
|
-
let canonical_row =
|
|
333
|
-
let mut json_writer = JsonStoreContext::new().writer();
|
|
334
|
-
self.version_ctx.canonical_ref_row(
|
|
335
|
-
&mut writes,
|
|
336
|
-
&mut json_writer,
|
|
337
|
-
version_id,
|
|
338
|
-
commit_id,
|
|
339
|
-
×tamp,
|
|
340
|
-
)?
|
|
341
|
-
};
|
|
503
|
+
let canonical_row = prepare_version_ref_row(version_id, commit_id, ×tamp)?;
|
|
342
504
|
self.version_ctx
|
|
343
|
-
.stage_canonical_ref_rows(&mut writes, &[canonical_row])?;
|
|
505
|
+
.stage_canonical_ref_rows(&mut writes, &[canonical_row.row])?;
|
|
344
506
|
writes
|
|
345
507
|
.apply(&mut self.storage_transaction.as_mut())
|
|
346
508
|
.await
|
|
@@ -375,11 +537,129 @@ impl Transaction {
|
|
|
375
537
|
pub(crate) fn commit_graph_reader(
|
|
376
538
|
&mut self,
|
|
377
539
|
) -> CommitGraphStoreReader<&mut dyn StorageWriteTransaction> {
|
|
378
|
-
CommitGraphContext::new(self.
|
|
379
|
-
.reader(self.storage_transaction.as_mut())
|
|
540
|
+
CommitGraphContext::new().reader(self.storage_transaction.as_mut())
|
|
380
541
|
}
|
|
381
542
|
}
|
|
382
543
|
|
|
544
|
+
fn prepare_state_row(
|
|
545
|
+
normalized: NormalizedTransactionWriteRow,
|
|
546
|
+
functions: &FunctionProviderHandle,
|
|
547
|
+
) -> Result<PreparedStateRow, LixError> {
|
|
548
|
+
let NormalizedTransactionWriteRow {
|
|
549
|
+
row,
|
|
550
|
+
snapshot,
|
|
551
|
+
schema_plan_id,
|
|
552
|
+
facts,
|
|
553
|
+
} = normalized;
|
|
554
|
+
let updated_at = row.updated_at.unwrap_or_else(|| functions.call_timestamp());
|
|
555
|
+
let snapshot = snapshot
|
|
556
|
+
.map(|value| stage_json_from_value(value, "prepared row snapshot_content"))
|
|
557
|
+
.transpose()?;
|
|
558
|
+
let metadata = row
|
|
559
|
+
.metadata
|
|
560
|
+
.map(|value| stage_json_from_value(value, "prepared row metadata"))
|
|
561
|
+
.transpose()?;
|
|
562
|
+
Ok(PreparedStateRow {
|
|
563
|
+
schema_plan_id,
|
|
564
|
+
facts,
|
|
565
|
+
entity_id: row.entity_id.ok_or_else(|| {
|
|
566
|
+
LixError::new(
|
|
567
|
+
"LIX_ERROR_UNKNOWN",
|
|
568
|
+
"normalized transaction write row is missing entity_id",
|
|
569
|
+
)
|
|
570
|
+
})?,
|
|
571
|
+
schema_key: row.schema_key,
|
|
572
|
+
file_id: row.file_id,
|
|
573
|
+
snapshot,
|
|
574
|
+
metadata,
|
|
575
|
+
origin: row.origin,
|
|
576
|
+
created_at: row.created_at.unwrap_or_else(|| updated_at.clone()),
|
|
577
|
+
updated_at,
|
|
578
|
+
global: row.global,
|
|
579
|
+
change_id: if row.untracked {
|
|
580
|
+
row.change_id
|
|
581
|
+
} else {
|
|
582
|
+
Some(row.change_id.unwrap_or_else(|| functions.call_uuid_v7()))
|
|
583
|
+
},
|
|
584
|
+
commit_id: row.commit_id,
|
|
585
|
+
untracked: row.untracked,
|
|
586
|
+
version_id: row.version_id,
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
fn remember_adopted_registered_schema(
|
|
591
|
+
domain: Domain,
|
|
592
|
+
snapshot_content: Option<&str>,
|
|
593
|
+
catalog: &mut crate::catalog::CatalogSnapshot,
|
|
594
|
+
) -> Result<(), LixError> {
|
|
595
|
+
let snapshot = snapshot_content
|
|
596
|
+
.map(|value| {
|
|
597
|
+
serde_json::from_str::<JsonValue>(value).map_err(|error| {
|
|
598
|
+
LixError::new(
|
|
599
|
+
LixError::CODE_UNKNOWN,
|
|
600
|
+
format!("adopted registered schema snapshot_content is invalid JSON: {error}"),
|
|
601
|
+
)
|
|
602
|
+
})
|
|
603
|
+
})
|
|
604
|
+
.transpose()?;
|
|
605
|
+
remember_pending_registered_schema(snapshot.as_ref(), domain, catalog)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
fn prepare_adopted_state_row(
|
|
609
|
+
change: TransactionAdoptedChange,
|
|
610
|
+
schema_plan_id: crate::catalog::SchemaPlanId,
|
|
611
|
+
) -> Result<PreparedAdoptedStateRow, LixError> {
|
|
612
|
+
if change.change_id != change.projected_row.change_id {
|
|
613
|
+
return Err(LixError::new(
|
|
614
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
615
|
+
format!(
|
|
616
|
+
"adopted change '{}' does not match projected row change_id '{}'",
|
|
617
|
+
change.change_id, change.projected_row.change_id
|
|
618
|
+
),
|
|
619
|
+
));
|
|
620
|
+
}
|
|
621
|
+
let row = change.projected_row;
|
|
622
|
+
let snapshot = row
|
|
623
|
+
.snapshot_content
|
|
624
|
+
.as_deref()
|
|
625
|
+
.map(|value| stage_materialized_json_text(value, "adopted row snapshot_content"))
|
|
626
|
+
.transpose()?;
|
|
627
|
+
let metadata = row
|
|
628
|
+
.metadata
|
|
629
|
+
.as_deref()
|
|
630
|
+
.map(|value| stage_materialized_json_text(value, "adopted row metadata"))
|
|
631
|
+
.transpose()?;
|
|
632
|
+
Ok(PreparedAdoptedStateRow {
|
|
633
|
+
schema_plan_id,
|
|
634
|
+
facts: PreparedRowFacts::default(),
|
|
635
|
+
entity_id: row.entity_id,
|
|
636
|
+
schema_key: row.schema_key,
|
|
637
|
+
file_id: row.file_id,
|
|
638
|
+
snapshot,
|
|
639
|
+
metadata,
|
|
640
|
+
created_at: row.created_at,
|
|
641
|
+
updated_at: row.updated_at,
|
|
642
|
+
global: change.version_id == GLOBAL_VERSION_ID,
|
|
643
|
+
change_id: change.change_id,
|
|
644
|
+
commit_id: String::new(),
|
|
645
|
+
version_id: change.version_id,
|
|
646
|
+
})
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
fn stage_materialized_json_text(
|
|
650
|
+
value: &str,
|
|
651
|
+
context: &str,
|
|
652
|
+
) -> Result<crate::transaction::types::StageJson, LixError> {
|
|
653
|
+
let parsed = serde_json::from_str::<serde_json::Value>(value).map_err(|error| {
|
|
654
|
+
LixError::new(
|
|
655
|
+
LixError::CODE_UNKNOWN,
|
|
656
|
+
format!("{context} is invalid JSON: {error}"),
|
|
657
|
+
)
|
|
658
|
+
})?;
|
|
659
|
+
let prepared = TransactionJson::from_value(parsed, context)?;
|
|
660
|
+
stage_json_from_value(prepared, context)
|
|
661
|
+
}
|
|
662
|
+
|
|
383
663
|
pub(crate) struct OpenTransaction {
|
|
384
664
|
pub(crate) transaction: Transaction,
|
|
385
665
|
pub(crate) runtime_functions: FunctionContext,
|
|
@@ -391,9 +671,9 @@ pub(crate) async fn open_transaction(
|
|
|
391
671
|
live_state: Arc<LiveStateContext>,
|
|
392
672
|
tracked_state: Arc<TrackedStateContext>,
|
|
393
673
|
binary_cas: Arc<BinaryCasContext>,
|
|
394
|
-
|
|
674
|
+
commit_store: Arc<CommitStoreContext>,
|
|
395
675
|
version_ctx: Arc<VersionContext>,
|
|
396
|
-
|
|
676
|
+
catalog_context: Arc<CatalogContext>,
|
|
397
677
|
) -> Result<OpenTransaction, LixError> {
|
|
398
678
|
Transaction::open(
|
|
399
679
|
mode,
|
|
@@ -401,9 +681,9 @@ pub(crate) async fn open_transaction(
|
|
|
401
681
|
live_state,
|
|
402
682
|
tracked_state,
|
|
403
683
|
binary_cas,
|
|
404
|
-
|
|
684
|
+
commit_store,
|
|
405
685
|
version_ctx,
|
|
406
|
-
|
|
686
|
+
catalog_context,
|
|
407
687
|
)
|
|
408
688
|
.await
|
|
409
689
|
}
|
|
@@ -432,7 +712,7 @@ impl SqlWriteExecutionContext for Transaction {
|
|
|
432
712
|
async fn scan_live_state(
|
|
433
713
|
&mut self,
|
|
434
714
|
request: &LiveStateScanRequest,
|
|
435
|
-
) -> Result<Vec<
|
|
715
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
436
716
|
let staged = self.staged_writes.staging_overlay()?;
|
|
437
717
|
let base = self.live_state.reader(self.storage_transaction.as_mut());
|
|
438
718
|
overlay_scan_rows(&base, &staged, request).await
|
|
@@ -445,115 +725,67 @@ impl SqlWriteExecutionContext for Transaction {
|
|
|
445
725
|
.await
|
|
446
726
|
}
|
|
447
727
|
|
|
448
|
-
async fn stage_write(
|
|
728
|
+
async fn stage_write(
|
|
729
|
+
&mut self,
|
|
730
|
+
write: TransactionWrite,
|
|
731
|
+
) -> Result<TransactionWriteOutcome, LixError> {
|
|
449
732
|
Transaction::stage_write(self, write).await
|
|
450
733
|
}
|
|
451
734
|
}
|
|
452
735
|
|
|
453
|
-
fn
|
|
736
|
+
fn transaction_write_version_ids(write: &TransactionWrite) -> BTreeSet<String> {
|
|
454
737
|
match write {
|
|
455
|
-
|
|
456
|
-
|
|
738
|
+
TransactionWrite::Rows { rows, .. } => transaction_write_row_version_ids(rows),
|
|
739
|
+
TransactionWrite::RowsWithFileData {
|
|
457
740
|
rows, file_data, ..
|
|
458
|
-
} =>
|
|
741
|
+
} => transaction_write_row_version_ids(rows)
|
|
459
742
|
.into_iter()
|
|
460
743
|
.chain(stage_file_data_version_ids(file_data))
|
|
461
744
|
.collect(),
|
|
462
|
-
|
|
745
|
+
TransactionWrite::AdoptedChanges { changes } => changes
|
|
463
746
|
.iter()
|
|
464
747
|
.map(|change| change.version_id.clone())
|
|
465
748
|
.collect(),
|
|
466
749
|
}
|
|
467
750
|
}
|
|
468
751
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
.
|
|
473
|
-
|
|
474
|
-
.
|
|
475
|
-
|
|
476
|
-
.adopted_rows
|
|
477
|
-
.iter()
|
|
478
|
-
.map(|row| row.schema_scope_version_id().to_string()),
|
|
479
|
-
)
|
|
480
|
-
.collect()
|
|
752
|
+
#[cfg(feature = "storage-benches")]
|
|
753
|
+
fn transaction_write_row_count(write: &TransactionWrite) -> usize {
|
|
754
|
+
match write {
|
|
755
|
+
TransactionWrite::Rows { rows, .. } => rows.len(),
|
|
756
|
+
TransactionWrite::RowsWithFileData { rows, .. } => rows.len(),
|
|
757
|
+
TransactionWrite::AdoptedChanges { changes } => changes.len(),
|
|
758
|
+
}
|
|
481
759
|
}
|
|
482
760
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
.filter(|row| row.schema_scope_version_id() == schema_scope_version_id)
|
|
492
|
-
.cloned()
|
|
493
|
-
.collect(),
|
|
494
|
-
adopted_rows: staged_writes
|
|
495
|
-
.adopted_rows
|
|
496
|
-
.iter()
|
|
497
|
-
.filter(|row| row.schema_scope_version_id() == schema_scope_version_id)
|
|
498
|
-
.cloned()
|
|
499
|
-
.collect(),
|
|
500
|
-
insert_identities: staged_writes
|
|
501
|
-
.insert_identities
|
|
502
|
-
.iter()
|
|
503
|
-
.filter(|(identity, _)| {
|
|
504
|
-
let identity_schema_scope = if identity.version_id == GLOBAL_VERSION_ID {
|
|
505
|
-
GLOBAL_VERSION_ID
|
|
506
|
-
} else {
|
|
507
|
-
identity.version_id.as_str()
|
|
508
|
-
};
|
|
509
|
-
identity_schema_scope == schema_scope_version_id
|
|
510
|
-
})
|
|
511
|
-
.map(|(identity, origin)| (identity.clone(), origin.clone()))
|
|
512
|
-
.collect(),
|
|
513
|
-
commit_members_by_version: staged_writes
|
|
514
|
-
.commit_members_by_version
|
|
515
|
-
.iter()
|
|
516
|
-
.filter(|(member_version_id, _)| {
|
|
517
|
-
let member_schema_scope = if member_version_id.as_str() == GLOBAL_VERSION_ID {
|
|
518
|
-
GLOBAL_VERSION_ID
|
|
519
|
-
} else {
|
|
520
|
-
member_version_id.as_str()
|
|
521
|
-
};
|
|
522
|
-
member_schema_scope == schema_scope_version_id
|
|
523
|
-
})
|
|
524
|
-
.map(|(member_version_id, members)| (member_version_id.clone(), members.clone()))
|
|
525
|
-
.collect(),
|
|
526
|
-
extra_commit_parents_by_version: staged_writes
|
|
527
|
-
.extra_commit_parents_by_version
|
|
528
|
-
.iter()
|
|
529
|
-
.filter(|(parent_version_id, _)| parent_version_id.as_str() == schema_scope_version_id)
|
|
530
|
-
.map(|(parent_version_id, parents)| (parent_version_id.clone(), parents.clone()))
|
|
531
|
-
.collect(),
|
|
532
|
-
file_data_writes: staged_writes
|
|
533
|
-
.file_data_writes
|
|
534
|
-
.iter()
|
|
535
|
-
.filter(|write| {
|
|
536
|
-
let write_schema_scope = if write.version_id == GLOBAL_VERSION_ID {
|
|
537
|
-
GLOBAL_VERSION_ID
|
|
538
|
-
} else {
|
|
539
|
-
write.version_id.as_str()
|
|
540
|
-
};
|
|
541
|
-
write_schema_scope == schema_scope_version_id
|
|
542
|
-
})
|
|
543
|
-
.cloned()
|
|
544
|
-
.collect(),
|
|
761
|
+
#[cfg(feature = "storage-benches")]
|
|
762
|
+
fn transaction_write_untracked_row_count(write: &TransactionWrite) -> usize {
|
|
763
|
+
match write {
|
|
764
|
+
TransactionWrite::Rows { rows, .. } => rows.iter().filter(|row| row.untracked).count(),
|
|
765
|
+
TransactionWrite::RowsWithFileData { rows, .. } => {
|
|
766
|
+
rows.iter().filter(|row| row.untracked).count()
|
|
767
|
+
}
|
|
768
|
+
TransactionWrite::AdoptedChanges { .. } => 0,
|
|
545
769
|
}
|
|
546
770
|
}
|
|
547
771
|
|
|
548
|
-
fn
|
|
772
|
+
fn require_valid_transaction_write_storage_scopes(
|
|
773
|
+
write: &TransactionWrite,
|
|
774
|
+
) -> Result<(), LixError> {
|
|
549
775
|
match write {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
776
|
+
TransactionWrite::Rows { rows, .. } => {
|
|
777
|
+
require_valid_transaction_write_row_storage_scopes(rows)
|
|
778
|
+
}
|
|
779
|
+
TransactionWrite::RowsWithFileData { rows, .. } => {
|
|
780
|
+
require_valid_transaction_write_row_storage_scopes(rows)
|
|
781
|
+
}
|
|
782
|
+
TransactionWrite::AdoptedChanges { .. } => Ok(()),
|
|
553
783
|
}
|
|
554
784
|
}
|
|
555
785
|
|
|
556
|
-
fn
|
|
786
|
+
fn require_valid_transaction_write_row_storage_scopes(
|
|
787
|
+
rows: &[TransactionWriteRow],
|
|
788
|
+
) -> Result<(), LixError> {
|
|
557
789
|
for row in rows {
|
|
558
790
|
require_valid_storage_scope(row.version_id.as_str(), row.global)?;
|
|
559
791
|
}
|
|
@@ -570,11 +802,11 @@ fn require_valid_storage_scope(version_id: &str, global: bool) -> Result<(), Lix
|
|
|
570
802
|
Ok(())
|
|
571
803
|
}
|
|
572
804
|
|
|
573
|
-
fn
|
|
805
|
+
fn transaction_write_row_version_ids(rows: &[TransactionWriteRow]) -> BTreeSet<String> {
|
|
574
806
|
rows.iter().map(|row| row.version_id.clone()).collect()
|
|
575
807
|
}
|
|
576
808
|
|
|
577
|
-
fn stage_file_data_version_ids(file_data: &[
|
|
809
|
+
fn stage_file_data_version_ids(file_data: &[TransactionFileData]) -> BTreeSet<String> {
|
|
578
810
|
file_data
|
|
579
811
|
.iter()
|
|
580
812
|
.map(|write| write.version_id.clone())
|
|
@@ -662,8 +894,9 @@ mod tests {
|
|
|
662
894
|
|
|
663
895
|
use super::*;
|
|
664
896
|
use crate::backend::testing::UnitTestBackend;
|
|
665
|
-
use crate::
|
|
897
|
+
use crate::commit_store::{ChangeScanRequest, CommitStoreContext};
|
|
666
898
|
use crate::tracked_state::{TrackedStateRowRequest, TrackedStateScanRequest};
|
|
899
|
+
use crate::transaction::types::TransactionJson;
|
|
667
900
|
use crate::untracked_state::{UntrackedStateContext, UntrackedStateRowRequest};
|
|
668
901
|
use crate::version::VersionContext;
|
|
669
902
|
use crate::Backend;
|
|
@@ -674,20 +907,23 @@ mod tests {
|
|
|
674
907
|
LiveStateContext::new(
|
|
675
908
|
crate::tracked_state::TrackedStateContext::new(),
|
|
676
909
|
crate::untracked_state::UntrackedStateContext::new(),
|
|
677
|
-
crate::commit_graph::CommitGraphContext::new(
|
|
910
|
+
crate::commit_graph::CommitGraphContext::new(),
|
|
678
911
|
)
|
|
679
912
|
}
|
|
680
913
|
|
|
914
|
+
const SCHEMA_FIXTURE_COMMIT_ID: &str = "schema-fixture-commit";
|
|
915
|
+
|
|
681
916
|
#[tokio::test]
|
|
682
917
|
async fn stage_rows_routes_tracked_and_untracked_rows_without_sql() {
|
|
683
918
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
684
919
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
685
920
|
let live_state = Arc::new(live_state_context());
|
|
686
|
-
seed_visible_schema_rows(storage.clone()
|
|
921
|
+
seed_visible_schema_rows(storage.clone()).await;
|
|
687
922
|
let binary_cas = Arc::new(BinaryCasContext::new());
|
|
688
|
-
let changelog = Arc::new(
|
|
923
|
+
let changelog = Arc::new(CommitStoreContext::new());
|
|
924
|
+
let commit_store = Arc::new(CommitStoreContext::new());
|
|
689
925
|
let version_ctx = Arc::new(VersionContext::new(Arc::new(UntrackedStateContext::new())));
|
|
690
|
-
let
|
|
926
|
+
let catalog_context = Arc::new(CatalogContext::new());
|
|
691
927
|
let opened = open_transaction(
|
|
692
928
|
&SessionMode::Pinned {
|
|
693
929
|
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
@@ -696,9 +932,9 @@ mod tests {
|
|
|
696
932
|
Arc::clone(&live_state),
|
|
697
933
|
Arc::new(crate::tracked_state::TrackedStateContext::new()),
|
|
698
934
|
Arc::clone(&binary_cas),
|
|
699
|
-
Arc::clone(&
|
|
935
|
+
Arc::clone(&commit_store),
|
|
700
936
|
Arc::clone(&version_ctx),
|
|
701
|
-
Arc::clone(&
|
|
937
|
+
Arc::clone(&catalog_context),
|
|
702
938
|
)
|
|
703
939
|
.await
|
|
704
940
|
.expect("transaction should open");
|
|
@@ -719,20 +955,25 @@ mod tests {
|
|
|
719
955
|
|
|
720
956
|
let changes = changelog
|
|
721
957
|
.reader(storage.clone())
|
|
722
|
-
.scan_changes(&
|
|
958
|
+
.scan_changes(&ChangeScanRequest::default())
|
|
723
959
|
.await
|
|
724
960
|
.expect("changelog should scan");
|
|
725
961
|
assert!(
|
|
726
|
-
changes
|
|
727
|
-
.
|
|
728
|
-
.
|
|
962
|
+
changes.iter().any(|change| change
|
|
963
|
+
.record
|
|
964
|
+
.entity_id
|
|
965
|
+
.as_single_string_owned()
|
|
966
|
+
.as_deref()
|
|
967
|
+
== Ok("tracked-programmatic")),
|
|
729
968
|
"tracked staged row should be appended to changelog"
|
|
730
969
|
);
|
|
731
970
|
assert!(
|
|
732
|
-
!changes
|
|
733
|
-
.
|
|
734
|
-
.
|
|
735
|
-
|
|
971
|
+
!changes.iter().any(|change| change
|
|
972
|
+
.record
|
|
973
|
+
.entity_id
|
|
974
|
+
.as_single_string_owned()
|
|
975
|
+
.as_deref()
|
|
976
|
+
== Ok("untracked-programmatic")),
|
|
736
977
|
"untracked staged row must not be appended to changelog"
|
|
737
978
|
);
|
|
738
979
|
|
|
@@ -745,18 +986,20 @@ mod tests {
|
|
|
745
986
|
|
|
746
987
|
let tracked_row = crate::tracked_state::TrackedStateContext::new()
|
|
747
988
|
.reader(storage.clone())
|
|
748
|
-
.
|
|
989
|
+
.load_rows_at_commit(
|
|
749
990
|
&head_commit_id,
|
|
750
|
-
&TrackedStateRowRequest {
|
|
991
|
+
&[TrackedStateRowRequest {
|
|
751
992
|
schema_key: "lix_key_value".to_string(),
|
|
752
993
|
entity_id: crate::entity_identity::EntityIdentity::single(
|
|
753
994
|
"tracked-programmatic",
|
|
754
995
|
),
|
|
755
996
|
file_id: NullableKeyFilter::Null,
|
|
756
|
-
},
|
|
997
|
+
}],
|
|
757
998
|
)
|
|
758
999
|
.await
|
|
759
1000
|
.expect("tracked state should load")
|
|
1001
|
+
.pop()
|
|
1002
|
+
.flatten()
|
|
760
1003
|
.expect("tracked row should be present in tracked state");
|
|
761
1004
|
assert_eq!(tracked_row.commit_id, head_commit_id);
|
|
762
1005
|
assert_eq!(
|
|
@@ -803,7 +1046,8 @@ mod tests {
|
|
|
803
1046
|
assert!(
|
|
804
1047
|
tracked_rows
|
|
805
1048
|
.iter()
|
|
806
|
-
.all(|row| row.entity_id.
|
|
1049
|
+
.all(|row| row.entity_id.as_single_string_owned().as_deref()
|
|
1050
|
+
!= Ok("untracked-programmatic")),
|
|
807
1051
|
"untracked staged rows should not be written into tracked state"
|
|
808
1052
|
);
|
|
809
1053
|
}
|
|
@@ -813,11 +1057,12 @@ mod tests {
|
|
|
813
1057
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
814
1058
|
let storage = StorageContext::new(Arc::clone(&backend));
|
|
815
1059
|
let live_state = Arc::new(live_state_context());
|
|
816
|
-
seed_visible_schema_rows(storage.clone()
|
|
1060
|
+
seed_visible_schema_rows(storage.clone()).await;
|
|
817
1061
|
let binary_cas = Arc::new(BinaryCasContext::new());
|
|
818
|
-
let changelog = Arc::new(
|
|
1062
|
+
let changelog = Arc::new(CommitStoreContext::new());
|
|
1063
|
+
let commit_store = Arc::new(CommitStoreContext::new());
|
|
819
1064
|
let version_ctx = Arc::new(VersionContext::new(Arc::new(UntrackedStateContext::new())));
|
|
820
|
-
let
|
|
1065
|
+
let catalog_context = Arc::new(CatalogContext::new());
|
|
821
1066
|
let opened = open_transaction(
|
|
822
1067
|
&SessionMode::Pinned {
|
|
823
1068
|
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
@@ -826,9 +1071,9 @@ mod tests {
|
|
|
826
1071
|
Arc::clone(&live_state),
|
|
827
1072
|
Arc::new(crate::tracked_state::TrackedStateContext::new()),
|
|
828
1073
|
Arc::clone(&binary_cas),
|
|
829
|
-
Arc::clone(&
|
|
1074
|
+
Arc::clone(&commit_store),
|
|
830
1075
|
Arc::clone(&version_ctx),
|
|
831
|
-
Arc::clone(&
|
|
1076
|
+
Arc::clone(&catalog_context),
|
|
832
1077
|
)
|
|
833
1078
|
.await
|
|
834
1079
|
.expect("transaction should open");
|
|
@@ -836,7 +1081,9 @@ mod tests {
|
|
|
836
1081
|
let runtime_functions = opened.runtime_functions;
|
|
837
1082
|
|
|
838
1083
|
let mut invalid_row = key_value_stage_row("invalid-programmatic", "invalid", false);
|
|
839
|
-
invalid_row.
|
|
1084
|
+
invalid_row.snapshot = Some(TransactionJson::from_value_for_test(
|
|
1085
|
+
json!({"key": "invalid-programmatic"}),
|
|
1086
|
+
));
|
|
840
1087
|
transaction
|
|
841
1088
|
.stage_rows(vec![invalid_row])
|
|
842
1089
|
.await
|
|
@@ -853,11 +1100,16 @@ mod tests {
|
|
|
853
1100
|
|
|
854
1101
|
let changes = changelog
|
|
855
1102
|
.reader(storage.clone())
|
|
856
|
-
.scan_changes(&
|
|
1103
|
+
.scan_changes(&ChangeScanRequest::default())
|
|
857
1104
|
.await
|
|
858
1105
|
.expect("changelog should scan after failed commit");
|
|
859
1106
|
assert!(
|
|
860
|
-
changes.
|
|
1107
|
+
changes.iter().all(|change| change
|
|
1108
|
+
.record
|
|
1109
|
+
.entity_id
|
|
1110
|
+
.as_single_string_owned()
|
|
1111
|
+
.as_deref()
|
|
1112
|
+
!= Ok("invalid-programmatic")),
|
|
861
1113
|
"validation failure must happen before changelog persistence"
|
|
862
1114
|
);
|
|
863
1115
|
let head = version_ctx
|
|
@@ -866,8 +1118,9 @@ mod tests {
|
|
|
866
1118
|
.await
|
|
867
1119
|
.expect("version ref should load after failed commit");
|
|
868
1120
|
assert_eq!(
|
|
869
|
-
head,
|
|
870
|
-
|
|
1121
|
+
head.as_deref(),
|
|
1122
|
+
Some(SCHEMA_FIXTURE_COMMIT_ID),
|
|
1123
|
+
"validation failure must not advance the version ref"
|
|
871
1124
|
);
|
|
872
1125
|
}
|
|
873
1126
|
|
|
@@ -879,7 +1132,7 @@ mod tests {
|
|
|
879
1132
|
open_test_transaction(&backend).await;
|
|
880
1133
|
|
|
881
1134
|
let mut row = key_value_stage_row("invalid-metadata", "value", false);
|
|
882
|
-
row.metadata = Some(json!("not-an-object"));
|
|
1135
|
+
row.metadata = Some(TransactionJson::from_value_for_test(json!("not-an-object")));
|
|
883
1136
|
transaction
|
|
884
1137
|
.stage_rows(vec![row])
|
|
885
1138
|
.await
|
|
@@ -900,6 +1153,7 @@ mod tests {
|
|
|
900
1153
|
&live_state,
|
|
901
1154
|
&changelog,
|
|
902
1155
|
&version_ref,
|
|
1156
|
+
"invalid-metadata",
|
|
903
1157
|
)
|
|
904
1158
|
.await;
|
|
905
1159
|
}
|
|
@@ -928,7 +1182,7 @@ mod tests {
|
|
|
928
1182
|
assert!(
|
|
929
1183
|
error
|
|
930
1184
|
.message
|
|
931
|
-
.contains("schema 'missing_schema'
|
|
1185
|
+
.contains("schema 'missing_schema' is not visible"),
|
|
932
1186
|
"error should explain missing schema visibility: {error:?}"
|
|
933
1187
|
);
|
|
934
1188
|
}
|
|
@@ -991,35 +1245,6 @@ mod tests {
|
|
|
991
1245
|
);
|
|
992
1246
|
}
|
|
993
1247
|
|
|
994
|
-
#[tokio::test]
|
|
995
|
-
async fn stage_rows_rejects_unknown_schema_version_without_sql() {
|
|
996
|
-
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
997
|
-
let (
|
|
998
|
-
_live_state,
|
|
999
|
-
_binary_cas,
|
|
1000
|
-
_changelog,
|
|
1001
|
-
_version_ref,
|
|
1002
|
-
_runtime_functions,
|
|
1003
|
-
mut transaction,
|
|
1004
|
-
) = open_test_transaction(&backend).await;
|
|
1005
|
-
|
|
1006
|
-
let mut row = key_value_stage_row("unknown-version", "value", false);
|
|
1007
|
-
row.schema_version = "999".to_string();
|
|
1008
|
-
|
|
1009
|
-
let error = transaction
|
|
1010
|
-
.stage_rows(vec![row])
|
|
1011
|
-
.await
|
|
1012
|
-
.expect_err("unknown schema version should be rejected while staging");
|
|
1013
|
-
|
|
1014
|
-
assert_eq!(error.code, LixError::CODE_SCHEMA_DEFINITION);
|
|
1015
|
-
assert!(
|
|
1016
|
-
error
|
|
1017
|
-
.message
|
|
1018
|
-
.contains("schema 'lix_key_value' version '999' is not visible"),
|
|
1019
|
-
"error should explain missing schema version visibility: {error:?}"
|
|
1020
|
-
);
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
1248
|
#[tokio::test]
|
|
1024
1249
|
async fn stage_rows_rejects_invalid_snapshot_json_without_sql() {
|
|
1025
1250
|
let backend: Arc<dyn Backend + Send + Sync> = Arc::new(UnitTestBackend::new());
|
|
@@ -1033,17 +1258,17 @@ mod tests {
|
|
|
1033
1258
|
) = open_test_transaction(&backend).await;
|
|
1034
1259
|
|
|
1035
1260
|
let mut row = key_value_stage_row("invalid-json", "value", false);
|
|
1036
|
-
row.
|
|
1261
|
+
row.snapshot = Some(TransactionJson::from_value_for_test(json!("not-an-object")));
|
|
1037
1262
|
|
|
1038
1263
|
let error = transaction
|
|
1039
1264
|
.stage_rows(vec![row])
|
|
1040
1265
|
.await
|
|
1041
|
-
.expect_err("
|
|
1266
|
+
.expect_err("non-object snapshot should be rejected while staging");
|
|
1042
1267
|
|
|
1043
1268
|
assert_eq!(error.code, LixError::CODE_SCHEMA_VALIDATION);
|
|
1044
1269
|
assert!(
|
|
1045
|
-
error.message.contains("
|
|
1046
|
-
"error should explain invalid
|
|
1270
|
+
error.message.contains("must be a JSON object"),
|
|
1271
|
+
"error should explain invalid snapshot shape: {error:?}"
|
|
1047
1272
|
);
|
|
1048
1273
|
}
|
|
1049
1274
|
|
|
@@ -1055,7 +1280,9 @@ mod tests {
|
|
|
1055
1280
|
open_test_transaction(&backend).await;
|
|
1056
1281
|
|
|
1057
1282
|
let mut row = key_value_stage_row("schema-mismatch", "value", false);
|
|
1058
|
-
row.
|
|
1283
|
+
row.snapshot = Some(TransactionJson::from_value_for_test(
|
|
1284
|
+
json!({"key": "schema-mismatch"}),
|
|
1285
|
+
));
|
|
1059
1286
|
transaction
|
|
1060
1287
|
.stage_rows(vec![row])
|
|
1061
1288
|
.await
|
|
@@ -1076,6 +1303,7 @@ mod tests {
|
|
|
1076
1303
|
&live_state,
|
|
1077
1304
|
&changelog,
|
|
1078
1305
|
&version_ref,
|
|
1306
|
+
"schema-mismatch",
|
|
1079
1307
|
)
|
|
1080
1308
|
.await;
|
|
1081
1309
|
}
|
|
@@ -1094,14 +1322,18 @@ mod tests {
|
|
|
1094
1322
|
|
|
1095
1323
|
let mut row = key_value_stage_row("malformed-registered-schema", "value", false);
|
|
1096
1324
|
row.schema_key = "lix_registered_schema".to_string();
|
|
1097
|
-
row.
|
|
1098
|
-
|
|
1099
|
-
"
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1325
|
+
row.snapshot = Some(TransactionJson::from_value_for_test(json!({
|
|
1326
|
+
"value": {
|
|
1327
|
+
"x-lix-key": "malformed_registered_schema",
|
|
1328
|
+
"x-lix-primary-key": ["id"],
|
|
1329
|
+
"type": "object",
|
|
1330
|
+
"properties": {
|
|
1331
|
+
"id": { "type": "string" }
|
|
1332
|
+
},
|
|
1333
|
+
"required": ["id"],
|
|
1334
|
+
"additionalProperties": false
|
|
1335
|
+
}
|
|
1336
|
+
})));
|
|
1105
1337
|
row.entity_id = None;
|
|
1106
1338
|
|
|
1107
1339
|
let error = transaction
|
|
@@ -1109,10 +1341,9 @@ mod tests {
|
|
|
1109
1341
|
.await
|
|
1110
1342
|
.expect_err("malformed registered schema should be rejected while staging");
|
|
1111
1343
|
|
|
1112
|
-
assert_eq!(error.code, LixError::
|
|
1344
|
+
assert_eq!(error.code, LixError::CODE_SCHEMA_DEFINITION);
|
|
1113
1345
|
assert!(
|
|
1114
|
-
error.message.contains("x-lix-
|
|
1115
|
-
|| error.message.contains("primary-key pointer"),
|
|
1346
|
+
error.message.contains("x-lix-primary-key"),
|
|
1116
1347
|
"error should explain malformed registered schema: {error:?}"
|
|
1117
1348
|
);
|
|
1118
1349
|
}
|
|
@@ -1151,18 +1382,19 @@ mod tests {
|
|
|
1151
1382
|
) -> (
|
|
1152
1383
|
Arc<LiveStateContext>,
|
|
1153
1384
|
Arc<BinaryCasContext>,
|
|
1154
|
-
Arc<
|
|
1385
|
+
Arc<CommitStoreContext>,
|
|
1155
1386
|
Arc<VersionContext>,
|
|
1156
1387
|
FunctionContext,
|
|
1157
1388
|
Transaction,
|
|
1158
1389
|
) {
|
|
1159
1390
|
let storage = StorageContext::new(Arc::clone(backend));
|
|
1160
1391
|
let live_state = Arc::new(live_state_context());
|
|
1161
|
-
seed_visible_schema_rows(storage.clone()
|
|
1392
|
+
seed_visible_schema_rows(storage.clone()).await;
|
|
1162
1393
|
let binary_cas = Arc::new(BinaryCasContext::new());
|
|
1163
|
-
let changelog = Arc::new(
|
|
1394
|
+
let changelog = Arc::new(CommitStoreContext::new());
|
|
1395
|
+
let commit_store = Arc::new(CommitStoreContext::new());
|
|
1164
1396
|
let version_ctx = Arc::new(VersionContext::new(Arc::new(UntrackedStateContext::new())));
|
|
1165
|
-
let
|
|
1397
|
+
let catalog_context = Arc::new(CatalogContext::new());
|
|
1166
1398
|
let opened = open_transaction(
|
|
1167
1399
|
&SessionMode::Pinned {
|
|
1168
1400
|
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
@@ -1171,9 +1403,9 @@ mod tests {
|
|
|
1171
1403
|
Arc::clone(&live_state),
|
|
1172
1404
|
Arc::new(crate::tracked_state::TrackedStateContext::new()),
|
|
1173
1405
|
Arc::clone(&binary_cas),
|
|
1174
|
-
Arc::clone(&
|
|
1406
|
+
Arc::clone(&commit_store),
|
|
1175
1407
|
Arc::clone(&version_ctx),
|
|
1176
|
-
|
|
1408
|
+
catalog_context,
|
|
1177
1409
|
)
|
|
1178
1410
|
.await
|
|
1179
1411
|
.expect("transaction should open");
|
|
@@ -1190,46 +1422,52 @@ mod tests {
|
|
|
1190
1422
|
)
|
|
1191
1423
|
}
|
|
1192
1424
|
|
|
1193
|
-
async fn seed_visible_schema_rows(storage: StorageContext
|
|
1425
|
+
async fn seed_visible_schema_rows(storage: StorageContext) {
|
|
1426
|
+
let mut writes = StorageWriteSet::new();
|
|
1194
1427
|
let rows = crate::schema::seed_schema_definitions()
|
|
1195
1428
|
.into_iter()
|
|
1196
1429
|
.map(|schema| {
|
|
1197
1430
|
let key = crate::schema::schema_key_from_definition(schema)
|
|
1198
1431
|
.expect("seed schema key should derive");
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
)
|
|
1204
|
-
.expect("registered schema identity should derive"),
|
|
1432
|
+
let snapshot_content = json!({ "value": schema }).to_string();
|
|
1433
|
+
crate::tracked_state::MaterializedTrackedStateRow {
|
|
1434
|
+
entity_id: crate::schema::registered_schema_entity_id(&key.schema_key)
|
|
1435
|
+
.expect("registered schema identity should derive"),
|
|
1205
1436
|
schema_key: "lix_registered_schema".to_string(),
|
|
1206
1437
|
file_id: None,
|
|
1207
|
-
|
|
1208
|
-
schema_version: "1".to_string(),
|
|
1209
|
-
snapshot_content: Some(json!({ "value": schema }).to_string()),
|
|
1438
|
+
snapshot_content: Some(snapshot_content),
|
|
1210
1439
|
metadata: None,
|
|
1440
|
+
deleted: false,
|
|
1211
1441
|
created_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
1212
1442
|
updated_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
1213
|
-
change_id:
|
|
1214
|
-
commit_id:
|
|
1215
|
-
untracked: true,
|
|
1216
|
-
global: true,
|
|
1443
|
+
change_id: format!("schema-fixture-{}", key.schema_key),
|
|
1444
|
+
commit_id: SCHEMA_FIXTURE_COMMIT_ID.to_string(),
|
|
1217
1445
|
}
|
|
1218
1446
|
})
|
|
1219
1447
|
.collect::<Vec<_>>();
|
|
1448
|
+
let version_ref_row = prepare_version_ref_row(
|
|
1449
|
+
GLOBAL_VERSION_ID,
|
|
1450
|
+
SCHEMA_FIXTURE_COMMIT_ID,
|
|
1451
|
+
"1970-01-01T00:00:00.000Z",
|
|
1452
|
+
)
|
|
1453
|
+
.expect("schema fixture version ref should stage");
|
|
1220
1454
|
let mut storage_transaction = storage
|
|
1221
1455
|
.begin_write_transaction()
|
|
1222
1456
|
.await
|
|
1223
1457
|
.expect("schema fixture transaction should open");
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1458
|
+
crate::test_support::stage_tracked_root_from_materialized(
|
|
1459
|
+
storage_transaction.as_mut(),
|
|
1460
|
+
&crate::tracked_state::TrackedStateContext::new(),
|
|
1461
|
+
SCHEMA_FIXTURE_COMMIT_ID,
|
|
1462
|
+
None,
|
|
1463
|
+
&rows,
|
|
1464
|
+
)
|
|
1465
|
+
.await
|
|
1466
|
+
.expect("schema fixture rows should stage");
|
|
1467
|
+
crate::untracked_state::UntrackedStateContext::new()
|
|
1468
|
+
.writer(&mut writes)
|
|
1469
|
+
.stage_rows([version_ref_row.row.as_ref()])
|
|
1470
|
+
.expect("schema fixture version ref should stage");
|
|
1233
1471
|
writes
|
|
1234
1472
|
.apply(&mut storage_transaction.as_mut())
|
|
1235
1473
|
.await
|
|
@@ -1243,16 +1481,22 @@ mod tests {
|
|
|
1243
1481
|
async fn assert_no_persistence_after_validation_failure(
|
|
1244
1482
|
storage: StorageContext,
|
|
1245
1483
|
live_state: &LiveStateContext,
|
|
1246
|
-
changelog: &
|
|
1484
|
+
changelog: &CommitStoreContext,
|
|
1247
1485
|
version_ctx: &VersionContext,
|
|
1486
|
+
rejected_entity_id: &str,
|
|
1248
1487
|
) {
|
|
1249
1488
|
let changes = changelog
|
|
1250
1489
|
.reader(storage.clone())
|
|
1251
|
-
.scan_changes(&
|
|
1490
|
+
.scan_changes(&ChangeScanRequest::default())
|
|
1252
1491
|
.await
|
|
1253
1492
|
.expect("changelog should scan after failed commit");
|
|
1254
1493
|
assert!(
|
|
1255
|
-
changes.
|
|
1494
|
+
changes.iter().all(|change| change
|
|
1495
|
+
.record
|
|
1496
|
+
.entity_id
|
|
1497
|
+
.as_single_string_owned()
|
|
1498
|
+
.as_deref()
|
|
1499
|
+
!= Ok(rejected_entity_id)),
|
|
1256
1500
|
"validation failure must happen before changelog persistence"
|
|
1257
1501
|
);
|
|
1258
1502
|
let head = version_ctx
|
|
@@ -1261,15 +1505,16 @@ mod tests {
|
|
|
1261
1505
|
.await
|
|
1262
1506
|
.expect("version ref should load after failed commit");
|
|
1263
1507
|
assert_eq!(
|
|
1264
|
-
head,
|
|
1265
|
-
|
|
1508
|
+
head.as_deref(),
|
|
1509
|
+
Some(SCHEMA_FIXTURE_COMMIT_ID),
|
|
1510
|
+
"validation failure must not advance the version ref"
|
|
1266
1511
|
);
|
|
1267
1512
|
let row = live_state
|
|
1268
1513
|
.reader(storage)
|
|
1269
1514
|
.load_row(&crate::live_state::LiveStateRowRequest {
|
|
1270
1515
|
schema_key: "lix_key_value".to_string(),
|
|
1271
1516
|
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
1272
|
-
entity_id: crate::entity_identity::EntityIdentity::single(
|
|
1517
|
+
entity_id: crate::entity_identity::EntityIdentity::single(rejected_entity_id),
|
|
1273
1518
|
file_id: NullableKeyFilter::Null,
|
|
1274
1519
|
})
|
|
1275
1520
|
.await
|
|
@@ -1280,21 +1525,17 @@ mod tests {
|
|
|
1280
1525
|
);
|
|
1281
1526
|
}
|
|
1282
1527
|
|
|
1283
|
-
fn key_value_stage_row(key: &str, value: &str, untracked: bool) ->
|
|
1284
|
-
|
|
1528
|
+
fn key_value_stage_row(key: &str, value: &str, untracked: bool) -> TransactionWriteRow {
|
|
1529
|
+
TransactionWriteRow {
|
|
1285
1530
|
entity_id: Some(crate::entity_identity::EntityIdentity::single(key)),
|
|
1286
1531
|
schema_key: "lix_key_value".to_string(),
|
|
1287
1532
|
file_id: None,
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
})
|
|
1293
|
-
.to_string(),
|
|
1294
|
-
),
|
|
1533
|
+
snapshot: Some(TransactionJson::from_value_for_test(json!({
|
|
1534
|
+
"key": key,
|
|
1535
|
+
"value": value,
|
|
1536
|
+
}))),
|
|
1295
1537
|
metadata: None,
|
|
1296
1538
|
origin: None,
|
|
1297
|
-
schema_version: "1".to_string(),
|
|
1298
1539
|
created_at: None,
|
|
1299
1540
|
updated_at: None,
|
|
1300
1541
|
global: true,
|