icydb_core/db/session/query/
explain.rs1use crate::{
7 db::{
8 DbSession, Query, QueryError, QueryTracePlan, TraceExecutionFamily,
9 access::summarize_executable_access_plan,
10 executor::{
11 EntityAuthority, ExecutionFamily, initial_read_plan_requires_materialized_sort,
12 },
13 query::admission::{
14 QueryAdmissionPolicy, QueryAdmissionSummary, QueryMaterializationSummary,
15 },
16 query::builder::{AggregateExplain, ProjectionExplain},
17 query::explain::{
18 ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor, ExplainPlan,
19 },
20 query::plan::{AccessPlannedQuery, QueryMode, VisibleIndexes},
21 session::query::{QueryPlanCacheAttribution, query_plan_cache_reuse_event},
22 },
23 traits::{CanisterKind, EntityKind, EntityValue},
24};
25
26const fn trace_execution_family_from_executor(family: ExecutionFamily) -> TraceExecutionFamily {
29 match family {
30 ExecutionFamily::PrimaryKey => TraceExecutionFamily::PrimaryKey,
31 ExecutionFamily::Ordered => TraceExecutionFamily::Ordered,
32 ExecutionFamily::Grouped => TraceExecutionFamily::Grouped,
33 }
34}
35
36impl<C: CanisterKind> DbSession<C> {
37 fn cached_finalized_explain_plan<E>(
41 &self,
42 query: &Query<E>,
43 visible_indexes: &VisibleIndexes<'_>,
44 ) -> Result<
45 (
46 AccessPlannedQuery,
47 EntityAuthority,
48 QueryPlanCacheAttribution,
49 ),
50 QueryError,
51 >
52 where
53 E: EntityKind<Canister = C>,
54 {
55 let (prepared_plan, cache_attribution) =
56 self.cached_shared_query_plan_for_entity::<E>(query)?;
57 let mut plan = prepared_plan.logical_plan().clone();
58 let authority = prepared_plan.authority();
59 let schema_info = authority
60 .accepted_schema_info()
61 .ok_or_else(QueryError::invariant)?;
62
63 plan.finalize_access_choice_for_model_with_semantic_indexes_and_schema(
64 query.structural().model(),
65 visible_indexes.accepted_semantic_index_contracts(),
66 schema_info,
67 );
68
69 Ok((plan, authority, cache_attribution))
70 }
71
72 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
74 &self,
75 query: &Query<E>,
76 ) -> Result<ExplainPlan, QueryError>
77 where
78 E: EntityKind<Canister = C>,
79 {
80 self.with_query_visible_indexes(query, |query, visible_indexes| {
81 let (plan, _, _) = self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
82
83 Ok(plan.explain())
84 })
85 }
86
87 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
90 &self,
91 query: &Query<E>,
92 ) -> Result<String, QueryError>
93 where
94 E: EntityKind<Canister = C>,
95 {
96 let (prepared_plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
97
98 Ok(prepared_plan.plan_hash_hex())
99 }
100
101 #[doc(hidden)]
103 pub fn ensure_default_query_read_admission<E>(
104 &self,
105 query: &Query<E>,
106 ) -> Result<QueryAdmissionSummary, QueryError>
107 where
108 E: EntityKind<Canister = C>,
109 {
110 self.ensure_query_read_admission_policy(
111 query,
112 &QueryAdmissionPolicy::default_bounded_read(),
113 )
114 }
115
116 pub(in crate::db) fn evaluate_query_read_admission_policy<E>(
122 &self,
123 query: &Query<E>,
124 policy: &QueryAdmissionPolicy,
125 ) -> Result<QueryAdmissionSummary, QueryError>
126 where
127 E: EntityKind<Canister = C>,
128 {
129 let (prepared_plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
130 let mut summary =
131 QueryAdmissionSummary::from_plan(policy.lane(), prepared_plan.logical_plan());
132
133 if initial_read_plan_requires_materialized_sort(&prepared_plan)
134 .map_err(QueryError::execute)?
135 {
136 let returned_row_bound = summary.returned_row_bound();
137 let returned_row_bound_kind = summary.returned_row_bound_kind();
138 summary = summary.with_materialization(QueryMaterializationSummary::sort(
139 returned_row_bound,
140 returned_row_bound_kind,
141 ));
142 }
143
144 Ok(policy.evaluate(summary))
145 }
146
147 pub(in crate::db) fn ensure_query_read_admission_policy<E>(
152 &self,
153 query: &Query<E>,
154 policy: &QueryAdmissionPolicy,
155 ) -> Result<QueryAdmissionSummary, QueryError>
156 where
157 E: EntityKind<Canister = C>,
158 {
159 let admission = self.evaluate_query_read_admission_policy(query, policy)?;
160
161 if let Some(rejection) = admission.rejection() {
162 Err(QueryError::from(rejection.code()))
163 } else {
164 Ok(admission)
165 }
166 }
167
168 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
171 &self,
172 query: &Query<E>,
173 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
174 where
175 E: EntityValue + EntityKind<Canister = C>,
176 {
177 self.with_query_visible_indexes(query, |query, visible_indexes| {
178 let (plan, authority, _) =
179 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
180
181 query
182 .structural()
183 .explain_execution_descriptor_from_plan_with_authority(&plan, &authority)
184 })
185 }
186
187 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
190 &self,
191 query: &Query<E>,
192 ) -> Result<String, QueryError>
193 where
194 E: EntityValue + EntityKind<Canister = C>,
195 {
196 self.with_query_visible_indexes(query, |query, visible_indexes| {
197 let (plan, authority, cache_attribution) =
198 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
199
200 query
201 .structural()
202 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
203 &plan,
204 &authority,
205 Some(query_plan_cache_reuse_event(cache_attribution)),
206 |_| {},
207 )
208 .map(|diagnostics| diagnostics.render_text_verbose())
209 })
210 }
211
212 pub(in crate::db) fn explain_query_execution_json_with_visible_indexes<E>(
215 &self,
216 query: &Query<E>,
217 ) -> Result<String, QueryError>
218 where
219 E: EntityValue + EntityKind<Canister = C>,
220 {
221 self.with_query_visible_indexes(query, |query, visible_indexes| {
222 let (plan, authority, cache_attribution) =
223 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
224
225 query
226 .structural()
227 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
228 &plan,
229 &authority,
230 Some(query_plan_cache_reuse_event(cache_attribution)),
231 |_| {},
232 )
233 .map(|diagnostics| diagnostics.render_json_canonical())
234 })
235 }
236
237 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
240 &self,
241 query: &Query<E>,
242 strategy: &S,
243 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
244 where
245 E: EntityKind<Canister = C>,
246 S: AggregateExplain,
247 {
248 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
249
250 plan.explain_prepared_aggregate_terminal(strategy)
251 }
252
253 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
256 &self,
257 query: &Query<E>,
258 target_field: &str,
259 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
260 where
261 E: EntityKind<Canister = C>,
262 {
263 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
264
265 plan.explain_bytes_by_terminal(target_field)
266 }
267
268 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
271 &self,
272 query: &Query<E>,
273 strategy: &S,
274 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
275 where
276 E: EntityKind<Canister = C>,
277 S: ProjectionExplain,
278 {
279 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
280
281 plan.explain_prepared_projection_terminal(strategy)
282 }
283
284 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
289 where
290 E: EntityKind<Canister = C>,
291 {
292 let (prepared_plan, cache_attribution) =
293 self.cached_shared_query_plan_for_entity::<E>(query)?;
294 let logical_plan = prepared_plan.logical_plan();
295 let explain = logical_plan.explain();
296 let plan_hash = prepared_plan.plan_hash_hex();
297 let executable_access = prepared_plan.access().executable_contract();
298 let access_strategy = summarize_executable_access_plan(&executable_access);
299 let execution_family = match prepared_plan.mode() {
300 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
301 prepared_plan
302 .execution_family()
303 .map_err(QueryError::execute)?,
304 )),
305 QueryMode::Delete(_) => None,
306 };
307 let reuse = query_plan_cache_reuse_event(cache_attribution);
308
309 Ok(QueryTracePlan::new(
310 plan_hash,
311 access_strategy,
312 execution_family,
313 reuse,
314 explain,
315 ))
316 }
317}