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")]
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 AcceptedEntityDescriptionMetadata, ConstraintValidationJob,
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
31#[cfg(feature = "migration")]
32use crate::db::{SchemaMigrationCommand, SchemaMigrationStatusPage, SchemaMigrationStatusRequest};
33
34impl<C: CanisterKind> DbSession<C> {
35 #[cfg(feature = "migration")]
38 pub fn defer_generated_schema_application_for_prepared_migration(
39 &self,
40 proposal: &SchemaProposal,
41 ) -> Result<bool, InternalError> {
42 crate::db::schema::defer_generated_schema_application_for_prepared_migration(
43 &self.db, proposal,
44 )
45 }
46
47 #[cfg(feature = "migration")]
49 pub fn migrate_schema(
50 &self,
51 proposal: &SchemaProposal,
52 command: SchemaMigrationCommand,
53 ) -> Result<SchemaMigrationStatusPage, InternalError> {
54 crate::db::schema::migrate_schema(&self.db, proposal, command)
55 }
56
57 #[cfg(feature = "migration")]
59 pub fn schema_migration_status(
60 &self,
61 proposal: &SchemaProposal,
62 request: &SchemaMigrationStatusRequest,
63 ) -> Result<SchemaMigrationStatusPage, InternalError> {
64 crate::db::schema::schema_migration_status(&self.db, proposal, request)
65 }
66
67 pub fn apply_schema(
70 &self,
71 proposal: &SchemaProposal,
72 ) -> Result<SchemaChangeReceipt, InternalError> {
73 crate::db::schema::apply_schema(&self.db, proposal)
74 }
75
76 pub fn schema_application_target(&self) -> Result<SchemaApplicationTarget, InternalError> {
79 crate::db::schema::schema_application_target(&self.db)
80 }
81
82 pub fn schema_application_receipt(
85 &self,
86 database_identity: TargetDatabaseIdentity,
87 submission_key: &SchemaSubmissionKey,
88 ) -> Result<Option<SchemaChangeReceipt>, InternalError> {
89 crate::db::schema::schema_application_receipt(&self.db, database_identity, submission_key)
90 }
91
92 pub fn continue_schema_application(
95 &self,
96 job_id: SchemaChangeJobId,
97 acknowledged_receipt: Option<u64>,
98 ) -> Result<SchemaChangeProgress, InternalError> {
99 crate::db::schema::continue_schema_application(&self.db, job_id, acknowledged_receipt)
100 }
101
102 pub fn abort_schema_application(
105 &self,
106 job_id: SchemaChangeJobId,
107 acknowledged_receipt: Option<u64>,
108 ) -> Result<SchemaChangeProgress, InternalError> {
109 crate::db::schema::abort_schema_application(&self.db, job_id, acknowledged_receipt)
110 }
111
112 #[cfg(feature = "sql")]
116 pub(in crate::db) fn show_indexes_for_store_schema_info(
117 &self,
118 store_path: &str,
119 schema: &SchemaInfo,
120 snapshot: &crate::db::schema::PersistedSchemaSnapshot,
121 ) -> Vec<String> {
122 let runtime_state = self
123 .db
124 .with_store_registry(|registry| registry.try_get_store(store_path).ok())
125 .map(|store| store.index_state());
126
127 show_indexes_for_schema_info_with_runtime_state(schema, snapshot, runtime_state)
128 }
129
130 pub fn show_entities(&self) -> Result<Vec<EntityCatalogDescription>, InternalError> {
132 let runtime_entities = self.db.accepted_runtime_entities()?;
133 let mut entities = Vec::with_capacity(runtime_entities.len());
134
135 for runtime_entity in runtime_entities {
136 let store = self.db.recovered_store(runtime_entity.store_path())?;
137 let storage = store
138 .storage_capabilities()
139 .storage_mode()
140 .as_str()
141 .to_string();
142 let accepted = self.accepted_schema_catalog_context_for_runtime_entity(
143 runtime_entity.clone(),
144 store,
145 )?;
146 let snapshot = accepted.snapshot().persisted_snapshot();
147
148 entities.push(EntityCatalogDescription::new(
149 snapshot.entity_name().to_string(),
150 snapshot.entity_path().to_string(),
151 runtime_entity.store_path().to_string(),
152 storage,
153 EntityCatalogCounts::new(
154 u32::try_from(snapshot.fields().len()).unwrap_or(u32::MAX),
155 u32::try_from(snapshot.indexes().len()).unwrap_or(u32::MAX),
156 u32::try_from(snapshot.relations().len()).unwrap_or(u32::MAX),
157 snapshot.version().get(),
158 ),
159 ));
160 }
161
162 Ok(entities)
163 }
164
165 #[must_use]
167 pub fn show_stores(&self) -> Vec<StoreCatalogDescription> {
168 self.db.runtime_store_catalog()
169 }
170
171 #[must_use]
173 pub fn show_memory(&self) -> Vec<crate::db::MemoryCatalogDescription> {
174 self.db.runtime_memory_catalog()
175 }
176
177 #[cfg(feature = "sql")]
180 pub(in crate::db::session) fn visible_indexes_for_store_accepted_schema(
181 &self,
182 store_path: &str,
183 schema_info: &SchemaInfo,
184 ) -> Result<VisibleIndexes, QueryError> {
185 let store = self
188 .db
189 .recovered_store(store_path)
190 .map_err(QueryError::execute)?;
191 let state = store.index_state();
192 if state != IndexState::Ready {
193 return Ok(VisibleIndexes::none());
194 }
195 debug_assert_eq!(state, IndexState::Ready);
196
197 let visible_indexes = VisibleIndexes::accepted_schema_visible(schema_info);
200 debug_assert!(visible_indexes.accepted_field_path_contracts_are_consistent());
201 debug_assert!(visible_indexes.accepted_expression_contracts_are_consistent());
202 debug_assert_eq!(
203 visible_indexes.accepted_expression_index_count(),
204 Some(visible_indexes.accepted_expression_indexes().len()),
205 );
206
207 Ok(visible_indexes)
208 }
209
210 pub fn try_describe_entity_by_source_key(
212 &self,
213 entity_source: &str,
214 ) -> Result<EntitySchemaDescription, InternalError> {
215 let catalog = self.accepted_schema_catalog_context_for_entity_source_key(entity_source)?;
216 self.describe_accepted_catalog(&catalog)
217 }
218
219 pub fn try_describe_entity_by_name(
221 &self,
222 entity: &str,
223 ) -> Result<EntitySchemaDescription, InternalError> {
224 let catalog = self.accepted_schema_catalog_context_for_entity_name(Some(entity))?;
225 self.describe_accepted_catalog(&catalog)
226 }
227
228 fn describe_accepted_catalog(
229 &self,
230 catalog: &crate::db::session::AcceptedSchemaCatalogContext,
231 ) -> Result<EntitySchemaDescription, InternalError> {
232 let validation_jobs = self.constraint_validation_jobs_for_accepted_catalog(catalog)?;
233 let identity = self.identity_description_for_accepted_catalog(catalog)?;
234
235 describe_accepted_entity_with_persisted_schema(
236 catalog.snapshot(),
237 catalog.value_catalog_handle(),
238 validation_jobs.as_slice(),
239 AcceptedEntityDescriptionMetadata::new(
240 identity,
241 catalog.identity().entity_tag().value(),
242 catalog.fingerprint_method_version(),
243 catalog.fingerprint(),
244 ),
245 |target_path| catalog.relation_target_description(target_path),
246 )
247 }
248
249 pub(in crate::db::session) fn identity_description_for_accepted_catalog(
250 &self,
251 catalog: &crate::db::session::AcceptedSchemaCatalogContext,
252 ) -> Result<Option<EntityIdentityDescription>, InternalError> {
253 let Some(identity) = catalog.inspection_plan().identity_inspection() else {
254 return Ok(None);
255 };
256 let catalog_identity = catalog.identity();
257 let store = self.db.recovered_store(catalog_identity.store_path())?;
258 let incarnation = database_incarnation_id()?;
259 let high_water = store.with_schema(|schema_store| {
260 schema_store.identity_high_water_for_integrity(
261 incarnation,
262 catalog_identity.entity_tag(),
263 identity.field_id(),
264 identity.accepted_kind(),
265 )
266 })?;
267 describe_accepted_identity(identity, high_water).map(Some)
268 }
269
270 pub(in crate::db::session) fn constraint_validation_jobs_for_accepted_catalog(
271 &self,
272 catalog: &crate::db::session::AcceptedSchemaCatalogContext,
273 ) -> Result<Vec<ConstraintValidationJob>, InternalError> {
274 let identity = catalog.inspection_plan().identity();
275 let store = self.db.recovered_store(identity.store_path())?;
276 store.with_schema(|schema_store| {
277 let jobs = catalog
278 .snapshot()
279 .persisted_snapshot()
280 .constraint_activations()
281 .iter()
282 .map(|activation| {
283 schema_store.constraint_validation_job(identity.entity_tag(), activation.id())
284 })
285 .collect::<Result<Vec<_>, InternalError>>()?;
286 jobs.into_iter()
287 .flatten()
288 .map(|job| {
289 if job.entity_tag() != identity.entity_tag()
290 || job.entity_path() != catalog.snapshot().entity_path()
291 {
292 return Err(InternalError::store_invariant());
293 }
294 Ok(job)
295 })
296 .collect()
297 })
298 }
299
300 pub fn storage_report(
302 &self,
303 name_to_path: &[(&'static str, &'static str)],
304 ) -> Result<StorageReport, InternalError> {
305 self.db.storage_report(name_to_path)
306 }
307
308 pub fn storage_report_default(&self) -> Result<StorageReport, InternalError> {
310 self.db.storage_report_default()
311 }
312}