Skip to main content

icydb_core/db/
mod.rs

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