Skip to main content

icydb_core/db/
mod.rs

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