nodedb_lite/engine/columnar/
truncate.rs1use nodedb_types::Namespace;
10
11use crate::error::LiteError;
12use crate::storage::engine::{StorageEngine, WriteOp};
13
14use super::store::{ColumnarEngine, SegmentMeta, remove_segment_bytes};
15
16impl<S: StorageEngine> ColumnarEngine<S> {
17 pub async fn truncate(&self, collection: &str) -> Result<usize, LiteError> {
24 let state_arc = self.lookup(collection)?;
25
26 let (segments, truncated): (Vec<SegmentMeta>, usize) = {
29 let mut s = Self::lock_state(&state_arc)?;
30 let segment_rows: u64 = s
31 .segments
32 .iter()
33 .filter(|m| m.fully_deleted_at_ms.is_none())
34 .map(|m| m.row_count)
35 .sum();
36 let truncated = segment_rows as usize + s.mutation.memtable().row_count();
37 drop(s.mutation.truncate());
39 (std::mem::take(&mut s.segments), truncated)
40 };
41
42 for seg in &segments {
43 if seg.fully_deleted_at_ms.is_none() {
44 remove_segment_bytes(&*self.storage, collection, seg.segment_id).await?;
45 }
46 }
47
48 let mut ops: Vec<WriteOp> = Vec::with_capacity(segments.len() + 1);
49 for seg in &segments {
50 ops.push(WriteOp::Delete {
51 ns: Namespace::Columnar,
52 key: format!("{collection}:del:{}", seg.segment_id).into_bytes(),
53 });
54 }
55 ops.push(WriteOp::Delete {
56 ns: Namespace::Columnar,
57 key: format!("{collection}:meta").into_bytes(),
58 });
59 self.storage.batch_write(&ops).await?;
60
61 Ok(truncated)
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use std::sync::Arc;
68
69 use nodedb_types::columnar::{ColumnDef, ColumnType, ColumnarProfile, ColumnarSchema};
70 use nodedb_types::value::Value;
71
72 use super::*;
73 use crate::PagedbStorageMem;
74 use crate::query::engine::{test_governor, test_scoped_memory};
75
76 async fn engine() -> ColumnarEngine<PagedbStorageMem> {
77 let storage = Arc::new(
78 PagedbStorageMem::open_in_memory()
79 .await
80 .expect("in-memory pagedb"),
81 );
82 let governor = test_governor();
83 let engine = ColumnarEngine::new(
84 storage,
85 test_scoped_memory(&governor, nodedb_mem::EngineId::Columnar),
86 );
87 let schema = ColumnarSchema::new(vec![
88 ColumnDef::required("id", ColumnType::Int64).with_primary_key(),
89 ColumnDef::nullable("v", ColumnType::Int64),
90 ])
91 .expect("schema");
92 engine
93 .create_collection("t", schema, ColumnarProfile::Plain, false)
94 .await
95 .expect("create");
96 engine
97 }
98
99 fn row(i: i64) -> Vec<Value> {
100 vec![Value::Integer(i), Value::Integer(i * 10)]
101 }
102
103 #[tokio::test]
104 async fn truncate_empties_memtable_and_keeps_schema() {
105 let engine = engine().await;
106 for i in 1..=3 {
107 engine.insert("t", &row(i)).expect("insert");
108 }
109 assert_eq!(engine.truncate("t").await.expect("truncate"), 3);
110 assert_eq!(engine.row_count("t"), 0);
111 assert!(engine.list_rows("t").await.expect("rows").is_empty());
112 assert!(engine.schema("t").is_some(), "schema survives");
113 engine.insert("t", &row(1)).expect("insert after truncate");
114 assert_eq!(engine.row_count("t"), 1);
115 }
116
117 #[tokio::test]
118 async fn truncate_drops_flushed_segments() {
119 let engine = engine().await;
120 for i in 1..=3 {
121 engine.insert("t", &row(i)).expect("insert");
122 }
123 engine.flush_collection("t").await.expect("flush");
124 engine.insert("t", &row(4)).expect("insert");
125 assert_eq!(engine.truncate("t").await.expect("truncate"), 4);
126 assert!(
127 engine
128 .read_segments("t")
129 .await
130 .expect("segments")
131 .is_empty()
132 );
133 assert!(engine.list_rows("t").await.expect("rows").is_empty());
134 let meta = engine
135 .storage
136 .get(Namespace::Columnar, b"t:meta")
137 .await
138 .expect("get");
139 assert!(meta.is_none(), "segment metadata is removed");
140 }
141
142 #[tokio::test]
143 async fn truncate_unknown_collection_is_an_error() {
144 let engine = engine().await;
145 assert!(engine.truncate("nope").await.is_err());
146 }
147}