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(
270 &self,
271 row_id: RowId,
272 snapshot: crate::epoch::Snapshot,
273 ) -> Option<(Epoch, Row)> {
274 let mut best: Option<Row> = None;
275 for segment in self
276 .frozen
277 .iter()
278 .map(|segment| &segment.tree)
279 .chain(std::iter::once(&self.active.tree))
280 {
281 for row in segment.versions() {
282 if row.row_id != row_id {
283 continue;
284 }
285 if !snapshot.observes_row(row.committed_epoch, row.commit_ts) {
286 continue;
287 }
288 if best.as_ref().is_none_or(|current| {
289 crate::epoch::Snapshot::version_is_newer(
290 row.committed_epoch,
291 row.commit_ts,
292 current.committed_epoch,
293 current.commit_ts,
294 )
295 }) {
296 best = Some(row);
297 }
298 }
299 }
300 best.map(|row| (row.committed_epoch, row))
301 }
302
303 pub fn len(&self) -> usize {
305 self.active.tree.mutations()
306 + self
307 .frozen
308 .iter()
309 .map(|segment| segment.tree.mutations())
310 .sum::<usize>()
311 }
312
313 pub fn is_empty(&self) -> bool {
314 self.active.tree.is_empty() && self.frozen.is_empty()
315 }
316
317 pub fn approx_bytes(&self) -> u64 {
318 self.byte_size
319 }
320
321 pub fn visible_rows(&self, snapshot_epoch: Epoch) -> Vec<Row> {
324 self.visible_versions(snapshot_epoch)
325 .into_iter()
326 .filter(|r| !r.deleted)
327 .collect()
328 }
329
330 pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
334 self.visible_versions_at(crate::epoch::Snapshot::at(snapshot_epoch))
335 }
336
337 pub fn visible_versions_at(&self, snapshot: crate::epoch::Snapshot) -> Vec<Row> {
338 let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
339 for segment in self
340 .frozen
341 .iter()
342 .map(|segment| &segment.tree)
343 .chain(std::iter::once(&self.active.tree))
344 {
345 for row in segment.versions() {
346 if !snapshot.observes_version(row.committed_epoch, row.commit_ts) {
347 continue;
348 }
349 by_row
350 .entry(row.row_id)
351 .and_modify(|existing| {
352 if crate::epoch::Snapshot::version_is_newer(
353 row.committed_epoch,
354 row.commit_ts,
355 existing.committed_epoch,
356 existing.commit_ts,
357 ) {
358 *existing = row.clone();
359 }
360 })
361 .or_insert(row);
362 }
363 }
364 by_row.into_values().collect()
365 }
366
367 pub(crate) fn seal(&mut self) {
369 if self.active.tree.is_empty() {
370 return;
371 }
372 let active = std::mem::replace(
373 &mut self.active,
374 MemtableSegment {
375 tree: BeTree::new(),
376 byte_size: 0,
377 },
378 );
379 Arc::make_mut(&mut self.frozen).push(Arc::new(active));
380 if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
381 self.consolidate();
382 }
383 }
384
385 fn consolidate(&mut self) {
386 let mut tree = BeTree::new();
387 for row in self
388 .frozen
389 .iter()
390 .flat_map(|segment| segment.tree.versions())
391 {
392 tree.insert_row(row);
393 }
394 self.frozen = Arc::new(vec![Arc::new(MemtableSegment {
395 tree,
396 byte_size: self.byte_size,
397 })]);
398 }
399
400 #[cfg(test)]
401 pub(crate) fn frozen_layer_count(&self) -> usize {
402 self.frozen.len()
403 }
404
405 pub fn drain_sorted(&mut self) -> Vec<Row> {
408 let mut out = self
409 .frozen
410 .iter()
411 .flat_map(|segment| segment.tree.versions())
412 .chain(self.active.tree.versions())
413 .collect::<Vec<_>>();
414 out.sort_by_key(|row| (row.row_id, row.committed_epoch));
415 self.frozen = Arc::new(Vec::new());
416 self.active = MemtableSegment {
417 tree: BeTree::new(),
418 byte_size: 0,
419 };
420 self.byte_size = 0;
421 out
422 }
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428
429 fn row(id: u64, epoch: u64) -> Row {
430 Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(id as i64 * 10))
431 }
432
433 #[test]
434 fn upsert_get_and_visibility() {
435 let mut m = Memtable::new();
436 m.upsert(row(1, 5));
437 assert_eq!(m.len(), 1);
438 assert!(m.get(RowId(1), Epoch(5)).is_some());
439 assert!(m.get(RowId(1), Epoch(4)).is_none()); assert!(m.get(RowId(2), Epoch(9)).is_none()); }
442
443 #[test]
444 fn tombstone_supersedes_at_its_epoch() {
445 let mut m = Memtable::new();
446 m.upsert(row(1, 1));
447 assert!(m.get(RowId(1), Epoch(1)).is_some());
449 m.tombstone(RowId(1), Epoch(2));
450 assert!(m.get(RowId(1), Epoch(2)).is_none());
452 assert!(m.get(RowId(1), Epoch(9)).is_none());
453 assert!(m.get(RowId(1), Epoch(1)).is_some());
455 }
456
457 #[test]
458 fn sealed_generations_share_rows_and_consolidate() {
459 let mut writer = Memtable::new();
460 for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
461 writer.upsert(row(id, id + 1));
462 writer.seal();
463 }
464 assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
465 let generation = writer.clone();
466 writer.upsert(row(99, 99));
467 assert!(generation.get(RowId(99), Epoch(99)).is_none());
468 assert!(writer.get(RowId(99), Epoch(99)).is_some());
469 }
470
471 #[test]
472 fn hlc_visibility_is_authoritative_when_stamped() {
473 use mongreldb_types::hlc::HlcTimestamp;
474 let mut m = Memtable::new();
475 let early = HlcTimestamp {
476 physical_micros: 100,
477 logical: 0,
478 node_tiebreaker: 1,
479 };
480 let late = HlcTimestamp {
481 physical_micros: 200,
482 logical: 0,
483 node_tiebreaker: 1,
484 };
485 let mut r1 = Row::new_with_hlc(RowId(1), Epoch(1), early);
486 r1.columns.insert(1, Value::Int64(1));
487 let mut r2 = Row::new_with_hlc(RowId(1), Epoch(2), late);
488 r2.columns.insert(1, Value::Int64(2));
489 m.upsert(r1);
490 m.upsert(r2);
491 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
492 let versions = m.visible_versions_at(snap);
493 assert_eq!(versions.len(), 1);
494 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
495 let snap2 = crate::epoch::Snapshot::at_hlc(Epoch(1), late);
496 assert_eq!(
497 m.visible_versions_at(snap2)[0].columns.get(&1),
498 Some(&Value::Int64(2))
499 );
500 }
501
502 #[test]
503 fn snapshot_hlc_hides_later_commit_ts_even_if_epoch_higher() {
504 use mongreldb_types::hlc::HlcTimestamp;
505 let mut m = Memtable::new();
506 let early = HlcTimestamp {
507 physical_micros: 100,
508 logical: 0,
509 node_tiebreaker: 1,
510 };
511 let late = HlcTimestamp {
512 physical_micros: 200,
513 logical: 0,
514 node_tiebreaker: 1,
515 };
516 let mut late_row = Row::new_with_hlc(RowId(1), Epoch(1), late);
519 late_row.columns.insert(1, Value::Int64(99));
520 let mut early_row = Row::new_with_hlc(RowId(1), Epoch(50), early);
521 early_row.columns.insert(1, Value::Int64(1));
522 m.upsert(late_row);
523 m.upsert(early_row);
524 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
525 let versions = m.visible_versions_at(snap);
526 assert_eq!(versions.len(), 1);
527 assert_eq!(versions[0].columns.get(&1), Some(&Value::Int64(1)));
528 assert_eq!(versions[0].commit_ts, Some(early));
529 }
530
531 #[test]
532 fn epoch_only_snapshot_sees_hlc_stamped_rows_by_epoch() {
533 use mongreldb_types::hlc::HlcTimestamp;
534 let mut m = Memtable::new();
535 let ts = HlcTimestamp {
536 physical_micros: 50,
537 logical: 0,
538 node_tiebreaker: 1,
539 };
540 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), ts).with_column(1, Value::Int64(1)));
541 m.upsert(Row::new(RowId(2), Epoch(1)).with_column(1, Value::Int64(2)));
542 let legacy = crate::epoch::Snapshot::at(Epoch(99));
543 let versions = m.visible_versions_at(legacy);
544 assert_eq!(
545 versions.len(),
546 2,
547 "dual-model: epoch pin sees HLC rows by epoch"
548 );
549 assert!(m.get_version_at(RowId(1), legacy).is_some());
550 assert!(m.get_version_at(RowId(2), legacy).is_some());
551 let future = crate::epoch::Snapshot::at(Epoch(0));
552 assert!(m.get_version_at(RowId(1), future).is_none());
553 }
554
555 #[test]
556 fn get_version_at_prefers_hlc_over_epoch_order() {
557 use mongreldb_types::hlc::HlcTimestamp;
558 let mut m = Memtable::new();
559 let early = HlcTimestamp {
560 physical_micros: 100,
561 logical: 0,
562 node_tiebreaker: 1,
563 };
564 let late = HlcTimestamp {
565 physical_micros: 200,
566 logical: 0,
567 node_tiebreaker: 1,
568 };
569 m.upsert(Row::new_with_hlc(RowId(1), Epoch(1), late).with_column(1, Value::Int64(99)));
570 m.upsert(Row::new_with_hlc(RowId(1), Epoch(50), early).with_column(1, Value::Int64(1)));
571 let snap = crate::epoch::Snapshot::at_hlc(Epoch(99), early);
572 let (_, row) = m.get_version_at(RowId(1), snap).expect("visible");
573 assert_eq!(row.columns.get(&1), Some(&Value::Int64(1)));
574 assert_eq!(row.commit_ts, Some(early));
575 }
576
577 #[test]
581 fn wal_put_row_bincode_matches_0_63_1_layout() {
582 use mongreldb_types::hlc::HlcTimestamp;
583 use serde::{Deserialize, Serialize};
584
585 #[derive(Serialize, Deserialize)]
586 struct LegacyRow {
587 row_id: RowId,
588 committed_epoch: Epoch,
589 columns: HashMap<u16, Value>,
590 deleted: bool,
591 }
592
593 let legacy = LegacyRow {
594 row_id: RowId(7),
595 committed_epoch: Epoch(3),
596 columns: [(1, Value::Int64(42))].into_iter().collect(),
597 deleted: false,
598 };
599 let bytes = bincode::serialize(&legacy).expect("legacy encode");
600
601 let decoded: Row = bincode::deserialize(&bytes).expect("0.63.1 payload must decode");
602 assert_eq!(decoded.row_id, RowId(7));
603 assert_eq!(decoded.committed_epoch, Epoch(3));
604 assert_eq!(decoded.columns.get(&1), Some(&Value::Int64(42)));
605 assert!(!decoded.deleted);
606 assert!(decoded.commit_ts.is_none());
607
608 let stamped = HlcTimestamp {
610 physical_micros: 1_700_000_000_000,
611 logical: 2,
612 node_tiebreaker: 9,
613 };
614 let mut live = Row::new_with_hlc(RowId(7), Epoch(3), stamped);
615 live.columns.insert(1, Value::Int64(42));
616 let wire = bincode::serialize(&live).expect("row encode");
617 assert_eq!(
618 wire, bytes,
619 "WAL Put encoding must match the 0.63.1 four-field layout"
620 );
621 let again: Row = bincode::deserialize(&wire).expect("row decode");
622 assert!(
623 again.commit_ts.is_none(),
624 "commit_ts is restored from Op::CommitTimestamp, not the Put blob"
625 );
626 }
627
628 #[test]
629 fn drain_sorted_is_ascending_and_empties() {
630 let mut m = Memtable::new();
631 m.upsert(row(3, 1));
632 m.upsert(row(1, 1));
633 m.upsert(row(2, 1));
634 let out = m.drain_sorted();
635 let ids: Vec<u64> = out.iter().map(|r| r.row_id.0).collect();
636 assert_eq!(ids, vec![1, 2, 3]);
637 assert!(m.is_empty());
638 assert_eq!(m.approx_bytes(), 0);
639 }
640
641 #[test]
642 fn visible_rows_dedups_to_newest_version() {
643 let mut m = Memtable::new();
644 m.upsert(row(1, 1));
645 m.upsert(row(2, 9)); m.upsert(row(3, 1));
647 m.upsert(row(1, 3)); let ids: Vec<u64> = m
649 .visible_rows(Epoch(5))
650 .iter()
651 .map(|r| r.row_id.0)
652 .collect();
653 assert_eq!(ids, vec![1, 3]);
654 }
655}