use clap::{Parser, Subcommand};
use oxidelake_core::BackendKind;
use oxidelake_runtime::{OxideSession, dashboard};
use oxidelake_storage::{Compression, demo_write_options, write_demo_table};
use tracing_subscriber::EnvFilter;
#[derive(Parser)]
#[command(
name = "oxide",
version,
about = "OxideLake: GPU-accelerated, Arrow-native query engine"
)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
GenData {
#[arg(long, default_value_t = 1_000_000)]
rows: u64,
#[arg(long)]
out: String,
#[arg(long, default_value_t = 42)]
seed: u64,
#[arg(long, default_value_t = 65_536)]
row_group_rows: usize,
#[arg(long, default_value = "zstd")]
compression: Compression,
},
Sql {
#[arg(short, long)]
query: String,
#[arg(short, long = "table", value_name = "NAME=PATH")]
tables: Vec<String>,
#[arg(long, value_name = "URL")]
cluster: Option<String>,
#[arg(long, value_name = "BACKEND")]
target: Option<BackendKind>,
},
Explain {
#[arg(short, long)]
query: String,
#[arg(short, long = "table", value_name = "NAME=PATH")]
tables: Vec<String>,
#[arg(long, value_name = "BACKEND")]
target: Option<BackendKind>,
},
Tui {
#[arg(short, long)]
query: Option<String>,
#[arg(short, long = "table", value_name = "NAME=PATH")]
tables: Vec<String>,
#[arg(long, value_name = "BACKEND")]
target: Option<BackendKind>,
},
}
fn parse_tables(tables: &[String]) -> anyhow::Result<Vec<(&str, &str)>> {
tables
.iter()
.map(|spec| {
spec.split_once('=')
.ok_or_else(|| anyhow::anyhow!("--table expects NAME=PATH, got '{spec}'"))
})
.collect()
}
async fn session(
cluster: Option<&str>,
target: Option<BackendKind>,
tables: &[String],
) -> anyhow::Result<OxideSession> {
let session = match (cluster, target) {
(Some(_), Some(_)) => anyhow::bail!(
"--target picks the embedded placement target; on a cluster the \
scheduler's OXIDE_CLUSTER_BACKEND decides placement"
),
(Some(url), None) => OxideSession::connect(url).await?,
(None, Some(target)) => OxideSession::local_with_target(target)?,
(None, None) => OxideSession::local()?,
};
for (name, path) in parse_tables(tables)? {
session.register_parquet(name, path).await?;
}
Ok(session)
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.with_writer(std::io::stderr)
.init();
let cli = Cli::parse();
match cli.command {
Command::GenData {
rows,
out,
seed,
row_group_rows,
compression,
} => {
let options = demo_write_options(row_group_rows, compression);
let table = write_demo_table(std::path::Path::new(&out), rows, seed, &options)?;
println!(
"wrote {} rows to {} (seed {seed}, {row_group_rows} rows per row group, {})",
table.rows,
table.path.display(),
compression.as_str(),
);
}
Command::Sql {
query,
tables,
cluster,
target,
} => {
let session = session(cluster.as_deref(), target, &tables).await?;
session.sql(&query).await?.show().await?;
}
Command::Explain {
query,
tables,
target,
} => {
let session = session(None, target, &tables).await?;
print!("{}", session.explain(&query).await?);
}
Command::Tui {
query,
tables,
target,
} => {
let model = match &query {
Some(sql) => {
let session = session(None, target, &tables).await?;
let names: Vec<String> = parse_tables(&tables)?
.into_iter()
.map(|(name, _)| name.to_owned())
.collect();
dashboard::query_dashboard(&session, sql, &names).await?
}
None => oxidelake_tui::demo_model(),
};
oxidelake_tui::run_terminal(model, None, |_| {})?;
}
}
Ok(())
}