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 relation;
15
16// 2️⃣ Public re-exports (Tier-2 API surface)
17pub use codec::cursor::{decode_cursor, encode_cursor};
18pub use data::DataStore;
19pub(crate) use data::StorageKey;
20pub use diagnostics::StorageReport;
21pub use executor::{ExecutionAccessPathVariant, ExecutionOptimization, ExecutionTrace};
22pub use identity::{EntityName, IndexName};
23pub use index::IndexStore;
24#[cfg(test)]
25pub(crate) use index::hash_value;
26pub use query::{
27    ReadConsistency,
28    builder::field::FieldRef,
29    expr::{FilterExpr, SortExpr},
30    fluent::{
31        delete::FluentDeleteQuery,
32        load::{FluentLoadQuery, PagedLoadQuery},
33    },
34    intent::{IntentError, Query, QueryError},
35    plan::{OrderDirection, PlanError},
36    predicate::{
37        CoercionId, CompareOp, ComparePredicate, Predicate, UnsupportedQueryFeature, ValidateError,
38    },
39};
40pub use registry::StoreRegistry;
41pub use relation::validate_delete_strong_relations_for_source;
42pub use response::{Response, ResponseError, Row, WriteBatchResponse, WriteResponse};
43pub use session::DbSession;
44
45// 3️⃣ Internal imports (implementation wiring)
46use crate::{
47    db::{
48        commit::{
49            CommitRowOp, PreparedRowCommitOp, ensure_recovered, prepare_row_commit_for_entity,
50        },
51        data::RawDataKey,
52        executor::Context,
53        relation::StrongRelationDeleteValidateFn,
54    },
55    error::InternalError,
56    traits::{CanisterKind, EntityIdentity, EntityKind, EntityValue},
57};
58use std::{collections::BTreeSet, marker::PhantomData, thread::LocalKey};
59
60///
61/// PagedLoadExecution
62///
63/// Cursor-paged load response with optional continuation cursor bytes.
64///
65pub type PagedLoadExecution<E> = (Response<E>, Option<Vec<u8>>);
66
67///
68/// PagedLoadExecutionWithTrace
69///
70/// Cursor-paged load response plus optional execution trace details.
71///
72pub type PagedLoadExecutionWithTrace<E> = (Response<E>, Option<Vec<u8>>, Option<ExecutionTrace>);
73
74///
75/// Db
76/// A handle to the set of stores registered for a specific canister domain.
77///
78
79pub struct Db<C: CanisterKind> {
80    store: &'static LocalKey<StoreRegistry>,
81    entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
82    _marker: PhantomData<C>,
83}
84
85impl<C: CanisterKind> Db<C> {
86    #[must_use]
87    pub const fn new(store: &'static LocalKey<StoreRegistry>) -> Self {
88        Self::new_with_hooks(store, &[])
89    }
90
91    #[must_use]
92    pub const fn new_with_hooks(
93        store: &'static LocalKey<StoreRegistry>,
94        entity_runtime_hooks: &'static [EntityRuntimeHooks<C>],
95    ) -> Self {
96        Self {
97            store,
98            entity_runtime_hooks,
99            _marker: PhantomData,
100        }
101    }
102
103    #[must_use]
104    pub(crate) const fn context<E>(&self) -> Context<'_, E>
105    where
106        E: EntityKind<Canister = C> + EntityValue,
107    {
108        Context::new(self)
109    }
110
111    /// Return a recovery-guarded context for read paths.
112    ///
113    /// This enforces startup recovery and a fast persisted-marker check so reads
114    /// do not proceed while an incomplete commit is pending replay.
115    pub(crate) fn recovered_context<E>(&self) -> Result<Context<'_, E>, InternalError>
116    where
117        E: EntityKind<Canister = C> + EntityValue,
118    {
119        ensure_recovered(self)?;
120
121        Ok(Context::new(self))
122    }
123
124    /// Ensure startup/in-progress commit recovery has been applied.
125    pub(crate) fn ensure_recovered_state(&self) -> Result<(), InternalError> {
126        ensure_recovered(self)
127    }
128
129    pub(crate) fn with_store_registry<R>(&self, f: impl FnOnce(&StoreRegistry) -> R) -> R {
130        self.store.with(|reg| f(reg))
131    }
132
133    pub fn storage_report(
134        &self,
135        name_to_path: &[(&'static str, &'static str)],
136    ) -> Result<StorageReport, InternalError> {
137        diagnostics::storage_report(self, name_to_path)
138    }
139
140    pub(in crate::db) fn prepare_row_commit_op(
141        &self,
142        op: &CommitRowOp,
143    ) -> Result<PreparedRowCommitOp, InternalError> {
144        let hooks = self
145            .entity_runtime_hooks
146            .iter()
147            .find(|hooks| hooks.entity_path == op.entity_path.as_str())
148            .ok_or_else(|| InternalError::unsupported_entity_path(op.entity_path.as_str()))?;
149
150        (hooks.prepare_row_commit)(self, op)
151    }
152
153    // Validate strong relation constraints for delete-selected target keys.
154    pub(crate) fn validate_delete_strong_relations(
155        &self,
156        target_path: &str,
157        deleted_target_keys: &BTreeSet<RawDataKey>,
158    ) -> Result<(), InternalError> {
159        if deleted_target_keys.is_empty() {
160            return Ok(());
161        }
162
163        for hooks in self.entity_runtime_hooks {
164            (hooks.validate_delete_strong_relations)(self, target_path, deleted_target_keys)?;
165        }
166
167        Ok(())
168    }
169}
170
171///
172/// EntityRuntimeHooks
173///
174/// Per-entity runtime callbacks used for commit preparation and delete-side
175/// strong relation validation.
176///
177
178pub struct EntityRuntimeHooks<C: CanisterKind> {
179    pub(crate) entity_name: &'static str,
180    pub(crate) entity_path: &'static str,
181    pub(in crate::db) prepare_row_commit:
182        fn(&Db<C>, &CommitRowOp) -> Result<PreparedRowCommitOp, InternalError>,
183    pub(crate) validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
184}
185
186impl<C: CanisterKind> EntityRuntimeHooks<C> {
187    #[must_use]
188    pub(in crate::db) const fn new(
189        entity_name: &'static str,
190        entity_path: &'static str,
191        prepare_row_commit: fn(&Db<C>, &CommitRowOp) -> Result<PreparedRowCommitOp, InternalError>,
192        validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
193    ) -> Self {
194        Self {
195            entity_name,
196            entity_path,
197            prepare_row_commit,
198            validate_delete_strong_relations,
199        }
200    }
201
202    #[must_use]
203    pub const fn for_entity<E>(
204        validate_delete_strong_relations: StrongRelationDeleteValidateFn<C>,
205    ) -> Self
206    where
207        E: EntityKind<Canister = C> + EntityValue,
208    {
209        Self::new(
210            <E as EntityIdentity>::ENTITY_NAME,
211            E::PATH,
212            prepare_row_commit_for_entity::<E>,
213            validate_delete_strong_relations,
214        )
215    }
216}
217
218impl<C: CanisterKind> Db<C> {
219    #[must_use]
220    pub(crate) const fn has_runtime_hooks(&self) -> bool {
221        !self.entity_runtime_hooks.is_empty()
222    }
223
224    pub(crate) fn runtime_hook_for_entity_name(
225        &self,
226        entity_name: &str,
227    ) -> Result<&EntityRuntimeHooks<C>, InternalError> {
228        let mut matched = None;
229        for hooks in self.entity_runtime_hooks {
230            if hooks.entity_name != entity_name {
231                continue;
232            }
233
234            if matched.is_some() {
235                return Err(InternalError::store_invariant(format!(
236                    "duplicate runtime hooks for entity name '{entity_name}'"
237                )));
238            }
239
240            matched = Some(hooks);
241        }
242
243        matched.ok_or_else(|| {
244            InternalError::store_unsupported(format!(
245                "unsupported entity name in data store: '{entity_name}'"
246            ))
247        })
248    }
249}
250
251impl<C: CanisterKind> Copy for Db<C> {}
252
253impl<C: CanisterKind> Clone for Db<C> {
254    fn clone(&self) -> Self {
255        *self
256    }
257}