1mod execute;
8mod explain;
9mod parameter;
10mod projection;
11
12#[cfg(feature = "diagnostics")]
13use candid::CandidType;
14#[cfg(feature = "diagnostics")]
15use serde::Deserialize;
16use std::{cell::RefCell, collections::HashMap, sync::Arc};
17
18const SQL_COMPILED_COMMAND_CACHE_METHOD_VERSION: u8 = 1;
24
25#[cfg(test)]
26pub(in crate::db) use self::parameter::PreparedSqlExecutionTemplateKind;
27#[allow(unused_imports)]
28pub(in crate::db) use self::parameter::PreparedSqlQuery;
29#[cfg(feature = "diagnostics")]
30use crate::db::DataStore;
31#[cfg(feature = "diagnostics")]
32use crate::db::executor::GroupedCountAttribution;
33#[cfg(feature = "diagnostics")]
34use crate::db::session::sql::projection::{
35 current_pure_covering_decode_local_instructions,
36 current_pure_covering_row_assembly_local_instructions,
37};
38#[allow(unused_imports)]
39pub(in crate::db) use crate::db::sql::lowering::{
40 PreparedSqlParameterContract, PreparedSqlParameterTypeFamily,
41};
42#[cfg(test)]
43use crate::db::sql::parser::parse_sql;
44use crate::db::sql::parser::{SqlDeleteStatement, SqlInsertStatement, SqlUpdateStatement};
45use crate::{
46 db::{
47 DbSession, GroupedRow, PersistedRow, QueryError,
48 commit::CommitSchemaFingerprint,
49 executor::{EntityAuthority, SharedPreparedExecutionPlan},
50 query::{
51 intent::StructuralQuery,
52 plan::{AccessPlannedQuery, VisibleIndexes},
53 },
54 schema::commit_schema_fingerprint_for_entity,
55 session::query::QueryPlanCacheAttribution,
56 session::sql::projection::{
57 projection_fixed_scales_from_projection_spec, projection_labels_from_projection_spec,
58 },
59 sql::lowering::{LoweredBaseQueryShape, LoweredSqlCommand, SqlGlobalAggregateCommandCore},
60 sql::parser::{SqlStatement, parse_sql_with_attribution},
61 },
62 traits::{CanisterKind, EntityValue},
63};
64
65#[cfg(all(test, not(feature = "diagnostics")))]
66pub(crate) use crate::db::session::sql::projection::with_sql_projection_materialization_metrics;
67#[cfg(feature = "diagnostics")]
68pub use crate::db::session::sql::projection::{
69 SqlProjectionMaterializationMetrics, with_sql_projection_materialization_metrics,
70};
71
72#[derive(Debug)]
74pub enum SqlStatementResult {
75 Count {
76 row_count: u32,
77 },
78 Projection {
79 columns: Vec<String>,
80 fixed_scales: Vec<Option<u32>>,
81 rows: Vec<Vec<crate::value::Value>>,
82 row_count: u32,
83 },
84 ProjectionText {
85 columns: Vec<String>,
86 rows: Vec<Vec<String>>,
87 row_count: u32,
88 },
89 Grouped {
90 columns: Vec<String>,
91 fixed_scales: Vec<Option<u32>>,
92 rows: Vec<GroupedRow>,
93 row_count: u32,
94 next_cursor: Option<String>,
95 },
96 Explain(String),
97 Describe(crate::db::EntitySchemaDescription),
98 ShowIndexes(Vec<String>),
99 ShowColumns(Vec<crate::db::EntityFieldDescription>),
100 ShowEntities(Vec<String>),
101}
102
103#[cfg(feature = "diagnostics")]
114#[derive(CandidType, Clone, Debug, Deserialize, Eq, PartialEq)]
115pub struct SqlQueryExecutionAttribution {
116 pub compile_local_instructions: u64,
117 pub compile_cache_key_local_instructions: u64,
118 pub compile_cache_lookup_local_instructions: u64,
119 pub compile_parse_local_instructions: u64,
120 pub compile_parse_tokenize_local_instructions: u64,
121 pub compile_parse_select_local_instructions: u64,
122 pub compile_parse_expr_local_instructions: u64,
123 pub compile_parse_predicate_local_instructions: u64,
124 pub compile_aggregate_lane_check_local_instructions: u64,
125 pub compile_prepare_local_instructions: u64,
126 pub compile_lower_local_instructions: u64,
127 pub compile_bind_local_instructions: u64,
128 pub compile_cache_insert_local_instructions: u64,
129 pub planner_local_instructions: u64,
130 pub store_local_instructions: u64,
131 pub executor_local_instructions: u64,
132 pub grouped_stream_local_instructions: u64,
133 pub grouped_fold_local_instructions: u64,
134 pub grouped_finalize_local_instructions: u64,
135 pub grouped_count_borrowed_hash_computations: u64,
136 pub grouped_count_bucket_candidate_checks: u64,
137 pub grouped_count_existing_group_hits: u64,
138 pub grouped_count_new_group_inserts: u64,
139 pub grouped_count_row_materialization_local_instructions: u64,
140 pub grouped_count_group_lookup_local_instructions: u64,
141 pub grouped_count_existing_group_update_local_instructions: u64,
142 pub grouped_count_new_group_insert_local_instructions: u64,
143 pub pure_covering_decode_local_instructions: u64,
144 pub pure_covering_row_assembly_local_instructions: u64,
145 pub store_get_calls: u64,
146 pub response_decode_local_instructions: u64,
147 pub execute_local_instructions: u64,
148 pub total_local_instructions: u64,
149 pub sql_compiled_command_cache_hits: u64,
150 pub sql_compiled_command_cache_misses: u64,
151 pub shared_query_plan_cache_hits: u64,
152 pub shared_query_plan_cache_misses: u64,
153}
154
155#[cfg(feature = "diagnostics")]
159#[derive(Clone, Copy, Debug, Eq, PartialEq)]
160pub(in crate::db) struct SqlExecutePhaseAttribution {
161 pub planner_local_instructions: u64,
162 pub store_local_instructions: u64,
163 pub executor_local_instructions: u64,
164 pub grouped_stream_local_instructions: u64,
165 pub grouped_fold_local_instructions: u64,
166 pub grouped_finalize_local_instructions: u64,
167 pub grouped_count: GroupedCountAttribution,
168}
169
170#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
182pub(in crate::db) struct SqlCompilePhaseAttribution {
183 pub cache_key: u64,
184 pub cache_lookup: u64,
185 pub parse: u64,
186 pub parse_tokenize: u64,
187 pub parse_select: u64,
188 pub parse_expr: u64,
189 pub parse_predicate: u64,
190 pub aggregate_lane_check: u64,
191 pub prepare: u64,
192 pub lower: u64,
193 pub bind: u64,
194 pub cache_insert: u64,
195}
196
197impl SqlCompilePhaseAttribution {
198 #[must_use]
199 const fn cache_hit(cache_key: u64, cache_lookup: u64) -> Self {
200 Self {
201 cache_key,
202 cache_lookup,
203 parse: 0,
204 parse_tokenize: 0,
205 parse_select: 0,
206 parse_expr: 0,
207 parse_predicate: 0,
208 aggregate_lane_check: 0,
209 prepare: 0,
210 lower: 0,
211 bind: 0,
212 cache_insert: 0,
213 }
214 }
215}
216
217#[cfg(feature = "diagnostics")]
218impl SqlExecutePhaseAttribution {
219 #[must_use]
220 pub(in crate::db) const fn from_execute_total_and_store_total(
221 execute_local_instructions: u64,
222 store_local_instructions: u64,
223 ) -> Self {
224 Self {
225 planner_local_instructions: 0,
226 store_local_instructions,
227 executor_local_instructions: execute_local_instructions
228 .saturating_sub(store_local_instructions),
229 grouped_stream_local_instructions: 0,
230 grouped_fold_local_instructions: 0,
231 grouped_finalize_local_instructions: 0,
232 grouped_count: GroupedCountAttribution::none(),
233 }
234 }
235}
236
237#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
243pub(in crate::db) struct SqlCacheAttribution {
244 pub sql_compiled_command_cache_hits: u64,
245 pub sql_compiled_command_cache_misses: u64,
246 pub shared_query_plan_cache_hits: u64,
247 pub shared_query_plan_cache_misses: u64,
248}
249
250impl SqlCacheAttribution {
251 #[must_use]
252 const fn none() -> Self {
253 Self {
254 sql_compiled_command_cache_hits: 0,
255 sql_compiled_command_cache_misses: 0,
256 shared_query_plan_cache_hits: 0,
257 shared_query_plan_cache_misses: 0,
258 }
259 }
260
261 #[must_use]
262 const fn sql_compiled_command_cache_hit() -> Self {
263 Self {
264 sql_compiled_command_cache_hits: 1,
265 ..Self::none()
266 }
267 }
268
269 #[must_use]
270 const fn sql_compiled_command_cache_miss() -> Self {
271 Self {
272 sql_compiled_command_cache_misses: 1,
273 ..Self::none()
274 }
275 }
276
277 #[must_use]
278 const fn from_shared_query_plan_cache(attribution: QueryPlanCacheAttribution) -> Self {
279 Self {
280 shared_query_plan_cache_hits: attribution.hits,
281 shared_query_plan_cache_misses: attribution.misses,
282 ..Self::none()
283 }
284 }
285
286 #[must_use]
287 const fn merge(self, other: Self) -> Self {
288 Self {
289 sql_compiled_command_cache_hits: self
290 .sql_compiled_command_cache_hits
291 .saturating_add(other.sql_compiled_command_cache_hits),
292 sql_compiled_command_cache_misses: self
293 .sql_compiled_command_cache_misses
294 .saturating_add(other.sql_compiled_command_cache_misses),
295 shared_query_plan_cache_hits: self
296 .shared_query_plan_cache_hits
297 .saturating_add(other.shared_query_plan_cache_hits),
298 shared_query_plan_cache_misses: self
299 .shared_query_plan_cache_misses
300 .saturating_add(other.shared_query_plan_cache_misses),
301 }
302 }
303}
304
305#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
306enum SqlCompiledCommandSurface {
307 Query,
308 Update,
309}
310
311#[derive(Clone, Debug, Eq, Hash, PartialEq)]
322pub(in crate::db) struct SqlCompiledCommandCacheKey {
323 cache_method_version: u8,
324 surface: SqlCompiledCommandSurface,
325 entity_path: &'static str,
326 schema_fingerprint: CommitSchemaFingerprint,
327 sql: String,
328}
329
330#[derive(Clone, Debug)]
340pub(in crate::db) struct SqlProjectionContract {
341 columns: Vec<String>,
342 fixed_scales: Vec<Option<u32>>,
343}
344
345impl SqlProjectionContract {
346 #[must_use]
347 pub(in crate::db) const fn new(columns: Vec<String>, fixed_scales: Vec<Option<u32>>) -> Self {
348 Self {
349 columns,
350 fixed_scales,
351 }
352 }
353
354 #[must_use]
355 pub(in crate::db) fn into_parts(self) -> (Vec<String>, Vec<Option<u32>>) {
356 (self.columns, self.fixed_scales)
357 }
358}
359
360impl SqlCompiledCommandCacheKey {
361 fn query_for_entity<E>(sql: &str) -> Self
362 where
363 E: PersistedRow + EntityValue,
364 {
365 Self::for_entity::<E>(SqlCompiledCommandSurface::Query, sql)
366 }
367
368 fn update_for_entity<E>(sql: &str) -> Self
369 where
370 E: PersistedRow + EntityValue,
371 {
372 Self::for_entity::<E>(SqlCompiledCommandSurface::Update, sql)
373 }
374
375 fn for_entity<E>(surface: SqlCompiledCommandSurface, sql: &str) -> Self
376 where
377 E: PersistedRow + EntityValue,
378 {
379 Self {
380 cache_method_version: SQL_COMPILED_COMMAND_CACHE_METHOD_VERSION,
381 surface,
382 entity_path: E::PATH,
383 schema_fingerprint: commit_schema_fingerprint_for_entity::<E>(),
384 sql: sql.to_string(),
385 }
386 }
387
388 #[must_use]
389 pub(in crate::db) const fn schema_fingerprint(&self) -> CommitSchemaFingerprint {
390 self.schema_fingerprint
391 }
392}
393
394#[cfg(test)]
395impl SqlCompiledCommandCacheKey {
396 pub(in crate::db) fn query_for_entity_with_method_version<E>(
397 sql: &str,
398 cache_method_version: u8,
399 ) -> Self
400 where
401 E: PersistedRow + EntityValue,
402 {
403 Self::for_entity_with_method_version::<E>(
404 SqlCompiledCommandSurface::Query,
405 sql,
406 cache_method_version,
407 )
408 }
409
410 pub(in crate::db) fn update_for_entity_with_method_version<E>(
411 sql: &str,
412 cache_method_version: u8,
413 ) -> Self
414 where
415 E: PersistedRow + EntityValue,
416 {
417 Self::for_entity_with_method_version::<E>(
418 SqlCompiledCommandSurface::Update,
419 sql,
420 cache_method_version,
421 )
422 }
423
424 fn for_entity_with_method_version<E>(
425 surface: SqlCompiledCommandSurface,
426 sql: &str,
427 cache_method_version: u8,
428 ) -> Self
429 where
430 E: PersistedRow + EntityValue,
431 {
432 Self {
433 cache_method_version,
434 surface,
435 entity_path: E::PATH,
436 schema_fingerprint: commit_schema_fingerprint_for_entity::<E>(),
437 sql: sql.to_string(),
438 }
439 }
440}
441
442pub(in crate::db) type SqlCompiledCommandCache =
443 HashMap<SqlCompiledCommandCacheKey, CompiledSqlCommand>;
444
445thread_local! {
446 static SQL_COMPILED_COMMAND_CACHES: RefCell<HashMap<usize, SqlCompiledCommandCache>> =
450 RefCell::new(HashMap::default());
451}
452
453#[derive(Clone, Debug)]
457pub(in crate::db) enum CompiledSqlCommand {
458 Select {
459 query: Arc<StructuralQuery>,
460 compiled_cache_key: SqlCompiledCommandCacheKey,
461 },
462 Delete {
463 query: LoweredBaseQueryShape,
464 statement: SqlDeleteStatement,
465 },
466 GlobalAggregate {
467 command: Box<SqlGlobalAggregateCommandCore>,
468 },
469 Explain(LoweredSqlCommand),
470 Insert(SqlInsertStatement),
471 Update(SqlUpdateStatement),
472 DescribeEntity,
473 ShowIndexesEntity,
474 ShowColumnsEntity,
475 ShowEntities,
476}
477
478impl CompiledSqlCommand {
479 fn new_select(query: StructuralQuery, compiled_cache_key: SqlCompiledCommandCacheKey) -> Self {
480 Self::Select {
481 query: Arc::new(query),
482 compiled_cache_key,
483 }
484 }
485}
486
487#[cfg(test)]
490pub(in crate::db) fn parse_sql_statement(sql: &str) -> Result<SqlStatement, QueryError> {
491 parse_sql(sql).map_err(QueryError::from_sql_parse_error)
492}
493
494fn parse_sql_statement_with_attribution(
498 sql: &str,
499) -> Result<
500 (
501 SqlStatement,
502 crate::db::sql::parser::SqlParsePhaseAttribution,
503 ),
504 QueryError,
505> {
506 parse_sql_with_attribution(sql).map_err(QueryError::from_sql_parse_error)
507}
508
509#[cfg(feature = "diagnostics")]
510#[expect(
511 clippy::missing_const_for_fn,
512 reason = "the wasm32 branch reads the runtime performance counter and cannot be const"
513)]
514fn read_sql_local_instruction_counter() -> u64 {
515 #[cfg(all(feature = "diagnostics", target_arch = "wasm32"))]
516 {
517 canic_cdk::api::performance_counter(1)
518 }
519
520 #[cfg(not(all(feature = "diagnostics", target_arch = "wasm32")))]
521 {
522 0
523 }
524}
525
526pub(in crate::db::session::sql) fn measure_sql_stage<T, E>(
527 run: impl FnOnce() -> Result<T, E>,
528) -> (u64, Result<T, E>) {
529 #[cfg(feature = "diagnostics")]
530 let start = read_sql_local_instruction_counter();
531
532 let result = run();
533
534 #[cfg(feature = "diagnostics")]
535 let delta = read_sql_local_instruction_counter().saturating_sub(start);
536
537 #[cfg(not(feature = "diagnostics"))]
538 let delta = 0;
539
540 (delta, result)
541}
542
543impl<C: CanisterKind> DbSession<C> {
544 fn sql_cache_scope_id(&self) -> usize {
545 self.db.cache_scope_id()
546 }
547
548 fn with_sql_compiled_command_cache<R>(
549 &self,
550 f: impl FnOnce(&mut SqlCompiledCommandCache) -> R,
551 ) -> R {
552 let scope_id = self.sql_cache_scope_id();
553
554 SQL_COMPILED_COMMAND_CACHES.with(|caches| {
555 let mut caches = caches.borrow_mut();
556 let cache = caches.entry(scope_id).or_default();
557
558 f(cache)
559 })
560 }
561
562 #[cfg(test)]
563 pub(in crate::db) fn sql_compiled_command_cache_len(&self) -> usize {
564 self.with_sql_compiled_command_cache(|cache| cache.len())
565 }
566
567 #[cfg(test)]
568 pub(in crate::db) fn clear_sql_caches_for_tests(&self) {
569 self.with_sql_compiled_command_cache(SqlCompiledCommandCache::clear);
570 }
571
572 fn sql_select_projection_contract_from_shared_prepared_plan(
576 authority: EntityAuthority,
577 prepared_plan: &SharedPreparedExecutionPlan,
578 ) -> SqlProjectionContract {
579 let projection = prepared_plan
580 .logical_plan()
581 .projection_spec(authority.model());
582 let columns = projection_labels_from_projection_spec(&projection);
583 let fixed_scales = projection_fixed_scales_from_projection_spec(&projection);
584
585 SqlProjectionContract::new(columns, fixed_scales)
586 }
587
588 fn sql_select_prepared_plan(
591 &self,
592 query: &StructuralQuery,
593 authority: EntityAuthority,
594 cache_schema_fingerprint: CommitSchemaFingerprint,
595 ) -> Result<
596 (
597 SharedPreparedExecutionPlan,
598 SqlProjectionContract,
599 SqlCacheAttribution,
600 ),
601 QueryError,
602 > {
603 let (prepared_plan, cache_attribution) = self.cached_shared_query_plan_for_authority(
604 authority,
605 cache_schema_fingerprint,
606 query,
607 )?;
608 let projection = Self::sql_select_projection_contract_from_shared_prepared_plan(
609 authority,
610 &prepared_plan,
611 );
612
613 Ok((
614 prepared_plan,
615 projection,
616 SqlCacheAttribution::from_shared_query_plan_cache(cache_attribution),
617 ))
618 }
619
620 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
623 &self,
624 query: StructuralQuery,
625 authority: EntityAuthority,
626 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
627 let visible_indexes =
628 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
629 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
630
631 Ok((visible_indexes, plan))
632 }
633
634 fn ensure_sql_query_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
637 match statement {
638 SqlStatement::Select(_)
639 | SqlStatement::Explain(_)
640 | SqlStatement::Describe(_)
641 | SqlStatement::ShowIndexes(_)
642 | SqlStatement::ShowColumns(_)
643 | SqlStatement::ShowEntities(_) => Ok(()),
644 SqlStatement::Insert(_) => Err(QueryError::unsupported_query(
645 "execute_sql_query rejects INSERT; use execute_sql_update::<E>()",
646 )),
647 SqlStatement::Update(_) => Err(QueryError::unsupported_query(
648 "execute_sql_query rejects UPDATE; use execute_sql_update::<E>()",
649 )),
650 SqlStatement::Delete(_) => Err(QueryError::unsupported_query(
651 "execute_sql_query rejects DELETE; use execute_sql_update::<E>()",
652 )),
653 }
654 }
655
656 fn ensure_sql_update_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
659 match statement {
660 SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
661 SqlStatement::Select(_) => Err(QueryError::unsupported_query(
662 "execute_sql_update rejects SELECT; use execute_sql_query::<E>()",
663 )),
664 SqlStatement::Explain(_) => Err(QueryError::unsupported_query(
665 "execute_sql_update rejects EXPLAIN; use execute_sql_query::<E>()",
666 )),
667 SqlStatement::Describe(_) => Err(QueryError::unsupported_query(
668 "execute_sql_update rejects DESCRIBE; use execute_sql_query::<E>()",
669 )),
670 SqlStatement::ShowIndexes(_) => Err(QueryError::unsupported_query(
671 "execute_sql_update rejects SHOW INDEXES; use execute_sql_query::<E>()",
672 )),
673 SqlStatement::ShowColumns(_) => Err(QueryError::unsupported_query(
674 "execute_sql_update rejects SHOW COLUMNS; use execute_sql_query::<E>()",
675 )),
676 SqlStatement::ShowEntities(_) => Err(QueryError::unsupported_query(
677 "execute_sql_update rejects SHOW ENTITIES; use execute_sql_query::<E>()",
678 )),
679 }
680 }
681
682 pub fn execute_sql_query<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
687 where
688 E: PersistedRow<Canister = C> + EntityValue,
689 {
690 let compiled = self.compile_sql_query::<E>(sql)?;
691
692 self.execute_compiled_sql::<E>(&compiled)
693 }
694
695 #[cfg(feature = "diagnostics")]
698 #[doc(hidden)]
699 pub fn execute_sql_query_with_attribution<E>(
700 &self,
701 sql: &str,
702 ) -> Result<(SqlStatementResult, SqlQueryExecutionAttribution), QueryError>
703 where
704 E: PersistedRow<Canister = C> + EntityValue,
705 {
706 let (compile_local_instructions, compiled) =
709 measure_sql_stage(|| self.compile_sql_query_with_cache_attribution::<E>(sql));
710 let (compiled, compile_cache_attribution, compile_phase_attribution) = compiled?;
711
712 let store_get_calls_before = DataStore::current_get_call_count();
715 let pure_covering_decode_before = current_pure_covering_decode_local_instructions();
716 let pure_covering_row_assembly_before =
717 current_pure_covering_row_assembly_local_instructions();
718 let (result, execute_cache_attribution, execute_phase_attribution) =
719 self.execute_compiled_sql_with_phase_attribution::<E>(&compiled)?;
720 let store_get_calls =
721 DataStore::current_get_call_count().saturating_sub(store_get_calls_before);
722 let pure_covering_decode_local_instructions =
723 current_pure_covering_decode_local_instructions()
724 .saturating_sub(pure_covering_decode_before);
725 let pure_covering_row_assembly_local_instructions =
726 current_pure_covering_row_assembly_local_instructions()
727 .saturating_sub(pure_covering_row_assembly_before);
728 let execute_local_instructions = execute_phase_attribution
729 .planner_local_instructions
730 .saturating_add(execute_phase_attribution.store_local_instructions)
731 .saturating_add(execute_phase_attribution.executor_local_instructions);
732 let cache_attribution = compile_cache_attribution.merge(execute_cache_attribution);
733 let total_local_instructions =
734 compile_local_instructions.saturating_add(execute_local_instructions);
735
736 Ok((
737 result,
738 SqlQueryExecutionAttribution {
739 compile_local_instructions,
740 compile_cache_key_local_instructions: compile_phase_attribution.cache_key,
741 compile_cache_lookup_local_instructions: compile_phase_attribution.cache_lookup,
742 compile_parse_local_instructions: compile_phase_attribution.parse,
743 compile_parse_tokenize_local_instructions: compile_phase_attribution.parse_tokenize,
744 compile_parse_select_local_instructions: compile_phase_attribution.parse_select,
745 compile_parse_expr_local_instructions: compile_phase_attribution.parse_expr,
746 compile_parse_predicate_local_instructions: compile_phase_attribution
747 .parse_predicate,
748 compile_aggregate_lane_check_local_instructions: compile_phase_attribution
749 .aggregate_lane_check,
750 compile_prepare_local_instructions: compile_phase_attribution.prepare,
751 compile_lower_local_instructions: compile_phase_attribution.lower,
752 compile_bind_local_instructions: compile_phase_attribution.bind,
753 compile_cache_insert_local_instructions: compile_phase_attribution.cache_insert,
754 planner_local_instructions: execute_phase_attribution.planner_local_instructions,
755 store_local_instructions: execute_phase_attribution.store_local_instructions,
756 executor_local_instructions: execute_phase_attribution.executor_local_instructions,
757 grouped_stream_local_instructions: execute_phase_attribution
758 .grouped_stream_local_instructions,
759 grouped_fold_local_instructions: execute_phase_attribution
760 .grouped_fold_local_instructions,
761 grouped_finalize_local_instructions: execute_phase_attribution
762 .grouped_finalize_local_instructions,
763 grouped_count_borrowed_hash_computations: execute_phase_attribution
764 .grouped_count
765 .borrowed_hash_computations,
766 grouped_count_bucket_candidate_checks: execute_phase_attribution
767 .grouped_count
768 .bucket_candidate_checks,
769 grouped_count_existing_group_hits: execute_phase_attribution
770 .grouped_count
771 .existing_group_hits,
772 grouped_count_new_group_inserts: execute_phase_attribution
773 .grouped_count
774 .new_group_inserts,
775 grouped_count_row_materialization_local_instructions: execute_phase_attribution
776 .grouped_count
777 .row_materialization_local_instructions,
778 grouped_count_group_lookup_local_instructions: execute_phase_attribution
779 .grouped_count
780 .group_lookup_local_instructions,
781 grouped_count_existing_group_update_local_instructions: execute_phase_attribution
782 .grouped_count
783 .existing_group_update_local_instructions,
784 grouped_count_new_group_insert_local_instructions: execute_phase_attribution
785 .grouped_count
786 .new_group_insert_local_instructions,
787 pure_covering_decode_local_instructions,
788 pure_covering_row_assembly_local_instructions,
789 store_get_calls,
790 response_decode_local_instructions: 0,
791 execute_local_instructions,
792 total_local_instructions,
793 sql_compiled_command_cache_hits: cache_attribution.sql_compiled_command_cache_hits,
794 sql_compiled_command_cache_misses: cache_attribution
795 .sql_compiled_command_cache_misses,
796 shared_query_plan_cache_hits: cache_attribution.shared_query_plan_cache_hits,
797 shared_query_plan_cache_misses: cache_attribution.shared_query_plan_cache_misses,
798 },
799 ))
800 }
801
802 pub fn execute_sql_update<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
807 where
808 E: PersistedRow<Canister = C> + EntityValue,
809 {
810 let compiled = self.compile_sql_update::<E>(sql)?;
811
812 self.execute_compiled_sql::<E>(&compiled)
813 }
814
815 pub(in crate::db) fn compile_sql_query<E>(
818 &self,
819 sql: &str,
820 ) -> Result<CompiledSqlCommand, QueryError>
821 where
822 E: PersistedRow<Canister = C> + EntityValue,
823 {
824 self.compile_sql_query_with_cache_attribution::<E>(sql)
825 .map(|(compiled, _, _)| compiled)
826 }
827
828 fn compile_sql_query_with_cache_attribution<E>(
829 &self,
830 sql: &str,
831 ) -> Result<
832 (
833 CompiledSqlCommand,
834 SqlCacheAttribution,
835 SqlCompilePhaseAttribution,
836 ),
837 QueryError,
838 >
839 where
840 E: PersistedRow<Canister = C> + EntityValue,
841 {
842 let (cache_key_local_instructions, cache_key) = measure_sql_stage(|| {
843 Ok::<_, QueryError>(SqlCompiledCommandCacheKey::query_for_entity::<E>(sql))
844 });
845 let cache_key = cache_key?;
846
847 self.compile_sql_statement_with_cache::<E>(
848 cache_key,
849 cache_key_local_instructions,
850 sql,
851 Self::ensure_sql_query_statement_supported,
852 )
853 }
854
855 pub(in crate::db) fn compile_sql_update<E>(
858 &self,
859 sql: &str,
860 ) -> Result<CompiledSqlCommand, QueryError>
861 where
862 E: PersistedRow<Canister = C> + EntityValue,
863 {
864 self.compile_sql_update_with_cache_attribution::<E>(sql)
865 .map(|(compiled, _, _)| compiled)
866 }
867
868 fn compile_sql_update_with_cache_attribution<E>(
869 &self,
870 sql: &str,
871 ) -> Result<
872 (
873 CompiledSqlCommand,
874 SqlCacheAttribution,
875 SqlCompilePhaseAttribution,
876 ),
877 QueryError,
878 >
879 where
880 E: PersistedRow<Canister = C> + EntityValue,
881 {
882 let (cache_key_local_instructions, cache_key) = measure_sql_stage(|| {
883 Ok::<_, QueryError>(SqlCompiledCommandCacheKey::update_for_entity::<E>(sql))
884 });
885 let cache_key = cache_key?;
886
887 self.compile_sql_statement_with_cache::<E>(
888 cache_key,
889 cache_key_local_instructions,
890 sql,
891 Self::ensure_sql_update_statement_supported,
892 )
893 }
894
895 fn compile_sql_statement_with_cache<E>(
898 &self,
899 cache_key: SqlCompiledCommandCacheKey,
900 cache_key_local_instructions: u64,
901 sql: &str,
902 ensure_surface_supported: fn(&SqlStatement) -> Result<(), QueryError>,
903 ) -> Result<
904 (
905 CompiledSqlCommand,
906 SqlCacheAttribution,
907 SqlCompilePhaseAttribution,
908 ),
909 QueryError,
910 >
911 where
912 E: PersistedRow<Canister = C> + EntityValue,
913 {
914 let (cache_lookup_local_instructions, cached) = measure_sql_stage(|| {
915 let cached =
916 self.with_sql_compiled_command_cache(|cache| cache.get(&cache_key).cloned());
917 Ok::<_, QueryError>(cached)
918 });
919 let cached = cached?;
920 if let Some(compiled) = cached {
921 return Ok((
922 compiled,
923 SqlCacheAttribution::sql_compiled_command_cache_hit(),
924 SqlCompilePhaseAttribution::cache_hit(
925 cache_key_local_instructions,
926 cache_lookup_local_instructions,
927 ),
928 ));
929 }
930
931 let (parse_local_instructions, parsed) =
932 measure_sql_stage(|| parse_sql_statement_with_attribution(sql));
933 let (parsed, parse_attribution) = parsed?;
934 let parse_select_local_instructions = parse_local_instructions
935 .saturating_sub(parse_attribution.tokenize)
936 .saturating_sub(parse_attribution.expr)
937 .saturating_sub(parse_attribution.predicate);
938 ensure_surface_supported(&parsed)?;
939 let authority = EntityAuthority::for_type::<E>();
940 let (
941 compiled,
942 aggregate_lane_check_local_instructions,
943 prepare_local_instructions,
944 lower_local_instructions,
945 bind_local_instructions,
946 ) = Self::compile_sql_statement_for_authority(&parsed, authority, cache_key.clone())?;
947
948 let (cache_insert_local_instructions, cache_insert) = measure_sql_stage(|| {
949 self.with_sql_compiled_command_cache(|cache| {
950 cache.insert(cache_key, compiled.clone());
951 });
952 Ok::<_, QueryError>(())
953 });
954 cache_insert?;
955
956 Ok((
957 compiled,
958 SqlCacheAttribution::sql_compiled_command_cache_miss(),
959 SqlCompilePhaseAttribution {
960 cache_key: cache_key_local_instructions,
961 cache_lookup: cache_lookup_local_instructions,
962 parse: parse_local_instructions,
963 parse_tokenize: parse_attribution.tokenize,
964 parse_select: parse_select_local_instructions,
965 parse_expr: parse_attribution.expr,
966 parse_predicate: parse_attribution.predicate,
967 aggregate_lane_check: aggregate_lane_check_local_instructions,
968 prepare: prepare_local_instructions,
969 lower: lower_local_instructions,
970 bind: bind_local_instructions,
971 cache_insert: cache_insert_local_instructions,
972 },
973 ))
974 }
975}