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::{
12 PreparedFluentAggregateExplainStrategy, PreparedFluentProjectionStrategy,
13 },
14 query::explain::{
15 ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor, ExplainPlan,
16 },
17 query::plan::{AccessPlannedQuery, QueryMode, VisibleIndexes},
18 session::query::{QueryPlanCacheAttribution, query_plan_cache_reuse_event},
19 },
20 traits::{CanisterKind, EntityKind, EntityValue},
21};
22
23const fn trace_execution_family_from_executor(family: ExecutionFamily) -> TraceExecutionFamily {
26 match family {
27 ExecutionFamily::PrimaryKey => TraceExecutionFamily::PrimaryKey,
28 ExecutionFamily::Ordered => TraceExecutionFamily::Ordered,
29 ExecutionFamily::Grouped => TraceExecutionFamily::Grouped,
30 }
31}
32
33impl<C: CanisterKind> DbSession<C> {
34 fn try_map_cached_logical_query_plan<E, T>(
37 &self,
38 query: &Query<E>,
39 map: impl FnOnce(&AccessPlannedQuery) -> Result<T, QueryError>,
40 ) -> Result<T, QueryError>
41 where
42 E: EntityKind<Canister = C>,
43 {
44 self.try_map_cached_shared_query_plan_ref_for_entity::<E, T>(query, |prepared_plan| {
45 map(prepared_plan.logical_plan())
46 })
47 }
48
49 fn cached_execution_explain_plan<E>(
53 &self,
54 query: &Query<E>,
55 visible_indexes: &VisibleIndexes<'_>,
56 ) -> Result<(AccessPlannedQuery, QueryPlanCacheAttribution), QueryError>
57 where
58 E: EntityKind<Canister = C>,
59 {
60 let (prepared_plan, cache_attribution) =
61 self.cached_shared_query_plan_for_entity::<E>(query)?;
62 let mut plan = prepared_plan.logical_plan().clone();
63
64 plan.finalize_access_choice_for_model_with_indexes(
65 query.structural().model(),
66 visible_indexes.as_slice(),
67 );
68
69 Ok((plan, cache_attribution))
70 }
71
72 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
74 &self,
75 query: &Query<E>,
76 ) -> Result<ExplainPlan, QueryError>
77 where
78 E: EntityKind<Canister = C>,
79 {
80 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.explain()))
81 }
82
83 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
86 &self,
87 query: &Query<E>,
88 ) -> Result<String, QueryError>
89 where
90 E: EntityKind<Canister = C>,
91 {
92 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.fingerprint().to_string()))
93 }
94
95 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
98 &self,
99 query: &Query<E>,
100 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
101 where
102 E: EntityValue + EntityKind<Canister = C>,
103 {
104 self.with_query_visible_indexes(query, |query, visible_indexes| {
105 let (plan, _) = self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
106
107 query
108 .structural()
109 .explain_execution_descriptor_from_plan(&plan)
110 })
111 }
112
113 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
116 &self,
117 query: &Query<E>,
118 ) -> Result<String, QueryError>
119 where
120 E: EntityValue + EntityKind<Canister = C>,
121 {
122 self.with_query_visible_indexes(query, |query, visible_indexes| {
123 let (plan, cache_attribution) =
124 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
125
126 query
127 .structural()
128 .finalized_execution_diagnostics_from_plan_with_descriptor_mutator(
129 &plan,
130 Some(query_plan_cache_reuse_event(cache_attribution)),
131 |_| {},
132 )
133 .map(|diagnostics| diagnostics.render_text_verbose())
134 })
135 }
136
137 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
140 &self,
141 query: &Query<E>,
142 strategy: &S,
143 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
144 where
145 E: EntityValue + EntityKind<Canister = C>,
146 S: PreparedFluentAggregateExplainStrategy,
147 {
148 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
149
150 plan.explain_prepared_aggregate_terminal(strategy)
151 }
152
153 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
156 &self,
157 query: &Query<E>,
158 target_field: &str,
159 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
160 where
161 E: EntityValue + EntityKind<Canister = C>,
162 {
163 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
164
165 plan.explain_bytes_by_terminal(target_field)
166 }
167
168 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E>(
171 &self,
172 query: &Query<E>,
173 strategy: &PreparedFluentProjectionStrategy,
174 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
175 where
176 E: EntityValue + EntityKind<Canister = C>,
177 {
178 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
179
180 plan.explain_prepared_projection_terminal(strategy)
181 }
182
183 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
188 where
189 E: EntityKind<Canister = C>,
190 {
191 let (prepared_plan, cache_attribution) =
192 self.cached_prepared_query_plan_for_entity::<E>(query)?;
193 let logical_plan = prepared_plan.logical_plan();
194 let explain = logical_plan.explain();
195 let plan_hash = logical_plan.fingerprint().to_string();
196 let executable_access = prepared_plan.access().executable_contract();
197 let access_strategy = summarize_executable_access_plan(&executable_access);
198 let execution_family = match query.mode() {
199 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
200 prepared_plan
201 .execution_family()
202 .map_err(QueryError::execute)?,
203 )),
204 QueryMode::Delete(_) => None,
205 };
206 let reuse = query_plan_cache_reuse_event(cache_attribution);
207
208 Ok(QueryTracePlan::new(
209 plan_hash,
210 access_strategy,
211 execution_family,
212 reuse,
213 explain,
214 ))
215 }
216}