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.accepted_schema_info().ok_or_else(|| {
70 QueryError::invariant(
71 "session query execution explain lost accepted schema authority before access-choice finalization",
72 )
73 })?;
74
75 plan.finalize_access_choice_for_model_with_accepted_indexes_and_schema(
76 query.structural().model(),
77 visible_indexes.accepted_field_path_indexes(),
78 visible_indexes.accepted_expression_indexes(),
79 schema_info,
80 );
81
82 Ok((plan, authority, cache_attribution))
83 }
84
85 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
87 &self,
88 query: &Query<E>,
89 ) -> Result<ExplainPlan, QueryError>
90 where
91 E: EntityKind<Canister = C>,
92 {
93 self.with_query_visible_indexes(query, |query, visible_indexes| {
94 self.try_map_cached_logical_query_plan(query, |plan| {
95 let mut plan = plan.clone();
96 let schema_info = visible_indexes.accepted_schema_info().ok_or_else(|| {
97 QueryError::invariant(
98 "session query explain lost accepted schema visibility before access-choice finalization",
99 )
100 })?;
101 plan.finalize_access_choice_for_model_with_accepted_indexes_and_schema(
102 query.structural().model(),
103 visible_indexes.accepted_field_path_indexes(),
104 visible_indexes.accepted_expression_indexes(),
105 schema_info,
106 );
107
108 Ok(plan.explain())
109 })
110 })
111 }
112
113 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
116 &self,
117 query: &Query<E>,
118 ) -> Result<String, QueryError>
119 where
120 E: EntityKind<Canister = C>,
121 {
122 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.fingerprint().to_string()))
123 }
124
125 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
128 &self,
129 query: &Query<E>,
130 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
131 where
132 E: EntityValue + EntityKind<Canister = C>,
133 {
134 self.with_query_visible_indexes(query, |query, visible_indexes| {
135 let (plan, authority, _) =
136 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
137
138 query
139 .structural()
140 .explain_execution_descriptor_from_plan_with_authority(&plan, &authority)
141 })
142 }
143
144 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
147 &self,
148 query: &Query<E>,
149 ) -> Result<String, QueryError>
150 where
151 E: EntityValue + EntityKind<Canister = C>,
152 {
153 self.with_query_visible_indexes(query, |query, visible_indexes| {
154 let (plan, authority, cache_attribution) =
155 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
156
157 query
158 .structural()
159 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
160 &plan,
161 &authority,
162 Some(query_plan_cache_reuse_event(cache_attribution)),
163 |_| {},
164 )
165 .map(|diagnostics| diagnostics.render_text_verbose())
166 })
167 }
168
169 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
172 &self,
173 query: &Query<E>,
174 strategy: &S,
175 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
176 where
177 E: EntityValue + EntityKind<Canister = C>,
178 S: AggregateExplain,
179 {
180 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
181
182 plan.explain_prepared_aggregate_terminal(strategy)
183 }
184
185 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
188 &self,
189 query: &Query<E>,
190 target_field: &str,
191 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
192 where
193 E: EntityValue + EntityKind<Canister = C>,
194 {
195 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
196
197 plan.explain_bytes_by_terminal(target_field)
198 }
199
200 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
203 &self,
204 query: &Query<E>,
205 strategy: &S,
206 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
207 where
208 E: EntityValue + EntityKind<Canister = C>,
209 S: ProjectionExplain,
210 {
211 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
212
213 plan.explain_prepared_projection_terminal(strategy)
214 }
215
216 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
221 where
222 E: EntityKind<Canister = C>,
223 {
224 let (prepared_plan, cache_attribution) =
225 self.cached_prepared_query_plan_for_entity::<E>(query)?;
226 let logical_plan = prepared_plan.logical_plan();
227 let explain = logical_plan.explain();
228 let plan_hash = logical_plan.fingerprint().to_string();
229 let executable_access = prepared_plan.access().executable_contract();
230 let access_strategy = summarize_executable_access_plan(&executable_access);
231 let execution_family = match prepared_plan.mode() {
232 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
233 prepared_plan
234 .execution_family()
235 .map_err(QueryError::execute)?,
236 )),
237 QueryMode::Delete(_) => None,
238 };
239 let reuse = query_plan_cache_reuse_event(cache_attribution);
240
241 Ok(QueryTracePlan::new(
242 plan_hash,
243 access_strategy,
244 execution_family,
245 reuse,
246 explain,
247 ))
248 }
249}