icydb_core/db/session/query/
explain.rs1use crate::{
7 db::{
8 DbSession, Query, QueryError, QueryTracePlan, TraceExecutionFamily,
9 access::summarize_executable_access_plan,
10 executor::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<(AccessPlannedQuery, QueryPlanCacheAttribution), QueryError>
55 where
56 E: EntityKind<Canister = C>,
57 {
58 let (prepared_plan, cache_attribution) =
59 self.cached_shared_query_plan_for_entity::<E>(query)?;
60 let mut plan = prepared_plan.logical_plan().clone();
61
62 plan.finalize_access_choice_for_model_with_indexes(
63 query.structural().model(),
64 visible_indexes.as_slice(),
65 );
66
67 Ok((plan, cache_attribution))
68 }
69
70 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
72 &self,
73 query: &Query<E>,
74 ) -> Result<ExplainPlan, QueryError>
75 where
76 E: EntityKind<Canister = C>,
77 {
78 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.explain()))
79 }
80
81 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
84 &self,
85 query: &Query<E>,
86 ) -> Result<String, QueryError>
87 where
88 E: EntityKind<Canister = C>,
89 {
90 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.fingerprint().to_string()))
91 }
92
93 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
96 &self,
97 query: &Query<E>,
98 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
99 where
100 E: EntityValue + EntityKind<Canister = C>,
101 {
102 self.with_query_visible_indexes(query, |query, visible_indexes| {
103 let (plan, _) = self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
104
105 query
106 .structural()
107 .explain_execution_descriptor_from_plan(&plan)
108 })
109 }
110
111 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
114 &self,
115 query: &Query<E>,
116 ) -> Result<String, QueryError>
117 where
118 E: EntityValue + EntityKind<Canister = C>,
119 {
120 self.with_query_visible_indexes(query, |query, visible_indexes| {
121 let (plan, cache_attribution) =
122 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
123
124 query
125 .structural()
126 .finalized_execution_diagnostics_from_plan_with_descriptor_mutator(
127 &plan,
128 Some(query_plan_cache_reuse_event(cache_attribution)),
129 |_| {},
130 )
131 .map(|diagnostics| diagnostics.render_text_verbose())
132 })
133 }
134
135 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
138 &self,
139 query: &Query<E>,
140 strategy: &S,
141 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
142 where
143 E: EntityValue + EntityKind<Canister = C>,
144 S: AggregateExplain,
145 {
146 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
147
148 plan.explain_prepared_aggregate_terminal(strategy)
149 }
150
151 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
154 &self,
155 query: &Query<E>,
156 target_field: &str,
157 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
158 where
159 E: EntityValue + EntityKind<Canister = C>,
160 {
161 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
162
163 plan.explain_bytes_by_terminal(target_field)
164 }
165
166 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
169 &self,
170 query: &Query<E>,
171 strategy: &S,
172 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
173 where
174 E: EntityValue + EntityKind<Canister = C>,
175 S: ProjectionExplain,
176 {
177 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
178
179 plan.explain_prepared_projection_terminal(strategy)
180 }
181
182 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
187 where
188 E: EntityKind<Canister = C>,
189 {
190 let (prepared_plan, cache_attribution) =
191 self.cached_prepared_query_plan_for_entity::<E>(query)?;
192 let logical_plan = prepared_plan.logical_plan();
193 let explain = logical_plan.explain();
194 let plan_hash = logical_plan.fingerprint().to_string();
195 let executable_access = prepared_plan.access().executable_contract();
196 let access_strategy = summarize_executable_access_plan(&executable_access);
197 let execution_family = match prepared_plan.mode() {
198 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
199 prepared_plan
200 .execution_family()
201 .map_err(QueryError::execute)?,
202 )),
203 QueryMode::Delete(_) => None,
204 };
205 let reuse = query_plan_cache_reuse_event(cache_attribution);
206
207 Ok(QueryTracePlan::new(
208 plan_hash,
209 access_strategy,
210 execution_family,
211 reuse,
212 explain,
213 ))
214 }
215}