Skip to main content

icydb_core/db/query/
session.rs

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