vibesql_executor/select/columnar/
scan.rs

1//! Columnar scan - zero-copy column extraction from row slices
2
3use vibesql_storage::Row;
4use vibesql_types::SqlValue;
5
6/// Columnar scan over a slice of rows
7///
8/// Provides zero-copy access to individual columns without materializing
9/// full Row objects. This is the foundation for columnar execution.
10///
11/// # Example
12///
13/// ```text
14/// let scan = ColumnarScan::new(&rows);
15/// let prices: Vec<&SqlValue> = scan.column(2).collect();
16/// let quantities: Vec<&SqlValue> = scan.column(4).collect();
17/// ```
18pub struct ColumnarScan<'a> {
19    rows: &'a [Row],
20}
21
22impl<'a> ColumnarScan<'a> {
23    /// Create a new columnar scan over a row slice
24    pub fn new(rows: &'a [Row]) -> Self {
25        Self { rows }
26    }
27
28    /// Get an iterator over a specific column
29    ///
30    /// Returns references to SqlValues, avoiding clones.
31    /// Returns None for rows that don't have the column index.
32    pub fn column(&self, index: usize) -> ColumnIterator<'a> {
33        ColumnIterator { rows: self.rows, column_index: index, row_index: 0 }
34    }
35
36    /// Get number of rows in this scan
37    pub fn len(&self) -> usize {
38        self.rows.len()
39    }
40
41    /// Check if scan is empty
42    pub fn is_empty(&self) -> bool {
43        self.rows.is_empty()
44    }
45
46    /// Get a row by index (returns reference, no clone)
47    pub fn row(&self, index: usize) -> Option<&'a Row> {
48        self.rows.get(index)
49    }
50}
51
52/// Iterator over a single column's values
53///
54/// Yields `&SqlValue` references for zero-copy access.
55pub struct ColumnIterator<'a> {
56    rows: &'a [Row],
57    column_index: usize,
58    row_index: usize,
59}
60
61impl<'a> Iterator for ColumnIterator<'a> {
62    type Item = Option<&'a SqlValue>;
63
64    fn next(&mut self) -> Option<Self::Item> {
65        if self.row_index >= self.rows.len() {
66            return None;
67        }
68
69        let row = &self.rows[self.row_index];
70        self.row_index += 1;
71
72        // Return reference to the value at column_index, or None if column doesn't exist
73        Some(row.get(self.column_index))
74    }
75
76    fn size_hint(&self) -> (usize, Option<usize>) {
77        let remaining = self.rows.len() - self.row_index;
78        (remaining, Some(remaining))
79    }
80}
81
82impl<'a> ExactSizeIterator for ColumnIterator<'a> {}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_columnar_scan() {
90        let rows = vec![
91            Row::new(vec![
92                SqlValue::Integer(1),
93                SqlValue::Double(10.5),
94                SqlValue::Varchar("A".to_string()),
95            ]),
96            Row::new(vec![
97                SqlValue::Integer(2),
98                SqlValue::Double(20.5),
99                SqlValue::Varchar("B".to_string()),
100            ]),
101        ];
102
103        let scan = ColumnarScan::new(&rows);
104        assert_eq!(scan.len(), 2);
105
106        // Test column extraction
107        let col0: Vec<Option<&SqlValue>> = scan.column(0).collect();
108        assert_eq!(col0.len(), 2);
109        assert!(matches!(col0[0], Some(&SqlValue::Integer(1))));
110        assert!(matches!(col0[1], Some(&SqlValue::Integer(2))));
111
112        // Test column 1
113        let col1: Vec<Option<&SqlValue>> = scan.column(1).collect();
114        assert!(matches!(col1[0], Some(&SqlValue::Double(10.5))));
115        assert!(matches!(col1[1], Some(&SqlValue::Double(20.5))));
116    }
117
118    #[test]
119    fn test_column_iterator_size_hint() {
120        let rows = vec![
121            Row::new(vec![SqlValue::Integer(1)]),
122            Row::new(vec![SqlValue::Integer(2)]),
123            Row::new(vec![SqlValue::Integer(3)]),
124        ];
125
126        let scan = ColumnarScan::new(&rows);
127        let mut iter = scan.column(0);
128
129        assert_eq!(iter.size_hint(), (3, Some(3)));
130        iter.next();
131        assert_eq!(iter.size_hint(), (2, Some(2)));
132    }
133}