icydb_core/db/query/fluent/load/
builder.rs1use crate::{
7 db::{
8 DbSession,
9 predicate::CompareOp,
10 query::{
11 admission::{QueryAdmissionPolicy, QueryAdmissionSummary},
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}
40
41impl<'a, E> FluentLoadQuery<'a, E>
42where
43 E: EntityKind,
44{
45 pub(in crate::db) const fn new(session: &'a DbSession<E::Canister>, query: Query<E>) -> Self {
46 Self {
47 session,
48 query,
49 cursor_token: None,
50 }
51 }
52
53 #[must_use]
59 pub const fn query(&self) -> &Query<E> {
60 &self.query
61 }
62
63 pub(super) fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
64 self.query = map(self.query);
65 self
66 }
67
68 pub(super) fn try_map_query(
69 mut self,
70 map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
71 ) -> Result<Self, QueryError> {
72 self.query = map(self.query)?;
73 Ok(self)
74 }
75
76 fn map_session_query_output<T>(
80 &self,
81 map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
82 ) -> Result<T, QueryError> {
83 map(self.session, self.query())
84 }
85
86 #[must_use]
94 pub fn by_id(self, id: Id<E>) -> Self {
95 self.map_query(|query| query.by_id(id.key()))
96 }
97
98 #[must_use]
102 pub fn by_ids<I>(self, ids: I) -> Self
103 where
104 I: IntoIterator<Item = Id<E>>,
105 {
106 self.map_query(|query| query.by_ids(ids.into_iter().map(|id| id.key())))
107 }
108
109 #[must_use]
115 pub fn filter(self, expr: impl Into<FilterExpr>) -> Self {
116 self.map_query(|query| query.filter(expr))
117 }
118
119 #[must_use]
121 pub fn order_term(self, term: OrderTerm) -> Self {
122 self.map_query(|query| query.order_term(term))
123 }
124
125 #[must_use]
127 pub fn order_terms<I>(self, terms: I) -> Self
128 where
129 I: IntoIterator<Item = OrderTerm>,
130 {
131 self.map_query(|query| query.order_terms(terms))
132 }
133
134 pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
136 let field = field.as_ref().to_owned();
137 let schema = self
138 .session
139 .accepted_schema_info_for_entity::<E>()
140 .map_err(QueryError::execute)?;
141
142 self.try_map_query(|query| query.group_by_with_schema(&field, &schema))
143 }
144
145 #[must_use]
147 pub fn aggregate(self, aggregate: AggregateExpr) -> Self {
148 self.map_query(|query| query.aggregate(aggregate))
149 }
150
151 #[must_use]
153 pub fn grouped_limits(self, max_groups: u64, max_group_bytes: u64) -> Self {
154 self.map_query(|query| query.grouped_limits(max_groups, max_group_bytes))
155 }
156
157 pub fn having_group(
159 self,
160 field: impl AsRef<str>,
161 op: CompareOp,
162 value: InputValue,
163 ) -> Result<Self, QueryError> {
164 let field = field.as_ref().to_owned();
165 let schema = self
166 .session
167 .accepted_schema_info_for_entity::<E>()
168 .map_err(QueryError::execute)?;
169
170 self.try_map_query(|query| query.having_group_with_schema(&field, &schema, op, value))
171 }
172
173 pub fn having_aggregate(
175 self,
176 aggregate_index: usize,
177 op: CompareOp,
178 value: InputValue,
179 ) -> Result<Self, QueryError> {
180 self.try_map_query(|query| query.having_aggregate(aggregate_index, op, value))
181 }
182
183 #[must_use]
189 pub fn limit(self, limit: u32) -> Self {
190 self.map_query(|query| query.limit(limit))
191 }
192
193 #[must_use]
199 pub fn offset(self, offset: u32) -> Self {
200 self.map_query(|query| query.offset(offset))
201 }
202
203 #[must_use]
209 pub fn cursor(mut self, token: impl Into<String>) -> Self {
210 self.cursor_token = Some(token.into());
211 self
212 }
213
214 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
220 self.map_session_query_output(DbSession::explain_query_with_visible_indexes)
221 }
222
223 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
225 self.map_session_query_output(DbSession::query_plan_hash_hex_with_visible_indexes)
226 }
227
228 pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
230 self.map_session_query_output(DbSession::trace_query)
231 }
232
233 pub fn read_admission(
239 &self,
240 policy: &QueryAdmissionPolicy,
241 ) -> Result<QueryAdmissionSummary, QueryError> {
242 self.map_session_query_output(|session, query| {
243 session.evaluate_query_read_admission_policy(query, policy)
244 })
245 }
246
247 pub fn ensure_read_admission(
252 &self,
253 policy: &QueryAdmissionPolicy,
254 ) -> Result<QueryAdmissionSummary, QueryError> {
255 self.map_session_query_output(|session, query| {
256 session.ensure_query_read_admission_policy(query, policy)
257 })
258 }
259
260 pub fn planned(&self) -> Result<PlannedQuery<E>, QueryError> {
262 self.ensure_cursor_mode_ready()?;
263 self.map_session_query_output(DbSession::planned_query_with_visible_indexes)
264 }
265
266 pub fn plan(&self) -> Result<CompiledQuery<E>, QueryError> {
268 self.ensure_cursor_mode_ready()?;
269 self.map_session_query_output(DbSession::compile_query_with_visible_indexes)
270 }
271}
272
273impl<E> FluentLoadQuery<'_, E>
274where
275 E: EntityKind + SingletonEntity,
276 E::Key: Default,
277{
278 #[must_use]
280 pub fn only(self) -> Self {
281 self.map_query(Query::only)
282 }
283}