icydb_core/db/
mod.rs

1pub mod executor;
2pub mod primitives;
3pub mod query;
4pub mod response;
5pub mod store;
6
7use crate::{
8    Error,
9    db::{
10        executor::{Context, DeleteExecutor, ExecutorError, LoadExecutor, SaveExecutor},
11        query::QueryError,
12        response::ResponseError,
13        store::{DataStoreRegistry, IndexStoreRegistry, StoreError},
14    },
15    serialize::SerializeError,
16    traits::{CanisterKind, EntityKind},
17    visitor::VisitorError,
18};
19use std::{marker::PhantomData, thread::LocalKey};
20use thiserror::Error as ThisError;
21
22///
23/// DbError
24///
25
26#[derive(Debug, ThisError)]
27pub enum DbError {
28    #[error(transparent)]
29    ExecutorError(#[from] ExecutorError),
30
31    #[error(transparent)]
32    QueryError(#[from] QueryError),
33
34    #[error(transparent)]
35    ResponseError(#[from] ResponseError),
36
37    #[error(transparent)]
38    SerializeError(#[from] SerializeError),
39
40    #[error(transparent)]
41    StoreError(#[from] StoreError),
42
43    #[error(transparent)]
44    VisitorError(#[from] VisitorError),
45}
46
47///
48/// Db
49///
50/// A handle to the set of stores registered for a specific canister domain.
51///
52/// - `C` is the [`CanisterKind`] (schema canister marker).
53///
54/// The `Db` acts as the entry point for querying, saving, and deleting entities
55/// within a single canister's store registry.
56///
57
58pub struct Db<C: CanisterKind> {
59    data: &'static LocalKey<DataStoreRegistry>,
60    index: &'static LocalKey<IndexStoreRegistry>,
61    _marker: PhantomData<C>,
62}
63
64impl<C: CanisterKind> Db<C> {
65    #[must_use]
66    pub const fn new(
67        data: &'static LocalKey<DataStoreRegistry>,
68        index: &'static LocalKey<IndexStoreRegistry>,
69    ) -> Self {
70        Self {
71            data,
72            index,
73            _marker: PhantomData,
74        }
75    }
76
77    #[must_use]
78    pub const fn context<E>(&self) -> Context<'_, E>
79    where
80        E: EntityKind<Canister = C>,
81    {
82        Context::new(self)
83    }
84
85    /// Run a closure with read access to the data store registry.
86    pub fn with_data<R>(&self, f: impl FnOnce(&DataStoreRegistry) -> R) -> R {
87        self.data.with(|reg| f(reg))
88    }
89
90    /// Run a closure with read access to the index store registry.
91    pub fn with_index<R>(&self, f: impl FnOnce(&IndexStoreRegistry) -> R) -> R {
92        self.index.with(|reg| f(reg))
93    }
94}
95
96// Manual Copy + Clone implementations.
97// Safe because Db only contains &'static LocalKey<_> handles,
98// duplicating them does not duplicate the contents.
99impl<C: CanisterKind> Copy for Db<C> {}
100
101impl<C: CanisterKind> Clone for Db<C> {
102    fn clone(&self) -> Self {
103        *self
104    }
105}
106
107///
108/// DbSession
109/// Database handle plus a debug flag that controls executor verbosity.
110///
111
112pub struct DbSession<C: CanisterKind> {
113    db: Db<C>,
114    debug: bool,
115}
116
117impl<C: CanisterKind> DbSession<C> {
118    #[must_use]
119    /// Create a new session scoped to the provided database.
120    pub const fn new(db: Db<C>) -> Self {
121        Self { db, debug: false }
122    }
123
124    #[must_use]
125    /// Enable debug logging for subsequent queries in this session.
126    pub const fn debug(mut self) -> Self {
127        self.debug = true;
128        self
129    }
130
131    //
132    // Low-level executors
133    //
134
135    /// Get a [`LoadExecutor`] for building and executing queries that read entities.
136    #[must_use]
137    pub const fn load<E>(&self) -> LoadExecutor<E>
138    where
139        E: EntityKind<Canister = C>,
140    {
141        LoadExecutor::new(self.db, self.debug)
142    }
143
144    /// Get a [`SaveExecutor`] for inserting or updating entities.
145    ///
146    /// Normally you will use the higher-level `create/replace/update` shortcuts instead.
147    #[must_use]
148    pub const fn save<E>(&self) -> SaveExecutor<E>
149    where
150        E: EntityKind<Canister = C>,
151    {
152        SaveExecutor::new(self.db, self.debug)
153    }
154
155    /// Get a [`DeleteExecutor`] for deleting entities by key or query.
156    #[must_use]
157    pub const fn delete<E>(&self) -> DeleteExecutor<E>
158    where
159        E: EntityKind<Canister = C>,
160    {
161        DeleteExecutor::new(self.db, self.debug)
162    }
163
164    //
165    // High-level save shortcuts
166    //
167
168    /// Insert a new entity, returning the stored value.
169    pub fn insert<E>(&self, entity: E) -> Result<E, Error>
170    where
171        E: EntityKind<Canister = C>,
172    {
173        self.save::<E>().insert(entity)
174    }
175
176    /// Replace an existing entity or insert it if it does not yet exist.
177    pub fn replace<E>(&self, entity: E) -> Result<E, Error>
178    where
179        E: EntityKind<Canister = C>,
180    {
181        self.save::<E>().replace(entity)
182    }
183
184    /// Partially update an existing entity.
185    pub fn update<E>(&self, entity: E) -> Result<E, Error>
186    where
187        E: EntityKind<Canister = C>,
188    {
189        self.save::<E>().update(entity)
190    }
191
192    /// Insert a new view value for an entity.
193    pub fn insert_view<E>(&self, view: E::ViewType) -> Result<E::ViewType, Error>
194    where
195        E: EntityKind<Canister = C>,
196    {
197        self.save::<E>().insert_view::<E::ViewType>(view)
198    }
199
200    /// Replace an existing view or insert it if it does not yet exist.
201    pub fn replace_view<E>(&self, view: E::ViewType) -> Result<E::ViewType, Error>
202    where
203        E: EntityKind<Canister = C>,
204    {
205        self.save::<E>().replace_view::<E::ViewType>(view)
206    }
207
208    /// Partially update an existing view.
209    pub fn update_view<E>(&self, view: E::ViewType) -> Result<E::ViewType, Error>
210    where
211        E: EntityKind<Canister = C>,
212    {
213        self.save::<E>().update_view::<E::ViewType>(view)
214    }
215}