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 schema::SchemaInfo,
17 session::query::{QueryPlanCacheAttribution, query_plan_cache_reuse_event},
18 },
19 traits::{CanisterKind, EntityKind, EntityValue},
20};
21
22const fn trace_execution_family_from_executor(family: ExecutionFamily) -> TraceExecutionFamily {
25 match family {
26 ExecutionFamily::PrimaryKey => TraceExecutionFamily::PrimaryKey,
27 ExecutionFamily::Ordered => TraceExecutionFamily::Ordered,
28 ExecutionFamily::Grouped => TraceExecutionFamily::Grouped,
29 }
30}
31
32impl<C: CanisterKind> DbSession<C> {
33 fn try_map_cached_logical_query_plan<E, T>(
36 &self,
37 query: &Query<E>,
38 map: impl FnOnce(&AccessPlannedQuery) -> Result<T, QueryError>,
39 ) -> Result<T, QueryError>
40 where
41 E: EntityKind<Canister = C>,
42 {
43 self.try_map_cached_shared_query_plan_ref_for_entity::<E, T>(query, |prepared_plan| {
44 map(prepared_plan.logical_plan())
45 })
46 }
47
48 fn cached_execution_explain_plan<E>(
52 &self,
53 query: &Query<E>,
54 visible_indexes: &VisibleIndexes<'_>,
55 ) -> Result<
56 (
57 AccessPlannedQuery,
58 EntityAuthority,
59 QueryPlanCacheAttribution,
60 ),
61 QueryError,
62 >
63 where
64 E: EntityKind<Canister = C>,
65 {
66 let (prepared_plan, cache_attribution) =
67 self.cached_shared_query_plan_for_entity::<E>(query)?;
68 let mut plan = prepared_plan.logical_plan().clone();
69 let accepted_schema = self
70 .ensure_accepted_schema_snapshot::<E>()
71 .map_err(QueryError::execute)?;
72 let schema_info = SchemaInfo::from_accepted_snapshot_for_model_with_expression_indexes(
73 query.structural().model(),
74 &accepted_schema,
75 true,
76 );
77
78 plan.finalize_access_choice_for_model_with_accepted_indexes_and_schema(
79 query.structural().model(),
80 visible_indexes.accepted_field_path_indexes(),
81 visible_indexes.accepted_expression_indexes(),
82 &schema_info,
83 );
84 let authority = Self::accepted_entity_authority_for_schema::<E>(&accepted_schema)
85 .map_err(QueryError::execute)?;
86
87 Ok((plan, authority, cache_attribution))
88 }
89
90 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
92 &self,
93 query: &Query<E>,
94 ) -> Result<ExplainPlan, QueryError>
95 where
96 E: EntityKind<Canister = C>,
97 {
98 self.with_query_visible_indexes(query, |query, visible_indexes| {
99 self.try_map_cached_logical_query_plan(query, |plan| {
100 let mut plan = plan.clone();
101 let schema_info = visible_indexes.accepted_schema_info().ok_or_else(|| {
102 QueryError::invariant(
103 "session query explain lost accepted schema visibility before access-choice finalization",
104 )
105 })?;
106 plan.finalize_access_choice_for_model_with_accepted_indexes_and_schema(
107 query.structural().model(),
108 visible_indexes.accepted_field_path_indexes(),
109 visible_indexes.accepted_expression_indexes(),
110 schema_info,
111 );
112
113 Ok(plan.explain())
114 })
115 })
116 }
117
118 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
121 &self,
122 query: &Query<E>,
123 ) -> Result<String, QueryError>
124 where
125 E: EntityKind<Canister = C>,
126 {
127 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.fingerprint().to_string()))
128 }
129
130 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
133 &self,
134 query: &Query<E>,
135 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
136 where
137 E: EntityValue + EntityKind<Canister = C>,
138 {
139 self.with_query_visible_indexes(query, |query, visible_indexes| {
140 let (plan, authority, _) =
141 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
142
143 query
144 .structural()
145 .explain_execution_descriptor_from_plan_with_authority(&plan, &authority)
146 })
147 }
148
149 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
152 &self,
153 query: &Query<E>,
154 ) -> Result<String, QueryError>
155 where
156 E: EntityValue + EntityKind<Canister = C>,
157 {
158 self.with_query_visible_indexes(query, |query, visible_indexes| {
159 let (plan, authority, cache_attribution) =
160 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
161
162 query
163 .structural()
164 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
165 &plan,
166 &authority,
167 Some(query_plan_cache_reuse_event(cache_attribution)),
168 |_| {},
169 )
170 .map(|diagnostics| diagnostics.render_text_verbose())
171 })
172 }
173
174 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
177 &self,
178 query: &Query<E>,
179 strategy: &S,
180 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
181 where
182 E: EntityValue + EntityKind<Canister = C>,
183 S: AggregateExplain,
184 {
185 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
186
187 plan.explain_prepared_aggregate_terminal(strategy)
188 }
189
190 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
193 &self,
194 query: &Query<E>,
195 target_field: &str,
196 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
197 where
198 E: EntityValue + EntityKind<Canister = C>,
199 {
200 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
201
202 plan.explain_bytes_by_terminal(target_field)
203 }
204
205 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
208 &self,
209 query: &Query<E>,
210 strategy: &S,
211 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
212 where
213 E: EntityValue + EntityKind<Canister = C>,
214 S: ProjectionExplain,
215 {
216 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
217
218 plan.explain_prepared_projection_terminal(strategy)
219 }
220
221 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
226 where
227 E: EntityKind<Canister = C>,
228 {
229 let (prepared_plan, cache_attribution) =
230 self.cached_prepared_query_plan_for_entity::<E>(query)?;
231 let logical_plan = prepared_plan.logical_plan();
232 let explain = logical_plan.explain();
233 let plan_hash = logical_plan.fingerprint().to_string();
234 let executable_access = prepared_plan.access().executable_contract();
235 let access_strategy = summarize_executable_access_plan(&executable_access);
236 let execution_family = match prepared_plan.mode() {
237 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
238 prepared_plan
239 .execution_family()
240 .map_err(QueryError::execute)?,
241 )),
242 QueryMode::Delete(_) => None,
243 };
244 let reuse = query_plan_cache_reuse_event(cache_attribution);
245
246 Ok(QueryTracePlan::new(
247 plan_hash,
248 access_strategy,
249 execution_family,
250 reuse,
251 explain,
252 ))
253 }
254}