1#[cfg(any(test, feature = "sql-explain"))]
10use crate::db::{IndexState, QueryError, query::plan::VisibleIndexes};
11use crate::{
12 db::{
13 DbSession, EntityCatalogCounts, EntityCatalogDescription, EntityConstraintDescription,
14 EntityFieldDescription, EntitySchemaDescription, IntegrityReport, StorageReport,
15 StoreCatalogDescription,
16 schema::{
17 AcceptedFieldKind, ConstraintValidationJob, PersistedFieldSnapshot, SchemaInfo,
18 describe_entity_fields, describe_entity_fields_with_persisted_schema,
19 describe_entity_model, describe_entity_model_with_persisted_schema,
20 show_indexes_for_model, show_indexes_for_model_with_runtime_state,
21 show_indexes_for_schema_info_with_runtime_state,
22 },
23 },
24 entity::EntityKind,
25 error::InternalError,
26 model::entity::EntityModel,
27 traits::{CanisterKind, Path},
28};
29
30fn relation_field_count(fields: &[PersistedFieldSnapshot]) -> usize {
31 fields
32 .iter()
33 .filter(|field| persisted_kind_is_relation_field(field.kind()))
34 .count()
35}
36
37fn persisted_kind_is_relation_field(kind: &AcceptedFieldKind) -> bool {
38 match kind {
39 AcceptedFieldKind::Relation { .. } => true,
40 AcceptedFieldKind::List(inner) | AcceptedFieldKind::Set(inner) => {
41 matches!(inner.as_ref(), AcceptedFieldKind::Relation { .. })
42 }
43 _ => false,
44 }
45}
46
47impl<C: CanisterKind> DbSession<C> {
48 #[must_use]
55 pub fn show_indexes<E>(&self) -> Vec<String>
56 where
57 E: EntityKind<Canister = C>,
58 {
59 self.show_indexes_for_store_model(E::Store::PATH, E::MODEL)
60 }
61
62 #[must_use]
68 pub fn show_indexes_for_model(&self, model: &'static EntityModel) -> Vec<String> {
69 show_indexes_for_model(model)
70 }
71
72 pub fn try_show_indexes<E>(&self) -> Result<Vec<String>, InternalError>
77 where
78 E: EntityKind<Canister = C>,
79 {
80 let schema = self.accepted_schema_info_for_entity::<E>()?;
81
82 Ok(self.show_indexes_for_store_schema_info(E::Store::PATH, &schema))
83 }
84
85 pub fn try_show_constraints<E>(&self) -> Result<Vec<EntityConstraintDescription>, InternalError>
87 where
88 E: EntityKind<Canister = C>,
89 {
90 Ok(self.try_describe_entity::<E>()?.constraints().to_vec())
91 }
92
93 pub(in crate::db) fn show_indexes_for_store_model(
97 &self,
98 store_path: &str,
99 model: &'static EntityModel,
100 ) -> Vec<String> {
101 let runtime_state = self
102 .db
103 .with_store_registry(|registry| registry.try_get_store(store_path).ok())
104 .map(|store| store.index_state());
105
106 show_indexes_for_model_with_runtime_state(model, runtime_state)
107 }
108
109 pub(in crate::db) fn show_indexes_for_store_schema_info(
113 &self,
114 store_path: &str,
115 schema: &SchemaInfo,
116 ) -> Vec<String> {
117 let runtime_state = self
118 .db
119 .with_store_registry(|registry| registry.try_get_store(store_path).ok())
120 .map(|store| store.index_state());
121
122 show_indexes_for_schema_info_with_runtime_state(schema, runtime_state)
123 }
124
125 #[must_use]
131 pub fn show_columns<E>(&self) -> Vec<EntityFieldDescription>
132 where
133 E: EntityKind<Canister = C>,
134 {
135 self.show_columns_for_model(E::MODEL)
136 }
137
138 #[must_use]
140 pub fn show_columns_for_model(
141 &self,
142 model: &'static EntityModel,
143 ) -> Vec<EntityFieldDescription> {
144 describe_entity_fields(model)
145 }
146
147 pub fn try_show_columns<E>(&self) -> Result<Vec<EntityFieldDescription>, InternalError>
153 where
154 E: EntityKind<Canister = C>,
155 {
156 let catalog = self.accepted_schema_catalog_context_for_query::<E>()?;
157
158 describe_entity_fields_with_persisted_schema(
159 catalog.snapshot(),
160 catalog.value_catalog_handle(),
161 )
162 }
163
164 #[must_use]
171 pub fn show_entities(&self) -> Vec<crate::db::EntityCatalogDescription> {
172 self.try_show_entities().expect("session invariant")
173 }
174
175 pub fn try_show_entities(&self) -> Result<Vec<EntityCatalogDescription>, InternalError> {
177 let mut entities = Vec::with_capacity(self.db.entity_runtime_hooks.len());
178
179 for hooks in self.db.entity_runtime_hooks {
180 let store = self.db.recovered_store(hooks.store_path)?;
181 let storage = store
182 .storage_capabilities()
183 .storage_mode()
184 .as_str()
185 .to_string();
186 let accepted = self.accepted_schema_catalog_context_for_runtime_hook(hooks, store)?;
187 let snapshot = accepted.snapshot().persisted_snapshot();
188
189 entities.push(EntityCatalogDescription::new(
190 snapshot.entity_name().to_string(),
191 snapshot.entity_path().to_string(),
192 hooks.store_path.to_string(),
193 storage,
194 EntityCatalogCounts::new(
195 u32::try_from(snapshot.fields().len()).unwrap_or(u32::MAX),
196 u32::try_from(snapshot.indexes().len()).unwrap_or(u32::MAX),
197 u32::try_from(relation_field_count(snapshot.fields())).unwrap_or(u32::MAX),
198 snapshot.version().get(),
199 ),
200 ));
201 }
202
203 Ok(entities)
204 }
205
206 #[must_use]
208 pub fn show_stores(&self) -> Vec<StoreCatalogDescription> {
209 self.db.runtime_store_catalog()
210 }
211
212 #[must_use]
214 pub fn show_memory(&self) -> Vec<crate::db::MemoryCatalogDescription> {
215 self.db.runtime_memory_catalog()
216 }
217
218 #[cfg(any(test, feature = "sql-explain"))]
221 pub(in crate::db::session) fn visible_indexes_for_store_accepted_schema(
222 &self,
223 store_path: &str,
224 schema_info: &SchemaInfo,
225 ) -> Result<VisibleIndexes<'static>, QueryError> {
226 let store = self
229 .db
230 .recovered_store(store_path)
231 .map_err(QueryError::execute)?;
232 let state = store.index_state();
233 if state != IndexState::Ready {
234 return Ok(VisibleIndexes::none());
235 }
236 debug_assert_eq!(state, IndexState::Ready);
237
238 let visible_indexes = VisibleIndexes::accepted_schema_visible(schema_info);
241 debug_assert!(visible_indexes.accepted_field_path_contracts_are_consistent());
242 debug_assert!(visible_indexes.accepted_expression_contracts_are_consistent());
243 debug_assert_eq!(
244 visible_indexes.accepted_expression_index_count(),
245 Some(visible_indexes.accepted_expression_indexes().len()),
246 );
247
248 Ok(visible_indexes)
249 }
250
251 #[must_use]
257 pub fn describe_entity<E>(&self) -> EntitySchemaDescription
258 where
259 E: EntityKind<Canister = C>,
260 {
261 self.describe_entity_model(E::MODEL)
262 }
263
264 #[must_use]
266 pub fn describe_entity_model(&self, model: &'static EntityModel) -> EntitySchemaDescription {
267 describe_entity_model(model)
268 }
269
270 pub fn try_describe_entity<E>(&self) -> Result<EntitySchemaDescription, InternalError>
276 where
277 E: EntityKind<Canister = C>,
278 {
279 let catalog = self.accepted_schema_catalog_context_for_query::<E>()?;
280 let validation_jobs = self.constraint_validation_jobs_for_catalog::<E>(&catalog)?;
281
282 describe_entity_model_with_persisted_schema(
283 E::MODEL,
284 catalog.snapshot(),
285 catalog.value_catalog_handle(),
286 validation_jobs.as_slice(),
287 )
288 }
289
290 pub(in crate::db::session) fn constraint_validation_jobs_for_catalog<E>(
291 &self,
292 catalog: &crate::db::session::AcceptedSchemaCatalogContext,
293 ) -> Result<Vec<ConstraintValidationJob>, InternalError>
294 where
295 E: EntityKind<Canister = C>,
296 {
297 let store = self.db.recovered_store(E::Store::PATH)?;
298 store.with_schema(|schema_store| {
299 let jobs = catalog
300 .snapshot()
301 .persisted_snapshot()
302 .constraint_activations()
303 .iter()
304 .map(|activation| {
305 schema_store.constraint_validation_job(E::ENTITY_TAG, activation.id())
306 })
307 .collect::<Result<Vec<_>, InternalError>>()?;
308 jobs.into_iter()
309 .flatten()
310 .map(|job| {
311 if job.entity_tag() != E::ENTITY_TAG
312 || job.entity_path() != catalog.snapshot().entity_path()
313 {
314 return Err(InternalError::store_invariant());
315 }
316 Ok(job)
317 })
318 .collect()
319 })
320 }
321
322 pub fn storage_report(
324 &self,
325 name_to_path: &[(&'static str, &'static str)],
326 ) -> Result<StorageReport, InternalError> {
327 self.db.storage_report(name_to_path)
328 }
329
330 pub fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
332 self.db.storage_report_default()
333 }
334
335 pub fn integrity_report(&self) -> Result<IntegrityReport, InternalError> {
337 self.db.integrity_report()
338 }
339}