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::{Query, QueryError},
17 trace::QueryTracePlan,
18 },
19 },
20 entity::{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(
48 session: &'a DbSession<E::Canister>,
49 missing_row_policy: crate::db::predicate::MissingRowPolicy,
50 ) -> Self {
51 Self {
52 session,
53 query: Query::new(missing_row_policy),
54 cursor_token: None,
55 trusted_read_unchecked: false,
56 }
57 }
58
59 #[must_use]
65 pub(in crate::db) const fn query(&self) -> &Query<E> {
66 &self.query
67 }
68
69 pub(super) fn map_query(mut self, map: impl FnOnce(Query<E>) -> Query<E>) -> Self {
70 self.query = map(self.query);
71 self
72 }
73
74 pub(super) fn try_map_query(
75 mut self,
76 map: impl FnOnce(Query<E>) -> Result<Query<E>, QueryError>,
77 ) -> Result<Self, QueryError> {
78 self.query = map(self.query)?;
79 Ok(self)
80 }
81
82 fn map_session_query_output<T>(
86 &self,
87 map: impl FnOnce(&DbSession<E::Canister>, &Query<E>) -> Result<T, QueryError>,
88 ) -> Result<T, QueryError> {
89 map(self.session, self.query())
90 }
91
92 #[must_use]
100 pub fn by_id(self, id: Id<E>) -> Self {
101 self.map_query(|query| query.by_id(id.key()))
102 }
103
104 #[must_use]
108 pub fn by_ids<I>(self, ids: I) -> Self
109 where
110 I: IntoIterator<Item = Id<E>>,
111 {
112 self.map_query(|query| query.by_ids(ids.into_iter().map(|id| id.key())))
113 }
114
115 #[must_use]
121 pub fn filter(self, expr: impl Into<FilterExpr>) -> Self {
122 self.map_query(|query| query.filter(expr))
123 }
124
125 #[must_use]
127 pub fn order_term(self, term: OrderTerm) -> Self {
128 self.map_query(|query| query.order_term(term))
129 }
130
131 #[must_use]
133 pub fn order_terms<I>(self, terms: I) -> Self
134 where
135 I: IntoIterator<Item = OrderTerm>,
136 {
137 self.map_query(|query| query.order_terms(terms))
138 }
139
140 pub fn group_by(self, field: impl AsRef<str>) -> Result<Self, QueryError> {
142 let field = field.as_ref().to_owned();
143 let schema = self
144 .session
145 .accepted_schema_info_for_entity::<E>()
146 .map_err(QueryError::execute)?;
147
148 self.try_map_query(|query| query.group_by_with_schema(&field, &schema))
149 }
150
151 #[must_use]
153 pub fn aggregate(self, aggregate: AggregateExpr) -> Self {
154 self.map_query(|query| query.aggregate(aggregate))
155 }
156
157 #[must_use]
159 pub fn grouped_limits(self, max_groups: u64, max_group_bytes: u64) -> Self {
160 self.map_query(|query| query.grouped_limits(max_groups, max_group_bytes))
161 }
162
163 pub fn having_group(
165 self,
166 field: impl AsRef<str>,
167 op: CompareOp,
168 value: InputValue,
169 ) -> Result<Self, QueryError> {
170 let field = field.as_ref().to_owned();
171 let schema = self
172 .session
173 .accepted_schema_info_for_entity::<E>()
174 .map_err(QueryError::execute)?;
175
176 self.try_map_query(|query| query.having_group_with_schema(&field, &schema, op, value))
177 }
178
179 pub fn having_aggregate(
181 self,
182 aggregate_index: usize,
183 op: CompareOp,
184 value: InputValue,
185 ) -> Result<Self, QueryError> {
186 self.try_map_query(|query| query.having_aggregate(aggregate_index, op, value))
187 }
188
189 #[must_use]
196 pub fn partial_window(self, limit: u32) -> PartialWindowLoadQuery<'a, E> {
197 PartialWindowLoadQuery::new(self.map_query(|query| query.limit(limit)))
198 }
199
200 pub(super) fn with_cursor_token(mut self, token: impl Into<String>) -> Self {
201 self.cursor_token = Some(token.into());
202 self
203 }
204
205 #[must_use]
210 pub const fn trusted_read_unchecked(mut self) -> Self {
211 self.trusted_read_unchecked = true;
212 self
213 }
214
215 pub(super) const fn trusted_read_unchecked_enabled(&self) -> bool {
216 self.trusted_read_unchecked
217 }
218
219 pub(super) fn ensure_default_read_admission(&self) -> Result<(), QueryError> {
220 if self.trusted_read_unchecked {
221 return Ok(());
222 }
223
224 self.map_session_query_output(|session, query| {
225 session.ensure_query_read_admission_policy(
226 query,
227 &QueryAdmissionPolicy::default_bounded_read(),
228 )
229 })?;
230 Ok(())
231 }
232
233 pub fn explain(&self) -> Result<ExplainPlan, QueryError> {
239 self.map_session_query_output(DbSession::explain_query_with_visible_indexes)
240 }
241
242 pub fn plan_hash_hex(&self) -> Result<String, QueryError> {
244 self.map_session_query_output(DbSession::query_plan_hash_hex_with_visible_indexes)
245 }
246
247 pub fn trace(&self) -> Result<QueryTracePlan, QueryError> {
249 self.map_session_query_output(DbSession::trace_query)
250 }
251}
252
253impl<E> FluentLoadQuery<'_, E>
254where
255 E: EntityKind + SingletonEntity,
256 E::Key: Default,
257{
258 #[must_use]
260 pub fn singleton(self) -> Self {
261 self.map_query(Query::singleton)
262 }
263}