Skip to content

Commit e87d1fb

Browse files
committed
Add MmapStorage for memory-mapped file support
1 parent d634693 commit e87d1fb

File tree

6 files changed

+636
-2
lines changed

6 files changed

+636
-2
lines changed

Cargo.toml

+3-2
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ categories = ["database", "storage"]
1414
[dependencies]
1515
nom = "7.1"
1616
serde = { version = "1.0", features = ["derive"] }
17-
bincode = "1.3"
18-
tempfile = "3.8"
17+
bincode = "1.3.3"
18+
tempfile = "3.2"
1919
rand = "0.8"
2020
rust-stemmers = "1.2"
2121
lazy_static = "1.4"
22+
memmap2 = "0.5.10"
2223

2324
[dev-dependencies]

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub mod tests;
5353

5454
pub type InMemoryReefDB = ReefDB<storage::memory::InMemoryStorage, fts::default::DefaultSearchIdx>;
5555
pub type OnDiskReefDB = ReefDB<storage::disk::OnDiskStorage, fts::default::DefaultSearchIdx>;
56+
pub type MmapReefDB = ReefDB<storage::mmap::MmapStorage, fts::default::DefaultSearchIdx>;
5657

5758
impl InMemoryReefDB {
5859
pub fn create_in_memory() -> Result<Self, ReefDBError> {

src/storage/mmap.rs

+364
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
1+
use crate::sql::column_def::ColumnDef;
2+
use crate::sql::data_value::DataValue;
3+
use crate::error::ReefDBError;
4+
use crate::indexes::{IndexManager, IndexType};
5+
use crate::indexes::index_manager::{IndexUpdate, DefaultIndexManager};
6+
use memmap2::{MmapMut, MmapOptions};
7+
use std::collections::HashMap;
8+
use std::fs::{File, OpenOptions};
9+
use std::path::Path;
10+
use bincode::{serialize, deserialize};
11+
use std::any::Any;
12+
use super::Storage;
13+
use crate::sql::data_type::DataType;
14+
15+
#[derive(Debug)]
16+
pub struct MmapStorage {
17+
file_path: String,
18+
tables: HashMap<String, (Vec<ColumnDef>, Vec<Vec<DataValue>>)>,
19+
index_manager: DefaultIndexManager,
20+
#[allow(dead_code)]
21+
mmap: Option<MmapMut>,
22+
}
23+
24+
impl Clone for MmapStorage {
25+
fn clone(&self) -> Self {
26+
MmapStorage {
27+
file_path: self.file_path.clone(),
28+
tables: self.tables.clone(),
29+
index_manager: self.index_manager.clone(),
30+
mmap: None,
31+
}
32+
}
33+
}
34+
35+
impl MmapStorage {
36+
pub fn new(file_path: String) -> Self {
37+
let tables = if Path::new(&file_path).exists() {
38+
let file = OpenOptions::new()
39+
.read(true)
40+
.write(true)
41+
.create(true)
42+
.open(&file_path)
43+
.unwrap();
44+
45+
// Ensure file has some content
46+
file.set_len(1024 * 1024).unwrap(); // 1MB initial size
47+
48+
let mmap = unsafe { MmapOptions::new().map_mut(&file).unwrap() };
49+
50+
if mmap.len() > 0 {
51+
match deserialize(&mmap[..]) {
52+
Ok(tables) => tables,
53+
Err(_) => HashMap::new(),
54+
}
55+
} else {
56+
HashMap::new()
57+
}
58+
} else {
59+
HashMap::new()
60+
};
61+
62+
MmapStorage {
63+
file_path,
64+
tables,
65+
index_manager: DefaultIndexManager::new(),
66+
mmap: None,
67+
}
68+
}
69+
70+
fn save(&mut self) -> Result<(), ReefDBError> {
71+
let serialized = serialize(&self.tables)
72+
.map_err(|e| ReefDBError::Other(format!("Serialization error: {}", e)))?;
73+
74+
let file = OpenOptions::new()
75+
.read(true)
76+
.write(true)
77+
.create(true)
78+
.open(&self.file_path)
79+
.map_err(|e| ReefDBError::IoError(e.to_string()))?;
80+
81+
// Ensure file is large enough
82+
let required_size = serialized.len() as u64;
83+
file.set_len(required_size)
84+
.map_err(|e| ReefDBError::IoError(e.to_string()))?;
85+
86+
// Create new memory mapping
87+
let mut mmap = unsafe {
88+
MmapOptions::new()
89+
.len(serialized.len())
90+
.map_mut(&file)
91+
.map_err(|e| ReefDBError::IoError(e.to_string()))?
92+
};
93+
94+
// Write data to memory map
95+
mmap.copy_from_slice(&serialized);
96+
97+
// Sync changes to disk
98+
mmap.flush()
99+
.map_err(|e| ReefDBError::IoError(e.to_string()))?;
100+
101+
self.mmap = Some(mmap);
102+
Ok(())
103+
}
104+
105+
fn get_default_value(data_type: &DataType) -> DataValue {
106+
match data_type {
107+
DataType::Integer => DataValue::Integer(0),
108+
DataType::Text => DataValue::Text(String::new()),
109+
DataType::TSVector => DataValue::Text(String::new()),
110+
}
111+
}
112+
}
113+
114+
impl Storage for MmapStorage {
115+
type NewArgs = String;
116+
117+
fn new(args: Self::NewArgs) -> Self {
118+
Self::new(args)
119+
}
120+
121+
fn insert_table(
122+
&mut self,
123+
table_name: String,
124+
columns: Vec<ColumnDef>,
125+
rows: Vec<Vec<DataValue>>,
126+
) {
127+
self.tables.insert(table_name, (columns, rows));
128+
let _ = self.save();
129+
}
130+
131+
fn get_table(
132+
&mut self,
133+
table_name: &str,
134+
) -> Option<&mut (Vec<ColumnDef>, Vec<Vec<DataValue>>)> {
135+
self.tables.get_mut(table_name)
136+
}
137+
138+
fn get_table_ref(&self, table_name: &str) -> Option<&(Vec<ColumnDef>, Vec<Vec<DataValue>>)> {
139+
self.tables.get(table_name)
140+
}
141+
142+
fn table_exists(&self, table_name: &str) -> bool {
143+
self.tables.contains_key(table_name)
144+
}
145+
146+
fn push_value(&mut self, table_name: &str, row: Vec<DataValue>) -> Result<usize, ReefDBError> {
147+
let len = if let Some((_, rows)) = self.tables.get_mut(table_name) {
148+
rows.push(row);
149+
rows.len()
150+
} else {
151+
return Err(ReefDBError::TableNotFound(table_name.to_string()));
152+
};
153+
let _ = self.save();
154+
Ok(len)
155+
}
156+
157+
fn update_table(
158+
&mut self,
159+
table_name: &str,
160+
updates: Vec<(String, DataValue)>,
161+
where_clause: Option<(String, DataValue)>,
162+
) -> usize {
163+
let mut updated_count = 0;
164+
if let Some((columns, rows)) = self.tables.get_mut(table_name) {
165+
for row in rows.iter_mut() {
166+
let should_update = where_clause.as_ref().map_or(true, |(col, val)| {
167+
if let Some(col_idx) = columns.iter().position(|c| c.name == *col) {
168+
&row[col_idx] == val
169+
} else {
170+
false
171+
}
172+
});
173+
174+
if should_update {
175+
for (col, val) in &updates {
176+
if let Some(col_idx) = columns.iter().position(|c| c.name == *col) {
177+
row[col_idx] = val.clone();
178+
}
179+
}
180+
updated_count += 1;
181+
}
182+
}
183+
let _ = self.save();
184+
}
185+
updated_count
186+
}
187+
188+
fn delete_table(
189+
&mut self,
190+
table_name: &str,
191+
where_clause: Option<(String, DataValue)>,
192+
) -> usize {
193+
let mut deleted_count = 0;
194+
if let Some((columns, rows)) = self.tables.get_mut(table_name) {
195+
let initial_len = rows.len();
196+
rows.retain(|row| {
197+
let should_keep = where_clause.as_ref().map_or(true, |(col, val)| {
198+
if let Some(col_idx) = columns.iter().position(|c| c.name == *col) {
199+
&row[col_idx] != val
200+
} else {
201+
true
202+
}
203+
});
204+
should_keep
205+
});
206+
deleted_count = initial_len - rows.len();
207+
let _ = self.save();
208+
}
209+
deleted_count
210+
}
211+
212+
fn remove_table(&mut self, table_name: &str) -> bool {
213+
let exists = self.tables.remove(table_name).is_some();
214+
if exists {
215+
let _ = self.save();
216+
}
217+
exists
218+
}
219+
220+
fn add_column(&mut self, table_name: &str, column_def: ColumnDef) -> Result<(), ReefDBError> {
221+
if let Some((columns, rows)) = self.tables.get_mut(table_name) {
222+
let default_value = Self::get_default_value(&column_def.data_type);
223+
columns.push(column_def.clone());
224+
for row in rows.iter_mut() {
225+
row.push(default_value.clone());
226+
}
227+
let _ = self.save();
228+
Ok(())
229+
} else {
230+
Err(ReefDBError::TableNotFound(table_name.to_string()))
231+
}
232+
}
233+
234+
fn drop_column(&mut self, table_name: &str, column_name: &str) -> Result<(), ReefDBError> {
235+
if let Some((columns, rows)) = self.tables.get_mut(table_name) {
236+
if let Some(index) = columns.iter().position(|c| c.name == column_name) {
237+
columns.remove(index);
238+
for row in rows.iter_mut() {
239+
row.remove(index);
240+
}
241+
let _ = self.save();
242+
Ok(())
243+
} else {
244+
Err(ReefDBError::ColumnNotFound(column_name.to_string()))
245+
}
246+
} else {
247+
Err(ReefDBError::TableNotFound(table_name.to_string()))
248+
}
249+
}
250+
251+
fn rename_column(&mut self, table_name: &str, old_name: &str, new_name: &str) -> Result<(), ReefDBError> {
252+
if let Some((columns, _)) = self.tables.get_mut(table_name) {
253+
if let Some(col) = columns.iter_mut().find(|c| c.name == old_name) {
254+
col.name = new_name.to_string();
255+
let _ = self.save();
256+
Ok(())
257+
} else {
258+
Err(ReefDBError::ColumnNotFound(old_name.to_string()))
259+
}
260+
} else {
261+
Err(ReefDBError::TableNotFound(table_name.to_string()))
262+
}
263+
}
264+
265+
fn drop_table(&mut self, table_name: &str) {
266+
self.tables.remove(table_name);
267+
let _ = self.save();
268+
}
269+
270+
fn clear(&mut self) {
271+
self.tables.clear();
272+
let _ = self.save();
273+
}
274+
275+
fn get_all_tables(&self) -> &HashMap<String, (Vec<ColumnDef>, Vec<Vec<DataValue>>)> {
276+
&self.tables
277+
}
278+
279+
fn as_any(&self) -> &dyn Any {
280+
self
281+
}
282+
}
283+
284+
impl IndexManager for MmapStorage {
285+
fn create_index(&mut self, table: &str, column: &str, index_type: IndexType) -> Result<(), ReefDBError> {
286+
self.index_manager.create_index(table, column, index_type)
287+
}
288+
289+
fn drop_index(&mut self, table: &str, column: &str) {
290+
self.index_manager.drop_index(table, column)
291+
}
292+
293+
fn get_index(&self, table: &str, column: &str) -> Result<&IndexType, ReefDBError> {
294+
self.index_manager.get_index(table, column)
295+
}
296+
297+
fn update_index(&mut self, table: &str, column: &str, old_value: Vec<u8>, new_value: Vec<u8>, row_id: usize) -> Result<(), ReefDBError> {
298+
self.index_manager.update_index(table, column, old_value, new_value, row_id)
299+
}
300+
301+
fn track_index_update(&mut self, update: IndexUpdate) -> Result<(), ReefDBError> {
302+
self.index_manager.track_index_update(update)
303+
}
304+
305+
fn commit_index_transaction(&mut self, transaction_id: u64) -> Result<(), ReefDBError> {
306+
self.index_manager.commit_index_transaction(transaction_id)
307+
}
308+
309+
fn rollback_index_transaction(&mut self, transaction_id: u64) -> Result<(), ReefDBError> {
310+
self.index_manager.rollback_index_transaction(transaction_id)
311+
}
312+
}
313+
314+
#[cfg(test)]
315+
mod tests {
316+
use super::*;
317+
use crate::sql::constraints::constraint::Constraint;
318+
use tempfile::NamedTempFile;
319+
320+
#[test]
321+
fn test_mmap_storage() {
322+
let temp_file = NamedTempFile::new().unwrap();
323+
let file_path = temp_file.path().to_string_lossy().to_string();
324+
325+
// Create and populate storage
326+
{
327+
let mut storage = MmapStorage::new(file_path.clone());
328+
let columns = vec![
329+
ColumnDef::new("id", DataType::Integer, vec![Constraint::PrimaryKey]),
330+
ColumnDef::new("name", DataType::Text, vec![]),
331+
ColumnDef::new("age", DataType::Integer, vec![]),
332+
];
333+
let rows = vec![
334+
vec![
335+
DataValue::Integer(1),
336+
DataValue::Text("John".to_string()),
337+
DataValue::Integer(20),
338+
],
339+
vec![
340+
DataValue::Integer(2),
341+
DataValue::Text("Jane".to_string()),
342+
DataValue::Integer(25),
343+
],
344+
];
345+
storage.insert_table("users".to_string(), columns, rows);
346+
}
347+
348+
// Create new storage instance and verify persistence
349+
{
350+
let mut storage = MmapStorage::new(file_path);
351+
let (schema, rows) = storage.get_table("users").unwrap();
352+
assert_eq!(schema.len(), 3);
353+
assert_eq!(rows.len(), 2);
354+
assert_eq!(rows[0].len(), 3);
355+
assert_eq!(rows[1].len(), 3);
356+
assert_eq!(rows[0][0], DataValue::Integer(1));
357+
assert_eq!(rows[0][1], DataValue::Text("John".to_string()));
358+
assert_eq!(rows[0][2], DataValue::Integer(20));
359+
assert_eq!(rows[1][0], DataValue::Integer(2));
360+
assert_eq!(rows[1][1], DataValue::Text("Jane".to_string()));
361+
assert_eq!(rows[1][2], DataValue::Integer(25));
362+
}
363+
}
364+
}

0 commit comments

Comments
 (0)