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 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<(AccessPlannedQuery, QueryPlanCacheAttribution), QueryError>
56 where
57 E: EntityKind<Canister = C>,
58 {
59 let (prepared_plan, cache_attribution) =
60 self.cached_shared_query_plan_for_entity::<E>(query)?;
61 let mut plan = prepared_plan.logical_plan().clone();
62 let accepted_schema = self
63 .ensure_accepted_schema_snapshot::<E>()
64 .map_err(QueryError::execute)?;
65 let schema_info = SchemaInfo::from_accepted_snapshot_for_model(
66 query.structural().model(),
67 &accepted_schema,
68 );
69
70 plan.finalize_access_choice_for_model_with_indexes_and_schema(
71 query.structural().model(),
72 visible_indexes.as_slice(),
73 &schema_info,
74 );
75
76 Ok((plan, cache_attribution))
77 }
78
79 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
81 &self,
82 query: &Query<E>,
83 ) -> Result<ExplainPlan, QueryError>
84 where
85 E: EntityKind<Canister = C>,
86 {
87 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.explain()))
88 }
89
90 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
93 &self,
94 query: &Query<E>,
95 ) -> Result<String, QueryError>
96 where
97 E: EntityKind<Canister = C>,
98 {
99 self.try_map_cached_logical_query_plan(query, |plan| Ok(plan.fingerprint().to_string()))
100 }
101
102 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
105 &self,
106 query: &Query<E>,
107 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
108 where
109 E: EntityValue + EntityKind<Canister = C>,
110 {
111 self.with_query_visible_indexes(query, |query, visible_indexes| {
112 let (plan, _) = self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
113
114 query
115 .structural()
116 .explain_execution_descriptor_from_plan(&plan)
117 })
118 }
119
120 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
123 &self,
124 query: &Query<E>,
125 ) -> Result<String, QueryError>
126 where
127 E: EntityValue + EntityKind<Canister = C>,
128 {
129 self.with_query_visible_indexes(query, |query, visible_indexes| {
130 let (plan, cache_attribution) =
131 self.cached_execution_explain_plan::<E>(query, visible_indexes)?;
132
133 query
134 .structural()
135 .finalized_execution_diagnostics_from_plan_with_descriptor_mutator(
136 &plan,
137 Some(query_plan_cache_reuse_event(cache_attribution)),
138 |_| {},
139 )
140 .map(|diagnostics| diagnostics.render_text_verbose())
141 })
142 }
143
144 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
147 &self,
148 query: &Query<E>,
149 strategy: &S,
150 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
151 where
152 E: EntityValue + EntityKind<Canister = C>,
153 S: AggregateExplain,
154 {
155 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
156
157 plan.explain_prepared_aggregate_terminal(strategy)
158 }
159
160 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
163 &self,
164 query: &Query<E>,
165 target_field: &str,
166 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
167 where
168 E: EntityValue + EntityKind<Canister = C>,
169 {
170 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
171
172 plan.explain_bytes_by_terminal(target_field)
173 }
174
175 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
178 &self,
179 query: &Query<E>,
180 strategy: &S,
181 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
182 where
183 E: EntityValue + EntityKind<Canister = C>,
184 S: ProjectionExplain,
185 {
186 let (plan, _) = self.cached_prepared_query_plan_for_entity::<E>(query)?;
187
188 plan.explain_prepared_projection_terminal(strategy)
189 }
190
191 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
196 where
197 E: EntityKind<Canister = C>,
198 {
199 let (prepared_plan, cache_attribution) =
200 self.cached_prepared_query_plan_for_entity::<E>(query)?;
201 let logical_plan = prepared_plan.logical_plan();
202 let explain = logical_plan.explain();
203 let plan_hash = logical_plan.fingerprint().to_string();
204 let executable_access = prepared_plan.access().executable_contract();
205 let access_strategy = summarize_executable_access_plan(&executable_access);
206 let execution_family = match prepared_plan.mode() {
207 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
208 prepared_plan
209 .execution_family()
210 .map_err(QueryError::execute)?,
211 )),
212 QueryMode::Delete(_) => None,
213 };
214 let reuse = query_plan_cache_reuse_event(cache_attribution);
215
216 Ok(QueryTracePlan::new(
217 plan_hash,
218 access_strategy,
219 execution_family,
220 reuse,
221 explain,
222 ))
223 }
224}