use anyhow::{Context, Result};
use sqlx::PgPool;
use std::path::Path;
use tracing::{debug, info};
use crate::catalog::Catalog;
use crate::catalog::file_dependencies::{
FileDependencyAugmentation, FileToObjectMapping, create_dependency_augmentation,
};
use crate::catalog::identity::{self, CatalogIdentity};
use crate::config::types::Objects;
use crate::db::cleaner;
use crate::db::schema_executor::SchemaFileExecutor;
use crate::schema_loader::{SchemaLoader, SchemaLoaderConfig};
use std::collections::BTreeMap;
#[derive(Debug, Clone)]
pub struct SchemaProcessorConfig {
pub verbose: bool,
pub clean_before_apply: bool,
pub objects: Objects,
}
impl Default for SchemaProcessorConfig {
fn default() -> Self {
Self {
verbose: false,
clean_before_apply: true,
objects: Objects::default(),
}
}
}
#[derive(Debug)]
pub struct ProcessedSchema {
pub catalog: Catalog,
pub augmentation: FileDependencyAugmentation,
pub file_mapping: FileToObjectMapping,
pub file_dependencies: BTreeMap<String, Vec<String>>,
}
impl ProcessedSchema {
pub fn with_file_dependencies_applied(self) -> Catalog {
self.catalog
.with_file_dependencies_augmented(self.augmentation)
}
}
pub struct SchemaProcessor {
pool: PgPool,
config: SchemaProcessorConfig,
}
impl SchemaProcessor {
pub fn new(pool: PgPool, config: SchemaProcessorConfig) -> Self {
Self { pool, config }
}
pub async fn process_schema_directory(&self, schema_dir: &Path) -> Result<ProcessedSchema> {
if self.config.clean_before_apply {
debug!("๐งน Cleaning database before applying schema...");
cleaner::clean_shadow_db(&self.pool, &self.config.objects)
.await
.context("Failed to clean database before applying schema")?;
}
debug!("๐ Loading schema files from: {}", schema_dir.display());
let loader = SchemaLoader::new(SchemaLoaderConfig::new(schema_dir.to_path_buf()));
let schema_files = loader.load_ordered_schema_files().with_context(|| {
format!(
"Failed to load and order schema files from directory: {}\n\n\
Common causes:\n\
โข Schema directory doesn't exist or is empty\n\
โข Circular dependencies between files (A requires B, B requires A)\n\
โข Missing dependency files referenced in '-- require:' headers\n\
โข Invalid file paths in dependency declarations",
schema_dir.display()
)
})?;
debug!(
"๐ Loaded {} schema files with dependency information",
schema_files.len()
);
let executor = SchemaFileExecutor::new(self.pool.clone(), self.config.verbose);
let mut file_mapping = FileToObjectMapping::new();
let mut previous_identity = CatalogIdentity::load(&self.pool)
.await
.context("Failed to load initial catalog identity")?;
debug!(
"Creating file-to-object mappings by applying {} schema files incrementally",
schema_files.len()
);
for (index, file) in schema_files.iter().enumerate() {
debug!(
" [{}/{}] Applying {} and taking snapshot",
index + 1,
schema_files.len(),
file.relative_path
);
executor.execute_schema_file(file).await?;
let current_identity = CatalogIdentity::load(&self.pool).await.with_context(|| {
format!(
"Failed to load catalog identity after applying {}",
file.relative_path
)
})?;
let new_objects = identity::find_new_objects(&previous_identity, ¤t_identity);
for object_id in new_objects {
file_mapping.add_object(file.relative_path.clone(), object_id);
}
previous_identity = current_identity;
}
debug!(
"File-to-object mapping complete: {} files mapped to {} total objects",
file_mapping.file_objects.len(),
file_mapping.object_files.len()
);
debug!("Loading full catalog for diff operations");
let final_catalog = Catalog::load(&self.pool)
.await
.context("Failed to load final catalog")?;
debug!("Creating file-based dependency augmentation");
let augmentation = create_dependency_augmentation(&file_mapping, &schema_files)
.context("Failed to create dependency augmentation from file mappings")?;
let file_dependencies: BTreeMap<String, Vec<String>> = schema_files
.iter()
.filter(|f| !f.dependencies.is_empty())
.map(|f| (f.relative_path.clone(), f.dependencies.clone()))
.collect();
info!(
"โ
Schema processing complete: {} files processed",
schema_files.len()
);
Ok(ProcessedSchema {
catalog: final_catalog,
augmentation,
file_mapping,
file_dependencies,
})
}
}