Skip to main content

icydb_core/db/query/
session.rs

1use crate::{
2    db::{
3        DbSession,
4        query::{
5            Query, QueryError, eq,
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        let key = key.into();
51        self.query = self.query.filter(eq(E::PRIMARY_KEY, key));
52        self
53    }
54
55    /// Add a predicate, implicitly AND-ing with any existing predicate.
56    #[must_use]
57    pub fn filter(mut self, predicate: Predicate) -> Self {
58        self.query = self.query.filter(predicate);
59        self
60    }
61
62    /// Append an ascending sort key.
63    #[must_use]
64    pub fn order_by(mut self, field: &'static str) -> Self {
65        self.query = self.query.order_by(field);
66        self
67    }
68
69    /// Append a descending sort key.
70    #[must_use]
71    pub fn order_by_desc(mut self, field: &'static str) -> Self {
72        self.query = self.query.order_by_desc(field);
73        self
74    }
75
76    /// Apply a load limit to bound result size.
77    #[must_use]
78    pub fn limit(mut self, limit: u32) -> Self {
79        self.query = self.query.limit(limit);
80        self
81    }
82
83    /// Apply a load offset.
84    #[must_use]
85    pub fn offset(mut self, offset: u64) -> Self {
86        self.query = self.query.offset(offset);
87        self
88    }
89
90    // ------------------------------------------------------------------
91    // Planning / diagnostics
92    // ------------------------------------------------------------------
93
94    /// Explain this query without executing it.
95    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
96        self.query.explain()
97    }
98
99    /// Plan this query into an executor-ready plan.
100    pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
101        self.query.plan()
102    }
103
104    // ------------------------------------------------------------------
105    // Execution (single boundary)
106    // ------------------------------------------------------------------
107
108    /// Execute this query using the session's policy settings.
109    pub fn execute(&self) -> Result<Response<E>, QueryError> {
110        self.session.execute_query(self.query())
111    }
112
113    // ------------------------------------------------------------------
114    // Execution terminals (interpretation)
115    // ------------------------------------------------------------------
116
117    /// Return whether any rows match this query.
118    pub fn exists(&self) -> Result<bool, QueryError> {
119        Ok(self.count()? > 0)
120    }
121
122    /// Execute and return the number of matching rows.
123    pub fn count(&self) -> Result<u64, QueryError> {
124        Ok(self.execute()?.count())
125    }
126
127    /// Execute and return all entities.
128    pub fn all(&self) -> Result<Vec<E>, QueryError> {
129        Ok(self.execute()?.entities())
130    }
131
132    /// Execute and return all results as views.
133    pub fn views(&self) -> Result<Vec<View<E>>, QueryError> {
134        Ok(self.execute()?.views())
135    }
136
137    /// Execute and require exactly one entity.
138    pub fn one(&self) -> Result<E, QueryError> {
139        self.execute()?.entity().map_err(QueryError::Execute)
140    }
141
142    /// Execute and require exactly one view.
143    pub fn view(&self) -> Result<View<E>, QueryError> {
144        self.execute()?.view().map_err(QueryError::Execute)
145    }
146
147    /// Execute and return zero or one entity.
148    pub fn one_opt(&self) -> Result<Option<E>, QueryError> {
149        self.execute()?.try_entity().map_err(QueryError::Execute)
150    }
151
152    /// Execute and return zero or one view.
153    pub fn view_opt(&self) -> Result<Option<View<E>>, QueryError> {
154        self.execute()?.view_opt().map_err(QueryError::Execute)
155    }
156}
157
158///
159/// SessionDeleteQuery
160///
161/// Fluent, session-bound delete query wrapper that keeps query intent pure
162/// while routing execution through the `DbSession` boundary.
163///
164
165pub struct SessionDeleteQuery<'a, C: CanisterKind, E: EntityKind<Canister = C>> {
166    session: &'a DbSession<C>,
167    query: Query<E>,
168}
169
170impl<'a, C: CanisterKind, E: EntityKind<Canister = C>> SessionDeleteQuery<'a, C, E> {
171    pub(crate) const fn new(session: &'a DbSession<C>, query: Query<E>) -> Self {
172        Self { session, query }
173    }
174
175    // ------------------------------------------------------------------
176    // Intent inspection
177    // ------------------------------------------------------------------
178
179    #[must_use]
180    pub const fn query(&self) -> &Query<E> {
181        &self.query
182    }
183
184    // ------------------------------------------------------------------
185    // Intent builders
186    // ------------------------------------------------------------------
187
188    /// Delete by primary key.
189    #[must_use]
190    pub fn key(mut self, key: impl Into<Key>) -> Self {
191        let key = key.into();
192        self.query = self.query.filter(eq(E::PRIMARY_KEY, key));
193        self
194    }
195
196    /// Add a predicate, implicitly AND-ing with any existing predicate.
197    #[must_use]
198    pub fn filter(mut self, predicate: Predicate) -> Self {
199        self.query = self.query.filter(predicate);
200        self
201    }
202
203    /// Append an ascending sort key.
204    #[must_use]
205    pub fn order_by(mut self, field: &'static str) -> Self {
206        self.query = self.query.order_by(field);
207        self
208    }
209
210    /// Append a descending sort key.
211    #[must_use]
212    pub fn order_by_desc(mut self, field: &'static str) -> Self {
213        self.query = self.query.order_by_desc(field);
214        self
215    }
216
217    /// Apply a delete limit to bound mutation size.
218    #[must_use]
219    pub fn limit(mut self, limit: u32) -> Self {
220        self.query = self.query.limit(limit);
221        self
222    }
223
224    // ------------------------------------------------------------------
225    // Planning / diagnostics
226    // ------------------------------------------------------------------
227
228    pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
229        self.query.explain()
230    }
231
232    pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
233        self.query.plan()
234    }
235
236    // ------------------------------------------------------------------
237    // Execution
238    // ------------------------------------------------------------------
239
240    pub fn execute(&self) -> Result<Response<E>, QueryError> {
241        self.session.execute_query(self.query())
242    }
243
244    /// Execute a delete query and return the deleted rows.
245    pub fn delete_rows(&self) -> Result<Response<E>, QueryError> {
246        self.execute()
247    }
248}