use std::path::Path; use rusqlite::{Connection, ToSql}; use chrono::{DateTime, Utc}; use crate::error; use crate::models::{Entry, Meta}; pub enum DBVersion { Timetrap, Version(u16), } pub trait Database { /// This is used to create tables and insert rows fn execute(&mut self, query: &str, params: &[&dyn ToSql]) -> error::Result<()>; /// And this is used to retrieve data fn entry_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result>; fn meta_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result>; // Entry queries fn entries_by_sheet(&self, sheet: &str) -> error::Result> { self.entry_query("select * from entries where sheet=?1 order by start asc", &[&sheet]) } fn entries_all_visible(&self) -> error::Result> { self.entry_query("select * from entries where sheet not like '!_%' escape \"!\" order by sheet asc, start asc", &[]) } fn entries_full(&self) -> error::Result> { self.entry_query("select * from entries order by sheet asc, start asc", &[]) } fn entry_insert(&mut self, at: DateTime, note: String) -> error::Result<()> { self.execute("", &[]) } // Meta queries fn current_sheet(&self) -> error::Result> { let results = self.meta_query("select * from meta where key='current_sheet'", &[])?; Ok(results.into_iter().next().map(|m| m.value)) } fn version(&self) -> error::Result { let results = self.meta_query("select * from meta where key='database_version'", &[])?; if let Some(v) = results.into_iter().next().map(|m| m.value) { Ok(DBVersion::Version(v.parse().map_err(|e| { error::Error::CorruptedData(format!( "Found value '{}' for key 'database_version' in meta table, which is not a valid integer", v )) })?)) } else { Ok(DBVersion::Timetrap) } } } pub struct SqliteDatabase { connection: Connection, } impl SqliteDatabase { pub fn from_memory() -> error::Result { Ok(SqliteDatabase { connection: Connection::open_in_memory()?, }) } pub fn from_path>(path: P) -> error::Result { Ok(SqliteDatabase { connection: Connection::open(path)?, }) } } impl Database for SqliteDatabase { fn execute(&mut self, query: &str, params: &[&dyn ToSql]) -> error::Result<()> { self.connection.execute(query, params)?; Ok(()) } fn entry_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result> { let mut stmt = self.connection.prepare(query)?; let results = stmt.query_map(params, |row| { let x = Ok(Entry { id: row.get("id")?, note: row.get("note")?, start: row.get("start")?, end: row.get("end")?, sheet: row.get("sheet")?, }); x})?.filter_map(|r| r.ok()).collect(); Ok(results) } fn meta_query(&self, query: &str, params: &[&dyn ToSql]) -> error::Result> { let mut stmt = self.connection.prepare(query)?; let results = stmt.query_map(params, |row| Ok(Meta { id: row.get("id")?, key: row.get("key")?, value: row.get("value")?, }))?.filter_map(|r| r.ok()).collect(); Ok(results) } }