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 pub(super) fn with_cursor_token(mut self, token: impl Into<String>) -> Self {
206 self.cursor_token = Some(token.into());
207 self
208 }
209
210 #[must_use]
215 pub const fn trusted_read_unchecked(mut self) -> Self {
216 self.trusted_read_unchecked = true;
217 self
218 }
219
220 pub(super) const fn trusted_read_unchecked_enabled(&self) -> bool {
221 self.trusted_read_unchecked
222 }
223
224 pub(super) fn ensure_default_read_admission(&self) -> Result<(), QueryError> {
225 if self.trusted_read_unchecked {
226 return Ok(());
227 }
228
229 self.map_session_query_output(|session, query| {
230 session.ensure_query_read_admission_policy(
231 query,
232 &QueryAdmissionPolicy::default_bounded_read(),
233 )
234 })?;
235 Ok(())
236 }
237
238 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
244 self.map_session_query_output(DbSession::explain_query_with_visible_indexes)
245 }
246
247 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
249 self.map_session_query_output(DbSession::query_plan_hash_hex_with_visible_indexes)
250 }
251
252 pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
254 self.map_session_query_output(DbSession::trace_query)
255 }
256
257 pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
259 self.ensure_cursor_mode_ready()?;
260 self.map_session_query_output(DbSession::planned_query_with_visible_indexes)
261 }
262
263 pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
265 self.ensure_cursor_mode_ready()?;
266 self.map_session_query_output(DbSession::compile_query_with_visible_indexes)
267 }
268}
269
270impl<E> FluentLoadQuery<'_, E>
271where
272 E: EntityKind + SingletonEntity,
273 E::Key: Default,
274{
275 #[must_use]
277 pub fn only(self) -> Self {
278 self.map_query(Query::only)
279 }
280}