Skip to main content

icydb_core/db/
mod.rs

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