icydb_core/db/query/fluent/
load.rs1use crate::{
2 db::{
3 DbSession, PagedLoadExecution, PagedLoadExecutionWithTrace,
4 query::{
5 expr::{FilterExpr, SortExpr},
6 intent::{IntentError, Query, QueryError},
7 plan::{ExecutablePlan, ExplainPlan},
8 policy,
9 predicate::Predicate,
10 },
11 response::Response,
12 },
13 traits::{EntityKind, EntityValue, SingletonEntity},
14 types::Id,
15};
16
17pub struct FluentLoadQuery<'a, E>
26where
27 E: EntityKind,
28{
29 session: &'a DbSession<E::Canister>,
30 query: Query<E>,
31 cursor_token: Option<String>,
32}
33
34pub struct PagedLoadQuery<'a, E>
42where
43 E: EntityKind,
44{
45 inner: FluentLoadQuery<'a, E>,
46}
47
48impl<'a, E> FluentLoadQuery<'a, E>
49where
50 E: EntityKind,
51{
52 pub(crate) const fn new(session: &'a DbSession<E::Canister>, query: Query<E>) -> Self {
53 Self {
54 session,
55 query,
56 cursor_token: None,
57 }
58 }
59
60 #[must_use]
65 pub const fn query(&self) -> &Query<E> {
66 &self.query
67 }
68
69 fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
70 self.query = map(self.query);
71 self
72 }
73
74 fn try_map_query(
75 mut self,
76 map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
77 ) -> Result<Self, QueryError> {
78 self.query = map(self.query)?;
79 Ok(self)
80 }
81
82 #[must_use]
90 pub fn by_id(mut self, id: Id<E>) -> Self {
91 self.query = self.query.by_id(id.key());
92 self
93 }
94
95 #[must_use]
99 pub fn by_ids<I>(mut self, ids: I) -> Self
100 where
101 I: IntoIterator<Item = Id<E>>,
102 {
103 self.query = self.query.by_ids(ids.into_iter().map(|id| id.key()));
104 self
105 }
106
107 #[must_use]
112 pub fn filter(self, predicate: Predicate) -> Self {
113 self.map_query(|query| query.filter(predicate))
114 }
115
116 pub fn filter_expr(self, expr: FilterExpr) -> Result<Self, QueryError> {
117 self.try_map_query(|query| query.filter_expr(expr))
118 }
119
120 pub fn sort_expr(self, expr: SortExpr) -> Result<Self, QueryError> {
121 self.try_map_query(|query| query.sort_expr(expr))
122 }
123
124 #[must_use]
125 pub fn order_by(self, field: impl AsRef<str>) -> Self {
126 self.map_query(|query| query.order_by(field))
127 }
128
129 #[must_use]
130 pub fn order_by_desc(self, field: impl AsRef<str>) -> Self {
131 self.map_query(|query| query.order_by_desc(field))
132 }
133
134 #[must_use]
139 pub fn limit(self, limit: u32) -> Self {
140 self.map_query(|query| query.limit(limit))
141 }
142
143 #[must_use]
148 pub fn offset(self, offset: u32) -> Self {
149 self.map_query(|query| query.offset(offset))
150 }
151
152 #[must_use]
158 pub fn cursor(mut self, token: impl Into<String>) -> Self {
159 self.cursor_token = Some(token.into());
160 self
161 }
162
163 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
168 self.query.explain()
169 }
170
171 pub fn plan(&self) -> Result<ExecutablePlan<E>, QueryError> {
172 if let Some(err) = self.cursor_intent_error() {
173 return Err(QueryError::Intent(err));
174 }
175
176 self.query.plan()
177 }
178
179 pub fn execute(&self) -> Result<Response<E>, QueryError>
185 where
186 E: EntityValue,
187 {
188 self.session.execute_query(self.query())
189 }
190
191 pub fn page(self) -> Result<PagedLoadQuery<'a, E>, QueryError> {
202 self.ensure_paged_mode_ready()?;
203
204 Ok(PagedLoadQuery { inner: self })
205 }
206
207 pub fn execute_paged(self) -> Result<(Response<E>, Option<Vec<u8>>), QueryError>
211 where
212 E: EntityValue,
213 {
214 self.page()?.execute()
215 }
216
217 pub fn is_empty(&self) -> Result<bool, QueryError>
223 where
224 E: EntityValue,
225 {
226 Ok(!self.exists()?)
227 }
228
229 pub fn exists(&self) -> Result<bool, QueryError>
231 where
232 E: EntityValue,
233 {
234 self.session.execute_load_query_exists(self.query())
235 }
236
237 pub fn count(&self) -> Result<u32, QueryError>
239 where
240 E: EntityValue,
241 {
242 self.session.execute_load_query_count(self.query())
243 }
244
245 pub fn min(&self) -> Result<Option<Id<E>>, QueryError>
247 where
248 E: EntityValue,
249 {
250 self.session.execute_load_query_min(self.query())
251 }
252
253 pub fn max(&self) -> Result<Option<Id<E>>, QueryError>
255 where
256 E: EntityValue,
257 {
258 self.session.execute_load_query_max(self.query())
259 }
260
261 pub fn require_one(&self) -> Result<(), QueryError>
263 where
264 E: EntityValue,
265 {
266 self.execute()?.require_one()?;
267 Ok(())
268 }
269
270 pub fn require_some(&self) -> Result<(), QueryError>
272 where
273 E: EntityValue,
274 {
275 self.execute()?.require_some()?;
276 Ok(())
277 }
278}
279
280impl<E> FluentLoadQuery<'_, E>
281where
282 E: EntityKind,
283{
284 fn cursor_intent_error(&self) -> Option<IntentError> {
285 self.cursor_token
286 .as_ref()
287 .and_then(|_| self.paged_intent_error())
288 }
289
290 fn paged_intent_error(&self) -> Option<IntentError> {
291 let spec = self.query.load_spec()?;
292
293 policy::validate_cursor_paging_requirements(self.query.has_explicit_order(), spec)
294 .err()
295 .map(IntentError::from)
296 }
297
298 fn ensure_paged_mode_ready(&self) -> Result<(), QueryError> {
299 if let Some(err) = self.paged_intent_error() {
300 return Err(QueryError::Intent(err));
301 }
302
303 Ok(())
304 }
305}
306
307impl<E> FluentLoadQuery<'_, E>
308where
309 E: EntityKind + SingletonEntity,
310 E::Key: Default,
311{
312 #[must_use]
313 pub fn only(self) -> Self {
314 self.map_query(Query::only)
315 }
316}
317
318impl<E> PagedLoadQuery<'_, E>
319where
320 E: EntityKind,
321{
322 #[must_use]
327 pub const fn query(&self) -> &Query<E> {
328 self.inner.query()
329 }
330
331 #[must_use]
337 pub fn cursor(mut self, token: impl Into<String>) -> Self {
338 self.inner = self.inner.cursor(token);
339 self
340 }
341
342 pub fn execute(self) -> Result<PagedLoadExecution<E>, QueryError>
352 where
353 E: EntityValue,
354 {
355 self.execute_with_trace()
356 .map(|(items, next_cursor, _)| (items, next_cursor))
357 }
358
359 pub fn execute_with_trace(self) -> Result<PagedLoadExecutionWithTrace<E>, QueryError>
365 where
366 E: EntityValue,
367 {
368 self.inner.ensure_paged_mode_ready()?;
369
370 self.inner.session.execute_load_query_paged_with_trace(
371 self.inner.query(),
372 self.inner.cursor_token.as_deref(),
373 )
374 }
375}