Skip to main content

nodedb_lite/engine/columnar/store/
read.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Columnar row scans, segment reads, and collection counts.
4
5use super::decode::decoded_column_value;
6use super::segments::load_segment_bytes;
7use super::state::{ColumnarEngine, SegmentMeta};
8use crate::error::LiteError;
9use crate::storage::engine::StorageEngine;
10use nodedb_columnar::delete_bitmap::DeleteBitmap;
11use nodedb_types::value::Value;
12
13impl<S: StorageEngine> ColumnarEngine<S> {
14    /// Scan all rows in a columnar collection, returning them in schema column order.
15    ///
16    /// Reads memtable rows first, then flushed segments. Each row is a
17    /// `Vec<Value>` whose entries correspond 1-to-1 with `schema().columns`.
18    pub async fn list_rows(&self, collection: &str) -> Result<Vec<Vec<Value>>, LiteError> {
19        let state_arc = self.lookup(collection)?;
20
21        // Collect memtable rows and segment metadata under the inner lock (briefly).
22        struct Snapshot {
23            memtable_rows: Vec<Vec<Value>>,
24            seg_metas: Vec<SegmentMeta>,
25            col_count: usize,
26        }
27        let snap = {
28            let s = Self::lock_state(&state_arc)?;
29            // A memtable row with a corrupt cell refuses the scan.
30            let memtable_rows: Vec<Vec<Value>> = s
31                .mutation
32                .memtable()
33                .iter_rows()
34                .collect::<Result<_, _>>()
35                .map_err(|e| LiteError::Storage {
36                    detail: format!("read memtable row of '{collection}': {e}"),
37                })?;
38            Snapshot {
39                memtable_rows,
40                seg_metas: s.segments.clone(),
41                col_count: s.mutation.schema().columns.len(),
42            }
43        };
44
45        let mut all_rows: Vec<Vec<Value>> = Vec::new();
46        all_rows.extend(snap.memtable_rows);
47
48        // Read each flushed segment from storage (lock dropped) and transpose
49        // the columnar layout back to row-major Values. Skip fully-deleted
50        // tombstones (their physical segment file has already been removed).
51        for seg_meta in &snap.seg_metas {
52            if seg_meta.fully_deleted_at_ms.is_some() {
53                continue;
54            }
55            let seg_bytes =
56                match load_segment_bytes(&*self.storage, collection, seg_meta.segment_id).await? {
57                    Some(b) => b,
58                    None => continue,
59                };
60
61            let reader = nodedb_columnar::reader::SegmentReader::open(&seg_bytes).map_err(|e| {
62                LiteError::Storage {
63                    detail: format!("open segment {}: {e}", seg_meta.segment_id),
64                }
65            })?;
66
67            let row_count = reader.row_count() as usize;
68            if row_count == 0 {
69                continue;
70            }
71
72            // Decode all columns.
73            let mut decoded: Vec<nodedb_columnar::reader::DecodedColumn> =
74                Vec::with_capacity(snap.col_count);
75            for col_idx in 0..snap.col_count {
76                let col = reader
77                    .read_column(col_idx)
78                    .map_err(|e| LiteError::Storage {
79                        detail: format!(
80                            "read column {col_idx} of segment {}: {e}",
81                            seg_meta.segment_id
82                        ),
83                    })?;
84                decoded.push(col);
85            }
86
87            // Transpose: iterate row indices, extract one Value per column.
88            for row_idx in 0..row_count {
89                let row: Vec<Value> = decoded
90                    .iter()
91                    .map(|col| decoded_column_value(col, row_idx))
92                    .collect();
93                all_rows.push(row);
94            }
95        }
96
97        Ok(all_rows)
98    }
99
100    /// Read all segment bytes for a collection (for the table provider).
101    pub async fn read_segments(&self, collection: &str) -> Result<Vec<(u32, Vec<u8>)>, LiteError> {
102        let state_arc = self.lookup(collection)?;
103        let seg_metas: Vec<SegmentMeta> = {
104            let s = Self::lock_state(&state_arc)?;
105            s.segments.clone()
106        };
107
108        let mut segments = Vec::with_capacity(seg_metas.len());
109        for seg_meta in &seg_metas {
110            if seg_meta.fully_deleted_at_ms.is_some() {
111                continue;
112            }
113            if let Some(bytes) =
114                load_segment_bytes(&*self.storage, collection, seg_meta.segment_id).await?
115            {
116                segments.push((seg_meta.segment_id, bytes));
117            }
118        }
119
120        Ok(segments)
121    }
122
123    /// Get the delete bitmap for a segment (returns a clone).
124    pub fn delete_bitmap(&self, collection: &str, segment_id: u32) -> Option<DeleteBitmap> {
125        let guard = self.collections.read().ok()?;
126        let state_arc = guard.get(collection)?;
127        let s = state_arc.lock().ok()?;
128        s.mutation.delete_bitmap(segment_id as u64).cloned()
129    }
130
131    /// Row count across all segments + memtable for a collection.
132    pub fn row_count(&self, collection: &str) -> usize {
133        let Ok(guard) = self.collections.read() else {
134            return 0;
135        };
136        let Some(state_arc) = guard.get(collection) else {
137            return 0;
138        };
139        let Ok(s) = state_arc.lock() else {
140            return 0;
141        };
142        let seg_rows: u64 = s
143            .segments
144            .iter()
145            .filter(|m| m.fully_deleted_at_ms.is_none())
146            .map(|m| m.row_count)
147            .sum();
148        seg_rows as usize + s.mutation.memtable().row_count()
149    }
150
151    /// Whether a collection has bitemporal tracking enabled.
152    pub fn is_bitemporal(&self, collection: &str) -> bool {
153        let Ok(guard) = self.collections.read() else {
154            return false;
155        };
156        let Some(state_arc) = guard.get(collection) else {
157            return false;
158        };
159        let Ok(s) = state_arc.lock() else {
160            return false;
161        };
162        s.bitemporal
163    }
164}