icydb_core/db/session/query/
exact_count.rs1use crate::{
7 db::{
8 DbSession, DynamicQuery, DynamicTypedEntityBinding, QueryError,
9 access::{
10 MAX_INDEX_BRANCH_SET_VALUES,
11 lower_exact_user_index_prefix_cardinality_keys_for_prefix_access,
12 },
13 executor::{
14 EntityAuthority, ExactCardinalityTarget, execute_exact_cardinality_for_canister,
15 },
16 index::UserIndexPrefixCardinalityKey,
17 query::{
18 expr::{CompareOperator, FilterExpr, SetOperator},
19 intent::StructuralQuery,
20 plan::VisibleIndexes,
21 },
22 schema::SchemaInfo,
23 session::AcceptedSchemaCatalogContext,
24 },
25 traits::CanisterKind,
26};
27use icydb_diagnostic_code::DiagnosticExecutionLane;
28
29enum ExactCountPlan {
30 Entity,
31 UserIndexPrefixes(Vec<UserIndexPrefixCardinalityKey>),
32}
33
34pub(in crate::db::session) fn exact_count_cardinality_prefix_keys_for_accepted_authority(
35 authority: &EntityAuthority,
36 query: &StructuralQuery,
37 visible_indexes: &VisibleIndexes,
38 schema_info: &SchemaInfo,
39) -> Result<Option<Vec<UserIndexPrefixCardinalityKey>>, QueryError> {
40 let Some(access) = query
41 .try_build_count_cardinality_prefix_access_with_schema_info(visible_indexes, schema_info)?
42 else {
43 return Ok(None);
44 };
45 let prefix_keys = lower_exact_user_index_prefix_cardinality_keys_for_prefix_access(
46 authority.entity_tag(),
47 &access,
48 schema_info,
49 )
50 .map_err(|_err| QueryError::invariant())?;
51
52 Ok((!prefix_keys.is_empty()).then_some(prefix_keys))
53}
54
55impl<C: CanisterKind> DbSession<C> {
56 fn exact_count_request_is_scalar_metadata_shape(request: &DynamicQuery) -> bool {
57 if !request.order_terms().is_empty()
58 || !request.selected_fields().is_empty()
59 || request.row_limit().is_some()
60 || request.has_grouping()
61 || request.grouped_execution_limits().is_some()
62 || request.continuation_cursor().is_some()
63 {
64 return false;
65 }
66 #[cfg(test)]
67 if request.projection_is_distinct() {
68 return false;
69 }
70
71 true
72 }
73
74 fn exact_count_plan_against_catalog(
75 &self,
76 request: &DynamicQuery,
77 catalog: &AcceptedSchemaCatalogContext,
78 ) -> Result<ExactCountPlan, QueryError> {
79 if !Self::exact_count_request_is_scalar_metadata_shape(request) {
80 return Err(QueryError::unsupported_query());
81 }
82 match request.filter_expr() {
83 None => return Ok(ExactCountPlan::Entity),
84 Some(FilterExpr::Compare {
85 operator: CompareOperator::Eq,
86 ..
87 }) => {}
88 Some(FilterExpr::Set {
89 operator: SetOperator::In,
90 values,
91 ..
92 }) if !values.is_empty() && values.len() <= MAX_INDEX_BRANCH_SET_VALUES => {}
93 Some(_) => return Err(QueryError::unsupported_query()),
94 }
95
96 let query = Self::structural_query_from_dynamic_request(request, catalog)?;
97 let schema_info = catalog.accepted_schema_info();
98 let authority = catalog.accepted_entity_authority();
99 let visible_indexes =
100 self.visible_indexes_for_store_accepted_schema(authority.store_path(), schema_info)?;
101 let prefix_keys = exact_count_cardinality_prefix_keys_for_accepted_authority(
102 &authority,
103 &query,
104 &visible_indexes,
105 schema_info,
106 )?
107 .ok_or_else(QueryError::unsupported_query)?;
108
109 Ok(ExactCountPlan::UserIndexPrefixes(prefix_keys))
110 }
111
112 fn execute_exact_count_against_catalog(
113 &self,
114 request: &DynamicQuery,
115 catalog: AcceptedSchemaCatalogContext,
116 ) -> Result<u64, QueryError> {
117 let plan = self.exact_count_plan_against_catalog(request, &catalog)?;
118 let authority = catalog.accepted_entity_authority();
119 let target = match &plan {
120 ExactCountPlan::Entity => ExactCardinalityTarget::Entity,
121 ExactCountPlan::UserIndexPrefixes(prefix_keys) => {
122 ExactCardinalityTarget::UserIndexPrefixes(prefix_keys)
123 }
124 };
125 execute_exact_cardinality_for_canister(
126 &self.db,
127 authority,
128 DiagnosticExecutionLane::PublicRead,
129 target,
130 )
131 .map_err(QueryError::execute)?
132 .ok_or_else(QueryError::unsupported_query)
133 }
134
135 pub fn execute_public_exact_count(&self, request: &DynamicQuery) -> Result<u64, QueryError> {
142 let catalog = self
143 .accepted_schema_catalog_context_for_entity_name(Some(request.entity()))
144 .map_err(QueryError::execute)?;
145 self.execute_exact_count_against_catalog(request, catalog)
146 }
147
148 #[doc(hidden)]
151 pub fn execute_public_exact_count_for_typed_binding(
152 &self,
153 binding: &DynamicTypedEntityBinding,
154 request: &DynamicQuery,
155 ) -> Result<Option<u64>, QueryError> {
156 let Some(catalog) = self
157 .current_typed_entity_binding_catalog(binding)
158 .map_err(QueryError::execute)?
159 else {
160 return Ok(None);
161 };
162 self.execute_exact_count_against_catalog(request, catalog)
163 .map(Some)
164 }
165}