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: 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_without_sql_cache(
563 &self,
564 query: &StructuralQuery,
565 authority: EntityAuthority,
566 ) -> Result<(SqlSelectPlanCacheEntry, SqlCacheAttribution), QueryError> {
567 let cache_schema_fingerprint = crate::db::schema::commit_schema_fingerprint_for_model(
568 authority.model().path,
569 authority.model(),
570 );
571 let (entry, cache_attribution) =
572 self.cached_query_plan_entry_for_authority(authority, cache_schema_fingerprint, query)?;
573 let projection = entry.logical_plan().projection_spec(authority.model());
574 let columns = projection_labels_from_projection_spec(&projection);
575 let fixed_scales = projection_fixed_scales_from_projection_spec(&projection);
576
577 Ok((
578 SqlSelectPlanCacheEntry::new(entry.prepared_plan().clone(), columns, fixed_scales),
579 SqlCacheAttribution::from_shared_query_plan_cache(cache_attribution),
580 ))
581 }
582
583 fn planned_sql_select_with_visibility(
586 &self,
587 query: &StructuralQuery,
588 authority: EntityAuthority,
589 compiled_cache_key: &SqlCompiledCommandCacheKey,
590 ) -> Result<(SqlSelectPlanCacheEntry, SqlCacheAttribution), QueryError> {
591 let visibility = self.query_plan_visibility_for_store_path(authority.store_path())?;
592 let cache_schema_fingerprint = compiled_cache_key.schema_fingerprint();
593
594 let plan_cache_key =
595 SqlSelectPlanCacheKey::from_compiled_key(compiled_cache_key.clone(), visibility);
596 {
597 let cached =
598 self.with_sql_select_plan_cache(|cache| cache.get(&plan_cache_key).cloned());
599 if let Some(plan) = cached {
600 return Ok((plan, SqlCacheAttribution::sql_select_plan_cache_hit()));
601 }
602 }
603
604 let (entry, cache_attribution) =
605 self.cached_query_plan_entry_for_authority(authority, cache_schema_fingerprint, query)?;
606 let projection = entry.logical_plan().projection_spec(authority.model());
607 let columns = projection_labels_from_projection_spec(&projection);
608 let fixed_scales = projection_fixed_scales_from_projection_spec(&projection);
609 let entry =
610 SqlSelectPlanCacheEntry::new(entry.prepared_plan().clone(), columns, fixed_scales);
611 self.with_sql_select_plan_cache(|cache| {
612 cache.insert(plan_cache_key, entry.clone());
613 });
614
615 Ok((
616 entry,
617 SqlCacheAttribution::sql_select_plan_cache_miss().merge(
618 SqlCacheAttribution::from_shared_query_plan_cache(cache_attribution),
619 ),
620 ))
621 }
622
623 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
626 &self,
627 query: StructuralQuery,
628 authority: EntityAuthority,
629 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
630 let visible_indexes =
631 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
632 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
633
634 Ok((visible_indexes, plan))
635 }
636
637 fn ensure_sql_query_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
640 match statement {
641 SqlStatement::Select(_)
642 | SqlStatement::Explain(_)
643 | SqlStatement::Describe(_)
644 | SqlStatement::ShowIndexes(_)
645 | SqlStatement::ShowColumns(_)
646 | SqlStatement::ShowEntities(_) => Ok(()),
647 SqlStatement::Insert(_) => Err(QueryError::unsupported_query(
648 "execute_sql_query rejects INSERT; use execute_sql_update::<E>()",
649 )),
650 SqlStatement::Update(_) => Err(QueryError::unsupported_query(
651 "execute_sql_query rejects UPDATE; use execute_sql_update::<E>()",
652 )),
653 SqlStatement::Delete(_) => Err(QueryError::unsupported_query(
654 "execute_sql_query rejects DELETE; use execute_sql_update::<E>()",
655 )),
656 }
657 }
658
659 fn ensure_sql_update_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
662 match statement {
663 SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
664 SqlStatement::Select(_) => Err(QueryError::unsupported_query(
665 "execute_sql_update rejects SELECT; use execute_sql_query::<E>()",
666 )),
667 SqlStatement::Explain(_) => Err(QueryError::unsupported_query(
668 "execute_sql_update rejects EXPLAIN; use execute_sql_query::<E>()",
669 )),
670 SqlStatement::Describe(_) => Err(QueryError::unsupported_query(
671 "execute_sql_update rejects DESCRIBE; use execute_sql_query::<E>()",
672 )),
673 SqlStatement::ShowIndexes(_) => Err(QueryError::unsupported_query(
674 "execute_sql_update rejects SHOW INDEXES; use execute_sql_query::<E>()",
675 )),
676 SqlStatement::ShowColumns(_) => Err(QueryError::unsupported_query(
677 "execute_sql_update rejects SHOW COLUMNS; use execute_sql_query::<E>()",
678 )),
679 SqlStatement::ShowEntities(_) => Err(QueryError::unsupported_query(
680 "execute_sql_update rejects SHOW ENTITIES; use execute_sql_query::<E>()",
681 )),
682 }
683 }
684
685 pub fn execute_sql_query<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
690 where
691 E: PersistedRow<Canister = C> + EntityValue,
692 {
693 let compiled = self.compile_sql_query::<E>(sql)?;
694
695 self.execute_compiled_sql::<E>(&compiled)
696 }
697
698 #[cfg(feature = "diagnostics")]
701 #[doc(hidden)]
702 pub fn execute_sql_query_with_attribution<E>(
703 &self,
704 sql: &str,
705 ) -> Result<(SqlStatementResult, SqlQueryExecutionAttribution), QueryError>
706 where
707 E: PersistedRow<Canister = C> + EntityValue,
708 {
709 let (compile_local_instructions, compiled) =
712 measure_sql_stage(|| self.compile_sql_query_with_cache_attribution::<E>(sql));
713 let (compiled, compile_cache_attribution) = compiled?;
714
715 let store_get_calls_before = DataStore::current_get_call_count();
718 let pure_covering_decode_before = current_pure_covering_decode_local_instructions();
719 let pure_covering_row_assembly_before =
720 current_pure_covering_row_assembly_local_instructions();
721 let (result, execute_cache_attribution, execute_phase_attribution) =
722 self.execute_compiled_sql_with_phase_attribution::<E>(&compiled)?;
723 let store_get_calls =
724 DataStore::current_get_call_count().saturating_sub(store_get_calls_before);
725 let pure_covering_decode_local_instructions =
726 current_pure_covering_decode_local_instructions()
727 .saturating_sub(pure_covering_decode_before);
728 let pure_covering_row_assembly_local_instructions =
729 current_pure_covering_row_assembly_local_instructions()
730 .saturating_sub(pure_covering_row_assembly_before);
731 let execute_local_instructions = execute_phase_attribution
732 .planner_local_instructions
733 .saturating_add(execute_phase_attribution.store_local_instructions)
734 .saturating_add(execute_phase_attribution.executor_local_instructions);
735 let cache_attribution = compile_cache_attribution.merge(execute_cache_attribution);
736 let total_local_instructions =
737 compile_local_instructions.saturating_add(execute_local_instructions);
738
739 Ok((
740 result,
741 SqlQueryExecutionAttribution {
742 compile_local_instructions,
743 planner_local_instructions: execute_phase_attribution.planner_local_instructions,
744 store_local_instructions: execute_phase_attribution.store_local_instructions,
745 executor_local_instructions: execute_phase_attribution.executor_local_instructions,
746 grouped_stream_local_instructions: execute_phase_attribution
747 .grouped_stream_local_instructions,
748 grouped_fold_local_instructions: execute_phase_attribution
749 .grouped_fold_local_instructions,
750 grouped_finalize_local_instructions: execute_phase_attribution
751 .grouped_finalize_local_instructions,
752 grouped_count_borrowed_hash_computations: execute_phase_attribution
753 .grouped_count
754 .borrowed_hash_computations,
755 grouped_count_bucket_candidate_checks: execute_phase_attribution
756 .grouped_count
757 .bucket_candidate_checks,
758 grouped_count_existing_group_hits: execute_phase_attribution
759 .grouped_count
760 .existing_group_hits,
761 grouped_count_new_group_inserts: execute_phase_attribution
762 .grouped_count
763 .new_group_inserts,
764 grouped_count_row_materialization_local_instructions: execute_phase_attribution
765 .grouped_count
766 .row_materialization_local_instructions,
767 grouped_count_group_lookup_local_instructions: execute_phase_attribution
768 .grouped_count
769 .group_lookup_local_instructions,
770 grouped_count_existing_group_update_local_instructions: execute_phase_attribution
771 .grouped_count
772 .existing_group_update_local_instructions,
773 grouped_count_new_group_insert_local_instructions: execute_phase_attribution
774 .grouped_count
775 .new_group_insert_local_instructions,
776 pure_covering_decode_local_instructions,
777 pure_covering_row_assembly_local_instructions,
778 store_get_calls,
779 response_decode_local_instructions: 0,
780 execute_local_instructions,
781 total_local_instructions,
782 sql_compiled_command_cache_hits: cache_attribution.sql_compiled_command_cache_hits,
783 sql_compiled_command_cache_misses: cache_attribution
784 .sql_compiled_command_cache_misses,
785 sql_select_plan_cache_hits: cache_attribution.sql_select_plan_cache_hits,
786 sql_select_plan_cache_misses: cache_attribution.sql_select_plan_cache_misses,
787 shared_query_plan_cache_hits: cache_attribution.shared_query_plan_cache_hits,
788 shared_query_plan_cache_misses: cache_attribution.shared_query_plan_cache_misses,
789 },
790 ))
791 }
792
793 pub fn execute_sql_update<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
798 where
799 E: PersistedRow<Canister = C> + EntityValue,
800 {
801 let compiled = self.compile_sql_update::<E>(sql)?;
802
803 self.execute_compiled_sql::<E>(&compiled)
804 }
805
806 pub(in crate::db) fn compile_sql_query<E>(
809 &self,
810 sql: &str,
811 ) -> Result<CompiledSqlCommand, QueryError>
812 where
813 E: PersistedRow<Canister = C> + EntityValue,
814 {
815 self.compile_sql_query_with_cache_attribution::<E>(sql)
816 .map(|(compiled, _)| compiled)
817 }
818
819 fn compile_sql_query_with_cache_attribution<E>(
820 &self,
821 sql: &str,
822 ) -> Result<(CompiledSqlCommand, SqlCacheAttribution), QueryError>
823 where
824 E: PersistedRow<Canister = C> + EntityValue,
825 {
826 self.compile_sql_statement_with_cache::<E>(
827 SqlCompiledCommandCacheKey::query_for_entity::<E>(sql),
828 sql,
829 Self::ensure_sql_query_statement_supported,
830 )
831 }
832
833 pub(in crate::db) fn compile_sql_update<E>(
836 &self,
837 sql: &str,
838 ) -> Result<CompiledSqlCommand, QueryError>
839 where
840 E: PersistedRow<Canister = C> + EntityValue,
841 {
842 self.compile_sql_update_with_cache_attribution::<E>(sql)
843 .map(|(compiled, _)| compiled)
844 }
845
846 fn compile_sql_update_with_cache_attribution<E>(
847 &self,
848 sql: &str,
849 ) -> Result<(CompiledSqlCommand, SqlCacheAttribution), QueryError>
850 where
851 E: PersistedRow<Canister = C> + EntityValue,
852 {
853 self.compile_sql_statement_with_cache::<E>(
854 SqlCompiledCommandCacheKey::update_for_entity::<E>(sql),
855 sql,
856 Self::ensure_sql_update_statement_supported,
857 )
858 }
859
860 fn compile_sql_statement_with_cache<E>(
863 &self,
864 cache_key: SqlCompiledCommandCacheKey,
865 sql: &str,
866 ensure_surface_supported: fn(&SqlStatement) -> Result<(), QueryError>,
867 ) -> Result<(CompiledSqlCommand, SqlCacheAttribution), QueryError>
868 where
869 E: PersistedRow<Canister = C> + EntityValue,
870 {
871 {
872 let cached =
873 self.with_sql_compiled_command_cache(|cache| cache.get(&cache_key).cloned());
874 if let Some(compiled) = cached {
875 return Ok((
876 compiled,
877 SqlCacheAttribution::sql_compiled_command_cache_hit(),
878 ));
879 }
880 }
881
882 let parsed = parse_sql_statement(sql)?;
883 ensure_surface_supported(&parsed)?;
884 let compiled = Self::compile_sql_statement_for_authority(
885 &parsed,
886 EntityAuthority::for_type::<E>(),
887 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}