gluesql_cli/
lib.rs

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    /// SQL file to execute
40    #[clap(short, long, value_parser)]
41    execute: Option<PathBuf>,
42
43    /// PATH to dump whole database
44    #[clap(short, long, value_parser)]
45    dump: Option<PathBuf>,
46
47    /// Storage type to store data, default is memory
48    #[clap(short, long, value_parser)]
49    storage: Option<Storage>,
50
51    /// Storage path to load
52    #[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}