use indexmap::IndexMap;
use crate::ast::SchemaDocument;
use crate::catalog::{Catalog, CatalogColumn, CatalogRelation, CatalogType, RelationKind};
use crate::error::HolocronError;
use crate::span::Span;
pub fn build_catalog(document: &SchemaDocument) -> Result<Catalog, Vec<HolocronError>> {
let mut errors = Vec::new();
let enums = collect_enums(document, &mut errors);
let mut catalog = Catalog::from_enums(enums);
add_tables(&mut catalog, document, &mut errors);
if errors.is_empty() {
Ok(catalog)
} else {
Err(errors)
}
}
fn collect_enums(
document: &SchemaDocument,
errors: &mut Vec<HolocronError>,
) -> IndexMap<String, Vec<String>> {
let mut enums = IndexMap::new();
let mut first_spans: IndexMap<String, Span> = IndexMap::new();
for declared in &document.types {
let name = declared.name.value.clone();
if let Some(&first_span) = first_spans.get(&name) {
errors.push(HolocronError::duplicate_enum(
name,
first_span,
declared.name.span,
));
continue;
}
let values: Vec<String> = declared
.r#enum
.iter()
.map(|value| value.value.clone())
.collect();
first_spans.insert(name.clone(), declared.name.span);
enums.insert(name, values);
}
enums
}
fn add_tables(catalog: &mut Catalog, document: &SchemaDocument, errors: &mut Vec<HolocronError>) {
for table in &document.tables {
let mut columns = Vec::with_capacity(table.columns.len());
for (name, column) in &table.columns {
match resolve_type(&column.r#type.value, &catalog.enums) {
Some(data_type) => columns.push(CatalogColumn {
name: name.clone(),
data_type,
nullable: column.null,
filterable: true,
searchable: false,
}),
None => errors.push(HolocronError::unknown_type(
table.name.value.clone(),
name,
column.r#type.value.clone(),
column.r#type.span,
)),
}
}
let relation = CatalogRelation {
name: table.name.value.clone(),
kind: RelationKind::Table,
columns,
};
if let Err(error) = catalog.insert_relation(relation, table.name.span) {
errors.push(error);
}
}
}
fn resolve_type(name: &str, enums: &IndexMap<String, Vec<String>>) -> Option<CatalogType> {
CatalogType::from_sql_name(name).or_else(|| {
enums
.contains_key(name)
.then(|| CatalogType::Enum(name.to_string()))
})
}