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 fluent::load::PartialWindowLoadQuery,
16 intent::{CompiledQuery, PlannedQuery, Query, QueryError},
17 trace::QueryTracePlan,
18 },
19 },
20 traits::{EntityKind, SingletonEntity},
21 types::Id,
22 value::InputValue,
23};
24
25#[cfg(feature = "diagnostics")]
26use crate::{
27 db::{PersistedRow, QueryExecutionAttribution, query::fluent::load::LoadQueryResult},
28 traits::EntityValue,
29};
30
31pub struct FluentLoadQuery<'a, E>
40where
41 E: EntityKind,
42{
43 pub(super) session: &'a DbSession<E::Canister>,
44 pub(super) query: Query<E>,
45 pub(super) cursor_token: Option<String>,
46 trusted_read_unchecked: bool,
47}
48
49impl<'a, E> FluentLoadQuery<'a, E>
50where
51 E: EntityKind,
52{
53 pub(in crate::db) const fn new(session: &'a DbSession<E::Canister>, query: Query<E>) -> Self {
54 Self {
55 session,
56 query,
57 cursor_token: None,
58 trusted_read_unchecked: false,
59 }
60 }
61
62 #[doc(hidden)]
68 #[must_use]
69 pub const fn query(&self) -> &Query<E> {
70 &self.query
71 }
72
73 #[cfg(feature = "diagnostics")]
75 #[doc(hidden)]
76 pub fn execute_with_attribution(
77 &self,
78 ) -> Result<(LoadQueryResult<E>, QueryExecutionAttribution), QueryError>
79 where
80 E: PersistedRow + EntityValue,
81 {
82 self.session
83 .execute_query_result_with_attribution(self.query())
84 }
85
86 pub(super) fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
87 self.query = map(self.query);
88 self
89 }
90
91 pub(super) fn try_map_query(
92 mut self,
93 map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
94 ) -> Result<Self, QueryError> {
95 self.query = map(self.query)?;
96 Ok(self)
97 }
98
99 fn map_session_query_output<T>(
103 &self,
104 map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
105 ) -> Result<T, QueryError> {
106 map(self.session, self.query())
107 }
108
109 #[must_use]
117 pub fn by_id(self, id: Id<E>) -> Self {
118 self.map_query(|query| query.by_id(id.key()))
119 }
120
121 #[must_use]
125 pub fn by_ids<I>(self, ids: I) -> Self
126 where
127 I: IntoIterator<Item = Id<E>>,
128 {
129 self.map_query(|query| query.by_ids(ids.into_iter().map(|id| id.key())))
130 }
131
132 #[must_use]
138 pub fn filter(self, expr: impl Into<FilterExpr>) -> Self {
139 self.map_query(|query| query.filter(expr))
140 }
141
142 #[must_use]
144 pub fn order_term(self, term: OrderTerm) -> Self {
145 self.map_query(|query| query.order_term(term))
146 }
147
148 #[must_use]
150 pub fn order_terms<I>(self, terms: I) -> Self
151 where
152 I: IntoIterator<Item = OrderTerm>,
153 {
154 self.map_query(|query| query.order_terms(terms))
155 }
156
157 pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
159 let field = field.as_ref().to_owned();
160 let schema = self
161 .session
162 .accepted_schema_info_for_entity::<E>()
163 .map_err(QueryError::execute)?;
164
165 self.try_map_query(|query| query.group_by_with_schema(&field, &schema))
166 }
167
168 #[must_use]
170 pub fn aggregate(self, aggregate: AggregateExpr) -> Self {
171 self.map_query(|query| query.aggregate(aggregate))
172 }
173
174 #[must_use]
176 pub fn grouped_limits(self, max_groups: u64, max_group_bytes: u64) -> Self {
177 self.map_query(|query| query.grouped_limits(max_groups, max_group_bytes))
178 }
179
180 pub fn having_group(
182 self,
183 field: impl AsRef<str>,
184 op: CompareOp,
185 value: InputValue,
186 ) -> Result<Self, QueryError> {
187 let field = field.as_ref().to_owned();
188 let schema = self
189 .session
190 .accepted_schema_info_for_entity::<E>()
191 .map_err(QueryError::execute)?;
192
193 self.try_map_query(|query| query.having_group_with_schema(&field, &schema, op, value))
194 }
195
196 pub fn having_aggregate(
198 self,
199 aggregate_index: usize,
200 op: CompareOp,
201 value: InputValue,
202 ) -> Result<Self, QueryError> {
203 self.try_map_query(|query| query.having_aggregate(aggregate_index, op, value))
204 }
205
206 #[must_use]
213 pub fn partial_window(self, limit: u32) -> PartialWindowLoadQuery<'a, E> {
214 PartialWindowLoadQuery::new(self.map_query(|query| query.limit(limit)))
215 }
216
217 #[must_use]
223 #[cfg(test)]
224 pub(in crate::db) fn limit(self, limit: u32) -> Self {
225 self.map_query(|query| query.limit(limit))
226 }
227
228 #[must_use]
234 #[cfg(test)]
235 pub(in crate::db) fn offset(self, offset: u32) -> Self {
236 self.map_query(|query| query.offset(offset))
237 }
238
239 pub(super) fn with_cursor_token(mut self, token: impl Into<String>) -> Self {
240 self.cursor_token = Some(token.into());
241 self
242 }
243
244 #[must_use]
249 pub const fn trusted_read_unchecked(mut self) -> Self {
250 self.trusted_read_unchecked = true;
251 self
252 }
253
254 pub(super) const fn trusted_read_unchecked_enabled(&self) -> bool {
255 self.trusted_read_unchecked
256 }
257
258 pub(super) fn ensure_default_read_admission(&self) -> Result<(), QueryError> {
259 if self.trusted_read_unchecked {
260 return Ok(());
261 }
262
263 self.map_session_query_output(|session, query| {
264 session.ensure_query_read_admission_policy(
265 query,
266 &QueryAdmissionPolicy::default_bounded_read(),
267 )
268 })?;
269 Ok(())
270 }
271
272 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
278 self.map_session_query_output(DbSession::explain_query_with_visible_indexes)
279 }
280
281 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
283 self.map_session_query_output(DbSession::query_plan_hash_hex_with_visible_indexes)
284 }
285
286 pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
288 self.map_session_query_output(DbSession::trace_query)
289 }
290
291 pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
293 self.ensure_cursor_mode_ready()?;
294 self.map_session_query_output(DbSession::planned_query_with_visible_indexes)
295 }
296
297 pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
299 self.ensure_cursor_mode_ready()?;
300 self.map_session_query_output(DbSession::compile_query_with_visible_indexes)
301 }
302}
303
304impl<E> FluentLoadQuery<'_, E>
305where
306 E: EntityKind + SingletonEntity,
307 E::Key: Default,
308{
309 #[must_use]
311 pub fn singleton(self) -> Self {
312 self.map_query(Query::singleton)
313 }
314}