@lix-js/sdk 0.6.0-preview.4 → 0.6.0-preview.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/SKILL.md +65 -64
- package/dist/engine-wasm/index.js +4 -4
- package/dist/engine-wasm/wasm/lix_engine.d.ts +5 -5
- package/dist/engine-wasm/wasm/lix_engine.js +130 -118
- package/dist/engine-wasm/wasm/lix_engine.wasm +0 -0
- package/dist/engine-wasm/wasm/lix_engine.wasm.d.ts +9 -8
- package/dist/generated/builtin-schemas.d.ts +69 -69
- package/dist/generated/builtin-schemas.js +94 -94
- package/dist/open-lix.d.ts +33 -26
- package/dist/open-lix.js +10 -10
- package/dist/sqlite/index.js +86 -30
- package/dist-engine-src/README.md +3 -3
- package/dist-engine-src/src/backend/capabilities.rs +67 -0
- package/dist-engine-src/src/backend/conformance/baseline.rs +1127 -0
- package/dist-engine-src/src/backend/conformance/factory.rs +93 -0
- package/dist-engine-src/src/backend/conformance/failure_tests.rs +608 -0
- package/dist-engine-src/src/backend/conformance/fixtures.rs +26 -0
- package/dist-engine-src/src/backend/conformance/mod.rs +75 -0
- package/dist-engine-src/src/backend/conformance/model.rs +28 -0
- package/dist-engine-src/src/backend/conformance/model_based.rs +257 -0
- package/dist-engine-src/src/backend/conformance/persistence.rs +204 -0
- package/dist-engine-src/src/backend/conformance/projection.rs +21 -0
- package/dist-engine-src/src/backend/conformance/pushdown.rs +24 -0
- package/dist-engine-src/src/backend/conformance/runner.rs +90 -0
- package/dist-engine-src/src/backend/conformance/scan.rs +24 -0
- package/dist-engine-src/src/backend/conformance/write.rs +16 -0
- package/dist-engine-src/src/backend/error.rs +94 -0
- package/dist-engine-src/src/backend/in_memory.rs +670 -0
- package/dist-engine-src/src/backend/mod.rs +36 -9
- package/dist-engine-src/src/backend/predicate.rs +80 -0
- package/dist-engine-src/src/backend/traits.rs +260 -0
- package/dist-engine-src/src/backend/types.rs +224 -81
- package/dist-engine-src/src/binary_cas/context.rs +8 -8
- package/dist-engine-src/src/binary_cas/kv.rs +234 -259
- package/dist-engine-src/src/{version → branch}/context.rs +12 -12
- package/dist-engine-src/src/branch/lifecycle.rs +221 -0
- package/dist-engine-src/src/branch/mod.rs +13 -0
- package/dist-engine-src/src/branch/refs.rs +321 -0
- package/dist-engine-src/src/branch/stage_rows.rs +67 -0
- package/dist-engine-src/src/branch/types.rs +21 -0
- package/dist-engine-src/src/catalog/context.rs +18 -18
- package/dist-engine-src/src/catalog/snapshot.rs +8 -8
- package/dist-engine-src/src/changelog/bench_support.rs +785 -0
- package/dist-engine-src/src/changelog/change.rs +1 -0
- package/dist-engine-src/src/changelog/codec.rs +497 -0
- package/dist-engine-src/src/changelog/commit.rs +1 -0
- package/dist-engine-src/src/changelog/context.rs +1614 -0
- package/dist-engine-src/src/changelog/mod.rs +29 -0
- package/dist-engine-src/src/changelog/store.rs +163 -0
- package/dist-engine-src/src/changelog/test_support.rs +54 -0
- package/dist-engine-src/src/changelog/types.rs +213 -0
- package/dist-engine-src/src/commit_graph/context.rs +317 -274
- package/dist-engine-src/src/commit_graph/mod.rs +2 -4
- package/dist-engine-src/src/commit_graph/types.rs +22 -42
- package/dist-engine-src/src/commit_graph/walker.rs +133 -103
- package/dist-engine-src/src/common/error.rs +52 -18
- package/dist-engine-src/src/common/identity.rs +2 -2
- package/dist-engine-src/src/common/mod.rs +1 -1
- package/dist-engine-src/src/domain.rs +42 -46
- package/dist-engine-src/src/engine.rs +74 -96
- package/dist-engine-src/src/{entity_identity.rs → entity_pk.rs} +89 -92
- package/dist-engine-src/src/functions/context.rs +56 -52
- package/dist-engine-src/src/functions/state.rs +51 -52
- package/dist-engine-src/src/init.rs +288 -154
- package/dist-engine-src/src/json_store/context.rs +15 -266
- package/dist-engine-src/src/json_store/mod.rs +26 -0
- package/dist-engine-src/src/json_store/store.rs +103 -718
- package/dist-engine-src/src/json_store/types.rs +4 -9
- package/dist-engine-src/src/lib.rs +49 -19
- package/dist-engine-src/src/live_state/context.rs +654 -790
- package/dist-engine-src/src/live_state/mod.rs +9 -3
- package/dist-engine-src/src/live_state/overlay.rs +4 -4
- package/dist-engine-src/src/live_state/types.rs +30 -21
- package/dist-engine-src/src/live_state/visibility.rs +514 -71
- package/dist-engine-src/src/plugin/install.rs +48 -48
- package/dist-engine-src/src/plugin/manifest.rs +7 -7
- package/dist-engine-src/src/plugin/materializer.rs +0 -275
- package/dist-engine-src/src/plugin/plugin_manifest.json +4 -3
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +2 -2
- package/dist-engine-src/src/schema/builtin/lix_branch_descriptor.json +34 -0
- package/dist-engine-src/src/schema/builtin/lix_branch_ref.json +48 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +3 -3
- package/dist-engine-src/src/schema/builtin/lix_commit.json +1 -1
- package/dist-engine-src/src/schema/builtin/lix_label_assignment.json +6 -6
- package/dist-engine-src/src/schema/builtin/mod.rs +18 -20
- package/dist-engine-src/src/schema/compatibility.rs +11 -11
- package/dist-engine-src/src/schema/definition.json +2 -2
- package/dist-engine-src/src/schema/definition.rs +5 -5
- package/dist-engine-src/src/schema/key.rs +3 -3
- package/dist-engine-src/src/schema/mod.rs +1 -1
- package/dist-engine-src/src/schema/tests.rs +18 -18
- package/dist-engine-src/src/session/context.rs +803 -148
- package/dist-engine-src/src/session/create_branch.rs +94 -0
- package/dist-engine-src/src/session/execute.rs +223 -83
- package/dist-engine-src/src/session/merge/analysis.rs +9 -3
- package/dist-engine-src/src/session/merge/{version.rs → branch.rs} +119 -129
- package/dist-engine-src/src/session/merge/conflicts.rs +2 -2
- package/dist-engine-src/src/session/merge/mod.rs +5 -6
- package/dist-engine-src/src/session/merge/stats.rs +7 -11
- package/dist-engine-src/src/session/mod.rs +15 -12
- package/dist-engine-src/src/session/switch_branch.rs +113 -0
- package/dist-engine-src/src/session/transaction.rs +495 -14
- package/dist-engine-src/src/sql2/{classify.rs → bind/classify.rs} +3 -75
- package/dist-engine-src/src/sql2/bind/error.rs +5 -0
- package/dist-engine-src/src/sql2/bind/expr.rs +29 -0
- package/dist-engine-src/src/sql2/bind/mod.rs +12 -0
- package/dist-engine-src/src/sql2/{udfs/public_call.rs → bind/public_udf.rs} +71 -3
- package/dist-engine-src/src/sql2/bind/read.rs +65 -0
- package/dist-engine-src/src/sql2/bind/statement.rs +2236 -0
- package/dist-engine-src/src/sql2/bind/table.rs +273 -0
- package/dist-engine-src/src/sql2/bind/write.rs +86 -0
- package/dist-engine-src/src/sql2/branch_scope.rs +436 -0
- package/dist-engine-src/src/sql2/catalog/capability.rs +20 -0
- package/dist-engine-src/src/sql2/catalog/entity_surface.rs +296 -0
- package/dist-engine-src/src/sql2/catalog/mod.rs +15 -0
- package/dist-engine-src/src/sql2/catalog/registry.rs +556 -0
- package/dist-engine-src/src/sql2/catalog/schema.rs +88 -0
- package/dist-engine-src/src/sql2/catalog/surface.rs +41 -0
- package/dist-engine-src/src/sql2/change_materialization.rs +122 -0
- package/dist-engine-src/src/sql2/context.rs +36 -30
- package/dist-engine-src/src/sql2/error.rs +1 -1
- package/dist-engine-src/src/sql2/exec/bound_public_write.rs +1593 -0
- package/dist-engine-src/src/sql2/exec/datafusion.rs +5266 -0
- package/dist-engine-src/src/sql2/exec/fast_write.rs +82 -0
- package/dist-engine-src/src/sql2/exec/mod.rs +24 -0
- package/dist-engine-src/src/sql2/exec/write.rs +661 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +72 -77
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +21 -21
- package/dist-engine-src/src/sql2/history_projection.rs +8 -8
- package/dist-engine-src/src/sql2/history_route.rs +35 -31
- package/dist-engine-src/src/sql2/mod.rs +28 -23
- package/dist-engine-src/src/sql2/optimize/datafusion.rs +1 -0
- package/dist-engine-src/src/sql2/optimize/mod.rs +2 -0
- package/dist-engine-src/src/sql2/optimize/simple_write.rs +116 -0
- package/dist-engine-src/src/sql2/parse/mod.rs +69 -0
- package/dist-engine-src/src/sql2/parse/normalize.rs +1 -0
- package/dist-engine-src/src/sql2/plan/branch_scope.rs +24 -0
- package/dist-engine-src/src/sql2/plan/mod.rs +5 -0
- package/dist-engine-src/src/sql2/plan/predicate.rs +22 -0
- package/dist-engine-src/src/sql2/plan/write.rs +147 -0
- package/dist-engine-src/src/sql2/predicate_typecheck.rs +258 -0
- package/dist-engine-src/src/sql2/{version_provider.rs → providers/branch.rs} +218 -214
- package/dist-engine-src/src/sql2/{change_provider.rs → providers/change.rs} +156 -42
- package/dist-engine-src/src/sql2/{directory_provider.rs → providers/directory.rs} +291 -322
- package/dist-engine-src/src/sql2/{directory_history_provider.rs → providers/directory_history.rs} +56 -42
- package/dist-engine-src/src/sql2/providers/entity.rs +1484 -0
- package/dist-engine-src/src/sql2/{entity_history_provider.rs → providers/entity_history.rs} +43 -31
- package/dist-engine-src/src/sql2/{file_provider.rs → providers/file.rs} +323 -316
- package/dist-engine-src/src/sql2/{file_history_provider.rs → providers/file_history.rs} +60 -46
- package/dist-engine-src/src/sql2/{history_provider.rs → providers/history.rs} +46 -32
- package/dist-engine-src/src/sql2/{lix_state_provider.rs → providers/lix_state.rs} +359 -329
- package/dist-engine-src/src/sql2/providers/mod.rs +508 -0
- package/dist-engine-src/src/sql2/read_only.rs +2 -2
- package/dist-engine-src/src/sql2/session.rs +47 -96
- package/dist-engine-src/src/sql2/storage/constraints.rs +1 -0
- package/dist-engine-src/src/sql2/storage/mod.rs +1 -0
- package/dist-engine-src/src/sql2/test_support/differential.rs +712 -0
- package/dist-engine-src/src/sql2/test_support/generators.rs +354 -0
- package/dist-engine-src/src/sql2/test_support/mod.rs +2 -0
- package/dist-engine-src/src/sql2/udfs/{lix_active_version_commit_id.rs → lix_active_branch_commit_id.rs} +7 -7
- package/dist-engine-src/src/sql2/udfs/mod.rs +3 -6
- package/dist-engine-src/src/sql2/write_normalization.rs +45 -22
- package/dist-engine-src/src/storage/conformance.rs +399 -0
- package/dist-engine-src/src/storage/context.rs +552 -288
- package/dist-engine-src/src/storage/mod.rs +48 -10
- package/dist-engine-src/src/storage/point.rs +440 -0
- package/dist-engine-src/src/storage/read_scope.rs +43 -64
- package/dist-engine-src/src/storage/reader.rs +867 -0
- package/dist-engine-src/src/storage/scan.rs +784 -0
- package/dist-engine-src/src/storage/spaces.rs +236 -0
- package/dist-engine-src/src/storage/stats.rs +80 -0
- package/dist-engine-src/src/storage/write_set.rs +962 -0
- package/dist-engine-src/src/storage_bench.rs +136 -4828
- package/dist-engine-src/src/test_support.rs +360 -138
- package/dist-engine-src/src/tracked_state/bench_support.rs +394 -0
- package/dist-engine-src/src/tracked_state/codec.rs +155 -1057
- package/dist-engine-src/src/tracked_state/commit_root_rebuild.rs +358 -0
- package/dist-engine-src/src/tracked_state/context.rs +1927 -993
- package/dist-engine-src/src/tracked_state/diff.rs +1715 -261
- package/dist-engine-src/src/tracked_state/merge.rs +74 -88
- package/dist-engine-src/src/tracked_state/mod.rs +19 -16
- package/dist-engine-src/src/tracked_state/{materialization.rs → row_materialization.rs} +50 -178
- package/dist-engine-src/src/tracked_state/storage.rs +243 -191
- package/dist-engine-src/src/tracked_state/tree.rs +247 -371
- package/dist-engine-src/src/tracked_state/types.rs +49 -42
- package/dist-engine-src/src/transaction/bench_support.rs +407 -0
- package/dist-engine-src/src/transaction/commit.rs +821 -713
- package/dist-engine-src/src/transaction/context.rs +705 -600
- package/dist-engine-src/src/transaction/mod.rs +13 -2
- package/dist-engine-src/src/transaction/normalization.rs +63 -76
- package/dist-engine-src/src/transaction/prep.rs +13 -13
- package/dist-engine-src/src/transaction/schema_resolver.rs +19 -5
- package/dist-engine-src/src/transaction/staging.rs +228 -434
- package/dist-engine-src/src/transaction/types.rs +41 -98
- package/dist-engine-src/src/transaction/validation.rs +382 -446
- package/dist-engine-src/src/untracked_state/codec.rs +337 -29
- package/dist-engine-src/src/untracked_state/context.rs +7 -7
- package/dist-engine-src/src/untracked_state/materialization.rs +2 -2
- package/dist-engine-src/src/untracked_state/mod.rs +1 -1
- package/dist-engine-src/src/untracked_state/storage.rs +659 -157
- package/dist-engine-src/src/untracked_state/types.rs +21 -21
- package/package.json +71 -68
- package/dist-engine-src/src/backend/kv.rs +0 -358
- package/dist-engine-src/src/backend/testing.rs +0 -658
- package/dist-engine-src/src/commit_store/codec.rs +0 -887
- package/dist-engine-src/src/commit_store/context.rs +0 -944
- package/dist-engine-src/src/commit_store/materialization.rs +0 -84
- package/dist-engine-src/src/commit_store/mod.rs +0 -16
- package/dist-engine-src/src/commit_store/storage.rs +0 -600
- package/dist-engine-src/src/commit_store/types.rs +0 -215
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +0 -34
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +0 -48
- package/dist-engine-src/src/session/create_version.rs +0 -88
- package/dist-engine-src/src/session/merge/apply.rs +0 -23
- package/dist-engine-src/src/session/optimization9_sql2_bench.rs +0 -100
- package/dist-engine-src/src/session/switch_version.rs +0 -110
- package/dist-engine-src/src/sql2/entity_provider.rs +0 -3211
- package/dist-engine-src/src/sql2/execute.rs +0 -3533
- package/dist-engine-src/src/sql2/public_bind/assignment.rs +0 -46
- package/dist-engine-src/src/sql2/public_bind/capability.rs +0 -41
- package/dist-engine-src/src/sql2/public_bind/dml.rs +0 -172
- package/dist-engine-src/src/sql2/public_bind/mod.rs +0 -26
- package/dist-engine-src/src/sql2/public_bind/table.rs +0 -168
- package/dist-engine-src/src/sql2/version_scope.rs +0 -394
- package/dist-engine-src/src/storage/types.rs +0 -501
- package/dist-engine-src/src/tracked_state/by_file_index.rs +0 -98
- package/dist-engine-src/src/tracked_state/materializer.rs +0 -488
- package/dist-engine-src/src/transaction/live_state_overlay.rs +0 -35
- package/dist-engine-src/src/version/lifecycle.rs +0 -221
- package/dist-engine-src/src/version/mod.rs +0 -13
- package/dist-engine-src/src/version/refs.rs +0 -330
- package/dist-engine-src/src/version/stage_rows.rs +0 -67
- package/dist-engine-src/src/version/types.rs +0 -21
|
@@ -7,11 +7,12 @@ use datafusion::logical_expr::{Expr, Operator};
|
|
|
7
7
|
use tokio::sync::Mutex;
|
|
8
8
|
|
|
9
9
|
use crate::commit_graph::{CommitGraphChangeHistoryRequest, CommitGraphReader};
|
|
10
|
-
use crate::
|
|
10
|
+
use crate::entity_pk::EntityPk;
|
|
11
11
|
use crate::LixError;
|
|
12
12
|
|
|
13
13
|
use super::SqlJsonReader;
|
|
14
|
-
use crate::
|
|
14
|
+
use crate::sql2::change_materialization::{materialize_located_history_change, MaterializedChange};
|
|
15
|
+
use crate::storage::StorageRead;
|
|
15
16
|
|
|
16
17
|
/// Shared routing state for commit-shaped history SQL surfaces.
|
|
17
18
|
///
|
|
@@ -21,7 +22,7 @@ use crate::commit_store::{materialize_change, MaterializedChange};
|
|
|
21
22
|
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
|
22
23
|
pub(crate) struct HistoryRoute {
|
|
23
24
|
pub(crate) start_commit_ids: Vec<String>,
|
|
24
|
-
pub(crate)
|
|
25
|
+
pub(crate) entity_pks: Vec<String>,
|
|
25
26
|
pub(crate) schema_keys: Vec<String>,
|
|
26
27
|
pub(crate) file_ids: Vec<String>,
|
|
27
28
|
pub(crate) min_depth: Option<i64>,
|
|
@@ -82,7 +83,7 @@ impl HistoryRoute {
|
|
|
82
83
|
pub(crate) fn matches_surface_row(
|
|
83
84
|
&self,
|
|
84
85
|
schema_key: &str,
|
|
85
|
-
|
|
86
|
+
entity_pk: &str,
|
|
86
87
|
file_id: Option<&str>,
|
|
87
88
|
depth: u32,
|
|
88
89
|
) -> bool {
|
|
@@ -97,11 +98,11 @@ impl HistoryRoute {
|
|
|
97
98
|
{
|
|
98
99
|
return false;
|
|
99
100
|
}
|
|
100
|
-
if !self.
|
|
101
|
+
if !self.entity_pks.is_empty()
|
|
101
102
|
&& !self
|
|
102
|
-
.
|
|
103
|
+
.entity_pks
|
|
103
104
|
.iter()
|
|
104
|
-
.any(|candidate| candidate ==
|
|
105
|
+
.any(|candidate| candidate == entity_pk)
|
|
105
106
|
{
|
|
106
107
|
return false;
|
|
107
108
|
}
|
|
@@ -140,7 +141,7 @@ pub(crate) struct HistoryEntry {
|
|
|
140
141
|
pub(crate) depth: u32,
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
pub(crate) const
|
|
144
|
+
pub(crate) const HISTORY_COL_ENTITY_PK: &str = "lixcol_entity_pk";
|
|
144
145
|
pub(crate) const HISTORY_COL_SCHEMA_KEY: &str = "lixcol_schema_key";
|
|
145
146
|
pub(crate) const HISTORY_COL_FILE_ID: &str = "lixcol_file_id";
|
|
146
147
|
pub(crate) const HISTORY_COL_SNAPSHOT_CONTENT: &str = "lixcol_snapshot_content";
|
|
@@ -187,10 +188,10 @@ pub(crate) fn commit_graph_history_request(
|
|
|
187
188
|
) -> Option<CommitGraphChangeHistoryRequest> {
|
|
188
189
|
let schema_keys = effective_schema_keys(route, schema_keys)?;
|
|
189
190
|
Some(CommitGraphChangeHistoryRequest {
|
|
190
|
-
|
|
191
|
-
.
|
|
191
|
+
entity_pks: route
|
|
192
|
+
.entity_pks
|
|
192
193
|
.iter()
|
|
193
|
-
.filter_map(|
|
|
194
|
+
.filter_map(|entity_pk| EntityPk::from_json_array_text(entity_pk).ok())
|
|
194
195
|
.collect(),
|
|
195
196
|
schema_keys,
|
|
196
197
|
file_ids: route.file_ids.clone(),
|
|
@@ -200,17 +201,20 @@ pub(crate) fn commit_graph_history_request(
|
|
|
200
201
|
})
|
|
201
202
|
}
|
|
202
203
|
|
|
203
|
-
/// Loads commit-graph history once for all SQL history providers.
|
|
204
|
+
/// Loads reachability-aware commit-graph history once for all SQL history providers.
|
|
204
205
|
///
|
|
205
206
|
/// Providers pass the schema keys they know how to shape. An empty list means
|
|
206
207
|
/// "do not constrain by provider schema"; this is what `lix_state_history` uses.
|
|
207
|
-
pub(crate) async fn load_history_entries(
|
|
208
|
+
pub(crate) async fn load_history_entries<S>(
|
|
208
209
|
descriptor: HistoryViewDescriptor<'_>,
|
|
209
210
|
commit_graph: Arc<Mutex<Box<dyn CommitGraphReader>>>,
|
|
210
|
-
mut json_reader: SqlJsonReader
|
|
211
|
+
mut json_reader: SqlJsonReader<S>,
|
|
211
212
|
route: &HistoryRoute,
|
|
212
213
|
schema_keys: Vec<String>,
|
|
213
|
-
) -> Result<Vec<HistoryEntry>, LixError>
|
|
214
|
+
) -> Result<Vec<HistoryEntry>, LixError>
|
|
215
|
+
where
|
|
216
|
+
S: StorageRead + Clone + Send + Sync + 'static,
|
|
217
|
+
{
|
|
214
218
|
if route.is_contradictory() {
|
|
215
219
|
return Ok(Vec::new());
|
|
216
220
|
}
|
|
@@ -223,7 +227,7 @@ pub(crate) async fn load_history_entries(
|
|
|
223
227
|
),
|
|
224
228
|
)
|
|
225
229
|
.with_hint(format!(
|
|
226
|
-
"Use WHERE {} =
|
|
230
|
+
"Use WHERE {} = lix_active_branch_commit_id() to inspect {} from the active branch head.",
|
|
227
231
|
descriptor.start_commit_column, descriptor.view_name
|
|
228
232
|
)));
|
|
229
233
|
}
|
|
@@ -252,7 +256,7 @@ pub(crate) async fn load_history_entries(
|
|
|
252
256
|
.collect::<BTreeMap<_, _>>();
|
|
253
257
|
|
|
254
258
|
for entry in entries {
|
|
255
|
-
let change =
|
|
259
|
+
let change = materialize_located_history_change(&mut json_reader, entry.change).await?;
|
|
256
260
|
rows.push(HistoryEntry {
|
|
257
261
|
commit_created_at: commit_created_at_by_id
|
|
258
262
|
.get(&entry.observed_commit_id)
|
|
@@ -366,7 +370,7 @@ fn parse_history_disjunction(
|
|
|
366
370
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
367
371
|
enum HistoryFilterTerm {
|
|
368
372
|
StartCommitIds(Vec<String>),
|
|
369
|
-
|
|
373
|
+
EntityPks(Vec<String>),
|
|
370
374
|
SchemaKeys(Vec<String>),
|
|
371
375
|
FileIds(Vec<String>),
|
|
372
376
|
MinDepth(i64),
|
|
@@ -383,9 +387,9 @@ fn merge_history_disjunction_terms(
|
|
|
383
387
|
extend_unique(&mut left, right);
|
|
384
388
|
Some(HistoryFilterTerm::StartCommitIds(left))
|
|
385
389
|
}
|
|
386
|
-
(HistoryFilterTerm::
|
|
390
|
+
(HistoryFilterTerm::EntityPks(mut left), HistoryFilterTerm::EntityPks(right)) => {
|
|
387
391
|
extend_unique(&mut left, right);
|
|
388
|
-
Some(HistoryFilterTerm::
|
|
392
|
+
Some(HistoryFilterTerm::EntityPks(left))
|
|
389
393
|
}
|
|
390
394
|
(HistoryFilterTerm::FileIds(mut left), HistoryFilterTerm::FileIds(right)) => {
|
|
391
395
|
extend_unique(&mut left, right);
|
|
@@ -419,8 +423,8 @@ fn parse_history_binary_filter(
|
|
|
419
423
|
_ => unreachable!(),
|
|
420
424
|
})
|
|
421
425
|
}
|
|
422
|
-
("
|
|
423
|
-
|
|
426
|
+
("entity_pk", Operator::Eq, Expr::Literal(ScalarValue::Utf8(Some(value)), _)) => {
|
|
427
|
+
canonical_entity_pk_value(value).map(|value| HistoryFilterTerm::EntityPks(vec![value]))
|
|
424
428
|
}
|
|
425
429
|
("depth", Operator::Eq, depth_expr) => {
|
|
426
430
|
scalar_i64_literal(depth_expr).map(HistoryFilterTerm::ExactDepth)
|
|
@@ -464,7 +468,7 @@ fn parse_history_in_list_filter(
|
|
|
464
468
|
|
|
465
469
|
match column_name {
|
|
466
470
|
"start_commit_id" => Some(HistoryFilterTerm::StartCommitIds(values)),
|
|
467
|
-
"
|
|
471
|
+
"entity_pk" => canonical_entity_pk_values(values).map(HistoryFilterTerm::EntityPks),
|
|
468
472
|
"schema_key" => Some(HistoryFilterTerm::SchemaKeys(values)),
|
|
469
473
|
"file_id" => Some(HistoryFilterTerm::FileIds(values)),
|
|
470
474
|
_ => None,
|
|
@@ -478,9 +482,9 @@ fn apply_history_filter(expr: &Expr, route: &mut HistoryRoute, column_style: His
|
|
|
478
482
|
route.contradictory |=
|
|
479
483
|
apply_conjunctive_values_filter(&mut route.start_commit_ids, values)
|
|
480
484
|
}
|
|
481
|
-
HistoryFilterTerm::
|
|
485
|
+
HistoryFilterTerm::EntityPks(values) => {
|
|
482
486
|
route.contradictory |=
|
|
483
|
-
apply_conjunctive_values_filter(&mut route.
|
|
487
|
+
apply_conjunctive_values_filter(&mut route.entity_pks, values)
|
|
484
488
|
}
|
|
485
489
|
HistoryFilterTerm::SchemaKeys(values) => {
|
|
486
490
|
route.contradictory |=
|
|
@@ -518,15 +522,15 @@ fn apply_conjunctive_values_filter(bucket: &mut Vec<String>, incoming_values: Ve
|
|
|
518
522
|
bucket.is_empty()
|
|
519
523
|
}
|
|
520
524
|
|
|
521
|
-
fn
|
|
525
|
+
fn canonical_entity_pk_values(values: Vec<String>) -> Option<Vec<String>> {
|
|
522
526
|
values
|
|
523
527
|
.into_iter()
|
|
524
|
-
.map(|value|
|
|
528
|
+
.map(|value| canonical_entity_pk_value(&value))
|
|
525
529
|
.collect()
|
|
526
530
|
}
|
|
527
531
|
|
|
528
|
-
fn
|
|
529
|
-
|
|
532
|
+
fn canonical_entity_pk_value(value: &str) -> Option<String> {
|
|
533
|
+
EntityPk::from_json_array_text(value)
|
|
530
534
|
.ok()?
|
|
531
535
|
.as_json_array_text()
|
|
532
536
|
.ok()
|
|
@@ -536,8 +540,8 @@ fn canonical_history_column_name(name: &str, column_style: HistoryColumnStyle) -
|
|
|
536
540
|
match (column_style, name) {
|
|
537
541
|
(HistoryColumnStyle::Bare, "start_commit_id")
|
|
538
542
|
| (HistoryColumnStyle::Prefixed, "lixcol_start_commit_id") => Some("start_commit_id"),
|
|
539
|
-
(HistoryColumnStyle::Bare, "
|
|
540
|
-
| (HistoryColumnStyle::Prefixed, "
|
|
543
|
+
(HistoryColumnStyle::Bare, "entity_pk")
|
|
544
|
+
| (HistoryColumnStyle::Prefixed, "lixcol_entity_pk") => Some("entity_pk"),
|
|
541
545
|
(HistoryColumnStyle::Bare, "schema_key")
|
|
542
546
|
| (HistoryColumnStyle::Prefixed, "lixcol_schema_key") => Some("schema_key"),
|
|
543
547
|
(HistoryColumnStyle::Bare, "file_id")
|
|
@@ -1,47 +1,52 @@
|
|
|
1
|
-
mod
|
|
2
|
-
mod
|
|
1
|
+
mod bind;
|
|
2
|
+
mod branch_scope;
|
|
3
|
+
mod catalog;
|
|
4
|
+
mod change_materialization;
|
|
3
5
|
mod context;
|
|
4
|
-
mod directory_history_provider;
|
|
5
|
-
mod directory_provider;
|
|
6
6
|
mod dml;
|
|
7
|
-
mod entity_history_provider;
|
|
8
|
-
mod entity_provider;
|
|
9
7
|
mod error;
|
|
10
|
-
mod
|
|
11
|
-
mod file_history_provider;
|
|
12
|
-
mod file_provider;
|
|
8
|
+
mod exec;
|
|
13
9
|
mod filesystem_planner;
|
|
14
10
|
mod filesystem_predicates;
|
|
15
11
|
mod filesystem_visibility;
|
|
16
12
|
mod history_projection;
|
|
17
|
-
mod history_provider;
|
|
18
13
|
mod history_route;
|
|
19
|
-
mod
|
|
14
|
+
mod optimize;
|
|
15
|
+
mod parse;
|
|
16
|
+
mod plan;
|
|
20
17
|
mod predicate_typecheck;
|
|
21
|
-
mod
|
|
18
|
+
mod providers;
|
|
22
19
|
mod read_only;
|
|
23
20
|
mod record_batch;
|
|
24
21
|
mod result_metadata;
|
|
25
22
|
mod runtime;
|
|
26
23
|
mod session;
|
|
24
|
+
pub(crate) mod storage;
|
|
25
|
+
#[cfg(test)]
|
|
26
|
+
mod test_support;
|
|
27
27
|
mod udfs;
|
|
28
|
-
mod version_provider;
|
|
29
|
-
mod version_scope;
|
|
30
28
|
mod write_normalization;
|
|
31
29
|
|
|
32
|
-
pub(crate) use
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
pub(crate) use bind::{
|
|
31
|
+
bind_read_statement, bind_statement, bind_statement_route,
|
|
32
|
+
statement_has_durable_runtime_function, BoundStatementRoute,
|
|
35
33
|
};
|
|
36
34
|
pub(crate) use context::{
|
|
37
|
-
|
|
38
|
-
SqlWriteContext, SqlWriteExecutionContext, WriteAccess,
|
|
39
|
-
|
|
35
|
+
ChangelogQuerySource, HistoryQuerySource, SqlChangelogQuerySource, SqlExecutionContext,
|
|
36
|
+
SqlHistoryQuerySource, SqlJsonReader, SqlWriteContext, SqlWriteExecutionContext, WriteAccess,
|
|
37
|
+
WriteContextBranchRefReader, WriteContextLiveStateReader,
|
|
40
38
|
};
|
|
41
39
|
#[allow(unused_imports)]
|
|
42
|
-
pub(crate) use
|
|
40
|
+
pub(crate) use exec::{
|
|
43
41
|
create_logical_plan, create_logical_plan_from_parsed,
|
|
44
42
|
create_transaction_read_logical_plan_from_parsed, create_write_logical_plan,
|
|
45
|
-
create_write_logical_plan_from_parsed, execute_logical_plan, execute_sql,
|
|
46
|
-
SqlLogicalPlan,
|
|
43
|
+
create_write_logical_plan_from_parsed, execute_logical_plan, execute_sql,
|
|
44
|
+
execute_write_logical_plan, SqlLogicalPlan,
|
|
47
45
|
};
|
|
46
|
+
#[cfg(test)]
|
|
47
|
+
pub(crate) use exec::{
|
|
48
|
+
execute_write_logical_plan_with_mode, execute_write_logical_plan_with_mode_and_trace,
|
|
49
|
+
WriteExecutorMode, WriteExecutorPath,
|
|
50
|
+
};
|
|
51
|
+
pub(crate) use parse::parse_statement;
|
|
52
|
+
pub(crate) use plan::plan_write;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//! DataFusion optimization hooks for bound sql2 plans.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
use crate::sql2::bind::write::{BoundWriteOp, BoundWriteTarget};
|
|
2
|
+
use crate::sql2::plan::branch_scope::BranchScope;
|
|
3
|
+
use crate::sql2::plan::predicate::FilterSet;
|
|
4
|
+
use crate::sql2::plan::LogicalWritePlan;
|
|
5
|
+
use crate::LixError;
|
|
6
|
+
|
|
7
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
8
|
+
pub(crate) enum FastWritePlan {
|
|
9
|
+
Update(FastUpdatePlan),
|
|
10
|
+
Delete(FastDeletePlan),
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
14
|
+
pub(crate) struct FastUpdatePlan;
|
|
15
|
+
|
|
16
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
17
|
+
pub(crate) struct FastDeletePlan;
|
|
18
|
+
|
|
19
|
+
pub(crate) fn try_make_fast_write_plan(
|
|
20
|
+
plan: &LogicalWritePlan,
|
|
21
|
+
) -> Result<Option<FastWritePlan>, LixError> {
|
|
22
|
+
if !is_supported_fast_target(plan) || !is_known_no_match(plan) {
|
|
23
|
+
return Ok(None);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Ok(match plan.bound.op {
|
|
27
|
+
BoundWriteOp::Insert => None,
|
|
28
|
+
BoundWriteOp::Update => Some(FastWritePlan::Update(FastUpdatePlan)),
|
|
29
|
+
BoundWriteOp::Delete => Some(FastWritePlan::Delete(FastDeletePlan)),
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn is_supported_fast_target(plan: &LogicalWritePlan) -> bool {
|
|
34
|
+
matches!(
|
|
35
|
+
plan.bound.target,
|
|
36
|
+
BoundWriteTarget::LixState | BoundWriteTarget::LixStateByBranch
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
fn is_known_no_match(plan: &LogicalWritePlan) -> bool {
|
|
41
|
+
matches!(plan.bound.branch_scope, BranchScope::Empty)
|
|
42
|
+
|| matches!(plan.filters.rows, FilterSet::None)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[cfg(test)]
|
|
46
|
+
mod tests {
|
|
47
|
+
use super::*;
|
|
48
|
+
use crate::sql2::bind::bind_statement;
|
|
49
|
+
use crate::sql2::parse_statement;
|
|
50
|
+
use crate::sql2::plan::plan_write;
|
|
51
|
+
|
|
52
|
+
#[test]
|
|
53
|
+
fn try_make_fast_write_plan_declines_column_contradictions() {
|
|
54
|
+
let plan = plan_sql(
|
|
55
|
+
"UPDATE lix_state SET metadata = '{}' \
|
|
56
|
+
WHERE schema_key = 'profile' AND schema_key = 'note'",
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
assert_eq!(
|
|
60
|
+
try_make_fast_write_plan(&plan).expect("optimization should not fail"),
|
|
61
|
+
None
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[test]
|
|
66
|
+
fn try_make_fast_write_plan_accepts_false_delete_as_noop() {
|
|
67
|
+
let plan = plan_sql("DELETE FROM lix_state WHERE false");
|
|
68
|
+
|
|
69
|
+
assert_eq!(
|
|
70
|
+
try_make_fast_write_plan(&plan).expect("optimization should not fail"),
|
|
71
|
+
Some(FastWritePlan::Delete(FastDeletePlan))
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[test]
|
|
76
|
+
fn try_make_fast_write_plan_declines_complex_update() {
|
|
77
|
+
let plan = plan_sql(
|
|
78
|
+
"UPDATE lix_state SET metadata = lix_json('{\"schema_key\":\"lix_key_value\"}') \
|
|
79
|
+
WHERE metadata = lix_json('{\"source\":\"match\"}')",
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
assert_eq!(
|
|
83
|
+
try_make_fast_write_plan(&plan).expect("optimization should not fail"),
|
|
84
|
+
None
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
#[test]
|
|
89
|
+
fn try_make_fast_write_plan_declines_literal_insert() {
|
|
90
|
+
let plan = plan_sql(
|
|
91
|
+
"INSERT INTO lix_state (entity_pk, schema_key, snapshot_content) \
|
|
92
|
+
VALUES (lix_json('[\"entity-1\"]'), 'lix_key_value', '{}')",
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
assert_eq!(
|
|
96
|
+
try_make_fast_write_plan(&plan).expect("optimization should not fail"),
|
|
97
|
+
None
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[test]
|
|
102
|
+
fn try_make_fast_write_plan_declines_unsupported_targets_even_when_no_match() {
|
|
103
|
+
let plan = plan_sql("DELETE FROM lix_branch WHERE false");
|
|
104
|
+
|
|
105
|
+
assert_eq!(
|
|
106
|
+
try_make_fast_write_plan(&plan).expect("optimization should not fail"),
|
|
107
|
+
None
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
fn plan_sql(sql: &str) -> LogicalWritePlan {
|
|
112
|
+
let statement = parse_statement(sql).expect("SQL parses");
|
|
113
|
+
let write = bind_statement(&statement, &[], "branch1").expect("SQL binds");
|
|
114
|
+
plan_write(write).expect("write plans")
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
mod normalize;
|
|
2
|
+
|
|
3
|
+
use datafusion::sql::parser::{DFParserBuilder, Statement as DataFusionStatement};
|
|
4
|
+
use datafusion::sql::sqlparser::dialect::GenericDialect;
|
|
5
|
+
use datafusion::sql::sqlparser::tokenizer::{Token, Tokenizer};
|
|
6
|
+
use serde_json::json;
|
|
7
|
+
|
|
8
|
+
use crate::LixError;
|
|
9
|
+
|
|
10
|
+
pub(crate) fn parse_statement(sql: &str) -> Result<DataFusionStatement, LixError> {
|
|
11
|
+
let dialect = GenericDialect {};
|
|
12
|
+
let mut next_index = 1usize;
|
|
13
|
+
let mut has_anonymous = false;
|
|
14
|
+
let mut explicit_placeholders = Vec::new();
|
|
15
|
+
|
|
16
|
+
let mut tokens = Vec::new();
|
|
17
|
+
Tokenizer::new(&dialect, sql)
|
|
18
|
+
.tokenize_with_location_into_buf_with_mapper(&mut tokens, |mut token_span| {
|
|
19
|
+
if let Token::Placeholder(placeholder) = &token_span.token {
|
|
20
|
+
if placeholder == "?" {
|
|
21
|
+
has_anonymous = true;
|
|
22
|
+
token_span.token = Token::Placeholder(format!("${next_index}"));
|
|
23
|
+
next_index += 1;
|
|
24
|
+
} else {
|
|
25
|
+
explicit_placeholders.push(placeholder.clone());
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
token_span
|
|
29
|
+
})
|
|
30
|
+
.map_err(|error| {
|
|
31
|
+
LixError::new(
|
|
32
|
+
LixError::CODE_PARSE_ERROR,
|
|
33
|
+
format!("sql2 SQL tokenize error: {error}"),
|
|
34
|
+
)
|
|
35
|
+
})?;
|
|
36
|
+
|
|
37
|
+
if has_anonymous && !explicit_placeholders.is_empty() {
|
|
38
|
+
return Err(LixError::new(
|
|
39
|
+
LixError::CODE_PARSE_ERROR,
|
|
40
|
+
"SQL mixes anonymous and explicit parameter placeholders",
|
|
41
|
+
)
|
|
42
|
+
.with_hint("Use either anonymous placeholders like ?, ? or numbered placeholders like $1, $2, but not both.")
|
|
43
|
+
.with_details(json!({
|
|
44
|
+
"operation": "execute",
|
|
45
|
+
"explicit_placeholders": explicit_placeholders,
|
|
46
|
+
})));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let mut statements = DFParserBuilder::new(tokens)
|
|
50
|
+
.with_dialect(&dialect)
|
|
51
|
+
.build()
|
|
52
|
+
.map_err(crate::sql2::error::datafusion_error_to_lix_error)?
|
|
53
|
+
.parse_statements()
|
|
54
|
+
.map_err(crate::sql2::error::datafusion_error_to_lix_error)?;
|
|
55
|
+
|
|
56
|
+
if statements.len() > 1 {
|
|
57
|
+
return Err(LixError::new(
|
|
58
|
+
LixError::CODE_UNSUPPORTED_SQL,
|
|
59
|
+
"Lix SQL only supports one statement per execute() call",
|
|
60
|
+
));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
statements.pop_front().ok_or_else(|| {
|
|
64
|
+
LixError::new(
|
|
65
|
+
LixError::CODE_PARSE_ERROR,
|
|
66
|
+
"sql2 DataFusion error: No SQL statements were provided in the query string",
|
|
67
|
+
)
|
|
68
|
+
})
|
|
69
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//! SQL text normalization belongs in parse-time code, before semantic binding.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
use std::collections::BTreeSet;
|
|
2
|
+
|
|
3
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
4
|
+
pub(crate) enum BranchScope {
|
|
5
|
+
Active {
|
|
6
|
+
branch_id: String,
|
|
7
|
+
},
|
|
8
|
+
Explicit {
|
|
9
|
+
branch_ids: BTreeSet<String>,
|
|
10
|
+
},
|
|
11
|
+
ExplicitDynamic {
|
|
12
|
+
branch_ids: BTreeSet<String>,
|
|
13
|
+
param_indexes: BTreeSet<usize>,
|
|
14
|
+
},
|
|
15
|
+
ExplicitRequired {
|
|
16
|
+
branch_ids: BTreeSet<String>,
|
|
17
|
+
},
|
|
18
|
+
ExplicitRequiredDynamic {
|
|
19
|
+
branch_ids: BTreeSet<String>,
|
|
20
|
+
param_indexes: BTreeSet<usize>,
|
|
21
|
+
},
|
|
22
|
+
Global,
|
|
23
|
+
Empty,
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
use crate::sql2::bind::expr::BoundExpr;
|
|
2
|
+
|
|
3
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
4
|
+
pub(crate) enum FilterSet {
|
|
5
|
+
All,
|
|
6
|
+
None,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
10
|
+
pub(crate) enum BoundPredicate {
|
|
11
|
+
True,
|
|
12
|
+
False,
|
|
13
|
+
And(Vec<BoundPredicate>),
|
|
14
|
+
Or(Vec<BoundPredicate>),
|
|
15
|
+
Eq(BoundExpr, BoundExpr),
|
|
16
|
+
IsNull(BoundExpr),
|
|
17
|
+
IsNotNull(BoundExpr),
|
|
18
|
+
In {
|
|
19
|
+
expr: BoundExpr,
|
|
20
|
+
values: Vec<BoundExpr>,
|
|
21
|
+
},
|
|
22
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
use crate::sql2::bind::write::BoundWrite;
|
|
2
|
+
use crate::sql2::plan::predicate::{BoundPredicate, FilterSet};
|
|
3
|
+
use crate::LixError;
|
|
4
|
+
|
|
5
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
6
|
+
pub(crate) struct LogicalWritePlan {
|
|
7
|
+
pub(crate) bound: BoundWrite,
|
|
8
|
+
pub(crate) filters: PlannedWriteFilters,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
12
|
+
pub(crate) struct PlannedWriteFilters {
|
|
13
|
+
pub(crate) rows: FilterSet,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
pub(crate) fn plan_write(bound: BoundWrite) -> Result<LogicalWritePlan, LixError> {
|
|
17
|
+
let mut filters = PlannedWriteFilters::default();
|
|
18
|
+
collect_predicate_filters(&bound.predicate, &mut filters)?;
|
|
19
|
+
|
|
20
|
+
Ok(LogicalWritePlan { bound, filters })
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
impl Default for PlannedWriteFilters {
|
|
24
|
+
fn default() -> Self {
|
|
25
|
+
Self {
|
|
26
|
+
rows: FilterSet::All,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl PlannedWriteFilters {
|
|
32
|
+
fn set_none(&mut self) {
|
|
33
|
+
self.rows = FilterSet::None;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn collect_predicate_filters(
|
|
38
|
+
predicate: &BoundPredicate,
|
|
39
|
+
filters: &mut PlannedWriteFilters,
|
|
40
|
+
) -> Result<(), LixError> {
|
|
41
|
+
match predicate {
|
|
42
|
+
BoundPredicate::True => Ok(()),
|
|
43
|
+
BoundPredicate::False => {
|
|
44
|
+
filters.set_none();
|
|
45
|
+
Ok(())
|
|
46
|
+
}
|
|
47
|
+
BoundPredicate::And(predicates) => {
|
|
48
|
+
for predicate in predicates {
|
|
49
|
+
collect_predicate_filters(predicate, filters)?;
|
|
50
|
+
}
|
|
51
|
+
Ok(())
|
|
52
|
+
}
|
|
53
|
+
BoundPredicate::Or(_) => Ok(()),
|
|
54
|
+
BoundPredicate::Eq(_, _)
|
|
55
|
+
| BoundPredicate::IsNull(_)
|
|
56
|
+
| BoundPredicate::IsNotNull(_)
|
|
57
|
+
| BoundPredicate::In { .. } => Ok(()),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
#[cfg(test)]
|
|
62
|
+
mod tests {
|
|
63
|
+
use super::*;
|
|
64
|
+
use crate::sql2::bind::bind_statement;
|
|
65
|
+
use crate::sql2::parse_statement;
|
|
66
|
+
use crate::sql2::plan::branch_scope::BranchScope;
|
|
67
|
+
use std::collections::BTreeSet;
|
|
68
|
+
|
|
69
|
+
#[test]
|
|
70
|
+
fn plan_write_contradiction_does_not_drop_bound_params() {
|
|
71
|
+
let plan = plan_sql(
|
|
72
|
+
"UPDATE lix_state SET metadata = $1 WHERE schema_key IN ('profile') AND schema_key IN ('note') AND entity_pk = $2",
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
assert_eq!(
|
|
76
|
+
plan.bound.params.params.keys().copied().collect::<Vec<_>>(),
|
|
77
|
+
vec![1, 2]
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#[test]
|
|
82
|
+
fn plan_write_applies_active_branch_scope_to_base_writes() {
|
|
83
|
+
let plan = plan_sql("DELETE FROM lix_state WHERE schema_key = 'profile'");
|
|
84
|
+
|
|
85
|
+
assert_eq!(
|
|
86
|
+
plan.bound.branch_scope,
|
|
87
|
+
BranchScope::Active {
|
|
88
|
+
branch_id: "branch1".to_string()
|
|
89
|
+
}
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[test]
|
|
94
|
+
fn plan_write_keeps_explicit_required_scope_for_by_branch_writes() {
|
|
95
|
+
let plan = plan_sql("DELETE FROM lix_state_by_branch WHERE branch_id IN ('v1', 'v2')");
|
|
96
|
+
|
|
97
|
+
assert_eq!(
|
|
98
|
+
plan.bound.branch_scope,
|
|
99
|
+
BranchScope::ExplicitRequired {
|
|
100
|
+
branch_ids: BTreeSet::from(["v1".to_string(), "v2".to_string()])
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[test]
|
|
106
|
+
fn plan_write_false_conjunct_sets_no_match_sentinel() {
|
|
107
|
+
let plan = plan_sql("UPDATE lix_file SET hidden = true WHERE id = 'file1' AND false");
|
|
108
|
+
|
|
109
|
+
assert_eq!(plan.filters.rows, FilterSet::None);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[test]
|
|
113
|
+
fn plan_write_user_column_names_do_not_become_storage_filters() {
|
|
114
|
+
let plan = plan_sql_with_schemas(
|
|
115
|
+
"UPDATE app_doc SET title = 'new' WHERE schema_key = 'draft'",
|
|
116
|
+
&[serde_json::json!({
|
|
117
|
+
"x-lix-key": "app_doc",
|
|
118
|
+
"type": "object",
|
|
119
|
+
"properties": {
|
|
120
|
+
"id": { "type": "string" },
|
|
121
|
+
"schema_key": { "type": "string" },
|
|
122
|
+
"title": { "type": "string" }
|
|
123
|
+
},
|
|
124
|
+
"x-lix-primary-key": ["/id"],
|
|
125
|
+
"required": ["id", "schema_key", "title"],
|
|
126
|
+
"additionalProperties": false
|
|
127
|
+
})],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
assert_eq!(
|
|
131
|
+
plan.bound.branch_scope,
|
|
132
|
+
BranchScope::Active {
|
|
133
|
+
branch_id: "branch1".to_string()
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn plan_sql(sql: &str) -> LogicalWritePlan {
|
|
139
|
+
plan_sql_with_schemas(sql, &[])
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn plan_sql_with_schemas(sql: &str, schemas: &[serde_json::Value]) -> LogicalWritePlan {
|
|
143
|
+
let statement = parse_statement(sql).expect("parse SQL");
|
|
144
|
+
let write = bind_statement(&statement, schemas, "branch1").expect("bind SQL");
|
|
145
|
+
plan_write(write).expect("plan write")
|
|
146
|
+
}
|
|
147
|
+
}
|