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, 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    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}