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
25pub struct FluentLoadQuery<'a, E>
34where
35 E: EntityKind,
36{
37 pub(super) session: &'a DbSession<E::Canister>,
38 pub(super) query: Query<E>,
39 pub(super) cursor_token: Option<String>,
40 trusted_read_unchecked: bool,
41}
42
43impl<'a, E> FluentLoadQuery<'a, E>
44where
45 E: EntityKind,
46{
47 pub(in crate::db) const fn new(session: &'a DbSession<E::Canister>, query: Query<E>) -> Self {
48 Self {
49 session,
50 query,
51 cursor_token: None,
52 trusted_read_unchecked: false,
53 }
54 }
55
56 #[doc(hidden)]
62 #[must_use]
63 pub const fn query(&self) -> &Query<E> {
64 &self.query
65 }
66
67 pub(super) fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
68 self.query = map(self.query);
69 self
70 }
71
72 pub(super) fn try_map_query(
73 mut self,
74 map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
75 ) -> Result<Self, QueryError> {
76 self.query = map(self.query)?;
77 Ok(self)
78 }
79
80 fn map_session_query_output<T>(
84 &self,
85 map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
86 ) -> Result<T, QueryError> {
87 map(self.session, self.query())
88 }
89
90 #[must_use]
98 pub fn by_id(self, id: Id<E>) -> Self {
99 self.map_query(|query| query.by_id(id.key()))
100 }
101
102 #[must_use]
106 pub fn by_ids<I>(self, ids: I) -> Self
107 where
108 I: IntoIterator<Item = Id<E>>,
109 {
110 self.map_query(|query| query.by_ids(ids.into_iter().map(|id| id.key())))
111 }
112
113 #[must_use]
119 pub fn filter(self, expr: impl Into<FilterExpr>) -> Self {
120 self.map_query(|query| query.filter(expr))
121 }
122
123 #[must_use]
125 pub fn order_term(self, term: OrderTerm) -> Self {
126 self.map_query(|query| query.order_term(term))
127 }
128
129 #[must_use]
131 pub fn order_terms<I>(self, terms: I) -> Self
132 where
133 I: IntoIterator<Item = OrderTerm>,
134 {
135 self.map_query(|query| query.order_terms(terms))
136 }
137
138 pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
140 let field = field.as_ref().to_owned();
141 let schema = self
142 .session
143 .accepted_schema_info_for_entity::<E>()
144 .map_err(QueryError::execute)?;
145
146 self.try_map_query(|query| query.group_by_with_schema(&field, &schema))
147 }
148
149 #[must_use]
151 pub fn aggregate(self, aggregate: AggregateExpr) -> Self {
152 self.map_query(|query| query.aggregate(aggregate))
153 }
154
155 #[must_use]
157 pub fn grouped_limits(self, max_groups: u64, max_group_bytes: u64) -> Self {
158 self.map_query(|query| query.grouped_limits(max_groups, max_group_bytes))
159 }
160
161 pub fn having_group(
163 self,
164 field: impl AsRef<str>,
165 op: CompareOp,
166 value: InputValue,
167 ) -> Result<Self, QueryError> {
168 let field = field.as_ref().to_owned();
169 let schema = self
170 .session
171 .accepted_schema_info_for_entity::<E>()
172 .map_err(QueryError::execute)?;
173
174 self.try_map_query(|query| query.having_group_with_schema(&field, &schema, op, value))
175 }
176
177 pub fn having_aggregate(
179 self,
180 aggregate_index: usize,
181 op: CompareOp,
182 value: InputValue,
183 ) -> Result<Self, QueryError> {
184 self.try_map_query(|query| query.having_aggregate(aggregate_index, op, value))
185 }
186
187 #[must_use]
194 pub fn partial_window(self, limit: u32) -> PartialWindowLoadQuery<'a, E> {
195 PartialWindowLoadQuery::new(self.map_query(|query| query.limit(limit)))
196 }
197
198 #[must_use]
204 #[cfg(test)]
205 pub(in crate::db) fn limit(self, limit: u32) -> Self {
206 self.map_query(|query| query.limit(limit))
207 }
208
209 #[must_use]
215 #[cfg(test)]
216 pub(in crate::db) fn offset(self, offset: u32) -> Self {
217 self.map_query(|query| query.offset(offset))
218 }
219
220 pub(super) fn with_cursor_token(mut self, token: impl Into<String>) -> Self {
221 self.cursor_token = Some(token.into());
222 self
223 }
224
225 #[must_use]
230 pub const fn trusted_read_unchecked(mut self) -> Self {
231 self.trusted_read_unchecked = true;
232 self
233 }
234
235 pub(super) const fn trusted_read_unchecked_enabled(&self) -> bool {
236 self.trusted_read_unchecked
237 }
238
239 pub(super) fn ensure_default_read_admission(&self) -> Result<(), QueryError> {
240 if self.trusted_read_unchecked {
241 return Ok(());
242 }
243
244 self.map_session_query_output(|session, query| {
245 session.ensure_query_read_admission_policy(
246 query,
247 &QueryAdmissionPolicy::default_bounded_read(),
248 )
249 })?;
250 Ok(())
251 }
252
253 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
259 self.map_session_query_output(DbSession::explain_query_with_visible_indexes)
260 }
261
262 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
264 self.map_session_query_output(DbSession::query_plan_hash_hex_with_visible_indexes)
265 }
266
267 pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
269 self.map_session_query_output(DbSession::trace_query)
270 }
271
272 pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
274 self.ensure_cursor_mode_ready()?;
275 self.map_session_query_output(DbSession::planned_query_with_visible_indexes)
276 }
277
278 pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
280 self.ensure_cursor_mode_ready()?;
281 self.map_session_query_output(DbSession::compile_query_with_visible_indexes)
282 }
283}
284
285impl<E> FluentLoadQuery<'_, E>
286where
287 E: EntityKind + SingletonEntity,
288 E::Key: Default,
289{
290 #[must_use]
292 pub fn singleton(self) -> Self {
293 self.map_query(Query::singleton)
294 }
295}