1use std::collections::HashMap;
12use std::sync::Arc;
13
14use nodedb_columnar::delete_bitmap::DeleteBitmap;
15use nodedb_columnar::mutation::MutationEngine;
16use nodedb_columnar::reader::SegmentReader;
17use nodedb_columnar::writer::SegmentWriter;
18use nodedb_types::Namespace;
19use nodedb_types::columnar::{ColumnarProfile, ColumnarSchema};
20use nodedb_types::value::Value;
21
22use crate::error::LiteError;
23use crate::storage::engine::{StorageEngine, WriteOp};
24
25const META_COLUMNAR_SCHEMA_PREFIX: &str = "columnar_schema:";
27const META_COLUMNAR_COLLECTIONS: &[u8] = b"meta:columnar_collections";
29
30#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
32struct SegmentMeta {
33 segment_id: u32,
34 row_count: u64,
35}
36
37struct CollectionState {
39 mutation: MutationEngine,
40 profile: ColumnarProfile,
41 segments: Vec<SegmentMeta>,
43 next_segment_id: u32,
45}
46
47pub struct ColumnarEngine<S: StorageEngine> {
49 storage: Arc<S>,
50 collections: HashMap<String, CollectionState>,
51}
52
53impl<S: StorageEngine> ColumnarEngine<S> {
54 pub fn new(storage: Arc<S>) -> Self {
56 Self {
57 storage,
58 collections: HashMap::new(),
59 }
60 }
61
62 pub async fn restore(storage: Arc<S>) -> Result<Self, LiteError> {
64 let mut engine = Self::new(Arc::clone(&storage));
65
66 let list_bytes = storage
67 .get(Namespace::Meta, META_COLUMNAR_COLLECTIONS)
68 .await?;
69 let names: Vec<String> = match list_bytes {
70 Some(bytes) => rmp_serde::from_slice(&bytes).map_err(|e| LiteError::Storage {
71 detail: format!("columnar collection list deserialization: {e}"),
72 })?,
73 None => Vec::new(),
74 };
75
76 for name in names {
77 let meta_key = format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}");
78 #[derive(serde::Deserialize)]
79 struct StoredSchema {
80 schema: ColumnarSchema,
81 profile: ColumnarProfile,
82 }
83 if let Some(schema_bytes) = storage.get(Namespace::Meta, meta_key.as_bytes()).await?
84 && let Ok(stored) = rmp_serde::from_slice::<StoredSchema>(&schema_bytes)
85 {
86 let seg_meta_key = format!("{name}:meta");
88 let segments: Vec<SegmentMeta> = storage
89 .get(Namespace::Columnar, seg_meta_key.as_bytes())
90 .await?
91 .and_then(|b| rmp_serde::from_slice(&b).ok())
92 .unwrap_or_default();
93
94 let next_id = segments.iter().map(|s| s.segment_id + 1).max().unwrap_or(1);
95
96 let mut mutation = MutationEngine::new(name.clone(), stored.schema.clone());
98
99 for seg_meta in &segments {
103 let seg_key = format!("{name}:seg:{}", seg_meta.segment_id);
104 if let Some(seg_bytes) =
105 storage.get(Namespace::Columnar, seg_key.as_bytes()).await?
106 && let Ok(reader) = SegmentReader::open(&seg_bytes)
107 {
108 if let Ok(pk_col) = reader.read_column(0) {
110 rebuild_pk_from_column(&mut mutation, &pk_col, seg_meta.segment_id);
111 }
112 }
113
114 let del_key = format!("{name}:del:{}", seg_meta.segment_id);
116 if let Some(del_bytes) =
117 storage.get(Namespace::Columnar, del_key.as_bytes()).await?
118 && let Ok(bitmap) = DeleteBitmap::from_bytes(&del_bytes)
119 {
120 for row_idx in bitmap.iter() {
122 let _ = row_idx;
126 }
127 }
128 }
129
130 engine.collections.insert(
131 name,
132 CollectionState {
133 mutation,
134 profile: stored.profile,
135 segments,
136 next_segment_id: next_id,
137 },
138 );
139 }
140 }
141
142 Ok(engine)
143 }
144
145 pub async fn create_collection(
149 &mut self,
150 name: &str,
151 schema: ColumnarSchema,
152 profile: ColumnarProfile,
153 ) -> Result<(), LiteError> {
154 if self.collections.contains_key(name) {
155 return Err(LiteError::BadRequest {
156 detail: format!("columnar collection '{name}' already exists"),
157 });
158 }
159
160 #[derive(serde::Serialize)]
162 struct StoredSchema<'a> {
163 schema: &'a ColumnarSchema,
164 profile: &'a ColumnarProfile,
165 }
166 let meta_key = format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}");
167 let schema_bytes = rmp_serde::to_vec_named(&StoredSchema {
168 schema: &schema,
169 profile: &profile,
170 })
171 .map_err(|e| LiteError::Serialization {
172 detail: e.to_string(),
173 })?;
174
175 let mut names: Vec<String> = self.collections.keys().cloned().collect();
176 names.push(name.to_string());
177 let names_bytes =
178 rmp_serde::to_vec_named(&names).map_err(|e| LiteError::Serialization {
179 detail: e.to_string(),
180 })?;
181
182 self.storage
183 .batch_write(&[
184 WriteOp::Put {
185 ns: Namespace::Meta,
186 key: meta_key.into_bytes(),
187 value: schema_bytes,
188 },
189 WriteOp::Put {
190 ns: Namespace::Meta,
191 key: META_COLUMNAR_COLLECTIONS.to_vec(),
192 value: names_bytes,
193 },
194 ])
195 .await?;
196
197 let mutation = MutationEngine::new(name.to_string(), schema);
198 self.collections.insert(
199 name.to_string(),
200 CollectionState {
201 mutation,
202 profile,
203 segments: Vec::new(),
204 next_segment_id: 1,
205 },
206 );
207
208 Ok(())
209 }
210
211 pub async fn drop_collection(&mut self, name: &str) -> Result<(), LiteError> {
213 let state = self.collections.remove(name).ok_or(LiteError::BadRequest {
214 detail: format!("columnar collection '{name}' does not exist"),
215 })?;
216
217 let mut ops = Vec::new();
218
219 for seg in &state.segments {
221 ops.push(WriteOp::Delete {
222 ns: Namespace::Columnar,
223 key: format!("{name}:seg:{}", seg.segment_id).into_bytes(),
224 });
225 ops.push(WriteOp::Delete {
226 ns: Namespace::Columnar,
227 key: format!("{name}:del:{}", seg.segment_id).into_bytes(),
228 });
229 }
230
231 ops.push(WriteOp::Delete {
233 ns: Namespace::Columnar,
234 key: format!("{name}:meta").into_bytes(),
235 });
236 ops.push(WriteOp::Delete {
237 ns: Namespace::Meta,
238 key: format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}").into_bytes(),
239 });
240
241 let names: Vec<String> = self.collections.keys().cloned().collect();
243 let names_bytes =
244 rmp_serde::to_vec_named(&names).map_err(|e| LiteError::Serialization {
245 detail: e.to_string(),
246 })?;
247 ops.push(WriteOp::Put {
248 ns: Namespace::Meta,
249 key: META_COLUMNAR_COLLECTIONS.to_vec(),
250 value: names_bytes,
251 });
252
253 self.storage.batch_write(&ops).await?;
254 Ok(())
255 }
256
257 pub async fn alter_add_column(
262 &mut self,
263 name: &str,
264 column: nodedb_types::columnar::ColumnDef,
265 ) -> Result<(), LiteError> {
266 let state = self
267 .collections
268 .get_mut(name)
269 .ok_or(LiteError::BadRequest {
270 detail: format!("columnar collection '{name}' does not exist"),
271 })?;
272
273 if !column.nullable && column.default.is_none() {
275 return Err(LiteError::BadRequest {
276 detail: format!(
277 "ALTER ADD COLUMN '{}': non-nullable column must have a DEFAULT",
278 column.name
279 ),
280 });
281 }
282
283 if state
285 .mutation
286 .schema()
287 .columns
288 .iter()
289 .any(|c| c.name == column.name)
290 {
291 return Err(LiteError::BadRequest {
292 detail: format!("column '{}' already exists in '{name}'", column.name),
293 });
294 }
295
296 let mut schema = state.mutation.schema().clone();
299 schema.columns.push(column);
300 schema.version = schema.version.saturating_add(1);
301
302 state.mutation = MutationEngine::new(name.to_string(), schema.clone());
305
306 #[derive(serde::Serialize)]
308 struct StoredSchema<'a> {
309 schema: &'a ColumnarSchema,
310 profile: &'a ColumnarProfile,
311 }
312 let meta_key = format!("{META_COLUMNAR_SCHEMA_PREFIX}{name}");
313 let schema_bytes = rmp_serde::to_vec_named(&StoredSchema {
314 schema: &schema,
315 profile: &state.profile,
316 })
317 .map_err(|e| LiteError::Serialization {
318 detail: e.to_string(),
319 })?;
320
321 self.storage
322 .put(Namespace::Meta, meta_key.as_bytes(), &schema_bytes)
323 .await?;
324
325 Ok(())
326 }
327
328 pub fn schema(&self, name: &str) -> Option<&ColumnarSchema> {
330 self.collections.get(name).map(|s| s.mutation.schema())
331 }
332
333 pub fn profile(&self, name: &str) -> Option<&ColumnarProfile> {
335 self.collections.get(name).map(|s| &s.profile)
336 }
337
338 pub fn collection_names(&self) -> Vec<&str> {
340 self.collections.keys().map(|s| s.as_str()).collect()
341 }
342
343 pub fn insert(&mut self, collection: &str, values: &[Value]) -> Result<(), LiteError> {
347 let state = self.get_state_mut(collection)?;
348 state
349 .mutation
350 .insert(values)
351 .map_err(columnar_err_to_lite)?;
352 Ok(())
353 }
354
355 pub fn delete(&mut self, collection: &str, pk: &Value) -> Result<bool, LiteError> {
361 let state = self.get_state_mut(collection)?;
362
363 if matches!(state.profile, ColumnarProfile::Timeseries { .. }) {
365 return Err(LiteError::BadRequest {
366 detail: format!(
367 "DELETE not allowed on timeseries collection '{collection}' (append-only)"
368 ),
369 });
370 }
371
372 match state.mutation.delete(pk) {
373 Ok(_) => Ok(true),
374 Err(nodedb_columnar::ColumnarError::PrimaryKeyNotFound) => Ok(false),
375 Err(e) => Err(columnar_err_to_lite(e)),
376 }
377 }
378
379 pub fn update(
383 &mut self,
384 collection: &str,
385 old_pk: &Value,
386 new_values: &[Value],
387 ) -> Result<bool, LiteError> {
388 let state = self.get_state_mut(collection)?;
389
390 if matches!(state.profile, ColumnarProfile::Timeseries { .. }) {
392 return Err(LiteError::BadRequest {
393 detail: format!(
394 "UPDATE not allowed on timeseries collection '{collection}' (append-only)"
395 ),
396 });
397 }
398
399 match state.mutation.update(old_pk, new_values) {
400 Ok(_) => Ok(true),
401 Err(nodedb_columnar::ColumnarError::PrimaryKeyNotFound) => Ok(false),
402 Err(e) => Err(columnar_err_to_lite(e)),
403 }
404 }
405
406 pub async fn flush_collection(&mut self, collection: &str) -> Result<(), LiteError> {
410 let state = self
411 .collections
412 .get_mut(collection)
413 .ok_or(LiteError::BadRequest {
414 detail: format!("columnar collection '{collection}' does not exist"),
415 })?;
416
417 if state.mutation.memtable().is_empty() {
418 return Ok(());
419 }
420
421 let segment_id = state.next_segment_id;
422 state.next_segment_id += 1;
423
424 let (schema, columns, row_count) = state.mutation.memtable_mut().drain();
426
427 let profile_tag = match &state.profile {
428 ColumnarProfile::Plain => 0,
429 ColumnarProfile::Timeseries { .. } => 1,
430 ColumnarProfile::Spatial { .. } => 2,
431 };
432
433 let writer = SegmentWriter::new(profile_tag);
434 let segment_bytes = writer
435 .write_segment(&schema, &columns, row_count)
436 .map_err(columnar_err_to_lite)?;
437
438 let seg_key = format!("{collection}:seg:{segment_id}");
440 state.segments.push(SegmentMeta {
441 segment_id,
442 row_count: row_count as u64,
443 });
444 let meta_key = format!("{collection}:meta");
445 let meta_bytes =
446 rmp_serde::to_vec_named(&state.segments).map_err(|e| LiteError::Serialization {
447 detail: e.to_string(),
448 })?;
449
450 state.mutation.on_memtable_flushed(segment_id);
451
452 let mut del_ops: Vec<(String, Vec<u8>)> = Vec::new();
454 for (&seg_id, bitmap) in state.mutation.delete_bitmaps() {
455 if !bitmap.is_empty() {
456 let del_key = format!("{collection}:del:{seg_id}");
457 let del_bytes = bitmap.to_bytes().map_err(columnar_err_to_lite)?;
458 del_ops.push((del_key, del_bytes));
459 }
460 }
461
462 let storage = &self.storage;
464 storage
465 .put(Namespace::Columnar, seg_key.as_bytes(), &segment_bytes)
466 .await?;
467 storage
468 .put(Namespace::Columnar, meta_key.as_bytes(), &meta_bytes)
469 .await?;
470 for (del_key, del_bytes) in &del_ops {
471 storage
472 .put(Namespace::Columnar, del_key.as_bytes(), del_bytes)
473 .await?;
474 }
475
476 Ok(())
477 }
478
479 pub async fn flush_all(&mut self) -> Result<(), LiteError> {
481 let names: Vec<String> = self.collections.keys().cloned().collect();
482 for name in names {
483 self.flush_collection(&name).await?;
484 }
485 Ok(())
486 }
487
488 pub async fn try_compact_collection(&mut self, collection: &str) -> Result<bool, LiteError> {
495 let state = self
496 .collections
497 .get(collection)
498 .ok_or(LiteError::BadRequest {
499 detail: format!("columnar collection '{collection}' does not exist"),
500 })?;
501
502 let mut to_compact = Vec::new();
504 for seg_meta in &state.segments {
505 if let Some(bitmap) = state.mutation.delete_bitmap(seg_meta.segment_id)
506 && bitmap.should_compact(seg_meta.row_count, 0.2)
507 {
508 to_compact.push(seg_meta.segment_id);
509 }
510 }
511
512 if to_compact.is_empty() {
513 return Ok(false);
514 }
515
516 let schema = state.mutation.schema().clone();
517 let profile_tag = match &state.profile {
518 ColumnarProfile::Plain => 0,
519 ColumnarProfile::Timeseries { .. } => 1,
520 ColumnarProfile::Spatial { .. } => 2,
521 };
522
523 for seg_id in &to_compact {
525 let seg_key = format!("{collection}:seg:{seg_id}");
526 let seg_bytes = match self
527 .storage
528 .get(Namespace::Columnar, seg_key.as_bytes())
529 .await?
530 {
531 Some(b) => b,
532 None => continue,
533 };
534
535 let empty_bitmap = DeleteBitmap::new();
536 let bitmap = self
537 .collections
538 .get(collection)
539 .and_then(|s| s.mutation.delete_bitmap(*seg_id))
540 .unwrap_or(&empty_bitmap);
541
542 let result = nodedb_columnar::compaction::compact_segment(
543 &seg_bytes,
544 bitmap,
545 &schema,
546 profile_tag,
547 )
548 .map_err(columnar_err_to_lite)?;
549
550 if let Some(new_seg_bytes) = result.segment {
551 self.storage
553 .put(Namespace::Columnar, seg_key.as_bytes(), &new_seg_bytes)
554 .await?;
555
556 if let Some(state) = self.collections.get_mut(collection)
558 && let Some(meta) = state.segments.iter_mut().find(|m| m.segment_id == *seg_id)
559 {
560 meta.row_count = result.live_rows as u64;
561 }
562
563 let del_key = format!("{collection}:del:{seg_id}");
565 self.storage
566 .delete(Namespace::Columnar, del_key.as_bytes())
567 .await?;
568 } else {
569 self.storage
571 .delete(Namespace::Columnar, seg_key.as_bytes())
572 .await?;
573 let del_key = format!("{collection}:del:{seg_id}");
574 self.storage
575 .delete(Namespace::Columnar, del_key.as_bytes())
576 .await?;
577
578 if let Some(state) = self.collections.get_mut(collection) {
579 state.segments.retain(|m| m.segment_id != *seg_id);
580 }
581 }
582 }
583
584 if let Some(state) = self.collections.get(collection) {
586 let meta_key = format!("{collection}:meta");
587 let meta_bytes =
588 rmp_serde::to_vec_named(&state.segments).map_err(|e| LiteError::Serialization {
589 detail: e.to_string(),
590 })?;
591 self.storage
592 .put(Namespace::Columnar, meta_key.as_bytes(), &meta_bytes)
593 .await?;
594 }
595
596 Ok(true)
597 }
598
599 pub async fn read_segments(&self, collection: &str) -> Result<Vec<(u32, Vec<u8>)>, LiteError> {
603 let state = self.get_state(collection)?;
604 let mut segments = Vec::with_capacity(state.segments.len());
605
606 for seg_meta in &state.segments {
607 let seg_key = format!("{collection}:seg:{}", seg_meta.segment_id);
608 if let Some(bytes) = self
609 .storage
610 .get(Namespace::Columnar, seg_key.as_bytes())
611 .await?
612 {
613 segments.push((seg_meta.segment_id, bytes));
614 }
615 }
616
617 Ok(segments)
618 }
619
620 pub fn delete_bitmap(&self, collection: &str, segment_id: u32) -> Option<&DeleteBitmap> {
622 self.collections
623 .get(collection)
624 .and_then(|s| s.mutation.delete_bitmap(segment_id))
625 }
626
627 pub fn row_count(&self, collection: &str) -> usize {
629 let Some(state) = self.collections.get(collection) else {
630 return 0;
631 };
632 let seg_rows: u64 = state.segments.iter().map(|s| s.row_count).sum();
633 seg_rows as usize + state.mutation.memtable().row_count()
634 }
635
636 fn get_state(&self, collection: &str) -> Result<&CollectionState, LiteError> {
639 self.collections
640 .get(collection)
641 .ok_or(LiteError::BadRequest {
642 detail: format!("columnar collection '{collection}' does not exist"),
643 })
644 }
645
646 fn get_state_mut(&mut self, collection: &str) -> Result<&mut CollectionState, LiteError> {
647 self.collections
648 .get_mut(collection)
649 .ok_or(LiteError::BadRequest {
650 detail: format!("columnar collection '{collection}' does not exist"),
651 })
652 }
653}
654
655fn rebuild_pk_from_column(
657 mutation: &mut MutationEngine,
658 pk_col: &nodedb_columnar::reader::DecodedColumn,
659 segment_id: u32,
660) {
661 use nodedb_columnar::pk_index::{RowLocation, encode_pk};
662 use nodedb_columnar::reader::DecodedColumn;
663
664 match pk_col {
665 DecodedColumn::Int64 { values, valid } => {
666 for (row_idx, (val, &is_valid)) in values.iter().zip(valid.iter()).enumerate() {
667 if is_valid {
668 let pk_bytes = encode_pk(&Value::Integer(*val));
669 mutation.pk_index_mut().upsert(
670 pk_bytes,
671 RowLocation {
672 segment_id,
673 row_index: row_idx as u32,
674 },
675 );
676 }
677 }
678 }
679 DecodedColumn::Binary {
680 data,
681 offsets,
682 valid,
683 } => {
684 for (row_idx, &is_valid) in valid.iter().enumerate() {
685 if is_valid {
686 let start = offsets[row_idx] as usize;
687 let end = offsets[row_idx + 1] as usize;
688 let pk_bytes = data[start..end].to_vec();
689 mutation.pk_index_mut().upsert(
690 pk_bytes,
691 RowLocation {
692 segment_id,
693 row_index: row_idx as u32,
694 },
695 );
696 }
697 }
698 }
699 _ => {}
700 }
701}
702
703fn columnar_err_to_lite(e: nodedb_columnar::ColumnarError) -> LiteError {
704 LiteError::BadRequest {
705 detail: e.to_string(),
706 }
707}