etanol_databases/
structs.rs

1use std::fs::File;
2use std::path::PathBuf;
3use std::slice::Iter;
4use std::sync::{Arc, Mutex};
5
6use etanol_utils::EtanolError;
7use rusqlite::{types::ValueRef, Connection, Error};
8
9pub use rusqlite::{params_from_iter, Params, ParamsFromIter};
10
11use crate::Column;
12
13lazy_static::lazy_static! {
14    static ref SQLITE_CONNECTION: Arc<Mutex<Option<Connection>>> = Arc::new(Mutex::new(None));
15}
16
17pub fn createParams(params: &[String]) -> ParamsFromIter<Iter<String>> {
18    params_from_iter(params.iter())
19}
20
21#[derive(Debug)]
22pub struct Database {}
23
24impl Database {
25    fn connectionError() {
26        EtanolError::new(
27            format!(
28                "Connection to database is not initialized. Please call `create_connection` first."
29            ),
30            "DatabaseConnection".to_string(),
31        );
32    }
33
34    pub fn createTable(name: String, columns: Vec<Column>) -> String {
35        let mut sql = String::from("--Create Table\n");
36
37        sql.push_str(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (\n", name));
38
39        for column in columns {
40            sql.push_str(&format!(
41                "   \"{}\" {}",
42                column.name,
43                Database::databaseType(column.columnType)
44            ));
45
46            if !column.isOptional {
47                sql.push_str(" NOT NULL");
48            }
49
50            if column.isPrimary {
51                sql.push_str(" PRIMARY KEY");
52            }
53
54            sql.push_str(",\n");
55        }
56
57        sql.remove(sql.len() - 2);
58        sql.push_str(");\n");
59
60        sql
61    }
62
63    pub fn createConnection(url: String) -> Result<(), Error> {
64        if !url.ends_with(".sqlite") {
65            EtanolError::new(
66                format!("Config database_url '{}' not supported", url),
67                "DatabaseNotSupported".to_string(),
68            );
69        }
70
71        let url = format!("etanol/{}", url);
72        let path = PathBuf::from(url.clone());
73        if !path.exists() {
74            match File::create(path) {
75                Ok(_) => {}
76                Err(e) => {
77                    EtanolError::new(
78                        format!("Could not create database file: {}", e),
79                        "FileSystemError".to_string(),
80                    );
81                }
82            }
83        }
84
85        if SQLITE_CONNECTION.lock().unwrap().is_none() {
86            if let Ok(conn) = Connection::open(url) {
87                *SQLITE_CONNECTION.lock().unwrap() = Some(conn);
88
89                return Ok(());
90            }
91
92            EtanolError::new(
93                format!("Could not create database connection"),
94                "DatabaseConnection".to_string(),
95            );
96        }
97
98        Ok(())
99    }
100
101    pub fn executeWithResults(
102        sql: String,
103        params: &[String],
104    ) -> Result<Vec<Vec<(String, String)>>, Error> {
105        if let Some(conn) = &*SQLITE_CONNECTION.lock().unwrap() {
106            let mut stmt = conn.prepare(&sql).unwrap();
107
108            let names = stmt
109                .column_names()
110                .into_iter()
111                .map(|s| String::from(s))
112                .collect::<Vec<_>>();
113
114            let mut rows = stmt.query(createParams(params)).unwrap();
115            let mut models = Vec::new();
116
117            while let Ok(row) = rows.next() {
118                if let Some(row) = row {
119                    let mut model = vec![];
120
121                    for name in names.iter() {
122                        let value = match row.get_ref_unwrap(name.as_ref()) {
123                            ValueRef::Integer(i) => i.to_string(),
124                            ValueRef::Text(s) => String::from_utf8(s.to_vec()).unwrap(),
125                            _ => "None".to_string(),
126                        };
127
128                        model.push((name.clone(), value));
129                    }
130
131                    models.push(model);
132                } else {
133                    break;
134                }
135            }
136
137            return Ok(models);
138        }
139
140        Database::connectionError();
141
142        Ok(vec![])
143    }
144
145    pub fn execute(sql: String, params: &[String]) -> Result<(), Error> {
146        if let Some(conn) = &*SQLITE_CONNECTION.lock().unwrap() {
147            conn.execute(&sql, params_from_iter(params.iter())).unwrap();
148
149            return Ok(());
150        }
151
152        Database::connectionError();
153
154        Ok(())
155    }
156
157    pub fn databaseType(columnType: String) -> String {
158        match columnType.as_str() {
159            "Integer" => String::from("INTEGER"),
160            "String" => String::from("TEXT"),
161            "Boolean" => String::from("BOOLEAN"),
162            _ => {
163                panic!("Invalid column type: {}", columnType);
164            }
165        }
166    }
167
168    pub fn formatQuery(query: String) -> String {
169        query
170            .split("\n")
171            .filter(|x| !x.to_string().starts_with("--"))
172            .collect::<Vec<&str>>()
173            .join("")
174            .split("\"")
175            .collect::<Vec<&str>>()
176            .join("")
177            .to_string()
178    }
179}
180
181// pub trait DatabaseT {
182//     fn createTable(name: String, columns: Vec<Column>) -> String;
183//     fn databaseType(columnType: String) -> String;
184//     fn formatQuery(query: String) -> String;
185
186//     fn createConnection(url: String) -> Result<(), Error>;
187//     fn getConnection() -> Arc<Mutex<Option<Connection>>>;
188//     fn execute(sql: String, params: &[String]) -> Result<(), Error>;
189// }