1use std::collections::HashMap;
4use std::fs::{rename, OpenOptions};
5use std::io::Write;
6use std::path::Path;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::sync::RwLock;
9
10use crate::columnar::error::{ColumnarError, Result};
11use crate::columnar::kvs_bridge::ColumnarKvsBridge;
12use crate::columnar::segment_v2::{
13 ColumnSegmentV2, InMemorySegmentSource, RecordBatch, Schema, SegmentReaderV2,
14};
15use crate::storage::format::{AlopexFileWriter, ColumnarSectionWriter};
16
17pub struct InMemorySegmentStore {
19 segments: RwLock<HashMap<(u32, u64), ColumnSegmentV2>>,
20 memory_usage: AtomicU64,
21 memory_limit: Option<u64>,
22}
23
24impl InMemorySegmentStore {
25 pub fn new(memory_limit: Option<u64>) -> Self {
27 Self {
28 segments: RwLock::new(HashMap::new()),
29 memory_usage: AtomicU64::new(0),
30 memory_limit,
31 }
32 }
33
34 pub fn write_segment(&self, table_id: u32, segment: ColumnSegmentV2) -> Result<u64> {
36 let size = segment.data.len() as u64;
37 let current = self.memory_usage.load(Ordering::Relaxed);
38 let requested = current.saturating_add(size);
39 if let Some(limit) = self.memory_limit {
40 if requested > limit {
41 return Err(ColumnarError::MemoryLimitExceeded {
42 limit: std::cmp::min(limit, usize::MAX as u64) as usize,
43 requested: std::cmp::min(requested, usize::MAX as u64) as usize,
44 });
45 }
46 }
47
48 let mut guard = self.segments.write().unwrap();
49 let next_id = guard
50 .keys()
51 .filter(|(tid, _)| *tid == table_id)
52 .map(|(_, sid)| *sid)
53 .max()
54 .map(|id| id.saturating_add(1))
55 .unwrap_or(0);
56 guard.insert((table_id, next_id), segment);
57 drop(guard);
58 self.memory_usage.store(requested, Ordering::Relaxed);
59 Ok(next_id)
60 }
61
62 pub fn read_segment(
64 &self,
65 table_id: u32,
66 segment_id: u64,
67 columns: &[usize],
68 ) -> Result<Vec<RecordBatch>> {
69 let guard = self.segments.read().unwrap();
70 let segment = guard
71 .get(&(table_id, segment_id))
72 .ok_or(ColumnarError::NotFound)?
73 .clone();
74 drop(guard);
75 let reader =
76 SegmentReaderV2::open(Box::new(InMemorySegmentSource::new(segment.data.clone())))?;
77 reader.read_columns(columns)
78 }
79
80 pub fn memory_usage(&self) -> u64 {
82 self.memory_usage.load(Ordering::Relaxed)
83 }
84
85 pub fn flush_to_segment_file<P: AsRef<Path>>(
87 &self,
88 table_id: u32,
89 segment_id: u64,
90 path: P,
91 ) -> Result<()> {
92 let guard = self.segments.read().unwrap();
93 let segment = guard
94 .get(&(table_id, segment_id))
95 .ok_or(ColumnarError::NotFound)?
96 .clone();
97 drop(guard);
98
99 let tmp_path = path.as_ref().with_extension("tmp");
100 let mut file = OpenOptions::new()
101 .create(true)
102 .write(true)
103 .truncate(true)
104 .open(&tmp_path)?;
105 file.write_all(&segment.data)?;
106 file.sync_all()?;
107 rename(&tmp_path, path)?;
108 Ok(())
109 }
110
111 pub fn flush_to_kvs(
113 &self,
114 table_id: u32,
115 segment_id: u64,
116 bridge: &ColumnarKvsBridge,
117 ) -> Result<u64> {
118 let guard = self.segments.read().unwrap();
119 let segment = guard
120 .get(&(table_id, segment_id))
121 .ok_or(ColumnarError::NotFound)?
122 .clone();
123 drop(guard);
124 bridge.write_segment(table_id, &segment)
125 }
126
127 pub fn flush_to_alopex(
129 &self,
130 table_id: u32,
131 segment_id: u64,
132 writer: &mut AlopexFileWriter,
133 ) -> Result<u32> {
134 let guard = self.segments.read().unwrap();
135 let segment = guard
136 .get(&(table_id, segment_id))
137 .ok_or(ColumnarError::NotFound)?
138 .clone();
139 drop(guard);
140
141 ColumnarSectionWriter::write_section(writer, &segment)
142 .map_err(|e| ColumnarError::InvalidFormat(e.to_string()))
143 }
144
145 pub fn column_count(&self, table_id: u32, segment_id: u64) -> Result<usize> {
147 let guard = self.segments.read().unwrap();
148 let segment = guard
149 .get(&(table_id, segment_id))
150 .ok_or(ColumnarError::NotFound)?;
151 Ok(segment.meta.schema.column_count())
152 }
153
154 pub fn schema(&self, table_id: u32, segment_id: u64) -> Result<Schema> {
156 let guard = self.segments.read().unwrap();
157 let segment = guard
158 .get(&(table_id, segment_id))
159 .ok_or(ColumnarError::NotFound)?;
160 Ok(segment.meta.schema.clone())
161 }
162
163 pub fn list_segments(&self) -> Vec<(u32, u64)> {
165 let guard = self.segments.read().unwrap();
166 guard.keys().cloned().collect()
167 }
168}
169
170#[cfg(all(test, not(target_arch = "wasm32")))]
171mod tests {
172 use super::*;
173 use crate::columnar::encoding::{Column, LogicalType};
174 use crate::columnar::segment_v2::{ColumnSchema, Schema, SegmentWriterV2};
175 use crate::kv::memory::MemoryKV;
176 use crate::storage::format::{
177 AlopexFileReader, FileFlags, FileReader, FileSource, FileVersion, SectionType,
178 };
179 use tempfile::tempdir;
180
181 fn make_segment() -> ColumnSegmentV2 {
182 let schema = Schema {
183 columns: vec![
184 ColumnSchema {
185 name: "id".into(),
186 logical_type: LogicalType::Int64,
187 nullable: false,
188 fixed_len: None,
189 },
190 ColumnSchema {
191 name: "val".into(),
192 logical_type: LogicalType::Int64,
193 nullable: false,
194 fixed_len: None,
195 },
196 ],
197 };
198 let batch = RecordBatch::new(
199 schema,
200 vec![Column::Int64(vec![1, 2]), Column::Int64(vec![10, 20])],
201 vec![None, None],
202 );
203 let mut writer = SegmentWriterV2::new(Default::default());
204 writer.write_batch(batch).unwrap();
205 writer.finish().unwrap()
206 }
207
208 #[test]
209 fn test_memory_limit_enforcement() {
210 let store = InMemorySegmentStore::new(Some(1));
211 let segment = make_segment();
212 let err = store.write_segment(1, segment).unwrap_err();
213 assert!(matches!(err, ColumnarError::MemoryLimitExceeded { .. }));
214 }
215
216 #[test]
217 fn test_write_and_read_segment() {
218 let store = InMemorySegmentStore::new(None);
219 let id = store.write_segment(2, make_segment()).unwrap();
220 let batches = store.read_segment(2, id, &[0, 1]).unwrap();
221 assert_eq!(batches[0].num_rows(), 2);
222 }
223
224 #[test]
225 fn test_flush_to_segment_file() {
226 let dir = tempdir().unwrap();
227 let path = dir.path().join("seg.bin");
228 let store = InMemorySegmentStore::new(None);
229 let id = store.write_segment(1, make_segment()).unwrap();
230 store
231 .flush_to_segment_file(1, id, &path)
232 .expect("flush succeeds");
233 let bytes = std::fs::read(&path).unwrap();
234 assert!(!bytes.is_empty());
235 }
236
237 #[test]
238 fn test_flush_to_kvs() {
239 let store = InMemorySegmentStore::new(None);
240 let seg = make_segment();
241 let id = store.write_segment(4, seg.clone()).unwrap();
242 let kv = MemoryKV::new();
243 let bridge = ColumnarKvsBridge::new(std::sync::Arc::new(crate::kv::AnyKV::Memory(kv)));
244 let new_id = store.flush_to_kvs(4, id, &bridge).unwrap();
245 assert_eq!(new_id, 0);
246 let batches = bridge.read_segment(4, new_id, &[0, 1]).unwrap();
247 assert_eq!(batches[0].num_rows(), 2);
248 }
249
250 #[test]
251 fn test_flush_to_alopex() {
252 let dir = tempdir().unwrap();
253 let path = dir.path().join("test.alopex");
254 let mut writer =
255 AlopexFileWriter::new(path.clone(), FileVersion::CURRENT, FileFlags(0)).unwrap();
256 let store = InMemorySegmentStore::new(None);
257 let id = store.write_segment(1, make_segment()).unwrap();
258 let section_id = store.flush_to_alopex(1, id, &mut writer).unwrap();
259 assert_eq!(section_id, 0);
260 writer.finalize().unwrap();
261
262 let reader =
263 AlopexFileReader::open(FileSource::Path(path)).expect("alopex file should open");
264 let entry = reader
265 .section_index()
266 .find_by_id(section_id)
267 .expect("entry exists");
268 assert_eq!(entry.section_type, SectionType::ColumnarSegment);
269 }
270}