icydb_core/db/session/query/
execution.rs1#[cfg(feature = "diagnostics")]
7use crate::db::executor::{GroupedExecutePhaseAttribution, ScalarExecutePhaseAttribution};
8use crate::{
9 db::{
10 DbSession, EntityResponse, LoadQueryResult, PersistedRow, Query, QueryError,
11 diagnostics::ExecutionTrace,
12 executor::{
13 ExecutionFamily, ExecutorPlanError, LoadExecutor, PreparedExecutionPlan,
14 StructuralGroupedProjectionResult,
15 },
16 query::plan::QueryMode,
17 schema::AcceptedEnumCatalogHandle,
18 session::finalize_structural_grouped_projection_result,
19 },
20 error::InternalError,
21 traits::CanisterKind,
22 types::Id,
23 value::Value,
24};
25
26#[cfg_attr(
35 not(feature = "diagnostics"),
36 expect(
37 clippy::large_enum_variant,
38 reason = "non-diagnostics builds keep the grouped execution trace inline to avoid boxing a private session-boundary outcome"
39 )
40)]
41pub(in crate::db::session::query) enum PreparedQueryExecutionOutcome<E>
42where
43 E: PersistedRow,
44{
45 Scalar {
46 rows: EntityResponse<E>,
47 #[cfg(feature = "diagnostics")]
48 phase: Option<ScalarExecutePhaseAttribution>,
49 #[cfg(feature = "diagnostics")]
50 response_decode_local_instructions: u64,
51 },
52 Grouped {
53 result: StructuralGroupedProjectionResult,
54 trace: Option<ExecutionTrace>,
55 #[cfg(feature = "diagnostics")]
56 phase: Option<GroupedExecutePhaseAttribution>,
57 },
58 Delete {
59 rows: EntityResponse<E>,
60 },
61 DeleteCount {
62 row_count: u32,
63 },
64}
65
66pub(in crate::db) struct AcceptedExecutionOutput<T> {
69 value: T,
70 enum_catalog: AcceptedEnumCatalogHandle,
71}
72
73pub(in crate::db) type AcceptedValuesOutput = AcceptedExecutionOutput<Vec<Value>>;
74pub(in crate::db) type AcceptedIdValuesOutput<E> = AcceptedExecutionOutput<Vec<(Id<E>, Value)>>;
75pub(in crate::db) type AcceptedOptionalValueOutput = AcceptedExecutionOutput<Option<Value>>;
76
77impl<T> AcceptedExecutionOutput<T> {
78 #[must_use]
79 pub(in crate::db) const fn new(value: T, enum_catalog: AcceptedEnumCatalogHandle) -> Self {
80 Self {
81 value,
82 enum_catalog,
83 }
84 }
85
86 #[must_use]
87 pub(in crate::db) fn into_parts(self) -> (T, AcceptedEnumCatalogHandle) {
88 (self.value, self.enum_catalog)
89 }
90
91 #[must_use]
92 pub(in crate::db) fn into_value(self) -> T {
93 self.value
94 }
95}
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107pub(in crate::db::session::query) enum PreparedQueryExecutionOutput {
108 Rows,
109 DeleteCount,
110}
111
112pub(in crate::db::session) fn query_error_from_executor_plan_error(
115 err: ExecutorPlanError,
116) -> QueryError {
117 match err {
118 ExecutorPlanError::Cursor(err) => QueryError::from_cursor_plan_error(*err),
119 }
120}
121
122impl<C: CanisterKind> DbSession<C> {
123 pub(in crate::db::session) fn ensure_prepared_query_plan_is_current<E>(
126 &self,
127 plan: &PreparedExecutionPlan<E>,
128 ) -> Result<(), QueryError>
129 where
130 E: PersistedRow<Canister = C>,
131 {
132 let authority = plan
133 .accepted_schema_authority()
134 .map_err(QueryError::execute)?;
135
136 self.ensure_accepted_schema_authority_is_current::<E>(authority)
137 .map_err(QueryError::execute)
138 }
139
140 pub(in crate::db::session::query) fn ensure_scalar_paged_execution_family(
143 family: ExecutionFamily,
144 ) -> Result<(), QueryError> {
145 match family {
146 ExecutionFamily::Ordered => Ok(()),
147 ExecutionFamily::PrimaryKey | ExecutionFamily::Grouped => Err(QueryError::invariant()),
148 }
149 }
150
151 pub(in crate::db::session::query) fn ensure_grouped_execution_family(
154 family: ExecutionFamily,
155 ) -> Result<(), QueryError> {
156 match family {
157 ExecutionFamily::Grouped => Ok(()),
158 ExecutionFamily::PrimaryKey | ExecutionFamily::Ordered => Err(QueryError::invariant()),
159 }
160 }
161
162 pub fn execute_scalar_query_rows<E>(
167 &self,
168 query: &Query<E>,
169 ) -> Result<EntityResponse<E>, QueryError>
170 where
171 E: PersistedRow<Canister = C>,
172 {
173 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
174 self.ensure_prepared_query_plan_is_current(&plan)?;
175
176 if plan.is_grouped() {
177 return Err(QueryError::invariant());
178 }
179
180 match plan.mode() {
181 QueryMode::Load(_) => self
182 .with_metrics(|| self.load_executor::<E>().execute(plan))
183 .map_err(QueryError::execute),
184 QueryMode::Delete(_) => Err(QueryError::unsupported_query()),
185 }
186 }
187
188 #[doc(hidden)]
190 pub fn execute_delete_rows<E>(&self, query: &Query<E>) -> Result<EntityResponse<E>, QueryError>
191 where
192 E: PersistedRow<Canister = C>,
193 {
194 if !query.mode().is_delete() {
196 return Err(QueryError::unsupported_query());
197 }
198
199 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
202
203 match self.execute_prepared(plan, false, PreparedQueryExecutionOutput::Rows)? {
206 PreparedQueryExecutionOutcome::Delete { rows } => Ok(rows),
207 PreparedQueryExecutionOutcome::Scalar { .. }
208 | PreparedQueryExecutionOutcome::Grouped { .. }
209 | PreparedQueryExecutionOutcome::DeleteCount { .. } => Err(QueryError::invariant()),
210 }
211 }
212
213 #[doc(hidden)]
216 pub fn execute_query_result<E>(
217 &self,
218 query: &Query<E>,
219 ) -> Result<LoadQueryResult<E>, QueryError>
220 where
221 E: PersistedRow<Canister = C>,
222 {
223 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
226
227 self.execute_prepared(plan, false, PreparedQueryExecutionOutput::Rows)
230 .and_then(Self::load_result_from_prepared_outcome)
231 }
232
233 #[doc(hidden)]
235 pub fn execute_delete_count<E>(&self, query: &Query<E>) -> Result<u32, QueryError>
236 where
237 E: PersistedRow<Canister = C>,
238 {
239 if !query.mode().is_delete() {
241 return Err(QueryError::unsupported_query());
242 }
243
244 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
248
249 match self.execute_prepared(plan, false, PreparedQueryExecutionOutput::DeleteCount)? {
252 PreparedQueryExecutionOutcome::DeleteCount { row_count } => Ok(row_count),
253 PreparedQueryExecutionOutcome::Scalar { .. }
254 | PreparedQueryExecutionOutcome::Grouped { .. }
255 | PreparedQueryExecutionOutcome::Delete { .. } => Err(QueryError::invariant()),
256 }
257 }
258
259 pub(in crate::db::session::query) fn execute_prepared<E>(
263 &self,
264 plan: PreparedExecutionPlan<E>,
265 collect_attribution: bool,
266 output: PreparedQueryExecutionOutput,
267 ) -> Result<PreparedQueryExecutionOutcome<E>, QueryError>
268 where
269 E: PersistedRow<Canister = C>,
270 {
271 #[cfg(not(feature = "diagnostics"))]
272 let _ = collect_attribution;
273
274 if plan.is_grouped() {
275 if output == PreparedQueryExecutionOutput::DeleteCount {
276 return Err(QueryError::invariant());
277 }
278
279 #[cfg(feature = "diagnostics")]
280 if collect_attribution {
281 let (result, trace, phase) =
282 self.execute_grouped_with_phase_attribution(plan, None)?;
283
284 return Ok(PreparedQueryExecutionOutcome::Grouped {
285 result,
286 trace,
287 phase: Some(phase),
288 });
289 }
290
291 let (result, trace) = self.execute_grouped_with_trace(plan, None)?;
292
293 return Ok(PreparedQueryExecutionOutcome::Grouped {
294 result,
295 trace,
296 #[cfg(feature = "diagnostics")]
297 phase: None,
298 });
299 }
300
301 self.ensure_prepared_query_plan_is_current(&plan)?;
302
303 match plan.mode() {
304 QueryMode::Load(_) => {
305 if output == PreparedQueryExecutionOutput::DeleteCount {
306 return Err(QueryError::invariant());
307 }
308
309 #[cfg(feature = "diagnostics")]
310 if collect_attribution {
311 let (rows, phase, response_decode_local_instructions) = self
312 .load_executor::<E>()
313 .execute_with_phase_attribution(plan)
314 .map_err(QueryError::execute)?;
315
316 return Ok(PreparedQueryExecutionOutcome::Scalar {
317 rows,
318 phase: Some(phase),
319 response_decode_local_instructions,
320 });
321 }
322
323 let rows = self
324 .with_metrics(|| self.load_executor::<E>().execute(plan))
325 .map_err(QueryError::execute)?;
326
327 Ok(PreparedQueryExecutionOutcome::Scalar {
328 rows,
329 #[cfg(feature = "diagnostics")]
330 phase: None,
331 #[cfg(feature = "diagnostics")]
332 response_decode_local_instructions: 0,
333 })
334 }
335 QueryMode::Delete(_) => match output {
336 PreparedQueryExecutionOutput::Rows => {
337 let rows = self
338 .with_metrics(|| self.delete_executor::<E>().execute(plan))
339 .map_err(QueryError::execute)?;
340
341 Ok(PreparedQueryExecutionOutcome::Delete { rows })
342 }
343 PreparedQueryExecutionOutput::DeleteCount => {
344 let row_count = self
345 .with_metrics(|| self.delete_executor::<E>().execute_count(plan))
346 .map_err(QueryError::execute)?;
347
348 Ok(PreparedQueryExecutionOutcome::DeleteCount { row_count })
349 }
350 },
351 }
352 }
353
354 fn load_result_from_prepared_outcome<E>(
358 outcome: PreparedQueryExecutionOutcome<E>,
359 ) -> Result<LoadQueryResult<E>, QueryError>
360 where
361 E: PersistedRow<Canister = C>,
362 {
363 match outcome {
364 PreparedQueryExecutionOutcome::Scalar { rows, .. }
365 | PreparedQueryExecutionOutcome::Delete { rows } => Ok(LoadQueryResult::Rows(rows)),
366 PreparedQueryExecutionOutcome::Grouped { result, trace, .. } => {
367 finalize_structural_grouped_projection_result(result, trace)
368 .map(LoadQueryResult::Grouped)
369 }
370 PreparedQueryExecutionOutcome::DeleteCount { .. } => Err(QueryError::invariant()),
371 }
372 }
373
374 pub(in crate::db) fn execute_with_plan<E, T>(
377 &self,
378 query: &Query<E>,
379 op: impl FnOnce(LoadExecutor<E>, PreparedExecutionPlan<E>) -> Result<T, InternalError>,
380 ) -> Result<T, QueryError>
381 where
382 E: PersistedRow<Canister = C>,
383 {
384 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
385 self.ensure_prepared_query_plan_is_current(&plan)?;
386
387 self.with_metrics(|| op(self.load_executor::<E>(), plan))
388 .map_err(QueryError::execute)
389 }
390
391 pub(in crate::db) fn execute_with_plan_and_catalog<E, T>(
394 &self,
395 query: &Query<E>,
396 op: impl FnOnce(LoadExecutor<E>, PreparedExecutionPlan<E>) -> Result<T, InternalError>,
397 ) -> Result<AcceptedExecutionOutput<T>, QueryError>
398 where
399 E: PersistedRow<Canister = C>,
400 {
401 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
402 self.ensure_prepared_query_plan_is_current(&plan)?;
403 let enum_catalog = plan
404 .accepted_enum_catalog_handle()
405 .map_err(QueryError::execute)?
406 .clone();
407 let value = self
408 .with_metrics(|| op(self.load_executor::<E>(), plan))
409 .map_err(QueryError::execute)?;
410
411 Ok(AcceptedExecutionOutput::new(value, enum_catalog))
412 }
413}