diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 5aad741..4a6f090 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -2,3 +2,4 @@ pub mod parse; pub mod sql_test; +pub mod transpile; diff --git a/src/cmd/transpile.rs b/src/cmd/transpile.rs new file mode 100644 index 0000000..c5b2ae2 --- /dev/null +++ b/src/cmd/transpile.rs @@ -0,0 +1,50 @@ +//! Transpile code to another dialect of SQL. + +use std::path::PathBuf; + +use clap::Parser; +use tracing::instrument; + +use crate::{ + ast::{parse_sql, Emit}, + drivers, + errors::{Context, Result}, +}; + +/// Run SQL tests from a directory. +#[derive(Debug, Parser)] +pub struct TranspileOpt { + /// An SQL file to transpile. + sql_path: PathBuf, + + /// A database locator to run tests against. (For now, this must be a + /// an actual database locator, and not the name of a dialect.) + #[clap(long, visible_alias = "db", default_value = "sqlite3::memory:")] + database: String, +} + +/// Run our SQL test suite. +#[instrument(skip(opt))] +pub async fn cmd_transpile(opt: &TranspileOpt) -> Result<()> { + // Get a database driver for our target. + let locator = opt.database.parse::>()?; + let driver = locator.driver().await?; + + // Parse our SQL. + let sql = tokio::fs::read_to_string(&opt.sql_path) + .await + .with_context(|| format!("could not read SQL file {}", opt.sql_path.display()))?; + let ast = parse_sql(&opt.sql_path, &sql)?; + let rewritten_ast = driver.rewrite_ast(&ast)?; + + // Print our rewritten AST. + for statement in rewritten_ast.extra.native_setup_sql { + println!("{};", statement); + } + let transpiled_sql = rewritten_ast.ast.emit_to_string(locator.target()); + println!("{}", transpiled_sql); + for statement in rewritten_ast.extra.native_teardown_sql { + println!("{};", statement); + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index be1bc6e..c6a5ddd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ mod util; use cmd::{ parse::{cmd_parse, ParseOpt}, sql_test::{cmd_sql_test, SqlTestOpt}, + transpile::{cmd_transpile, TranspileOpt}, }; use tracing::info_span; @@ -23,6 +24,8 @@ enum Opt { Parse(ParseOpt), /// Run SQL tests from a directory. SqlTest(SqlTestOpt), + /// Transpile BigQuery SQL to another dialect. + Transpile(TranspileOpt), } #[tokio::main] @@ -35,6 +38,7 @@ async fn main() { let result = match opt { Opt::Parse(parse_opt) => cmd_parse(&parse_opt), Opt::SqlTest(sql_test_opt) => cmd_sql_test(&sql_test_opt).await, + Opt::Transpile(transpile_opt) => cmd_transpile(&transpile_opt).await, }; if let Err(e) = result { e.emit();