Skip to main content

icydb_core/db/
mod.rs

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