1use crate::be_tree::BeTree;
14use crate::epoch::Epoch;
15use crate::rowid::RowId;
16use serde::{Deserialize, Serialize};
17use std::collections::{BTreeMap, HashMap};
18use std::sync::Arc;
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub enum Value {
24 Null,
25 Bool(bool),
26 Int64(i64),
27 Float64(f64),
28 Bytes(Vec<u8>),
29 Embedding(Vec<f32>),
30 Decimal(i128),
33 Interval {
35 months: i64,
36 days: i32,
37 nanos: i64,
38 },
39 Uuid([u8; 16]),
41 Json(Vec<u8>),
43 GeneratedEmbedding(Box<crate::embedding::GeneratedEmbeddingValue>),
47}
48
49impl Value {
50 pub fn as_embedding(&self) -> Option<&[f32]> {
51 match self {
52 Self::Embedding(values) => Some(values),
53 Self::GeneratedEmbedding(value) => Some(&value.vector),
54 _ => None,
55 }
56 }
57
58 pub fn generated_embedding_metadata(
59 &self,
60 ) -> Option<&crate::embedding::GeneratedEmbeddingMetadata> {
61 match self {
62 Self::GeneratedEmbedding(value) => Some(&value.metadata),
63 _ => None,
64 }
65 }
66
67 pub fn encode_key(&self) -> Vec<u8> {
70 match self {
71 Value::Null => Vec::new(),
72 Value::Bool(b) => vec![*b as u8],
73 Value::Int64(n) => n.to_be_bytes().to_vec(),
74 Value::Float64(f) => f.to_bits().to_be_bytes().to_vec(),
75 Value::Bytes(b) => b.clone(),
76 Value::Embedding(v) => {
77 let mut out = Vec::with_capacity(v.len() * 4);
78 for x in v {
79 out.extend_from_slice(&x.to_bits().to_be_bytes());
80 }
81 out
82 }
83 Value::GeneratedEmbedding(value) => {
84 let mut out = Vec::with_capacity(value.vector.len() * 4);
85 for x in &value.vector {
86 out.extend_from_slice(&x.to_bits().to_be_bytes());
87 }
88 out
89 }
90 Value::Decimal(d) => d.to_be_bytes().to_vec(),
91 Value::Interval {
92 months,
93 days,
94 nanos,
95 } => {
96 let mut out = Vec::with_capacity(20);
97 out.extend_from_slice(&months.to_be_bytes());
98 out.extend_from_slice(&days.to_be_bytes());
99 out.extend_from_slice(&nanos.to_be_bytes());
100 out
101 }
102 Value::Uuid(b) => b.to_vec(),
103 Value::Json(b) => b.clone(),
104 }
105 }
106
107 pub(crate) fn estimated_bytes(&self) -> u64 {
108 match self {
109 Value::Null => 1,
110 Value::Bool(_) => 1,
111 Value::Int64(_) | Value::Float64(_) => 8,
112 Value::Bytes(bytes) | Value::Json(bytes) => 16 + bytes.len() as u64,
113 Value::Embedding(values) => 16 + (values.len() as u64) * 4,
114 Value::GeneratedEmbedding(value) => {
115 16 + (value.vector.len() as u64) * 4
116 + value.metadata.provider_id.len() as u64
117 + value.metadata.model_id.len() as u64
118 + value.metadata.model_version.len() as u64
119 + value.metadata.preprocessing_version.len() as u64
120 + 48
121 }
122 Value::Decimal(_) | Value::Uuid(_) => 16,
123 Value::Interval { .. } => 20,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct Row {
131 pub row_id: RowId,
132 pub committed_epoch: Epoch,
133 #[serde(default)]
136 pub commit_ts: Option<mongreldb_types::hlc::HlcTimestamp>,
137 pub columns: HashMap<u16, Value>,
138 pub deleted: bool,
139}
140
141impl Row {
142 pub fn new(row_id: RowId, committed_epoch: Epoch) -> Self {
143 Self {
144 row_id,
145 committed_epoch,
146 commit_ts: None,
147 columns: HashMap::new(),
148 deleted: false,
149 }
150 }
151
152 pub fn new_with_hlc(
153 row_id: RowId,
154 committed_epoch: Epoch,
155 commit_ts: mongreldb_types::hlc::HlcTimestamp,
156 ) -> Self {
157 Self {
158 row_id,
159 committed_epoch,
160 commit_ts: Some(commit_ts),
161 columns: HashMap::new(),
162 deleted: false,
163 }
164 }
165
166 pub fn with_column(mut self, column_id: u16, value: Value) -> Self {
167 self.columns.insert(column_id, value);
168 self
169 }
170
171 pub fn estimated_bytes(&self) -> u64 {
173 self.columns
174 .values()
175 .fold(32, |bytes, value| bytes + value.estimated_bytes())
176 }
177}
178
179#[derive(Clone)]
183struct MemtableSegment {
184 tree: BeTree,
185 byte_size: u64,
186}
187
188#[derive(Clone)]
190pub struct Memtable {
191 frozen: Arc<Vec<Arc<MemtableSegment>>>,
192 active: MemtableSegment,
193 byte_size: u64,
194}
195
196impl Default for Memtable {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202impl Memtable {
203 pub fn new() -> Self {
204 Self {
205 frozen: Arc::new(Vec::new()),
206 active: MemtableSegment {
207 tree: BeTree::new(),
208 byte_size: 0,
209 },
210 byte_size: 0,
211 }
212 }
213
214 pub fn upsert(&mut self, row: Row) {
217 let bytes = row.estimated_bytes();
218 self.byte_size = self.byte_size.saturating_add(bytes);
219 self.active.byte_size = self.active.byte_size.saturating_add(bytes);
220 self.active.tree.insert_row(row);
221 }
222
223 pub fn tombstone(&mut self, row_id: RowId, epoch: Epoch) {
227 let mut columns = HashMap::new();
228 if let Some(live) = self.get(row_id, Epoch(epoch.0.saturating_sub(1))) {
229 columns = live.columns;
230 }
231 let row = Row {
232 row_id,
233 committed_epoch: epoch,
234 commit_ts: None,
235 columns,
236 deleted: true,
237 };
238 self.upsert(row);
239 }
240
241 pub fn get(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<Row> {
245 self.get_version(row_id, snapshot_epoch)
246 .and_then(|(_, row)| (!row.deleted).then_some(row))
247 }
248
249 pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
254 self.get_version_at(row_id, crate::epoch::Snapshot::at(snapshot_epoch))
255 }
256
257 pub fn get_version_at(
263 &self,
264 row_id: RowId,
265 snapshot: crate::epoch::Snapshot,
266 ) -> Option<(Epoch, Row)> {
267 let mut best: Option<Row> = None;
268 for segment in self
269 .frozen
270 .iter()
271 .map(|segment| &segment.tree)
272 .chain(std::iter::once(&self.active.tree))
273 {
274 for row in segment.versions() {
275 if row.row_id != row_id {
276 continue;
277 }
278 if !snapshot.observes_row(row.committed_epoch, row.commit_ts) {
279 continue;
280 }
281 if best.as_ref().is_none_or(|current| {
282 crate::epoch::Snapshot::version_is_newer(
283 row.committed_epoch,
284 row.commit_ts,
285 current.committed_epoch,
286 current.commit_ts,
287 )
288 }) {
289 best = Some(row);
290 }
291 }
292 }
293 best.map(|row| (row.committed_epoch, row))
294 }
295
296 pub fn len(&self) -> usize {
298 self.active.tree.mutations()
299 + self
300 .frozen
301 .iter()
302 .map(|segment| segment.tree.mutations())
303 .sum::<usize>()
304 }
305
306 pub fn is_empty(&self) -> bool {
307 self.active.tree.is_empty() && self.frozen.is_empty()
308 }
309
310 pub fn approx_bytes(&self) -> u64 {
311 self.byte_size
312 }
313
314 pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
317 self.visible_versions(snapshot_epoch)
318 .into_iter()
319 .filter(|r| !r.deleted)
320 .collect()
321 }
322
323 pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
327 self.visible_versions_at(crate::epoch::Snapshot::at(snapshot_epoch))
328 }
329
330 pub fn visible_versions_at(&self, snapshot: crate::epoch::Snapshot) -> Vec<Row> {
331 let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
332 for segment in self
333 .frozen
334 .iter()
335 .map(|segment| &segment.tree)
336 .chain(std::iter::once(&self.active.tree))
337 {
338 for row in segment.versions() {
339 if !snapshot.observes_version(row.committed_epoch, row.commit_ts) {
340 continue;
341 }
342 by_row
343 .entry(row.row_id)
344 .and_modify(|existing| {
345 if crate::epoch::Snapshot::version_is_newer(
346 row.committed_epoch,
347 row.commit_ts,
348 existing.committed_epoch,
349 existing.commit_ts,
350 ) {
351 *existing = row.clone();
352 }
353 })
354 .or_insert(row);
355 }
356 }
357 by_row.into_values().collect()
358 }
359
360 pub(crate) fn seal(&mut self) {
362 if self.active.tree.is_empty() {
363 return;
364 }
365 let active = std::mem::replace(
366 &mut self.active,
367 MemtableSegment {
368 tree: BeTree::new(),
369 byte_size: 0,
370 },
371 );
372 Arc::make_mut(&mut self.frozen).push(Arc::new(active));
373 if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
374 self.consolidate();
375 }
376 }
377
378 fn consolidate(&mut self) {
379 let mut tree = BeTree::new();
380 for row in self
381 .frozen
382 .iter()
383 .flat_map(|segment| segment.tree.versions())
384 {
385 tree.insert_row(row);
386 }
387 self.frozen = Arc::new(vec![Arc::new(MemtableSegment {
388 tree,
389 byte_size: self.byte_size,
390 })]);
391 }
392
393 #[cfg(test)]
394 pub(crate) fn frozen_layer_count(&self) -> usize {
395 self.frozen.len()
396 }
397
398 pub fn drain_sorted(&mut self) -> Vec<Row> {
401 let mut out = self
402 .frozen
403 .iter()
404 .flat_map(|segment| segment.tree.versions())
405 .chain(self.active.tree.versions())
406 .collect::<Vec<_>>();
407 out.sort_by_key(|row| (row.row_id, row.committed_epoch));
408 self.frozen = Arc::new(Vec::new());
409 self.active = MemtableSegment {
410 tree: BeTree::new(),
411 byte_size: 0,
412 };
413 self.byte_size = 0;
414 out
415 }
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 fn row(id: u64, epoch: u64) -> Row {
423 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
424 }
425
426 #[test]
427 fn upsert_get_and_visibility() {
428 let mut m = Memtable::new();
429 m.upsert(row(1, 5));
430 assert_eq!(m.len(), 1);
431 assert!(m.get(RowId(1), Epoch(5)).is_some());
432 assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
435
436 #[test]
437 fn tombstone_supersedes_at_its_epoch() {
438 let mut m = Memtable::new();
439 m.upsert(row(1, 1));
440 assert!(m.get(RowId(1), Epoch(1)).is_some());
442 m.tombstone(RowId(1), Epoch(2));
443 assert!(m.get(RowId(1), Epoch(2)).is_none());
445 assert!(m.get(RowId(1), Epoch(9)).is_none());
446 assert!(m.get(RowId(1), Epoch(1)).is_some());
448 }
449
450 #[test]
451 fn sealed_generations_share_rows_and_consolidate() {
452 let mut writer = Memtable::new();
453 for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
454 writer.upsert(row(id, id + 1));
455 writer.seal();
456 }
457 assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
458 let generation = writer.clone();
459 writer.upsert(row(99, 99));
460 assert!(generation.get(RowId(99), Epoch(99)).is_none());
461 assert!(writer.get(RowId(99), Epoch(99)).is_some());
462 }
463
464 #[test]
465 fn hlc_visibility_is_authoritative_when_stamped() {
466 use mongreldb_types::hlc::HlcTimestamp;
467 let mut m = Memtable::new();
468 let early = HlcTimestamp {
469 physical_micros: 100,
470 logical: 0,
471 node_tiebreaker: 1,
472 };
473 let late = HlcTimestamp {
474 physical_micros: 200,
475 logical: 0,
476 node_tiebreaker: 1,
477 };
478 let mut r1 = Row::new_with_hlc(RowId(1), Epoch(1), early);
479 r1.columns.insert(1, Value::Int64(1));
480 let mut r2 = Row::new_with_hlc(RowId(1), Epoch(2), late);
481 r2.columns.insert(1, Value::Int64(2));
482 m.upsert(r1);
483 m.upsert(r2);
484 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
485 let versions = m.visible_versions_at(snap);
486 assert_eq!(versions.len(), 1);
487 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
488 let snap2 = crate::epoch::Snapshot::at_hlc(Epoch(1), late);
489 assert_eq!(
490 m.visible_versions_at(snap2)[0].columns.get(&1),
491 Some(&Value::Int64(2))
492 );
493 }
494
495 #[test]
496 fn snapshot_hlc_hides_later_commit_ts_even_if_epoch_higher() {
497 use mongreldb_types::hlc::HlcTimestamp;
498 let mut m = Memtable::new();
499 let early = HlcTimestamp {
500 physical_micros: 100,
501 logical: 0,
502 node_tiebreaker: 1,
503 };
504 let late = HlcTimestamp {
505 physical_micros: 200,
506 logical: 0,
507 node_tiebreaker: 1,
508 };
509 let mut late_row = Row::new_with_hlc(RowId(1), Epoch(1), late);
512 late_row.columns.insert(1, Value::Int64(99));
513 let mut early_row = Row::new_with_hlc(RowId(1), Epoch(50), early);
514 early_row.columns.insert(1, Value::Int64(1));
515 m.upsert(late_row);
516 m.upsert(early_row);
517 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
518 let versions = m.visible_versions_at(snap);
519 assert_eq!(versions.len(), 1);
520 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
521 assert_eq!(versions[0].commit_ts, Some(early));
522 }
523
524 #[test]
525 fn epoch_only_snapshot_sees_hlc_stamped_rows_by_epoch() {
526 use mongreldb_types::hlc::HlcTimestamp;
527 let mut m = Memtable::new();
528 let ts = HlcTimestamp {
529 physical_micros: 50,
530 logical: 0,
531 node_tiebreaker: 1,
532 };
533 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), ts).with_column(1, Value::Int64(1)));
534 m.upsert(Row::new(RowId(2), Epoch(1)).with_column(1, Value::Int64(2)));
535 let legacy = crate::epoch::Snapshot::at(Epoch(99));
536 let versions = m.visible_versions_at(legacy);
537 assert_eq!(
538 versions.len(),
539 2,
540 "dual-model: epoch pin sees HLC rows by epoch"
541 );
542 assert!(m.get_version_at(RowId(1), legacy).is_some());
543 assert!(m.get_version_at(RowId(2), legacy).is_some());
544 let future = crate::epoch::Snapshot::at(Epoch(0));
545 assert!(m.get_version_at(RowId(1), future).is_none());
546 }
547
548 #[test]
549 fn get_version_at_prefers_hlc_over_epoch_order() {
550 use mongreldb_types::hlc::HlcTimestamp;
551 let mut m = Memtable::new();
552 let early = HlcTimestamp {
553 physical_micros: 100,
554 logical: 0,
555 node_tiebreaker: 1,
556 };
557 let late = HlcTimestamp {
558 physical_micros: 200,
559 logical: 0,
560 node_tiebreaker: 1,
561 };
562 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
563 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
564 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
565 let (_, row) = m.get_version_at(RowId(1), snap).expect("visible");
566 assert_eq!(row.columns.get(&1), Some(&Value::Int64(1)));
567 assert_eq!(row.commit_ts, Some(early));
568 }
569
570 #[test]
571 fn drain_sorted_is_ascending_and_empties() {
572 let mut m = Memtable::new();
573 m.upsert(row(3, 1));
574 m.upsert(row(1, 1));
575 m.upsert(row(2, 1));
576 let out = m.drain_sorted();
577 let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
578 assert_eq!(ids, vec![1, 2, 3]);
579 assert!(m.is_empty());
580 assert_eq!(m.approx_bytes(), 0);
581 }
582
583 #[test]
584 fn visible_rows_dedups_to_newest_version() {
585 let mut m = Memtable::new();
586 m.upsert(row(1, 1));
587 m.upsert(row(2, 9)); m.upsert(row(3, 1));
589 m.upsert(row(1, 3)); let ids: Vec<u64> = m
591 .visible_rows(Epoch(5))
592 .iter()
593 .map(|r| r.row_id.0)
594 .collect();
595 assert_eq!(ids, vec![1, 3]);
596 }
597}