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