icydb_core/db/
mod.rs

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