icydb_core/db/query/fluent/load/
builder.rs1use crate::{
7 db::{
8 DbSession,
9 predicate::CompareOp,
10 query::{
11 admission::QueryAdmissionPolicy,
12 builder::aggregate::AggregateExpr,
13 explain::ExplainPlan,
14 expr::{FilterExpr, OrderTerm},
15 intent::{CompiledQuery, PlannedQuery, Query, QueryError},
16 trace::QueryTracePlan,
17 },
18 },
19 traits::{EntityKind, SingletonEntity},
20 types::Id,
21 value::InputValue,
22};
23
24pub struct FluentLoadQuery<'a, E>
33where
34 E: EntityKind,
35{
36 pub(super) session: &'a DbSession<E::Canister>,
37 pub(super) query: Query<E>,
38 pub(super) cursor_token: Option<String>,
39 trusted_read_unchecked: bool,
40}
41
42impl<'a, E> FluentLoadQuery<'a, E>
43where
44 E: EntityKind,
45{
46 pub(in crate::db) const fn new(session: &'a DbSession<E::Canister>, query: Query<E>) -> Self {
47 Self {
48 session,
49 query,
50 cursor_token: None,
51 trusted_read_unchecked: false,
52 }
53 }
54
55 #[must_use]
61 pub const fn query(&self) -> &Query<E> {
62 &self.query
63 }
64
65 pub(super) fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
66 self.query = map(self.query);
67 self
68 }
69
70 pub(super) fn try_map_query(
71 mut self,
72 map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
73 ) -> Result<Self, QueryError> {
74 self.query = map(self.query)?;
75 Ok(self)
76 }
77
78 fn map_session_query_output<T>(
82 &self,
83 map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
84 ) -> Result<T, QueryError> {
85 map(self.session, self.query())
86 }
87
88 #[must_use]
96 pub fn by_id(self, id: Id<E>) -> Self {
97 self.map_query(|query| query.by_id(id.key()))
98 }
99
100 #[must_use]
104 pub fn by_ids<I>(self, ids: I) -> Self
105 where
106 I: IntoIterator<Item = Id<E>>,
107 {
108 self.map_query(|query| query.by_ids(ids.into_iter().map(|id| id.key())))
109 }
110
111 #[must_use]
117 pub fn filter(self, expr: impl Into<FilterExpr>) -> Self {
118 self.map_query(|query| query.filter(expr))
119 }
120
121 #[must_use]
123 pub fn order_term(self, term: OrderTerm) -> Self {
124 self.map_query(|query| query.order_term(term))
125 }
126
127 #[must_use]
129 pub fn order_terms<I>(self, terms: I) -> Self
130 where
131 I: IntoIterator<Item = OrderTerm>,
132 {
133 self.map_query(|query| query.order_terms(terms))
134 }
135
136 pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
138 let field = field.as_ref().to_owned();
139 let schema = self
140 .session
141 .accepted_schema_info_for_entity::<E>()
142 .map_err(QueryError::execute)?;
143
144 self.try_map_query(|query| query.group_by_with_schema(&field, &schema))
145 }
146
147 #[must_use]
149 pub fn aggregate(self, aggregate: AggregateExpr) -> Self {
150 self.map_query(|query| query.aggregate(aggregate))
151 }
152
153 #[must_use]
155 pub fn grouped_limits(self, max_groups: u64, max_group_bytes: u64) -> Self {
156 self.map_query(|query| query.grouped_limits(max_groups, max_group_bytes))
157 }
158
159 pub fn having_group(
161 self,
162 field: impl AsRef<str>,
163 op: CompareOp,
164 value: InputValue,
165 ) -> Result<Self, QueryError> {
166 let field = field.as_ref().to_owned();
167 let schema = self
168 .session
169 .accepted_schema_info_for_entity::<E>()
170 .map_err(QueryError::execute)?;
171
172 self.try_map_query(|query| query.having_group_with_schema(&field, &schema, op, value))
173 }
174
175 pub fn having_aggregate(
177 self,
178 aggregate_index: usize,
179 op: CompareOp,
180 value: InputValue,
181 ) -> Result<Self, QueryError> {
182 self.try_map_query(|query| query.having_aggregate(aggregate_index, op, value))
183 }
184
185 #[must_use]
191 pub fn limit(self, limit: u32) -> Self {
192 self.map_query(|query| query.limit(limit))
193 }
194
195 #[must_use]
201 pub fn offset(self, offset: u32) -> Self {
202 self.map_query(|query| query.offset(offset))
203 }
204
205 #[must_use]
211 pub fn cursor(mut self, token: impl Into<String>) -> Self {
212 self.cursor_token = Some(token.into());
213 self
214 }
215
216 #[must_use]
221 pub const fn trusted_read_unchecked(mut self) -> Self {
222 self.trusted_read_unchecked = true;
223 self
224 }
225
226 pub(super) fn ensure_default_read_admission(&self) -> Result<(), QueryError> {
227 if self.trusted_read_unchecked {
228 return Ok(());
229 }
230
231 self.map_session_query_output(|session, query| {
232 session.ensure_query_read_admission_policy(
233 query,
234 &QueryAdmissionPolicy::default_bounded_read(),
235 )
236 })?;
237 Ok(())
238 }
239
240 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
246 self.map_session_query_output(DbSession::explain_query_with_visible_indexes)
247 }
248
249 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
251 self.map_session_query_output(DbSession::query_plan_hash_hex_with_visible_indexes)
252 }
253
254 pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
256 self.map_session_query_output(DbSession::trace_query)
257 }
258
259 pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
261 self.ensure_cursor_mode_ready()?;
262 self.map_session_query_output(DbSession::planned_query_with_visible_indexes)
263 }
264
265 pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
267 self.ensure_cursor_mode_ready()?;
268 self.map_session_query_output(DbSession::compile_query_with_visible_indexes)
269 }
270}
271
272impl<E> FluentLoadQuery<'_, E>
273where
274 E: EntityKind + SingletonEntity,
275 E::Key: Default,
276{
277 #[must_use]
279 pub fn only(self) -> Self {
280 self.map_query(Query::only)
281 }
282}