@lix-js/sdk 0.6.0-preview.1 → 0.6.0-preview.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +304 -320
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -0
- package/dist/engine-wasm/wasm/lix_engine.js +9 -13
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +1 -0
- package/dist/generated/builtin-schemas.d.ts +87 -162
- package/dist/generated/builtin-schemas.js +139 -236
- package/dist/open-lix.d.ts +103 -14
- package/dist/open-lix.js +3 -0
- package/dist/sqlite/index.js +99 -22
- package/dist-engine-src/README.md +18 -0
- package/dist-engine-src/src/backend/kv.rs +358 -0
- package/dist-engine-src/src/backend/mod.rs +12 -0
- package/dist-engine-src/src/backend/testing.rs +658 -0
- package/dist-engine-src/src/backend/types.rs +96 -0
- package/dist-engine-src/src/binary_cas/chunking.rs +31 -0
- package/dist-engine-src/src/binary_cas/codec.rs +346 -0
- package/dist-engine-src/src/binary_cas/context.rs +139 -0
- package/dist-engine-src/src/binary_cas/kv.rs +1063 -0
- package/dist-engine-src/src/binary_cas/mod.rs +11 -0
- package/dist-engine-src/src/binary_cas/types.rs +121 -0
- package/dist-engine-src/src/catalog/context.rs +412 -0
- package/dist-engine-src/src/catalog/mod.rs +10 -0
- package/dist-engine-src/src/catalog/schema.rs +4 -0
- package/dist-engine-src/src/catalog/snapshot.rs +1114 -0
- package/dist-engine-src/src/cel/context.rs +86 -0
- package/dist-engine-src/src/cel/error.rs +19 -0
- package/dist-engine-src/src/cel/mod.rs +8 -0
- package/dist-engine-src/src/cel/provider.rs +9 -0
- package/dist-engine-src/src/cel/runtime.rs +167 -0
- package/dist-engine-src/src/cel/value.rs +50 -0
- package/dist-engine-src/src/commit_graph/context.rs +901 -0
- package/dist-engine-src/src/commit_graph/mod.rs +11 -0
- package/dist-engine-src/src/commit_graph/types.rs +109 -0
- package/dist-engine-src/src/commit_graph/walker.rs +756 -0
- package/dist-engine-src/src/commit_store/codec.rs +887 -0
- package/dist-engine-src/src/commit_store/context.rs +944 -0
- package/dist-engine-src/src/commit_store/materialization.rs +84 -0
- package/dist-engine-src/src/commit_store/mod.rs +16 -0
- package/dist-engine-src/src/commit_store/storage.rs +600 -0
- package/dist-engine-src/src/commit_store/types.rs +215 -0
- package/dist-engine-src/src/common/error.rs +313 -0
- package/dist-engine-src/src/common/fingerprint.rs +3 -0
- package/dist-engine-src/src/common/fs_path.rs +1336 -0
- package/dist-engine-src/src/common/identity.rs +145 -0
- package/dist-engine-src/src/common/json_pointer.rs +67 -0
- package/dist-engine-src/src/common/metadata.rs +40 -0
- package/dist-engine-src/src/common/mod.rs +23 -0
- package/dist-engine-src/src/common/types.rs +105 -0
- package/dist-engine-src/src/common/wire.rs +222 -0
- package/dist-engine-src/src/domain.rs +324 -0
- package/dist-engine-src/src/engine.rs +225 -0
- package/dist-engine-src/src/entity_identity.rs +405 -0
- package/dist-engine-src/src/functions/context.rs +292 -0
- package/dist-engine-src/src/functions/deterministic.rs +113 -0
- package/dist-engine-src/src/functions/mod.rs +18 -0
- package/dist-engine-src/src/functions/provider.rs +130 -0
- package/dist-engine-src/src/functions/state.rs +336 -0
- package/dist-engine-src/src/functions/types.rs +37 -0
- package/dist-engine-src/src/init.rs +558 -0
- package/dist-engine-src/src/json_store/compression.rs +77 -0
- package/dist-engine-src/src/json_store/context.rs +423 -0
- package/dist-engine-src/src/json_store/encoded.rs +15 -0
- package/dist-engine-src/src/json_store/mod.rs +12 -0
- package/dist-engine-src/src/json_store/store.rs +1109 -0
- package/dist-engine-src/src/json_store/types.rs +217 -0
- package/dist-engine-src/src/lib.rs +62 -0
- package/dist-engine-src/src/live_state/context.rs +2019 -0
- package/dist-engine-src/src/live_state/mod.rs +15 -0
- package/dist-engine-src/src/live_state/overlay.rs +75 -0
- package/dist-engine-src/src/live_state/reader.rs +23 -0
- package/dist-engine-src/src/live_state/types.rs +222 -0
- package/dist-engine-src/src/live_state/visibility.rs +223 -0
- package/dist-engine-src/src/plugin/archive.rs +438 -0
- package/dist-engine-src/src/plugin/component.rs +183 -0
- package/dist-engine-src/src/plugin/install.rs +619 -0
- package/dist-engine-src/src/plugin/manifest.rs +516 -0
- package/dist-engine-src/src/plugin/materializer.rs +477 -0
- package/dist-engine-src/src/plugin/mod.rs +33 -0
- package/dist-engine-src/src/plugin/plugin_manifest.json +118 -0
- package/dist-engine-src/src/plugin/storage.rs +74 -0
- package/dist-engine-src/src/schema/annotations/defaults.rs +275 -0
- package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
- package/dist-engine-src/src/schema/builtin/lix_account.json +21 -0
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +29 -0
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +29 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +63 -0
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +45 -0
- package/dist-engine-src/src/schema/builtin/lix_commit.json +24 -0
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +52 -0
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +52 -0
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +40 -0
- package/dist-engine-src/src/schema/builtin/lix_label.json +29 -0
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +74 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +25 -0
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/mod.rs +222 -0
- package/dist-engine-src/src/schema/compatibility.rs +787 -0
- package/dist-engine-src/src/schema/definition.json +187 -0
- package/dist-engine-src/src/schema/definition.rs +742 -0
- package/dist-engine-src/src/schema/key.rs +138 -0
- package/dist-engine-src/src/schema/mod.rs +20 -0
- package/dist-engine-src/src/schema/seed.rs +14 -0
- package/dist-engine-src/src/schema/tests.rs +780 -0
- package/dist-engine-src/src/session/context.rs +364 -0
- package/dist-engine-src/src/session/create_version.rs +88 -0
- package/dist-engine-src/src/session/execute.rs +478 -0
- package/dist-engine-src/src/session/merge/analysis.rs +102 -0
- package/dist-engine-src/src/session/merge/apply.rs +23 -0
- package/dist-engine-src/src/session/merge/conflicts.rs +63 -0
- package/dist-engine-src/src/session/merge/mod.rs +11 -0
- package/dist-engine-src/src/session/merge/stats.rs +65 -0
- package/dist-engine-src/src/session/merge/version.rs +427 -0
- package/dist-engine-src/src/session/mod.rs +27 -0
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +100 -0
- package/dist-engine-src/src/session/switch_version.rs +109 -0
- package/dist-engine-src/src/sql2/change_provider.rs +331 -0
- package/dist-engine-src/src/sql2/classify.rs +182 -0
- package/dist-engine-src/src/sql2/context.rs +311 -0
- package/dist-engine-src/src/sql2/directory_history_provider.rs +631 -0
- package/dist-engine-src/src/sql2/directory_provider.rs +2453 -0
- package/dist-engine-src/src/sql2/dml.rs +148 -0
- package/dist-engine-src/src/sql2/entity_history_provider.rs +440 -0
- package/dist-engine-src/src/sql2/entity_provider.rs +3211 -0
- package/dist-engine-src/src/sql2/error.rs +216 -0
- package/dist-engine-src/src/sql2/execute.rs +3440 -0
- package/dist-engine-src/src/sql2/file_history_provider.rs +910 -0
- package/dist-engine-src/src/sql2/file_provider.rs +3679 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +1490 -0
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +383 -0
- package/dist-engine-src/src/sql2/history_projection.rs +56 -0
- package/dist-engine-src/src/sql2/history_provider.rs +412 -0
- package/dist-engine-src/src/sql2/history_route.rs +657 -0
- package/dist-engine-src/src/sql2/lix_state_provider.rs +2512 -0
- package/dist-engine-src/src/sql2/mod.rs +46 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +246 -0
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +46 -0
- package/dist-engine-src/src/sql2/public_bind/capability.rs +41 -0
- package/dist-engine-src/src/sql2/public_bind/dml.rs +166 -0
- package/dist-engine-src/src/sql2/public_bind/mod.rs +25 -0
- package/dist-engine-src/src/sql2/public_bind/table.rs +168 -0
- package/dist-engine-src/src/sql2/read_only.rs +63 -0
- package/dist-engine-src/src/sql2/record_batch.rs +17 -0
- package/dist-engine-src/src/sql2/result_metadata.rs +29 -0
- package/dist-engine-src/src/sql2/runtime.rs +60 -0
- package/dist-engine-src/src/sql2/session.rs +132 -0
- package/dist-engine-src/src/sql2/udfs/common.rs +295 -0
- package/dist-engine-src/src/sql2/udfs/lix_active_version_commit_id.rs +53 -0
- package/dist-engine-src/src/sql2/udfs/lix_empty_blob.rs +47 -0
- package/dist-engine-src/src/sql2/udfs/lix_json.rs +100 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_json_get_text.rs +99 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_decode.rs +82 -0
- package/dist-engine-src/src/sql2/udfs/lix_text_encode.rs +85 -0
- package/dist-engine-src/src/sql2/udfs/lix_timestamp.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/lix_uuid_v7.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +89 -0
- package/dist-engine-src/src/sql2/udfs/public_call.rs +211 -0
- package/dist-engine-src/src/sql2/version_provider.rs +1202 -0
- package/dist-engine-src/src/sql2/version_scope.rs +394 -0
- package/dist-engine-src/src/sql2/write_normalization.rs +345 -0
- package/dist-engine-src/src/storage/context.rs +356 -0
- package/dist-engine-src/src/storage/mod.rs +14 -0
- package/dist-engine-src/src/storage/read_scope.rs +88 -0
- package/dist-engine-src/src/storage/types.rs +501 -0
- package/dist-engine-src/src/storage_bench.rs +4863 -0
- package/dist-engine-src/src/test_support.rs +228 -0
- package/dist-engine-src/src/tracked_state/by_file_index.rs +98 -0
- package/dist-engine-src/src/tracked_state/codec.rs +2085 -0
- package/dist-engine-src/src/tracked_state/context.rs +1867 -0
- package/dist-engine-src/src/tracked_state/diff.rs +686 -0
- package/dist-engine-src/src/tracked_state/materialization.rs +403 -0
- package/dist-engine-src/src/tracked_state/materializer.rs +488 -0
- package/dist-engine-src/src/tracked_state/merge.rs +492 -0
- package/dist-engine-src/src/tracked_state/mod.rs +32 -0
- package/dist-engine-src/src/tracked_state/storage.rs +375 -0
- package/dist-engine-src/src/tracked_state/tree.rs +3187 -0
- package/dist-engine-src/src/tracked_state/types.rs +231 -0
- package/dist-engine-src/src/transaction/commit.rs +1484 -0
- package/dist-engine-src/src/transaction/context.rs +1548 -0
- package/dist-engine-src/src/transaction/live_state_overlay.rs +35 -0
- package/dist-engine-src/src/transaction/mod.rs +13 -0
- package/dist-engine-src/src/transaction/normalization.rs +890 -0
- package/dist-engine-src/src/transaction/prep.rs +37 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +149 -0
- package/dist-engine-src/src/transaction/staging.rs +1731 -0
- package/dist-engine-src/src/transaction/types.rs +460 -0
- package/dist-engine-src/src/transaction/validation.rs +5830 -0
- package/dist-engine-src/src/untracked_state/codec.rs +307 -0
- package/dist-engine-src/src/untracked_state/context.rs +98 -0
- package/dist-engine-src/src/untracked_state/materialization.rs +63 -0
- package/dist-engine-src/src/untracked_state/mod.rs +15 -0
- package/dist-engine-src/src/untracked_state/storage.rs +396 -0
- package/dist-engine-src/src/untracked_state/types.rs +146 -0
- package/dist-engine-src/src/version/context.rs +40 -0
- package/dist-engine-src/src/version/lifecycle.rs +221 -0
- package/dist-engine-src/src/version/mod.rs +13 -0
- package/dist-engine-src/src/version/refs.rs +330 -0
- package/dist-engine-src/src/version/stage_rows.rs +67 -0
- package/dist-engine-src/src/version/types.rs +21 -0
- package/dist-engine-src/src/wasm/mod.rs +60 -0
- package/package.json +68 -64
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
use crate::tracked_state::{
|
|
2
|
+
TrackedStateDiff, TrackedStateDiffKind, TrackedStateMergePatch, TrackedStateMergePlan,
|
|
3
|
+
};
|
|
4
|
+
use crate::LixError;
|
|
5
|
+
|
|
6
|
+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
7
|
+
pub(crate) struct MergeStats {
|
|
8
|
+
pub(crate) total: usize,
|
|
9
|
+
pub(crate) added: usize,
|
|
10
|
+
pub(crate) modified: usize,
|
|
11
|
+
pub(crate) removed: usize,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
pub(crate) fn stats_from_diff(diff: &TrackedStateDiff) -> MergeStats {
|
|
15
|
+
let mut stats = MergeStats::default();
|
|
16
|
+
for entry in &diff.entries {
|
|
17
|
+
stats.add(entry.kind);
|
|
18
|
+
}
|
|
19
|
+
stats
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub(crate) fn stats_from_plan(
|
|
23
|
+
plan: &TrackedStateMergePlan,
|
|
24
|
+
source_diff: &TrackedStateDiff,
|
|
25
|
+
) -> Result<MergeStats, LixError> {
|
|
26
|
+
let mut stats = MergeStats::default();
|
|
27
|
+
for patch in &plan.patches {
|
|
28
|
+
let identity = patch_identity(patch);
|
|
29
|
+
let Some(entry) = source_diff
|
|
30
|
+
.entries
|
|
31
|
+
.iter()
|
|
32
|
+
.find(|entry| &entry.identity == identity)
|
|
33
|
+
else {
|
|
34
|
+
return Err(LixError::new(
|
|
35
|
+
"LIX_ERROR_UNKNOWN",
|
|
36
|
+
format!(
|
|
37
|
+
"merge analysis could not find source diff entry for adopted schema '{}' entity '{}'",
|
|
38
|
+
identity.schema_key,
|
|
39
|
+
identity.entity_id.as_json_array_text()?
|
|
40
|
+
),
|
|
41
|
+
));
|
|
42
|
+
};
|
|
43
|
+
stats.add(entry.kind);
|
|
44
|
+
}
|
|
45
|
+
Ok(stats)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl MergeStats {
|
|
49
|
+
fn add(&mut self, kind: TrackedStateDiffKind) {
|
|
50
|
+
self.total += 1;
|
|
51
|
+
match kind {
|
|
52
|
+
TrackedStateDiffKind::Added => self.added += 1,
|
|
53
|
+
TrackedStateDiffKind::Modified => self.modified += 1,
|
|
54
|
+
TrackedStateDiffKind::Removed => self.removed += 1,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
fn patch_identity(
|
|
60
|
+
patch: &TrackedStateMergePatch,
|
|
61
|
+
) -> &crate::tracked_state::TrackedStateDiffIdentity {
|
|
62
|
+
match patch {
|
|
63
|
+
TrackedStateMergePatch::Adopt { identity, .. } => identity,
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
use serde_json::{json, Value as JsonValue};
|
|
2
|
+
|
|
3
|
+
use crate::transaction::types::TransactionWrite;
|
|
4
|
+
use crate::version::{VersionLifecycle, VersionOperation, VersionReferenceRole};
|
|
5
|
+
use crate::LixError;
|
|
6
|
+
|
|
7
|
+
use super::analysis::{analyze, MergeCommits, MergeOutcome};
|
|
8
|
+
use super::apply::adopted_changes_from_merge_plan;
|
|
9
|
+
use super::conflicts::{
|
|
10
|
+
MergeConflict as AnalysisMergeConflict,
|
|
11
|
+
MergeConflictChangeKind as AnalysisMergeConflictChangeKind,
|
|
12
|
+
MergeConflictKind as AnalysisMergeConflictKind, MergeConflictSide as AnalysisMergeConflictSide,
|
|
13
|
+
};
|
|
14
|
+
use super::stats::MergeStats;
|
|
15
|
+
use crate::session::context::SessionContext;
|
|
16
|
+
|
|
17
|
+
/// Options for merging another version into this session's active version.
|
|
18
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
19
|
+
pub struct MergeVersionOptions {
|
|
20
|
+
/// Version whose changes should be merged into the active session version.
|
|
21
|
+
pub source_version_id: String,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// Options for previewing a merge from another version into this session's
|
|
25
|
+
/// active version.
|
|
26
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
27
|
+
pub struct MergeVersionPreviewOptions {
|
|
28
|
+
/// Version whose changes would be merged into the active session version.
|
|
29
|
+
pub source_version_id: String,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/// Receipt returned after merging a version.
|
|
33
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
34
|
+
pub struct MergeVersionReceipt {
|
|
35
|
+
pub outcome: MergeVersionOutcome,
|
|
36
|
+
pub target_version_id: String,
|
|
37
|
+
pub source_version_id: String,
|
|
38
|
+
pub base_commit_id: String,
|
|
39
|
+
pub target_head_before_commit_id: String,
|
|
40
|
+
pub source_head_before_commit_id: String,
|
|
41
|
+
pub target_head_after_commit_id: String,
|
|
42
|
+
pub created_merge_commit_id: Option<String>,
|
|
43
|
+
pub change_stats: MergeChangeStats,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
47
|
+
pub struct MergeChangeStats {
|
|
48
|
+
pub total: usize,
|
|
49
|
+
pub added: usize,
|
|
50
|
+
pub modified: usize,
|
|
51
|
+
pub removed: usize,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
55
|
+
pub struct MergeVersionPreview {
|
|
56
|
+
pub outcome: MergeVersionOutcome,
|
|
57
|
+
pub target_version_id: String,
|
|
58
|
+
pub source_version_id: String,
|
|
59
|
+
pub base_commit_id: String,
|
|
60
|
+
pub target_head_commit_id: String,
|
|
61
|
+
pub source_head_commit_id: String,
|
|
62
|
+
pub change_stats: MergeChangeStats,
|
|
63
|
+
pub conflicts: Vec<MergeConflict>,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
67
|
+
pub struct MergeConflict {
|
|
68
|
+
pub kind: MergeConflictKind,
|
|
69
|
+
pub schema_key: String,
|
|
70
|
+
pub entity_id: JsonValue,
|
|
71
|
+
pub file_id: Option<String>,
|
|
72
|
+
pub target: MergeConflictSide,
|
|
73
|
+
pub source: MergeConflictSide,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
77
|
+
pub enum MergeConflictKind {
|
|
78
|
+
SameEntityChanged,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
82
|
+
pub struct MergeConflictSide {
|
|
83
|
+
pub kind: MergeConflictChangeKind,
|
|
84
|
+
pub before_change_id: Option<String>,
|
|
85
|
+
pub after_change_id: Option<String>,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
89
|
+
pub enum MergeConflictChangeKind {
|
|
90
|
+
Added,
|
|
91
|
+
Modified,
|
|
92
|
+
Removed,
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
96
|
+
pub enum MergeVersionOutcome {
|
|
97
|
+
AlreadyUpToDate,
|
|
98
|
+
FastForward,
|
|
99
|
+
MergeCommitted,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
impl SessionContext {
|
|
103
|
+
/// Previews merging `source_version_id` into this session's active version
|
|
104
|
+
/// without advancing refs, staging changes, or creating commits.
|
|
105
|
+
pub async fn merge_version_preview(
|
|
106
|
+
&self,
|
|
107
|
+
options: MergeVersionPreviewOptions,
|
|
108
|
+
) -> Result<MergeVersionPreview, LixError> {
|
|
109
|
+
let source_version_id = options.source_version_id;
|
|
110
|
+
|
|
111
|
+
self.with_write_transaction(|transaction| {
|
|
112
|
+
Box::pin(async move {
|
|
113
|
+
let active_version_id = transaction.active_version_id().to_string();
|
|
114
|
+
if source_version_id == active_version_id {
|
|
115
|
+
return Err(LixError::invalid_self_merge(active_version_id));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let (target_head, source_head) = {
|
|
119
|
+
let reader = transaction.version_ref_reader();
|
|
120
|
+
let lifecycle = VersionLifecycle::new(&reader);
|
|
121
|
+
let target_head = lifecycle
|
|
122
|
+
.require_existing_commit_id(
|
|
123
|
+
&active_version_id,
|
|
124
|
+
VersionOperation::MergeVersionPreview,
|
|
125
|
+
VersionReferenceRole::Target,
|
|
126
|
+
)
|
|
127
|
+
.await?;
|
|
128
|
+
let source_head = lifecycle
|
|
129
|
+
.require_existing_commit_id(
|
|
130
|
+
&source_version_id,
|
|
131
|
+
VersionOperation::MergeVersionPreview,
|
|
132
|
+
VersionReferenceRole::Source,
|
|
133
|
+
)
|
|
134
|
+
.await?;
|
|
135
|
+
(target_head, source_head)
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
let merge_base = {
|
|
139
|
+
let mut reader = transaction.commit_graph_reader();
|
|
140
|
+
reader.merge_base(&target_head, &source_head).await?
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
let analysis = {
|
|
144
|
+
let mut reader = transaction.tracked_state_reader();
|
|
145
|
+
analyze(
|
|
146
|
+
&mut reader,
|
|
147
|
+
MergeCommits {
|
|
148
|
+
base_commit_id: merge_base.commit_id,
|
|
149
|
+
target_commit_id: target_head,
|
|
150
|
+
source_commit_id: source_head,
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
.await?
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
Ok(preview_from_analysis(
|
|
157
|
+
&active_version_id,
|
|
158
|
+
&source_version_id,
|
|
159
|
+
&analysis,
|
|
160
|
+
))
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
.await
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Merges `source_version_id` into this session's active version.
|
|
167
|
+
///
|
|
168
|
+
/// The generated target commit keeps the previous target head as its first
|
|
169
|
+
/// parent and records the source head as an additional parent, so the
|
|
170
|
+
/// commit graph preserves branch ancestry while tracked-state storage can
|
|
171
|
+
/// build the new root by applying source effects onto the target root.
|
|
172
|
+
pub async fn merge_version(
|
|
173
|
+
&self,
|
|
174
|
+
options: MergeVersionOptions,
|
|
175
|
+
) -> Result<MergeVersionReceipt, LixError> {
|
|
176
|
+
let source_version_id = options.source_version_id;
|
|
177
|
+
|
|
178
|
+
self.with_write_transaction(|transaction| {
|
|
179
|
+
Box::pin(async move {
|
|
180
|
+
let active_version_id = transaction.active_version_id().to_string();
|
|
181
|
+
if source_version_id == active_version_id {
|
|
182
|
+
return Err(LixError::invalid_self_merge(active_version_id));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
let (target_head, source_head) = {
|
|
186
|
+
let reader = transaction.version_ref_reader();
|
|
187
|
+
let lifecycle = VersionLifecycle::new(&reader);
|
|
188
|
+
let target_head = lifecycle
|
|
189
|
+
.require_existing_commit_id(
|
|
190
|
+
&active_version_id,
|
|
191
|
+
VersionOperation::MergeVersion,
|
|
192
|
+
VersionReferenceRole::Target,
|
|
193
|
+
)
|
|
194
|
+
.await?;
|
|
195
|
+
let source_head = lifecycle
|
|
196
|
+
.require_existing_commit_id(
|
|
197
|
+
&source_version_id,
|
|
198
|
+
VersionOperation::MergeVersion,
|
|
199
|
+
VersionReferenceRole::Source,
|
|
200
|
+
)
|
|
201
|
+
.await?;
|
|
202
|
+
(target_head, source_head)
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
let merge_base = {
|
|
206
|
+
let mut reader = transaction.commit_graph_reader();
|
|
207
|
+
reader.merge_base(&target_head, &source_head).await?
|
|
208
|
+
};
|
|
209
|
+
let base_commit_id = merge_base.commit_id;
|
|
210
|
+
|
|
211
|
+
let analysis = {
|
|
212
|
+
let mut reader = transaction.tracked_state_reader();
|
|
213
|
+
analyze(
|
|
214
|
+
&mut reader,
|
|
215
|
+
MergeCommits {
|
|
216
|
+
base_commit_id,
|
|
217
|
+
target_commit_id: target_head,
|
|
218
|
+
source_commit_id: source_head,
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
.await?
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if analysis.outcome == MergeOutcome::AlreadyUpToDate {
|
|
225
|
+
return Ok(MergeVersionReceipt {
|
|
226
|
+
outcome: MergeVersionOutcome::AlreadyUpToDate,
|
|
227
|
+
target_version_id: active_version_id,
|
|
228
|
+
source_version_id,
|
|
229
|
+
base_commit_id: analysis.commits.base_commit_id,
|
|
230
|
+
target_head_after_commit_id: analysis.commits.target_commit_id.clone(),
|
|
231
|
+
target_head_before_commit_id: analysis.commits.target_commit_id,
|
|
232
|
+
source_head_before_commit_id: analysis.commits.source_commit_id,
|
|
233
|
+
created_merge_commit_id: None,
|
|
234
|
+
change_stats: merge_change_stats_from_analysis(&analysis.stats),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if analysis.outcome == MergeOutcome::FastForward {
|
|
239
|
+
transaction
|
|
240
|
+
.advance_version_ref(&active_version_id, &analysis.commits.source_commit_id)
|
|
241
|
+
.await?;
|
|
242
|
+
|
|
243
|
+
return Ok(MergeVersionReceipt {
|
|
244
|
+
outcome: MergeVersionOutcome::FastForward,
|
|
245
|
+
target_version_id: active_version_id,
|
|
246
|
+
source_version_id,
|
|
247
|
+
base_commit_id: analysis.commits.base_commit_id,
|
|
248
|
+
target_head_before_commit_id: analysis.commits.target_commit_id,
|
|
249
|
+
source_head_before_commit_id: analysis.commits.source_commit_id.clone(),
|
|
250
|
+
target_head_after_commit_id: analysis.commits.source_commit_id,
|
|
251
|
+
created_merge_commit_id: None,
|
|
252
|
+
change_stats: merge_change_stats_from_analysis(&analysis.stats),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
let merge_plan = analysis
|
|
257
|
+
.merge_plan()
|
|
258
|
+
.expect("merge analysis should include a plan for mergeCommitted");
|
|
259
|
+
|
|
260
|
+
if !analysis.conflicts.is_empty() {
|
|
261
|
+
return Err(merge_conflict_error(
|
|
262
|
+
&analysis
|
|
263
|
+
.conflicts
|
|
264
|
+
.iter()
|
|
265
|
+
.map(merge_conflict_from_analysis)
|
|
266
|
+
.collect::<Vec<_>>(),
|
|
267
|
+
)?);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let adopted_changes =
|
|
271
|
+
adopted_changes_from_merge_plan(merge_plan, &active_version_id);
|
|
272
|
+
if adopted_changes.is_empty() {
|
|
273
|
+
let created_merge_commit_id =
|
|
274
|
+
transaction.stage_empty_commit(active_version_id.clone())?;
|
|
275
|
+
transaction.add_commit_parent(
|
|
276
|
+
active_version_id.clone(),
|
|
277
|
+
analysis.commits.source_commit_id.clone(),
|
|
278
|
+
)?;
|
|
279
|
+
return Ok(MergeVersionReceipt {
|
|
280
|
+
outcome: MergeVersionOutcome::MergeCommitted,
|
|
281
|
+
target_version_id: active_version_id,
|
|
282
|
+
source_version_id,
|
|
283
|
+
base_commit_id: analysis.commits.base_commit_id,
|
|
284
|
+
target_head_after_commit_id: created_merge_commit_id.clone(),
|
|
285
|
+
target_head_before_commit_id: analysis.commits.target_commit_id,
|
|
286
|
+
source_head_before_commit_id: analysis.commits.source_commit_id,
|
|
287
|
+
created_merge_commit_id: Some(created_merge_commit_id),
|
|
288
|
+
change_stats: merge_change_stats_from_analysis(&analysis.stats),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
transaction
|
|
293
|
+
.stage_write(TransactionWrite::AdoptedChanges {
|
|
294
|
+
changes: adopted_changes,
|
|
295
|
+
})
|
|
296
|
+
.await?;
|
|
297
|
+
let created_merge_commit_id = transaction
|
|
298
|
+
.staged_commit_id(&active_version_id)?
|
|
299
|
+
.ok_or_else(|| {
|
|
300
|
+
LixError::new(
|
|
301
|
+
"LIX_ERROR_UNKNOWN",
|
|
302
|
+
"merge_version staged tracked rows without a commit id",
|
|
303
|
+
)
|
|
304
|
+
})?;
|
|
305
|
+
transaction.add_commit_parent(
|
|
306
|
+
active_version_id.clone(),
|
|
307
|
+
analysis.commits.source_commit_id.clone(),
|
|
308
|
+
)?;
|
|
309
|
+
|
|
310
|
+
Ok(MergeVersionReceipt {
|
|
311
|
+
outcome: MergeVersionOutcome::MergeCommitted,
|
|
312
|
+
target_version_id: active_version_id,
|
|
313
|
+
source_version_id,
|
|
314
|
+
base_commit_id: analysis.commits.base_commit_id,
|
|
315
|
+
target_head_before_commit_id: analysis.commits.target_commit_id,
|
|
316
|
+
source_head_before_commit_id: analysis.commits.source_commit_id,
|
|
317
|
+
created_merge_commit_id: Some(created_merge_commit_id.clone()),
|
|
318
|
+
target_head_after_commit_id: created_merge_commit_id,
|
|
319
|
+
change_stats: merge_change_stats_from_analysis(&analysis.stats),
|
|
320
|
+
})
|
|
321
|
+
})
|
|
322
|
+
})
|
|
323
|
+
.await
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
fn preview_from_analysis(
|
|
328
|
+
target_version_id: &str,
|
|
329
|
+
source_version_id: &str,
|
|
330
|
+
analysis: &super::analysis::MergeAnalysis,
|
|
331
|
+
) -> MergeVersionPreview {
|
|
332
|
+
MergeVersionPreview {
|
|
333
|
+
outcome: merge_version_outcome_from_analysis(analysis.outcome),
|
|
334
|
+
target_version_id: target_version_id.to_string(),
|
|
335
|
+
source_version_id: source_version_id.to_string(),
|
|
336
|
+
base_commit_id: analysis.commits.base_commit_id.clone(),
|
|
337
|
+
target_head_commit_id: analysis.commits.target_commit_id.clone(),
|
|
338
|
+
source_head_commit_id: analysis.commits.source_commit_id.clone(),
|
|
339
|
+
change_stats: merge_change_stats_from_analysis(&analysis.stats),
|
|
340
|
+
conflicts: analysis
|
|
341
|
+
.conflicts
|
|
342
|
+
.iter()
|
|
343
|
+
.map(merge_conflict_from_analysis)
|
|
344
|
+
.collect(),
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
fn merge_version_outcome_from_analysis(outcome: MergeOutcome) -> MergeVersionOutcome {
|
|
349
|
+
match outcome {
|
|
350
|
+
MergeOutcome::AlreadyUpToDate => MergeVersionOutcome::AlreadyUpToDate,
|
|
351
|
+
MergeOutcome::FastForward => MergeVersionOutcome::FastForward,
|
|
352
|
+
MergeOutcome::MergeCommitted => MergeVersionOutcome::MergeCommitted,
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
fn merge_change_stats_from_analysis(stats: &MergeStats) -> MergeChangeStats {
|
|
357
|
+
MergeChangeStats {
|
|
358
|
+
total: stats.total,
|
|
359
|
+
added: stats.added,
|
|
360
|
+
modified: stats.modified,
|
|
361
|
+
removed: stats.removed,
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
fn merge_conflict_from_analysis(conflict: &AnalysisMergeConflict) -> MergeConflict {
|
|
366
|
+
MergeConflict {
|
|
367
|
+
kind: match conflict.kind {
|
|
368
|
+
AnalysisMergeConflictKind::SameEntityChanged => MergeConflictKind::SameEntityChanged,
|
|
369
|
+
},
|
|
370
|
+
schema_key: conflict.schema_key.clone(),
|
|
371
|
+
entity_id: conflict.entity_id.clone(),
|
|
372
|
+
file_id: conflict.file_id.clone(),
|
|
373
|
+
target: merge_conflict_side_from_analysis(&conflict.target),
|
|
374
|
+
source: merge_conflict_side_from_analysis(&conflict.source),
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
fn merge_conflict_side_from_analysis(side: &AnalysisMergeConflictSide) -> MergeConflictSide {
|
|
379
|
+
MergeConflictSide {
|
|
380
|
+
kind: match side.kind {
|
|
381
|
+
AnalysisMergeConflictChangeKind::Added => MergeConflictChangeKind::Added,
|
|
382
|
+
AnalysisMergeConflictChangeKind::Modified => MergeConflictChangeKind::Modified,
|
|
383
|
+
AnalysisMergeConflictChangeKind::Removed => MergeConflictChangeKind::Removed,
|
|
384
|
+
},
|
|
385
|
+
before_change_id: side.before_change_id.clone(),
|
|
386
|
+
after_change_id: side.after_change_id.clone(),
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
fn merge_conflict_error(conflicts: &[MergeConflict]) -> Result<LixError, LixError> {
|
|
391
|
+
let conflict_count = conflicts.len();
|
|
392
|
+
Ok(LixError::new(
|
|
393
|
+
LixError::CODE_MERGE_CONFLICT,
|
|
394
|
+
format!("merge_version found {conflict_count} tracked-state conflict(s)"),
|
|
395
|
+
)
|
|
396
|
+
.with_hint("Resolve the conflicting entities in the target version, then retry the merge.")
|
|
397
|
+
.with_details(json!({
|
|
398
|
+
"conflicts": conflicts.iter()
|
|
399
|
+
.map(merge_conflict_details)
|
|
400
|
+
.collect::<Vec<_>>(),
|
|
401
|
+
})))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
fn merge_conflict_details(conflict: &MergeConflict) -> serde_json::Value {
|
|
405
|
+
json!({
|
|
406
|
+
"kind": match conflict.kind {
|
|
407
|
+
MergeConflictKind::SameEntityChanged => "sameEntityChanged",
|
|
408
|
+
},
|
|
409
|
+
"schemaKey": conflict.schema_key,
|
|
410
|
+
"entityId": conflict.entity_id,
|
|
411
|
+
"fileId": conflict.file_id,
|
|
412
|
+
"target": merge_conflict_side_details(&conflict.target),
|
|
413
|
+
"source": merge_conflict_side_details(&conflict.source),
|
|
414
|
+
})
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
fn merge_conflict_side_details(side: &MergeConflictSide) -> serde_json::Value {
|
|
418
|
+
json!({
|
|
419
|
+
"kind": match side.kind {
|
|
420
|
+
MergeConflictChangeKind::Added => "added",
|
|
421
|
+
MergeConflictChangeKind::Modified => "modified",
|
|
422
|
+
MergeConflictChangeKind::Removed => "removed",
|
|
423
|
+
},
|
|
424
|
+
"beforeChangeId": side.before_change_id,
|
|
425
|
+
"afterChangeId": side.after_change_id,
|
|
426
|
+
})
|
|
427
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//! Engine session boundary.
|
|
2
|
+
//!
|
|
3
|
+
//! Transaction invariant:
|
|
4
|
+
//! any engine operation that may write must enter through
|
|
5
|
+
//! `SessionContext::with_write_transaction`. Reads that influence writes are
|
|
6
|
+
//! only available from the transaction capability. Session APIs must not
|
|
7
|
+
//! open `Transaction` directly or use session-level read helpers inside write
|
|
8
|
+
//! flows.
|
|
9
|
+
|
|
10
|
+
mod context;
|
|
11
|
+
mod create_version;
|
|
12
|
+
mod execute;
|
|
13
|
+
mod merge;
|
|
14
|
+
#[cfg(feature = "storage-benches")]
|
|
15
|
+
pub mod optimization9_sql2_bench;
|
|
16
|
+
mod switch_version;
|
|
17
|
+
|
|
18
|
+
pub use context::SessionContext;
|
|
19
|
+
pub(crate) use context::{SessionMode, WORKSPACE_VERSION_KEY};
|
|
20
|
+
pub use create_version::{CreateVersionOptions, CreateVersionReceipt};
|
|
21
|
+
pub use execute::{ExecuteResult, Row, RowRef, TryFromValue};
|
|
22
|
+
pub use merge::{
|
|
23
|
+
MergeChangeStats, MergeConflict, MergeConflictChangeKind, MergeConflictKind, MergeConflictSide,
|
|
24
|
+
MergeVersionOptions, MergeVersionOutcome, MergeVersionPreview, MergeVersionPreviewOptions,
|
|
25
|
+
MergeVersionReceipt,
|
|
26
|
+
};
|
|
27
|
+
pub use switch_version::{SwitchVersionOptions, SwitchVersionReceipt};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
use crate::functions::FunctionContext;
|
|
2
|
+
use crate::session::context::{SessionContext, SessionSqlExecutionContext};
|
|
3
|
+
use crate::sql2::{self, SqlLogicalPlan};
|
|
4
|
+
use crate::storage::StorageReadScope;
|
|
5
|
+
use crate::transaction::open_transaction;
|
|
6
|
+
use crate::{LixError, SqlQueryResult, Value};
|
|
7
|
+
|
|
8
|
+
/// Opaque read plan used by the Optimization 9 SQL2 diagnostic benchmark.
|
|
9
|
+
///
|
|
10
|
+
/// This module is gated behind `storage-benches` and exists only to split SQL2
|
|
11
|
+
/// planning cost from SQL2 execution cost without widening the normal session
|
|
12
|
+
/// API.
|
|
13
|
+
pub struct PreparedReadPlan {
|
|
14
|
+
plan: SqlLogicalPlan,
|
|
15
|
+
read_scope:
|
|
16
|
+
StorageReadScope<Box<dyn crate::storage::StorageReadTransaction + Send + Sync + 'static>>,
|
|
17
|
+
runtime_functions: FunctionContext,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
pub async fn plan_read_only(session: &SessionContext, sql: &str) -> Result<(), LixError> {
|
|
21
|
+
let prepared = prepare_read_plan(session, sql).await?;
|
|
22
|
+
drop(prepared.plan);
|
|
23
|
+
drop(prepared.runtime_functions);
|
|
24
|
+
prepared.read_scope.rollback().await
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
pub async fn plan_write_only(session: &SessionContext, sql: &str) -> Result<(), LixError> {
|
|
28
|
+
session.ensure_open()?;
|
|
29
|
+
let opened = open_transaction(
|
|
30
|
+
&session.mode,
|
|
31
|
+
session.storage.clone(),
|
|
32
|
+
std::sync::Arc::clone(&session.live_state),
|
|
33
|
+
std::sync::Arc::clone(&session.tracked_state),
|
|
34
|
+
std::sync::Arc::clone(&session.binary_cas),
|
|
35
|
+
std::sync::Arc::clone(&session.commit_store),
|
|
36
|
+
std::sync::Arc::clone(&session.version_ctx),
|
|
37
|
+
std::sync::Arc::clone(&session.catalog_context),
|
|
38
|
+
)
|
|
39
|
+
.await?;
|
|
40
|
+
let mut transaction = opened.transaction;
|
|
41
|
+
let runtime_functions = opened.runtime_functions;
|
|
42
|
+
let plan = sql2::create_write_logical_plan(&mut transaction, sql).await?;
|
|
43
|
+
drop(plan);
|
|
44
|
+
drop(runtime_functions);
|
|
45
|
+
transaction.rollback().await
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
pub async fn prepare_read_plan(
|
|
49
|
+
session: &SessionContext,
|
|
50
|
+
sql: &str,
|
|
51
|
+
) -> Result<PreparedReadPlan, LixError> {
|
|
52
|
+
session.ensure_open()?;
|
|
53
|
+
let read_scope = StorageReadScope::new(session.storage.begin_read_transaction().await?);
|
|
54
|
+
let mut read_store = read_scope.store();
|
|
55
|
+
let live_state: std::sync::Arc<dyn crate::live_state::LiveStateReader> =
|
|
56
|
+
std::sync::Arc::new(session.live_state.reader(read_store.clone()));
|
|
57
|
+
let runtime_functions = FunctionContext::prepare(live_state.as_ref()).await?;
|
|
58
|
+
let functions = runtime_functions.provider();
|
|
59
|
+
let active_version_id = session
|
|
60
|
+
.active_version_id_from_reader(&mut read_store)
|
|
61
|
+
.await?;
|
|
62
|
+
let visible_schemas = session
|
|
63
|
+
.catalog_context
|
|
64
|
+
.schema_jsons_for_sql_read_planning(live_state.as_ref(), &active_version_id)
|
|
65
|
+
.await?;
|
|
66
|
+
let ctx = SessionSqlExecutionContext {
|
|
67
|
+
active_version_id: &active_version_id,
|
|
68
|
+
read_store,
|
|
69
|
+
live_state: std::sync::Arc::clone(&session.live_state),
|
|
70
|
+
binary_cas: std::sync::Arc::clone(&session.binary_cas),
|
|
71
|
+
commit_store: std::sync::Arc::clone(&session.commit_store),
|
|
72
|
+
version_ctx: std::sync::Arc::clone(&session.version_ctx),
|
|
73
|
+
visible_schemas,
|
|
74
|
+
functions: functions.clone(),
|
|
75
|
+
};
|
|
76
|
+
let plan = sql2::create_logical_plan(&ctx, sql).await?;
|
|
77
|
+
drop(ctx);
|
|
78
|
+
drop(live_state);
|
|
79
|
+
|
|
80
|
+
Ok(PreparedReadPlan {
|
|
81
|
+
plan,
|
|
82
|
+
read_scope,
|
|
83
|
+
runtime_functions,
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
pub async fn execute_read_plan(
|
|
88
|
+
prepared: PreparedReadPlan,
|
|
89
|
+
params: &[Value],
|
|
90
|
+
) -> Result<SqlQueryResult, LixError> {
|
|
91
|
+
let PreparedReadPlan {
|
|
92
|
+
plan,
|
|
93
|
+
read_scope,
|
|
94
|
+
runtime_functions,
|
|
95
|
+
} = prepared;
|
|
96
|
+
let result = sql2::execute_logical_plan(plan, params).await;
|
|
97
|
+
read_scope.rollback().await?;
|
|
98
|
+
drop(runtime_functions);
|
|
99
|
+
result
|
|
100
|
+
}
|