1use crate::be_tree::BeTree;
13use crate::epoch::Epoch;
14use crate::rowid::RowId;
15use serde::{Deserialize, Serialize};
16use std::collections::{BTreeMap, HashMap};
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub enum Value {
22 Null,
23 Bool(bool),
24 Int64(i64),
25 Float64(f64),
26 Bytes(Vec<u8>),
27 Embedding(Vec<f32>),
28}
29
30impl Value {
31 pub fn encode_key(&self) -> Vec<u8> {
34 match self {
35 Value::Null => Vec::new(),
36 Value::Bool(b) => vec![*b as u8],
37 Value::Int64(n) => n.to_be_bytes().to_vec(),
38 Value::Float64(f) => f.to_bits().to_be_bytes().to_vec(),
39 Value::Bytes(b) => b.clone(),
40 Value::Embedding(v) => {
41 let mut out = Vec::with_capacity(v.len() * 4);
42 for x in v {
43 out.extend_from_slice(&x.to_bits().to_be_bytes());
44 }
45 out
46 }
47 }
48 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Row {
54 pub row_id: RowId,
55 pub committed_epoch: Epoch,
56 pub columns: HashMap<u16, Value>,
57 pub deleted: bool,
58}
59
60impl Row {
61 pub fn new(row_id: RowId, committed_epoch: Epoch) -> Self {
62 Self {
63 row_id,
64 committed_epoch,
65 columns: HashMap::new(),
66 deleted: false,
67 }
68 }
69
70 pub fn with_column(mut self, column_id: u16, value: Value) -> Self {
71 self.columns.insert(column_id, value);
72 self
73 }
74
75 pub fn estimated_bytes(&self) -> u64 {
77 let mut n = 32; for v in self.columns.values() {
79 n += match v {
80 Value::Null => 1,
81 Value::Bool(_) => 1,
82 Value::Int64(_) => 8,
83 Value::Float64(_) => 8,
84 Value::Bytes(b) => 16 + b.len() as u64,
85 Value::Embedding(v) => 16 + (v.len() as u64) * 4,
86 };
87 }
88 n
89 }
90}
91
92pub struct Memtable {
96 tree: BeTree,
97 byte_size: u64,
98}
99
100impl Default for Memtable {
101 fn default() -> Self {
102 Self::new()
103 }
104}
105
106impl Memtable {
107 pub fn new() -> Self {
108 Self {
109 tree: BeTree::new(),
110 byte_size: 0,
111 }
112 }
113
114 pub fn upsert(&mut self, row: Row) {
117 self.byte_size += row.estimated_bytes();
118 self.tree.insert_row(row);
119 }
120
121 pub fn tombstone(&mut self, row_id: RowId, epoch: Epoch) {
125 let mut columns = HashMap::new();
126 if let Some(live) = self.get(row_id, Epoch(epoch.0.saturating_sub(1))) {
127 columns = live.columns;
128 }
129 let row = Row {
130 row_id,
131 committed_epoch: epoch,
132 columns,
133 deleted: true,
134 };
135 self.upsert(row);
136 }
137
138 pub fn get(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<Row> {
142 self.tree.get_visible(row_id, snapshot_epoch)
143 }
144
145 pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
149 self.tree.get_version(row_id, snapshot_epoch)
150 }
151
152 pub fn len(&self) -> usize {
154 self.tree.mutations()
155 }
156
157 pub fn is_empty(&self) -> bool {
158 self.tree.is_empty()
159 }
160
161 pub fn approx_bytes(&self) -> u64 {
162 self.byte_size
163 }
164
165 pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
168 self.visible_versions(snapshot_epoch)
169 .into_iter()
170 .filter(|r| !r.deleted)
171 .collect()
172 }
173
174 pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
178 let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
179 for row in self.tree.versions() {
180 if row.committed_epoch <= snapshot_epoch {
181 by_row
182 .entry(row.row_id)
183 .and_modify(|e| {
184 if row.committed_epoch > e.committed_epoch {
185 *e = row.clone();
186 }
187 })
188 .or_insert(row);
189 }
190 }
191 by_row.into_values().collect()
192 }
193
194 pub fn drain_sorted(&mut self) -> Vec<Row> {
197 let out = std::mem::take(&mut self.tree).into_sorted_rows();
198 self.byte_size = 0;
199 out
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 fn row(id: u64, epoch: u64) -> Row {
208 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
209 }
210
211 #[test]
212 fn upsert_get_and_visibility() {
213 let mut m = Memtable::new();
214 m.upsert(row(1, 5));
215 assert_eq!(m.len(), 1);
216 assert!(m.get(RowId(1), Epoch(5)).is_some());
217 assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
220
221 #[test]
222 fn tombstone_supersedes_at_its_epoch() {
223 let mut m = Memtable::new();
224 m.upsert(row(1, 1));
225 assert!(m.get(RowId(1), Epoch(1)).is_some());
227 m.tombstone(RowId(1), Epoch(2));
228 assert!(m.get(RowId(1), Epoch(2)).is_none());
230 assert!(m.get(RowId(1), Epoch(9)).is_none());
231 assert!(m.get(RowId(1), Epoch(1)).is_some());
233 }
234
235 #[test]
236 fn drain_sorted_is_ascending_and_empties() {
237 let mut m = Memtable::new();
238 m.upsert(row(3, 1));
239 m.upsert(row(1, 1));
240 m.upsert(row(2, 1));
241 let out = m.drain_sorted();
242 let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
243 assert_eq!(ids, vec![1, 2, 3]);
244 assert!(m.is_empty());
245 assert_eq!(m.approx_bytes(), 0);
246 }
247
248 #[test]
249 fn visible_rows_dedups_to_newest_version() {
250 let mut m = Memtable::new();
251 m.upsert(row(1, 1));
252 m.upsert(row(2, 9)); m.upsert(row(3, 1));
254 m.upsert(row(1, 3)); let ids: Vec<u64> = m
256 .visible_rows(Epoch(5))
257 .iter()
258 .map(|r| r.row_id.0)
259 .collect();
260 assert_eq!(ids, vec![1, 3]);
261 }
262}