nodedb_columnar/mutation/
truncate.rs1use std::collections::HashMap;
7
8use nodedb_types::surrogate::Surrogate;
9
10use crate::delete_bitmap::DeleteBitmap;
11use crate::memtable::ColumnarMemtable;
12use crate::pk_index::PkIndex;
13
14use super::engine::MutationEngine;
15
16pub struct TruncatedRows {
21 memtable: ColumnarMemtable,
22 pk_index: PkIndex,
23 delete_bitmaps: HashMap<u64, DeleteBitmap>,
24 memtable_row_counter: u32,
25 memtable_surrogates: Vec<Option<Surrogate>>,
26}
27
28impl MutationEngine {
29 pub fn live_row_count(&self) -> usize {
32 self.pk_index.len()
33 }
34
35 pub fn truncate(&mut self) -> TruncatedRows {
41 let threshold = self.memtable.flush_threshold();
42 let memtable = std::mem::replace(
43 &mut self.memtable,
44 ColumnarMemtable::with_threshold(&self.schema, threshold),
45 );
46 TruncatedRows {
47 memtable,
48 pk_index: std::mem::take(&mut self.pk_index),
49 delete_bitmaps: std::mem::take(&mut self.delete_bitmaps),
50 memtable_row_counter: std::mem::replace(&mut self.memtable_row_counter, 0),
51 memtable_surrogates: std::mem::take(&mut self.memtable_surrogates),
52 }
53 }
54
55 pub fn restore_truncated(&mut self, rows: TruncatedRows) {
58 let TruncatedRows {
59 memtable,
60 pk_index,
61 delete_bitmaps,
62 memtable_row_counter,
63 memtable_surrogates,
64 } = rows;
65 self.memtable = memtable;
66 self.pk_index = pk_index;
67 self.delete_bitmaps = delete_bitmaps;
68 self.memtable_row_counter = memtable_row_counter;
69 self.memtable_surrogates = memtable_surrogates;
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use nodedb_types::columnar::{ColumnDef, ColumnType, ColumnarSchema};
76 use nodedb_types::value::Value;
77
78 use super::*;
79
80 fn schema() -> ColumnarSchema {
81 ColumnarSchema::new(vec![
82 ColumnDef::required("id", ColumnType::Int64).with_primary_key(),
83 ColumnDef::required("name", ColumnType::String),
84 ])
85 .expect("valid")
86 }
87
88 fn seeded() -> MutationEngine {
89 let mut engine = MutationEngine::with_flush_threshold("t".into(), schema(), 7);
90 for i in 0..4 {
91 engine
92 .insert_with_surrogate(
93 &[Value::Integer(i), Value::String(format!("r{i}"))],
94 Surrogate(100 + i as u32),
95 )
96 .expect("insert");
97 }
98 engine.delete(&Value::Integer(1)).expect("delete");
99 engine
100 }
101
102 #[test]
103 fn truncate_empties_every_row_bearing_part_and_keeps_the_threshold() {
104 let mut engine = seeded();
105 assert_eq!(engine.live_row_count(), 3);
106
107 let _pre = engine.truncate();
108
109 assert_eq!(engine.live_row_count(), 0);
110 assert_eq!(engine.memtable().row_count(), 0);
111 assert!(engine.delete_bitmaps().is_empty());
112 assert!(engine.memtable_surrogates().is_empty());
113 assert_eq!(engine.scan_memtable_rows().count(), 0);
114 assert_eq!(engine.memtable().flush_threshold(), 7);
115 assert_eq!(engine.next_segment_id(), 1);
116
117 engine
118 .insert(&[Value::Integer(9), Value::String("z".into())])
119 .expect("insert after truncate");
120 assert_eq!(engine.live_row_count(), 1);
121 assert_eq!(engine.scan_memtable_rows().count(), 1);
122 }
123
124 #[test]
125 fn restore_truncated_brings_back_rows_and_tombstones_exactly() {
126 let mut engine = seeded();
127 let before: Vec<Vec<Value>> = engine
128 .scan_memtable_rows()
129 .collect::<Result<_, _>>()
130 .expect("read");
131
132 let pre = engine.truncate();
133 engine
134 .insert(&[Value::Integer(9), Value::String("z".into())])
135 .expect("insert after truncate");
136 engine.restore_truncated(pre);
137
138 let after: Vec<Vec<Value>> = engine
139 .scan_memtable_rows()
140 .collect::<Result<_, _>>()
141 .expect("read");
142 assert_eq!(
143 after, before,
144 "restore must reproduce the pre-truncate rows"
145 );
146 assert_eq!(engine.live_row_count(), 3);
147 assert!(
148 engine
149 .pk_index()
150 .get(&crate::pk_index::encode_pk(&Value::Integer(1)))
151 .is_none(),
152 "the tombstone deleted before the truncate stays deleted"
153 );
154 assert_eq!(engine.memtable_surrogates().len(), 4);
155 }
156
157 #[test]
158 fn truncate_keeps_segment_ids_monotonic_after_a_flush() {
159 let mut engine = seeded();
160 engine.on_memtable_flushed(1).expect("flush");
161 assert_eq!(engine.next_segment_id(), 2);
162 let _pre = engine.truncate();
163 assert_eq!(engine.next_segment_id(), 2);
164 }
165}