@lix-js/sdk 0.6.0-preview.4 → 0.6.0-preview.5
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/README.md +1 -1
- package/SKILL.md +65 -64
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
- package/dist/engine-wasm/wasm/lix_engine.js +130 -118
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +33 -26
- package/dist/open-lix.js +10 -10
- package/dist/sqlite/index.js +86 -30
- package/dist-engine-src/README.md +3 -3
- package/dist-engine-src/src/backend/capabilities.rs +67 -0
- package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
- package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
- package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
- package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
- package/dist-engine-src/src/backend/conformance/model.rs +28 -0
- package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
- package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
- package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
- package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
- package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
- package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
- package/dist-engine-src/src/backend/conformance/write.rs +16 -0
- package/dist-engine-src/src/backend/error.rs +94 -0
- package/dist-engine-src/src/backend/in_memory.rs +670 -0
- package/dist-engine-src/src/backend/mod.rs +36 -9
- package/dist-engine-src/src/backend/predicate.rs +80 -0
- package/dist-engine-src/src/backend/traits.rs +260 -0
- package/dist-engine-src/src/backend/types.rs +224 -81
- package/dist-engine-src/src/binary_cas/context.rs +8 -8
- package/dist-engine-src/src/binary_cas/kv.rs +234 -259
- package/dist-engine-src/src/{version → branch}/context.rs +12 -12
- package/dist-engine-src/src/branch/lifecycle.rs +221 -0
- package/dist-engine-src/src/branch/mod.rs +13 -0
- package/dist-engine-src/src/branch/refs.rs +321 -0
- package/dist-engine-src/src/branch/stage_rows.rs +67 -0
- package/dist-engine-src/src/branch/types.rs +21 -0
- package/dist-engine-src/src/catalog/context.rs +18 -18
- package/dist-engine-src/src/catalog/snapshot.rs +8 -8
- package/dist-engine-src/src/changelog/bench_support.rs +785 -0
- package/dist-engine-src/src/changelog/change.rs +1 -0
- package/dist-engine-src/src/changelog/codec.rs +497 -0
- package/dist-engine-src/src/changelog/commit.rs +1 -0
- package/dist-engine-src/src/changelog/context.rs +1614 -0
- package/dist-engine-src/src/changelog/mod.rs +29 -0
- package/dist-engine-src/src/changelog/store.rs +163 -0
- package/dist-engine-src/src/changelog/test_support.rs +54 -0
- package/dist-engine-src/src/changelog/types.rs +213 -0
- package/dist-engine-src/src/commit_graph/context.rs +317 -274
- package/dist-engine-src/src/commit_graph/mod.rs +2 -4
- package/dist-engine-src/src/commit_graph/types.rs +22 -42
- package/dist-engine-src/src/commit_graph/walker.rs +133 -103
- package/dist-engine-src/src/common/error.rs +52 -18
- package/dist-engine-src/src/common/identity.rs +2 -2
- package/dist-engine-src/src/common/mod.rs +1 -1
- package/dist-engine-src/src/domain.rs +42 -46
- package/dist-engine-src/src/engine.rs +74 -96
- package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
- package/dist-engine-src/src/functions/context.rs +56 -52
- package/dist-engine-src/src/functions/state.rs +51 -52
- package/dist-engine-src/src/init.rs +288 -154
- package/dist-engine-src/src/json_store/context.rs +15 -266
- package/dist-engine-src/src/json_store/mod.rs +26 -0
- package/dist-engine-src/src/json_store/store.rs +103 -718
- package/dist-engine-src/src/json_store/types.rs +4 -9
- package/dist-engine-src/src/lib.rs +49 -19
- package/dist-engine-src/src/live_state/context.rs +654 -790
- package/dist-engine-src/src/live_state/mod.rs +9 -3
- package/dist-engine-src/src/live_state/overlay.rs +4 -4
- package/dist-engine-src/src/live_state/types.rs +30 -21
- package/dist-engine-src/src/live_state/visibility.rs +514 -71
- package/dist-engine-src/src/plugin/install.rs +48 -48
- package/dist-engine-src/src/plugin/manifest.rs +7 -7
- package/dist-engine-src/src/plugin/materializer.rs +0 -275
- package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
- package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
- package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
- package/dist-engine-src/src/schema/compatibility.rs +11 -11
- package/dist-engine-src/src/schema/definition.json +2 -2
- package/dist-engine-src/src/schema/definition.rs +5 -5
- package/dist-engine-src/src/schema/key.rs +3 -3
- package/dist-engine-src/src/schema/mod.rs +1 -1
- package/dist-engine-src/src/schema/tests.rs +18 -18
- package/dist-engine-src/src/session/context.rs +803 -148
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +223 -83
- package/dist-engine-src/src/session/merge/analysis.rs +9 -3
- package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
- package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
- package/dist-engine-src/src/session/merge/mod.rs +5 -6
- package/dist-engine-src/src/session/merge/stats.rs +7 -11
- package/dist-engine-src/src/session/mod.rs +15 -12
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +495 -14
- package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
- package/dist-engine-src/src/sql2/bind/error.rs +5 -0
- package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
- package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
- package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
- package/dist-engine-src/src/sql2/bind/read.rs +65 -0
- package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
- package/dist-engine-src/src/sql2/bind/table.rs +273 -0
- package/dist-engine-src/src/sql2/bind/write.rs +86 -0
- package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
- package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
- package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
- package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
- package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
- package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
- package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
- package/dist-engine-src/src/sql2/context.rs +36 -30
- package/dist-engine-src/src/sql2/error.rs +1 -1
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
- package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
- package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
- package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
- package/dist-engine-src/src/sql2/exec/write.rs +661 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
- package/dist-engine-src/src/sql2/history_projection.rs +8 -8
- package/dist-engine-src/src/sql2/history_route.rs +35 -31
- package/dist-engine-src/src/sql2/mod.rs +28 -23
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
- package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
- package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
- package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
- package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
- package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
- package/dist-engine-src/src/sql2/plan/write.rs +147 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
- package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
- package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
- package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
- package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
- package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
- package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
- package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
- package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
- package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
- package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
- package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
- package/dist-engine-src/src/sql2/read_only.rs +2 -2
- package/dist-engine-src/src/sql2/session.rs +47 -96
- package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
- package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
- package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
- package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
- package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
- package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
- package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
- package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
- package/dist-engine-src/src/storage/conformance.rs +399 -0
- package/dist-engine-src/src/storage/context.rs +552 -288
- package/dist-engine-src/src/storage/mod.rs +48 -10
- package/dist-engine-src/src/storage/point.rs +440 -0
- package/dist-engine-src/src/storage/read_scope.rs +43 -64
- package/dist-engine-src/src/storage/reader.rs +867 -0
- package/dist-engine-src/src/storage/scan.rs +784 -0
- package/dist-engine-src/src/storage/spaces.rs +236 -0
- package/dist-engine-src/src/storage/stats.rs +80 -0
- package/dist-engine-src/src/storage/write_set.rs +962 -0
- package/dist-engine-src/src/storage_bench.rs +136 -4828
- package/dist-engine-src/src/test_support.rs +360 -138
- package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
- package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
- package/dist-engine-src/src/tracked_state/context.rs +1927 -993
- package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
- package/dist-engine-src/src/tracked_state/merge.rs +74 -88
- package/dist-engine-src/src/tracked_state/mod.rs +19 -16
- package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
- package/dist-engine-src/src/tracked_state/storage.rs +243 -191
- package/dist-engine-src/src/tracked_state/tree.rs +247 -371
- package/dist-engine-src/src/tracked_state/types.rs +49 -42
- package/dist-engine-src/src/transaction/bench_support.rs +407 -0
- package/dist-engine-src/src/transaction/commit.rs +821 -713
- package/dist-engine-src/src/transaction/context.rs +705 -600
- package/dist-engine-src/src/transaction/mod.rs +13 -2
- package/dist-engine-src/src/transaction/normalization.rs +63 -76
- package/dist-engine-src/src/transaction/prep.rs +13 -13
- package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
- package/dist-engine-src/src/transaction/staging.rs +228 -434
- package/dist-engine-src/src/transaction/types.rs +41 -98
- package/dist-engine-src/src/transaction/validation.rs +382 -446
- package/dist-engine-src/src/untracked_state/codec.rs +337 -29
- package/dist-engine-src/src/untracked_state/context.rs +7 -7
- package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
- package/dist-engine-src/src/untracked_state/mod.rs +1 -1
- package/dist-engine-src/src/untracked_state/storage.rs +659 -157
- package/dist-engine-src/src/untracked_state/types.rs +21 -21
- package/package.json +71 -68
- package/dist-engine-src/src/backend/kv.rs +0 -358
- package/dist-engine-src/src/backend/testing.rs +0 -658
- package/dist-engine-src/src/commit_store/codec.rs +0 -887
- package/dist-engine-src/src/commit_store/context.rs +0 -944
- package/dist-engine-src/src/commit_store/materialization.rs +0 -84
- package/dist-engine-src/src/commit_store/mod.rs +0 -16
- package/dist-engine-src/src/commit_store/storage.rs +0 -600
- package/dist-engine-src/src/commit_store/types.rs +0 -215
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
- package/dist-engine-src/src/session/create_version.rs +0 -88
- package/dist-engine-src/src/session/merge/apply.rs +0 -23
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
- package/dist-engine-src/src/session/switch_version.rs +0 -110
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3533
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
- package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
- package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
- package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
- package/dist-engine-src/src/sql2/version_scope.rs +0 -394
- package/dist-engine-src/src/storage/types.rs +0 -501
- package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
- package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
- package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
- package/dist-engine-src/src/version/lifecycle.rs +0 -221
- package/dist-engine-src/src/version/mod.rs +0 -13
- package/dist-engine-src/src/version/refs.rs +0 -330
- package/dist-engine-src/src/version/stage_rows.rs +0 -67
- package/dist-engine-src/src/version/types.rs +0 -21
|
@@ -1,44 +1,54 @@
|
|
|
1
1
|
use std::collections::{BTreeMap, BTreeSet};
|
|
2
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
2
3
|
use std::sync::Arc;
|
|
3
4
|
|
|
4
5
|
use async_trait::async_trait;
|
|
5
6
|
use serde_json::Value as JsonValue;
|
|
6
7
|
|
|
7
8
|
use crate::binary_cas::{BinaryCasContext, BlobBytesBatch, BlobHash};
|
|
9
|
+
use crate::branch::{BranchContext, BranchRefReader};
|
|
8
10
|
use crate::catalog::CatalogContext;
|
|
9
11
|
use crate::commit_graph::{CommitGraphContext, CommitGraphStoreReader};
|
|
10
|
-
use crate::commit_store::CommitStoreContext;
|
|
11
12
|
use crate::domain::Domain;
|
|
12
|
-
use crate::
|
|
13
|
+
use crate::entity_pk::EntityPk;
|
|
13
14
|
use crate::functions::{FunctionContext, FunctionProviderHandle};
|
|
15
|
+
use crate::live_state::overlay_scan_rows;
|
|
14
16
|
use crate::live_state::{
|
|
15
17
|
LiveStateContext, LiveStateRowRequest, LiveStateScanRequest, MaterializedLiveStateRow,
|
|
16
18
|
};
|
|
17
|
-
use crate::session::{SessionMode,
|
|
19
|
+
use crate::session::{SessionMode, WORKSPACE_BRANCH_KEY};
|
|
18
20
|
use crate::sql2::SqlWriteExecutionContext;
|
|
19
|
-
use crate::
|
|
21
|
+
use crate::sql2::{
|
|
22
|
+
ChangelogQuerySource, HistoryQuerySource, SqlChangelogQuerySource, SqlExecutionContext,
|
|
23
|
+
SqlHistoryQuerySource,
|
|
24
|
+
};
|
|
25
|
+
use crate::storage::{
|
|
26
|
+
InMemoryStorageBackend, StorageBackend, StorageReadOptions, StorageWriteOptions,
|
|
27
|
+
StorageWriteSetStats,
|
|
28
|
+
};
|
|
29
|
+
use crate::storage::{StorageContext, StorageRead, StorageReadScope, StorageWriteSet};
|
|
20
30
|
use crate::tracked_state::{TrackedStateContext, TrackedStateStoreReader};
|
|
21
31
|
use crate::transaction::commit;
|
|
22
|
-
use crate::transaction::live_state_overlay::overlay_scan_rows;
|
|
23
32
|
use crate::transaction::normalization::{
|
|
24
33
|
normalize_transaction_write_row, remember_pending_registered_schema,
|
|
25
34
|
NormalizedTransactionWriteRow, REGISTERED_SCHEMA_KEY,
|
|
26
35
|
};
|
|
27
|
-
use crate::transaction::
|
|
36
|
+
use crate::transaction::prepare_branch_ref_row;
|
|
28
37
|
use crate::transaction::schema_resolver::TransactionSchemaResolver;
|
|
29
38
|
use crate::transaction::staging::{PreparedWriteSet, TransactionWriteBuffer};
|
|
30
39
|
use crate::transaction::types::{
|
|
31
|
-
stage_json_from_value,
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
stage_json_from_value, PreparedStateRow, PreparedTransactionWrite, StagedCommitChangeRef,
|
|
41
|
+
TransactionFileData, TransactionJson, TransactionWrite, TransactionWriteMode,
|
|
42
|
+
TransactionWriteOutcome, TransactionWriteRow,
|
|
34
43
|
};
|
|
35
44
|
use crate::transaction::validation::{validate_prepared_writes, TransactionValidationInput};
|
|
36
|
-
use crate::
|
|
37
|
-
use crate::GLOBAL_VERSION_ID;
|
|
45
|
+
use crate::GLOBAL_BRANCH_ID;
|
|
38
46
|
use crate::{LixError, NullableKeyFilter};
|
|
39
47
|
|
|
40
48
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
41
|
-
pub(crate) struct TransactionCommitOutcome
|
|
49
|
+
pub(crate) struct TransactionCommitOutcome {
|
|
50
|
+
pub(crate) storage_stats: StorageWriteSetStats,
|
|
51
|
+
}
|
|
42
52
|
|
|
43
53
|
/// One execution-scoped transaction capability for engine write paths.
|
|
44
54
|
///
|
|
@@ -51,98 +61,233 @@ pub(crate) struct TransactionCommitOutcome;
|
|
|
51
61
|
/// that may write. Write-relevant reads must be exposed from this transaction,
|
|
52
62
|
/// after the backend write transaction has begun, rather than from session-level
|
|
53
63
|
/// helpers.
|
|
54
|
-
pub(crate) struct Transaction {
|
|
55
|
-
|
|
64
|
+
pub(crate) struct Transaction<B: StorageBackend = InMemoryStorageBackend> {
|
|
65
|
+
active_branch_id: String,
|
|
56
66
|
live_state: Arc<LiveStateContext>,
|
|
57
67
|
tracked_state: Arc<TrackedStateContext>,
|
|
58
68
|
binary_cas: Arc<BinaryCasContext>,
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
branch_ctx: Arc<BranchContext>,
|
|
70
|
+
catalog_context: Arc<CatalogContext>,
|
|
61
71
|
schema_resolver: TransactionSchemaResolver,
|
|
62
72
|
staged_writes: Arc<TransactionWriteBuffer>,
|
|
63
|
-
|
|
64
|
-
|
|
73
|
+
staged_storage_writes: StorageWriteSet,
|
|
74
|
+
storage: StorageContext<B>,
|
|
75
|
+
sql_schema_cache: SqlSchemaCache,
|
|
65
76
|
functions: FunctionProviderHandle,
|
|
77
|
+
commit_boundary: Option<TransactionCommitBoundary>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[derive(Default)]
|
|
81
|
+
struct SqlSchemaCache {
|
|
82
|
+
visible_schemas: Option<Vec<JsonValue>>,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
impl SqlSchemaCache {
|
|
86
|
+
fn is_prepared(&self) -> bool {
|
|
87
|
+
self.visible_schemas.is_some()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn prepare(&mut self, visible_schemas: Vec<JsonValue>) {
|
|
91
|
+
self.visible_schemas = Some(visible_schemas);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fn visible_schemas(&self) -> Result<&[JsonValue], LixError> {
|
|
95
|
+
self.visible_schemas.as_deref().ok_or_else(|| {
|
|
96
|
+
LixError::new(
|
|
97
|
+
LixError::CODE_INTERNAL_ERROR,
|
|
98
|
+
"SQL visible schemas were requested before SQL transaction context preparation",
|
|
99
|
+
)
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[derive(Clone)]
|
|
105
|
+
pub(crate) struct TransactionCommitBoundary {
|
|
106
|
+
state: CommitBoundaryState,
|
|
107
|
+
pre_commit_check: Arc<dyn Fn() -> Result<(), LixError> + Send + Sync>,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
impl TransactionCommitBoundary {
|
|
111
|
+
pub(crate) fn new(
|
|
112
|
+
state: CommitBoundaryState,
|
|
113
|
+
pre_commit_check: Arc<dyn Fn() -> Result<(), LixError> + Send + Sync>,
|
|
114
|
+
) -> Self {
|
|
115
|
+
Self {
|
|
116
|
+
state,
|
|
117
|
+
pre_commit_check,
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn begin(&self) -> CommitBoundaryGuard {
|
|
122
|
+
self.state.begin()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
fn check(&self) -> Result<(), LixError> {
|
|
126
|
+
(self.pre_commit_check)()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn commit<T>(&self, commit: impl FnOnce() -> Result<T, LixError>) -> Result<T, LixError> {
|
|
130
|
+
let _gate = self.state.lock_durable_commit();
|
|
131
|
+
self.check()?;
|
|
132
|
+
commit()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
#[derive(Clone)]
|
|
137
|
+
pub(crate) struct CommitBoundaryState {
|
|
138
|
+
active_count: Arc<AtomicUsize>,
|
|
139
|
+
durable_commit_gate: Arc<std::sync::Mutex<()>>,
|
|
140
|
+
watch: tokio::sync::watch::Sender<usize>,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
impl CommitBoundaryState {
|
|
144
|
+
pub(crate) fn new() -> Self {
|
|
145
|
+
let (watch, _) = tokio::sync::watch::channel(0);
|
|
146
|
+
Self {
|
|
147
|
+
active_count: Arc::new(AtomicUsize::new(0)),
|
|
148
|
+
durable_commit_gate: Arc::new(std::sync::Mutex::new(())),
|
|
149
|
+
watch,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pub(crate) fn begin(&self) -> CommitBoundaryGuard {
|
|
154
|
+
let previous = self.active_count.fetch_add(1, Ordering::SeqCst);
|
|
155
|
+
self.watch.send_replace(previous + 1);
|
|
156
|
+
CommitBoundaryGuard {
|
|
157
|
+
state: self.clone(),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pub(crate) fn active_count(&self) -> usize {
|
|
162
|
+
self.active_count.load(Ordering::SeqCst)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub(crate) fn is_active(&self) -> bool {
|
|
166
|
+
self.active_count() > 0
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
pub(crate) fn subscribe(&self) -> tokio::sync::watch::Receiver<usize> {
|
|
170
|
+
self.watch.subscribe()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
pub(crate) fn lock_durable_commit(&self) -> std::sync::MutexGuard<'_, ()> {
|
|
174
|
+
self.durable_commit_gate
|
|
175
|
+
.lock()
|
|
176
|
+
.expect("commit boundary durable commit gate should not poison")
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pub(crate) fn try_lock_durable_commit(&self) -> Option<std::sync::MutexGuard<'_, ()>> {
|
|
180
|
+
match self.durable_commit_gate.try_lock() {
|
|
181
|
+
Ok(guard) => Some(guard),
|
|
182
|
+
Err(std::sync::TryLockError::WouldBlock) => None,
|
|
183
|
+
Err(std::sync::TryLockError::Poisoned(_)) => {
|
|
184
|
+
panic!("commit boundary durable commit gate should not poison")
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
66
188
|
}
|
|
67
189
|
|
|
68
|
-
|
|
190
|
+
pub(crate) struct CommitBoundaryGuard {
|
|
191
|
+
state: CommitBoundaryState,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
impl Drop for CommitBoundaryGuard {
|
|
195
|
+
fn drop(&mut self) {
|
|
196
|
+
let remaining = self.state.active_count.fetch_sub(1, Ordering::SeqCst) - 1;
|
|
197
|
+
self.state.watch.send_replace(remaining);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pub(crate) fn begin_commit_boundary(
|
|
202
|
+
boundary: Option<&TransactionCommitBoundary>,
|
|
203
|
+
) -> Option<CommitBoundaryGuard> {
|
|
204
|
+
let boundary = boundary?;
|
|
205
|
+
Some(boundary.begin())
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
fn check_commit_boundary(boundary: Option<&TransactionCommitBoundary>) -> Result<(), LixError> {
|
|
209
|
+
if let Some(boundary) = boundary {
|
|
210
|
+
boundary.check()?;
|
|
211
|
+
}
|
|
212
|
+
Ok(())
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
pub(crate) fn commit_at_boundary<T>(
|
|
216
|
+
boundary: Option<&TransactionCommitBoundary>,
|
|
217
|
+
commit: impl FnOnce() -> Result<T, LixError>,
|
|
218
|
+
) -> Result<T, LixError> {
|
|
219
|
+
match boundary {
|
|
220
|
+
Some(boundary) => boundary.commit(commit),
|
|
221
|
+
None => commit(),
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
impl<B> Transaction<B>
|
|
226
|
+
where
|
|
227
|
+
B: StorageBackend + Clone + Send + Sync + 'static,
|
|
228
|
+
for<'backend> B::Read<'backend>: Clone + Send + Sync + 'static,
|
|
229
|
+
for<'backend> B::Write<'backend>: Send,
|
|
230
|
+
{
|
|
69
231
|
/// Opens a backend write transaction and creates an execution-scoped
|
|
70
232
|
/// staging area for SQL/provider hooks.
|
|
71
233
|
async fn open(
|
|
72
234
|
mode: &SessionMode,
|
|
73
|
-
storage: StorageContext
|
|
235
|
+
storage: StorageContext<B>,
|
|
74
236
|
live_state: Arc<LiveStateContext>,
|
|
75
237
|
tracked_state: Arc<TrackedStateContext>,
|
|
76
238
|
binary_cas: Arc<BinaryCasContext>,
|
|
77
|
-
|
|
78
|
-
version_ctx: Arc<VersionContext>,
|
|
239
|
+
branch_ctx: Arc<BranchContext>,
|
|
79
240
|
catalog_context: Arc<CatalogContext>,
|
|
80
|
-
) -> Result<OpenTransaction
|
|
81
|
-
let
|
|
241
|
+
) -> Result<OpenTransaction<B>, LixError> {
|
|
242
|
+
let read = storage.begin_read(StorageReadOptions::default())?;
|
|
82
243
|
let setup_result = async {
|
|
83
|
-
let
|
|
84
|
-
mode,
|
|
85
|
-
|
|
86
|
-
version_ctx.as_ref(),
|
|
87
|
-
storage_transaction.as_mut(),
|
|
88
|
-
)
|
|
89
|
-
.await?;
|
|
244
|
+
let active_branch_id =
|
|
245
|
+
resolve_active_branch_id(mode, live_state.as_ref(), branch_ctx.as_ref(), &read)
|
|
246
|
+
.await?;
|
|
90
247
|
let runtime_functions = {
|
|
91
|
-
let runtime_live_state = live_state.reader(
|
|
248
|
+
let runtime_live_state = live_state.reader(&read);
|
|
92
249
|
FunctionContext::prepare(&runtime_live_state).await?
|
|
93
250
|
};
|
|
94
251
|
let functions = runtime_functions.provider();
|
|
95
|
-
let visible_schemas = {
|
|
96
|
-
let visible_live_state = live_state.reader(storage_transaction.as_mut());
|
|
97
|
-
catalog_context
|
|
98
|
-
.schema_jsons_for_sql_read_planning(&visible_live_state, &active_version_id)
|
|
99
|
-
.await?
|
|
100
|
-
};
|
|
101
252
|
let schema_facts = {
|
|
102
|
-
let visible_live_state = live_state.reader(
|
|
253
|
+
let visible_live_state = live_state.reader(&read);
|
|
103
254
|
catalog_context
|
|
104
255
|
.schema_facts_for_domain(
|
|
105
256
|
&visible_live_state,
|
|
106
|
-
&Domain::schema_catalog(
|
|
257
|
+
&Domain::schema_catalog(active_branch_id.clone(), true),
|
|
107
258
|
)
|
|
108
259
|
.await?
|
|
109
260
|
};
|
|
110
|
-
Ok::<_, LixError>((
|
|
111
|
-
active_version_id,
|
|
112
|
-
runtime_functions,
|
|
113
|
-
functions,
|
|
114
|
-
visible_schemas,
|
|
115
|
-
schema_facts,
|
|
116
|
-
))
|
|
261
|
+
Ok::<_, LixError>((active_branch_id, runtime_functions, functions, schema_facts))
|
|
117
262
|
}
|
|
118
263
|
.await;
|
|
119
|
-
let (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
Err(error)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
};
|
|
127
|
-
let mut schema_resolver = TransactionSchemaResolver::new(catalog_context);
|
|
264
|
+
let (active_branch_id, runtime_functions, functions, schema_facts) = match setup_result {
|
|
265
|
+
Ok(result) => result,
|
|
266
|
+
Err(error) => {
|
|
267
|
+
return Err(error);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
let mut schema_resolver = TransactionSchemaResolver::new(Arc::clone(&catalog_context));
|
|
128
271
|
schema_resolver.remember_schema_facts(
|
|
129
|
-
&Domain::schema_catalog(
|
|
272
|
+
&Domain::schema_catalog(active_branch_id.clone(), true),
|
|
130
273
|
schema_facts,
|
|
131
274
|
);
|
|
132
275
|
let staged_writes = Arc::new(TransactionWriteBuffer::new(functions.clone()));
|
|
133
276
|
Ok(OpenTransaction {
|
|
134
277
|
transaction: Self {
|
|
135
|
-
|
|
278
|
+
active_branch_id,
|
|
136
279
|
live_state,
|
|
137
280
|
tracked_state,
|
|
138
281
|
binary_cas,
|
|
139
|
-
|
|
140
|
-
|
|
282
|
+
branch_ctx,
|
|
283
|
+
catalog_context,
|
|
141
284
|
schema_resolver,
|
|
142
285
|
staged_writes,
|
|
143
|
-
|
|
144
|
-
|
|
286
|
+
staged_storage_writes: StorageWriteSet::new(),
|
|
287
|
+
storage,
|
|
288
|
+
sql_schema_cache: SqlSchemaCache::default(),
|
|
145
289
|
functions,
|
|
290
|
+
commit_boundary: None,
|
|
146
291
|
},
|
|
147
292
|
runtime_functions,
|
|
148
293
|
})
|
|
@@ -150,42 +295,57 @@ impl Transaction {
|
|
|
150
295
|
|
|
151
296
|
/// Commits prepared writes, runtime function state, and the backend transaction.
|
|
152
297
|
///
|
|
153
|
-
/// Commit owns the execution boundary: prepared rows become
|
|
154
|
-
/// facts,
|
|
298
|
+
/// Commit owns the execution boundary: prepared rows become changelog
|
|
299
|
+
/// facts, branch-ref updates, and visible live_state rows before the
|
|
155
300
|
/// backend transaction is committed.
|
|
301
|
+
#[allow(dead_code)]
|
|
156
302
|
pub(crate) async fn commit(
|
|
157
|
-
|
|
303
|
+
self,
|
|
158
304
|
runtime_functions: &FunctionContext,
|
|
159
305
|
) -> Result<TransactionCommitOutcome, LixError> {
|
|
160
|
-
let
|
|
306
|
+
let mut transaction = self;
|
|
307
|
+
let commit_boundary = transaction.commit_boundary.clone();
|
|
308
|
+
let prepared_writes = match transaction.staged_writes.drain() {
|
|
161
309
|
Ok(prepared_writes) => prepared_writes,
|
|
162
310
|
Err(error) => {
|
|
163
|
-
let _ = self.storage_transaction.rollback().await;
|
|
164
311
|
return Err(error);
|
|
165
312
|
}
|
|
166
313
|
};
|
|
167
|
-
|
|
168
|
-
|
|
314
|
+
let _commit_guard = begin_commit_boundary(commit_boundary.as_ref());
|
|
315
|
+
check_commit_boundary(commit_boundary.as_ref())?;
|
|
316
|
+
if let Err(error) = transaction
|
|
317
|
+
.validate_prepared_writes_by_branch(&prepared_writes)
|
|
169
318
|
.await
|
|
170
319
|
{
|
|
171
|
-
let _ = self.storage_transaction.rollback().await;
|
|
172
320
|
return Err(error);
|
|
173
321
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
322
|
+
let mut read = transaction
|
|
323
|
+
.storage
|
|
324
|
+
.begin_read(StorageReadOptions::default())?;
|
|
325
|
+
let mut writes = match commit::commit_prepared_writes(
|
|
326
|
+
&transaction.binary_cas,
|
|
327
|
+
transaction.branch_ctx.as_ref(),
|
|
178
328
|
Some(runtime_functions),
|
|
179
|
-
|
|
329
|
+
&mut read,
|
|
180
330
|
prepared_writes,
|
|
181
331
|
)
|
|
182
332
|
.await
|
|
183
333
|
{
|
|
184
|
-
|
|
185
|
-
return Err(error)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
334
|
+
Ok(writes) => writes,
|
|
335
|
+
Err(error) => return Err(error),
|
|
336
|
+
};
|
|
337
|
+
writes.extend(transaction.staged_storage_writes);
|
|
338
|
+
let storage_stats = commit_at_boundary(commit_boundary.as_ref(), || {
|
|
339
|
+
let (_commit, stats) = transaction
|
|
340
|
+
.storage
|
|
341
|
+
.commit_write_set(writes, StorageWriteOptions::default())?;
|
|
342
|
+
Ok(stats)
|
|
343
|
+
})?;
|
|
344
|
+
Ok(TransactionCommitOutcome { storage_stats })
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
pub(crate) fn attach_commit_boundary(&mut self, boundary: TransactionCommitBoundary) {
|
|
348
|
+
self.commit_boundary = Some(boundary);
|
|
189
349
|
}
|
|
190
350
|
|
|
191
351
|
/// Rolls back the backend transaction.
|
|
@@ -195,7 +355,7 @@ impl Transaction {
|
|
|
195
355
|
/// rely on.
|
|
196
356
|
#[allow(dead_code)]
|
|
197
357
|
pub(crate) async fn rollback(self) -> Result<(), LixError> {
|
|
198
|
-
|
|
358
|
+
Ok(())
|
|
199
359
|
}
|
|
200
360
|
|
|
201
361
|
/// Stages one decoded write batch into this transaction.
|
|
@@ -203,7 +363,7 @@ impl Transaction {
|
|
|
203
363
|
/// This is the programmatic write entrypoint used by non-SQL APIs. The
|
|
204
364
|
/// transaction still owns preparation from `TransactionWriteRow` into
|
|
205
365
|
/// `PreparedStateRow`, so generated timestamps, change ids, commit ids, and
|
|
206
|
-
/// commit
|
|
366
|
+
/// commit change refs stay in one place.
|
|
207
367
|
#[allow(dead_code)]
|
|
208
368
|
pub(crate) async fn stage_write(
|
|
209
369
|
&mut self,
|
|
@@ -219,7 +379,7 @@ impl Transaction {
|
|
|
219
379
|
transaction_write_untracked_row_count(&write),
|
|
220
380
|
);
|
|
221
381
|
}
|
|
222
|
-
self.
|
|
382
|
+
self.require_existing_transaction_write_branch_ids(&write)
|
|
223
383
|
.await?;
|
|
224
384
|
let write = self.prepare_transaction_write(write).await?;
|
|
225
385
|
self.staged_writes.stage_write(write)
|
|
@@ -245,11 +405,6 @@ impl Transaction {
|
|
|
245
405
|
file_data,
|
|
246
406
|
count,
|
|
247
407
|
},
|
|
248
|
-
TransactionWrite::AdoptedChanges { changes } => {
|
|
249
|
-
PreparedTransactionWrite::AdoptedChanges {
|
|
250
|
-
rows: self.prepare_adopted_changes(changes).await?,
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
408
|
})
|
|
254
409
|
}
|
|
255
410
|
|
|
@@ -259,12 +414,13 @@ impl Transaction {
|
|
|
259
414
|
) -> Result<Vec<PreparedStateRow>, LixError> {
|
|
260
415
|
let row_count = rows.len();
|
|
261
416
|
let staged = self.staged_writes.staging_overlay()?;
|
|
262
|
-
let
|
|
417
|
+
let read = self.storage.begin_read(StorageReadOptions::default())?;
|
|
418
|
+
let live_state = self.live_state.reader(&read);
|
|
263
419
|
let mut rows_by_scope = BTreeMap::<Domain, Vec<(usize, TransactionWriteRow)>>::new();
|
|
264
420
|
for (index, row) in rows.into_iter().enumerate() {
|
|
265
421
|
rows_by_scope
|
|
266
422
|
.entry(Domain::schema_catalog(
|
|
267
|
-
row.
|
|
423
|
+
row.schema_scope_branch_id().to_string(),
|
|
268
424
|
row.untracked,
|
|
269
425
|
))
|
|
270
426
|
.or_default()
|
|
@@ -288,14 +444,11 @@ impl Transaction {
|
|
|
288
444
|
LixError::CODE_SCHEMA_DEFINITION,
|
|
289
445
|
"lix_registered_schema rows must not be scoped to a file",
|
|
290
446
|
)
|
|
291
|
-
.with_hint("Schema definitions are scoped by
|
|
447
|
+
.with_hint("Schema definitions are scoped by branch and durability only; write them with null file_id."));
|
|
292
448
|
}
|
|
293
449
|
remember_pending_registered_schema(
|
|
294
450
|
row.snapshot.as_ref().map(TransactionJson::value),
|
|
295
|
-
Domain::schema_catalog(
|
|
296
|
-
row.schema_scope_version_id().to_string(),
|
|
297
|
-
row.untracked,
|
|
298
|
-
),
|
|
451
|
+
Domain::schema_catalog(row.schema_scope_branch_id().to_string(), row.untracked),
|
|
299
452
|
catalog,
|
|
300
453
|
)?;
|
|
301
454
|
}
|
|
@@ -318,109 +471,23 @@ impl Transaction {
|
|
|
318
471
|
.collect())
|
|
319
472
|
}
|
|
320
473
|
|
|
321
|
-
async fn
|
|
322
|
-
&mut self,
|
|
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(
|
|
474
|
+
async fn validate_prepared_writes_by_branch(
|
|
409
475
|
&mut self,
|
|
410
476
|
prepared_writes: &PreparedWriteSet,
|
|
411
477
|
) -> Result<(), LixError> {
|
|
412
478
|
let validation_index = prepared_writes.validation_index();
|
|
413
479
|
for scope in validation_index.schema_scopes() {
|
|
414
480
|
#[cfg(feature = "storage-benches")]
|
|
415
|
-
crate::storage_bench::
|
|
416
|
-
let
|
|
417
|
-
let
|
|
481
|
+
crate::storage_bench::record_transaction_validation_branch();
|
|
482
|
+
let branch_prepared_writes = validation_index.validation_set_for_schema_scope(scope);
|
|
483
|
+
let read = self.storage.begin_read(StorageReadOptions::default())?;
|
|
484
|
+
let live_state = self.live_state.reader(&read);
|
|
418
485
|
let schema_catalog = self
|
|
419
486
|
.schema_resolver
|
|
420
487
|
.catalog_for_validation(&live_state, scope)
|
|
421
488
|
.await?;
|
|
422
489
|
validate_prepared_writes(TransactionValidationInput::new(
|
|
423
|
-
&
|
|
490
|
+
&branch_prepared_writes,
|
|
424
491
|
&schema_catalog,
|
|
425
492
|
&live_state,
|
|
426
493
|
))
|
|
@@ -442,21 +509,20 @@ impl Transaction {
|
|
|
442
509
|
.await
|
|
443
510
|
}
|
|
444
511
|
|
|
445
|
-
async fn
|
|
512
|
+
async fn require_existing_transaction_write_branch_ids(
|
|
446
513
|
&mut self,
|
|
447
514
|
write: &TransactionWrite,
|
|
448
515
|
) -> Result<(), LixError> {
|
|
449
|
-
let
|
|
450
|
-
let
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if version_id == GLOBAL_VERSION_ID {
|
|
516
|
+
let branch_ids = transaction_write_branch_ids(write);
|
|
517
|
+
let read = self.storage.begin_read(StorageReadOptions::default())?;
|
|
518
|
+
let reader = self.branch_ctx.ref_reader(&read);
|
|
519
|
+
for branch_id in branch_ids {
|
|
520
|
+
if branch_id == GLOBAL_BRANCH_ID {
|
|
455
521
|
continue;
|
|
456
522
|
}
|
|
457
|
-
if reader.load_head_commit_id(&
|
|
458
|
-
return Err(LixError::
|
|
459
|
-
|
|
523
|
+
if reader.load_head_commit_id(&branch_id).await?.is_none() {
|
|
524
|
+
return Err(LixError::branch_not_found(
|
|
525
|
+
branch_id,
|
|
460
526
|
"stage_write",
|
|
461
527
|
"target",
|
|
462
528
|
));
|
|
@@ -465,9 +531,9 @@ impl Transaction {
|
|
|
465
531
|
Ok(())
|
|
466
532
|
}
|
|
467
533
|
|
|
468
|
-
/// Returns the active
|
|
469
|
-
pub(crate) fn
|
|
470
|
-
&self.
|
|
534
|
+
/// Returns the active branch resolved inside this write transaction.
|
|
535
|
+
pub(crate) fn active_branch_id(&self) -> &str {
|
|
536
|
+
&self.active_branch_id
|
|
471
537
|
}
|
|
472
538
|
|
|
473
539
|
/// Returns this transaction's prepared runtime functions.
|
|
@@ -475,69 +541,216 @@ impl Transaction {
|
|
|
475
541
|
self.functions.clone()
|
|
476
542
|
}
|
|
477
543
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
544
|
+
pub(crate) async fn sql_read_execution_context(
|
|
545
|
+
&mut self,
|
|
546
|
+
) -> Result<TransactionSqlReadExecutionContext<B::Read<'_>>, LixError> {
|
|
547
|
+
self.prepare_sql_visible_schemas().await?;
|
|
548
|
+
self.sql_read_execution_context_from_cached_schemas()
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
fn sql_read_execution_context_from_cached_schemas(
|
|
484
552
|
&self,
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
.
|
|
553
|
+
) -> Result<TransactionSqlReadExecutionContext<B::Read<'_>>, LixError> {
|
|
554
|
+
let read_store = self.storage.begin_read(StorageReadOptions::default())?;
|
|
555
|
+
let staged = self.staged_writes.staging_overlay()?;
|
|
556
|
+
Ok(TransactionSqlReadExecutionContext {
|
|
557
|
+
active_branch_id: self.active_branch_id.clone(),
|
|
558
|
+
read_store,
|
|
559
|
+
live_state: Arc::clone(&self.live_state),
|
|
560
|
+
binary_cas: Arc::clone(&self.binary_cas),
|
|
561
|
+
branch_ctx: Arc::clone(&self.branch_ctx),
|
|
562
|
+
visible_schemas: self.cached_visible_schemas()?.to_vec(),
|
|
563
|
+
functions: self.functions.clone(),
|
|
564
|
+
staged,
|
|
565
|
+
})
|
|
490
566
|
}
|
|
491
567
|
|
|
492
|
-
|
|
568
|
+
pub(crate) async fn prepare_sql_visible_schemas(&mut self) -> Result<(), LixError> {
|
|
569
|
+
if self.sql_schema_cache.is_prepared() {
|
|
570
|
+
return Ok(());
|
|
571
|
+
}
|
|
572
|
+
let read = self.storage.begin_read(StorageReadOptions::default())?;
|
|
573
|
+
let live_state = self.live_state.reader(&read);
|
|
574
|
+
let visible_schemas = self
|
|
575
|
+
.catalog_context
|
|
576
|
+
.schema_jsons_for_sql_read_planning(&live_state, &self.active_branch_id)
|
|
577
|
+
.await?;
|
|
578
|
+
self.sql_schema_cache.prepare(visible_schemas);
|
|
579
|
+
Ok(())
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
fn cached_visible_schemas(&self) -> Result<&[JsonValue], LixError> {
|
|
583
|
+
self.sql_schema_cache.visible_schemas()
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/// Advances a branch ref without staging tracked rows.
|
|
493
587
|
///
|
|
494
588
|
/// Fast-forward merges use this path because the commit graph already
|
|
495
589
|
/// contains the source head; the target ref only needs to move to it.
|
|
496
|
-
pub(crate) async fn
|
|
590
|
+
pub(crate) async fn advance_branch_ref(
|
|
497
591
|
&mut self,
|
|
498
|
-
|
|
592
|
+
branch_id: &str,
|
|
499
593
|
commit_id: &str,
|
|
500
594
|
) -> Result<(), LixError> {
|
|
501
595
|
let timestamp = self.functions.call_timestamp();
|
|
502
|
-
let
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
.stage_canonical_ref_rows(&mut writes, &[canonical_row.row])?;
|
|
506
|
-
writes
|
|
507
|
-
.apply(&mut self.storage_transaction.as_mut())
|
|
508
|
-
.await
|
|
509
|
-
.map(|_| ())
|
|
596
|
+
let canonical_row = prepare_branch_ref_row(branch_id, commit_id, ×tamp)?;
|
|
597
|
+
self.branch_ctx
|
|
598
|
+
.stage_canonical_ref_rows(&mut self.staged_storage_writes, &[canonical_row.row])
|
|
510
599
|
}
|
|
511
600
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
601
|
+
pub(crate) fn stage_merge_commit(
|
|
602
|
+
&self,
|
|
603
|
+
branch_id: String,
|
|
604
|
+
source_parent_commit_id: String,
|
|
605
|
+
selected_changes: impl IntoIterator<Item = StagedCommitChangeRef>,
|
|
606
|
+
) -> Result<String, LixError> {
|
|
607
|
+
let commit_id = self
|
|
608
|
+
.staged_writes
|
|
609
|
+
.stage_selected_commit_change_refs(branch_id.clone(), selected_changes)?;
|
|
610
|
+
self.staged_writes
|
|
611
|
+
.add_commit_parent(branch_id, source_parent_commit_id)?;
|
|
612
|
+
Ok(commit_id)
|
|
521
613
|
}
|
|
522
614
|
|
|
523
|
-
/// Creates a
|
|
524
|
-
pub(crate) fn
|
|
525
|
-
self
|
|
526
|
-
.
|
|
615
|
+
/// Creates a branch-ref reader scoped to this write transaction.
|
|
616
|
+
pub(crate) fn branch_ref_reader(&mut self) -> impl BranchRefReader + '_ {
|
|
617
|
+
let read = self
|
|
618
|
+
.storage
|
|
619
|
+
.begin_read(StorageReadOptions::default())
|
|
620
|
+
.expect("open transaction read scope");
|
|
621
|
+
self.branch_ctx.ref_reader(read)
|
|
527
622
|
}
|
|
528
623
|
|
|
529
624
|
/// Creates a tracked-state reader scoped to this write transaction.
|
|
530
625
|
pub(crate) fn tracked_state_reader(
|
|
531
626
|
&mut self,
|
|
532
|
-
) -> TrackedStateStoreReader
|
|
533
|
-
self
|
|
627
|
+
) -> TrackedStateStoreReader<StorageReadScope<B::Read<'_>>> {
|
|
628
|
+
let read = self
|
|
629
|
+
.storage
|
|
630
|
+
.begin_read(StorageReadOptions::default())
|
|
631
|
+
.expect("open transaction read scope");
|
|
632
|
+
self.tracked_state.reader(read)
|
|
534
633
|
}
|
|
535
634
|
|
|
536
635
|
/// Creates a commit-graph reader scoped to this write transaction.
|
|
537
636
|
pub(crate) fn commit_graph_reader(
|
|
538
637
|
&mut self,
|
|
539
|
-
) -> CommitGraphStoreReader
|
|
540
|
-
|
|
638
|
+
) -> CommitGraphStoreReader<StorageReadScope<B::Read<'_>>> {
|
|
639
|
+
let read = self
|
|
640
|
+
.storage
|
|
641
|
+
.begin_read(StorageReadOptions::default())
|
|
642
|
+
.expect("open transaction read scope");
|
|
643
|
+
CommitGraphContext::new().reader(read)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
pub(crate) struct TransactionSqlReadExecutionContext<R> {
|
|
648
|
+
active_branch_id: String,
|
|
649
|
+
read_store: StorageReadScope<R>,
|
|
650
|
+
live_state: Arc<LiveStateContext>,
|
|
651
|
+
binary_cas: Arc<BinaryCasContext>,
|
|
652
|
+
branch_ctx: Arc<BranchContext>,
|
|
653
|
+
visible_schemas: Vec<JsonValue>,
|
|
654
|
+
functions: FunctionProviderHandle,
|
|
655
|
+
staged: crate::transaction::staging::PreparedStateRowOverlay,
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
impl<R> TransactionSqlReadExecutionContext<R>
|
|
659
|
+
where
|
|
660
|
+
R: crate::storage::StorageBackendRead,
|
|
661
|
+
{
|
|
662
|
+
pub(crate) fn close(self) -> Result<(), LixError> {
|
|
663
|
+
self.read_store.close().map_err(Into::into)
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
impl<R> SqlExecutionContext for TransactionSqlReadExecutionContext<R>
|
|
668
|
+
where
|
|
669
|
+
R: crate::storage::StorageBackendRead + Clone + Send + Sync + 'static,
|
|
670
|
+
{
|
|
671
|
+
type ReadStore = StorageReadScope<R>;
|
|
672
|
+
|
|
673
|
+
fn active_branch_id(&self) -> &str {
|
|
674
|
+
&self.active_branch_id
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
fn live_state(&self) -> Arc<dyn crate::live_state::LiveStateReader> {
|
|
678
|
+
Arc::new(TransactionReadLiveStateReader {
|
|
679
|
+
base: self.live_state.reader(self.read_store.clone()),
|
|
680
|
+
staged: self.staged.clone(),
|
|
681
|
+
})
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
fn functions(&self) -> FunctionProviderHandle {
|
|
685
|
+
self.functions.clone()
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
fn history_query_source(&self) -> SqlHistoryQuerySource<Self::ReadStore> {
|
|
689
|
+
HistoryQuerySource {
|
|
690
|
+
json_reader: crate::json_store::JsonStoreContext::new().reader(self.read_store.store()),
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
fn changelog_query_source(&self) -> SqlChangelogQuerySource<Self::ReadStore> {
|
|
695
|
+
ChangelogQuerySource {
|
|
696
|
+
store: self.read_store.clone(),
|
|
697
|
+
json_reader: crate::json_store::JsonStoreContext::new().reader(self.read_store.store()),
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
fn commit_graph(&self) -> Box<dyn crate::commit_graph::CommitGraphReader> {
|
|
702
|
+
Box::new(CommitGraphContext::new().reader(self.read_store.clone()))
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
fn branch_ref(&self) -> Arc<dyn BranchRefReader> {
|
|
706
|
+
Arc::new(self.branch_ctx.ref_reader(self.read_store.clone()))
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
fn blob_reader(&self) -> Arc<dyn crate::binary_cas::BlobDataReader> {
|
|
710
|
+
Arc::new(self.binary_cas.reader(self.read_store.clone()))
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
fn list_visible_schemas(&self) -> Result<Vec<JsonValue>, LixError> {
|
|
714
|
+
Ok(self.visible_schemas.clone())
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
struct TransactionReadLiveStateReader<R> {
|
|
719
|
+
base: crate::live_state::LiveStateStoreReader<StorageReadScope<R>>,
|
|
720
|
+
staged: crate::transaction::staging::PreparedStateRowOverlay,
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
#[async_trait]
|
|
724
|
+
impl<R> crate::live_state::LiveStateReader for TransactionReadLiveStateReader<R>
|
|
725
|
+
where
|
|
726
|
+
R: crate::storage::StorageBackendRead + Clone + Send + Sync,
|
|
727
|
+
{
|
|
728
|
+
async fn scan_rows(
|
|
729
|
+
&self,
|
|
730
|
+
request: &LiveStateScanRequest,
|
|
731
|
+
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
732
|
+
overlay_scan_rows(&self.base, &self.staged, request).await
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async fn load_row(
|
|
736
|
+
&self,
|
|
737
|
+
request: &LiveStateRowRequest,
|
|
738
|
+
) -> Result<Option<MaterializedLiveStateRow>, LixError> {
|
|
739
|
+
Ok(self
|
|
740
|
+
.scan_rows(&LiveStateScanRequest {
|
|
741
|
+
filter: crate::live_state::LiveStateFilter {
|
|
742
|
+
schema_keys: vec![request.schema_key.clone()],
|
|
743
|
+
entity_pks: vec![request.entity_pk.clone()],
|
|
744
|
+
branch_ids: vec![request.branch_id.clone()],
|
|
745
|
+
file_ids: vec![request.file_id.clone()],
|
|
746
|
+
..Default::default()
|
|
747
|
+
},
|
|
748
|
+
limit: Some(1),
|
|
749
|
+
..Default::default()
|
|
750
|
+
})
|
|
751
|
+
.await?
|
|
752
|
+
.into_iter()
|
|
753
|
+
.next())
|
|
541
754
|
}
|
|
542
755
|
}
|
|
543
756
|
|
|
@@ -562,10 +775,10 @@ fn prepare_state_row(
|
|
|
562
775
|
Ok(PreparedStateRow {
|
|
563
776
|
schema_plan_id,
|
|
564
777
|
facts,
|
|
565
|
-
|
|
778
|
+
entity_pk: row.entity_pk.ok_or_else(|| {
|
|
566
779
|
LixError::new(
|
|
567
780
|
"LIX_ERROR_UNKNOWN",
|
|
568
|
-
"normalized transaction write row is missing
|
|
781
|
+
"normalized transaction write row is missing entity_pk",
|
|
569
782
|
)
|
|
570
783
|
})?,
|
|
571
784
|
schema_key: row.schema_key,
|
|
@@ -583,115 +796,50 @@ fn prepare_state_row(
|
|
|
583
796
|
},
|
|
584
797
|
commit_id: row.commit_id,
|
|
585
798
|
untracked: row.untracked,
|
|
586
|
-
|
|
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,
|
|
799
|
+
branch_id: row.branch_id,
|
|
646
800
|
})
|
|
647
801
|
}
|
|
648
802
|
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
663
|
-
pub(crate) struct OpenTransaction {
|
|
664
|
-
pub(crate) transaction: Transaction,
|
|
803
|
+
pub(crate) struct OpenTransaction<B: StorageBackend = InMemoryStorageBackend> {
|
|
804
|
+
pub(crate) transaction: Transaction<B>,
|
|
665
805
|
pub(crate) runtime_functions: FunctionContext,
|
|
666
806
|
}
|
|
667
807
|
|
|
668
|
-
pub(crate) async fn open_transaction(
|
|
808
|
+
pub(crate) async fn open_transaction<B>(
|
|
669
809
|
mode: &SessionMode,
|
|
670
|
-
storage: StorageContext
|
|
810
|
+
storage: StorageContext<B>,
|
|
671
811
|
live_state: Arc<LiveStateContext>,
|
|
672
812
|
tracked_state: Arc<TrackedStateContext>,
|
|
673
813
|
binary_cas: Arc<BinaryCasContext>,
|
|
674
|
-
|
|
675
|
-
version_ctx: Arc<VersionContext>,
|
|
814
|
+
branch_ctx: Arc<BranchContext>,
|
|
676
815
|
catalog_context: Arc<CatalogContext>,
|
|
677
|
-
) -> Result<OpenTransaction
|
|
816
|
+
) -> Result<OpenTransaction<B>, LixError>
|
|
817
|
+
where
|
|
818
|
+
B: StorageBackend + Clone + Send + Sync + 'static,
|
|
819
|
+
for<'backend> B::Read<'backend>: Clone + Send + Sync + 'static,
|
|
820
|
+
for<'backend> B::Write<'backend>: Send,
|
|
821
|
+
{
|
|
678
822
|
Transaction::open(
|
|
679
823
|
mode,
|
|
680
824
|
storage,
|
|
681
825
|
live_state,
|
|
682
826
|
tracked_state,
|
|
683
827
|
binary_cas,
|
|
684
|
-
|
|
685
|
-
version_ctx,
|
|
828
|
+
branch_ctx,
|
|
686
829
|
catalog_context,
|
|
687
830
|
)
|
|
688
831
|
.await
|
|
689
832
|
}
|
|
690
833
|
|
|
691
834
|
#[async_trait]
|
|
692
|
-
impl SqlWriteExecutionContext for Transaction
|
|
693
|
-
|
|
694
|
-
|
|
835
|
+
impl<B> SqlWriteExecutionContext for Transaction<B>
|
|
836
|
+
where
|
|
837
|
+
B: StorageBackend + Clone + Send + Sync + 'static,
|
|
838
|
+
for<'backend> B::Read<'backend>: Clone + Send + Sync + 'static,
|
|
839
|
+
for<'backend> B::Write<'backend>: Send,
|
|
840
|
+
{
|
|
841
|
+
fn active_branch_id(&self) -> &str {
|
|
842
|
+
&self.active_branch_id
|
|
695
843
|
}
|
|
696
844
|
|
|
697
845
|
fn functions(&self) -> FunctionProviderHandle {
|
|
@@ -699,14 +847,12 @@ impl SqlWriteExecutionContext for Transaction {
|
|
|
699
847
|
}
|
|
700
848
|
|
|
701
849
|
fn list_visible_schemas(&self) -> Result<Vec<JsonValue>, LixError> {
|
|
702
|
-
Ok(self.
|
|
850
|
+
Ok(self.cached_visible_schemas()?.to_vec())
|
|
703
851
|
}
|
|
704
852
|
|
|
705
853
|
async fn load_bytes_many(&mut self, hashes: &[BlobHash]) -> Result<BlobBytesBatch, LixError> {
|
|
706
|
-
self.
|
|
707
|
-
|
|
708
|
-
.load_bytes_many(hashes)
|
|
709
|
-
.await
|
|
854
|
+
let read = self.storage.begin_read(StorageReadOptions::default())?;
|
|
855
|
+
self.binary_cas.reader(&read).load_bytes_many(hashes).await
|
|
710
856
|
}
|
|
711
857
|
|
|
712
858
|
async fn scan_live_state(
|
|
@@ -714,15 +860,19 @@ impl SqlWriteExecutionContext for Transaction {
|
|
|
714
860
|
request: &LiveStateScanRequest,
|
|
715
861
|
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
716
862
|
let staged = self.staged_writes.staging_overlay()?;
|
|
717
|
-
let
|
|
863
|
+
let read = self.storage.begin_read(StorageReadOptions::default())?;
|
|
864
|
+
let base = self.live_state.reader(&read);
|
|
718
865
|
overlay_scan_rows(&base, &staged, request).await
|
|
719
866
|
}
|
|
720
867
|
|
|
721
|
-
async fn
|
|
722
|
-
self.
|
|
723
|
-
|
|
724
|
-
.
|
|
725
|
-
.
|
|
868
|
+
async fn load_branch_head(&mut self, branch_id: &str) -> Result<Option<String>, LixError> {
|
|
869
|
+
let read = self.storage.begin_read(StorageReadOptions::default())?;
|
|
870
|
+
let result = self
|
|
871
|
+
.branch_ctx
|
|
872
|
+
.ref_reader(&read)
|
|
873
|
+
.load_head_commit_id(branch_id)
|
|
874
|
+
.await;
|
|
875
|
+
result
|
|
726
876
|
}
|
|
727
877
|
|
|
728
878
|
async fn stage_write(
|
|
@@ -733,18 +883,14 @@ impl SqlWriteExecutionContext for Transaction {
|
|
|
733
883
|
}
|
|
734
884
|
}
|
|
735
885
|
|
|
736
|
-
fn
|
|
886
|
+
fn transaction_write_branch_ids(write: &TransactionWrite) -> BTreeSet<String> {
|
|
737
887
|
match write {
|
|
738
|
-
TransactionWrite::Rows { rows, .. } =>
|
|
888
|
+
TransactionWrite::Rows { rows, .. } => transaction_write_row_branch_ids(rows),
|
|
739
889
|
TransactionWrite::RowsWithFileData {
|
|
740
890
|
rows, file_data, ..
|
|
741
|
-
} =>
|
|
891
|
+
} => transaction_write_row_branch_ids(rows)
|
|
742
892
|
.into_iter()
|
|
743
|
-
.chain(
|
|
744
|
-
.collect(),
|
|
745
|
-
TransactionWrite::AdoptedChanges { changes } => changes
|
|
746
|
-
.iter()
|
|
747
|
-
.map(|change| change.version_id.clone())
|
|
893
|
+
.chain(stage_file_data_branch_ids(file_data))
|
|
748
894
|
.collect(),
|
|
749
895
|
}
|
|
750
896
|
}
|
|
@@ -754,7 +900,6 @@ fn transaction_write_row_count(write: &TransactionWrite) -> usize {
|
|
|
754
900
|
match write {
|
|
755
901
|
TransactionWrite::Rows { rows, .. } => rows.len(),
|
|
756
902
|
TransactionWrite::RowsWithFileData { rows, .. } => rows.len(),
|
|
757
|
-
TransactionWrite::AdoptedChanges { changes } => changes.len(),
|
|
758
903
|
}
|
|
759
904
|
}
|
|
760
905
|
|
|
@@ -765,7 +910,6 @@ fn transaction_write_untracked_row_count(write: &TransactionWrite) -> usize {
|
|
|
765
910
|
TransactionWrite::RowsWithFileData { rows, .. } => {
|
|
766
911
|
rows.iter().filter(|row| row.untracked).count()
|
|
767
912
|
}
|
|
768
|
-
TransactionWrite::AdoptedChanges { .. } => 0,
|
|
769
913
|
}
|
|
770
914
|
}
|
|
771
915
|
|
|
@@ -779,7 +923,6 @@ fn require_valid_transaction_write_storage_scopes(
|
|
|
779
923
|
TransactionWrite::RowsWithFileData { rows, .. } => {
|
|
780
924
|
require_valid_transaction_write_row_storage_scopes(rows)
|
|
781
925
|
}
|
|
782
|
-
TransactionWrite::AdoptedChanges { .. } => Ok(()),
|
|
783
926
|
}
|
|
784
927
|
}
|
|
785
928
|
|
|
@@ -787,103 +930,101 @@ fn require_valid_transaction_write_row_storage_scopes(
|
|
|
787
930
|
rows: &[TransactionWriteRow],
|
|
788
931
|
) -> Result<(), LixError> {
|
|
789
932
|
for row in rows {
|
|
790
|
-
require_valid_storage_scope(row.
|
|
933
|
+
require_valid_storage_scope(row.branch_id.as_str(), row.global)?;
|
|
791
934
|
}
|
|
792
935
|
Ok(())
|
|
793
936
|
}
|
|
794
937
|
|
|
795
|
-
fn require_valid_storage_scope(
|
|
796
|
-
if global != (
|
|
938
|
+
fn require_valid_storage_scope(branch_id: &str, global: bool) -> Result<(), LixError> {
|
|
939
|
+
if global != (branch_id == GLOBAL_BRANCH_ID) {
|
|
797
940
|
return Err(LixError::new(
|
|
798
941
|
LixError::CODE_INVALID_STORAGE_SCOPE,
|
|
799
|
-
format!("invalid storage scope:
|
|
942
|
+
format!("invalid storage scope: branch_id='{branch_id}', global={global}"),
|
|
800
943
|
));
|
|
801
944
|
}
|
|
802
945
|
Ok(())
|
|
803
946
|
}
|
|
804
947
|
|
|
805
|
-
fn
|
|
806
|
-
rows.iter().map(|row| row.
|
|
948
|
+
fn transaction_write_row_branch_ids(rows: &[TransactionWriteRow]) -> BTreeSet<String> {
|
|
949
|
+
rows.iter().map(|row| row.branch_id.clone()).collect()
|
|
807
950
|
}
|
|
808
951
|
|
|
809
|
-
fn
|
|
952
|
+
fn stage_file_data_branch_ids(file_data: &[TransactionFileData]) -> BTreeSet<String> {
|
|
810
953
|
file_data
|
|
811
954
|
.iter()
|
|
812
|
-
.map(|write| write.
|
|
955
|
+
.map(|write| write.branch_id.clone())
|
|
813
956
|
.collect()
|
|
814
957
|
}
|
|
815
958
|
|
|
816
|
-
async fn
|
|
959
|
+
async fn resolve_active_branch_id(
|
|
817
960
|
mode: &SessionMode,
|
|
818
961
|
live_state: &LiveStateContext,
|
|
819
|
-
|
|
820
|
-
|
|
962
|
+
branch_ctx: &BranchContext,
|
|
963
|
+
read: &(impl StorageRead + Send + Sync + ?Sized),
|
|
821
964
|
) -> Result<String, LixError> {
|
|
822
965
|
match mode {
|
|
823
|
-
SessionMode::Pinned {
|
|
824
|
-
SessionMode::Workspace =>
|
|
825
|
-
load_workspace_version_id(live_state, version_ctx, transaction).await
|
|
826
|
-
}
|
|
966
|
+
SessionMode::Pinned { branch_id } => Ok(branch_id.clone()),
|
|
967
|
+
SessionMode::Workspace => load_workspace_branch_id(live_state, branch_ctx, read).await,
|
|
827
968
|
}
|
|
828
969
|
}
|
|
829
970
|
|
|
830
|
-
async fn
|
|
971
|
+
async fn load_workspace_branch_id(
|
|
831
972
|
live_state: &LiveStateContext,
|
|
832
|
-
|
|
833
|
-
|
|
973
|
+
branch_ctx: &BranchContext,
|
|
974
|
+
read: &(impl StorageRead + Send + Sync + ?Sized),
|
|
834
975
|
) -> Result<String, LixError> {
|
|
835
976
|
let row = live_state
|
|
836
|
-
.reader(
|
|
977
|
+
.reader(read)
|
|
837
978
|
.load_row(&LiveStateRowRequest {
|
|
838
979
|
schema_key: "lix_key_value".to_string(),
|
|
839
|
-
|
|
840
|
-
|
|
980
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
981
|
+
entity_pk: EntityPk::single(WORKSPACE_BRANCH_KEY),
|
|
841
982
|
file_id: NullableKeyFilter::Null,
|
|
842
983
|
})
|
|
843
984
|
.await?
|
|
844
985
|
.ok_or_else(|| {
|
|
845
986
|
LixError::new(
|
|
846
987
|
"LIX_ERROR_UNKNOWN",
|
|
847
|
-
"workspace
|
|
988
|
+
"workspace branch selector is missing lix_key_value:lix_workspace_branch_id",
|
|
848
989
|
)
|
|
849
990
|
})?;
|
|
850
991
|
let snapshot_content = row.snapshot_content.as_deref().ok_or_else(|| {
|
|
851
992
|
LixError::new(
|
|
852
993
|
"LIX_ERROR_UNKNOWN",
|
|
853
|
-
"workspace
|
|
994
|
+
"workspace branch selector is missing snapshot_content",
|
|
854
995
|
)
|
|
855
996
|
})?;
|
|
856
997
|
let snapshot = serde_json::from_str::<JsonValue>(snapshot_content).map_err(|error| {
|
|
857
998
|
LixError::new(
|
|
858
999
|
"LIX_ERROR_UNKNOWN",
|
|
859
|
-
format!("workspace
|
|
1000
|
+
format!("workspace branch selector snapshot is invalid JSON: {error}"),
|
|
860
1001
|
)
|
|
861
1002
|
})?;
|
|
862
|
-
let
|
|
1003
|
+
let branch_id = snapshot
|
|
863
1004
|
.get("value")
|
|
864
1005
|
.and_then(JsonValue::as_str)
|
|
865
1006
|
.filter(|value| !value.is_empty())
|
|
866
1007
|
.ok_or_else(|| {
|
|
867
1008
|
LixError::new(
|
|
868
1009
|
"LIX_ERROR_UNKNOWN",
|
|
869
|
-
"workspace
|
|
1010
|
+
"workspace branch selector value must be a non-empty string",
|
|
870
1011
|
)
|
|
871
1012
|
})?
|
|
872
1013
|
.to_string();
|
|
873
1014
|
|
|
874
|
-
let head =
|
|
875
|
-
.ref_reader(
|
|
876
|
-
.load_head_commit_id(&
|
|
1015
|
+
let head = branch_ctx
|
|
1016
|
+
.ref_reader(read)
|
|
1017
|
+
.load_head_commit_id(&branch_id)
|
|
877
1018
|
.await?;
|
|
878
1019
|
if head.is_none() {
|
|
879
|
-
return Err(LixError::
|
|
880
|
-
|
|
881
|
-
"
|
|
1020
|
+
return Err(LixError::branch_not_found(
|
|
1021
|
+
branch_id,
|
|
1022
|
+
"load_workspace_branch_id",
|
|
882
1023
|
"workspace_selector",
|
|
883
1024
|
));
|
|
884
1025
|
}
|
|
885
1026
|
|
|
886
|
-
Ok(
|
|
1027
|
+
Ok(branch_id)
|
|
887
1028
|
}
|
|
888
1029
|
|
|
889
1030
|
#[cfg(test)]
|
|
@@ -893,15 +1034,14 @@ mod tests {
|
|
|
893
1034
|
use serde_json::json;
|
|
894
1035
|
|
|
895
1036
|
use super::*;
|
|
896
|
-
use crate::
|
|
897
|
-
use crate::
|
|
898
|
-
use crate::
|
|
1037
|
+
use crate::branch::BranchContext;
|
|
1038
|
+
use crate::changelog::ChangelogReader;
|
|
1039
|
+
use crate::storage::{InMemoryStorageBackend, StorageReadOptions};
|
|
1040
|
+
use crate::tracked_state::{TrackedStateKey, TrackedStateScanRequest};
|
|
899
1041
|
use crate::transaction::types::TransactionJson;
|
|
900
1042
|
use crate::untracked_state::{UntrackedStateContext, UntrackedStateRowRequest};
|
|
901
|
-
use crate::version::VersionContext;
|
|
902
|
-
use crate::Backend;
|
|
903
1043
|
use crate::NullableKeyFilter;
|
|
904
|
-
use crate::
|
|
1044
|
+
use crate::GLOBAL_BRANCH_ID;
|
|
905
1045
|
|
|
906
1046
|
fn live_state_context() -> LiveStateContext {
|
|
907
1047
|
LiveStateContext::new(
|
|
@@ -915,25 +1055,23 @@ mod tests {
|
|
|
915
1055
|
|
|
916
1056
|
#[tokio::test]
|
|
917
1057
|
async fn stage_rows_routes_tracked_and_untracked_rows_without_sql() {
|
|
918
|
-
let backend
|
|
919
|
-
let storage = StorageContext::new(
|
|
1058
|
+
let backend = InMemoryStorageBackend::new();
|
|
1059
|
+
let storage = StorageContext::new(backend.clone());
|
|
920
1060
|
let live_state = Arc::new(live_state_context());
|
|
921
1061
|
seed_visible_schema_rows(storage.clone()).await;
|
|
922
1062
|
let binary_cas = Arc::new(BinaryCasContext::new());
|
|
923
|
-
let
|
|
924
|
-
let
|
|
925
|
-
let version_ctx = Arc::new(VersionContext::new(Arc::new(UntrackedStateContext::new())));
|
|
1063
|
+
let tracked_state = Arc::new(crate::tracked_state::TrackedStateContext::new());
|
|
1064
|
+
let branch_ctx = Arc::new(BranchContext::new(Arc::new(UntrackedStateContext::new())));
|
|
926
1065
|
let catalog_context = Arc::new(CatalogContext::new());
|
|
927
1066
|
let opened = open_transaction(
|
|
928
1067
|
&SessionMode::Pinned {
|
|
929
|
-
|
|
1068
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
930
1069
|
},
|
|
931
1070
|
storage.clone(),
|
|
932
1071
|
Arc::clone(&live_state),
|
|
933
|
-
Arc::
|
|
1072
|
+
Arc::clone(&tracked_state),
|
|
934
1073
|
Arc::clone(&binary_cas),
|
|
935
|
-
Arc::clone(&
|
|
936
|
-
Arc::clone(&version_ctx),
|
|
1074
|
+
Arc::clone(&branch_ctx),
|
|
937
1075
|
Arc::clone(&catalog_context),
|
|
938
1076
|
)
|
|
939
1077
|
.await
|
|
@@ -953,47 +1091,70 @@ mod tests {
|
|
|
953
1091
|
.await
|
|
954
1092
|
.expect("transaction should commit");
|
|
955
1093
|
|
|
956
|
-
let
|
|
957
|
-
.reader(
|
|
958
|
-
|
|
1094
|
+
let tracked_row = live_state
|
|
1095
|
+
.reader(
|
|
1096
|
+
storage
|
|
1097
|
+
.begin_read(StorageReadOptions::default())
|
|
1098
|
+
.expect("read should open"),
|
|
1099
|
+
)
|
|
1100
|
+
.load_row(&LiveStateRowRequest {
|
|
1101
|
+
schema_key: "lix_key_value".to_string(),
|
|
1102
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1103
|
+
entity_pk: crate::entity_pk::EntityPk::single("tracked-programmatic"),
|
|
1104
|
+
file_id: NullableKeyFilter::Null,
|
|
1105
|
+
})
|
|
959
1106
|
.await
|
|
960
|
-
.expect("
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1107
|
+
.expect("tracked row should load")
|
|
1108
|
+
.expect("tracked row should exist");
|
|
1109
|
+
let tracked_change_id = tracked_row
|
|
1110
|
+
.change_id
|
|
1111
|
+
.as_ref()
|
|
1112
|
+
.expect("tracked row should have a change id")
|
|
1113
|
+
.clone();
|
|
1114
|
+
let mut changelog_reader = crate::changelog::ChangelogContext::new().reader(
|
|
1115
|
+
storage
|
|
1116
|
+
.begin_read(StorageReadOptions::default())
|
|
1117
|
+
.expect("read should open"),
|
|
969
1118
|
);
|
|
1119
|
+
let changes = changelog_reader
|
|
1120
|
+
.load_changes(crate::changelog::ChangeLoadRequest {
|
|
1121
|
+
change_ids: &[tracked_change_id],
|
|
1122
|
+
})
|
|
1123
|
+
.await
|
|
1124
|
+
.expect("changelog should load tracked change");
|
|
970
1125
|
assert!(
|
|
971
|
-
!
|
|
972
|
-
.
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
"
|
|
1126
|
+
matches!(
|
|
1127
|
+
changes.entries.as_slice(),
|
|
1128
|
+
[Some(change)]
|
|
1129
|
+
if change.entity_pk.as_single_string_owned().as_deref()
|
|
1130
|
+
== Ok("tracked-programmatic")
|
|
1131
|
+
),
|
|
1132
|
+
"tracked staged row should be appended to changelog"
|
|
978
1133
|
);
|
|
979
1134
|
|
|
980
|
-
let head_commit_id =
|
|
981
|
-
.ref_reader(
|
|
982
|
-
|
|
1135
|
+
let head_commit_id = branch_ctx
|
|
1136
|
+
.ref_reader(
|
|
1137
|
+
storage
|
|
1138
|
+
.begin_read(StorageReadOptions::default())
|
|
1139
|
+
.expect("read should open"),
|
|
1140
|
+
)
|
|
1141
|
+
.load_head_commit_id(GLOBAL_BRANCH_ID)
|
|
983
1142
|
.await
|
|
984
|
-
.expect("
|
|
985
|
-
.expect("tracked commit should advance the global
|
|
1143
|
+
.expect("branch ref should load")
|
|
1144
|
+
.expect("tracked commit should advance the global branch ref");
|
|
986
1145
|
|
|
987
1146
|
let tracked_row = crate::tracked_state::TrackedStateContext::new()
|
|
988
|
-
.reader(
|
|
1147
|
+
.reader(
|
|
1148
|
+
storage
|
|
1149
|
+
.begin_read(StorageReadOptions::default())
|
|
1150
|
+
.expect("read should open"),
|
|
1151
|
+
)
|
|
989
1152
|
.load_rows_at_commit(
|
|
990
1153
|
&head_commit_id,
|
|
991
|
-
&[
|
|
1154
|
+
&[TrackedStateKey {
|
|
992
1155
|
schema_key: "lix_key_value".to_string(),
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
),
|
|
996
|
-
file_id: NullableKeyFilter::Null,
|
|
1156
|
+
entity_pk: crate::entity_pk::EntityPk::single("tracked-programmatic"),
|
|
1157
|
+
file_id: None,
|
|
997
1158
|
}],
|
|
998
1159
|
)
|
|
999
1160
|
.await
|
|
@@ -1008,11 +1169,15 @@ mod tests {
|
|
|
1008
1169
|
);
|
|
1009
1170
|
|
|
1010
1171
|
let untracked_row = crate::untracked_state::UntrackedStateContext::new()
|
|
1011
|
-
.reader(
|
|
1172
|
+
.reader(
|
|
1173
|
+
storage
|
|
1174
|
+
.begin_read(StorageReadOptions::default())
|
|
1175
|
+
.expect("read should open"),
|
|
1176
|
+
)
|
|
1012
1177
|
.load_row(&UntrackedStateRowRequest {
|
|
1013
1178
|
schema_key: "lix_key_value".to_string(),
|
|
1014
|
-
|
|
1015
|
-
|
|
1179
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1180
|
+
entity_pk: crate::entity_pk::EntityPk::single("untracked-programmatic"),
|
|
1016
1181
|
file_id: NullableKeyFilter::Null,
|
|
1017
1182
|
})
|
|
1018
1183
|
.await
|
|
@@ -1024,11 +1189,15 @@ mod tests {
|
|
|
1024
1189
|
);
|
|
1025
1190
|
|
|
1026
1191
|
let live_untracked_row = live_state
|
|
1027
|
-
.reader(
|
|
1192
|
+
.reader(
|
|
1193
|
+
storage
|
|
1194
|
+
.begin_read(StorageReadOptions::default())
|
|
1195
|
+
.expect("read should open"),
|
|
1196
|
+
)
|
|
1028
1197
|
.load_row(&crate::live_state::LiveStateRowRequest {
|
|
1029
1198
|
schema_key: "lix_key_value".to_string(),
|
|
1030
|
-
|
|
1031
|
-
|
|
1199
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1200
|
+
entity_pk: crate::entity_pk::EntityPk::single("untracked-programmatic"),
|
|
1032
1201
|
file_id: NullableKeyFilter::Null,
|
|
1033
1202
|
})
|
|
1034
1203
|
.await
|
|
@@ -1036,17 +1205,21 @@ mod tests {
|
|
|
1036
1205
|
.expect("untracked row should be visible through live state");
|
|
1037
1206
|
assert!(live_untracked_row.untracked);
|
|
1038
1207
|
assert!(live_untracked_row.global);
|
|
1039
|
-
assert_eq!(live_untracked_row.
|
|
1208
|
+
assert_eq!(live_untracked_row.branch_id, GLOBAL_BRANCH_ID);
|
|
1040
1209
|
|
|
1041
1210
|
let tracked_rows = crate::tracked_state::TrackedStateContext::new()
|
|
1042
|
-
.reader(
|
|
1211
|
+
.reader(
|
|
1212
|
+
storage
|
|
1213
|
+
.begin_read(StorageReadOptions::default())
|
|
1214
|
+
.expect("read should open"),
|
|
1215
|
+
)
|
|
1043
1216
|
.scan_rows_at_commit(&head_commit_id, &TrackedStateScanRequest::default())
|
|
1044
1217
|
.await
|
|
1045
1218
|
.expect("tracked state should scan");
|
|
1046
1219
|
assert!(
|
|
1047
1220
|
tracked_rows
|
|
1048
1221
|
.iter()
|
|
1049
|
-
.all(|row| row.
|
|
1222
|
+
.all(|row| row.entity_pk.as_single_string_owned().as_deref()
|
|
1050
1223
|
!= Ok("untracked-programmatic")),
|
|
1051
1224
|
"untracked staged rows should not be written into tracked state"
|
|
1052
1225
|
);
|
|
@@ -1054,25 +1227,22 @@ mod tests {
|
|
|
1054
1227
|
|
|
1055
1228
|
#[tokio::test]
|
|
1056
1229
|
async fn commit_validates_staged_rows_before_persistence() {
|
|
1057
|
-
let backend
|
|
1058
|
-
let storage = StorageContext::new(
|
|
1230
|
+
let backend = InMemoryStorageBackend::new();
|
|
1231
|
+
let storage = StorageContext::new(backend.clone());
|
|
1059
1232
|
let live_state = Arc::new(live_state_context());
|
|
1060
1233
|
seed_visible_schema_rows(storage.clone()).await;
|
|
1061
1234
|
let binary_cas = Arc::new(BinaryCasContext::new());
|
|
1062
|
-
let
|
|
1063
|
-
let commit_store = Arc::new(CommitStoreContext::new());
|
|
1064
|
-
let version_ctx = Arc::new(VersionContext::new(Arc::new(UntrackedStateContext::new())));
|
|
1235
|
+
let branch_ctx = Arc::new(BranchContext::new(Arc::new(UntrackedStateContext::new())));
|
|
1065
1236
|
let catalog_context = Arc::new(CatalogContext::new());
|
|
1066
1237
|
let opened = open_transaction(
|
|
1067
1238
|
&SessionMode::Pinned {
|
|
1068
|
-
|
|
1239
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1069
1240
|
},
|
|
1070
1241
|
storage.clone(),
|
|
1071
1242
|
Arc::clone(&live_state),
|
|
1072
1243
|
Arc::new(crate::tracked_state::TrackedStateContext::new()),
|
|
1073
1244
|
Arc::clone(&binary_cas),
|
|
1074
|
-
Arc::clone(&
|
|
1075
|
-
Arc::clone(&version_ctx),
|
|
1245
|
+
Arc::clone(&branch_ctx),
|
|
1076
1246
|
Arc::clone(&catalog_context),
|
|
1077
1247
|
)
|
|
1078
1248
|
.await
|
|
@@ -1098,37 +1268,27 @@ mod tests {
|
|
|
1098
1268
|
"validation error should explain the rejected schema data: {error:?}"
|
|
1099
1269
|
);
|
|
1100
1270
|
|
|
1101
|
-
let
|
|
1102
|
-
.
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
.record
|
|
1109
|
-
.entity_id
|
|
1110
|
-
.as_single_string_owned()
|
|
1111
|
-
.as_deref()
|
|
1112
|
-
!= Ok("invalid-programmatic")),
|
|
1113
|
-
"validation failure must happen before changelog persistence"
|
|
1114
|
-
);
|
|
1115
|
-
let head = version_ctx
|
|
1116
|
-
.ref_reader(storage.clone())
|
|
1117
|
-
.load_head_commit_id(GLOBAL_VERSION_ID)
|
|
1271
|
+
let head = branch_ctx
|
|
1272
|
+
.ref_reader(
|
|
1273
|
+
storage
|
|
1274
|
+
.begin_read(StorageReadOptions::default())
|
|
1275
|
+
.expect("read should open"),
|
|
1276
|
+
)
|
|
1277
|
+
.load_head_commit_id(GLOBAL_BRANCH_ID)
|
|
1118
1278
|
.await
|
|
1119
|
-
.expect("
|
|
1279
|
+
.expect("branch ref should load after failed commit");
|
|
1120
1280
|
assert_eq!(
|
|
1121
1281
|
head.as_deref(),
|
|
1122
1282
|
Some(SCHEMA_FIXTURE_COMMIT_ID),
|
|
1123
|
-
"validation failure must not advance the
|
|
1283
|
+
"validation failure must not advance the branch ref"
|
|
1124
1284
|
);
|
|
1125
1285
|
}
|
|
1126
1286
|
|
|
1127
1287
|
#[tokio::test]
|
|
1128
1288
|
async fn commit_rejects_non_object_metadata_without_sql() {
|
|
1129
|
-
let backend
|
|
1130
|
-
let storage = StorageContext::new(
|
|
1131
|
-
let (live_state, _binary_cas,
|
|
1289
|
+
let backend = InMemoryStorageBackend::new();
|
|
1290
|
+
let storage = StorageContext::new(backend.clone());
|
|
1291
|
+
let (live_state, _binary_cas, branch_ref, runtime_functions, mut transaction) =
|
|
1132
1292
|
open_test_transaction(&backend).await;
|
|
1133
1293
|
|
|
1134
1294
|
let mut row = key_value_stage_row("invalid-metadata", "value", false);
|
|
@@ -1151,8 +1311,7 @@ mod tests {
|
|
|
1151
1311
|
assert_no_persistence_after_validation_failure(
|
|
1152
1312
|
storage.clone(),
|
|
1153
1313
|
&live_state,
|
|
1154
|
-
&
|
|
1155
|
-
&version_ref,
|
|
1314
|
+
&branch_ref,
|
|
1156
1315
|
"invalid-metadata",
|
|
1157
1316
|
)
|
|
1158
1317
|
.await;
|
|
@@ -1160,15 +1319,9 @@ mod tests {
|
|
|
1160
1319
|
|
|
1161
1320
|
#[tokio::test]
|
|
1162
1321
|
async fn stage_rows_rejects_unknown_schema_key_without_sql() {
|
|
1163
|
-
let backend
|
|
1164
|
-
let (
|
|
1165
|
-
|
|
1166
|
-
_binary_cas,
|
|
1167
|
-
_changelog,
|
|
1168
|
-
_version_ref,
|
|
1169
|
-
_runtime_functions,
|
|
1170
|
-
mut transaction,
|
|
1171
|
-
) = open_test_transaction(&backend).await;
|
|
1322
|
+
let backend = InMemoryStorageBackend::new();
|
|
1323
|
+
let (_live_state, _binary_cas, _branch_ref, _runtime_functions, mut transaction) =
|
|
1324
|
+
open_test_transaction(&backend).await;
|
|
1172
1325
|
|
|
1173
1326
|
let mut row = key_value_stage_row("unknown-schema", "value", false);
|
|
1174
1327
|
row.schema_key = "missing_schema".to_string();
|
|
@@ -1188,49 +1341,37 @@ mod tests {
|
|
|
1188
1341
|
}
|
|
1189
1342
|
|
|
1190
1343
|
#[tokio::test]
|
|
1191
|
-
async fn
|
|
1192
|
-
let backend
|
|
1193
|
-
let (
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
_runtime_functions,
|
|
1199
|
-
mut transaction,
|
|
1200
|
-
) = open_test_transaction(&backend).await;
|
|
1201
|
-
|
|
1202
|
-
let mut row = key_value_stage_row("ghost-version-row", "value", false);
|
|
1203
|
-
row.version_id = "ghost-version".to_string();
|
|
1344
|
+
async fn stage_rows_rejects_missing_branch_without_sql() {
|
|
1345
|
+
let backend = InMemoryStorageBackend::new();
|
|
1346
|
+
let (_live_state, _binary_cas, _branch_ref, _runtime_functions, mut transaction) =
|
|
1347
|
+
open_test_transaction(&backend).await;
|
|
1348
|
+
|
|
1349
|
+
let mut row = key_value_stage_row("ghost-branch-row", "value", false);
|
|
1350
|
+
row.branch_id = "ghost-branch".to_string();
|
|
1204
1351
|
row.global = false;
|
|
1205
1352
|
|
|
1206
1353
|
let error = transaction
|
|
1207
1354
|
.stage_rows(vec![row])
|
|
1208
1355
|
.await
|
|
1209
|
-
.expect_err("missing
|
|
1356
|
+
.expect_err("missing branch should be rejected before staging");
|
|
1210
1357
|
|
|
1211
|
-
assert_eq!(error.code, LixError::
|
|
1358
|
+
assert_eq!(error.code, LixError::CODE_BRANCH_NOT_FOUND);
|
|
1212
1359
|
assert!(
|
|
1213
1360
|
error
|
|
1214
1361
|
.message
|
|
1215
|
-
.contains("
|
|
1216
|
-
"error should explain missing
|
|
1362
|
+
.contains("branch 'ghost-branch' was not found"),
|
|
1363
|
+
"error should explain missing branch: {error:?}"
|
|
1217
1364
|
);
|
|
1218
1365
|
}
|
|
1219
1366
|
|
|
1220
1367
|
#[tokio::test]
|
|
1221
1368
|
async fn stage_rows_rejects_invalid_storage_scope_without_sql() {
|
|
1222
|
-
let backend
|
|
1223
|
-
let (
|
|
1224
|
-
|
|
1225
|
-
_binary_cas,
|
|
1226
|
-
_changelog,
|
|
1227
|
-
_version_ref,
|
|
1228
|
-
_runtime_functions,
|
|
1229
|
-
mut transaction,
|
|
1230
|
-
) = open_test_transaction(&backend).await;
|
|
1369
|
+
let backend = InMemoryStorageBackend::new();
|
|
1370
|
+
let (_live_state, _binary_cas, _branch_ref, _runtime_functions, mut transaction) =
|
|
1371
|
+
open_test_transaction(&backend).await;
|
|
1231
1372
|
|
|
1232
1373
|
let mut row = key_value_stage_row("invalid-storage-scope", "value", false);
|
|
1233
|
-
row.
|
|
1374
|
+
row.branch_id = GLOBAL_BRANCH_ID.to_string();
|
|
1234
1375
|
row.global = false;
|
|
1235
1376
|
|
|
1236
1377
|
let error = transaction
|
|
@@ -1240,22 +1381,16 @@ mod tests {
|
|
|
1240
1381
|
|
|
1241
1382
|
assert_eq!(error.code, LixError::CODE_INVALID_STORAGE_SCOPE);
|
|
1242
1383
|
assert!(
|
|
1243
|
-
error.message.contains("
|
|
1384
|
+
error.message.contains("branch_id='global', global=false"),
|
|
1244
1385
|
"error should explain invalid storage scope: {error:?}"
|
|
1245
1386
|
);
|
|
1246
1387
|
}
|
|
1247
1388
|
|
|
1248
1389
|
#[tokio::test]
|
|
1249
1390
|
async fn stage_rows_rejects_invalid_snapshot_json_without_sql() {
|
|
1250
|
-
let backend
|
|
1251
|
-
let (
|
|
1252
|
-
|
|
1253
|
-
_binary_cas,
|
|
1254
|
-
_changelog,
|
|
1255
|
-
_version_ref,
|
|
1256
|
-
_runtime_functions,
|
|
1257
|
-
mut transaction,
|
|
1258
|
-
) = open_test_transaction(&backend).await;
|
|
1391
|
+
let backend = InMemoryStorageBackend::new();
|
|
1392
|
+
let (_live_state, _binary_cas, _branch_ref, _runtime_functions, mut transaction) =
|
|
1393
|
+
open_test_transaction(&backend).await;
|
|
1259
1394
|
|
|
1260
1395
|
let mut row = key_value_stage_row("invalid-json", "value", false);
|
|
1261
1396
|
row.snapshot = Some(TransactionJson::from_value_for_test(json!("not-an-object")));
|
|
@@ -1274,9 +1409,9 @@ mod tests {
|
|
|
1274
1409
|
|
|
1275
1410
|
#[tokio::test]
|
|
1276
1411
|
async fn commit_rejects_snapshot_that_violates_json_schema_without_sql() {
|
|
1277
|
-
let backend
|
|
1278
|
-
let storage = StorageContext::new(
|
|
1279
|
-
let (live_state, _binary_cas,
|
|
1412
|
+
let backend = InMemoryStorageBackend::new();
|
|
1413
|
+
let storage = StorageContext::new(backend.clone());
|
|
1414
|
+
let (live_state, _binary_cas, branch_ref, runtime_functions, mut transaction) =
|
|
1280
1415
|
open_test_transaction(&backend).await;
|
|
1281
1416
|
|
|
1282
1417
|
let mut row = key_value_stage_row("schema-mismatch", "value", false);
|
|
@@ -1301,8 +1436,7 @@ mod tests {
|
|
|
1301
1436
|
assert_no_persistence_after_validation_failure(
|
|
1302
1437
|
storage.clone(),
|
|
1303
1438
|
&live_state,
|
|
1304
|
-
&
|
|
1305
|
-
&version_ref,
|
|
1439
|
+
&branch_ref,
|
|
1306
1440
|
"schema-mismatch",
|
|
1307
1441
|
)
|
|
1308
1442
|
.await;
|
|
@@ -1310,15 +1444,9 @@ mod tests {
|
|
|
1310
1444
|
|
|
1311
1445
|
#[tokio::test]
|
|
1312
1446
|
async fn stage_rows_rejects_malformed_registered_schema_without_sql() {
|
|
1313
|
-
let backend
|
|
1314
|
-
let (
|
|
1315
|
-
|
|
1316
|
-
_binary_cas,
|
|
1317
|
-
_changelog,
|
|
1318
|
-
_version_ref,
|
|
1319
|
-
_runtime_functions,
|
|
1320
|
-
mut transaction,
|
|
1321
|
-
) = open_test_transaction(&backend).await;
|
|
1447
|
+
let backend = InMemoryStorageBackend::new();
|
|
1448
|
+
let (_live_state, _binary_cas, _branch_ref, _runtime_functions, mut transaction) =
|
|
1449
|
+
open_test_transaction(&backend).await;
|
|
1322
1450
|
|
|
1323
1451
|
let mut row = key_value_stage_row("malformed-registered-schema", "value", false);
|
|
1324
1452
|
row.schema_key = "lix_registered_schema".to_string();
|
|
@@ -1334,7 +1462,7 @@ mod tests {
|
|
|
1334
1462
|
"additionalProperties": false
|
|
1335
1463
|
}
|
|
1336
1464
|
})));
|
|
1337
|
-
row.
|
|
1465
|
+
row.entity_pk = None;
|
|
1338
1466
|
|
|
1339
1467
|
let error = transaction
|
|
1340
1468
|
.stage_rows(vec![row])
|
|
@@ -1349,62 +1477,52 @@ mod tests {
|
|
|
1349
1477
|
}
|
|
1350
1478
|
|
|
1351
1479
|
#[tokio::test]
|
|
1352
|
-
async fn
|
|
1353
|
-
let backend
|
|
1354
|
-
let (
|
|
1355
|
-
|
|
1356
|
-
_binary_cas,
|
|
1357
|
-
_changelog,
|
|
1358
|
-
_version_ref,
|
|
1359
|
-
_runtime_functions,
|
|
1360
|
-
mut transaction,
|
|
1361
|
-
) = open_test_transaction(&backend).await;
|
|
1480
|
+
async fn stage_rows_rejects_primary_key_entity_pk_mismatch_without_sql() {
|
|
1481
|
+
let backend = InMemoryStorageBackend::new();
|
|
1482
|
+
let (_live_state, _binary_cas, _branch_ref, _runtime_functions, mut transaction) =
|
|
1483
|
+
open_test_transaction(&backend).await;
|
|
1362
1484
|
|
|
1363
1485
|
let mut row = key_value_stage_row("right-id", "value", false);
|
|
1364
|
-
row.
|
|
1486
|
+
row.entity_pk = Some(crate::entity_pk::EntityPk::single("wrong-id"));
|
|
1365
1487
|
|
|
1366
1488
|
let error = transaction
|
|
1367
1489
|
.stage_rows(vec![row])
|
|
1368
1490
|
.await
|
|
1369
|
-
.expect_err("entity
|
|
1491
|
+
.expect_err("entity pk mismatch should be rejected while staging");
|
|
1370
1492
|
|
|
1371
1493
|
assert_eq!(error.code, LixError::CODE_SCHEMA_VALIDATION);
|
|
1372
1494
|
assert!(
|
|
1373
1495
|
error
|
|
1374
1496
|
.message
|
|
1375
|
-
.contains("does not match x-lix-primary-key derived
|
|
1376
|
-
"error should explain entity
|
|
1497
|
+
.contains("does not match x-lix-primary-key derived entity_pk"),
|
|
1498
|
+
"error should explain entity pk mismatch: {error:?}"
|
|
1377
1499
|
);
|
|
1378
1500
|
}
|
|
1379
1501
|
|
|
1380
1502
|
async fn open_test_transaction(
|
|
1381
|
-
backend: &
|
|
1503
|
+
backend: &InMemoryStorageBackend,
|
|
1382
1504
|
) -> (
|
|
1383
1505
|
Arc<LiveStateContext>,
|
|
1384
1506
|
Arc<BinaryCasContext>,
|
|
1385
|
-
Arc<
|
|
1386
|
-
Arc<VersionContext>,
|
|
1507
|
+
Arc<BranchContext>,
|
|
1387
1508
|
FunctionContext,
|
|
1388
1509
|
Transaction,
|
|
1389
1510
|
) {
|
|
1390
|
-
let storage = StorageContext::new(
|
|
1511
|
+
let storage = StorageContext::new(backend.clone());
|
|
1391
1512
|
let live_state = Arc::new(live_state_context());
|
|
1392
1513
|
seed_visible_schema_rows(storage.clone()).await;
|
|
1393
1514
|
let binary_cas = Arc::new(BinaryCasContext::new());
|
|
1394
|
-
let
|
|
1395
|
-
let commit_store = Arc::new(CommitStoreContext::new());
|
|
1396
|
-
let version_ctx = Arc::new(VersionContext::new(Arc::new(UntrackedStateContext::new())));
|
|
1515
|
+
let branch_ctx = Arc::new(BranchContext::new(Arc::new(UntrackedStateContext::new())));
|
|
1397
1516
|
let catalog_context = Arc::new(CatalogContext::new());
|
|
1398
1517
|
let opened = open_transaction(
|
|
1399
1518
|
&SessionMode::Pinned {
|
|
1400
|
-
|
|
1519
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1401
1520
|
},
|
|
1402
1521
|
storage,
|
|
1403
1522
|
Arc::clone(&live_state),
|
|
1404
1523
|
Arc::new(crate::tracked_state::TrackedStateContext::new()),
|
|
1405
1524
|
Arc::clone(&binary_cas),
|
|
1406
|
-
Arc::clone(&
|
|
1407
|
-
Arc::clone(&version_ctx),
|
|
1525
|
+
Arc::clone(&branch_ctx),
|
|
1408
1526
|
catalog_context,
|
|
1409
1527
|
)
|
|
1410
1528
|
.await
|
|
@@ -1415,8 +1533,7 @@ mod tests {
|
|
|
1415
1533
|
(
|
|
1416
1534
|
live_state,
|
|
1417
1535
|
binary_cas,
|
|
1418
|
-
|
|
1419
|
-
version_ctx,
|
|
1536
|
+
branch_ctx,
|
|
1420
1537
|
runtime_functions,
|
|
1421
1538
|
transaction,
|
|
1422
1539
|
)
|
|
@@ -1431,7 +1548,7 @@ mod tests {
|
|
|
1431
1548
|
.expect("seed schema key should derive");
|
|
1432
1549
|
let snapshot_content = json!({ "value": schema }).to_string();
|
|
1433
1550
|
crate::tracked_state::MaterializedTrackedStateRow {
|
|
1434
|
-
|
|
1551
|
+
entity_pk: crate::schema::registered_schema_entity_pk(&key.schema_key)
|
|
1435
1552
|
.expect("registered schema identity should derive"),
|
|
1436
1553
|
schema_key: "lix_registered_schema".to_string(),
|
|
1437
1554
|
file_id: None,
|
|
@@ -1445,18 +1562,18 @@ mod tests {
|
|
|
1445
1562
|
}
|
|
1446
1563
|
})
|
|
1447
1564
|
.collect::<Vec<_>>();
|
|
1448
|
-
let
|
|
1449
|
-
|
|
1565
|
+
let branch_ref_row = prepare_branch_ref_row(
|
|
1566
|
+
GLOBAL_BRANCH_ID,
|
|
1450
1567
|
SCHEMA_FIXTURE_COMMIT_ID,
|
|
1451
1568
|
"1970-01-01T00:00:00.000Z",
|
|
1452
1569
|
)
|
|
1453
|
-
.expect("schema fixture
|
|
1454
|
-
let mut
|
|
1455
|
-
.
|
|
1456
|
-
.
|
|
1457
|
-
.expect("schema fixture transaction should open");
|
|
1570
|
+
.expect("schema fixture branch ref should stage");
|
|
1571
|
+
let mut read = storage
|
|
1572
|
+
.begin_read(crate::storage::StorageReadOptions::default())
|
|
1573
|
+
.expect("schema fixture read should open");
|
|
1458
1574
|
crate::test_support::stage_tracked_root_from_materialized(
|
|
1459
|
-
|
|
1575
|
+
&mut read,
|
|
1576
|
+
&mut writes,
|
|
1460
1577
|
&crate::tracked_state::TrackedStateContext::new(),
|
|
1461
1578
|
SCHEMA_FIXTURE_COMMIT_ID,
|
|
1462
1579
|
None,
|
|
@@ -1466,55 +1583,43 @@ mod tests {
|
|
|
1466
1583
|
.expect("schema fixture rows should stage");
|
|
1467
1584
|
crate::untracked_state::UntrackedStateContext::new()
|
|
1468
1585
|
.writer(&mut writes)
|
|
1469
|
-
.stage_rows([
|
|
1470
|
-
.expect("schema fixture
|
|
1471
|
-
|
|
1472
|
-
.
|
|
1473
|
-
.await
|
|
1474
|
-
.expect("schema fixture rows should apply");
|
|
1475
|
-
storage_transaction
|
|
1476
|
-
.commit()
|
|
1477
|
-
.await
|
|
1586
|
+
.stage_rows([branch_ref_row.row.as_ref()])
|
|
1587
|
+
.expect("schema fixture branch ref should stage");
|
|
1588
|
+
storage
|
|
1589
|
+
.commit_write_set(writes, crate::storage::StorageWriteOptions::default())
|
|
1478
1590
|
.expect("schema fixture transaction should commit");
|
|
1479
1591
|
}
|
|
1480
1592
|
|
|
1481
1593
|
async fn assert_no_persistence_after_validation_failure(
|
|
1482
1594
|
storage: StorageContext,
|
|
1483
1595
|
live_state: &LiveStateContext,
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
rejected_entity_id: &str,
|
|
1596
|
+
branch_ctx: &BranchContext,
|
|
1597
|
+
rejected_entity_pk: &str,
|
|
1487
1598
|
) {
|
|
1488
|
-
let
|
|
1489
|
-
.
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
.record
|
|
1496
|
-
.entity_id
|
|
1497
|
-
.as_single_string_owned()
|
|
1498
|
-
.as_deref()
|
|
1499
|
-
!= Ok(rejected_entity_id)),
|
|
1500
|
-
"validation failure must happen before changelog persistence"
|
|
1501
|
-
);
|
|
1502
|
-
let head = version_ctx
|
|
1503
|
-
.ref_reader(storage.clone())
|
|
1504
|
-
.load_head_commit_id(GLOBAL_VERSION_ID)
|
|
1599
|
+
let head = branch_ctx
|
|
1600
|
+
.ref_reader(
|
|
1601
|
+
storage
|
|
1602
|
+
.begin_read(StorageReadOptions::default())
|
|
1603
|
+
.expect("read should open"),
|
|
1604
|
+
)
|
|
1605
|
+
.load_head_commit_id(GLOBAL_BRANCH_ID)
|
|
1505
1606
|
.await
|
|
1506
|
-
.expect("
|
|
1607
|
+
.expect("branch ref should load after failed commit");
|
|
1507
1608
|
assert_eq!(
|
|
1508
1609
|
head.as_deref(),
|
|
1509
1610
|
Some(SCHEMA_FIXTURE_COMMIT_ID),
|
|
1510
|
-
"validation failure must not advance the
|
|
1611
|
+
"validation failure must not advance the branch ref"
|
|
1511
1612
|
);
|
|
1512
1613
|
let row = live_state
|
|
1513
|
-
.reader(
|
|
1614
|
+
.reader(
|
|
1615
|
+
storage
|
|
1616
|
+
.begin_read(StorageReadOptions::default())
|
|
1617
|
+
.expect("read should open"),
|
|
1618
|
+
)
|
|
1514
1619
|
.load_row(&crate::live_state::LiveStateRowRequest {
|
|
1515
1620
|
schema_key: "lix_key_value".to_string(),
|
|
1516
|
-
|
|
1517
|
-
|
|
1621
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1622
|
+
entity_pk: crate::entity_pk::EntityPk::single(rejected_entity_pk),
|
|
1518
1623
|
file_id: NullableKeyFilter::Null,
|
|
1519
1624
|
})
|
|
1520
1625
|
.await
|
|
@@ -1527,7 +1632,7 @@ mod tests {
|
|
|
1527
1632
|
|
|
1528
1633
|
fn key_value_stage_row(key: &str, value: &str, untracked: bool) -> TransactionWriteRow {
|
|
1529
1634
|
TransactionWriteRow {
|
|
1530
|
-
|
|
1635
|
+
entity_pk: Some(crate::entity_pk::EntityPk::single(key)),
|
|
1531
1636
|
schema_key: "lix_key_value".to_string(),
|
|
1532
1637
|
file_id: None,
|
|
1533
1638
|
snapshot: Some(TransactionJson::from_value_for_test(json!({
|
|
@@ -1542,7 +1647,7 @@ mod tests {
|
|
|
1542
1647
|
change_id: None,
|
|
1543
1648
|
commit_id: None,
|
|
1544
1649
|
untracked,
|
|
1545
|
-
|
|
1650
|
+
branch_id: GLOBAL_BRANCH_ID.to_string(),
|
|
1546
1651
|
}
|
|
1547
1652
|
}
|
|
1548
1653
|
}
|