1mod execute;
8mod explain;
9mod parameter;
10mod projection;
11
12#[cfg(feature = "diagnostics")]
13use candid::CandidType;
14use icydb_utils::Xxh3;
15#[cfg(feature = "diagnostics")]
16use serde::Deserialize;
17use std::{cell::RefCell, collections::HashMap, hash::BuildHasherDefault, sync::Arc};
18
19type CacheBuildHasher = BuildHasherDefault<Xxh3>;
20
21const 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)]
241pub(in crate::db) struct SqlCacheAttribution {
242 pub sql_compiled_command_cache_hits: u64,
243 pub sql_compiled_command_cache_misses: u64,
244 pub shared_query_plan_cache_hits: u64,
245 pub shared_query_plan_cache_misses: u64,
246}
247
248impl SqlCacheAttribution {
249 #[must_use]
250 const fn none() -> Self {
251 Self {
252 sql_compiled_command_cache_hits: 0,
253 sql_compiled_command_cache_misses: 0,
254 shared_query_plan_cache_hits: 0,
255 shared_query_plan_cache_misses: 0,
256 }
257 }
258
259 #[must_use]
260 const fn sql_compiled_command_cache_hit() -> Self {
261 Self {
262 sql_compiled_command_cache_hits: 1,
263 ..Self::none()
264 }
265 }
266
267 #[must_use]
268 const fn sql_compiled_command_cache_miss() -> Self {
269 Self {
270 sql_compiled_command_cache_misses: 1,
271 ..Self::none()
272 }
273 }
274
275 #[must_use]
276 const fn from_shared_query_plan_cache(attribution: QueryPlanCacheAttribution) -> Self {
277 Self {
278 shared_query_plan_cache_hits: attribution.hits,
279 shared_query_plan_cache_misses: attribution.misses,
280 ..Self::none()
281 }
282 }
283
284 #[must_use]
285 const fn merge(self, other: Self) -> Self {
286 Self {
287 sql_compiled_command_cache_hits: self
288 .sql_compiled_command_cache_hits
289 .saturating_add(other.sql_compiled_command_cache_hits),
290 sql_compiled_command_cache_misses: self
291 .sql_compiled_command_cache_misses
292 .saturating_add(other.sql_compiled_command_cache_misses),
293 shared_query_plan_cache_hits: self
294 .shared_query_plan_cache_hits
295 .saturating_add(other.shared_query_plan_cache_hits),
296 shared_query_plan_cache_misses: self
297 .shared_query_plan_cache_misses
298 .saturating_add(other.shared_query_plan_cache_misses),
299 }
300 }
301}
302
303#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
304enum SqlCompiledCommandSurface {
305 Query,
306 Update,
307}
308
309#[derive(Clone, Debug, Eq, Hash, PartialEq)]
320pub(in crate::db) struct SqlCompiledCommandCacheKey {
321 cache_method_version: u8,
322 surface: SqlCompiledCommandSurface,
323 entity_path: &'static str,
324 schema_fingerprint: CommitSchemaFingerprint,
325 sql: String,
326}
327
328#[derive(Clone, Debug)]
338pub(in crate::db) struct SqlProjectionContract {
339 columns: Vec<String>,
340 fixed_scales: Vec<Option<u32>>,
341}
342
343impl SqlProjectionContract {
344 #[must_use]
345 pub(in crate::db) const fn new(columns: Vec<String>, fixed_scales: Vec<Option<u32>>) -> Self {
346 Self {
347 columns,
348 fixed_scales,
349 }
350 }
351
352 #[must_use]
353 pub(in crate::db) fn into_parts(self) -> (Vec<String>, Vec<Option<u32>>) {
354 (self.columns, self.fixed_scales)
355 }
356}
357
358impl SqlCompiledCommandCacheKey {
359 fn query_for_entity<E>(sql: &str) -> Self
360 where
361 E: PersistedRow + EntityValue,
362 {
363 Self::for_entity::<E>(SqlCompiledCommandSurface::Query, sql)
364 }
365
366 fn update_for_entity<E>(sql: &str) -> Self
367 where
368 E: PersistedRow + EntityValue,
369 {
370 Self::for_entity::<E>(SqlCompiledCommandSurface::Update, sql)
371 }
372
373 fn for_entity<E>(surface: SqlCompiledCommandSurface, sql: &str) -> Self
374 where
375 E: PersistedRow + EntityValue,
376 {
377 Self {
378 cache_method_version: SQL_COMPILED_COMMAND_CACHE_METHOD_VERSION,
379 surface,
380 entity_path: E::PATH,
381 schema_fingerprint: commit_schema_fingerprint_for_entity::<E>(),
382 sql: sql.to_string(),
383 }
384 }
385
386 #[must_use]
387 pub(in crate::db) const fn schema_fingerprint(&self) -> CommitSchemaFingerprint {
388 self.schema_fingerprint
389 }
390}
391
392#[cfg(test)]
393impl SqlCompiledCommandCacheKey {
394 pub(in crate::db) fn query_for_entity_with_method_version<E>(
395 sql: &str,
396 cache_method_version: u8,
397 ) -> Self
398 where
399 E: PersistedRow + EntityValue,
400 {
401 Self::for_entity_with_method_version::<E>(
402 SqlCompiledCommandSurface::Query,
403 sql,
404 cache_method_version,
405 )
406 }
407
408 pub(in crate::db) fn update_for_entity_with_method_version<E>(
409 sql: &str,
410 cache_method_version: u8,
411 ) -> Self
412 where
413 E: PersistedRow + EntityValue,
414 {
415 Self::for_entity_with_method_version::<E>(
416 SqlCompiledCommandSurface::Update,
417 sql,
418 cache_method_version,
419 )
420 }
421
422 fn for_entity_with_method_version<E>(
423 surface: SqlCompiledCommandSurface,
424 sql: &str,
425 cache_method_version: u8,
426 ) -> Self
427 where
428 E: PersistedRow + EntityValue,
429 {
430 Self {
431 cache_method_version,
432 surface,
433 entity_path: E::PATH,
434 schema_fingerprint: commit_schema_fingerprint_for_entity::<E>(),
435 sql: sql.to_string(),
436 }
437 }
438}
439
440pub(in crate::db) type SqlCompiledCommandCache =
441 HashMap<SqlCompiledCommandCacheKey, CompiledSqlCommand, CacheBuildHasher>;
442
443thread_local! {
444 static SQL_COMPILED_COMMAND_CACHES: RefCell<HashMap<usize, SqlCompiledCommandCache, CacheBuildHasher>> =
448 RefCell::new(HashMap::default());
449}
450
451#[derive(Clone, Debug)]
455pub(in crate::db) enum CompiledSqlCommand {
456 Select {
457 query: Arc<StructuralQuery>,
458 compiled_cache_key: SqlCompiledCommandCacheKey,
459 },
460 Delete {
461 query: LoweredBaseQueryShape,
462 statement: SqlDeleteStatement,
463 },
464 GlobalAggregate {
465 command: Box<SqlGlobalAggregateCommandCore>,
466 },
467 Explain(LoweredSqlCommand),
468 Insert(SqlInsertStatement),
469 Update(SqlUpdateStatement),
470 DescribeEntity,
471 ShowIndexesEntity,
472 ShowColumnsEntity,
473 ShowEntities,
474}
475
476impl CompiledSqlCommand {
477 fn new_select(query: StructuralQuery, compiled_cache_key: SqlCompiledCommandCacheKey) -> Self {
478 Self::Select {
479 query: Arc::new(query),
480 compiled_cache_key,
481 }
482 }
483}
484
485#[cfg(test)]
488pub(in crate::db) fn parse_sql_statement(sql: &str) -> Result<SqlStatement, QueryError> {
489 parse_sql(sql).map_err(QueryError::from_sql_parse_error)
490}
491
492fn parse_sql_statement_with_attribution(
496 sql: &str,
497) -> Result<
498 (
499 SqlStatement,
500 crate::db::sql::parser::SqlParsePhaseAttribution,
501 ),
502 QueryError,
503> {
504 parse_sql_with_attribution(sql).map_err(QueryError::from_sql_parse_error)
505}
506
507#[cfg(feature = "diagnostics")]
508#[expect(
509 clippy::missing_const_for_fn,
510 reason = "the wasm32 branch reads the runtime performance counter and cannot be const"
511)]
512fn read_sql_local_instruction_counter() -> u64 {
513 #[cfg(all(feature = "diagnostics", target_arch = "wasm32"))]
514 {
515 canic_cdk::api::performance_counter(1)
516 }
517
518 #[cfg(not(all(feature = "diagnostics", target_arch = "wasm32")))]
519 {
520 0
521 }
522}
523
524pub(in crate::db::session::sql) fn measure_sql_stage<T, E>(
525 run: impl FnOnce() -> Result<T, E>,
526) -> (u64, Result<T, E>) {
527 #[cfg(feature = "diagnostics")]
528 let start = read_sql_local_instruction_counter();
529
530 let result = run();
531
532 #[cfg(feature = "diagnostics")]
533 let delta = read_sql_local_instruction_counter().saturating_sub(start);
534
535 #[cfg(not(feature = "diagnostics"))]
536 let delta = 0;
537
538 (delta, result)
539}
540
541impl<C: CanisterKind> DbSession<C> {
542 fn sql_cache_scope_id(&self) -> usize {
543 self.db.cache_scope_id()
544 }
545
546 fn with_sql_compiled_command_cache<R>(
547 &self,
548 f: impl FnOnce(&mut SqlCompiledCommandCache) -> R,
549 ) -> R {
550 let scope_id = self.sql_cache_scope_id();
551
552 SQL_COMPILED_COMMAND_CACHES.with(|caches| {
553 let mut caches = caches.borrow_mut();
554 let cache = caches.entry(scope_id).or_default();
555
556 f(cache)
557 })
558 }
559
560 #[cfg(test)]
561 pub(in crate::db) fn sql_compiled_command_cache_len(&self) -> usize {
562 self.with_sql_compiled_command_cache(|cache| cache.len())
563 }
564
565 #[cfg(test)]
566 pub(in crate::db) fn clear_sql_caches_for_tests(&self) {
567 self.with_sql_compiled_command_cache(SqlCompiledCommandCache::clear);
568 }
569
570 fn sql_select_projection_contract_from_shared_prepared_plan(
574 authority: EntityAuthority,
575 prepared_plan: &SharedPreparedExecutionPlan,
576 ) -> SqlProjectionContract {
577 let projection = prepared_plan
578 .logical_plan()
579 .projection_spec(authority.model());
580 let columns = projection_labels_from_projection_spec(&projection);
581 let fixed_scales = projection_fixed_scales_from_projection_spec(&projection);
582
583 SqlProjectionContract::new(columns, fixed_scales)
584 }
585
586 fn sql_select_prepared_plan(
589 &self,
590 query: &StructuralQuery,
591 authority: EntityAuthority,
592 cache_schema_fingerprint: CommitSchemaFingerprint,
593 ) -> Result<
594 (
595 SharedPreparedExecutionPlan,
596 SqlProjectionContract,
597 SqlCacheAttribution,
598 ),
599 QueryError,
600 > {
601 let (prepared_plan, cache_attribution) = self.cached_shared_query_plan_for_authority(
602 authority,
603 cache_schema_fingerprint,
604 query,
605 )?;
606 let projection = Self::sql_select_projection_contract_from_shared_prepared_plan(
607 authority,
608 &prepared_plan,
609 );
610
611 Ok((
612 prepared_plan,
613 projection,
614 SqlCacheAttribution::from_shared_query_plan_cache(cache_attribution),
615 ))
616 }
617
618 pub(in crate::db::session::sql) fn build_structural_plan_with_visible_indexes_for_authority(
621 &self,
622 query: StructuralQuery,
623 authority: EntityAuthority,
624 ) -> Result<(VisibleIndexes<'_>, AccessPlannedQuery), QueryError> {
625 let visible_indexes =
626 self.visible_indexes_for_store_model(authority.store_path(), authority.model())?;
627 let plan = query.build_plan_with_visible_indexes(&visible_indexes)?;
628
629 Ok((visible_indexes, plan))
630 }
631
632 fn ensure_sql_query_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
635 match statement {
636 SqlStatement::Select(_)
637 | SqlStatement::Explain(_)
638 | SqlStatement::Describe(_)
639 | SqlStatement::ShowIndexes(_)
640 | SqlStatement::ShowColumns(_)
641 | SqlStatement::ShowEntities(_) => Ok(()),
642 SqlStatement::Insert(_) => Err(QueryError::unsupported_query(
643 "execute_sql_query rejects INSERT; use execute_sql_update::<E>()",
644 )),
645 SqlStatement::Update(_) => Err(QueryError::unsupported_query(
646 "execute_sql_query rejects UPDATE; use execute_sql_update::<E>()",
647 )),
648 SqlStatement::Delete(_) => Err(QueryError::unsupported_query(
649 "execute_sql_query rejects DELETE; use execute_sql_update::<E>()",
650 )),
651 }
652 }
653
654 fn ensure_sql_update_statement_supported(statement: &SqlStatement) -> Result<(), QueryError> {
657 match statement {
658 SqlStatement::Insert(_) | SqlStatement::Update(_) | SqlStatement::Delete(_) => Ok(()),
659 SqlStatement::Select(_) => Err(QueryError::unsupported_query(
660 "execute_sql_update rejects SELECT; use execute_sql_query::<E>()",
661 )),
662 SqlStatement::Explain(_) => Err(QueryError::unsupported_query(
663 "execute_sql_update rejects EXPLAIN; use execute_sql_query::<E>()",
664 )),
665 SqlStatement::Describe(_) => Err(QueryError::unsupported_query(
666 "execute_sql_update rejects DESCRIBE; use execute_sql_query::<E>()",
667 )),
668 SqlStatement::ShowIndexes(_) => Err(QueryError::unsupported_query(
669 "execute_sql_update rejects SHOW INDEXES; use execute_sql_query::<E>()",
670 )),
671 SqlStatement::ShowColumns(_) => Err(QueryError::unsupported_query(
672 "execute_sql_update rejects SHOW COLUMNS; use execute_sql_query::<E>()",
673 )),
674 SqlStatement::ShowEntities(_) => Err(QueryError::unsupported_query(
675 "execute_sql_update rejects SHOW ENTITIES; use execute_sql_query::<E>()",
676 )),
677 }
678 }
679
680 pub fn execute_sql_query<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
685 where
686 E: PersistedRow<Canister = C> + EntityValue,
687 {
688 let compiled = self.compile_sql_query::<E>(sql)?;
689
690 self.execute_compiled_sql::<E>(&compiled)
691 }
692
693 #[cfg(feature = "diagnostics")]
696 #[doc(hidden)]
697 pub fn execute_sql_query_with_attribution<E>(
698 &self,
699 sql: &str,
700 ) -> Result<(SqlStatementResult, SqlQueryExecutionAttribution), QueryError>
701 where
702 E: PersistedRow<Canister = C> + EntityValue,
703 {
704 let (compile_local_instructions, compiled) =
707 measure_sql_stage(|| self.compile_sql_query_with_cache_attribution::<E>(sql));
708 let (compiled, compile_cache_attribution, compile_phase_attribution) = compiled?;
709
710 let store_get_calls_before = DataStore::current_get_call_count();
713 let pure_covering_decode_before = current_pure_covering_decode_local_instructions();
714 let pure_covering_row_assembly_before =
715 current_pure_covering_row_assembly_local_instructions();
716 let (result, execute_cache_attribution, execute_phase_attribution) =
717 self.execute_compiled_sql_with_phase_attribution::<E>(&compiled)?;
718 let store_get_calls =
719 DataStore::current_get_call_count().saturating_sub(store_get_calls_before);
720 let pure_covering_decode_local_instructions =
721 current_pure_covering_decode_local_instructions()
722 .saturating_sub(pure_covering_decode_before);
723 let pure_covering_row_assembly_local_instructions =
724 current_pure_covering_row_assembly_local_instructions()
725 .saturating_sub(pure_covering_row_assembly_before);
726 let execute_local_instructions = execute_phase_attribution
727 .planner_local_instructions
728 .saturating_add(execute_phase_attribution.store_local_instructions)
729 .saturating_add(execute_phase_attribution.executor_local_instructions);
730 let cache_attribution = compile_cache_attribution.merge(execute_cache_attribution);
731 let total_local_instructions =
732 compile_local_instructions.saturating_add(execute_local_instructions);
733
734 Ok((
735 result,
736 SqlQueryExecutionAttribution {
737 compile_local_instructions,
738 compile_cache_key_local_instructions: compile_phase_attribution.cache_key,
739 compile_cache_lookup_local_instructions: compile_phase_attribution.cache_lookup,
740 compile_parse_local_instructions: compile_phase_attribution.parse,
741 compile_parse_tokenize_local_instructions: compile_phase_attribution.parse_tokenize,
742 compile_parse_select_local_instructions: compile_phase_attribution.parse_select,
743 compile_parse_expr_local_instructions: compile_phase_attribution.parse_expr,
744 compile_parse_predicate_local_instructions: compile_phase_attribution
745 .parse_predicate,
746 compile_aggregate_lane_check_local_instructions: compile_phase_attribution
747 .aggregate_lane_check,
748 compile_prepare_local_instructions: compile_phase_attribution.prepare,
749 compile_lower_local_instructions: compile_phase_attribution.lower,
750 compile_bind_local_instructions: compile_phase_attribution.bind,
751 compile_cache_insert_local_instructions: compile_phase_attribution.cache_insert,
752 planner_local_instructions: execute_phase_attribution.planner_local_instructions,
753 store_local_instructions: execute_phase_attribution.store_local_instructions,
754 executor_local_instructions: execute_phase_attribution.executor_local_instructions,
755 grouped_stream_local_instructions: execute_phase_attribution
756 .grouped_stream_local_instructions,
757 grouped_fold_local_instructions: execute_phase_attribution
758 .grouped_fold_local_instructions,
759 grouped_finalize_local_instructions: execute_phase_attribution
760 .grouped_finalize_local_instructions,
761 grouped_count_borrowed_hash_computations: execute_phase_attribution
762 .grouped_count
763 .borrowed_hash_computations,
764 grouped_count_bucket_candidate_checks: execute_phase_attribution
765 .grouped_count
766 .bucket_candidate_checks,
767 grouped_count_existing_group_hits: execute_phase_attribution
768 .grouped_count
769 .existing_group_hits,
770 grouped_count_new_group_inserts: execute_phase_attribution
771 .grouped_count
772 .new_group_inserts,
773 grouped_count_row_materialization_local_instructions: execute_phase_attribution
774 .grouped_count
775 .row_materialization_local_instructions,
776 grouped_count_group_lookup_local_instructions: execute_phase_attribution
777 .grouped_count
778 .group_lookup_local_instructions,
779 grouped_count_existing_group_update_local_instructions: execute_phase_attribution
780 .grouped_count
781 .existing_group_update_local_instructions,
782 grouped_count_new_group_insert_local_instructions: execute_phase_attribution
783 .grouped_count
784 .new_group_insert_local_instructions,
785 pure_covering_decode_local_instructions,
786 pure_covering_row_assembly_local_instructions,
787 store_get_calls,
788 response_decode_local_instructions: 0,
789 execute_local_instructions,
790 total_local_instructions,
791 sql_compiled_command_cache_hits: cache_attribution.sql_compiled_command_cache_hits,
792 sql_compiled_command_cache_misses: cache_attribution
793 .sql_compiled_command_cache_misses,
794 shared_query_plan_cache_hits: cache_attribution.shared_query_plan_cache_hits,
795 shared_query_plan_cache_misses: cache_attribution.shared_query_plan_cache_misses,
796 },
797 ))
798 }
799
800 pub fn execute_sql_update<E>(&self, sql: &str) -> Result<SqlStatementResult, QueryError>
805 where
806 E: PersistedRow<Canister = C> + EntityValue,
807 {
808 let compiled = self.compile_sql_update::<E>(sql)?;
809
810 self.execute_compiled_sql::<E>(&compiled)
811 }
812
813 pub(in crate::db) fn compile_sql_query<E>(
816 &self,
817 sql: &str,
818 ) -> Result<CompiledSqlCommand, QueryError>
819 where
820 E: PersistedRow<Canister = C> + EntityValue,
821 {
822 self.compile_sql_query_with_cache_attribution::<E>(sql)
823 .map(|(compiled, _, _)| compiled)
824 }
825
826 fn compile_sql_query_with_cache_attribution<E>(
827 &self,
828 sql: &str,
829 ) -> Result<
830 (
831 CompiledSqlCommand,
832 SqlCacheAttribution,
833 SqlCompilePhaseAttribution,
834 ),
835 QueryError,
836 >
837 where
838 E: PersistedRow<Canister = C> + EntityValue,
839 {
840 let (cache_key_local_instructions, cache_key) = measure_sql_stage(|| {
841 Ok::<_, QueryError>(SqlCompiledCommandCacheKey::query_for_entity::<E>(sql))
842 });
843 let cache_key = cache_key?;
844
845 self.compile_sql_statement_with_cache::<E>(
846 cache_key,
847 cache_key_local_instructions,
848 sql,
849 Self::ensure_sql_query_statement_supported,
850 )
851 }
852
853 pub(in crate::db) fn compile_sql_update<E>(
856 &self,
857 sql: &str,
858 ) -> Result<CompiledSqlCommand, QueryError>
859 where
860 E: PersistedRow<Canister = C> + EntityValue,
861 {
862 self.compile_sql_update_with_cache_attribution::<E>(sql)
863 .map(|(compiled, _, _)| compiled)
864 }
865
866 fn compile_sql_update_with_cache_attribution<E>(
867 &self,
868 sql: &str,
869 ) -> Result<
870 (
871 CompiledSqlCommand,
872 SqlCacheAttribution,
873 SqlCompilePhaseAttribution,
874 ),
875 QueryError,
876 >
877 where
878 E: PersistedRow<Canister = C> + EntityValue,
879 {
880 let (cache_key_local_instructions, cache_key) = measure_sql_stage(|| {
881 Ok::<_, QueryError>(SqlCompiledCommandCacheKey::update_for_entity::<E>(sql))
882 });
883 let cache_key = cache_key?;
884
885 self.compile_sql_statement_with_cache::<E>(
886 cache_key,
887 cache_key_local_instructions,
888 sql,
889 Self::ensure_sql_update_statement_supported,
890 )
891 }
892
893 fn compile_sql_statement_with_cache<E>(
896 &self,
897 cache_key: SqlCompiledCommandCacheKey,
898 cache_key_local_instructions: u64,
899 sql: &str,
900 ensure_surface_supported: fn(&SqlStatement) -> Result<(), QueryError>,
901 ) -> Result<
902 (
903 CompiledSqlCommand,
904 SqlCacheAttribution,
905 SqlCompilePhaseAttribution,
906 ),
907 QueryError,
908 >
909 where
910 E: PersistedRow<Canister = C> + EntityValue,
911 {
912 let (cache_lookup_local_instructions, cached) = measure_sql_stage(|| {
913 let cached =
914 self.with_sql_compiled_command_cache(|cache| cache.get(&cache_key).cloned());
915 Ok::<_, QueryError>(cached)
916 });
917 let cached = cached?;
918 if let Some(compiled) = cached {
919 return Ok((
920 compiled,
921 SqlCacheAttribution::sql_compiled_command_cache_hit(),
922 SqlCompilePhaseAttribution::cache_hit(
923 cache_key_local_instructions,
924 cache_lookup_local_instructions,
925 ),
926 ));
927 }
928
929 let (parse_local_instructions, parsed) =
930 measure_sql_stage(|| parse_sql_statement_with_attribution(sql));
931 let (parsed, parse_attribution) = parsed?;
932 let parse_select_local_instructions = parse_local_instructions
933 .saturating_sub(parse_attribution.tokenize)
934 .saturating_sub(parse_attribution.expr)
935 .saturating_sub(parse_attribution.predicate);
936 ensure_surface_supported(&parsed)?;
937 let authority = EntityAuthority::for_type::<E>();
938 let (
939 compiled,
940 aggregate_lane_check_local_instructions,
941 prepare_local_instructions,
942 lower_local_instructions,
943 bind_local_instructions,
944 ) = Self::compile_sql_statement_for_authority(&parsed, authority, cache_key.clone())?;
945
946 let (cache_insert_local_instructions, cache_insert) = measure_sql_stage(|| {
947 self.with_sql_compiled_command_cache(|cache| {
948 cache.insert(cache_key, compiled.clone());
949 });
950 Ok::<_, QueryError>(())
951 });
952 cache_insert?;
953
954 Ok((
955 compiled,
956 SqlCacheAttribution::sql_compiled_command_cache_miss(),
957 SqlCompilePhaseAttribution {
958 cache_key: cache_key_local_instructions,
959 cache_lookup: cache_lookup_local_instructions,
960 parse: parse_local_instructions,
961 parse_tokenize: parse_attribution.tokenize,
962 parse_select: parse_select_local_instructions,
963 parse_expr: parse_attribution.expr,
964 parse_predicate: parse_attribution.predicate,
965 aggregate_lane_check: aggregate_lane_check_local_instructions,
966 prepare: prepare_local_instructions,
967 lower: lower_local_instructions,
968 bind: bind_local_instructions,
969 cache_insert: cache_insert_local_instructions,
970 },
971 ))
972 }
973}