Skip to main content

icydb_core/db/
mod.rs

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