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