Skip to main content

icydb_core/db/
mod.rs

1pub(crate) mod access;
2// 1️⃣ Module declarations
3pub(crate) mod consistency;
4pub(crate) mod cursor;
5pub(crate) mod diagnostics;
6pub(in crate::db) mod direction;
7pub(crate) mod identity;
8pub(crate) mod intent;
9pub(in crate::db) mod plan;
10pub(crate) mod policy;
11pub(crate) mod predicate;
12pub(crate) mod query;
13pub(crate) mod registry;
14pub(crate) mod response;
15pub(crate) mod session;
16
17pub(in crate::db) mod codec;
18pub(in crate::db) mod commit;
19pub(in crate::db) mod data;
20pub(in crate::db) mod executor;
21pub(in crate::db) mod index;
22pub(in crate::db) mod lowering;
23pub(in crate::db) mod relation;
24pub(in crate::db) mod value_hash;
25
26// 2️⃣ Public re-exports (Tier-2 API surface)
27pub use codec::cursor::{decode_cursor, encode_cursor};
28pub use consistency::ReadConsistency;
29pub use data::DataStore;
30pub(crate) use data::StorageKey;
31pub use diagnostics::StorageReport;
32pub use executor::{ExecutionAccessPathVariant, ExecutionOptimization, ExecutionTrace};
33pub use identity::{EntityName, IndexName};
34pub use index::IndexStore;
35pub use predicate::{CoercionId, CompareOp, ComparePredicate, Predicate, UnsupportedQueryFeature};
36pub use query::{
37    builder::field::FieldRef,
38    expr::{FilterExpr, SortExpr},
39    fluent::{
40        delete::FluentDeleteQuery,
41        load::{FluentLoadQuery, PagedLoadQuery},
42    },
43    intent::{IntentError, Query, QueryError},
44    plan::{OrderDirection, PlanError},
45    predicate::ValidateError,
46};
47pub use registry::StoreRegistry;
48pub use relation::validate_delete_strong_relations_for_source;
49pub use response::{Response, ResponseError, Row, WriteBatchResponse, WriteResponse};
50pub use session::DbSession;
51#[cfg(test)]
52pub(crate) use value_hash::hash_value;
53
54// 3️⃣ Internal imports (implementation wiring)
55use crate::{
56    db::{
57        commit::{
58            CommitRowOp, PreparedRowCommitOp, ensure_recovered, prepare_row_commit_for_entity,
59        },
60        data::RawDataKey,
61        executor::Context,
62        relation::StrongRelationDeleteValidateFn,
63    },
64    error::InternalError,
65    traits::{CanisterKind, EntityIdentity, EntityKind, EntityValue},
66};
67use std::{collections::BTreeSet, marker::PhantomData, thread::LocalKey};
68
69///
70/// PagedLoadExecution
71///
72/// Cursor-paged load response with optional continuation cursor bytes.
73///
74
75#[derive(Debug)]
76pub struct PagedLoadExecution<E: EntityKind> {
77    response: Response<E>,
78    continuation_cursor: Option<Vec<u8>>,
79}
80
81impl<E: EntityKind> PagedLoadExecution<E> {
82    /// Create a paged load execution payload.
83    #[must_use]
84    pub const fn new(response: Response<E>, continuation_cursor: Option<Vec<u8>>) -> Self {
85        Self {
86            response,
87            continuation_cursor,
88        }
89    }
90
91    /// Borrow the paged response rows.
92    #[must_use]
93    pub const fn response(&self) -> &Response<E> {
94        &self.response
95    }
96
97    /// Borrow the optional continuation cursor bytes.
98    #[must_use]
99    pub fn continuation_cursor(&self) -> Option<&[u8]> {
100        self.continuation_cursor.as_deref()
101    }
102
103    /// Consume this payload and return `(response, continuation_cursor)`.
104    #[must_use]
105    pub fn into_parts(self) -> (Response<E>, Option<Vec<u8>>) {
106        (self.response, self.continuation_cursor)
107    }
108}
109
110impl<E: EntityKind> From<(Response<E>, Option<Vec<u8>>)> for PagedLoadExecution<E> {
111    fn from(value: (Response<E>, Option<Vec<u8>>)) -> Self {
112        let (response, continuation_cursor) = value;
113
114        Self::new(response, continuation_cursor)
115    }
116}
117
118impl<E: EntityKind> From<PagedLoadExecution<E>> for (Response<E>, Option<Vec<u8>>) {
119    fn from(value: PagedLoadExecution<E>) -> Self {
120        value.into_parts()
121    }
122}
123
124///
125/// PagedLoadExecutionWithTrace
126///
127/// Cursor-paged load response plus optional execution trace details.
128///
129
130#[derive(Debug)]
131pub struct PagedLoadExecutionWithTrace<E: EntityKind> {
132    execution: PagedLoadExecution<E>,
133    execution_trace: Option<ExecutionTrace>,
134}
135
136impl<E: EntityKind> PagedLoadExecutionWithTrace<E> {
137    /// Create a traced paged load execution payload.
138    #[must_use]
139    pub const fn new(
140        response: Response<E>,
141        continuation_cursor: Option<Vec<u8>>,
142        execution_trace: Option<ExecutionTrace>,
143    ) -> Self {
144        Self {
145            execution: PagedLoadExecution::new(response, continuation_cursor),
146            execution_trace,
147        }
148    }
149
150    /// Borrow the paged execution payload.
151    #[must_use]
152    pub const fn execution(&self) -> &PagedLoadExecution<E> {
153        &self.execution
154    }
155
156    /// Borrow the paged response rows.
157    #[must_use]
158    pub const fn response(&self) -> &Response<E> {
159        self.execution.response()
160    }
161
162    /// Borrow the optional continuation cursor bytes.
163    #[must_use]
164    pub fn continuation_cursor(&self) -> Option<&[u8]> {
165        self.execution.continuation_cursor()
166    }
167
168    /// Borrow optional execution trace details.
169    #[must_use]
170    pub const fn execution_trace(&self) -> Option<&ExecutionTrace> {
171        self.execution_trace.as_ref()
172    }
173
174    /// Consume this payload and drop trace details.
175    #[must_use]
176    pub fn into_execution(self) -> PagedLoadExecution<E> {
177        self.execution
178    }
179
180    /// Consume this payload and return `(response, continuation_cursor, trace)`.
181    #[must_use]
182    pub fn into_parts(self) -> (Response<E>, Option<Vec<u8>>, Option<ExecutionTrace>) {
183        let (response, continuation_cursor) = self.execution.into_parts();
184
185        (response, continuation_cursor, self.execution_trace)
186    }
187}
188
189impl<E: EntityKind> From<(Response<E>, Option<Vec<u8>>, Option<ExecutionTrace>)>
190    for PagedLoadExecutionWithTrace<E>
191{
192    fn from(value: (Response<E>, Option<Vec<u8>>, Option<ExecutionTrace>)) -> Self {
193        let (response, continuation_cursor, execution_trace) = value;
194
195        Self::new(response, continuation_cursor, execution_trace)
196    }
197}
198
199impl<E: EntityKind> From<PagedLoadExecutionWithTrace<E>>
200    for (Response<E>, Option<Vec<u8>>, Option<ExecutionTrace>)
201{
202    fn from(value: PagedLoadExecutionWithTrace<E>) -> Self {
203        value.into_parts()
204    }
205}
206
207///
208/// Db
209/// A handle to the set of stores registered for a specific canister domain.
210///
211
212pub struct Db<C: CanisterKind> {
213    store: &'static LocalKey<StoreRegistry>,
214    entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
215    _marker: PhantomData<C>,
216}
217
218impl<C: CanisterKind> Db<C> {
219    #[must_use]
220    pub const fn new(store: &'static LocalKey<StoreRegistry>) -> Self {
221        Self::new_with_hooks(store, &[])
222    }
223
224    #[must_use]
225    pub const fn new_with_hooks(
226        store: &'static LocalKey<StoreRegistry>,
227        entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
228    ) -> Self {
229        Self {
230            store,
231            entity_runtime_hooks,
232            _marker: PhantomData,
233        }
234    }
235
236    #[must_use]
237    pub(crate) const fn context<E>(&self) -> Context<'_, E>
238    where
239        E: EntityKind<Canister = C> + EntityValue,
240    {
241        Context::new(self)
242    }
243
244    /// Return a recovery-guarded context for read paths.
245    ///
246    /// This enforces startup recovery and a fast persisted-marker check so reads
247    /// do not proceed while an incomplete commit is pending replay.
248    pub(crate) fn recovered_context<E>(&self) -> Result<Context<'_, E>, InternalError>
249    where
250        E: EntityKind<Canister = C> + EntityValue,
251    {
252        ensure_recovered(self)?;
253
254        Ok(Context::new(self))
255    }
256
257    /// Ensure startup/in-progress commit recovery has been applied.
258    pub(crate) fn ensure_recovered_state(&self) -> Result<(), InternalError> {
259        ensure_recovered(self)
260    }
261
262    pub(crate) fn with_store_registry<R>(&self, f: impl FnOnce(&StoreRegistry) -> R) -> R {
263        self.store.with(|reg| f(reg))
264    }
265
266    pub fn storage_report(
267        &self,
268        name_to_path: &[(&'static str, &'static str)],
269    ) -> Result<StorageReport, InternalError> {
270        diagnostics::storage_report(self, name_to_path)
271    }
272
273    pub(in crate::db) fn prepare_row_commit_op(
274        &self,
275        op: &CommitRowOp,
276    ) -> Result<PreparedRowCommitOp, InternalError> {
277        let hooks = self.runtime_hook_for_entity_path(op.entity_path.as_str())?;
278
279        (hooks.prepare_row_commit)(self, op)
280    }
281
282    // Validate strong relation constraints for delete-selected target keys.
283    pub(crate) fn validate_delete_strong_relations(
284        &self,
285        target_path: &str,
286        deleted_target_keys: &BTreeSet<RawDataKey>,
287    ) -> Result<(), InternalError> {
288        if deleted_target_keys.is_empty() {
289            return Ok(());
290        }
291
292        for hooks in self.entity_runtime_hooks {
293            (hooks.validate_delete_strong_relations)(self, target_path, deleted_target_keys)?;
294        }
295
296        Ok(())
297    }
298}
299
300///
301/// EntityRuntimeHooks
302///
303/// Per-entity runtime callbacks used for commit preparation and delete-side
304/// strong relation validation.
305///
306
307pub struct EntityRuntimeHooks<C: CanisterKind> {
308    pub(crate) entity_name: &'static str,
309    pub(crate) entity_path: &'static str,
310    pub(in crate::db) prepare_row_commit:
311        fn(&Db<C>, &CommitRowOp) -> Result<PreparedRowCommitOp, InternalError>,
312    pub(crate) validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
313}
314
315impl<C: CanisterKind> EntityRuntimeHooks<C> {
316    #[must_use]
317    pub(in crate::db) const fn new(
318        entity_name: &'static str,
319        entity_path: &'static str,
320        prepare_row_commit: fn(&Db<C>, &CommitRowOp) -> Result<PreparedRowCommitOp, InternalError>,
321        validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
322    ) -> Self {
323        Self {
324            entity_name,
325            entity_path,
326            prepare_row_commit,
327            validate_delete_strong_relations,
328        }
329    }
330
331    #[must_use]
332    pub const fn for_entity<E>(
333        validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
334    ) -> Self
335    where
336        E: EntityKind<Canister = C> + EntityValue,
337    {
338        Self::new(
339            <E as EntityIdentity>::ENTITY_NAME,
340            E::PATH,
341            prepare_row_commit_for_entity::<E>,
342            validate_delete_strong_relations,
343        )
344    }
345}
346
347impl<C: CanisterKind> Db<C> {
348    #[must_use]
349    pub(crate) const fn has_runtime_hooks(&self) -> bool {
350        !self.entity_runtime_hooks.is_empty()
351    }
352
353    // Resolve exactly one runtime hook for a persisted entity name.
354    // Duplicate matches are treated as store invariants.
355    pub(crate) fn runtime_hook_for_entity_name(
356        &self,
357        entity_name: &str,
358    ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
359        let mut matched = None;
360        for hooks in self.entity_runtime_hooks {
361            if hooks.entity_name != entity_name {
362                continue;
363            }
364
365            if matched.is_some() {
366                return Err(InternalError::store_invariant(format!(
367                    "duplicate runtime hooks for entity name '{entity_name}'"
368                )));
369            }
370
371            matched = Some(hooks);
372        }
373
374        matched.ok_or_else(|| {
375            InternalError::store_unsupported(format!(
376                "unsupported entity name in data store: '{entity_name}'"
377            ))
378        })
379    }
380
381    // Resolve exactly one runtime hook for a persisted entity path.
382    // Duplicate matches are treated as store invariants.
383    pub(crate) fn runtime_hook_for_entity_path(
384        &self,
385        entity_path: &str,
386    ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
387        let mut matched = None;
388        for hooks in self.entity_runtime_hooks {
389            if hooks.entity_path != entity_path {
390                continue;
391            }
392
393            if matched.is_some() {
394                return Err(InternalError::store_invariant(format!(
395                    "duplicate runtime hooks for entity path '{entity_path}'"
396                )));
397            }
398
399            matched = Some(hooks);
400        }
401
402        matched.ok_or_else(|| InternalError::unsupported_entity_path(entity_path))
403    }
404}
405
406impl<C: CanisterKind> Copy for Db<C> {}
407
408impl<C: CanisterKind> Clone for Db<C> {
409    fn clone(&self) -> Self {
410        *self
411    }
412}