@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,478 @@
|
|
|
1
|
+
use std::sync::Arc;
|
|
2
|
+
|
|
3
|
+
use crate::functions::FunctionContext;
|
|
4
|
+
use crate::sql2;
|
|
5
|
+
use crate::storage::{StorageReadScope, StorageWriteSet};
|
|
6
|
+
use crate::{LixError, LixNotice, SqlQueryResult, Value};
|
|
7
|
+
|
|
8
|
+
use super::context::{SessionContext, SessionSqlExecutionContext};
|
|
9
|
+
|
|
10
|
+
/// Result of executing one SQL statement through engine.
|
|
11
|
+
///
|
|
12
|
+
/// Column names live once at the result-set level. Individual rows only own
|
|
13
|
+
/// values, which keeps the public API row-oriented without copying schema
|
|
14
|
+
/// metadata into every row.
|
|
15
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
16
|
+
pub struct ExecuteResult {
|
|
17
|
+
columns: Vec<String>,
|
|
18
|
+
rows: Vec<Row>,
|
|
19
|
+
rows_affected: u64,
|
|
20
|
+
notices: Vec<LixNotice>,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl ExecuteResult {
|
|
24
|
+
fn from_sql_query_result(result: SqlQueryResult) -> Self {
|
|
25
|
+
Self {
|
|
26
|
+
columns: result.columns,
|
|
27
|
+
rows: Vec::new(),
|
|
28
|
+
rows_affected: 0,
|
|
29
|
+
notices: result.notices,
|
|
30
|
+
}
|
|
31
|
+
.with_rows(result.rows)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
pub fn from_rows_affected(rows_affected: u64) -> Self {
|
|
35
|
+
Self {
|
|
36
|
+
columns: Vec::new(),
|
|
37
|
+
rows: Vec::new(),
|
|
38
|
+
rows_affected,
|
|
39
|
+
notices: Vec::new(),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub fn from_rows(columns: Vec<String>, rows: Vec<Vec<Value>>) -> Self {
|
|
44
|
+
Self {
|
|
45
|
+
columns,
|
|
46
|
+
rows: Vec::new(),
|
|
47
|
+
rows_affected: 0,
|
|
48
|
+
notices: Vec::new(),
|
|
49
|
+
}
|
|
50
|
+
.with_rows(rows)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn with_rows(mut self, rows: Vec<Vec<Value>>) -> Self {
|
|
54
|
+
let columns = Arc::<[String]>::from(self.columns.clone().into_boxed_slice());
|
|
55
|
+
self.rows = rows
|
|
56
|
+
.into_iter()
|
|
57
|
+
.map(|values| Row {
|
|
58
|
+
columns: Arc::clone(&columns),
|
|
59
|
+
values,
|
|
60
|
+
})
|
|
61
|
+
.collect();
|
|
62
|
+
self
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Returns the result-set column names in row value order.
|
|
66
|
+
pub fn columns(&self) -> &[String] {
|
|
67
|
+
&self.columns
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Returns the owned rows. Use `iter()` for name-based access.
|
|
71
|
+
pub fn rows(&self) -> &[Row] {
|
|
72
|
+
&self.rows
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// Iterates rows with borrowed access to the shared column metadata.
|
|
76
|
+
pub fn iter(&self) -> impl Iterator<Item = RowRef<'_>> {
|
|
77
|
+
self.rows.iter().map(|row| RowRef {
|
|
78
|
+
columns: self.columns.as_slice(),
|
|
79
|
+
values: row.values.as_slice(),
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// Returns the number of rows in this result set.
|
|
84
|
+
pub fn len(&self) -> usize {
|
|
85
|
+
self.rows.len()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Returns true when this result set has no rows.
|
|
89
|
+
pub fn is_empty(&self) -> bool {
|
|
90
|
+
self.rows.is_empty()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Returns the number of rows affected by a mutation statement.
|
|
94
|
+
pub fn rows_affected(&self) -> u64 {
|
|
95
|
+
self.rows_affected
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Returns non-fatal diagnostics produced while executing the statement.
|
|
99
|
+
pub fn notices(&self) -> &[LixNotice] {
|
|
100
|
+
&self.notices
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/// Looks up the value for `column_name` on an owned row from this set.
|
|
104
|
+
pub fn get<'a>(&self, row: &'a Row, column_name: &str) -> Option<&'a Value> {
|
|
105
|
+
let index = self.column_index(column_name)?;
|
|
106
|
+
row.get_index(index)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// Returns the index for a column name.
|
|
110
|
+
pub fn column_index(&self, column_name: &str) -> Option<usize> {
|
|
111
|
+
self.columns.iter().position(|column| column == column_name)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// One owned row returned by a query.
|
|
116
|
+
#[derive(Debug, Clone, PartialEq)]
|
|
117
|
+
pub struct Row {
|
|
118
|
+
columns: Arc<[String]>,
|
|
119
|
+
values: Vec<Value>,
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
impl Row {
|
|
123
|
+
/// Returns the values in result-set column order.
|
|
124
|
+
pub fn values(&self) -> &[Value] {
|
|
125
|
+
&self.values
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// Returns the value at `index`.
|
|
129
|
+
pub fn get_index(&self, index: usize) -> Option<&Value> {
|
|
130
|
+
self.values.get(index)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Returns the raw value for `column_name`, or an error when the column is absent.
|
|
134
|
+
pub fn value(&self, column_name: &str) -> Result<&Value, LixError> {
|
|
135
|
+
let index = self.column_index(column_name)?;
|
|
136
|
+
self.values.get(index).ok_or_else(|| {
|
|
137
|
+
LixError::new(
|
|
138
|
+
LixError::CODE_COLUMN_NOT_FOUND,
|
|
139
|
+
format!(
|
|
140
|
+
"column '{}' points past row width {}; available columns: {}",
|
|
141
|
+
column_name,
|
|
142
|
+
self.values.len(),
|
|
143
|
+
self.available_columns()
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// Converts the named column to a native Rust value.
|
|
150
|
+
pub fn get<T>(&self, column_name: &str) -> Result<T, LixError>
|
|
151
|
+
where
|
|
152
|
+
T: TryFromValue,
|
|
153
|
+
{
|
|
154
|
+
T::try_from_value(self.value(column_name)?)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
fn column_index(&self, column_name: &str) -> Result<usize, LixError> {
|
|
158
|
+
self.columns
|
|
159
|
+
.iter()
|
|
160
|
+
.position(|column| column == column_name)
|
|
161
|
+
.ok_or_else(|| {
|
|
162
|
+
LixError::new(
|
|
163
|
+
LixError::CODE_COLUMN_NOT_FOUND,
|
|
164
|
+
format!(
|
|
165
|
+
"column '{}' does not exist; available columns: {}",
|
|
166
|
+
column_name,
|
|
167
|
+
self.available_columns()
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fn available_columns(&self) -> String {
|
|
174
|
+
if self.columns.is_empty() {
|
|
175
|
+
"<none>".to_string()
|
|
176
|
+
} else {
|
|
177
|
+
self.columns.join(", ")
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
pub trait TryFromValue: Sized {
|
|
183
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError>;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
impl TryFromValue for Value {
|
|
187
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError> {
|
|
188
|
+
Ok(value.clone())
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
impl TryFromValue for String {
|
|
193
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError> {
|
|
194
|
+
match value {
|
|
195
|
+
Value::Text(value) => Ok(value.clone()),
|
|
196
|
+
other => Err(value_type_error("text", other)),
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
impl TryFromValue for bool {
|
|
202
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError> {
|
|
203
|
+
match value {
|
|
204
|
+
Value::Boolean(value) => Ok(*value),
|
|
205
|
+
other => Err(value_type_error("boolean", other)),
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
impl TryFromValue for i64 {
|
|
211
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError> {
|
|
212
|
+
match value {
|
|
213
|
+
Value::Integer(value) => Ok(*value),
|
|
214
|
+
other => Err(value_type_error("integer", other)),
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
impl TryFromValue for f64 {
|
|
220
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError> {
|
|
221
|
+
match value {
|
|
222
|
+
Value::Real(value) => Ok(*value),
|
|
223
|
+
other => Err(value_type_error("real", other)),
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
impl TryFromValue for serde_json::Value {
|
|
229
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError> {
|
|
230
|
+
match value {
|
|
231
|
+
Value::Json(value) => Ok(value.clone()),
|
|
232
|
+
other => Err(value_type_error("json", other)),
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
impl TryFromValue for Vec<u8> {
|
|
238
|
+
fn try_from_value(value: &Value) -> Result<Self, LixError> {
|
|
239
|
+
match value {
|
|
240
|
+
Value::Blob(value) => Ok(value.clone()),
|
|
241
|
+
other => Err(value_type_error("blob", other)),
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
fn value_type_error(expected: &str, actual: &Value) -> LixError {
|
|
247
|
+
LixError::new(
|
|
248
|
+
"LIX_ERROR_VALUE_TYPE",
|
|
249
|
+
format!("expected {expected} value, got {actual:?}"),
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// Zero-copy row view with access to the result-set column names.
|
|
254
|
+
///
|
|
255
|
+
/// This is the ergonomic path for callers that want `row.get("column")`
|
|
256
|
+
/// without storing column metadata on every owned row.
|
|
257
|
+
#[derive(Debug, Clone, Copy)]
|
|
258
|
+
pub struct RowRef<'a> {
|
|
259
|
+
columns: &'a [String],
|
|
260
|
+
values: &'a [Value],
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
impl RowRef<'_> {
|
|
264
|
+
/// Returns the result-set column names in row value order.
|
|
265
|
+
pub fn columns(&self) -> &[String] {
|
|
266
|
+
self.columns
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/// Returns the row values in result-set column order.
|
|
270
|
+
pub fn values(&self) -> &[Value] {
|
|
271
|
+
self.values
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/// Returns the value for `column_name`.
|
|
275
|
+
pub fn get(&self, column_name: &str) -> Option<&Value> {
|
|
276
|
+
let index = self
|
|
277
|
+
.columns
|
|
278
|
+
.iter()
|
|
279
|
+
.position(|column| column == column_name)?;
|
|
280
|
+
self.values.get(index)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/// Returns the value at `index`.
|
|
284
|
+
pub fn get_index(&self, index: usize) -> Option<&Value> {
|
|
285
|
+
self.values.get(index)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
impl SessionContext {
|
|
290
|
+
/// Executes one DataFusion SQL statement against this Lix session.
|
|
291
|
+
///
|
|
292
|
+
/// The SQL dialect is DataFusion SQL, not SQLite SQL. Positional
|
|
293
|
+
/// placeholders use `$1`, `$2`, and so on. SQLite-specific catalog tables
|
|
294
|
+
/// and transaction statements such as `sqlite_master`, `BEGIN`, and
|
|
295
|
+
/// `COMMIT` are not part of this contract; use `information_schema` for
|
|
296
|
+
/// catalog inspection. Lix owns transaction boundaries for each statement.
|
|
297
|
+
pub async fn execute(&self, sql: &str, params: &[Value]) -> Result<ExecuteResult, LixError> {
|
|
298
|
+
self.ensure_open()?;
|
|
299
|
+
let kind = sql2::classify_statement(sql)?;
|
|
300
|
+
if kind == sql2::SqlStatementKind::Write {
|
|
301
|
+
let sql = sql.to_string();
|
|
302
|
+
let sql_for_error = sql.clone();
|
|
303
|
+
let params = params.to_vec();
|
|
304
|
+
return self
|
|
305
|
+
.with_write_transaction(|transaction| {
|
|
306
|
+
Box::pin(async move {
|
|
307
|
+
// Re-plan against the transaction-backed write
|
|
308
|
+
// session so provider hooks read and stage through the
|
|
309
|
+
// transaction-owned SQL write context.
|
|
310
|
+
let tx_plan = sql2::create_write_logical_plan(transaction, &sql).await?;
|
|
311
|
+
let result = sql2::execute_logical_plan(tx_plan, ¶ms).await?;
|
|
312
|
+
let affected_rows = affected_rows_from_query_result(result)?;
|
|
313
|
+
Ok(ExecuteResult::from_rows_affected(affected_rows))
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
.await
|
|
317
|
+
.map_err(|error| normalize_sql_surface_error(error, &sql_for_error));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let read_scope = StorageReadScope::new(self.storage.begin_read_transaction().await?);
|
|
321
|
+
let read_result = async {
|
|
322
|
+
let mut read_store = read_scope.store();
|
|
323
|
+
let live_state: Arc<dyn crate::live_state::LiveStateReader> =
|
|
324
|
+
Arc::new(self.live_state.reader(read_store.clone()));
|
|
325
|
+
let runtime_functions = FunctionContext::prepare(live_state.as_ref()).await?;
|
|
326
|
+
let functions = runtime_functions.provider();
|
|
327
|
+
let active_version_id = self.active_version_id_from_reader(&mut read_store).await?;
|
|
328
|
+
let visible_schemas = self
|
|
329
|
+
.catalog_context
|
|
330
|
+
.schema_jsons_for_sql_read_planning(live_state.as_ref(), &active_version_id)
|
|
331
|
+
.await?;
|
|
332
|
+
let ctx = SessionSqlExecutionContext {
|
|
333
|
+
active_version_id: &active_version_id,
|
|
334
|
+
read_store,
|
|
335
|
+
live_state: Arc::clone(&self.live_state),
|
|
336
|
+
binary_cas: Arc::clone(&self.binary_cas),
|
|
337
|
+
commit_store: Arc::clone(&self.commit_store),
|
|
338
|
+
version_ctx: Arc::clone(&self.version_ctx),
|
|
339
|
+
visible_schemas,
|
|
340
|
+
functions: functions.clone(),
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
let plan = sql2::create_logical_plan(&ctx, sql).await?;
|
|
344
|
+
let result = sql2::execute_logical_plan(plan, params).await?;
|
|
345
|
+
drop(ctx);
|
|
346
|
+
drop(live_state);
|
|
347
|
+
Ok::<_, LixError>((runtime_functions, result))
|
|
348
|
+
};
|
|
349
|
+
let (runtime_functions, result) = match read_result.await {
|
|
350
|
+
Ok(result) => {
|
|
351
|
+
read_scope.rollback().await?;
|
|
352
|
+
result
|
|
353
|
+
}
|
|
354
|
+
Err(error) => {
|
|
355
|
+
let _ = read_scope.rollback().await;
|
|
356
|
+
return Err(normalize_sql_surface_error(error, sql));
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
self.persist_runtime_functions_if_needed(&runtime_functions)
|
|
360
|
+
.await?;
|
|
361
|
+
Ok(ExecuteResult::from_sql_query_result(result))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// Persists execution-scoped runtime function state after a successful read.
|
|
365
|
+
///
|
|
366
|
+
/// Reads do not otherwise own a write transaction, but SQL functions such as
|
|
367
|
+
/// `lix_uuid_v7()` can still advance runtime state. Persisting happens only
|
|
368
|
+
/// after successful execution so failed reads do not consume durable
|
|
369
|
+
/// sequence state.
|
|
370
|
+
async fn persist_runtime_functions_if_needed(
|
|
371
|
+
&self,
|
|
372
|
+
runtime_functions: &FunctionContext,
|
|
373
|
+
) -> Result<(), LixError> {
|
|
374
|
+
let mut transaction = self.storage.begin_write_transaction().await?;
|
|
375
|
+
let mut writes = StorageWriteSet::new();
|
|
376
|
+
runtime_functions
|
|
377
|
+
.stage_persist_if_needed(&mut writes)
|
|
378
|
+
.await?;
|
|
379
|
+
if !writes.is_empty() {
|
|
380
|
+
writes.apply(&mut transaction.as_mut()).await?;
|
|
381
|
+
}
|
|
382
|
+
transaction.commit().await
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
fn normalize_sql_surface_error(error: LixError, sql: &str) -> LixError {
|
|
387
|
+
if error.code.starts_with("LIX_ERROR_PATH_") && sql_uses_public_filesystem_path_surface(sql) {
|
|
388
|
+
return LixError {
|
|
389
|
+
code: LixError::CODE_INVALID_PARAM.to_string(),
|
|
390
|
+
..error
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
if error.code == LixError::CODE_INVALID_JSON_PATH
|
|
394
|
+
&& error
|
|
395
|
+
.message
|
|
396
|
+
.to_ascii_lowercase()
|
|
397
|
+
.contains("uses variadic path segments")
|
|
398
|
+
{
|
|
399
|
+
return LixError {
|
|
400
|
+
code: LixError::CODE_INVALID_PARAM.to_string(),
|
|
401
|
+
..error
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if error.code == LixError::CODE_FOREIGN_KEY {
|
|
405
|
+
let lower = error.message.to_ascii_lowercase();
|
|
406
|
+
if lower.contains("schema 'lix_version_ref'") && lower.contains("target 'lix_commit.") {
|
|
407
|
+
return LixError {
|
|
408
|
+
code: LixError::CODE_VERSION_NOT_FOUND.to_string(),
|
|
409
|
+
..error
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
error
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
fn sql_uses_public_filesystem_path_surface(sql: &str) -> bool {
|
|
417
|
+
let lower = sql.to_ascii_lowercase();
|
|
418
|
+
(lower.contains("lix_file") || lower.contains("lix_directory")) && lower.contains("path")
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
fn affected_rows_from_query_result(result: SqlQueryResult) -> Result<u64, LixError> {
|
|
422
|
+
let Some(first_row) = result.rows.first() else {
|
|
423
|
+
return Ok(0);
|
|
424
|
+
};
|
|
425
|
+
let Some(first_value) = first_row.first() else {
|
|
426
|
+
return Ok(0);
|
|
427
|
+
};
|
|
428
|
+
match first_value {
|
|
429
|
+
Value::Integer(value) if *value >= 0 => Ok(*value as u64),
|
|
430
|
+
Value::Text(value) => value.parse::<u64>().map_err(|error| {
|
|
431
|
+
LixError::new(
|
|
432
|
+
"LIX_ERROR_UNKNOWN",
|
|
433
|
+
format!("failed to parse affected row count from SQL result: {error}"),
|
|
434
|
+
)
|
|
435
|
+
}),
|
|
436
|
+
other => Err(LixError::new(
|
|
437
|
+
"LIX_ERROR_UNKNOWN",
|
|
438
|
+
format!("expected affected row count, got {other:?}"),
|
|
439
|
+
)),
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
#[cfg(test)]
|
|
444
|
+
mod tests {
|
|
445
|
+
use super::*;
|
|
446
|
+
|
|
447
|
+
#[test]
|
|
448
|
+
fn row_get_converts_native_values_and_value_keeps_wrapper() {
|
|
449
|
+
let result = ExecuteResult::from_rows(
|
|
450
|
+
vec!["title".to_string(), "done".to_string()],
|
|
451
|
+
vec![vec![Value::Text("Hello".to_string()), Value::Boolean(true)]],
|
|
452
|
+
);
|
|
453
|
+
let row = &result.rows()[0];
|
|
454
|
+
|
|
455
|
+
assert_eq!(row.get::<String>("title").unwrap(), "Hello");
|
|
456
|
+
assert!(row.get::<bool>("done").unwrap());
|
|
457
|
+
assert_eq!(
|
|
458
|
+
row.value("title").unwrap(),
|
|
459
|
+
&Value::Text("Hello".to_string())
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
#[test]
|
|
464
|
+
fn row_get_errors_on_missing_column_and_wrong_type() {
|
|
465
|
+
let result = ExecuteResult::from_rows(
|
|
466
|
+
vec!["title".to_string()],
|
|
467
|
+
vec![vec![Value::Text("Hello".to_string())]],
|
|
468
|
+
);
|
|
469
|
+
let row = &result.rows()[0];
|
|
470
|
+
|
|
471
|
+
let missing = row.get::<String>("missing").unwrap_err();
|
|
472
|
+
assert_eq!(missing.code, LixError::CODE_COLUMN_NOT_FOUND);
|
|
473
|
+
assert!(missing.message.contains("available columns: title"));
|
|
474
|
+
|
|
475
|
+
let wrong_type = row.get::<bool>("title").unwrap_err();
|
|
476
|
+
assert_eq!(wrong_type.code, "LIX_ERROR_VALUE_TYPE");
|
|
477
|
+
}
|
|
478
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
use crate::storage::StorageReader;
|
|
2
|
+
use crate::tracked_state::{
|
|
3
|
+
plan_merge, TrackedStateDiff, TrackedStateDiffRequest, TrackedStateMergePlan,
|
|
4
|
+
TrackedStateStoreReader,
|
|
5
|
+
};
|
|
6
|
+
use crate::LixError;
|
|
7
|
+
|
|
8
|
+
use super::conflicts::{conflicts_from_plan, MergeConflict};
|
|
9
|
+
use super::stats::{stats_from_diff, stats_from_plan, MergeStats};
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
12
|
+
pub(crate) enum MergeOutcome {
|
|
13
|
+
AlreadyUpToDate,
|
|
14
|
+
FastForward,
|
|
15
|
+
MergeCommitted,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
19
|
+
pub(crate) struct MergeCommits {
|
|
20
|
+
pub(crate) base_commit_id: String,
|
|
21
|
+
pub(crate) target_commit_id: String,
|
|
22
|
+
pub(crate) source_commit_id: String,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
26
|
+
pub(crate) struct MergeAnalysis {
|
|
27
|
+
pub(crate) outcome: MergeOutcome,
|
|
28
|
+
pub(crate) commits: MergeCommits,
|
|
29
|
+
pub(crate) source_diff: TrackedStateDiff,
|
|
30
|
+
pub(crate) target_diff: TrackedStateDiff,
|
|
31
|
+
pub(crate) stats: MergeStats,
|
|
32
|
+
pub(crate) conflicts: Vec<MergeConflict>,
|
|
33
|
+
pub(crate) merge_plan: Option<TrackedStateMergePlan>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
impl MergeAnalysis {
|
|
37
|
+
pub(crate) fn merge_plan(&self) -> Option<&TrackedStateMergePlan> {
|
|
38
|
+
self.merge_plan.as_ref()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub(crate) async fn analyze<S>(
|
|
43
|
+
reader: &mut TrackedStateStoreReader<S>,
|
|
44
|
+
commits: MergeCommits,
|
|
45
|
+
) -> Result<MergeAnalysis, LixError>
|
|
46
|
+
where
|
|
47
|
+
S: StorageReader,
|
|
48
|
+
{
|
|
49
|
+
let request = TrackedStateDiffRequest::default();
|
|
50
|
+
let source_diff = reader
|
|
51
|
+
.diff_commits(&commits.base_commit_id, &commits.source_commit_id, &request)
|
|
52
|
+
.await?;
|
|
53
|
+
let target_diff = if commits.base_commit_id == commits.source_commit_id
|
|
54
|
+
|| commits.base_commit_id == commits.target_commit_id
|
|
55
|
+
{
|
|
56
|
+
TrackedStateDiff::default()
|
|
57
|
+
} else {
|
|
58
|
+
reader
|
|
59
|
+
.diff_commits(&commits.base_commit_id, &commits.target_commit_id, &request)
|
|
60
|
+
.await?
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
let outcome = if commits.base_commit_id == commits.source_commit_id {
|
|
64
|
+
MergeOutcome::AlreadyUpToDate
|
|
65
|
+
} else if commits.base_commit_id == commits.target_commit_id {
|
|
66
|
+
MergeOutcome::FastForward
|
|
67
|
+
} else {
|
|
68
|
+
MergeOutcome::MergeCommitted
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
let merge_plan = if outcome == MergeOutcome::MergeCommitted {
|
|
72
|
+
Some(plan_merge(&target_diff, &source_diff)?)
|
|
73
|
+
} else {
|
|
74
|
+
None
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
let stats = match outcome {
|
|
78
|
+
MergeOutcome::AlreadyUpToDate => MergeStats::default(),
|
|
79
|
+
MergeOutcome::FastForward => stats_from_diff(&source_diff),
|
|
80
|
+
MergeOutcome::MergeCommitted => merge_plan
|
|
81
|
+
.as_ref()
|
|
82
|
+
.map(|plan| stats_from_plan(plan, &source_diff))
|
|
83
|
+
.transpose()?
|
|
84
|
+
.unwrap_or_default(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
let conflicts = merge_plan
|
|
88
|
+
.as_ref()
|
|
89
|
+
.map(conflicts_from_plan)
|
|
90
|
+
.transpose()?
|
|
91
|
+
.unwrap_or_default();
|
|
92
|
+
|
|
93
|
+
Ok(MergeAnalysis {
|
|
94
|
+
outcome,
|
|
95
|
+
commits,
|
|
96
|
+
source_diff,
|
|
97
|
+
target_diff,
|
|
98
|
+
stats,
|
|
99
|
+
conflicts,
|
|
100
|
+
merge_plan,
|
|
101
|
+
})
|
|
102
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
use crate::tracked_state::TrackedStateMergePlan;
|
|
2
|
+
use crate::transaction::types::TransactionAdoptedChange;
|
|
3
|
+
|
|
4
|
+
pub(crate) fn adopted_changes_from_merge_plan(
|
|
5
|
+
plan: &TrackedStateMergePlan,
|
|
6
|
+
target_version_id: &str,
|
|
7
|
+
) -> Vec<TransactionAdoptedChange> {
|
|
8
|
+
plan.patches
|
|
9
|
+
.iter()
|
|
10
|
+
.map(|patch| stage_adopted_change_from_patch(patch, target_version_id))
|
|
11
|
+
.collect()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fn stage_adopted_change_from_patch(
|
|
15
|
+
patch: &crate::tracked_state::TrackedStateMergePatch,
|
|
16
|
+
target_version_id: &str,
|
|
17
|
+
) -> TransactionAdoptedChange {
|
|
18
|
+
TransactionAdoptedChange {
|
|
19
|
+
version_id: target_version_id.to_string(),
|
|
20
|
+
change_id: patch.change_id().to_string(),
|
|
21
|
+
projected_row: patch.projected_row().clone(),
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
use crate::tracked_state::{
|
|
2
|
+
TrackedStateDiffEntry, TrackedStateDiffKind, TrackedStateMergeConflict, TrackedStateMergePlan,
|
|
3
|
+
};
|
|
4
|
+
use crate::LixError;
|
|
5
|
+
use serde_json::Value as JsonValue;
|
|
6
|
+
|
|
7
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
8
|
+
pub(crate) struct MergeConflict {
|
|
9
|
+
pub(crate) kind: MergeConflictKind,
|
|
10
|
+
pub(crate) schema_key: String,
|
|
11
|
+
pub(crate) entity_id: JsonValue,
|
|
12
|
+
pub(crate) file_id: Option<String>,
|
|
13
|
+
pub(crate) target: MergeConflictSide,
|
|
14
|
+
pub(crate) source: MergeConflictSide,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
18
|
+
pub(crate) enum MergeConflictKind {
|
|
19
|
+
SameEntityChanged,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
23
|
+
pub(crate) struct MergeConflictSide {
|
|
24
|
+
pub(crate) kind: MergeConflictChangeKind,
|
|
25
|
+
pub(crate) before_change_id: Option<String>,
|
|
26
|
+
pub(crate) after_change_id: Option<String>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
30
|
+
pub(crate) enum MergeConflictChangeKind {
|
|
31
|
+
Added,
|
|
32
|
+
Modified,
|
|
33
|
+
Removed,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
pub(crate) fn conflicts_from_plan(
|
|
37
|
+
plan: &TrackedStateMergePlan,
|
|
38
|
+
) -> Result<Vec<MergeConflict>, LixError> {
|
|
39
|
+
plan.conflicts.iter().map(conflict_from_tracked).collect()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
fn conflict_from_tracked(conflict: &TrackedStateMergeConflict) -> Result<MergeConflict, LixError> {
|
|
43
|
+
Ok(MergeConflict {
|
|
44
|
+
kind: MergeConflictKind::SameEntityChanged,
|
|
45
|
+
schema_key: conflict.identity.schema_key.clone(),
|
|
46
|
+
entity_id: conflict.identity.entity_id.as_json_array_value()?,
|
|
47
|
+
file_id: conflict.identity.file_id.clone(),
|
|
48
|
+
target: conflict_side_from_diff_entry(&conflict.target),
|
|
49
|
+
source: conflict_side_from_diff_entry(&conflict.source),
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn conflict_side_from_diff_entry(entry: &TrackedStateDiffEntry) -> MergeConflictSide {
|
|
54
|
+
MergeConflictSide {
|
|
55
|
+
kind: match entry.kind {
|
|
56
|
+
TrackedStateDiffKind::Added => MergeConflictChangeKind::Added,
|
|
57
|
+
TrackedStateDiffKind::Modified => MergeConflictChangeKind::Modified,
|
|
58
|
+
TrackedStateDiffKind::Removed => MergeConflictChangeKind::Removed,
|
|
59
|
+
},
|
|
60
|
+
before_change_id: entry.before.as_ref().map(|row| row.change_id.clone()),
|
|
61
|
+
after_change_id: entry.after.as_ref().map(|row| row.change_id.clone()),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
mod analysis;
|
|
2
|
+
mod apply;
|
|
3
|
+
mod conflicts;
|
|
4
|
+
mod stats;
|
|
5
|
+
mod version;
|
|
6
|
+
|
|
7
|
+
pub use version::{
|
|
8
|
+
MergeChangeStats, MergeConflict, MergeConflictChangeKind, MergeConflictKind, MergeConflictSide,
|
|
9
|
+
MergeVersionOptions, MergeVersionOutcome, MergeVersionPreview, MergeVersionPreviewOptions,
|
|
10
|
+
MergeVersionReceipt,
|
|
11
|
+
};
|