infusedb/
mod.rs

1// Written by Alberto Ruiz 2024-03-08
2// InfuseDB is a in-memory database,
3// it will store the data in memory and provide a simple API to interact with it
4
5mod collection;
6mod data_type;
7pub mod utils;
8pub use collection::Collection;
9pub use data_type::DataType;
10pub use data_type::FindOp; //TODO: change to own trait and file
11use std::fs;
12use std::path::Path;
13
14pub const VERSION: &'static str = env!("CARGO_PKG_VERSION");
15
16pub struct InfuseDB {
17    pub path: String,
18    collections: Vec<Collection>,
19}
20
21impl InfuseDB {
22    pub fn new() -> Self {
23        InfuseDB {
24            path: "./default.mdb".to_string(),
25            collections: Vec::new(),
26        }
27    }
28
29    pub fn load(path: &str) -> Result<Self, &str> {
30        let mut collections = Vec::new();
31        let contents = fs::read_to_string(path);
32        if contents.is_err() {
33            return Err("Error reading file");
34        }
35        let contents = contents.unwrap();
36        let mut page = String::new();
37        for line in contents.lines() {
38            let line = line.trim();
39            let line = if line.ends_with('\n') {
40                line.strip_prefix('\n').unwrap()
41            } else {
42                line
43            };
44
45            if line.len() == 0 || line.starts_with("#") {
46                continue;
47            }
48            if line.starts_with('[') && line.ends_with(']') && page.len() != 0 {
49                collections.push(Collection::load(page.as_str()));
50                page = String::new();
51            }
52            page.push_str(line);
53            page.push('\n');
54        }
55        if !page.is_empty() {
56            collections.push(Collection::load(page.as_str()));
57        }
58
59        Ok(InfuseDB {
60            collections,
61            path: path.to_string(),
62        })
63    }
64
65    pub fn dump(&self) -> Result<(), &str> {
66        let mut result = String::new();
67        for collection in self.collections.iter() {
68            let page = collection.dump();
69            result.push_str(page.as_str());
70            result.push_str("\n");
71        }
72        let path = Path::new(&self.path);
73        if !path.exists() {
74            if let Err(e) = fs::File::create(&self.path) {
75                println!("{} {:?}", self.path, e);
76                return Err("Error creating file");
77            }
78        }
79
80        let r = fs::write(path, result);
81        if r.is_err() {
82            println!("{:?}", r.err().unwrap());
83            return Err("Error saving file");
84        }
85        Ok(())
86    }
87
88    pub fn create_collection(&mut self, name: &str) -> Result<&mut Collection, &str> {
89        //check if collection exists
90        if self.collections.iter().any(|x| x.name == name) {
91            Err("Collection already exists")
92        } else {
93            let collection = Collection::new(name);
94            self.collections.push(collection);
95            return Ok(self.collections.last_mut().unwrap());
96        }
97    }
98
99    pub fn get_collection(&mut self, name: &str) -> Option<&mut Collection> {
100        //return a mutable reference to collection
101        let index = self
102            .collections
103            .iter()
104            .position(|x| x.name == name.to_string());
105        if index.is_none() {
106            return None;
107        }
108        let index = index.unwrap();
109        let c = self.collections.get_mut(index).unwrap();
110        return Some(c);
111    }
112
113    pub fn get_collection_list(&self) -> Vec<String> {
114        let mut collection_list: Vec<String> = Vec::new();
115        for collection in self.collections.iter() {
116            collection_list.push(collection.name.clone());
117        }
118        collection_list
119    }
120
121    pub fn remove_collection(&mut self, name: String) {
122        let index = self
123            .collections
124            .iter()
125            .position(|x| x.name == name)
126            .unwrap();
127        self.collections.remove(index);
128    }
129}
130
131//TEST
132#[cfg(test)]
133#[test]
134fn test_infusedb() {
135    let mut infusedb = InfuseDB::new();
136    let r1 = infusedb.create_collection("users").is_ok();
137    let r2 = infusedb.create_collection("posts").is_ok();
138    assert!(r1);
139    assert!(r2);
140    assert_eq!(infusedb.collections.len(), 2);
141    assert_eq!(infusedb.collections[0].name, "users");
142    assert_eq!(infusedb.collections[1].name, "posts");
143    assert_eq!(infusedb.get_collection("users").unwrap().name, "users");
144    assert_eq!(infusedb.get_collection("posts").unwrap().name, "posts");
145    assert_eq!(infusedb.get_collection_list().len(), 2);
146    infusedb.remove_collection("users".to_string());
147    assert_eq!(infusedb.collections.len(), 1);
148    infusedb.remove_collection("posts".to_string());
149    assert_eq!(infusedb.collections.len(), 0);
150}
151
152// #[test]
153// fn add_document() {
154//     let mut infusedb = infusedb::new();
155//     let _ = infusedb.create_collection("users");
156//     let get_collection = infusedb.get_collection("users").unwrap();
157//     let mut collection = get_collection.borrow_mut();
158//     let id1 = collection.add("John", doc! {"name" => "John", "age" => 30});
159//     let id2 = collection.add("Jane", doc! {"name" => "Jane", "age" => 25});
160//     assert_eq!(collection.count(), 2);
161//     let document = collection.get("John").unwrap();
162// }
163
164//