icydb_core/db/session/query/
explain.rs1use crate::{
7 db::{
8 DbSession, Query, QueryError, QueryTracePlan, TraceExecutionFamily,
9 access::summarize_executable_access_plan,
10 executor::{
11 EntityAuthority, ExecutionFamily, initial_read_plan_requires_materialized_sort,
12 },
13 query::admission::{
14 QueryAdmissionPolicy, QueryAdmissionSummary, QueryMaterializationSummary,
15 },
16 query::builder::{AggregateExplain, ProjectionExplain},
17 query::explain::{
18 ExplainAggregateTerminalPlan, ExplainExecutionNodeDescriptor, ExplainPlan,
19 },
20 query::plan::{AccessPlannedQuery, QueryMode, VisibleIndexes},
21 session::query::{QueryPlanCacheAttribution, query_plan_cache_reuse_event},
22 },
23 traits::{CanisterKind, EntityKind, EntityValue},
24};
25
26const fn trace_execution_family_from_executor(family: ExecutionFamily) -> TraceExecutionFamily {
29 match family {
30 ExecutionFamily::PrimaryKey => TraceExecutionFamily::PrimaryKey,
31 ExecutionFamily::Ordered => TraceExecutionFamily::Ordered,
32 ExecutionFamily::Grouped => TraceExecutionFamily::Grouped,
33 }
34}
35
36impl<C: CanisterKind> DbSession<C> {
37 fn cached_finalized_explain_plan<E>(
41 &self,
42 query: &Query<E>,
43 visible_indexes: &VisibleIndexes<'_>,
44 ) -> Result<
45 (
46 AccessPlannedQuery,
47 EntityAuthority,
48 QueryPlanCacheAttribution,
49 ),
50 QueryError,
51 >
52 where
53 E: EntityKind<Canister = C>,
54 {
55 let (prepared_plan, cache_attribution) =
56 self.cached_shared_query_plan_for_entity::<E>(query)?;
57 let mut plan = prepared_plan.logical_plan().clone();
58 let authority = prepared_plan.authority();
59 let schema_info = authority
60 .accepted_schema_info()
61 .ok_or_else(QueryError::invariant)?;
62
63 plan.finalize_access_choice_for_model_with_semantic_indexes_and_schema(
64 query.structural().model(),
65 visible_indexes.accepted_semantic_index_contracts(),
66 schema_info,
67 );
68
69 Ok((plan, authority, 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.with_query_visible_indexes(query, |query, visible_indexes| {
81 let (plan, _, _) = self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
82
83 Ok(plan.explain())
84 })
85 }
86
87 pub(in crate::db) fn query_plan_hash_hex_with_visible_indexes<E>(
90 &self,
91 query: &Query<E>,
92 ) -> Result<String, QueryError>
93 where
94 E: EntityKind<Canister = C>,
95 {
96 let (prepared_plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
97
98 Ok(prepared_plan.plan_hash_hex())
99 }
100
101 pub fn evaluate_query_read_admission_policy<E>(
107 &self,
108 query: &Query<E>,
109 policy: &QueryAdmissionPolicy,
110 ) -> Result<QueryAdmissionSummary, QueryError>
111 where
112 E: EntityKind<Canister = C>,
113 {
114 let (prepared_plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
115 let mut summary =
116 QueryAdmissionSummary::from_plan(policy.lane(), prepared_plan.logical_plan());
117
118 if initial_read_plan_requires_materialized_sort(&prepared_plan)
119 .map_err(QueryError::execute)?
120 {
121 let returned_row_bound = summary.returned_row_bound();
122 let returned_row_bound_kind = summary.returned_row_bound_kind();
123 summary = summary.with_materialization(QueryMaterializationSummary::sort(
124 returned_row_bound,
125 returned_row_bound_kind,
126 ));
127 }
128
129 Ok(policy.evaluate(summary))
130 }
131
132 pub fn ensure_query_read_admission_policy<E>(
137 &self,
138 query: &Query<E>,
139 policy: &QueryAdmissionPolicy,
140 ) -> Result<QueryAdmissionSummary, QueryError>
141 where
142 E: EntityKind<Canister = C>,
143 {
144 let admission = self.evaluate_query_read_admission_policy(query, policy)?;
145
146 if let Some(rejection) = admission.rejection() {
147 Err(QueryError::from(rejection.code()))
148 } else {
149 Ok(admission)
150 }
151 }
152
153 pub(in crate::db) fn explain_query_execution_with_visible_indexes<E>(
156 &self,
157 query: &Query<E>,
158 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
159 where
160 E: EntityValue + EntityKind<Canister = C>,
161 {
162 self.with_query_visible_indexes(query, |query, visible_indexes| {
163 let (plan, authority, _) =
164 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
165
166 query
167 .structural()
168 .explain_execution_descriptor_from_plan_with_authority(&plan, &authority)
169 })
170 }
171
172 pub(in crate::db) fn explain_query_execution_verbose_with_visible_indexes<E>(
175 &self,
176 query: &Query<E>,
177 ) -> Result<String, QueryError>
178 where
179 E: EntityValue + EntityKind<Canister = C>,
180 {
181 self.with_query_visible_indexes(query, |query, visible_indexes| {
182 let (plan, authority, cache_attribution) =
183 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
184
185 query
186 .structural()
187 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
188 &plan,
189 &authority,
190 Some(query_plan_cache_reuse_event(cache_attribution)),
191 |_| {},
192 )
193 .map(|diagnostics| diagnostics.render_text_verbose())
194 })
195 }
196
197 pub(in crate::db) fn explain_query_execution_json_with_visible_indexes<E>(
200 &self,
201 query: &Query<E>,
202 ) -> Result<String, QueryError>
203 where
204 E: EntityValue + EntityKind<Canister = C>,
205 {
206 self.with_query_visible_indexes(query, |query, visible_indexes| {
207 let (plan, authority, cache_attribution) =
208 self.cached_finalized_explain_plan::<E>(query, visible_indexes)?;
209
210 query
211 .structural()
212 .finalized_execution_diagnostics_from_plan_with_authority_and_descriptor_mutator(
213 &plan,
214 &authority,
215 Some(query_plan_cache_reuse_event(cache_attribution)),
216 |_| {},
217 )
218 .map(|diagnostics| diagnostics.render_json_canonical())
219 })
220 }
221
222 pub(in crate::db) fn explain_query_prepared_aggregate_terminal_with_visible_indexes<E, S>(
225 &self,
226 query: &Query<E>,
227 strategy: &S,
228 ) -> Result<ExplainAggregateTerminalPlan, QueryError>
229 where
230 E: EntityKind<Canister = C>,
231 S: AggregateExplain,
232 {
233 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
234
235 plan.explain_prepared_aggregate_terminal(strategy)
236 }
237
238 pub(in crate::db) fn explain_query_bytes_by_with_visible_indexes<E>(
241 &self,
242 query: &Query<E>,
243 target_field: &str,
244 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
245 where
246 E: EntityKind<Canister = C>,
247 {
248 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
249
250 plan.explain_bytes_by_terminal(target_field)
251 }
252
253 pub(in crate::db) fn explain_query_prepared_projection_terminal_with_visible_indexes<E, S>(
256 &self,
257 query: &Query<E>,
258 strategy: &S,
259 ) -> Result<ExplainExecutionNodeDescriptor, QueryError>
260 where
261 E: EntityKind<Canister = C>,
262 S: ProjectionExplain,
263 {
264 let (plan, _) = self.cached_shared_query_plan_for_entity::<E>(query)?;
265
266 plan.explain_prepared_projection_terminal(strategy)
267 }
268
269 pub fn trace_query<E>(&self, query: &Query<E>) -> Result<QueryTracePlan, QueryError>
274 where
275 E: EntityKind<Canister = C>,
276 {
277 let (prepared_plan, cache_attribution) =
278 self.cached_shared_query_plan_for_entity::<E>(query)?;
279 let logical_plan = prepared_plan.logical_plan();
280 let explain = logical_plan.explain();
281 let plan_hash = prepared_plan.plan_hash_hex();
282 let executable_access = prepared_plan.access().executable_contract();
283 let access_strategy = summarize_executable_access_plan(&executable_access);
284 let execution_family = match prepared_plan.mode() {
285 QueryMode::Load(_) => Some(trace_execution_family_from_executor(
286 prepared_plan
287 .execution_family()
288 .map_err(QueryError::execute)?,
289 )),
290 QueryMode::Delete(_) => None,
291 };
292 let reuse = query_plan_cache_reuse_event(cache_attribution);
293
294 Ok(QueryTracePlan::new(
295 plan_hash,
296 access_strategy,
297 execution_family,
298 reuse,
299 explain,
300 ))
301 }
302}