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