use std::fs;
use std::io::{self, BufWriter, Write};
use std::path::{Path, PathBuf};
use subms_bloom_filter::BloomFilter;
const MAGIC: u32 = 0x4C534D54;
const FLAG_VALUE: u8 = 0x00;
const FLAG_TOMBSTONE: u8 = 0x01;
const FOOTER_BYTES: usize = 8 + 4;
pub(crate) struct SsTable {
#[allow(dead_code)]
path: PathBuf,
buf: Vec<u8>,
records_end: usize,
bloom: BloomFilter,
offsets: Vec<usize>,
}
impl SsTable {
pub(crate) fn open(path: impl Into<PathBuf>) -> io::Result<Self> {
let path = path.into();
let buf = fs::read(&path)?;
if buf.len() < FOOTER_BYTES {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"sstable too small",
));
}
let magic_off = buf.len() - 4;
let magic = u32::from_be_bytes(buf[magic_off..magic_off + 4].try_into().unwrap());
if magic != MAGIC {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"bad sstable magic",
));
}
let footer_off = buf.len() - FOOTER_BYTES;
let records_end =
u64::from_be_bytes(buf[footer_off..footer_off + 8].try_into().unwrap()) as usize;
if records_end > footer_off {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"bad records_end offset",
));
}
let bloom = BloomFilter::parse(&buf[records_end..footer_off])?;
let offsets = Self::index_offsets(&buf, records_end);
Ok(Self {
path,
buf,
records_end,
bloom,
offsets,
})
}
fn index_offsets(buf: &[u8], records_end: usize) -> Vec<usize> {
let mut offsets = Vec::new();
let mut p = 0usize;
while p < records_end {
offsets.push(p);
let key_len = u32::from_be_bytes(buf[p..p + 4].try_into().unwrap()) as usize;
p += 4 + key_len + 1;
let value_len = u32::from_be_bytes(buf[p..p + 4].try_into().unwrap()) as usize;
p += 4 + value_len;
}
offsets
}
fn key_at(&self, off: usize) -> &[u8] {
let key_len = u32::from_be_bytes(self.buf[off..off + 4].try_into().unwrap()) as usize;
&self.buf[off + 4..off + 4 + key_len]
}
pub(crate) fn write<'a, I>(
path: impl Into<PathBuf>,
expected_entries: usize,
sorted_entries: I,
) -> io::Result<Self>
where
I: IntoIterator<Item = (&'a str, Option<&'a [u8]>)>,
{
let path = path.into();
let mut bloom = BloomFilter::new(expected_entries);
let mut records_end: u64 = 0;
{
let mut out = BufWriter::new(fs::File::create(&path)?);
for (key, value) in sorted_entries {
bloom.add(key);
let kb = key.as_bytes();
out.write_all(&(kb.len() as u32).to_be_bytes())?;
out.write_all(kb)?;
match value {
Some(v) => {
out.write_all(&[FLAG_VALUE])?;
out.write_all(&(v.len() as u32).to_be_bytes())?;
out.write_all(v)?;
records_end += (4 + kb.len() + 1 + 4 + v.len()) as u64;
}
None => {
out.write_all(&[FLAG_TOMBSTONE])?;
out.write_all(&0u32.to_be_bytes())?;
records_end += (4 + kb.len() + 1 + 4) as u64;
}
}
}
bloom.write_to(&mut out)?;
out.write_all(&records_end.to_be_bytes())?;
out.write_all(&MAGIC.to_be_bytes())?;
out.flush()?;
}
Self::open(path)
}
pub(crate) fn get(&self, key: &str, check_bloom: bool) -> Option<Option<Vec<u8>>> {
if check_bloom && !self.bloom.might_contain(key) {
return None;
}
let kb = key.as_bytes();
match self
.offsets
.binary_search_by(|&off| self.key_at(off).cmp(kb))
{
Ok(idx) => {
let off = self.offsets[idx];
let key_len =
u32::from_be_bytes(self.buf[off..off + 4].try_into().unwrap()) as usize;
let mut p = off + 4 + key_len;
let flag = self.buf[p];
p += 1;
let value_len = u32::from_be_bytes(self.buf[p..p + 4].try_into().unwrap()) as usize;
p += 4;
Some(if flag == FLAG_TOMBSTONE {
None
} else {
Some(self.buf[p..p + value_len].to_vec())
})
}
Err(_) => None,
}
}
pub(crate) fn range(
&self,
lo: Option<&str>,
hi: Option<&str>,
) -> Vec<(String, Option<Vec<u8>>)> {
let start = match lo {
Some(l) => self
.offsets
.partition_point(|&off| self.key_at(off) < l.as_bytes()),
None => 0,
};
let mut out = Vec::new();
for &off in &self.offsets[start..] {
let key_len = u32::from_be_bytes(self.buf[off..off + 4].try_into().unwrap()) as usize;
let key_bytes = &self.buf[off + 4..off + 4 + key_len];
if hi.map(|h| key_bytes >= h.as_bytes()).unwrap_or(false) {
break;
}
let mut p = off + 4 + key_len;
let flag = self.buf[p];
p += 1;
let value_len = u32::from_be_bytes(self.buf[p..p + 4].try_into().unwrap()) as usize;
p += 4;
let key = String::from_utf8_lossy(key_bytes).into_owned();
let value = if flag == FLAG_TOMBSTONE {
None
} else {
Some(self.buf[p..p + value_len].to_vec())
};
out.push((key, value));
}
out
}
#[allow(dead_code)]
pub(crate) fn path(&self) -> &Path {
&self.path
}
pub(crate) fn entries(&self) -> Vec<(String, Option<Vec<u8>>)> {
let mut out = Vec::new();
let mut p = 0usize;
while p < self.records_end {
let key_len = u32::from_be_bytes(self.buf[p..p + 4].try_into().unwrap()) as usize;
p += 4;
let key = String::from_utf8_lossy(&self.buf[p..p + key_len]).into_owned();
p += key_len;
let flag = self.buf[p];
p += 1;
let value_len = u32::from_be_bytes(self.buf[p..p + 4].try_into().unwrap()) as usize;
p += 4;
let value = if flag == FLAG_TOMBSTONE {
None
} else {
Some(self.buf[p..p + value_len].to_vec())
};
p += value_len;
out.push((key, value));
}
out
}
}