pub mod dialect;
pub mod error;
pub mod transpiler;
pub(crate) mod transforms;
pub use dialect::{SourceDialect, TargetDialect};
pub use error::{Error, Result};
pub use transpiler::Transpiler;
use std::fmt;
use std::sync::Arc;
use sqlparser::ast::Statement;
use sqlparser::dialect::{GenericDialect, HiveDialect, RedshiftSqlDialect};
use sqlparser::parser::Parser;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ExternalTableBehavior {
MapToView,
#[default]
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum IcebergTableBehavior {
MapToView,
#[default]
Error,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CopyBehavior {
MapToInsert,
#[default]
Error,
}
#[derive(Clone)]
#[allow(clippy::type_complexity)]
pub struct SerdeClassResolver(Arc<dyn Fn(&str) -> Option<String> + Send + Sync + 'static>);
impl SerdeClassResolver {
pub fn new(f: impl Fn(&str) -> Option<String> + Send + Sync + 'static) -> Self {
Self(Arc::new(f))
}
pub(crate) fn resolve(&self, class: &str) -> Option<String> {
(self.0)(class)
}
}
impl fmt::Debug for SerdeClassResolver {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SerdeClassResolver(<fn>)")
}
}
#[derive(Debug, Clone, Default)]
pub struct TranspileOptions {
pub target: TargetDialect,
pub external_table: ExternalTableBehavior,
pub iceberg_table: IcebergTableBehavior,
pub copy: CopyBehavior,
pub serde_class_resolver: Option<SerdeClassResolver>,
}
pub fn transpile(sql: &str, source: SourceDialect) -> Result<String> {
transpile_with_options(sql, source, &TranspileOptions::default())
}
pub fn transpile_with_options(
sql: &str,
source: SourceDialect,
opts: &TranspileOptions,
) -> Result<String> {
let statements = parse(sql, source)?;
let transpiler: Box<dyn transpiler::Transpiler> = match source {
SourceDialect::Trino => Box::new(dialect::TrinoDialect),
SourceDialect::Redshift => Box::new(dialect::RedshiftDialect),
SourceDialect::Hive => Box::new(dialect::HiveTranspileDialect),
};
let output: Result<Vec<String>> = statements
.into_iter()
.map(|stmt| {
let transformed = transpiler.transpile_statement(stmt, opts)?;
Ok(emit(&transformed))
})
.collect();
Ok(output?.join(";\n"))
}
fn parse(sql: &str, source: SourceDialect) -> Result<Vec<Statement>> {
let dialect: Box<dyn sqlparser::dialect::Dialect> = match source {
SourceDialect::Trino => Box::new(GenericDialect),
SourceDialect::Redshift => Box::new(RedshiftSqlDialect {}),
SourceDialect::Hive => Box::new(HiveDialect {}),
};
let stmts = Parser::parse_sql(&*dialect, sql)?;
Ok(stmts)
}
fn emit(stmt: &Statement) -> String {
stmt.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn passthrough_simple_select() {
let sql = "SELECT 1";
let result = transpile(sql, SourceDialect::Trino).unwrap();
assert_eq!(result, "SELECT 1");
}
#[test]
fn passthrough_select_from_table() {
let sql = "SELECT a, b FROM my_table WHERE a > 10";
let result = transpile(sql, SourceDialect::Redshift).unwrap();
assert_eq!(result, "SELECT a, b FROM my_table WHERE a > 10");
}
}