use std::path::{Path, PathBuf};
use sqlparser::{
ast::Statement,
dialect::GenericDialect,
parser::{Parser, ParserError},
};
use crate::source::SqlSource;
#[derive(Debug)]
pub struct ParsedSqlFile {
file: SqlSource,
statements: Vec<Statement>,
}
impl ParsedSqlFile {
pub fn parse(file: SqlSource) -> Result<Self, ParserError> {
let dialect = GenericDialect {};
let statements = Parser::parse_sql(&dialect, file.content())?;
Ok(Self { file, statements })
}
#[must_use]
pub const fn file(&self) -> &SqlSource {
&self.file
}
#[must_use]
pub fn path(&self) -> Option<&Path> {
self.file.path()
}
#[must_use]
pub fn path_into_path_buf(&self) -> Option<PathBuf> {
self.file.path_into_path_buf()
}
#[must_use]
pub fn content(&self) -> &str {
self.file.content()
}
#[must_use]
pub fn statements(&self) -> &[Statement] {
&self.statements
}
}
#[derive(Debug)]
pub struct ParsedSqlFileSet {
files: Vec<ParsedSqlFile>,
}
impl ParsedSqlFileSet {
pub fn parse_all(set: Vec<SqlSource>) -> Result<Self, ParserError> {
let files = set.into_iter().map(ParsedSqlFile::parse).collect::<Result<Vec<_>, _>>()?;
Ok(Self { files })
}
#[must_use]
pub fn files(&self) -> &[ParsedSqlFile] {
&self.files
}
}
#[cfg(test)]
mod tests {
use std::{env, fs};
use super::*;
use crate::source::SqlSource;
#[test]
fn parsed_sql_file_parses_single_statement() -> Result<(), Box<dyn std::error::Error>> {
let base = env::temp_dir().join("parsed_sql_file_single_stmt_test");
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(&base)?;
let file_path = base.join("one.sql");
let sql = "CREATE TABLE users (id INTEGER PRIMARY KEY);";
fs::write(&file_path, sql)?;
let sql_file = SqlSource::from_path(&file_path)?;
let parsed = ParsedSqlFile::parse(sql_file)?;
assert_eq!(parsed.path(), Some(file_path.as_path()));
assert_eq!(parsed.content(), sql);
assert_eq!(parsed.statements().len(), 1);
let _ = fs::remove_dir_all(&base);
Ok(())
}
#[test]
fn parsed_sql_file_set_parses_multiple_files() -> Result<(), Box<dyn std::error::Error>> {
let base = env::temp_dir().join("parsed_sql_file_set_multi_test");
let _ = fs::remove_dir_all(&base);
fs::create_dir_all(&base)?;
let sub = base.join("subdir");
fs::create_dir_all(&sub)?;
let file1 = base.join("one.sql");
let file2 = sub.join("two.sql");
let sql1 = "CREATE TABLE users (id INTEGER PRIMARY KEY);";
let sql2 = "CREATE TABLE posts (id INTEGER PRIMARY KEY);";
fs::write(&file1, sql1)?;
fs::write(&file2, sql2)?;
let set = SqlSource::sql_sources(&base, &[])?;
let parsed_set = ParsedSqlFileSet::parse_all(set)?;
let existing_files = parsed_set.files();
assert_eq!(existing_files.len(), 2);
for parsed in existing_files {
assert_eq!(parsed.statements().len(), 1);
let stmt = &parsed.statements()[0];
match stmt {
Statement::CreateTable { .. } => {}
other => panic!("expected CreateTable, got: {other:?}"),
}
}
let _ = fs::remove_dir_all(&base);
Ok(())
}
}