1#![deny(clippy::str_to_string)]
2
3mod cli;
4mod command;
5mod helper;
6mod print;
7
8use {
9 crate::cli::Cli,
10 anyhow::Result,
11 clap::Parser,
12 futures::{
13 executor::block_on,
14 stream::{StreamExt, TryStreamExt},
15 },
16 gluesql_core::{
17 ast::{Expr, SetExpr, Statement, ToSql, Values},
18 data::Value,
19 store::{DataRow, GStore, GStoreMut, Store, Transaction},
20 },
21 gluesql_csv_storage::CsvStorage,
22 gluesql_file_storage::FileStorage,
23 gluesql_json_storage::JsonStorage,
24 gluesql_memory_storage::MemoryStorage,
25 gluesql_parquet_storage::ParquetStorage,
26 gluesql_sled_storage::SledStorage,
27 std::{
28 fmt::Debug,
29 fs::File,
30 io::Write,
31 path::{Path, PathBuf},
32 },
33};
34
35#[derive(Parser, Debug)]
36#[clap(name = "gluesql", about, version)]
37struct Args {
38 #[clap(short, long, value_parser)]
40 execute: Option<PathBuf>,
41
42 #[clap(short, long, value_parser)]
44 dump: Option<PathBuf>,
45
46 #[clap(short, long, value_parser)]
48 storage: Option<Storage>,
49
50 #[clap(short, long, value_parser)]
52 path: Option<PathBuf>,
53}
54
55#[derive(clap::ValueEnum, Debug, Clone)]
56enum Storage {
57 Memory,
58 Sled,
59 Json,
60 Csv,
61 Parquet,
62 File,
63}
64
65pub fn run() -> Result<()> {
66 let args = Args::parse();
67 let path = args.path.as_deref().and_then(Path::to_str);
68
69 match (path, args.storage, args.dump) {
70 (None, None, _) | (None, Some(Storage::Memory), _) => {
71 println!("[memory-storage] initialized");
72
73 run(MemoryStorage::default(), args.execute);
74 }
75 (Some(_), Some(Storage::Memory), _) => {
76 panic!("failed to load memory-storage: it should be without path");
77 }
78 (Some(path), Some(Storage::Sled), _) => {
79 println!("[sled-storage] connected to {}", path);
80
81 run(
82 SledStorage::new(path).expect("failed to load sled-storage"),
83 args.execute,
84 );
85 }
86 (Some(path), Some(Storage::Json), _) => {
87 println!("[json-storage] connected to {}", path);
88
89 run(
90 JsonStorage::new(path).expect("failed to load json-storage"),
91 args.execute,
92 );
93 }
94 (Some(path), Some(Storage::Csv), _) => {
95 println!("[csv-storage] connected to {}", path);
96
97 run(
98 CsvStorage::new(path).expect("failed to load csv-storage"),
99 args.execute,
100 );
101 }
102 (Some(path), Some(Storage::Parquet), _) => {
103 println!("[parquet-storage] connected to {}", path);
104
105 run(
106 ParquetStorage::new(path).expect("failed to load parquet-storage"),
107 args.execute,
108 );
109 }
110 (Some(path), Some(Storage::File), _) => {
111 println!("[file-storage] connected to {}", path);
112
113 run(
114 FileStorage::new(path).expect("failed to load file-storage"),
115 args.execute,
116 );
117 }
118 (Some(path), None, Some(dump_path)) => {
119 let mut storage = SledStorage::new(path).expect("failed to load sled-storage");
120
121 dump_database(&mut storage, dump_path)?;
122 }
123 (None, Some(_), _) | (Some(_), None, None) => {
124 panic!("both path and storage should be specified");
125 }
126 }
127
128 fn run<T: GStore + GStoreMut>(storage: T, input: Option<PathBuf>) {
129 let output = std::io::stdout();
130 let mut cli = Cli::new(storage, output);
131
132 if let Some(path) = input {
133 if let Err(e) = cli.load(path.as_path()) {
134 println!("[error] {}\n", e);
135 };
136 }
137
138 if let Err(e) = cli.run() {
139 eprintln!("{}", e);
140 }
141 }
142
143 Ok(())
144}
145
146pub fn dump_database(storage: &mut SledStorage, dump_path: PathBuf) -> Result<()> {
147 let file = File::create(dump_path)?;
148
149 block_on(async {
150 storage.begin(true).await?;
151 let schemas = storage.fetch_all_schemas().await?;
152 for schema in schemas {
153 writeln!(&file, "{}", schema.to_ddl())?;
154
155 let mut rows_list = storage
156 .scan_data(&schema.table_name)
157 .await?
158 .map_ok(|(_, row)| row)
159 .chunks(100);
160
161 while let Some(rows) = rows_list.next().await {
162 let exprs_list = rows
163 .into_iter()
164 .map(|result| {
165 result.map(|data_row| {
166 let values = match data_row {
167 DataRow::Vec(values) => values,
168 DataRow::Map(values) => vec![Value::Map(values)],
169 };
170
171 values
172 .into_iter()
173 .map(|value| Ok(Expr::try_from(value)?))
174 .collect::<Result<Vec<_>>>()
175 })?
176 })
177 .collect::<Result<Vec<_>, _>>()?;
178
179 let insert_statement = Statement::Insert {
180 table_name: schema.table_name.clone(),
181 columns: Vec::new(),
182 source: gluesql_core::ast::Query {
183 body: SetExpr::Values(Values(exprs_list)),
184 order_by: Vec::new(),
185 limit: None,
186 offset: None,
187 },
188 }
189 .to_sql();
190
191 writeln!(&file, "{}", insert_statement)?;
192 }
193
194 writeln!(&file)?;
195 }
196
197 Ok(())
198 })
199}