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