use systemprompt_extension::{ExtensionRegistry, LoaderError};
use systemprompt_identifiers::ExtensionId;
use super::super::prepare::prepare_extension_schema;
use crate::lifecycle::migrations::{
ChecksumDrift, MigrationService, OrphanedMigration, PendingMigration,
};
use crate::services::DatabaseProvider;
#[derive(Debug, Default)]
pub struct SchemaCurrency {
pub fresh_extensions: Vec<ExtensionId>,
pub pending: Vec<PendingMigration>,
pub drift: Vec<ChecksumDrift>,
pub ahead: Vec<OrphanedMigration>,
pub superseded_drift: Vec<ChecksumDrift>,
pub breaking: Vec<OrphanedMigration>,
}
impl SchemaCurrency {
#[must_use]
pub const fn is_current(&self) -> bool {
self.fresh_extensions.is_empty() && self.pending.is_empty() && self.drift.is_empty()
}
#[must_use]
pub const fn is_ahead(&self) -> bool {
!self.ahead.is_empty()
}
#[must_use]
pub const fn breaks_this_release(&self) -> bool {
!self.breaking.is_empty()
}
}
pub async fn schema_currency(
db: &dyn DatabaseProvider,
registry: &ExtensionRegistry,
disabled_extensions: &[ExtensionId],
) -> Result<SchemaCurrency, LoaderError> {
let migrations = MigrationService::new(db);
let mut currency = SchemaCurrency::default();
for ext in registry.enabled_schema_extensions(disabled_extensions)? {
let prepared = prepare_extension_schema(ext.as_ref())?;
let freshness = migrations
.assess_freshness(&prepared.extension_id, &prepared.owned_tables)
.await?;
if freshness.tables_total > 0 && freshness.tables_present == 0 {
currency.fresh_extensions.push(prepared.extension_id);
continue;
}
if ext.has_migrations() {
let latest = ext.migrations().iter().map(|m| m.version).max();
let status = migrations.status(ext.as_ref()).await?;
let ahead: Vec<OrphanedMigration> = status
.orphaned
.into_iter()
.filter(|m| latest.is_some_and(|latest| m.version > latest))
.collect();
currency.pending.extend(status.pending);
if ahead.is_empty() {
currency.drift.extend(status.drift);
} else {
let breaking = migrations.breaking_versions(&prepared.extension_id).await?;
currency.breaking.extend(
ahead
.iter()
.filter(|m| breaking.contains(&m.version))
.cloned(),
);
currency.superseded_drift.extend(status.drift);
currency.ahead.extend(ahead);
}
}
}
Ok(currency)
}