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 {
34            rows: self.rows,
35            column_index: index,
36            row_index: 0,
37        }
38    }
39
40    /// Get number of rows in this scan
41    pub fn len(&self) -> usize {
42        self.rows.len()
43    }
44
45    /// Check if scan is empty
46    pub fn is_empty(&self) -> bool {
47        self.rows.is_empty()
48    }
49
50    /// Get a row by index (returns reference, no clone)
51    pub fn row(&self, index: usize) -> Option<&'a Row> {
52        self.rows.get(index)
53    }
54}
55
56/// Iterator over a single column's values
57///
58/// Yields `&SqlValue` references for zero-copy access.
59pub struct ColumnIterator<'a> {
60    rows: &'a [Row],
61    column_index: usize,
62    row_index: usize,
63}
64
65impl<'a> Iterator for ColumnIterator<'a> {
66    type Item = Option<&'a SqlValue>;
67
68    fn next(&mut self) -> Option<Self::Item> {
69        if self.row_index >= self.rows.len() {
70            return None;
71        }
72
73        let row = &self.rows[self.row_index];
74        self.row_index += 1;
75
76        // Return reference to the value at column_index, or None if column doesn't exist
77        Some(row.get(self.column_index))
78    }
79
80    fn size_hint(&self) -> (usize, Option<usize>) {
81        let remaining = self.rows.len() - self.row_index;
82        (remaining, Some(remaining))
83    }
84}
85
86impl<'a> ExactSizeIterator for ColumnIterator<'a> {}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_columnar_scan() {
94        let rows = vec![
95            Row::new(vec![
96                SqlValue::Integer(1),
97                SqlValue::Double(10.5),
98                SqlValue::Varchar("A".to_string()),
99            ]),
100            Row::new(vec![
101                SqlValue::Integer(2),
102                SqlValue::Double(20.5),
103                SqlValue::Varchar("B".to_string()),
104            ]),
105        ];
106
107        let scan = ColumnarScan::new(&rows);
108        assert_eq!(scan.len(), 2);
109
110        // Test column extraction
111        let col0: Vec<Option<&SqlValue>> = scan.column(0).collect();
112        assert_eq!(col0.len(), 2);
113        assert!(matches!(col0[0], Some(&SqlValue::Integer(1))));
114        assert!(matches!(col0[1], Some(&SqlValue::Integer(2))));
115
116        // Test column 1
117        let col1: Vec<Option<&SqlValue>> = scan.column(1).collect();
118        assert!(matches!(col1[0], Some(&SqlValue::Double(10.5))));
119        assert!(matches!(col1[1], Some(&SqlValue::Double(20.5))));
120    }
121
122    #[test]
123    fn test_column_iterator_size_hint() {
124        let rows = vec![
125            Row::new(vec![SqlValue::Integer(1)]),
126            Row::new(vec![SqlValue::Integer(2)]),
127            Row::new(vec![SqlValue::Integer(3)]),
128        ];
129
130        let scan = ColumnarScan::new(&rows);
131        let mut iter = scan.column(0);
132
133        assert_eq!(iter.size_hint(), (3, Some(3)));
134        iter.next();
135        assert_eq!(iter.size_hint(), (2, Some(2)));
136    }
137}