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