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