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 cached_finalized_explain_plan<E>(
36 &self,
37 query: &Query<E>,
38 visible_indexes: &VisibleIndexes<'_>,
39 ) -> Result<
40 (
41 AccessPlannedQuery,
42 EntityAuthority,
43 QueryPlanCacheAttribution,
44 ),
45 QueryError,
46 >
47 where
48 E: EntityKind<Canister = C>,
49 {
50 let (prepared_plan, cache_attribution) =
51 self.cached_shared_query_plan_for_entity::<E>(query)?;
52 let mut plan = prepared_plan.logical_plan().clone();
53 let authority = prepared_plan.authority();
54 let schema_info = authority
55 .accepted_schema_info()
56 .ok_or_else(QueryError::invariant)?;
57
58 plan.finalize_access_choice_for_model_with_semantic_indexes_and_schema(
59 query.structural().model(),
60 visible_indexes.accepted_semantic_index_contracts(),
61 schema_info,
62 );
63
64 Ok((plan, authority, cache_attribution))
65 }
66
67 pub(in crate::db) fn explain_query_with_visible_indexes<E>(
69 &self,
70 query: &Query<E>,
71 ) -> Result<ExplainPlan, QueryError>
72 where
73 E: EntityKind<Canister = C>,
74 {
75 self.with_query_visible_indexes(query, |query, visible_indexes| {
76 let (plan, _, _) = self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
77
78 Ok(plan.explain())
79 })
80 }
81
82 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
85 &self,
86 query: &Query<E>,
87 ) -> Result<String, QueryError>
88 where
89 E: EntityKind<Canister = C>,
90 {
91 let (prepared_plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
92
93 Ok(prepared_plan.plan_hash_hex())
94 }
95
96 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
99 &self,
100 query: &Query<E>,
101 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
102 where
103 E: EntityValue + EntityKind<Canister = C>,
104 {
105 self.with_query_visible_indexes(query, |query, visible_indexes| {
106 let (plan, authority, _) =
107 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
108
109 query
110 .structural()
111 .explain_execution_descriptor_from_plan_with_authority(&plan, &authority)
112 })
113 }
114
115 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
118 &self,
119 query: &Query<E>,
120 ) -> Result<String, QueryError>
121 where
122 E: EntityValue + EntityKind<Canister = C>,
123 {
124 self.with_query_visible_indexes(query, |query, visible_indexes| {
125 let (plan, authority, cache_attribution) =
126 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
127
128 query
129 .structural()
130 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
131 &plan,
132 &authority,
133 Some(query_plan_cache_reuse_event(cache_attribution)),
134 |_| {},
135 )
136 .map(|diagnostics| diagnostics.render_text_verbose())
137 })
138 }
139
140 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
143 &self,
144 query: &Query<E>,
145 strategy: &S,
146 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
147 where
148 E: EntityKind<Canister = C>,
149 S: AggregateExplain,
150 {
151 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
152
153 plan.explain_prepared_aggregate_terminal(strategy)
154 }
155
156 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
159 &self,
160 query: &Query<E>,
161 target_field: &str,
162 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
163 where
164 E: EntityKind<Canister = C>,
165 {
166 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
167
168 plan.explain_bytes_by_terminal(target_field)
169 }
170
171 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
174 &self,
175 query: &Query<E>,
176 strategy: &S,
177 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
178 where
179 E: EntityKind<Canister = C>,
180 S: ProjectionExplain,
181 {
182 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
183
184 plan.explain_prepared_projection_terminal(strategy)
185 }
186
187 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
192 where
193 E: EntityKind<Canister = C>,
194 {
195 let (prepared_plan, cache_attribution) =
196 self.cached_shared_query_plan_for_entity::<E>(query)?;
197 let logical_plan = prepared_plan.logical_plan();
198 let explain = logical_plan.explain();
199 let plan_hash = prepared_plan.plan_hash_hex();
200 let executable_access = prepared_plan.access().executable_contract();
201 let access_strategy = summarize_executable_access_plan(&executable_access);
202 let execution_family = match prepared_plan.mode() {
203 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
204 prepared_plan
205 .execution_family()
206 .map_err(QueryError::execute)?,
207 )),
208 QueryMode::Delete(_) => None,
209 };
210 let reuse = query_plan_cache_reuse_event(cache_attribution);
211
212 Ok(QueryTracePlan::new(
213 plan_hash,
214 access_strategy,
215 execution_family,
216 reuse,
217 explain,
218 ))
219 }
220}