icydb_core/db/session/query/
explain.rs1use crate::{
7 db::{
8 DbSession, Query, QueryError, QueryTracePlan, TraceExecutionFamily,
9 access::summarize_executable_access_plan,
10 executor::{EntityAuthority, ExecutionFamily},
11 query::builder::{AggregateExplain, ProjectionExplain},
12 query::explain::{
13 ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor, ExplainPlan,
14 },
15 query::plan::{AccessPlannedQuery, QueryMode, VisibleIndexes},
16 session::query::{QueryPlanCacheAttribution, query_plan_cache_reuse_event},
17 },
18 traits::{CanisterKind, EntityKind, EntityValue},
19};
20
21const fn trace_execution_family_from_executor(family: ExecutionFamily) -> TraceExecutionFamily {
24 match family {
25 ExecutionFamily::PrimaryKey => TraceExecutionFamily::PrimaryKey,
26 ExecutionFamily::Ordered => TraceExecutionFamily::Ordered,
27 ExecutionFamily::Grouped => TraceExecutionFamily::Grouped,
28 }
29}
30
31impl<C: CanisterKind> DbSession<C> {
32 fn try_map_cached_logical_query_plan<E, T>(
35 &self,
36 query: &Query<E>,
37 map: impl FnOnce(&AccessPlannedQuery) -> Result<T, QueryError>,
38 ) -> Result<T, QueryError>
39 where
40 E: EntityKind<Canister = C>,
41 {
42 self.try_map_cached_shared_query_plan_ref_for_entity::<E, T>(query, |prepared_plan| {
43 map(prepared_plan.logical_plan())
44 })
45 }
46
47 fn cached_execution_explain_plan<E>(
51 &self,
52 query: &Query<E>,
53 visible_indexes: &VisibleIndexes<'_>,
54 ) -> Result<
55 (
56 AccessPlannedQuery,
57 EntityAuthority,
58 QueryPlanCacheAttribution,
59 ),
60 QueryError,
61 >
62 where
63 E: EntityKind<Canister = C>,
64 {
65 let (prepared_plan, cache_attribution) =
66 self.cached_shared_query_plan_for_entity::<E>(query)?;
67 let mut plan = prepared_plan.logical_plan().clone();
68 let authority = prepared_plan.authority();
69 let schema_info = authority
70 .accepted_schema_info()
71 .ok_or_else(QueryError::invariant)?;
72
73 plan.finalize_access_choice_for_model_with_semantic_indexes_and_schema(
74 query.structural().model(),
75 visible_indexes.accepted_semantic_index_contracts(),
76 schema_info,
77 );
78
79 Ok((plan, authority, cache_attribution))
80 }
81
82 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
84 &self,
85 query: &Query<E>,
86 ) -> Result<ExplainPlan, QueryError>
87 where
88 E: EntityKind<Canister = C>,
89 {
90 self.with_query_visible_indexes(query, |query, visible_indexes| {
91 self.try_map_cached_logical_query_plan(query, |plan| {
92 let mut plan = plan.clone();
93 let schema_info = visible_indexes
94 .accepted_schema_info()
95 .ok_or_else(QueryError::invariant)?;
96 plan.finalize_access_choice_for_model_with_semantic_indexes_and_schema(
97 query.structural().model(),
98 visible_indexes.accepted_semantic_index_contracts(),
99 schema_info,
100 );
101
102 Ok(plan.explain())
103 })
104 })
105 }
106
107 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
110 &self,
111 query: &Query<E>,
112 ) -> Result<String, QueryError>
113 where
114 E: EntityKind<Canister = C>,
115 {
116 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.fingerprint().to_string()))
117 }
118
119 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
122 &self,
123 query: &Query<E>,
124 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
125 where
126 E: EntityValue + EntityKind<Canister = C>,
127 {
128 self.with_query_visible_indexes(query, |query, visible_indexes| {
129 let (plan, authority, _) =
130 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
131
132 query
133 .structural()
134 .explain_execution_descriptor_from_plan_with_authority(&plan, &authority)
135 })
136 }
137
138 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
141 &self,
142 query: &Query<E>,
143 ) -> Result<String, QueryError>
144 where
145 E: EntityValue + EntityKind<Canister = C>,
146 {
147 self.with_query_visible_indexes(query, |query, visible_indexes| {
148 let (plan, authority, cache_attribution) =
149 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
150
151 query
152 .structural()
153 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
154 &plan,
155 &authority,
156 Some(query_plan_cache_reuse_event(cache_attribution)),
157 |_| {},
158 )
159 .map(|diagnostics| diagnostics.render_text_verbose())
160 })
161 }
162
163 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
166 &self,
167 query: &Query<E>,
168 strategy: &S,
169 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
170 where
171 E: EntityValue + EntityKind<Canister = C>,
172 S: AggregateExplain,
173 {
174 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
175
176 plan.explain_prepared_aggregate_terminal(strategy)
177 }
178
179 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
182 &self,
183 query: &Query<E>,
184 target_field: &str,
185 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
186 where
187 E: EntityValue + EntityKind<Canister = C>,
188 {
189 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
190
191 plan.explain_bytes_by_terminal(target_field)
192 }
193
194 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
197 &self,
198 query: &Query<E>,
199 strategy: &S,
200 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
201 where
202 E: EntityValue + EntityKind<Canister = C>,
203 S: ProjectionExplain,
204 {
205 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
206
207 plan.explain_prepared_projection_terminal(strategy)
208 }
209
210 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
215 where
216 E: EntityKind<Canister = C>,
217 {
218 let (prepared_plan, cache_attribution) =
219 self.cached_prepared_query_plan_for_entity::<E>(query)?;
220 let logical_plan = prepared_plan.logical_plan();
221 let explain = logical_plan.explain();
222 let plan_hash = logical_plan.fingerprint().to_string();
223 let executable_access = prepared_plan.access().executable_contract();
224 let access_strategy = summarize_executable_access_plan(&executable_access);
225 let execution_family = match prepared_plan.mode() {
226 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
227 prepared_plan
228 .execution_family()
229 .map_err(QueryError::execute)?,
230 )),
231 QueryMode::Delete(_) => None,
232 };
233 let reuse = query_plan_cache_reuse_event(cache_attribution);
234
235 Ok(QueryTracePlan::new(
236 plan_hash,
237 access_strategy,
238 execution_family,
239 reuse,
240 explain,
241 ))
242 }
243}