aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/database.rs20
-rw-r--r--src/list.rs230
-rw-r--r--src/main.rs1
3 files changed, 243 insertions, 8 deletions
diff --git a/src/database.rs b/src/database.rs
index a1d0a18..de77f95 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -1,22 +1,22 @@
use anyhow::Result;
use rusqlite::{Connection, Transaction, config::DbConfig};
-const DB_NAME: &str = "database.sqlite";
+pub const DB_NAME: &str = "database.sqlite";
const DB_VERSION: i64 = 0;
-struct Database {
+pub struct Database {
conn: Connection,
}
impl Database {
- fn new() -> Result<Self> {
+ pub fn new(db_file: &str) -> Result<Self> {
let mut init = false;
- if !std::fs::exists(DB_NAME)? {
+ if !std::fs::exists(db_file)? {
init = true;
}
let mut db = Database {
- conn: Connection::open(DB_NAME)?,
+ conn: Connection::open(db_file)?,
};
if init {
db.initialize()?;
@@ -35,7 +35,7 @@ impl Database {
tx.execute(q, ())?;
q = "INSERT INTO version VALUES (?)";
- tx.execute(q, &[&DB_VERSION])?;
+ tx.execute(q, [&DB_VERSION])?;
q = "CREATE TABLE user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -61,7 +61,10 @@ impl Database {
Ok(())
}
- fn execute<Ex>(&mut self, q: Ex) -> Result<QueryResult<Ex::T>> where Ex: DBExecutable {
+ pub fn execute<Ex>(&mut self, q: Ex) -> Result<QueryResult<Ex::T>>
+ where
+ Ex: DBExecutable,
+ {
let tx = self.conn.transaction()?;
let ris = q.execute(&tx)?;
tx.commit()?;
@@ -74,7 +77,8 @@ pub trait DBExecutable {
fn execute(&self, tx: &Transaction) -> Result<QueryResult<Self::T>>;
}
+#[derive(Debug)]
pub enum QueryResult<T> {
Empty,
- Vec(Vec<T>)
+ Vec(Vec<T>),
}
diff --git a/src/list.rs b/src/list.rs
new file mode 100644
index 0000000..36cd492
--- /dev/null
+++ b/src/list.rs
@@ -0,0 +1,230 @@
+use anyhow::Result;
+use rusqlite::fallible_iterator::FallibleIterator;
+
+use crate::database::{DBExecutable, QueryResult};
+
+#[derive(PartialEq, Debug)]
+struct User {
+ name: Option<String>,
+ email: String,
+}
+
+impl User {
+ fn new(email: &str) -> Self {
+ Self {
+ name: None,
+ email: String::from(email),
+ }
+ }
+
+ fn insert(&self) -> UserQuery<'_> {
+ let name = self.name.as_deref();
+ UserQuery::Insert(name, self.email.as_str())
+ }
+
+ fn delete(&self) -> UserQuery<'_> {
+ UserQuery::Delete(&self.email)
+ }
+
+ fn query_by_email(&self) -> UserQuery<'_> {
+ UserQuery::QueryByEmail(&self.email)
+ }
+
+ fn query_all<'a>() -> UserQuery<'a> {
+ UserQuery::QueryAll
+ }
+}
+
+enum UserQuery<'a> {
+ Insert(Option<&'a str>, &'a str),
+ Delete(&'a str),
+ QueryByEmail(&'a str),
+ QueryAll,
+}
+
+impl DBExecutable for UserQuery<'_> {
+ type T = User;
+ fn execute(&self, tx: &rusqlite::Transaction) -> Result<QueryResult<Self::T>> {
+ match self {
+ UserQuery::Insert(_, _) => self.db_insert(tx),
+ UserQuery::Delete(_) => self.db_delete(tx),
+ UserQuery::QueryByEmail(_) => self.db_query_by_email(tx),
+ UserQuery::QueryAll => self.db_query_all(tx),
+ }
+ }
+}
+
+impl UserQuery<'_> {
+ fn db_insert(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> {
+ let (name, email) = if let UserQuery::Insert(name, email) = self {
+ (name, email)
+ } else {
+ unreachable!("this should only be called by a UserQuery::Insert")
+ };
+
+ if let Some(name) = name {
+ let q = "INSERT INTO user (name, email) VALUES (?, ?)";
+ tx.execute(q, [name, email])?;
+ return Ok(QueryResult::Empty);
+ }
+
+ let q = "INSERT INTO user (email) VALUES (?)";
+ tx.execute(q, [email])?;
+ Ok(QueryResult::Empty)
+ }
+
+ fn db_delete(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> {
+ let email = if let UserQuery::Delete(email) = self {
+ email
+ } else {
+ unreachable!("this should only be called by a UserQuery::Delete")
+ };
+
+ let q = "DELETE FROM user WHERE email = ?";
+ tx.execute(q, [email])?;
+ Ok(QueryResult::Empty)
+ }
+
+ fn db_query_by_email(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> {
+ let email = if let UserQuery::QueryByEmail(email) = self {
+ email
+ } else {
+ unreachable!("this should only be called by a UserQuery::QueryByEmail")
+ };
+
+ let q = "SELECT * FROM user WHERE email LIKE ?";
+ let mut stmt = tx.prepare(q)?;
+ let ris = stmt
+ .query([email])?
+ .map(|row| {
+ Ok(User {
+ name: row.get(1).unwrap(),
+ email: row.get(2).unwrap(),
+ })
+ })
+ .collect()?;
+ Ok(QueryResult::Vec(ris))
+ }
+
+ fn db_query_all(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> {
+ let q = "SELECT * FROM user";
+ let mut stmt = tx.prepare(q)?;
+ let ris = stmt
+ .query(())?
+ .map(|row| {
+ Ok(User {
+ name: row.get(1).unwrap(),
+ email: row.get(2).unwrap(),
+ })
+ })
+ .collect()?;
+ Ok(QueryResult::Vec(ris))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use anyhow::Result;
+
+ use crate::{
+ database::{DB_NAME, Database},
+ list::User,
+ };
+
+ fn setup() -> Result<Database> {
+ std::fs::create_dir("test")?;
+ Database::new(&format!("test/{}", DB_NAME))
+ }
+
+ fn cleanup() -> Result<()> {
+ std::fs::remove_dir_all("test")?;
+ Ok(())
+ }
+
+ #[test]
+ fn insert_wo_name() {
+ let mut database = setup().expect("Failed setup");
+
+ let user = User {
+ name: None,
+ email: String::from("mroik@poul.org"),
+ };
+ database.execute(user.insert()).expect("Failed insert");
+ let user2 = database
+ .execute(user.query_by_email())
+ .expect("Failed query");
+ match user2 {
+ crate::database::QueryResult::Empty => assert!(false),
+ crate::database::QueryResult::Vec(items) => {
+ assert_eq!(items.len(), 1);
+ assert_eq!(items[0], user);
+ }
+ }
+
+ drop(database);
+ cleanup().expect("Failed cleanup");
+ }
+
+ #[test]
+ fn insert_with_name() {
+ let mut database = setup().expect("Failed setup");
+
+ let user = User {
+ name: Some(String::from("Mirko Faina")),
+ email: String::from("mroik@poul.org"),
+ };
+ database.execute(user.insert()).expect("Failed insert");
+ let user2 = database
+ .execute(user.query_by_email())
+ .expect("Failed query");
+ match &user2 {
+ crate::database::QueryResult::Empty => assert!(false),
+ crate::database::QueryResult::Vec(items) => {
+ assert_eq!(items.len(), 1);
+ assert_eq!(items[0], user);
+ }
+ }
+
+ drop(database);
+ cleanup().expect("Failed cleanup");
+ }
+
+ #[test]
+ fn insert_twice() {
+ let mut database = setup().expect("Failed setup");
+
+ let user = User {
+ name: Some(String::from("Mirko Faina")),
+ email: String::from("mroik@poul.org"),
+ };
+ database.execute(user.insert()).expect("Failed insert");
+ let ris = database.execute(user.insert());
+ assert!(ris.is_err());
+
+ drop(database);
+ cleanup().expect("Failed cleanup");
+ }
+
+ #[test]
+ fn delete() {
+ let mut database = setup().expect("Failed setup");
+
+ let user = User {
+ name: Some(String::from("Mirko Faina")),
+ email: String::from("mroik@poul.org"),
+ };
+ database.execute(user.insert()).expect("Failed insert");
+ database.execute(user.delete()).expect("Failed delete");
+
+ let ris = database
+ .execute(User::query_all())
+ .expect("Failed query all");
+ match ris {
+ crate::database::QueryResult::Empty => assert!(false),
+ crate::database::QueryResult::Vec(items) => assert!(items.is_empty()),
+ }
+
+ drop(database);
+ cleanup().expect("Failed cleanup");
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 189b68b..60b75af 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,4 +1,5 @@
mod database;
+mod list;
fn main() {
println!("Hello, world!");
XMR address: 854DmXNrxULU3ZFJVs4Wc8PFhbq29RhqHhY8W6cdWrtFN3qmooKyyeYPcDzZTNRxphhJ5UzASQfAdEMwSteVqymk28aLhqj