use crate::be_tree::BeTree;
use crate::epoch::Epoch;
use crate::rowid::RowId;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum Value {
Null,
Bool(bool),
Int64(i64),
Float64(f64),
Bytes(Vec<u8>),
Embedding(Vec<f32>),
}
impl Value {
pub fn encode_key(&self) -> Vec<u8> {
match self {
Value::Null => Vec::new(),
Value::Bool(b) => vec![*b as u8],
Value::Int64(n) => n.to_be_bytes().to_vec(),
Value::Float64(f) => f.to_bits().to_be_bytes().to_vec(),
Value::Bytes(b) => b.clone(),
Value::Embedding(v) => {
let mut out = Vec::with_capacity(v.len() * 4);
for x in v {
out.extend_from_slice(&x.to_bits().to_be_bytes());
}
out
}
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Row {
pub row_id: RowId,
pub committed_epoch: Epoch,
pub columns: HashMap<u16, Value>,
pub deleted: bool,
}
impl Row {
pub fn new(row_id: RowId, committed_epoch: Epoch) -> Self {
Self {
row_id,
committed_epoch,
columns: HashMap::new(),
deleted: false,
}
}
pub fn with_column(mut self, column_id: u16, value: Value) -> Self {
self.columns.insert(column_id, value);
self
}
pub fn estimated_bytes(&self) -> u64 {
let mut n = 32; for v in self.columns.values() {
n += match v {
Value::Null => 1,
Value::Bool(_) => 1,
Value::Int64(_) => 8,
Value::Float64(_) => 8,
Value::Bytes(b) => 16 + b.len() as u64,
Value::Embedding(v) => 16 + (v.len() as u64) * 4,
};
}
n
}
}
pub struct Memtable {
tree: BeTree,
byte_size: u64,
}
impl Default for Memtable {
fn default() -> Self {
Self::new()
}
}
impl Memtable {
pub fn new() -> Self {
Self {
tree: BeTree::new(),
byte_size: 0,
}
}
pub fn upsert(&mut self, row: Row) {
self.byte_size += row.estimated_bytes();
self.tree.insert_row(row);
}
pub fn tombstone(&mut self, row_id: RowId, epoch: Epoch) {
let mut columns = HashMap::new();
if let Some(live) = self.get(row_id, Epoch(epoch.0.saturating_sub(1))) {
columns = live.columns;
}
let row = Row {
row_id,
committed_epoch: epoch,
columns,
deleted: true,
};
self.upsert(row);
}
pub fn get(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<Row> {
self.tree.get_visible(row_id, snapshot_epoch)
}
pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
self.tree.get_version(row_id, snapshot_epoch)
}
pub fn len(&self) -> usize {
self.tree.mutations()
}
pub fn is_empty(&self) -> bool {
self.tree.is_empty()
}
pub fn approx_bytes(&self) -> u64 {
self.byte_size
}
pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
self.visible_versions(snapshot_epoch)
.into_iter()
.filter(|r| !r.deleted)
.collect()
}
pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
for row in self.tree.versions() {
if row.committed_epoch <= snapshot_epoch {
by_row
.entry(row.row_id)
.and_modify(|e| {
if row.committed_epoch > e.committed_epoch {
*e = row.clone();
}
})
.or_insert(row);
}
}
by_row.into_values().collect()
}
pub fn drain_sorted(&mut self) -> Vec<Row> {
let out = std::mem::take(&mut self.tree).into_sorted_rows();
self.byte_size = 0;
out
}
}
#[cfg(test)]
mod tests {
use super::*;
fn row(id: u64, epoch: u64) -> Row {
Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
}
#[test]
fn upsert_get_and_visibility() {
let mut m = Memtable::new();
m.upsert(row(1, 5));
assert_eq!(m.len(), 1);
assert!(m.get(RowId(1), Epoch(5)).is_some());
assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
#[test]
fn tombstone_supersedes_at_its_epoch() {
let mut m = Memtable::new();
m.upsert(row(1, 1));
assert!(m.get(RowId(1), Epoch(1)).is_some());
m.tombstone(RowId(1), Epoch(2));
assert!(m.get(RowId(1), Epoch(2)).is_none());
assert!(m.get(RowId(1), Epoch(9)).is_none());
assert!(m.get(RowId(1), Epoch(1)).is_some());
}
#[test]
fn drain_sorted_is_ascending_and_empties() {
let mut m = Memtable::new();
m.upsert(row(3, 1));
m.upsert(row(1, 1));
m.upsert(row(2, 1));
let out = m.drain_sorted();
let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
assert_eq!(ids, vec![1, 2, 3]);
assert!(m.is_empty());
assert_eq!(m.approx_bytes(), 0);
}
#[test]
fn visible_rows_dedups_to_newest_version() {
let mut m = Memtable::new();
m.upsert(row(1, 1));
m.upsert(row(2, 9)); m.upsert(row(3, 1));
m.upsert(row(1, 3)); let ids: Vec<u64> = m
.visible_rows(Epoch(5))
.iter()
.map(|r| r.row_id.0)
.collect();
assert_eq!(ids, vec![1, 3]);
}
}