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, ToSql},
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_redb_storage::RedbStorage,
27 gluesql_sled_storage::SledStorage,
28 std::{
29 fmt::Debug,
30 fs::File,
31 io::Write,
32 path::{Path, PathBuf},
33 },
34};
35
36#[derive(Parser, Debug)]
37#[clap(name = "gluesql", about, version)]
38struct Args {
39 #[clap(short, long, value_parser)]
41 execute: Option<PathBuf>,
42
43 #[clap(short, long, value_parser)]
45 dump: Option<PathBuf>,
46
47 #[clap(short, long, value_parser)]
49 storage: Option<Storage>,
50
51 #[clap(short, long, value_parser)]
53 path: Option<PathBuf>,
54}
55
56#[derive(clap::ValueEnum, Debug, Clone)]
57enum Storage {
58 Memory,
59 Sled,
60 Redb,
61 Json,
62 Csv,
63 Parquet,
64 File,
65}
66
67pub fn run() -> Result<()> {
68 let args = Args::parse();
69 let path = args.path.as_deref().and_then(Path::to_str);
70
71 match (path, args.storage, args.dump) {
72 (None, None, _) | (None, Some(Storage::Memory), _) => {
73 println!("[memory-storage] initialized");
74
75 run(MemoryStorage::default(), args.execute);
76 }
77 (Some(_), Some(Storage::Memory), _) => {
78 panic!("failed to load memory-storage: it should be without path");
79 }
80 (Some(path), Some(Storage::Sled), _) => {
81 println!("[sled-storage] connected to {path}");
82
83 run(
84 SledStorage::new(path).expect("failed to load sled-storage"),
85 args.execute,
86 );
87 }
88 (Some(path), Some(Storage::Redb), _) => {
89 println!("[redb-storage] connected to {path}");
90
91 run(
92 RedbStorage::new(path).expect("failed to load redb-storage"),
93 args.execute,
94 );
95 }
96 (Some(path), Some(Storage::Json), _) => {
97 println!("[json-storage] connected to {path}");
98
99 run(
100 JsonStorage::new(path).expect("failed to load json-storage"),
101 args.execute,
102 );
103 }
104 (Some(path), Some(Storage::Csv), _) => {
105 println!("[csv-storage] connected to {path}");
106
107 run(
108 CsvStorage::new(path).expect("failed to load csv-storage"),
109 args.execute,
110 );
111 }
112 (Some(path), Some(Storage::Parquet), _) => {
113 println!("[parquet-storage] connected to {path}");
114
115 run(
116 ParquetStorage::new(path).expect("failed to load parquet-storage"),
117 args.execute,
118 );
119 }
120 (Some(path), Some(Storage::File), _) => {
121 println!("[file-storage] connected to {path}");
122
123 run(
124 FileStorage::new(path).expect("failed to load file-storage"),
125 args.execute,
126 );
127 }
128 (Some(path), None, Some(dump_path)) => {
129 let mut storage = SledStorage::new(path).expect("failed to load sled-storage");
130
131 dump_database(&mut storage, dump_path)?;
132 }
133 (None, Some(_), _) | (Some(_), None, None) => {
134 panic!("both path and storage should be specified");
135 }
136 }
137
138 fn run<T: GStore + GStoreMut>(storage: T, input: Option<PathBuf>) {
139 let output = std::io::stdout();
140 let mut cli = Cli::new(storage, output);
141
142 if let Some(path) = input
143 && let Err(e) = cli.load(path.as_path())
144 {
145 println!("[error] {e}\n");
146 };
147
148 if let Err(e) = cli.run() {
149 eprintln!("{e}");
150 }
151 }
152
153 Ok(())
154}
155
156pub fn dump_database(storage: &mut SledStorage, dump_path: PathBuf) -> Result<()> {
157 let file = File::create(dump_path)?;
158
159 block_on(async {
160 storage.begin(true).await?;
161 let schemas = storage.fetch_all_schemas().await?;
162 for schema in schemas {
163 writeln!(&file, "{}", schema.to_ddl())?;
164
165 let mut rows_list = storage
166 .scan_data(&schema.table_name)
167 .await?
168 .map_ok(|(_, row)| row)
169 .chunks(100);
170
171 while let Some(rows) = rows_list.next().await {
172 let exprs_list = rows
173 .into_iter()
174 .map(|result| {
175 result.and_then(|data_row| {
176 let values = match data_row {
177 DataRow::Vec(values) => values,
178 DataRow::Map(values) => vec![Value::Map(values)],
179 };
180
181 values
182 .into_iter()
183 .map(Expr::try_from)
184 .collect::<std::result::Result<Vec<_>, _>>()
185 })
186 })
187 .collect::<std::result::Result<Vec<_>, _>>()?;
188
189 let values = exprs_list
190 .into_iter()
191 .map(|exprs| {
192 let row = exprs
193 .into_iter()
194 .map(|expr| expr.to_sql())
195 .collect::<Vec<_>>()
196 .join(", ");
197 format!("({row})")
198 })
199 .collect::<Vec<_>>()
200 .join(", ");
201
202 let insert_statement =
203 format!(r#"INSERT INTO "{}" VALUES {values};"#, schema.table_name);
204
205 writeln!(&file, "{insert_statement}")?;
206 }
207
208 writeln!(&file)?;
209 }
210
211 Ok(())
212 })
213}