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::AcceptedValueCatalogHandle,
18 session::finalize_structural_grouped_projection_result,
19 },
20 error::InternalError,
21 traits::CanisterKind,
22 types::Id,
23 value::Value,
24};
25
26#[expect(
35 clippy::large_enum_variant,
36 reason = "the private grouped outcome keeps its execution trace and optional diagnostics attribution inline so attribution does not measure a boundary-only box allocation"
37)]
38pub(in crate::db::session::query) enum PreparedQueryExecutionOutcome<E>
39where
40 E: PersistedRow,
41{
42 Scalar {
43 rows: EntityResponse<E>,
44 #[cfg(feature = "diagnostics")]
45 phase: Option<ScalarExecutePhaseAttribution>,
46 #[cfg(feature = "diagnostics")]
47 response_decode_local_instructions: u64,
48 },
49 Grouped {
50 result: StructuralGroupedProjectionResult,
51 trace: Option<ExecutionTrace>,
52 #[cfg(feature = "diagnostics")]
53 phase: Option<GroupedExecutePhaseAttribution>,
54 },
55 Delete {
56 rows: EntityResponse<E>,
57 },
58 DeleteCount {
59 row_count: u32,
60 },
61}
62
63pub(in crate::db) struct AcceptedExecutionOutput<T> {
66 value: T,
67 value_catalog: AcceptedValueCatalogHandle,
68}
69
70pub(in crate::db) type AcceptedValuesOutput = AcceptedExecutionOutput<Vec<Value>>;
71pub(in crate::db) type AcceptedIdValuesOutput<E> = AcceptedExecutionOutput<Vec<(Id<E>, Value)>>;
72pub(in crate::db) type AcceptedOptionalValueOutput = AcceptedExecutionOutput<Option<Value>>;
73
74impl<T> AcceptedExecutionOutput<T> {
75 #[must_use]
76 pub(in crate::db) const fn new(value: T, value_catalog: AcceptedValueCatalogHandle) -> Self {
77 Self {
78 value,
79 value_catalog,
80 }
81 }
82
83 #[must_use]
84 pub(in crate::db) fn into_parts(self) -> (T, AcceptedValueCatalogHandle) {
85 (self.value, self.value_catalog)
86 }
87
88 #[must_use]
89 pub(in crate::db) fn into_value(self) -> T {
90 self.value
91 }
92}
93
94#[derive(Clone, Copy, Debug, Eq, PartialEq)]
104pub(in crate::db::session::query) enum PreparedQueryExecutionOutput {
105 Rows,
106 DeleteCount,
107}
108
109pub(in crate::db::session) fn query_error_from_executor_plan_error(
112 err: ExecutorPlanError,
113) -> QueryError {
114 match err {
115 ExecutorPlanError::Cursor(err) => QueryError::from_cursor_plan_error(*err),
116 }
117}
118
119impl<C: CanisterKind> DbSession<C> {
120 pub(in crate::db::session) fn ensure_prepared_query_plan_is_current<E>(
123 &self,
124 plan: &PreparedExecutionPlan<E>,
125 ) -> Result<(), QueryError>
126 where
127 E: PersistedRow<Canister = C>,
128 {
129 let authority = plan
130 .accepted_schema_authority()
131 .map_err(QueryError::execute)?;
132
133 self.ensure_accepted_schema_authority_is_current::<E>(authority)
134 .map_err(QueryError::execute)
135 }
136
137 pub(in crate::db::session::query) fn ensure_scalar_paged_execution_family(
140 family: ExecutionFamily,
141 ) -> Result<(), QueryError> {
142 match family {
143 ExecutionFamily::Ordered => Ok(()),
144 ExecutionFamily::PrimaryKey | ExecutionFamily::Grouped => Err(QueryError::invariant()),
145 }
146 }
147
148 pub(in crate::db::session::query) fn ensure_grouped_execution_family(
151 family: ExecutionFamily,
152 ) -> Result<(), QueryError> {
153 match family {
154 ExecutionFamily::Grouped => Ok(()),
155 ExecutionFamily::PrimaryKey | ExecutionFamily::Ordered => Err(QueryError::invariant()),
156 }
157 }
158
159 pub fn execute_scalar_query_rows<E>(
164 &self,
165 query: &Query<E>,
166 ) -> Result<EntityResponse<E>, QueryError>
167 where
168 E: PersistedRow<Canister = C>,
169 {
170 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
171 self.ensure_prepared_query_plan_is_current(&plan)?;
172
173 if plan.is_grouped() {
174 return Err(QueryError::invariant());
175 }
176
177 match plan.mode() {
178 QueryMode::Load(_) => self
179 .with_metrics(|| self.load_executor::<E>().execute(plan))
180 .map_err(QueryError::execute),
181 QueryMode::Delete(_) => Err(QueryError::unsupported_query()),
182 }
183 }
184
185 #[doc(hidden)]
187 pub fn execute_delete_rows<E>(&self, query: &Query<E>) -> Result<EntityResponse<E>, QueryError>
188 where
189 E: PersistedRow<Canister = C>,
190 {
191 if !query.mode().is_delete() {
193 return Err(QueryError::unsupported_query());
194 }
195
196 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
199
200 match self.execute_prepared(plan, false, PreparedQueryExecutionOutput::Rows)? {
203 PreparedQueryExecutionOutcome::Delete { rows } => Ok(rows),
204 PreparedQueryExecutionOutcome::Scalar { .. }
205 | PreparedQueryExecutionOutcome::Grouped { .. }
206 | PreparedQueryExecutionOutcome::DeleteCount { .. } => Err(QueryError::invariant()),
207 }
208 }
209
210 #[doc(hidden)]
213 pub fn execute_query_result<E>(
214 &self,
215 query: &Query<E>,
216 ) -> Result<LoadQueryResult<E>, QueryError>
217 where
218 E: PersistedRow<Canister = C>,
219 {
220 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
223
224 self.execute_prepared(plan, false, PreparedQueryExecutionOutput::Rows)
227 .and_then(Self::load_result_from_prepared_outcome)
228 }
229
230 #[doc(hidden)]
232 pub fn execute_delete_count<E>(&self, query: &Query<E>) -> Result<u32, QueryError>
233 where
234 E: PersistedRow<Canister = C>,
235 {
236 if !query.mode().is_delete() {
238 return Err(QueryError::unsupported_query());
239 }
240
241 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
245
246 match self.execute_prepared(plan, false, PreparedQueryExecutionOutput::DeleteCount)? {
249 PreparedQueryExecutionOutcome::DeleteCount { row_count } => Ok(row_count),
250 PreparedQueryExecutionOutcome::Scalar { .. }
251 | PreparedQueryExecutionOutcome::Grouped { .. }
252 | PreparedQueryExecutionOutcome::Delete { .. } => Err(QueryError::invariant()),
253 }
254 }
255
256 pub(in crate::db::session::query) fn execute_prepared<E>(
260 &self,
261 plan: PreparedExecutionPlan<E>,
262 collect_attribution: bool,
263 output: PreparedQueryExecutionOutput,
264 ) -> Result<PreparedQueryExecutionOutcome<E>, QueryError>
265 where
266 E: PersistedRow<Canister = C>,
267 {
268 #[cfg(not(feature = "diagnostics"))]
269 let _ = collect_attribution;
270
271 if plan.is_grouped() {
272 if output == PreparedQueryExecutionOutput::DeleteCount {
273 return Err(QueryError::invariant());
274 }
275
276 #[cfg(feature = "diagnostics")]
277 if collect_attribution {
278 let (result, trace, phase) =
279 self.execute_grouped_with_phase_attribution(plan, None)?;
280
281 return Ok(PreparedQueryExecutionOutcome::Grouped {
282 result,
283 trace,
284 phase: Some(phase),
285 });
286 }
287
288 let (result, trace) = self.execute_grouped_with_trace(plan, None)?;
289
290 return Ok(PreparedQueryExecutionOutcome::Grouped {
291 result,
292 trace,
293 #[cfg(feature = "diagnostics")]
294 phase: None,
295 });
296 }
297
298 self.ensure_prepared_query_plan_is_current(&plan)?;
299
300 match plan.mode() {
301 QueryMode::Load(_) => {
302 if output == PreparedQueryExecutionOutput::DeleteCount {
303 return Err(QueryError::invariant());
304 }
305
306 #[cfg(feature = "diagnostics")]
307 if collect_attribution {
308 let (rows, phase, response_decode_local_instructions) = self
309 .load_executor::<E>()
310 .execute_with_phase_attribution(plan)
311 .map_err(QueryError::execute)?;
312
313 return Ok(PreparedQueryExecutionOutcome::Scalar {
314 rows,
315 phase: Some(phase),
316 response_decode_local_instructions,
317 });
318 }
319
320 let rows = self
321 .with_metrics(|| self.load_executor::<E>().execute(plan))
322 .map_err(QueryError::execute)?;
323
324 Ok(PreparedQueryExecutionOutcome::Scalar {
325 rows,
326 #[cfg(feature = "diagnostics")]
327 phase: None,
328 #[cfg(feature = "diagnostics")]
329 response_decode_local_instructions: 0,
330 })
331 }
332 QueryMode::Delete(_) => match output {
333 PreparedQueryExecutionOutput::Rows => {
334 let rows = self
335 .with_metrics(|| self.delete_executor::<E>().execute(plan))
336 .map_err(QueryError::execute)?;
337
338 Ok(PreparedQueryExecutionOutcome::Delete { rows })
339 }
340 PreparedQueryExecutionOutput::DeleteCount => {
341 let row_count = self
342 .with_metrics(|| self.delete_executor::<E>().execute_count(plan))
343 .map_err(QueryError::execute)?;
344
345 Ok(PreparedQueryExecutionOutcome::DeleteCount { row_count })
346 }
347 },
348 }
349 }
350
351 fn load_result_from_prepared_outcome<E>(
355 outcome: PreparedQueryExecutionOutcome<E>,
356 ) -> Result<LoadQueryResult<E>, QueryError>
357 where
358 E: PersistedRow<Canister = C>,
359 {
360 match outcome {
361 PreparedQueryExecutionOutcome::Scalar { rows, .. }
362 | PreparedQueryExecutionOutcome::Delete { rows } => Ok(LoadQueryResult::Rows(rows)),
363 PreparedQueryExecutionOutcome::Grouped { result, trace, .. } => {
364 finalize_structural_grouped_projection_result(result, trace)
365 .map(LoadQueryResult::Grouped)
366 }
367 PreparedQueryExecutionOutcome::DeleteCount { .. } => Err(QueryError::invariant()),
368 }
369 }
370
371 pub(in crate::db) fn execute_with_plan<E, T>(
374 &self,
375 query: &Query<E>,
376 op: impl FnOnce(LoadExecutor<E>, PreparedExecutionPlan<E>) -> Result<T, InternalError>,
377 ) -> Result<T, QueryError>
378 where
379 E: PersistedRow<Canister = C>,
380 {
381 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
382 self.ensure_prepared_query_plan_is_current(&plan)?;
383
384 self.with_metrics(|| op(self.load_executor::<E>(), plan))
385 .map_err(QueryError::execute)
386 }
387
388 pub(in crate::db) fn execute_with_plan_and_catalog<E, T>(
391 &self,
392 query: &Query<E>,
393 op: impl FnOnce(LoadExecutor<E>, PreparedExecutionPlan<E>) -> Result<T, InternalError>,
394 ) -> Result<AcceptedExecutionOutput<T>, QueryError>
395 where
396 E: PersistedRow<Canister = C>,
397 {
398 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
399 self.ensure_prepared_query_plan_is_current(&plan)?;
400 let value_catalog = plan
401 .accepted_value_catalog_handle()
402 .map_err(QueryError::execute)?
403 .clone();
404 let value = self
405 .with_metrics(|| op(self.load_executor::<E>(), plan))
406 .map_err(QueryError::execute)?;
407
408 Ok(AcceptedExecutionOutput::new(value, value_catalog))
409 }
410}