@lix-js/sdk 0.6.0-preview.5 → 0.6.0
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 +76 -4
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +19 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.js +3 -3
- package/dist/native.d.ts +1 -0
- package/dist/native.js +47 -0
- package/dist/open-lix.d.ts +38 -207
- package/dist/open-lix.js +59 -284
- package/dist/result.d.ts +18 -0
- package/dist/result.js +48 -0
- package/dist/types.d.ts +114 -1
- package/dist/value.d.ts +28 -0
- package/dist/value.js +245 -0
- package/package.json +38 -71
- package/SKILL.md +0 -507
- package/dist/builtin-schemas.d.ts +0 -1
- package/dist/builtin-schemas.js +0 -1
- package/dist/engine-wasm/index.d.ts +0 -87
- package/dist/engine-wasm/index.js +0 -339
- package/dist/engine-wasm/wasm/lix_engine.d.ts +0 -79
- package/dist/engine-wasm/wasm/lix_engine.js +0 -833
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +0 -27
- package/dist/generated/builtin-schemas.d.ts +0 -427
- package/dist/generated/builtin-schemas.js +0 -643
- package/dist/sqlite/index.d.ts +0 -12
- package/dist/sqlite/index.js +0 -359
- package/dist-engine-src/README.md +0 -18
- package/dist-engine-src/src/backend/capabilities.rs +0 -67
- package/dist-engine-src/src/backend/conformance/baseline.rs +0 -1127
- package/dist-engine-src/src/backend/conformance/factory.rs +0 -93
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +0 -608
- package/dist-engine-src/src/backend/conformance/fixtures.rs +0 -26
- package/dist-engine-src/src/backend/conformance/mod.rs +0 -75
- package/dist-engine-src/src/backend/conformance/model.rs +0 -28
- package/dist-engine-src/src/backend/conformance/model_based.rs +0 -257
- package/dist-engine-src/src/backend/conformance/persistence.rs +0 -204
- package/dist-engine-src/src/backend/conformance/projection.rs +0 -21
- package/dist-engine-src/src/backend/conformance/pushdown.rs +0 -24
- package/dist-engine-src/src/backend/conformance/runner.rs +0 -90
- package/dist-engine-src/src/backend/conformance/scan.rs +0 -24
- package/dist-engine-src/src/backend/conformance/write.rs +0 -16
- package/dist-engine-src/src/backend/error.rs +0 -94
- package/dist-engine-src/src/backend/in_memory.rs +0 -670
- package/dist-engine-src/src/backend/mod.rs +0 -39
- package/dist-engine-src/src/backend/predicate.rs +0 -80
- package/dist-engine-src/src/backend/traits.rs +0 -260
- package/dist-engine-src/src/backend/types.rs +0 -239
- package/dist-engine-src/src/binary_cas/chunking.rs +0 -31
- package/dist-engine-src/src/binary_cas/codec.rs +0 -346
- package/dist-engine-src/src/binary_cas/context.rs +0 -139
- package/dist-engine-src/src/binary_cas/kv.rs +0 -1038
- package/dist-engine-src/src/binary_cas/mod.rs +0 -11
- package/dist-engine-src/src/binary_cas/types.rs +0 -121
- package/dist-engine-src/src/branch/context.rs +0 -40
- package/dist-engine-src/src/branch/lifecycle.rs +0 -221
- package/dist-engine-src/src/branch/mod.rs +0 -13
- package/dist-engine-src/src/branch/refs.rs +0 -321
- package/dist-engine-src/src/branch/stage_rows.rs +0 -67
- package/dist-engine-src/src/branch/types.rs +0 -21
- package/dist-engine-src/src/catalog/context.rs +0 -412
- package/dist-engine-src/src/catalog/mod.rs +0 -10
- package/dist-engine-src/src/catalog/schema.rs +0 -4
- package/dist-engine-src/src/catalog/snapshot.rs +0 -1114
- package/dist-engine-src/src/cel/context.rs +0 -86
- package/dist-engine-src/src/cel/error.rs +0 -19
- package/dist-engine-src/src/cel/mod.rs +0 -8
- package/dist-engine-src/src/cel/provider.rs +0 -9
- package/dist-engine-src/src/cel/runtime.rs +0 -167
- package/dist-engine-src/src/cel/value.rs +0 -50
- package/dist-engine-src/src/changelog/bench_support.rs +0 -785
- package/dist-engine-src/src/changelog/change.rs +0 -1
- package/dist-engine-src/src/changelog/codec.rs +0 -497
- package/dist-engine-src/src/changelog/commit.rs +0 -1
- package/dist-engine-src/src/changelog/context.rs +0 -1614
- package/dist-engine-src/src/changelog/mod.rs +0 -29
- package/dist-engine-src/src/changelog/store.rs +0 -163
- package/dist-engine-src/src/changelog/test_support.rs +0 -54
- package/dist-engine-src/src/changelog/types.rs +0 -213
- package/dist-engine-src/src/commit_graph/context.rs +0 -944
- package/dist-engine-src/src/commit_graph/mod.rs +0 -9
- package/dist-engine-src/src/commit_graph/types.rs +0 -89
- package/dist-engine-src/src/commit_graph/walker.rs +0 -786
- package/dist-engine-src/src/common/error.rs +0 -347
- package/dist-engine-src/src/common/fingerprint.rs +0 -3
- package/dist-engine-src/src/common/fs_path.rs +0 -1336
- package/dist-engine-src/src/common/identity.rs +0 -145
- package/dist-engine-src/src/common/json_pointer.rs +0 -67
- package/dist-engine-src/src/common/metadata.rs +0 -40
- package/dist-engine-src/src/common/mod.rs +0 -23
- package/dist-engine-src/src/common/types.rs +0 -105
- package/dist-engine-src/src/common/wire.rs +0 -222
- package/dist-engine-src/src/domain.rs +0 -320
- package/dist-engine-src/src/engine.rs +0 -203
- package/dist-engine-src/src/entity_pk.rs +0 -402
- package/dist-engine-src/src/functions/context.rs +0 -296
- package/dist-engine-src/src/functions/deterministic.rs +0 -113
- package/dist-engine-src/src/functions/mod.rs +0 -18
- package/dist-engine-src/src/functions/provider.rs +0 -130
- package/dist-engine-src/src/functions/state.rs +0 -335
- package/dist-engine-src/src/functions/types.rs +0 -37
- package/dist-engine-src/src/init.rs +0 -692
- package/dist-engine-src/src/json_store/compression.rs +0 -77
- package/dist-engine-src/src/json_store/context.rs +0 -172
- package/dist-engine-src/src/json_store/encoded.rs +0 -15
- package/dist-engine-src/src/json_store/mod.rs +0 -38
- package/dist-engine-src/src/json_store/store.rs +0 -494
- package/dist-engine-src/src/json_store/types.rs +0 -212
- package/dist-engine-src/src/lib.rs +0 -92
- package/dist-engine-src/src/live_state/context.rs +0 -1883
- package/dist-engine-src/src/live_state/mod.rs +0 -21
- package/dist-engine-src/src/live_state/overlay.rs +0 -75
- package/dist-engine-src/src/live_state/reader.rs +0 -23
- package/dist-engine-src/src/live_state/types.rs +0 -231
- package/dist-engine-src/src/live_state/visibility.rs +0 -666
- package/dist-engine-src/src/plugin/archive.rs +0 -438
- package/dist-engine-src/src/plugin/component.rs +0 -183
- package/dist-engine-src/src/plugin/install.rs +0 -619
- package/dist-engine-src/src/plugin/manifest.rs +0 -516
- package/dist-engine-src/src/plugin/materializer.rs +0 -202
- package/dist-engine-src/src/plugin/mod.rs +0 -33
- package/dist-engine-src/src/plugin/plugin_manifest.json +0 -119
- package/dist-engine-src/src/plugin/storage.rs +0 -74
- package/dist-engine-src/src/schema/annotations/defaults.rs +0 -275
- package/dist-engine-src/src/schema/annotations/mod.rs +0 -1
- package/dist-engine-src/src/schema/builtin/lix_account.json +0 -21
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +0 -48
- package/dist-engine-src/src/schema/builtin/lix_change.json +0 -63
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +0 -45
- package/dist-engine-src/src/schema/builtin/lix_commit.json +0 -24
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +0 -53
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +0 -52
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +0 -52
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +0 -40
- package/dist-engine-src/src/schema/builtin/lix_label.json +0 -29
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +0 -74
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +0 -25
- package/dist-engine-src/src/schema/builtin/mod.rs +0 -220
- package/dist-engine-src/src/schema/compatibility.rs +0 -787
- package/dist-engine-src/src/schema/definition.json +0 -187
- package/dist-engine-src/src/schema/definition.rs +0 -742
- package/dist-engine-src/src/schema/key.rs +0 -138
- package/dist-engine-src/src/schema/mod.rs +0 -20
- package/dist-engine-src/src/schema/seed.rs +0 -14
- package/dist-engine-src/src/schema/tests.rs +0 -780
- package/dist-engine-src/src/session/context.rs +0 -1059
- package/dist-engine-src/src/session/create_branch.rs +0 -94
- package/dist-engine-src/src/session/execute.rs +0 -681
- package/dist-engine-src/src/session/merge/analysis.rs +0 -108
- package/dist-engine-src/src/session/merge/branch.rs +0 -417
- package/dist-engine-src/src/session/merge/conflicts.rs +0 -63
- package/dist-engine-src/src/session/merge/mod.rs +0 -10
- package/dist-engine-src/src/session/merge/stats.rs +0 -61
- package/dist-engine-src/src/session/mod.rs +0 -30
- package/dist-engine-src/src/session/switch_branch.rs +0 -113
- package/dist-engine-src/src/session/transaction.rs +0 -557
- package/dist-engine-src/src/sql2/bind/classify.rs +0 -102
- package/dist-engine-src/src/sql2/bind/error.rs +0 -5
- package/dist-engine-src/src/sql2/bind/expr.rs +0 -29
- package/dist-engine-src/src/sql2/bind/mod.rs +0 -12
- package/dist-engine-src/src/sql2/bind/public_udf.rs +0 -306
- package/dist-engine-src/src/sql2/bind/read.rs +0 -65
- package/dist-engine-src/src/sql2/bind/statement.rs +0 -2236
- package/dist-engine-src/src/sql2/bind/table.rs +0 -273
- package/dist-engine-src/src/sql2/bind/write.rs +0 -86
- package/dist-engine-src/src/sql2/branch_scope.rs +0 -436
- package/dist-engine-src/src/sql2/catalog/capability.rs +0 -20
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +0 -296
- package/dist-engine-src/src/sql2/catalog/mod.rs +0 -15
- package/dist-engine-src/src/sql2/catalog/registry.rs +0 -556
- package/dist-engine-src/src/sql2/catalog/schema.rs +0 -88
- package/dist-engine-src/src/sql2/catalog/surface.rs +0 -41
- package/dist-engine-src/src/sql2/change_materialization.rs +0 -122
- package/dist-engine-src/src/sql2/context.rs +0 -317
- package/dist-engine-src/src/sql2/dml.rs +0 -148
- package/dist-engine-src/src/sql2/error.rs +0 -215
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +0 -1593
- package/dist-engine-src/src/sql2/exec/datafusion.rs +0 -5266
- package/dist-engine-src/src/sql2/exec/fast_write.rs +0 -82
- package/dist-engine-src/src/sql2/exec/mod.rs +0 -24
- package/dist-engine-src/src/sql2/exec/write.rs +0 -661
- package/dist-engine-src/src/sql2/filesystem_planner.rs +0 -1485
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +0 -159
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +0 -383
- package/dist-engine-src/src/sql2/history_projection.rs +0 -56
- package/dist-engine-src/src/sql2/history_route.rs +0 -661
- package/dist-engine-src/src/sql2/mod.rs +0 -52
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +0 -1
- package/dist-engine-src/src/sql2/optimize/mod.rs +0 -2
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +0 -116
- package/dist-engine-src/src/sql2/parse/mod.rs +0 -69
- package/dist-engine-src/src/sql2/parse/normalize.rs +0 -1
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +0 -24
- package/dist-engine-src/src/sql2/plan/mod.rs +0 -5
- package/dist-engine-src/src/sql2/plan/predicate.rs +0 -22
- package/dist-engine-src/src/sql2/plan/write.rs +0 -147
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +0 -504
- package/dist-engine-src/src/sql2/providers/branch.rs +0 -1206
- package/dist-engine-src/src/sql2/providers/change.rs +0 -445
- package/dist-engine-src/src/sql2/providers/directory.rs +0 -2422
- package/dist-engine-src/src/sql2/providers/directory_history.rs +0 -645
- package/dist-engine-src/src/sql2/providers/entity.rs +0 -1484
- package/dist-engine-src/src/sql2/providers/entity_history.rs +0 -452
- package/dist-engine-src/src/sql2/providers/file.rs +0 -3686
- package/dist-engine-src/src/sql2/providers/file_history.rs +0 -924
- package/dist-engine-src/src/sql2/providers/history.rs +0 -426
- package/dist-engine-src/src/sql2/providers/lix_state.rs +0 -2542
- package/dist-engine-src/src/sql2/providers/mod.rs +0 -508
- package/dist-engine-src/src/sql2/read_only.rs +0 -63
- package/dist-engine-src/src/sql2/record_batch.rs +0 -17
- package/dist-engine-src/src/sql2/result_metadata.rs +0 -29
- package/dist-engine-src/src/sql2/runtime.rs +0 -60
- package/dist-engine-src/src/sql2/session.rs +0 -83
- package/dist-engine-src/src/sql2/storage/constraints.rs +0 -1
- package/dist-engine-src/src/sql2/storage/mod.rs +0 -1
- package/dist-engine-src/src/sql2/test_support/differential.rs +0 -712
- package/dist-engine-src/src/sql2/test_support/generators.rs +0 -354
- package/dist-engine-src/src/sql2/test_support/mod.rs +0 -2
- package/dist-engine-src/src/sql2/udfs/common.rs +0 -295
- package/dist-engine-src/src/sql2/udfs/lix_active_branch_commit_id.rs +0 -53
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +0 -47
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +0 -100
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +0 -99
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +0 -99
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +0 -82
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +0 -85
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +0 -76
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +0 -76
- package/dist-engine-src/src/sql2/udfs/mod.rs +0 -86
- package/dist-engine-src/src/sql2/write_normalization.rs +0 -368
- package/dist-engine-src/src/storage/conformance.rs +0 -399
- package/dist-engine-src/src/storage/context.rs +0 -620
- package/dist-engine-src/src/storage/mod.rs +0 -52
- package/dist-engine-src/src/storage/point.rs +0 -440
- package/dist-engine-src/src/storage/read_scope.rs +0 -67
- package/dist-engine-src/src/storage/reader.rs +0 -867
- package/dist-engine-src/src/storage/scan.rs +0 -784
- package/dist-engine-src/src/storage/spaces.rs +0 -236
- package/dist-engine-src/src/storage/stats.rs +0 -80
- package/dist-engine-src/src/storage/write_set.rs +0 -962
- package/dist-engine-src/src/storage_bench.rs +0 -171
- package/dist-engine-src/src/test_support.rs +0 -450
- package/dist-engine-src/src/tracked_state/bench_support.rs +0 -394
- package/dist-engine-src/src/tracked_state/codec.rs +0 -1183
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +0 -358
- package/dist-engine-src/src/tracked_state/context.rs +0 -2801
- package/dist-engine-src/src/tracked_state/diff.rs +0 -2140
- package/dist-engine-src/src/tracked_state/merge.rs +0 -478
- package/dist-engine-src/src/tracked_state/mod.rs +0 -35
- package/dist-engine-src/src/tracked_state/row_materialization.rs +0 -275
- package/dist-engine-src/src/tracked_state/storage.rs +0 -427
- package/dist-engine-src/src/tracked_state/tree.rs +0 -3063
- package/dist-engine-src/src/tracked_state/types.rs +0 -238
- package/dist-engine-src/src/transaction/bench_support.rs +0 -407
- package/dist-engine-src/src/transaction/commit.rs +0 -1592
- package/dist-engine-src/src/transaction/context.rs +0 -1653
- package/dist-engine-src/src/transaction/mod.rs +0 -24
- package/dist-engine-src/src/transaction/normalization.rs +0 -877
- package/dist-engine-src/src/transaction/prep.rs +0 -37
- package/dist-engine-src/src/transaction/schema_resolver.rs +0 -163
- package/dist-engine-src/src/transaction/staging.rs +0 -1525
- package/dist-engine-src/src/transaction/types.rs +0 -403
- package/dist-engine-src/src/transaction/validation.rs +0 -5766
- package/dist-engine-src/src/untracked_state/codec.rs +0 -615
- package/dist-engine-src/src/untracked_state/context.rs +0 -98
- package/dist-engine-src/src/untracked_state/materialization.rs +0 -63
- package/dist-engine-src/src/untracked_state/mod.rs +0 -15
- package/dist-engine-src/src/untracked_state/storage.rs +0 -898
- package/dist-engine-src/src/untracked_state/types.rs +0 -146
- package/dist-engine-src/src/wasm/mod.rs +0 -60
|
@@ -1,1525 +0,0 @@
|
|
|
1
|
-
use std::collections::{BTreeMap, HashMap};
|
|
2
|
-
use std::sync::{Arc, Mutex};
|
|
3
|
-
|
|
4
|
-
use crate::catalog::SchemaPlanId;
|
|
5
|
-
use crate::domain::{Domain, DomainRowIdentity};
|
|
6
|
-
use crate::entity_pk::EntityPk;
|
|
7
|
-
use crate::functions::{FunctionProvider, FunctionProviderHandle};
|
|
8
|
-
#[cfg(test)]
|
|
9
|
-
use crate::live_state::LiveStateRowRequest;
|
|
10
|
-
use crate::live_state::{LiveStateScanRequest, MaterializedLiveStateRow};
|
|
11
|
-
#[cfg(test)]
|
|
12
|
-
use crate::transaction::types::{stage_json_from_value, TransactionJson};
|
|
13
|
-
use crate::transaction::types::{
|
|
14
|
-
LogicalPrimaryKey, PreparedTransactionWrite, StagedCommitChangeRef, TransactionFileData,
|
|
15
|
-
TransactionWriteMode, TransactionWriteOperation, TransactionWriteOrigin,
|
|
16
|
-
TransactionWriteOutcome,
|
|
17
|
-
};
|
|
18
|
-
use crate::transaction::types::{PreparedStateRow, StagedCommitChangeRefs};
|
|
19
|
-
use crate::GLOBAL_BRANCH_ID;
|
|
20
|
-
use crate::{LixError, NullableKeyFilter};
|
|
21
|
-
|
|
22
|
-
/// Transaction-local write buffer after transaction-boundary preparation.
|
|
23
|
-
///
|
|
24
|
-
/// This is the engine seam between SQL execution and transaction ownership:
|
|
25
|
-
/// write frontends pass decoded `TransactionWriteRow`s to `Transaction`, the
|
|
26
|
-
/// transaction prepares them into stable `PreparedStateRow`s, reads build a
|
|
27
|
-
/// `PreparedStateRowOverlay` from those rows, and commit drains the same rows.
|
|
28
|
-
pub(crate) struct TransactionWriteBuffer {
|
|
29
|
-
functions: FunctionProviderHandle,
|
|
30
|
-
rows: Mutex<Vec<Option<PreparedStateRow>>>,
|
|
31
|
-
by_identity: Mutex<HashMap<PreparedStateRowIdentity, RowSlot>>,
|
|
32
|
-
insert_identities: Mutex<BTreeMap<PreparedStateRowIdentity, Option<TransactionWriteOrigin>>>,
|
|
33
|
-
commit_change_refs_by_branch: Mutex<BTreeMap<String, StagedCommitChangeRefs>>,
|
|
34
|
-
extra_commit_parents_by_branch: Mutex<BTreeMap<String, Vec<String>>>,
|
|
35
|
-
file_data_writes: Mutex<Vec<TransactionFileData>>,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
39
|
-
pub(crate) enum RowSlot {
|
|
40
|
-
State(usize),
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/// Drained prepared transaction writes ready for commit.
|
|
44
|
-
pub(crate) struct PreparedWriteSet {
|
|
45
|
-
pub(crate) state_rows: Vec<PreparedStateRow>,
|
|
46
|
-
pub(crate) insert_identities:
|
|
47
|
-
BTreeMap<PreparedStateRowIdentity, Option<TransactionWriteOrigin>>,
|
|
48
|
-
pub(crate) commit_change_refs_by_branch: BTreeMap<String, StagedCommitChangeRefs>,
|
|
49
|
-
pub(crate) extra_commit_parents_by_branch: BTreeMap<String, Vec<String>>,
|
|
50
|
-
pub(crate) file_data_writes: Vec<TransactionFileData>,
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
pub(crate) struct PreparedWriteValidationSet<'a> {
|
|
54
|
-
rows: Vec<PreparedValidationRow<'a>>,
|
|
55
|
-
constraint_rows: Vec<PreparedValidationRow<'a>>,
|
|
56
|
-
insert_identities: Vec<(
|
|
57
|
-
&'a PreparedStateRowIdentity,
|
|
58
|
-
Option<&'a TransactionWriteOrigin>,
|
|
59
|
-
)>,
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
pub(crate) struct PreparedWriteValidationIndex<'a> {
|
|
63
|
-
rows_by_schema_scope: BTreeMap<Domain, Vec<PreparedValidationRow<'a>>>,
|
|
64
|
-
insert_identities_by_schema_scope: BTreeMap<
|
|
65
|
-
Domain,
|
|
66
|
-
Vec<(
|
|
67
|
-
&'a PreparedStateRowIdentity,
|
|
68
|
-
Option<&'a TransactionWriteOrigin>,
|
|
69
|
-
)>,
|
|
70
|
-
>,
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
#[derive(Clone, Copy)]
|
|
74
|
-
pub(crate) enum PreparedValidationRow<'a> {
|
|
75
|
-
State(&'a PreparedStateRow),
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
impl<'a> PreparedValidationRow<'a> {
|
|
79
|
-
pub(crate) fn entity_pk(&self) -> &EntityPk {
|
|
80
|
-
match self {
|
|
81
|
-
Self::State(row) => &row.entity_pk,
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
pub(crate) fn schema_plan_id(&self) -> SchemaPlanId {
|
|
86
|
-
match self {
|
|
87
|
-
Self::State(row) => row.schema_plan_id,
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
pub(crate) fn schema_key(&self) -> &str {
|
|
92
|
-
match self {
|
|
93
|
-
Self::State(row) => &row.schema_key,
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
pub(crate) fn file_id(&self) -> &Option<String> {
|
|
98
|
-
match self {
|
|
99
|
-
Self::State(row) => &row.file_id,
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
#[cfg(test)]
|
|
104
|
-
pub(crate) fn snapshot_content(&self) -> Option<&str> {
|
|
105
|
-
match self {
|
|
106
|
-
Self::State(row) => row
|
|
107
|
-
.snapshot
|
|
108
|
-
.as_ref()
|
|
109
|
-
.map(|snapshot| snapshot.normalized.as_ref()),
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
pub(crate) fn snapshot_json(self) -> Option<&'a serde_json::Value> {
|
|
114
|
-
match self {
|
|
115
|
-
Self::State(row) => row
|
|
116
|
-
.snapshot
|
|
117
|
-
.as_ref()
|
|
118
|
-
.map(|snapshot| snapshot.value.as_ref()),
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
pub(crate) fn metadata_json(self) -> Option<&'a serde_json::Value> {
|
|
123
|
-
match self {
|
|
124
|
-
Self::State(row) => row
|
|
125
|
-
.metadata
|
|
126
|
-
.as_ref()
|
|
127
|
-
.map(|metadata| metadata.value.as_ref()),
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
pub(crate) fn is_tombstone(&self) -> bool {
|
|
132
|
-
match self {
|
|
133
|
-
Self::State(row) => row.snapshot.is_none(),
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
pub(crate) fn untracked(&self) -> bool {
|
|
138
|
-
match self {
|
|
139
|
-
Self::State(row) => row.untracked,
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
pub(crate) fn branch_id(&self) -> &str {
|
|
144
|
-
match self {
|
|
145
|
-
Self::State(row) => &row.branch_id,
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
pub(crate) fn domain(&self) -> Domain {
|
|
150
|
-
Domain::exact_file(
|
|
151
|
-
self.branch_id().to_string(),
|
|
152
|
-
self.untracked(),
|
|
153
|
-
self.file_id().clone(),
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
pub(crate) fn domain_row_identity(&self) -> DomainRowIdentity {
|
|
158
|
-
DomainRowIdentity::in_domain(
|
|
159
|
-
self.domain(),
|
|
160
|
-
self.schema_key().to_string(),
|
|
161
|
-
self.entity_pk().clone(),
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
impl<'a> PreparedWriteValidationIndex<'a> {
|
|
167
|
-
pub(crate) fn schema_scopes(&self) -> impl Iterator<Item = &Domain> {
|
|
168
|
-
self.rows_by_schema_scope.keys()
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
pub(crate) fn validation_set_for_schema_scope(
|
|
172
|
-
&self,
|
|
173
|
-
schema_scope: &Domain,
|
|
174
|
-
) -> PreparedWriteValidationSet<'a> {
|
|
175
|
-
let constraint_rows = self
|
|
176
|
-
.rows_by_schema_scope
|
|
177
|
-
.iter()
|
|
178
|
-
.flat_map(|(target_scope, rows)| {
|
|
179
|
-
rows.iter().copied().filter(move |row| {
|
|
180
|
-
schema_scope.validation_scope_contains_constraint_domain(target_scope)
|
|
181
|
-
|| (row.is_tombstone()
|
|
182
|
-
&& target_scope.tombstone_domain_affects_validation_scope(schema_scope))
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
.collect();
|
|
186
|
-
PreparedWriteValidationSet {
|
|
187
|
-
rows: self
|
|
188
|
-
.rows_by_schema_scope
|
|
189
|
-
.get(schema_scope)
|
|
190
|
-
.cloned()
|
|
191
|
-
.unwrap_or_default(),
|
|
192
|
-
constraint_rows,
|
|
193
|
-
insert_identities: self
|
|
194
|
-
.insert_identities_by_schema_scope
|
|
195
|
-
.get(schema_scope)
|
|
196
|
-
.cloned()
|
|
197
|
-
.unwrap_or_default(),
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
impl<'a> PreparedWriteValidationSet<'a> {
|
|
203
|
-
pub(crate) fn rows(&self) -> impl Iterator<Item = PreparedValidationRow<'a>> + '_ {
|
|
204
|
-
self.rows.iter().copied()
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
pub(crate) fn constraint_rows(&self) -> impl Iterator<Item = PreparedValidationRow<'a>> + '_ {
|
|
208
|
-
self.constraint_rows.iter().copied()
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
pub(crate) fn insert_identities(
|
|
212
|
-
&self,
|
|
213
|
-
) -> impl Iterator<Item = (&PreparedStateRowIdentity, Option<&TransactionWriteOrigin>)> {
|
|
214
|
-
self.insert_identities
|
|
215
|
-
.iter()
|
|
216
|
-
.map(|(identity, origin)| (*identity, *origin))
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
impl PreparedWriteSet {
|
|
221
|
-
#[cfg(test)]
|
|
222
|
-
pub(crate) fn validation_rows(&self) -> impl Iterator<Item = PreparedValidationRow<'_>> + '_ {
|
|
223
|
-
self.state_rows.iter().map(PreparedValidationRow::State)
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
pub(crate) fn validation_index(&self) -> PreparedWriteValidationIndex<'_> {
|
|
227
|
-
let mut rows_by_schema_scope = BTreeMap::<Domain, Vec<PreparedValidationRow<'_>>>::new();
|
|
228
|
-
for row in &self.state_rows {
|
|
229
|
-
let row = PreparedValidationRow::State(row);
|
|
230
|
-
rows_by_schema_scope
|
|
231
|
-
.entry(row.domain().schema_catalog_domain())
|
|
232
|
-
.or_default()
|
|
233
|
-
.push(row);
|
|
234
|
-
}
|
|
235
|
-
let mut insert_identities_by_schema_scope = BTreeMap::<
|
|
236
|
-
Domain,
|
|
237
|
-
Vec<(&PreparedStateRowIdentity, Option<&TransactionWriteOrigin>)>,
|
|
238
|
-
>::new();
|
|
239
|
-
for (identity, origin) in &self.insert_identities {
|
|
240
|
-
insert_identities_by_schema_scope
|
|
241
|
-
.entry(identity.domain().schema_catalog_domain())
|
|
242
|
-
.or_default()
|
|
243
|
-
.push((identity, origin.as_ref()));
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
PreparedWriteValidationIndex {
|
|
247
|
-
rows_by_schema_scope,
|
|
248
|
-
insert_identities_by_schema_scope,
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
#[cfg(test)]
|
|
253
|
-
pub(crate) fn validation_set_for_tests(&self) -> PreparedWriteValidationSet<'_> {
|
|
254
|
-
let rows: Vec<_> = self.validation_rows().collect();
|
|
255
|
-
let insert_identities = self
|
|
256
|
-
.insert_identities
|
|
257
|
-
.iter()
|
|
258
|
-
.map(|(identity, origin)| (identity, origin.as_ref()))
|
|
259
|
-
.collect();
|
|
260
|
-
PreparedWriteValidationSet {
|
|
261
|
-
constraint_rows: rows.clone(),
|
|
262
|
-
rows,
|
|
263
|
-
insert_identities,
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
impl TransactionWriteBuffer {
|
|
269
|
-
pub(crate) fn new(functions: FunctionProviderHandle) -> Self {
|
|
270
|
-
Self {
|
|
271
|
-
functions,
|
|
272
|
-
rows: Mutex::new(Vec::new()),
|
|
273
|
-
by_identity: Mutex::new(HashMap::new()),
|
|
274
|
-
insert_identities: Mutex::new(BTreeMap::new()),
|
|
275
|
-
commit_change_refs_by_branch: Mutex::new(BTreeMap::new()),
|
|
276
|
-
extra_commit_parents_by_branch: Mutex::new(BTreeMap::new()),
|
|
277
|
-
file_data_writes: Mutex::new(Vec::new()),
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/// Drains staged writes for commit.
|
|
282
|
-
pub(crate) fn drain(&self) -> Result<PreparedWriteSet, LixError> {
|
|
283
|
-
let mut rows_guard = self.rows.lock().map_err(|_| {
|
|
284
|
-
LixError::new(
|
|
285
|
-
"LIX_ERROR_UNKNOWN",
|
|
286
|
-
"failed to acquire transaction staged writes lock",
|
|
287
|
-
)
|
|
288
|
-
})?;
|
|
289
|
-
let mut by_identity_guard = self.by_identity.lock().map_err(|_| {
|
|
290
|
-
LixError::new(
|
|
291
|
-
"LIX_ERROR_UNKNOWN",
|
|
292
|
-
"failed to acquire transaction staged identity index lock",
|
|
293
|
-
)
|
|
294
|
-
})?;
|
|
295
|
-
let mut file_data_guard = self.file_data_writes.lock().map_err(|_| {
|
|
296
|
-
LixError::new(
|
|
297
|
-
"LIX_ERROR_UNKNOWN",
|
|
298
|
-
"failed to acquire transaction staged file data lock",
|
|
299
|
-
)
|
|
300
|
-
})?;
|
|
301
|
-
let mut insert_identities_guard = self.insert_identities.lock().map_err(|_| {
|
|
302
|
-
LixError::new(
|
|
303
|
-
"LIX_ERROR_UNKNOWN",
|
|
304
|
-
"failed to acquire transaction staged insert identity lock",
|
|
305
|
-
)
|
|
306
|
-
})?;
|
|
307
|
-
let mut commit_change_refs_guard =
|
|
308
|
-
self.commit_change_refs_by_branch.lock().map_err(|_| {
|
|
309
|
-
LixError::new(
|
|
310
|
-
"LIX_ERROR_UNKNOWN",
|
|
311
|
-
"failed to acquire transaction staged commit change refs lock",
|
|
312
|
-
)
|
|
313
|
-
})?;
|
|
314
|
-
let mut extra_parents_guard = self.extra_commit_parents_by_branch.lock().map_err(|_| {
|
|
315
|
-
LixError::new(
|
|
316
|
-
"LIX_ERROR_UNKNOWN",
|
|
317
|
-
"failed to acquire transaction staged extra commit parents lock",
|
|
318
|
-
)
|
|
319
|
-
})?;
|
|
320
|
-
let result = Ok(PreparedWriteSet {
|
|
321
|
-
state_rows: std::mem::take(&mut *rows_guard)
|
|
322
|
-
.into_iter()
|
|
323
|
-
.flatten()
|
|
324
|
-
.collect(),
|
|
325
|
-
insert_identities: std::mem::take(&mut *insert_identities_guard),
|
|
326
|
-
commit_change_refs_by_branch: std::mem::take(&mut *commit_change_refs_guard),
|
|
327
|
-
extra_commit_parents_by_branch: std::mem::take(&mut *extra_parents_guard),
|
|
328
|
-
file_data_writes: std::mem::take(&mut *file_data_guard),
|
|
329
|
-
});
|
|
330
|
-
by_identity_guard.clear();
|
|
331
|
-
result
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/// Records an additional parent for the commit generated for `branch_id`.
|
|
335
|
-
///
|
|
336
|
-
/// Normal writes parent the new commit to the branch's previous head.
|
|
337
|
-
/// Merges add the source branch head as an extra parent so the commit graph
|
|
338
|
-
/// preserves branch ancestry while tracked-state roots still apply source
|
|
339
|
-
/// rows onto the target root.
|
|
340
|
-
pub(crate) fn add_commit_parent(
|
|
341
|
-
&self,
|
|
342
|
-
branch_id: String,
|
|
343
|
-
parent_commit_id: String,
|
|
344
|
-
) -> Result<(), LixError> {
|
|
345
|
-
let mut guard = self.extra_commit_parents_by_branch.lock().map_err(|_| {
|
|
346
|
-
LixError::new(
|
|
347
|
-
"LIX_ERROR_UNKNOWN",
|
|
348
|
-
"failed to acquire transaction staged extra commit parents lock",
|
|
349
|
-
)
|
|
350
|
-
})?;
|
|
351
|
-
let parents = guard.entry(branch_id).or_default();
|
|
352
|
-
if !parents.contains(&parent_commit_id) {
|
|
353
|
-
parents.push(parent_commit_id);
|
|
354
|
-
}
|
|
355
|
-
Ok(())
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
pub(crate) fn stage_selected_commit_change_refs(
|
|
359
|
-
&self,
|
|
360
|
-
branch_id: String,
|
|
361
|
-
selected_change_refs: impl IntoIterator<Item = StagedCommitChangeRef>,
|
|
362
|
-
) -> Result<String, LixError> {
|
|
363
|
-
let mut functions = self.functions.clone();
|
|
364
|
-
let mut guard = self.commit_change_refs_by_branch.lock().map_err(|_| {
|
|
365
|
-
LixError::new(
|
|
366
|
-
"LIX_ERROR_UNKNOWN",
|
|
367
|
-
"failed to acquire transaction staged commit change refs lock",
|
|
368
|
-
)
|
|
369
|
-
})?;
|
|
370
|
-
let change_refs = guard.entry(branch_id).or_insert_with(|| {
|
|
371
|
-
StagedCommitChangeRefs::new(
|
|
372
|
-
functions.uuid_v7(),
|
|
373
|
-
functions.uuid_v7(),
|
|
374
|
-
functions.timestamp(),
|
|
375
|
-
)
|
|
376
|
-
});
|
|
377
|
-
change_refs.allow_empty();
|
|
378
|
-
for change_ref in selected_change_refs {
|
|
379
|
-
change_refs.add_selected_change_ref(change_ref);
|
|
380
|
-
}
|
|
381
|
-
Ok(change_refs.commit_id.clone())
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
/// Builds the transaction-local read overlay from currently staged writes.
|
|
385
|
-
pub(crate) fn staging_overlay(self: &Arc<Self>) -> Result<PreparedStateRowOverlay, LixError> {
|
|
386
|
-
Ok(PreparedStateRowOverlay {
|
|
387
|
-
staged_writes: Arc::clone(self),
|
|
388
|
-
})
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/// Stages one prepared write batch into this transaction.
|
|
392
|
-
///
|
|
393
|
-
/// Frontends hand raw `TransactionWriteRow`s to `Transaction`; normalization prepares
|
|
394
|
-
/// stable `PreparedStateRow`s before this method indexes them for transaction-
|
|
395
|
-
/// local reads and commit routing.
|
|
396
|
-
pub(crate) fn stage_write(
|
|
397
|
-
&self,
|
|
398
|
-
write: PreparedTransactionWrite,
|
|
399
|
-
) -> Result<TransactionWriteOutcome, LixError> {
|
|
400
|
-
let (mode, count) = match &write {
|
|
401
|
-
PreparedTransactionWrite::Rows { mode, rows } => (Some(*mode), rows.len() as u64),
|
|
402
|
-
PreparedTransactionWrite::RowsWithFileData { mode, count, .. } => (Some(*mode), *count),
|
|
403
|
-
};
|
|
404
|
-
let mut functions = self.functions.clone();
|
|
405
|
-
let (rows, file_data_writes) = self.state_rows_from_stage_write(write);
|
|
406
|
-
reject_duplicate_present_rows_in_batch(&rows)?;
|
|
407
|
-
let mut guard = self.rows.lock().map_err(|_| {
|
|
408
|
-
LixError::new(
|
|
409
|
-
"LIX_ERROR_UNKNOWN",
|
|
410
|
-
"failed to acquire transaction staged writes lock",
|
|
411
|
-
)
|
|
412
|
-
})?;
|
|
413
|
-
let mut by_identity_guard = self.by_identity.lock().map_err(|_| {
|
|
414
|
-
LixError::new(
|
|
415
|
-
"LIX_ERROR_UNKNOWN",
|
|
416
|
-
"failed to acquire transaction staged identity index lock",
|
|
417
|
-
)
|
|
418
|
-
})?;
|
|
419
|
-
let mut commit_change_refs_guard =
|
|
420
|
-
self.commit_change_refs_by_branch.lock().map_err(|_| {
|
|
421
|
-
LixError::new(
|
|
422
|
-
"LIX_ERROR_UNKNOWN",
|
|
423
|
-
"failed to acquire transaction staged commit change refs lock",
|
|
424
|
-
)
|
|
425
|
-
})?;
|
|
426
|
-
let mut insert_identities_guard = self.insert_identities.lock().map_err(|_| {
|
|
427
|
-
LixError::new(
|
|
428
|
-
"LIX_ERROR_UNKNOWN",
|
|
429
|
-
"failed to acquire transaction staged insert identity lock",
|
|
430
|
-
)
|
|
431
|
-
})?;
|
|
432
|
-
for mut row in rows {
|
|
433
|
-
if row.global && row.branch_id != GLOBAL_BRANCH_ID {
|
|
434
|
-
return Err(LixError::new(
|
|
435
|
-
LixError::CODE_INVALID_PARAM,
|
|
436
|
-
"global staged rows must use the global branch id",
|
|
437
|
-
));
|
|
438
|
-
}
|
|
439
|
-
let identity = PreparedStateRowIdentity::from(&row);
|
|
440
|
-
if mode == Some(TransactionWriteMode::Insert)
|
|
441
|
-
&& by_identity_guard.contains_key(&identity)
|
|
442
|
-
{
|
|
443
|
-
return Err(duplicate_insert_identity_error(&row));
|
|
444
|
-
}
|
|
445
|
-
let existing_slot = by_identity_guard.remove(&identity);
|
|
446
|
-
if let Some(RowSlot::State(index)) = existing_slot {
|
|
447
|
-
if let Some(previous) = guard.get_mut(index).and_then(Option::take) {
|
|
448
|
-
remove_row_from_commit_change_refs(&mut commit_change_refs_guard, &previous);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
add_row_to_commit_change_refs(&mut commit_change_refs_guard, &mut row, &mut functions);
|
|
452
|
-
let identity = PreparedStateRowIdentity::from(&row);
|
|
453
|
-
if mode == Some(TransactionWriteMode::Insert) {
|
|
454
|
-
insert_identities_guard.insert(identity.clone(), row.origin.clone());
|
|
455
|
-
}
|
|
456
|
-
let slot = match existing_slot {
|
|
457
|
-
Some(RowSlot::State(index)) => {
|
|
458
|
-
guard[index] = Some(row);
|
|
459
|
-
RowSlot::State(index)
|
|
460
|
-
}
|
|
461
|
-
_ => {
|
|
462
|
-
let index = guard.len();
|
|
463
|
-
guard.push(Some(row));
|
|
464
|
-
RowSlot::State(index)
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
by_identity_guard.insert(identity, slot);
|
|
468
|
-
}
|
|
469
|
-
if !file_data_writes.is_empty() {
|
|
470
|
-
self.file_data_writes
|
|
471
|
-
.lock()
|
|
472
|
-
.map_err(|_| {
|
|
473
|
-
LixError::new(
|
|
474
|
-
"LIX_ERROR_UNKNOWN",
|
|
475
|
-
"failed to acquire transaction staged file data lock",
|
|
476
|
-
)
|
|
477
|
-
})?
|
|
478
|
-
.extend(file_data_writes);
|
|
479
|
-
}
|
|
480
|
-
Ok(TransactionWriteOutcome { count })
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
fn state_rows_from_stage_write(
|
|
484
|
-
&self,
|
|
485
|
-
write: PreparedTransactionWrite,
|
|
486
|
-
) -> (Vec<PreparedStateRow>, Vec<TransactionFileData>) {
|
|
487
|
-
let mut state_rows = Vec::new();
|
|
488
|
-
let mut file_data_writes = Vec::new();
|
|
489
|
-
match write {
|
|
490
|
-
PreparedTransactionWrite::Rows { rows, .. } => {
|
|
491
|
-
state_rows.extend(rows);
|
|
492
|
-
}
|
|
493
|
-
PreparedTransactionWrite::RowsWithFileData {
|
|
494
|
-
rows, file_data, ..
|
|
495
|
-
} => {
|
|
496
|
-
state_rows.extend(rows);
|
|
497
|
-
file_data_writes.extend(file_data);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
(state_rows, file_data_writes)
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
/// Read overlay derived from staged transaction writes.
|
|
505
|
-
#[derive(Clone)]
|
|
506
|
-
pub(crate) struct PreparedStateRowOverlay {
|
|
507
|
-
staged_writes: Arc<TransactionWriteBuffer>,
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
pub(crate) struct StagedScanParts {
|
|
511
|
-
pub(crate) rows: Vec<MaterializedLiveStateRow>,
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
impl PreparedStateRowOverlay {
|
|
515
|
-
/// Returns staged rows visible for a scan request.
|
|
516
|
-
#[cfg(test)]
|
|
517
|
-
pub(crate) fn scan(
|
|
518
|
-
&self,
|
|
519
|
-
request: &LiveStateScanRequest,
|
|
520
|
-
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
521
|
-
Ok(crate::live_state::resolve_visible_rows(
|
|
522
|
-
self.scan_parts(request)?.rows,
|
|
523
|
-
Vec::new(),
|
|
524
|
-
&crate::live_state::VisibilityRequest {
|
|
525
|
-
branch_scope: crate::live_state::VisibilityBranchScope::BranchIds {
|
|
526
|
-
branch_ids: request.filter.branch_ids.clone(),
|
|
527
|
-
},
|
|
528
|
-
include_tombstones: request.filter.include_tombstones,
|
|
529
|
-
limit: None,
|
|
530
|
-
},
|
|
531
|
-
))
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/// Returns staged rows and base-row identities hidden by staged rows in one pass.
|
|
535
|
-
///
|
|
536
|
-
/// Tombstones hide base rows even when the request does not include
|
|
537
|
-
/// tombstone rows in the visible result set.
|
|
538
|
-
pub(crate) fn scan_parts(
|
|
539
|
-
&self,
|
|
540
|
-
request: &LiveStateScanRequest,
|
|
541
|
-
) -> Result<StagedScanParts, LixError> {
|
|
542
|
-
if matches!(
|
|
543
|
-
request.filter.rows,
|
|
544
|
-
crate::live_state::LiveStateRowFilter::None
|
|
545
|
-
) {
|
|
546
|
-
return Ok(StagedScanParts { rows: Vec::new() });
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
let rows_guard = self.staged_writes.rows.lock().map_err(|_| {
|
|
550
|
-
LixError::new(
|
|
551
|
-
"LIX_ERROR_UNKNOWN",
|
|
552
|
-
"failed to acquire transaction staged writes lock",
|
|
553
|
-
)
|
|
554
|
-
})?;
|
|
555
|
-
let by_identity_guard = self.staged_writes.by_identity.lock().map_err(|_| {
|
|
556
|
-
LixError::new(
|
|
557
|
-
"LIX_ERROR_UNKNOWN",
|
|
558
|
-
"failed to acquire transaction staged identity index lock",
|
|
559
|
-
)
|
|
560
|
-
})?;
|
|
561
|
-
|
|
562
|
-
let mut rows = Vec::new();
|
|
563
|
-
for slot in by_identity_guard.values() {
|
|
564
|
-
match *slot {
|
|
565
|
-
RowSlot::State(index) => {
|
|
566
|
-
let Some(row) = rows_guard.get(index).and_then(Option::as_ref) else {
|
|
567
|
-
continue;
|
|
568
|
-
};
|
|
569
|
-
if !staged_row_identity_matches_scan(row, request) {
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
rows.push(MaterializedLiveStateRow::from(row));
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
Ok(StagedScanParts { rows })
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/// Returns a staged exact-row answer, if this transaction has one.
|
|
580
|
-
#[cfg(test)]
|
|
581
|
-
pub(crate) fn load_exact(&self, request: &LiveStateRowRequest) -> Option<StagedExactRow> {
|
|
582
|
-
let untracked_identity = PreparedStateRowIdentity::from_exact_request(request, true)?;
|
|
583
|
-
if let Some(row) = self.load_state_slot(&untracked_identity) {
|
|
584
|
-
return Some(if row.snapshot.is_none() {
|
|
585
|
-
StagedExactRow::Tombstone
|
|
586
|
-
} else {
|
|
587
|
-
StagedExactRow::Row(MaterializedLiveStateRow::from(&row))
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
let identity = PreparedStateRowIdentity::from_exact_request(request, false)?;
|
|
592
|
-
if let Some(row) = self.load_state_slot(&identity) {
|
|
593
|
-
return Some(if row.snapshot.is_none() {
|
|
594
|
-
StagedExactRow::Tombstone
|
|
595
|
-
} else {
|
|
596
|
-
StagedExactRow::Row(MaterializedLiveStateRow::from(&row))
|
|
597
|
-
});
|
|
598
|
-
}
|
|
599
|
-
None
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
#[cfg(test)]
|
|
603
|
-
fn load_state_slot(&self, identity: &PreparedStateRowIdentity) -> Option<PreparedStateRow> {
|
|
604
|
-
let rows_guard = self.staged_writes.rows.lock().ok()?;
|
|
605
|
-
let by_identity_guard = self.staged_writes.by_identity.lock().ok()?;
|
|
606
|
-
let Some(RowSlot::State(index)) = by_identity_guard.get(identity).copied() else {
|
|
607
|
-
return None;
|
|
608
|
-
};
|
|
609
|
-
rows_guard.get(index)?.as_ref().cloned()
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
impl crate::live_state::StagedLiveStateRows for PreparedStateRowOverlay {
|
|
614
|
-
fn staged_rows(
|
|
615
|
-
&self,
|
|
616
|
-
request: &LiveStateScanRequest,
|
|
617
|
-
) -> Result<Vec<MaterializedLiveStateRow>, LixError> {
|
|
618
|
-
Ok(self.scan_parts(request)?.rows)
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
#[cfg(test)]
|
|
623
|
-
pub(crate) enum StagedExactRow {
|
|
624
|
-
Row(MaterializedLiveStateRow),
|
|
625
|
-
Tombstone,
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
629
|
-
pub(crate) struct PreparedStateRowIdentity {
|
|
630
|
-
untracked: bool,
|
|
631
|
-
schema_key: String,
|
|
632
|
-
entity_pk: crate::entity_pk::EntityPk,
|
|
633
|
-
file_id: Option<String>,
|
|
634
|
-
branch_id: String,
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
impl PreparedStateRowIdentity {
|
|
638
|
-
fn from_staged_row(row: &PreparedStateRow) -> Self {
|
|
639
|
-
Self {
|
|
640
|
-
untracked: row.untracked,
|
|
641
|
-
schema_key: row.schema_key.clone(),
|
|
642
|
-
entity_pk: row.entity_pk.clone(),
|
|
643
|
-
file_id: row.file_id.clone(),
|
|
644
|
-
branch_id: row.branch_id.clone(),
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
#[cfg(test)]
|
|
649
|
-
fn from_exact_request(request: &LiveStateRowRequest, untracked: bool) -> Option<Self> {
|
|
650
|
-
let file_id = match &request.file_id {
|
|
651
|
-
NullableKeyFilter::Null => None,
|
|
652
|
-
NullableKeyFilter::Value(value) => Some(value.clone()),
|
|
653
|
-
// Exact overlay lookup requires a concrete row identity.
|
|
654
|
-
NullableKeyFilter::Any => return None,
|
|
655
|
-
};
|
|
656
|
-
Some(Self {
|
|
657
|
-
untracked,
|
|
658
|
-
schema_key: request.schema_key.clone(),
|
|
659
|
-
entity_pk: request.entity_pk.clone(),
|
|
660
|
-
file_id,
|
|
661
|
-
branch_id: request.branch_id.clone(),
|
|
662
|
-
})
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
pub(crate) fn schema_key(&self) -> &str {
|
|
666
|
-
&self.schema_key
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
pub(crate) fn entity_pk(&self) -> &crate::entity_pk::EntityPk {
|
|
670
|
-
&self.entity_pk
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
pub(crate) fn domain(&self) -> Domain {
|
|
674
|
-
Domain::exact_file(self.branch_id.clone(), self.untracked, self.file_id.clone())
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
impl From<&PreparedStateRow> for PreparedStateRowIdentity {
|
|
679
|
-
fn from(row: &PreparedStateRow) -> Self {
|
|
680
|
-
Self::from_staged_row(row)
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
impl From<&MaterializedLiveStateRow> for PreparedStateRowIdentity {
|
|
685
|
-
fn from(row: &MaterializedLiveStateRow) -> Self {
|
|
686
|
-
Self {
|
|
687
|
-
untracked: row.untracked,
|
|
688
|
-
schema_key: row.schema_key.clone(),
|
|
689
|
-
entity_pk: row.entity_pk.clone(),
|
|
690
|
-
file_id: row.file_id.clone(),
|
|
691
|
-
branch_id: row.branch_id.clone(),
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
fn reject_duplicate_present_rows_in_batch(rows: &[PreparedStateRow]) -> Result<(), LixError> {
|
|
697
|
-
let mut pending_present_rows = BTreeMap::<PreparedStateRowIdentity, &PreparedStateRow>::new();
|
|
698
|
-
for row in rows {
|
|
699
|
-
let identity = PreparedStateRowIdentity::from(row);
|
|
700
|
-
if row.snapshot.is_none() {
|
|
701
|
-
pending_present_rows.remove(&identity);
|
|
702
|
-
continue;
|
|
703
|
-
}
|
|
704
|
-
if let Some(previous) = pending_present_rows.insert(identity, row) {
|
|
705
|
-
return Err(duplicate_staged_present_row_error(row, previous));
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
Ok(())
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
fn duplicate_staged_present_row_error(
|
|
712
|
-
row: &PreparedStateRow,
|
|
713
|
-
previous: &PreparedStateRow,
|
|
714
|
-
) -> LixError {
|
|
715
|
-
let message = logical_primary_key_violation_message(row.origin.as_ref())
|
|
716
|
-
.unwrap_or_else(|| {
|
|
717
|
-
format!(
|
|
718
|
-
"primary-key constraint violation on schema '{}': duplicate staged rows for entity_pk '{}' in branch '{}'",
|
|
719
|
-
row.schema_key,
|
|
720
|
-
previous
|
|
721
|
-
.entity_pk
|
|
722
|
-
.as_json_array_text()
|
|
723
|
-
.unwrap_or_else(|_| "<invalid entity_pk>".to_string()),
|
|
724
|
-
row.branch_id
|
|
725
|
-
)
|
|
726
|
-
});
|
|
727
|
-
LixError::new(LixError::CODE_UNIQUE, message)
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
pub(crate) fn duplicate_insert_identity_message(
|
|
731
|
-
schema_key: &str,
|
|
732
|
-
entity_pk: &crate::entity_pk::EntityPk,
|
|
733
|
-
branch_id: Option<&str>,
|
|
734
|
-
origin: Option<&TransactionWriteOrigin>,
|
|
735
|
-
) -> String {
|
|
736
|
-
if let Some(message) = logical_primary_key_violation_message(origin) {
|
|
737
|
-
return message;
|
|
738
|
-
}
|
|
739
|
-
let entity_pk = entity_pk
|
|
740
|
-
.as_json_array_text()
|
|
741
|
-
.unwrap_or_else(|_| "<invalid entity_pk>".to_string());
|
|
742
|
-
match branch_id {
|
|
743
|
-
Some(branch_id) => format!(
|
|
744
|
-
"primary-key constraint violation on schema '{schema_key}': INSERT would duplicate entity_pk '{entity_pk}' in branch '{branch_id}'"
|
|
745
|
-
),
|
|
746
|
-
None => format!(
|
|
747
|
-
"primary-key constraint violation on schema '{schema_key}': INSERT would duplicate entity_pk '{entity_pk}'"
|
|
748
|
-
),
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
fn duplicate_insert_identity_error(row: &PreparedStateRow) -> LixError {
|
|
753
|
-
let message = duplicate_insert_identity_message(
|
|
754
|
-
&row.schema_key,
|
|
755
|
-
&row.entity_pk,
|
|
756
|
-
Some(&row.branch_id),
|
|
757
|
-
row.origin.as_ref(),
|
|
758
|
-
);
|
|
759
|
-
LixError::new(LixError::CODE_UNIQUE, message)
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
fn logical_primary_key_violation_message(
|
|
763
|
-
origin: Option<&TransactionWriteOrigin>,
|
|
764
|
-
) -> Option<String> {
|
|
765
|
-
let origin = origin?;
|
|
766
|
-
if origin.operation != TransactionWriteOperation::Insert {
|
|
767
|
-
return None;
|
|
768
|
-
}
|
|
769
|
-
let primary_key = origin.primary_key.as_ref()?;
|
|
770
|
-
Some(format!(
|
|
771
|
-
"primary-key constraint violation on table '{}': INSERT would duplicate {}",
|
|
772
|
-
origin.surface,
|
|
773
|
-
format_logical_primary_key(primary_key)
|
|
774
|
-
))
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
fn format_logical_primary_key(primary_key: &LogicalPrimaryKey) -> String {
|
|
778
|
-
primary_key
|
|
779
|
-
.columns
|
|
780
|
-
.iter()
|
|
781
|
-
.enumerate()
|
|
782
|
-
.map(|(index, column)| {
|
|
783
|
-
let value = primary_key
|
|
784
|
-
.values
|
|
785
|
-
.get(index)
|
|
786
|
-
.map(String::as_str)
|
|
787
|
-
.unwrap_or("<missing>");
|
|
788
|
-
format!("{column} '{value}'")
|
|
789
|
-
})
|
|
790
|
-
.collect::<Vec<_>>()
|
|
791
|
-
.join(", ")
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
fn add_row_to_commit_change_refs(
|
|
795
|
-
change_refs_by_branch: &mut BTreeMap<String, StagedCommitChangeRefs>,
|
|
796
|
-
row: &mut PreparedStateRow,
|
|
797
|
-
functions: &mut dyn FunctionProvider,
|
|
798
|
-
) {
|
|
799
|
-
if row.untracked {
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
let change_id = row
|
|
803
|
-
.change_id
|
|
804
|
-
.clone()
|
|
805
|
-
.expect("tracked staged rows must carry change_id for commit change refs");
|
|
806
|
-
let change_refs = change_refs_by_branch
|
|
807
|
-
.entry(row.branch_id.clone())
|
|
808
|
-
.or_insert_with(|| {
|
|
809
|
-
StagedCommitChangeRefs::new(
|
|
810
|
-
functions.uuid_v7(),
|
|
811
|
-
functions.uuid_v7(),
|
|
812
|
-
functions.timestamp(),
|
|
813
|
-
)
|
|
814
|
-
});
|
|
815
|
-
row.commit_id = Some(change_refs.commit_id.clone());
|
|
816
|
-
change_refs.add_change_id(change_id);
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
fn remove_row_from_commit_change_refs(
|
|
820
|
-
change_refs_by_branch: &mut BTreeMap<String, StagedCommitChangeRefs>,
|
|
821
|
-
row: &PreparedStateRow,
|
|
822
|
-
) {
|
|
823
|
-
if row.untracked {
|
|
824
|
-
return;
|
|
825
|
-
}
|
|
826
|
-
let Some(change_refs) = change_refs_by_branch.get_mut(&row.branch_id) else {
|
|
827
|
-
return;
|
|
828
|
-
};
|
|
829
|
-
let Some(change_id) = row.change_id.as_deref() else {
|
|
830
|
-
return;
|
|
831
|
-
};
|
|
832
|
-
change_refs.remove_change_id(change_id);
|
|
833
|
-
if change_refs.is_empty() {
|
|
834
|
-
change_refs_by_branch.remove(&row.branch_id);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
fn staged_row_identity_matches_scan(
|
|
839
|
-
row: &PreparedStateRow,
|
|
840
|
-
request: &LiveStateScanRequest,
|
|
841
|
-
) -> bool {
|
|
842
|
-
if !request.filter.schema_keys.is_empty()
|
|
843
|
-
&& !request.filter.schema_keys.contains(&row.schema_key)
|
|
844
|
-
{
|
|
845
|
-
return false;
|
|
846
|
-
}
|
|
847
|
-
if !request.filter.entity_pks.is_empty() && !request.filter.entity_pks.contains(&row.entity_pk)
|
|
848
|
-
{
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
if !staged_branch_matches_scan(&row.branch_id, request) {
|
|
852
|
-
return false;
|
|
853
|
-
}
|
|
854
|
-
if request
|
|
855
|
-
.filter
|
|
856
|
-
.untracked
|
|
857
|
-
.is_some_and(|untracked| row.untracked != untracked)
|
|
858
|
-
{
|
|
859
|
-
return false;
|
|
860
|
-
}
|
|
861
|
-
nullable_key_matches_filters(&row.file_id, &request.filter.file_ids)
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
fn nullable_key_matches_filters(
|
|
865
|
-
value: &Option<String>,
|
|
866
|
-
filters: &[NullableKeyFilter<String>],
|
|
867
|
-
) -> bool {
|
|
868
|
-
filters.is_empty()
|
|
869
|
-
|| filters
|
|
870
|
-
.iter()
|
|
871
|
-
.any(|filter| nullable_key_matches_filter(value, filter))
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
fn staged_branch_matches_scan(branch_id: &str, request: &LiveStateScanRequest) -> bool {
|
|
875
|
-
request.filter.branch_ids.is_empty()
|
|
876
|
-
|| request
|
|
877
|
-
.filter
|
|
878
|
-
.branch_ids
|
|
879
|
-
.iter()
|
|
880
|
-
.any(|requested| requested == branch_id)
|
|
881
|
-
|| (branch_id == GLOBAL_BRANCH_ID
|
|
882
|
-
&& request
|
|
883
|
-
.filter
|
|
884
|
-
.branch_ids
|
|
885
|
-
.iter()
|
|
886
|
-
.any(|requested| requested != GLOBAL_BRANCH_ID))
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
fn nullable_key_matches_filter(value: &Option<String>, filter: &NullableKeyFilter<String>) -> bool {
|
|
890
|
-
match filter {
|
|
891
|
-
NullableKeyFilter::Any => true,
|
|
892
|
-
NullableKeyFilter::Null => value.is_none(),
|
|
893
|
-
NullableKeyFilter::Value(expected) => value.as_ref() == Some(expected),
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
#[cfg(test)]
|
|
898
|
-
mod tests {
|
|
899
|
-
use super::*;
|
|
900
|
-
use crate::functions::SharedFunctionProvider;
|
|
901
|
-
use crate::live_state::{LiveStateFilter, LiveStateRowRequest};
|
|
902
|
-
|
|
903
|
-
#[tokio::test]
|
|
904
|
-
async fn staging_overlay_uses_last_staged_row_for_exact_load() {
|
|
905
|
-
let staged_writes = test_staged_writes();
|
|
906
|
-
|
|
907
|
-
staged_writes
|
|
908
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
909
|
-
mode: TransactionWriteMode::Replace,
|
|
910
|
-
rows: vec![state_row("sql2-duplicate-key", "first")],
|
|
911
|
-
})
|
|
912
|
-
.expect("initial row should stage");
|
|
913
|
-
staged_writes
|
|
914
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
915
|
-
mode: TransactionWriteMode::Replace,
|
|
916
|
-
rows: vec![state_row("sql2-duplicate-key", "second")],
|
|
917
|
-
})
|
|
918
|
-
.expect("staging rows should succeed");
|
|
919
|
-
|
|
920
|
-
let overlay = staged_writes
|
|
921
|
-
.staging_overlay()
|
|
922
|
-
.expect("overlay should build from staged rows");
|
|
923
|
-
let row = overlay
|
|
924
|
-
.load_exact(&LiveStateRowRequest {
|
|
925
|
-
schema_key: "lix_key_value".to_string(),
|
|
926
|
-
branch_id: "global".to_string(),
|
|
927
|
-
entity_pk: crate::entity_pk::EntityPk::single("sql2-duplicate-key"),
|
|
928
|
-
file_id: NullableKeyFilter::Null,
|
|
929
|
-
})
|
|
930
|
-
.expect("staged row should be visible");
|
|
931
|
-
|
|
932
|
-
let StagedExactRow::Row(row) = row else {
|
|
933
|
-
panic!("latest staged row should not be a tombstone");
|
|
934
|
-
};
|
|
935
|
-
assert_eq!(
|
|
936
|
-
row.snapshot_content.as_deref(),
|
|
937
|
-
Some("{\"key\":\"sql2-duplicate-key\",\"value\":\"second\"}")
|
|
938
|
-
);
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
#[tokio::test]
|
|
942
|
-
async fn staging_overlay_scan_returns_only_latest_row_per_identity() {
|
|
943
|
-
let staged_writes = test_staged_writes();
|
|
944
|
-
|
|
945
|
-
staged_writes
|
|
946
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
947
|
-
mode: TransactionWriteMode::Replace,
|
|
948
|
-
rows: vec![state_row("sql2-duplicate-key", "first")],
|
|
949
|
-
})
|
|
950
|
-
.expect("initial row should stage");
|
|
951
|
-
staged_writes
|
|
952
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
953
|
-
mode: TransactionWriteMode::Replace,
|
|
954
|
-
rows: vec![state_row("sql2-duplicate-key", "second")],
|
|
955
|
-
})
|
|
956
|
-
.expect("staging rows should succeed");
|
|
957
|
-
|
|
958
|
-
let overlay = staged_writes
|
|
959
|
-
.staging_overlay()
|
|
960
|
-
.expect("overlay should build from staged rows");
|
|
961
|
-
let rows = overlay
|
|
962
|
-
.scan(&scan_request_for_key("sql2-duplicate-key", false))
|
|
963
|
-
.expect("overlay scan should succeed");
|
|
964
|
-
|
|
965
|
-
assert_eq!(rows.len(), 1);
|
|
966
|
-
assert_eq!(
|
|
967
|
-
rows[0].snapshot_content.as_deref(),
|
|
968
|
-
Some("{\"key\":\"sql2-duplicate-key\",\"value\":\"second\"}")
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
#[tokio::test]
|
|
973
|
-
async fn staging_overlay_delete_hides_prior_staged_insert() {
|
|
974
|
-
let staged_writes = test_staged_writes();
|
|
975
|
-
|
|
976
|
-
staged_writes
|
|
977
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
978
|
-
mode: TransactionWriteMode::Replace,
|
|
979
|
-
rows: vec![
|
|
980
|
-
state_row("sql2-delete-key", "visible"),
|
|
981
|
-
tombstone_row("sql2-delete-key"),
|
|
982
|
-
],
|
|
983
|
-
})
|
|
984
|
-
.expect("staging rows should succeed");
|
|
985
|
-
|
|
986
|
-
let overlay = staged_writes
|
|
987
|
-
.staging_overlay()
|
|
988
|
-
.expect("overlay should build from staged rows");
|
|
989
|
-
let exact = overlay
|
|
990
|
-
.load_exact(&exact_request_for_key("sql2-delete-key"))
|
|
991
|
-
.expect("staged tombstone should answer exact load");
|
|
992
|
-
assert!(matches!(exact, StagedExactRow::Tombstone));
|
|
993
|
-
assert!(overlay
|
|
994
|
-
.scan(&scan_request_for_key("sql2-delete-key", false))
|
|
995
|
-
.expect("overlay scan should succeed")
|
|
996
|
-
.is_empty());
|
|
997
|
-
|
|
998
|
-
let tombstones = overlay
|
|
999
|
-
.scan(&scan_request_for_key("sql2-delete-key", true))
|
|
1000
|
-
.expect("overlay scan should succeed");
|
|
1001
|
-
assert_eq!(tombstones.len(), 1);
|
|
1002
|
-
assert_eq!(tombstones[0].snapshot_content, None);
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
#[tokio::test]
|
|
1006
|
-
async fn staging_overlay_insert_after_delete_resurrects_row() {
|
|
1007
|
-
let staged_writes = test_staged_writes();
|
|
1008
|
-
|
|
1009
|
-
staged_writes
|
|
1010
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1011
|
-
mode: TransactionWriteMode::Replace,
|
|
1012
|
-
rows: vec![
|
|
1013
|
-
tombstone_row("sql2-resurrect-key"),
|
|
1014
|
-
state_row("sql2-resurrect-key", "visible-again"),
|
|
1015
|
-
],
|
|
1016
|
-
})
|
|
1017
|
-
.expect("staging rows should succeed");
|
|
1018
|
-
|
|
1019
|
-
let overlay = staged_writes
|
|
1020
|
-
.staging_overlay()
|
|
1021
|
-
.expect("overlay should build from staged rows");
|
|
1022
|
-
let exact = overlay
|
|
1023
|
-
.load_exact(&exact_request_for_key("sql2-resurrect-key"))
|
|
1024
|
-
.expect("staged row should answer exact load");
|
|
1025
|
-
|
|
1026
|
-
let StagedExactRow::Row(row) = exact else {
|
|
1027
|
-
panic!("latest staged row should be visible");
|
|
1028
|
-
};
|
|
1029
|
-
assert_eq!(
|
|
1030
|
-
row.snapshot_content.as_deref(),
|
|
1031
|
-
Some("{\"key\":\"sql2-resurrect-key\",\"value\":\"visible-again\"}")
|
|
1032
|
-
);
|
|
1033
|
-
assert_eq!(
|
|
1034
|
-
overlay
|
|
1035
|
-
.scan(&scan_request_for_key("sql2-resurrect-key", false))
|
|
1036
|
-
.expect("overlay scan should succeed")
|
|
1037
|
-
.len(),
|
|
1038
|
-
1
|
|
1039
|
-
);
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
#[tokio::test]
|
|
1043
|
-
async fn staged_writes_drain_returns_coalesced_latest_rows() {
|
|
1044
|
-
let staged_writes = test_staged_writes();
|
|
1045
|
-
|
|
1046
|
-
staged_writes
|
|
1047
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1048
|
-
mode: TransactionWriteMode::Replace,
|
|
1049
|
-
rows: vec![
|
|
1050
|
-
state_row("sql2-key-a", "first"),
|
|
1051
|
-
state_row("sql2-key-b", "only"),
|
|
1052
|
-
],
|
|
1053
|
-
})
|
|
1054
|
-
.expect("initial rows should stage");
|
|
1055
|
-
staged_writes
|
|
1056
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1057
|
-
mode: TransactionWriteMode::Replace,
|
|
1058
|
-
rows: vec![state_row("sql2-key-a", "second")],
|
|
1059
|
-
})
|
|
1060
|
-
.expect("staging rows should succeed");
|
|
1061
|
-
|
|
1062
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1063
|
-
|
|
1064
|
-
assert_eq!(drained.state_rows.len(), 2);
|
|
1065
|
-
assert!(drained.state_rows.iter().any(|row| {
|
|
1066
|
-
row.entity_pk == crate::entity_pk::EntityPk::single("sql2-key-a")
|
|
1067
|
-
&& row
|
|
1068
|
-
.snapshot
|
|
1069
|
-
.as_ref()
|
|
1070
|
-
.map(|snapshot| snapshot.normalized.as_ref())
|
|
1071
|
-
== Some("{\"key\":\"sql2-key-a\",\"value\":\"second\"}")
|
|
1072
|
-
}));
|
|
1073
|
-
assert!(drained.state_rows.iter().any(|row| {
|
|
1074
|
-
row.entity_pk == crate::entity_pk::EntityPk::single("sql2-key-b")
|
|
1075
|
-
&& row
|
|
1076
|
-
.snapshot
|
|
1077
|
-
.as_ref()
|
|
1078
|
-
.map(|snapshot| snapshot.normalized.as_ref())
|
|
1079
|
-
== Some("{\"key\":\"sql2-key-b\",\"value\":\"only\"}")
|
|
1080
|
-
}));
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
#[tokio::test]
|
|
1084
|
-
async fn staged_writes_drain_preserves_file_data_payloads() {
|
|
1085
|
-
let staged_writes = test_staged_writes();
|
|
1086
|
-
|
|
1087
|
-
staged_writes
|
|
1088
|
-
.stage_write(PreparedTransactionWrite::RowsWithFileData {
|
|
1089
|
-
mode: TransactionWriteMode::Replace,
|
|
1090
|
-
rows: vec![state_row("file-readme", "descriptor")],
|
|
1091
|
-
file_data: vec![TransactionFileData {
|
|
1092
|
-
file_id: "file-readme".to_string(),
|
|
1093
|
-
branch_id: "global".to_string(),
|
|
1094
|
-
untracked: true,
|
|
1095
|
-
data: b"hello".to_vec(),
|
|
1096
|
-
}],
|
|
1097
|
-
count: 1,
|
|
1098
|
-
})
|
|
1099
|
-
.expect("staging rows with file data should succeed");
|
|
1100
|
-
|
|
1101
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1102
|
-
|
|
1103
|
-
assert_eq!(drained.state_rows.len(), 1);
|
|
1104
|
-
assert_eq!(drained.file_data_writes.len(), 1);
|
|
1105
|
-
assert_eq!(drained.file_data_writes[0].file_id, "file-readme");
|
|
1106
|
-
assert_eq!(drained.file_data_writes[0].data, b"hello");
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
#[tokio::test]
|
|
1110
|
-
async fn staged_writes_track_commit_members_for_tracked_global_rows() {
|
|
1111
|
-
let staged_writes = test_staged_writes();
|
|
1112
|
-
|
|
1113
|
-
staged_writes
|
|
1114
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1115
|
-
mode: TransactionWriteMode::Replace,
|
|
1116
|
-
rows: vec![state_row("tracked-key", "value").with_tracked()],
|
|
1117
|
-
})
|
|
1118
|
-
.expect("tracked global row should stage");
|
|
1119
|
-
|
|
1120
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1121
|
-
let change_refs = drained
|
|
1122
|
-
.commit_change_refs_by_branch
|
|
1123
|
-
.get("global")
|
|
1124
|
-
.expect("global commit change_refs should exist");
|
|
1125
|
-
assert_eq!(
|
|
1126
|
-
change_refs.change_ids.iter().cloned().collect::<Vec<_>>(),
|
|
1127
|
-
vec!["test-change-id".to_string()]
|
|
1128
|
-
);
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
#[tokio::test]
|
|
1132
|
-
async fn staged_writes_do_not_track_untracked_rows_as_commit_members() {
|
|
1133
|
-
let staged_writes = test_staged_writes();
|
|
1134
|
-
|
|
1135
|
-
staged_writes
|
|
1136
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1137
|
-
mode: TransactionWriteMode::Replace,
|
|
1138
|
-
rows: vec![state_row("untracked-key", "value")],
|
|
1139
|
-
})
|
|
1140
|
-
.expect("untracked row should stage");
|
|
1141
|
-
|
|
1142
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1143
|
-
assert!(drained.commit_change_refs_by_branch.is_empty());
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
#[tokio::test]
|
|
1147
|
-
async fn staged_writes_replace_commit_member_on_tracked_overwrite() {
|
|
1148
|
-
let staged_writes = test_staged_writes();
|
|
1149
|
-
|
|
1150
|
-
staged_writes
|
|
1151
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1152
|
-
mode: TransactionWriteMode::Replace,
|
|
1153
|
-
rows: vec![state_row("overwrite-key", "first")
|
|
1154
|
-
.with_tracked()
|
|
1155
|
-
.with_change_id("change-first")],
|
|
1156
|
-
})
|
|
1157
|
-
.expect("initial tracked row should stage");
|
|
1158
|
-
staged_writes
|
|
1159
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1160
|
-
mode: TransactionWriteMode::Replace,
|
|
1161
|
-
rows: vec![state_row("overwrite-key", "second")
|
|
1162
|
-
.with_tracked()
|
|
1163
|
-
.with_change_id("change-second")],
|
|
1164
|
-
})
|
|
1165
|
-
.expect("tracked overwrite should stage");
|
|
1166
|
-
|
|
1167
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1168
|
-
let change_refs = drained
|
|
1169
|
-
.commit_change_refs_by_branch
|
|
1170
|
-
.get("global")
|
|
1171
|
-
.expect("global commit change_refs should exist");
|
|
1172
|
-
assert_eq!(
|
|
1173
|
-
change_refs.change_ids.iter().cloned().collect::<Vec<_>>(),
|
|
1174
|
-
vec!["change-second".to_string()]
|
|
1175
|
-
);
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
#[tokio::test]
|
|
1179
|
-
async fn staged_writes_keep_tracked_and_untracked_domains_separate() {
|
|
1180
|
-
let staged_writes = test_staged_writes();
|
|
1181
|
-
|
|
1182
|
-
staged_writes
|
|
1183
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1184
|
-
mode: TransactionWriteMode::Replace,
|
|
1185
|
-
rows: vec![
|
|
1186
|
-
state_row("tracked-to-untracked-key", "tracked")
|
|
1187
|
-
.with_tracked()
|
|
1188
|
-
.with_change_id("change-tracked"),
|
|
1189
|
-
state_row("tracked-to-untracked-key", "untracked")
|
|
1190
|
-
.with_change_id("change-untracked"),
|
|
1191
|
-
],
|
|
1192
|
-
})
|
|
1193
|
-
.expect("untracked overwrite should stage");
|
|
1194
|
-
|
|
1195
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1196
|
-
assert_eq!(drained.state_rows.len(), 2);
|
|
1197
|
-
assert!(drained
|
|
1198
|
-
.state_rows
|
|
1199
|
-
.iter()
|
|
1200
|
-
.any(|row| { row.change_id.as_deref() == Some("change-tracked") && !row.untracked }));
|
|
1201
|
-
assert!(drained
|
|
1202
|
-
.state_rows
|
|
1203
|
-
.iter()
|
|
1204
|
-
.any(|row| { row.change_id.as_deref() == Some("change-untracked") && row.untracked }));
|
|
1205
|
-
let change_refs = drained
|
|
1206
|
-
.commit_change_refs_by_branch
|
|
1207
|
-
.get("global")
|
|
1208
|
-
.expect("tracked commit member should remain in tracked domain");
|
|
1209
|
-
assert_eq!(
|
|
1210
|
-
change_refs.change_ids.iter().cloned().collect::<Vec<_>>(),
|
|
1211
|
-
vec!["change-tracked".to_string()]
|
|
1212
|
-
);
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
#[tokio::test]
|
|
1216
|
-
async fn staged_writes_reject_duplicate_present_rows_in_one_batch() {
|
|
1217
|
-
let staged_writes = test_staged_writes();
|
|
1218
|
-
|
|
1219
|
-
let error = staged_writes
|
|
1220
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1221
|
-
mode: TransactionWriteMode::Replace,
|
|
1222
|
-
rows: vec![
|
|
1223
|
-
state_row("duplicate-present-key", "first"),
|
|
1224
|
-
state_row("duplicate-present-key", "second"),
|
|
1225
|
-
],
|
|
1226
|
-
})
|
|
1227
|
-
.expect_err("same-batch duplicate present rows should fail");
|
|
1228
|
-
|
|
1229
|
-
assert_eq!(error.code, LixError::CODE_UNIQUE);
|
|
1230
|
-
assert!(
|
|
1231
|
-
error.message.contains("primary-key constraint violation"),
|
|
1232
|
-
"error should explain the duplicate primary key: {error:?}"
|
|
1233
|
-
);
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
#[tokio::test]
|
|
1237
|
-
async fn staged_writes_insert_keeps_tracked_and_untracked_rows_as_distinct_identities() {
|
|
1238
|
-
let staged_writes = test_staged_writes();
|
|
1239
|
-
|
|
1240
|
-
staged_writes
|
|
1241
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1242
|
-
mode: TransactionWriteMode::Insert,
|
|
1243
|
-
rows: vec![
|
|
1244
|
-
state_row("shared-domain-key", "tracked").with_tracked(),
|
|
1245
|
-
state_row("shared-domain-key", "untracked"),
|
|
1246
|
-
],
|
|
1247
|
-
})
|
|
1248
|
-
.expect("tracked and untracked rows are distinct domain identities");
|
|
1249
|
-
|
|
1250
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1251
|
-
assert_eq!(drained.state_rows.len(), 2);
|
|
1252
|
-
assert!(drained.state_rows.iter().any(|row| {
|
|
1253
|
-
row.entity_pk == crate::entity_pk::EntityPk::single("shared-domain-key")
|
|
1254
|
-
&& !row.untracked
|
|
1255
|
-
}));
|
|
1256
|
-
assert!(drained.state_rows.iter().any(|row| {
|
|
1257
|
-
row.entity_pk == crate::entity_pk::EntityPk::single("shared-domain-key")
|
|
1258
|
-
&& row.untracked
|
|
1259
|
-
}));
|
|
1260
|
-
}
|
|
1261
|
-
|
|
1262
|
-
#[tokio::test]
|
|
1263
|
-
async fn staged_writes_track_active_branch_members_separately() {
|
|
1264
|
-
let staged_writes = test_staged_writes();
|
|
1265
|
-
|
|
1266
|
-
staged_writes
|
|
1267
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1268
|
-
mode: TransactionWriteMode::Replace,
|
|
1269
|
-
rows: vec![state_row("active-branch-key", "value")
|
|
1270
|
-
.with_tracked()
|
|
1271
|
-
.with_branch("branch-a")],
|
|
1272
|
-
})
|
|
1273
|
-
.expect("active-branch tracked staging should accumulate change_refs");
|
|
1274
|
-
|
|
1275
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1276
|
-
let change_refs = drained
|
|
1277
|
-
.commit_change_refs_by_branch
|
|
1278
|
-
.get("branch-a")
|
|
1279
|
-
.expect("active-branch commit change_refs should exist");
|
|
1280
|
-
assert_eq!(
|
|
1281
|
-
change_refs.change_ids.iter().cloned().collect::<Vec<_>>(),
|
|
1282
|
-
vec!["test-change-id".to_string()]
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
#[tokio::test]
|
|
1287
|
-
async fn staged_writes_reject_global_rows_with_non_global_branch_id() {
|
|
1288
|
-
let staged_writes = test_staged_writes();
|
|
1289
|
-
|
|
1290
|
-
let error = staged_writes
|
|
1291
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1292
|
-
mode: TransactionWriteMode::Replace,
|
|
1293
|
-
rows: vec![{
|
|
1294
|
-
let mut row = state_row("invalid-global-key", "value");
|
|
1295
|
-
row.branch_id = "branch-a".to_string();
|
|
1296
|
-
row
|
|
1297
|
-
}],
|
|
1298
|
-
})
|
|
1299
|
-
.expect_err("global row with non-global branch should fail");
|
|
1300
|
-
|
|
1301
|
-
assert!(error
|
|
1302
|
-
.message
|
|
1303
|
-
.contains("global staged rows must use the global branch id"));
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
#[tokio::test]
|
|
1307
|
-
async fn staging_overlay_identity_matches_live_state_conflict_key() {
|
|
1308
|
-
let staged_writes = test_staged_writes();
|
|
1309
|
-
|
|
1310
|
-
staged_writes
|
|
1311
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1312
|
-
mode: TransactionWriteMode::Replace,
|
|
1313
|
-
rows: vec![state_row("shared-entity", "base")],
|
|
1314
|
-
})
|
|
1315
|
-
.expect("initial same-identity row should stage");
|
|
1316
|
-
staged_writes
|
|
1317
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1318
|
-
mode: TransactionWriteMode::Replace,
|
|
1319
|
-
rows: vec![
|
|
1320
|
-
state_row("shared-entity", "base"),
|
|
1321
|
-
state_row("shared-entity", "other-branch").with_branch("branch-b"),
|
|
1322
|
-
state_row("shared-entity", "other-schema").with_schema("other_schema"),
|
|
1323
|
-
state_row("shared-entity", "other-file").with_file_id("file-a"),
|
|
1324
|
-
state_row("shared-entity", "tracked").with_tracked(),
|
|
1325
|
-
],
|
|
1326
|
-
})
|
|
1327
|
-
.expect("staging rows should succeed");
|
|
1328
|
-
|
|
1329
|
-
let overlay = staged_writes
|
|
1330
|
-
.staging_overlay()
|
|
1331
|
-
.expect("overlay should build from staged rows");
|
|
1332
|
-
let rows = overlay
|
|
1333
|
-
.scan(&LiveStateScanRequest {
|
|
1334
|
-
filter: LiveStateFilter {
|
|
1335
|
-
entity_pks: vec![crate::entity_pk::EntityPk::single("shared-entity")],
|
|
1336
|
-
include_tombstones: true,
|
|
1337
|
-
..LiveStateFilter::default()
|
|
1338
|
-
},
|
|
1339
|
-
..LiveStateScanRequest::default()
|
|
1340
|
-
})
|
|
1341
|
-
.expect("overlay scan should succeed");
|
|
1342
|
-
|
|
1343
|
-
assert_eq!(rows.len(), 4);
|
|
1344
|
-
assert_eq!(
|
|
1345
|
-
rows.iter()
|
|
1346
|
-
.filter(
|
|
1347
|
-
|row| row.entity_pk == crate::entity_pk::EntityPk::single("shared-entity")
|
|
1348
|
-
&& row.branch_id == "global"
|
|
1349
|
-
&& row.schema_key == "lix_key_value"
|
|
1350
|
-
&& row.file_id.is_none()
|
|
1351
|
-
)
|
|
1352
|
-
.count(),
|
|
1353
|
-
1
|
|
1354
|
-
);
|
|
1355
|
-
assert!(rows.iter().any(|row| {
|
|
1356
|
-
row.snapshot_content.as_deref()
|
|
1357
|
-
== Some("{\"key\":\"shared-entity\",\"value\":\"base\"}")
|
|
1358
|
-
}));
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
#[tokio::test]
|
|
1362
|
-
async fn staged_writes_use_injected_function_provider_for_commit_metadata() {
|
|
1363
|
-
let staged_writes = test_staged_writes();
|
|
1364
|
-
|
|
1365
|
-
staged_writes
|
|
1366
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1367
|
-
mode: TransactionWriteMode::Replace,
|
|
1368
|
-
rows: vec![state_row("sql2-functions-key", "value").with_tracked()],
|
|
1369
|
-
})
|
|
1370
|
-
.expect("staging rows should succeed");
|
|
1371
|
-
|
|
1372
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1373
|
-
let change_refs = drained
|
|
1374
|
-
.commit_change_refs_by_branch
|
|
1375
|
-
.get("global")
|
|
1376
|
-
.expect("global commit change_refs should exist");
|
|
1377
|
-
assert_eq!(change_refs.commit_id, "test-uuid-1");
|
|
1378
|
-
assert_eq!(change_refs.commit_change_id, "test-uuid-2");
|
|
1379
|
-
assert_eq!(change_refs.created_at, "test-timestamp-1");
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
#[tokio::test]
|
|
1383
|
-
async fn staged_writes_stamp_tracked_rows_with_commit_id_during_staging() {
|
|
1384
|
-
let staged_writes = test_staged_writes();
|
|
1385
|
-
|
|
1386
|
-
staged_writes
|
|
1387
|
-
.stage_write(PreparedTransactionWrite::Rows {
|
|
1388
|
-
mode: TransactionWriteMode::Replace,
|
|
1389
|
-
rows: vec![state_row("tracked-commit-key", "value").with_tracked()],
|
|
1390
|
-
})
|
|
1391
|
-
.expect("tracked row should stage");
|
|
1392
|
-
|
|
1393
|
-
let drained = staged_writes.drain().expect("drain should succeed");
|
|
1394
|
-
assert_eq!(drained.state_rows.len(), 1);
|
|
1395
|
-
assert_eq!(
|
|
1396
|
-
drained.state_rows[0].commit_id.as_deref(),
|
|
1397
|
-
Some("test-uuid-1")
|
|
1398
|
-
);
|
|
1399
|
-
assert_eq!(
|
|
1400
|
-
drained
|
|
1401
|
-
.commit_change_refs_by_branch
|
|
1402
|
-
.get("global")
|
|
1403
|
-
.expect("global commit change_refs should exist")
|
|
1404
|
-
.commit_id,
|
|
1405
|
-
"test-uuid-1"
|
|
1406
|
-
);
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
fn test_staged_writes() -> Arc<TransactionWriteBuffer> {
|
|
1410
|
-
Arc::new(TransactionWriteBuffer::new(SharedFunctionProvider::new(
|
|
1411
|
-
Box::new(TestFunctionProvider::default()) as Box<dyn FunctionProvider + Send>,
|
|
1412
|
-
)))
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
#[derive(Default)]
|
|
1416
|
-
struct TestFunctionProvider {
|
|
1417
|
-
uuid_count: usize,
|
|
1418
|
-
timestamp_count: usize,
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
impl FunctionProvider for TestFunctionProvider {
|
|
1422
|
-
fn uuid_v7(&mut self) -> String {
|
|
1423
|
-
self.uuid_count += 1;
|
|
1424
|
-
format!("test-uuid-{}", self.uuid_count)
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
fn timestamp(&mut self) -> String {
|
|
1428
|
-
self.timestamp_count += 1;
|
|
1429
|
-
format!("test-timestamp-{}", self.timestamp_count)
|
|
1430
|
-
}
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
fn state_row(key: &str, value: &str) -> PreparedStateRow {
|
|
1434
|
-
let snapshot = stage_json_from_value(
|
|
1435
|
-
TransactionJson::from_value_for_test(serde_json::json!({ "key": key, "value": value })),
|
|
1436
|
-
"test staged row snapshot_content",
|
|
1437
|
-
)
|
|
1438
|
-
.expect("test snapshot should prepare");
|
|
1439
|
-
PreparedStateRow {
|
|
1440
|
-
schema_plan_id: SchemaPlanId::for_test(0),
|
|
1441
|
-
facts: crate::transaction::types::PreparedRowFacts::default(),
|
|
1442
|
-
entity_pk: crate::entity_pk::EntityPk::single(key),
|
|
1443
|
-
schema_key: "lix_key_value".to_string(),
|
|
1444
|
-
file_id: None,
|
|
1445
|
-
snapshot: Some(snapshot),
|
|
1446
|
-
metadata: None,
|
|
1447
|
-
origin: None,
|
|
1448
|
-
created_at: "test-created-at".to_string(),
|
|
1449
|
-
updated_at: "test-updated-at".to_string(),
|
|
1450
|
-
global: true,
|
|
1451
|
-
change_id: None,
|
|
1452
|
-
commit_id: None,
|
|
1453
|
-
untracked: true,
|
|
1454
|
-
branch_id: "global".to_string(),
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
fn tombstone_row(key: &str) -> PreparedStateRow {
|
|
1459
|
-
let mut row = state_row(key, "deleted");
|
|
1460
|
-
row.snapshot = None;
|
|
1461
|
-
row
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
fn exact_request_for_key(key: &str) -> LiveStateRowRequest {
|
|
1465
|
-
LiveStateRowRequest {
|
|
1466
|
-
schema_key: "lix_key_value".to_string(),
|
|
1467
|
-
branch_id: "global".to_string(),
|
|
1468
|
-
entity_pk: crate::entity_pk::EntityPk::single(key),
|
|
1469
|
-
file_id: NullableKeyFilter::Null,
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
fn scan_request_for_key(key: &str, include_tombstones: bool) -> LiveStateScanRequest {
|
|
1474
|
-
LiveStateScanRequest {
|
|
1475
|
-
filter: LiveStateFilter {
|
|
1476
|
-
schema_keys: vec!["lix_key_value".to_string()],
|
|
1477
|
-
entity_pks: vec![crate::entity_pk::EntityPk::single(key)],
|
|
1478
|
-
branch_ids: vec!["global".to_string()],
|
|
1479
|
-
file_ids: vec![NullableKeyFilter::Null],
|
|
1480
|
-
include_tombstones,
|
|
1481
|
-
..LiveStateFilter::default()
|
|
1482
|
-
},
|
|
1483
|
-
..LiveStateScanRequest::default()
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
trait StateRowTestExt {
|
|
1488
|
-
fn with_schema(self, schema_key: &str) -> Self;
|
|
1489
|
-
fn with_file_id(self, file_id: &str) -> Self;
|
|
1490
|
-
fn with_tracked(self) -> Self;
|
|
1491
|
-
fn with_branch(self, branch_id: &str) -> Self;
|
|
1492
|
-
fn with_change_id(self, change_id: &str) -> Self;
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
impl StateRowTestExt for PreparedStateRow {
|
|
1496
|
-
fn with_schema(mut self, schema_key: &str) -> Self {
|
|
1497
|
-
self.schema_key = schema_key.to_string();
|
|
1498
|
-
self
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
fn with_file_id(mut self, file_id: &str) -> Self {
|
|
1502
|
-
self.file_id = Some(file_id.to_string());
|
|
1503
|
-
self
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
fn with_tracked(mut self) -> Self {
|
|
1507
|
-
self.untracked = false;
|
|
1508
|
-
if self.change_id.is_none() {
|
|
1509
|
-
self.change_id = Some("test-change-id".to_string());
|
|
1510
|
-
}
|
|
1511
|
-
self
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
fn with_branch(mut self, branch_id: &str) -> Self {
|
|
1515
|
-
self.branch_id = branch_id.to_string();
|
|
1516
|
-
self.global = branch_id == GLOBAL_BRANCH_ID;
|
|
1517
|
-
self
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
fn with_change_id(mut self, change_id: &str) -> Self {
|
|
1521
|
-
self.change_id = Some(change_id.to_string());
|
|
1522
|
-
self
|
|
1523
|
-
}
|
|
1524
|
-
}
|
|
1525
|
-
}
|