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