mod alter;
mod checks;
mod constraints;
mod enums;
mod names;
mod recover;
mod statements;
mod table;
#[cfg(test)]
mod tests;
mod triggers;
mod types;
use std::collections::{BTreeMap, HashMap};
use std::path::{Path, PathBuf};
use anyhow::Result;
use self::table::table_failure;
use super::diagnostics::{LastGood, TableFailure};
use crate::schema::{Schema, Table};
pub(crate) use self::names::{ident_name, object_leaf_name};
pub use self::statements::parse_sources_with_modules;
pub(crate) use self::types::map_type;
pub struct ParsedSchema {
pub schema: Schema,
pub failures: Vec<TableFailure>,
created: HashMap<String, (PathBuf, Option<usize>)>,
source_removed: HashMap<(String, Option<String>), StatementLocation>,
pub unmatched_modules: Vec<String>,
}
#[derive(Debug, Clone)]
struct StatementLocation {
order: usize,
path: PathBuf,
line: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct Rejection {
pub reason: String,
pub unsourced_column: Option<String>,
}
impl ParsedSchema {
pub fn fail_tables(&mut self, mut check: impl FnMut(&Table) -> Option<Rejection>) {
let mut failed = Vec::new();
self.schema.tables.retain(|table| {
let Some(rejection) = check(table) else {
return true;
};
failed.push((table.clone(), rejection));
false
});
for (table, rejection) in failed {
let (mut path, mut line) = self.created.remove(&table.module_name).unwrap_or_default();
if let Some(column) = &rejection.unsourced_column {
let removed = |column: Option<String>| {
self.source_removed
.get(&(table.module_name.clone(), column))
};
let latest = [removed(Some(column.clone())), removed(None)]
.into_iter()
.flatten()
.max_by_key(|location| location.order);
if let Some(location) = latest {
path.clone_from(&location.path);
line = location.line;
}
}
self.failures.push(TableFailure {
path: Some(path).filter(|path| !path.as_os_str().is_empty()),
line,
schema: table.schema.clone(),
..table_failure(&table.sql_name, rejection.reason)
});
if let Some(failure) = self.failures.last_mut() {
failure.last_good = Some(LastGood::new(table));
}
}
}
}
const GENERATED_MODULES: &[&str] = &["nullable", "schema", "search", "session", "types"];
pub struct SchemaSource<'a> {
pub path: &'a Path,
pub sql: &'a str,
}
pub fn parse_schema(sql: &str) -> Result<ParsedSchema> {
let mut parsed = parse_sources(&[SchemaSource {
path: Path::new(""),
sql,
}])?;
for failure in &mut parsed.failures {
failure.path = None;
}
Ok(parsed)
}
pub fn parse_sources(sources: &[SchemaSource<'_>]) -> Result<ParsedSchema> {
parse_sources_with_modules(sources, &BTreeMap::new())
}
pub fn names_table(key: &str, schema: Option<&str>, sql_name: &str) -> bool {
let schema = schema.unwrap_or("public");
key == sql_name
|| key
.strip_suffix(sql_name)
.and_then(|qualifier| qualifier.strip_suffix('.'))
.is_some_and(|qualifier| qualifier == schema)
}