Initial model + some various changes
[recipes.git] / backend / src / db.rs
1 use crate::consts::SQL_FILENAME;
2
3 use super::consts;
4
5 use std::{fs::{self, File}, path::Path, io::Read};
6
7 //use rusqlite::types::ToSql;
8 //use rusqlite::{Connection, Result, NO_PARAMS};
9 use r2d2::Pool;
10 use r2d2_sqlite::SqliteConnectionManager;
11
12 const CURRENT_DB_VERSION: u32 = 1;
13
14 #[derive(Debug)]
15 pub enum DBError {
16 SqliteError(rusqlite::Error),
17 R2d2Error(r2d2::Error),
18 UnsupportedVersion(u32),
19 Other(String),
20 }
21
22 pub struct Connection {
23 //con: rusqlite::Connection
24 pool: Pool<SqliteConnectionManager>
25 }
26
27 pub struct Recipe {
28 pub title: String,
29 pub id: i32,
30 }
31
32 impl std::convert::From<rusqlite::Error> for DBError {
33 fn from(error: rusqlite::Error) -> Self {
34 DBError::SqliteError(error)
35 }
36 }
37
38 impl std::convert::From<r2d2::Error> for DBError {
39 fn from(error: r2d2::Error) -> Self {
40 DBError::R2d2Error(error)
41 }
42 }
43
44 impl Connection {
45 pub fn new() -> Result<Connection, DBError> {
46
47 let data_dir = Path::new(consts::DB_DIRECTORY);
48
49 if !data_dir.exists() {
50 fs::DirBuilder::new().create(data_dir).unwrap();
51 }
52
53 let manager = SqliteConnectionManager::file(consts::DB_FILENAME);
54 let pool = r2d2::Pool::new(manager).unwrap();
55
56 let connection = Connection { pool };
57 connection.create_or_update()?;
58 Ok(connection)
59 }
60
61 /*
62 * Called after the connection has been established for creating or updating the database.
63 * The 'Version' table tracks the current state of the database.
64 */
65 fn create_or_update(self: &Self) -> Result<(), DBError> {
66 // let connection = Connection::new();
67 // let mut stmt = connection.sqlite_con.prepare("SELECT * FROM versions ORDER BY date").unwrap();
68 // let mut stmt = connection.sqlite_con.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='versions'").unwrap();
69
70 // Check the Database version.
71 let mut con = self.pool.get()?;
72 let tx = con.transaction()?;
73
74 // Version 0 corresponds to an empty database.
75 let mut version = {
76 match tx.query_row(
77 "SELECT [name] FROM [sqlite_master] WHERE [type] = 'table' AND [name] = 'Version'",
78 [],
79 |row| row.get::<usize, String>(0)
80 ) {
81 Ok(_) => tx.query_row("SELECT [version] FROM [Version]", [], |row| row.get(0)).unwrap_or_default(),
82 Err(_) => 0
83 }
84 };
85
86 while Connection::update_to_next_version(version, &tx)? {
87 version += 1;
88 }
89
90 tx.commit()?;
91
92 Ok(())
93 }
94
95 fn update_to_next_version(current_version: u32, tx: &rusqlite::Transaction) -> Result<bool, DBError> {
96 let next_version = current_version + 1;
97
98 if next_version <= CURRENT_DB_VERSION {
99 println!("Update to version {}...", next_version);
100 }
101
102 fn ok(updated: bool) -> Result<bool, DBError> {
103 if updated {
104 println!("Version updated");
105 }
106 Ok(updated)
107 }
108
109 match next_version {
110 1 => {
111 tx.execute_batch(&load_sql_file(next_version)?)?;
112
113 ok(true)
114 }
115
116 // Version 1 doesn't exist yet.
117 2 =>
118 ok(false),
119
120 v =>
121 Err(DBError::UnsupportedVersion(v)),
122 }
123 }
124
125 pub fn get_all_recipes() {
126
127 }
128 }
129
130 fn load_sql_file(version: u32) -> Result<String, DBError> {
131 let sql_file = SQL_FILENAME.replace("{VERSION}", &version.to_string());
132 let mut file = File::open(&sql_file).map_err(|err| DBError::Other(format!("Cannot open SQL file ({}): {}", &sql_file, err.to_string())))?;
133 let mut sql = String::new();
134 file.read_to_string(&mut sql).map_err(|err| DBError::Other(format!("Cannot read SQL file ({}) : {}", &sql_file, err.to_string())))?;
135 Ok(sql)
136 }