@lix-js/sdk 0.6.0-preview.1 → 0.6.0-preview.2
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 +305 -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/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 +127 -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/changelog/codec.rs +321 -0
- package/dist-engine-src/src/changelog/context.rs +92 -0
- package/dist-engine-src/src/changelog/materialization.rs +121 -0
- package/dist-engine-src/src/changelog/mod.rs +13 -0
- package/dist-engine-src/src/changelog/reader.rs +20 -0
- package/dist-engine-src/src/changelog/storage.rs +220 -0
- package/dist-engine-src/src/changelog/types.rs +38 -0
- package/dist-engine-src/src/commit_graph/context.rs +1588 -0
- package/dist-engine-src/src/commit_graph/mod.rs +12 -0
- package/dist-engine-src/src/commit_graph/types.rs +145 -0
- package/dist-engine-src/src/commit_graph/walker.rs +780 -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 +135 -0
- package/dist-engine-src/src/common/metadata.rs +35 -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/engine.rs +239 -0
- package/dist-engine-src/src/entity_identity.rs +285 -0
- package/dist-engine-src/src/functions/context.rs +327 -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 +363 -0
- package/dist-engine-src/src/functions/types.rs +37 -0
- package/dist-engine-src/src/init.rs +505 -0
- package/dist-engine-src/src/json_store/compression.rs +77 -0
- package/dist-engine-src/src/json_store/context.rs +129 -0
- package/dist-engine-src/src/json_store/encoded.rs +15 -0
- package/dist-engine-src/src/json_store/mod.rs +9 -0
- package/dist-engine-src/src/json_store/store.rs +236 -0
- package/dist-engine-src/src/json_store/types.rs +52 -0
- package/dist-engine-src/src/lib.rs +61 -0
- package/dist-engine-src/src/live_state/context.rs +2241 -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 +239 -0
- package/dist-engine-src/src/live_state/visibility.rs +218 -0
- package/dist-engine-src/src/plugin/archive.rs +441 -0
- package/dist-engine-src/src/plugin/component.rs +183 -0
- package/dist-engine-src/src/plugin/install.rs +637 -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 +119 -0
- package/dist-engine-src/src/plugin/storage.rs +74 -0
- package/dist-engine-src/src/schema/annotations/defaults.rs +280 -0
- package/dist-engine-src/src/schema/annotations/mod.rs +1 -0
- package/dist-engine-src/src/schema/builtin/lix_account.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_active_account.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_binary_blob_ref.json +30 -0
- package/dist-engine-src/src/schema/builtin/lix_change.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_change_author.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set.json +18 -0
- package/dist-engine-src/src/schema/builtin/lix_change_set_element.json +75 -0
- package/dist-engine-src/src/schema/builtin/lix_commit.json +62 -0
- package/dist-engine-src/src/schema/builtin/lix_commit_edge.json +46 -0
- package/dist-engine-src/src/schema/builtin/lix_directory_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_entity_label.json +63 -0
- package/dist-engine-src/src/schema/builtin/lix_file_descriptor.json +53 -0
- package/dist-engine-src/src/schema/builtin/lix_key_value.json +41 -0
- package/dist-engine-src/src/schema/builtin/lix_label.json +22 -0
- package/dist-engine-src/src/schema/builtin/lix_registered_schema.json +31 -0
- package/dist-engine-src/src/schema/builtin/lix_version_descriptor.json +35 -0
- package/dist-engine-src/src/schema/builtin/lix_version_ref.json +49 -0
- package/dist-engine-src/src/schema/builtin/mod.rs +271 -0
- package/dist-engine-src/src/schema/definition.json +157 -0
- package/dist-engine-src/src/schema/definition.rs +636 -0
- package/dist-engine-src/src/schema/key.rs +206 -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 +739 -0
- package/dist-engine-src/src/schema_registry.rs +294 -0
- package/dist-engine-src/src/session/context.rs +366 -0
- package/dist-engine-src/src/session/create_version.rs +80 -0
- package/dist-engine-src/src/session/execute.rs +447 -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 +62 -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 +437 -0
- package/dist-engine-src/src/session/mod.rs +25 -0
- package/dist-engine-src/src/session/switch_version.rs +121 -0
- package/dist-engine-src/src/sql2/change_provider.rs +337 -0
- package/dist-engine-src/src/sql2/classify.rs +147 -0
- package/dist-engine-src/src/sql2/commit_derived_provider.rs +591 -0
- package/dist-engine-src/src/sql2/context.rs +307 -0
- package/dist-engine-src/src/sql2/directory_history_provider.rs +623 -0
- package/dist-engine-src/src/sql2/directory_provider.rs +2405 -0
- package/dist-engine-src/src/sql2/dml.rs +148 -0
- package/dist-engine-src/src/sql2/entity_history_provider.rs +444 -0
- package/dist-engine-src/src/sql2/entity_provider.rs +2700 -0
- package/dist-engine-src/src/sql2/error.rs +196 -0
- package/dist-engine-src/src/sql2/execute.rs +3379 -0
- package/dist-engine-src/src/sql2/file_history_provider.rs +902 -0
- package/dist-engine-src/src/sql2/file_provider.rs +3254 -0
- package/dist-engine-src/src/sql2/filesystem_planner.rs +1526 -0
- package/dist-engine-src/src/sql2/filesystem_predicates.rs +159 -0
- package/dist-engine-src/src/sql2/filesystem_visibility.rs +369 -0
- package/dist-engine-src/src/sql2/history_projection.rs +80 -0
- package/dist-engine-src/src/sql2/history_provider.rs +418 -0
- package/dist-engine-src/src/sql2/history_route.rs +643 -0
- package/dist-engine-src/src/sql2/lix_state_provider.rs +2430 -0
- package/dist-engine-src/src/sql2/mod.rs +43 -0
- package/dist-engine-src/src/sql2/read_only.rs +65 -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 +135 -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_uuid_v7.rs +76 -0
- package/dist-engine-src/src/sql2/udfs/mod.rs +82 -0
- package/dist-engine-src/src/sql2/version_provider.rs +1187 -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 +3406 -0
- package/dist-engine-src/src/test_support.rs +81 -0
- package/dist-engine-src/src/tracked_state/by_file_index.rs +102 -0
- package/dist-engine-src/src/tracked_state/codec.rs +747 -0
- package/dist-engine-src/src/tracked_state/context.rs +983 -0
- package/dist-engine-src/src/tracked_state/diff.rs +494 -0
- package/dist-engine-src/src/tracked_state/materialization.rs +141 -0
- package/dist-engine-src/src/tracked_state/merge.rs +474 -0
- package/dist-engine-src/src/tracked_state/mod.rs +31 -0
- package/dist-engine-src/src/tracked_state/rebuild.rs +771 -0
- package/dist-engine-src/src/tracked_state/storage.rs +243 -0
- package/dist-engine-src/src/tracked_state/tree.rs +2744 -0
- package/dist-engine-src/src/tracked_state/tree_types.rs +176 -0
- package/dist-engine-src/src/tracked_state/types.rs +61 -0
- package/dist-engine-src/src/transaction/commit.rs +1224 -0
- package/dist-engine-src/src/transaction/context.rs +1307 -0
- package/dist-engine-src/src/transaction/live_state_overlay.rs +34 -0
- package/dist-engine-src/src/transaction/mod.rs +11 -0
- package/dist-engine-src/src/transaction/normalization.rs +1026 -0
- package/dist-engine-src/src/transaction/schema_resolver.rs +127 -0
- package/dist-engine-src/src/transaction/staging.rs +1436 -0
- package/dist-engine-src/src/transaction/types.rs +351 -0
- package/dist-engine-src/src/transaction/validation.rs +4811 -0
- package/dist-engine-src/src/untracked_state/codec.rs +363 -0
- package/dist-engine-src/src/untracked_state/context.rs +82 -0
- package/dist-engine-src/src/untracked_state/materialization.rs +157 -0
- package/dist-engine-src/src/untracked_state/mod.rs +17 -0
- package/dist-engine-src/src/untracked_state/storage.rs +348 -0
- package/dist-engine-src/src/untracked_state/types.rs +96 -0
- package/dist-engine-src/src/version/context.rs +52 -0
- package/dist-engine-src/src/version/mod.rs +12 -0
- package/dist-engine-src/src/version/refs.rs +421 -0
- package/dist-engine-src/src/version/stage_rows.rs +71 -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,327 @@
|
|
|
1
|
+
use crate::functions::{
|
|
2
|
+
state, DeterministicFunctionProvider, DeterministicSequence, FunctionProvider,
|
|
3
|
+
FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
|
|
4
|
+
};
|
|
5
|
+
use crate::json_store::JsonStoreWriter;
|
|
6
|
+
use crate::live_state::{LiveStateReader, LiveStateWriter};
|
|
7
|
+
use crate::storage::{StorageReader, StorageWriteSet};
|
|
8
|
+
use crate::LixError;
|
|
9
|
+
|
|
10
|
+
/// Execution-scoped runtime function context.
|
|
11
|
+
///
|
|
12
|
+
/// Lower layers should only receive function providers. This context owns the
|
|
13
|
+
/// lifecycle at the session/transaction boundary: prepare the right function
|
|
14
|
+
/// source before execution and persist deterministic sequence progress after
|
|
15
|
+
/// successful execution.
|
|
16
|
+
pub(crate) struct FunctionContext {
|
|
17
|
+
functions: FunctionProviderHandle,
|
|
18
|
+
bookkeeping_timestamp: String,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl FunctionContext {
|
|
22
|
+
/// Prepares the runtime function provider for one execution.
|
|
23
|
+
///
|
|
24
|
+
/// If deterministic mode is absent or disabled, the context uses system
|
|
25
|
+
/// functions. If enabled, it starts from the persisted sequence + 1.
|
|
26
|
+
pub(crate) async fn prepare(live_state: &dyn LiveStateReader) -> Result<Self, LixError> {
|
|
27
|
+
let mode = state::load_mode(live_state).await?;
|
|
28
|
+
let mut bookkeeping_functions = SystemFunctionProvider;
|
|
29
|
+
let bookkeeping_timestamp = bookkeeping_functions.timestamp();
|
|
30
|
+
if !mode.enabled {
|
|
31
|
+
return Ok(Self {
|
|
32
|
+
functions: SharedFunctionProvider::new(
|
|
33
|
+
Box::new(SystemFunctionProvider) as Box<dyn FunctionProvider + Send>
|
|
34
|
+
),
|
|
35
|
+
bookkeeping_timestamp,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let sequence = state::load_sequence(live_state).await?;
|
|
40
|
+
Ok(Self {
|
|
41
|
+
functions: SharedFunctionProvider::new(Box::new(DeterministicFunctionProvider::new(
|
|
42
|
+
sequence.next_sequence(),
|
|
43
|
+
mode.timestamp_shuffle,
|
|
44
|
+
))
|
|
45
|
+
as Box<dyn FunctionProvider + Send>),
|
|
46
|
+
bookkeeping_timestamp,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Returns the engine2-owned provider used by SQL and transaction staging.
|
|
51
|
+
pub(crate) fn provider(&self) -> FunctionProviderHandle {
|
|
52
|
+
self.functions.clone()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// Persists deterministic sequence progress if this execution used any.
|
|
56
|
+
///
|
|
57
|
+
/// System functions report no sequence state, so this is a no-op when
|
|
58
|
+
/// deterministic mode is disabled.
|
|
59
|
+
pub(crate) async fn stage_persist_if_needed<S>(
|
|
60
|
+
&self,
|
|
61
|
+
writer: &mut LiveStateWriter<S>,
|
|
62
|
+
writes: &mut StorageWriteSet,
|
|
63
|
+
json_writer: &mut JsonStoreWriter,
|
|
64
|
+
) -> Result<(), LixError>
|
|
65
|
+
where
|
|
66
|
+
S: StorageReader,
|
|
67
|
+
{
|
|
68
|
+
let Some(highest_seen) = self.functions.deterministic_sequence_persist_highest_seen()
|
|
69
|
+
else {
|
|
70
|
+
return Ok(());
|
|
71
|
+
};
|
|
72
|
+
state::stage_sequence(
|
|
73
|
+
writer,
|
|
74
|
+
writes,
|
|
75
|
+
json_writer,
|
|
76
|
+
DeterministicSequence { highest_seen },
|
|
77
|
+
&self.bookkeeping_timestamp,
|
|
78
|
+
)
|
|
79
|
+
.await
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[cfg(test)]
|
|
84
|
+
mod tests {
|
|
85
|
+
use std::sync::Arc;
|
|
86
|
+
|
|
87
|
+
use crate::backend::testing::UnitTestBackend;
|
|
88
|
+
use crate::functions::state::{DETERMINISTIC_MODE_KEY, DETERMINISTIC_SEQUENCE_KEY};
|
|
89
|
+
use crate::functions::{state::load_sequence, DeterministicSequence};
|
|
90
|
+
use crate::live_state::{LiveStateContext, LiveStateRow};
|
|
91
|
+
use crate::storage::StorageContext;
|
|
92
|
+
use crate::GLOBAL_VERSION_ID;
|
|
93
|
+
|
|
94
|
+
use super::*;
|
|
95
|
+
|
|
96
|
+
fn live_state_context() -> LiveStateContext {
|
|
97
|
+
LiveStateContext::new(
|
|
98
|
+
crate::tracked_state::TrackedStateContext::new(),
|
|
99
|
+
crate::untracked_state::UntrackedStateContext::new(),
|
|
100
|
+
crate::commit_graph::CommitGraphContext::new(crate::changelog::ChangelogContext::new()),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[tokio::test]
|
|
105
|
+
async fn prepare_uses_system_functions_when_mode_missing() {
|
|
106
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
107
|
+
let storage = StorageContext::new(backend.clone());
|
|
108
|
+
let live_state = live_state_context();
|
|
109
|
+
let reader = live_state.reader(storage.clone());
|
|
110
|
+
|
|
111
|
+
let context = FunctionContext::prepare(&reader)
|
|
112
|
+
.await
|
|
113
|
+
.expect("runtime context should prepare");
|
|
114
|
+
|
|
115
|
+
assert_eq!(
|
|
116
|
+
context
|
|
117
|
+
.provider()
|
|
118
|
+
.deterministic_sequence_persist_highest_seen(),
|
|
119
|
+
None
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[tokio::test]
|
|
124
|
+
async fn prepare_starts_deterministic_functions_at_sequence_zero() {
|
|
125
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
126
|
+
let storage = StorageContext::new(backend.clone());
|
|
127
|
+
let live_state = live_state_context();
|
|
128
|
+
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
129
|
+
write_key_value(
|
|
130
|
+
storage.clone(),
|
|
131
|
+
&live_state,
|
|
132
|
+
DETERMINISTIC_MODE_KEY,
|
|
133
|
+
serde_json::json!({
|
|
134
|
+
"enabled": true,
|
|
135
|
+
}),
|
|
136
|
+
)
|
|
137
|
+
.await;
|
|
138
|
+
|
|
139
|
+
let reader = live_state.reader(storage.clone());
|
|
140
|
+
let context = FunctionContext::prepare(&reader)
|
|
141
|
+
.await
|
|
142
|
+
.expect("runtime context should prepare");
|
|
143
|
+
let functions = context.provider();
|
|
144
|
+
|
|
145
|
+
assert_eq!(
|
|
146
|
+
functions.call_uuid_v7(),
|
|
147
|
+
"01920000-0000-7000-8000-000000000000"
|
|
148
|
+
);
|
|
149
|
+
assert_eq!(functions.call_timestamp(), "1970-01-01T00:00:00.001Z");
|
|
150
|
+
assert_eq!(
|
|
151
|
+
context
|
|
152
|
+
.provider()
|
|
153
|
+
.deterministic_sequence_persist_highest_seen(),
|
|
154
|
+
Some(1)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#[tokio::test]
|
|
159
|
+
async fn prepare_continues_from_persisted_sequence() {
|
|
160
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
161
|
+
let storage = StorageContext::new(backend.clone());
|
|
162
|
+
let live_state = live_state_context();
|
|
163
|
+
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
164
|
+
write_key_value(
|
|
165
|
+
storage.clone(),
|
|
166
|
+
&live_state,
|
|
167
|
+
DETERMINISTIC_MODE_KEY,
|
|
168
|
+
serde_json::json!({
|
|
169
|
+
"enabled": true,
|
|
170
|
+
}),
|
|
171
|
+
)
|
|
172
|
+
.await;
|
|
173
|
+
write_key_value(
|
|
174
|
+
storage.clone(),
|
|
175
|
+
&live_state,
|
|
176
|
+
DETERMINISTIC_SEQUENCE_KEY,
|
|
177
|
+
serde_json::json!(41),
|
|
178
|
+
)
|
|
179
|
+
.await;
|
|
180
|
+
|
|
181
|
+
let reader = live_state.reader(storage.clone());
|
|
182
|
+
let context = FunctionContext::prepare(&reader)
|
|
183
|
+
.await
|
|
184
|
+
.expect("runtime context should prepare");
|
|
185
|
+
let functions = context.provider();
|
|
186
|
+
|
|
187
|
+
assert_eq!(
|
|
188
|
+
functions.call_uuid_v7(),
|
|
189
|
+
"01920000-0000-7000-8000-00000000002a"
|
|
190
|
+
);
|
|
191
|
+
assert_eq!(
|
|
192
|
+
context
|
|
193
|
+
.provider()
|
|
194
|
+
.deterministic_sequence_persist_highest_seen(),
|
|
195
|
+
Some(42)
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
#[tokio::test]
|
|
200
|
+
async fn persist_if_needed_writes_sequence_when_deterministic_functions_advanced() {
|
|
201
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
202
|
+
let storage = StorageContext::new(backend.clone());
|
|
203
|
+
let live_state = live_state_context();
|
|
204
|
+
crate::test_support::seed_global_version_head(storage.clone()).await;
|
|
205
|
+
write_key_value(
|
|
206
|
+
storage.clone(),
|
|
207
|
+
&live_state,
|
|
208
|
+
DETERMINISTIC_MODE_KEY,
|
|
209
|
+
serde_json::json!({
|
|
210
|
+
"enabled": true,
|
|
211
|
+
}),
|
|
212
|
+
)
|
|
213
|
+
.await;
|
|
214
|
+
|
|
215
|
+
let context = {
|
|
216
|
+
let reader = live_state.reader(storage.clone());
|
|
217
|
+
FunctionContext::prepare(&reader)
|
|
218
|
+
.await
|
|
219
|
+
.expect("runtime context should prepare")
|
|
220
|
+
};
|
|
221
|
+
context.provider().call_uuid_v7();
|
|
222
|
+
|
|
223
|
+
let mut tx = storage
|
|
224
|
+
.begin_write_transaction()
|
|
225
|
+
.await
|
|
226
|
+
.expect("transaction should open");
|
|
227
|
+
let mut writes = StorageWriteSet::new();
|
|
228
|
+
let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
|
|
229
|
+
context
|
|
230
|
+
.stage_persist_if_needed(
|
|
231
|
+
&mut live_state.writer(tx.as_mut()),
|
|
232
|
+
&mut writes,
|
|
233
|
+
&mut json_writer,
|
|
234
|
+
)
|
|
235
|
+
.await
|
|
236
|
+
.expect("sequence should stage");
|
|
237
|
+
writes
|
|
238
|
+
.apply(&mut tx.as_mut())
|
|
239
|
+
.await
|
|
240
|
+
.expect("sequence should apply");
|
|
241
|
+
tx.commit().await.expect("transaction should commit");
|
|
242
|
+
|
|
243
|
+
let reader = live_state.reader(storage.clone());
|
|
244
|
+
let sequence = load_sequence(&reader).await.expect("sequence should load");
|
|
245
|
+
assert_eq!(sequence, DeterministicSequence { highest_seen: 0 });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
#[tokio::test]
|
|
249
|
+
async fn persist_if_needed_is_noop_for_system_functions() {
|
|
250
|
+
let backend = Arc::new(UnitTestBackend::new());
|
|
251
|
+
let storage = StorageContext::new(backend.clone());
|
|
252
|
+
let live_state = live_state_context();
|
|
253
|
+
let reader = live_state.reader(storage.clone());
|
|
254
|
+
let context = FunctionContext::prepare(&reader)
|
|
255
|
+
.await
|
|
256
|
+
.expect("runtime context should prepare");
|
|
257
|
+
|
|
258
|
+
let mut tx = storage
|
|
259
|
+
.begin_write_transaction()
|
|
260
|
+
.await
|
|
261
|
+
.expect("transaction should open");
|
|
262
|
+
let mut writes = StorageWriteSet::new();
|
|
263
|
+
let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
|
|
264
|
+
context
|
|
265
|
+
.stage_persist_if_needed(
|
|
266
|
+
&mut live_state.writer(tx.as_mut()),
|
|
267
|
+
&mut writes,
|
|
268
|
+
&mut json_writer,
|
|
269
|
+
)
|
|
270
|
+
.await
|
|
271
|
+
.expect("persist should no-op");
|
|
272
|
+
assert!(writes.is_empty());
|
|
273
|
+
tx.commit().await.expect("transaction should commit");
|
|
274
|
+
|
|
275
|
+
let reader = live_state.reader(storage.clone());
|
|
276
|
+
let sequence = load_sequence(&reader)
|
|
277
|
+
.await
|
|
278
|
+
.expect("missing sequence should load");
|
|
279
|
+
assert_eq!(sequence, DeterministicSequence::uninitialized());
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
async fn write_key_value(
|
|
283
|
+
storage: StorageContext,
|
|
284
|
+
live_state: &LiveStateContext,
|
|
285
|
+
key: &str,
|
|
286
|
+
value: serde_json::Value,
|
|
287
|
+
) {
|
|
288
|
+
let mut tx = storage
|
|
289
|
+
.begin_write_transaction()
|
|
290
|
+
.await
|
|
291
|
+
.expect("transaction should open");
|
|
292
|
+
let snapshot_content = serde_json::to_string(&serde_json::json!({
|
|
293
|
+
"key": key,
|
|
294
|
+
"value": value,
|
|
295
|
+
}))
|
|
296
|
+
.expect("snapshot should serialize");
|
|
297
|
+
let row = LiveStateRow {
|
|
298
|
+
entity_id: crate::entity_identity::EntityIdentity::single(key),
|
|
299
|
+
schema_key: "lix_key_value".to_string(),
|
|
300
|
+
file_id: None,
|
|
301
|
+
snapshot_content: Some(snapshot_content),
|
|
302
|
+
metadata: None,
|
|
303
|
+
schema_version: "1".to_string(),
|
|
304
|
+
created_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
305
|
+
updated_at: "1970-01-01T00:00:00.000Z".to_string(),
|
|
306
|
+
global: true,
|
|
307
|
+
change_id: None,
|
|
308
|
+
commit_id: None,
|
|
309
|
+
untracked: true,
|
|
310
|
+
version_id: GLOBAL_VERSION_ID.to_string(),
|
|
311
|
+
};
|
|
312
|
+
let mut writes = StorageWriteSet::new();
|
|
313
|
+
let mut json_writer = crate::json_store::JsonStoreContext::new().writer();
|
|
314
|
+
{
|
|
315
|
+
let mut writer = live_state.writer(tx.as_mut());
|
|
316
|
+
writer
|
|
317
|
+
.stage_rows(&mut writes, &mut json_writer, &[row])
|
|
318
|
+
.await
|
|
319
|
+
.expect("test key-value should stage");
|
|
320
|
+
}
|
|
321
|
+
writes
|
|
322
|
+
.apply(&mut tx.as_mut())
|
|
323
|
+
.await
|
|
324
|
+
.expect("test key-value should apply");
|
|
325
|
+
tx.commit().await.expect("transaction should commit");
|
|
326
|
+
}
|
|
327
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
use crate::functions::FunctionProvider;
|
|
2
|
+
|
|
3
|
+
const DETERMINISTIC_UUID_COUNTER_MASK: u64 = 0x0000_FFFF_FFFF_FFFF;
|
|
4
|
+
|
|
5
|
+
/// Deterministic function provider for engine2 execution.
|
|
6
|
+
///
|
|
7
|
+
/// The provider is pure runtime state: it does not load or persist the sequence
|
|
8
|
+
/// itself. Session/transaction code owns that boundary so tests can decide when
|
|
9
|
+
/// deterministic state is read and written.
|
|
10
|
+
#[derive(Debug, Clone)]
|
|
11
|
+
pub(crate) struct DeterministicFunctionProvider {
|
|
12
|
+
next_sequence: i64,
|
|
13
|
+
timestamp_shuffle: bool,
|
|
14
|
+
highest_seen: Option<i64>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
impl DeterministicFunctionProvider {
|
|
18
|
+
pub(crate) fn new(next_sequence: i64, timestamp_shuffle: bool) -> Self {
|
|
19
|
+
Self {
|
|
20
|
+
next_sequence,
|
|
21
|
+
timestamp_shuffle,
|
|
22
|
+
highest_seen: None,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
pub(crate) fn highest_seen(&self) -> Option<i64> {
|
|
27
|
+
self.highest_seen
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn take_sequence(&mut self) -> i64 {
|
|
31
|
+
let current = self.next_sequence;
|
|
32
|
+
self.next_sequence += 1;
|
|
33
|
+
self.highest_seen = Some(current);
|
|
34
|
+
current
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl FunctionProvider for DeterministicFunctionProvider {
|
|
39
|
+
fn uuid_v7(&mut self) -> String {
|
|
40
|
+
let counter = self.take_sequence();
|
|
41
|
+
let counter_bits = (counter as u64) & DETERMINISTIC_UUID_COUNTER_MASK;
|
|
42
|
+
format!("01920000-0000-7000-8000-{counter_bits:012x}")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn timestamp(&mut self) -> String {
|
|
46
|
+
let counter = self.take_sequence();
|
|
47
|
+
let millis = if self.timestamp_shuffle {
|
|
48
|
+
shuffled_timestamp_millis(counter)
|
|
49
|
+
} else {
|
|
50
|
+
counter
|
|
51
|
+
};
|
|
52
|
+
let dt = chrono::DateTime::<chrono::Utc>::from_timestamp_millis(millis)
|
|
53
|
+
.unwrap_or(chrono::DateTime::<chrono::Utc>::UNIX_EPOCH);
|
|
54
|
+
dt.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
|
|
58
|
+
self.highest_seen()
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fn shuffled_timestamp_millis(counter: i64) -> i64 {
|
|
63
|
+
const WINDOW: i64 = 1000;
|
|
64
|
+
const MULTIPLIER: i64 = 733;
|
|
65
|
+
const OFFSET: i64 = 271;
|
|
66
|
+
|
|
67
|
+
let cycle = counter.div_euclid(WINDOW);
|
|
68
|
+
let within = counter.rem_euclid(WINDOW);
|
|
69
|
+
let shuffled = (within * MULTIPLIER + OFFSET).rem_euclid(WINDOW);
|
|
70
|
+
cycle * WINDOW + shuffled
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#[cfg(test)]
|
|
74
|
+
mod tests {
|
|
75
|
+
use super::*;
|
|
76
|
+
use crate::functions::DeterministicSequence;
|
|
77
|
+
|
|
78
|
+
#[test]
|
|
79
|
+
fn deterministic_uuid_uses_sequence_counter() {
|
|
80
|
+
let mut provider = DeterministicFunctionProvider::new(0, false);
|
|
81
|
+
|
|
82
|
+
assert_eq!(provider.uuid_v7(), "01920000-0000-7000-8000-000000000000");
|
|
83
|
+
assert_eq!(provider.uuid_v7(), "01920000-0000-7000-8000-000000000001");
|
|
84
|
+
assert_eq!(provider.highest_seen(), Some(1));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[test]
|
|
88
|
+
fn deterministic_timestamp_uses_sequence_counter() {
|
|
89
|
+
let mut provider = DeterministicFunctionProvider::new(1, false);
|
|
90
|
+
|
|
91
|
+
assert_eq!(provider.timestamp(), "1970-01-01T00:00:00.001Z");
|
|
92
|
+
assert_eq!(provider.highest_seen(), Some(1));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[test]
|
|
96
|
+
fn deterministic_timestamp_shuffle_can_be_non_monotonic() {
|
|
97
|
+
let mut provider = DeterministicFunctionProvider::new(0, true);
|
|
98
|
+
let first = provider.timestamp();
|
|
99
|
+
let second = provider.timestamp();
|
|
100
|
+
|
|
101
|
+
assert!(second < first);
|
|
102
|
+
assert_eq!(provider.highest_seen(), Some(1));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[test]
|
|
106
|
+
fn deterministic_sequence_can_start_after_persisted_highest_seen() {
|
|
107
|
+
let sequence = DeterministicSequence { highest_seen: 41 };
|
|
108
|
+
let mut provider = DeterministicFunctionProvider::new(sequence.next_sequence(), false);
|
|
109
|
+
|
|
110
|
+
assert_eq!(provider.uuid_v7(), "01920000-0000-7000-8000-00000000002a");
|
|
111
|
+
assert_eq!(provider.highest_seen(), Some(42));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//! Engine2 runtime function boundary.
|
|
2
|
+
//!
|
|
3
|
+
//! Sessions prepare one function context per execution. SQL, providers, and
|
|
4
|
+
//! transaction staging receive only a function provider; deterministic mode is
|
|
5
|
+
//! resolved privately inside this module.
|
|
6
|
+
|
|
7
|
+
mod context;
|
|
8
|
+
mod deterministic;
|
|
9
|
+
mod provider;
|
|
10
|
+
mod state;
|
|
11
|
+
mod types;
|
|
12
|
+
|
|
13
|
+
pub(crate) use context::FunctionContext;
|
|
14
|
+
pub(crate) use deterministic::DeterministicFunctionProvider;
|
|
15
|
+
pub(crate) use provider::{
|
|
16
|
+
FunctionProvider, FunctionProviderHandle, SharedFunctionProvider, SystemFunctionProvider,
|
|
17
|
+
};
|
|
18
|
+
pub(crate) use types::{DeterministicMode, DeterministicSequence};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
use std::sync::{Arc, Mutex};
|
|
2
|
+
|
|
3
|
+
use crate::cel::CelFunctionProvider;
|
|
4
|
+
|
|
5
|
+
/// Engine2-owned runtime function provider trait.
|
|
6
|
+
pub(crate) trait FunctionProvider: Send {
|
|
7
|
+
fn uuid_v7(&mut self) -> String;
|
|
8
|
+
fn timestamp(&mut self) -> String;
|
|
9
|
+
|
|
10
|
+
fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
|
|
11
|
+
None
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub(crate) type FunctionProviderHandle = SharedFunctionProvider<Box<dyn FunctionProvider + Send>>;
|
|
16
|
+
|
|
17
|
+
/// Shareable function provider used across SQL planning, UDFs, and staging.
|
|
18
|
+
pub(crate) struct SharedFunctionProvider<P> {
|
|
19
|
+
inner: Arc<Mutex<P>>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
impl<P> Clone for SharedFunctionProvider<P> {
|
|
23
|
+
fn clone(&self) -> Self {
|
|
24
|
+
Self {
|
|
25
|
+
inner: Arc::clone(&self.inner),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl<P> SharedFunctionProvider<P> {
|
|
31
|
+
pub(crate) fn new(provider: P) -> Self {
|
|
32
|
+
Self {
|
|
33
|
+
inner: Arc::new(Mutex::new(provider)),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
fn with_lock<R>(&self, f: impl FnOnce(&P) -> R) -> R {
|
|
38
|
+
let guard = self
|
|
39
|
+
.inner
|
|
40
|
+
.lock()
|
|
41
|
+
.expect("engine2 function provider mutex poisoned");
|
|
42
|
+
f(&guard)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
fn with_lock_mut<R>(&self, f: impl FnOnce(&mut P) -> R) -> R {
|
|
46
|
+
let mut guard = self
|
|
47
|
+
.inner
|
|
48
|
+
.lock()
|
|
49
|
+
.expect("engine2 function provider mutex poisoned");
|
|
50
|
+
f(&mut guard)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
impl<P> SharedFunctionProvider<P>
|
|
55
|
+
where
|
|
56
|
+
P: FunctionProvider,
|
|
57
|
+
{
|
|
58
|
+
pub(crate) fn call_uuid_v7(&self) -> String {
|
|
59
|
+
self.with_lock_mut(|provider| provider.uuid_v7())
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub(crate) fn call_timestamp(&self) -> String {
|
|
63
|
+
self.with_lock_mut(|provider| provider.timestamp())
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub(crate) fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
|
|
67
|
+
self.with_lock(|provider| provider.deterministic_sequence_persist_highest_seen())
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
impl<P> CelFunctionProvider for SharedFunctionProvider<P>
|
|
72
|
+
where
|
|
73
|
+
P: FunctionProvider + Send + 'static,
|
|
74
|
+
{
|
|
75
|
+
fn call_uuid_v7(&self) -> String {
|
|
76
|
+
SharedFunctionProvider::call_uuid_v7(self)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
fn call_timestamp(&self) -> String {
|
|
80
|
+
SharedFunctionProvider::call_timestamp(self)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
impl<P> FunctionProvider for SharedFunctionProvider<P>
|
|
85
|
+
where
|
|
86
|
+
P: FunctionProvider,
|
|
87
|
+
{
|
|
88
|
+
fn uuid_v7(&mut self) -> String {
|
|
89
|
+
self.call_uuid_v7()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn timestamp(&mut self) -> String {
|
|
93
|
+
self.call_timestamp()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
|
|
97
|
+
SharedFunctionProvider::deterministic_sequence_persist_highest_seen(self)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
impl<T> FunctionProvider for Box<T>
|
|
102
|
+
where
|
|
103
|
+
T: FunctionProvider + ?Sized,
|
|
104
|
+
{
|
|
105
|
+
fn uuid_v7(&mut self) -> String {
|
|
106
|
+
(**self).uuid_v7()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn timestamp(&mut self) -> String {
|
|
110
|
+
(**self).timestamp()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn deterministic_sequence_persist_highest_seen(&self) -> Option<i64> {
|
|
114
|
+
(**self).deterministic_sequence_persist_highest_seen()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// System-backed engine2 function provider.
|
|
119
|
+
#[derive(Debug, Default, Clone, Copy)]
|
|
120
|
+
pub(crate) struct SystemFunctionProvider;
|
|
121
|
+
|
|
122
|
+
impl FunctionProvider for SystemFunctionProvider {
|
|
123
|
+
fn uuid_v7(&mut self) -> String {
|
|
124
|
+
uuid::Uuid::now_v7().to_string()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
fn timestamp(&mut self) -> String {
|
|
128
|
+
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
|
|
129
|
+
}
|
|
130
|
+
}
|