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}