1#[cfg(feature = "sql")]
10use crate::db::schema::SchemaInfo;
11#[cfg(feature = "sql")]
12use crate::db::schema::show_indexes_for_schema_info_with_runtime_state;
13#[cfg(feature = "sql-explain")]
14use crate::db::{IndexState, QueryError, query::plan::VisibleIndexes};
15use crate::{
16 db::{
17 DbSession, EntityCatalogCounts, EntityCatalogDescription, EntityIdentityDescription,
18 EntitySchemaDescription, SchemaApplicationTarget, SchemaChangeJobId, SchemaChangeProgress,
19 SchemaChangeReceipt, StorageReport, StoreCatalogDescription,
20 commit::database_incarnation_id,
21 schema::{
22 AcceptedFieldKind, ConstraintValidationJob, PersistedFieldSnapshot,
23 describe_accepted_entity_with_persisted_schema, describe_accepted_identity,
24 },
25 },
26 error::InternalError,
27 traits::CanisterKind,
28};
29use icydb_schema::{SchemaProposal, SchemaSubmissionKey, TargetDatabaseIdentity};
30
31fn relation_field_count(fields: &[PersistedFieldSnapshot]) -> usize {
32 fields
33 .iter()
34 .filter(|field| persisted_kind_is_relation_field(field.kind()))
35 .count()
36}
37
38fn persisted_kind_is_relation_field(kind: &AcceptedFieldKind) -> bool {
39 match kind {
40 AcceptedFieldKind::Relation { .. } => true,
41 AcceptedFieldKind::List(inner) | AcceptedFieldKind::Set(inner) => {
42 matches!(inner.as_ref(), AcceptedFieldKind::Relation { .. })
43 }
44 _ => false,
45 }
46}
47
48impl<C: CanisterKind> DbSession<C> {
49 pub fn apply_schema(
52 &self,
53 proposal: &SchemaProposal,
54 ) -> Result<SchemaChangeReceipt, InternalError> {
55 crate::db::schema::apply_schema(&self.db, proposal)
56 }
57
58 pub fn schema_application_target(&self) -> Result<SchemaApplicationTarget, InternalError> {
61 crate::db::schema::schema_application_target(&self.db)
62 }
63
64 pub fn schema_application_receipt(
67 &self,
68 database_identity: TargetDatabaseIdentity,
69 submission_key: &SchemaSubmissionKey,
70 ) -> Result<Option<SchemaChangeReceipt>, InternalError> {
71 crate::db::schema::schema_application_receipt(&self.db, database_identity, submission_key)
72 }
73
74 pub fn continue_schema_application(
77 &self,
78 job_id: SchemaChangeJobId,
79 acknowledged_receipt: Option<u64>,
80 ) -> Result<SchemaChangeProgress, InternalError> {
81 crate::db::schema::continue_schema_application(&self.db, job_id, acknowledged_receipt)
82 }
83
84 pub fn abort_schema_application(
87 &self,
88 job_id: SchemaChangeJobId,
89 acknowledged_receipt: Option<u64>,
90 ) -> Result<SchemaChangeProgress, InternalError> {
91 crate::db::schema::abort_schema_application(&self.db, job_id, acknowledged_receipt)
92 }
93
94 #[cfg(feature = "sql")]
98 pub(in crate::db) fn show_indexes_for_store_schema_info(
99 &self,
100 store_path: &str,
101 schema: &SchemaInfo,
102 snapshot: &crate::db::schema::PersistedSchemaSnapshot,
103 ) -> Vec<String> {
104 let runtime_state = self
105 .db
106 .with_store_registry(|registry| registry.try_get_store(store_path).ok())
107 .map(|store| store.index_state());
108
109 show_indexes_for_schema_info_with_runtime_state(schema, snapshot, runtime_state)
110 }
111
112 pub fn show_entities(&self) -> Result<Vec<EntityCatalogDescription>, InternalError> {
114 let runtime_entities = self.db.accepted_runtime_entities()?;
115 let mut entities = Vec::with_capacity(runtime_entities.len());
116
117 for runtime_entity in runtime_entities {
118 let store = self.db.recovered_store(runtime_entity.store_path())?;
119 let storage = store
120 .storage_capabilities()
121 .storage_mode()
122 .as_str()
123 .to_string();
124 let accepted = self.accepted_schema_catalog_context_for_runtime_entity(
125 runtime_entity.clone(),
126 store,
127 )?;
128 let snapshot = accepted.snapshot().persisted_snapshot();
129
130 entities.push(EntityCatalogDescription::new(
131 snapshot.entity_name().to_string(),
132 snapshot.entity_path().to_string(),
133 runtime_entity.store_path().to_string(),
134 storage,
135 EntityCatalogCounts::new(
136 u32::try_from(snapshot.fields().len()).unwrap_or(u32::MAX),
137 u32::try_from(snapshot.indexes().len()).unwrap_or(u32::MAX),
138 u32::try_from(relation_field_count(snapshot.fields())).unwrap_or(u32::MAX),
139 snapshot.version().get(),
140 ),
141 ));
142 }
143
144 Ok(entities)
145 }
146
147 #[must_use]
149 pub fn show_stores(&self) -> Vec<StoreCatalogDescription> {
150 self.db.runtime_store_catalog()
151 }
152
153 #[must_use]
155 pub fn show_memory(&self) -> Vec<crate::db::MemoryCatalogDescription> {
156 self.db.runtime_memory_catalog()
157 }
158
159 #[cfg(feature = "sql-explain")]
162 pub(in crate::db::session) fn visible_indexes_for_store_accepted_schema(
163 &self,
164 store_path: &str,
165 schema_info: &SchemaInfo,
166 ) -> Result<VisibleIndexes, QueryError> {
167 let store = self
170 .db
171 .recovered_store(store_path)
172 .map_err(QueryError::execute)?;
173 let state = store.index_state();
174 if state != IndexState::Ready {
175 return Ok(VisibleIndexes::none());
176 }
177 debug_assert_eq!(state, IndexState::Ready);
178
179 let visible_indexes = VisibleIndexes::accepted_schema_visible(schema_info);
182 debug_assert!(visible_indexes.accepted_field_path_contracts_are_consistent());
183 debug_assert!(visible_indexes.accepted_expression_contracts_are_consistent());
184 debug_assert_eq!(
185 visible_indexes.accepted_expression_index_count(),
186 Some(visible_indexes.accepted_expression_indexes().len()),
187 );
188
189 Ok(visible_indexes)
190 }
191
192 pub fn try_describe_entity_by_source_key(
194 &self,
195 entity_source: &str,
196 ) -> Result<EntitySchemaDescription, InternalError> {
197 let catalog = self.accepted_schema_catalog_context_for_entity_source_key(entity_source)?;
198 self.describe_accepted_catalog(&catalog)
199 }
200
201 pub fn try_describe_entity_by_name(
203 &self,
204 entity: &str,
205 ) -> Result<EntitySchemaDescription, InternalError> {
206 let catalog = self.accepted_schema_catalog_context_for_entity_name(Some(entity))?;
207 self.describe_accepted_catalog(&catalog)
208 }
209
210 fn describe_accepted_catalog(
211 &self,
212 catalog: &crate::db::session::AcceptedSchemaCatalogContext,
213 ) -> Result<EntitySchemaDescription, InternalError> {
214 let validation_jobs = self.constraint_validation_jobs_for_accepted_catalog(catalog)?;
215 let identity = self.identity_description_for_accepted_catalog(catalog)?;
216
217 describe_accepted_entity_with_persisted_schema(
218 catalog.snapshot(),
219 catalog.value_catalog_handle(),
220 validation_jobs.as_slice(),
221 identity,
222 )
223 }
224
225 pub(in crate::db::session) fn identity_description_for_accepted_catalog(
226 &self,
227 catalog: &crate::db::session::AcceptedSchemaCatalogContext,
228 ) -> Result<Option<EntityIdentityDescription>, InternalError> {
229 let Some(identity) = catalog.inspection_plan().identity_inspection() else {
230 return Ok(None);
231 };
232 let catalog_identity = catalog.identity();
233 let store = self.db.recovered_store(catalog_identity.store_path())?;
234 let incarnation = database_incarnation_id()?;
235 let high_water = store.with_schema(|schema_store| {
236 schema_store.identity_high_water_for_integrity(
237 incarnation,
238 catalog_identity.entity_tag(),
239 identity.field_id(),
240 identity.accepted_kind(),
241 )
242 })?;
243 describe_accepted_identity(identity, high_water).map(Some)
244 }
245
246 pub(in crate::db::session) fn constraint_validation_jobs_for_accepted_catalog(
247 &self,
248 catalog: &crate::db::session::AcceptedSchemaCatalogContext,
249 ) -> Result<Vec<ConstraintValidationJob>, InternalError> {
250 let identity = catalog.inspection_plan().identity();
251 let store = self.db.recovered_store(identity.store_path())?;
252 store.with_schema(|schema_store| {
253 let jobs = catalog
254 .snapshot()
255 .persisted_snapshot()
256 .constraint_activations()
257 .iter()
258 .map(|activation| {
259 schema_store.constraint_validation_job(identity.entity_tag(), activation.id())
260 })
261 .collect::<Result<Vec<_>, InternalError>>()?;
262 jobs.into_iter()
263 .flatten()
264 .map(|job| {
265 if job.entity_tag() != identity.entity_tag()
266 || job.entity_path() != catalog.snapshot().entity_path()
267 {
268 return Err(InternalError::store_invariant());
269 }
270 Ok(job)
271 })
272 .collect()
273 })
274 }
275
276 pub fn storage_report(
278 &self,
279 name_to_path: &[(&'static str, &'static str)],
280 ) -> Result<StorageReport, InternalError> {
281 self.db.storage_report(name_to_path)
282 }
283
284 pub fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
286 self.db.storage_report_default()
287 }
288}