icydb_core/db/
mod.rs

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