1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
#[macro_use]
extern crate log;
pub mod log_db {
use fs2::FileExt;
use priority_queue::PriorityQueue;
use std::collections::BTreeMap;
use std::fs;
use std::io;
use std::io::Write;
use std::path::{Path, PathBuf};
const ACTIVE_LOG_FILENAME: &str = "db";
pub trait Record {
fn serialize(&self) -> Vec<u8>;
fn deserialize(data: Vec<u8>) -> Self;
}
pub struct Config {
/// Directory where the database will store its data.
pub data_dir: String,
/// The maximum size of a segment file in bytes.
/// Once a segment file reaches this size, it is closed and a new one is created.
/// Closed segment files can be compacted.
pub segment_size: u64,
/// The maximum size of a single memtable in bytes.
/// Note that each secondary index will have its own memtable.
pub memtable_size: u64,
}
pub struct DB<T: Record> {
data_dir: String,
segment_size: u64,
memtable_size: u64,
pub log_path: PathBuf,
primary_memtable: BTreeMap<u64, T>,
}
impl<T: Record> DB<T> {
pub fn initialize(config: &Config) -> Result<DB<T>, io::Error> {
info!("Initializing DB");
// If data_dir does not exist, create it
if !fs::exists(&config.data_dir)? {
fs::create_dir_all(&config.data_dir)?;
}
let log_path = Path::new(&config.data_dir).join(ACTIVE_LOG_FILENAME);
let db = DB::<T> {
data_dir: config.data_dir.clone(),
segment_size: config.segment_size,
memtable_size: config.memtable_size,
log_path,
primary_memtable: BTreeMap::new(),
};
Ok(db)
}
pub fn upsert(&self, record: &T) -> Result<(), io::Error> {
let mut file = fs::OpenOptions::new()
.create(true)
.append(true)
.open(&self.log_path)?;
// Acquire an exclusive lock for writing
file.lock_exclusive()?;
// Write the record to the log
let serialized = record.serialize();
file.write_all(&serialized)?;
// Sync to disk
file.flush()?;
file.sync_all()?;
file.unlock()?;
Ok(())
}
}
}
|