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