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