Skip to main content

akar_common/
data_chunk.rs

1use crate::selection::SelectionVector;
2use crate::types::{PhysicalTypeID, Value};
3use arrow::array::{ArrayRef, AsArray};
4use arrow::compute::take;
5use arrow::datatypes::*;
6
7/// A contiguous batch of columnar data — the fundamental unit of data exchange
8/// between physical operators.
9///
10/// Each `DataChunk` holds zero or more Arrow `ArrayRef` columns of equal length.
11/// Operators consume input chunks and produce output chunks, forming a pipeline.
12///
13/// # Selection vectors
14/// When `sel_vector` is set, only the rows indicated by the selection are "active".
15/// Call `active_rows()` or `iter_rows()` to respect the selection; do not iterate
16/// `0..size` directly.
17///
18/// # Example
19/// ```text
20/// let chunk = DataChunk::new(fields, field_types).with_names(names);
21/// for row in chunk.iter_rows() {
22///     let val = chunk.get_value(0, row); // column 0, current row
23/// }
24/// ```
25#[derive(Debug, Clone)]
26pub struct DataChunk {
27    pub fields: Vec<ArrayRef>,
28    pub field_types: Vec<PhysicalTypeID>,
29    pub size: usize,
30    pub field_names: Vec<String>,
31    /// Optional selection vector for zero-copy filtering.
32    /// When set, only the rows at `sel_vector.indices[0..sel_vector.size]`
33    /// are considered active. Operators should iterate using the sel_vector
34    /// rather than `size` for correct results.
35    pub sel_vector: Option<SelectionVector>,
36}
37
38/// Resize a DataChunk to the given number of rows.
39pub fn resize_chunk(chunk: &mut DataChunk, new_size: usize) {
40    chunk.size = new_size;
41    for field in &mut chunk.fields {
42        if new_size <= field.len() {
43            *field = field.slice(0, new_size);
44        }
45    }
46}
47
48impl DataChunk {
49    /// Create a new `DataChunk` from Arrow arrays and their physical types.
50    ///
51    /// The chunk `size` is set to the length of the first field. All fields
52    /// must have the same length.
53    pub fn new(fields: Vec<ArrayRef>, field_types: Vec<PhysicalTypeID>) -> Self {
54        let size = fields.first().map(|f| f.len()).unwrap_or(0);
55        Self {
56            fields,
57            field_types,
58            size,
59            field_names: vec![],
60            sel_vector: None,
61        }
62    }
63
64    /// Construct a DataChunk from legacy vectors (for backward-compatibility in benchmarks)
65    pub fn from_legacy(legacy_fields: Vec<crate::vector::ValueVector>) -> Self {
66        let field_types = legacy_fields.iter().map(|f| f.physical_type()).collect();
67        let arrow_fields = legacy_fields
68            .iter()
69            .map(|f| crate::arrow_vector::ArrowVector::from_legacy(f).array.clone())
70            .collect();
71        Self::new(arrow_fields, field_types)
72    }
73
74    /// Attach column names to this chunk (builder pattern).
75    pub fn with_names(mut self, names: Vec<String>) -> Self {
76        self.field_names = names;
77        self
78    }
79
80    /// Attach a selection vector to this chunk.
81    pub fn with_sel(mut self, sel: SelectionVector) -> Self {
82        self.sel_vector = Some(sel);
83        self
84    }
85
86    /// Return a reference to the Arrow array at the given column index.
87    #[inline(always)]
88    pub fn field(&self, idx: usize) -> &ArrayRef {
89        &self.fields[idx]
90    }
91
92    /// Return the number of columns in this chunk.
93    #[inline(always)]
94    pub fn num_fields(&self) -> usize {
95        self.fields.len()
96    }
97
98    /// Resize the chunk (and all its fields) to the given number of rows.
99    #[inline(always)]
100    pub fn resize(&mut self, new_size: usize) {
101        resize_chunk(self, new_size);
102    }
103
104    /// Return the number of active rows, taking the selection vector into account.
105    #[inline(always)]
106    pub fn active_rows(&self) -> usize {
107        self.sel_vector.as_ref().map_or(self.size, |s| s.size)
108    }
109
110    /// Iterate over active row indices.
111    /// When a selection vector is present, yields the selected indices.
112    /// Otherwise, yields 0..size.
113    pub fn iter_rows(&self) -> RowIter<'_> {
114        RowIter {
115            sel: self.sel_vector.as_ref(),
116            size: self.size,
117            pos: 0,
118        }
119    }
120
121    /// Materialize the selection vector into physical data.
122    /// After calling this, sel_vector is None and only the selected rows remain
123    /// in the data buffers. This is a no-op if sel_vector is already None.
124    pub fn materialize(&mut self) {
125        let sel = match self.sel_vector.take() {
126            Some(s) => s,
127            None => return,
128        };
129        if sel.size == self.size {
130            return;
131        }
132        let indices = arrow::array::UInt32Array::from_iter_values(sel.indices[..sel.size].iter().copied());
133
134        for field in &mut self.fields {
135            *field = take(field.as_ref(), &indices, None).unwrap();
136        }
137        self.size = sel.size;
138    }
139
140    /// Return `true` if the value at `(field_idx, row_idx)` is null.
141    #[inline(always)]
142    pub fn is_null(&self, field_idx: usize, row_idx: usize) -> bool {
143        self.fields[field_idx].is_null(row_idx)
144    }
145
146    /// Read an `i64` value from column `field_idx` at `row_idx`, or `None` if null.
147    ///
148    /// Returns `None` if the column is not physically an `Int64` array (e.g. a
149    /// `UInt64` column) — callers reading `UInt64` columns must use `get_u64`.
150    #[inline]
151    pub fn get_i64(&self, field_idx: usize, row_idx: usize) -> Option<i64> {
152        if self.is_null(field_idx, row_idx) {
153            return None;
154        }
155        if self.fields[field_idx].data_type() != &DataType::Int64 {
156            return None;
157        }
158        let arr = self.fields[field_idx].as_primitive::<Int64Type>();
159        Some(arr.value(row_idx))
160    }
161
162    /// Read a `u64` value from column `field_idx` at `row_idx`, or `None` if null.
163    #[inline]
164    pub fn get_u64(&self, field_idx: usize, row_idx: usize) -> Option<u64> {
165        if self.is_null(field_idx, row_idx) {
166            return None;
167        }
168        if self.fields[field_idx].data_type() != &DataType::UInt64 {
169            return None;
170        }
171        let arr = self.fields[field_idx].as_primitive::<UInt64Type>();
172        Some(arr.value(row_idx))
173    }
174
175    /// Read an `i32` value from column `field_idx` at `row_idx`, or `None` if null.
176    #[inline]
177    pub fn get_i32(&self, field_idx: usize, row_idx: usize) -> Option<i32> {
178        if self.is_null(field_idx, row_idx) {
179            return None;
180        }
181        let arr = self.fields[field_idx].as_primitive::<Int32Type>();
182        Some(arr.value(row_idx))
183    }
184
185    /// Read an `f64` value from column `field_idx` at `row_idx`, or `None` if null.
186    #[inline]
187    pub fn get_f64(&self, field_idx: usize, row_idx: usize) -> Option<f64> {
188        if self.is_null(field_idx, row_idx) {
189            return None;
190        }
191        let arr = self.fields[field_idx].as_primitive::<Float64Type>();
192        Some(arr.value(row_idx))
193    }
194
195    /// Read an `f32` value from column `field_idx` at `row_idx`, or `None` if null.
196    #[inline]
197    pub fn get_f32(&self, field_idx: usize, row_idx: usize) -> Option<f32> {
198        if self.is_null(field_idx, row_idx) {
199            return None;
200        }
201        let arr = self.fields[field_idx].as_primitive::<Float32Type>();
202        Some(arr.value(row_idx))
203    }
204
205    /// Read a `bool` value from column `field_idx` at `row_idx`, or `None` if null.
206    #[inline]
207    pub fn get_bool(&self, field_idx: usize, row_idx: usize) -> Option<bool> {
208        if self.is_null(field_idx, row_idx) {
209            return None;
210        }
211        let arr = self.fields[field_idx].as_boolean();
212        Some(arr.value(row_idx))
213    }
214
215    /// Read a string slice from column `field_idx` at `row_idx`, or `None` if null.
216    #[inline]
217    pub fn get_string(&self, field_idx: usize, row_idx: usize) -> Option<&str> {
218        if self.is_null(field_idx, row_idx) {
219            return None;
220        }
221        let arr = self.fields[field_idx].as_string::<i32>();
222        Some(arr.value(row_idx))
223    }
224
225    /// Read a value from column `field_idx` at `row_idx`, returning a boxed `Value`.
226    ///
227    /// Returns `None` if the cell is null. The physical type of the column
228    /// determines which `Value` variant is produced.
229    pub fn get_value(&self, field_idx: usize, row_idx: usize) -> Option<Value> {
230        if self.is_null(field_idx, row_idx) {
231            return None;
232        }
233        match self.field_types[field_idx] {
234            PhysicalTypeID::Int64 => self.get_i64(field_idx, row_idx).map(Value::Int64),
235            PhysicalTypeID::UInt64 => self.get_u64(field_idx, row_idx).map(Value::UInt64),
236            PhysicalTypeID::Int32 => self.get_i32(field_idx, row_idx).map(Value::Int32),
237            PhysicalTypeID::Double => self.get_f64(field_idx, row_idx).map(Value::Double),
238            PhysicalTypeID::Float => self.get_f32(field_idx, row_idx).map(Value::Float),
239            PhysicalTypeID::Bool => self.get_bool(field_idx, row_idx).map(Value::Bool),
240            PhysicalTypeID::String => self
241                .get_string(field_idx, row_idx)
242                .map(|s| Value::String(s.to_string())),
243            PhysicalTypeID::List | PhysicalTypeID::Array | PhysicalTypeID::Struct => {
244                crate::arrow_vector::convert_arrow_scalar(&self.fields[field_idx], row_idx)
245            }
246            _ => None, // Expand as needed
247        }
248    }
249}
250
251/// Iterator over active row indices of a `DataChunk`.
252///
253/// When a selection vector is present, yields the selected indices.
254/// Otherwise yields `0..size`.
255pub struct RowIter<'a> {
256    sel: Option<&'a SelectionVector>,
257    size: usize,
258    pos: usize,
259}
260
261impl<'a> Iterator for RowIter<'a> {
262    type Item = usize;
263
264    fn next(&mut self) -> Option<Self::Item> {
265        if let Some(sel) = self.sel {
266            if self.pos < sel.size {
267                let idx = sel.indices[self.pos] as usize;
268                self.pos += 1;
269                Some(idx)
270            } else {
271                None
272            }
273        } else {
274            if self.pos < self.size {
275                let idx = self.pos;
276                self.pos += 1;
277                Some(idx)
278            } else {
279                None
280            }
281        }
282    }
283
284    fn size_hint(&self) -> (usize, Option<usize>) {
285        let remaining = if let Some(sel) = self.sel {
286            sel.size.saturating_sub(self.pos)
287        } else {
288            self.size.saturating_sub(self.pos)
289        };
290        (remaining, Some(remaining))
291    }
292}
293
294impl<'a> ExactSizeIterator for RowIter<'a> {}