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)]
136pub struct Row {
137 pub row_id: RowId,
138 pub committed_epoch: Epoch,
139 pub columns: HashMap<u16, Value>,
140 pub deleted: bool,
141 #[serde(skip)]
145 pub commit_ts: Option<mongreldb_types::hlc::HlcTimestamp>,
146}
147
148impl Row {
149 pub fn new(row_id: RowId, committed_epoch: Epoch) -> Self {
150 Self {
151 row_id,
152 committed_epoch,
153 columns: HashMap::new(),
154 deleted: false,
155 commit_ts: None,
156 }
157 }
158
159 pub fn new_with_hlc(
160 row_id: RowId,
161 committed_epoch: Epoch,
162 commit_ts: mongreldb_types::hlc::HlcTimestamp,
163 ) -> Self {
164 Self {
165 row_id,
166 committed_epoch,
167 columns: HashMap::new(),
168 deleted: false,
169 commit_ts: Some(commit_ts),
170 }
171 }
172
173 pub fn with_column(mut self, column_id: u16, value: Value) -> Self {
174 self.columns.insert(column_id, value);
175 self
176 }
177
178 pub fn estimated_bytes(&self) -> u64 {
180 self.columns
181 .values()
182 .fold(32, |bytes, value| bytes + value.estimated_bytes())
183 }
184}
185
186#[derive(Clone)]
190struct MemtableSegment {
191 tree: BeTree,
192 byte_size: u64,
193}
194
195#[derive(Clone)]
197pub struct Memtable {
198 frozen: Arc<Vec<Arc<MemtableSegment>>>,
199 active: MemtableSegment,
200 byte_size: u64,
201}
202
203impl Default for Memtable {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209impl Memtable {
210 pub fn new() -> Self {
211 Self {
212 frozen: Arc::new(Vec::new()),
213 active: MemtableSegment {
214 tree: BeTree::new(),
215 byte_size: 0,
216 },
217 byte_size: 0,
218 }
219 }
220
221 pub fn upsert(&mut self, row: Row) {
224 let bytes = row.estimated_bytes();
225 self.byte_size = self.byte_size.saturating_add(bytes);
226 self.active.byte_size = self.active.byte_size.saturating_add(bytes);
227 self.active.tree.insert_row(row);
228 }
229
230 pub fn tombstone(&mut self, row_id: RowId, epoch: Epoch) {
234 let mut columns = HashMap::new();
235 if let Some(live) = self.get(row_id, Epoch(epoch.0.saturating_sub(1))) {
236 columns = live.columns;
237 }
238 let row = Row {
239 row_id,
240 committed_epoch: epoch,
241 columns,
242 deleted: true,
243 commit_ts: None,
244 };
245 self.upsert(row);
246 }
247
248 pub fn get(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<Row> {
252 self.get_version(row_id, snapshot_epoch)
253 .and_then(|(_, row)| (!row.deleted).then_some(row))
254 }
255
256 pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
261 self.get_version_at(row_id, crate::epoch::Snapshot::at(snapshot_epoch))
262 }
263
264 pub fn get_version_at(
271 &self,
272 row_id: RowId,
273 snapshot: crate::epoch::Snapshot,
274 ) -> Option<(Epoch, Row)> {
275 if !snapshot.uses_hlc_authority() {
276 let mut best = self.active.tree.get_version(row_id, snapshot.epoch);
277 for segment in self.frozen.iter().rev() {
278 let Some(candidate) = segment.tree.get_version(row_id, snapshot.epoch) else {
279 continue;
280 };
281 if best.as_ref().is_none_or(|(epoch, _)| candidate.0 > *epoch) {
282 best = Some(candidate);
283 }
284 }
285 return best;
286 }
287
288 let mut best: Option<Row> = None;
289 for segment in self
290 .frozen
291 .iter()
292 .map(|segment| &segment.tree)
293 .chain(std::iter::once(&self.active.tree))
294 {
295 segment.visit_versions(row_id, |row| {
296 if !snapshot.observes_row(row.committed_epoch, row.commit_ts) {
297 return;
298 }
299 if best.as_ref().is_none_or(|current| {
300 crate::epoch::Snapshot::version_is_newer(
301 row.committed_epoch,
302 row.commit_ts,
303 current.committed_epoch,
304 current.commit_ts,
305 )
306 }) {
307 best = Some(row);
308 }
309 });
310 }
311 best.map(|row| (row.committed_epoch, row))
312 }
313
314 pub fn len(&self) -> usize {
316 self.active.tree.mutations()
317 + self
318 .frozen
319 .iter()
320 .map(|segment| segment.tree.mutations())
321 .sum::<usize>()
322 }
323
324 pub fn is_empty(&self) -> bool {
325 self.active.tree.is_empty() && self.frozen.is_empty()
326 }
327
328 pub fn approx_bytes(&self) -> u64 {
329 self.byte_size
330 }
331
332 pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
335 self.visible_versions(snapshot_epoch)
336 .into_iter()
337 .filter(|r| !r.deleted)
338 .collect()
339 }
340
341 pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
345 self.visible_versions_at(crate::epoch::Snapshot::at(snapshot_epoch))
346 }
347
348 pub fn visible_versions_at(&self, snapshot: crate::epoch::Snapshot) -> Vec<Row> {
349 let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
350 for segment in self
351 .frozen
352 .iter()
353 .map(|segment| &segment.tree)
354 .chain(std::iter::once(&self.active.tree))
355 {
356 for row in segment.versions() {
357 if !snapshot.observes_version(row.committed_epoch, row.commit_ts) {
358 continue;
359 }
360 by_row
361 .entry(row.row_id)
362 .and_modify(|existing| {
363 if crate::epoch::Snapshot::version_is_newer(
364 row.committed_epoch,
365 row.commit_ts,
366 existing.committed_epoch,
367 existing.commit_ts,
368 ) {
369 *existing = row.clone();
370 }
371 })
372 .or_insert(row);
373 }
374 }
375 by_row.into_values().collect()
376 }
377
378 pub(crate) fn seal(&mut self) {
380 if self.active.tree.is_empty() {
381 return;
382 }
383 let active = std::mem::replace(
384 &mut self.active,
385 MemtableSegment {
386 tree: BeTree::new(),
387 byte_size: 0,
388 },
389 );
390 Arc::make_mut(&mut self.frozen).push(Arc::new(active));
391 if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
392 self.consolidate();
393 }
394 }
395
396 fn consolidate(&mut self) {
397 let mut tree = BeTree::new();
398 for row in self
399 .frozen
400 .iter()
401 .flat_map(|segment| segment.tree.versions())
402 {
403 tree.insert_row(row);
404 }
405 self.frozen = Arc::new(vec![Arc::new(MemtableSegment {
406 tree,
407 byte_size: self.byte_size,
408 })]);
409 }
410
411 #[cfg(test)]
412 pub(crate) fn frozen_layer_count(&self) -> usize {
413 self.frozen.len()
414 }
415
416 pub fn drain_sorted(&mut self) -> Vec<Row> {
419 let mut out = self
420 .frozen
421 .iter()
422 .flat_map(|segment| segment.tree.versions())
423 .chain(self.active.tree.versions())
424 .collect::<Vec<_>>();
425 out.sort_by_key(|row| (row.row_id, row.committed_epoch));
426 self.frozen = Arc::new(Vec::new());
427 self.active = MemtableSegment {
428 tree: BeTree::new(),
429 byte_size: 0,
430 };
431 self.byte_size = 0;
432 out
433 }
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439
440 fn row(id: u64, epoch: u64) -> Row {
441 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
442 }
443
444 #[test]
445 fn upsert_get_and_visibility() {
446 let mut m = Memtable::new();
447 m.upsert(row(1, 5));
448 assert_eq!(m.len(), 1);
449 assert!(m.get(RowId(1), Epoch(5)).is_some());
450 assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
453
454 #[test]
455 fn tombstone_supersedes_at_its_epoch() {
456 let mut m = Memtable::new();
457 m.upsert(row(1, 1));
458 assert!(m.get(RowId(1), Epoch(1)).is_some());
460 m.tombstone(RowId(1), Epoch(2));
461 assert!(m.get(RowId(1), Epoch(2)).is_none());
463 assert!(m.get(RowId(1), Epoch(9)).is_none());
464 assert!(m.get(RowId(1), Epoch(1)).is_some());
466 }
467
468 #[test]
469 fn sealed_generations_share_rows_and_consolidate() {
470 let mut writer = Memtable::new();
471 for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
472 writer.upsert(row(id, id + 1));
473 writer.seal();
474 }
475 assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
476 let generation = writer.clone();
477 writer.upsert(row(99, 99));
478 assert!(generation.get(RowId(99), Epoch(99)).is_none());
479 assert!(writer.get(RowId(99), Epoch(99)).is_some());
480 }
481
482 #[test]
483 fn hlc_visibility_is_authoritative_when_stamped() {
484 use mongreldb_types::hlc::HlcTimestamp;
485 let mut m = Memtable::new();
486 let early = HlcTimestamp {
487 physical_micros: 100,
488 logical: 0,
489 node_tiebreaker: 1,
490 };
491 let late = HlcTimestamp {
492 physical_micros: 200,
493 logical: 0,
494 node_tiebreaker: 1,
495 };
496 let mut r1 = Row::new_with_hlc(RowId(1), Epoch(1), early);
497 r1.columns.insert(1, Value::Int64(1));
498 let mut r2 = Row::new_with_hlc(RowId(1), Epoch(2), late);
499 r2.columns.insert(1, Value::Int64(2));
500 m.upsert(r1);
501 m.upsert(r2);
502 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
503 let versions = m.visible_versions_at(snap);
504 assert_eq!(versions.len(), 1);
505 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
506 let snap2 = crate::epoch::Snapshot::at_hlc(Epoch(1), late);
507 assert_eq!(
508 m.visible_versions_at(snap2)[0].columns.get(&1),
509 Some(&Value::Int64(2))
510 );
511 }
512
513 #[test]
514 fn snapshot_hlc_hides_later_commit_ts_even_if_epoch_higher() {
515 use mongreldb_types::hlc::HlcTimestamp;
516 let mut m = Memtable::new();
517 let early = HlcTimestamp {
518 physical_micros: 100,
519 logical: 0,
520 node_tiebreaker: 1,
521 };
522 let late = HlcTimestamp {
523 physical_micros: 200,
524 logical: 0,
525 node_tiebreaker: 1,
526 };
527 let mut late_row = Row::new_with_hlc(RowId(1), Epoch(1), late);
530 late_row.columns.insert(1, Value::Int64(99));
531 let mut early_row = Row::new_with_hlc(RowId(1), Epoch(50), early);
532 early_row.columns.insert(1, Value::Int64(1));
533 m.upsert(late_row);
534 m.upsert(early_row);
535 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
536 let versions = m.visible_versions_at(snap);
537 assert_eq!(versions.len(), 1);
538 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
539 assert_eq!(versions[0].commit_ts, Some(early));
540 }
541
542 #[test]
543 fn epoch_only_snapshot_sees_hlc_stamped_rows_by_epoch() {
544 use mongreldb_types::hlc::HlcTimestamp;
545 let mut m = Memtable::new();
546 let ts = HlcTimestamp {
547 physical_micros: 50,
548 logical: 0,
549 node_tiebreaker: 1,
550 };
551 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), ts).with_column(1, Value::Int64(1)));
552 m.upsert(Row::new(RowId(2), Epoch(1)).with_column(1, Value::Int64(2)));
553 let legacy = crate::epoch::Snapshot::at(Epoch(99));
554 let versions = m.visible_versions_at(legacy);
555 assert_eq!(
556 versions.len(),
557 2,
558 "dual-model: epoch pin sees HLC rows by epoch"
559 );
560 assert!(m.get_version_at(RowId(1), legacy).is_some());
561 assert!(m.get_version_at(RowId(2), legacy).is_some());
562 let future = crate::epoch::Snapshot::at(Epoch(0));
563 assert!(m.get_version_at(RowId(1), future).is_none());
564 }
565
566 #[test]
567 fn get_version_at_prefers_hlc_over_epoch_order() {
568 use mongreldb_types::hlc::HlcTimestamp;
569 let mut m = Memtable::new();
570 let early = HlcTimestamp {
571 physical_micros: 100,
572 logical: 0,
573 node_tiebreaker: 1,
574 };
575 let late = HlcTimestamp {
576 physical_micros: 200,
577 logical: 0,
578 node_tiebreaker: 1,
579 };
580 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
581 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
582 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
583 let (_, row) = m.get_version_at(RowId(1), snap).expect("visible");
584 assert_eq!(row.columns.get(&1), Some(&Value::Int64(1)));
585 assert_eq!(row.commit_ts, Some(early));
586 }
587
588 #[test]
592 fn wal_put_row_bincode_matches_0_63_1_layout() {
593 use mongreldb_types::hlc::HlcTimestamp;
594 use serde::{Deserialize, Serialize};
595
596 #[derive(Serialize, Deserialize)]
597 struct LegacyRow {
598 row_id: RowId,
599 committed_epoch: Epoch,
600 columns: HashMap<u16, Value>,
601 deleted: bool,
602 }
603
604 let legacy = LegacyRow {
605 row_id: RowId(7),
606 committed_epoch: Epoch(3),
607 columns: [(1, Value::Int64(42))].into_iter().collect(),
608 deleted: false,
609 };
610 let bytes = bincode::serialize(&legacy).expect("legacy encode");
611
612 let decoded: Row = bincode::deserialize(&bytes).expect("0.63.1 payload must decode");
613 assert_eq!(decoded.row_id, RowId(7));
614 assert_eq!(decoded.committed_epoch, Epoch(3));
615 assert_eq!(decoded.columns.get(&1), Some(&Value::Int64(42)));
616 assert!(!decoded.deleted);
617 assert!(decoded.commit_ts.is_none());
618
619 let stamped = HlcTimestamp {
621 physical_micros: 1_700_000_000_000,
622 logical: 2,
623 node_tiebreaker: 9,
624 };
625 let mut live = Row::new_with_hlc(RowId(7), Epoch(3), stamped);
626 live.columns.insert(1, Value::Int64(42));
627 let wire = bincode::serialize(&live).expect("row encode");
628 assert_eq!(
629 wire, bytes,
630 "WAL Put encoding must match the 0.63.1 four-field layout"
631 );
632 let again: Row = bincode::deserialize(&wire).expect("row decode");
633 assert!(
634 again.commit_ts.is_none(),
635 "commit_ts is restored from Op::CommitTimestamp, not the Put blob"
636 );
637 }
638
639 #[test]
640 fn drain_sorted_is_ascending_and_empties() {
641 let mut m = Memtable::new();
642 m.upsert(row(3, 1));
643 m.upsert(row(1, 1));
644 m.upsert(row(2, 1));
645 let out = m.drain_sorted();
646 let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
647 assert_eq!(ids, vec![1, 2, 3]);
648 assert!(m.is_empty());
649 assert_eq!(m.approx_bytes(), 0);
650 }
651
652 #[test]
653 fn visible_rows_dedups_to_newest_version() {
654 let mut m = Memtable::new();
655 m.upsert(row(1, 1));
656 m.upsert(row(2, 9)); m.upsert(row(3, 1));
658 m.upsert(row(1, 3)); let ids: Vec<u64> = m
660 .visible_rows(Epoch(5))
661 .iter()
662 .map(|r| r.row_id.0)
663 .collect();
664 assert_eq!(ids, vec![1, 3]);
665 }
666}