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