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 session::finalize_structural_grouped_projection_result,
18 },
19 error::InternalError,
20 traits::{CanisterKind, EntityValue},
21};
22
23#[cfg_attr(
32 not(feature = "diagnostics"),
33 expect(
34 clippy::large_enum_variant,
35 reason = "non-diagnostics builds keep the grouped execution trace inline to avoid boxing a private session-boundary outcome"
36 )
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
63#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73pub(in crate::db::session::query) enum PreparedQueryExecutionOutput {
74 Rows,
75 DeleteCount,
76}
77
78pub(in crate::db::session) fn query_error_from_executor_plan_error(
81 err: ExecutorPlanError,
82) -> QueryError {
83 match err {
84 ExecutorPlanError::Cursor(err) => QueryError::from_cursor_plan_error(*err),
85 }
86}
87
88impl<C: CanisterKind> DbSession<C> {
89 pub(in crate::db::session::query) fn ensure_scalar_paged_execution_family(
92 family: ExecutionFamily,
93 ) -> Result<(), QueryError> {
94 match family {
95 ExecutionFamily::Ordered => Ok(()),
96 ExecutionFamily::PrimaryKey | ExecutionFamily::Grouped => Err(QueryError::invariant()),
97 }
98 }
99
100 pub(in crate::db::session::query) fn ensure_grouped_execution_family(
103 family: ExecutionFamily,
104 ) -> Result<(), QueryError> {
105 match family {
106 ExecutionFamily::Grouped => Ok(()),
107 ExecutionFamily::PrimaryKey | ExecutionFamily::Ordered => Err(QueryError::invariant()),
108 }
109 }
110
111 pub fn execute_scalar_query_rows<E>(
116 &self,
117 query: &Query<E>,
118 ) -> Result<EntityResponse<E>, QueryError>
119 where
120 E: PersistedRow<Canister = C> + EntityValue,
121 {
122 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
123
124 if plan.is_grouped() {
125 return Err(QueryError::invariant());
126 }
127
128 match plan.mode() {
129 QueryMode::Load(_) => self
130 .with_metrics(|| self.load_executor::<E>().execute(plan))
131 .map_err(QueryError::execute),
132 QueryMode::Delete(_) => Err(QueryError::unsupported_query()),
133 }
134 }
135
136 #[doc(hidden)]
138 pub fn execute_delete_rows<E>(&self, query: &Query<E>) -> Result<EntityResponse<E>, QueryError>
139 where
140 E: PersistedRow<Canister = C> + EntityValue,
141 {
142 if !query.mode().is_delete() {
144 return Err(QueryError::unsupported_query());
145 }
146
147 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
150
151 match self.execute_prepared(plan, false, PreparedQueryExecutionOutput::Rows)? {
154 PreparedQueryExecutionOutcome::Delete { rows } => Ok(rows),
155 PreparedQueryExecutionOutcome::Scalar { .. }
156 | PreparedQueryExecutionOutcome::Grouped { .. }
157 | PreparedQueryExecutionOutcome::DeleteCount { .. } => Err(QueryError::invariant()),
158 }
159 }
160
161 #[doc(hidden)]
164 pub fn execute_query_result<E>(
165 &self,
166 query: &Query<E>,
167 ) -> Result<LoadQueryResult<E>, QueryError>
168 where
169 E: PersistedRow<Canister = C> + EntityValue,
170 {
171 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
174
175 self.execute_prepared(plan, false, PreparedQueryExecutionOutput::Rows)
178 .and_then(Self::load_result_from_prepared_outcome)
179 }
180
181 #[doc(hidden)]
183 pub fn execute_delete_count<E>(&self, query: &Query<E>) -> Result<u32, QueryError>
184 where
185 E: PersistedRow<Canister = C> + EntityValue,
186 {
187 if !query.mode().is_delete() {
189 return Err(QueryError::unsupported_query());
190 }
191
192 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
196
197 match self.execute_prepared(plan, false, PreparedQueryExecutionOutput::DeleteCount)? {
200 PreparedQueryExecutionOutcome::DeleteCount { row_count } => Ok(row_count),
201 PreparedQueryExecutionOutcome::Scalar { .. }
202 | PreparedQueryExecutionOutcome::Grouped { .. }
203 | PreparedQueryExecutionOutcome::Delete { .. } => Err(QueryError::invariant()),
204 }
205 }
206
207 pub(in crate::db::session::query) fn execute_prepared<E>(
211 &self,
212 plan: PreparedExecutionPlan<E>,
213 collect_attribution: bool,
214 output: PreparedQueryExecutionOutput,
215 ) -> Result<PreparedQueryExecutionOutcome<E>, QueryError>
216 where
217 E: PersistedRow<Canister = C> + EntityValue,
218 {
219 #[cfg(not(feature = "diagnostics"))]
220 let _ = collect_attribution;
221
222 if plan.is_grouped() {
223 if output == PreparedQueryExecutionOutput::DeleteCount {
224 return Err(QueryError::invariant());
225 }
226
227 #[cfg(feature = "diagnostics")]
228 if collect_attribution {
229 let (result, trace, phase) =
230 self.execute_grouped_with_phase_attribution(plan, None)?;
231
232 return Ok(PreparedQueryExecutionOutcome::Grouped {
233 result,
234 trace,
235 phase: Some(phase),
236 });
237 }
238
239 let (result, trace) = self.execute_grouped_with_trace(plan, None)?;
240
241 return Ok(PreparedQueryExecutionOutcome::Grouped {
242 result,
243 trace,
244 #[cfg(feature = "diagnostics")]
245 phase: None,
246 });
247 }
248
249 match plan.mode() {
250 QueryMode::Load(_) => {
251 if output == PreparedQueryExecutionOutput::DeleteCount {
252 return Err(QueryError::invariant());
253 }
254
255 #[cfg(feature = "diagnostics")]
256 if collect_attribution {
257 let (rows, phase, response_decode_local_instructions) = self
258 .load_executor::<E>()
259 .execute_with_phase_attribution(plan)
260 .map_err(QueryError::execute)?;
261
262 return Ok(PreparedQueryExecutionOutcome::Scalar {
263 rows,
264 phase: Some(phase),
265 response_decode_local_instructions,
266 });
267 }
268
269 let rows = self
270 .with_metrics(|| self.load_executor::<E>().execute(plan))
271 .map_err(QueryError::execute)?;
272
273 Ok(PreparedQueryExecutionOutcome::Scalar {
274 rows,
275 #[cfg(feature = "diagnostics")]
276 phase: None,
277 #[cfg(feature = "diagnostics")]
278 response_decode_local_instructions: 0,
279 })
280 }
281 QueryMode::Delete(_) => match output {
282 PreparedQueryExecutionOutput::Rows => {
283 let rows = self
284 .with_metrics(|| self.delete_executor::<E>().execute(plan))
285 .map_err(QueryError::execute)?;
286
287 Ok(PreparedQueryExecutionOutcome::Delete { rows })
288 }
289 PreparedQueryExecutionOutput::DeleteCount => {
290 let row_count = self
291 .with_metrics(|| self.delete_executor::<E>().execute_count(plan))
292 .map_err(QueryError::execute)?;
293
294 Ok(PreparedQueryExecutionOutcome::DeleteCount { row_count })
295 }
296 },
297 }
298 }
299
300 fn load_result_from_prepared_outcome<E>(
304 outcome: PreparedQueryExecutionOutcome<E>,
305 ) -> Result<LoadQueryResult<E>, QueryError>
306 where
307 E: PersistedRow<Canister = C> + EntityValue,
308 {
309 match outcome {
310 PreparedQueryExecutionOutcome::Scalar { rows, .. }
311 | PreparedQueryExecutionOutcome::Delete { rows } => Ok(LoadQueryResult::Rows(rows)),
312 PreparedQueryExecutionOutcome::Grouped { result, trace, .. } => {
313 finalize_structural_grouped_projection_result(result, trace)
314 .map(LoadQueryResult::Grouped)
315 }
316 PreparedQueryExecutionOutcome::DeleteCount { .. } => Err(QueryError::invariant()),
317 }
318 }
319
320 pub(in crate::db) fn execute_with_plan<E, T>(
323 &self,
324 query: &Query<E>,
325 op: impl FnOnce(LoadExecutor<E>, PreparedExecutionPlan<E>) -> Result<T, InternalError>,
326 ) -> Result<T, QueryError>
327 where
328 E: PersistedRow<Canister = C> + EntityValue,
329 {
330 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
331
332 self.with_metrics(|| op(self.load_executor::<E>(), plan))
333 .map_err(QueryError::execute)
334 }
335}