#![deny(clippy::str_to_string)]
#![allow(deprecated)]
mod cli;
mod command;
mod helper;
mod print;
mod upgrade;
use {
crate::cli::Cli,
anyhow::Result,
clap::Parser,
gluesql_core::{
ast::{Expr, ToSql},
store::{GStore, GStoreMut, Planner, Store, Transaction},
},
gluesql_csv_storage::CsvStorage,
gluesql_file_storage::FileStorage,
gluesql_json_storage::JsonStorage,
gluesql_memory_storage::MemoryStorage,
gluesql_parquet_storage::ParquetStorage,
gluesql_redb_storage::RedbStorage,
gluesql_sled_storage::SledStorage,
std::{fmt::Debug, fs::File, io::Write, path::PathBuf},
};
const SLED_STORAGE_DEPRECATION_WARNING: &str = "[warning] sled-storage is deprecated and will be removed in v0.21.0; use redb-storage for new persistent-storage deployments";
fn warn_sled_storage_deprecated() {
eprintln!("{SLED_STORAGE_DEPRECATION_WARNING}");
}
#[derive(Parser, Debug)]
#[clap(name = "gluesql", about, version)]
struct Args {
#[clap(short, long, value_parser)]
execute: Option<PathBuf>,
#[clap(short, long, value_parser)]
dump: Option<PathBuf>,
#[clap(short, long, value_parser)]
storage: Option<Storage>,
#[clap(short, long, value_parser)]
path: Option<PathBuf>,
#[clap(
long,
requires_all = &["storage", "path"],
conflicts_with_all = &["execute", "dump"]
)]
upgrade: bool,
}
#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
enum Storage {
Memory,
Sled,
Redb,
Json,
Csv,
Parquet,
File,
}
pub fn run() -> Result<()> {
fn run<T: GStore + GStoreMut + Planner>(storage: T, input: Option<PathBuf>) {
let output = std::io::stdout();
let mut cli = Cli::new(storage, output);
if let Some(path) = input
&& let Err(e) = cli.load(path.as_path())
{
println!("[error] {e}\n");
}
if let Err(e) = cli.run() {
eprintln!("{e}");
}
}
let Args {
execute,
dump,
storage,
path,
upgrade,
} = Args::parse();
if upgrade {
return upgrade::run_upgrade(path.as_deref(), storage, execute.is_some(), dump.is_some());
}
let path = path.as_deref();
match (path, storage, dump) {
(Some(path), None, Some(dump_path)) => {
warn_sled_storage_deprecated();
let mut storage = SledStorage::new(path).expect("failed to load sled-storage");
dump_database(&mut storage, dump_path)?;
}
(None, None | Some(Storage::Memory), _) => {
println!("[memory-storage] initialized");
run(MemoryStorage::default(), execute);
}
(Some(_), Some(Storage::Memory), _) => {
panic!("failed to load memory-storage: it should be without path");
}
(Some(path), Some(Storage::Sled), _) => {
warn_sled_storage_deprecated();
println!("[sled-storage] connected to {}", path.display());
run(
SledStorage::new(path).expect("failed to load sled-storage"),
execute,
);
}
(Some(path), Some(Storage::Redb), _) => {
println!("[redb-storage] connected to {}", path.display());
run(
RedbStorage::new(path).expect("failed to load redb-storage"),
execute,
);
}
(Some(path), Some(Storage::Json), _) => {
println!("[json-storage] connected to {}", path.display());
run(
JsonStorage::new(path).expect("failed to load json-storage"),
execute,
);
}
(Some(path), Some(Storage::Csv), _) => {
println!("[csv-storage] connected to {}", path.display());
run(
CsvStorage::new(path).expect("failed to load csv-storage"),
execute,
);
}
(Some(path), Some(Storage::Parquet), _) => {
println!("[parquet-storage] connected to {}", path.display());
run(
ParquetStorage::new(path).expect("failed to load parquet-storage"),
execute,
);
}
(Some(path), Some(Storage::File), _) => {
println!("[file-storage] connected to {}", path.display());
run(
FileStorage::new(path).expect("failed to load file-storage"),
execute,
);
}
(None, Some(_), _) | (Some(_), None, None) => {
panic!("both path and storage should be specified");
}
}
Ok(())
}
pub fn dump_database(storage: &mut SledStorage, dump_path: PathBuf) -> Result<()> {
let file = File::create(dump_path)?;
storage.begin(true)?;
let schemas = storage.fetch_all_schemas()?;
for schema in schemas {
writeln!(&file, "{}", schema.to_ddl())?;
let mut rows = storage
.scan_data(&schema.table_name)?
.map(|result| result.map(|(_, row)| row));
loop {
let exprs_list = rows
.by_ref()
.take(100)
.map(|result| {
result.map(|row| row.into_iter().map(Expr::Value).collect::<Vec<_>>())
})
.collect::<std::result::Result<Vec<_>, _>>()?;
if exprs_list.is_empty() {
break;
}
let values = exprs_list
.into_iter()
.map(|exprs| {
let row = exprs
.into_iter()
.map(|expr| expr.to_sql())
.collect::<Vec<_>>()
.join(", ");
format!("({row})")
})
.collect::<Vec<_>>()
.join(", ");
let insert_statement =
format!(r#"INSERT INTO "{}" VALUES {values};"#, schema.table_name);
writeln!(&file, "{insert_statement}")?;
}
writeln!(&file)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use {super::Args, clap::Parser};
#[test]
fn parse_upgrade_requires_storage_and_path() {
let args = Args::try_parse_from(["gluesql", "--upgrade"]);
assert!(args.is_err());
}
#[test]
fn parse_upgrade_rejects_execute() {
let args = Args::try_parse_from([
"gluesql",
"--upgrade",
"--storage",
"sled",
"--path",
"./tmp",
"--execute",
"query.sql",
]);
assert!(args.is_err());
}
#[test]
fn parse_upgrade_rejects_dump() {
let args = Args::try_parse_from([
"gluesql",
"--upgrade",
"--storage",
"file",
"--path",
"./tmp",
"--dump",
"dump.sql",
]);
assert!(args.is_err());
}
}