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, Planner, 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 fn run<T: GStore + GStoreMut + Planner>(storage: T, input: Option<PathBuf>) {
69 let output = std::io::stdout();
70 let mut cli = Cli::new(storage, output);
71
72 if let Some(path) = input
73 && let Err(e) = cli.load(path.as_path())
74 {
75 println!("[error] {e}\n");
76 }
77
78 if let Err(e) = cli.run() {
79 eprintln!("{e}");
80 }
81 }
82
83 let args = Args::parse();
84 let path = args.path.as_deref().and_then(Path::to_str);
85
86 match (path, args.storage, args.dump) {
87 (None, None | Some(Storage::Memory), _) => {
88 println!("[memory-storage] initialized");
89
90 run(MemoryStorage::default(), args.execute);
91 }
92 (Some(_), Some(Storage::Memory), _) => {
93 panic!("failed to load memory-storage: it should be without path");
94 }
95 (Some(path), Some(Storage::Sled), _) => {
96 println!("[sled-storage] connected to {path}");
97
98 run(
99 SledStorage::new(path).expect("failed to load sled-storage"),
100 args.execute,
101 );
102 }
103 (Some(path), Some(Storage::Redb), _) => {
104 println!("[redb-storage] connected to {path}");
105
106 run(
107 RedbStorage::new(path).expect("failed to load redb-storage"),
108 args.execute,
109 );
110 }
111 (Some(path), Some(Storage::Json), _) => {
112 println!("[json-storage] connected to {path}");
113
114 run(
115 JsonStorage::new(path).expect("failed to load json-storage"),
116 args.execute,
117 );
118 }
119 (Some(path), Some(Storage::Csv), _) => {
120 println!("[csv-storage] connected to {path}");
121
122 run(
123 CsvStorage::new(path).expect("failed to load csv-storage"),
124 args.execute,
125 );
126 }
127 (Some(path), Some(Storage::Parquet), _) => {
128 println!("[parquet-storage] connected to {path}");
129
130 run(
131 ParquetStorage::new(path).expect("failed to load parquet-storage"),
132 args.execute,
133 );
134 }
135 (Some(path), Some(Storage::File), _) => {
136 println!("[file-storage] connected to {path}");
137
138 run(
139 FileStorage::new(path).expect("failed to load file-storage"),
140 args.execute,
141 );
142 }
143 (Some(path), None, Some(dump_path)) => {
144 let mut storage = SledStorage::new(path).expect("failed to load sled-storage");
145
146 dump_database(&mut storage, dump_path)?;
147 }
148 (None, Some(_), _) | (Some(_), None, None) => {
149 panic!("both path and storage should be specified");
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.map(|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.into_iter().map(Expr::Value).collect::<Vec<_>>()
182 })
183 })
184 .collect::<std::result::Result<Vec<_>, _>>()?;
185
186 let values = exprs_list
187 .into_iter()
188 .map(|exprs| {
189 let row = exprs
190 .into_iter()
191 .map(|expr| expr.to_sql())
192 .collect::<Vec<_>>()
193 .join(", ");
194 format!("({row})")
195 })
196 .collect::<Vec<_>>()
197 .join(", ");
198
199 let insert_statement =
200 format!(r#"INSERT INTO "{}" VALUES {values};"#, schema.table_name);
201
202 writeln!(&file, "{insert_statement}")?;
203 }
204
205 writeln!(&file)?;
206 }
207
208 Ok(())
209 })
210}