Skip to main content

icydb_core/db/query/
session.rs

1use crate::{
2    db::{
3        DbSession,
4        query::{
5            Query, QueryError,
6            plan::{ExecutablePlan, ExplainPlan},
7            predicate::Predicate,
8        },
9        response::Response,
10    },
11    key::Key,
12    traits::{CanisterKind, EntityKind},
13    view::View,
14};
15
16///
17/// SessionLoadQuery
18///
19/// Fluent, session-bound load query wrapper that keeps intent pure
20/// while routing execution through the `DbSession` boundary.
21///
22
23pub struct SessionLoadQuery<'a, C: CanisterKind, E: EntityKind<Canister = C>> {
24    session: &'a DbSession<C>,
25    query: Query<E>,
26}
27
28impl<'a, C: CanisterKind, E: EntityKind<Canister = C>> SessionLoadQuery<'a, C, E> {
29    pub(crate) const fn new(session: &'a DbSession<C>, query: Query<E>) -> Self {
30        Self { session, query }
31    }
32
33    // ------------------------------------------------------------------
34    // Intent inspection
35    // ------------------------------------------------------------------
36
37    /// Return a reference to the underlying query intent.
38    #[must_use]
39    pub const fn query(&self) -> &Query<E> {
40        &self.query
41    }
42
43    // ------------------------------------------------------------------
44    // Intent builders (pure, no execution)
45    // ------------------------------------------------------------------
46
47    /// Filter by primary key.
48    #[must_use]
49    pub fn key(mut self, key: impl Into<Key>) -> Self {
50        self.query = self.query.by_key(key.into());
51        self
52    }
53
54    /// Load multiple entities by primary key.
55    ///
56    /// Semantics:
57    /// - Equivalent to `WHERE pk IN (…)`
58    /// - Uses key-based access (ByKey / ByKeys)
59    /// - Missing keys are ignored in MissingOk mode
60    /// - Strict mode treats missing rows as corruption
61    #[must_use]
62    pub fn many<I>(mut self, keys: I) -> Self
63    where
64        I: IntoIterator<Item = E::PrimaryKey>,
65    {
66        self.query = self.query.by_keys(keys.into_iter().map(Into::into));
67        self
68    }
69
70    /// Add a predicate, implicitly AND-ing with any existing predicate.
71    #[must_use]
72    pub fn filter(mut self, predicate: Predicate) -> Self {
73        self.query = self.query.filter(predicate);
74        self
75    }
76
77    /// Append an ascending sort key.
78    #[must_use]
79    pub fn order_by(mut self, field: impl AsRef<str>) -> Self {
80        self.query = self.query.order_by(field);
81        self
82    }
83
84    /// Append a descending sort key.
85    #[must_use]
86    pub fn order_by_desc(mut self, field: impl AsRef<str>) -> Self {
87        self.query = self.query.order_by_desc(field);
88        self
89    }
90
91    /// Apply a load limit to bound result size.
92    #[must_use]
93    pub fn limit(mut self, limit: u32) -> Self {
94        self.query = self.query.limit(limit);
95        self
96    }
97
98    /// Apply a load offset.
99    #[must_use]
100    pub fn offset(mut self, offset: u64) -> Self {
101        self.query = self.query.offset(offset);
102        self
103    }
104
105    // ------------------------------------------------------------------
106    // Planning / diagnostics
107    // ------------------------------------------------------------------
108
109    /// Explain this query without executing it.
110    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
111        self.query.explain()
112    }
113
114    /// Plan this query into an executor-ready plan.
115    pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
116        self.query.plan()
117    }
118
119    // ------------------------------------------------------------------
120    // Execution (single boundary)
121    // ------------------------------------------------------------------
122
123    /// Execute this query using the session's policy settings.
124    pub fn execute(&self) -> Result<Response<E>, QueryError> {
125        self.session.execute_query(self.query())
126    }
127
128    // ------------------------------------------------------------------
129    // Execution terminals (interpretation)
130    // ------------------------------------------------------------------
131
132    /// Return whether any rows match this query.
133    pub fn exists(&self) -> Result<bool, QueryError> {
134        Ok(self.count()? > 0)
135    }
136
137    /// Execute and return the number of matching rows.
138    pub fn count(&self) -> Result<u64, QueryError> {
139        Ok(self.execute()?.count())
140    }
141
142    /// Execute and return all entities.
143    pub fn all(&self) -> Result<Vec<E>, QueryError> {
144        Ok(self.execute()?.entities())
145    }
146
147    /// Execute and return all results as views.
148    pub fn views(&self) -> Result<Vec<View<E>>, QueryError> {
149        Ok(self.execute()?.views())
150    }
151
152    /// Execute and require exactly one entity.
153    pub fn one(&self) -> Result<E, QueryError> {
154        self.execute()?.entity().map_err(QueryError::Execute)
155    }
156
157    /// Execute and require exactly one view.
158    pub fn view(&self) -> Result<View<E>, QueryError> {
159        self.execute()?.view().map_err(QueryError::Execute)
160    }
161
162    /// Execute and return zero or one entity.
163    pub fn one_opt(&self) -> Result<Option<E>, QueryError> {
164        self.execute()?.try_entity().map_err(QueryError::Execute)
165    }
166
167    /// Execute and return zero or one view.
168    pub fn view_opt(&self) -> Result<Option<View<E>>, QueryError> {
169        self.execute()?.view_opt().map_err(QueryError::Execute)
170    }
171}
172
173impl<C: CanisterKind, E: EntityKind<Canister = C, PrimaryKey = ()>> SessionLoadQuery<'_, C, E> {
174    /// Load the singleton entity identified by the unit primary key `()`.
175    ///
176    /// Semantics:
177    /// - Equivalent to `WHERE pk = ()`
178    /// - Uses key-based access (ByKey)
179    /// - Does not allow predicates
180    /// - MissingOk mode returns empty
181    /// - Strict mode treats missing row as corruption
182    #[must_use]
183    pub fn only(mut self) -> Self {
184        self.query = self.query.only();
185        self
186    }
187}
188
189///
190/// SessionDeleteQuery
191///
192/// Fluent, session-bound delete query wrapper that keeps query intent pure
193/// while routing execution through the `DbSession` boundary.
194///
195
196pub struct SessionDeleteQuery<'a, C: CanisterKind, E: EntityKind<Canister = C>> {
197    session: &'a DbSession<C>,
198    query: Query<E>,
199}
200
201impl<'a, C: CanisterKind, E: EntityKind<Canister = C>> SessionDeleteQuery<'a, C, E> {
202    pub(crate) const fn new(session: &'a DbSession<C>, query: Query<E>) -> Self {
203        Self { session, query }
204    }
205
206    // ------------------------------------------------------------------
207    // Intent inspection
208    // ------------------------------------------------------------------
209
210    #[must_use]
211    pub const fn query(&self) -> &Query<E> {
212        &self.query
213    }
214
215    // ------------------------------------------------------------------
216    // Intent builders
217    // ------------------------------------------------------------------
218
219    /// Delete by primary key.
220    #[must_use]
221    pub fn key(mut self, key: impl Into<Key>) -> Self {
222        self.query = self.query.by_key(key.into());
223        self
224    }
225
226    /// Delete multiple entities by primary key.
227    ///
228    /// Semantics:
229    /// - Equivalent to `WHERE pk IN (…)`
230    /// - Uses key-based access (ByKey / ByKeys)
231    /// - Missing keys are ignored in MissingOk mode
232    /// - Strict mode treats missing rows as corruption
233    #[must_use]
234    pub fn many<I>(mut self, keys: I) -> Self
235    where
236        I: IntoIterator<Item = E::PrimaryKey>,
237    {
238        self.query = self.query.by_keys(keys.into_iter().map(Into::into));
239        self
240    }
241
242    /// Add a predicate, implicitly AND-ing with any existing predicate.
243    #[must_use]
244    pub fn filter(mut self, predicate: Predicate) -> Self {
245        self.query = self.query.filter(predicate);
246        self
247    }
248
249    /// Append an ascending sort key.
250    #[must_use]
251    pub fn order_by(mut self, field: impl AsRef<str>) -> Self {
252        self.query = self.query.order_by(field);
253        self
254    }
255
256    /// Append a descending sort key.
257    #[must_use]
258    pub fn order_by_desc(mut self, field: impl AsRef<str>) -> Self {
259        self.query = self.query.order_by_desc(field);
260        self
261    }
262
263    /// Apply a delete limit to bound mutation size.
264    #[must_use]
265    pub fn limit(mut self, limit: u32) -> Self {
266        self.query = self.query.limit(limit);
267        self
268    }
269
270    // ------------------------------------------------------------------
271    // Planning / diagnostics
272    // ------------------------------------------------------------------
273
274    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
275        self.query.explain()
276    }
277
278    pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
279        self.query.plan()
280    }
281
282    // ------------------------------------------------------------------
283    // Execution
284    // ------------------------------------------------------------------
285
286    pub fn execute(&self) -> Result<Response<E>, QueryError> {
287        self.session.execute_query(self.query())
288    }
289
290    /// Execute a delete query and return the deleted rows.
291    pub fn delete_rows(&self) -> Result<Response<E>, QueryError> {
292        self.execute()
293    }
294}
295
296impl<C: CanisterKind, E: EntityKind<Canister = C, PrimaryKey = ()>> SessionDeleteQuery<'_, C, E> {
297    /// Delete the singleton entity identified by the unit primary key `()`.
298    ///
299    /// Semantics:
300    /// - Equivalent to `DELETE … WHERE pk = ()`
301    /// - Uses key-based access (ByKey)
302    /// - MissingOk mode is idempotent
303    /// - Strict mode treats missing row as corruption
304    #[must_use]
305    pub fn only(mut self) -> Self {
306        self.query = self.query.only();
307        self
308    }
309}