1mod execute;
8mod explain;
9mod projection;
10
11#[cfg(feature = "diagnostics")]
12use candid::CandidType;
13use icydb_utils::Xxh3;
14#[cfg(feature = "diagnostics")]
15use serde::Deserialize;
16use std::{cell::RefCell, collections::HashMap, hash::BuildHasherDefault};
17
18type CacheBuildHasher = BuildHasherDefault<Xxh3>;
19
20const SQL_COMPILED_COMMAND_CACHE_METHOD_VERSION: u8 = 1;
23const SQL_SELECT_PLAN_CACHE_METHOD_VERSION: u8 = 1;
24
25#[cfg(feature = "diagnostics")]
26use crate::db::DataStore;
27#[cfg(feature = "diagnostics")]
28use crate::db::executor::GroupedCountAttribution;
29#[cfg(feature = "diagnostics")]
30use crate::db::session::sql::projection::{
31 current_pure_covering_decode_local_instructions,
32 current_pure_covering_row_assembly_local_instructions,
33};
34use crate::db::sql::parser::{SqlDeleteStatement, SqlInsertStatement, SqlUpdateStatement};
35use crate::{
36 db::{
37 DbSession, GroupedRow, PersistedRow, QueryError,
38 commit::CommitSchemaFingerprint,
39 executor::{EntityAuthority, SharedPreparedExecutionPlan},
40 query::{
41 intent::StructuralQuery,
42 plan::{AccessPlannedQuery, VisibleIndexes},
43 },
44 schema::commit_schema_fingerprint_for_entity,
45 session::query::QueryPlanCacheAttribution,
46 session::sql::projection::{
47 projection_fixed_scales_from_projection_spec, projection_labels_from_projection_spec,
48 },
49 sql::lowering::{LoweredBaseQueryShape, LoweredSqlCommand, SqlGlobalAggregateCommandCore},
50 sql::parser::{SqlStatement, parse_sql},
51 },
52 traits::{CanisterKind, EntityValue},
53};
54
55#[cfg(all(test, not(feature = "diagnostics")))]
56pub(crate) use crate::db::session::sql::projection::with_sql_projection_materialization_metrics;
57#[cfg(feature = "diagnostics")]
58pub use crate::db::session::sql::projection::{
59 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
60};
61
62#[derive(Debug)]
64pub enum SqlStatementResult {
65 Count {
66 row_count: u32,
67 },
68 Projection {
69 columns: Vec<String>,
70 fixed_scales: Vec<Option<u32>>,
71 rows: Vec<Vec<crate::value::Value>>,
72 row_count: u32,
73 },
74 ProjectionText {
75 columns: Vec<String>,
76 rows: Vec<Vec<String>>,
77 row_count: u32,
78 },
79 Grouped {
80 columns: Vec<String>,
81 fixed_scales: Vec<Option<u32>>,
82 rows: Vec<GroupedRow>,
83 row_count: u32,
84 next_cursor: Option<String>,
85 },
86 Explain(String),
87 Describe(crate::db::EntitySchemaDescription),
88 ShowIndexes(Vec<String>),
89 ShowColumns(Vec<crate::db::EntityFieldDescription>),
90 ShowEntities(Vec<String>),
91}
92
93#[cfg(feature = "diagnostics")]
104#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
105pub struct SqlQueryExecutionAttribution {
106 pub compile_local_instructions: u64,
107 pub planner_local_instructions: u64,
108 pub store_local_instructions: u64,
109 pub executor_local_instructions: u64,
110 pub grouped_stream_local_instructions: u64,
111 pub grouped_fold_local_instructions: u64,
112 pub grouped_finalize_local_instructions: u64,
113 pub grouped_count_borrowed_hash_computations: u64,
114 pub grouped_count_bucket_candidate_checks: u64,
115 pub grouped_count_existing_group_hits: u64,
116 pub grouped_count_new_group_inserts: u64,
117 pub grouped_count_row_materialization_local_instructions: u64,
118 pub grouped_count_group_lookup_local_instructions: u64,
119 pub grouped_count_existing_group_update_local_instructions: u64,
120 pub grouped_count_new_group_insert_local_instructions: u64,
121 pub pure_covering_decode_local_instructions: u64,
122 pub pure_covering_row_assembly_local_instructions: u64,
123 pub store_get_calls: u64,
124 pub response_decode_local_instructions: u64,
125 pub execute_local_instructions: u64,
126 pub total_local_instructions: u64,
127 pub sql_compiled_command_cache_hits: u64,
128 pub sql_compiled_command_cache_misses: u64,
129 pub sql_select_plan_cache_hits: u64,
130 pub sql_select_plan_cache_misses: u64,
131 pub shared_query_plan_cache_hits: u64,
132 pub shared_query_plan_cache_misses: u64,
133}
134
135#[cfg(feature = "diagnostics")]
139#[derive(Clone, Copy, Debug, Eq, PartialEq)]
140pub(in crate::db) struct SqlExecutePhaseAttribution {
141 pub planner_local_instructions: u64,
142 pub store_local_instructions: u64,
143 pub executor_local_instructions: u64,
144 pub grouped_stream_local_instructions: u64,
145 pub grouped_fold_local_instructions: u64,
146 pub grouped_finalize_local_instructions: u64,
147 pub grouped_count: GroupedCountAttribution,
148}
149
150#[cfg(feature = "diagnostics")]
151impl SqlExecutePhaseAttribution {
152 #[must_use]
153 pub(in crate::db) const fn from_execute_total_and_store_total(
154 execute_local_instructions: u64,
155 store_local_instructions: u64,
156 ) -> Self {
157 Self {
158 planner_local_instructions: 0,
159 store_local_instructions,
160 executor_local_instructions: execute_local_instructions
161 .saturating_sub(store_local_instructions),
162 grouped_stream_local_instructions: 0,
163 grouped_fold_local_instructions: 0,
164 grouped_finalize_local_instructions: 0,
165 grouped_count: GroupedCountAttribution::none(),
166 }
167 }
168}
169
170#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
174pub(in crate::db) struct SqlCacheAttribution {
175 pub sql_compiled_command_cache_hits: u64,
176 pub sql_compiled_command_cache_misses: u64,
177 pub sql_select_plan_cache_hits: u64,
178 pub sql_select_plan_cache_misses: u64,
179 pub shared_query_plan_cache_hits: u64,
180 pub shared_query_plan_cache_misses: u64,
181}
182
183impl SqlCacheAttribution {
184 #[must_use]
185 const fn none() -> Self {
186 Self {
187 sql_compiled_command_cache_hits: 0,
188 sql_compiled_command_cache_misses: 0,
189 sql_select_plan_cache_hits: 0,
190 sql_select_plan_cache_misses: 0,
191 shared_query_plan_cache_hits: 0,
192 shared_query_plan_cache_misses: 0,
193 }
194 }
195
196 #[must_use]
197 const fn sql_compiled_command_cache_hit() -> Self {
198 Self {
199 sql_compiled_command_cache_hits: 1,
200 ..Self::none()
201 }
202 }
203
204 #[must_use]
205 const fn sql_compiled_command_cache_miss() -> Self {
206 Self {
207 sql_compiled_command_cache_misses: 1,
208 ..Self::none()
209 }
210 }
211
212 #[must_use]
213 const fn sql_select_plan_cache_hit() -> Self {
214 Self {
215 sql_select_plan_cache_hits: 1,
216 ..Self::none()
217 }
218 }
219
220 #[must_use]
221 const fn sql_select_plan_cache_miss() -> Self {
222 Self {
223 sql_select_plan_cache_misses: 1,
224 ..Self::none()
225 }
226 }
227
228 #[must_use]
229 const fn from_shared_query_plan_cache(attribution: QueryPlanCacheAttribution) -> Self {
230 Self {
231 shared_query_plan_cache_hits: attribution.hits,
232 shared_query_plan_cache_misses: attribution.misses,
233 ..Self::none()
234 }
235 }
236
237 #[must_use]
238 const fn merge(self, other: Self) -> Self {
239 Self {
240 sql_compiled_command_cache_hits: self
241 .sql_compiled_command_cache_hits
242 .saturating_add(other.sql_compiled_command_cache_hits),
243 sql_compiled_command_cache_misses: self
244 .sql_compiled_command_cache_misses
245 .saturating_add(other.sql_compiled_command_cache_misses),
246 sql_select_plan_cache_hits: self
247 .sql_select_plan_cache_hits
248 .saturating_add(other.sql_select_plan_cache_hits),
249 sql_select_plan_cache_misses: self
250 .sql_select_plan_cache_misses
251 .saturating_add(other.sql_select_plan_cache_misses),
252 shared_query_plan_cache_hits: self
253 .shared_query_plan_cache_hits
254 .saturating_add(other.shared_query_plan_cache_hits),
255 shared_query_plan_cache_misses: self
256 .shared_query_plan_cache_misses
257 .saturating_add(other.shared_query_plan_cache_misses),
258 }
259 }
260}
261
262#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
263enum SqlCompiledCommandSurface {
264 Query,
265 Update,
266}
267
268#[derive(Clone, Debug, Eq, Hash, PartialEq)]
279pub(in crate::db) struct SqlCompiledCommandCacheKey {
280 cache_method_version: u8,
281 surface: SqlCompiledCommandSurface,
282 entity_path: &'static str,
283 schema_fingerprint: CommitSchemaFingerprint,
284 sql: String,
285}
286
287#[derive(Clone, Debug, Eq, Hash, PartialEq)]
288pub(in crate::db) struct SqlSelectPlanCacheKey {
289 cache_method_version: u8,
290 compiled: SqlCompiledCommandCacheKey,
291 visibility: crate::db::session::query::QueryPlanVisibility,
292}
293
294#[derive(Clone, Debug)]
304pub(in crate::db) struct SqlSelectPlanCacheEntry {
305 prepared_plan: SharedPreparedExecutionPlan,
306 columns: Vec<String>,
307 fixed_scales: Vec<Option<u32>>,
308}
309
310impl SqlSelectPlanCacheEntry {
311 #[must_use]
312 pub(in crate::db) const fn new(
313 prepared_plan: SharedPreparedExecutionPlan,
314 columns: Vec<String>,
315 fixed_scales: Vec<Option<u32>>,
316 ) -> Self {
317 Self {
318 prepared_plan,
319 columns,
320 fixed_scales,
321 }
322 }
323
324 #[must_use]
325 pub(in crate::db) fn into_parts(
326 self,
327 ) -> (SharedPreparedExecutionPlan, Vec<String>, Vec<Option<u32>>) {
328 (self.prepared_plan, self.columns, self.fixed_scales)
329 }
330}
331
332impl SqlCompiledCommandCacheKey {
333 fn query_for_entity<E>(sql: &str) -> Self
334 where
335 E: PersistedRow + EntityValue,
336 {
337 Self::for_entity::<E>(SqlCompiledCommandSurface::Query, sql)
338 }
339
340 fn update_for_entity<E>(sql: &str) -> Self
341 where
342 E: PersistedRow + EntityValue,
343 {
344 Self::for_entity::<E>(SqlCompiledCommandSurface::Update, sql)
345 }
346
347 fn for_entity<E>(surface: SqlCompiledCommandSurface, sql: &str) -> Self
348 where
349 E: PersistedRow + EntityValue,
350 {
351 Self {
352 cache_method_version: SQL_COMPILED_COMMAND_CACHE_METHOD_VERSION,
353 surface,
354 entity_path: E::PATH,
355 schema_fingerprint: commit_schema_fingerprint_for_entity::<E>(),
356 sql: sql.to_string(),
357 }
358 }
359
360 #[must_use]
361 pub(in crate::db) const fn schema_fingerprint(&self) -> CommitSchemaFingerprint {
362 self.schema_fingerprint
363 }
364}
365
366impl SqlSelectPlanCacheKey {
367 const fn from_compiled_key(
368 compiled: SqlCompiledCommandCacheKey,
369 visibility: crate::db::session::query::QueryPlanVisibility,
370 ) -> Self {
371 Self {
372 cache_method_version: SQL_SELECT_PLAN_CACHE_METHOD_VERSION,
373 compiled,
374 visibility,
375 }
376 }
377}
378
379#[cfg(test)]
380impl SqlCompiledCommandCacheKey {
381 pub(in crate::db) fn query_for_entity_with_method_version<E>(
382 sql: &str,
383 cache_method_version: u8,
384 ) -> Self
385 where
386 E: PersistedRow + EntityValue,
387 {
388 Self::for_entity_with_method_version::<E>(
389 SqlCompiledCommandSurface::Query,
390 sql,
391 cache_method_version,
392 )
393 }
394
395 pub(in crate::db) fn update_for_entity_with_method_version<E>(
396 sql: &str,
397 cache_method_version: u8,
398 ) -> Self
399 where
400 E: PersistedRow + EntityValue,
401 {
402 Self::for_entity_with_method_version::<E>(
403 SqlCompiledCommandSurface::Update,
404 sql,
405 cache_method_version,
406 )
407 }
408
409 fn for_entity_with_method_version<E>(
410 surface: SqlCompiledCommandSurface,
411 sql: &str,
412 cache_method_version: u8,
413 ) -> Self
414 where
415 E: PersistedRow + EntityValue,
416 {
417 Self {
418 cache_method_version,
419 surface,
420 entity_path: E::PATH,
421 schema_fingerprint: commit_schema_fingerprint_for_entity::<E>(),
422 sql: sql.to_string(),
423 }
424 }
425}
426
427#[cfg(test)]
428impl SqlSelectPlanCacheKey {
429 pub(in crate::db) const fn from_compiled_key_with_method_version(
430 compiled: SqlCompiledCommandCacheKey,
431 visibility: crate::db::session::query::QueryPlanVisibility,
432 cache_method_version: u8,
433 ) -> Self {
434 Self {
435 cache_method_version,
436 compiled,
437 visibility,
438 }
439 }
440}
441
442pub(in crate::db) type SqlCompiledCommandCache =
443 HashMap<SqlCompiledCommandCacheKey, CompiledSqlCommand, CacheBuildHasher>;
444pub(in crate::db) type SqlSelectPlanCache =
445 HashMap<SqlSelectPlanCacheKey, SqlSelectPlanCacheEntry, CacheBuildHasher>;
446
447thread_local! {
448 static SQL_COMPILED_COMMAND_CACHES: RefCell<HashMap<usize, SqlCompiledCommandCache, CacheBuildHasher>> =
452 RefCell::new(HashMap::default());
453 static SQL_SELECT_PLAN_CACHES: RefCell<HashMap<usize, SqlSelectPlanCache, CacheBuildHasher>> =
454 RefCell::new(HashMap::default());
455}
456
457#[derive(Clone, Debug)]
461pub(in crate::db) enum CompiledSqlCommand {
462 Select {
463 query: StructuralQuery,
464 compiled_cache_key: Option<SqlCompiledCommandCacheKey>,
465 },
466 Delete {
467 query: LoweredBaseQueryShape,
468 statement: SqlDeleteStatement,
469 },
470 GlobalAggregate {
471 command: SqlGlobalAggregateCommandCore,
472 },
473 Explain(LoweredSqlCommand),
474 Insert(SqlInsertStatement),
475 Update(SqlUpdateStatement),
476 DescribeEntity,
477 ShowIndexesEntity,
478 ShowColumnsEntity,
479 ShowEntities,
480}
481
482pub(in crate::db) fn parse_sql_statement(sql: &str) -> Result<SqlStatement, QueryError> {
485 parse_sql(sql).map_err(QueryError::from_sql_parse_error)
486}
487
488#[cfg(feature = "diagnostics")]
489#[expect(
490 clippy::missing_const_for_fn,
491 reason = "the wasm32 branch reads the runtime performance counter and cannot be const"
492)]
493fn read_sql_local_instruction_counter() -> u64 {
494 #[cfg(target_arch = "wasm32")]
495 {
496 canic_cdk::api::performance_counter(1)
497 }
498
499 #[cfg(not(target_arch = "wasm32"))]
500 {
501 0
502 }
503}
504
505#[cfg(feature = "diagnostics")]
506fn measure_sql_stage<T, E>(run: impl FnOnce() -> Result<T, E>) -> (u64, Result<T, E>) {
507 let start = read_sql_local_instruction_counter();
508 let result = run();
509 let delta = read_sql_local_instruction_counter().saturating_sub(start);
510
511 (delta, result)
512}
513
514impl<C: CanisterKind> DbSession<C> {
515 fn sql_cache_scope_id(&self) -> usize {
516 self.db.cache_scope_id()
517 }
518
519 fn with_sql_compiled_command_cache<R>(
520 &self,
521 f: impl FnOnce(&mut SqlCompiledCommandCache) -> R,
522 ) -> R {
523 let scope_id = self.sql_cache_scope_id();
524
525 SQL_COMPILED_COMMAND_CACHES.with(|caches| {
526 let mut caches = caches.borrow_mut();
527 let cache = caches.entry(scope_id).or_default();
528
529 f(cache)
530 })
531 }
532
533 fn with_sql_select_plan_cache<R>(&self, f: impl FnOnce(&mut SqlSelectPlanCache) -> R) -> R {
534 let scope_id = self.sql_cache_scope_id();
535
536 SQL_SELECT_PLAN_CACHES.with(|caches| {
537 let mut caches = caches.borrow_mut();
538 let cache = caches.entry(scope_id).or_default();
539
540 f(cache)
541 })
542 }
543
544 #[cfg(test)]
545 pub(in crate::db) fn sql_compiled_command_cache_len(&self) -> usize {
546 self.with_sql_compiled_command_cache(|cache| cache.len())
547 }
548
549 #[cfg(test)]
550 pub(in crate::db) fn sql_select_plan_cache_len(&self) -> usize {
551 self.with_sql_select_plan_cache(|cache| cache.len())
552 }
553
554 #[cfg(test)]
555 pub(in crate::db) fn clear_sql_caches_for_tests(&self) {
556 self.with_sql_compiled_command_cache(SqlCompiledCommandCache::clear);
557 self.with_sql_select_plan_cache(SqlSelectPlanCache::clear);
558 }
559
560 fn planned_sql_select_with_visibility(
561 &self,
562 query: &StructuralQuery,
563 authority: EntityAuthority,
564 compiled_cache_key: Option<&SqlCompiledCommandCacheKey>,
565 ) -> Result<(SqlSelectPlanCacheEntry, SqlCacheAttribution), QueryError> {
566 let visibility = self.query_plan_visibility_for_store_path(authority.store_path())?;
567 let fallback_schema_fingerprint = crate::db::schema::commit_schema_fingerprint_for_model(
568 authority.model().path,
569 authority.model(),
570 );
571 let cache_schema_fingerprint = compiled_cache_key.map_or(
572 fallback_schema_fingerprint,
573 SqlCompiledCommandCacheKey::schema_fingerprint,
574 );
575
576 let Some(compiled_cache_key) = compiled_cache_key else {
577 let (entry, cache_attribution) = self.cached_query_plan_entry_for_authority(
578 authority,
579 cache_schema_fingerprint,
580 query,
581 )?;
582 let projection = entry.logical_plan().projection_spec(authority.model());
583 let columns = projection_labels_from_projection_spec(&projection);
584 let fixed_scales = projection_fixed_scales_from_projection_spec(&projection);
585
586 return Ok((
587 SqlSelectPlanCacheEntry::new(entry.prepared_plan().clone(), columns, fixed_scales),
588 SqlCacheAttribution::from_shared_query_plan_cache(cache_attribution),
589 ));
590 };
591
592 let plan_cache_key =
593 SqlSelectPlanCacheKey::from_compiled_key(compiled_cache_key.clone(), visibility);
594 {
595 let cached =
596 self.with_sql_select_plan_cache(|cache| cache.get(&plan_cache_key).cloned());
597 if let Some(plan) = cached {
598 return Ok((plan, SqlCacheAttribution::sql_select_plan_cache_hit()));
599 }
600 }
601
602 let (entry, cache_attribution) =
603 self.cached_query_plan_entry_for_authority(authority, cache_schema_fingerprint, query)?;
604 let projection = entry.logical_plan().projection_spec(authority.model());
605 let columns = projection_labels_from_projection_spec(&projection);
606 let fixed_scales = projection_fixed_scales_from_projection_spec(&projection);
607 let entry =
608 SqlSelectPlanCacheEntry::new(entry.prepared_plan().clone(), columns, fixed_scales);
609 self.with_sql_select_plan_cache(|cache| {
610 cache.insert(plan_cache_key, entry.clone());
611 });
612
613 Ok((
614 entry,
615 SqlCacheAttribution::sql_select_plan_cache_miss().merge(
616 SqlCacheAttribution::from_shared_query_plan_cache(cache_attribution),
617 ),
618 ))
619 }
620
621 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
624 &self,
625 query: StructuralQuery,
626 authority: EntityAuthority,
627 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
628 let visible_indexes =
629 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
630 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
631
632 Ok((visible_indexes, plan))
633 }
634
635 fn ensure_sql_query_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
638 match statement {
639 SqlStatement::Select(_)
640 | SqlStatement::Explain(_)
641 | SqlStatement::Describe(_)
642 | SqlStatement::ShowIndexes(_)
643 | SqlStatement::ShowColumns(_)
644 | SqlStatement::ShowEntities(_) => Ok(()),
645 SqlStatement::Insert(_) => Err(QueryError::unsupported_query(
646 "execute_sql_query rejects INSERT; use execute_sql_update::<E>()",
647 )),
648 SqlStatement::Update(_) => Err(QueryError::unsupported_query(
649 "execute_sql_query rejects UPDATE; use execute_sql_update::<E>()",
650 )),
651 SqlStatement::Delete(_) => Err(QueryError::unsupported_query(
652 "execute_sql_query rejects DELETE; use execute_sql_update::<E>()",
653 )),
654 }
655 }
656
657 fn ensure_sql_update_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
660 match statement {
661 SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
662 SqlStatement::Select(_) => Err(QueryError::unsupported_query(
663 "execute_sql_update rejects SELECT; use execute_sql_query::<E>()",
664 )),
665 SqlStatement::Explain(_) => Err(QueryError::unsupported_query(
666 "execute_sql_update rejects EXPLAIN; use execute_sql_query::<E>()",
667 )),
668 SqlStatement::Describe(_) => Err(QueryError::unsupported_query(
669 "execute_sql_update rejects DESCRIBE; use execute_sql_query::<E>()",
670 )),
671 SqlStatement::ShowIndexes(_) => Err(QueryError::unsupported_query(
672 "execute_sql_update rejects SHOW INDEXES; use execute_sql_query::<E>()",
673 )),
674 SqlStatement::ShowColumns(_) => Err(QueryError::unsupported_query(
675 "execute_sql_update rejects SHOW COLUMNS; use execute_sql_query::<E>()",
676 )),
677 SqlStatement::ShowEntities(_) => Err(QueryError::unsupported_query(
678 "execute_sql_update rejects SHOW ENTITIES; use execute_sql_query::<E>()",
679 )),
680 }
681 }
682
683 pub fn execute_sql_query<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
688 where
689 E: PersistedRow<Canister = C> + EntityValue,
690 {
691 let compiled = self.compile_sql_query::<E>(sql)?;
692
693 self.execute_compiled_sql::<E>(&compiled)
694 }
695
696 #[cfg(feature = "diagnostics")]
699 #[doc(hidden)]
700 pub fn execute_sql_query_with_attribution<E>(
701 &self,
702 sql: &str,
703 ) -> Result<(SqlStatementResult, SqlQueryExecutionAttribution), QueryError>
704 where
705 E: PersistedRow<Canister = C> + EntityValue,
706 {
707 let (compile_local_instructions, compiled) =
710 measure_sql_stage(|| self.compile_sql_query_with_cache_attribution::<E>(sql));
711 let (compiled, compile_cache_attribution) = compiled?;
712
713 let store_get_calls_before = DataStore::current_get_call_count();
716 let pure_covering_decode_before = current_pure_covering_decode_local_instructions();
717 let pure_covering_row_assembly_before =
718 current_pure_covering_row_assembly_local_instructions();
719 let (result, execute_cache_attribution, execute_phase_attribution) =
720 self.execute_compiled_sql_with_phase_attribution::<E>(&compiled)?;
721 let store_get_calls =
722 DataStore::current_get_call_count().saturating_sub(store_get_calls_before);
723 let pure_covering_decode_local_instructions =
724 current_pure_covering_decode_local_instructions()
725 .saturating_sub(pure_covering_decode_before);
726 let pure_covering_row_assembly_local_instructions =
727 current_pure_covering_row_assembly_local_instructions()
728 .saturating_sub(pure_covering_row_assembly_before);
729 let execute_local_instructions = execute_phase_attribution
730 .planner_local_instructions
731 .saturating_add(execute_phase_attribution.store_local_instructions)
732 .saturating_add(execute_phase_attribution.executor_local_instructions);
733 let cache_attribution = compile_cache_attribution.merge(execute_cache_attribution);
734 let total_local_instructions =
735 compile_local_instructions.saturating_add(execute_local_instructions);
736
737 Ok((
738 result,
739 SqlQueryExecutionAttribution {
740 compile_local_instructions,
741 planner_local_instructions: execute_phase_attribution.planner_local_instructions,
742 store_local_instructions: execute_phase_attribution.store_local_instructions,
743 executor_local_instructions: execute_phase_attribution.executor_local_instructions,
744 grouped_stream_local_instructions: execute_phase_attribution
745 .grouped_stream_local_instructions,
746 grouped_fold_local_instructions: execute_phase_attribution
747 .grouped_fold_local_instructions,
748 grouped_finalize_local_instructions: execute_phase_attribution
749 .grouped_finalize_local_instructions,
750 grouped_count_borrowed_hash_computations: execute_phase_attribution
751 .grouped_count
752 .borrowed_hash_computations,
753 grouped_count_bucket_candidate_checks: execute_phase_attribution
754 .grouped_count
755 .bucket_candidate_checks,
756 grouped_count_existing_group_hits: execute_phase_attribution
757 .grouped_count
758 .existing_group_hits,
759 grouped_count_new_group_inserts: execute_phase_attribution
760 .grouped_count
761 .new_group_inserts,
762 grouped_count_row_materialization_local_instructions: execute_phase_attribution
763 .grouped_count
764 .row_materialization_local_instructions,
765 grouped_count_group_lookup_local_instructions: execute_phase_attribution
766 .grouped_count
767 .group_lookup_local_instructions,
768 grouped_count_existing_group_update_local_instructions: execute_phase_attribution
769 .grouped_count
770 .existing_group_update_local_instructions,
771 grouped_count_new_group_insert_local_instructions: execute_phase_attribution
772 .grouped_count
773 .new_group_insert_local_instructions,
774 pure_covering_decode_local_instructions,
775 pure_covering_row_assembly_local_instructions,
776 store_get_calls,
777 response_decode_local_instructions: 0,
778 execute_local_instructions,
779 total_local_instructions,
780 sql_compiled_command_cache_hits: cache_attribution.sql_compiled_command_cache_hits,
781 sql_compiled_command_cache_misses: cache_attribution
782 .sql_compiled_command_cache_misses,
783 sql_select_plan_cache_hits: cache_attribution.sql_select_plan_cache_hits,
784 sql_select_plan_cache_misses: cache_attribution.sql_select_plan_cache_misses,
785 shared_query_plan_cache_hits: cache_attribution.shared_query_plan_cache_hits,
786 shared_query_plan_cache_misses: cache_attribution.shared_query_plan_cache_misses,
787 },
788 ))
789 }
790
791 pub fn execute_sql_update<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
796 where
797 E: PersistedRow<Canister = C> + EntityValue,
798 {
799 let compiled = self.compile_sql_update::<E>(sql)?;
800
801 self.execute_compiled_sql::<E>(&compiled)
802 }
803
804 pub(in crate::db) fn compile_sql_query<E>(
807 &self,
808 sql: &str,
809 ) -> Result<CompiledSqlCommand, QueryError>
810 where
811 E: PersistedRow<Canister = C> + EntityValue,
812 {
813 self.compile_sql_query_with_cache_attribution::<E>(sql)
814 .map(|(compiled, _)| compiled)
815 }
816
817 fn compile_sql_query_with_cache_attribution<E>(
818 &self,
819 sql: &str,
820 ) -> Result<(CompiledSqlCommand, SqlCacheAttribution), QueryError>
821 where
822 E: PersistedRow<Canister = C> + EntityValue,
823 {
824 self.compile_sql_statement_with_cache::<E>(
825 SqlCompiledCommandCacheKey::query_for_entity::<E>(sql),
826 sql,
827 Self::ensure_sql_query_statement_supported,
828 )
829 }
830
831 pub(in crate::db) fn compile_sql_update<E>(
834 &self,
835 sql: &str,
836 ) -> Result<CompiledSqlCommand, QueryError>
837 where
838 E: PersistedRow<Canister = C> + EntityValue,
839 {
840 self.compile_sql_update_with_cache_attribution::<E>(sql)
841 .map(|(compiled, _)| compiled)
842 }
843
844 fn compile_sql_update_with_cache_attribution<E>(
845 &self,
846 sql: &str,
847 ) -> Result<(CompiledSqlCommand, SqlCacheAttribution), QueryError>
848 where
849 E: PersistedRow<Canister = C> + EntityValue,
850 {
851 self.compile_sql_statement_with_cache::<E>(
852 SqlCompiledCommandCacheKey::update_for_entity::<E>(sql),
853 sql,
854 Self::ensure_sql_update_statement_supported,
855 )
856 }
857
858 fn compile_sql_statement_with_cache<E>(
861 &self,
862 cache_key: SqlCompiledCommandCacheKey,
863 sql: &str,
864 ensure_surface_supported: fn(&SqlStatement) -> Result<(), QueryError>,
865 ) -> Result<(CompiledSqlCommand, SqlCacheAttribution), QueryError>
866 where
867 E: PersistedRow<Canister = C> + EntityValue,
868 {
869 {
870 let cached =
871 self.with_sql_compiled_command_cache(|cache| cache.get(&cache_key).cloned());
872 if let Some(compiled) = cached {
873 return Ok((
874 compiled,
875 SqlCacheAttribution::sql_compiled_command_cache_hit(),
876 ));
877 }
878 }
879
880 let parsed = parse_sql_statement(sql)?;
881 ensure_surface_supported(&parsed)?;
882 let mut compiled = Self::compile_sql_statement_inner::<E>(&parsed)?;
883 if let CompiledSqlCommand::Select {
884 compiled_cache_key, ..
885 } = &mut compiled
886 {
887 *compiled_cache_key = Some(cache_key.clone());
888 }
889
890 self.with_sql_compiled_command_cache(|cache| {
891 cache.insert(cache_key, compiled.clone());
892 });
893
894 Ok((
895 compiled,
896 SqlCacheAttribution::sql_compiled_command_cache_miss(),
897 ))
898 }
899
900 pub(in crate::db) fn compile_sql_statement_inner<E>(
903 sql_statement: &SqlStatement,
904 ) -> Result<CompiledSqlCommand, QueryError>
905 where
906 E: PersistedRow<Canister = C> + EntityValue,
907 {
908 Self::compile_sql_statement_for_authority(sql_statement, EntityAuthority::for_type::<E>())
909 }
910}